• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from .DefaultTable import DefaultTable
4import sys
5import array
6import struct
7import logging
8
9log = logging.getLogger(__name__)
10
11class OverflowErrorRecord(object):
12	def __init__(self, overflowTuple):
13		self.tableType = overflowTuple[0]
14		self.LookupListIndex = overflowTuple[1]
15		self.SubTableIndex = overflowTuple[2]
16		self.itemName = overflowTuple[3]
17		self.itemIndex = overflowTuple[4]
18
19	def __repr__(self):
20		return str((self.tableType, "LookupIndex:", self.LookupListIndex, "SubTableIndex:", self.SubTableIndex, "ItemName:", self.itemName, "ItemIndex:", self.itemIndex))
21
22class OTLOffsetOverflowError(Exception):
23	def __init__(self, overflowErrorRecord):
24		self.value = overflowErrorRecord
25
26	def __str__(self):
27		return repr(self.value)
28
29
30class BaseTTXConverter(DefaultTable):
31
32	"""Generic base class for TTX table converters. It functions as an
33	adapter between the TTX (ttLib actually) table model and the model
34	we use for OpenType tables, which is necessarily subtly different.
35	"""
36
37	def decompile(self, data, font):
38		from . import otTables
39		reader = OTTableReader(data, tableTag=self.tableTag)
40		tableClass = getattr(otTables, self.tableTag)
41		self.table = tableClass()
42		self.table.decompile(reader, font)
43
44	def compile(self, font):
45		""" Create a top-level OTTableWriter for the GPOS/GSUB table.
46			Call the compile method for the the table
47				for each 'converter' record in the table converter list
48					call converter's write method for each item in the value.
49						- For simple items, the write method adds a string to the
50						writer's self.items list.
51						- For Struct/Table/Subtable items, it add first adds new writer to the
52						to the writer's self.items, then calls the item's compile method.
53						This creates a tree of writers, rooted at the GUSB/GPOS writer, with
54						each writer representing a table, and the writer.items list containing
55						the child data strings and writers.
56			call the getAllData method
57				call _doneWriting, which removes duplicates
58				call _gatherTables. This traverses the tables, adding unique occurences to a flat list of tables
59				Traverse the flat list of tables, calling getDataLength on each to update their position
60				Traverse the flat list of tables again, calling getData each get the data in the table, now that
61				pos's and offset are known.
62
63				If a lookup subtable overflows an offset, we have to start all over.
64		"""
65		overflowRecord = None
66
67		while True:
68			try:
69				writer = OTTableWriter(tableTag=self.tableTag)
70				self.table.compile(writer, font)
71				return writer.getAllData()
72
73			except OTLOffsetOverflowError as e:
74
75				if overflowRecord == e.value:
76					raise # Oh well...
77
78				overflowRecord = e.value
79				log.info("Attempting to fix OTLOffsetOverflowError %s", e)
80				lastItem = overflowRecord
81
82				ok = 0
83				if overflowRecord.itemName is None:
84					from .otTables import fixLookupOverFlows
85					ok = fixLookupOverFlows(font, overflowRecord)
86				else:
87					from .otTables import fixSubTableOverFlows
88					ok = fixSubTableOverFlows(font, overflowRecord)
89				if not ok:
90					# Try upgrading lookup to Extension and hope
91					# that cross-lookup sharing not happening would
92					# fix overflow...
93					from .otTables import fixLookupOverFlows
94					ok = fixLookupOverFlows(font, overflowRecord)
95					if not ok:
96						raise
97
98	def toXML(self, writer, font):
99		self.table.toXML2(writer, font)
100
101	def fromXML(self, name, attrs, content, font):
102		from . import otTables
103		if not hasattr(self, "table"):
104			tableClass = getattr(otTables, self.tableTag)
105			self.table = tableClass()
106		self.table.fromXML(name, attrs, content, font)
107		self.table.populateDefaults()
108
109
110class OTTableReader(object):
111
112	"""Helper class to retrieve data from an OpenType table."""
113
114	__slots__ = ('data', 'offset', 'pos', 'localState', 'tableTag')
115
116	def __init__(self, data, localState=None, offset=0, tableTag=None):
117		self.data = data
118		self.offset = offset
119		self.pos = offset
120		self.localState = localState
121		self.tableTag = tableTag
122
123	def advance(self, count):
124		self.pos += count
125
126	def seek(self, pos):
127		self.pos = pos
128
129	def copy(self):
130		other = self.__class__(self.data, self.localState, self.offset, self.tableTag)
131		other.pos = self.pos
132		return other
133
134	def getSubReader(self, offset):
135		offset = self.offset + offset
136		return self.__class__(self.data, self.localState, offset, self.tableTag)
137
138	def readUShort(self):
139		pos = self.pos
140		newpos = pos + 2
141		value, = struct.unpack(">H", self.data[pos:newpos])
142		self.pos = newpos
143		return value
144
145	def readUShortArray(self, count):
146		pos = self.pos
147		newpos = pos + count * 2
148		value = array.array("H", self.data[pos:newpos])
149		if sys.byteorder != "big": value.byteswap()
150		self.pos = newpos
151		return value
152
153	def readInt8(self):
154		pos = self.pos
155		newpos = pos + 1
156		value, = struct.unpack(">b", self.data[pos:newpos])
157		self.pos = newpos
158		return value
159
160	def readShort(self):
161		pos = self.pos
162		newpos = pos + 2
163		value, = struct.unpack(">h", self.data[pos:newpos])
164		self.pos = newpos
165		return value
166
167	def readLong(self):
168		pos = self.pos
169		newpos = pos + 4
170		value, = struct.unpack(">l", self.data[pos:newpos])
171		self.pos = newpos
172		return value
173
174	def readUInt8(self):
175		pos = self.pos
176		newpos = pos + 1
177		value, = struct.unpack(">B", self.data[pos:newpos])
178		self.pos = newpos
179		return value
180
181	def readUInt24(self):
182		pos = self.pos
183		newpos = pos + 3
184		value, = struct.unpack(">l", b'\0'+self.data[pos:newpos])
185		self.pos = newpos
186		return value
187
188	def readULong(self):
189		pos = self.pos
190		newpos = pos + 4
191		value, = struct.unpack(">L", self.data[pos:newpos])
192		self.pos = newpos
193		return value
194
195	def readTag(self):
196		pos = self.pos
197		newpos = pos + 4
198		value = Tag(self.data[pos:newpos])
199		assert len(value) == 4, value
200		self.pos = newpos
201		return value
202
203	def readData(self, count):
204		pos = self.pos
205		newpos = pos + count
206		value = self.data[pos:newpos]
207		self.pos = newpos
208		return value
209
210	def __setitem__(self, name, value):
211		state = self.localState.copy() if self.localState else dict()
212		state[name] = value
213		self.localState = state
214
215	def __getitem__(self, name):
216		return self.localState and self.localState[name]
217
218	def __contains__(self, name):
219		return self.localState and name in self.localState
220
221
222class OTTableWriter(object):
223
224	"""Helper class to gather and assemble data for OpenType tables."""
225
226	def __init__(self, localState=None, tableTag=None):
227		self.items = []
228		self.pos = None
229		self.localState = localState
230		self.tableTag = tableTag
231		self.longOffset = False
232		self.parent = None
233
234	def __setitem__(self, name, value):
235		state = self.localState.copy() if self.localState else dict()
236		state[name] = value
237		self.localState = state
238
239	def __getitem__(self, name):
240		return self.localState[name]
241
242	def __delitem__(self, name):
243		del self.localState[name]
244
245	# assembler interface
246
247	def getDataLength(self):
248		"""Return the length of this table in bytes, without subtables."""
249		l = 0
250		for item in self.items:
251			if hasattr(item, "getCountData"):
252				l += item.size
253			elif hasattr(item, "getData"):
254				l += 4 if item.longOffset else 2
255			else:
256				l = l + len(item)
257		return l
258
259	def getData(self):
260		"""Assemble the data for this writer/table, without subtables."""
261		items = list(self.items)  # make a shallow copy
262		pos = self.pos
263		numItems = len(items)
264		for i in range(numItems):
265			item = items[i]
266
267			if hasattr(item, "getData"):
268				if item.longOffset:
269					items[i] = packULong(item.pos - pos)
270				else:
271					try:
272						items[i] = packUShort(item.pos - pos)
273					except struct.error:
274						# provide data to fix overflow problem.
275						overflowErrorRecord = self.getOverflowErrorRecord(item)
276
277						raise OTLOffsetOverflowError(overflowErrorRecord)
278
279		return bytesjoin(items)
280
281	def __hash__(self):
282		# only works after self._doneWriting() has been called
283		return hash(self.items)
284
285	def __ne__(self, other):
286		result = self.__eq__(other)
287		return result if result is NotImplemented else not result
288
289	def __eq__(self, other):
290		if type(self) != type(other):
291			return NotImplemented
292		return self.longOffset == other.longOffset and self.items == other.items
293
294	def _doneWriting(self, internedTables):
295		# Convert CountData references to data string items
296		# collapse duplicate table references to a unique entry
297		# "tables" are OTTableWriter objects.
298
299		# For Extension Lookup types, we can
300		# eliminate duplicates only within the tree under the Extension Lookup,
301		# as offsets may exceed 64K even between Extension LookupTable subtables.
302		isExtension = hasattr(self, "Extension")
303
304		# Certain versions of Uniscribe reject the font if the GSUB/GPOS top-level
305		# arrays (ScriptList, FeatureList, LookupList) point to the same, possibly
306		# empty, array.  So, we don't share those.
307		# See: https://github.com/fonttools/fonttools/issues/518
308		dontShare = hasattr(self, 'DontShare')
309
310		if isExtension:
311			internedTables = {}
312
313		items = self.items
314		for i in range(len(items)):
315			item = items[i]
316			if hasattr(item, "getCountData"):
317				items[i] = item.getCountData()
318			elif hasattr(item, "getData"):
319				item._doneWriting(internedTables)
320				if not dontShare:
321					items[i] = item = internedTables.setdefault(item, item)
322		self.items = tuple(items)
323
324	def _gatherTables(self, tables, extTables, done):
325		# Convert table references in self.items tree to a flat
326		# list of tables in depth-first traversal order.
327		# "tables" are OTTableWriter objects.
328		# We do the traversal in reverse order at each level, in order to
329		# resolve duplicate references to be the last reference in the list of tables.
330		# For extension lookups, duplicate references can be merged only within the
331		# writer tree under the  extension lookup.
332
333		done[id(self)] = True
334
335		numItems = len(self.items)
336		iRange = list(range(numItems))
337		iRange.reverse()
338
339		isExtension = hasattr(self, "Extension")
340
341		selfTables = tables
342
343		if isExtension:
344			assert extTables is not None, "Program or XML editing error. Extension subtables cannot contain extensions subtables"
345			tables, extTables, done = extTables, None, {}
346
347		# add Coverage table if it is sorted last.
348		sortCoverageLast = 0
349		if hasattr(self, "sortCoverageLast"):
350			# Find coverage table
351			for i in range(numItems):
352				item = self.items[i]
353				if hasattr(item, "name") and (item.name == "Coverage"):
354					sortCoverageLast = 1
355					break
356			if id(item) not in done:
357				item._gatherTables(tables, extTables, done)
358			else:
359				# We're a new parent of item
360				pass
361
362		for i in iRange:
363			item = self.items[i]
364			if not hasattr(item, "getData"):
365				continue
366
367			if sortCoverageLast and (i==1) and item.name == 'Coverage':
368				# we've already 'gathered' it above
369				continue
370
371			if id(item) not in done:
372				item._gatherTables(tables, extTables, done)
373			else:
374				# Item is already written out by other parent
375				pass
376
377		selfTables.append(self)
378
379	def getAllData(self):
380		"""Assemble all data, including all subtables."""
381		internedTables = {}
382		self._doneWriting(internedTables)
383		tables = []
384		extTables = []
385		done = {}
386		self._gatherTables(tables, extTables, done)
387		tables.reverse()
388		extTables.reverse()
389		# Gather all data in two passes: the absolute positions of all
390		# subtable are needed before the actual data can be assembled.
391		pos = 0
392		for table in tables:
393			table.pos = pos
394			pos = pos + table.getDataLength()
395
396		for table in extTables:
397			table.pos = pos
398			pos = pos + table.getDataLength()
399
400		data = []
401		for table in tables:
402			tableData = table.getData()
403			data.append(tableData)
404
405		for table in extTables:
406			tableData = table.getData()
407			data.append(tableData)
408
409		return bytesjoin(data)
410
411	# interface for gathering data, as used by table.compile()
412
413	def getSubWriter(self):
414		subwriter = self.__class__(self.localState, self.tableTag)
415		subwriter.parent = self # because some subtables have idential values, we discard
416					# the duplicates under the getAllData method. Hence some
417					# subtable writers can have more than one parent writer.
418					# But we just care about first one right now.
419		return subwriter
420
421	def writeUShort(self, value):
422		assert 0 <= value < 0x10000, value
423		self.items.append(struct.pack(">H", value))
424
425	def writeShort(self, value):
426		assert -32768 <= value < 32768, value
427		self.items.append(struct.pack(">h", value))
428
429	def writeUInt8(self, value):
430		assert 0 <= value < 256, value
431		self.items.append(struct.pack(">B", value))
432
433	def writeInt8(self, value):
434		assert -128 <= value < 128, value
435		self.items.append(struct.pack(">b", value))
436
437	def writeUInt24(self, value):
438		assert 0 <= value < 0x1000000, value
439		b = struct.pack(">L", value)
440		self.items.append(b[1:])
441
442	def writeLong(self, value):
443		self.items.append(struct.pack(">l", value))
444
445	def writeULong(self, value):
446		self.items.append(struct.pack(">L", value))
447
448	def writeTag(self, tag):
449		tag = Tag(tag).tobytes()
450		assert len(tag) == 4, tag
451		self.items.append(tag)
452
453	def writeSubTable(self, subWriter):
454		self.items.append(subWriter)
455
456	def writeCountReference(self, table, name, size=2, value=None):
457		ref = CountReference(table, name, size=size, value=value)
458		self.items.append(ref)
459		return ref
460
461	def writeStruct(self, format, values):
462		data = struct.pack(*(format,) + values)
463		self.items.append(data)
464
465	def writeData(self, data):
466		self.items.append(data)
467
468	def getOverflowErrorRecord(self, item):
469		LookupListIndex = SubTableIndex = itemName = itemIndex = None
470		if self.name == 'LookupList':
471			LookupListIndex = item.repeatIndex
472		elif self.name == 'Lookup':
473			LookupListIndex = self.repeatIndex
474			SubTableIndex = item.repeatIndex
475		else:
476			itemName = getattr(item, 'name', '<none>')
477			if hasattr(item, 'repeatIndex'):
478				itemIndex = item.repeatIndex
479			if self.name == 'SubTable':
480				LookupListIndex = self.parent.repeatIndex
481				SubTableIndex = self.repeatIndex
482			elif self.name == 'ExtSubTable':
483				LookupListIndex = self.parent.parent.repeatIndex
484				SubTableIndex = self.parent.repeatIndex
485			else: # who knows how far below the SubTable level we are! Climb back up to the nearest subtable.
486				itemName = ".".join([self.name, itemName])
487				p1 = self.parent
488				while p1 and p1.name not in ['ExtSubTable', 'SubTable']:
489					itemName = ".".join([p1.name, itemName])
490					p1 = p1.parent
491				if p1:
492					if p1.name == 'ExtSubTable':
493						LookupListIndex = p1.parent.parent.repeatIndex
494						SubTableIndex = p1.parent.repeatIndex
495					else:
496						LookupListIndex = p1.parent.repeatIndex
497						SubTableIndex = p1.repeatIndex
498
499		return OverflowErrorRecord( (self.tableTag, LookupListIndex, SubTableIndex, itemName, itemIndex) )
500
501
502class CountReference(object):
503	"""A reference to a Count value, not a count of references."""
504	def __init__(self, table, name, size=None, value=None):
505		self.table = table
506		self.name = name
507		self.size = size
508		if value is not None:
509			self.setValue(value)
510	def setValue(self, value):
511		table = self.table
512		name = self.name
513		if table[name] is None:
514			table[name] = value
515		else:
516			assert table[name] == value, (name, table[name], value)
517	def getCountData(self):
518		v = self.table[self.name]
519		if v is None: v = 0
520		return {1:packUInt8, 2:packUShort, 4:packULong}[self.size](v)
521
522
523def packUInt8 (value):
524	return struct.pack(">B", value)
525
526def packUShort(value):
527	return struct.pack(">H", value)
528
529def packULong(value):
530	assert 0 <= value < 0x100000000, value
531	return struct.pack(">L", value)
532
533
534class BaseTable(object):
535
536	"""Generic base class for all OpenType (sub)tables."""
537
538	def __getattr__(self, attr):
539		reader = self.__dict__.get("reader")
540		if reader:
541			del self.reader
542			font = self.font
543			del self.font
544			self.decompile(reader, font)
545			return getattr(self, attr)
546
547		raise AttributeError(attr)
548
549	def ensureDecompiled(self):
550		reader = self.__dict__.get("reader")
551		if reader:
552			del self.reader
553			font = self.font
554			del self.font
555			self.decompile(reader, font)
556
557	@classmethod
558	def getRecordSize(cls, reader):
559		totalSize = 0
560		for conv in cls.converters:
561			size = conv.getRecordSize(reader)
562			if size is NotImplemented: return NotImplemented
563			countValue = 1
564			if conv.repeat:
565				if conv.repeat in reader:
566					countValue = reader[conv.repeat]
567				else:
568					return NotImplemented
569			totalSize += size * countValue
570		return totalSize
571
572	def getConverters(self):
573		return self.converters
574
575	def getConverterByName(self, name):
576		return self.convertersByName[name]
577
578	def populateDefaults(self, propagator=None):
579		for conv in self.getConverters():
580			if conv.repeat:
581				if not hasattr(self, conv.name):
582					setattr(self, conv.name, [])
583				countValue = len(getattr(self, conv.name)) - conv.aux
584				try:
585					count_conv = self.getConverterByName(conv.repeat)
586					setattr(self, conv.repeat, countValue)
587				except KeyError:
588					# conv.repeat is a propagated count
589					if propagator and conv.repeat in propagator:
590						propagator[conv.repeat].setValue(countValue)
591			else:
592				if conv.aux and not eval(conv.aux, None, self.__dict__):
593					continue
594				if hasattr(self, conv.name):
595					continue # Warn if it should NOT be present?!
596				if hasattr(conv, 'writeNullOffset'):
597					setattr(self, conv.name, None) # Warn?
598				#elif not conv.isCount:
599				#	# Warn?
600				#	pass
601
602	def decompile(self, reader, font):
603		self.readFormat(reader)
604		table = {}
605		self.__rawTable = table  # for debugging
606		for conv in self.getConverters():
607			if conv.name == "SubTable":
608				conv = conv.getConverter(reader.tableTag,
609						table["LookupType"])
610			if conv.name == "ExtSubTable":
611				conv = conv.getConverter(reader.tableTag,
612						table["ExtensionLookupType"])
613			if conv.name == "FeatureParams":
614				conv = conv.getConverter(reader["FeatureTag"])
615			if conv.name == "SubStruct":
616				conv = conv.getConverter(reader.tableTag,
617				                         table["MorphType"])
618			try:
619				if conv.repeat:
620					if isinstance(conv.repeat, int):
621						countValue = conv.repeat
622					elif conv.repeat in table:
623						countValue = table[conv.repeat]
624					else:
625						# conv.repeat is a propagated count
626						countValue = reader[conv.repeat]
627					countValue += conv.aux
628					table[conv.name] = conv.readArray(reader, font, table, countValue)
629				else:
630					if conv.aux and not eval(conv.aux, None, table):
631						continue
632					table[conv.name] = conv.read(reader, font, table)
633					if conv.isPropagated:
634						reader[conv.name] = table[conv.name]
635			except Exception as e:
636				name = conv.name
637				e.args = e.args + (name,)
638				raise
639
640		if hasattr(self, 'postRead'):
641			self.postRead(table, font)
642		else:
643			self.__dict__.update(table)
644
645		del self.__rawTable  # succeeded, get rid of debugging info
646
647	def compile(self, writer, font):
648		self.ensureDecompiled()
649		if hasattr(self, 'preWrite'):
650			table = self.preWrite(font)
651		else:
652			table = self.__dict__.copy()
653
654
655		if hasattr(self, 'sortCoverageLast'):
656			writer.sortCoverageLast = 1
657
658		if hasattr(self, 'DontShare'):
659			writer.DontShare = True
660
661		if hasattr(self.__class__, 'LookupType'):
662			writer['LookupType'].setValue(self.__class__.LookupType)
663
664		self.writeFormat(writer)
665		for conv in self.getConverters():
666			value = table.get(conv.name) # TODO Handle defaults instead of defaulting to None!
667			if conv.repeat:
668				if value is None:
669					value = []
670				countValue = len(value) - conv.aux
671				if isinstance(conv.repeat, int):
672					assert len(value) == conv.repeat, 'expected %d values, got %d' % (conv.repeat, len(value))
673				elif conv.repeat in table:
674					CountReference(table, conv.repeat, value=countValue)
675				else:
676					# conv.repeat is a propagated count
677					writer[conv.repeat].setValue(countValue)
678				values = value
679				for i, value in enumerate(values):
680					try:
681						conv.write(writer, font, table, value, i)
682					except Exception as e:
683						name = value.__class__.__name__ if value is not None else conv.name
684						e.args = e.args + (name+'['+str(i)+']',)
685						raise
686			elif conv.isCount:
687				# Special-case Count values.
688				# Assumption: a Count field will *always* precede
689				# the actual array(s).
690				# We need a default value, as it may be set later by a nested
691				# table. We will later store it here.
692				# We add a reference: by the time the data is assembled
693				# the Count value will be filled in.
694				ref = writer.writeCountReference(table, conv.name, conv.staticSize)
695				table[conv.name] = None
696				if conv.isPropagated:
697					writer[conv.name] = ref
698			elif conv.isLookupType:
699				# We make sure that subtables have the same lookup type,
700				# and that the type is the same as the one set on the
701				# Lookup object, if any is set.
702				if conv.name not in table:
703					table[conv.name] = None
704				ref = writer.writeCountReference(table, conv.name, conv.staticSize, table[conv.name])
705				writer['LookupType'] = ref
706			else:
707				if conv.aux and not eval(conv.aux, None, table):
708					continue
709				try:
710					conv.write(writer, font, table, value)
711				except Exception as e:
712					name = value.__class__.__name__ if value is not None else conv.name
713					e.args = e.args + (name,)
714					raise
715				if conv.isPropagated:
716					writer[conv.name] = value
717
718	def readFormat(self, reader):
719		pass
720
721	def writeFormat(self, writer):
722		pass
723
724	def toXML(self, xmlWriter, font, attrs=None, name=None):
725		tableName = name if name else self.__class__.__name__
726		if attrs is None:
727			attrs = []
728		if hasattr(self, "Format"):
729			attrs = attrs + [("Format", self.Format)]
730		xmlWriter.begintag(tableName, attrs)
731		xmlWriter.newline()
732		self.toXML2(xmlWriter, font)
733		xmlWriter.endtag(tableName)
734		xmlWriter.newline()
735
736	def toXML2(self, xmlWriter, font):
737		# Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB).
738		# This is because in TTX our parent writes our main tag, and in otBase.py we
739		# do it ourselves. I think I'm getting schizophrenic...
740		for conv in self.getConverters():
741			if conv.repeat:
742				value = getattr(self, conv.name, [])
743				for i in range(len(value)):
744					item = value[i]
745					conv.xmlWrite(xmlWriter, font, item, conv.name,
746							[("index", i)])
747			else:
748				if conv.aux and not eval(conv.aux, None, vars(self)):
749					continue
750				value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None!
751				conv.xmlWrite(xmlWriter, font, value, conv.name, [])
752
753	def fromXML(self, name, attrs, content, font):
754		try:
755			conv = self.getConverterByName(name)
756		except KeyError:
757			raise    # XXX on KeyError, raise nice error
758		value = conv.xmlRead(attrs, content, font)
759		if conv.repeat:
760			seq = getattr(self, conv.name, None)
761			if seq is None:
762				seq = []
763				setattr(self, conv.name, seq)
764			seq.append(value)
765		else:
766			setattr(self, conv.name, value)
767
768	def __ne__(self, other):
769		result = self.__eq__(other)
770		return result if result is NotImplemented else not result
771
772	def __eq__(self, other):
773		if type(self) != type(other):
774			return NotImplemented
775
776		self.ensureDecompiled()
777		other.ensureDecompiled()
778
779		return self.__dict__ == other.__dict__
780
781
782class FormatSwitchingBaseTable(BaseTable):
783
784	"""Minor specialization of BaseTable, for tables that have multiple
785	formats, eg. CoverageFormat1 vs. CoverageFormat2."""
786
787	@classmethod
788	def getRecordSize(cls, reader):
789		return NotImplemented
790
791	def getConverters(self):
792		return self.converters.get(self.Format, [])
793
794	def getConverterByName(self, name):
795		return self.convertersByName[self.Format][name]
796
797	def readFormat(self, reader):
798		self.Format = reader.readUShort()
799
800	def writeFormat(self, writer):
801		writer.writeUShort(self.Format)
802
803	def toXML(self, xmlWriter, font, attrs=None, name=None):
804		BaseTable.toXML(self, xmlWriter, font, attrs, name)
805
806
807#
808# Support for ValueRecords
809#
810# This data type is so different from all other OpenType data types that
811# it requires quite a bit of code for itself. It even has special support
812# in OTTableReader and OTTableWriter...
813#
814
815valueRecordFormat = [
816#	Mask	 Name		isDevice signed
817	(0x0001, "XPlacement",	0,	1),
818	(0x0002, "YPlacement",	0,	1),
819	(0x0004, "XAdvance",	0,	1),
820	(0x0008, "YAdvance",	0,	1),
821	(0x0010, "XPlaDevice",	1,	0),
822	(0x0020, "YPlaDevice",	1,	0),
823	(0x0040, "XAdvDevice",	1,	0),
824	(0x0080, "YAdvDevice",	1,	0),
825#	reserved:
826	(0x0100, "Reserved1",	0,	0),
827	(0x0200, "Reserved2",	0,	0),
828	(0x0400, "Reserved3",	0,	0),
829	(0x0800, "Reserved4",	0,	0),
830	(0x1000, "Reserved5",	0,	0),
831	(0x2000, "Reserved6",	0,	0),
832	(0x4000, "Reserved7",	0,	0),
833	(0x8000, "Reserved8",	0,	0),
834]
835
836def _buildDict():
837	d = {}
838	for mask, name, isDevice, signed in valueRecordFormat:
839		d[name] = mask, isDevice, signed
840	return d
841
842valueRecordFormatDict = _buildDict()
843
844
845class ValueRecordFactory(object):
846
847	"""Given a format code, this object convert ValueRecords."""
848
849	def __init__(self, valueFormat):
850		format = []
851		for mask, name, isDevice, signed in valueRecordFormat:
852			if valueFormat & mask:
853				format.append((name, isDevice, signed))
854		self.format = format
855
856	def __len__(self):
857		return len(self.format)
858
859	def readValueRecord(self, reader, font):
860		format = self.format
861		if not format:
862			return None
863		valueRecord = ValueRecord()
864		for name, isDevice, signed in format:
865			if signed:
866				value = reader.readShort()
867			else:
868				value = reader.readUShort()
869			if isDevice:
870				if value:
871					from . import otTables
872					subReader = reader.getSubReader(value)
873					value = getattr(otTables, name)()
874					value.decompile(subReader, font)
875				else:
876					value = None
877			setattr(valueRecord, name, value)
878		return valueRecord
879
880	def writeValueRecord(self, writer, font, valueRecord):
881		for name, isDevice, signed in self.format:
882			value = getattr(valueRecord, name, 0)
883			if isDevice:
884				if value:
885					subWriter = writer.getSubWriter()
886					writer.writeSubTable(subWriter)
887					value.compile(subWriter, font)
888				else:
889					writer.writeUShort(0)
890			elif signed:
891				writer.writeShort(value)
892			else:
893				writer.writeUShort(value)
894
895
896class ValueRecord(object):
897
898	# see ValueRecordFactory
899
900	def __init__(self, valueFormat=None, src=None):
901		if valueFormat is not None:
902			for mask, name, isDevice, signed in valueRecordFormat:
903				if valueFormat & mask:
904					setattr(self, name, None if isDevice else 0)
905			if src is not None:
906				for key,val in src.__dict__.items():
907					if not hasattr(self, key):
908						continue
909					setattr(self, key, val)
910		elif src is not None:
911			self.__dict__ = src.__dict__.copy()
912
913	def getFormat(self):
914		format = 0
915		for name in self.__dict__.keys():
916			format = format | valueRecordFormatDict[name][0]
917		return format
918
919	def toXML(self, xmlWriter, font, valueName, attrs=None):
920		if attrs is None:
921			simpleItems = []
922		else:
923			simpleItems = list(attrs)
924		for mask, name, isDevice, format in valueRecordFormat[:4]:  # "simple" values
925			if hasattr(self, name):
926				simpleItems.append((name, getattr(self, name)))
927		deviceItems = []
928		for mask, name, isDevice, format in valueRecordFormat[4:8]:  # device records
929			if hasattr(self, name):
930				device = getattr(self, name)
931				if device is not None:
932					deviceItems.append((name, device))
933		if deviceItems:
934			xmlWriter.begintag(valueName, simpleItems)
935			xmlWriter.newline()
936			for name, deviceRecord in deviceItems:
937				if deviceRecord is not None:
938					deviceRecord.toXML(xmlWriter, font, name=name)
939			xmlWriter.endtag(valueName)
940			xmlWriter.newline()
941		else:
942			xmlWriter.simpletag(valueName, simpleItems)
943			xmlWriter.newline()
944
945	def fromXML(self, name, attrs, content, font):
946		from . import otTables
947		for k, v in attrs.items():
948			setattr(self, k, int(v))
949		for element in content:
950			if not isinstance(element, tuple):
951				continue
952			name, attrs, content = element
953			value = getattr(otTables, name)()
954			for elem2 in content:
955				if not isinstance(elem2, tuple):
956					continue
957				name2, attrs2, content2 = elem2
958				value.fromXML(name2, attrs2, content2, font)
959			setattr(self, name, value)
960
961	def __ne__(self, other):
962		result = self.__eq__(other)
963		return result if result is NotImplemented else not result
964
965	def __eq__(self, other):
966		if type(self) != type(other):
967			return NotImplemented
968		return self.__dict__ == other.__dict__
969