• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc import sstruct
4from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
5from fontTools.misc.textTools import safeEval
6from fontTools.ttLib import TTLibError
7from . import DefaultTable
8import struct
9try:
10	from collections.abc import MutableMapping
11except ImportError:
12	from UserDict import DictMixin as MutableMapping
13
14
15# Apple's documentation of 'trak':
16# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html
17
18TRAK_HEADER_FORMAT = """
19	> # big endian
20	version:     16.16F
21	format:      H
22	horizOffset: H
23	vertOffset:  H
24	reserved:    H
25"""
26
27TRAK_HEADER_FORMAT_SIZE = sstruct.calcsize(TRAK_HEADER_FORMAT)
28
29
30TRACK_DATA_FORMAT = """
31	> # big endian
32	nTracks:         H
33	nSizes:          H
34	sizeTableOffset: L
35"""
36
37TRACK_DATA_FORMAT_SIZE = sstruct.calcsize(TRACK_DATA_FORMAT)
38
39
40TRACK_TABLE_ENTRY_FORMAT = """
41	> # big endian
42	track:      16.16F
43	nameIndex:       H
44	offset:          H
45"""
46
47TRACK_TABLE_ENTRY_FORMAT_SIZE = sstruct.calcsize(TRACK_TABLE_ENTRY_FORMAT)
48
49
50# size values are actually '16.16F' fixed-point values, but here I do the
51# fixedToFloat conversion manually instead of relying on sstruct
52SIZE_VALUE_FORMAT = ">l"
53SIZE_VALUE_FORMAT_SIZE = struct.calcsize(SIZE_VALUE_FORMAT)
54
55# per-Size values are in 'FUnits', i.e. 16-bit signed integers
56PER_SIZE_VALUE_FORMAT = ">h"
57PER_SIZE_VALUE_FORMAT_SIZE = struct.calcsize(PER_SIZE_VALUE_FORMAT)
58
59
60class table__t_r_a_k(DefaultTable.DefaultTable):
61	dependencies = ['name']
62
63	def compile(self, ttFont):
64		dataList = []
65		offset = TRAK_HEADER_FORMAT_SIZE
66		for direction in ('horiz', 'vert'):
67			trackData = getattr(self, direction + 'Data', TrackData())
68			offsetName = direction + 'Offset'
69			# set offset to 0 if None or empty
70			if not trackData:
71				setattr(self, offsetName, 0)
72				continue
73			# TrackData table format must be longword aligned
74			alignedOffset = (offset + 3) & ~3
75			padding, offset = b"\x00"*(alignedOffset - offset), alignedOffset
76			setattr(self, offsetName, offset)
77
78			data = trackData.compile(offset)
79			offset += len(data)
80			dataList.append(padding + data)
81
82		self.reserved = 0
83		tableData = bytesjoin([sstruct.pack(TRAK_HEADER_FORMAT, self)] + dataList)
84		return tableData
85
86	def decompile(self, data, ttFont):
87		sstruct.unpack(TRAK_HEADER_FORMAT, data[:TRAK_HEADER_FORMAT_SIZE], self)
88		for direction in ('horiz', 'vert'):
89			trackData = TrackData()
90			offset = getattr(self, direction + 'Offset')
91			if offset != 0:
92				trackData.decompile(data, offset)
93			setattr(self, direction + 'Data', trackData)
94
95	def toXML(self, writer, ttFont):
96		writer.simpletag('version', value=self.version)
97		writer.newline()
98		writer.simpletag('format', value=self.format)
99		writer.newline()
100		for direction in ('horiz', 'vert'):
101			dataName = direction + 'Data'
102			writer.begintag(dataName)
103			writer.newline()
104			trackData = getattr(self, dataName, TrackData())
105			trackData.toXML(writer, ttFont)
106			writer.endtag(dataName)
107			writer.newline()
108
109	def fromXML(self, name, attrs, content, ttFont):
110		if name == 'version':
111			self.version = safeEval(attrs['value'])
112		elif name == 'format':
113			self.format = safeEval(attrs['value'])
114		elif name in ('horizData', 'vertData'):
115			trackData = TrackData()
116			setattr(self, name, trackData)
117			for element in content:
118				if not isinstance(element, tuple):
119					continue
120				name, attrs, content_ = element
121				trackData.fromXML(name, attrs, content_, ttFont)
122
123
124class TrackData(MutableMapping):
125
126	def __init__(self, initialdata={}):
127		self._map = dict(initialdata)
128
129	def compile(self, offset):
130		nTracks = len(self)
131		sizes = self.sizes()
132		nSizes = len(sizes)
133
134		# offset to the start of the size subtable
135		offset += TRACK_DATA_FORMAT_SIZE + TRACK_TABLE_ENTRY_FORMAT_SIZE*nTracks
136		trackDataHeader = sstruct.pack(
137			TRACK_DATA_FORMAT,
138			{'nTracks': nTracks, 'nSizes': nSizes, 'sizeTableOffset': offset})
139
140		entryDataList = []
141		perSizeDataList = []
142		# offset to per-size tracking values
143		offset += SIZE_VALUE_FORMAT_SIZE*nSizes
144		# sort track table entries by track value
145		for track, entry in sorted(self.items()):
146			assert entry.nameIndex is not None
147			entry.track = track
148			entry.offset = offset
149			entryDataList += [sstruct.pack(TRACK_TABLE_ENTRY_FORMAT, entry)]
150			# sort per-size values by size
151			for size, value in sorted(entry.items()):
152				perSizeDataList += [struct.pack(PER_SIZE_VALUE_FORMAT, value)]
153			offset += PER_SIZE_VALUE_FORMAT_SIZE*nSizes
154		# sort size values
155		sizeDataList = [struct.pack(SIZE_VALUE_FORMAT, fl2fi(sv, 16)) for sv in sorted(sizes)]
156
157		data = bytesjoin([trackDataHeader] + entryDataList + sizeDataList + perSizeDataList)
158		return data
159
160	def decompile(self, data, offset):
161		# initial offset is from the start of trak table to the current TrackData
162		trackDataHeader = data[offset:offset+TRACK_DATA_FORMAT_SIZE]
163		if len(trackDataHeader) != TRACK_DATA_FORMAT_SIZE:
164			raise TTLibError('not enough data to decompile TrackData header')
165		sstruct.unpack(TRACK_DATA_FORMAT, trackDataHeader, self)
166		offset += TRACK_DATA_FORMAT_SIZE
167
168		nSizes = self.nSizes
169		sizeTableOffset = self.sizeTableOffset
170		sizeTable = []
171		for i in range(nSizes):
172			sizeValueData = data[sizeTableOffset:sizeTableOffset+SIZE_VALUE_FORMAT_SIZE]
173			if len(sizeValueData) < SIZE_VALUE_FORMAT_SIZE:
174				raise TTLibError('not enough data to decompile TrackData size subtable')
175			sizeValue, = struct.unpack(SIZE_VALUE_FORMAT, sizeValueData)
176			sizeTable.append(fi2fl(sizeValue, 16))
177			sizeTableOffset += SIZE_VALUE_FORMAT_SIZE
178
179		for i in range(self.nTracks):
180			entry = TrackTableEntry()
181			entryData = data[offset:offset+TRACK_TABLE_ENTRY_FORMAT_SIZE]
182			if len(entryData) < TRACK_TABLE_ENTRY_FORMAT_SIZE:
183				raise TTLibError('not enough data to decompile TrackTableEntry record')
184			sstruct.unpack(TRACK_TABLE_ENTRY_FORMAT, entryData, entry)
185			perSizeOffset = entry.offset
186			for j in range(nSizes):
187				size = sizeTable[j]
188				perSizeValueData = data[perSizeOffset:perSizeOffset+PER_SIZE_VALUE_FORMAT_SIZE]
189				if len(perSizeValueData) < PER_SIZE_VALUE_FORMAT_SIZE:
190					raise TTLibError('not enough data to decompile per-size track values')
191				perSizeValue, = struct.unpack(PER_SIZE_VALUE_FORMAT, perSizeValueData)
192				entry[size] = perSizeValue
193				perSizeOffset += PER_SIZE_VALUE_FORMAT_SIZE
194			self[entry.track] = entry
195			offset += TRACK_TABLE_ENTRY_FORMAT_SIZE
196
197	def toXML(self, writer, ttFont):
198		nTracks = len(self)
199		nSizes = len(self.sizes())
200		writer.comment("nTracks=%d, nSizes=%d" % (nTracks, nSizes))
201		writer.newline()
202		for track, entry in sorted(self.items()):
203			assert entry.nameIndex is not None
204			entry.track = track
205			entry.toXML(writer, ttFont)
206
207	def fromXML(self, name, attrs, content, ttFont):
208		if name != 'trackEntry':
209			return
210		entry = TrackTableEntry()
211		entry.fromXML(name, attrs, content, ttFont)
212		self[entry.track] = entry
213
214	def sizes(self):
215		if not self:
216			return frozenset()
217		tracks = list(self.tracks())
218		sizes = self[tracks.pop(0)].sizes()
219		for track in tracks:
220			entrySizes = self[track].sizes()
221			if sizes != entrySizes:
222				raise TTLibError(
223					"'trak' table entries must specify the same sizes: "
224					"%s != %s" % (sorted(sizes), sorted(entrySizes)))
225		return frozenset(sizes)
226
227	def __getitem__(self, track):
228		return self._map[track]
229
230	def __delitem__(self, track):
231		del self._map[track]
232
233	def __setitem__(self, track, entry):
234		self._map[track] = entry
235
236	def __len__(self):
237		return len(self._map)
238
239	def __iter__(self):
240		return iter(self._map)
241
242	def keys(self):
243		return self._map.keys()
244
245	tracks = keys
246
247	def __repr__(self):
248		return "TrackData({})".format(self._map if self else "")
249
250
251class TrackTableEntry(MutableMapping):
252
253	def __init__(self, values={}, nameIndex=None):
254		self.nameIndex = nameIndex
255		self._map = dict(values)
256
257	def toXML(self, writer, ttFont):
258		name = ttFont["name"].getDebugName(self.nameIndex)
259		writer.begintag(
260			"trackEntry",
261			(('value', self.track), ('nameIndex', self.nameIndex)))
262		writer.newline()
263		if name:
264			writer.comment(name)
265			writer.newline()
266		for size, perSizeValue in sorted(self.items()):
267			writer.simpletag("track", size=size, value=perSizeValue)
268			writer.newline()
269		writer.endtag("trackEntry")
270		writer.newline()
271
272	def fromXML(self, name, attrs, content, ttFont):
273		self.track = safeEval(attrs['value'])
274		self.nameIndex = safeEval(attrs['nameIndex'])
275		for element in content:
276			if not isinstance(element, tuple):
277				continue
278			name, attrs, _ = element
279			if name != 'track':
280				continue
281			size = safeEval(attrs['size'])
282			self[size] = safeEval(attrs['value'])
283
284	def __getitem__(self, size):
285		return self._map[size]
286
287	def __delitem__(self, size):
288		del self._map[size]
289
290	def __setitem__(self, size, value):
291		self._map[size] = value
292
293	def __len__(self):
294		return len(self._map)
295
296	def __iter__(self):
297		return iter(self._map)
298
299	def keys(self):
300		return self._map.keys()
301
302	sizes = keys
303
304	def __repr__(self):
305		return "TrackTableEntry({}, nameIndex={})".format(self._map, self.nameIndex)
306
307	def __eq__(self, other):
308		if not isinstance(other, self.__class__):
309			return NotImplemented
310		return self.nameIndex == other.nameIndex and dict(self) == dict(other)
311
312	def __ne__(self, other):
313		result = self.__eq__(other)
314		return result if result is NotImplemented else not result
315