1from fontTools.misc.testTools import parseXML, getXML 2from fontTools.misc.textTools import deHexStr 3from fontTools.ttLib import TTFont, TTLibError 4from fontTools.ttLib.tables._t_r_a_k import * 5from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord 6import unittest 7 8 9# /Library/Fonts/Osaka.ttf from OSX has trak table with both horiz and vertData 10OSAKA_TRAK_TABLE_DATA = deHexStr( 11 "00 01 00 00 00 00 00 0c 00 40 00 00 00 03 00 02 00 00 00 2c ff ff " 12 "00 00 01 06 00 34 00 00 00 00 01 07 00 38 00 01 00 00 01 08 00 3c " 13 "00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 00 00 00 0c 00 0c 00 03 " 14 "00 02 00 00 00 60 ff ff 00 00 01 09 00 68 00 00 00 00 01 0a 00 6c " 15 "00 01 00 00 01 0b 00 70 00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 " 16 "00 00 00 0c 00 0c" 17) 18 19# decompiled horizData and vertData entries from Osaka.ttf 20OSAKA_HORIZ_TRACK_ENTRIES = { 21 -1.0: TrackTableEntry({24.0: -12, 12.0: -12}, nameIndex=262), 22 0.0: TrackTableEntry({24.0: 0, 12.0: 0}, nameIndex=263), 23 1.0: TrackTableEntry({24.0: 12, 12.0: 12}, nameIndex=264), 24} 25 26OSAKA_VERT_TRACK_ENTRIES = { 27 -1.0: TrackTableEntry({24.0: -12, 12.0: -12}, nameIndex=265), 28 0.0: TrackTableEntry({24.0: 0, 12.0: 0}, nameIndex=266), 29 1.0: TrackTableEntry({24.0: 12, 12.0: 12}, nameIndex=267), 30} 31 32OSAKA_TRAK_TABLE_XML = [ 33 '<version value="1.0"/>', 34 '<format value="0"/>', 35 "<horizData>", 36 " <!-- nTracks=3, nSizes=2 -->", 37 ' <trackEntry value="-1.0" nameIndex="262">', 38 " <!-- Tight -->", 39 ' <track size="12.0" value="-12"/>', 40 ' <track size="24.0" value="-12"/>', 41 " </trackEntry>", 42 ' <trackEntry value="0.0" nameIndex="263">', 43 " <!-- Normal -->", 44 ' <track size="12.0" value="0"/>', 45 ' <track size="24.0" value="0"/>', 46 " </trackEntry>", 47 ' <trackEntry value="1.0" nameIndex="264">', 48 " <!-- Loose -->", 49 ' <track size="12.0" value="12"/>', 50 ' <track size="24.0" value="12"/>', 51 " </trackEntry>", 52 "</horizData>", 53 "<vertData>", 54 " <!-- nTracks=3, nSizes=2 -->", 55 ' <trackEntry value="-1.0" nameIndex="265">', 56 " <!-- Tight -->", 57 ' <track size="12.0" value="-12"/>', 58 ' <track size="24.0" value="-12"/>', 59 " </trackEntry>", 60 ' <trackEntry value="0.0" nameIndex="266">', 61 " <!-- Normal -->", 62 ' <track size="12.0" value="0"/>', 63 ' <track size="24.0" value="0"/>', 64 " </trackEntry>", 65 ' <trackEntry value="1.0" nameIndex="267">', 66 " <!-- Loose -->", 67 ' <track size="12.0" value="12"/>', 68 ' <track size="24.0" value="12"/>', 69 " </trackEntry>", 70 "</vertData>", 71] 72 73# made-up table containing only vertData (no horizData) 74OSAKA_VERT_ONLY_TRAK_TABLE_DATA = deHexStr( 75 "00 01 00 00 00 00 00 00 00 0c 00 00 00 03 00 02 00 00 00 2c ff ff " 76 "00 00 01 09 00 34 00 00 00 00 01 0a 00 38 00 01 00 00 01 0b 00 3c " 77 "00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 00 00 00 0c 00 0c" 78) 79 80OSAKA_VERT_ONLY_TRAK_TABLE_XML = [ 81 '<version value="1.0"/>', 82 '<format value="0"/>', 83 "<horizData>", 84 " <!-- nTracks=0, nSizes=0 -->", 85 "</horizData>", 86 "<vertData>", 87 " <!-- nTracks=3, nSizes=2 -->", 88 ' <trackEntry value="-1.0" nameIndex="265">', 89 " <!-- Tight -->", 90 ' <track size="12.0" value="-12"/>', 91 ' <track size="24.0" value="-12"/>', 92 " </trackEntry>", 93 ' <trackEntry value="0.0" nameIndex="266">', 94 " <!-- Normal -->", 95 ' <track size="12.0" value="0"/>', 96 ' <track size="24.0" value="0"/>', 97 " </trackEntry>", 98 ' <trackEntry value="1.0" nameIndex="267">', 99 " <!-- Loose -->", 100 ' <track size="12.0" value="12"/>', 101 ' <track size="24.0" value="12"/>', 102 " </trackEntry>", 103 "</vertData>", 104] 105 106 107# also /Library/Fonts/Skia.ttf contains a trak table with horizData 108SKIA_TRAK_TABLE_DATA = deHexStr( 109 "00 01 00 00 00 00 00 0c 00 00 00 00 00 03 00 05 00 00 00 2c ff ff " 110 "00 00 01 13 00 40 00 00 00 00 01 2f 00 4a 00 01 00 00 01 14 00 54 " 111 "00 09 00 00 00 0a 00 00 00 0c 00 00 00 12 00 00 00 13 00 00 ff f6 " 112 "ff e2 ff c4 ff c1 ff c1 00 0f 00 00 ff fb ff e7 ff e7 00 8c 00 82 " 113 "00 7d 00 73 00 73" 114) 115 116SKIA_TRACK_ENTRIES = { 117 -1.0: TrackTableEntry( 118 {9.0: -10, 10.0: -30, 19.0: -63, 12.0: -60, 18.0: -63}, nameIndex=275 119 ), 120 0.0: TrackTableEntry( 121 {9.0: 15, 10.0: 0, 19.0: -25, 12.0: -5, 18.0: -25}, nameIndex=303 122 ), 123 1.0: TrackTableEntry( 124 {9.0: 140, 10.0: 130, 19.0: 115, 12.0: 125, 18.0: 115}, nameIndex=276 125 ), 126} 127 128SKIA_TRAK_TABLE_XML = [ 129 '<version value="1.0"/>', 130 '<format value="0"/>', 131 "<horizData>", 132 " <!-- nTracks=3, nSizes=5 -->", 133 ' <trackEntry value="-1.0" nameIndex="275">', 134 " <!-- Tight -->", 135 ' <track size="9.0" value="-10"/>', 136 ' <track size="10.0" value="-30"/>', 137 ' <track size="12.0" value="-60"/>', 138 ' <track size="18.0" value="-63"/>', 139 ' <track size="19.0" value="-63"/>', 140 " </trackEntry>", 141 ' <trackEntry value="0.0" nameIndex="303">', 142 " <!-- Normal -->", 143 ' <track size="9.0" value="15"/>', 144 ' <track size="10.0" value="0"/>', 145 ' <track size="12.0" value="-5"/>', 146 ' <track size="18.0" value="-25"/>', 147 ' <track size="19.0" value="-25"/>', 148 " </trackEntry>", 149 ' <trackEntry value="1.0" nameIndex="276">', 150 " <!-- Loose -->", 151 ' <track size="9.0" value="140"/>', 152 ' <track size="10.0" value="130"/>', 153 ' <track size="12.0" value="125"/>', 154 ' <track size="18.0" value="115"/>', 155 ' <track size="19.0" value="115"/>', 156 " </trackEntry>", 157 "</horizData>", 158 "<vertData>", 159 " <!-- nTracks=0, nSizes=0 -->", 160 "</vertData>", 161] 162 163 164class TrackingTableTest(unittest.TestCase): 165 def __init__(self, methodName): 166 unittest.TestCase.__init__(self, methodName) 167 # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 168 # and fires deprecation warnings if a program uses the old name. 169 if not hasattr(self, "assertRaisesRegex"): 170 self.assertRaisesRegex = self.assertRaisesRegexp 171 172 def setUp(self): 173 table = table__t_r_a_k() 174 table.version = 1.0 175 table.format = 0 176 self.font = {"trak": table} 177 178 def test_compile_horiz(self): 179 table = self.font["trak"] 180 table.horizData = TrackData(SKIA_TRACK_ENTRIES) 181 trakData = table.compile(self.font) 182 self.assertEqual(trakData, SKIA_TRAK_TABLE_DATA) 183 184 def test_compile_vert(self): 185 table = self.font["trak"] 186 table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES) 187 trakData = table.compile(self.font) 188 self.assertEqual(trakData, OSAKA_VERT_ONLY_TRAK_TABLE_DATA) 189 190 def test_compile_horiz_and_vert(self): 191 table = self.font["trak"] 192 table.horizData = TrackData(OSAKA_HORIZ_TRACK_ENTRIES) 193 table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES) 194 trakData = table.compile(self.font) 195 self.assertEqual(trakData, OSAKA_TRAK_TABLE_DATA) 196 197 def test_compile_longword_aligned(self): 198 table = self.font["trak"] 199 # without padding, this 'horizData' would end up 46 byte long 200 table.horizData = TrackData( 201 {0.0: TrackTableEntry(nameIndex=256, values={12.0: 0, 24.0: 0, 36.0: 0})} 202 ) 203 table.vertData = TrackData( 204 {0.0: TrackTableEntry(nameIndex=257, values={12.0: 0, 24.0: 0, 36.0: 0})} 205 ) 206 trakData = table.compile(self.font) 207 self.assertTrue(table.vertOffset % 4 == 0) 208 209 def test_compile_sizes_mismatch(self): 210 table = self.font["trak"] 211 table.horizData = TrackData( 212 { 213 -1.0: TrackTableEntry(nameIndex=256, values={9.0: -10, 10.0: -30}), 214 0.0: TrackTableEntry(nameIndex=257, values={8.0: 20, 12.0: 0}), 215 } 216 ) 217 with self.assertRaisesRegex(TTLibError, "entries must specify the same sizes"): 218 table.compile(self.font) 219 220 def test_decompile_horiz(self): 221 table = self.font["trak"] 222 table.decompile(SKIA_TRAK_TABLE_DATA, self.font) 223 self.assertEqual(table.horizData, SKIA_TRACK_ENTRIES) 224 self.assertEqual(table.vertData, TrackData()) 225 226 def test_decompile_vert(self): 227 table = self.font["trak"] 228 table.decompile(OSAKA_VERT_ONLY_TRAK_TABLE_DATA, self.font) 229 self.assertEqual(table.horizData, TrackData()) 230 self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES) 231 232 def test_decompile_horiz_and_vert(self): 233 table = self.font["trak"] 234 table.decompile(OSAKA_TRAK_TABLE_DATA, self.font) 235 self.assertEqual(table.horizData, OSAKA_HORIZ_TRACK_ENTRIES) 236 self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES) 237 238 def test_roundtrip_decompile_compile(self): 239 for trakData in ( 240 OSAKA_TRAK_TABLE_DATA, 241 OSAKA_VERT_ONLY_TRAK_TABLE_DATA, 242 SKIA_TRAK_TABLE_DATA, 243 ): 244 table = table__t_r_a_k() 245 table.decompile(trakData, ttFont=None) 246 newTrakData = table.compile(ttFont=None) 247 self.assertEqual(trakData, newTrakData) 248 249 def test_fromXML_horiz(self): 250 table = self.font["trak"] 251 for name, attrs, content in parseXML(SKIA_TRAK_TABLE_XML): 252 table.fromXML(name, attrs, content, self.font) 253 self.assertEqual(table.version, 1.0) 254 self.assertEqual(table.format, 0) 255 self.assertEqual(table.horizData, SKIA_TRACK_ENTRIES) 256 self.assertEqual(table.vertData, TrackData()) 257 258 def test_fromXML_horiz_and_vert(self): 259 table = self.font["trak"] 260 for name, attrs, content in parseXML(OSAKA_TRAK_TABLE_XML): 261 table.fromXML(name, attrs, content, self.font) 262 self.assertEqual(table.version, 1.0) 263 self.assertEqual(table.format, 0) 264 self.assertEqual(table.horizData, OSAKA_HORIZ_TRACK_ENTRIES) 265 self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES) 266 267 def test_fromXML_vert(self): 268 table = self.font["trak"] 269 for name, attrs, content in parseXML(OSAKA_VERT_ONLY_TRAK_TABLE_XML): 270 table.fromXML(name, attrs, content, self.font) 271 self.assertEqual(table.version, 1.0) 272 self.assertEqual(table.format, 0) 273 self.assertEqual(table.horizData, TrackData()) 274 self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES) 275 276 def test_toXML_horiz(self): 277 table = self.font["trak"] 278 table.horizData = TrackData(SKIA_TRACK_ENTRIES) 279 add_name(self.font, "Tight", nameID=275) 280 add_name(self.font, "Normal", nameID=303) 281 add_name(self.font, "Loose", nameID=276) 282 self.assertEqual(SKIA_TRAK_TABLE_XML, getXML(table.toXML, self.font)) 283 284 def test_toXML_horiz_and_vert(self): 285 table = self.font["trak"] 286 table.horizData = TrackData(OSAKA_HORIZ_TRACK_ENTRIES) 287 table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES) 288 add_name(self.font, "Tight", nameID=262) 289 add_name(self.font, "Normal", nameID=263) 290 add_name(self.font, "Loose", nameID=264) 291 add_name(self.font, "Tight", nameID=265) 292 add_name(self.font, "Normal", nameID=266) 293 add_name(self.font, "Loose", nameID=267) 294 self.assertEqual(OSAKA_TRAK_TABLE_XML, getXML(table.toXML, self.font)) 295 296 def test_toXML_vert(self): 297 table = self.font["trak"] 298 table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES) 299 add_name(self.font, "Tight", nameID=265) 300 add_name(self.font, "Normal", nameID=266) 301 add_name(self.font, "Loose", nameID=267) 302 self.assertEqual(OSAKA_VERT_ONLY_TRAK_TABLE_XML, getXML(table.toXML, self.font)) 303 304 def test_roundtrip_fromXML_toXML(self): 305 font = {} 306 add_name(font, "Tight", nameID=275) 307 add_name(font, "Normal", nameID=303) 308 add_name(font, "Loose", nameID=276) 309 add_name(font, "Tight", nameID=262) 310 add_name(font, "Normal", nameID=263) 311 add_name(font, "Loose", nameID=264) 312 add_name(font, "Tight", nameID=265) 313 add_name(font, "Normal", nameID=266) 314 add_name(font, "Loose", nameID=267) 315 for input_xml in ( 316 SKIA_TRAK_TABLE_XML, 317 OSAKA_TRAK_TABLE_XML, 318 OSAKA_VERT_ONLY_TRAK_TABLE_XML, 319 ): 320 table = table__t_r_a_k() 321 font["trak"] = table 322 for name, attrs, content in parseXML(input_xml): 323 table.fromXML(name, attrs, content, font) 324 output_xml = getXML(table.toXML, font) 325 self.assertEqual(input_xml, output_xml) 326 327 328def add_name(font, string, nameID): 329 nameTable = font.get("name") 330 if nameTable is None: 331 nameTable = font["name"] = table__n_a_m_e() 332 nameTable.names = [] 333 namerec = NameRecord() 334 namerec.nameID = nameID 335 namerec.string = string.encode("mac_roman") 336 namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0) 337 nameTable.names.append(namerec) 338 339 340if __name__ == "__main__": 341 import sys 342 343 sys.exit(unittest.main()) 344