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": (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 # Restrict weight and move default, new minimum (500) > old default (400) 130 ( 131 {"wght": (500, 900)}, 132 { 133 (1, 3, 1, 0x409): "Test Variable Font Medium", 134 (2, 3, 1, 0x409): "Regular", 135 (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Medium", 136 (6, 3, 1, 0x409): "TestVariableFont-Medium", 137 (16, 3, 1, 0x409): "Test Variable Font", 138 (17, 3, 1, 0x409): "Medium", 139 }, 140 True, 141 ), 142 ], 143) 144def test_updateNameTable_with_registered_axes_ribbi( 145 varfont, limits, expected, isNonRIBBI 146): 147 instancer.names.updateNameTable(varfont, limits) 148 _test_name_records(varfont, expected, isNonRIBBI) 149 150 151def test_updatetNameTable_axis_order(varfont): 152 axes = [ 153 dict( 154 tag="wght", 155 name="Weight", 156 values=[ 157 dict(value=400, name="Regular"), 158 ], 159 ), 160 dict( 161 tag="wdth", 162 name="Width", 163 values=[ 164 dict(value=75, name="Condensed"), 165 ], 166 ), 167 ] 168 nametable = varfont["name"] 169 buildStatTable(varfont, axes) 170 instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400}) 171 assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Regular Condensed" 172 173 # Swap the axes so the names get swapped 174 axes[0], axes[1] = axes[1], axes[0] 175 176 buildStatTable(varfont, axes) 177 instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400}) 178 assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Condensed Regular" 179 180 181@pytest.mark.parametrize( 182 "limits, expected, isNonRIBBI", 183 [ 184 # Regular | Normal 185 ( 186 {"wght": 400}, 187 { 188 (1, 3, 1, 0x409): "Test Variable Font", 189 (2, 3, 1, 0x409): "Normal", 190 }, 191 False, 192 ), 193 # Black | Negreta 194 ( 195 {"wght": 900}, 196 { 197 (1, 3, 1, 0x409): "Test Variable Font Negreta", 198 (2, 3, 1, 0x409): "Normal", 199 (16, 3, 1, 0x409): "Test Variable Font", 200 (17, 3, 1, 0x409): "Negreta", 201 }, 202 True, 203 ), 204 # Black Condensed | Negreta Zhuštěné 205 ( 206 {"wght": 900, "wdth": 79}, 207 { 208 (1, 3, 1, 0x409): "Test Variable Font Negreta Zhuštěné", 209 (2, 3, 1, 0x409): "Normal", 210 (16, 3, 1, 0x409): "Test Variable Font", 211 (17, 3, 1, 0x409): "Negreta Zhuštěné", 212 }, 213 True, 214 ), 215 ], 216) 217def test_updateNameTable_with_multilingual_names(varfont, limits, expected, isNonRIBBI): 218 name = varfont["name"] 219 # langID 0x405 is the Czech Windows langID 220 name.setName("Test Variable Font", 1, 3, 1, 0x405) 221 name.setName("Normal", 2, 3, 1, 0x405) 222 name.setName("Normal", 261, 3, 1, 0x405) # nameID 261=Regular STAT entry 223 name.setName("Negreta", 266, 3, 1, 0x405) # nameID 266=Black STAT entry 224 name.setName("Zhuštěné", 279, 3, 1, 0x405) # nameID 279=Condensed STAT entry 225 226 instancer.names.updateNameTable(varfont, limits) 227 _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x405]) 228 229 230def test_updateNameTable_missing_axisValues(varfont): 231 with pytest.raises(ValueError, match="Cannot find Axis Values {'wght': 200}"): 232 instancer.names.updateNameTable(varfont, {"wght": 200}) 233 234 235def test_updateNameTable_missing_stat(varfont): 236 del varfont["STAT"] 237 with pytest.raises( 238 ValueError, match="Cannot update name table since there is no STAT table." 239 ): 240 instancer.names.updateNameTable(varfont, {"wght": 400}) 241 242 243@pytest.mark.parametrize( 244 "limits, expected, isNonRIBBI", 245 [ 246 # Regular | Normal 247 ( 248 {"wght": 400}, 249 { 250 (1, 3, 1, 0x409): "Test Variable Font", 251 (2, 3, 1, 0x409): "Italic", 252 (6, 3, 1, 0x409): "TestVariableFont-Italic", 253 }, 254 False, 255 ), 256 # Black Condensed Italic 257 ( 258 {"wght": 900, "wdth": 79}, 259 { 260 (1, 3, 1, 0x409): "Test Variable Font Black Condensed", 261 (2, 3, 1, 0x409): "Italic", 262 (6, 3, 1, 0x409): "TestVariableFont-BlackCondensedItalic", 263 (16, 3, 1, 0x409): "Test Variable Font", 264 (17, 3, 1, 0x409): "Black Condensed Italic", 265 }, 266 True, 267 ), 268 ], 269) 270def test_updateNameTable_vf_with_italic_attribute( 271 varfont, limits, expected, isNonRIBBI 272): 273 font_link_axisValue = varfont["STAT"].table.AxisValueArray.AxisValue[5] 274 # Unset ELIDABLE_AXIS_VALUE_NAME flag 275 font_link_axisValue.Flags &= ~instancer.names.ELIDABLE_AXIS_VALUE_NAME 276 font_link_axisValue.ValueNameID = 294 # Roman --> Italic 277 278 instancer.names.updateNameTable(varfont, limits) 279 _test_name_records(varfont, expected, isNonRIBBI) 280 281 282def test_updateNameTable_format4_axisValues(varfont): 283 # format 4 axisValues should dominate the other axisValues 284 stat = varfont["STAT"].table 285 286 axisValue = otTables.AxisValue() 287 axisValue.Format = 4 288 axisValue.Flags = 0 289 varfont["name"].setName("Dominant Value", 297, 3, 1, 0x409) 290 axisValue.ValueNameID = 297 291 axisValue.AxisValueRecord = [] 292 for tag, value in (("wght", 900), ("wdth", 79)): 293 rec = otTables.AxisValueRecord() 294 rec.AxisIndex = next( 295 i for i, a in enumerate(stat.DesignAxisRecord.Axis) if a.AxisTag == tag 296 ) 297 rec.Value = value 298 axisValue.AxisValueRecord.append(rec) 299 stat.AxisValueArray.AxisValue.append(axisValue) 300 301 instancer.names.updateNameTable(varfont, {"wdth": 79, "wght": 900}) 302 expected = { 303 (1, 3, 1, 0x409): "Test Variable Font Dominant Value", 304 (2, 3, 1, 0x409): "Regular", 305 (16, 3, 1, 0x409): "Test Variable Font", 306 (17, 3, 1, 0x409): "Dominant Value", 307 } 308 _test_name_records(varfont, expected, isNonRIBBI=True) 309 310 311def test_updateNameTable_elided_axisValues(varfont): 312 stat = varfont["STAT"].table 313 # set ELIDABLE_AXIS_VALUE_NAME flag for all axisValues 314 for axisValue in stat.AxisValueArray.AxisValue: 315 axisValue.Flags |= instancer.names.ELIDABLE_AXIS_VALUE_NAME 316 317 stat.ElidedFallbackNameID = 266 # Regular --> Black 318 instancer.names.updateNameTable(varfont, {"wght": 400}) 319 # Since all axis values are elided, the elided fallback name 320 # must be used to construct the style names. Since we 321 # changed it to Black, we need both a typoSubFamilyName and 322 # the subFamilyName set so it conforms to the RIBBI model. 323 expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Black"} 324 _test_name_records(varfont, expected, isNonRIBBI=True) 325 326 327def test_updateNameTable_existing_subfamily_name_is_not_regular(varfont): 328 # Check the subFamily name will be set to Regular when we update a name 329 # table to a non-RIBBI style and the current subFamily name is a RIBBI 330 # style which isn't Regular. 331 varfont["name"].setName("Bold", 2, 3, 1, 0x409) # subFamily Regular --> Bold 332 333 instancer.names.updateNameTable(varfont, {"wght": 100}) 334 expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Thin"} 335 _test_name_records(varfont, expected, isNonRIBBI=True) 336 337 338def test_name_irrelevant_axes(varfont): 339 # Cannot update name table if not on a named axis value location 340 with pytest.raises(ValueError) as excinfo: 341 location = {"wght": 400, "wdth": 90} 342 instance = instancer.instantiateVariableFont( 343 varfont, location, updateFontNames=True 344 ) 345 assert "Cannot find Axis Values" in str(excinfo.value) 346 347 # Now let's make the wdth axis "irrelevant" to naming (no axis values) 348 varfont["STAT"].table.AxisValueArray.AxisValue.pop(6) 349 varfont["STAT"].table.AxisValueArray.AxisValue.pop(4) 350 location = {"wght": 400, "wdth": 90} 351 instance = instancer.instantiateVariableFont( 352 varfont, location, updateFontNames=True 353 ) 354