otTables.py revision f2164abef39af7cfef989352fadfca628ed077b2
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 290f2164abef39af7cfef989352fadfca628ed077b2jvrclass LigatureSubst(FormatSwitchingBaseTable): 291f2164abef39af7cfef989352fadfca628ed077b2jvr 292f2164abef39af7cfef989352fadfca628ed077b2jvr def postRead(self, rawTable, font): 293f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures = {} 294f2164abef39af7cfef989352fadfca628ed077b2jvr if self.Format == 1: 295f2164abef39af7cfef989352fadfca628ed077b2jvr input = rawTable["Coverage"].glyphs 296f2164abef39af7cfef989352fadfca628ed077b2jvr ligSets = rawTable["LigatureSet"] 297f2164abef39af7cfef989352fadfca628ed077b2jvr assert len(input) == len(ligSets) 298f2164abef39af7cfef989352fadfca628ed077b2jvr for i in range(len(input)): 299f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures[input[i]] = ligSets[i].Ligature 300f2164abef39af7cfef989352fadfca628ed077b2jvr else: 301f2164abef39af7cfef989352fadfca628ed077b2jvr assert 0, "unknown format: %s" % self.Format 302f2164abef39af7cfef989352fadfca628ed077b2jvr self.ligatures = ligatures 303f2164abef39af7cfef989352fadfca628ed077b2jvr 304f2164abef39af7cfef989352fadfca628ed077b2jvr def preWrite(self, font): 305f2164abef39af7cfef989352fadfca628ed077b2jvr self.Format = 1 306f2164abef39af7cfef989352fadfca628ed077b2jvr items = self.ligatures.items() 307f2164abef39af7cfef989352fadfca628ed077b2jvr for i in range(len(items)): 308f2164abef39af7cfef989352fadfca628ed077b2jvr glyphName, set = items[i] 309f2164abef39af7cfef989352fadfca628ed077b2jvr items[i] = font.getGlyphID(glyphName), glyphName, set 310f2164abef39af7cfef989352fadfca628ed077b2jvr items.sort() 311f2164abef39af7cfef989352fadfca628ed077b2jvr glyphs = [] 312f2164abef39af7cfef989352fadfca628ed077b2jvr cov = Coverage() 313f2164abef39af7cfef989352fadfca628ed077b2jvr cov.glyphs = glyphs 314f2164abef39af7cfef989352fadfca628ed077b2jvr ligSets = [] 315f2164abef39af7cfef989352fadfca628ed077b2jvr for glyphID, glyphName, set in items: 316f2164abef39af7cfef989352fadfca628ed077b2jvr glyphs.append(glyphName) 317f2164abef39af7cfef989352fadfca628ed077b2jvr ligSet = LigatureSet() 318f2164abef39af7cfef989352fadfca628ed077b2jvr ligs = ligSet.Ligature = [] 319f2164abef39af7cfef989352fadfca628ed077b2jvr for lig in set: 320f2164abef39af7cfef989352fadfca628ed077b2jvr ligs.append(lig) 321f2164abef39af7cfef989352fadfca628ed077b2jvr ligSets.append(ligSet) 322f2164abef39af7cfef989352fadfca628ed077b2jvr return {"Coverage": cov, "LigatureSet": ligSets} 323f2164abef39af7cfef989352fadfca628ed077b2jvr 324f2164abef39af7cfef989352fadfca628ed077b2jvr def toXML2(self, xmlWriter, font): 325f2164abef39af7cfef989352fadfca628ed077b2jvr items = self.ligatures.items() 326f2164abef39af7cfef989352fadfca628ed077b2jvr items.sort() 327f2164abef39af7cfef989352fadfca628ed077b2jvr for glyphName, ligSets in items: 328f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.begintag("LigatureSet", glyph=glyphName) 329f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.newline() 330f2164abef39af7cfef989352fadfca628ed077b2jvr for lig in ligSets: 331f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph, 332f2164abef39af7cfef989352fadfca628ed077b2jvr components=",".join(lig.Component)) 333f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.newline() 334f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.endtag("LigatureSet") 335f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.newline() 336f2164abef39af7cfef989352fadfca628ed077b2jvr 337f2164abef39af7cfef989352fadfca628ed077b2jvr def fromXML(self, (name, attrs, content), font): 338f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures = getattr(self, "ligatures", None) 339f2164abef39af7cfef989352fadfca628ed077b2jvr if ligatures is None: 340f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures = {} 341f2164abef39af7cfef989352fadfca628ed077b2jvr self.ligatures = ligatures 342f2164abef39af7cfef989352fadfca628ed077b2jvr glyphName = attrs["glyph"] 343f2164abef39af7cfef989352fadfca628ed077b2jvr ligs = [] 344f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures[glyphName] = ligs 345f2164abef39af7cfef989352fadfca628ed077b2jvr for element in content: 346f2164abef39af7cfef989352fadfca628ed077b2jvr if type(element) != TupleType: 347f2164abef39af7cfef989352fadfca628ed077b2jvr continue 348f2164abef39af7cfef989352fadfca628ed077b2jvr name, attrs, content = element 349f2164abef39af7cfef989352fadfca628ed077b2jvr lig = Ligature() 350f2164abef39af7cfef989352fadfca628ed077b2jvr lig.LigGlyph = attrs["glyph"] 351f2164abef39af7cfef989352fadfca628ed077b2jvr lig.Component = attrs["components"].split(",") 352f2164abef39af7cfef989352fadfca628ed077b2jvr ligs.append(lig) 353f2164abef39af7cfef989352fadfca628ed077b2jvr 354f2164abef39af7cfef989352fadfca628ed077b2jvr 35564b5c80e80444a124da335e8d4d208bffcf2737bjvr# 35664b5c80e80444a124da335e8d4d208bffcf2737bjvr# For each subtable format there is a class. However, we don't really distinguish 35764b5c80e80444a124da335e8d4d208bffcf2737bjvr# between "field name" and "format name": often these are the same. Yet there's 35864b5c80e80444a124da335e8d4d208bffcf2737bjvr# a whole bunch of fields with different names. The following dict is a mapping 35964b5c80e80444a124da335e8d4d208bffcf2737bjvr# from "format name" to "field name". _buildClasses() uses this to create a 36064b5c80e80444a124da335e8d4d208bffcf2737bjvr# subclass for each alternate field name. 36164b5c80e80444a124da335e8d4d208bffcf2737bjvr# 36264b5c80e80444a124da335e8d4d208bffcf2737bjvr_equivalents = { 36364b5c80e80444a124da335e8d4d208bffcf2737bjvr 'MarkArray': ("Mark1Array",), 36464b5c80e80444a124da335e8d4d208bffcf2737bjvr 'LangSys': ('DefaultLangSys',), 36564b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage', 366d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage', 36764b5c80e80444a124da335e8d4d208bffcf2737bjvr 'LookaheadCoverage'), 36864b5c80e80444a124da335e8d4d208bffcf2737bjvr 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', 36964b5c80e80444a124da335e8d4d208bffcf2737bjvr 'LookaheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), 37064b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', 37164b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Mark2Anchor', 'MarkAnchor'), 37264b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', 37364b5c80e80444a124da335e8d4d208bffcf2737bjvr 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), 37464b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Axis': ('HorizAxis', 'VertAxis',), 37564b5c80e80444a124da335e8d4d208bffcf2737bjvr 'MinMax': ('DefaultMinMax',), 37664b5c80e80444a124da335e8d4d208bffcf2737bjvr 'BaseCoord': ('MinCoord', 'MaxCoord',), 37764b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfLangSys': ('DefJstfLangSys',), 37864b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', 37964b5c80e80444a124da335e8d4d208bffcf2737bjvr 'ExtensionDisableGSUB',), 38064b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', 38164b5c80e80444a124da335e8d4d208bffcf2737bjvr 'ExtensionDisableGPOS',), 38264b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), 38364b5c80e80444a124da335e8d4d208bffcf2737bjvr} 384d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 385d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 386d4d151390d1288f8d2df30f6dfa26a309c7334dajvrdef _buildClasses(): 387d4d151390d1288f8d2df30f6dfa26a309c7334dajvr import new, re 388d4d151390d1288f8d2df30f6dfa26a309c7334dajvr from otData import otData 389d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 390d4d151390d1288f8d2df30f6dfa26a309c7334dajvr formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$") 391d4d151390d1288f8d2df30f6dfa26a309c7334dajvr namespace = globals() 392d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 393d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # populate module with classes 394d4d151390d1288f8d2df30f6dfa26a309c7334dajvr for name, table in otData: 395d4d151390d1288f8d2df30f6dfa26a309c7334dajvr baseClass = BaseTable 396d4d151390d1288f8d2df30f6dfa26a309c7334dajvr m = formatPat.match(name) 397d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if m: 398d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # XxxFormatN subtable, we only add the "base" table 399d4d151390d1288f8d2df30f6dfa26a309c7334dajvr name = m.group(1) 400d4d151390d1288f8d2df30f6dfa26a309c7334dajvr baseClass = FormatSwitchingBaseTable 401d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if not namespace.has_key(name): 40264b5c80e80444a124da335e8d4d208bffcf2737bjvr # the class doesn't exist yet, so the base implementation is used. 403d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls = new.classobj(name, (baseClass,), {}) 404d4d151390d1288f8d2df30f6dfa26a309c7334dajvr namespace[name] = cls 405d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 40664b5c80e80444a124da335e8d4d208bffcf2737bjvr for base, alts in _equivalents.items(): 407d4d151390d1288f8d2df30f6dfa26a309c7334dajvr base = namespace[base] 408d4d151390d1288f8d2df30f6dfa26a309c7334dajvr for alt in alts: 409d4d151390d1288f8d2df30f6dfa26a309c7334dajvr namespace[alt] = new.classobj(alt, (base,), {}) 410d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 411d4d151390d1288f8d2df30f6dfa26a309c7334dajvr global lookupTypes 412d4d151390d1288f8d2df30f6dfa26a309c7334dajvr lookupTypes = { 413d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 'GSUB': { 414d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 1: SingleSubst, 415d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 2: MultipleSubst, 416d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 3: AlternateSubst, 417d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 4: LigatureSubst, 418d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 5: ContextSubst, 419d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 6: ChainContextSubst, 420d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 7: ExtensionSubst, 421d4d151390d1288f8d2df30f6dfa26a309c7334dajvr }, 422d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 'GPOS': { 423d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 1: SinglePos, 424d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 2: PairPos, 425d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 3: CursivePos, 426d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 4: MarkBasePos, 427d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 5: MarkLigPos, 428d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 6: MarkMarkPos, 429d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 7: ContextPos, 430d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 8: ChainContextPos, 431d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 9: ExtensionPos, 432d4d151390d1288f8d2df30f6dfa26a309c7334dajvr }, 433d4d151390d1288f8d2df30f6dfa26a309c7334dajvr } 434d4d151390d1288f8d2df30f6dfa26a309c7334dajvr lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS 43564b5c80e80444a124da335e8d4d208bffcf2737bjvr for lookupEnum in lookupTypes.values(): 43664b5c80e80444a124da335e8d4d208bffcf2737bjvr for enum, cls in lookupEnum.items(): 43764b5c80e80444a124da335e8d4d208bffcf2737bjvr cls.LookupType = enum 438d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 439d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # add converters to classes 44064b5c80e80444a124da335e8d4d208bffcf2737bjvr from otConverters import buildConverters 441d4d151390d1288f8d2df30f6dfa26a309c7334dajvr for name, table in otData: 442d4d151390d1288f8d2df30f6dfa26a309c7334dajvr m = formatPat.match(name) 443d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if m: 444d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # XxxFormatN subtable, add converter to "base" table 445d4d151390d1288f8d2df30f6dfa26a309c7334dajvr name, format = m.groups() 446d4d151390d1288f8d2df30f6dfa26a309c7334dajvr format = int(format) 447d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls = namespace[name] 448d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if not hasattr(cls, "converters"): 449d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.converters = {} 450d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.convertersByName = {} 45164b5c80e80444a124da335e8d4d208bffcf2737bjvr converters, convertersByName = buildConverters(table[1:], namespace) 452d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.converters[format] = converters 453d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.convertersByName[format] = convertersByName 454d4d151390d1288f8d2df30f6dfa26a309c7334dajvr else: 455d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls = namespace[name] 45664b5c80e80444a124da335e8d4d208bffcf2737bjvr cls.converters, cls.convertersByName = buildConverters(table, namespace) 457d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 458d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 459d4d151390d1288f8d2df30f6dfa26a309c7334dajvr_buildClasses() 460