• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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