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