• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# coding: utf-8
2"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
3OpenType subtables.
4
5Most are constructed upon import from data in otData.py, all are populated with
6converter objects from otConverters.py.
7"""
8from enum import IntEnum
9import itertools
10from collections import namedtuple
11from fontTools.misc.py23 import bytesjoin
12from fontTools.misc.roundTools import otRound
13from fontTools.misc.textTools import pad, safeEval
14from .otBase import (
15	BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference,
16	getFormatSwitchingBaseTableClass,
17)
18from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
19import logging
20import struct
21
22
23log = logging.getLogger(__name__)
24
25
26class AATStateTable(object):
27	def __init__(self):
28		self.GlyphClasses = {}  # GlyphID --> GlyphClass
29		self.States = []  # List of AATState, indexed by state number
30		self.PerGlyphLookups = []  # [{GlyphID:GlyphID}, ...]
31
32
33class AATState(object):
34	def __init__(self):
35		self.Transitions = {}  # GlyphClass --> AATAction
36
37
38class AATAction(object):
39	_FLAGS = None
40
41	@staticmethod
42	def compileActions(font, states):
43		return (None, None)
44
45	def _writeFlagsToXML(self, xmlWriter):
46		flags = [f for f in self._FLAGS if self.__dict__[f]]
47		if flags:
48			xmlWriter.simpletag("Flags", value=",".join(flags))
49			xmlWriter.newline()
50		if self.ReservedFlags != 0:
51			xmlWriter.simpletag(
52				"ReservedFlags",
53				value='0x%04X' % self.ReservedFlags)
54			xmlWriter.newline()
55
56	def _setFlag(self, flag):
57		assert flag in self._FLAGS, "unsupported flag %s" % flag
58		self.__dict__[flag] = True
59
60
61class RearrangementMorphAction(AATAction):
62	staticSize = 4
63	actionHeaderSize = 0
64	_FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"]
65
66	_VERBS = {
67		0: "no change",
68		1: "Ax ⇒ xA",
69		2: "xD ⇒ Dx",
70		3: "AxD ⇒ DxA",
71		4: "ABx ⇒ xAB",
72		5: "ABx ⇒ xBA",
73		6: "xCD ⇒ CDx",
74		7: "xCD ⇒ DCx",
75		8: "AxCD ⇒ CDxA",
76		9: "AxCD ⇒ DCxA",
77		10: "ABxD ⇒ DxAB",
78		11: "ABxD ⇒ DxBA",
79		12: "ABxCD ⇒ CDxAB",
80		13: "ABxCD ⇒ CDxBA",
81		14: "ABxCD ⇒ DCxAB",
82		15: "ABxCD ⇒ DCxBA",
83        }
84
85	def __init__(self):
86		self.NewState = 0
87		self.Verb = 0
88		self.MarkFirst = False
89		self.DontAdvance = False
90		self.MarkLast = False
91		self.ReservedFlags = 0
92
93	def compile(self, writer, font, actionIndex):
94		assert actionIndex is None
95		writer.writeUShort(self.NewState)
96		assert self.Verb >= 0 and self.Verb <= 15, self.Verb
97		flags = self.Verb | self.ReservedFlags
98		if self.MarkFirst: flags |= 0x8000
99		if self.DontAdvance: flags |= 0x4000
100		if self.MarkLast: flags |= 0x2000
101		writer.writeUShort(flags)
102
103	def decompile(self, reader, font, actionReader):
104		assert actionReader is None
105		self.NewState = reader.readUShort()
106		flags = reader.readUShort()
107		self.Verb = flags & 0xF
108		self.MarkFirst = bool(flags & 0x8000)
109		self.DontAdvance = bool(flags & 0x4000)
110		self.MarkLast = bool(flags & 0x2000)
111		self.ReservedFlags = flags & 0x1FF0
112
113	def toXML(self, xmlWriter, font, attrs, name):
114		xmlWriter.begintag(name, **attrs)
115		xmlWriter.newline()
116		xmlWriter.simpletag("NewState", value=self.NewState)
117		xmlWriter.newline()
118		self._writeFlagsToXML(xmlWriter)
119		xmlWriter.simpletag("Verb", value=self.Verb)
120		verbComment = self._VERBS.get(self.Verb)
121		if verbComment is not None:
122			xmlWriter.comment(verbComment)
123		xmlWriter.newline()
124		xmlWriter.endtag(name)
125		xmlWriter.newline()
126
127	def fromXML(self, name, attrs, content, font):
128		self.NewState = self.Verb = self.ReservedFlags = 0
129		self.MarkFirst = self.DontAdvance = self.MarkLast = False
130		content = [t for t in content if isinstance(t, tuple)]
131		for eltName, eltAttrs, eltContent in content:
132			if eltName == "NewState":
133				self.NewState = safeEval(eltAttrs["value"])
134			elif eltName == "Verb":
135				self.Verb = safeEval(eltAttrs["value"])
136			elif eltName == "ReservedFlags":
137				self.ReservedFlags = safeEval(eltAttrs["value"])
138			elif eltName == "Flags":
139				for flag in eltAttrs["value"].split(","):
140					self._setFlag(flag.strip())
141
142
143class ContextualMorphAction(AATAction):
144	staticSize = 8
145	actionHeaderSize = 0
146	_FLAGS = ["SetMark", "DontAdvance"]
147
148	def __init__(self):
149		self.NewState = 0
150		self.SetMark, self.DontAdvance = False, False
151		self.ReservedFlags = 0
152		self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF
153
154	def compile(self, writer, font, actionIndex):
155		assert actionIndex is None
156		writer.writeUShort(self.NewState)
157		flags = self.ReservedFlags
158		if self.SetMark: flags |= 0x8000
159		if self.DontAdvance: flags |= 0x4000
160		writer.writeUShort(flags)
161		writer.writeUShort(self.MarkIndex)
162		writer.writeUShort(self.CurrentIndex)
163
164	def decompile(self, reader, font, actionReader):
165		assert actionReader is None
166		self.NewState = reader.readUShort()
167		flags = reader.readUShort()
168		self.SetMark = bool(flags & 0x8000)
169		self.DontAdvance = bool(flags & 0x4000)
170		self.ReservedFlags = flags & 0x3FFF
171		self.MarkIndex = reader.readUShort()
172		self.CurrentIndex = reader.readUShort()
173
174	def toXML(self, xmlWriter, font, attrs, name):
175		xmlWriter.begintag(name, **attrs)
176		xmlWriter.newline()
177		xmlWriter.simpletag("NewState", value=self.NewState)
178		xmlWriter.newline()
179		self._writeFlagsToXML(xmlWriter)
180		xmlWriter.simpletag("MarkIndex", value=self.MarkIndex)
181		xmlWriter.newline()
182		xmlWriter.simpletag("CurrentIndex",
183		                    value=self.CurrentIndex)
184		xmlWriter.newline()
185		xmlWriter.endtag(name)
186		xmlWriter.newline()
187
188	def fromXML(self, name, attrs, content, font):
189		self.NewState = self.ReservedFlags = 0
190		self.SetMark = self.DontAdvance = False
191		self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF
192		content = [t for t in content if isinstance(t, tuple)]
193		for eltName, eltAttrs, eltContent in content:
194			if eltName == "NewState":
195				self.NewState = safeEval(eltAttrs["value"])
196			elif eltName == "Flags":
197				for flag in eltAttrs["value"].split(","):
198					self._setFlag(flag.strip())
199			elif eltName == "ReservedFlags":
200				self.ReservedFlags = safeEval(eltAttrs["value"])
201			elif eltName == "MarkIndex":
202				self.MarkIndex = safeEval(eltAttrs["value"])
203			elif eltName == "CurrentIndex":
204				self.CurrentIndex = safeEval(eltAttrs["value"])
205
206
207class LigAction(object):
208	def __init__(self):
209		self.Store = False
210		# GlyphIndexDelta is a (possibly negative) delta that gets
211		# added to the glyph ID at the top of the AAT runtime
212		# execution stack. It is *not* a byte offset into the
213		# morx table. The result of the addition, which is performed
214		# at run time by the shaping engine, is an index into
215		# the ligature components table. See 'morx' specification.
216		# In the AAT specification, this field is called Offset;
217		# but its meaning is quite different from other offsets
218		# in either AAT or OpenType, so we use a different name.
219		self.GlyphIndexDelta = 0
220
221
222class LigatureMorphAction(AATAction):
223	staticSize = 6
224
225	# 4 bytes for each of {action,ligComponents,ligatures}Offset
226	actionHeaderSize = 12
227
228	_FLAGS = ["SetComponent", "DontAdvance"]
229
230	def __init__(self):
231		self.NewState = 0
232		self.SetComponent, self.DontAdvance = False, False
233		self.ReservedFlags = 0
234		self.Actions = []
235
236	def compile(self, writer, font, actionIndex):
237		assert actionIndex is not None
238		writer.writeUShort(self.NewState)
239		flags = self.ReservedFlags
240		if self.SetComponent: flags |= 0x8000
241		if self.DontAdvance: flags |= 0x4000
242		if len(self.Actions) > 0: flags |= 0x2000
243		writer.writeUShort(flags)
244		if len(self.Actions) > 0:
245			actions = self.compileLigActions()
246			writer.writeUShort(actionIndex[actions])
247		else:
248			writer.writeUShort(0)
249
250	def decompile(self, reader, font, actionReader):
251		assert actionReader is not None
252		self.NewState = reader.readUShort()
253		flags = reader.readUShort()
254		self.SetComponent = bool(flags & 0x8000)
255		self.DontAdvance = bool(flags & 0x4000)
256		performAction = bool(flags & 0x2000)
257		# As of 2017-09-12, the 'morx' specification says that
258		# the reserved bitmask in ligature subtables is 0x3FFF.
259		# However, the specification also defines a flag 0x2000,
260		# so the reserved value should actually be 0x1FFF.
261		# TODO: Report this specification bug to Apple.
262		self.ReservedFlags = flags & 0x1FFF
263		actionIndex = reader.readUShort()
264		if performAction:
265			self.Actions = self._decompileLigActions(
266				actionReader, actionIndex)
267		else:
268			self.Actions = []
269
270	@staticmethod
271	def compileActions(font, states):
272		result, actions, actionIndex = b"", set(), {}
273		for state in states:
274			for _glyphClass, trans in state.Transitions.items():
275				actions.add(trans.compileLigActions())
276		# Sort the compiled actions in decreasing order of
277		# length, so that the longer sequence come before the
278		# shorter ones.  For each compiled action ABCD, its
279		# suffixes BCD, CD, and D do not be encoded separately
280		# (in case they occur); instead, we can just store an
281		# index that points into the middle of the longer
282		# sequence. Every compiled AAT ligature sequence is
283		# terminated with an end-of-sequence flag, which can
284		# only be set on the last element of the sequence.
285		# Therefore, it is sufficient to consider just the
286		# suffixes.
287		for a in sorted(actions, key=lambda x:(-len(x), x)):
288			if a not in actionIndex:
289				for i in range(0, len(a), 4):
290					suffix = a[i:]
291					suffixIndex = (len(result) + i) // 4
292					actionIndex.setdefault(
293						suffix, suffixIndex)
294				result += a
295		result = pad(result, 4)
296		return (result, actionIndex)
297
298	def compileLigActions(self):
299		result = []
300		for i, action in enumerate(self.Actions):
301			last = (i == len(self.Actions) - 1)
302			value = action.GlyphIndexDelta & 0x3FFFFFFF
303			value |= 0x80000000 if last else 0
304			value |= 0x40000000 if action.Store else 0
305			result.append(struct.pack(">L", value))
306		return bytesjoin(result)
307
308	def _decompileLigActions(self, actionReader, actionIndex):
309		actions = []
310		last = False
311		reader = actionReader.getSubReader(
312			actionReader.pos + actionIndex * 4)
313		while not last:
314			value = reader.readULong()
315			last = bool(value & 0x80000000)
316			action = LigAction()
317			actions.append(action)
318			action.Store = bool(value & 0x40000000)
319			delta = value & 0x3FFFFFFF
320			if delta >= 0x20000000: # sign-extend 30-bit value
321				delta = -0x40000000 + delta
322			action.GlyphIndexDelta = delta
323		return actions
324
325	def fromXML(self, name, attrs, content, font):
326		self.NewState = self.ReservedFlags = 0
327		self.SetComponent = self.DontAdvance = False
328		self.ReservedFlags = 0
329		self.Actions = []
330		content = [t for t in content if isinstance(t, tuple)]
331		for eltName, eltAttrs, eltContent in content:
332			if eltName == "NewState":
333				self.NewState = safeEval(eltAttrs["value"])
334			elif eltName == "Flags":
335				for flag in eltAttrs["value"].split(","):
336					self._setFlag(flag.strip())
337			elif eltName == "ReservedFlags":
338				self.ReservedFlags = safeEval(eltAttrs["value"])
339			elif eltName == "Action":
340				action = LigAction()
341				flags = eltAttrs.get("Flags", "").split(",")
342				flags = [f.strip() for f in flags]
343				action.Store = "Store" in flags
344				action.GlyphIndexDelta = safeEval(
345					eltAttrs["GlyphIndexDelta"])
346				self.Actions.append(action)
347
348	def toXML(self, xmlWriter, font, attrs, name):
349		xmlWriter.begintag(name, **attrs)
350		xmlWriter.newline()
351		xmlWriter.simpletag("NewState", value=self.NewState)
352		xmlWriter.newline()
353		self._writeFlagsToXML(xmlWriter)
354		for action in self.Actions:
355			attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)]
356			if action.Store:
357				attribs.append(("Flags", "Store"))
358			xmlWriter.simpletag("Action", attribs)
359			xmlWriter.newline()
360		xmlWriter.endtag(name)
361		xmlWriter.newline()
362
363
364class InsertionMorphAction(AATAction):
365	staticSize = 8
366	actionHeaderSize = 4  # 4 bytes for actionOffset
367	_FLAGS = ["SetMark", "DontAdvance",
368	          "CurrentIsKashidaLike", "MarkedIsKashidaLike",
369	          "CurrentInsertBefore", "MarkedInsertBefore"]
370
371	def __init__(self):
372		self.NewState = 0
373		for flag in self._FLAGS:
374			setattr(self, flag, False)
375		self.ReservedFlags = 0
376		self.CurrentInsertionAction, self.MarkedInsertionAction = [], []
377
378	def compile(self, writer, font, actionIndex):
379		assert actionIndex is not None
380		writer.writeUShort(self.NewState)
381		flags = self.ReservedFlags
382		if self.SetMark: flags |= 0x8000
383		if self.DontAdvance: flags |= 0x4000
384		if self.CurrentIsKashidaLike: flags |= 0x2000
385		if self.MarkedIsKashidaLike: flags |= 0x1000
386		if self.CurrentInsertBefore: flags |= 0x0800
387		if self.MarkedInsertBefore: flags |= 0x0400
388		flags |= len(self.CurrentInsertionAction) << 5
389		flags |= len(self.MarkedInsertionAction)
390		writer.writeUShort(flags)
391		if len(self.CurrentInsertionAction) > 0:
392			currentIndex = actionIndex[
393				tuple(self.CurrentInsertionAction)]
394		else:
395			currentIndex = 0xFFFF
396		writer.writeUShort(currentIndex)
397		if len(self.MarkedInsertionAction) > 0:
398			markedIndex = actionIndex[
399				tuple(self.MarkedInsertionAction)]
400		else:
401			markedIndex = 0xFFFF
402		writer.writeUShort(markedIndex)
403
404	def decompile(self, reader, font, actionReader):
405		assert actionReader is not None
406		self.NewState = reader.readUShort()
407		flags = reader.readUShort()
408		self.SetMark = bool(flags & 0x8000)
409		self.DontAdvance = bool(flags & 0x4000)
410		self.CurrentIsKashidaLike = bool(flags & 0x2000)
411		self.MarkedIsKashidaLike = bool(flags & 0x1000)
412		self.CurrentInsertBefore = bool(flags & 0x0800)
413		self.MarkedInsertBefore = bool(flags & 0x0400)
414		self.CurrentInsertionAction = self._decompileInsertionAction(
415			actionReader, font,
416			index=reader.readUShort(),
417			count=((flags & 0x03E0) >> 5))
418		self.MarkedInsertionAction = self._decompileInsertionAction(
419			actionReader, font,
420			index=reader.readUShort(),
421			count=(flags & 0x001F))
422
423	def _decompileInsertionAction(self, actionReader, font, index, count):
424		if index == 0xFFFF or count == 0:
425			return []
426		reader = actionReader.getSubReader(
427			actionReader.pos + index * 2)
428		return [font.getGlyphName(glyphID)
429		        for glyphID in reader.readUShortArray(count)]
430
431	def toXML(self, xmlWriter, font, attrs, name):
432		xmlWriter.begintag(name, **attrs)
433		xmlWriter.newline()
434		xmlWriter.simpletag("NewState", value=self.NewState)
435		xmlWriter.newline()
436		self._writeFlagsToXML(xmlWriter)
437		for g in self.CurrentInsertionAction:
438			xmlWriter.simpletag("CurrentInsertionAction", glyph=g)
439			xmlWriter.newline()
440		for g in self.MarkedInsertionAction:
441			xmlWriter.simpletag("MarkedInsertionAction", glyph=g)
442			xmlWriter.newline()
443		xmlWriter.endtag(name)
444		xmlWriter.newline()
445
446	def fromXML(self, name, attrs, content, font):
447		self.__init__()
448		content = [t for t in content if isinstance(t, tuple)]
449		for eltName, eltAttrs, eltContent in content:
450			if eltName == "NewState":
451				self.NewState = safeEval(eltAttrs["value"])
452			elif eltName == "Flags":
453				for flag in eltAttrs["value"].split(","):
454					self._setFlag(flag.strip())
455			elif eltName == "CurrentInsertionAction":
456				self.CurrentInsertionAction.append(
457					eltAttrs["glyph"])
458			elif eltName == "MarkedInsertionAction":
459				self.MarkedInsertionAction.append(
460					eltAttrs["glyph"])
461			else:
462				assert False, eltName
463
464	@staticmethod
465	def compileActions(font, states):
466		actions, actionIndex, result = set(), {}, b""
467		for state in states:
468			for _glyphClass, trans in state.Transitions.items():
469				if trans.CurrentInsertionAction is not None:
470					actions.add(tuple(trans.CurrentInsertionAction))
471				if trans.MarkedInsertionAction is not None:
472					actions.add(tuple(trans.MarkedInsertionAction))
473		# Sort the compiled actions in decreasing order of
474		# length, so that the longer sequence come before the
475		# shorter ones.
476		for action in sorted(actions, key=lambda x:(-len(x), x)):
477			# We insert all sub-sequences of the action glyph sequence
478			# into actionIndex. For example, if one action triggers on
479			# glyph sequence [A, B, C, D, E] and another action triggers
480			# on [C, D], we return result=[A, B, C, D, E] (as list of
481			# encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0,
482			# ('C','D'): 2}.
483			if action in actionIndex:
484				continue
485			for start in range(0, len(action)):
486				startIndex = (len(result) // 2) + start
487				for limit in range(start, len(action)):
488					glyphs = action[start : limit + 1]
489					actionIndex.setdefault(glyphs, startIndex)
490			for glyph in action:
491				glyphID = font.getGlyphID(glyph)
492				result += struct.pack(">H", glyphID)
493		return result, actionIndex
494
495
496class FeatureParams(BaseTable):
497
498	def compile(self, writer, font):
499		assert featureParamTypes.get(writer['FeatureTag']) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
500		BaseTable.compile(self, writer, font)
501
502	def toXML(self, xmlWriter, font, attrs=None, name=None):
503		BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__)
504
505class FeatureParamsSize(FeatureParams):
506	pass
507
508class FeatureParamsStylisticSet(FeatureParams):
509	pass
510
511class FeatureParamsCharacterVariants(FeatureParams):
512	pass
513
514class Coverage(FormatSwitchingBaseTable):
515
516	# manual implementation to get rid of glyphID dependencies
517
518	def populateDefaults(self, propagator=None):
519		if not hasattr(self, 'glyphs'):
520			self.glyphs = []
521
522	def postRead(self, rawTable, font):
523		if self.Format == 1:
524			# TODO only allow glyphs that are valid?
525			self.glyphs = rawTable["GlyphArray"]
526		elif self.Format == 2:
527			glyphs = self.glyphs = []
528			ranges = rawTable["RangeRecord"]
529			glyphOrder = font.getGlyphOrder()
530			# Some SIL fonts have coverage entries that don't have sorted
531			# StartCoverageIndex.  If it is so, fixup and warn.  We undo
532			# this when writing font out.
533			sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
534			if ranges != sorted_ranges:
535				log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
536				ranges = sorted_ranges
537			del sorted_ranges
538			for r in ranges:
539				assert r.StartCoverageIndex == len(glyphs), \
540					(r.StartCoverageIndex, len(glyphs))
541				start = r.Start
542				end = r.End
543				try:
544					startID = font.getGlyphID(start, requireReal=True)
545				except KeyError:
546					log.warning("Coverage table has start glyph ID out of range: %s.", start)
547					continue
548				try:
549					endID = font.getGlyphID(end, requireReal=True) + 1
550				except KeyError:
551					# Apparently some tools use 65535 to "match all" the range
552					if end != 'glyph65535':
553						log.warning("Coverage table has end glyph ID out of range: %s.", end)
554					# NOTE: We clobber out-of-range things here.  There are legit uses for those,
555					# but none that we have seen in the wild.
556					endID = len(glyphOrder)
557				glyphs.extend(glyphOrder[glyphID] for glyphID in range(startID, endID))
558		else:
559			self.glyphs = []
560			log.warning("Unknown Coverage format: %s", self.Format)
561		del self.Format # Don't need this anymore
562
563	def preWrite(self, font):
564		glyphs = getattr(self, "glyphs", None)
565		if glyphs is None:
566			glyphs = self.glyphs = []
567		format = 1
568		rawTable = {"GlyphArray": glyphs}
569		getGlyphID = font.getGlyphID
570		if glyphs:
571			# find out whether Format 2 is more compact or not
572			glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ]
573			brokenOrder = sorted(glyphIDs) != glyphIDs
574
575			last = glyphIDs[0]
576			ranges = [[last]]
577			for glyphID in glyphIDs[1:]:
578				if glyphID != last + 1:
579					ranges[-1].append(last)
580					ranges.append([glyphID])
581				last = glyphID
582			ranges[-1].append(last)
583
584			if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
585				# Format 2 is more compact
586				index = 0
587				for i in range(len(ranges)):
588					start, end = ranges[i]
589					r = RangeRecord()
590					r.StartID = start
591					r.Start = font.getGlyphName(start)
592					r.End = font.getGlyphName(end)
593					r.StartCoverageIndex = index
594					ranges[i] = r
595					index = index + end - start + 1
596				if brokenOrder:
597					log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
598					ranges.sort(key=lambda a: a.StartID)
599				for r in ranges:
600					del r.StartID
601				format = 2
602				rawTable = {"RangeRecord": ranges}
603			#else:
604			#	fallthrough; Format 1 is more compact
605		self.Format = format
606		return rawTable
607
608	def toXML2(self, xmlWriter, font):
609		for glyphName in getattr(self, "glyphs", []):
610			xmlWriter.simpletag("Glyph", value=glyphName)
611			xmlWriter.newline()
612
613	def fromXML(self, name, attrs, content, font):
614		glyphs = getattr(self, "glyphs", None)
615		if glyphs is None:
616			glyphs = []
617			self.glyphs = glyphs
618		glyphs.append(attrs["value"])
619
620
621class VarIdxMap(BaseTable):
622
623	def populateDefaults(self, propagator=None):
624		if not hasattr(self, 'mapping'):
625			self.mapping = {}
626
627	def postRead(self, rawTable, font):
628		assert (rawTable['EntryFormat'] & 0xFFC0) == 0
629		glyphOrder = font.getGlyphOrder()
630		mapList = rawTable['mapping']
631		mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList)))
632		self.mapping = dict(zip(glyphOrder, mapList))
633
634	def preWrite(self, font):
635		mapping = getattr(self, "mapping", None)
636		if mapping is None:
637			mapping = self.mapping = {}
638
639		glyphOrder = font.getGlyphOrder()
640		mapping = [mapping[g] for g in glyphOrder]
641		while len(mapping) > 1 and mapping[-2] == mapping[-1]:
642			del mapping[-1]
643
644		rawTable = { 'mapping': mapping }
645		rawTable['MappingCount'] = len(mapping)
646
647		ored = 0
648		for idx in mapping:
649			ored |= idx
650
651		inner = ored & 0xFFFF
652		innerBits = 0
653		while inner:
654			innerBits += 1
655			inner >>= 1
656		innerBits = max(innerBits, 1)
657		assert innerBits <= 16
658
659		ored = (ored >> (16-innerBits)) | (ored & ((1<<innerBits)-1))
660		if   ored <= 0x000000FF:
661			entrySize = 1
662		elif ored <= 0x0000FFFF:
663			entrySize = 2
664		elif ored <= 0x00FFFFFF:
665			entrySize = 3
666		else:
667			entrySize = 4
668
669		entryFormat = ((entrySize - 1) << 4) | (innerBits - 1)
670
671		rawTable['EntryFormat'] = entryFormat
672		return rawTable
673
674	def toXML2(self, xmlWriter, font):
675		for glyph, value in sorted(getattr(self, "mapping", {}).items()):
676			attrs = (
677				('glyph', glyph),
678				('outer', value >> 16),
679				('inner', value & 0xFFFF),
680			)
681			xmlWriter.simpletag("Map", attrs)
682			xmlWriter.newline()
683
684	def fromXML(self, name, attrs, content, font):
685		mapping = getattr(self, "mapping", None)
686		if mapping is None:
687			mapping = {}
688			self.mapping = mapping
689		try:
690			glyph = attrs['glyph']
691		except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836
692			glyph = font.getGlyphOrder()[attrs['index']]
693		outer = safeEval(attrs['outer'])
694		inner = safeEval(attrs['inner'])
695		assert inner <= 0xFFFF
696		mapping[glyph] = (outer << 16) | inner
697
698
699class VarRegionList(BaseTable):
700
701	def preWrite(self, font):
702		# The OT spec says VarStore.VarRegionList.RegionAxisCount should always
703		# be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
704		# even when the VarRegionList is empty. We can't treat RegionAxisCount
705		# like a normal propagated count (== len(Region[i].VarRegionAxis)),
706		# otherwise it would default to 0 if VarRegionList is empty.
707		# Thus, we force it to always be equal to fvar.axisCount.
708		# https://github.com/khaledhosny/ots/pull/192
709		fvarTable = font.get("fvar")
710		if fvarTable:
711			self.RegionAxisCount = len(fvarTable.axes)
712		return {
713			**self.__dict__,
714			"RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount")
715		}
716
717
718class SingleSubst(FormatSwitchingBaseTable):
719
720	def populateDefaults(self, propagator=None):
721		if not hasattr(self, 'mapping'):
722			self.mapping = {}
723
724	def postRead(self, rawTable, font):
725		mapping = {}
726		input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
727		if self.Format == 1:
728			delta = rawTable["DeltaGlyphID"]
729			inputGIDS =  [ font.getGlyphID(name) for name in input ]
730			outGIDS = [ (glyphID + delta) % 65536 for glyphID in inputGIDS ]
731			outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ]
732			for inp, out in zip(input, outNames):
733				mapping[inp] = out
734		elif self.Format == 2:
735			assert len(input) == rawTable["GlyphCount"], \
736					"invalid SingleSubstFormat2 table"
737			subst = rawTable["Substitute"]
738			for inp, sub in zip(input, subst):
739				mapping[inp] = sub
740		else:
741			assert 0, "unknown format: %s" % self.Format
742		self.mapping = mapping
743		del self.Format # Don't need this anymore
744
745	def preWrite(self, font):
746		mapping = getattr(self, "mapping", None)
747		if mapping is None:
748			mapping = self.mapping = {}
749		items = list(mapping.items())
750		getGlyphID = font.getGlyphID
751		gidItems = [(getGlyphID(a), getGlyphID(b)) for a,b in items]
752		sortableItems = sorted(zip(gidItems, items))
753
754		# figure out format
755		format = 2
756		delta = None
757		for inID, outID in gidItems:
758			if delta is None:
759				delta = (outID - inID) % 65536
760
761			if (inID + delta) % 65536 != outID:
762					break
763		else:
764			if delta is None:
765				# the mapping is empty, better use format 2
766				format = 2
767			else:
768				format = 1
769
770		rawTable = {}
771		self.Format = format
772		cov = Coverage()
773		input =  [ item [1][0] for item in sortableItems]
774		subst =  [ item [1][1] for item in sortableItems]
775		cov.glyphs = input
776		rawTable["Coverage"] = cov
777		if format == 1:
778			assert delta is not None
779			rawTable["DeltaGlyphID"] = delta
780		else:
781			rawTable["Substitute"] = subst
782		return rawTable
783
784	def toXML2(self, xmlWriter, font):
785		items = sorted(self.mapping.items())
786		for inGlyph, outGlyph in items:
787			xmlWriter.simpletag("Substitution",
788					[("in", inGlyph), ("out", outGlyph)])
789			xmlWriter.newline()
790
791	def fromXML(self, name, attrs, content, font):
792		mapping = getattr(self, "mapping", None)
793		if mapping is None:
794			mapping = {}
795			self.mapping = mapping
796		mapping[attrs["in"]] = attrs["out"]
797
798
799class MultipleSubst(FormatSwitchingBaseTable):
800
801	def populateDefaults(self, propagator=None):
802		if not hasattr(self, 'mapping'):
803			self.mapping = {}
804
805	def postRead(self, rawTable, font):
806		mapping = {}
807		if self.Format == 1:
808			glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"])
809			subst = [s.Substitute for s in rawTable["Sequence"]]
810			mapping = dict(zip(glyphs, subst))
811		else:
812			assert 0, "unknown format: %s" % self.Format
813		self.mapping = mapping
814		del self.Format # Don't need this anymore
815
816	def preWrite(self, font):
817		mapping = getattr(self, "mapping", None)
818		if mapping is None:
819			mapping = self.mapping = {}
820		cov = Coverage()
821		cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID)
822		self.Format = 1
823		rawTable = {
824                        "Coverage": cov,
825                        "Sequence": [self.makeSequence_(mapping[glyph])
826                                     for glyph in cov.glyphs],
827                }
828		return rawTable
829
830	def toXML2(self, xmlWriter, font):
831		items = sorted(self.mapping.items())
832		for inGlyph, outGlyphs in items:
833			out = ",".join(outGlyphs)
834			xmlWriter.simpletag("Substitution",
835					[("in", inGlyph), ("out", out)])
836			xmlWriter.newline()
837
838	def fromXML(self, name, attrs, content, font):
839		mapping = getattr(self, "mapping", None)
840		if mapping is None:
841			mapping = {}
842			self.mapping = mapping
843
844		# TTX v3.0 and earlier.
845		if name == "Coverage":
846			self.old_coverage_ = []
847			for element in content:
848				if not isinstance(element, tuple):
849					continue
850				element_name, element_attrs, _ = element
851				if element_name == "Glyph":
852					self.old_coverage_.append(element_attrs["value"])
853			return
854		if name == "Sequence":
855			index = int(attrs.get("index", len(mapping)))
856			glyph = self.old_coverage_[index]
857			glyph_mapping = mapping[glyph] = []
858			for element in content:
859				if not isinstance(element, tuple):
860					continue
861				element_name, element_attrs, _ = element
862				if element_name == "Substitute":
863					glyph_mapping.append(element_attrs["value"])
864			return
865
866                # TTX v3.1 and later.
867		outGlyphs = attrs["out"].split(",") if attrs["out"] else []
868		mapping[attrs["in"]] = [g.strip() for g in outGlyphs]
869
870	@staticmethod
871	def makeSequence_(g):
872		seq = Sequence()
873		seq.Substitute = g
874		return seq
875
876
877class ClassDef(FormatSwitchingBaseTable):
878
879	def populateDefaults(self, propagator=None):
880		if not hasattr(self, 'classDefs'):
881			self.classDefs = {}
882
883	def postRead(self, rawTable, font):
884		classDefs = {}
885		glyphOrder = font.getGlyphOrder()
886
887		if self.Format == 1:
888			start = rawTable["StartGlyph"]
889			classList = rawTable["ClassValueArray"]
890			try:
891				startID = font.getGlyphID(start, requireReal=True)
892			except KeyError:
893				log.warning("ClassDef table has start glyph ID out of range: %s.", start)
894				startID = len(glyphOrder)
895			endID = startID + len(classList)
896			if endID > len(glyphOrder):
897				log.warning("ClassDef table has entries for out of range glyph IDs: %s,%s.",
898					start, len(classList))
899				# NOTE: We clobber out-of-range things here.  There are legit uses for those,
900				# but none that we have seen in the wild.
901				endID = len(glyphOrder)
902
903			for glyphID, cls in zip(range(startID, endID), classList):
904				if cls:
905					classDefs[glyphOrder[glyphID]] = cls
906
907		elif self.Format == 2:
908			records = rawTable["ClassRangeRecord"]
909			for rec in records:
910				start = rec.Start
911				end = rec.End
912				cls = rec.Class
913				try:
914					startID = font.getGlyphID(start, requireReal=True)
915				except KeyError:
916					log.warning("ClassDef table has start glyph ID out of range: %s.", start)
917					continue
918				try:
919					endID = font.getGlyphID(end, requireReal=True) + 1
920				except KeyError:
921					# Apparently some tools use 65535 to "match all" the range
922					if end != 'glyph65535':
923						log.warning("ClassDef table has end glyph ID out of range: %s.", end)
924					# NOTE: We clobber out-of-range things here.  There are legit uses for those,
925					# but none that we have seen in the wild.
926					endID = len(glyphOrder)
927				for glyphID in range(startID, endID):
928					if cls:
929						classDefs[glyphOrder[glyphID]] = cls
930		else:
931			log.warning("Unknown ClassDef format: %s", self.Format)
932		self.classDefs = classDefs
933		del self.Format # Don't need this anymore
934
935	def _getClassRanges(self, font):
936		classDefs = getattr(self, "classDefs", None)
937		if classDefs is None:
938			self.classDefs = {}
939			return
940		getGlyphID = font.getGlyphID
941		items = []
942		for glyphName, cls in classDefs.items():
943			if not cls:
944				continue
945			items.append((getGlyphID(glyphName), glyphName, cls))
946		if items:
947			items.sort()
948			last, lastName, lastCls = items[0]
949			ranges = [[lastCls, last, lastName]]
950			for glyphID, glyphName, cls in items[1:]:
951				if glyphID != last + 1 or cls != lastCls:
952					ranges[-1].extend([last, lastName])
953					ranges.append([cls, glyphID, glyphName])
954				last = glyphID
955				lastName = glyphName
956				lastCls = cls
957			ranges[-1].extend([last, lastName])
958			return ranges
959
960	def preWrite(self, font):
961		format = 2
962		rawTable = {"ClassRangeRecord": []}
963		ranges = self._getClassRanges(font)
964		if ranges:
965			startGlyph = ranges[0][1]
966			endGlyph = ranges[-1][3]
967			glyphCount = endGlyph - startGlyph + 1
968			if len(ranges) * 3 < glyphCount + 1:
969				# Format 2 is more compact
970				for i in range(len(ranges)):
971					cls, start, startName, end, endName = ranges[i]
972					rec = ClassRangeRecord()
973					rec.Start = startName
974					rec.End = endName
975					rec.Class = cls
976					ranges[i] = rec
977				format = 2
978				rawTable = {"ClassRangeRecord": ranges}
979			else:
980				# Format 1 is more compact
981				startGlyphName = ranges[0][2]
982				classes = [0] * glyphCount
983				for cls, start, startName, end, endName in ranges:
984					for g in range(start - startGlyph, end - startGlyph + 1):
985						classes[g] = cls
986				format = 1
987				rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes}
988		self.Format = format
989		return rawTable
990
991	def toXML2(self, xmlWriter, font):
992		items = sorted(self.classDefs.items())
993		for glyphName, cls in items:
994			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
995			xmlWriter.newline()
996
997	def fromXML(self, name, attrs, content, font):
998		classDefs = getattr(self, "classDefs", None)
999		if classDefs is None:
1000			classDefs = {}
1001			self.classDefs = classDefs
1002		classDefs[attrs["glyph"]] = int(attrs["class"])
1003
1004
1005class AlternateSubst(FormatSwitchingBaseTable):
1006
1007	def populateDefaults(self, propagator=None):
1008		if not hasattr(self, 'alternates'):
1009			self.alternates = {}
1010
1011	def postRead(self, rawTable, font):
1012		alternates = {}
1013		if self.Format == 1:
1014			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
1015			alts = rawTable["AlternateSet"]
1016			assert len(input) == len(alts)
1017			for inp,alt in zip(input,alts):
1018				alternates[inp] = alt.Alternate
1019		else:
1020			assert 0, "unknown format: %s" % self.Format
1021		self.alternates = alternates
1022		del self.Format # Don't need this anymore
1023
1024	def preWrite(self, font):
1025		self.Format = 1
1026		alternates = getattr(self, "alternates", None)
1027		if alternates is None:
1028			alternates = self.alternates = {}
1029		items = list(alternates.items())
1030		for i in range(len(items)):
1031			glyphName, set = items[i]
1032			items[i] = font.getGlyphID(glyphName), glyphName, set
1033		items.sort()
1034		cov = Coverage()
1035		cov.glyphs = [ item[1] for item in items]
1036		alternates = []
1037		setList = [ item[-1] for item in items]
1038		for set in setList:
1039			alts = AlternateSet()
1040			alts.Alternate = set
1041			alternates.append(alts)
1042		# a special case to deal with the fact that several hundred Adobe Japan1-5
1043		# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
1044		# Also useful in that when splitting a sub-table because of an offset overflow
1045		# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
1046		# Allows packing more rules in subtable.
1047		self.sortCoverageLast = 1
1048		return {"Coverage": cov, "AlternateSet": alternates}
1049
1050	def toXML2(self, xmlWriter, font):
1051		items = sorted(self.alternates.items())
1052		for glyphName, alternates in items:
1053			xmlWriter.begintag("AlternateSet", glyph=glyphName)
1054			xmlWriter.newline()
1055			for alt in alternates:
1056				xmlWriter.simpletag("Alternate", glyph=alt)
1057				xmlWriter.newline()
1058			xmlWriter.endtag("AlternateSet")
1059			xmlWriter.newline()
1060
1061	def fromXML(self, name, attrs, content, font):
1062		alternates = getattr(self, "alternates", None)
1063		if alternates is None:
1064			alternates = {}
1065			self.alternates = alternates
1066		glyphName = attrs["glyph"]
1067		set = []
1068		alternates[glyphName] = set
1069		for element in content:
1070			if not isinstance(element, tuple):
1071				continue
1072			name, attrs, content = element
1073			set.append(attrs["glyph"])
1074
1075
1076class LigatureSubst(FormatSwitchingBaseTable):
1077
1078	def populateDefaults(self, propagator=None):
1079		if not hasattr(self, 'ligatures'):
1080			self.ligatures = {}
1081
1082	def postRead(self, rawTable, font):
1083		ligatures = {}
1084		if self.Format == 1:
1085			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
1086			ligSets = rawTable["LigatureSet"]
1087			assert len(input) == len(ligSets)
1088			for i in range(len(input)):
1089				ligatures[input[i]] = ligSets[i].Ligature
1090		else:
1091			assert 0, "unknown format: %s" % self.Format
1092		self.ligatures = ligatures
1093		del self.Format # Don't need this anymore
1094
1095	def preWrite(self, font):
1096		self.Format = 1
1097		ligatures = getattr(self, "ligatures", None)
1098		if ligatures is None:
1099			ligatures = self.ligatures = {}
1100
1101		if ligatures and isinstance(next(iter(ligatures)), tuple):
1102			# New high-level API in v3.1 and later.  Note that we just support compiling this
1103			# for now.  We don't load to this API, and don't do XML with it.
1104
1105			# ligatures is map from components-sequence to lig-glyph
1106			newLigatures = dict()
1107			for comps,lig in sorted(ligatures.items(), key=lambda item: (-len(item[0]), item[0])):
1108				ligature = Ligature()
1109				ligature.Component = comps[1:]
1110				ligature.CompCount = len(comps)
1111				ligature.LigGlyph = lig
1112				newLigatures.setdefault(comps[0], []).append(ligature)
1113			ligatures = newLigatures
1114
1115		items = list(ligatures.items())
1116		for i in range(len(items)):
1117			glyphName, set = items[i]
1118			items[i] = font.getGlyphID(glyphName), glyphName, set
1119		items.sort()
1120		cov = Coverage()
1121		cov.glyphs = [ item[1] for item in items]
1122
1123		ligSets = []
1124		setList = [ item[-1] for item in items ]
1125		for set in setList:
1126			ligSet = LigatureSet()
1127			ligs = ligSet.Ligature = []
1128			for lig in set:
1129				ligs.append(lig)
1130			ligSets.append(ligSet)
1131		# Useful in that when splitting a sub-table because of an offset overflow
1132		# I don't need to calculate the change in subtabl offset due to the coverage table size.
1133		# Allows packing more rules in subtable.
1134		self.sortCoverageLast = 1
1135		return {"Coverage": cov, "LigatureSet": ligSets}
1136
1137	def toXML2(self, xmlWriter, font):
1138		items = sorted(self.ligatures.items())
1139		for glyphName, ligSets in items:
1140			xmlWriter.begintag("LigatureSet", glyph=glyphName)
1141			xmlWriter.newline()
1142			for lig in ligSets:
1143				xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
1144					components=",".join(lig.Component))
1145				xmlWriter.newline()
1146			xmlWriter.endtag("LigatureSet")
1147			xmlWriter.newline()
1148
1149	def fromXML(self, name, attrs, content, font):
1150		ligatures = getattr(self, "ligatures", None)
1151		if ligatures is None:
1152			ligatures = {}
1153			self.ligatures = ligatures
1154		glyphName = attrs["glyph"]
1155		ligs = []
1156		ligatures[glyphName] = ligs
1157		for element in content:
1158			if not isinstance(element, tuple):
1159				continue
1160			name, attrs, content = element
1161			lig = Ligature()
1162			lig.LigGlyph = attrs["glyph"]
1163			components = attrs["components"]
1164			lig.Component = components.split(",") if components else []
1165			lig.CompCount = len(lig.Component)
1166			ligs.append(lig)
1167
1168
1169class COLR(BaseTable):
1170
1171	def decompile(self, reader, font):
1172		# COLRv0 is exceptional in that LayerRecordCount appears *after* the
1173		# LayerRecordArray it counts, but the parser logic expects Count fields
1174		# to always precede the arrays. Here we work around this by parsing the
1175		# LayerRecordCount before the rest of the table, and storing it in
1176		# the reader's local state.
1177		subReader = reader.getSubReader(offset=0)
1178		for conv in self.getConverters():
1179			if conv.name != "LayerRecordCount":
1180				subReader.advance(conv.staticSize)
1181				continue
1182			conv = self.getConverterByName("LayerRecordCount")
1183			reader[conv.name] = conv.read(subReader, font, tableDict={})
1184			break
1185		else:
1186			raise AssertionError("LayerRecordCount converter not found")
1187		return BaseTable.decompile(self, reader, font)
1188
1189	def preWrite(self, font):
1190		# The writer similarly assumes Count values precede the things counted,
1191		# thus here we pre-initialize a CountReference; the actual count value
1192		# will be set to the lenght of the array by the time this is assembled.
1193		self.LayerRecordCount = None
1194		return {
1195			**self.__dict__,
1196			"LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount")
1197		}
1198
1199
1200class LookupList(BaseTable):
1201	@property
1202	def table(self):
1203		for l in self.Lookup:
1204			for st in l.SubTable:
1205				if type(st).__name__.endswith("Subst"):
1206					return "GSUB"
1207				if type(st).__name__.endswith("Pos"):
1208					return "GPOS"
1209		raise ValueError
1210
1211	def toXML2(self, xmlWriter, font):
1212		if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data:
1213			return super().toXML2(xmlWriter, font)
1214		debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
1215		for conv in self.getConverters():
1216			if conv.repeat:
1217				value = getattr(self, conv.name, [])
1218				for lookupIndex, item in enumerate(value):
1219					if str(lookupIndex) in debugData:
1220						info = LookupDebugInfo(*debugData[str(lookupIndex)])
1221						tag = info.location
1222						if info.name:
1223							tag = f'{info.name}: {tag}'
1224						if info.feature:
1225							script,language,feature = info.feature
1226							tag = f'{tag} in {feature} ({script}/{language})'
1227						xmlWriter.comment(tag)
1228						xmlWriter.newline()
1229
1230					conv.xmlWrite(xmlWriter, font, item, conv.name,
1231							[("index", lookupIndex)])
1232			else:
1233				if conv.aux and not eval(conv.aux, None, vars(self)):
1234					continue
1235				value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None!
1236				conv.xmlWrite(xmlWriter, font, value, conv.name, [])
1237
1238class BaseGlyphRecordArray(BaseTable):
1239
1240	def preWrite(self, font):
1241		self.BaseGlyphRecord = sorted(
1242			self.BaseGlyphRecord,
1243			key=lambda rec: font.getGlyphID(rec.BaseGlyph)
1244		)
1245		return self.__dict__.copy()
1246
1247
1248class BaseGlyphV1List(BaseTable):
1249
1250	def preWrite(self, font):
1251		self.BaseGlyphV1Record = sorted(
1252			self.BaseGlyphV1Record,
1253			key=lambda rec: font.getGlyphID(rec.BaseGlyph)
1254		)
1255		return self.__dict__.copy()
1256
1257
1258
1259class VariableValue(namedtuple("VariableValue", ["value", "varIdx"])):
1260	__slots__ = ()
1261
1262	_value_mapper = None
1263
1264	def __new__(cls, value, varIdx=0):
1265		return super().__new__(
1266			cls,
1267			cls._value_mapper(value) if cls._value_mapper else value,
1268			varIdx
1269		)
1270
1271	@classmethod
1272	def _make(cls, iterable):
1273		if cls._value_mapper:
1274			it = iter(iterable)
1275			try:
1276				value = next(it)
1277			except StopIteration:
1278				pass
1279			else:
1280				value = cls._value_mapper(value)
1281				iterable = itertools.chain((value,), it)
1282		return super()._make(iterable)
1283
1284
1285class VariableFloat(VariableValue):
1286	__slots__ = ()
1287	_value_mapper = float
1288
1289
1290class VariableInt(VariableValue):
1291	__slots__ = ()
1292	_value_mapper = otRound
1293
1294
1295class ExtendMode(IntEnum):
1296	PAD = 0
1297	REPEAT = 1
1298	REFLECT = 2
1299
1300
1301# Porter-Duff modes for COLRv1 PaintComposite:
1302# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration
1303class CompositeMode(IntEnum):
1304	CLEAR = 0
1305	SRC = 1
1306	DEST = 2
1307	SRC_OVER = 3
1308	DEST_OVER = 4
1309	SRC_IN = 5
1310	DEST_IN = 6
1311	SRC_OUT = 7
1312	DEST_OUT = 8
1313	SRC_ATOP = 9
1314	DEST_ATOP = 10
1315	XOR = 11
1316	SCREEN = 12
1317	OVERLAY = 13
1318	DARKEN = 14
1319	LIGHTEN = 15
1320	COLOR_DODGE = 16
1321	COLOR_BURN = 17
1322	HARD_LIGHT = 18
1323	SOFT_LIGHT = 19
1324	DIFFERENCE = 20
1325	EXCLUSION = 21
1326	MULTIPLY = 22
1327	HSL_HUE = 23
1328	HSL_SATURATION = 24
1329	HSL_COLOR = 25
1330	HSL_LUMINOSITY = 26
1331
1332
1333class PaintFormat(IntEnum):
1334	PaintColrLayers = 1
1335	PaintSolid = 2
1336	PaintVarSolid = 3,
1337	PaintLinearGradient = 4
1338	PaintVarLinearGradient = 5
1339	PaintRadialGradient = 6
1340	PaintVarRadialGradient = 7
1341	PaintSweepGradient = 8
1342	PaintVarSweepGradient = 9
1343	PaintGlyph = 10
1344	PaintColrGlyph = 11
1345	PaintTransform = 12
1346	PaintVarTransform = 13
1347	PaintTranslate = 14
1348	PaintVarTranslate = 15
1349	PaintRotate = 16
1350	PaintVarRotate = 17
1351	PaintSkew = 18
1352	PaintVarSkew = 19
1353	PaintComposite = 20
1354
1355
1356class Paint(getFormatSwitchingBaseTableClass("uint8")):
1357
1358	def getFormatName(self):
1359		try:
1360			return PaintFormat(self.Format).name
1361		except ValueError:
1362			raise NotImplementedError(f"Unknown Paint format: {self.Format}")
1363
1364	def toXML(self, xmlWriter, font, attrs=None, name=None):
1365		tableName = name if name else self.__class__.__name__
1366		if attrs is None:
1367			attrs = []
1368		attrs.append(("Format", self.Format))
1369		xmlWriter.begintag(tableName, attrs)
1370		xmlWriter.comment(self.getFormatName())
1371		xmlWriter.newline()
1372		self.toXML2(xmlWriter, font)
1373		xmlWriter.endtag(tableName)
1374		xmlWriter.newline()
1375
1376	def getChildren(self, colr):
1377		if self.Format == PaintFormat.PaintColrLayers:
1378			return colr.LayerV1List.Paint[
1379				self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers
1380			]
1381
1382		if self.Format == PaintFormat.PaintColrGlyph:
1383			for record in colr.BaseGlyphV1List.BaseGlyphV1Record:
1384				if record.BaseGlyph == self.Glyph:
1385					return [record.Paint]
1386			else:
1387				raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphV1List")
1388
1389		children = []
1390		for conv in self.getConverters():
1391			if conv.tableClass is not None and issubclass(conv.tableClass, type(self)):
1392				children.append(getattr(self, conv.name))
1393
1394		return children
1395
1396	def traverse(self, colr: COLR, callback):
1397		"""Depth-first traversal of graph rooted at self, callback on each node."""
1398		if not callable(callback):
1399			raise TypeError("callback must be callable")
1400		stack = [self]
1401		visited = set()
1402		while stack:
1403			current = stack.pop()
1404			if id(current) in visited:
1405				continue
1406			callback(current)
1407			visited.add(id(current))
1408			stack.extend(reversed(current.getChildren(colr)))
1409
1410
1411# For each subtable format there is a class. However, we don't really distinguish
1412# between "field name" and "format name": often these are the same. Yet there's
1413# a whole bunch of fields with different names. The following dict is a mapping
1414# from "format name" to "field name". _buildClasses() uses this to create a
1415# subclass for each alternate field name.
1416#
1417_equivalents = {
1418	'MarkArray': ("Mark1Array",),
1419	'LangSys': ('DefaultLangSys',),
1420	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
1421			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
1422			'LookAheadCoverage', 'VertGlyphCoverage', 'HorizGlyphCoverage',
1423			'TopAccentCoverage', 'ExtendedShapeCoverage', 'MathKernCoverage'),
1424	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
1425			'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
1426	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
1427			'Mark2Anchor', 'MarkAnchor'),
1428	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
1429			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
1430	'Axis': ('HorizAxis', 'VertAxis',),
1431	'MinMax': ('DefaultMinMax',),
1432	'BaseCoord': ('MinCoord', 'MaxCoord',),
1433	'JstfLangSys': ('DefJstfLangSys',),
1434	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
1435			'ExtensionDisableGSUB',),
1436	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
1437			'ExtensionDisableGPOS',),
1438	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
1439	'MathKern': ('TopRightMathKern', 'TopLeftMathKern', 'BottomRightMathKern',
1440			'BottomLeftMathKern'),
1441	'MathGlyphConstruction': ('VertGlyphConstruction', 'HorizGlyphConstruction'),
1442}
1443
1444#
1445# OverFlow logic, to automatically create ExtensionLookups
1446# XXX This should probably move to otBase.py
1447#
1448
1449def fixLookupOverFlows(ttf, overflowRecord):
1450	""" Either the offset from the LookupList to a lookup overflowed, or
1451	an offset from a lookup to a subtable overflowed.
1452	The table layout is:
1453	GPSO/GUSB
1454		Script List
1455		Feature List
1456		LookUpList
1457			Lookup[0] and contents
1458				SubTable offset list
1459					SubTable[0] and contents
1460					...
1461					SubTable[n] and contents
1462			...
1463			Lookup[n] and contents
1464				SubTable offset list
1465					SubTable[0] and contents
1466					...
1467					SubTable[n] and contents
1468	If the offset to a lookup overflowed (SubTableIndex is None)
1469		we must promote the *previous*	lookup to an Extension type.
1470	If the offset from a lookup to subtable overflowed, then we must promote it
1471		to an Extension Lookup type.
1472	"""
1473	ok = 0
1474	lookupIndex = overflowRecord.LookupListIndex
1475	if (overflowRecord.SubTableIndex is None):
1476		lookupIndex = lookupIndex - 1
1477	if lookupIndex < 0:
1478		return ok
1479	if overflowRecord.tableType == 'GSUB':
1480		extType = 7
1481	elif overflowRecord.tableType == 'GPOS':
1482		extType = 9
1483
1484	lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
1485	lookup = lookups[lookupIndex]
1486	# If the previous lookup is an extType, look further back. Very unlikely, but possible.
1487	while lookup.SubTable[0].__class__.LookupType == extType:
1488		lookupIndex = lookupIndex -1
1489		if lookupIndex < 0:
1490			return ok
1491		lookup = lookups[lookupIndex]
1492
1493	lookup.LookupType = extType
1494	for si in range(len(lookup.SubTable)):
1495		subTable = lookup.SubTable[si]
1496		extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
1497		extSubTable = extSubTableClass()
1498		extSubTable.Format = 1
1499		extSubTable.ExtSubTable = subTable
1500		lookup.SubTable[si] = extSubTable
1501	ok = 1
1502	return ok
1503
1504def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
1505	ok = 1
1506	newSubTable.Format = oldSubTable.Format
1507	oldMapping = sorted(oldSubTable.mapping.items())
1508	oldLen = len(oldMapping)
1509
1510	if overflowRecord.itemName in ['Coverage', 'RangeRecord']:
1511		# Coverage table is written last. Overflow is to or within the
1512		# the coverage table. We will just cut the subtable in half.
1513		newLen = oldLen // 2
1514
1515	elif overflowRecord.itemName == 'Sequence':
1516		# We just need to back up by two items from the overflowed
1517		# Sequence index to make sure the offset to the Coverage table
1518		# doesn't overflow.
1519		newLen = overflowRecord.itemIndex - 1
1520
1521	newSubTable.mapping = {}
1522	for i in range(newLen, oldLen):
1523		item = oldMapping[i]
1524		key = item[0]
1525		newSubTable.mapping[key] = item[1]
1526		del oldSubTable.mapping[key]
1527
1528	return ok
1529
1530def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
1531	ok = 1
1532	newSubTable.Format = oldSubTable.Format
1533	if hasattr(oldSubTable, 'sortCoverageLast'):
1534		newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
1535
1536	oldAlts = sorted(oldSubTable.alternates.items())
1537	oldLen = len(oldAlts)
1538
1539	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
1540		# Coverage table is written last. overflow is to or within the
1541		# the coverage table. We will just cut the subtable in half.
1542		newLen = oldLen//2
1543
1544	elif overflowRecord.itemName == 'AlternateSet':
1545		# We just need to back up by two items
1546		# from the overflowed AlternateSet index to make sure the offset
1547		# to the Coverage table doesn't overflow.
1548		newLen = overflowRecord.itemIndex - 1
1549
1550	newSubTable.alternates = {}
1551	for i in range(newLen, oldLen):
1552		item = oldAlts[i]
1553		key = item[0]
1554		newSubTable.alternates[key] = item[1]
1555		del oldSubTable.alternates[key]
1556
1557	return ok
1558
1559
1560def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
1561	ok = 1
1562	newSubTable.Format = oldSubTable.Format
1563	oldLigs = sorted(oldSubTable.ligatures.items())
1564	oldLen = len(oldLigs)
1565
1566	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
1567		# Coverage table is written last. overflow is to or within the
1568		# the coverage table. We will just cut the subtable in half.
1569		newLen = oldLen//2
1570
1571	elif overflowRecord.itemName == 'LigatureSet':
1572		# We just need to back up by two items
1573		# from the overflowed AlternateSet index to make sure the offset
1574		# to the Coverage table doesn't overflow.
1575		newLen = overflowRecord.itemIndex - 1
1576
1577	newSubTable.ligatures = {}
1578	for i in range(newLen, oldLen):
1579		item = oldLigs[i]
1580		key = item[0]
1581		newSubTable.ligatures[key] = item[1]
1582		del oldSubTable.ligatures[key]
1583
1584	return ok
1585
1586
1587def splitPairPos(oldSubTable, newSubTable, overflowRecord):
1588	st = oldSubTable
1589	ok = False
1590	newSubTable.Format = oldSubTable.Format
1591	if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1:
1592		for name in 'ValueFormat1', 'ValueFormat2':
1593			setattr(newSubTable, name, getattr(oldSubTable, name))
1594
1595		# Move top half of coverage to new subtable
1596
1597		newSubTable.Coverage = oldSubTable.Coverage.__class__()
1598
1599		coverage = oldSubTable.Coverage.glyphs
1600		records = oldSubTable.PairSet
1601
1602		oldCount = len(oldSubTable.PairSet) // 2
1603
1604		oldSubTable.Coverage.glyphs = coverage[:oldCount]
1605		oldSubTable.PairSet = records[:oldCount]
1606
1607		newSubTable.Coverage.glyphs = coverage[oldCount:]
1608		newSubTable.PairSet = records[oldCount:]
1609
1610		oldSubTable.PairSetCount = len(oldSubTable.PairSet)
1611		newSubTable.PairSetCount = len(newSubTable.PairSet)
1612
1613		ok = True
1614
1615	elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1:
1616		if not hasattr(oldSubTable, 'Class2Count'):
1617			oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record)
1618		for name in 'Class2Count', 'ClassDef2', 'ValueFormat1', 'ValueFormat2':
1619			setattr(newSubTable, name, getattr(oldSubTable, name))
1620
1621		# The two subtables will still have the same ClassDef2 and the table
1622		# sharing will still cause the sharing to overflow.  As such, disable
1623		# sharing on the one that is serialized second (that's oldSubTable).
1624		oldSubTable.DontShare = True
1625
1626		# Move top half of class numbers to new subtable
1627
1628		newSubTable.Coverage = oldSubTable.Coverage.__class__()
1629		newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__()
1630
1631		coverage = oldSubTable.Coverage.glyphs
1632		classDefs = oldSubTable.ClassDef1.classDefs
1633		records = oldSubTable.Class1Record
1634
1635		oldCount = len(oldSubTable.Class1Record) // 2
1636		newGlyphs = set(k for k,v in classDefs.items() if v >= oldCount)
1637
1638		oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs]
1639		oldSubTable.ClassDef1.classDefs = {k:v for k,v in classDefs.items() if v < oldCount}
1640		oldSubTable.Class1Record = records[:oldCount]
1641
1642		newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs]
1643		newSubTable.ClassDef1.classDefs = {k:(v-oldCount) for k,v in classDefs.items() if v > oldCount}
1644		newSubTable.Class1Record = records[oldCount:]
1645
1646		oldSubTable.Class1Count = len(oldSubTable.Class1Record)
1647		newSubTable.Class1Count = len(newSubTable.Class1Record)
1648
1649		ok = True
1650
1651	return ok
1652
1653
1654def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
1655	# split half of the mark classes to the new subtable
1656	classCount = oldSubTable.ClassCount
1657	if classCount < 2:
1658		# oh well, not much left to split...
1659		return False
1660
1661	oldClassCount = classCount // 2
1662	newClassCount = classCount - oldClassCount
1663
1664	oldMarkCoverage, oldMarkRecords = [], []
1665	newMarkCoverage, newMarkRecords = [], []
1666	for glyphName, markRecord in zip(
1667		oldSubTable.MarkCoverage.glyphs,
1668		oldSubTable.MarkArray.MarkRecord
1669	):
1670		if markRecord.Class < oldClassCount:
1671			oldMarkCoverage.append(glyphName)
1672			oldMarkRecords.append(markRecord)
1673		else:
1674			markRecord.Class -= oldClassCount
1675			newMarkCoverage.append(glyphName)
1676			newMarkRecords.append(markRecord)
1677
1678	oldBaseRecords, newBaseRecords = [], []
1679	for rec in oldSubTable.BaseArray.BaseRecord:
1680		oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__()
1681		oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount]
1682		newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:]
1683		oldBaseRecords.append(oldBaseRecord)
1684		newBaseRecords.append(newBaseRecord)
1685
1686	newSubTable.Format = oldSubTable.Format
1687
1688	oldSubTable.MarkCoverage.glyphs = oldMarkCoverage
1689	newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__()
1690	newSubTable.MarkCoverage.glyphs = newMarkCoverage
1691
1692	# share the same BaseCoverage in both halves
1693	newSubTable.BaseCoverage = oldSubTable.BaseCoverage
1694
1695	oldSubTable.ClassCount = oldClassCount
1696	newSubTable.ClassCount = newClassCount
1697
1698	oldSubTable.MarkArray.MarkRecord = oldMarkRecords
1699	newSubTable.MarkArray = oldSubTable.MarkArray.__class__()
1700	newSubTable.MarkArray.MarkRecord = newMarkRecords
1701
1702	oldSubTable.MarkArray.MarkCount = len(oldMarkRecords)
1703	newSubTable.MarkArray.MarkCount = len(newMarkRecords)
1704
1705	oldSubTable.BaseArray.BaseRecord = oldBaseRecords
1706	newSubTable.BaseArray = oldSubTable.BaseArray.__class__()
1707	newSubTable.BaseArray.BaseRecord = newBaseRecords
1708
1709	oldSubTable.BaseArray.BaseCount = len(oldBaseRecords)
1710	newSubTable.BaseArray.BaseCount = len(newBaseRecords)
1711
1712	return True
1713
1714
1715splitTable = {	'GSUB': {
1716#					1: splitSingleSubst,
1717					2: splitMultipleSubst,
1718					3: splitAlternateSubst,
1719					4: splitLigatureSubst,
1720#					5: splitContextSubst,
1721#					6: splitChainContextSubst,
1722#					7: splitExtensionSubst,
1723#					8: splitReverseChainSingleSubst,
1724					},
1725				'GPOS': {
1726#					1: splitSinglePos,
1727					2: splitPairPos,
1728#					3: splitCursivePos,
1729					4: splitMarkBasePos,
1730#					5: splitMarkLigPos,
1731#					6: splitMarkMarkPos,
1732#					7: splitContextPos,
1733#					8: splitChainContextPos,
1734#					9: splitExtensionPos,
1735					}
1736
1737			}
1738
1739def fixSubTableOverFlows(ttf, overflowRecord):
1740	"""
1741	An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
1742	"""
1743	table = ttf[overflowRecord.tableType].table
1744	lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
1745	subIndex = overflowRecord.SubTableIndex
1746	subtable = lookup.SubTable[subIndex]
1747
1748	# First, try not sharing anything for this subtable...
1749	if not hasattr(subtable, "DontShare"):
1750		subtable.DontShare = True
1751		return True
1752
1753	if hasattr(subtable, 'ExtSubTable'):
1754		# We split the subtable of the Extension table, and add a new Extension table
1755		# to contain the new subtable.
1756
1757		subTableType = subtable.ExtSubTable.__class__.LookupType
1758		extSubTable = subtable
1759		subtable = extSubTable.ExtSubTable
1760		newExtSubTableClass = lookupTypes[overflowRecord.tableType][extSubTable.__class__.LookupType]
1761		newExtSubTable = newExtSubTableClass()
1762		newExtSubTable.Format = extSubTable.Format
1763		toInsert = newExtSubTable
1764
1765		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
1766		newSubTable = newSubTableClass()
1767		newExtSubTable.ExtSubTable = newSubTable
1768	else:
1769		subTableType = subtable.__class__.LookupType
1770		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
1771		newSubTable = newSubTableClass()
1772		toInsert = newSubTable
1773
1774	if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
1775		lookup.SubTableCount = lookup.SubTableCount + 1
1776
1777	try:
1778		splitFunc = splitTable[overflowRecord.tableType][subTableType]
1779	except KeyError:
1780		log.error(
1781			"Don't know how to split %s lookup type %s",
1782			overflowRecord.tableType,
1783			subTableType,
1784		)
1785		return False
1786
1787	ok = splitFunc(subtable, newSubTable, overflowRecord)
1788	if ok:
1789		lookup.SubTable.insert(subIndex + 1, toInsert)
1790	return ok
1791
1792# End of OverFlow logic
1793
1794
1795def _buildClasses():
1796	import re
1797	from .otData import otData
1798
1799	formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$")
1800	namespace = globals()
1801
1802	# populate module with classes
1803	for name, table in otData:
1804		baseClass = BaseTable
1805		m = formatPat.match(name)
1806		if m:
1807			# XxxFormatN subtable, we only add the "base" table
1808			name = m.group(1)
1809			# the first row of a format-switching otData table describes the Format;
1810			# the first column defines the type of the Format field.
1811			# Currently this can be either 'uint16' or 'uint8'.
1812			formatType = table[0][0]
1813			baseClass = getFormatSwitchingBaseTableClass(formatType)
1814		if name not in namespace:
1815			# the class doesn't exist yet, so the base implementation is used.
1816			cls = type(name, (baseClass,), {})
1817			if name in ('GSUB', 'GPOS'):
1818				cls.DontShare = True
1819			namespace[name] = cls
1820
1821	for base, alts in _equivalents.items():
1822		base = namespace[base]
1823		for alt in alts:
1824			namespace[alt] = base
1825
1826	global lookupTypes
1827	lookupTypes = {
1828		'GSUB': {
1829			1: SingleSubst,
1830			2: MultipleSubst,
1831			3: AlternateSubst,
1832			4: LigatureSubst,
1833			5: ContextSubst,
1834			6: ChainContextSubst,
1835			7: ExtensionSubst,
1836			8: ReverseChainSingleSubst,
1837		},
1838		'GPOS': {
1839			1: SinglePos,
1840			2: PairPos,
1841			3: CursivePos,
1842			4: MarkBasePos,
1843			5: MarkLigPos,
1844			6: MarkMarkPos,
1845			7: ContextPos,
1846			8: ChainContextPos,
1847			9: ExtensionPos,
1848		},
1849		'mort': {
1850			4: NoncontextualMorph,
1851		},
1852		'morx': {
1853			0: RearrangementMorph,
1854			1: ContextualMorph,
1855			2: LigatureMorph,
1856			# 3: Reserved,
1857			4: NoncontextualMorph,
1858			5: InsertionMorph,
1859		},
1860	}
1861	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
1862	for lookupEnum in lookupTypes.values():
1863		for enum, cls in lookupEnum.items():
1864			cls.LookupType = enum
1865
1866	global featureParamTypes
1867	featureParamTypes = {
1868		'size': FeatureParamsSize,
1869	}
1870	for i in range(1, 20+1):
1871		featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
1872	for i in range(1, 99+1):
1873		featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
1874
1875	# add converters to classes
1876	from .otConverters import buildConverters
1877	for name, table in otData:
1878		m = formatPat.match(name)
1879		if m:
1880			# XxxFormatN subtable, add converter to "base" table
1881			name, format = m.groups()
1882			format = int(format)
1883			cls = namespace[name]
1884			if not hasattr(cls, "converters"):
1885				cls.converters = {}
1886				cls.convertersByName = {}
1887			converters, convertersByName = buildConverters(table[1:], namespace)
1888			cls.converters[format] = converters
1889			cls.convertersByName[format] = convertersByName
1890			# XXX Add staticSize?
1891		else:
1892			cls = namespace[name]
1893			cls.converters, cls.convertersByName = buildConverters(table, namespace)
1894			# XXX Add staticSize?
1895
1896
1897_buildClasses()
1898
1899
1900def _getGlyphsFromCoverageTable(coverage):
1901	if coverage is None:
1902		# empty coverage table
1903		return []
1904	else:
1905		return coverage.glyphs
1906