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