| from fontTools.ttLib.tables import otTables |
| from fontTools.otlLib.builder import buildStatTable |
| from fontTools.varLib import instancer |
| |
| import pytest |
| |
| |
| def test_pruningUnusedNames(varfont): |
| varNameIDs = instancer.names.getVariationNameIDs(varfont) |
| |
| assert varNameIDs == set(range(256, 297 + 1)) |
| |
| fvar = varfont["fvar"] |
| stat = varfont["STAT"].table |
| |
| with instancer.names.pruningUnusedNames(varfont): |
| del fvar.axes[0] # Weight (nameID=256) |
| del fvar.instances[0] # Thin (nameID=258) |
| del stat.DesignAxisRecord.Axis[0] # Weight (nameID=256) |
| del stat.AxisValueArray.AxisValue[0] # Thin (nameID=258) |
| |
| assert not any(n for n in varfont["name"].names if n.nameID in {256, 258}) |
| |
| with instancer.names.pruningUnusedNames(varfont): |
| del varfont["fvar"] |
| del varfont["STAT"] |
| |
| assert not any(n for n in varfont["name"].names if n.nameID in varNameIDs) |
| assert "ltag" not in varfont |
| |
| |
| def _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x409]): |
| nametable = varfont["name"] |
| font_names = { |
| (r.nameID, r.platformID, r.platEncID, r.langID): r.toUnicode() |
| for r in nametable.names |
| } |
| for k in expected: |
| if k[-1] not in platforms: |
| continue |
| assert font_names[k] == expected[k] |
| |
| font_nameids = set(i[0] for i in font_names) |
| if isNonRIBBI: |
| assert 16 in font_nameids |
| assert 17 in font_nameids |
| |
| if "fvar" not in varfont: |
| assert 25 not in font_nameids |
| |
| |
| @pytest.mark.parametrize( |
| "limits, expected, isNonRIBBI", |
| [ |
| # Regular |
| ( |
| {"wght": 400}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font", |
| (2, 3, 1, 0x409): "Regular", |
| (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Regular", |
| (6, 3, 1, 0x409): "TestVariableFont-Regular", |
| }, |
| False, |
| ), |
| # Regular Normal (width axis Normal isn't included since it is elided) |
| ( |
| {"wght": 400, "wdth": 100}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font", |
| (2, 3, 1, 0x409): "Regular", |
| (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Regular", |
| (6, 3, 1, 0x409): "TestVariableFont-Regular", |
| }, |
| False, |
| ), |
| # Black |
| ( |
| {"wght": 900}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Black", |
| (2, 3, 1, 0x409): "Regular", |
| (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Black", |
| (6, 3, 1, 0x409): "TestVariableFont-Black", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Black", |
| }, |
| True, |
| ), |
| # Thin |
| ( |
| {"wght": 100}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Thin", |
| (2, 3, 1, 0x409): "Regular", |
| (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Thin", |
| (6, 3, 1, 0x409): "TestVariableFont-Thin", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Thin", |
| }, |
| True, |
| ), |
| # Thin Condensed |
| ( |
| {"wght": 100, "wdth": 79}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Thin Condensed", |
| (2, 3, 1, 0x409): "Regular", |
| (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-ThinCondensed", |
| (6, 3, 1, 0x409): "TestVariableFont-ThinCondensed", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Thin Condensed", |
| }, |
| True, |
| ), |
| # Condensed with unpinned weights |
| ( |
| {"wdth": 79, "wght": instancer.AxisRange(400, 900)}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Condensed", |
| (2, 3, 1, 0x409): "Regular", |
| (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Condensed", |
| (6, 3, 1, 0x409): "TestVariableFont-Condensed", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Condensed", |
| }, |
| True, |
| ), |
| ], |
| ) |
| def test_updateNameTable_with_registered_axes_ribbi( |
| varfont, limits, expected, isNonRIBBI |
| ): |
| instancer.names.updateNameTable(varfont, limits) |
| _test_name_records(varfont, expected, isNonRIBBI) |
| |
| |
| def test_updatetNameTable_axis_order(varfont): |
| axes = [ |
| dict( |
| tag="wght", |
| name="Weight", |
| values=[ |
| dict(value=400, name="Regular"), |
| ], |
| ), |
| dict( |
| tag="wdth", |
| name="Width", |
| values=[ |
| dict(value=75, name="Condensed"), |
| ], |
| ), |
| ] |
| nametable = varfont["name"] |
| buildStatTable(varfont, axes) |
| instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400}) |
| assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Regular Condensed" |
| |
| # Swap the axes so the names get swapped |
| axes[0], axes[1] = axes[1], axes[0] |
| |
| buildStatTable(varfont, axes) |
| instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400}) |
| assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Condensed Regular" |
| |
| |
| @pytest.mark.parametrize( |
| "limits, expected, isNonRIBBI", |
| [ |
| # Regular | Normal |
| ( |
| {"wght": 400}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font", |
| (2, 3, 1, 0x409): "Normal", |
| }, |
| False, |
| ), |
| # Black | Negreta |
| ( |
| {"wght": 900}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Negreta", |
| (2, 3, 1, 0x409): "Normal", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Negreta", |
| }, |
| True, |
| ), |
| # Black Condensed | Negreta Zhuštěné |
| ( |
| {"wght": 900, "wdth": 79}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Negreta Zhuštěné", |
| (2, 3, 1, 0x409): "Normal", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Negreta Zhuštěné", |
| }, |
| True, |
| ), |
| ], |
| ) |
| def test_updateNameTable_with_multilingual_names(varfont, limits, expected, isNonRIBBI): |
| name = varfont["name"] |
| # langID 0x405 is the Czech Windows langID |
| name.setName("Test Variable Font", 1, 3, 1, 0x405) |
| name.setName("Normal", 2, 3, 1, 0x405) |
| name.setName("Normal", 261, 3, 1, 0x405) # nameID 261=Regular STAT entry |
| name.setName("Negreta", 266, 3, 1, 0x405) # nameID 266=Black STAT entry |
| name.setName("Zhuštěné", 279, 3, 1, 0x405) # nameID 279=Condensed STAT entry |
| |
| instancer.names.updateNameTable(varfont, limits) |
| _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x405]) |
| |
| |
| def test_updateNameTable_missing_axisValues(varfont): |
| with pytest.raises(ValueError, match="Cannot find Axis Values \['wght=200'\]"): |
| instancer.names.updateNameTable(varfont, {"wght": 200}) |
| |
| |
| def test_updateNameTable_missing_stat(varfont): |
| del varfont["STAT"] |
| with pytest.raises( |
| ValueError, match="Cannot update name table since there is no STAT table." |
| ): |
| instancer.names.updateNameTable(varfont, {"wght": 400}) |
| |
| |
| @pytest.mark.parametrize( |
| "limits, expected, isNonRIBBI", |
| [ |
| # Regular | Normal |
| ( |
| {"wght": 400}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font", |
| (2, 3, 1, 0x409): "Italic", |
| (6, 3, 1, 0x409): "TestVariableFont-Italic", |
| }, |
| False, |
| ), |
| # Black Condensed Italic |
| ( |
| {"wght": 900, "wdth": 79}, |
| { |
| (1, 3, 1, 0x409): "Test Variable Font Black Condensed", |
| (2, 3, 1, 0x409): "Italic", |
| (6, 3, 1, 0x409): "TestVariableFont-BlackCondensedItalic", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Black Condensed Italic", |
| }, |
| True, |
| ), |
| ], |
| ) |
| def test_updateNameTable_vf_with_italic_attribute( |
| varfont, limits, expected, isNonRIBBI |
| ): |
| font_link_axisValue = varfont["STAT"].table.AxisValueArray.AxisValue[4] |
| # Unset ELIDABLE_AXIS_VALUE_NAME flag |
| font_link_axisValue.Flags &= ~instancer.names.ELIDABLE_AXIS_VALUE_NAME |
| font_link_axisValue.ValueNameID = 294 # Roman --> Italic |
| |
| instancer.names.updateNameTable(varfont, limits) |
| _test_name_records(varfont, expected, isNonRIBBI) |
| |
| |
| def test_updateNameTable_format4_axisValues(varfont): |
| # format 4 axisValues should dominate the other axisValues |
| stat = varfont["STAT"].table |
| |
| axisValue = otTables.AxisValue() |
| axisValue.Format = 4 |
| axisValue.Flags = 0 |
| varfont["name"].setName("Dominant Value", 297, 3, 1, 0x409) |
| axisValue.ValueNameID = 297 |
| axisValue.AxisValueRecord = [] |
| for tag, value in (("wght", 900), ("wdth", 79)): |
| rec = otTables.AxisValueRecord() |
| rec.AxisIndex = next( |
| i for i, a in enumerate(stat.DesignAxisRecord.Axis) if a.AxisTag == tag |
| ) |
| rec.Value = value |
| axisValue.AxisValueRecord.append(rec) |
| stat.AxisValueArray.AxisValue.append(axisValue) |
| |
| instancer.names.updateNameTable(varfont, {"wdth": 79, "wght": 900}) |
| expected = { |
| (1, 3, 1, 0x409): "Test Variable Font Dominant Value", |
| (2, 3, 1, 0x409): "Regular", |
| (16, 3, 1, 0x409): "Test Variable Font", |
| (17, 3, 1, 0x409): "Dominant Value", |
| } |
| _test_name_records(varfont, expected, isNonRIBBI=True) |
| |
| |
| def test_updateNameTable_elided_axisValues(varfont): |
| stat = varfont["STAT"].table |
| # set ELIDABLE_AXIS_VALUE_NAME flag for all axisValues |
| for axisValue in stat.AxisValueArray.AxisValue: |
| axisValue.Flags |= instancer.names.ELIDABLE_AXIS_VALUE_NAME |
| |
| stat.ElidedFallbackNameID = 266 # Regular --> Black |
| instancer.names.updateNameTable(varfont, {"wght": 400}) |
| # Since all axis values are elided, the elided fallback name |
| # must be used to construct the style names. Since we |
| # changed it to Black, we need both a typoSubFamilyName and |
| # the subFamilyName set so it conforms to the RIBBI model. |
| expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Black"} |
| _test_name_records(varfont, expected, isNonRIBBI=True) |
| |
| |
| def test_updateNameTable_existing_subfamily_name_is_not_regular(varfont): |
| # Check the subFamily name will be set to Regular when we update a name |
| # table to a non-RIBBI style and the current subFamily name is a RIBBI |
| # style which isn't Regular. |
| varfont["name"].setName("Bold", 2, 3, 1, 0x409) # subFamily Regular --> Bold |
| |
| instancer.names.updateNameTable(varfont, {"wght": 100}) |
| expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Thin"} |
| _test_name_records(varfont, expected, isNonRIBBI=True) |