_c_m_a_p.py revision 18316aa769566eeb6f3f4a6ed2685fa8f8e861c2
132c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbodfrom __future__ import print_function, division 230e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import * 330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.textTools import safeEval, readHex 42b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbodfrom . import DefaultTable 530e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport sys 67842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport struct 77842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport array 8d299b55d14fa77411140c0cc1c2524583b4ffa58jvrimport operator 97842e56b97ce677b83bdab09cda48bc2d89ac75aJust 107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 117842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass table__c_m_a_p(DefaultTable.DefaultTable): 127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 137842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getcmap(self, platformID, platEncID): 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust for subtable in self.tables: 157842e56b97ce677b83bdab09cda48bc2d89ac75aJust if (subtable.platformID == platformID and 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust subtable.platEncID == platEncID): 177842e56b97ce677b83bdab09cda48bc2d89ac75aJust return subtable 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust return None # not found 197842e56b97ce677b83bdab09cda48bc2d89ac75aJust 207842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 217842e56b97ce677b83bdab09cda48bc2d89ac75aJust tableVersion, numSubTables = struct.unpack(">HH", data[:4]) 227842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = int(tableVersion) 237842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = tables = [] 24d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets = {} 257842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(numSubTables): 267842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID, offset = struct.unpack( 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust ">HHl", data[4+i*8:4+(i+1)*8]) 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID = int(platformID), int(platEncID) 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust format, length = struct.unpack(">HH", data[offset:offset+4]) 3051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if format in [8,10,12,13]: 31924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length = struct.unpack(">HHL", data[offset:offset+8]) 320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr elif format in [14]: 330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length = struct.unpack(">HL", data[offset:offset+6]) 340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 352db352c748a933d85264deb102036137f06b840fjvr if not length: 363ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("Error: cmap subtable is reported as having zero length: platformID %s, platEncID %s, format %s offset %s. Skipping table." % (platformID, platEncID,format, offset)) 372db352c748a933d85264deb102036137f06b840fjvr continue 38bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 397842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 407842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = platformID 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = platEncID 44d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that by default we decompile only the subtable header info; 45d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # any other data gets decompiled only when an attribute of the 46d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # subtable is referenced. 47d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.decompileHeader(data[offset:offset+int(length)], ttFont) 48bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if offset in seenOffsets: 49d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.cmap = tables[seenOffsets[offset]].cmap 50d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 51d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets[offset] = i 527842e56b97ce677b83bdab09cda48bc2d89ac75aJust tables.append(table) 537842e56b97ce677b83bdab09cda48bc2d89ac75aJust 547842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 55b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__() 567842e56b97ce677b83bdab09cda48bc2d89ac75aJust numSubTables = len(self.tables) 577842e56b97ce677b83bdab09cda48bc2d89ac75aJust totalOffset = 4 + 8 * numSubTables 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = struct.pack(">HH", self.tableVersion, numSubTables) 59821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod tableData = b"" 60d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seen = {} # Some tables are the same object reference. Don't compile them twice. 61d299b55d14fa77411140c0cc1c2524583b4ffa58jvr done = {} # Some tables are different objects, but compile to the same data chunk 627842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 63d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 64d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] 65d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 66d299b55d14fa77411140c0cc1c2524583b4ffa58jvr chunk = table.compile(ttFont) 67bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if chunk in done: 68d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = done[chunk] 69d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 70d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(tableData) 71d299b55d14fa77411140c0cc1c2524583b4ffa58jvr tableData = tableData + chunk 727842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset) 737842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data + tableData 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 757842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.simpletag("tableVersion", version=self.tableVersion) 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 787842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 797842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.toXML(writer, ttFont) 807842e56b97ce677b83bdab09cda48bc2d89ac75aJust 813a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 827842e56b97ce677b83bdab09cda48bc2d89ac75aJust if name == "tableVersion": 837842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = safeEval(attrs["version"]) 847842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 85180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name[:12] != "cmap_format_": 867842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not hasattr(self, "tables"): 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = [] 890cd79a5642101821d66392e3bd0e3c445e97f09bjvr format = safeEval(name[12:]) 90bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 917842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 927842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 937842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 947842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = safeEval(attrs["platformID"]) 957842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = safeEval(attrs["platEncID"]) 963a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod table.fromXML(name, attrs, content, ttFont) 977842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables.append(table) 987842e56b97ce677b83bdab09cda48bc2d89ac75aJust 997842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1007842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass CmapSubtable: 1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __init__(self, format): 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.format = format 104d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 105d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 106d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 107d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __getattr__(self, attr): 108d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow lazy decompilation of subtables. 109d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if attr[:2] == '__': # don't handle requests for member functions like '__lt__' 110cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 111d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data == None: 112cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 113d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompile(None, None) # use saved data. 114d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None # Once this table has been decompiled, make sure we don't 115d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # just return the original data. Also avoids recursion when 116d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # called with an attribute that the cmap subtable doesn't have. 117d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return getattr(self, attr) 1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 119d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 120d299b55d14fa77411140c0cc1c2524583b4ffa58jvr format, length, language = struct.unpack(">HHH", data[:6]) 121d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == length, "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 122d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = int(format) 123d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = int(length) 124d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.language = int(language) 125d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[6:] 126d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 127d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.begintag(self.__class__.__name__, [ 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platformID", self.platformID), 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platEncID", self.platEncID), 132a84b28d934fb697755823c62799f4b65e2b92237jvr ("language", self.language), 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust ]) 1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 135ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 136a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.endtag(self.__class__.__name__) 1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 139a84b28d934fb697755823c62799f4b65e2b92237jvr 140a84b28d934fb697755823c62799f4b65e2b92237jvr def _writeCodes(self, codes, writer): 141d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (self.platformID, self.platEncID) == (3, 1) or (self.platformID, self.platEncID) == (3, 10) or self.platformID == 0: 142a84b28d934fb697755823c62799f4b65e2b92237jvr from fontTools.unicode import Unicode 143a84b28d934fb697755823c62799f4b65e2b92237jvr isUnicode = 1 144a84b28d934fb697755823c62799f4b65e2b92237jvr else: 145a84b28d934fb697755823c62799f4b65e2b92237jvr isUnicode = 0 146a84b28d934fb697755823c62799f4b65e2b92237jvr for code, name in codes: 147a84b28d934fb697755823c62799f4b65e2b92237jvr writer.simpletag("map", code=hex(code), name=name) 148a84b28d934fb697755823c62799f4b65e2b92237jvr if isUnicode: 149a84b28d934fb697755823c62799f4b65e2b92237jvr writer.comment(Unicode[code]) 150a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust 152b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod def __lt__(self, other): 153b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod if not isinstance(other, CmapSubtable): 154b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod raise TypeError("unordered types %s() < %s()", type(self), type(other)) 15596b321c8aea4dc64110d15a541c6f85152ae19cfBehdad Esfahbod 156b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod # implemented so that list.sort() sorts according to the spec. 1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust selfTuple = ( 15894118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platformID", None), 15994118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platEncID", None), 16094118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "language", None), 16194118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod self.__dict__) 1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust otherTuple = ( 16394118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platformID", None), 16494118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platEncID", None), 16594118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "language", None), 16694118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod other.__dict__) 167b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod return selfTuple < otherTuple 1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1707842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_0(CmapSubtable): 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1727842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 173d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 174d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 175d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 176d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 177d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 17817012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 179d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 180d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 262 == self.length, "Format 0 cmap subtable not 262 bytes" 1817842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIdArray = array.array("B") 182d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphIdArray.fromstring(self.data) 1837842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 184d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIdArray) 18597dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(lenArray)) 186d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = map(self.ttFont.getGlyphName, glyphIdArray) 187e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 188d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1897842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 191d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 192d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", 0, 262, self.language) + self.data 193d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 194ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod charCodeList = sorted(self.cmap.items()) 195d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [entry[0] for entry in charCodeList] 196d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = [entry[1] for entry in charCodeList] 19797dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod assert charCodes == list(range(256)) 198d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 199d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2008da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIdArray = array.array("B", valueList) 2010cd79a5642101821d66392e3bd0e3c445e97f09bjvr data = struct.pack(">HHH", 0, 262, self.language) + glyphIdArray.tostring() 2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert len(data) == 262 2037842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 2047842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2053a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 2060cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 207d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 208d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 209d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 2107842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 211b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 2137842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 214180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 216d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 219bafa66e665afa581b58391585f1792578a4d3d2djvrsubHeaderFormat = ">HHhH" 220bafa66e665afa581b58391585f1792578a4d3d2djvrclass SubHeader: 221bafa66e665afa581b58391585f1792578a4d3d2djvr def __init__(self): 222bafa66e665afa581b58391585f1792578a4d3d2djvr self.firstCode = None 223bafa66e665afa581b58391585f1792578a4d3d2djvr self.entryCount = None 224bafa66e665afa581b58391585f1792578a4d3d2djvr self.idDelta = None 225bafa66e665afa581b58391585f1792578a4d3d2djvr self.idRangeOffset = None 226bafa66e665afa581b58391585f1792578a4d3d2djvr self.glyphIndexArray = [] 227bafa66e665afa581b58391585f1792578a4d3d2djvr 2287842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_2(CmapSubtable): 2297842e56b97ce677b83bdab09cda48bc2d89ac75aJust 230d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, subHeader): 231d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 232d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # find the minGI which is not zero. 233d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = subHeader.glyphIndexArray[0] 234d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for gid in subHeader.glyphIndexArray: 235d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (gid != 0) and (gid < minGI): 236d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = gid 237d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 238d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K. 239d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We would like to pick an idDelta such that the first glyphArray GID is 1, 240d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # so that we are more likely to be able to combine glypharray GID subranges. 241d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem when minGI is > 32K 242d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 243d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 244d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the 245d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 246d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 247d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (minGI > 1): 248d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if minGI > 0x7FFF: 249d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = -(0x10000 - minGI) -1 250d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 251d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = minGI -1 252d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = subHeader.idDelta 253d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(subHeader.entryCount): 254d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = subHeader.glyphIndexArray[i] 255d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid > 0: 256d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray[i] = gid - idDelta 257d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 258d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2597842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 260d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 261d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 262d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 264d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 26517012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 266d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 267d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 268bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys = [] 269bafa66e665afa581b58391585f1792578a4d3d2djvr maxSubHeaderindex = 0 270bafa66e665afa581b58391585f1792578a4d3d2djvr # get the key array, and determine the number of subHeaders. 271d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys = array.array("H") 272d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.fromstring(data[:512]) 273d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[512:] 274180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 275d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.byteswap() 27632c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod subHeaderKeys = [ key//8 for key in allKeys] 277d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxSubHeaderindex = max(subHeaderKeys) 2787842e56b97ce677b83bdab09cda48bc2d89ac75aJust 279bafa66e665afa581b58391585f1792578a4d3d2djvr #Load subHeaders 280bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 281d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 282bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(maxSubHeaderindex + 1): 283bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 284bafa66e665afa581b58391585f1792578a4d3d2djvr (subHeader.firstCode, subHeader.entryCount, subHeader.idDelta, \ 285d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset) = struct.unpack(subHeaderFormat, data[pos:pos + 8]) 286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 8 287d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giDataPos = pos + subHeader.idRangeOffset-2 288d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList = array.array("H") 289d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2]) 290180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.byteswap() 292d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = giList 293bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 294bafa66e665afa581b58391585f1792578a4d3d2djvr # How this gets processed. 295bafa66e665afa581b58391585f1792578a4d3d2djvr # Charcodes may be one or two bytes. 296bafa66e665afa581b58391585f1792578a4d3d2djvr # The first byte of a charcode is mapped through the subHeaderKeys, to select 297bafa66e665afa581b58391585f1792578a4d3d2djvr # a subHeader. For any subheader but 0, the next byte is then mapped through the 298bafa66e665afa581b58391585f1792578a4d3d2djvr # selected subheader. If subheader Index 0 is selected, then the byte itself is 299bafa66e665afa581b58391585f1792578a4d3d2djvr # mapped through the subheader, and there is no second byte. 300bafa66e665afa581b58391585f1792578a4d3d2djvr # Then assume that the subsequent byte is the first byte of the next charcode,and repeat. 301bafa66e665afa581b58391585f1792578a4d3d2djvr # 302bafa66e665afa581b58391585f1792578a4d3d2djvr # Each subheader references a range in the glyphIndexArray whose length is entryCount. 303bafa66e665afa581b58391585f1792578a4d3d2djvr # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray 304bafa66e665afa581b58391585f1792578a4d3d2djvr # referenced by another subheader. 305bafa66e665afa581b58391585f1792578a4d3d2djvr # The only subheader that will be referenced by more than one first-byte value is the subheader 306bafa66e665afa581b58391585f1792578a4d3d2djvr # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef: 307bafa66e665afa581b58391585f1792578a4d3d2djvr # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx} 308bafa66e665afa581b58391585f1792578a4d3d2djvr # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex. 309bafa66e665afa581b58391585f1792578a4d3d2djvr # A subheader specifies a subrange within (0...256) by the 310bafa66e665afa581b58391585f1792578a4d3d2djvr # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero 311bafa66e665afa581b58391585f1792578a4d3d2djvr # (e.g. glyph not in font). 312bafa66e665afa581b58391585f1792578a4d3d2djvr # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar). 313bafa66e665afa581b58391585f1792578a4d3d2djvr # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by 314bafa66e665afa581b58391585f1792578a4d3d2djvr # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the 315bafa66e665afa581b58391585f1792578a4d3d2djvr # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex. 316bafa66e665afa581b58391585f1792578a4d3d2djvr # Example for Logocut-Medium 317bafa66e665afa581b58391585f1792578a4d3d2djvr # first byte of charcode = 129; selects subheader 1. 318bafa66e665afa581b58391585f1792578a4d3d2djvr # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252} 319bafa66e665afa581b58391585f1792578a4d3d2djvr # second byte of charCode = 66 320bafa66e665afa581b58391585f1792578a4d3d2djvr # the index offset = 66-64 = 2. 321bafa66e665afa581b58391585f1792578a4d3d2djvr # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is: 322bafa66e665afa581b58391585f1792578a4d3d2djvr # [glyphIndexArray index], [subrange array index] = glyphIndex 323bafa66e665afa581b58391585f1792578a4d3d2djvr # [256], [0]=1 from charcode [129, 64] 324bafa66e665afa581b58391585f1792578a4d3d2djvr # [257], [1]=2 from charcode [129, 65] 325bafa66e665afa581b58391585f1792578a4d3d2djvr # [258], [2]=3 from charcode [129, 66] 326bafa66e665afa581b58391585f1792578a4d3d2djvr # [259], [3]=4 from charcode [129, 67] 327d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero, 328d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # add it to the glyphID to get the final glyphIndex 329bafa66e665afa581b58391585f1792578a4d3d2djvr # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew! 330bafa66e665afa581b58391585f1792578a4d3d2djvr 331bafa66e665afa581b58391585f1792578a4d3d2djvr self.data = "" 332d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 333d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 334bafa66e665afa581b58391585f1792578a4d3d2djvr for firstByte in range(256): 335bafa66e665afa581b58391585f1792578a4d3d2djvr subHeadindex = subHeaderKeys[firstByte] 336bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = subHeaderList[subHeadindex] 337bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeadindex == 0: 338bafa66e665afa581b58391585f1792578a4d3d2djvr if (firstByte < subHeader.firstCode) or (firstByte >= subHeader.firstCode + subHeader.entryCount): 339d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 340bafa66e665afa581b58391585f1792578a4d3d2djvr else: 341bafa66e665afa581b58391585f1792578a4d3d2djvr charCode = firstByte 342bafa66e665afa581b58391585f1792578a4d3d2djvr offsetIndex = firstByte - subHeader.firstCode 343bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 344bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 345d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 346d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 347d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 348d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 349bafa66e665afa581b58391585f1792578a4d3d2djvr else: 350bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeader.entryCount: 351d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodeOffset = firstByte * 256 + subHeader.firstCode 352bafa66e665afa581b58391585f1792578a4d3d2djvr for offsetIndex in range(subHeader.entryCount): 353d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodeOffset + offsetIndex 354bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 355bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 356d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 357d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 358d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 359d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 360d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not subHeader.entryCount, then all char codes with this first byte are 361d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the 362d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same as mapping it to .notdef. 363d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # cmap values are GID's. 364d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 365c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod gids = list(cmap.values()) 366c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(cmap.keys()) 367d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 368d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 369e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 370d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 371d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 372e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 373e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 374d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 375bafa66e665afa581b58391585f1792578a4d3d2djvr 3767842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 377d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 378d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 379bafa66e665afa581b58391585f1792578a4d3d2djvr kEmptyTwoCharCodeRange = -1 380d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 381d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 382ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.cmap.items()) 383d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [item[0] for item in items] 384d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = [item[1] for item in items] 385d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 386d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 387d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 388e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 389d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 390d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap(rebuild=1) 391d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 392e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 393d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 394d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 2 tables 395d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 396d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 397d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 398d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 399d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 400d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 401d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 402d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 403d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 404d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 405d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 406d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 407d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 408d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 409d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 410d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Process the (char code to gid) item list in char code order. 411d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # By definition, all one byte char codes map to subheader 0. 412d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # For all the two byte char codes, we assume that the first byte maps maps to the empty subhead (with an entry count of 0, 413d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # which defines all char codes in its range to map to notdef) unless proven otherwise. 414d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that since the char code items are processed in char code order, all the char codes with the 415d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same first byte are in sequential order. 416bafa66e665afa581b58391585f1792578a4d3d2djvr 417d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys = [ kEmptyTwoCharCodeRange for x in range(256)] # list of indices into subHeaderList. 418bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 419d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 420d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up 421d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with a cmap where all the one byte char codes map to notdef, 422d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with the result that the subhead 0 would not get created just by processing the item list. 423d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[0] 424d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode > 255: 425d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = SubHeader() 426d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.firstCode = 0 427d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 0 428d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 429d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 430d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 431d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 432bafa66e665afa581b58391585f1792578a4d3d2djvr 433bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = -1 434d299b55d14fa77411140c0cc1c2524583b4ffa58jvr items = zip(charCodes, gids) 435d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode, gid in items: 436d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid == 0: 437d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 438bafa66e665afa581b58391585f1792578a4d3d2djvr firstbyte = charCode >> 8 439bafa66e665afa581b58391585f1792578a4d3d2djvr secondByte = charCode & 0x00FF 440d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 441d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if firstbyte != lastFirstByte: # Need to update the current subhead, and start a new one. 442bafa66e665afa581b58391585f1792578a4d3d2djvr if lastFirstByte > -1: 443d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of current subheader. 444d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 445d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 446d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero 447d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # for the indices matching the char codes. 448d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lastFirstByte == 0: 449d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subHeader.entryCount): 450d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = subHeader.firstCode + index 451d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[charCode] = 0 452d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 453bafa66e665afa581b58391585f1792578a4d3d2djvr assert (subHeader.entryCount == len(subHeader.glyphIndexArray)), "Error - subhead entry count does not match len of glyphID subrange." 454bafa66e665afa581b58391585f1792578a4d3d2djvr # init new subheader 455bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 456bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = secondByte 457d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 1 458d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 459d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 460d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[firstbyte] = len(subHeaderList) -1 461bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = firstbyte 462bafa66e665afa581b58391585f1792578a4d3d2djvr else: 463d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # need to fill in with notdefs all the code points between the last charCode and the current charCode. 464bafa66e665afa581b58391585f1792578a4d3d2djvr codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount) 465bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(codeDiff): 466d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(notdefGI) 467d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 468bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = subHeader.entryCount + codeDiff + 1 469d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 470d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of last subheader that we we added to the subheader array. 471d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 472d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 473d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges. 474bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 475bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = 0 476bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = 0 477bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idDelta = 0 478bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idRangeOffset = 2 479bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 480bafa66e665afa581b58391585f1792578a4d3d2djvr emptySubheadIndex = len(subHeaderList) - 1 481bafa66e665afa581b58391585f1792578a4d3d2djvr for index in range(256): 482d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeaderKeys[index] == kEmptyTwoCharCodeRange: 483bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys[index] = emptySubheadIndex 484bafa66e665afa581b58391585f1792578a4d3d2djvr # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the 485d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray, 486bafa66e665afa581b58391585f1792578a4d3d2djvr # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with 487bafa66e665afa581b58391585f1792578a4d3d2djvr # charcode 0 and GID 0. 488bafa66e665afa581b58391585f1792578a4d3d2djvr 489bafa66e665afa581b58391585f1792578a4d3d2djvr idRangeOffset = (len(subHeaderList)-1)*8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset. 490d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subheadRangeLen = len(subHeaderList) -1 # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2. 491d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subheadRangeLen): 492d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = subHeaderList[index] 493d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 494d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for j in range(index): 495d299b55d14fa77411140c0cc1c2524583b4ffa58jvr prevSubhead = subHeaderList[j] 496d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if prevSubhead.glyphIndexArray == subHeader.glyphIndexArray: # use the glyphIndexArray subarray 497d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = prevSubhead.idRangeOffset - (index-j)*8 498d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = [] 499d299b55d14fa77411140c0cc1c2524583b4ffa58jvr break 500d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeader.idRangeOffset == 0: # didn't find one. 501d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = idRangeOffset 502d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = (idRangeOffset - 8) + subHeader.entryCount*2 # one less subheader, one more subArray. 503d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 504d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = idRangeOffset - 8 # one less subheader 505d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 506bafa66e665afa581b58391585f1792578a4d3d2djvr # Now we can write out the data! 507bafa66e665afa581b58391585f1792578a4d3d2djvr length = 6 + 512 + 8*len(subHeaderList) # header, 256 subHeaderKeys, and subheader array. 508bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 509d299b55d14fa77411140c0cc1c2524583b4ffa58jvr length = length + len(subhead.glyphIndexArray)*2 # We can't use subhead.entryCount, as some of the subhead may share subArrays. 510d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [struct.pack(">HHH", 2, length, self.language)] 511bafa66e665afa581b58391585f1792578a4d3d2djvr for index in subHeaderKeys: 512d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", index*8)) 513bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList: 514d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(subHeaderFormat, subhead.firstCode, subhead.entryCount, subhead.idDelta, subhead.idRangeOffset)) 515bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 516bafa66e665afa581b58391585f1792578a4d3d2djvr for gi in subhead.glyphIndexArray: 517d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", gi)) 51818316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 519bafa66e665afa581b58391585f1792578a4d3d2djvr assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length) 520bafa66e665afa581b58391585f1792578a4d3d2djvr return data 521d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 522d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 5233a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 5240cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 525d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 526d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 527d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 528d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 529bafa66e665afa581b58391585f1792578a4d3d2djvr for element in content: 530b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 531bafa66e665afa581b58391585f1792578a4d3d2djvr continue 532bafa66e665afa581b58391585f1792578a4d3d2djvr name, attrs, content = element 533180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 534bafa66e665afa581b58391585f1792578a4d3d2djvr continue 535d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 5367842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5377842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5387842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_format_4_format = ">7H" 5397842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5401f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF. 5411f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 reservedPad # This value should be zero 5421f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 startCode[segCount] # Starting character code for each segment 5431f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idDelta[segCount] # Delta for all character codes in segment 5441f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0 5451f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 glyphIndexArray[variable] # Glyph index array 5467842e56b97ce677b83bdab09cda48bc2d89ac75aJust 547542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvrdef splitRange(startCode, endCode, cmap): 5481f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Try to split a range of character codes into subranges with consecutive 5491f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # glyph IDs in such a way that the cmap4 subtable can be stored "most" 5501f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # efficiently. I can't prove I've got the optimal solution, but it seems 5511f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # to do well with the fonts I tested: none became bigger, many became smaller. 552542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if startCode == endCode: 553542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 554542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 555542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = cmap[startCode] 556542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = startCode 557542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = None 558542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 5591f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = [] 560542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5611f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Gather subranges in which the glyph IDs are consecutive. 562542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for code in range(startCode + 1, endCode + 1): 563542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphID = cmap[code] 564542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 565542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if glyphID - 1 == lastID: 566542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder is None or not inOrder: 567542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 1 568542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = lastCode 569542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 570542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 571542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 0 5721f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 573542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 574542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 575542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = glyphID 576542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = code 577542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 578542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 5791f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 580542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert lastCode == endCode 581542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5821f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Now filter out those new subranges that would only make the data bigger. 5831f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # A new segment cost 8 bytes, not using a new segment costs 2 bytes per 5841f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # character. 5851f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges = [] 5861f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 587542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode and e == endCode: 588542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr break # the whole range, we're fine 589542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode or e == endCode: 590542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 4 # split costs one more segment 591542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 592542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 8 # split costs two more segments 593542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if (e - b + 1) > threshold: 5941f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges.append((b, e)) 5951f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = newRanges 596542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5971f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if not subRanges: 598542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 599542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6001f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[0][0] != startCode: 6011f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(0, (startCode, subRanges[0][0] - 1)) 6021f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[-1][1] != endCode: 6031f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((subRanges[-1][1] + 1, endCode)) 6041f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr 6051f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Fill the "holes" in the segments list -- those are the segments in which 6061f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # the glyph IDs are _not_ consecutive. 607542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = 1 6081f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr while i < len(subRanges): 6091f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[i-1][1] + 1 != subRanges[i][0]: 6101f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(i, (subRanges[i-1][1] + 1, subRanges[i][0] - 1)) 611542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 612542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 613542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6141f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Transform the ranges into startCode/endCode lists. 615542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start = [] 616542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end = [] 6171f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 618542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.append(b) 619542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end.append(e) 620542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.pop(0) 621542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 622542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert len(start) + 1 == len(end) 623542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return start, end 624542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 625542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6267842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_4(CmapSubtable): 6277842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6287842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 629d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 630d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 631d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 632d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(self.data[offset:offset+int(length)], ttFont) 633d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 63417012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 635d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 636d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 637d299b55d14fa77411140c0cc1c2524583b4ffa58jvr (segCountX2, searchRange, entrySelector, rangeShift) = \ 638d299b55d14fa77411140c0cc1c2524583b4ffa58jvr struct.unpack(">4H", data[:8]) 639d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[8:] 64032c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod segCount = segCountX2 // 2 6417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 642542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = array.array("H") 643d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allCodes.fromstring(data) 644d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 645d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 646180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 647542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes.byteswap() 6487842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6497842e56b97ce677b83bdab09cda48bc2d89ac75aJust # divide the data 650542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr endCode = allCodes[:segCount] 651542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount+1:] # the +1 is skipping the reservedPad field 652542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr startCode = allCodes[:segCount] 653542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 654542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idDelta = allCodes[:segCount] 655542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 656542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idRangeOffset = allCodes[:segCount] 657542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray = allCodes[segCount:] 658d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGIArray = len(glyphIndexArray) 659d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6607842e56b97ce677b83bdab09cda48bc2d89ac75aJust # build 2-byte character mapping 661d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 662d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 6637842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(startCode) - 1): # don't do 0xffff! 66497dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod rangeCharCodes = list(range(startCode[i], endCode[i] + 1)) 665d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = charCodes + rangeCharCodes 666d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in rangeCharCodes: 6677842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeOffset = idRangeOffset[i] 6687842e56b97ce677b83bdab09cda48bc2d89ac75aJust if rangeOffset == 0: 6697842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = charCode + idDelta[i] 6707842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6717842e56b97ce677b83bdab09cda48bc2d89ac75aJust # *someone* needs to get killed. 67232c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod index = idRangeOffset[i] // 2 + (charCode - startCode[i]) + i - len(idRangeOffset) 673d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert (index < lenGIArray), "In format 4 cmap, range (%d), the calculated index (%d) into the glyph index array is not less than the length of the array (%d) !" % (i, index, lenGIArray) 674180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if glyphIndexArray[index] != 0: # if not missing glyph 6757842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = glyphIndexArray[index] + idDelta[i] 6767842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6777842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = 0 # missing glyph 678d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(glyphID % 0x10000) 679d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 680d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 681d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 682d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 683d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 684e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 685d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 686d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 687e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 688e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 689d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 690d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 691d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 692d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, idDelta): 693d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 694d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K 695d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # startCode can be between 0 and 64K-1, and the first glyph index can be between 1 and 64K-1 696d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem because we can need to assign to idDelta values 697d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # between -(64K-2) and 64K -1. 698d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 699d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 700d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a startCode of 0 to a final GID of 64 -1K by subtracting 1, and casting the 701d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 702d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Similarly , we can get from a startCode of 64K-1 to a final GID of 1 by adding 2, because of 703d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # the modulo arithmetic. 704d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 705d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if idDelta > 0x7FFF: 706d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta - 0x10000 707d299b55d14fa77411140c0cc1c2524583b4ffa58jvr elif idDelta < -0x7FFF: 708d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta + 0x10000 709d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 710d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return idDelta 711d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 712d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 7137842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 714d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 715d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 716d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 717ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr from fontTools.ttLib.sfnt import maxPowerOfTwo 7187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 719c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 720d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 721d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lenCharCodes == 0: 722d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [0xffff] 723d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [0xffff] 724d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 7252db352c748a933d85264deb102036137f06b840fjvr charCodes.sort() 726e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) 727d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 728d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 729e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 730d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 731d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap(rebuild=1) 732d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 733e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 734d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 735d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 4 tables 736d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 737d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 738d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 739d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 740d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 741d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 742d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 743d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 744d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 745d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 746d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 747d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 748d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 749d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 750d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = {} # code:glyphID mapping 751e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 7527842e56b97ce677b83bdab09cda48bc2d89ac75aJust 753d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Build startCode and endCode lists. 754d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Split the char codes in ranges of consecutive char codes, then split 755d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # each range in more ranges of consecutive/not consecutive glyph IDs. 756d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # See splitRange(). 757d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCodes[0] 758d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [] 759d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [lastCode] 760d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in charCodes[1:]: # skip the first code, it's the first start code 761d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode == lastCode + 1: 762d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCode 763d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 764d299b55d14fa77411140c0cc1c2524583b4ffa58jvr start, end = splitRange(startCode[-1], lastCode, cmap) 765d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.extend(start) 766d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.extend(end) 767d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(charCode) 768542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = charCode 769d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(lastCode) 770d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(0xffff) 771d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(0xffff) 7727842e56b97ce677b83bdab09cda48bc2d89ac75aJust 773542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr # build up rest of cruft 7747842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta = [] 7757842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset = [] 7767842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = [] 7777842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(endCode)-1): # skip the closing codes (0xffff) 7787842e56b97ce677b83bdab09cda48bc2d89ac75aJust indices = [] 779542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for charCode in range(startCode[i], endCode[i] + 1): 780542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr indices.append(cmap[charCode]) 78197dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod if (indices == list(range(indices[0], indices[0] + len(indices)))): 782d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDeltaTemp = self.setIDDelta(indices[0] - startCode[i]) 783d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta.append( idDeltaTemp) 7847842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7857842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 7867842e56b97ce677b83bdab09cda48bc2d89ac75aJust # someone *definitely* needs to get killed. 7877842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(0) 7887842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i)) 789542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray.extend(indices) 7907842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef 7917842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7927842e56b97ce677b83bdab09cda48bc2d89ac75aJust 7937842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Insane. 7947842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCount = len(endCode) 7957842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2 = segCount * 2 796542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr maxExponent = maxPowerOfTwo(segCount) 797542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr searchRange = 2 * (2 ** maxExponent) 798542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr entrySelector = maxExponent 7997842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeShift = 2 * segCount - searchRange 8007842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8018da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray = array.array("H", endCode + [0] + startCode) 8028da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray = array.array("h", idDelta) 8038da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray = array.array("H", idRangeOffset + glyphIndexArray) 804180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8058da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray.byteswap() 8068da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray.byteswap() 8078da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray.byteswap() 808d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = charCodeArray.tostring() + idDeltaeArray.tostring() + restArray.tostring() 809d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8107842e56b97ce677b83bdab09cda48bc2d89ac75aJust length = struct.calcsize(cmap_format_4_format) + len(data) 8110cd79a5642101821d66392e3bd0e3c445e97f09bjvr header = struct.pack(cmap_format_4_format, self.format, length, self.language, 8127842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2, searchRange, entrySelector, rangeShift) 813d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return header + data 8147842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8153a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8160cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 817d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 818d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 819d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 820d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8217842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 822b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8237842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 824d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap, attrsMap, dummyContent = element 825180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if nameMap != "map": 826d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 0, "Unrecognized keyword in cmap subtable" 827d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrsMap["code"])] = attrsMap["name"] 8287842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8297842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8307842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_6(CmapSubtable): 8317842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8327842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 833d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 834d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 835d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 836d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 837d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 83817012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 839d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 840d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 841d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode, entryCount = struct.unpack(">HH", data[:4]) 8427842e56b97ce677b83bdab09cda48bc2d89ac75aJust firstCode = int(firstCode) 843d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[4:] 844f6b1563e0dc4e396264d62598cac856b0959c0f7Just #assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!! 8457842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = array.array("H") 84643fa4be9483ec7cfc2f3c183be8bed746862b7f3Just glyphIndexArray.fromstring(data[:2 * int(entryCount)]) 847180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8487842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray.byteswap() 849d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 850d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8517842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 852d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 853d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIndexArray) 85497dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(firstCode, firstCode + lenArray)) 855d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 856d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 857e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenArray, glyphIndexArray )) 858d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 859d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 860e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, glyphIndexArray )) 861e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 8627842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8637842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 864d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 865d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 866d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 867c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod codes = list(cmap.keys()) 868d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if codes: # yes, there are empty cmap tables. 86997dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod codes = list(range(codes[0], codes[-1] + 1)) 870d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = codes[0] 87113a08d0c3a59402459875155b7dbd194787fb229Behdad Esfahbod valueList = [cmap.get(code, ".notdef") for code in codes] 872d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 8738da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray = array.array("H", valueList) 874180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8758da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray.byteswap() 876d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = glyphIndexArray.tostring() 877d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 878d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = "" 879d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = 0 8807842e56b97ce677b83bdab09cda48bc2d89ac75aJust header = struct.pack(">HHHHH", 881d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6, len(data) + 10, self.language, firstCode, len(codes)) 8827842e56b97ce677b83bdab09cda48bc2d89ac75aJust return header + data 8837842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8843a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8850cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 886d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 887d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 888d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 889d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8907842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 891b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8927842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 8937842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 894180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 8957842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 896d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 8977842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8987842e56b97ce677b83bdab09cda48bc2d89ac75aJust 89951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12_or_13(CmapSubtable): 900924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 901d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __init__(self, format): 902d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = format 903d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = 0 904d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 905d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 906d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 907d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 908924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16]) 90951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader assert len(data) == (16 + nGroups*12) == (length), "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 910924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.format = format 911924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.reserved = reserved 912924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.length = length 913924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = language 914924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.nGroups = nGroups 915d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[16:] 916d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 917d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 918d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 919d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 920d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 921d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 922d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 923d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 92417012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 925d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 926d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 927d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 928d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 929d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 930d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(self.nGroups): 931d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] ) 932d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 12 933d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGroup = 1 + endCharCode - startCharCode 93497dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes += list(range(startCharCode, endCharCode +1)) 93551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader gids += self._computeGIDs(glyphID, lenGroup) 936d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 937d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 938d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 939d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 940d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 941e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 942d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 943d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 944e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 945e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 946924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 947924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def compile(self, ttFont): 948d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 94951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data 950c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 951d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 952c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod names = list(self.cmap.values()) 953d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 954d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 955e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 956d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 957d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap(rebuild=1) 958d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 959e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 960d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 961d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 12 tables 962d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 963d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 964d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 965d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 966d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 967d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 968d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 969d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 970d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 971d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 972d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 973d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 974d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 975d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 976d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 977924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr cmap = {} # code:glyphID mapping 978e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 979924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 980924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr charCodes.sort() 981d299b55d14fa77411140c0cc1c2524583b4ffa58jvr index = 0 982924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCodes[0] 983924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startGlyphID = cmap[startCharCode] 98451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader lastGlyphID = startGlyphID - self._format_step 985d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = startCharCode - 1 9860cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = 0 987d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [] 988d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxIndex = len(charCodes) 989d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(maxIndex): 990d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[index] 991924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr glyphID = cmap[charCode] 99251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode): 993d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 994924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCode 995d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startGlyphID = glyphID 996924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr nGroups = nGroups + 1 997d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastGlyphID = glyphID 998d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = charCode 999d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 10000cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = nGroups + 1 100118316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 1002d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lengthSubtable = len(data) +16 1003d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == (nGroups*12) == (lengthSubtable-16) 1004d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data 1005924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1006924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def toXML(self, writer, ttFont): 1007924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.begintag(self.__class__.__name__, [ 1008924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platformID", self.platformID), 1009924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platEncID", self.platEncID), 1010924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("format", self.format), 1011924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("reserved", self.reserved), 1012924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("length", self.length), 1013924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("language", self.language), 1014924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("nGroups", self.nGroups), 1015924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ]) 1016924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1017ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 1018a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1019924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.endtag(self.__class__.__name__) 1020924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1021924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 10223a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1023d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = safeEval(attrs["format"]) 1024d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = safeEval(attrs["reserved"]) 1025d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = safeEval(attrs["length"]) 1026924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = safeEval(attrs["language"]) 1027d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.nGroups = safeEval(attrs["nGroups"]) 1028d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 1029d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1030d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 1031d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1032924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr for element in content: 1033b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 1034924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1035924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr name, attrs, content = element 1036180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 1037924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1038d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 1039924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1040924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 104151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12(cmap_format_12_or_13): 104251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 104351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 104451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 1 104551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 104797dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod return list(range(startingGlyph, startingGlyph + numberOfGlyphs)) 104851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 105051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode) 105151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_13(cmap_format_12_or_13): 105451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 105551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 105651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 0 105751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 105951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return [startingGlyph] * numberOfGlyphs 106051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 106251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode) 106351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 10650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtToUVS(threeByteString): 1066180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 10670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = "\0" +threeByteString 10680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = threeByteString + "\0" 10700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr val, = struct.unpack(">L", data) 10710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return val 10720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtFromUVS(val): 1074180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 10750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr threeByteString = struct.pack(">L", val)[1:] 10760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr threeByteString = struct.pack(">L", val)[:3] 10780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return threeByteString 10790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 1080b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod 10810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrclass cmap_format_14(CmapSubtable): 10820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompileHeader(self, data, ttFont): 10840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10]) 10850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = data[10:] 10860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = length 10870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = numVarSelectorRecords 10880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.ttFont = ttFont 10890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.language = 0xFF # has no language. 10900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompile(self, data, ttFont): 10927ab0cb0b09f67f69990c47944100010435cc3b6aBehdad Esfahbod if data != None and ttFont != None and ttFont.lazy: 10930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.decompileHeader(data, ttFont) 10940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 109517012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 10960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = self.data 10970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 10990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = {} 11000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset = 0 11010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for n in range(self.numVarSelectorRecords): 11020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(">3sLL", data[recOffset:recOffset +11]) 11030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset += 11 11040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varUVS = cvtToUVS(uvs) 11050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defOVSOffset: 11060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = defOVSOffset - 10 11070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numValues, = struct.unpack(">L", data[startOffset:startOffset+4]) 11080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numValues): 11100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, addtlCnt = struct.unpack(">3sB", data[startOffset:startOffset+4]) 11110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 4 11120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr firstBaseUV = cvtToUVS(uv) 11130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = addtlCnt+1 111497dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod baseUVList = list(range(firstBaseUV, firstBaseUV+cnt)) 11150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphList = [None]*cnt 11160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = zip(baseUVList, glyphList) 11170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 1120fa5f2e85ab49c349468f5ae08f15163daa256a04Behdad Esfahbod uvsDict[varUVS] = list(localUVList) 11210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if nonDefUVSOffset: 11230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = nonDefUVSOffset - 10 11240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numRecs, = struct.unpack(">L", data[startOffset:startOffset+4]) 11250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = [] 11270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numRecs): 11280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, gid = struct.unpack(">3sH", data[startOffset:startOffset+5]) 11290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 5 11300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = cvtToUVS(uv) 11310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphName = self.ttFont.getGlyphName(gid) 11320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList.append( [uv, glyphName] ) 11330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS] = localUVList 11370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = uvsDict 11390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def toXML(self, writer, ttFont): 11410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.begintag(self.__class__.__name__, [ 11420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platformID", self.platformID), 11430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platEncID", self.platEncID), 11440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("format", self.format), 11450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("length", self.length), 11460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("numVarSelectorRecords", self.numVarSelectorRecords), 11470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ]) 11480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1150ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvList = uvsDict[uvs] 1153b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod uvList.sort(key=lambda item: (item[1] != None, item[0], item[1])) 11540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in uvList: 11550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == None: 11560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = "None" 11570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr # I use the arg rather than th keyword syntax in order to preserve the attribute order. 11580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.simpletag("map", [ ("uvs",hex(uvs)), ("uv",hex(uv)), ("name", gname)] ) 11590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11600cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.endtag(self.__class__.__name__) 11610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11633a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 11640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.format = safeEval(attrs["format"]) 11650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = safeEval(attrs["length"]) 11660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = safeEval(attrs["numVarSelectorRecords"]) 1167b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.language = 0xFF # provide a value so that CmapSubtable.__lt__() won't fail 11680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "cmap"): 11690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "uvsDict"): 11710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = {} 11720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 11730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for element in content: 1175b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 11760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr name, attrs, content = element 1178180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 11790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs = safeEval(attrs["uvs"]) 11810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = safeEval(attrs["uv"]) 11820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = attrs["name"] 11830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == "None": 11840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = None 11850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs].append( [uv, gname]) 11870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs] = [ [uv, gname] ] 11890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def compile(self, ttFont): 11920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if self.data: 11930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) + self.data 11940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1196ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = len(uvsList) 11980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset = 10 + self.numVarSelectorRecords*11 # current value is end of VarSelectorRecords block. 11990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = [] 12000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords =[] 12010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 12020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr entryList = uvsDict[uvs] 12030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 120428aeabb08b2656cb240063865c37f192532badf5Behdad Esfahbod defList = [entry for entry in entryList if entry[1] == None] 12050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defList: 1206e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod defList = [entry[0] for entry in defList] 12070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = offset 12080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defList.sort() 12090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defList[0] 12110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = -1 12120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs = [] 12130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for defEntry in defList: 12140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt +=1 12150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if (lastUV+cnt) != defEntry: 12160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt-1) 12170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defEntry 12180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = 0 12200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt) 12220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numDefRecs = len(defRecs) 12250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numDefRecs)) 12260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.extend(defRecs) 12270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numDefRecs*4 12280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = 0 12300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 123128aeabb08b2656cb240063865c37f192532badf5Behdad Esfahbod ndefList = [entry for entry in entryList if entry[1] != None] 12320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if ndefList: 12330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = offset 12340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndefList.sort() 12350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numNonDefRecs = len(ndefList) 12360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numNonDefRecs)) 12370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numNonDefRecs*5 12380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in ndefList: 12400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gid = ttFont.getGlyphID(gname) 12410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid) 12420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(ndrec) 12430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = 0 12450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset) 12470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords.append(vrec) 12480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 124918316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(varSelectorRecords) + bytesjoin(data) 12500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = 10 + len(data) 12510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) 12520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = headerdata + data 12530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return self.data 12550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12577842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_unknown(CmapSubtable): 12587842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1259a84b28d934fb697755823c62799f4b65e2b92237jvr def toXML(self, writer, ttFont): 1260d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmapName = self.__class__.__name__[:12] + str(self.format) 1261d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.begintag(cmapName, [ 1262a84b28d934fb697755823c62799f4b65e2b92237jvr ("platformID", self.platformID), 1263a84b28d934fb697755823c62799f4b65e2b92237jvr ("platEncID", self.platEncID), 1264a84b28d934fb697755823c62799f4b65e2b92237jvr ]) 1265a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1266d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.dumphex(self.data) 1267d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.endtag(cmapName) 1268a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1269a84b28d934fb697755823c62799f4b65e2b92237jvr 12703a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1271d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = readHex(content) 1272d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1273d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1274d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 1275427f9802bccf942f51567170e72a71ac14443c71jvr self.language = 0 # dummy value 12767842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.data = data 12777842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1278d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 1279d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 1280d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 1281d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 1282d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 1283d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 128417012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 12857842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def compile(self, ttFont): 1287d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 1288d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return self.data 1289d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1290d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return None 12917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 12927842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_classes = { 12937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 0: cmap_format_0, 12947842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2: cmap_format_2, 12957842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4: cmap_format_4, 12967842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6: cmap_format_6, 1297924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 12: cmap_format_12, 129851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 13: cmap_format_13, 12990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 14: cmap_format_14, 13007842e56b97ce677b83bdab09cda48bc2d89ac75aJust } 1301