otTables.py revision 8e1d75b1a3fb4874bb78d9c34a11ba30de81a5cc
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)OpenType subtables.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Most are constructed upon import from data in otData.py, all are populated with
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)converter objects from otConverters.py.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from otBase import BaseTable, FormatSwitchingBaseTable
9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)from types import TupleType
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class LookupOrder(BaseTable):
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class FeatureParams(BaseTable):
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	# XXX The above is no longer true; the 'size' feature uses FeatureParams now.
191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Coverage(FormatSwitchingBaseTable):
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# manual implementation to get rid of glyphID dependencies
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch	def postRead(self, rawTable, font):
26a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)		if self.Format == 1:
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.glyphs = rawTable["GlyphArray"]
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif self.Format == 2:
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			glyphs = self.glyphs = []
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ranges = rawTable["RangeRecord"]
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for r in ranges:
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				assert r.StartCoverageIndex == len(glyphs), \
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					(r.StartCoverageIndex, len(glyphs))
344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)				start = r.Start
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				end = r.End
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				startID = font.getGlyphID(start)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				endID = font.getGlyphID(end)
38116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch				glyphs.append(start)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				for glyphID in range(startID + 1, endID):
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					glyphs.append(font.getGlyphName(glyphID))
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)				if start != end:
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					glyphs.append(end)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert 0, "unknown format: %s" % self.Format
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
46ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch	def preWrite(self, font):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		glyphs = getattr(self, "glyphs")
48116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		if glyphs is None:
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			glyphs = self.glyphs = []
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		format = 1
5168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)		rawTable = {"GlyphArray": glyphs}
5268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)		if glyphs:
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# find out whether Format 2 is more compact or not
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			glyphIDs = []
551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			for glyphName in glyphs:
561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci				glyphIDs.append(font.getGlyphID(glyphName))
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			last = glyphIDs[0]
591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			ranges = [[last]]
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for glyphID in glyphIDs[1:]:
611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci				if glyphID != last + 1:
62a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)					ranges[-1].append(last)
63a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)					ranges.append([glyphID])
64a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)				last = glyphID
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ranges[-1].append(last)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Format 2 is more compact
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				index = 0
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				for i in range(len(ranges)):
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					start, end = ranges[i]
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					r = RangeRecord()
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					r.Start = font.getGlyphName(start)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					r.End = font.getGlyphName(end)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					r.StartCoverageIndex = index
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci					ranges[i] = r
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					index = index + end - start + 1
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				format = 2
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci				rawTable = {"RangeRecord": ranges}
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			#else:
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			#	fallthrough; Format 1 is more compact
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Format = format
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return rawTable
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	def toXML2(self, xmlWriter, font):
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for glyphName in getattr(self, "glyphs", []):
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.simpletag("Glyph", value=glyphName)
881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			xmlWriter.newline()
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	def fromXML(self, (name, attrs, content), font):
911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		glyphs = getattr(self, "glyphs", None)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if glyphs is None:
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			glyphs = []
941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			self.glyphs = glyphs
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		glyphs.append(attrs["value"])
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SingleSubst(FormatSwitchingBaseTable):
997d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def postRead(self, rawTable, font):
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)		mapping = {}
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		input = rawTable["Coverage"].glyphs
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)		if self.Format == 1:
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			delta = rawTable["DeltaGlyphID"]
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for inGlyph in input:
1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci				glyphID = font.getGlyphID(inGlyph)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				mapping[inGlyph] = font.getGlyphName(glyphID + delta)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif self.Format == 2:
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert len(input) == rawTable["GlyphCount"], \
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci					"invalid SingleSubstFormat2 table"
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			subst = rawTable["Substitute"]
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for i in range(len(input)):
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				mapping[input[i]] = subst[i]
1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		else:
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert 0, "unknown format: %s" % self.Format
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.mapping = mapping
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	def preWrite(self, font):
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items = self.mapping.items()
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for i in range(len(items)):
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			inGlyph, outGlyph = items[i]
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			items[i] = font.getGlyphID(inGlyph), font.getGlyphID(outGlyph), \
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)					inGlyph, outGlyph
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items.sort()
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		format = 2
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		delta = None
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for inID, outID, inGlyph, outGlyph in items:
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if delta is None:
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				delta = outID - inID
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if delta != outID - inID:
1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci					break
134a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)		else:
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			format = 1
136a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rawTable = {}
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Format = format
139e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch		cov = Coverage()
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cov.glyphs = input = []
1411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		subst = []
1421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		for inID, outID, inGlyph, outGlyph in items:
1431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			input.append(inGlyph)
1441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			subst.append(outGlyph)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rawTable["Coverage"] = cov
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if format == 1:
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert delta is not None
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			rawTable["DeltaGlyphID"] = delta
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			rawTable["Substitute"] = subst
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return rawTable
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)	def toXML2(self, xmlWriter, font):
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items = self.mapping.items()
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		items.sort()
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for inGlyph, outGlyph in items:
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.simpletag("Substitution",
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					[("in", inGlyph), ("out", outGlyph)])
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)			xmlWriter.newline()
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def fromXML(self, (name, attrs, content), font):
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		mapping = getattr(self, "mapping", None)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if mapping is None:
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			mapping = {}
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.mapping = mapping
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)		mapping[attrs["in"]] = attrs["out"]
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ClassDef(FormatSwitchingBaseTable):
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def postRead(self, rawTable, font):
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		classDefs = {}
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.Format == 1:
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			start = rawTable["StartGlyph"]
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			glyphID = font.getGlyphID(start)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for cls in rawTable["ClassValueArray"]:
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				classDefs[font.getGlyphName(glyphID)] = cls
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				glyphID = glyphID + 1
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif self.Format == 2:
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			records = rawTable["ClassRangeRecord"]
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for rec in records:
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				start = rec.Start
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				end = rec.End
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				cls = rec.Class
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				classDefs[start] = cls
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				for glyphID in range(font.getGlyphID(start) + 1,
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						font.getGlyphID(end)):
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					classDefs[font.getGlyphName(glyphID)] = cls
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				classDefs[end] = cls
1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		else:
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert 0, "unknown format: %s" % self.Format
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.classDefs = classDefs
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def preWrite(self, font):
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items = self.classDefs.items()
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for i in range(len(items)):
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			glyphName, cls = items[i]
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			items[i] = font.getGlyphID(glyphName), glyphName, cls
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items.sort()
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		last, lastName, lastCls = items[0]
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rec = ClassRangeRecord()
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rec.Start = lastName
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rec.Class = lastCls
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		ranges = [rec]
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for glyphID, glyphName, cls in items[1:]:
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if glyphID != last + 1 or cls != lastCls:
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				rec.End = lastName
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				rec = ClassRangeRecord()
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)				rec.Start = glyphName
2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)				rec.Class = cls
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ranges.append(rec)
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last = glyphID
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			lastName = glyphName
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			lastCls = cls
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rec.End = lastName
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Format = 2  # currently no support for Format 1
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return {"ClassRangeRecord": ranges}
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)	def toXML2(self, xmlWriter, font):
2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		items = self.classDefs.items()
2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		items.sort()
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for glyphName, cls in items:
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.newline()
2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def fromXML(self, (name, attrs, content), font):
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		classDefs = getattr(self, "classDefs", None)
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if classDefs is None:
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			classDefs = {}
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.classDefs = classDefs
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		classDefs[attrs["glyph"]] = int(attrs["class"])
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciclass AlternateSubst(FormatSwitchingBaseTable):
2351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def postRead(self, rawTable, font):
2371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		alternates = {}
2381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		if self.Format == 1:
2391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			input = rawTable["Coverage"].glyphs
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			alts = rawTable["AlternateSet"]
2411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			assert len(input) == len(alts)
242a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)			for i in range(len(input)):
243a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)				alternates[input[i]] = alts[i].Alternate
244a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)		else:
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert 0, "unknown format: %s" % self.Format
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.alternates = alternates
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def preWrite(self, font):
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Format = 1
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items = self.alternates.items()
2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		for i in range(len(items)):
252116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			glyphName, set = items[i]
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			items[i] = font.getGlyphID(glyphName), glyphName, set
2541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		items.sort()
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cov = Coverage()
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		glyphs = []
2571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		alternates = []
2582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)		cov.glyphs = glyphs
259116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		for glyphID, glyphName, set in items:
2601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			glyphs.append(glyphName)
261116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			alts = AlternateSet()
262116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			alts.Alternate = set
263116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			alternates.append(alts)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return {"Coverage": cov, "AlternateSet": alternates}
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	def toXML2(self, xmlWriter, font):
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items = self.alternates.items()
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items.sort()
2691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		for glyphName, alternates in items:
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.begintag("AlternateSet", glyph=glyphName)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.newline()
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for alt in alternates:
2731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci				xmlWriter.simpletag("Alternate", glyph=alt)
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				xmlWriter.newline()
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.endtag("AlternateSet")
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			xmlWriter.newline()
277116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
278116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch	def fromXML(self, (name, attrs, content), font):
279116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		alternates = getattr(self, "alternates", None)
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if alternates is None:
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			alternates = {}
2821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			self.alternates = alternates
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		glyphName = attrs["glyph"]
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set = []
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		alternates[glyphName] = set
2861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		for element in content:
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if type(element) != TupleType:
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			name, attrs, content = element
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			set.append(attrs["glyph"])
2911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class LigatureSubst(FormatSwitchingBaseTable):
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def postRead(self, rawTable, font):
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ligatures = {}
297116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		if self.Format == 1:
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			input = rawTable["Coverage"].glyphs
2992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)			ligSets = rawTable["LigatureSet"]
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert len(input) == len(ligSets)
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for i in range(len(input)):
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ligatures[input[i]] = ligSets[i].Ligature
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			assert 0, "unknown format: %s" % self.Format
305116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		self.ligatures = ligatures
306116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
307116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch	def preWrite(self, font):
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Format = 1
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items = self.ligatures.items()
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for i in range(len(items)):
3112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)			glyphName, set = items[i]
3122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)			items[i] = font.getGlyphID(glyphName), glyphName, set
3131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		items.sort()
3141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		glyphs = []
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cov = Coverage()
3161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		cov.glyphs = glyphs
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ligSets = []
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for glyphID, glyphName, set in items:
3191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			glyphs.append(glyphName)
320116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			ligSet = LigatureSet()
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ligs = ligSet.Ligature = []
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for lig in set:
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ligs.append(lig)
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ligSets.append(ligSet)
325116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		return {"Coverage": cov, "LigatureSet": ligSets}
326116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
327116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch	def toXML2(self, xmlWriter, font):
328116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		items = self.ligatures.items()
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		items.sort()
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for glyphName, ligSets in items:
3311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			xmlWriter.begintag("LigatureSet", glyph=glyphName)
3321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			xmlWriter.newline()
3331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci			for lig in ligSets:
3341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci				xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					components=",".join(lig.Component))
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				xmlWriter.newline()
337116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			xmlWriter.endtag("LigatureSet")
338116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			xmlWriter.newline()
339116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
340116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch	def fromXML(self, (name, attrs, content), font):
341116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		ligatures = getattr(self, "ligatures", None)
342116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		if ligatures is None:
343116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			ligatures = {}
344116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			self.ligatures = ligatures
345116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		glyphName = attrs["glyph"]
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ligs = []
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ligatures[glyphName] = ligs
348116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch		for element in content:
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if type(element) != TupleType:
350116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch				continue
351116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			name, attrs, content = element
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			lig = Ligature()
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			lig.LigGlyph = attrs["glyph"]
354116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			lig.Component = attrs["components"].split(",")
355116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch			ligs.append(lig)
356116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
357116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
358116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#
359116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch# For each subtable format there is a class. However, we don't really distinguish
360116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch# between "field name" and "format name": often these are the same. Yet there's
361116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch# a whole bunch of fields with different names. The following dict is a mapping
362116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch# from "format name" to "field name". _buildClasses() uses this to create a
363116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch# subclass for each alternate field name.
364116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#
365116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch_equivalents = {
366116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch	'MarkArray': ("Mark1Array",),
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	'LangSys': ('DefaultLangSys',),
368	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
369			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
370			'LookAheadCoverage'),
371	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
372			'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
373	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
374			'Mark2Anchor', 'MarkAnchor'),
375	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
376			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
377	'Axis': ('HorizAxis', 'VertAxis',),
378	'MinMax': ('DefaultMinMax',),
379	'BaseCoord': ('MinCoord', 'MaxCoord',),
380	'JstfLangSys': ('DefJstfLangSys',),
381	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
382			'ExtensionDisableGSUB',),
383	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
384			'ExtensionDisableGPOS',),
385	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
386}
387
388
389def _buildClasses():
390	import new, re
391	from otData import otData
392
393	formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
394	namespace = globals()
395
396	# populate module with classes
397	for name, table in otData:
398		baseClass = BaseTable
399		m = formatPat.match(name)
400		if m:
401			# XxxFormatN subtable, we only add the "base" table
402			name = m.group(1)
403			baseClass = FormatSwitchingBaseTable
404		if not namespace.has_key(name):
405			# the class doesn't exist yet, so the base implementation is used.
406			cls = new.classobj(name, (baseClass,), {})
407			namespace[name] = cls
408
409	for base, alts in _equivalents.items():
410		base = namespace[base]
411		for alt in alts:
412			namespace[alt] = new.classobj(alt, (base,), {})
413
414	global lookupTypes
415	lookupTypes = {
416		'GSUB': {
417			1: SingleSubst,
418			2: MultipleSubst,
419			3: AlternateSubst,
420			4: LigatureSubst,
421			5: ContextSubst,
422			6: ChainContextSubst,
423			7: ExtensionSubst,
424			8: ReverseChainSingleSubst,
425		},
426		'GPOS': {
427			1: SinglePos,
428			2: PairPos,
429			3: CursivePos,
430			4: MarkBasePos,
431			5: MarkLigPos,
432			6: MarkMarkPos,
433			7: ContextPos,
434			8: ChainContextPos,
435			9: ExtensionPos,
436		},
437	}
438	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
439	for lookupEnum in lookupTypes.values():
440		for enum, cls in lookupEnum.items():
441			cls.LookupType = enum
442
443	# add converters to classes
444	from otConverters import buildConverters
445	for name, table in otData:
446		m = formatPat.match(name)
447		if m:
448			# XxxFormatN subtable, add converter to "base" table
449			name, format = m.groups()
450			format = int(format)
451			cls = namespace[name]
452			if not hasattr(cls, "converters"):
453				cls.converters = {}
454				cls.convertersByName = {}
455			converters, convertersByName = buildConverters(table[1:], namespace)
456			cls.converters[format] = converters
457			cls.convertersByName[format] = convertersByName
458		else:
459			cls = namespace[name]
460			cls.converters, cls.convertersByName = buildConverters(table, namespace)
461
462
463_buildClasses()
464