Exemple #1
0
def test_rulesConditions(tmpdir):
    # tests of rules, conditionsets and conditions
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='axisName_a', minimum=0, maximum=1000),
        dict(name='axisName_b', minimum=0, maximum=3000)
    ])
    r1.subs.append(("a", "a.alt"))

    assert evaluateRule(r1, dict(axisName_a=500, axisName_b=0)) == True
    assert evaluateRule(r1, dict(axisName_a=0, axisName_b=0)) == True
    assert evaluateRule(r1, dict(axisName_a=1000, axisName_b=0)) == True
    assert evaluateRule(r1, dict(axisName_a=1000, axisName_b=-100)) == False
    assert evaluateRule(r1, dict(axisName_a=1000.0001, axisName_b=0)) == False
    assert evaluateRule(r1, dict(axisName_a=-0.0001, axisName_b=0)) == False
    assert evaluateRule(r1, dict(axisName_a=-100, axisName_b=0)) == False
    assert processRules([r1], dict(axisName_a=500, axisName_b=0),
                        ["a", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(axisName_a=500, axisName_b=0),
                        ["a.alt", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(axisName_a=2000, axisName_b=0),
                        ["a", "b", "c"]) == ['a', 'b', 'c']

    # rule with only a maximum
    r2 = RuleDescriptor()
    r2.name = "named.rule.2"
    r2.conditionSets.append([dict(name='axisName_a', maximum=500)])
    r2.subs.append(("b", "b.alt"))

    assert evaluateRule(r2, dict(axisName_a=0)) == True
    assert evaluateRule(r2, dict(axisName_a=-500)) == True
    assert evaluateRule(r2, dict(axisName_a=1000)) == False

    # rule with only a minimum
    r3 = RuleDescriptor()
    r3.name = "named.rule.3"
    r3.conditionSets.append([dict(name='axisName_a', minimum=500)])
    r3.subs.append(("c", "c.alt"))

    assert evaluateRule(r3, dict(axisName_a=0)) == False
    assert evaluateRule(r3, dict(axisName_a=1000)) == True
    assert evaluateRule(r3, dict(axisName_a=1000)) == True

    # rule with only a minimum, maximum in separate conditions
    r4 = RuleDescriptor()
    r4.name = "named.rule.4"
    r4.conditionSets.append([
        dict(name='axisName_a', minimum=500),
        dict(name='axisName_b', maximum=500)
    ])
    r4.subs.append(("c", "c.alt"))

    assert evaluateRule(r4, dict(axisName_a=1000, axisName_b=0)) == True
    assert evaluateRule(r4, dict(axisName_a=0, axisName_b=0)) == False
    assert evaluateRule(r4, dict(axisName_a=1000, axisName_b=1000)) == False
def test_rulesConditions(tmpdir):
    # tests of rules, conditionsets and conditions
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='axisName_a', minimum=0, maximum=1000),
        dict(name='axisName_b', minimum=0, maximum=3000)
    ])
    r1.subs.append(("a", "a.alt"))

    assert evaluateRule(r1, dict(axisName_a = 500, axisName_b = 0)) == True
    assert evaluateRule(r1, dict(axisName_a = 0, axisName_b = 0)) == True
    assert evaluateRule(r1, dict(axisName_a = 1000, axisName_b = 0)) == True
    assert evaluateRule(r1, dict(axisName_a = 1000, axisName_b = -100)) == False
    assert evaluateRule(r1, dict(axisName_a = 1000.0001, axisName_b = 0)) == False
    assert evaluateRule(r1, dict(axisName_a = -0.0001, axisName_b = 0)) == False
    assert evaluateRule(r1, dict(axisName_a = -100, axisName_b = 0)) == False
    assert processRules([r1], dict(axisName_a = 500, axisName_b = 0), ["a", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(axisName_a = 500, axisName_b = 0), ["a.alt", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(axisName_a = 2000, axisName_b = 0), ["a", "b", "c"]) == ['a', 'b', 'c']

    # rule with only a maximum
    r2 = RuleDescriptor()
    r2.name = "named.rule.2"
    r2.conditionSets.append([dict(name='axisName_a', maximum=500)])
    r2.subs.append(("b", "b.alt"))

    assert evaluateRule(r2, dict(axisName_a = 0)) == True
    assert evaluateRule(r2, dict(axisName_a = -500)) == True
    assert evaluateRule(r2, dict(axisName_a = 1000)) == False

    # rule with only a minimum
    r3 = RuleDescriptor()
    r3.name = "named.rule.3"
    r3.conditionSets.append([dict(name='axisName_a', minimum=500)])
    r3.subs.append(("c", "c.alt"))

    assert evaluateRule(r3, dict(axisName_a = 0)) == False
    assert evaluateRule(r3, dict(axisName_a = 1000)) == True
    assert evaluateRule(r3, dict(axisName_a = 1000)) == True

    # rule with only a minimum, maximum in separate conditions
    r4 = RuleDescriptor()
    r4.name = "named.rule.4"
    r4.conditionSets.append([
        dict(name='axisName_a', minimum=500),
        dict(name='axisName_b', maximum=500)
    ])
    r4.subs.append(("c", "c.alt"))

    assert evaluateRule(r4, dict(axisName_a = 1000, axisName_b = 0)) == True
    assert evaluateRule(r4, dict(axisName_a = 0, axisName_b = 0)) == False
    assert evaluateRule(r4, dict(axisName_a = 1000, axisName_b = 1000)) == False
Exemple #3
0
def test_rules(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testRules.designspace")
    testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
    doc = DesignSpaceDocument()
    # write some axes
    a1 = AxisDescriptor()
    a1.tag = "taga"
    a1.name = "aaaa"
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.tag = "tagb"
    a2.name = "bbbb"
    a2.minimum = 0
    a2.maximum = 3000
    a2.default = 0
    doc.addAxis(a2)

    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditions.append(dict(name='aaaa', minimum=0, maximum=1000))
    r1.conditions.append(dict(name='bbbb', minimum=0, maximum=3000))
    r1.subs.append(("a", "a.alt"))

    # rule with minium and maximum
    doc.addRule(r1)
    assert len(doc.rules) == 1
    assert len(doc.rules[0].conditions) == 2
    assert evaluateRule(r1, dict(aaaa=500, bbbb=0)) == True
    assert evaluateRule(r1, dict(aaaa=0, bbbb=0)) == True
    assert evaluateRule(r1, dict(aaaa=1000, bbbb=0)) == True
    assert evaluateRule(r1, dict(aaaa=1000, bbbb=-100)) == False
    assert evaluateRule(r1, dict(aaaa=1000.0001, bbbb=0)) == False
    assert evaluateRule(r1, dict(aaaa=-0.0001, bbbb=0)) == False
    assert evaluateRule(r1, dict(aaaa=-100, bbbb=0)) == False
    assert processRules([r1], dict(aaaa=500),
                        ["a", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(aaaa=500),
                        ["a.alt", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(aaaa=2000),
                        ["a", "b", "c"]) == ['a', 'b', 'c']

    # rule with only a maximum
    r2 = RuleDescriptor()
    r2.name = "named.rule.2"
    r2.conditions.append(dict(name='aaaa', maximum=500))
    r2.subs.append(("b", "b.alt"))

    assert evaluateRule(r2, dict(aaaa=0)) == True
    assert evaluateRule(r2, dict(aaaa=-500)) == True
    assert evaluateRule(r2, dict(aaaa=1000)) == False

    # rule with only a minimum
    r3 = RuleDescriptor()
    r3.name = "named.rule.3"
    r3.conditions.append(dict(name='aaaa', minimum=500))
    r3.subs.append(("c", "c.alt"))

    assert evaluateRule(r3, dict(aaaa=0)) == False
    assert evaluateRule(r3, dict(aaaa=1000)) == True
    assert evaluateRule(r3, dict(bbbb=1000)) == True

    # rule with only a minimum, maximum in separate conditions
    r4 = RuleDescriptor()
    r4.name = "named.rule.4"
    r4.conditions.append(dict(name='aaaa', minimum=500))
    r4.conditions.append(dict(name='bbbb', maximum=500))
    r4.subs.append(("c", "c.alt"))

    assert evaluateRule(r4, dict()) == True  # is this what we expect though?
    assert evaluateRule(r4, dict(aaaa=1000, bbbb=0)) == True
    assert evaluateRule(r4, dict(aaaa=0, bbbb=0)) == False
    assert evaluateRule(r4, dict(aaaa=1000, bbbb=1000)) == False

    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "aaaa"
    a1.tag = "aaaa"
    b1 = AxisDescriptor()
    b1.minimum = 2000
    b1.maximum = 3000
    b1.default = 2000
    b1.name = "bbbb"
    b1.tag = "bbbb"
    doc.addAxis(a1)
    doc.addAxis(b1)
    assert doc._prepAxesForBender() == {
        'aaaa': {
            'map': [],
            'name': 'aaaa',
            'default': 0,
            'minimum': 0,
            'maximum': 1000,
            'tag': 'aaaa'
        },
        'bbbb': {
            'map': [],
            'name': 'bbbb',
            'default': 2000,
            'minimum': 2000,
            'maximum': 3000,
            'tag': 'bbbb'
        }
    }

    assert doc.rules[0].conditions == [{
        'minimum': 0,
        'maximum': 1000,
        'name': 'aaaa'
    }, {
        'minimum': 0,
        'maximum': 3000,
        'name': 'bbbb'
    }]

    assert doc.rules[0].subs == [('a', 'a.alt')]

    doc.normalize()
    assert doc.rules[0].name == 'named.rule.1'
    assert doc.rules[0].conditions == [{
        'minimum': 0.0,
        'maximum': 1.0,
        'name': 'aaaa'
    }, {
        'minimum': 0.0,
        'maximum': 1.0,
        'name': 'bbbb'
    }]

    doc.write(testDocPath)
    new = DesignSpaceDocument()

    new.read(testDocPath)
    assert len(new.axes) == 4
    assert len(new.rules) == 1
    new.write(testDocPath2)
Exemple #4
0
    def generate_instance(
            self, instance: designspaceLib.InstanceDescriptor) -> ufoLib2.Font:
        """Generate an interpolated instance font object for an
        InstanceDescriptor."""
        if anisotropic(instance.location):
            raise InstantiatorError(
                f"Instance {instance.familyName}-"
                f"{instance.styleName}: Anisotropic location "
                f"{instance.location} not supported by varLib.")

        font = ufoLib2.Font()

        # Instances may leave out locations that match the default source, so merge
        # default location with the instance's location.
        location = {**self.default_design_location, **instance.location}
        location_normalized = varLib.models.normalizeLocation(
            location, self.axis_bounds)

        # Kerning
        kerning_instance = self.kerning_mutator.instance_at(
            location_normalized)
        if self.round_geometry:
            kerning_instance.round()
        kerning_instance.extractKerning(font)

        # Info
        self._generate_instance_info(instance, location_normalized, location,
                                     font)

        # Non-kerning groups. Kerning groups have been taken care of by the kerning
        # instance.
        for key, glyph_names in self.copy_nonkerning_groups.items():
            font.groups[key] = [name for name in glyph_names]

        # Features
        font.features.text = self.copy_feature_text

        # Lib
        #  1. Copy the default lib to the instance.
        font.lib = typing.cast(dict, copy.deepcopy(self.copy_lib))
        #  2. Copy the Designspace's skipExportGlyphs list over to the UFO to
        #     make sure it wins over the default UFO one.
        font.lib["public.skipExportGlyphs"] = [
            name for name in self.skip_export_glyphs
        ]
        #  3. Write _design_ location to instance's lib.
        font.lib["designspace.location"] = [loc for loc in location.items()]

        # Glyphs
        for glyph_name, glyph_mutator in self.glyph_mutators.items():
            glyph = font.newGlyph(glyph_name)

            try:
                glyph_instance = glyph_mutator.instance_at(location_normalized)

                if self.round_geometry:
                    glyph_instance = glyph_instance.round()

                # onlyGeometry=True does not set name and unicodes, in ufoLib2 we can't
                # modify a glyph's name. Copy unicodes from default font.
                glyph_instance.extractGlyph(glyph, onlyGeometry=True)
            except Exception as e:
                # TODO: Figure out what exceptions fontMath/varLib can throw.
                # By default, explode if we cannot generate a glyph instance for
                # whatever reason (usually outline incompatibility)...
                if glyph_name not in self.skip_export_glyphs:
                    raise InstantiatorError(
                        f"Failed to generate instance of glyph '{glyph_name}'."
                    ) from e

                # ...except if the glyph is in public.skipExportGlyphs and would
                # therefore be removed from the compiled font anyway. There's not much
                # we can do except leave it empty in the instance and tell the user.
                logger.warning(
                    "Failed to generate instance of glyph '%s', which is marked as "
                    "non-exportable. Glyph will be left empty. Failure reason: %s",
                    glyph_name,
                    e,
                )

            glyph.unicodes = [
                uv for uv in self.glyph_name_to_unicodes[glyph_name]
            ]

        # Process rules
        glyph_names_list = self.glyph_mutators.keys()
        glyph_names_list_renamed = designspaceLib.processRules(
            self.designspace_rules, location, glyph_names_list)
        for name_old, name_new in zip(glyph_names_list,
                                      glyph_names_list_renamed):
            if name_old != name_new:
                swap_glyph_names(font, name_old, name_new)

        return font
    def generate_instance(
        self, instance: designspaceLib.InstanceDescriptor
    ) -> ufoLib2.Font:
        """Generate a font object for an InstanceDescriptor."""
        font = ufoLib2.Font()

        location = instance.location
        if anisotropic(location):
            raise ValueError(
                f"Instance {instance.familyName}-"
                f"{instance.styleName}: Anisotropic location "
                f"{instance.location} not supported by varLib."
            )
        location_normalized = normalize_design_location(location, self.axis_bounds)

        # Kerning
        if instance.kerning:
            kerning_instance = self.kerning_mutator.instance_at(location_normalized)
            kerning_instance.extractKerning(font)

        # Info
        info_instance = self.info_mutator.instance_at(location_normalized)
        if self.round_geometry:
            info_instance = info_instance.round()
        info_instance.extractInfo(font.info)

        # Copy metadata from sources marked with `<copy info="1">` etc.
        for attribute in ufoLib.fontInfoAttributesVersion3:
            if hasattr(info_instance, attribute):
                continue  # Skip mutated attributes.
            if hasattr(self.copy_info, attribute):
                setattr(font.info, attribute, getattr(self.copy_info, attribute))
        for key, value in self.copy_lib.items():
            font.lib[key] = value
        font.lib["public.skipExportGlyphs"] = self.skip_export_glyphs
        for key, value in self.copy_groups.items():
            font.groups[key] = value
        font.features.text = self.copy_feature_text

        # TODO: multilingual names to replace possibly existing name records.
        if instance.familyName:
            font.info.familyName = instance.familyName
        if instance.styleName:
            font.info.styleName = instance.styleName
        if instance.postScriptFontName:
            font.info.postscriptFontName = instance.postScriptFontName
        if instance.styleMapFamilyName:
            font.info.styleMapFamilyName = instance.styleMapFamilyName
        if instance.styleMapStyleName:
            font.info.styleMapStyleName = instance.styleMapStyleName

        # If the masters haven't set the OS/2 weight and width class, use the
        # user-space values ("input") of the axis mapping in the Designspace file for
        # weight and width axes, if they exist.
        if info_instance.openTypeOS2WeightClass is None:
            if "wght" in self.weight_width_axes:
                weight_axis = self.weight_width_axes["wght"]
                weight_axis_instance_location = instance.location[weight_axis.name]
                font.info.openTypeOS2WeightClass = fontTools.misc.fixedTools.otRound(
                    weight_axis.map_backward(weight_axis_instance_location)
                )
        if info_instance.openTypeOS2WidthClass is None:
            if "wdth" in self.weight_width_axes:
                width_axis = self.weight_width_axes["wdth"]
                width_axis_instance_location = instance.location[width_axis.name]
                font.info.openTypeOS2WidthClass = fontTools.misc.fixedTools.otRound(
                    width_axis.map_backward(width_axis_instance_location)
                )

        # Glyphs
        for glyph_name, glyph_mutator in self.glyph_mutators.items():
            glyph = font.newGlyph(glyph_name)

            glyph_instance = glyph_mutator.instance_at(location_normalized)
            if self.round_geometry:
                glyph_instance = glyph_instance.round()

            # onlyGeometry=True does not set name and unicodes, in ufoLib2 we can't
            # modify a glyph's name. Copy unicodes from default font.
            glyph_instance.extractGlyph(glyph, onlyGeometry=True)
            glyph.unicodes = self.glyph_name_to_unicodes[glyph_name]

        # Process rules
        glyph_names_list = self.glyph_mutators.keys()
        resultNames = designspaceLib.processRules(
            self.designspace_rules, location, glyph_names_list
        )
        for oldName, newName in zip(glyph_names_list, resultNames):
            if oldName != newName:
                swapGlyphNames(font, oldName, newName)

        font.lib["designspace.location"] = list(instance.location.items())

        return font
Exemple #6
0
 def makeInstance(self, instanceDescriptor, doRules=False, glyphNames=None):
     """ Generate a font object for this instance """
     font = self._instantiateFont(None)
     # make fonty things here
     loc = instanceDescriptor.location
     anisotropic = False
     locHorizontal = locVertical = loc
     if self.isAnisotropic(loc):
         anisotropic = True
         locHorizontal, locVertical = self.splitAnisotropic(loc)
     # groups
     if hasattr(self.fonts[self.default.name],
                "kerningGroupConversionRenameMaps"):
         renameMap = self.fonts[
             self.default.name].kerningGroupConversionRenameMaps
     else:
         renameMap = {}
     font.kerningGroupConversionRenameMaps = renameMap
     # make the kerning
     # this kerning is always horizontal. We can take the horizontal location
     if instanceDescriptor.kerning:
         try:
             kerningMutator = self.getKerningMutator()
             kerningObject = kerningMutator.makeInstance(locHorizontal)
             kerningObject.extractKerning(font)
         except:
             self.problems.append("Could not make kerning for %s. %s" %
                                  (loc, traceback.format_exc()))
     # make the info
     try:
         infoMutator = self.getInfoMutator()
         if not anisotropic:
             infoInstanceObject = infoMutator.makeInstance(loc)
         else:
             horizontalInfoInstanceObject = infoMutator.makeInstance(
                 locHorizontal)
             verticalInfoInstanceObject = infoMutator.makeInstance(
                 locVertical)
             # merge them again
             infoInstanceObject = (1, 0) * horizontalInfoInstanceObject + (
                 0, 1) * verticalInfoInstanceObject
         infoInstanceObject.extractInfo(font.info)
         font.info.familyName = instanceDescriptor.familyName
         font.info.styleName = instanceDescriptor.styleName
         font.info.postScriptFontName = instanceDescriptor.postScriptFontName
         font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName
         font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName
         # NEED SOME HELP WITH THIS
         # localised names need to go to the right openTypeNameRecords
         # records = []
         # nameID = 1
         # platformID =
         # for languageCode, name in instanceDescriptor.localisedStyleMapFamilyName.items():
         #    # Name ID 1 (font family name) is found at the generic styleMapFamily attribute.
         #    records.append((nameID, ))
     except:
         self.problems.append("Could not make fontinfo for %s. %s" %
                              (loc, traceback.format_exc()))
     for sourceDescriptor in self.sources:
         if sourceDescriptor.copyInfo:
             # this is the source
             self._copyFontInfo(self.fonts[sourceDescriptor.name].info,
                                font.info)
         if sourceDescriptor.copyLib:
             # excplicitly copy the font.lib items
             for key, value in self.fonts[
                     sourceDescriptor.name].lib.items():
                 font.lib[key] = value
         if sourceDescriptor.copyFeatures:
             featuresText = self.fonts[sourceDescriptor.name].features.text
             if isinstance(featuresText, str):
                 font.features.text = u"" + featuresText
             elif isinstance(featuresText, unicode):
                 font.features.text = featuresText
     # glyphs
     if glyphNames:
         selectedGlyphNames = glyphNames
     else:
         selectedGlyphNames = self.glyphNames
     # add the glyphnames to the font.lib['public.glyphOrder']
     if not 'public.glyphOrder' in font.lib.keys():
         font.lib['public.glyphOrder'] = selectedGlyphNames
     for glyphName in selectedGlyphNames:
         try:
             glyphMutator = self.getGlyphMutator(glyphName)
             if glyphMutator is None:
                 continue
         except:
             self.problems.append("Could not make mutator for glyph %s %s" %
                                  (glyphName, traceback.format_exc()))
             continue
         if glyphName in instanceDescriptor.glyphs.keys():
             # XXX this should be able to go now that we have full rule support.
             # reminder: this is what the glyphData can look like
             # {'instanceLocation': {'custom': 0.0, 'weight': 824.0},
             #  'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 0.0, 'weight': 0.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_1.1',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 0.0, 'weight': 368.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_2.2',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 0.0, 'weight': 1000.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_3.3',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 100.0, 'weight': 1000.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_0.4',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 100.0, 'weight': 0.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_4.5',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 100.0, 'weight': 368.0}}],
             #  'unicodes': [36]}
             glyphData = instanceDescriptor.glyphs[glyphName]
         else:
             glyphData = {}
         font.newGlyph(glyphName)
         font[glyphName].clear()
         if glyphData.get('mute', False):
             # mute this glyph, skip
             continue
         glyphInstanceLocation = glyphData.get("instanceLocation",
                                               instanceDescriptor.location)
         uniValues = []
         neutral = glyphMutator.get(())
         if neutral is not None:
             uniValues = neutral[0].unicodes
         glyphInstanceUnicodes = glyphData.get("unicodes", uniValues)
         note = glyphData.get("note")
         if note:
             font[glyphName] = note
         masters = glyphData.get("masters", None)
         if masters:
             items = []
             for glyphMaster in masters:
                 sourceGlyphFont = glyphMaster.get("font")
                 sourceGlyphName = glyphMaster.get("glyphName", glyphName)
                 m = self.fonts.get(sourceGlyphFont)
                 if not sourceGlyphName in m:
                     continue
                 if hasattr(m[sourceGlyphName], "toMathGlyph"):
                     sourceGlyph = m[sourceGlyphName].toMathGlyph()
                 else:
                     sourceGlyph = MathGlyph(m[sourceGlyphName])
                 sourceGlyphLocation = glyphMaster.get("location")
                 items.append((sourceGlyphLocation, sourceGlyph))
             bias, glyphMutator = self.getVariationModel(
                 items, axes=self.serializedAxes, bias=self.defaultLoc)
         try:
             if not self.isAnisotropic(glyphInstanceLocation):
                 glyphInstanceObject = glyphMutator.makeInstance(
                     glyphInstanceLocation)
             else:
                 # split anisotropic location into horizontal and vertical components
                 horizontal, vertical = self.splitAnisotropic(
                     glyphInstanceLocation)
                 horizontalGlyphInstanceObject = glyphMutator.makeInstance(
                     horizontal)
                 verticalGlyphInstanceObject = glyphMutator.makeInstance(
                     vertical)
                 # merge them again
                 glyphInstanceObject = (
                     0, 1) * horizontalGlyphInstanceObject + (
                         1, 0) * verticalGlyphInstanceObject
         except IndexError:
             # alignment problem with the data?
             print("Error making instance %s" % glyphName)
             continue
         font.newGlyph(glyphName)
         font[glyphName].clear()
         if self.roundGeometry:
             try:
                 glyphInstanceObject = glyphInstanceObject.round()
             except AttributeError:
                 pass
         try:
             glyphInstanceObject.extractGlyph(font[glyphName],
                                              onlyGeometry=True)
         except TypeError:
             # this causes ruled glyphs to end up in the wrong glyphname
             # but defcon2 objects don't support it
             pPen = font[glyphName].getPointPen()
             font[glyphName].clear()
             glyphInstanceObject.drawPoints(pPen)
         font[glyphName].width = glyphInstanceObject.width
         font[glyphName].unicodes = glyphInstanceUnicodes
     if doRules:
         resultNames = processRules(self.rules, loc, self.glyphNames)
         for oldName, newName in zip(self.glyphNames, resultNames):
             if oldName != newName:
                 swapGlyphNames(font, oldName, newName)
     # copy the glyph lib?
     #for sourceDescriptor in self.sources:
     #    if sourceDescriptor.copyLib:
     #        pass
     #    pass
     # store designspace location in the font.lib
     font.lib['designspace'] = list(instanceDescriptor.location.items())
     return font
Exemple #7
0
 def makeInstance(self, instanceDescriptor,
         doRules=False,
         glyphNames=None,
         pairs=None,
         bend=False):
     """ Generate a font object for this instance """
     font = self._instantiateFont(None)
     # make fonty things here
     loc = Location(instanceDescriptor.location)
     anisotropic = False
     locHorizontal = locVertical = loc
     if self.isAnisotropic(loc):
         anisotropic = True
         locHorizontal, locVertical = self.splitAnisotropic(loc)
     # groups
     renameMap = getattr(self.fonts[self.default.name], "kerningGroupConversionRenameMaps", None)
     font.kerningGroupConversionRenameMaps = renameMap if renameMap is not None else {'side1': {}, 'side2': {}}
     # make the kerning
     # this kerning is always horizontal. We can take the horizontal location
     # filter the available pairs?
     if instanceDescriptor.kerning:
         if pairs:
             try:
                 kerningMutator = self.getKerningMutator(pairs=pairs)
                 kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
                 kerningObject.extractKerning(font)
             except:
                 self.problems.append("Could not make kerning for %s. %s" % (loc, traceback.format_exc()))
         else:
             kerningMutator = self.getKerningMutator()
             if kerningMutator is not None:
                 kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
                 kerningObject.extractKerning(font)
     # make the info
     try:
         infoMutator = self.getInfoMutator()
         if infoMutator is not None:
             if not anisotropic:
                 infoInstanceObject = infoMutator.makeInstance(loc, bend=bend)
             else:
                 horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend)
                 verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend)
                 # merge them again
                 infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject
             if self.roundGeometry:
                 try:
                     infoInstanceObject = infoInstanceObject.round()
                 except AttributeError:
                     pass
             infoInstanceObject.extractInfo(font.info)
         font.info.familyName = instanceDescriptor.familyName
         font.info.styleName = instanceDescriptor.styleName
         font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation..
         font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName
         font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName
         # NEED SOME HELP WITH THIS
         # localised names need to go to the right openTypeNameRecords
         # records = []
         # nameID = 1
         # platformID =
         # for languageCode, name in instanceDescriptor.localisedStyleMapFamilyName.items():
         #    # Name ID 1 (font family name) is found at the generic styleMapFamily attribute.
         #    records.append((nameID, ))
     except:
         self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc()))
     for sourceDescriptor in self.sources:
         if sourceDescriptor.copyInfo:
             # this is the source
             if self.fonts[sourceDescriptor.name] is not None:
                 self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info)
         if sourceDescriptor.copyLib:
             # excplicitly copy the font.lib items
             if self.fonts[sourceDescriptor.name] is not None:
                 for key, value in self.fonts[sourceDescriptor.name].lib.items():
                     font.lib[key] = value
         if sourceDescriptor.copyGroups:
             if self.fonts[sourceDescriptor.name] is not None:
                 sides = font.kerningGroupConversionRenameMaps.get('side1', {})
                 sides.update(font.kerningGroupConversionRenameMaps.get('side2', {}))
                 for key, value in self.fonts[sourceDescriptor.name].groups.items():
                     if key not in sides:
                         font.groups[key] = value
         if sourceDescriptor.copyFeatures:
             if self.fonts[sourceDescriptor.name] is not None:
                 featuresText = self.fonts[sourceDescriptor.name].features.text
                 font.features.text = featuresText
     # glyphs
     if glyphNames:
         selectedGlyphNames = glyphNames
     else:
         selectedGlyphNames = self.glyphNames
     # add the glyphnames to the font.lib['public.glyphOrder']
     if not 'public.glyphOrder' in font.lib.keys():
         font.lib['public.glyphOrder'] = selectedGlyphNames
     for glyphName in selectedGlyphNames:
         try:
             glyphMutator = self.getGlyphMutator(glyphName)
             if glyphMutator is None:
                 self.problems.append("Could not make mutator for glyph %s" % (glyphName))
                 continue
         except:
             self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc()))
             continue
         if glyphName in instanceDescriptor.glyphs.keys():
             # XXX this should be able to go now that we have full rule support.
             # reminder: this is what the glyphData can look like
             # {'instanceLocation': {'custom': 0.0, 'weight': 824.0},
             #  'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 0.0, 'weight': 0.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_1.1',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 0.0, 'weight': 368.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_2.2',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 0.0, 'weight': 1000.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_3.3',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 100.0, 'weight': 1000.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_0.4',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 100.0, 'weight': 0.0}},
             #              {'font': 'master.Adobe VF Prototype.Master_4.5',
             #               'glyphName': 'dollar.nostroke',
             #               'location': {'custom': 100.0, 'weight': 368.0}}],
             #  'unicodes': [36]}
             glyphData = instanceDescriptor.glyphs[glyphName]
         else:
             glyphData = {}
         font.newGlyph(glyphName)
         font[glyphName].clear()
         if glyphData.get('mute', False):
             # mute this glyph, skip
             continue
         glyphInstanceLocation = glyphData.get("instanceLocation", instanceDescriptor.location)
         glyphInstanceLocation = Location(glyphInstanceLocation)
         uniValues = []
         neutral = glyphMutator.get(())
         if neutral is not None:
             uniValues = neutral[0].unicodes
         else:
             neutralFont = self.getNeutralFont()
             if glyphName in neutralFont:
                 uniValues = neutralFont[glyphName].unicodes
         glyphInstanceUnicodes = glyphData.get("unicodes", uniValues)
         note = glyphData.get("note")
         if note:
             font[glyphName] = note
         # XXXX phase out support for instance-specific masters
         # this should be handled by the rules system.
         masters = glyphData.get("masters", None)
         if masters is not None:
             items = []
             for glyphMaster in masters:
                 sourceGlyphFont = glyphMaster.get("font")
                 sourceGlyphName = glyphMaster.get("glyphName", glyphName)
                 m = self.fonts.get(sourceGlyphFont)
                 if not sourceGlyphName in m:
                     continue
                 if hasattr(m[sourceGlyphName], "toMathGlyph"):
                     sourceGlyph = m[sourceGlyphName].toMathGlyph()
                 else:
                     sourceGlyph = MathGlyph(m[sourceGlyphName])
                 sourceGlyphLocation = glyphMaster.get("location")
                 items.append((Location(sourceGlyphLocation), sourceGlyph))
             bias, glyphMutator = self.getVariationModel(items, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True))
         try:
             if not self.isAnisotropic(glyphInstanceLocation):
                 glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation, bend=bend)
             else:
                 # split anisotropic location into horizontal and vertical components
                 horizontal, vertical = self.splitAnisotropic(glyphInstanceLocation)
                 horizontalGlyphInstanceObject = glyphMutator.makeInstance(horizontal, bend=bend)
                 verticalGlyphInstanceObject = glyphMutator.makeInstance(vertical, bend=bend)
                 # merge them again
                 glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject
         except IndexError:
             # alignment problem with the data?
             self.problems.append("Quite possibly some sort of data alignment error in %s" % glyphName)
             continue
         font.newGlyph(glyphName)
         font[glyphName].clear()
         if self.roundGeometry:
             try:
                 glyphInstanceObject = glyphInstanceObject.round()
             except AttributeError:
                 pass
         try:
             # File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance
             #   glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True)
             # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontMath/mathGlyph.py", line 315, in extractGlyph
             #   glyph.anchors = [dict(anchor) for anchor in self.anchors]
             # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontParts/base/base.py", line 103, in __set__
             #   raise FontPartsError("no setter for %r" % self.name)
             #   fontParts.base.errors.FontPartsError: no setter for 'anchors'
             if hasattr(font[glyphName], "fromMathGlyph"):
                 font[glyphName].fromMathGlyph(glyphInstanceObject)
             else:
                 glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True)
         except TypeError:
             # this causes ruled glyphs to end up in the wrong glyphname
             # but defcon2 objects don't support it
             pPen = font[glyphName].getPointPen()
             font[glyphName].clear()
             glyphInstanceObject.drawPoints(pPen)
         font[glyphName].width = glyphInstanceObject.width
         font[glyphName].unicodes = glyphInstanceUnicodes
     if doRules:
         resultNames = processRules(self.rules, loc, self.glyphNames)
         for oldName, newName in zip(self.glyphNames, resultNames):
             if oldName != newName:
                 swapGlyphNames(font, oldName, newName)
     # copy the glyph lib?
     #for sourceDescriptor in self.sources:
     #    if sourceDescriptor.copyLib:
     #        pass
     #    pass
     # store designspace location in the font.lib
     font.lib['designspace.location'] = list(instanceDescriptor.location.items())
     return font