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