• 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"""
8import copy
9from enum import IntEnum
10import itertools
11from collections import defaultdict, namedtuple
12from fontTools.misc.roundTools import otRound
13from fontTools.misc.textTools import bytesjoin, 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.getGlyphNameMany(reader.readUShortArray(count))
429
430	def toXML(self, xmlWriter, font, attrs, name):
431		xmlWriter.begintag(name, **attrs)
432		xmlWriter.newline()
433		xmlWriter.simpletag("NewState", value=self.NewState)
434		xmlWriter.newline()
435		self._writeFlagsToXML(xmlWriter)
436		for g in self.CurrentInsertionAction:
437			xmlWriter.simpletag("CurrentInsertionAction", glyph=g)
438			xmlWriter.newline()
439		for g in self.MarkedInsertionAction:
440			xmlWriter.simpletag("MarkedInsertionAction", glyph=g)
441			xmlWriter.newline()
442		xmlWriter.endtag(name)
443		xmlWriter.newline()
444
445	def fromXML(self, name, attrs, content, font):
446		self.__init__()
447		content = [t for t in content if isinstance(t, tuple)]
448		for eltName, eltAttrs, eltContent in content:
449			if eltName == "NewState":
450				self.NewState = safeEval(eltAttrs["value"])
451			elif eltName == "Flags":
452				for flag in eltAttrs["value"].split(","):
453					self._setFlag(flag.strip())
454			elif eltName == "CurrentInsertionAction":
455				self.CurrentInsertionAction.append(
456					eltAttrs["glyph"])
457			elif eltName == "MarkedInsertionAction":
458				self.MarkedInsertionAction.append(
459					eltAttrs["glyph"])
460			else:
461				assert False, eltName
462
463	@staticmethod
464	def compileActions(font, states):
465		actions, actionIndex, result = set(), {}, b""
466		for state in states:
467			for _glyphClass, trans in state.Transitions.items():
468				if trans.CurrentInsertionAction is not None:
469					actions.add(tuple(trans.CurrentInsertionAction))
470				if trans.MarkedInsertionAction is not None:
471					actions.add(tuple(trans.MarkedInsertionAction))
472		# Sort the compiled actions in decreasing order of
473		# length, so that the longer sequence come before the
474		# shorter ones.
475		for action in sorted(actions, key=lambda x:(-len(x), x)):
476			# We insert all sub-sequences of the action glyph sequence
477			# into actionIndex. For example, if one action triggers on
478			# glyph sequence [A, B, C, D, E] and another action triggers
479			# on [C, D], we return result=[A, B, C, D, E] (as list of
480			# encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0,
481			# ('C','D'): 2}.
482			if action in actionIndex:
483				continue
484			for start in range(0, len(action)):
485				startIndex = (len(result) // 2) + start
486				for limit in range(start, len(action)):
487					glyphs = action[start : limit + 1]
488					actionIndex.setdefault(glyphs, startIndex)
489			for glyph in action:
490				glyphID = font.getGlyphID(glyph)
491				result += struct.pack(">H", glyphID)
492		return result, actionIndex
493
494
495class FeatureParams(BaseTable):
496
497	def compile(self, writer, font):
498		assert featureParamTypes.get(writer['FeatureTag']) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
499		BaseTable.compile(self, writer, font)
500
501	def toXML(self, xmlWriter, font, attrs=None, name=None):
502		BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__)
503
504class FeatureParamsSize(FeatureParams):
505	pass
506
507class FeatureParamsStylisticSet(FeatureParams):
508	pass
509
510class FeatureParamsCharacterVariants(FeatureParams):
511	pass
512
513class Coverage(FormatSwitchingBaseTable):
514
515	# manual implementation to get rid of glyphID dependencies
516
517	def populateDefaults(self, propagator=None):
518		if not hasattr(self, 'glyphs'):
519			self.glyphs = []
520
521	def postRead(self, rawTable, font):
522		if self.Format == 1:
523			self.glyphs = rawTable["GlyphArray"]
524		elif self.Format == 2:
525			glyphs = self.glyphs = []
526			ranges = rawTable["RangeRecord"]
527			# Some SIL fonts have coverage entries that don't have sorted
528			# StartCoverageIndex.  If it is so, fixup and warn.  We undo
529			# this when writing font out.
530			sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
531			if ranges != sorted_ranges:
532				log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
533				ranges = sorted_ranges
534			del sorted_ranges
535			for r in ranges:
536				start = r.Start
537				end = r.End
538				startID = font.getGlyphID(start)
539				endID = font.getGlyphID(end) + 1
540				glyphs.extend(font.getGlyphNameMany(range(startID, endID)))
541		else:
542			self.glyphs = []
543			log.warning("Unknown Coverage format: %s", self.Format)
544		del self.Format # Don't need this anymore
545
546	def preWrite(self, font):
547		glyphs = getattr(self, "glyphs", None)
548		if glyphs is None:
549			glyphs = self.glyphs = []
550		format = 1
551		rawTable = {"GlyphArray": glyphs}
552		if glyphs:
553			# find out whether Format 2 is more compact or not
554			glyphIDs = font.getGlyphIDMany(glyphs)
555			brokenOrder = sorted(glyphIDs) != glyphIDs
556
557			last = glyphIDs[0]
558			ranges = [[last]]
559			for glyphID in glyphIDs[1:]:
560				if glyphID != last + 1:
561					ranges[-1].append(last)
562					ranges.append([glyphID])
563				last = glyphID
564			ranges[-1].append(last)
565
566			if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
567				# Format 2 is more compact
568				index = 0
569				for i in range(len(ranges)):
570					start, end = ranges[i]
571					r = RangeRecord()
572					r.StartID = start
573					r.Start = font.getGlyphName(start)
574					r.End = font.getGlyphName(end)
575					r.StartCoverageIndex = index
576					ranges[i] = r
577					index = index + end - start + 1
578				if brokenOrder:
579					log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
580					ranges.sort(key=lambda a: a.StartID)
581				for r in ranges:
582					del r.StartID
583				format = 2
584				rawTable = {"RangeRecord": ranges}
585			#else:
586			#	fallthrough; Format 1 is more compact
587		self.Format = format
588		return rawTable
589
590	def toXML2(self, xmlWriter, font):
591		for glyphName in getattr(self, "glyphs", []):
592			xmlWriter.simpletag("Glyph", value=glyphName)
593			xmlWriter.newline()
594
595	def fromXML(self, name, attrs, content, font):
596		glyphs = getattr(self, "glyphs", None)
597		if glyphs is None:
598			glyphs = []
599			self.glyphs = glyphs
600		glyphs.append(attrs["value"])
601
602
603# The special 0xFFFFFFFF delta-set index is used to indicate that there
604# is no variation data in the ItemVariationStore for a given variable field
605NO_VARIATION_INDEX = 0xFFFFFFFF
606
607
608class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
609
610	def populateDefaults(self, propagator=None):
611		if not hasattr(self, 'mapping'):
612			self.mapping = []
613
614	def postRead(self, rawTable, font):
615		assert (rawTable['EntryFormat'] & 0xFFC0) == 0
616		self.mapping = rawTable['mapping']
617
618	@staticmethod
619	def getEntryFormat(mapping):
620		ored = 0
621		for idx in mapping:
622			ored |= idx
623
624		inner = ored & 0xFFFF
625		innerBits = 0
626		while inner:
627			innerBits += 1
628			inner >>= 1
629		innerBits = max(innerBits, 1)
630		assert innerBits <= 16
631
632		ored = (ored >> (16-innerBits)) | (ored & ((1<<innerBits)-1))
633		if   ored <= 0x000000FF:
634			entrySize = 1
635		elif ored <= 0x0000FFFF:
636			entrySize = 2
637		elif ored <= 0x00FFFFFF:
638			entrySize = 3
639		else:
640			entrySize = 4
641
642		return ((entrySize - 1) << 4) | (innerBits - 1)
643
644	def preWrite(self, font):
645		mapping = getattr(self, "mapping", None)
646		if mapping is None:
647			mapping = self.mapping = []
648		self.Format = 1 if len(mapping) > 0xFFFF else 0
649		rawTable = self.__dict__.copy()
650		rawTable['MappingCount'] = len(mapping)
651		rawTable['EntryFormat'] = self.getEntryFormat(mapping)
652		return rawTable
653
654	def toXML2(self, xmlWriter, font):
655		# Make xml dump less verbose, by omitting no-op entries like:
656		#   <Map index="..." outer="65535" inner="65535"/>
657		xmlWriter.comment(
658			"Omitted values default to 0xFFFF/0xFFFF (no variations)"
659		)
660		xmlWriter.newline()
661		for i, value in enumerate(getattr(self, "mapping", [])):
662			attrs = [('index', i)]
663			if value != NO_VARIATION_INDEX:
664				attrs.extend([
665					('outer', value >> 16),
666					('inner', value & 0xFFFF),
667				])
668			xmlWriter.simpletag("Map", attrs)
669			xmlWriter.newline()
670
671	def fromXML(self, name, attrs, content, font):
672		mapping = getattr(self, "mapping", None)
673		if mapping is None:
674			self.mapping = mapping = []
675		index = safeEval(attrs['index'])
676		outer = safeEval(attrs.get('outer', '0xFFFF'))
677		inner = safeEval(attrs.get('inner', '0xFFFF'))
678		assert inner <= 0xFFFF
679		mapping.insert(index, (outer << 16) | inner)
680
681
682class VarIdxMap(BaseTable):
683
684	def populateDefaults(self, propagator=None):
685		if not hasattr(self, 'mapping'):
686			self.mapping = {}
687
688	def postRead(self, rawTable, font):
689		assert (rawTable['EntryFormat'] & 0xFFC0) == 0
690		glyphOrder = font.getGlyphOrder()
691		mapList = rawTable['mapping']
692		mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList)))
693		self.mapping = dict(zip(glyphOrder, mapList))
694
695	def preWrite(self, font):
696		mapping = getattr(self, "mapping", None)
697		if mapping is None:
698			mapping = self.mapping = {}
699
700		glyphOrder = font.getGlyphOrder()
701		mapping = [mapping[g] for g in glyphOrder]
702		while len(mapping) > 1 and mapping[-2] == mapping[-1]:
703			del mapping[-1]
704
705		rawTable = {'mapping': mapping}
706		rawTable['MappingCount'] = len(mapping)
707		rawTable['EntryFormat'] = DeltaSetIndexMap.getEntryFormat(mapping)
708		return rawTable
709
710	def toXML2(self, xmlWriter, font):
711		for glyph, value in sorted(getattr(self, "mapping", {}).items()):
712			attrs = (
713				('glyph', glyph),
714				('outer', value >> 16),
715				('inner', value & 0xFFFF),
716			)
717			xmlWriter.simpletag("Map", attrs)
718			xmlWriter.newline()
719
720	def fromXML(self, name, attrs, content, font):
721		mapping = getattr(self, "mapping", None)
722		if mapping is None:
723			mapping = {}
724			self.mapping = mapping
725		try:
726			glyph = attrs['glyph']
727		except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836
728			glyph = font.getGlyphOrder()[attrs['index']]
729		outer = safeEval(attrs['outer'])
730		inner = safeEval(attrs['inner'])
731		assert inner <= 0xFFFF
732		mapping[glyph] = (outer << 16) | inner
733
734
735class VarRegionList(BaseTable):
736
737	def preWrite(self, font):
738		# The OT spec says VarStore.VarRegionList.RegionAxisCount should always
739		# be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
740		# even when the VarRegionList is empty. We can't treat RegionAxisCount
741		# like a normal propagated count (== len(Region[i].VarRegionAxis)),
742		# otherwise it would default to 0 if VarRegionList is empty.
743		# Thus, we force it to always be equal to fvar.axisCount.
744		# https://github.com/khaledhosny/ots/pull/192
745		fvarTable = font.get("fvar")
746		if fvarTable:
747			self.RegionAxisCount = len(fvarTable.axes)
748		return {
749			**self.__dict__,
750			"RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount")
751		}
752
753
754class SingleSubst(FormatSwitchingBaseTable):
755
756	def populateDefaults(self, propagator=None):
757		if not hasattr(self, 'mapping'):
758			self.mapping = {}
759
760	def postRead(self, rawTable, font):
761		mapping = {}
762		input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
763		if self.Format == 1:
764			delta = rawTable["DeltaGlyphID"]
765			inputGIDS = font.getGlyphIDMany(input)
766			outGIDS = [ (glyphID + delta) % 65536 for glyphID in inputGIDS ]
767			outNames = font.getGlyphNameMany(outGIDS)
768			for inp, out in zip(input, outNames):
769				mapping[inp] = out
770		elif self.Format == 2:
771			assert len(input) == rawTable["GlyphCount"], \
772					"invalid SingleSubstFormat2 table"
773			subst = rawTable["Substitute"]
774			for inp, sub in zip(input, subst):
775				mapping[inp] = sub
776		else:
777			assert 0, "unknown format: %s" % self.Format
778		self.mapping = mapping
779		del self.Format # Don't need this anymore
780
781	def preWrite(self, font):
782		mapping = getattr(self, "mapping", None)
783		if mapping is None:
784			mapping = self.mapping = {}
785		items = list(mapping.items())
786		getGlyphID = font.getGlyphID
787		gidItems = [(getGlyphID(a), getGlyphID(b)) for a,b in items]
788		sortableItems = sorted(zip(gidItems, items))
789
790		# figure out format
791		format = 2
792		delta = None
793		for inID, outID in gidItems:
794			if delta is None:
795				delta = (outID - inID) % 65536
796
797			if (inID + delta) % 65536 != outID:
798					break
799		else:
800			if delta is None:
801				# the mapping is empty, better use format 2
802				format = 2
803			else:
804				format = 1
805
806		rawTable = {}
807		self.Format = format
808		cov = Coverage()
809		input =  [ item [1][0] for item in sortableItems]
810		subst =  [ item [1][1] for item in sortableItems]
811		cov.glyphs = input
812		rawTable["Coverage"] = cov
813		if format == 1:
814			assert delta is not None
815			rawTable["DeltaGlyphID"] = delta
816		else:
817			rawTable["Substitute"] = subst
818		return rawTable
819
820	def toXML2(self, xmlWriter, font):
821		items = sorted(self.mapping.items())
822		for inGlyph, outGlyph in items:
823			xmlWriter.simpletag("Substitution",
824					[("in", inGlyph), ("out", outGlyph)])
825			xmlWriter.newline()
826
827	def fromXML(self, name, attrs, content, font):
828		mapping = getattr(self, "mapping", None)
829		if mapping is None:
830			mapping = {}
831			self.mapping = mapping
832		mapping[attrs["in"]] = attrs["out"]
833
834
835class MultipleSubst(FormatSwitchingBaseTable):
836
837	def populateDefaults(self, propagator=None):
838		if not hasattr(self, 'mapping'):
839			self.mapping = {}
840
841	def postRead(self, rawTable, font):
842		mapping = {}
843		if self.Format == 1:
844			glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"])
845			subst = [s.Substitute for s in rawTable["Sequence"]]
846			mapping = dict(zip(glyphs, subst))
847		else:
848			assert 0, "unknown format: %s" % self.Format
849		self.mapping = mapping
850		del self.Format # Don't need this anymore
851
852	def preWrite(self, font):
853		mapping = getattr(self, "mapping", None)
854		if mapping is None:
855			mapping = self.mapping = {}
856		cov = Coverage()
857		cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID)
858		self.Format = 1
859		rawTable = {
860                        "Coverage": cov,
861                        "Sequence": [self.makeSequence_(mapping[glyph])
862                                     for glyph in cov.glyphs],
863                }
864		return rawTable
865
866	def toXML2(self, xmlWriter, font):
867		items = sorted(self.mapping.items())
868		for inGlyph, outGlyphs in items:
869			out = ",".join(outGlyphs)
870			xmlWriter.simpletag("Substitution",
871					[("in", inGlyph), ("out", out)])
872			xmlWriter.newline()
873
874	def fromXML(self, name, attrs, content, font):
875		mapping = getattr(self, "mapping", None)
876		if mapping is None:
877			mapping = {}
878			self.mapping = mapping
879
880		# TTX v3.0 and earlier.
881		if name == "Coverage":
882			self.old_coverage_ = []
883			for element in content:
884				if not isinstance(element, tuple):
885					continue
886				element_name, element_attrs, _ = element
887				if element_name == "Glyph":
888					self.old_coverage_.append(element_attrs["value"])
889			return
890		if name == "Sequence":
891			index = int(attrs.get("index", len(mapping)))
892			glyph = self.old_coverage_[index]
893			glyph_mapping = mapping[glyph] = []
894			for element in content:
895				if not isinstance(element, tuple):
896					continue
897				element_name, element_attrs, _ = element
898				if element_name == "Substitute":
899					glyph_mapping.append(element_attrs["value"])
900			return
901
902                # TTX v3.1 and later.
903		outGlyphs = attrs["out"].split(",") if attrs["out"] else []
904		mapping[attrs["in"]] = [g.strip() for g in outGlyphs]
905
906	@staticmethod
907	def makeSequence_(g):
908		seq = Sequence()
909		seq.Substitute = g
910		return seq
911
912
913class ClassDef(FormatSwitchingBaseTable):
914
915	def populateDefaults(self, propagator=None):
916		if not hasattr(self, 'classDefs'):
917			self.classDefs = {}
918
919	def postRead(self, rawTable, font):
920		classDefs = {}
921
922		if self.Format == 1:
923			start = rawTable["StartGlyph"]
924			classList = rawTable["ClassValueArray"]
925			startID = font.getGlyphID(start)
926			endID = startID + len(classList)
927			glyphNames = font.getGlyphNameMany(range(startID, endID))
928			for glyphName, cls in zip(glyphNames, classList):
929				if cls:
930					classDefs[glyphName] = cls
931
932		elif self.Format == 2:
933			records = rawTable["ClassRangeRecord"]
934			for rec in records:
935				cls = rec.Class
936				if not cls:
937					continue
938				start = rec.Start
939				end = rec.End
940				startID = font.getGlyphID(start)
941				endID = font.getGlyphID(end) + 1
942				glyphNames = font.getGlyphNameMany(range(startID, endID))
943				for glyphName in glyphNames:
944					classDefs[glyphName] = cls
945		else:
946			log.warning("Unknown ClassDef format: %s", self.Format)
947		self.classDefs = classDefs
948		del self.Format # Don't need this anymore
949
950	def _getClassRanges(self, font):
951		classDefs = getattr(self, "classDefs", None)
952		if classDefs is None:
953			self.classDefs = {}
954			return
955		getGlyphID = font.getGlyphID
956		items = []
957		for glyphName, cls in classDefs.items():
958			if not cls:
959				continue
960			items.append((getGlyphID(glyphName), glyphName, cls))
961		if items:
962			items.sort()
963			last, lastName, lastCls = items[0]
964			ranges = [[lastCls, last, lastName]]
965			for glyphID, glyphName, cls in items[1:]:
966				if glyphID != last + 1 or cls != lastCls:
967					ranges[-1].extend([last, lastName])
968					ranges.append([cls, glyphID, glyphName])
969				last = glyphID
970				lastName = glyphName
971				lastCls = cls
972			ranges[-1].extend([last, lastName])
973			return ranges
974
975	def preWrite(self, font):
976		format = 2
977		rawTable = {"ClassRangeRecord": []}
978		ranges = self._getClassRanges(font)
979		if ranges:
980			startGlyph = ranges[0][1]
981			endGlyph = ranges[-1][3]
982			glyphCount = endGlyph - startGlyph + 1
983			if len(ranges) * 3 < glyphCount + 1:
984				# Format 2 is more compact
985				for i in range(len(ranges)):
986					cls, start, startName, end, endName = ranges[i]
987					rec = ClassRangeRecord()
988					rec.Start = startName
989					rec.End = endName
990					rec.Class = cls
991					ranges[i] = rec
992				format = 2
993				rawTable = {"ClassRangeRecord": ranges}
994			else:
995				# Format 1 is more compact
996				startGlyphName = ranges[0][2]
997				classes = [0] * glyphCount
998				for cls, start, startName, end, endName in ranges:
999					for g in range(start - startGlyph, end - startGlyph + 1):
1000						classes[g] = cls
1001				format = 1
1002				rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes}
1003		self.Format = format
1004		return rawTable
1005
1006	def toXML2(self, xmlWriter, font):
1007		items = sorted(self.classDefs.items())
1008		for glyphName, cls in items:
1009			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
1010			xmlWriter.newline()
1011
1012	def fromXML(self, name, attrs, content, font):
1013		classDefs = getattr(self, "classDefs", None)
1014		if classDefs is None:
1015			classDefs = {}
1016			self.classDefs = classDefs
1017		classDefs[attrs["glyph"]] = int(attrs["class"])
1018
1019
1020class AlternateSubst(FormatSwitchingBaseTable):
1021
1022	def populateDefaults(self, propagator=None):
1023		if not hasattr(self, 'alternates'):
1024			self.alternates = {}
1025
1026	def postRead(self, rawTable, font):
1027		alternates = {}
1028		if self.Format == 1:
1029			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
1030			alts = rawTable["AlternateSet"]
1031			assert len(input) == len(alts)
1032			for inp,alt in zip(input,alts):
1033				alternates[inp] = alt.Alternate
1034		else:
1035			assert 0, "unknown format: %s" % self.Format
1036		self.alternates = alternates
1037		del self.Format # Don't need this anymore
1038
1039	def preWrite(self, font):
1040		self.Format = 1
1041		alternates = getattr(self, "alternates", None)
1042		if alternates is None:
1043			alternates = self.alternates = {}
1044		items = list(alternates.items())
1045		for i in range(len(items)):
1046			glyphName, set = items[i]
1047			items[i] = font.getGlyphID(glyphName), glyphName, set
1048		items.sort()
1049		cov = Coverage()
1050		cov.glyphs = [ item[1] for item in items]
1051		alternates = []
1052		setList = [ item[-1] for item in items]
1053		for set in setList:
1054			alts = AlternateSet()
1055			alts.Alternate = set
1056			alternates.append(alts)
1057		# a special case to deal with the fact that several hundred Adobe Japan1-5
1058		# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
1059		# Also useful in that when splitting a sub-table because of an offset overflow
1060		# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
1061		# Allows packing more rules in subtable.
1062		self.sortCoverageLast = 1
1063		return {"Coverage": cov, "AlternateSet": alternates}
1064
1065	def toXML2(self, xmlWriter, font):
1066		items = sorted(self.alternates.items())
1067		for glyphName, alternates in items:
1068			xmlWriter.begintag("AlternateSet", glyph=glyphName)
1069			xmlWriter.newline()
1070			for alt in alternates:
1071				xmlWriter.simpletag("Alternate", glyph=alt)
1072				xmlWriter.newline()
1073			xmlWriter.endtag("AlternateSet")
1074			xmlWriter.newline()
1075
1076	def fromXML(self, name, attrs, content, font):
1077		alternates = getattr(self, "alternates", None)
1078		if alternates is None:
1079			alternates = {}
1080			self.alternates = alternates
1081		glyphName = attrs["glyph"]
1082		set = []
1083		alternates[glyphName] = set
1084		for element in content:
1085			if not isinstance(element, tuple):
1086				continue
1087			name, attrs, content = element
1088			set.append(attrs["glyph"])
1089
1090
1091class LigatureSubst(FormatSwitchingBaseTable):
1092
1093	def populateDefaults(self, propagator=None):
1094		if not hasattr(self, 'ligatures'):
1095			self.ligatures = {}
1096
1097	def postRead(self, rawTable, font):
1098		ligatures = {}
1099		if self.Format == 1:
1100			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
1101			ligSets = rawTable["LigatureSet"]
1102			assert len(input) == len(ligSets)
1103			for i in range(len(input)):
1104				ligatures[input[i]] = ligSets[i].Ligature
1105		else:
1106			assert 0, "unknown format: %s" % self.Format
1107		self.ligatures = ligatures
1108		del self.Format # Don't need this anymore
1109
1110	def preWrite(self, font):
1111		self.Format = 1
1112		ligatures = getattr(self, "ligatures", None)
1113		if ligatures is None:
1114			ligatures = self.ligatures = {}
1115
1116		if ligatures and isinstance(next(iter(ligatures)), tuple):
1117			# New high-level API in v3.1 and later.  Note that we just support compiling this
1118			# for now.  We don't load to this API, and don't do XML with it.
1119
1120			# ligatures is map from components-sequence to lig-glyph
1121			newLigatures = dict()
1122			for comps,lig in sorted(ligatures.items(), key=lambda item: (-len(item[0]), item[0])):
1123				ligature = Ligature()
1124				ligature.Component = comps[1:]
1125				ligature.CompCount = len(comps)
1126				ligature.LigGlyph = lig
1127				newLigatures.setdefault(comps[0], []).append(ligature)
1128			ligatures = newLigatures
1129
1130		items = list(ligatures.items())
1131		for i in range(len(items)):
1132			glyphName, set = items[i]
1133			items[i] = font.getGlyphID(glyphName), glyphName, set
1134		items.sort()
1135		cov = Coverage()
1136		cov.glyphs = [ item[1] for item in items]
1137
1138		ligSets = []
1139		setList = [ item[-1] for item in items ]
1140		for set in setList:
1141			ligSet = LigatureSet()
1142			ligs = ligSet.Ligature = []
1143			for lig in set:
1144				ligs.append(lig)
1145			ligSets.append(ligSet)
1146		# Useful in that when splitting a sub-table because of an offset overflow
1147		# I don't need to calculate the change in subtabl offset due to the coverage table size.
1148		# Allows packing more rules in subtable.
1149		self.sortCoverageLast = 1
1150		return {"Coverage": cov, "LigatureSet": ligSets}
1151
1152	def toXML2(self, xmlWriter, font):
1153		items = sorted(self.ligatures.items())
1154		for glyphName, ligSets in items:
1155			xmlWriter.begintag("LigatureSet", glyph=glyphName)
1156			xmlWriter.newline()
1157			for lig in ligSets:
1158				xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
1159					components=",".join(lig.Component))
1160				xmlWriter.newline()
1161			xmlWriter.endtag("LigatureSet")
1162			xmlWriter.newline()
1163
1164	def fromXML(self, name, attrs, content, font):
1165		ligatures = getattr(self, "ligatures", None)
1166		if ligatures is None:
1167			ligatures = {}
1168			self.ligatures = ligatures
1169		glyphName = attrs["glyph"]
1170		ligs = []
1171		ligatures[glyphName] = ligs
1172		for element in content:
1173			if not isinstance(element, tuple):
1174				continue
1175			name, attrs, content = element
1176			lig = Ligature()
1177			lig.LigGlyph = attrs["glyph"]
1178			components = attrs["components"]
1179			lig.Component = components.split(",") if components else []
1180			lig.CompCount = len(lig.Component)
1181			ligs.append(lig)
1182
1183
1184class COLR(BaseTable):
1185
1186	def decompile(self, reader, font):
1187		# COLRv0 is exceptional in that LayerRecordCount appears *after* the
1188		# LayerRecordArray it counts, but the parser logic expects Count fields
1189		# to always precede the arrays. Here we work around this by parsing the
1190		# LayerRecordCount before the rest of the table, and storing it in
1191		# the reader's local state.
1192		subReader = reader.getSubReader(offset=0)
1193		for conv in self.getConverters():
1194			if conv.name != "LayerRecordCount":
1195				subReader.advance(conv.staticSize)
1196				continue
1197			reader[conv.name] = conv.read(subReader, font, tableDict={})
1198			break
1199		else:
1200			raise AssertionError("LayerRecordCount converter not found")
1201		return BaseTable.decompile(self, reader, font)
1202
1203	def preWrite(self, font):
1204		# The writer similarly assumes Count values precede the things counted,
1205		# thus here we pre-initialize a CountReference; the actual count value
1206		# will be set to the lenght of the array by the time this is assembled.
1207		self.LayerRecordCount = None
1208		return {
1209			**self.__dict__,
1210			"LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount")
1211		}
1212
1213
1214class LookupList(BaseTable):
1215	@property
1216	def table(self):
1217		for l in self.Lookup:
1218			for st in l.SubTable:
1219				if type(st).__name__.endswith("Subst"):
1220					return "GSUB"
1221				if type(st).__name__.endswith("Pos"):
1222					return "GPOS"
1223		raise ValueError
1224
1225	def toXML2(self, xmlWriter, font):
1226		if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data:
1227			return super().toXML2(xmlWriter, font)
1228		debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
1229		for conv in self.getConverters():
1230			if conv.repeat:
1231				value = getattr(self, conv.name, [])
1232				for lookupIndex, item in enumerate(value):
1233					if str(lookupIndex) in debugData:
1234						info = LookupDebugInfo(*debugData[str(lookupIndex)])
1235						tag = info.location
1236						if info.name:
1237							tag = f'{info.name}: {tag}'
1238						if info.feature:
1239							script,language,feature = info.feature
1240							tag = f'{tag} in {feature} ({script}/{language})'
1241						xmlWriter.comment(tag)
1242						xmlWriter.newline()
1243
1244					conv.xmlWrite(xmlWriter, font, item, conv.name,
1245							[("index", lookupIndex)])
1246			else:
1247				if conv.aux and not eval(conv.aux, None, vars(self)):
1248					continue
1249				value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None!
1250				conv.xmlWrite(xmlWriter, font, value, conv.name, [])
1251
1252class BaseGlyphRecordArray(BaseTable):
1253
1254	def preWrite(self, font):
1255		self.BaseGlyphRecord = sorted(
1256			self.BaseGlyphRecord,
1257			key=lambda rec: font.getGlyphID(rec.BaseGlyph)
1258		)
1259		return self.__dict__.copy()
1260
1261
1262class BaseGlyphList(BaseTable):
1263
1264	def preWrite(self, font):
1265		self.BaseGlyphPaintRecord = sorted(
1266			self.BaseGlyphPaintRecord,
1267			key=lambda rec: font.getGlyphID(rec.BaseGlyph)
1268		)
1269		return self.__dict__.copy()
1270
1271
1272class ClipBoxFormat(IntEnum):
1273	Static = 1
1274	Variable = 2
1275
1276	def is_variable(self):
1277		return self is self.Variable
1278
1279	def as_variable(self):
1280		return self.Variable
1281
1282
1283class ClipBox(getFormatSwitchingBaseTableClass("uint8")):
1284	formatEnum = ClipBoxFormat
1285
1286	def as_tuple(self):
1287		return tuple(getattr(self, conv.name) for conv in self.getConverters())
1288
1289	def __repr__(self):
1290		return f"{self.__class__.__name__}{self.as_tuple()}"
1291
1292
1293class ClipList(getFormatSwitchingBaseTableClass("uint8")):
1294
1295	def populateDefaults(self, propagator=None):
1296		if not hasattr(self, "clips"):
1297			self.clips = {}
1298
1299	def postRead(self, rawTable, font):
1300		clips = {}
1301		glyphOrder = font.getGlyphOrder()
1302		for i, rec in enumerate(rawTable["ClipRecord"]):
1303			if rec.StartGlyphID > rec.EndGlyphID:
1304				log.warning(
1305					"invalid ClipRecord[%i].StartGlyphID (%i) > "
1306					"EndGlyphID (%i); skipped",
1307					i,
1308					rec.StartGlyphID,
1309					rec.EndGlyphID,
1310				)
1311				continue
1312			redefinedGlyphs = []
1313			missingGlyphs = []
1314			for glyphID in range(rec.StartGlyphID, rec.EndGlyphID + 1):
1315				try:
1316					glyph = glyphOrder[glyphID]
1317				except IndexError:
1318					missingGlyphs.append(glyphID)
1319					continue
1320				if glyph not in clips:
1321					clips[glyph] = copy.copy(rec.ClipBox)
1322				else:
1323					redefinedGlyphs.append(glyphID)
1324			if redefinedGlyphs:
1325				log.warning(
1326					"ClipRecord[%i] overlaps previous records; "
1327					"ignoring redefined clip boxes for the "
1328					"following glyph ID range: [%i-%i]",
1329					i,
1330					min(redefinedGlyphs),
1331					max(redefinedGlyphs),
1332				)
1333			if missingGlyphs:
1334				log.warning(
1335					"ClipRecord[%i] range references missing "
1336					"glyph IDs: [%i-%i]",
1337					i,
1338					min(missingGlyphs),
1339					max(missingGlyphs),
1340				)
1341		self.clips = clips
1342
1343	def groups(self):
1344		glyphsByClip = defaultdict(list)
1345		uniqueClips = {}
1346		for glyphName, clipBox in self.clips.items():
1347			key = clipBox.as_tuple()
1348			glyphsByClip[key].append(glyphName)
1349			if key not in uniqueClips:
1350				uniqueClips[key] = clipBox
1351		return {
1352			frozenset(glyphs): uniqueClips[key]
1353			for key, glyphs in glyphsByClip.items()
1354		}
1355
1356	def preWrite(self, font):
1357		if not hasattr(self, "clips"):
1358			self.clips = {}
1359		clipBoxRanges = {}
1360		glyphMap = font.getReverseGlyphMap()
1361		for glyphs, clipBox in self.groups().items():
1362			glyphIDs = sorted(
1363				glyphMap[glyphName] for glyphName in glyphs
1364				if glyphName in glyphMap
1365			)
1366			if not glyphIDs:
1367				continue
1368			last = glyphIDs[0]
1369			ranges = [[last]]
1370			for glyphID in glyphIDs[1:]:
1371				if glyphID != last + 1:
1372					ranges[-1].append(last)
1373					ranges.append([glyphID])
1374				last = glyphID
1375			ranges[-1].append(last)
1376			for start, end in ranges:
1377				assert (start, end) not in clipBoxRanges
1378				clipBoxRanges[(start, end)] = clipBox
1379
1380		clipRecords = []
1381		for (start, end), clipBox in sorted(clipBoxRanges.items()):
1382			record = ClipRecord()
1383			record.StartGlyphID = start
1384			record.EndGlyphID = end
1385			record.ClipBox = clipBox
1386			clipRecords.append(record)
1387		rawTable = {
1388			"ClipCount": len(clipRecords),
1389			"ClipRecord": clipRecords,
1390		}
1391		return rawTable
1392
1393	def toXML(self, xmlWriter, font, attrs=None, name=None):
1394		tableName = name if name else self.__class__.__name__
1395		if attrs is None:
1396			attrs = []
1397		if hasattr(self, "Format"):
1398			attrs.append(("Format", self.Format))
1399		xmlWriter.begintag(tableName, attrs)
1400		xmlWriter.newline()
1401		# sort clips alphabetically to ensure deterministic XML dump
1402		for glyphs, clipBox in sorted(
1403			self.groups().items(), key=lambda item: min(item[0])
1404		):
1405			xmlWriter.begintag("Clip")
1406			xmlWriter.newline()
1407			for glyphName in sorted(glyphs):
1408				xmlWriter.simpletag("Glyph", value=glyphName)
1409				xmlWriter.newline()
1410			xmlWriter.begintag("ClipBox", [("Format", clipBox.Format)])
1411			xmlWriter.newline()
1412			clipBox.toXML2(xmlWriter, font)
1413			xmlWriter.endtag("ClipBox")
1414			xmlWriter.newline()
1415			xmlWriter.endtag("Clip")
1416			xmlWriter.newline()
1417		xmlWriter.endtag(tableName)
1418		xmlWriter.newline()
1419
1420	def fromXML(self, name, attrs, content, font):
1421		clips = getattr(self, "clips", None)
1422		if clips is None:
1423			self.clips = clips = {}
1424		assert name == "Clip"
1425		glyphs = []
1426		clipBox = None
1427		for elem in content:
1428			if not isinstance(elem, tuple):
1429				continue
1430			name, attrs, content = elem
1431			if name == "Glyph":
1432				glyphs.append(attrs["value"])
1433			elif name == "ClipBox":
1434				clipBox = ClipBox()
1435				clipBox.Format = safeEval(attrs["Format"])
1436				for elem in content:
1437					if not isinstance(elem, tuple):
1438						continue
1439					name, attrs, content = elem
1440					clipBox.fromXML(name, attrs, content, font)
1441		if clipBox:
1442			for glyphName in glyphs:
1443				clips[glyphName] = clipBox
1444
1445
1446class ExtendMode(IntEnum):
1447	PAD = 0
1448	REPEAT = 1
1449	REFLECT = 2
1450
1451
1452# Porter-Duff modes for COLRv1 PaintComposite:
1453# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration
1454class CompositeMode(IntEnum):
1455	CLEAR = 0
1456	SRC = 1
1457	DEST = 2
1458	SRC_OVER = 3
1459	DEST_OVER = 4
1460	SRC_IN = 5
1461	DEST_IN = 6
1462	SRC_OUT = 7
1463	DEST_OUT = 8
1464	SRC_ATOP = 9
1465	DEST_ATOP = 10
1466	XOR = 11
1467	PLUS = 12
1468	SCREEN = 13
1469	OVERLAY = 14
1470	DARKEN = 15
1471	LIGHTEN = 16
1472	COLOR_DODGE = 17
1473	COLOR_BURN = 18
1474	HARD_LIGHT = 19
1475	SOFT_LIGHT = 20
1476	DIFFERENCE = 21
1477	EXCLUSION = 22
1478	MULTIPLY = 23
1479	HSL_HUE = 24
1480	HSL_SATURATION = 25
1481	HSL_COLOR = 26
1482	HSL_LUMINOSITY = 27
1483
1484
1485class PaintFormat(IntEnum):
1486	PaintColrLayers = 1
1487	PaintSolid = 2
1488	PaintVarSolid = 3,
1489	PaintLinearGradient = 4
1490	PaintVarLinearGradient = 5
1491	PaintRadialGradient = 6
1492	PaintVarRadialGradient = 7
1493	PaintSweepGradient = 8
1494	PaintVarSweepGradient = 9
1495	PaintGlyph = 10
1496	PaintColrGlyph = 11
1497	PaintTransform = 12
1498	PaintVarTransform = 13
1499	PaintTranslate = 14
1500	PaintVarTranslate = 15
1501	PaintScale = 16
1502	PaintVarScale = 17
1503	PaintScaleAroundCenter = 18
1504	PaintVarScaleAroundCenter = 19
1505	PaintScaleUniform = 20
1506	PaintVarScaleUniform = 21
1507	PaintScaleUniformAroundCenter = 22
1508	PaintVarScaleUniformAroundCenter = 23
1509	PaintRotate = 24
1510	PaintVarRotate = 25
1511	PaintRotateAroundCenter = 26
1512	PaintVarRotateAroundCenter = 27
1513	PaintSkew = 28
1514	PaintVarSkew = 29
1515	PaintSkewAroundCenter = 30
1516	PaintVarSkewAroundCenter = 31
1517	PaintComposite = 32
1518
1519	def is_variable(self):
1520		return self.name.startswith("PaintVar")
1521
1522	def as_variable(self):
1523		if self.is_variable():
1524			return self
1525		try:
1526			return PaintFormat.__members__[f"PaintVar{self.name[5:]}"]
1527		except KeyError:
1528			return None
1529
1530
1531class Paint(getFormatSwitchingBaseTableClass("uint8")):
1532	formatEnum = PaintFormat
1533
1534	def getFormatName(self):
1535		try:
1536			return self.formatEnum(self.Format).name
1537		except ValueError:
1538			raise NotImplementedError(f"Unknown Paint format: {self.Format}")
1539
1540	def toXML(self, xmlWriter, font, attrs=None, name=None):
1541		tableName = name if name else self.__class__.__name__
1542		if attrs is None:
1543			attrs = []
1544		attrs.append(("Format", self.Format))
1545		xmlWriter.begintag(tableName, attrs)
1546		xmlWriter.comment(self.getFormatName())
1547		xmlWriter.newline()
1548		self.toXML2(xmlWriter, font)
1549		xmlWriter.endtag(tableName)
1550		xmlWriter.newline()
1551
1552	def getChildren(self, colr):
1553		if self.Format == PaintFormat.PaintColrLayers:
1554			# https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists
1555			layers = []
1556			if colr.LayerList is not None:
1557				layers = colr.LayerList.Paint
1558			return layers[
1559				self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers
1560			]
1561
1562		if self.Format == PaintFormat.PaintColrGlyph:
1563			for record in colr.BaseGlyphList.BaseGlyphPaintRecord:
1564				if record.BaseGlyph == self.Glyph:
1565					return [record.Paint]
1566			else:
1567				raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphList")
1568
1569		children = []
1570		for conv in self.getConverters():
1571			if conv.tableClass is not None and issubclass(conv.tableClass, type(self)):
1572				children.append(getattr(self, conv.name))
1573
1574		return children
1575
1576	def traverse(self, colr: COLR, callback):
1577		"""Depth-first traversal of graph rooted at self, callback on each node."""
1578		if not callable(callback):
1579			raise TypeError("callback must be callable")
1580		stack = [self]
1581		visited = set()
1582		while stack:
1583			current = stack.pop()
1584			if id(current) in visited:
1585				continue
1586			callback(current)
1587			visited.add(id(current))
1588			stack.extend(reversed(current.getChildren(colr)))
1589
1590
1591# For each subtable format there is a class. However, we don't really distinguish
1592# between "field name" and "format name": often these are the same. Yet there's
1593# a whole bunch of fields with different names. The following dict is a mapping
1594# from "format name" to "field name". _buildClasses() uses this to create a
1595# subclass for each alternate field name.
1596#
1597_equivalents = {
1598	'MarkArray': ("Mark1Array",),
1599	'LangSys': ('DefaultLangSys',),
1600	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
1601			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
1602			'LookAheadCoverage', 'VertGlyphCoverage', 'HorizGlyphCoverage',
1603			'TopAccentCoverage', 'ExtendedShapeCoverage', 'MathKernCoverage'),
1604	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
1605			'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
1606	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
1607			'Mark2Anchor', 'MarkAnchor'),
1608	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
1609			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
1610	'Axis': ('HorizAxis', 'VertAxis',),
1611	'MinMax': ('DefaultMinMax',),
1612	'BaseCoord': ('MinCoord', 'MaxCoord',),
1613	'JstfLangSys': ('DefJstfLangSys',),
1614	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
1615			'ExtensionDisableGSUB',),
1616	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
1617			'ExtensionDisableGPOS',),
1618	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
1619	'MathKern': ('TopRightMathKern', 'TopLeftMathKern', 'BottomRightMathKern',
1620			'BottomLeftMathKern'),
1621	'MathGlyphConstruction': ('VertGlyphConstruction', 'HorizGlyphConstruction'),
1622}
1623
1624#
1625# OverFlow logic, to automatically create ExtensionLookups
1626# XXX This should probably move to otBase.py
1627#
1628
1629def fixLookupOverFlows(ttf, overflowRecord):
1630	""" Either the offset from the LookupList to a lookup overflowed, or
1631	an offset from a lookup to a subtable overflowed.
1632	The table layout is:
1633	GPSO/GUSB
1634		Script List
1635		Feature List
1636		LookUpList
1637			Lookup[0] and contents
1638				SubTable offset list
1639					SubTable[0] and contents
1640					...
1641					SubTable[n] and contents
1642			...
1643			Lookup[n] and contents
1644				SubTable offset list
1645					SubTable[0] and contents
1646					...
1647					SubTable[n] and contents
1648	If the offset to a lookup overflowed (SubTableIndex is None)
1649		we must promote the *previous*	lookup to an Extension type.
1650	If the offset from a lookup to subtable overflowed, then we must promote it
1651		to an Extension Lookup type.
1652	"""
1653	ok = 0
1654	lookupIndex = overflowRecord.LookupListIndex
1655	if (overflowRecord.SubTableIndex is None):
1656		lookupIndex = lookupIndex - 1
1657	if lookupIndex < 0:
1658		return ok
1659	if overflowRecord.tableType == 'GSUB':
1660		extType = 7
1661	elif overflowRecord.tableType == 'GPOS':
1662		extType = 9
1663
1664	lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
1665	lookup = lookups[lookupIndex]
1666	# If the previous lookup is an extType, look further back. Very unlikely, but possible.
1667	while lookup.SubTable[0].__class__.LookupType == extType:
1668		lookupIndex = lookupIndex -1
1669		if lookupIndex < 0:
1670			return ok
1671		lookup = lookups[lookupIndex]
1672
1673	for lookupIndex in range(lookupIndex, len(lookups)):
1674		lookup = lookups[lookupIndex]
1675		if lookup.LookupType != extType:
1676			lookup.LookupType = extType
1677			for si in range(len(lookup.SubTable)):
1678				subTable = lookup.SubTable[si]
1679				extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
1680				extSubTable = extSubTableClass()
1681				extSubTable.Format = 1
1682				extSubTable.ExtSubTable = subTable
1683				lookup.SubTable[si] = extSubTable
1684	ok = 1
1685	return ok
1686
1687def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
1688	ok = 1
1689	oldMapping = sorted(oldSubTable.mapping.items())
1690	oldLen = len(oldMapping)
1691
1692	if overflowRecord.itemName in ['Coverage', 'RangeRecord']:
1693		# Coverage table is written last. Overflow is to or within the
1694		# the coverage table. We will just cut the subtable in half.
1695		newLen = oldLen // 2
1696
1697	elif overflowRecord.itemName == 'Sequence':
1698		# We just need to back up by two items from the overflowed
1699		# Sequence index to make sure the offset to the Coverage table
1700		# doesn't overflow.
1701		newLen = overflowRecord.itemIndex - 1
1702
1703	newSubTable.mapping = {}
1704	for i in range(newLen, oldLen):
1705		item = oldMapping[i]
1706		key = item[0]
1707		newSubTable.mapping[key] = item[1]
1708		del oldSubTable.mapping[key]
1709
1710	return ok
1711
1712def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
1713	ok = 1
1714	if hasattr(oldSubTable, 'sortCoverageLast'):
1715		newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
1716
1717	oldAlts = sorted(oldSubTable.alternates.items())
1718	oldLen = len(oldAlts)
1719
1720	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
1721		# Coverage table is written last. overflow is to or within the
1722		# the coverage table. We will just cut the subtable in half.
1723		newLen = oldLen//2
1724
1725	elif overflowRecord.itemName == 'AlternateSet':
1726		# We just need to back up by two items
1727		# from the overflowed AlternateSet index to make sure the offset
1728		# to the Coverage table doesn't overflow.
1729		newLen = overflowRecord.itemIndex - 1
1730
1731	newSubTable.alternates = {}
1732	for i in range(newLen, oldLen):
1733		item = oldAlts[i]
1734		key = item[0]
1735		newSubTable.alternates[key] = item[1]
1736		del oldSubTable.alternates[key]
1737
1738	return ok
1739
1740
1741def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
1742	ok = 1
1743	oldLigs = sorted(oldSubTable.ligatures.items())
1744	oldLen = len(oldLigs)
1745
1746	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
1747		# Coverage table is written last. overflow is to or within the
1748		# the coverage table. We will just cut the subtable in half.
1749		newLen = oldLen//2
1750
1751	elif overflowRecord.itemName == 'LigatureSet':
1752		# We just need to back up by two items
1753		# from the overflowed AlternateSet index to make sure the offset
1754		# to the Coverage table doesn't overflow.
1755		newLen = overflowRecord.itemIndex - 1
1756
1757	newSubTable.ligatures = {}
1758	for i in range(newLen, oldLen):
1759		item = oldLigs[i]
1760		key = item[0]
1761		newSubTable.ligatures[key] = item[1]
1762		del oldSubTable.ligatures[key]
1763
1764	return ok
1765
1766
1767def splitPairPos(oldSubTable, newSubTable, overflowRecord):
1768	st = oldSubTable
1769	ok = False
1770	newSubTable.Format = oldSubTable.Format
1771	if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1:
1772		for name in 'ValueFormat1', 'ValueFormat2':
1773			setattr(newSubTable, name, getattr(oldSubTable, name))
1774
1775		# Move top half of coverage to new subtable
1776
1777		newSubTable.Coverage = oldSubTable.Coverage.__class__()
1778
1779		coverage = oldSubTable.Coverage.glyphs
1780		records = oldSubTable.PairSet
1781
1782		oldCount = len(oldSubTable.PairSet) // 2
1783
1784		oldSubTable.Coverage.glyphs = coverage[:oldCount]
1785		oldSubTable.PairSet = records[:oldCount]
1786
1787		newSubTable.Coverage.glyphs = coverage[oldCount:]
1788		newSubTable.PairSet = records[oldCount:]
1789
1790		oldSubTable.PairSetCount = len(oldSubTable.PairSet)
1791		newSubTable.PairSetCount = len(newSubTable.PairSet)
1792
1793		ok = True
1794
1795	elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1:
1796		if not hasattr(oldSubTable, 'Class2Count'):
1797			oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record)
1798		for name in 'Class2Count', 'ClassDef2', 'ValueFormat1', 'ValueFormat2':
1799			setattr(newSubTable, name, getattr(oldSubTable, name))
1800
1801		# The two subtables will still have the same ClassDef2 and the table
1802		# sharing will still cause the sharing to overflow.  As such, disable
1803		# sharing on the one that is serialized second (that's oldSubTable).
1804		oldSubTable.DontShare = True
1805
1806		# Move top half of class numbers to new subtable
1807
1808		newSubTable.Coverage = oldSubTable.Coverage.__class__()
1809		newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__()
1810
1811		coverage = oldSubTable.Coverage.glyphs
1812		classDefs = oldSubTable.ClassDef1.classDefs
1813		records = oldSubTable.Class1Record
1814
1815		oldCount = len(oldSubTable.Class1Record) // 2
1816		newGlyphs = set(k for k,v in classDefs.items() if v >= oldCount)
1817
1818		oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs]
1819		oldSubTable.ClassDef1.classDefs = {k:v for k,v in classDefs.items() if v < oldCount}
1820		oldSubTable.Class1Record = records[:oldCount]
1821
1822		newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs]
1823		newSubTable.ClassDef1.classDefs = {k:(v-oldCount) for k,v in classDefs.items() if v > oldCount}
1824		newSubTable.Class1Record = records[oldCount:]
1825
1826		oldSubTable.Class1Count = len(oldSubTable.Class1Record)
1827		newSubTable.Class1Count = len(newSubTable.Class1Record)
1828
1829		ok = True
1830
1831	return ok
1832
1833
1834def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
1835	# split half of the mark classes to the new subtable
1836	classCount = oldSubTable.ClassCount
1837	if classCount < 2:
1838		# oh well, not much left to split...
1839		return False
1840
1841	oldClassCount = classCount // 2
1842	newClassCount = classCount - oldClassCount
1843
1844	oldMarkCoverage, oldMarkRecords = [], []
1845	newMarkCoverage, newMarkRecords = [], []
1846	for glyphName, markRecord in zip(
1847		oldSubTable.MarkCoverage.glyphs,
1848		oldSubTable.MarkArray.MarkRecord
1849	):
1850		if markRecord.Class < oldClassCount:
1851			oldMarkCoverage.append(glyphName)
1852			oldMarkRecords.append(markRecord)
1853		else:
1854			markRecord.Class -= oldClassCount
1855			newMarkCoverage.append(glyphName)
1856			newMarkRecords.append(markRecord)
1857
1858	oldBaseRecords, newBaseRecords = [], []
1859	for rec in oldSubTable.BaseArray.BaseRecord:
1860		oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__()
1861		oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount]
1862		newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:]
1863		oldBaseRecords.append(oldBaseRecord)
1864		newBaseRecords.append(newBaseRecord)
1865
1866	newSubTable.Format = oldSubTable.Format
1867
1868	oldSubTable.MarkCoverage.glyphs = oldMarkCoverage
1869	newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__()
1870	newSubTable.MarkCoverage.glyphs = newMarkCoverage
1871
1872	# share the same BaseCoverage in both halves
1873	newSubTable.BaseCoverage = oldSubTable.BaseCoverage
1874
1875	oldSubTable.ClassCount = oldClassCount
1876	newSubTable.ClassCount = newClassCount
1877
1878	oldSubTable.MarkArray.MarkRecord = oldMarkRecords
1879	newSubTable.MarkArray = oldSubTable.MarkArray.__class__()
1880	newSubTable.MarkArray.MarkRecord = newMarkRecords
1881
1882	oldSubTable.MarkArray.MarkCount = len(oldMarkRecords)
1883	newSubTable.MarkArray.MarkCount = len(newMarkRecords)
1884
1885	oldSubTable.BaseArray.BaseRecord = oldBaseRecords
1886	newSubTable.BaseArray = oldSubTable.BaseArray.__class__()
1887	newSubTable.BaseArray.BaseRecord = newBaseRecords
1888
1889	oldSubTable.BaseArray.BaseCount = len(oldBaseRecords)
1890	newSubTable.BaseArray.BaseCount = len(newBaseRecords)
1891
1892	return True
1893
1894
1895splitTable = {	'GSUB': {
1896#					1: splitSingleSubst,
1897					2: splitMultipleSubst,
1898					3: splitAlternateSubst,
1899					4: splitLigatureSubst,
1900#					5: splitContextSubst,
1901#					6: splitChainContextSubst,
1902#					7: splitExtensionSubst,
1903#					8: splitReverseChainSingleSubst,
1904					},
1905				'GPOS': {
1906#					1: splitSinglePos,
1907					2: splitPairPos,
1908#					3: splitCursivePos,
1909					4: splitMarkBasePos,
1910#					5: splitMarkLigPos,
1911#					6: splitMarkMarkPos,
1912#					7: splitContextPos,
1913#					8: splitChainContextPos,
1914#					9: splitExtensionPos,
1915					}
1916
1917			}
1918
1919def fixSubTableOverFlows(ttf, overflowRecord):
1920	"""
1921	An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
1922	"""
1923	table = ttf[overflowRecord.tableType].table
1924	lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
1925	subIndex = overflowRecord.SubTableIndex
1926	subtable = lookup.SubTable[subIndex]
1927
1928	# First, try not sharing anything for this subtable...
1929	if not hasattr(subtable, "DontShare"):
1930		subtable.DontShare = True
1931		return True
1932
1933	if hasattr(subtable, 'ExtSubTable'):
1934		# We split the subtable of the Extension table, and add a new Extension table
1935		# to contain the new subtable.
1936
1937		subTableType = subtable.ExtSubTable.__class__.LookupType
1938		extSubTable = subtable
1939		subtable = extSubTable.ExtSubTable
1940		newExtSubTableClass = lookupTypes[overflowRecord.tableType][extSubTable.__class__.LookupType]
1941		newExtSubTable = newExtSubTableClass()
1942		newExtSubTable.Format = extSubTable.Format
1943		toInsert = newExtSubTable
1944
1945		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
1946		newSubTable = newSubTableClass()
1947		newExtSubTable.ExtSubTable = newSubTable
1948	else:
1949		subTableType = subtable.__class__.LookupType
1950		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
1951		newSubTable = newSubTableClass()
1952		toInsert = newSubTable
1953
1954	if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
1955		lookup.SubTableCount = lookup.SubTableCount + 1
1956
1957	try:
1958		splitFunc = splitTable[overflowRecord.tableType][subTableType]
1959	except KeyError:
1960		log.error(
1961			"Don't know how to split %s lookup type %s",
1962			overflowRecord.tableType,
1963			subTableType,
1964		)
1965		return False
1966
1967	ok = splitFunc(subtable, newSubTable, overflowRecord)
1968	if ok:
1969		lookup.SubTable.insert(subIndex + 1, toInsert)
1970	return ok
1971
1972# End of OverFlow logic
1973
1974
1975def _buildClasses():
1976	import re
1977	from .otData import otData
1978
1979	formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$")
1980	namespace = globals()
1981
1982	# populate module with classes
1983	for name, table in otData:
1984		baseClass = BaseTable
1985		m = formatPat.match(name)
1986		if m:
1987			# XxxFormatN subtable, we only add the "base" table
1988			name = m.group(1)
1989			# the first row of a format-switching otData table describes the Format;
1990			# the first column defines the type of the Format field.
1991			# Currently this can be either 'uint16' or 'uint8'.
1992			formatType = table[0][0]
1993			baseClass = getFormatSwitchingBaseTableClass(formatType)
1994		if name not in namespace:
1995			# the class doesn't exist yet, so the base implementation is used.
1996			cls = type(name, (baseClass,), {})
1997			if name in ('GSUB', 'GPOS'):
1998				cls.DontShare = True
1999			namespace[name] = cls
2000
2001	# link Var{Table} <-> {Table} (e.g. ColorStop <-> VarColorStop, etc.)
2002	for name, _ in otData:
2003		if name.startswith("Var") and len(name) > 3 and name[3:] in namespace:
2004			varType = namespace[name]
2005			noVarType = namespace[name[3:]]
2006			varType.NoVarType = noVarType
2007			noVarType.VarType = varType
2008
2009	for base, alts in _equivalents.items():
2010		base = namespace[base]
2011		for alt in alts:
2012			namespace[alt] = base
2013
2014	global lookupTypes
2015	lookupTypes = {
2016		'GSUB': {
2017			1: SingleSubst,
2018			2: MultipleSubst,
2019			3: AlternateSubst,
2020			4: LigatureSubst,
2021			5: ContextSubst,
2022			6: ChainContextSubst,
2023			7: ExtensionSubst,
2024			8: ReverseChainSingleSubst,
2025		},
2026		'GPOS': {
2027			1: SinglePos,
2028			2: PairPos,
2029			3: CursivePos,
2030			4: MarkBasePos,
2031			5: MarkLigPos,
2032			6: MarkMarkPos,
2033			7: ContextPos,
2034			8: ChainContextPos,
2035			9: ExtensionPos,
2036		},
2037		'mort': {
2038			4: NoncontextualMorph,
2039		},
2040		'morx': {
2041			0: RearrangementMorph,
2042			1: ContextualMorph,
2043			2: LigatureMorph,
2044			# 3: Reserved,
2045			4: NoncontextualMorph,
2046			5: InsertionMorph,
2047		},
2048	}
2049	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
2050	for lookupEnum in lookupTypes.values():
2051		for enum, cls in lookupEnum.items():
2052			cls.LookupType = enum
2053
2054	global featureParamTypes
2055	featureParamTypes = {
2056		'size': FeatureParamsSize,
2057	}
2058	for i in range(1, 20+1):
2059		featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
2060	for i in range(1, 99+1):
2061		featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
2062
2063	# add converters to classes
2064	from .otConverters import buildConverters
2065	for name, table in otData:
2066		m = formatPat.match(name)
2067		if m:
2068			# XxxFormatN subtable, add converter to "base" table
2069			name, format = m.groups()
2070			format = int(format)
2071			cls = namespace[name]
2072			if not hasattr(cls, "converters"):
2073				cls.converters = {}
2074				cls.convertersByName = {}
2075			converters, convertersByName = buildConverters(table[1:], namespace)
2076			cls.converters[format] = converters
2077			cls.convertersByName[format] = convertersByName
2078			# XXX Add staticSize?
2079		else:
2080			cls = namespace[name]
2081			cls.converters, cls.convertersByName = buildConverters(table, namespace)
2082			# XXX Add staticSize?
2083
2084
2085_buildClasses()
2086
2087
2088def _getGlyphsFromCoverageTable(coverage):
2089	if coverage is None:
2090		# empty coverage table
2091		return []
2092	else:
2093		return coverage.glyphs
2094