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