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