otTables.py revision b2486125e9438443a1419706c50958ab0676eb5a
1d4d151390d1288f8d2df30f6dfa26a309c7334dajvr"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
2d4d151390d1288f8d2df30f6dfa26a309c7334dajvrOpenType subtables.
3d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
464b5c80e80444a124da335e8d4d208bffcf2737bjvrMost are constructed upon import from data in otData.py, all are populated with
564b5c80e80444a124da335e8d4d208bffcf2737bjvrconverter objects from otConverters.py.
6d4d151390d1288f8d2df30f6dfa26a309c7334dajvr"""
7d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
8d4d151390d1288f8d2df30f6dfa26a309c7334dajvrfrom otBase import BaseTable, FormatSwitchingBaseTable
9b2486125e9438443a1419706c50958ab0676eb5ajvrfrom types import TupleType
10d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
11d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
12d4d151390d1288f8d2df30f6dfa26a309c7334dajvrclass LookupOrder(BaseTable):
13d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
14d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
15d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
16d4d151390d1288f8d2df30f6dfa26a309c7334dajvrclass FeatureParams(BaseTable):
17d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
18d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	# XXX The above is no longer true; the 'size' feature uses FeatureParams now.
19d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
20d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
21d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvrclass Coverage(FormatSwitchingBaseTable):
22d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
23d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	# manual implementation to get rid of glyphID dependencies
24d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
251a53beb7b4bb252dc002dcdc2de70517d4727494jvr	def postRead(self, rawTable, font):
26d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		if self.Format == 1:
271a53beb7b4bb252dc002dcdc2de70517d4727494jvr			self.glyphs = rawTable["GlyphArray"]
28d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		elif self.Format == 2:
29d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			glyphs = self.glyphs = []
301a53beb7b4bb252dc002dcdc2de70517d4727494jvr			ranges = rawTable["RangeRecord"]
31d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			for r in ranges:
32d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				assert r.StartCoverageIndex == len(glyphs), \
33d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					(r.StartCoverageIndex, len(glyphs))
34d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				start = r.Start
35d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				end = r.End
36d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				startID = font.getGlyphID(start)
37d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				endID = font.getGlyphID(end)
38d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				glyphs.append(start)
39d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				for glyphID in range(startID + 1, endID):
40d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					glyphs.append(font.getGlyphName(glyphID))
41d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				if start != end:
42d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					glyphs.append(end)
43d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		else:
441a53beb7b4bb252dc002dcdc2de70517d4727494jvr			assert 0, "unknown format: %s" % self.Format
45d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
46d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	def preWrite(self, font):
47d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		format = 1
481a53beb7b4bb252dc002dcdc2de70517d4727494jvr		rawTable = {"GlyphArray": self.glyphs}
49d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		if self.glyphs:
501a53beb7b4bb252dc002dcdc2de70517d4727494jvr			# find out whether Format 2 is more compact or not
51d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			glyphIDs = []
52d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			for glyphName in self.glyphs:
53d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				glyphIDs.append(font.getGlyphID(glyphName))
54d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
55d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			last = glyphIDs[0]
56d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			ranges = [[last]]
57d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			for glyphID in glyphIDs[1:]:
58a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				if glyphID != last + 1:
59d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					ranges[-1].append(last)
60d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					ranges.append([glyphID])
61d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				last = glyphID
62d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			ranges[-1].append(last)
63d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
64d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			if len(ranges) * 3 < len(self.glyphs):  # 3 words vs. 1 word
65d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				# Format 2 is more compact
66d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				index = 0
67d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				for i in range(len(ranges)):
68d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					start, end = ranges[i]
69d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r = RangeRecord()
70d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r.Start = font.getGlyphName(start)
71d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r.End = font.getGlyphName(end)
72d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r.StartCoverageIndex = index
73d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					ranges[i] = r
74d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					index = index + end - start + 1
75d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				format = 2
761a53beb7b4bb252dc002dcdc2de70517d4727494jvr				rawTable = {"RangeRecord": ranges}
77d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			#else:
78d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			#	fallthrough; Format 1 is more compact
79d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		self.Format = format
801a53beb7b4bb252dc002dcdc2de70517d4727494jvr		return rawTable
81d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
82d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	def toXML2(self, xmlWriter, font):
83d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		for glyphName in self.glyphs:
84d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			xmlWriter.simpletag("Glyph", value=glyphName)
85d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			xmlWriter.newline()
86d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
87d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	def fromXML(self, (name, attrs, content), font):
88d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		glyphs = getattr(self, "glyphs", None)
89d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		if glyphs is None:
90d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			glyphs = []
91d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			self.glyphs = glyphs
92d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		glyphs.append(attrs["value"])
93d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
94d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
951d6360af4c553a7b26447b0e1e96f7b701c9325ejvrclass SingleSubst(FormatSwitchingBaseTable):
961d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
971d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def postRead(self, rawTable, font):
981d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		mapping = {}
991d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		input = rawTable["Coverage"].glyphs
1001d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		if self.Format == 1:
1011d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			delta = rawTable["DeltaGlyphID"]
1021d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			for inGlyph in input:
1031d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				glyphID = font.getGlyphID(inGlyph)
1041d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				mapping[inGlyph] = font.getGlyphName(glyphID + delta)
1051d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		elif self.Format == 2:
1061d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			assert len(input) == rawTable["GlyphCount"], \
1071d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					"invalid SingleSubstFormat2 table"
1081d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			subst = rawTable["Substitute"]
1091d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			for i in range(len(input)):
1101d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				mapping[input[i]] = subst[i]
1111d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		else:
1121d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			assert 0, "unknown format: %s" % self.Format
1131d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		self.mapping = mapping
1141d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1151d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def preWrite(self, font):
1161d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		items = self.mapping.items()
1171d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		for i in range(len(items)):
1181d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			inGlyph, outGlyph = items[i]
1191d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			items[i] = font.getGlyphID(inGlyph), font.getGlyphID(outGlyph), \
1201d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					inGlyph, outGlyph
1211d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		items.sort()
1221d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1231d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		format = 2
1241d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		delta = None
1251d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		for inID, outID, inGlyph, outGlyph in items:
1261d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			if delta is None:
1271d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				delta = outID - inID
1281d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			else:
1291d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				if delta != outID - inID:
1301d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					break
1311d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		else:
1321d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			format = 1
1331d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1341d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		rawTable = {}
1351d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		self.Format = format
1361d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		cov = Coverage()
1371d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		cov.glyphs = input = []
1381d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		subst = []
1391d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		for inID, outID, inGlyph, outGlyph in items:
1401d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			input.append(inGlyph)
1411d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			subst.append(outGlyph)
1421d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		rawTable["Coverage"] = cov
1431d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		if format == 1:
1441d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			assert delta is not None
1451d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			rawTable["DeltaGlyphID"] = delta
1461d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		else:
1471d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			rawTable["Substitute"] = subst
1481d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		return rawTable
1491d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1501d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def toXML2(self, xmlWriter, font):
1511d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		items = self.mapping.items()
1521d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		items.sort()
1531d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		for inGlyph, outGlyph in items:
1541d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			xmlWriter.simpletag("Substitution",
1551d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					[("in", inGlyph), ("out", outGlyph)])
1561d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			xmlWriter.newline()
1571d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1581d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def fromXML(self, (name, attrs, content), font):
1591d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		mapping = getattr(self, "mapping", None)
1601d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		if mapping is None:
1611d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			mapping = {}
1621d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			self.mapping = mapping
1631d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		mapping[attrs["in"]] = attrs["out"]
1641d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1651d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
166a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvrclass ClassDef(FormatSwitchingBaseTable):
167a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
168a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def postRead(self, rawTable, font):
169a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		classDefs = {}
170a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		if self.Format == 1:
171a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			start = rawTable["StartGlyph"]
172a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			glyphID = font.getGlyphID(start)
173a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			for cls in rawTable["ClassValueArray"]:
174a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				classDefs[cls] = font.getGlyphName(glyphID)
175a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				glyphID = glyphID + 1
176a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		elif self.Format == 2:
177a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			records = rawTable["ClassRangeRecord"]
178a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			for rec in records:
179a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				start = rec.Start
180a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				end = rec.End
181a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				cls = rec.Class
182a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				classDefs[start] = cls
183a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				for glyphID in range(font.getGlyphID(start) + 1,
184a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr						font.getGlyphID(end)):
185a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr					classDefs[font.getGlyphName(glyphID)] = cls
186a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				classDefs[end] = cls
187a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		else:
188a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			assert 0, "unknown format: %s" % self.Format
189a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		self.classDefs = classDefs
190a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
191a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def preWrite(self, font):
192a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items = self.classDefs.items()
193a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		for i in range(len(items)):
194a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			glyphName, cls = items[i]
195a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			items[i] = font.getGlyphID(glyphName), glyphName, cls
196a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items.sort()
197a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		last, lastName, lastCls = items[0]
198a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		rec = ClassRangeRecord()
199a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		rec.Start = lastName
200a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		rec.Class = lastCls
201a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		ranges = [rec]
202a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		for glyphID, glyphName, cls in items[1:]:
203a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			if glyphID != last + 1 or cls != lastCls:
204a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				rec.End = lastName
205a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				rec = ClassRangeRecord()
206a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				rec.Start = glyphName
207a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				rec.Class = cls
208a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				ranges.append(rec)
209a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			last = glyphID
210a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			lastName = glyphName
211a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			lastCls = cls
212a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		rec.End = lastName
213a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		self.Format = 2  # currently no support for Format 1
214a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		return {"ClassRangeRecord": ranges}
215a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
216a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def toXML2(self, xmlWriter, font):
217a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items = self.classDefs.items()
218a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items.sort()
219a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		for glyphName, cls in items:
220a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
221a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			xmlWriter.newline()
222a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
223a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def fromXML(self, (name, attrs, content), font):
224a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		classDefs = getattr(self, "classDefs", None)
225a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		if classDefs is None:
226a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			classDefs = {}
227a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			self.classDefs = classDefs
228a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		classDefs[attrs["glyph"]] = int(attrs["class"])
229a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
230a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
231b2486125e9438443a1419706c50958ab0676eb5ajvrclass AlternateSubst(FormatSwitchingBaseTable):
232b2486125e9438443a1419706c50958ab0676eb5ajvr
233b2486125e9438443a1419706c50958ab0676eb5ajvr	def postRead(self, rawTable, font):
234b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates = {}
235b2486125e9438443a1419706c50958ab0676eb5ajvr		if self.Format == 1:
236b2486125e9438443a1419706c50958ab0676eb5ajvr			input = rawTable["Coverage"].glyphs
237b2486125e9438443a1419706c50958ab0676eb5ajvr			alts = rawTable["AlternateSet"]
238b2486125e9438443a1419706c50958ab0676eb5ajvr			assert len(input) == len(alts)
239b2486125e9438443a1419706c50958ab0676eb5ajvr			for i in range(len(input)):
240b2486125e9438443a1419706c50958ab0676eb5ajvr				alternates[input[i]] = alts[i].Alternate
241b2486125e9438443a1419706c50958ab0676eb5ajvr		else:
242b2486125e9438443a1419706c50958ab0676eb5ajvr			assert 0, "unknown format: %s" % self.Format
243b2486125e9438443a1419706c50958ab0676eb5ajvr		self.alternates = alternates
244b2486125e9438443a1419706c50958ab0676eb5ajvr
245b2486125e9438443a1419706c50958ab0676eb5ajvr	def preWrite(self, font):
246b2486125e9438443a1419706c50958ab0676eb5ajvr		self.Format = 1
247b2486125e9438443a1419706c50958ab0676eb5ajvr		items = self.alternates.items()
248b2486125e9438443a1419706c50958ab0676eb5ajvr		for i in range(len(items)):
249b2486125e9438443a1419706c50958ab0676eb5ajvr			glyphName, set = items[i]
250b2486125e9438443a1419706c50958ab0676eb5ajvr			items[i] = font.getGlyphID(glyphName), glyphName, set
251b2486125e9438443a1419706c50958ab0676eb5ajvr		items.sort()
252b2486125e9438443a1419706c50958ab0676eb5ajvr		cov = Coverage()
253b2486125e9438443a1419706c50958ab0676eb5ajvr		glyphs = []
254b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates = []
255b2486125e9438443a1419706c50958ab0676eb5ajvr		cov.glyphs = glyphs
256b2486125e9438443a1419706c50958ab0676eb5ajvr		for glyphID, glyphName, set in items:
257b2486125e9438443a1419706c50958ab0676eb5ajvr			glyphs.append(glyphName)
258b2486125e9438443a1419706c50958ab0676eb5ajvr			alts = AlternateSet()
259b2486125e9438443a1419706c50958ab0676eb5ajvr			alts.Alternate = set
260b2486125e9438443a1419706c50958ab0676eb5ajvr			alternates.append(alts)
261b2486125e9438443a1419706c50958ab0676eb5ajvr		return {"Coverage": cov, "AlternateSet": alternates}
262b2486125e9438443a1419706c50958ab0676eb5ajvr
263b2486125e9438443a1419706c50958ab0676eb5ajvr	def toXML2(self, xmlWriter, font):
264b2486125e9438443a1419706c50958ab0676eb5ajvr		items = self.alternates.items()
265b2486125e9438443a1419706c50958ab0676eb5ajvr		items.sort()
266b2486125e9438443a1419706c50958ab0676eb5ajvr		for glyphName, alternates in items:
267b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.begintag("AlternateSet", glyph=glyphName)
268b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.newline()
269b2486125e9438443a1419706c50958ab0676eb5ajvr			for alt in alternates:
270b2486125e9438443a1419706c50958ab0676eb5ajvr				xmlWriter.simpletag("Alternate", glyph=alt)
271b2486125e9438443a1419706c50958ab0676eb5ajvr				xmlWriter.newline()
272b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.endtag("AlternateSet")
273b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.newline()
274b2486125e9438443a1419706c50958ab0676eb5ajvr
275b2486125e9438443a1419706c50958ab0676eb5ajvr	def fromXML(self, (name, attrs, content), font):
276b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates = getattr(self, "alternates", None)
277b2486125e9438443a1419706c50958ab0676eb5ajvr		if alternates is None:
278b2486125e9438443a1419706c50958ab0676eb5ajvr			alternates = {}
279b2486125e9438443a1419706c50958ab0676eb5ajvr			self.alternates = alternates
280b2486125e9438443a1419706c50958ab0676eb5ajvr		glyphName = attrs["glyph"]
281b2486125e9438443a1419706c50958ab0676eb5ajvr		set = []
282b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates[glyphName] = set
283b2486125e9438443a1419706c50958ab0676eb5ajvr		for element in content:
284b2486125e9438443a1419706c50958ab0676eb5ajvr			if type(element) != TupleType:
285b2486125e9438443a1419706c50958ab0676eb5ajvr				continue
286b2486125e9438443a1419706c50958ab0676eb5ajvr			name, attrs, content = element
287b2486125e9438443a1419706c50958ab0676eb5ajvr			set.append(attrs["glyph"])
288b2486125e9438443a1419706c50958ab0676eb5ajvr
289b2486125e9438443a1419706c50958ab0676eb5ajvr
29064b5c80e80444a124da335e8d4d208bffcf2737bjvr#
29164b5c80e80444a124da335e8d4d208bffcf2737bjvr# For each subtable format there is a class. However, we don't really distinguish
29264b5c80e80444a124da335e8d4d208bffcf2737bjvr# between "field name" and "format name": often these are the same. Yet there's
29364b5c80e80444a124da335e8d4d208bffcf2737bjvr# a whole bunch of fields with different names. The following dict is a mapping
29464b5c80e80444a124da335e8d4d208bffcf2737bjvr# from "format name" to "field name". _buildClasses() uses this to create a
29564b5c80e80444a124da335e8d4d208bffcf2737bjvr# subclass for each alternate field name.
29664b5c80e80444a124da335e8d4d208bffcf2737bjvr#
29764b5c80e80444a124da335e8d4d208bffcf2737bjvr_equivalents = {
29864b5c80e80444a124da335e8d4d208bffcf2737bjvr	'MarkArray': ("Mark1Array",),
29964b5c80e80444a124da335e8d4d208bffcf2737bjvr	'LangSys': ('DefaultLangSys',),
30064b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
301d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
30264b5c80e80444a124da335e8d4d208bffcf2737bjvr			'LookaheadCoverage'),
30364b5c80e80444a124da335e8d4d208bffcf2737bjvr	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
30464b5c80e80444a124da335e8d4d208bffcf2737bjvr			'LookaheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
30564b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
30664b5c80e80444a124da335e8d4d208bffcf2737bjvr			'Mark2Anchor', 'MarkAnchor'),
30764b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
30864b5c80e80444a124da335e8d4d208bffcf2737bjvr			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
30964b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Axis': ('HorizAxis', 'VertAxis',),
31064b5c80e80444a124da335e8d4d208bffcf2737bjvr	'MinMax': ('DefaultMinMax',),
31164b5c80e80444a124da335e8d4d208bffcf2737bjvr	'BaseCoord': ('MinCoord', 'MaxCoord',),
31264b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfLangSys': ('DefJstfLangSys',),
31364b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
31464b5c80e80444a124da335e8d4d208bffcf2737bjvr			'ExtensionDisableGSUB',),
31564b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
31664b5c80e80444a124da335e8d4d208bffcf2737bjvr			'ExtensionDisableGPOS',),
31764b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
31864b5c80e80444a124da335e8d4d208bffcf2737bjvr}
319d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
320d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
321d4d151390d1288f8d2df30f6dfa26a309c7334dajvrdef _buildClasses():
322d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	import new, re
323d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	from otData import otData
324d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
325d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
326d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	namespace = globals()
327d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
328d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	# populate module with classes
329d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	for name, table in otData:
330d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		baseClass = BaseTable
331d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		m = formatPat.match(name)
332d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		if m:
333d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			# XxxFormatN subtable, we only add the "base" table
334d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			name = m.group(1)
335d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			baseClass = FormatSwitchingBaseTable
336d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		if not namespace.has_key(name):
33764b5c80e80444a124da335e8d4d208bffcf2737bjvr			# the class doesn't exist yet, so the base implementation is used.
338d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls = new.classobj(name, (baseClass,), {})
339d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			namespace[name] = cls
340d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
34164b5c80e80444a124da335e8d4d208bffcf2737bjvr	for base, alts in _equivalents.items():
342d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		base = namespace[base]
343d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		for alt in alts:
344d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			namespace[alt] = new.classobj(alt, (base,), {})
345d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
346d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	global lookupTypes
347d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	lookupTypes = {
348d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		'GSUB': {
349d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			1: SingleSubst,
350d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			2: MultipleSubst,
351d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			3: AlternateSubst,
352d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			4: LigatureSubst,
353d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			5: ContextSubst,
354d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			6: ChainContextSubst,
355d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			7: ExtensionSubst,
356d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		},
357d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		'GPOS': {
358d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			1: SinglePos,
359d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			2: PairPos,
360d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			3: CursivePos,
361d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			4: MarkBasePos,
362d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			5: MarkLigPos,
363d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			6: MarkMarkPos,
364d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			7: ContextPos,
365d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			8: ChainContextPos,
366d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			9: ExtensionPos,
367d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		},
368d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	}
369d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
37064b5c80e80444a124da335e8d4d208bffcf2737bjvr	for lookupEnum in lookupTypes.values():
37164b5c80e80444a124da335e8d4d208bffcf2737bjvr		for enum, cls in lookupEnum.items():
37264b5c80e80444a124da335e8d4d208bffcf2737bjvr			cls.LookupType = enum
373d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
374d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	# add converters to classes
37564b5c80e80444a124da335e8d4d208bffcf2737bjvr	from otConverters import buildConverters
376d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	for name, table in otData:
377d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		m = formatPat.match(name)
378d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		if m:
379d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			# XxxFormatN subtable, add converter to "base" table
380d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			name, format = m.groups()
381d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			format = int(format)
382d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls = namespace[name]
383d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			if not hasattr(cls, "converters"):
384d4d151390d1288f8d2df30f6dfa26a309c7334dajvr				cls.converters = {}
385d4d151390d1288f8d2df30f6dfa26a309c7334dajvr				cls.convertersByName = {}
38664b5c80e80444a124da335e8d4d208bffcf2737bjvr			converters, convertersByName = buildConverters(table[1:], namespace)
387d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls.converters[format] = converters
388d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls.convertersByName[format] = convertersByName
389d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		else:
390d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls = namespace[name]
39164b5c80e80444a124da335e8d4d208bffcf2737bjvr			cls.converters, cls.convertersByName = buildConverters(table, namespace)
392d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
393d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
394d4d151390d1288f8d2df30f6dfa26a309c7334dajvr_buildClasses()
395