def test_compile_withoutPostScriptName(self): inst = NamedInstance() inst.subfamilyNameID = 345 inst.postscriptNameID = 564 inst.coordinates = {"wght": 0.7, "wdth": 0.5} self.assertEqual(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, inst.compile(["wght", "wdth"], False))
def _add_fvar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font) axis.flags = int(a.hidden) fvar.axes.append(axis) for instance in instances: coordinates = instance.location if "en" not in instance.localisedStyleName: if not instance.styleName: raise VarLibValidationError( f"Instance at location '{coordinates}' must have a default English " "style name ('stylename' attribute on the instance element or a " "stylename element with an 'xml:lang=\"en\"' attribute).") localisedStyleName = dict(instance.localisedStyleName) localisedStyleName["en"] = tounicode(instance.styleName) else: localisedStyleName = instance.localisedStyleName psname = instance.postScriptFontName inst = NamedInstance() inst.subfamilyNameID = nameTable.addMultilingualName( localisedStyleName) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = { axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items() } #inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()} fvar.instances.append(inst) assert "fvar" not in font font['fvar'] = fvar return fvar
def test_fromXML(self): inst = NamedInstance() attrs = {"nameID": "345"} inst.fromXML("NamedInstance", attrs, [ ("coord", {"axis": "wght", "value": "0.7"}, []), ("coord", {"axis": "wdth", "value": "0.5"}, []), ], ttFont=MakeFont()) self.assertEqual(345, inst.nameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def test_decompile_withoutPostScriptName(self): inst = NamedInstance() inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"]) self.assertEqual(0xFFFF, inst.postscriptNameID) self.assertEqual(345, inst.subfamilyNameID) self.assertDictAlmostEqual({ "wght": 0.6999969, "wdth": 0.5 }, inst.coordinates)
def test_fromXML_withoutPostScriptName(self): inst = NamedInstance() for name, attrs, content in parseXML( '<NamedInstance subfamilyNameID="345">' ' <coord axis="wght" value="0.7"/>' ' <coord axis="wdth" value="0.5"/>' '</NamedInstance>'): inst.fromXML(name, attrs, content, ttFont=MakeFont()) self.assertEqual(345, inst.subfamilyNameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def test_fromXML(self): inst = NamedInstance() for name, attrs, content in parseXML( '<NamedInstance nameID="345">' ' <coord axis="wght" value="0.7"/>' ' <coord axis="wdth" value="0.5"/>' "</NamedInstance>" ): inst.fromXML(name, attrs, content, ttFont=MakeFont()) self.assertEqual(345, inst.nameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def _add_fvar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font) axis.flags = int(a.hidden) fvar.axes.append(axis) for instance in instances: coordinates = instance.location if "en" not in instance.localisedStyleName: assert instance.styleName localisedStyleName = dict(instance.localisedStyleName) localisedStyleName["en"] = tounicode(instance.styleName) else: localisedStyleName = instance.localisedStyleName psname = instance.postScriptFontName inst = NamedInstance() inst.subfamilyNameID = nameTable.addMultilingualName(localisedStyleName) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = {axes[k].tag:axes[k].map_backward(v) for k,v in coordinates.items()} #inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()} fvar.instances.append(inst) assert "fvar" not in font font['fvar'] = fvar return fvar
def _add_fvar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addName(tounicode(a.labelname['en'])) # TODO: # Replace previous line with the following when the following issues are resolved: # https://github.com/fonttools/fonttools/issues/930 # https://github.com/fonttools/fonttools/issues/931 # axis.axisNameID = nameTable.addMultilingualName(a.labelname, font) fvar.axes.append(axis) for instance in instances: coordinates = instance['location'] name = tounicode(instance['stylename']) psname = instance.get('postscriptfontname') inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = { axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items() } #inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()} fvar.instances.append(inst) assert "fvar" not in font font['fvar'] = fvar return fvar
def test_fromXML_withPostScriptName(self): inst = NamedInstance() for name, attrs, content in parseXML( '<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">' ' <coord axis="wght" value="0.7"/>' ' <coord axis="wdth" value="0.5"/>' '</NamedInstance>'): inst.fromXML(name, attrs, content, ttFont=MakeFont()) self.assertEqual(257, inst.postscriptNameID) self.assertEqual(345, inst.subfamilyNameID) self.assertDictAlmostEqual({ "wght": 0.6999969, "wdth": 0.5 }, inst.coordinates)
def _add_fvar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addName(tounicode(a.labelNames['en'])) # TODO: # Replace previous line with the following when the following issues are resolved: # https://github.com/fonttools/fonttools/issues/930 # https://github.com/fonttools/fonttools/issues/931 # axis.axisNameID = nameTable.addMultilingualName(a.labelname, font) fvar.axes.append(axis) for instance in instances: coordinates = instance.location name = tounicode(instance.styleName) psname = instance.postScriptFontName inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = {axes[k].tag:axes[k].map_backward(v) for k,v in coordinates.items()} #inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()} fvar.instances.append(inst) assert "fvar" not in font font['fvar'] = fvar return fvar
def gen_instances(is_italic): results = [] for wght_val in range(wght_min, wght_max + 100, 100): name = (WEIGHT_VALUES[wght_val] if not is_italic else f"{WEIGHT_VALUES[wght_val]} Italic".strip()) name = name.replace("Regular Italic", "Italic") coordinates = deepcopy(default_axis_vals) coordinates["wght"] = wght_val inst = NamedInstance() inst.subfamilyNameID = nametable.addName(name) inst.coordinates = coordinates results.append(inst) return results
def test_toXML(self): font = MakeFont() inst = NamedInstance() inst.nameID = AddName(font, "Light Condensed").nameID inst.coordinates = {"wght": 0.7, "wdth": 0.5} writer = XMLWriter(StringIO()) inst.toXML(writer, font) self.assertEqual([ '', '<!-- Light Condensed -->', '<NamedInstance nameID="%s">' % inst.nameID, '<coord axis="wght" value="0.7"/>', '<coord axis="wdth" value="0.5"/>', '</NamedInstance>' ], xml_lines(writer))
def _add_fvar(font, axes, instances): assert "fvar" not in font font['fvar'] = fvar = table__f_v_a_r() for tag in sorted(axes.keys()): axis = Axis() axis.axisTag = tag name, axis.minValue, axis.defaultValue, axis.maxValue = axes[tag] axis.nameID = _AddName(font, name).nameID fvar.axes.append(axis) for name, coordinates in instances: inst = NamedInstance() inst.nameID = _AddName(font, name).nameID inst.coordinates = coordinates fvar.instances.append(inst)
def AddFontVariations(font): assert "fvar" not in font fvar = font["fvar"] = table__f_v_a_r() weight = Axis() weight.axisTag = "wght" weight.nameID = AddName(font, "Weight").nameID weight.minValue, weight.defaultValue, weight.maxValue = (100, 400, 900) fvar.axes.append(weight) # https://www.microsoft.com/typography/otspec/os2.htm#wtc for name, wght in (("Thin", 100), ("Light", 300), ("Regular", 400), ("Bold", 700), ("Black", 900)): inst = NamedInstance() inst.nameID = AddName(font, name).nameID inst.coordinates = {"wght": wght} fvar.instances.append(inst)
def test_fromXML(self): inst = NamedInstance() attrs = {"nameID": "345"} inst.fromXML("NamedInstance", attrs, [ ("coord", { "axis": "wght", "value": "0.7" }, []), ("coord", { "axis": "wdth", "value": "0.5" }, []), ], ttFont=MakeFont()) self.assertEqual(345, inst.nameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def MakeFont(): axes = [("wght", "Weight", 100, 400, 900), ("wdth", "Width", 50, 100, 200)] instances = [("Light", 300, 100), ("Light Condensed", 300, 75)] fvarTable = table__f_v_a_r() font = {"fvar": fvarTable} for tag, name, minValue, defaultValue, maxValue in axes: axis = Axis() axis.axisTag = tag axis.defaultValue = defaultValue axis.minValue, axis.maxValue = minValue, maxValue axis.axisNameID = AddName(font, name).nameID fvarTable.axes.append(axis) for name, weight, width in instances: inst = NamedInstance() inst.subfamilyNameID = AddName(font, name).nameID inst.coordinates = {"wght": weight, "wdth": width} fvarTable.instances.append(inst) return font
def MakeFont(): axes = [("wght", "Weight", 100, 400, 900), ("wdth", "Width", 50, 100, 200)] instances = [("Light", 300, 100), ("Light Condensed", 300, 75)] fvarTable = table__f_v_a_r() font = {"fvar": fvarTable} for tag, name, minValue, defaultValue, maxValue in axes: axis = Axis() axis.axisTag = tag axis.defaultValue = defaultValue axis.minValue, axis.maxValue = minValue, maxValue axis.nameID = AddName(font, name).nameID fvarTable.axes.append(axis) for name, weight, width in instances: inst = NamedInstance() inst.nameID = AddName(font, name).nameID inst.coordinates = {"wght": weight, "wdth": width} fvarTable.instances.append(inst) return font
def _add_fvar(font, axes, instances, axis_map): """ Add 'fvar' table to font. axes is a dictionary mapping axis-id to axis (min,default,max) coordinate values. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. axis_map is an ordered dictionary mapping axis-id to (axis-tag, axis-name). """ assert "fvar" not in font font['fvar'] = fvar = newTable('fvar') nameTable = font['name'] for iden in axis_map.keys(): if iden not in axes: continue axis = Axis() axis.axisTag = Tag(axis_map[iden][0]) axis.minValue, axis.defaultValue, axis.maxValue = axes[iden] axisName = tounicode(axis_map[iden][1]) axis.axisNameID = nameTable.addName(axisName) fvar.axes.append(axis) for instance in instances: coordinates = instance['location'] name = tounicode(instance['stylename']) psname = instance.get('postscriptfontname') inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = {axis_map[k][0]: v for k, v in coordinates.items()} fvar.instances.append(inst) return fvar
def AddFontVariations(font): assert "fvar" not in font fvar = font["fvar"] = table__f_v_a_r() weight = Axis() weight.axisTag = "wght" weight.nameID = AddName(font, "Weight").nameID weight.minValue, weight.defaultValue, weight.maxValue = (100, 400, 900) fvar.axes.append(weight) # https://www.microsoft.com/typography/otspec/os2.htm#wtc for name, wght in ( ("Thin", 100), ("Light", 300), ("Regular", 400), ("Bold", 700), ("Black", 900)): inst = NamedInstance() inst.nameID = AddName(font, name).nameID inst.coordinates = {"wght": wght} fvar.instances.append(inst)
def _add_fvar(font, axes, instances, axis_map): """ Add 'fvar' table to font. axes is a dictionary mapping axis-id to axis (min,default,max) coordinate values. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. axisMap is dictionary mapping axis-id to (axis-tag, axis-name). """ assert "fvar" not in font font['fvar'] = fvar = newTable('fvar') nameTable = font['name'] for iden in sorted(axes.keys(), key=lambda k: axis_map[k][0]): axis = Axis() axis.axisTag = Tag(axis_map[iden][0]) axis.minValue, axis.defaultValue, axis.maxValue = axes[iden] axisName = tounicode(axis_map[iden][1]) axis.axisNameID = nameTable.addName(axisName) fvar.axes.append(axis) for instance in instances: coordinates = instance['location'] name = tounicode(instance['stylename']) psname = instance.get('postscriptfontname') inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = {axis_map[k][0]:v for k,v in coordinates.items()} fvar.instances.append(inst) return fvar
def test_toXML_withPostScriptName(self): font = MakeFont() inst = NamedInstance() inst.flags = 0xE9 inst.subfamilyNameID = AddName(font, "Light Condensed").nameID inst.postscriptNameID = AddName(font, "Test-LightCondensed").nameID inst.coordinates = {"wght": 0.7, "wdth": 0.5} writer = XMLWriter(BytesIO()) inst.toXML(writer, font) self.assertEqual([ '', '<!-- Light Condensed -->', '<!-- PostScript: Test-LightCondensed -->', '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">' % (inst.postscriptNameID, inst.subfamilyNameID), '<coord axis="wght" value="0.7"/>', '<coord axis="wdth" value="0.5"/>', '</NamedInstance>' ], xml_lines(writer))
def test_toXML_withPostScriptName(self): font = MakeFont() inst = NamedInstance() inst.flags = 0xE9 inst.subfamilyNameID = AddName(font, "Light Condensed").nameID inst.postscriptNameID = AddName(font, "Test-LightCondensed").nameID inst.coordinates = {"wght": 0.7, "wdth": 0.5} writer = XMLWriter(BytesIO()) inst.toXML(writer, font) self.assertEqual([ '', '<!-- Light Condensed -->', '<!-- PostScript: Test-LightCondensed -->', '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">' % ( inst.postscriptNameID, inst.subfamilyNameID), '<coord axis="wght" value="0.7"/>', '<coord axis="wdth" value="0.5"/>', '</NamedInstance>' ], xml_lines(writer))
def test_decompile(self): inst = NamedInstance() inst.decompile(FVAR_INSTANCE_DATA, ["wght", "wdth"]) self.assertEqual(345, inst.nameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def _add_fvar_avar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar / avar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addName(tounicode(a.labelname['en'])) # TODO: # Replace previous line with the following when the following issues are resolved: # https://github.com/fonttools/fonttools/issues/930 # https://github.com/fonttools/fonttools/issues/931 # axis.axisNameID = nameTable.addMultilingualName(a.labelname, font) fvar.axes.append(axis) for instance in instances: coordinates = instance['location'] name = tounicode(instance['stylename']) psname = instance.get('postscriptfontname') inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = {axes[k].tag:axes[k].map_backward(v) for k,v in coordinates.items()} fvar.instances.append(inst) avar = newTable('avar') interesting = False for axis in axes.values(): curve = avar.segments[axis.tag] = {} if not axis.map or all(k==v for k,v in axis.map.items()): continue interesting = True items = sorted(axis.map.items()) keys = [item[0] for item in items] vals = [item[1] for item in items] # Current avar requirements. We don't have to enforce # these on the designer and can deduce some ourselves, # but for now just enforce them. assert axis.minimum == min(keys) assert axis.maximum == max(keys) assert axis.default in keys # No duplicates assert len(set(keys)) == len(keys) assert len(set(vals)) == len(vals) # Ascending values assert sorted(vals) == vals keys_triple = (axis.minimum, axis.default, axis.maximum) vals_triple = tuple(axis.map_forward(v) for v in keys_triple) keys = [models.normalizeValue(v, keys_triple) for v in keys] vals = [models.normalizeValue(v, vals_triple) for v in vals] curve.update(zip(keys, vals)) if not interesting: log.info("No need for avar") avar = None assert "fvar" not in font font['fvar'] = fvar assert "avar" not in font if avar: font['avar'] = avar return fvar,avar
def test_decompile_withPostScriptName(self): inst = NamedInstance() inst.decompile(FVAR_INSTANCE_DATA_WITH_PSNAME, ["wght", "wdth"]) self.assertEqual(564, inst.postscriptNameID) self.assertEqual(345, inst.subfamilyNameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def _add_fvar_avar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar / avar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addName(tounicode(a.labelname['en'])) # TODO: # Replace previous line with the following when the following issues are resolved: # https://github.com/fonttools/fonttools/issues/930 # https://github.com/fonttools/fonttools/issues/931 # axis.axisNameID = nameTable.addMultilingualName(a.labelname, font) fvar.axes.append(axis) for instance in instances: coordinates = instance['location'] name = tounicode(instance['stylename']) psname = instance.get('postscriptfontname') inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) if psname is not None: psname = tounicode(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = { axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items() } fvar.instances.append(inst) avar = newTable('avar') interesting = False for axis in axes.values(): curve = avar.segments[axis.tag] = {} if not axis.map or all(k == v for k, v in axis.map.items()): continue interesting = True items = sorted(axis.map.items()) keys = [item[0] for item in items] vals = [item[1] for item in items] # Current avar requirements. We don't have to enforce # these on the designer and can deduce some ourselves, # but for now just enforce them. assert axis.minimum == min(keys) assert axis.maximum == max(keys) assert axis.default in keys # No duplicates assert len(set(keys)) == len(keys) assert len(set(vals)) == len(vals) # Ascending values assert sorted(vals) == vals keys_triple = (axis.minimum, axis.default, axis.maximum) vals_triple = tuple(axis.map_forward(v) for v in keys_triple) keys = [models.normalizeValue(v, keys_triple) for v in keys] vals = [models.normalizeValue(v, vals_triple) for v in vals] curve.update(zip(keys, vals)) if not interesting: log.info("No need for avar") avar = None assert "fvar" not in font font['fvar'] = fvar assert "avar" not in font if avar: font['avar'] = avar return fvar, avar
def test_decompile_withoutPostScriptName(self): inst = NamedInstance() inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"]) self.assertEqual(0xFFFF, inst.postscriptNameID) self.assertEqual(345, inst.subfamilyNameID) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def test_compile(self): inst = NamedInstance() inst.nameID = 345 inst.coordinates = {"wght": 0.7, "wdth": 0.5} self.assertEqual(FVAR_INSTANCE_DATA, inst.compile(["wght", "wdth"]))