otTables.py revision 58acba2d0284ec63bf27c457fa261adf7774e996
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
9from types import TupleType
10import warnings
11
12
13class LookupOrder(BaseTable):
14	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
15
16
17class FeatureParams(BaseTable):
18	"""This class has been used by Adobe, but but this one implementation was done wrong.
19	No other use has been made, becuase there is no way to know how to interpret
20	the data at the offset.. For now, if we see one, just skip the data on
21	decompiling and dumping to XML. """
22	# XXX The above is no longer true; the 'size' feature uses FeatureParams now.
23	def __init__(self):
24		BaseTable.__init__(self)
25		self.converters = []
26
27class Coverage(FormatSwitchingBaseTable):
28
29	# manual implementation to get rid of glyphID dependencies
30
31	def postRead(self, rawTable, font):
32		if self.Format == 1:
33			self.glyphs = rawTable["GlyphArray"]
34		elif self.Format == 2:
35			glyphs = self.glyphs = []
36			ranges = rawTable["RangeRecord"]
37			glyphOrder = font.getGlyphOrder()
38			# Some SIL fonts have coverage entries that don't have sorted
39			# StartCoverageIndex.  If it is so, fixup and warn.  We undo
40			# this when writing font out.
41			sorted_ranges = sorted(ranges, cmp=lambda a,b: cmp(a.StartCoverageIndex,b.StartCoverageIndex))
42			if ranges != sorted_ranges:
43				warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
44				ranges = sorted_ranges
45			del sorted_ranges
46			for r in ranges:
47				assert r.StartCoverageIndex == len(glyphs), \
48					(r.StartCoverageIndex, len(glyphs))
49				start = r.Start
50				end = r.End
51				try:
52					startID = font.getGlyphID(start, requireReal=1)
53				except KeyError:
54					warnings.warn("Coverage table has start glyph ID out of range: %s." % start)
55					continue
56				try:
57					endID = font.getGlyphID(end, requireReal=1)
58				except KeyError:
59					warnings.warn("Coverage table has end glyph ID out of range: %s." % end)
60					endID = len(glyphOrder)
61				glyphs.append(start)
62				rangeList = [glyphOrder[glyphID] for glyphID in range(startID + 1, endID) ]
63				glyphs += rangeList
64				if start != end and endID < len(glyphOrder):
65					glyphs.append(end)
66		else:
67			assert 0, "unknown format: %s" % self.Format
68
69	def preWrite(self, font):
70		glyphs = getattr(self, "glyphs", None)
71		if glyphs is None:
72			glyphs = self.glyphs = []
73		format = 1
74		rawTable = {"GlyphArray": glyphs}
75		getGlyphID = font.getGlyphID
76		if glyphs:
77			# find out whether Format 2 is more compact or not
78			glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ]
79			brokenOrder = sorted(glyphIDs) != glyphIDs
80
81			last = glyphIDs[0]
82			ranges = [[last]]
83			for glyphID in glyphIDs[1:]:
84				if glyphID != last + 1:
85					ranges[-1].append(last)
86					ranges.append([glyphID])
87				last = glyphID
88			ranges[-1].append(last)
89
90			if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
91				# Format 2 is more compact
92				index = 0
93				for i in range(len(ranges)):
94					start, end = ranges[i]
95					r = RangeRecord()
96					r.StartID = start
97					r.Start = font.getGlyphName(start)
98					r.End = font.getGlyphName(end)
99					r.StartCoverageIndex = index
100					ranges[i] = r
101					index = index + end - start + 1
102				if brokenOrder:
103					warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
104					ranges.sort(cmp=lambda a,b: cmp(a.StartID,b.StartID))
105				for r in ranges:
106					del r.StartID
107				format = 2
108				rawTable = {"RangeRecord": ranges}
109			#else:
110			#	fallthrough; Format 1 is more compact
111		self.Format = format
112		return rawTable
113
114	def toXML2(self, xmlWriter, font):
115		for glyphName in getattr(self, "glyphs", []):
116			xmlWriter.simpletag("Glyph", value=glyphName)
117			xmlWriter.newline()
118
119	def fromXML(self, (name, attrs, content), font):
120		glyphs = getattr(self, "glyphs", None)
121		if glyphs is None:
122			glyphs = []
123			self.glyphs = glyphs
124		glyphs.append(attrs["value"])
125
126
127def doModulo(value):
128	if value < 0:
129		return value + 65536
130	return value
131
132class SingleSubst(FormatSwitchingBaseTable):
133
134	def postRead(self, rawTable, font):
135		mapping = {}
136		input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
137		lenMapping = len(input)
138		if self.Format == 1:
139			delta = rawTable["DeltaGlyphID"]
140			inputGIDS =  [ font.getGlyphID(name) for name in input ]
141			inputGIDS = map(doModulo, inputGIDS)
142			outGIDS = [ glyphID + delta for glyphID in inputGIDS ]
143			outGIDS = map(doModulo, outGIDS)
144			outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ]
145			map(operator.setitem, [mapping]*lenMapping, input, outNames)
146		elif self.Format == 2:
147			assert len(input) == rawTable["GlyphCount"], \
148					"invalid SingleSubstFormat2 table"
149			subst = rawTable["Substitute"]
150			map(operator.setitem, [mapping]*lenMapping, input, subst)
151		else:
152			assert 0, "unknown format: %s" % self.Format
153		self.mapping = mapping
154
155	def preWrite(self, font):
156		mapping = getattr(self, "mapping", None)
157		if mapping is None:
158			mapping = self.mapping = {}
159		items = mapping.items()
160		getGlyphID = font.getGlyphID
161		gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items]
162		sortableItems = zip(gidItems, items)
163		sortableItems.sort()
164
165		# figure out format
166		format = 2
167		delta = None
168		for inID, outID in gidItems:
169			if delta is None:
170				delta = outID - inID
171			else:
172				if delta != outID - inID:
173					break
174		else:
175			format = 1
176
177		rawTable = {}
178		self.Format = format
179		cov = Coverage()
180		input =  [ item [1][0] for item in sortableItems]
181		subst =  [ item [1][1] for item in sortableItems]
182		cov.glyphs = input
183		rawTable["Coverage"] = cov
184		if format == 1:
185			assert delta is not None
186			rawTable["DeltaGlyphID"] = delta
187		else:
188			rawTable["Substitute"] = subst
189		return rawTable
190
191	def toXML2(self, xmlWriter, font):
192		items = self.mapping.items()
193		items.sort()
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		getGlyphName = font.getGlyphName
212
213		if self.Format == 1:
214			start = rawTable["StartGlyph"]
215			classList = rawTable["ClassValueArray"]
216			lenList = len(classList)
217			glyphID = font.getGlyphID(start)
218			gidList = range(glyphID, glyphID + len(classList))
219			keyList = [getGlyphName(glyphID) for glyphID in gidList]
220
221			map(operator.setitem, [classDefs]*lenList, keyList, classList)
222
223		elif self.Format == 2:
224			records = rawTable["ClassRangeRecord"]
225			for rec in records:
226				start = rec.Start
227				end = rec.End
228				cls = rec.Class
229				classDefs[start] = cls
230				glyphIDs = range(font.getGlyphID(start) + 1, font.getGlyphID(end))
231				lenList = len(glyphIDs)
232				keyList = [getGlyphName(glyphID) for glyphID in glyphIDs]
233				map(operator.setitem,  [classDefs]*lenList, keyList, [cls]*lenList)
234				classDefs[end] = cls
235		else:
236			assert 0, "unknown format: %s" % self.Format
237		self.classDefs = classDefs
238
239	def preWrite(self, font):
240		classDefs = getattr(self, "classDefs", None)
241		if classDefs is None:
242			classDefs = self.classDefs = {}
243		items = classDefs.items()
244		getGlyphID = font.getGlyphID
245		for i in range(len(items)):
246			glyphName, cls = items[i]
247			items[i] = getGlyphID(glyphName), glyphName, cls
248		items.sort()
249		if items:
250			last, lastName, lastCls = items[0]
251			rec = ClassRangeRecord()
252			rec.Start = lastName
253			rec.Class = lastCls
254			ranges = [rec]
255			for glyphID, glyphName, cls in items[1:]:
256				if glyphID != last + 1 or cls != lastCls:
257					rec.End = lastName
258					rec = ClassRangeRecord()
259					rec.Start = glyphName
260					rec.Class = cls
261					ranges.append(rec)
262				last = glyphID
263				lastName = glyphName
264				lastCls = cls
265			rec.End = lastName
266		else:
267			ranges = []
268		self.Format = 2  # currently no support for Format 1
269		return {"ClassRangeRecord": ranges}
270
271	def toXML2(self, xmlWriter, font):
272		items = self.classDefs.items()
273		items.sort()
274		for glyphName, cls in items:
275			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
276			xmlWriter.newline()
277
278	def fromXML(self, (name, attrs, content), font):
279		classDefs = getattr(self, "classDefs", None)
280		if classDefs is None:
281			classDefs = {}
282			self.classDefs = classDefs
283		classDefs[attrs["glyph"]] = int(attrs["class"])
284
285
286class AlternateSubst(FormatSwitchingBaseTable):
287
288	def postRead(self, rawTable, font):
289		alternates = {}
290		if self.Format == 1:
291			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
292			alts = rawTable["AlternateSet"]
293			if len(input) != len(alts):
294				assert len(input) == len(alts)
295			for i in range(len(input)):
296				alternates[input[i]] = alts[i].Alternate
297		else:
298			assert 0, "unknown format: %s" % self.Format
299		self.alternates = alternates
300
301	def preWrite(self, font):
302		self.Format = 1
303		alternates = getattr(self, "alternates", None)
304		if alternates is None:
305			alternates = self.alternates = {}
306		items = alternates.items()
307		for i in range(len(items)):
308			glyphName, set = items[i]
309			items[i] = font.getGlyphID(glyphName), glyphName, set
310		items.sort()
311		cov = Coverage()
312		cov.glyphs = [ item[1] for item in items]
313		alternates = []
314		setList = [ item[-1] for item in items]
315		for  set in setList:
316			alts = AlternateSet()
317			alts.Alternate = set
318			alternates.append(alts)
319		# a special case to deal with the fact that several hundred Adobe Japan1-5
320		# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
321		# Also useful in that when splitting a sub-table because of an offset overflow
322		# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
323		# Allows packing more rules in subtable.
324		self.sortCoverageLast = 1
325		return {"Coverage": cov, "AlternateSet": alternates}
326
327	def toXML2(self, xmlWriter, font):
328		items = self.alternates.items()
329		items.sort()
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 type(element) != TupleType:
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 = 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 = self.ligatures.items()
396		items.sort()
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 type(element) != TupleType:
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 = oldSubTable.alternates.items()
523	oldAlts.sort()
524	oldLen = len(oldAlts)
525
526	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
527		# Coverage table is written last. overflow is to or within the
528		# the coverage table. We will just cut the subtable in half.
529		newLen = int(oldLen/2)
530
531	elif overflowRecord.itemName == 'AlternateSet':
532		# We just need to back up by two items
533		# from the overflowed AlternateSet index to make sure the offset
534		# to the Coverage table doesn't overflow.
535		newLen  = overflowRecord.itemIndex - 1
536
537	newSubTable.alternates = {}
538	for i in range(newLen, oldLen):
539		item = oldAlts[i]
540		key = item[0]
541		newSubTable.alternates[key] = item[1]
542		del oldSubTable.alternates[key]
543
544
545	return ok
546
547
548def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
549	ok = 1
550	newSubTable.Format = oldSubTable.Format
551	oldLigs = oldSubTable.ligatures.items()
552	oldLigs.sort()
553	oldLen = len(oldLigs)
554
555	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
556		# Coverage table is written last. overflow is to or within the
557		# the coverage table. We will just cut the subtable in half.
558		newLen = int(oldLen/2)
559
560	elif overflowRecord.itemName == 'LigatureSet':
561		# We just need to back up by two items
562		# from the overflowed AlternateSet index to make sure the offset
563		# to the Coverage table doesn't overflow.
564		newLen  = overflowRecord.itemIndex - 1
565
566	newSubTable.ligatures = {}
567	for i in range(newLen, oldLen):
568		item = oldLigs[i]
569		key = item[0]
570		newSubTable.ligatures[key] = item[1]
571		del oldSubTable.ligatures[key]
572
573	return ok
574
575
576splitTable = {	'GSUB': {
577#					1: splitSingleSubst,
578#					2: splitMultipleSubst,
579					3: splitAlternateSubst,
580					4: splitLigatureSubst,
581#					5: splitContextSubst,
582#					6: splitChainContextSubst,
583#					7: splitExtensionSubst,
584#					8: splitReverseChainSingleSubst,
585					},
586				'GPOS': {
587#					1: splitSinglePos,
588#					2: splitPairPos,
589#					3: splitCursivePos,
590#					4: splitMarkBasePos,
591#					5: splitMarkLigPos,
592#					6: splitMarkMarkPos,
593#					7: splitContextPos,
594#					8: splitChainContextPos,
595#					9: splitExtensionPos,
596					}
597
598			}
599
600def fixSubTableOverFlows(ttf, overflowRecord):
601	"""
602	An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
603	"""
604	ok = 0
605	table = ttf[overflowRecord.tableType].table
606	lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
607	subIndex = overflowRecord.SubTableIndex
608	subtable = lookup.SubTable[subIndex]
609
610	if hasattr(subtable, 'ExtSubTable'):
611		# We split the subtable of the Extension table, and add a new Extension table
612		# to contain the new subtable.
613
614		subTableType = subtable.ExtensionLookupType
615		extSubTable = subtable
616		subtable = extSubTable.ExtSubTable
617		newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType]
618		newExtSubTable = newExtSubTableClass()
619		newExtSubTable.Format = extSubTable.Format
620		newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType
621		lookup.SubTable.insert(subIndex + 1, newExtSubTable)
622
623		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
624		newSubTable = newSubTableClass()
625		newExtSubTable.ExtSubTable = newSubTable
626	else:
627		subTableType = lookup.LookupType
628		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
629		newSubTable = newSubTableClass()
630		lookup.SubTable.insert(subIndex + 1, newSubTable)
631
632	if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
633		lookup.SubTableCount = lookup.SubTableCount + 1
634
635	try:
636		splitFunc = splitTable[overflowRecord.tableType][subTableType]
637	except KeyError:
638		return ok
639
640	ok = splitFunc(subtable, newSubTable, overflowRecord)
641	return ok
642
643# End of OverFlow logic
644
645
646def _buildClasses():
647	import new, re
648	from otData import otData
649
650	formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
651	namespace = globals()
652
653	# populate module with classes
654	for name, table in otData:
655		baseClass = BaseTable
656		m = formatPat.match(name)
657		if m:
658			# XxxFormatN subtable, we only add the "base" table
659			name = m.group(1)
660			baseClass = FormatSwitchingBaseTable
661		if not namespace.has_key(name):
662			# the class doesn't exist yet, so the base implementation is used.
663			cls = new.classobj(name, (baseClass,), {})
664			namespace[name] = cls
665
666	for base, alts in _equivalents.items():
667		base = namespace[base]
668		for alt in alts:
669			namespace[alt] = new.classobj(alt, (base,), {})
670
671	global lookupTypes
672	lookupTypes = {
673		'GSUB': {
674			1: SingleSubst,
675			2: MultipleSubst,
676			3: AlternateSubst,
677			4: LigatureSubst,
678			5: ContextSubst,
679			6: ChainContextSubst,
680			7: ExtensionSubst,
681			8: ReverseChainSingleSubst,
682		},
683		'GPOS': {
684			1: SinglePos,
685			2: PairPos,
686			3: CursivePos,
687			4: MarkBasePos,
688			5: MarkLigPos,
689			6: MarkMarkPos,
690			7: ContextPos,
691			8: ChainContextPos,
692			9: ExtensionPos,
693		},
694	}
695	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
696	for lookupEnum in lookupTypes.values():
697		for enum, cls in lookupEnum.items():
698			cls.LookupType = enum
699
700	# add converters to classes
701	from otConverters import buildConverters
702	for name, table in otData:
703		m = formatPat.match(name)
704		if m:
705			# XxxFormatN subtable, add converter to "base" table
706			name, format = m.groups()
707			format = int(format)
708			cls = namespace[name]
709			if not hasattr(cls, "converters"):
710				cls.converters = {}
711				cls.convertersByName = {}
712			converters, convertersByName = buildConverters(table[1:], namespace)
713			cls.converters[format] = converters
714			cls.convertersByName[format] = convertersByName
715		else:
716			cls = namespace[name]
717			cls.converters, cls.convertersByName = buildConverters(table, namespace)
718
719
720_buildClasses()
721
722
723def _getGlyphsFromCoverageTable(coverage):
724	if coverage is None:
725		# empty coverage table
726		return []
727	else:
728		return coverage.glyphs
729