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
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)
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
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
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