Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from .DefaultTable import DefaultTable
      4 import sys
      5 import array
      6 import struct
      7 import logging
      8 
      9 log = logging.getLogger(__name__)
     10 
     11 class 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 
     22 class OTLOffsetOverflowError(Exception):
     23 	def __init__(self, overflowErrorRecord):
     24 		self.value = overflowErrorRecord
     25 
     26 	def __str__(self):
     27 		return repr(self.value)
     28 
     29 
     30 class 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 
    109 class 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 
    221 class 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 
    501 class 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 
    522 def packUInt8 (value):
    523 	return struct.pack(">B", value)
    524 
    525 def packUShort(value):
    526 	return struct.pack(">H", value)
    527 
    528 def packULong(value):
    529 	assert 0 <= value < 0x100000000, value
    530 	return struct.pack(">L", value)
    531 
    532 
    533 class 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 
    781 class 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 
    814 valueRecordFormat = [
    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 
    835 def _buildDict():
    836 	d = {}
    837 	for mask, name, isDevice, signed in valueRecordFormat:
    838 		d[name] = mask, isDevice, signed
    839 	return d
    840 
    841 valueRecordFormatDict = _buildDict()
    842 
    843 
    844 class 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 
    895 class 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