otTables.py revision e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fd
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
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 = 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 = 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 = 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, 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 = 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, TupleType):
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 new, 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 = new.classobj(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] = new.classobj(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