• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc.fixedTools import (
4	fixedToFloat as fi2fl, floatToFixed as fl2fi, ensureVersionIsLong as fi2ve,
5	versionToFixed as ve2fi)
6from fontTools.misc.textTools import pad, safeEval
7from fontTools.ttLib import getSearchRange
8from .otBase import (CountReference, FormatSwitchingBaseTable,
9                     OTTableReader, OTTableWriter, ValueRecordFactory)
10from .otTables import (lookupTypes, AATStateTable, AATState, AATAction,
11                       ContextualMorphAction, LigatureMorphAction,
12                       InsertionMorphAction, MorxSubtable)
13from functools import partial
14import struct
15import logging
16
17
18log = logging.getLogger(__name__)
19istuple = lambda t: isinstance(t, tuple)
20
21
22def 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
80class _MissingItem(tuple):
81	__slots__ = ()
82
83
84try:
85	from collections import UserList
86except ImportError:
87	from UserList import UserList
88
89
90class _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
121class 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
183class 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
190class IntValue(SimpleValue):
191	def xmlRead(self, attrs, content, font):
192		return int(attrs["value"], 0)
193
194class 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
201class 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
208class Flags32(ULong):
209	def xmlWrite(self, xmlWriter, font, value, name, attrs):
210		xmlWriter.simpletag(name, attrs + [("value", "0x%08X" % value)])
211		xmlWriter.newline()
212
213class 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
220class 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
227class 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
234class 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
241class 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
248class 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
254class ComputedUInt8(ComputedInt, UInt8):
255	pass
256class ComputedUShort(ComputedInt, UShort):
257	pass
258class ComputedULong(ComputedInt, ULong):
259	pass
260
261class 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
268class 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
285class 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
301class FloatValue(SimpleValue):
302	def xmlRead(self, attrs, content, font):
303		return float(attrs["value"])
304
305class 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
313class 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
320class 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
327class 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
352class 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
383class 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
455class 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
483class 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
522class 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
533class 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
541class 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
549class 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
556class 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
562class 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
576class 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
595class 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.
825class 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
886class 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.
1019class 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
1337class 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
1374class 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
1420class 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
1474class 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
1523class 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
1559converterMapping = {
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