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