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