• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.ttLib.tables import otTables
2from fontTools.otlLib.builder import buildStatTable
3from fontTools.varLib import instancer
4
5import pytest
6
7
8def test_pruningUnusedNames(varfont):
9    varNameIDs = instancer.names.getVariationNameIDs(varfont)
10
11    assert varNameIDs == set(range(256, 297 + 1))
12
13    fvar = varfont["fvar"]
14    stat = varfont["STAT"].table
15
16    with instancer.names.pruningUnusedNames(varfont):
17        del fvar.axes[0]  # Weight (nameID=256)
18        del fvar.instances[0]  # Thin (nameID=258)
19        del stat.DesignAxisRecord.Axis[0]  # Weight (nameID=256)
20        del stat.AxisValueArray.AxisValue[0]  # Thin (nameID=258)
21
22    assert not any(n for n in varfont["name"].names if n.nameID in {256, 258})
23
24    with instancer.names.pruningUnusedNames(varfont):
25        del varfont["fvar"]
26        del varfont["STAT"]
27
28    assert not any(n for n in varfont["name"].names if n.nameID in varNameIDs)
29    assert "ltag" not in varfont
30
31
32def _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x409]):
33    nametable = varfont["name"]
34    font_names = {
35        (r.nameID, r.platformID, r.platEncID, r.langID): r.toUnicode()
36        for r in nametable.names
37    }
38    for k in expected:
39        if k[-1] not in platforms:
40            continue
41        assert font_names[k] == expected[k]
42
43    font_nameids = set(i[0] for i in font_names)
44    if isNonRIBBI:
45        assert 16 in font_nameids
46        assert 17 in font_nameids
47
48    if "fvar" not in varfont:
49        assert 25 not in font_nameids
50
51
52@pytest.mark.parametrize(
53    "limits, expected, isNonRIBBI",
54    [
55        # Regular
56        (
57            {"wght": 400},
58            {
59                (1, 3, 1, 0x409): "Test Variable Font",
60                (2, 3, 1, 0x409): "Regular",
61                (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Regular",
62                (6, 3, 1, 0x409): "TestVariableFont-Regular",
63            },
64            False,
65        ),
66        # Regular Normal (width axis Normal isn't included since it is elided)
67        (
68            {"wght": 400, "wdth": 100},
69            {
70                (1, 3, 1, 0x409): "Test Variable Font",
71                (2, 3, 1, 0x409): "Regular",
72                (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Regular",
73                (6, 3, 1, 0x409): "TestVariableFont-Regular",
74            },
75            False,
76        ),
77        # Black
78        (
79            {"wght": 900},
80            {
81                (1, 3, 1, 0x409): "Test Variable Font Black",
82                (2, 3, 1, 0x409): "Regular",
83                (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Black",
84                (6, 3, 1, 0x409): "TestVariableFont-Black",
85                (16, 3, 1, 0x409): "Test Variable Font",
86                (17, 3, 1, 0x409): "Black",
87            },
88            True,
89        ),
90        # Thin
91        (
92            {"wght": 100},
93            {
94                (1, 3, 1, 0x409): "Test Variable Font Thin",
95                (2, 3, 1, 0x409): "Regular",
96                (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Thin",
97                (6, 3, 1, 0x409): "TestVariableFont-Thin",
98                (16, 3, 1, 0x409): "Test Variable Font",
99                (17, 3, 1, 0x409): "Thin",
100            },
101            True,
102        ),
103        # Thin Condensed
104        (
105            {"wght": 100, "wdth": 79},
106            {
107                (1, 3, 1, 0x409): "Test Variable Font Thin Condensed",
108                (2, 3, 1, 0x409): "Regular",
109                (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-ThinCondensed",
110                (6, 3, 1, 0x409): "TestVariableFont-ThinCondensed",
111                (16, 3, 1, 0x409): "Test Variable Font",
112                (17, 3, 1, 0x409): "Thin Condensed",
113            },
114            True,
115        ),
116        # Condensed with unpinned weights
117        (
118            {"wdth": 79, "wght": instancer.AxisRange(400, 900)},
119            {
120                (1, 3, 1, 0x409): "Test Variable Font Condensed",
121                (2, 3, 1, 0x409): "Regular",
122                (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Condensed",
123                (6, 3, 1, 0x409): "TestVariableFont-Condensed",
124                (16, 3, 1, 0x409): "Test Variable Font",
125                (17, 3, 1, 0x409): "Condensed",
126            },
127            True,
128        ),
129    ],
130)
131def test_updateNameTable_with_registered_axes_ribbi(
132    varfont, limits, expected, isNonRIBBI
133):
134    instancer.names.updateNameTable(varfont, limits)
135    _test_name_records(varfont, expected, isNonRIBBI)
136
137
138def test_updatetNameTable_axis_order(varfont):
139    axes = [
140        dict(
141            tag="wght",
142            name="Weight",
143            values=[
144                dict(value=400, name="Regular"),
145            ],
146        ),
147        dict(
148            tag="wdth",
149            name="Width",
150            values=[
151                dict(value=75, name="Condensed"),
152            ],
153        ),
154    ]
155    nametable = varfont["name"]
156    buildStatTable(varfont, axes)
157    instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400})
158    assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Regular Condensed"
159
160    # Swap the axes so the names get swapped
161    axes[0], axes[1] = axes[1], axes[0]
162
163    buildStatTable(varfont, axes)
164    instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400})
165    assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Condensed Regular"
166
167
168@pytest.mark.parametrize(
169    "limits, expected, isNonRIBBI",
170    [
171        # Regular | Normal
172        (
173            {"wght": 400},
174            {
175                (1, 3, 1, 0x409): "Test Variable Font",
176                (2, 3, 1, 0x409): "Normal",
177            },
178            False,
179        ),
180        # Black | Negreta
181        (
182            {"wght": 900},
183            {
184                (1, 3, 1, 0x409): "Test Variable Font Negreta",
185                (2, 3, 1, 0x409): "Normal",
186                (16, 3, 1, 0x409): "Test Variable Font",
187                (17, 3, 1, 0x409): "Negreta",
188            },
189            True,
190        ),
191        # Black Condensed | Negreta Zhuštěné
192        (
193            {"wght": 900, "wdth": 79},
194            {
195                (1, 3, 1, 0x409): "Test Variable Font Negreta Zhuštěné",
196                (2, 3, 1, 0x409): "Normal",
197                (16, 3, 1, 0x409): "Test Variable Font",
198                (17, 3, 1, 0x409): "Negreta Zhuštěné",
199            },
200            True,
201        ),
202    ],
203)
204def test_updateNameTable_with_multilingual_names(varfont, limits, expected, isNonRIBBI):
205    name = varfont["name"]
206    # langID 0x405 is the Czech Windows langID
207    name.setName("Test Variable Font", 1, 3, 1, 0x405)
208    name.setName("Normal", 2, 3, 1, 0x405)
209    name.setName("Normal", 261, 3, 1, 0x405)  # nameID 261=Regular STAT entry
210    name.setName("Negreta", 266, 3, 1, 0x405)  # nameID 266=Black STAT entry
211    name.setName("Zhuštěné", 279, 3, 1, 0x405)  # nameID 279=Condensed STAT entry
212
213    instancer.names.updateNameTable(varfont, limits)
214    _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x405])
215
216
217def test_updateNameTable_missing_axisValues(varfont):
218    with pytest.raises(ValueError, match="Cannot find Axis Values \['wght=200'\]"):
219        instancer.names.updateNameTable(varfont, {"wght": 200})
220
221
222def test_updateNameTable_missing_stat(varfont):
223    del varfont["STAT"]
224    with pytest.raises(
225        ValueError, match="Cannot update name table since there is no STAT table."
226    ):
227        instancer.names.updateNameTable(varfont, {"wght": 400})
228
229
230@pytest.mark.parametrize(
231    "limits, expected, isNonRIBBI",
232    [
233        # Regular | Normal
234        (
235            {"wght": 400},
236            {
237                (1, 3, 1, 0x409): "Test Variable Font",
238                (2, 3, 1, 0x409): "Italic",
239                (6, 3, 1, 0x409): "TestVariableFont-Italic",
240            },
241            False,
242        ),
243        # Black Condensed Italic
244        (
245            {"wght": 900, "wdth": 79},
246            {
247                (1, 3, 1, 0x409): "Test Variable Font Black Condensed",
248                (2, 3, 1, 0x409): "Italic",
249                (6, 3, 1, 0x409): "TestVariableFont-BlackCondensedItalic",
250                (16, 3, 1, 0x409): "Test Variable Font",
251                (17, 3, 1, 0x409): "Black Condensed Italic",
252            },
253            True,
254        ),
255    ],
256)
257def test_updateNameTable_vf_with_italic_attribute(
258    varfont, limits, expected, isNonRIBBI
259):
260    font_link_axisValue = varfont["STAT"].table.AxisValueArray.AxisValue[4]
261    # Unset ELIDABLE_AXIS_VALUE_NAME flag
262    font_link_axisValue.Flags &= ~instancer.names.ELIDABLE_AXIS_VALUE_NAME
263    font_link_axisValue.ValueNameID = 294  # Roman --> Italic
264
265    instancer.names.updateNameTable(varfont, limits)
266    _test_name_records(varfont, expected, isNonRIBBI)
267
268
269def test_updateNameTable_format4_axisValues(varfont):
270    # format 4 axisValues should dominate the other axisValues
271    stat = varfont["STAT"].table
272
273    axisValue = otTables.AxisValue()
274    axisValue.Format = 4
275    axisValue.Flags = 0
276    varfont["name"].setName("Dominant Value", 297, 3, 1, 0x409)
277    axisValue.ValueNameID = 297
278    axisValue.AxisValueRecord = []
279    for tag, value in (("wght", 900), ("wdth", 79)):
280        rec = otTables.AxisValueRecord()
281        rec.AxisIndex = next(
282            i for i, a in enumerate(stat.DesignAxisRecord.Axis) if a.AxisTag == tag
283        )
284        rec.Value = value
285        axisValue.AxisValueRecord.append(rec)
286    stat.AxisValueArray.AxisValue.append(axisValue)
287
288    instancer.names.updateNameTable(varfont, {"wdth": 79, "wght": 900})
289    expected = {
290        (1, 3, 1, 0x409): "Test Variable Font Dominant Value",
291        (2, 3, 1, 0x409): "Regular",
292        (16, 3, 1, 0x409): "Test Variable Font",
293        (17, 3, 1, 0x409): "Dominant Value",
294    }
295    _test_name_records(varfont, expected, isNonRIBBI=True)
296
297
298def test_updateNameTable_elided_axisValues(varfont):
299    stat = varfont["STAT"].table
300    # set ELIDABLE_AXIS_VALUE_NAME flag for all axisValues
301    for axisValue in stat.AxisValueArray.AxisValue:
302        axisValue.Flags |= instancer.names.ELIDABLE_AXIS_VALUE_NAME
303
304    stat.ElidedFallbackNameID = 266  # Regular --> Black
305    instancer.names.updateNameTable(varfont, {"wght": 400})
306    # Since all axis values are elided, the elided fallback name
307    # must be used to construct the style names. Since we
308    # changed it to Black, we need both a typoSubFamilyName and
309    # the subFamilyName set so it conforms to the RIBBI model.
310    expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Black"}
311    _test_name_records(varfont, expected, isNonRIBBI=True)
312
313
314def test_updateNameTable_existing_subfamily_name_is_not_regular(varfont):
315    # Check the subFamily name will be set to Regular when we update a name
316    # table to a non-RIBBI style and the current subFamily name is a RIBBI
317    # style which isn't Regular.
318    varfont["name"].setName("Bold", 2, 3, 1, 0x409)  # subFamily Regular --> Bold
319
320    instancer.names.updateNameTable(varfont, {"wght": 100})
321    expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Thin"}
322    _test_name_records(varfont, expected, isNonRIBBI=True)
323