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