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