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