• 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# 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