1from __future__ import print_function, division, absolute_import, unicode_literals
2from fontTools.misc.py23 import *
3from fontTools.misc.testTools import parseXML, getXML
4from fontTools.misc.textTools import deHexStr
5from fontTools.ttLib import TTFont, TTLibError
6from fontTools.ttLib.tables._t_r_a_k import *
7from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord
8import unittest
9
10
11# /Library/Fonts/Osaka.ttf from OSX has trak table with both horiz and vertData
12OSAKA_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
21OSAKA_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
27OSAKA_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
33OSAKA_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)
75OSAKA_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
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
115SKIA_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
124SKIA_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
160class 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
327def 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
339if __name__ == "__main__":
340	import sys
341	sys.exit(unittest.main())
342