Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc.fixedTools import (
      4 	fixedToFloat as fi2fl, floatToFixed as fl2fi, ensureVersionIsLong as fi2ve,
      5 	versionToFixed as ve2fi)
      6 from fontTools.misc.textTools import pad, safeEval
      7 from fontTools.ttLib import getSearchRange
      8 from .otBase import (CountReference, FormatSwitchingBaseTable,
      9                      OTTableReader, OTTableWriter, ValueRecordFactory)
     10 from .otTables import (lookupTypes, AATStateTable, AATState, AATAction,
     11                        ContextualMorphAction, LigatureMorphAction,
     12                        InsertionMorphAction, MorxSubtable)
     13 from functools import partial
     14 import struct
     15 import logging
     16 
     17 
     18 log = logging.getLogger(__name__)
     19 istuple = lambda t: isinstance(t, tuple)
     20 
     21 
     22 def buildConverters(tableSpec, tableNamespace):
     23 	"""Given a table spec from otData.py, build a converter object for each
     24 	field of the table. This is called for each table in otData.py, and
     25 	the results are assigned to the corresponding class in otTables.py."""
     26 	converters = []
     27 	convertersByName = {}
     28 	for tp, name, repeat, aux, descr in tableSpec:
     29 		tableName = name
     30 		if name.startswith("ValueFormat"):
     31 			assert tp == "uint16"
     32 			converterClass = ValueFormat
     33 		elif name.endswith("Count") or name in ("StructLength", "MorphType"):
     34 			converterClass = {
     35 				"uint8": ComputedUInt8,
     36 				"uint16": ComputedUShort,
     37 				"uint32": ComputedULong,
     38 			}[tp]
     39 		elif name == "SubTable":
     40 			converterClass = SubTable
     41 		elif name == "ExtSubTable":
     42 			converterClass = ExtSubTable
     43 		elif name == "SubStruct":
     44 			converterClass = SubStruct
     45 		elif name == "FeatureParams":
     46 			converterClass = FeatureParams
     47 		elif name in ("CIDGlyphMapping", "GlyphCIDMapping"):
     48 			converterClass = StructWithLength
     49 		else:
     50 			if not tp in converterMapping and '(' not in tp:
     51 				tableName = tp
     52 				converterClass = Struct
     53 			else:
     54 				converterClass = eval(tp, tableNamespace, converterMapping)
     55 		if tp in ('MortChain', 'MortSubtable', 'MorxChain'):
     56 			tableClass = tableNamespace.get(tp)
     57 		else:
     58 			tableClass = tableNamespace.get(tableName)
     59 		if tableClass is not None:
     60 			conv = converterClass(name, repeat, aux, tableClass=tableClass)
     61 		else:
     62 			conv = converterClass(name, repeat, aux)
     63 		if name in ["SubTable", "ExtSubTable", "SubStruct"]:
     64 			conv.lookupTypes = tableNamespace['lookupTypes']
     65 			# also create reverse mapping
     66 			for t in conv.lookupTypes.values():
     67 				for cls in t.values():
     68 					convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
     69 		if name == "FeatureParams":
     70 			conv.featureParamTypes = tableNamespace['featureParamTypes']
     71 			conv.defaultFeatureParams = tableNamespace['FeatureParams']
     72 			for cls in conv.featureParamTypes.values():
     73 				convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
     74 		converters.append(conv)
     75 		assert name not in convertersByName, name
     76 		convertersByName[name] = conv
     77 	return converters, convertersByName
     78 
     79 
     80 class _MissingItem(tuple):
     81 	__slots__ = ()
     82 
     83 
     84 try:
     85 	from collections import UserList
     86 except ImportError:
     87 	from UserList import UserList
     88 
     89 
     90 class _LazyList(UserList):
     91 
     92 	def __getslice__(self, i, j):
     93 		return self.__getitem__(slice(i, j))
     94 
     95 	def __getitem__(self, k):
     96 		if isinstance(k, slice):
     97 			indices = range(*k.indices(len(self)))
     98 			return [self[i] for i in indices]
     99 		item = self.data[k]
    100 		if isinstance(item, _MissingItem):
    101 			self.reader.seek(self.pos + item[0] * self.recordSize)
    102 			item = self.conv.read(self.reader, self.font, {})
    103 			self.data[k] = item
    104 		return item
    105 
    106 	def __add__(self, other):
    107 		if isinstance(other, _LazyList):
    108 			other = list(other)
    109 		elif isinstance(other, list):
    110 			pass
    111 		else:
    112 			return NotImplemented
    113 		return list(self) + other
    114 
    115 	def __radd__(self, other):
    116 		if not isinstance(other, list):
    117 			return NotImplemented
    118 		return other + list(self)
    119 
    120 
    121 class BaseConverter(object):
    122 
    123 	"""Base class for converter objects. Apart from the constructor, this
    124 	is an abstract class."""
    125 
    126 	def __init__(self, name, repeat, aux, tableClass=None):
    127 		self.name = name
    128 		self.repeat = repeat
    129 		self.aux = aux
    130 		self.tableClass = tableClass
    131 		self.isCount = name.endswith("Count") or name in ['DesignAxisRecordSize', 'ValueRecordSize']
    132 		self.isLookupType = name.endswith("LookupType") or name == "MorphType"
    133 		self.isPropagated = name in ["ClassCount", "Class2Count", "FeatureTag", "SettingsCount", "VarRegionCount", "MappingCount", "RegionAxisCount", 'DesignAxisCount', 'DesignAxisRecordSize', 'AxisValueCount', 'ValueRecordSize', 'AxisCount']
    134 
    135 	def readArray(self, reader, font, tableDict, count):
    136 		"""Read an array of values from the reader."""
    137 		lazy = font.lazy and count > 8
    138 		if lazy:
    139 			recordSize = self.getRecordSize(reader)
    140 			if recordSize is NotImplemented:
    141 				lazy = False
    142 		if not lazy:
    143 			l = []
    144 			for i in range(count):
    145 				l.append(self.read(reader, font, tableDict))
    146 			return l
    147 		else:
    148 			l = _LazyList()
    149 			l.reader = reader.copy()
    150 			l.pos = l.reader.pos
    151 			l.font = font
    152 			l.conv = self
    153 			l.recordSize = recordSize
    154 			l.extend(_MissingItem([i]) for i in range(count))
    155 			reader.advance(count * recordSize)
    156 			return l
    157 
    158 	def getRecordSize(self, reader):
    159 		if hasattr(self, 'staticSize'): return self.staticSize
    160 		return NotImplemented
    161 
    162 	def read(self, reader, font, tableDict):
    163 		"""Read a value from the reader."""
    164 		raise NotImplementedError(self)
    165 
    166 	def writeArray(self, writer, font, tableDict, values):
    167 		for i, value in enumerate(values):
    168 			self.write(writer, font, tableDict, value, i)
    169 
    170 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    171 		"""Write a value to the writer."""
    172 		raise NotImplementedError(self)
    173 
    174 	def xmlRead(self, attrs, content, font):
    175 		"""Read a value from XML."""
    176 		raise NotImplementedError(self)
    177 
    178 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    179 		"""Write a value to XML."""
    180 		raise NotImplementedError(self)
    181 
    182 
    183 class SimpleValue(BaseConverter):
    184 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    185 		xmlWriter.simpletag(name, attrs + [("value", value)])
    186 		xmlWriter.newline()
    187 	def xmlRead(self, attrs, content, font):
    188 		return attrs["value"]
    189 
    190 class IntValue(SimpleValue):
    191 	def xmlRead(self, attrs, content, font):
    192 		return int(attrs["value"], 0)
    193 
    194 class Long(IntValue):
    195 	staticSize = 4
    196 	def read(self, reader, font, tableDict):
    197 		return reader.readLong()
    198 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    199 		writer.writeLong(value)
    200 
    201 class ULong(IntValue):
    202 	staticSize = 4
    203 	def read(self, reader, font, tableDict):
    204 		return reader.readULong()
    205 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    206 		writer.writeULong(value)
    207 
    208 class Flags32(ULong):
    209 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    210 		xmlWriter.simpletag(name, attrs + [("value", "0x%08X" % value)])
    211 		xmlWriter.newline()
    212 
    213 class Short(IntValue):
    214 	staticSize = 2
    215 	def read(self, reader, font, tableDict):
    216 		return reader.readShort()
    217 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    218 		writer.writeShort(value)
    219 
    220 class UShort(IntValue):
    221 	staticSize = 2
    222 	def read(self, reader, font, tableDict):
    223 		return reader.readUShort()
    224 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    225 		writer.writeUShort(value)
    226 
    227 class Int8(IntValue):
    228 	staticSize = 1
    229 	def read(self, reader, font, tableDict):
    230 		return reader.readInt8()
    231 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    232 		writer.writeInt8(value)
    233 
    234 class UInt8(IntValue):
    235 	staticSize = 1
    236 	def read(self, reader, font, tableDict):
    237 		return reader.readUInt8()
    238 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    239 		writer.writeUInt8(value)
    240 
    241 class UInt24(IntValue):
    242 	staticSize = 3
    243 	def read(self, reader, font, tableDict):
    244 		return reader.readUInt24()
    245 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    246 		writer.writeUInt24(value)
    247 
    248 class ComputedInt(IntValue):
    249 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    250 		if value is not None:
    251 			xmlWriter.comment("%s=%s" % (name, value))
    252 			xmlWriter.newline()
    253 
    254 class ComputedUInt8(ComputedInt, UInt8):
    255 	pass
    256 class ComputedUShort(ComputedInt, UShort):
    257 	pass
    258 class ComputedULong(ComputedInt, ULong):
    259 	pass
    260 
    261 class Tag(SimpleValue):
    262 	staticSize = 4
    263 	def read(self, reader, font, tableDict):
    264 		return reader.readTag()
    265 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    266 		writer.writeTag(value)
    267 
    268 class GlyphID(SimpleValue):
    269 	staticSize = 2
    270 	def readArray(self, reader, font, tableDict, count):
    271 		glyphOrder = font.getGlyphOrder()
    272 		gids = reader.readUShortArray(count)
    273 		try:
    274 			l = [glyphOrder[gid] for gid in gids]
    275 		except IndexError:
    276 			# Slower, but will not throw an IndexError on an invalid glyph id.
    277 			l = [font.getGlyphName(gid) for gid in gids]
    278 		return l
    279 	def read(self, reader, font, tableDict):
    280 		return font.getGlyphName(reader.readUShort())
    281 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    282 		writer.writeUShort(font.getGlyphID(value))
    283 
    284 
    285 class NameID(UShort):
    286 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    287 		xmlWriter.simpletag(name, attrs + [("value", value)])
    288 		if font and value:
    289 			nameTable = font.get("name")
    290 			if nameTable:
    291 				name = nameTable.getDebugName(value)
    292 				xmlWriter.write("  ")
    293 				if name:
    294 					xmlWriter.comment(name)
    295 				else:
    296 					xmlWriter.comment("missing from name table")
    297 					log.warning("name id %d missing from name table" % value)
    298 		xmlWriter.newline()
    299 
    300 
    301 class FloatValue(SimpleValue):
    302 	def xmlRead(self, attrs, content, font):
    303 		return float(attrs["value"])
    304 
    305 class DeciPoints(FloatValue):
    306 	staticSize = 2
    307 	def read(self, reader, font, tableDict):
    308 		return reader.readUShort() / 10
    309 
    310 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    311 		writer.writeUShort(round(value * 10))
    312 
    313 class Fixed(FloatValue):
    314 	staticSize = 4
    315 	def read(self, reader, font, tableDict):
    316 		return  fi2fl(reader.readLong(), 16)
    317 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    318 		writer.writeLong(fl2fi(value, 16))
    319 
    320 class F2Dot14(FloatValue):
    321 	staticSize = 2
    322 	def read(self, reader, font, tableDict):
    323 		return  fi2fl(reader.readShort(), 14)
    324 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    325 		writer.writeShort(fl2fi(value, 14))
    326 
    327 class Version(BaseConverter):
    328 	staticSize = 4
    329 	def read(self, reader, font, tableDict):
    330 		value = reader.readLong()
    331 		assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
    332 		return value
    333 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    334 		value = fi2ve(value)
    335 		assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
    336 		writer.writeLong(value)
    337 	def xmlRead(self, attrs, content, font):
    338 		value = attrs["value"]
    339 		value = ve2fi(value)
    340 		return value
    341 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    342 		value = fi2ve(value)
    343 		value = "0x%08x" % value
    344 		xmlWriter.simpletag(name, attrs + [("value", value)])
    345 		xmlWriter.newline()
    346 
    347 	@staticmethod
    348 	def fromFloat(v):
    349 		return fl2fi(v, 16)
    350 
    351 
    352 class Char64(SimpleValue):
    353 	"""An ASCII string with up to 64 characters.
    354 
    355 	Unused character positions are filled with 0x00 bytes.
    356 	Used in Apple AAT fonts in the `gcid` table.
    357 	"""
    358 	staticSize = 64
    359 
    360 	def read(self, reader, font, tableDict):
    361 		data = reader.readData(self.staticSize)
    362 		zeroPos = data.find(b"\0")
    363 		if zeroPos >= 0:
    364 			data = data[:zeroPos]
    365 		s = tounicode(data, encoding="ascii", errors="replace")
    366 		if s != tounicode(data, encoding="ascii", errors="ignore"):
    367 			log.warning('replaced non-ASCII characters in "%s"' %
    368 			            s)
    369 		return s
    370 
    371 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    372 		data = tobytes(value, encoding="ascii", errors="replace")
    373 		if data != tobytes(value, encoding="ascii", errors="ignore"):
    374 			log.warning('replacing non-ASCII characters in "%s"' %
    375 			            value)
    376 		if len(data) > self.staticSize:
    377 			log.warning('truncating overlong "%s" to %d bytes' %
    378 			            (value, self.staticSize))
    379 		data = (data + b"\0" * self.staticSize)[:self.staticSize]
    380 		writer.writeData(data)
    381 
    382 
    383 class Struct(BaseConverter):
    384 
    385 	def getRecordSize(self, reader):
    386 		return self.tableClass and self.tableClass.getRecordSize(reader)
    387 
    388 	def read(self, reader, font, tableDict):
    389 		table = self.tableClass()
    390 		table.decompile(reader, font)
    391 		return table
    392 
    393 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    394 		value.compile(writer, font)
    395 
    396 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    397 		if value is None:
    398 			if attrs:
    399 				# If there are attributes (probably index), then
    400 				# don't drop this even if it's NULL.  It will mess
    401 				# up the array indices of the containing element.
    402 				xmlWriter.simpletag(name, attrs + [("empty", 1)])
    403 				xmlWriter.newline()
    404 			else:
    405 				pass # NULL table, ignore
    406 		else:
    407 			value.toXML(xmlWriter, font, attrs, name=name)
    408 
    409 	def xmlRead(self, attrs, content, font):
    410 		if "empty" in attrs and safeEval(attrs["empty"]):
    411 			return None
    412 		table = self.tableClass()
    413 		Format = attrs.get("Format")
    414 		if Format is not None:
    415 			table.Format = int(Format)
    416 
    417 		noPostRead = not hasattr(table, 'postRead')
    418 		if noPostRead:
    419 			# TODO Cache table.hasPropagated.
    420 			cleanPropagation = False
    421 			for conv in table.getConverters():
    422 				if conv.isPropagated:
    423 					cleanPropagation = True
    424 					if not hasattr(font, '_propagator'):
    425 						font._propagator = {}
    426 					propagator = font._propagator
    427 					assert conv.name not in propagator, (conv.name, propagator)
    428 					setattr(table, conv.name, None)
    429 					propagator[conv.name] = CountReference(table.__dict__, conv.name)
    430 
    431 		for element in content:
    432 			if isinstance(element, tuple):
    433 				name, attrs, content = element
    434 				table.fromXML(name, attrs, content, font)
    435 			else:
    436 				pass
    437 
    438 		table.populateDefaults(propagator=getattr(font, '_propagator', None))
    439 
    440 		if noPostRead:
    441 			if cleanPropagation:
    442 				for conv in table.getConverters():
    443 					if conv.isPropagated:
    444 						propagator = font._propagator
    445 						del propagator[conv.name]
    446 						if not propagator:
    447 							del font._propagator
    448 
    449 		return table
    450 
    451 	def __repr__(self):
    452 		return "Struct of " + repr(self.tableClass)
    453 
    454 
    455 class StructWithLength(Struct):
    456 	def read(self, reader, font, tableDict):
    457 		pos = reader.pos
    458 		table = self.tableClass()
    459 		table.decompile(reader, font)
    460 		reader.seek(pos + table.StructLength)
    461 		return table
    462 
    463 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    464 		for convIndex, conv in enumerate(value.getConverters()):
    465 			if conv.name == "StructLength":
    466 				break
    467 		lengthIndex = len(writer.items) + convIndex
    468 		if isinstance(value, FormatSwitchingBaseTable):
    469 			lengthIndex += 1  # implicit Format field
    470 		deadbeef = {1:0xDE, 2:0xDEAD, 4:0xDEADBEEF}[conv.staticSize]
    471 
    472 		before = writer.getDataLength()
    473 		value.StructLength = deadbeef
    474 		value.compile(writer, font)
    475 		length = writer.getDataLength() - before
    476 		lengthWriter = writer.getSubWriter()
    477 		conv.write(lengthWriter, font, tableDict, length)
    478 		assert(writer.items[lengthIndex] ==
    479 		       b"\xde\xad\xbe\xef"[:conv.staticSize])
    480 		writer.items[lengthIndex] = lengthWriter.getAllData()
    481 
    482 
    483 class Table(Struct):
    484 
    485 	longOffset = False
    486 	staticSize = 2
    487 
    488 	def readOffset(self, reader):
    489 		return reader.readUShort()
    490 
    491 	def writeNullOffset(self, writer):
    492 		if self.longOffset:
    493 			writer.writeULong(0)
    494 		else:
    495 			writer.writeUShort(0)
    496 
    497 	def read(self, reader, font, tableDict):
    498 		offset = self.readOffset(reader)
    499 		if offset == 0:
    500 			return None
    501 		table = self.tableClass()
    502 		reader = reader.getSubReader(offset)
    503 		if font.lazy:
    504 			table.reader = reader
    505 			table.font = font
    506 		else:
    507 			table.decompile(reader, font)
    508 		return table
    509 
    510 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    511 		if value is None:
    512 			self.writeNullOffset(writer)
    513 		else:
    514 			subWriter = writer.getSubWriter()
    515 			subWriter.longOffset = self.longOffset
    516 			subWriter.name = self.name
    517 			if repeatIndex is not None:
    518 				subWriter.repeatIndex = repeatIndex
    519 			writer.writeSubTable(subWriter)
    520 			value.compile(subWriter, font)
    521 
    522 class LTable(Table):
    523 
    524 	longOffset = True
    525 	staticSize = 4
    526 
    527 	def readOffset(self, reader):
    528 		return reader.readULong()
    529 
    530 
    531 # TODO Clean / merge the SubTable and SubStruct
    532 
    533 class SubStruct(Struct):
    534 	def getConverter(self, tableType, lookupType):
    535 		tableClass = self.lookupTypes[tableType][lookupType]
    536 		return self.__class__(self.name, self.repeat, self.aux, tableClass)
    537 
    538 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    539 		super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs)
    540 
    541 class SubTable(Table):
    542 	def getConverter(self, tableType, lookupType):
    543 		tableClass = self.lookupTypes[tableType][lookupType]
    544 		return self.__class__(self.name, self.repeat, self.aux, tableClass)
    545 
    546 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    547 		super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs)
    548 
    549 class ExtSubTable(LTable, SubTable):
    550 
    551 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    552 		writer.Extension = True # actually, mere presence of the field flags it as an Ext Subtable writer.
    553 		Table.write(self, writer, font, tableDict, value, repeatIndex)
    554 
    555 
    556 class FeatureParams(Table):
    557 	def getConverter(self, featureTag):
    558 		tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
    559 		return self.__class__(self.name, self.repeat, self.aux, tableClass)
    560 
    561 
    562 class ValueFormat(IntValue):
    563 	staticSize = 2
    564 	def __init__(self, name, repeat, aux, tableClass=None):
    565 		BaseConverter.__init__(self, name, repeat, aux, tableClass)
    566 		self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
    567 	def read(self, reader, font, tableDict):
    568 		format = reader.readUShort()
    569 		reader[self.which] = ValueRecordFactory(format)
    570 		return format
    571 	def write(self, writer, font, tableDict, format, repeatIndex=None):
    572 		writer.writeUShort(format)
    573 		writer[self.which] = ValueRecordFactory(format)
    574 
    575 
    576 class ValueRecord(ValueFormat):
    577 	def getRecordSize(self, reader):
    578 		return 2 * len(reader[self.which])
    579 	def read(self, reader, font, tableDict):
    580 		return reader[self.which].readValueRecord(reader, font)
    581 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    582 		writer[self.which].writeValueRecord(writer, font, value)
    583 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    584 		if value is None:
    585 			pass  # NULL table, ignore
    586 		else:
    587 			value.toXML(xmlWriter, font, self.name, attrs)
    588 	def xmlRead(self, attrs, content, font):
    589 		from .otBase import ValueRecord
    590 		value = ValueRecord()
    591 		value.fromXML(None, attrs, content, font)
    592 		return value
    593 
    594 
    595 class AATLookup(BaseConverter):
    596 	BIN_SEARCH_HEADER_SIZE = 10
    597 
    598 	def __init__(self, name, repeat, aux, tableClass):
    599 		BaseConverter.__init__(self, name, repeat, aux, tableClass)
    600 		if issubclass(self.tableClass, SimpleValue):
    601 			self.converter = self.tableClass(name='Value', repeat=None, aux=None)
    602 		else:
    603 			self.converter = Table(name='Value', repeat=None, aux=None, tableClass=self.tableClass)
    604 
    605 	def read(self, reader, font, tableDict):
    606 		format = reader.readUShort()
    607 		if format == 0:
    608 			return self.readFormat0(reader, font)
    609 		elif format == 2:
    610 			return self.readFormat2(reader, font)
    611 		elif format == 4:
    612 			return self.readFormat4(reader, font)
    613 		elif format == 6:
    614 			return self.readFormat6(reader, font)
    615 		elif format == 8:
    616 			return self.readFormat8(reader, font)
    617 		else:
    618 			assert False, "unsupported lookup format: %d" % format
    619 
    620 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    621 		values = list(sorted([(font.getGlyphID(glyph), val)
    622 		                      for glyph, val in value.items()]))
    623 		# TODO: Also implement format 4.
    624 		formats = list(sorted(filter(None, [
    625 			self.buildFormat0(writer, font, values),
    626 			self.buildFormat2(writer, font, values),
    627 			self.buildFormat6(writer, font, values),
    628 			self.buildFormat8(writer, font, values),
    629 		])))
    630 		# We use the format ID as secondary sort key to make the output
    631 		# deterministic when multiple formats have same encoded size.
    632 		dataSize, lookupFormat, writeMethod = formats[0]
    633 		pos = writer.getDataLength()
    634 		writeMethod()
    635 		actualSize = writer.getDataLength() - pos
    636 		assert actualSize == dataSize, (
    637 			"AATLookup format %d claimed to write %d bytes, but wrote %d" %
    638 			(lookupFormat, dataSize, actualSize))
    639 
    640 	@staticmethod
    641 	def writeBinSearchHeader(writer, numUnits, unitSize):
    642 		writer.writeUShort(unitSize)
    643 		writer.writeUShort(numUnits)
    644 		searchRange, entrySelector, rangeShift = \
    645 			getSearchRange(n=numUnits, itemSize=unitSize)
    646 		writer.writeUShort(searchRange)
    647 		writer.writeUShort(entrySelector)
    648 		writer.writeUShort(rangeShift)
    649 
    650 	def buildFormat0(self, writer, font, values):
    651 		numGlyphs = len(font.getGlyphOrder())
    652 		if len(values) != numGlyphs:
    653 			return None
    654 		valueSize = self.converter.staticSize
    655 		return (2 + numGlyphs * valueSize, 0,
    656 			lambda: self.writeFormat0(writer, font, values))
    657 
    658 	def writeFormat0(self, writer, font, values):
    659 		writer.writeUShort(0)
    660 		for glyphID_, value in values:
    661 			self.converter.write(
    662 				writer, font, tableDict=None,
    663 				value=value, repeatIndex=None)
    664 
    665 	def buildFormat2(self, writer, font, values):
    666 		segStart, segValue = values[0]
    667 		segEnd = segStart
    668 		segments = []
    669 		for glyphID, curValue in values[1:]:
    670 			if glyphID != segEnd + 1 or curValue != segValue:
    671 				segments.append((segStart, segEnd, segValue))
    672 				segStart = segEnd = glyphID
    673 				segValue = curValue
    674 			else:
    675 				segEnd = glyphID
    676 		segments.append((segStart, segEnd, segValue))
    677 		valueSize = self.converter.staticSize
    678 		numUnits, unitSize = len(segments) + 1, valueSize + 4
    679 		return (2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize, 2,
    680 		        lambda: self.writeFormat2(writer, font, segments))
    681 
    682 	def writeFormat2(self, writer, font, segments):
    683 		writer.writeUShort(2)
    684 		valueSize = self.converter.staticSize
    685 		numUnits, unitSize = len(segments), valueSize + 4
    686 		self.writeBinSearchHeader(writer, numUnits, unitSize)
    687 		for firstGlyph, lastGlyph, value in segments:
    688 			writer.writeUShort(lastGlyph)
    689 			writer.writeUShort(firstGlyph)
    690 			self.converter.write(
    691 				writer, font, tableDict=None,
    692 				value=value, repeatIndex=None)
    693 		writer.writeUShort(0xFFFF)
    694 		writer.writeUShort(0xFFFF)
    695 		writer.writeData(b'\x00' * valueSize)
    696 
    697 	def buildFormat6(self, writer, font, values):
    698 		valueSize = self.converter.staticSize
    699 		numUnits, unitSize = len(values), valueSize + 2
    700 		return (2 + self.BIN_SEARCH_HEADER_SIZE + (numUnits + 1) * unitSize, 6,
    701 			lambda: self.writeFormat6(writer, font, values))
    702 
    703 	def writeFormat6(self, writer, font, values):
    704 		writer.writeUShort(6)
    705 		valueSize = self.converter.staticSize
    706 		numUnits, unitSize = len(values), valueSize + 2
    707 		self.writeBinSearchHeader(writer, numUnits, unitSize)
    708 		for glyphID, value in values:
    709 			writer.writeUShort(glyphID)
    710 			self.converter.write(
    711 				writer, font, tableDict=None,
    712 				value=value, repeatIndex=None)
    713 		writer.writeUShort(0xFFFF)
    714 		writer.writeData(b'\x00' * valueSize)
    715 
    716 	def buildFormat8(self, writer, font, values):
    717 		minGlyphID, maxGlyphID = values[0][0], values[-1][0]
    718 		if len(values) != maxGlyphID - minGlyphID + 1:
    719 			return None
    720 		valueSize = self.converter.staticSize
    721 		return (6 + len(values) * valueSize, 8,
    722                         lambda: self.writeFormat8(writer, font, values))
    723 
    724 	def writeFormat8(self, writer, font, values):
    725 		firstGlyphID = values[0][0]
    726 		writer.writeUShort(8)
    727 		writer.writeUShort(firstGlyphID)
    728 		writer.writeUShort(len(values))
    729 		for _, value in values:
    730 			self.converter.write(
    731 				writer, font, tableDict=None,
    732 				value=value, repeatIndex=None)
    733 
    734 	def readFormat0(self, reader, font):
    735 		numGlyphs = len(font.getGlyphOrder())
    736 		data = self.converter.readArray(
    737 			reader, font, tableDict=None, count=numGlyphs)
    738 		return {font.getGlyphName(k): value
    739 		        for k, value in enumerate(data)}
    740 
    741 	def readFormat2(self, reader, font):
    742 		mapping = {}
    743 		pos = reader.pos - 2  # start of table is at UShort for format
    744 		unitSize, numUnits = reader.readUShort(), reader.readUShort()
    745 		assert unitSize >= 4 + self.converter.staticSize, unitSize
    746 		for i in range(numUnits):
    747 			reader.seek(pos + i * unitSize + 12)
    748 			last = reader.readUShort()
    749 			first = reader.readUShort()
    750 			value = self.converter.read(reader, font, tableDict=None)
    751 			if last != 0xFFFF:
    752 				for k in range(first, last + 1):
    753 					mapping[font.getGlyphName(k)] = value
    754 		return mapping
    755 
    756 	def readFormat4(self, reader, font):
    757 		mapping = {}
    758 		pos = reader.pos - 2  # start of table is at UShort for format
    759 		unitSize = reader.readUShort()
    760 		assert unitSize >= 6, unitSize
    761 		for i in range(reader.readUShort()):
    762 			reader.seek(pos + i * unitSize + 12)
    763 			last = reader.readUShort()
    764 			first = reader.readUShort()
    765 			offset = reader.readUShort()
    766 			if last != 0xFFFF:
    767 				dataReader = reader.getSubReader(0)  # relative to current position
    768 				dataReader.seek(pos + offset)  # relative to start of table
    769 				data = self.converter.readArray(
    770 					dataReader, font, tableDict=None,
    771 					count=last - first + 1)
    772 				for k, v in enumerate(data):
    773 					mapping[font.getGlyphName(first + k)] = v
    774 		return mapping
    775 
    776 	def readFormat6(self, reader, font):
    777 		mapping = {}
    778 		pos = reader.pos - 2  # start of table is at UShort for format
    779 		unitSize = reader.readUShort()
    780 		assert unitSize >= 2 + self.converter.staticSize, unitSize
    781 		for i in range(reader.readUShort()):
    782 			reader.seek(pos + i * unitSize + 12)
    783 			glyphID = reader.readUShort()
    784 			value = self.converter.read(
    785 				reader, font, tableDict=None)
    786 			if glyphID != 0xFFFF:
    787 				mapping[font.getGlyphName(glyphID)] = value
    788 		return mapping
    789 
    790 	def readFormat8(self, reader, font):
    791 		first = reader.readUShort()
    792 		count = reader.readUShort()
    793 		data = self.converter.readArray(
    794 			reader, font, tableDict=None, count=count)
    795 		return {font.getGlyphName(first + k): value
    796 		        for (k, value) in enumerate(data)}
    797 
    798 	def xmlRead(self, attrs, content, font):
    799 		value = {}
    800 		for element in content:
    801 			if isinstance(element, tuple):
    802 				name, a, eltContent = element
    803 				if name == "Lookup":
    804 					value[a["glyph"]] = self.converter.xmlRead(a, eltContent, font)
    805 		return value
    806 
    807 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    808 		xmlWriter.begintag(name, attrs)
    809 		xmlWriter.newline()
    810 		for glyph, value in sorted(value.items()):
    811 			self.converter.xmlWrite(
    812 				xmlWriter, font, value=value,
    813 				name="Lookup", attrs=[("glyph", glyph)])
    814 		xmlWriter.endtag(name)
    815 		xmlWriter.newline()
    816 
    817 
    818 # The AAT 'ankr' table has an unusual structure: An offset to an AATLookup
    819 # followed by an offset to a glyph data table. Other than usual, the
    820 # offsets in the AATLookup are not relative to the beginning of
    821 # the beginning of the 'ankr' table, but relative to the glyph data table.
    822 # So, to find the anchor data for a glyph, one needs to add the offset
    823 # to the data table to the offset found in the AATLookup, and then use
    824 # the sum of these two offsets to find the actual data.
    825 class AATLookupWithDataOffset(BaseConverter):
    826 	def read(self, reader, font, tableDict):
    827 		lookupOffset = reader.readULong()
    828 		dataOffset = reader.readULong()
    829 		lookupReader = reader.getSubReader(lookupOffset)
    830 		lookup = AATLookup('DataOffsets', None, None, UShort)
    831 		offsets = lookup.read(lookupReader, font, tableDict)
    832 		result = {}
    833 		for glyph, offset in offsets.items():
    834 			dataReader = reader.getSubReader(offset + dataOffset)
    835 			item = self.tableClass()
    836 			item.decompile(dataReader, font)
    837 			result[glyph] = item
    838 		return result
    839 
    840 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    841 		# We do not work with OTTableWriter sub-writers because
    842 		# the offsets in our AATLookup are relative to our data
    843 		# table, for which we need to provide an offset value itself.
    844 		# It might have been possible to somehow make a kludge for
    845 		# performing this indirect offset computation directly inside
    846 		# OTTableWriter. But this would have made the internal logic
    847 		# of OTTableWriter even more complex than it already is,
    848 		# so we decided to roll our own offset computation for the
    849 		# contents of the AATLookup and associated data table.
    850 		offsetByGlyph, offsetByData, dataLen = {}, {}, 0
    851 		compiledData = []
    852 		for glyph in sorted(value, key=font.getGlyphID):
    853 			subWriter = OTTableWriter()
    854 			value[glyph].compile(subWriter, font)
    855 			data = subWriter.getAllData()
    856 			offset = offsetByData.get(data, None)
    857 			if offset == None:
    858 				offset = dataLen
    859 				dataLen = dataLen + len(data)
    860 				offsetByData[data] = offset
    861 				compiledData.append(data)
    862 			offsetByGlyph[glyph] = offset
    863 		# For calculating the offsets to our AATLookup and data table,
    864 		# we can use the regular OTTableWriter infrastructure.
    865 		lookupWriter = writer.getSubWriter()
    866 		lookupWriter.longOffset = True
    867 		lookup = AATLookup('DataOffsets', None, None, UShort)
    868 		lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
    869 
    870 		dataWriter = writer.getSubWriter()
    871 		dataWriter.longOffset = True
    872 		writer.writeSubTable(lookupWriter)
    873 		writer.writeSubTable(dataWriter)
    874 		for d in compiledData:
    875 			dataWriter.writeData(d)
    876 
    877 	def xmlRead(self, attrs, content, font):
    878 		lookup = AATLookup('DataOffsets', None, None, self.tableClass)
    879 		return lookup.xmlRead(attrs, content, font)
    880 
    881 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    882 		lookup = AATLookup('DataOffsets', None, None, self.tableClass)
    883 		lookup.xmlWrite(xmlWriter, font, value, name, attrs)
    884 
    885 
    886 class MorxSubtableConverter(BaseConverter):
    887 	_PROCESSING_ORDERS = {
    888 		# bits 30 and 28 of morx.CoverageFlags; see morx spec
    889 		(False, False): "LayoutOrder",
    890 		(True, False): "ReversedLayoutOrder",
    891 		(False, True): "LogicalOrder",
    892 		(True, True): "ReversedLogicalOrder",
    893 	}
    894 
    895 	_PROCESSING_ORDERS_REVERSED = {
    896 		val: key for key, val in _PROCESSING_ORDERS.items()
    897 	}
    898 
    899 	def __init__(self, name, repeat, aux):
    900 		BaseConverter.__init__(self, name, repeat, aux)
    901 
    902 	def _setTextDirectionFromCoverageFlags(self, flags, subtable):
    903 		if (flags & 0x20) != 0:
    904 			subtable.TextDirection = "Any"
    905 		elif (flags & 0x80) != 0:
    906 			subtable.TextDirection = "Vertical"
    907 		else:
    908 			subtable.TextDirection = "Horizontal"
    909 
    910 	def read(self, reader, font, tableDict):
    911 		pos = reader.pos
    912 		m = MorxSubtable()
    913 		m.StructLength = reader.readULong()
    914 		flags = reader.readUInt8()
    915 		orderKey = ((flags & 0x40) != 0, (flags & 0x10) != 0)
    916 		m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
    917 		self._setTextDirectionFromCoverageFlags(flags, m)
    918 		m.Reserved = reader.readUShort()
    919 		m.Reserved |= (flags & 0xF) << 16
    920 		m.MorphType = reader.readUInt8()
    921 		m.SubFeatureFlags = reader.readULong()
    922 		tableClass = lookupTypes["morx"].get(m.MorphType)
    923 		if tableClass is None:
    924 			assert False, ("unsupported 'morx' lookup type %s" %
    925 			               m.MorphType)
    926 		# To decode AAT ligatures, we need to know the subtable size.
    927 		# The easiest way to pass this along is to create a new reader
    928 		# that works on just the subtable as its data.
    929 		headerLength = reader.pos - pos
    930 		data = reader.data[
    931 			reader.pos
    932 			: reader.pos + m.StructLength - headerLength]
    933 		assert len(data) == m.StructLength - headerLength
    934 		subReader = OTTableReader(data=data, tableTag=reader.tableTag)
    935 		m.SubStruct = tableClass()
    936 		m.SubStruct.decompile(subReader, font)
    937 		reader.seek(pos + m.StructLength)
    938 		return m
    939 
    940 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    941 		xmlWriter.begintag(name, attrs)
    942 		xmlWriter.newline()
    943 		xmlWriter.comment("StructLength=%d" % value.StructLength)
    944 		xmlWriter.newline()
    945 		xmlWriter.simpletag("TextDirection", value=value.TextDirection)
    946 		xmlWriter.newline()
    947 		xmlWriter.simpletag("ProcessingOrder",
    948 		                    value=value.ProcessingOrder)
    949 		xmlWriter.newline()
    950 		if value.Reserved != 0:
    951 			xmlWriter.simpletag("Reserved",
    952 			                    value="0x%04x" % value.Reserved)
    953 			xmlWriter.newline()
    954 		xmlWriter.comment("MorphType=%d" % value.MorphType)
    955 		xmlWriter.newline()
    956 		xmlWriter.simpletag("SubFeatureFlags",
    957 		                    value="0x%08x" % value.SubFeatureFlags)
    958 		xmlWriter.newline()
    959 		value.SubStruct.toXML(xmlWriter, font)
    960 		xmlWriter.endtag(name)
    961 		xmlWriter.newline()
    962 
    963 	def xmlRead(self, attrs, content, font):
    964 		m = MorxSubtable()
    965 		covFlags = 0
    966 		m.Reserved = 0
    967 		for eltName, eltAttrs, eltContent in filter(istuple, content):
    968 			if eltName == "CoverageFlags":
    969 				# Only in XML from old versions of fonttools.
    970 				covFlags = safeEval(eltAttrs["value"])
    971 				orderKey = ((covFlags & 0x40) != 0,
    972 				            (covFlags & 0x10) != 0)
    973 				m.ProcessingOrder = self._PROCESSING_ORDERS[
    974 					orderKey]
    975 				self._setTextDirectionFromCoverageFlags(
    976 					covFlags, m)
    977 			elif eltName == "ProcessingOrder":
    978 				m.ProcessingOrder = eltAttrs["value"]
    979 				assert m.ProcessingOrder in self._PROCESSING_ORDERS_REVERSED, "unknown ProcessingOrder: %s" % m.ProcessingOrder
    980 			elif eltName == "TextDirection":
    981 				m.TextDirection = eltAttrs["value"]
    982 				assert m.TextDirection in {"Horizontal", "Vertical", "Any"}, "unknown TextDirection %s" % m.TextDirection
    983 			elif eltName == "Reserved":
    984 				m.Reserved = safeEval(eltAttrs["value"])
    985 			elif eltName == "SubFeatureFlags":
    986 				m.SubFeatureFlags = safeEval(eltAttrs["value"])
    987 			elif eltName.endswith("Morph"):
    988 				m.fromXML(eltName, eltAttrs, eltContent, font)
    989 			else:
    990 				assert False, eltName
    991 		m.Reserved = (covFlags & 0xF) << 16 | m.Reserved
    992 		return m
    993 
    994 	def write(self, writer, font, tableDict, value, repeatIndex=None):
    995 		covFlags = (value.Reserved & 0x000F0000) >> 16
    996 		reverseOrder, logicalOrder = self._PROCESSING_ORDERS_REVERSED[
    997 			value.ProcessingOrder]
    998 		covFlags |= 0x80 if value.TextDirection == "Vertical" else 0
    999 		covFlags |= 0x40 if reverseOrder else 0
   1000 		covFlags |= 0x20 if value.TextDirection == "Any" else 0
   1001 		covFlags |= 0x10 if logicalOrder else 0
   1002 		value.CoverageFlags = covFlags
   1003 		lengthIndex = len(writer.items)
   1004 		before = writer.getDataLength()
   1005 		value.StructLength = 0xdeadbeef
   1006 		# The high nibble of value.Reserved is actuallly encoded
   1007 		# into coverageFlags, so we need to clear it here.
   1008 		origReserved = value.Reserved # including high nibble
   1009 		value.Reserved = value.Reserved & 0xFFFF # without high nibble
   1010 		value.compile(writer, font)
   1011 		value.Reserved = origReserved  # restore original value
   1012 		assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"
   1013 		length = writer.getDataLength() - before
   1014 		writer.items[lengthIndex] = struct.pack(">L", length)
   1015 
   1016 
   1017 # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader
   1018 # TODO: Untangle the implementation of the various lookup-specific formats.
   1019 class STXHeader(BaseConverter):
   1020 	def __init__(self, name, repeat, aux, tableClass):
   1021 		BaseConverter.__init__(self, name, repeat, aux, tableClass)
   1022 		assert issubclass(self.tableClass, AATAction)
   1023 		self.classLookup = AATLookup("GlyphClasses", None, None, UShort)
   1024 		if issubclass(self.tableClass, ContextualMorphAction):
   1025 			self.perGlyphLookup = AATLookup("PerGlyphLookup",
   1026 			                                None, None, GlyphID)
   1027 		else:
   1028 			self.perGlyphLookup = None
   1029 
   1030 	def read(self, reader, font, tableDict):
   1031 		table = AATStateTable()
   1032 		pos = reader.pos
   1033 		classTableReader = reader.getSubReader(0)
   1034 		stateArrayReader = reader.getSubReader(0)
   1035 		entryTableReader = reader.getSubReader(0)
   1036 		actionReader = None
   1037 		ligaturesReader = None
   1038 		table.GlyphClassCount = reader.readULong()
   1039 		classTableReader.seek(pos + reader.readULong())
   1040 		stateArrayReader.seek(pos + reader.readULong())
   1041 		entryTableReader.seek(pos + reader.readULong())
   1042 		if self.perGlyphLookup is not None:
   1043 			perGlyphTableReader = reader.getSubReader(0)
   1044 			perGlyphTableReader.seek(pos + reader.readULong())
   1045 		if issubclass(self.tableClass, LigatureMorphAction):
   1046 			actionReader = reader.getSubReader(0)
   1047 			actionReader.seek(pos + reader.readULong())
   1048 			ligComponentReader = reader.getSubReader(0)
   1049 			ligComponentReader.seek(pos + reader.readULong())
   1050 			ligaturesReader = reader.getSubReader(0)
   1051 			ligaturesReader.seek(pos + reader.readULong())
   1052 			numLigComponents = (ligaturesReader.pos
   1053 			                    - ligComponentReader.pos) // 2
   1054 			assert numLigComponents >= 0
   1055 			table.LigComponents = \
   1056 				ligComponentReader.readUShortArray(numLigComponents)
   1057 			table.Ligatures = self._readLigatures(ligaturesReader, font)
   1058 		elif issubclass(self.tableClass, InsertionMorphAction):
   1059 			actionReader = reader.getSubReader(0)
   1060 			actionReader.seek(pos + reader.readULong())
   1061 		table.GlyphClasses = self.classLookup.read(classTableReader,
   1062 		                                           font, tableDict)
   1063 		numStates = int((entryTableReader.pos - stateArrayReader.pos)
   1064 		                 / (table.GlyphClassCount * 2))
   1065 		for stateIndex in range(numStates):
   1066 			state = AATState()
   1067 			table.States.append(state)
   1068 			for glyphClass in range(table.GlyphClassCount):
   1069 				entryIndex = stateArrayReader.readUShort()
   1070 				state.Transitions[glyphClass] = \
   1071 					self._readTransition(entryTableReader,
   1072 					                     entryIndex, font,
   1073 					                     actionReader)
   1074 		if self.perGlyphLookup is not None:
   1075 			table.PerGlyphLookups = self._readPerGlyphLookups(
   1076 				table, perGlyphTableReader, font)
   1077 		return table
   1078 
   1079 	def _readTransition(self, reader, entryIndex, font, actionReader):
   1080 		transition = self.tableClass()
   1081 		entryReader = reader.getSubReader(
   1082 			reader.pos + entryIndex * transition.staticSize)
   1083 		transition.decompile(entryReader, font, actionReader)
   1084 		return transition
   1085 
   1086 	def _readLigatures(self, reader, font):
   1087 		limit = len(reader.data)
   1088 		numLigatureGlyphs = (limit - reader.pos) // 2
   1089 		return [font.getGlyphName(g)
   1090 		        for g in reader.readUShortArray(numLigatureGlyphs)]
   1091 
   1092 	def _countPerGlyphLookups(self, table):
   1093 		# Somewhat annoyingly, the morx table does not encode
   1094 		# the size of the per-glyph table. So we need to find
   1095 		# the maximum value that MorphActions use as index
   1096 		# into this table.
   1097 		numLookups = 0
   1098 		for state in table.States:
   1099 			for t in state.Transitions.values():
   1100 				if isinstance(t, ContextualMorphAction):
   1101 					if t.MarkIndex != 0xFFFF:
   1102 						numLookups = max(
   1103 							numLookups,
   1104 							t.MarkIndex + 1)
   1105 					if t.CurrentIndex != 0xFFFF:
   1106 						numLookups = max(
   1107 							numLookups,
   1108 							t.CurrentIndex + 1)
   1109 		return numLookups
   1110 
   1111 	def _readPerGlyphLookups(self, table, reader, font):
   1112 		pos = reader.pos
   1113 		lookups = []
   1114 		for _ in range(self._countPerGlyphLookups(table)):
   1115 			lookupReader = reader.getSubReader(0)
   1116 			lookupReader.seek(pos + reader.readULong())
   1117 			lookups.append(
   1118 				self.perGlyphLookup.read(lookupReader, font, {}))
   1119 		return lookups
   1120 
   1121 	def write(self, writer, font, tableDict, value, repeatIndex=None):
   1122 		glyphClassWriter = OTTableWriter()
   1123 		self.classLookup.write(glyphClassWriter, font, tableDict,
   1124 		                       value.GlyphClasses, repeatIndex=None)
   1125 		glyphClassData = pad(glyphClassWriter.getAllData(), 2)
   1126 		glyphClassCount = max(value.GlyphClasses.values()) + 1
   1127 		glyphClassTableOffset = 16  # size of STXHeader
   1128 		if self.perGlyphLookup is not None:
   1129 			glyphClassTableOffset += 4
   1130 
   1131 		glyphClassTableOffset += self.tableClass.actionHeaderSize
   1132 		actionData, actionIndex = \
   1133 			self.tableClass.compileActions(font, value.States)
   1134 		stateArrayData, entryTableData = self._compileStates(
   1135 			font, value.States, glyphClassCount, actionIndex)
   1136 		stateArrayOffset = glyphClassTableOffset + len(glyphClassData)
   1137 		entryTableOffset = stateArrayOffset + len(stateArrayData)
   1138 		perGlyphOffset = entryTableOffset + len(entryTableData)
   1139 		perGlyphData = \
   1140 			pad(self._compilePerGlyphLookups(value, font), 4)
   1141 		if actionData is not None:
   1142 			actionOffset = entryTableOffset + len(entryTableData)
   1143 		else:
   1144 			actionOffset = None
   1145 
   1146 		ligaturesOffset, ligComponentsOffset = None, None
   1147 		ligComponentsData = self._compileLigComponents(value, font)
   1148 		ligaturesData = self._compileLigatures(value, font)
   1149 		if ligComponentsData is not None:
   1150 			assert len(perGlyphData) == 0
   1151 			ligComponentsOffset = actionOffset + len(actionData)
   1152 			ligaturesOffset = ligComponentsOffset + len(ligComponentsData)
   1153 
   1154 		writer.writeULong(glyphClassCount)
   1155 		writer.writeULong(glyphClassTableOffset)
   1156 		writer.writeULong(stateArrayOffset)
   1157 		writer.writeULong(entryTableOffset)
   1158 		if self.perGlyphLookup is not None:
   1159 			writer.writeULong(perGlyphOffset)
   1160 		if actionOffset is not None:
   1161 			writer.writeULong(actionOffset)
   1162 		if ligComponentsOffset is not None:
   1163 			writer.writeULong(ligComponentsOffset)
   1164 			writer.writeULong(ligaturesOffset)
   1165 		writer.writeData(glyphClassData)
   1166 		writer.writeData(stateArrayData)
   1167 		writer.writeData(entryTableData)
   1168 		writer.writeData(perGlyphData)
   1169 		if actionData is not None:
   1170 			writer.writeData(actionData)
   1171 		if ligComponentsData is not None:
   1172 			writer.writeData(ligComponentsData)
   1173 		if ligaturesData is not None:
   1174 			writer.writeData(ligaturesData)
   1175 
   1176 	def _compileStates(self, font, states, glyphClassCount, actionIndex):
   1177 		stateArrayWriter = OTTableWriter()
   1178 		entries, entryIDs = [], {}
   1179 		for state in states:
   1180 			for glyphClass in range(glyphClassCount):
   1181 				transition = state.Transitions[glyphClass]
   1182 				entryWriter = OTTableWriter()
   1183 				transition.compile(entryWriter, font,
   1184 				                   actionIndex)
   1185 				entryData = entryWriter.getAllData()
   1186 				assert len(entryData)  == transition.staticSize, ( \
   1187 					"%s has staticSize %d, "
   1188 					"but actually wrote %d bytes" % (
   1189 						repr(transition),
   1190 						transition.staticSize,
   1191 						len(entryData)))
   1192 				entryIndex = entryIDs.get(entryData)
   1193 				if entryIndex is None:
   1194 					entryIndex = len(entries)
   1195 					entryIDs[entryData] = entryIndex
   1196 					entries.append(entryData)
   1197 				stateArrayWriter.writeUShort(entryIndex)
   1198 		stateArrayData = pad(stateArrayWriter.getAllData(), 4)
   1199 		entryTableData = pad(bytesjoin(entries), 4)
   1200 		return stateArrayData, entryTableData
   1201 
   1202 	def _compilePerGlyphLookups(self, table, font):
   1203 		if self.perGlyphLookup is None:
   1204 			return b""
   1205 		numLookups = self._countPerGlyphLookups(table)
   1206 		assert len(table.PerGlyphLookups) == numLookups, (
   1207 			"len(AATStateTable.PerGlyphLookups) is %d, "
   1208 			"but the actions inside the table refer to %d" %
   1209 				(len(table.PerGlyphLookups), numLookups))
   1210 		writer = OTTableWriter()
   1211 		for lookup in table.PerGlyphLookups:
   1212 			lookupWriter = writer.getSubWriter()
   1213 			lookupWriter.longOffset = True
   1214 			self.perGlyphLookup.write(lookupWriter, font,
   1215 			                          {}, lookup, None)
   1216 			writer.writeSubTable(lookupWriter)
   1217 		return writer.getAllData()
   1218 
   1219 	def _compileLigComponents(self, table, font):
   1220 		if not hasattr(table, "LigComponents"):
   1221 			return None
   1222 		writer = OTTableWriter()
   1223 		for component in table.LigComponents:
   1224 			writer.writeUShort(component)
   1225 		return writer.getAllData()
   1226 
   1227 	def _compileLigatures(self, table, font):
   1228 		if not hasattr(table, "Ligatures"):
   1229 			return None
   1230 		writer = OTTableWriter()
   1231 		for glyphName in table.Ligatures:
   1232 			writer.writeUShort(font.getGlyphID(glyphName))
   1233 		return writer.getAllData()
   1234 
   1235 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
   1236 		xmlWriter.begintag(name, attrs)
   1237 		xmlWriter.newline()
   1238 		xmlWriter.comment("GlyphClassCount=%s" %value.GlyphClassCount)
   1239 		xmlWriter.newline()
   1240 		for g, klass in sorted(value.GlyphClasses.items()):
   1241 			xmlWriter.simpletag("GlyphClass", glyph=g, value=klass)
   1242 			xmlWriter.newline()
   1243 		for stateIndex, state in enumerate(value.States):
   1244 			xmlWriter.begintag("State", index=stateIndex)
   1245 			xmlWriter.newline()
   1246 			for glyphClass, trans in sorted(state.Transitions.items()):
   1247 				trans.toXML(xmlWriter, font=font,
   1248 				            attrs={"onGlyphClass": glyphClass},
   1249 				            name="Transition")
   1250 			xmlWriter.endtag("State")
   1251 			xmlWriter.newline()
   1252 		for i, lookup in enumerate(value.PerGlyphLookups):
   1253 			xmlWriter.begintag("PerGlyphLookup", index=i)
   1254 			xmlWriter.newline()
   1255 			for glyph, val in sorted(lookup.items()):
   1256 				xmlWriter.simpletag("Lookup", glyph=glyph,
   1257 				                    value=val)
   1258 				xmlWriter.newline()
   1259 			xmlWriter.endtag("PerGlyphLookup")
   1260 			xmlWriter.newline()
   1261 		if hasattr(value, "LigComponents"):
   1262 			xmlWriter.begintag("LigComponents")
   1263 			xmlWriter.newline()
   1264 			for i, val in enumerate(getattr(value, "LigComponents")):
   1265 				xmlWriter.simpletag("LigComponent", index=i,
   1266 				                    value=val)
   1267 				xmlWriter.newline()
   1268 			xmlWriter.endtag("LigComponents")
   1269 			xmlWriter.newline()
   1270 		self._xmlWriteLigatures(xmlWriter, font, value, name, attrs)
   1271 		xmlWriter.endtag(name)
   1272 		xmlWriter.newline()
   1273 
   1274 	def _xmlWriteLigatures(self, xmlWriter, font, value, name, attrs):
   1275 		if not hasattr(value, "Ligatures"):
   1276 			return
   1277 		xmlWriter.begintag("Ligatures")
   1278 		xmlWriter.newline()
   1279 		for i, g in enumerate(getattr(value, "Ligatures")):
   1280 			xmlWriter.simpletag("Ligature", index=i, glyph=g)
   1281 			xmlWriter.newline()
   1282 		xmlWriter.endtag("Ligatures")
   1283 		xmlWriter.newline()
   1284 
   1285 	def xmlRead(self, attrs, content, font):
   1286 		table = AATStateTable()
   1287 		for eltName, eltAttrs, eltContent in filter(istuple, content):
   1288 			if eltName == "GlyphClass":
   1289 				glyph = eltAttrs["glyph"]
   1290 				value = eltAttrs["value"]
   1291 				table.GlyphClasses[glyph] = safeEval(value)
   1292 			elif eltName == "State":
   1293 				state = self._xmlReadState(eltAttrs, eltContent, font)
   1294 				table.States.append(state)
   1295 			elif eltName == "PerGlyphLookup":
   1296 				lookup = self.perGlyphLookup.xmlRead(
   1297 					eltAttrs, eltContent, font)
   1298 				table.PerGlyphLookups.append(lookup)
   1299 			elif eltName == "LigComponents":
   1300 				table.LigComponents = \
   1301 					self._xmlReadLigComponents(
   1302 						eltAttrs, eltContent, font)
   1303 			elif eltName == "Ligatures":
   1304 				table.Ligatures = \
   1305 					self._xmlReadLigatures(
   1306 						eltAttrs, eltContent, font)
   1307 		table.GlyphClassCount = max(table.GlyphClasses.values()) + 1
   1308 		return table
   1309 
   1310 	def _xmlReadState(self, attrs, content, font):
   1311 		state = AATState()
   1312 		for eltName, eltAttrs, eltContent in filter(istuple, content):
   1313 			if eltName == "Transition":
   1314 				glyphClass = safeEval(eltAttrs["onGlyphClass"])
   1315 				transition = self.tableClass()
   1316 				transition.fromXML(eltName, eltAttrs,
   1317 				                   eltContent, font)
   1318 				state.Transitions[glyphClass] = transition
   1319 		return state
   1320 
   1321 	def _xmlReadLigComponents(self, attrs, content, font):
   1322 		ligComponents = []
   1323 		for eltName, eltAttrs, _eltContent in filter(istuple, content):
   1324 			if eltName == "LigComponent":
   1325 				ligComponents.append(
   1326 					safeEval(eltAttrs["value"]))
   1327 		return ligComponents
   1328 
   1329 	def _xmlReadLigatures(self, attrs, content, font):
   1330 		ligs = []
   1331 		for eltName, eltAttrs, _eltContent in filter(istuple, content):
   1332 			if eltName == "Ligature":
   1333 				ligs.append(eltAttrs["glyph"])
   1334 		return ligs
   1335 
   1336 
   1337 class CIDGlyphMap(BaseConverter):
   1338 	def read(self, reader, font, tableDict):
   1339 		numCIDs = reader.readUShort()
   1340 		result = {}
   1341 		for cid, glyphID in enumerate(reader.readUShortArray(numCIDs)):
   1342 			if glyphID != 0xFFFF:
   1343 				result[cid] = font.getGlyphName(glyphID)
   1344 		return result
   1345 
   1346 	def write(self, writer, font, tableDict, value, repeatIndex=None):
   1347 		items = {cid: font.getGlyphID(glyph)
   1348 		         for cid, glyph in value.items()}
   1349 		count = max(items) + 1 if items else 0
   1350 		writer.writeUShort(count)
   1351 		for cid in range(count):
   1352 			writer.writeUShort(items.get(cid, 0xFFFF))
   1353 
   1354 	def xmlRead(self, attrs, content, font):
   1355 		result = {}
   1356 		for eName, eAttrs, _eContent in filter(istuple, content):
   1357 			if eName == "CID":
   1358 				result[safeEval(eAttrs["cid"])] = \
   1359 					eAttrs["glyph"].strip()
   1360 		return result
   1361 
   1362 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
   1363 		xmlWriter.begintag(name, attrs)
   1364 		xmlWriter.newline()
   1365 		for cid, glyph in sorted(value.items()):
   1366 			if glyph is not None and glyph != 0xFFFF:
   1367 				xmlWriter.simpletag(
   1368 					"CID", cid=cid, glyph=glyph)
   1369 				xmlWriter.newline()
   1370 		xmlWriter.endtag(name)
   1371 		xmlWriter.newline()
   1372 
   1373 
   1374 class GlyphCIDMap(BaseConverter):
   1375 	def read(self, reader, font, tableDict):
   1376 		glyphOrder = font.getGlyphOrder()
   1377 		count = reader.readUShort()
   1378 		cids = reader.readUShortArray(count)
   1379 		if count > len(glyphOrder):
   1380 			log.warning("GlyphCIDMap has %d elements, "
   1381 			            "but the font has only %d glyphs; "
   1382 			            "ignoring the rest" %
   1383 			             (count, len(glyphOrder)))
   1384 		result = {}
   1385 		for glyphID in range(min(len(cids), len(glyphOrder))):
   1386 			cid = cids[glyphID]
   1387 			if cid != 0xFFFF:
   1388 				result[glyphOrder[glyphID]] = cid
   1389 		return result
   1390 
   1391 	def write(self, writer, font, tableDict, value, repeatIndex=None):
   1392 		items = {font.getGlyphID(g): cid
   1393 		         for g, cid in value.items()
   1394 		         if cid is not None and cid != 0xFFFF}
   1395 		count = max(items) + 1 if items else 0
   1396 		writer.writeUShort(count)
   1397 		for glyphID in range(count):
   1398 			writer.writeUShort(items.get(glyphID, 0xFFFF))
   1399 
   1400 	def xmlRead(self, attrs, content, font):
   1401 		result = {}
   1402 		for eName, eAttrs, _eContent in filter(istuple, content):
   1403 			if eName == "CID":
   1404 				result[eAttrs["glyph"]] = \
   1405 					safeEval(eAttrs["value"])
   1406 		return result
   1407 
   1408 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
   1409 		xmlWriter.begintag(name, attrs)
   1410 		xmlWriter.newline()
   1411 		for glyph, cid in sorted(value.items()):
   1412 			if cid is not None and cid != 0xFFFF:
   1413 				xmlWriter.simpletag(
   1414 					"CID", glyph=glyph, value=cid)
   1415 				xmlWriter.newline()
   1416 		xmlWriter.endtag(name)
   1417 		xmlWriter.newline()
   1418 
   1419 
   1420 class DeltaValue(BaseConverter):
   1421 
   1422 	def read(self, reader, font, tableDict):
   1423 		StartSize = tableDict["StartSize"]
   1424 		EndSize = tableDict["EndSize"]
   1425 		DeltaFormat = tableDict["DeltaFormat"]
   1426 		assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
   1427 		nItems = EndSize - StartSize + 1
   1428 		nBits = 1 << DeltaFormat
   1429 		minusOffset = 1 << nBits
   1430 		mask = (1 << nBits) - 1
   1431 		signMask = 1 << (nBits - 1)
   1432 
   1433 		DeltaValue = []
   1434 		tmp, shift = 0, 0
   1435 		for i in range(nItems):
   1436 			if shift == 0:
   1437 				tmp, shift = reader.readUShort(), 16
   1438 			shift = shift - nBits
   1439 			value = (tmp >> shift) & mask
   1440 			if value & signMask:
   1441 				value = value - minusOffset
   1442 			DeltaValue.append(value)
   1443 		return DeltaValue
   1444 
   1445 	def write(self, writer, font, tableDict, value, repeatIndex=None):
   1446 		StartSize = tableDict["StartSize"]
   1447 		EndSize = tableDict["EndSize"]
   1448 		DeltaFormat = tableDict["DeltaFormat"]
   1449 		DeltaValue = value
   1450 		assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
   1451 		nItems = EndSize - StartSize + 1
   1452 		nBits = 1 << DeltaFormat
   1453 		assert len(DeltaValue) == nItems
   1454 		mask = (1 << nBits) - 1
   1455 
   1456 		tmp, shift = 0, 16
   1457 		for value in DeltaValue:
   1458 			shift = shift - nBits
   1459 			tmp = tmp | ((value & mask) << shift)
   1460 			if shift == 0:
   1461 				writer.writeUShort(tmp)
   1462 				tmp, shift = 0, 16
   1463 		if shift != 16:
   1464 			writer.writeUShort(tmp)
   1465 
   1466 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
   1467 		xmlWriter.simpletag(name, attrs + [("value", value)])
   1468 		xmlWriter.newline()
   1469 
   1470 	def xmlRead(self, attrs, content, font):
   1471 		return safeEval(attrs["value"])
   1472 
   1473 
   1474 class VarIdxMapValue(BaseConverter):
   1475 
   1476 	def read(self, reader, font, tableDict):
   1477 		fmt = tableDict['EntryFormat']
   1478 		nItems = tableDict['MappingCount']
   1479 
   1480 		innerBits = 1 + (fmt & 0x000F)
   1481 		innerMask = (1<<innerBits) - 1
   1482 		outerMask = 0xFFFFFFFF - innerMask
   1483 		outerShift = 16 - innerBits
   1484 
   1485 		entrySize = 1 + ((fmt & 0x0030) >> 4)
   1486 		read = {
   1487 			1: reader.readUInt8,
   1488 			2: reader.readUShort,
   1489 			3: reader.readUInt24,
   1490 			4: reader.readULong,
   1491 		}[entrySize]
   1492 
   1493 		mapping = []
   1494 		for i in range(nItems):
   1495 			raw = read()
   1496 			idx = ((raw & outerMask) << outerShift) | (raw & innerMask)
   1497 			mapping.append(idx)
   1498 
   1499 		return mapping
   1500 
   1501 	def write(self, writer, font, tableDict, value, repeatIndex=None):
   1502 		fmt = tableDict['EntryFormat']
   1503 		mapping = value
   1504 		writer['MappingCount'].setValue(len(mapping))
   1505 
   1506 		innerBits = 1 + (fmt & 0x000F)
   1507 		innerMask = (1<<innerBits) - 1
   1508 		outerShift = 16 - innerBits
   1509 
   1510 		entrySize = 1 + ((fmt & 0x0030) >> 4)
   1511 		write = {
   1512 			1: writer.writeUInt8,
   1513 			2: writer.writeUShort,
   1514 			3: writer.writeUInt24,
   1515 			4: writer.writeULong,
   1516 		}[entrySize]
   1517 
   1518 		for idx in mapping:
   1519 			raw = ((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask)
   1520 			write(raw)
   1521 
   1522 
   1523 class VarDataValue(BaseConverter):
   1524 
   1525 	def read(self, reader, font, tableDict):
   1526 		values = []
   1527 
   1528 		regionCount = tableDict["VarRegionCount"]
   1529 		shortCount = tableDict["NumShorts"]
   1530 
   1531 		for i in range(min(regionCount, shortCount)):
   1532 			values.append(reader.readShort())
   1533 		for i in range(min(regionCount, shortCount), regionCount):
   1534 			values.append(reader.readInt8())
   1535 		for i in range(regionCount, shortCount):
   1536 			reader.readInt8()
   1537 
   1538 		return values
   1539 
   1540 	def write(self, writer, font, tableDict, value, repeatIndex=None):
   1541 		regionCount = tableDict["VarRegionCount"]
   1542 		shortCount = tableDict["NumShorts"]
   1543 
   1544 		for i in range(min(regionCount, shortCount)):
   1545 			writer.writeShort(value[i])
   1546 		for i in range(min(regionCount, shortCount), regionCount):
   1547 			writer.writeInt8(value[i])
   1548 		for i in range(regionCount, shortCount):
   1549 			writer.writeInt8(0)
   1550 
   1551 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
   1552 		xmlWriter.simpletag(name, attrs + [("value", value)])
   1553 		xmlWriter.newline()
   1554 
   1555 	def xmlRead(self, attrs, content, font):
   1556 		return safeEval(attrs["value"])
   1557 
   1558 
   1559 converterMapping = {
   1560 	# type		class
   1561 	"int8":		Int8,
   1562 	"int16":	Short,
   1563 	"uint8":	UInt8,
   1564 	"uint8":	UInt8,
   1565 	"uint16":	UShort,
   1566 	"uint24":	UInt24,
   1567 	"uint32":	ULong,
   1568 	"char64":	Char64,
   1569 	"Flags32":	Flags32,
   1570 	"Version":	Version,
   1571 	"Tag":		Tag,
   1572 	"GlyphID":	GlyphID,
   1573 	"NameID":	NameID,
   1574 	"DeciPoints":	DeciPoints,
   1575 	"Fixed":	Fixed,
   1576 	"F2Dot14":	F2Dot14,
   1577 	"struct":	Struct,
   1578 	"Offset":	Table,
   1579 	"LOffset":	LTable,
   1580 	"ValueRecord":	ValueRecord,
   1581 	"DeltaValue":	DeltaValue,
   1582 	"VarIdxMapValue":	VarIdxMapValue,
   1583 	"VarDataValue":	VarDataValue,
   1584 
   1585 	# AAT
   1586 	"CIDGlyphMap":	CIDGlyphMap,
   1587 	"GlyphCIDMap":	GlyphCIDMap,
   1588 	"MortChain":	StructWithLength,
   1589 	"MortSubtable": StructWithLength,
   1590 	"MorxChain":	StructWithLength,
   1591 	"MorxSubtable": MorxSubtableConverter,
   1592 
   1593 	# "Template" types
   1594 	"AATLookup":	lambda C: partial(AATLookup, tableClass=C),
   1595 	"AATLookupWithDataOffset":	lambda C: partial(AATLookupWithDataOffset, tableClass=C),
   1596 	"STXHeader":	lambda C: partial(STXHeader, tableClass=C),
   1597 	"OffsetTo":	lambda C: partial(Table, tableClass=C),
   1598 	"LOffsetTo":	lambda C: partial(LTable, tableClass=C),
   1599 }
   1600