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