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 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.nameID) 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.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 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 _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 _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 test_fromXML(self): axis = Axis() for name, attrs, content in parseXML( '<Axis>' ' <AxisTag>wght</AxisTag>' ' <Flags>0x123ABC</Flags>' ' <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(0x123ABC, axis.flags) self.assertEqual(100, axis.minValue) self.assertEqual(400, axis.defaultValue) self.assertEqual(900, axis.maxValue) self.assertEqual(256, axis.axisNameID)
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 test_check_varfont_regular_opsz_coord(): """ The variable font 'opsz' (Optical Size) axis coordinate should be between 9 and 13 on the 'Regular' instance. """ from fontbakery.profiles.fvar import com_google_fonts_check_varfont_regular_opsz_coord as check from fontbakery.profiles.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(f'Test PASS with a good Regular:opsz coordinate ({value})...') status, message = list(check(ttFont, regular_opticalsize_coord))[-1] assert status == PASS
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): """ 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 _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(self): font = MakeFont() axis = Axis() axis.decompile(FVAR_AXIS_DATA) AddName(font, "Optical Size").nameID = 256 axis.axisNameID = 256 axis.flags = 0xABC writer = XMLWriter(BytesIO()) axis.toXML(writer, font) self.assertEqual([ '', '<!-- Optical Size -->', '<Axis>', '<AxisTag>opsz</AxisTag>', '<Flags>0xABC</Flags>', '<MinValue>-0.5</MinValue>', '<DefaultValue>1.3</DefaultValue>', '<MaxValue>1.5</MaxValue>', '<AxisNameID>256</AxisNameID>', '</Axis>' ], xml_lines(writer))
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 test_toXML(self): font = MakeFont() axis = Axis() axis.decompile(FVAR_AXIS_DATA) AddName(font, "Optical Size").nameID = 256 axis.nameID = 256 writer = XMLWriter(BytesIO()) 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 test_compile(self): axis = Axis() axis.axisTag, axis.nameID, axis.flags = ("opsz", 345, 0x9876) axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5) self.assertEqual(FVAR_AXIS_DATA, axis.compile())
def buildAxis(axisTag): axis = Axis() axis.axisTag = axisTag return axis
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())
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.nameID = ('opsz', 345) axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5) self.assertEqual(FVAR_AXIS_DATA, axis.compile())
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