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_check_varfont_regular_slnt_coord(): """ The variable font 'slnt' (Slant) axis coordinate must be zero on the 'Regular' instance. """ from fontbakery.profiles.fvar import com_google_fonts_check_varfont_regular_slnt_coord as check from fontbakery.profiles.shared_conditions import regular_slnt_coord from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks a 'slnt' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "slnt" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["slnt"] = 123 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # then we test the code of the regular_slnt_coord condition: regular_slant_coord = regular_slnt_coord(ttFont) # And with this the test must FAIL print('Test FAIL with a bad Regular:slnt coordinate (123)...') status, message = list(check(ttFont, regular_slant_coord))[-1] assert status == FAIL # We then fix the Regular:slnt coordinate value value: regular_slant_coord = 0 # and now this should PASS the test: print('Test PASS with a good Regular:slnt coordinate (zero)...') status, message = list(check(ttFont, regular_slant_coord))[-1] assert status == PASS
def test_check_varfont_regular_ital_coord(): """ The variable font 'ital' (Italic) axis coordinate must be zero on the 'Regular' instance. """ from fontbakery.profiles.fvar import com_google_fonts_check_varfont_regular_ital_coord as check from fontbakery.profiles.shared_conditions import regular_ital_coord from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'ital' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "ital" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["ital"] = 123 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # then we test the code of the regular_ital_coord condition: regular_italic_coord = regular_ital_coord(ttFont) # So it must FAIL the test assert_results_contain(check(ttFont, regular_italic_coord), FAIL, 'non-zero', 'with a bad Regular:ital coordinate (123)...') # We then fix the Regular:ital coordinate: regular_italic_coord = 0 # and now this should PASS the test: assert_PASS(check(ttFont, regular_italic_coord), 'with a good Regular:ital coordinate (zero)...')
def test_check_varfont_regular_ital_coord(): """ The variable font 'ital' (Italic) axis coordinate must be zero on the 'Regular' instance. """ check = CheckTester(opentype_profile, "com.google.fonts/check/varfont/regular_ital_coord") from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'ital' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "ital" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["ital"] = 123 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # And with this the check must detect the problem: assert_results_contain(check(ttFont), FAIL, 'non-zero', 'with a bad Regular:ital coordinate (123)...') # but with zero it must PASS the check: assert_PASS(check(ttFont, {"regular_ital_coord": 0}), 'with a good Regular:ital coordinate (zero)...')
def test_check_varfont_regular_opsz_coord(): """ The variable font 'opsz' (Optical Size) axis coordinate should be between 9 and 13 on the 'Regular' instance. """ check = CheckTester(opentype_profile, "com.google.fonts/check/varfont/regular_opsz_coord") from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'opsz' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "opsz" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["opsz"] = 8 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # Then we ensure the problem is detected: assert_results_contain(check(ttFont), WARN, 'out-of-range', 'with a bad Regular:opsz coordinate (8)...') # We try yet another bad value # and the check should detect the problem: assert_results_contain(check(ttFont, {"regular_opsz_coord": 14}), WARN, 'out-of-range', 'with another bad Regular:opsz value (14)...') # We then test with good default opsz values: for value in [9, 10, 11, 12, 13]: assert_PASS(check(ttFont, {"regular_opsz_coord": value}), f'with a good Regular:opsz coordinate ({value})...')
def makeFont(axisTags): """['opsz', 'wdth'] --> ttFont""" fvar = table__f_v_a_r() for tag in axisTags: axis = Axis() axis.axisTag = tag fvar.axes.append(axis) return {"fvar": fvar}
def test_decompile(self): axis = Axis() axis.decompile(FVAR_AXIS_DATA) self.assertEqual("opsz", axis.axisTag) self.assertEqual(345, axis.axisNameID) self.assertEqual(-0.5, axis.minValue) self.assertEqual(1.3, axis.defaultValue) self.assertEqual(1.5, axis.maxValue)
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(self): axis = Axis() axis.fromXML("Axis", {}, [("AxisTag", {}, ["wght"]), ("MinValue", {}, ["100"]), ("DefaultValue", {}, ["400"]), ("MaxValue", {}, ["900"]), ("NameID", {}, ["256"])], ttFont=None) self.assertEqual("wght", axis.axisTag) self.assertEqual(100, axis.minValue) self.assertEqual(400, axis.defaultValue) self.assertEqual(900, axis.maxValue) self.assertEqual(256, axis.nameID)
def test_toXML(self): font = MakeFont() axis = Axis() axis.decompile(FVAR_AXIS_DATA) AddName(font, "Optical Size").nameID = 256 axis.nameID = 256 writer = XMLWriter(StringIO()) axis.toXML(writer, font) self.assertEqual([ '', '<!-- Optical Size -->', '<Axis>', '<AxisTag>opsz</AxisTag>', '<MinValue>-0.5</MinValue>', '<DefaultValue>1.3</DefaultValue>', '<MaxValue>1.5</MaxValue>', '<NameID>256</NameID>', '</Axis>' ], 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 test_fromXML(self): axis = Axis() for name, attrs, content in parseXML( '<Axis>' ' <AxisTag>wght</AxisTag>' ' <MinValue>100</MinValue>' ' <DefaultValue>400</DefaultValue>' ' <MaxValue>900</MaxValue>' ' <AxisNameID>256</AxisNameID>' '</Axis>'): axis.fromXML(name, attrs, content, ttFont=None) self.assertEqual("wght", axis.axisTag) self.assertEqual(100, axis.minValue) self.assertEqual(400, axis.defaultValue) self.assertEqual(900, axis.maxValue) self.assertEqual(256, axis.axisNameID)
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_check_171(): """ The variable font 'opsz' (Optical Size) axis coordinate should be between 9 and 13 on the 'Regular' instance. """ from fontbakery.specifications.fvar import com_google_fonts_check_171 as check from fontbakery.specifications.shared_conditions import regular_opsz_coord from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'opsz' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "opsz" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["opsz"] = 8 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # then we test the regular_opsz_coord condition: regular_opticalsize_coord = regular_opsz_coord(ttFont) # And it must WARN the test print('Test WARN with a bad Regular:opsz coordinate (8)...') status, message = list(check(ttFont, regular_opticalsize_coord))[-1] assert status == WARN # We try yet another bad value regualr_opticalsize_coord = 14 # And it must also WARN the test print('Test WARN with another bad Regular:opsz value (14)...') status, message = list(check(ttFont, regular_opticalsize_coord))[-1] assert status == WARN # We then test with good default opsz values: for value in [9, 10, 11, 12, 13]: regular_opticalsize_coord = value # and now this should PASS the test: print('Test PASS with a good Regular:opsz coordinate ({})...'.format( value)) status, message = list(check(ttFont, regular_opticalsize_coord))[-1] assert status == PASS
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 _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 buildAxis(axisTag): axis = Axis() axis.axisTag = axisTag return axis
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_compile(self): axis = Axis() axis.axisTag, axis.axisNameID = ('opsz', 345) axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5) self.assertEqual(FVAR_AXIS_DATA, axis.compile())