otBase.py revision b774f9f684c5a0f91f5fa177c9a461968789123f
125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heofrom .DefaultTable import DefaultTable
225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heofrom . import otData
325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heoimport struct
425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heoclass OverflowErrorRecord:
625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __init__(self, overflowTuple):
725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.tableType = overflowTuple[0]
825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.LookupListIndex = overflowTuple[1]
925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.SubTableIndex = overflowTuple[2]
1025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.itemName = overflowTuple[3]
1125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.itemIndex = overflowTuple[4]
1225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
1325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __repr__(self):
1425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return str((self.tableType, "LookupIndex:", self.LookupListIndex, "SubTableIndex:", self.SubTableIndex, "ItemName:", self.itemName, "ItemIndex:", self.itemIndex))
1525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
1625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heoclass OTLOffsetOverflowError(Exception):
1725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __init__(self, overflowErrorRecord):
1825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.value = overflowErrorRecord
1925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
2025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __str__(self):
2125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return repr(self.value)
2225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
2325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
2425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heoclass BaseTTXConverter(DefaultTable):
2525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
2625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	"""Generic base class for TTX table converters. It functions as an
2725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	adapter between the TTX (ttLib actually) table model and the model
2825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	we use for OpenType tables, which is necessarily subtly different.
2925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	"""
3025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
3125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def decompile(self, data, font):
3225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		from . import otTables
3325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		cachingStats = None if True else {}
3425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		class GlobalState:
3525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			def __init__(self, tableType, cachingStats):
3625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				self.tableType = tableType
3725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				self.cachingStats = cachingStats
3825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		globalState = GlobalState(tableType=self.tableTag,
3925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo					  cachingStats=cachingStats)
4025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		reader = OTTableReader(data, globalState)
4125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		tableClass = getattr(otTables, self.tableTag)
4225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.table = tableClass()
4325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.table.decompile(reader, font)
4425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		if cachingStats:
4525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			stats = sorted([(v, k) for k, v in cachingStats.items()])
4625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			stats.reverse()
4725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			print("cachingsstats for ", self.tableTag)
4825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			for v, k in stats:
4925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				if v < 2:
5025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo					break
516aae6528a6672497b1d1dffb5c083093d5c46dc8Yuncheol Heo				print(v, k)
5225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			print("---", len(stats))
5325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
5425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def compile(self, font):
5525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		""" Create a top-level OTFWriter for the GPOS/GSUB table.
5625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			Call the compile method for the the table
5725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				for each 'converter' record in the table converter list
5825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo					call converter's write method for each item in the value.
5925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						- For simple items, the write method adds a string to the
6025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						writer's self.items list.
6125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						- For Struct/Table/Subtable items, it add first adds new writer to the
6225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						to the writer's self.items, then calls the item's compile method.
6325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						This creates a tree of writers, rooted at the GUSB/GPOS writer, with
6425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						each writer representing a table, and the writer.items list containing
6525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo						the child data strings and writers.
6625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			call the getAllData method
6725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				call _doneWriting, which removes duplicates
6825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				call _gatherTables. This traverses the tables, adding unique occurences to a flat list of tables
6925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				Traverse the flat list of tables, calling getDataLength on each to update their position
7025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				Traverse the flat list of tables again, calling getData each get the data in the table, now that
7125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				pos's and offset are known.
7225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
7325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				If a lookup subtable overflows an offset, we have to start all over.
7425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		"""
7525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		class GlobalState:
7625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			def __init__(self, tableType):
7725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo				self.tableType = tableType
7825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		globalState = GlobalState(tableType=self.tableTag)
7925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		writer = OTTableWriter(globalState)
8025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		writer.parent = None
8125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.table.compile(writer, font)
8225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return writer.getAllData()
8325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
8425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def toXML(self, writer, font):
8525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.table.toXML2(writer, font)
8625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
8725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def fromXML(self, name, attrs, content, font):
8825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		from . import otTables
8925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		if not hasattr(self, "table"):
9025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			tableClass = getattr(otTables, self.tableTag)
9125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			self.table = tableClass()
9225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.table.fromXML(name, attrs, content, font)
9325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
9425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
9525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heoclass OTTableReader(object):
9625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
9725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	"""Helper class to retrieve data from an OpenType table."""
9825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
9925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	__slots__ = ('data', 'offset', 'pos', 'globalState', 'localState')
10025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
10125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __init__(self, data, globalState={}, localState=None, offset=0):
10225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.data = data
10325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.offset = offset
10425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = offset
10525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.globalState = globalState
10625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.localState = localState
10725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
10825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def getSubReader(self, offset):
10925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		offset = self.offset + offset
11025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		cachingStats = self.globalState.cachingStats
11125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		if cachingStats is not None:
11225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo			cachingStats[offset] = cachingStats.get(offset, 0) + 1
11325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return self.__class__(self.data, self.globalState, self.localState, offset)
11425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
11525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def readUShort(self):
11625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		pos = self.pos
11725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		newpos = pos + 2
11825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value, = struct.unpack(">H", self.data[pos:newpos])
11925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = newpos
12025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return value
12125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
12225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def readShort(self):
12325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		pos = self.pos
12425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		newpos = pos + 2
12525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value, = struct.unpack(">h", self.data[pos:newpos])
12625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = newpos
12725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return value
12825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
12925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def readLong(self):
13025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		pos = self.pos
13125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		newpos = pos + 4
13225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value, = struct.unpack(">l", self.data[pos:newpos])
13325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = newpos
13425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return value
13525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
13625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def readUInt24(self):
13725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		pos = self.pos
13825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		newpos = pos + 3
13925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value = (ord(self.data[pos]) << 16) | (ord(self.data[pos+1]) << 8) | ord(self.data[pos+2])
14025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value, = struct.unpack(">H", self.data[pos:newpos])
14125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = newpos
14225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return value
14325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
14425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def readULong(self):
14525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		pos = self.pos
14625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		newpos = pos + 4
14725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value, = struct.unpack(">L", self.data[pos:newpos])
14825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = newpos
14925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return value
15025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
15125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def readTag(self):
15225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		pos = self.pos
15325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		newpos = pos + 4
15425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		value = self.data[pos:newpos]
15525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		assert len(value) == 4
15625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.pos = newpos
15725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return value
15825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
15925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __setitem__(self, name, value):
16025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		state = self.localState.copy() if self.localState else dict()
16125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		state[name] = value
16225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		self.localState = state
16325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
16425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo	def __getitem__(self, name):
16525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo		return self.localState[name]
166
167
168class OTTableWriter(object):
169
170	"""Helper class to gather and assemble data for OpenType tables."""
171
172	def __init__(self, globalState, localState=None):
173		self.items = []
174		self.pos = None
175		self.globalState = globalState
176		self.localState = localState
177
178	def __setitem__(self, name, value):
179		state = self.localState.copy() if self.localState else dict()
180		state[name] = value
181		self.localState = state
182
183	def __getitem__(self, name):
184		return self.localState[name]
185
186	# assembler interface
187
188	def getAllData(self):
189		"""Assemble all data, including all subtables."""
190		self._doneWriting()
191		tables, extTables = self._gatherTables()
192		tables.reverse()
193		extTables.reverse()
194		# Gather all data in two passes: the absolute positions of all
195		# subtable are needed before the actual data can be assembled.
196		pos = 0
197		for table in tables:
198			table.pos = pos
199			pos = pos + table.getDataLength()
200
201		for table in extTables:
202			table.pos = pos
203			pos = pos + table.getDataLength()
204
205
206		data = []
207		for table in tables:
208			tableData = table.getData()
209			data.append(tableData)
210
211		for table in extTables:
212			tableData = table.getData()
213			data.append(tableData)
214
215		return "".join(data)
216
217	def getDataLength(self):
218		"""Return the length of this table in bytes, without subtables."""
219		l = 0
220		for item in self.items:
221			if hasattr(item, "getData") or hasattr(item, "getCountData"):
222				if item.longOffset:
223					l = l + 4  # sizeof(ULong)
224				else:
225					l = l + 2  # sizeof(UShort)
226			else:
227				l = l + len(item)
228		return l
229
230	def getData(self):
231		"""Assemble the data for this writer/table, without subtables."""
232		items = list(self.items)  # make a shallow copy
233		pos = self.pos
234		numItems = len(items)
235		for i in range(numItems):
236			item = items[i]
237
238			if hasattr(item, "getData"):
239				if item.longOffset:
240					items[i] = packULong(item.pos - pos)
241				else:
242					try:
243						items[i] = packUShort(item.pos - pos)
244					except AssertionError:
245						# provide data to fix overflow problem.
246						# If the overflow is to a lookup, or from a lookup to a subtable,
247						# just report the current item.
248						if self.name in [ 'LookupList', 'Lookup']:
249							overflowErrorRecord = self.getOverflowErrorRecord(item)
250						else:
251							# overflow is within a subTable. Life is more complicated.
252							# If we split the sub-table just before the current item, we may still suffer overflow.
253							# This is because duplicate table merging is done only within an Extension subTable tree;
254							# when we split the subtable in two, some items may no longer be duplicates.
255							# Get worst case by adding up all the item lengths, depth first traversal.
256							# and then report the first item that overflows a short.
257							def getDeepItemLength(table):
258								if hasattr(table, "getDataLength"):
259									length = 0
260									for item in table.items:
261										length = length + getDeepItemLength(item)
262								else:
263									length = len(table)
264								return length
265
266							length = self.getDataLength()
267							if hasattr(self, "sortCoverageLast") and item.name == "Coverage":
268								# Coverage is first in the item list, but last in the table list,
269								# The original overflow is really in the item list. Skip the Coverage
270								# table in the following test.
271								items = items[i+1:]
272
273							for j in range(len(items)):
274								item = items[j]
275								length = length + getDeepItemLength(item)
276								if length > 65535:
277									break
278						overflowErrorRecord = self.getOverflowErrorRecord(item)
279
280
281						raise OTLOffsetOverflowError(overflowErrorRecord)
282
283		return "".join(items)
284
285	def __hash__(self):
286		# only works after self._doneWriting() has been called
287		return hash(self.items)
288
289	def __cmp__(self, other):
290		if not isinstance(self, type(other)): return cmp(type(self), type(other))
291		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
292
293		return cmp(self.items, other.items)
294
295	def _doneWriting(self, internedTables=None):
296		# Convert CountData references to data string items
297		# collapse duplicate table references to a unique entry
298		# "tables" are OTTableWriter objects.
299
300		# For Extension Lookup types, we can
301		# eliminate duplicates only within the tree under the Extension Lookup,
302		# as offsets may exceed 64K even between Extension LookupTable subtables.
303		if internedTables is None:
304			internedTables = {}
305		items = self.items
306		iRange = list(range(len(items)))
307
308		if hasattr(self, "Extension"):
309			newTree = 1
310		else:
311			newTree = 0
312		for i in iRange:
313			item = items[i]
314			if hasattr(item, "getCountData"):
315				items[i] = item.getCountData()
316			elif hasattr(item, "getData"):
317				if newTree:
318					item._doneWriting()
319				else:
320					item._doneWriting(internedTables)
321					if item in internedTables:
322						items[i] = item = internedTables[item]
323					else:
324						internedTables[item] = item
325		self.items = tuple(items)
326
327	def _gatherTables(self, tables=None, extTables=None, done=None):
328		# Convert table references in self.items tree to a flat
329		# list of tables in depth-first traversal order.
330		# "tables" are OTTableWriter objects.
331		# We do the traversal in reverse order at each level, in order to
332		# resolve duplicate references to be the last reference in the list of tables.
333		# For extension lookups, duplicate references can be merged only within the
334		# writer tree under the  extension lookup.
335		if tables is None: # init call for first time.
336			tables = []
337			extTables = []
338			done = {}
339
340		done[self] = 1
341
342		numItems = len(self.items)
343		iRange = list(range(numItems))
344		iRange.reverse()
345
346		if hasattr(self, "Extension"):
347			appendExtensions = 1
348		else:
349			appendExtensions = 0
350
351		# add Coverage table if it is sorted last.
352		sortCoverageLast = 0
353		if hasattr(self, "sortCoverageLast"):
354			# Find coverage table
355			for i in range(numItems):
356				item = self.items[i]
357				if hasattr(item, "name") and (item.name == "Coverage"):
358					sortCoverageLast = 1
359					break
360			if item not in done:
361				item._gatherTables(tables, extTables, done)
362			else:
363				index = max(item.parent.keys())
364				item.parent[index + 1] = self
365
366		saveItem = None
367		for i in iRange:
368			item = self.items[i]
369			if not hasattr(item, "getData"):
370				continue
371
372			if sortCoverageLast and (i==1) and item.name == 'Coverage':
373				# we've already 'gathered' it above
374				continue
375
376			if appendExtensions:
377				assert extTables != None, "Program or XML editing error. Extension subtables cannot contain extensions subtables"
378				newDone = {}
379				item._gatherTables(extTables, None, newDone)
380
381			elif item not in done:
382				item._gatherTables(tables, extTables, done)
383			else:
384				index = max(item.parent.keys())
385				item.parent[index + 1] = self
386
387
388		tables.append(self)
389		return tables, extTables
390
391	# interface for gathering data, as used by table.compile()
392
393	def getSubWriter(self):
394		subwriter = self.__class__(self.globalState, self.localState)
395		subwriter.parent = {0:self} # because some subtables have idential values, we discard
396									# the duplicates under the getAllData method. Hence some
397									# subtable writers can have more than one parent writer.
398		return subwriter
399
400	def writeUShort(self, value):
401		assert 0 <= value < 0x10000
402		self.items.append(struct.pack(">H", value))
403
404	def writeShort(self, value):
405		self.items.append(struct.pack(">h", value))
406
407	def writeUInt24(self, value):
408		assert 0 <= value < 0x1000000
409		self.items.append(''.join(chr(v) for v in (value>>16, (value>>8)&0xFF, value&0xff)))
410
411	def writeLong(self, value):
412		self.items.append(struct.pack(">l", value))
413
414	def writeULong(self, value):
415		self.items.append(struct.pack(">L", value))
416
417	def writeTag(self, tag):
418		assert len(tag) == 4
419		self.items.append(tag)
420
421	def writeSubTable(self, subWriter):
422		self.items.append(subWriter)
423
424	def writeCountReference(self, table, name):
425		ref = CountReference(table, name)
426		self.items.append(ref)
427		return ref
428
429	def writeStruct(self, format, values):
430		data = struct.pack(*(format,) + values)
431		self.items.append(data)
432
433	def writeData(self, data):
434		self.items.append(data)
435
436	def	getOverflowErrorRecord(self, item):
437		LookupListIndex = SubTableIndex = itemName = itemIndex = None
438		if self.name == 'LookupList':
439			LookupListIndex = item.repeatIndex
440		elif self.name == 'Lookup':
441			LookupListIndex = self.repeatIndex
442			SubTableIndex = item.repeatIndex
443		else:
444			itemName = item.name
445			if hasattr(item, 'repeatIndex'):
446				itemIndex = item.repeatIndex
447			if self.name == 'SubTable':
448				LookupListIndex = self.parent[0].repeatIndex
449				SubTableIndex = self.repeatIndex
450			elif self.name == 'ExtSubTable':
451				LookupListIndex = self.parent[0].parent[0].repeatIndex
452				SubTableIndex = self.parent[0].repeatIndex
453			else: # who knows how far below the SubTable level we are! Climb back up to the nearest subtable.
454				itemName = ".".join(self.name, item.name)
455				p1 = self.parent[0]
456				while p1 and p1.name not in ['ExtSubTable', 'SubTable']:
457					itemName = ".".join(p1.name, item.name)
458					p1 = p1.parent[0]
459				if p1:
460					if p1.name == 'ExtSubTable':
461						LookupListIndex = self.parent[0].parent[0].repeatIndex
462						SubTableIndex = self.parent[0].repeatIndex
463					else:
464						LookupListIndex = self.parent[0].repeatIndex
465						SubTableIndex = self.repeatIndex
466
467		return OverflowErrorRecord( (self.globalState.tableType, LookupListIndex, SubTableIndex, itemName, itemIndex) )
468
469
470class CountReference:
471	"""A reference to a Count value, not a count of references."""
472	def __init__(self, table, name):
473		self.table = table
474		self.name = name
475	def setValue(self, value):
476		table = self.table
477		name = self.name
478		if table[name] is None:
479			table[name] = value
480		else:
481			assert table[name] == value, (name, table[name], value)
482	def getCountData(self):
483		return packUShort(self.table[self.name])
484
485
486def packUShort(value):
487	assert 0 <= value < 0x10000, value
488	return struct.pack(">H", value)
489
490
491def packULong(value):
492	assert 0 <= value < 0x100000000, value
493	return struct.pack(">L", value)
494
495
496class BaseTable(object):
497	def __init__(self):
498		self.compileStatus = 0 # 0 means table was created
499									# 1 means the table.read() function was called by a table which is subject
500									# to delayed compilation
501									# 2 means that it was subject to delayed compilation, and
502									# has been decompiled
503
504		self.recurse = 0
505
506	def __getattr__(self, attr):
507		# we get here only when the table does not have the attribute.
508		# This method ovveride exists so that we can try to de-compile
509		# a table which is subject to delayed decompilation, and then try
510		# to get the value again after decompilation.
511		self.recurse +=1
512		if self.recurse > 2:
513			# shouldn't ever get here - we should only get to two levels of recursion.
514			# this guards against self.decompile NOT setting compileStatus to other than 1.
515			raise AttributeError(attr)
516		if self.compileStatus == 1:
517			self.ensureDecompiled()
518			val = getattr(self, attr)
519			self.recurse -=1
520			return val
521
522		raise AttributeError(attr)
523
524
525	"""Generic base class for all OpenType (sub)tables."""
526
527	def getConverters(self):
528		return self.converters
529
530	def getConverterByName(self, name):
531		return self.convertersByName[name]
532
533	def decompile(self, reader, font):
534		self.compileStatus = 2 # table has been decompiled.
535		self.readFormat(reader)
536		table = {}
537		self.__rawTable = table  # for debugging
538		converters = self.getConverters()
539		for conv in converters:
540			if conv.name == "SubTable":
541				conv = conv.getConverter(reader.globalState.tableType,
542						table["LookupType"])
543			if conv.name == "ExtSubTable":
544				conv = conv.getConverter(reader.globalState.tableType,
545						table["ExtensionLookupType"])
546			if conv.name == "FeatureParams":
547				conv = conv.getConverter(reader["FeatureTag"])
548			if conv.repeat:
549				l = []
550				if conv.repeat in table:
551					countValue = table[conv.repeat]
552				else:
553					# conv.repeat is a propagated count
554					countValue = reader[conv.repeat]
555				for i in range(countValue + conv.aux):
556					l.append(conv.read(reader, font, table))
557				table[conv.name] = l
558			else:
559				if conv.aux and not eval(conv.aux, None, table):
560					continue
561				table[conv.name] = conv.read(reader, font, table)
562				if conv.isPropagated:
563					reader[conv.name] = table[conv.name]
564
565		self.postRead(table, font)
566
567		del self.__rawTable  # succeeded, get rid of debugging info
568
569	def ensureDecompiled(self):
570		if self.compileStatus != 1:
571			return
572		self.decompile(self.reader, self.font)
573		del self.reader, self.font
574
575	def compile(self, writer, font):
576		self.ensureDecompiled()
577		table = self.preWrite(font)
578
579		if hasattr(self, 'sortCoverageLast'):
580			writer.sortCoverageLast = 1
581
582		self.writeFormat(writer)
583		for conv in self.getConverters():
584			value = table.get(conv.name)
585			if conv.repeat:
586				if value is None:
587					value = []
588				countValue = len(value) - conv.aux
589				if conv.repeat in table:
590					ref = table[conv.repeat]
591					table[conv.repeat] = None
592					ref.setValue(countValue)
593				else:
594					# conv.repeat is a propagated count
595					writer[conv.repeat].setValue(countValue)
596				for i in range(len(value)):
597					conv.write(writer, font, table, value[i], i)
598			elif conv.isCount:
599				# Special-case Count values.
600				# Assumption: a Count field will *always* precede
601				# the actual array(s).
602				# We need a default value, as it may be set later by a nested
603				# table. We will later store it here.
604				# We add a reference: by the time the data is assembled
605				# the Count value will be filled in.
606				ref = writer.writeCountReference(table, conv.name)
607				if conv.isPropagated:
608					table[conv.name] = None
609					writer[conv.name] = ref
610				else:
611					table[conv.name] = ref
612			else:
613				if conv.aux and not eval(conv.aux, None, table):
614					continue
615				conv.write(writer, font, table, value)
616				if conv.isPropagated:
617					writer[conv.name] = value
618
619	def readFormat(self, reader):
620		pass
621
622	def writeFormat(self, writer):
623		pass
624
625	def postRead(self, table, font):
626		self.__dict__.update(table)
627
628	def preWrite(self, font):
629		return self.__dict__.copy()
630
631	def toXML(self, xmlWriter, font, attrs=None):
632		tableName = self.__class__.__name__
633		if attrs is None:
634			attrs = []
635		if hasattr(self, "Format"):
636			attrs = attrs + [("Format", self.Format)]
637		xmlWriter.begintag(tableName, attrs)
638		xmlWriter.newline()
639		self.toXML2(xmlWriter, font)
640		xmlWriter.endtag(tableName)
641		xmlWriter.newline()
642
643	def toXML2(self, xmlWriter, font):
644		# Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB).
645		# This is because in TTX our parent writes our main tag, and in otBase.py we
646		# do it ourselves. I think I'm getting schizophrenic...
647		for conv in self.getConverters():
648			if conv.repeat:
649				value = getattr(self, conv.name)
650				for i in range(len(value)):
651					item = value[i]
652					conv.xmlWrite(xmlWriter, font, item, conv.name,
653							[("index", i)])
654			else:
655				if conv.aux and not eval(conv.aux, None, vars(self)):
656					continue
657				value = getattr(self, conv.name)
658				conv.xmlWrite(xmlWriter, font, value, conv.name, [])
659
660	def fromXML(self, name, attrs, content, font):
661		try:
662			conv = self.getConverterByName(name)
663		except KeyError:
664			raise    # XXX on KeyError, raise nice error
665		value = conv.xmlRead(attrs, content, font)
666		if conv.repeat:
667			seq = getattr(self, conv.name, None)
668			if seq is None:
669				seq = []
670				setattr(self, conv.name, seq)
671			seq.append(value)
672		else:
673			setattr(self, conv.name, value)
674
675	def __cmp__(self, other):
676		if not isinstance(self, type(other)): return cmp(type(self), type(other))
677		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
678
679		self.ensureDecompiled()
680
681		return cmp(self.__dict__, other.__dict__)
682
683
684class FormatSwitchingBaseTable(BaseTable):
685
686	"""Minor specialization of BaseTable, for tables that have multiple
687	formats, eg. CoverageFormat1 vs. CoverageFormat2."""
688
689	def getConverters(self):
690		return self.converters[self.Format]
691
692	def getConverterByName(self, name):
693		return self.convertersByName[self.Format][name]
694
695	def readFormat(self, reader):
696		self.Format = reader.readUShort()
697		assert self.Format != 0, (self, reader.pos, len(reader.data))
698
699	def writeFormat(self, writer):
700		writer.writeUShort(self.Format)
701
702
703#
704# Support for ValueRecords
705#
706# This data type is so different from all other OpenType data types that
707# it requires quite a bit of code for itself. It even has special support
708# in OTTableReader and OTTableWriter...
709#
710
711valueRecordFormat = [
712#	Mask	 Name            isDevice  signed
713	(0x0001, "XPlacement",   0,        1),
714	(0x0002, "YPlacement",   0,        1),
715	(0x0004, "XAdvance",     0,        1),
716	(0x0008, "YAdvance",     0,        1),
717	(0x0010, "XPlaDevice",   1,        0),
718	(0x0020, "YPlaDevice",   1,        0),
719	(0x0040, "XAdvDevice",   1,        0),
720	(0x0080, "YAdvDevice",   1,        0),
721# 	reserved:
722	(0x0100, "Reserved1",    0,        0),
723	(0x0200, "Reserved2",    0,        0),
724	(0x0400, "Reserved3",    0,        0),
725	(0x0800, "Reserved4",    0,        0),
726	(0x1000, "Reserved5",    0,        0),
727	(0x2000, "Reserved6",    0,        0),
728	(0x4000, "Reserved7",    0,        0),
729	(0x8000, "Reserved8",    0,        0),
730]
731
732def _buildDict():
733	d = {}
734	for mask, name, isDevice, signed in valueRecordFormat:
735		d[name] = mask, isDevice, signed
736	return d
737
738valueRecordFormatDict = _buildDict()
739
740
741class ValueRecordFactory:
742
743	"""Given a format code, this object convert ValueRecords."""
744
745	def __init__(self, valueFormat):
746		format = []
747		for mask, name, isDevice, signed in valueRecordFormat:
748			if valueFormat & mask:
749				format.append((name, isDevice, signed))
750		self.format = format
751
752	def readValueRecord(self, reader, font):
753		format = self.format
754		if not format:
755			return None
756		valueRecord = ValueRecord()
757		for name, isDevice, signed in format:
758			if signed:
759				value = reader.readShort()
760			else:
761				value = reader.readUShort()
762			if isDevice:
763				if value:
764					from . import otTables
765					subReader = reader.getSubReader(value)
766					value = getattr(otTables, name)()
767					value.decompile(subReader, font)
768				else:
769					value = None
770			setattr(valueRecord, name, value)
771		return valueRecord
772
773	def writeValueRecord(self, writer, font, valueRecord):
774		for name, isDevice, signed in self.format:
775			value = getattr(valueRecord, name, 0)
776			if isDevice:
777				if value:
778					subWriter = writer.getSubWriter()
779					writer.writeSubTable(subWriter)
780					value.compile(subWriter, font)
781				else:
782					writer.writeUShort(0)
783			elif signed:
784				writer.writeShort(value)
785			else:
786				writer.writeUShort(value)
787
788
789class ValueRecord:
790
791	# see ValueRecordFactory
792
793	def getFormat(self):
794		format = 0
795		for name in self.__dict__.keys():
796			format = format | valueRecordFormatDict[name][0]
797		return format
798
799	def toXML(self, xmlWriter, font, valueName, attrs=None):
800		if attrs is None:
801			simpleItems = []
802		else:
803			simpleItems = list(attrs)
804		for mask, name, isDevice, format in valueRecordFormat[:4]:  # "simple" values
805			if hasattr(self, name):
806				simpleItems.append((name, getattr(self, name)))
807		deviceItems = []
808		for mask, name, isDevice, format in valueRecordFormat[4:8]:  # device records
809			if hasattr(self, name):
810				device = getattr(self, name)
811				if device is not None:
812					deviceItems.append((name, device))
813		if deviceItems:
814			xmlWriter.begintag(valueName, simpleItems)
815			xmlWriter.newline()
816			for name, deviceRecord in deviceItems:
817				if deviceRecord is not None:
818					deviceRecord.toXML(xmlWriter, font)
819			xmlWriter.endtag(valueName)
820			xmlWriter.newline()
821		else:
822			xmlWriter.simpletag(valueName, simpleItems)
823			xmlWriter.newline()
824
825	def fromXML(self, name, attrs, content, font):
826		from . import otTables
827		for k, v in attrs.items():
828			setattr(self, k, int(v))
829		for element in content:
830			if not isinstance(element, tuple):
831				continue
832			name, attrs, content = element
833			value = getattr(otTables, name)()
834			for elem2 in content:
835				if not isinstance(elem2, tuple):
836					continue
837				name2, attrs2, content2 = elem2
838				value.fromXML(name2, attrs2, content2, font)
839			setattr(self, name, value)
840
841	def __cmp__(self, other):
842		if not isinstance(self, type(other)): return cmp(type(self), type(other))
843		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
844
845		return cmp(self.__dict__, other.__dict__)
846