otTables.py revision 9a980a8d2d81bbd4c7332b8c9ed1befe7dcc9936
1"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
2OpenType subtables.
3
4Most are constructed upon import from data in otData.py, all are populated with
5converter objects from otConverters.py.
6"""
7from __future__ import print_function, division
8from fontTools.misc.py23 import *
9from .otBase import BaseTable, FormatSwitchingBaseTable
10import operator
11import warnings
12
13
14class LookupOrder(BaseTable):
15	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
16
17class FeatureParams(BaseTable):
18
19	def compile(self, writer, font):
20		assert featureParamTypes.get(writer['FeatureTag'], None) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
21		BaseTable.compile(self, writer, font)
22
23class FeatureParamsSize(FeatureParams):
24	pass
25
26class FeatureParamsStylisticSet(FeatureParams):
27	pass
28
29class FeatureParamsCharacterVariants(FeatureParams):
30	pass
31
32class Coverage(FormatSwitchingBaseTable):
33
34	# manual implementation to get rid of glyphID dependencies
35
36	def postRead(self, rawTable, font):
37		if self.Format == 1:
38			# TODO only allow glyphs that are valid?
39			self.glyphs = rawTable["GlyphArray"]
40		elif self.Format == 2:
41			glyphs = self.glyphs = []
42			ranges = rawTable["RangeRecord"]
43			glyphOrder = font.getGlyphOrder()
44			# Some SIL fonts have coverage entries that don't have sorted
45			# StartCoverageIndex.  If it is so, fixup and warn.  We undo
46			# this when writing font out.
47			sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
48			if ranges != sorted_ranges:
49				warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
50				ranges = sorted_ranges
51			del sorted_ranges
52			for r in ranges:
53				assert r.StartCoverageIndex == len(glyphs), \
54					(r.StartCoverageIndex, len(glyphs))
55				start = r.Start
56				end = r.End
57				try:
58					startID = font.getGlyphID(start, requireReal=True)
59				except KeyError:
60					warnings.warn("Coverage table has start glyph ID out of range: %s." % start)
61					continue
62				try:
63					endID = font.getGlyphID(end, requireReal=True) + 1
64				except KeyError:
65					warnings.warn("Coverage table has end glyph ID out of range: %s." % end)
66					endID = len(glyphOrder)
67				glyphs.extend(glyphOrder[glyphID] for glyphID in range(startID, endID))
68		else:
69			assert 0, "unknown format: %s" % self.Format
70
71	def preWrite(self, font):
72		glyphs = getattr(self, "glyphs", None)
73		if glyphs is None:
74			glyphs = self.glyphs = []
75		format = 1
76		rawTable = {"GlyphArray": glyphs}
77		getGlyphID = font.getGlyphID
78		if glyphs:
79			# find out whether Format 2 is more compact or not
80			glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ]
81			brokenOrder = sorted(glyphIDs) != glyphIDs
82
83			last = glyphIDs[0]
84			ranges = [[last]]
85			for glyphID in glyphIDs[1:]:
86				if glyphID != last + 1:
87					ranges[-1].append(last)
88					ranges.append([glyphID])
89				last = glyphID
90			ranges[-1].append(last)
91
92			if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
93				# Format 2 is more compact
94				index = 0
95				for i in range(len(ranges)):
96					start, end = ranges[i]
97					r = RangeRecord()
98					r.StartID = start
99					r.Start = font.getGlyphName(start)
100					r.End = font.getGlyphName(end)
101					r.StartCoverageIndex = index
102					ranges[i] = r
103					index = index + end - start + 1
104				if brokenOrder:
105					warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
106					ranges.sort(key=lambda a: a.StartID)
107				for r in ranges:
108					del r.StartID
109				format = 2
110				rawTable = {"RangeRecord": ranges}
111			#else:
112			#	fallthrough; Format 1 is more compact
113		self.Format = format
114		return rawTable
115
116	def toXML2(self, xmlWriter, font):
117		for glyphName in getattr(self, "glyphs", []):
118			xmlWriter.simpletag("Glyph", value=glyphName)
119			xmlWriter.newline()
120
121	def fromXML(self, name, attrs, content, font):
122		glyphs = getattr(self, "glyphs", None)
123		if glyphs is None:
124			glyphs = []
125			self.glyphs = glyphs
126		glyphs.append(attrs["value"])
127
128
129def doModulo(value):
130	if value < 0:
131		return value + 65536
132	return value
133
134class SingleSubst(FormatSwitchingBaseTable):
135
136	def postRead(self, rawTable, font):
137		mapping = {}
138		input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
139		lenMapping = len(input)
140		if self.Format == 1:
141			delta = rawTable["DeltaGlyphID"]
142			inputGIDS =  [ font.getGlyphID(name) for name in input ]
143			inputGIDS = map(doModulo, inputGIDS)
144			outGIDS = [ glyphID + delta for glyphID in inputGIDS ]
145			outGIDS = map(doModulo, outGIDS)
146			outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ]
147			list(map(operator.setitem, [mapping]*lenMapping, input, outNames))
148		elif self.Format == 2:
149			assert len(input) == rawTable["GlyphCount"], \
150					"invalid SingleSubstFormat2 table"
151			subst = rawTable["Substitute"]
152			list(map(operator.setitem, [mapping]*lenMapping, input, subst))
153		else:
154			assert 0, "unknown format: %s" % self.Format
155		self.mapping = mapping
156
157	def preWrite(self, font):
158		mapping = getattr(self, "mapping", None)
159		if mapping is None:
160			mapping = self.mapping = {}
161		items = list(mapping.items())
162		getGlyphID = font.getGlyphID
163		gidItems = ((getGlyphID(a), getGlyphID(b)) for a,b in items)
164		sortableItems = sorted(zip(gidItems, items))
165
166		# figure out format
167		format = 2
168		delta = None
169		for inID, outID in gidItems:
170			if delta is None:
171				delta = outID - inID
172			else:
173				if delta != outID - inID:
174					break
175		else:
176			format = 1
177
178		rawTable = {}
179		self.Format = format
180		cov = Coverage()
181		input =  [ item [1][0] for item in sortableItems]
182		subst =  [ item [1][1] for item in sortableItems]
183		cov.glyphs = input
184		rawTable["Coverage"] = cov
185		if format == 1:
186			assert delta is not None
187			rawTable["DeltaGlyphID"] = delta
188		else:
189			rawTable["Substitute"] = subst
190		return rawTable
191
192	def toXML2(self, xmlWriter, font):
193		items = sorted(self.mapping.items())
194		for inGlyph, outGlyph in items:
195			xmlWriter.simpletag("Substitution",
196					[("in", inGlyph), ("out", outGlyph)])
197			xmlWriter.newline()
198
199	def fromXML(self, name, attrs, content, font):
200		mapping = getattr(self, "mapping", None)
201		if mapping is None:
202			mapping = {}
203			self.mapping = mapping
204		mapping[attrs["in"]] = attrs["out"]
205
206
207class ClassDef(FormatSwitchingBaseTable):
208
209	def postRead(self, rawTable, font):
210		classDefs = {}
211		glyphOrder = font.getGlyphOrder()
212
213		if self.Format == 1:
214			start = rawTable["StartGlyph"]
215			classList = rawTable["ClassValueArray"]
216			try:
217				startID = font.getGlyphID(start, requireReal=True)
218			except KeyError:
219				warnings.warn("ClassDef table has start glyph ID out of range: %s." % start)
220				startID = len(glyphOrder)
221			endID = startID + len(classList)
222			if endID > len(glyphOrder):
223				warnings.warn("ClassDef table has entries for out of range glyph IDs: %s,%s." % (start, len(classList)))
224				endID = len(glyphOrder)
225
226			for glyphID, cls in zip(range(startID, endID), classList):
227				classDefs[glyphOrder[glyphID]] = cls
228
229		elif self.Format == 2:
230			records = rawTable["ClassRangeRecord"]
231			for rec in records:
232				start = rec.Start
233				end = rec.End
234				cls = rec.Class
235				try:
236					startID = font.getGlyphID(start, requireReal=True)
237				except KeyError:
238					warnings.warn("ClassDef table has start glyph ID out of range: %s." % start)
239					continue
240				try:
241					endID = font.getGlyphID(end, requireReal=True)
242				except KeyError:
243					warnings.warn("ClassDef table has end glyph ID out of range: %s." % end)
244					endID = len(glyphOrder)
245				for glyphID in range(startID, endID):
246					classDefs[glyphOrder[glyphID]] = cls
247		else:
248			assert 0, "unknown format: %s" % self.Format
249		self.classDefs = classDefs
250
251	def preWrite(self, font):
252		classDefs = getattr(self, "classDefs", None)
253		if classDefs is None:
254			classDefs = self.classDefs = {}
255		items = list(classDefs.items())
256		format = 2
257		rawTable = {"ClassRangeRecord": []}
258		getGlyphID = font.getGlyphID
259		for i in range(len(items)):
260			glyphName, cls = items[i]
261			items[i] = getGlyphID(glyphName), glyphName, cls
262		items.sort()
263		if items:
264			last, lastName, lastCls = items[0]
265			ranges = [[lastCls, last, lastName]]
266			for glyphID, glyphName, cls in items[1:]:
267				if glyphID != last + 1 or cls != lastCls:
268					ranges[-1].extend([last, lastName])
269					ranges.append([cls, glyphID, glyphName])
270				last = glyphID
271				lastName = glyphName
272				lastCls = cls
273			ranges[-1].extend([last, lastName])
274
275			startGlyph = ranges[0][1]
276			endGlyph = ranges[-1][3]
277			glyphCount = endGlyph - startGlyph + 1
278			if len(ranges) * 3 < glyphCount + 1:
279				# Format 2 is more compact
280				for i in range(len(ranges)):
281					cls, start, startName, end, endName = ranges[i]
282					rec = ClassRangeRecord()
283					rec.Start = startName
284					rec.End = endName
285					rec.Class = cls
286					ranges[i] = rec
287				format = 2
288				rawTable = {"ClassRangeRecord": ranges}
289			else:
290				# Format 1 is more compact
291				startGlyphName = ranges[0][2]
292				classes = [0] * glyphCount
293				for cls, start, startName, end, endName in ranges:
294					for g in range(start - startGlyph, end - startGlyph + 1):
295						classes[g] = cls
296				format = 1
297				rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes}
298		self.Format = format
299		return rawTable
300
301	def toXML2(self, xmlWriter, font):
302		items = sorted(self.classDefs.items())
303		for glyphName, cls in items:
304			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
305			xmlWriter.newline()
306
307	def fromXML(self, name, attrs, content, font):
308		classDefs = getattr(self, "classDefs", None)
309		if classDefs is None:
310			classDefs = {}
311			self.classDefs = classDefs
312		classDefs[attrs["glyph"]] = int(attrs["class"])
313
314
315class AlternateSubst(FormatSwitchingBaseTable):
316
317	def postRead(self, rawTable, font):
318		alternates = {}
319		if self.Format == 1:
320			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
321			alts = rawTable["AlternateSet"]
322			if len(input) != len(alts):
323				assert len(input) == len(alts)
324			for i in range(len(input)):
325				alternates[input[i]] = alts[i].Alternate
326		else:
327			assert 0, "unknown format: %s" % self.Format
328		self.alternates = alternates
329
330	def preWrite(self, font):
331		self.Format = 1
332		alternates = getattr(self, "alternates", None)
333		if alternates is None:
334			alternates = self.alternates = {}
335		items = list(alternates.items())
336		for i in range(len(items)):
337			glyphName, set = items[i]
338			items[i] = font.getGlyphID(glyphName), glyphName, set
339		items.sort()
340		cov = Coverage()
341		cov.glyphs = [ item[1] for item in items]
342		alternates = []
343		setList = [ item[-1] for item in items]
344		for  set in setList:
345			alts = AlternateSet()
346			alts.Alternate = set
347			alternates.append(alts)
348		# a special case to deal with the fact that several hundred Adobe Japan1-5
349		# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
350		# Also useful in that when splitting a sub-table because of an offset overflow
351		# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
352		# Allows packing more rules in subtable.
353		self.sortCoverageLast = 1
354		return {"Coverage": cov, "AlternateSet": alternates}
355
356	def toXML2(self, xmlWriter, font):
357		items = sorted(self.alternates.items())
358		for glyphName, alternates in items:
359			xmlWriter.begintag("AlternateSet", glyph=glyphName)
360			xmlWriter.newline()
361			for alt in alternates:
362				xmlWriter.simpletag("Alternate", glyph=alt)
363				xmlWriter.newline()
364			xmlWriter.endtag("AlternateSet")
365			xmlWriter.newline()
366
367	def fromXML(self, name, attrs, content, font):
368		alternates = getattr(self, "alternates", None)
369		if alternates is None:
370			alternates = {}
371			self.alternates = alternates
372		glyphName = attrs["glyph"]
373		set = []
374		alternates[glyphName] = set
375		for element in content:
376			if not isinstance(element, tuple):
377				continue
378			name, attrs, content = element
379			set.append(attrs["glyph"])
380
381
382class LigatureSubst(FormatSwitchingBaseTable):
383
384	def postRead(self, rawTable, font):
385		ligatures = {}
386		if self.Format == 1:
387			input = rawTable["Coverage"].glyphs
388			ligSets = rawTable["LigatureSet"]
389			assert len(input) == len(ligSets)
390			for i in range(len(input)):
391				ligatures[input[i]] = ligSets[i].Ligature
392		else:
393			assert 0, "unknown format: %s" % self.Format
394		self.ligatures = ligatures
395
396	def preWrite(self, font):
397		ligatures = getattr(self, "ligatures", None)
398		if ligatures is None:
399			ligatures = self.ligatures = {}
400		items = list(ligatures.items())
401		for i in range(len(items)):
402			glyphName, set = items[i]
403			items[i] = font.getGlyphID(glyphName), glyphName, set
404		items.sort()
405		cov = Coverage()
406		cov.glyphs = [ item[1] for item in items]
407
408		ligSets = []
409		setList = [ item[-1] for item in items ]
410		for set in setList:
411			ligSet = LigatureSet()
412			ligs = ligSet.Ligature = []
413			for lig in set:
414				ligs.append(lig)
415			ligSets.append(ligSet)
416		# Useful in that when splitting a sub-table because of an offset overflow
417		# I don't need to calculate the change in subtabl offset due to the coverage table size.
418		# Allows packing more rules in subtable.
419		self.sortCoverageLast = 1
420		return {"Coverage": cov, "LigatureSet": ligSets}
421
422	def toXML2(self, xmlWriter, font):
423		items = sorted(self.ligatures.items())
424		for glyphName, ligSets in items:
425			xmlWriter.begintag("LigatureSet", glyph=glyphName)
426			xmlWriter.newline()
427			for lig in ligSets:
428				xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
429					components=",".join(lig.Component))
430				xmlWriter.newline()
431			xmlWriter.endtag("LigatureSet")
432			xmlWriter.newline()
433
434	def fromXML(self, name, attrs, content, font):
435		ligatures = getattr(self, "ligatures", None)
436		if ligatures is None:
437			ligatures = {}
438			self.ligatures = ligatures
439		glyphName = attrs["glyph"]
440		ligs = []
441		ligatures[glyphName] = ligs
442		for element in content:
443			if not isinstance(element, tuple):
444				continue
445			name, attrs, content = element
446			lig = Ligature()
447			lig.LigGlyph = attrs["glyph"]
448			lig.Component = attrs["components"].split(",")
449			ligs.append(lig)
450
451
452#
453# For each subtable format there is a class. However, we don't really distinguish
454# between "field name" and "format name": often these are the same. Yet there's
455# a whole bunch of fields with different names. The following dict is a mapping
456# from "format name" to "field name". _buildClasses() uses this to create a
457# subclass for each alternate field name.
458#
459_equivalents = {
460	'MarkArray': ("Mark1Array",),
461	'LangSys': ('DefaultLangSys',),
462	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
463			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
464			'LookAheadCoverage'),
465	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
466			'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
467	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
468			'Mark2Anchor', 'MarkAnchor'),
469	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
470			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
471	'Axis': ('HorizAxis', 'VertAxis',),
472	'MinMax': ('DefaultMinMax',),
473	'BaseCoord': ('MinCoord', 'MaxCoord',),
474	'JstfLangSys': ('DefJstfLangSys',),
475	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
476			'ExtensionDisableGSUB',),
477	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
478			'ExtensionDisableGPOS',),
479	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
480}
481
482#
483# OverFlow logic, to automatically create ExtensionLookups
484# XXX This should probably move to otBase.py
485#
486
487def fixLookupOverFlows(ttf, overflowRecord):
488	""" Either the offset from the LookupList to a lookup overflowed, or
489	an offset from a lookup to a subtable overflowed.
490	The table layout is:
491	GPSO/GUSB
492		Script List
493		Feature List
494		LookUpList
495			Lookup[0] and contents
496				SubTable offset list
497					SubTable[0] and contents
498					...
499					SubTable[n] and contents
500			...
501			Lookup[n] and contents
502				SubTable offset list
503					SubTable[0] and contents
504					...
505					SubTable[n] and contents
506	If the offset to a lookup overflowed (SubTableIndex is None)
507		we must promote the *previous*	lookup to an Extension type.
508	If the offset from a lookup to subtable overflowed, then we must promote it
509		to an Extension Lookup type.
510	"""
511	ok = 0
512	lookupIndex = overflowRecord.LookupListIndex
513	if (overflowRecord.SubTableIndex is None):
514		lookupIndex = lookupIndex - 1
515	if lookupIndex < 0:
516		return ok
517	if overflowRecord.tableType == 'GSUB':
518		extType = 7
519	elif overflowRecord.tableType == 'GPOS':
520		extType = 9
521
522	lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
523	lookup = lookups[lookupIndex]
524	# If the previous lookup is an extType, look further back. Very unlikely, but possible.
525	while lookup.LookupType == extType:
526		lookupIndex = lookupIndex -1
527		if lookupIndex < 0:
528			return ok
529		lookup = lookups[lookupIndex]
530
531	for si in range(len(lookup.SubTable)):
532		subTable = lookup.SubTable[si]
533		extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
534		extSubTable = extSubTableClass()
535		extSubTable.Format = 1
536		extSubTable.ExtensionLookupType = lookup.LookupType
537		extSubTable.ExtSubTable = subTable
538		lookup.SubTable[si] = extSubTable
539	lookup.LookupType = extType
540	ok = 1
541	return ok
542
543def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
544	ok = 1
545	newSubTable.Format = oldSubTable.Format
546	if hasattr(oldSubTable, 'sortCoverageLast'):
547		newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
548
549	oldAlts = sorted(oldSubTable.alternates.items())
550	oldLen = len(oldAlts)
551
552	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
553		# Coverage table is written last. overflow is to or within the
554		# the coverage table. We will just cut the subtable in half.
555		newLen = oldLen//2
556
557	elif overflowRecord.itemName == 'AlternateSet':
558		# We just need to back up by two items
559		# from the overflowed AlternateSet index to make sure the offset
560		# to the Coverage table doesn't overflow.
561		newLen  = overflowRecord.itemIndex - 1
562
563	newSubTable.alternates = {}
564	for i in range(newLen, oldLen):
565		item = oldAlts[i]
566		key = item[0]
567		newSubTable.alternates[key] = item[1]
568		del oldSubTable.alternates[key]
569
570
571	return ok
572
573
574def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
575	ok = 1
576	newSubTable.Format = oldSubTable.Format
577	oldLigs = sorted(oldSubTable.ligatures.items())
578	oldLen = len(oldLigs)
579
580	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
581		# Coverage table is written last. overflow is to or within the
582		# the coverage table. We will just cut the subtable in half.
583		newLen = oldLen//2
584
585	elif overflowRecord.itemName == 'LigatureSet':
586		# We just need to back up by two items
587		# from the overflowed AlternateSet index to make sure the offset
588		# to the Coverage table doesn't overflow.
589		newLen  = overflowRecord.itemIndex - 1
590
591	newSubTable.ligatures = {}
592	for i in range(newLen, oldLen):
593		item = oldLigs[i]
594		key = item[0]
595		newSubTable.ligatures[key] = item[1]
596		del oldSubTable.ligatures[key]
597
598	return ok
599
600
601splitTable = {	'GSUB': {
602#					1: splitSingleSubst,
603#					2: splitMultipleSubst,
604					3: splitAlternateSubst,
605					4: splitLigatureSubst,
606#					5: splitContextSubst,
607#					6: splitChainContextSubst,
608#					7: splitExtensionSubst,
609#					8: splitReverseChainSingleSubst,
610					},
611				'GPOS': {
612#					1: splitSinglePos,
613#					2: splitPairPos,
614#					3: splitCursivePos,
615#					4: splitMarkBasePos,
616#					5: splitMarkLigPos,
617#					6: splitMarkMarkPos,
618#					7: splitContextPos,
619#					8: splitChainContextPos,
620#					9: splitExtensionPos,
621					}
622
623			}
624
625def fixSubTableOverFlows(ttf, overflowRecord):
626	"""
627	An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
628	"""
629	ok = 0
630	table = ttf[overflowRecord.tableType].table
631	lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
632	subIndex = overflowRecord.SubTableIndex
633	subtable = lookup.SubTable[subIndex]
634
635	if hasattr(subtable, 'ExtSubTable'):
636		# We split the subtable of the Extension table, and add a new Extension table
637		# to contain the new subtable.
638
639		subTableType = subtable.ExtensionLookupType
640		extSubTable = subtable
641		subtable = extSubTable.ExtSubTable
642		newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType]
643		newExtSubTable = newExtSubTableClass()
644		newExtSubTable.Format = extSubTable.Format
645		newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType
646		lookup.SubTable.insert(subIndex + 1, newExtSubTable)
647
648		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
649		newSubTable = newSubTableClass()
650		newExtSubTable.ExtSubTable = newSubTable
651	else:
652		subTableType = lookup.LookupType
653		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
654		newSubTable = newSubTableClass()
655		lookup.SubTable.insert(subIndex + 1, newSubTable)
656
657	if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
658		lookup.SubTableCount = lookup.SubTableCount + 1
659
660	try:
661		splitFunc = splitTable[overflowRecord.tableType][subTableType]
662	except KeyError:
663		return ok
664
665	ok = splitFunc(subtable, newSubTable, overflowRecord)
666	return ok
667
668# End of OverFlow logic
669
670
671def _buildClasses():
672	import re
673	from .otData import otData
674
675	formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
676	namespace = globals()
677
678	# populate module with classes
679	for name, table in otData:
680		baseClass = BaseTable
681		m = formatPat.match(name)
682		if m:
683			# XxxFormatN subtable, we only add the "base" table
684			name = m.group(1)
685			baseClass = FormatSwitchingBaseTable
686		if name not in namespace:
687			# the class doesn't exist yet, so the base implementation is used.
688			cls = type(name, (baseClass,), {})
689			namespace[name] = cls
690
691	for base, alts in _equivalents.items():
692		base = namespace[base]
693		for alt in alts:
694			namespace[alt] = type(alt, (base,), {})
695
696	global lookupTypes
697	lookupTypes = {
698		'GSUB': {
699			1: SingleSubst,
700			2: MultipleSubst,
701			3: AlternateSubst,
702			4: LigatureSubst,
703			5: ContextSubst,
704			6: ChainContextSubst,
705			7: ExtensionSubst,
706			8: ReverseChainSingleSubst,
707		},
708		'GPOS': {
709			1: SinglePos,
710			2: PairPos,
711			3: CursivePos,
712			4: MarkBasePos,
713			5: MarkLigPos,
714			6: MarkMarkPos,
715			7: ContextPos,
716			8: ChainContextPos,
717			9: ExtensionPos,
718		},
719	}
720	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
721	for lookupEnum in lookupTypes.values():
722		for enum, cls in lookupEnum.items():
723			cls.LookupType = enum
724
725	global featureParamTypes
726	featureParamTypes = {
727		'size': FeatureParamsSize,
728	}
729	for i in range(1, 20+1):
730		featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
731	for i in range(1, 99+1):
732		featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
733
734	# add converters to classes
735	from .otConverters import buildConverters
736	for name, table in otData:
737		m = formatPat.match(name)
738		if m:
739			# XxxFormatN subtable, add converter to "base" table
740			name, format = m.groups()
741			format = int(format)
742			cls = namespace[name]
743			if not hasattr(cls, "converters"):
744				cls.converters = {}
745				cls.convertersByName = {}
746			converters, convertersByName = buildConverters(table[1:], namespace)
747			cls.converters[format] = converters
748			cls.convertersByName[format] = convertersByName
749		else:
750			cls = namespace[name]
751			cls.converters, cls.convertersByName = buildConverters(table, namespace)
752
753
754_buildClasses()
755
756
757def _getGlyphsFromCoverageTable(coverage):
758	if coverage is None:
759		# empty coverage table
760		return []
761	else:
762		return coverage.glyphs
763