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 struct
      5 
      6 class 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 
     17 class OTLOffsetOverflowError(Exception):
     18 	def __init__(self, overflowErrorRecord):
     19 		self.value = overflowErrorRecord
     20 
     21 	def __str__(self):
     22 		return repr(self.value)
     23 
     24 
     25 class 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 
    118 class 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 
    190 class 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 
    495 class 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 
    511 def packUShort(value):
    512 	return struct.pack(">H", value)
    513 
    514 
    515 def packULong(value):
    516 	assert 0 <= value < 0x100000000, value
    517 	return struct.pack(">L", value)
    518 
    519 
    520 class 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 
    699 class 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 
    729 valueRecordFormat = [
    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 
    750 def _buildDict():
    751 	d = {}
    752 	for mask, name, isDevice, signed in valueRecordFormat:
    753 		d[name] = mask, isDevice, signed
    754 	return d
    755 
    756 valueRecordFormatDict = _buildDict()
    757 
    758 
    759 class 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 
    807 class 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