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