11ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import 230e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import * 330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.textTools import safeEval, readHex 462dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbodfrom fontTools.ttLib import getSearchRange 50f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbodfrom fontTools.unicode import Unicode 62b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbodfrom . import DefaultTable 730e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport sys 87842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport struct 97842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport array 10d299b55d14fa77411140c0cc1c2524583b4ffa58jvrimport operator 117842e56b97ce677b83bdab09cda48bc2d89ac75aJust 127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 137842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass table__c_m_a_p(DefaultTable.DefaultTable): 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust 157842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getcmap(self, platformID, platEncID): 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust for subtable in self.tables: 177842e56b97ce677b83bdab09cda48bc2d89ac75aJust if (subtable.platformID == platformID and 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust subtable.platEncID == platEncID): 197842e56b97ce677b83bdab09cda48bc2d89ac75aJust return subtable 207842e56b97ce677b83bdab09cda48bc2d89ac75aJust return None # not found 217842e56b97ce677b83bdab09cda48bc2d89ac75aJust 227842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 237842e56b97ce677b83bdab09cda48bc2d89ac75aJust tableVersion, numSubTables = struct.unpack(">HH", data[:4]) 247842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = int(tableVersion) 257842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = tables = [] 26d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets = {} 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(numSubTables): 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID, offset = struct.unpack( 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust ">HHl", data[4+i*8:4+(i+1)*8]) 307842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID = int(platformID), int(platEncID) 317842e56b97ce677b83bdab09cda48bc2d89ac75aJust format, length = struct.unpack(">HH", data[offset:offset+4]) 3251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if format in [8,10,12,13]: 33924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length = struct.unpack(">HHL", data[offset:offset+8]) 340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr elif format in [14]: 350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length = struct.unpack(">HL", data[offset:offset+6]) 360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 372db352c748a933d85264deb102036137f06b840fjvr if not length: 383ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad 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)) 392db352c748a933d85264deb102036137f06b840fjvr continue 40bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 447842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = platformID 457842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = platEncID 46d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that by default we decompile only the subtable header info; 47d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # any other data gets decompiled only when an attribute of the 48d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # subtable is referenced. 49d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.decompileHeader(data[offset:offset+int(length)], ttFont) 50bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if offset in seenOffsets: 51d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.cmap = tables[seenOffsets[offset]].cmap 52d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 53d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets[offset] = i 547842e56b97ce677b83bdab09cda48bc2d89ac75aJust tables.append(table) 557842e56b97ce677b83bdab09cda48bc2d89ac75aJust 567842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 57b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__() 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust numSubTables = len(self.tables) 597842e56b97ce677b83bdab09cda48bc2d89ac75aJust totalOffset = 4 + 8 * numSubTables 607842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = struct.pack(">HH", self.tableVersion, numSubTables) 61821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod tableData = b"" 62d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seen = {} # Some tables are the same object reference. Don't compile them twice. 63d299b55d14fa77411140c0cc1c2524583b4ffa58jvr done = {} # Some tables are different objects, but compile to the same data chunk 647842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 65d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 66d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] 67d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 68d299b55d14fa77411140c0cc1c2524583b4ffa58jvr chunk = table.compile(ttFont) 69bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if chunk in done: 70d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = done[chunk] 71d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 72d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(tableData) 73d299b55d14fa77411140c0cc1c2524583b4ffa58jvr tableData = tableData + chunk 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset) 757842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data + tableData 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 787842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.simpletag("tableVersion", version=self.tableVersion) 797842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 807842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 817842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.toXML(writer, ttFont) 827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 833a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 847842e56b97ce677b83bdab09cda48bc2d89ac75aJust if name == "tableVersion": 857842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = safeEval(attrs["version"]) 867842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 87180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name[:12] != "cmap_format_": 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 897842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not hasattr(self, "tables"): 907842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = [] 910cd79a5642101821d66392e3bd0e3c445e97f09bjvr format = safeEval(name[12:]) 92bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 937842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 947842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 957842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 967842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = safeEval(attrs["platformID"]) 977842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = safeEval(attrs["platEncID"]) 983a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod table.fromXML(name, attrs, content, ttFont) 997842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables.append(table) 1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust 102e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass CmapSubtable(object): 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __init__(self, format): 1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.format = format 106d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 107d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 108d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 109d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __getattr__(self, attr): 110d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow lazy decompilation of subtables. 111d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if attr[:2] == '__': # don't handle requests for member functions like '__lt__' 112cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 1139e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if self.data is None: 114cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 115d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompile(None, None) # use saved data. 116d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None # Once this table has been decompiled, make sure we don't 117d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # just return the original data. Also avoids recursion when 118d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # called with an attribute that the cmap subtable doesn't have. 119d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return getattr(self, attr) 1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 121d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 122d299b55d14fa77411140c0cc1c2524583b4ffa58jvr format, length, language = struct.unpack(">HHH", data[:6]) 123d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == length, "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 124d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = int(format) 125d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = int(length) 126d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.language = int(language) 127d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[6:] 128d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 129d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.begintag(self.__class__.__name__, [ 1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platformID", self.platformID), 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platEncID", self.platEncID), 134a84b28d934fb697755823c62799f4b65e2b92237jvr ("language", self.language), 1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust ]) 1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 137ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 138a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.endtag(self.__class__.__name__) 1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 141a84b28d934fb697755823c62799f4b65e2b92237jvr 1420f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod def isUnicode(self): 1430f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod return (self.platformID == 0 or 1440f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod (self.platformID == 3 and self.platEncID in [1, 10])) 1450f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod 1460f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod def isSymbol(self): 1470f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod return self.platformID == 3 and self.platEncID == 0 1480f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod 149a84b28d934fb697755823c62799f4b65e2b92237jvr def _writeCodes(self, codes, writer): 1500f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod isUnicode = self.isUnicode() 151a84b28d934fb697755823c62799f4b65e2b92237jvr for code, name in codes: 152a84b28d934fb697755823c62799f4b65e2b92237jvr writer.simpletag("map", code=hex(code), name=name) 153a84b28d934fb697755823c62799f4b65e2b92237jvr if isUnicode: 154a84b28d934fb697755823c62799f4b65e2b92237jvr writer.comment(Unicode[code]) 155a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust 157b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod def __lt__(self, other): 158b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod if not isinstance(other, CmapSubtable): 159273a90074ac209d67b5e2cb8ea510cd6c2b10272Behdad Esfahbod return NotImplemented 16096b321c8aea4dc64110d15a541c6f85152ae19cfBehdad Esfahbod 161b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod # implemented so that list.sort() sorts according to the spec. 1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust selfTuple = ( 16394118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platformID", None), 16494118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platEncID", None), 16594118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "language", None), 16694118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod self.__dict__) 1677842e56b97ce677b83bdab09cda48bc2d89ac75aJust otherTuple = ( 16894118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platformID", None), 16994118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platEncID", None), 17094118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "language", None), 17194118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod other.__dict__) 172b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod return selfTuple < otherTuple 1737842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1757842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_0(CmapSubtable): 1767842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1777842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 178d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 179d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 1809e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 181d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 182d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1839e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 184d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 185d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 262 == self.length, "Format 0 cmap subtable not 262 bytes" 1867842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIdArray = array.array("B") 187d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphIdArray.fromstring(self.data) 1887842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 189d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIdArray) 19097dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(lenArray)) 191d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = map(self.ttFont.getGlyphName, glyphIdArray) 192e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 193d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1947842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 196d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 197d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", 0, 262, self.language) + self.data 198d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 199ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod charCodeList = sorted(self.cmap.items()) 200d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [entry[0] for entry in charCodeList] 201d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = [entry[1] for entry in charCodeList] 20297dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod assert charCodes == list(range(256)) 203d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 204d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2058da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIdArray = array.array("B", valueList) 2060cd79a5642101821d66392e3bd0e3c445e97f09bjvr data = struct.pack(">HHH", 0, 262, self.language) + glyphIdArray.tostring() 2077842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert len(data) == 262 2087842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 2097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2103a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 2110cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 212d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 213d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 214d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 216b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 2187842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 219180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 2207842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 221d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 2227842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust 224bafa66e665afa581b58391585f1792578a4d3d2djvrsubHeaderFormat = ">HHhH" 225e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass SubHeader(object): 226bafa66e665afa581b58391585f1792578a4d3d2djvr def __init__(self): 227bafa66e665afa581b58391585f1792578a4d3d2djvr self.firstCode = None 228bafa66e665afa581b58391585f1792578a4d3d2djvr self.entryCount = None 229bafa66e665afa581b58391585f1792578a4d3d2djvr self.idDelta = None 230bafa66e665afa581b58391585f1792578a4d3d2djvr self.idRangeOffset = None 231bafa66e665afa581b58391585f1792578a4d3d2djvr self.glyphIndexArray = [] 232bafa66e665afa581b58391585f1792578a4d3d2djvr 2337842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_2(CmapSubtable): 2347842e56b97ce677b83bdab09cda48bc2d89ac75aJust 235d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, subHeader): 236d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 237d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # find the minGI which is not zero. 238d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = subHeader.glyphIndexArray[0] 239d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for gid in subHeader.glyphIndexArray: 240d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (gid != 0) and (gid < minGI): 241d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = gid 242d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 243d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K. 244d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We would like to pick an idDelta such that the first glyphArray GID is 1, 245d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # so that we are more likely to be able to combine glypharray GID subranges. 246d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem when minGI is > 32K 247d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 248d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 249d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the 250d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 251d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 252d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (minGI > 1): 253d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if minGI > 0x7FFF: 254d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = -(0x10000 - minGI) -1 255d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 256d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = minGI -1 257d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = subHeader.idDelta 258d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(subHeader.entryCount): 259d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = subHeader.glyphIndexArray[i] 260d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid > 0: 261d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray[i] = gid - idDelta 262d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2647842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 265d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 266d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 2679e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 268d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 269d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 2709e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 271d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 272d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 273bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys = [] 274bafa66e665afa581b58391585f1792578a4d3d2djvr maxSubHeaderindex = 0 275bafa66e665afa581b58391585f1792578a4d3d2djvr # get the key array, and determine the number of subHeaders. 276d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys = array.array("H") 277d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.fromstring(data[:512]) 278d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[512:] 279180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 280d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.byteswap() 28132c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod subHeaderKeys = [ key//8 for key in allKeys] 282d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxSubHeaderindex = max(subHeaderKeys) 2837842e56b97ce677b83bdab09cda48bc2d89ac75aJust 284bafa66e665afa581b58391585f1792578a4d3d2djvr #Load subHeaders 285bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 287bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(maxSubHeaderindex + 1): 288bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 289bafa66e665afa581b58391585f1792578a4d3d2djvr (subHeader.firstCode, subHeader.entryCount, subHeader.idDelta, \ 290d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset) = struct.unpack(subHeaderFormat, data[pos:pos + 8]) 291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 8 292d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giDataPos = pos + subHeader.idRangeOffset-2 293d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList = array.array("H") 294d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2]) 295180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 296d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.byteswap() 297d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = giList 298bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 299bafa66e665afa581b58391585f1792578a4d3d2djvr # How this gets processed. 300bafa66e665afa581b58391585f1792578a4d3d2djvr # Charcodes may be one or two bytes. 301bafa66e665afa581b58391585f1792578a4d3d2djvr # The first byte of a charcode is mapped through the subHeaderKeys, to select 302bafa66e665afa581b58391585f1792578a4d3d2djvr # a subHeader. For any subheader but 0, the next byte is then mapped through the 303bafa66e665afa581b58391585f1792578a4d3d2djvr # selected subheader. If subheader Index 0 is selected, then the byte itself is 304bafa66e665afa581b58391585f1792578a4d3d2djvr # mapped through the subheader, and there is no second byte. 305bafa66e665afa581b58391585f1792578a4d3d2djvr # Then assume that the subsequent byte is the first byte of the next charcode,and repeat. 306bafa66e665afa581b58391585f1792578a4d3d2djvr # 307bafa66e665afa581b58391585f1792578a4d3d2djvr # Each subheader references a range in the glyphIndexArray whose length is entryCount. 308bafa66e665afa581b58391585f1792578a4d3d2djvr # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray 309bafa66e665afa581b58391585f1792578a4d3d2djvr # referenced by another subheader. 310bafa66e665afa581b58391585f1792578a4d3d2djvr # The only subheader that will be referenced by more than one first-byte value is the subheader 311bafa66e665afa581b58391585f1792578a4d3d2djvr # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef: 312bafa66e665afa581b58391585f1792578a4d3d2djvr # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx} 313bafa66e665afa581b58391585f1792578a4d3d2djvr # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex. 314bafa66e665afa581b58391585f1792578a4d3d2djvr # A subheader specifies a subrange within (0...256) by the 315bafa66e665afa581b58391585f1792578a4d3d2djvr # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero 316bafa66e665afa581b58391585f1792578a4d3d2djvr # (e.g. glyph not in font). 317bafa66e665afa581b58391585f1792578a4d3d2djvr # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar). 318bafa66e665afa581b58391585f1792578a4d3d2djvr # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by 319bafa66e665afa581b58391585f1792578a4d3d2djvr # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the 320bafa66e665afa581b58391585f1792578a4d3d2djvr # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex. 321bafa66e665afa581b58391585f1792578a4d3d2djvr # Example for Logocut-Medium 322bafa66e665afa581b58391585f1792578a4d3d2djvr # first byte of charcode = 129; selects subheader 1. 323bafa66e665afa581b58391585f1792578a4d3d2djvr # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252} 324bafa66e665afa581b58391585f1792578a4d3d2djvr # second byte of charCode = 66 325bafa66e665afa581b58391585f1792578a4d3d2djvr # the index offset = 66-64 = 2. 326bafa66e665afa581b58391585f1792578a4d3d2djvr # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is: 327bafa66e665afa581b58391585f1792578a4d3d2djvr # [glyphIndexArray index], [subrange array index] = glyphIndex 328bafa66e665afa581b58391585f1792578a4d3d2djvr # [256], [0]=1 from charcode [129, 64] 329bafa66e665afa581b58391585f1792578a4d3d2djvr # [257], [1]=2 from charcode [129, 65] 330bafa66e665afa581b58391585f1792578a4d3d2djvr # [258], [2]=3 from charcode [129, 66] 331bafa66e665afa581b58391585f1792578a4d3d2djvr # [259], [3]=4 from charcode [129, 67] 332d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero, 333d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # add it to the glyphID to get the final glyphIndex 334bafa66e665afa581b58391585f1792578a4d3d2djvr # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew! 335bafa66e665afa581b58391585f1792578a4d3d2djvr 3365f6418d9e1fa15a89dcec29cdc433ba2c99732c3Behdad Esfahbod self.data = b"" 337d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 338d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 339bafa66e665afa581b58391585f1792578a4d3d2djvr for firstByte in range(256): 340bafa66e665afa581b58391585f1792578a4d3d2djvr subHeadindex = subHeaderKeys[firstByte] 341bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = subHeaderList[subHeadindex] 342bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeadindex == 0: 343bafa66e665afa581b58391585f1792578a4d3d2djvr if (firstByte < subHeader.firstCode) or (firstByte >= subHeader.firstCode + subHeader.entryCount): 344d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 345bafa66e665afa581b58391585f1792578a4d3d2djvr else: 346bafa66e665afa581b58391585f1792578a4d3d2djvr charCode = firstByte 347bafa66e665afa581b58391585f1792578a4d3d2djvr offsetIndex = firstByte - subHeader.firstCode 348bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 349bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 350d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 351d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 352d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 353d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 354bafa66e665afa581b58391585f1792578a4d3d2djvr else: 355bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeader.entryCount: 356d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodeOffset = firstByte * 256 + subHeader.firstCode 357bafa66e665afa581b58391585f1792578a4d3d2djvr for offsetIndex in range(subHeader.entryCount): 358d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodeOffset + offsetIndex 359bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 360bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 361d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 362d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 363d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 364d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 365d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not subHeader.entryCount, then all char codes with this first byte are 366d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the 367d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same as mapping it to .notdef. 368d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # cmap values are GID's. 369d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 370c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod gids = list(cmap.values()) 371c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(cmap.keys()) 372d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 373d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 374e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 375d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 376d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 377e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 378e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 379d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 380bafa66e665afa581b58391585f1792578a4d3d2djvr 3817842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 382d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 383d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 384bafa66e665afa581b58391585f1792578a4d3d2djvr kEmptyTwoCharCodeRange = -1 385d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 386d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 387ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.cmap.items()) 388d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [item[0] for item in items] 389d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = [item[1] for item in items] 390d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 391d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 392d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 393e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 394d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 395dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 396d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 397e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 398d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 399d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 2 tables 400d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 401d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 402d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 403d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 404d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 405d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 406d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 407d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 408d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 409d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 410d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 411d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 412d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 413d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 414d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 415d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Process the (char code to gid) item list in char code order. 416d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # By definition, all one byte char codes map to subheader 0. 417d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # 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, 418d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # which defines all char codes in its range to map to notdef) unless proven otherwise. 419d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that since the char code items are processed in char code order, all the char codes with the 420d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same first byte are in sequential order. 421bafa66e665afa581b58391585f1792578a4d3d2djvr 422d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys = [ kEmptyTwoCharCodeRange for x in range(256)] # list of indices into subHeaderList. 423bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 424d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 425d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up 426d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with a cmap where all the one byte char codes map to notdef, 427d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with the result that the subhead 0 would not get created just by processing the item list. 428d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[0] 429d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode > 255: 430d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = SubHeader() 431d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.firstCode = 0 432d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 0 433d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 434d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 435d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 436d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 437bafa66e665afa581b58391585f1792578a4d3d2djvr 438bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = -1 439d299b55d14fa77411140c0cc1c2524583b4ffa58jvr items = zip(charCodes, gids) 440d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode, gid in items: 441d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid == 0: 442d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 443bafa66e665afa581b58391585f1792578a4d3d2djvr firstbyte = charCode >> 8 444bafa66e665afa581b58391585f1792578a4d3d2djvr secondByte = charCode & 0x00FF 445d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 446d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if firstbyte != lastFirstByte: # Need to update the current subhead, and start a new one. 447bafa66e665afa581b58391585f1792578a4d3d2djvr if lastFirstByte > -1: 448d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of current subheader. 449d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 450d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 451d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero 452d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # for the indices matching the char codes. 453d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lastFirstByte == 0: 454d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subHeader.entryCount): 455d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = subHeader.firstCode + index 456d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[charCode] = 0 457d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 458bafa66e665afa581b58391585f1792578a4d3d2djvr assert (subHeader.entryCount == len(subHeader.glyphIndexArray)), "Error - subhead entry count does not match len of glyphID subrange." 459bafa66e665afa581b58391585f1792578a4d3d2djvr # init new subheader 460bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 461bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = secondByte 462d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 1 463d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 464d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 465d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[firstbyte] = len(subHeaderList) -1 466bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = firstbyte 467bafa66e665afa581b58391585f1792578a4d3d2djvr else: 468d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # need to fill in with notdefs all the code points between the last charCode and the current charCode. 469bafa66e665afa581b58391585f1792578a4d3d2djvr codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount) 470bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(codeDiff): 471d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(notdefGI) 472d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 473bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = subHeader.entryCount + codeDiff + 1 474d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 475d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of last subheader that we we added to the subheader array. 476d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 477d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 478d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges. 479bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 480bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = 0 481bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = 0 482bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idDelta = 0 483bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idRangeOffset = 2 484bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 485bafa66e665afa581b58391585f1792578a4d3d2djvr emptySubheadIndex = len(subHeaderList) - 1 486bafa66e665afa581b58391585f1792578a4d3d2djvr for index in range(256): 487d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeaderKeys[index] == kEmptyTwoCharCodeRange: 488bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys[index] = emptySubheadIndex 489bafa66e665afa581b58391585f1792578a4d3d2djvr # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the 490d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray, 491bafa66e665afa581b58391585f1792578a4d3d2djvr # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with 492bafa66e665afa581b58391585f1792578a4d3d2djvr # charcode 0 and GID 0. 493bafa66e665afa581b58391585f1792578a4d3d2djvr 494bafa66e665afa581b58391585f1792578a4d3d2djvr idRangeOffset = (len(subHeaderList)-1)*8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset. 495d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subheadRangeLen = len(subHeaderList) -1 # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2. 496d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subheadRangeLen): 497d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = subHeaderList[index] 498d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 499d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for j in range(index): 500d299b55d14fa77411140c0cc1c2524583b4ffa58jvr prevSubhead = subHeaderList[j] 501d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if prevSubhead.glyphIndexArray == subHeader.glyphIndexArray: # use the glyphIndexArray subarray 502d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = prevSubhead.idRangeOffset - (index-j)*8 503d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = [] 504d299b55d14fa77411140c0cc1c2524583b4ffa58jvr break 505d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeader.idRangeOffset == 0: # didn't find one. 506d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = idRangeOffset 507d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = (idRangeOffset - 8) + subHeader.entryCount*2 # one less subheader, one more subArray. 508d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 509d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = idRangeOffset - 8 # one less subheader 510d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 511bafa66e665afa581b58391585f1792578a4d3d2djvr # Now we can write out the data! 512bafa66e665afa581b58391585f1792578a4d3d2djvr length = 6 + 512 + 8*len(subHeaderList) # header, 256 subHeaderKeys, and subheader array. 513bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 514d299b55d14fa77411140c0cc1c2524583b4ffa58jvr length = length + len(subhead.glyphIndexArray)*2 # We can't use subhead.entryCount, as some of the subhead may share subArrays. 515d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [struct.pack(">HHH", 2, length, self.language)] 516bafa66e665afa581b58391585f1792578a4d3d2djvr for index in subHeaderKeys: 517d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", index*8)) 518bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList: 519d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(subHeaderFormat, subhead.firstCode, subhead.entryCount, subhead.idDelta, subhead.idRangeOffset)) 520bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 521bafa66e665afa581b58391585f1792578a4d3d2djvr for gi in subhead.glyphIndexArray: 522d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", gi)) 52318316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 524bafa66e665afa581b58391585f1792578a4d3d2djvr assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length) 525bafa66e665afa581b58391585f1792578a4d3d2djvr return data 526d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 527d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 5283a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 5290cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 530d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 531d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 532d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 533d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 534bafa66e665afa581b58391585f1792578a4d3d2djvr for element in content: 535b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 536bafa66e665afa581b58391585f1792578a4d3d2djvr continue 537bafa66e665afa581b58391585f1792578a4d3d2djvr name, attrs, content = element 538180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 539bafa66e665afa581b58391585f1792578a4d3d2djvr continue 540d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 5417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5427842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5437842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_format_4_format = ">7H" 5447842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5451f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF. 5461f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 reservedPad # This value should be zero 5471f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 startCode[segCount] # Starting character code for each segment 5481f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idDelta[segCount] # Delta for all character codes in segment 5491f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0 5501f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 glyphIndexArray[variable] # Glyph index array 5517842e56b97ce677b83bdab09cda48bc2d89ac75aJust 552542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvrdef splitRange(startCode, endCode, cmap): 5531f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Try to split a range of character codes into subranges with consecutive 5541f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # glyph IDs in such a way that the cmap4 subtable can be stored "most" 5551f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # efficiently. I can't prove I've got the optimal solution, but it seems 5561f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # to do well with the fonts I tested: none became bigger, many became smaller. 557542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if startCode == endCode: 558542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 559542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 560542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = cmap[startCode] 561542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = startCode 562542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = None 563542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 5641f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = [] 565542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5661f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Gather subranges in which the glyph IDs are consecutive. 567542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for code in range(startCode + 1, endCode + 1): 568542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphID = cmap[code] 569542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 570542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if glyphID - 1 == lastID: 571542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder is None or not inOrder: 572542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 1 573542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = lastCode 574542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 575542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 576542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 0 5771f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 578542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 579542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 580542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = glyphID 581542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = code 582542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 583542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 5841f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 585542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert lastCode == endCode 586542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5871f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Now filter out those new subranges that would only make the data bigger. 5881f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # A new segment cost 8 bytes, not using a new segment costs 2 bytes per 5891f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # character. 5901f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges = [] 5911f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 592542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode and e == endCode: 593542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr break # the whole range, we're fine 594542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode or e == endCode: 595542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 4 # split costs one more segment 596542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 597542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 8 # split costs two more segments 598542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if (e - b + 1) > threshold: 5991f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges.append((b, e)) 6001f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = newRanges 601542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6021f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if not subRanges: 603542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 604542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6051f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[0][0] != startCode: 6061f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(0, (startCode, subRanges[0][0] - 1)) 6071f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[-1][1] != endCode: 6081f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((subRanges[-1][1] + 1, endCode)) 6091f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr 6101f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Fill the "holes" in the segments list -- those are the segments in which 6111f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # the glyph IDs are _not_ consecutive. 612542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = 1 6131f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr while i < len(subRanges): 6141f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[i-1][1] + 1 != subRanges[i][0]: 6151f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(i, (subRanges[i-1][1] + 1, subRanges[i][0] - 1)) 616542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 617542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 618542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6191f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Transform the ranges into startCode/endCode lists. 620542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start = [] 621542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end = [] 6221f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 623542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.append(b) 624542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end.append(e) 625542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.pop(0) 626542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 627542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert len(start) + 1 == len(end) 628542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return start, end 629542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 630542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6317842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_4(CmapSubtable): 6327842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6337842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 634d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 635d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 6369e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 637d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(self.data[offset:offset+int(length)], ttFont) 638d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 6399e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 640d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 641d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 642d299b55d14fa77411140c0cc1c2524583b4ffa58jvr (segCountX2, searchRange, entrySelector, rangeShift) = \ 643d299b55d14fa77411140c0cc1c2524583b4ffa58jvr struct.unpack(">4H", data[:8]) 644d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[8:] 64532c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod segCount = segCountX2 // 2 6467842e56b97ce677b83bdab09cda48bc2d89ac75aJust 647542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = array.array("H") 648d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allCodes.fromstring(data) 649d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 650d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 651180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 652542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes.byteswap() 6537842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6547842e56b97ce677b83bdab09cda48bc2d89ac75aJust # divide the data 655542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr endCode = allCodes[:segCount] 656542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount+1:] # the +1 is skipping the reservedPad field 657542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr startCode = allCodes[:segCount] 658542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 659542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idDelta = allCodes[:segCount] 660542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 661542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idRangeOffset = allCodes[:segCount] 662542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray = allCodes[segCount:] 663d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGIArray = len(glyphIndexArray) 664d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6657842e56b97ce677b83bdab09cda48bc2d89ac75aJust # build 2-byte character mapping 666d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 667d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 6687842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(startCode) - 1): # don't do 0xffff! 6692db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod start = startCode[i] 6702db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod delta = idDelta[i] 6712db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod rangeOffset = idRangeOffset[i] 6722db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod # *someone* needs to get killed. 6732db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod partial = rangeOffset // 2 - start + i - len(idRangeOffset) 6742db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod 67597dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod rangeCharCodes = list(range(startCode[i], endCode[i] + 1)) 6760d182bfb8078665313280db759b782c3144f65faBehdad Esfahbod charCodes.extend(rangeCharCodes) 677470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod if rangeOffset == 0: 678470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod gids.extend([(charCode + delta) & 0xFFFF for charCode in rangeCharCodes]) 679470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod else: 680470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod for charCode in rangeCharCodes: 6812db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod index = charCode + partial 682d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 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) 683180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if glyphIndexArray[index] != 0: # if not missing glyph 6842db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod glyphID = glyphIndexArray[index] + delta 6857842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6867842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = 0 # missing glyph 687470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod gids.append(glyphID & 0xFFFF) 688d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 689d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 690d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 691d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 692d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 693e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 694d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 695d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 696e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 697e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 698d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 699d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 7007842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 701d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 702d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 7037842e56b97ce677b83bdab09cda48bc2d89ac75aJust 704c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 705d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 706d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lenCharCodes == 0: 707d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [0xffff] 708d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [0xffff] 709d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 7102db352c748a933d85264deb102036137f06b840fjvr charCodes.sort() 711e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) 712d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 713d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 714e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 715d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 716dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 717d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 718e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 719d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 720d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 4 tables 721d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 722d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 723d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 724d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 725d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 726d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 727d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 728d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 729d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 730d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 731d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 732d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 733d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 734d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 735d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = {} # code:glyphID mapping 736e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 7377842e56b97ce677b83bdab09cda48bc2d89ac75aJust 738d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Build startCode and endCode lists. 739d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Split the char codes in ranges of consecutive char codes, then split 740d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # each range in more ranges of consecutive/not consecutive glyph IDs. 741d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # See splitRange(). 742d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCodes[0] 743d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [] 744d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [lastCode] 745d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in charCodes[1:]: # skip the first code, it's the first start code 746d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode == lastCode + 1: 747d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCode 748d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 749d299b55d14fa77411140c0cc1c2524583b4ffa58jvr start, end = splitRange(startCode[-1], lastCode, cmap) 750d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.extend(start) 751d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.extend(end) 752d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(charCode) 753542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = charCode 754d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(lastCode) 755d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(0xffff) 756d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(0xffff) 7577842e56b97ce677b83bdab09cda48bc2d89ac75aJust 758542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr # build up rest of cruft 7597842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta = [] 7607842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset = [] 7617842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = [] 7627842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(endCode)-1): # skip the closing codes (0xffff) 7637842e56b97ce677b83bdab09cda48bc2d89ac75aJust indices = [] 764542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for charCode in range(startCode[i], endCode[i] + 1): 765542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr indices.append(cmap[charCode]) 76697dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod if (indices == list(range(indices[0], indices[0] + len(indices)))): 767ec5f5150e2c9ba53696fbddae504a004398b367aBehdad Esfahbod idDelta.append((indices[0] - startCode[i]) % 0x10000) 7687842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7697842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 7707842e56b97ce677b83bdab09cda48bc2d89ac75aJust # someone *definitely* needs to get killed. 7717842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(0) 7727842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i)) 773542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray.extend(indices) 7747842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef 7757842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7767842e56b97ce677b83bdab09cda48bc2d89ac75aJust 77762dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod # Insane. 7787842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCount = len(endCode) 7797842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2 = segCount * 2 78062dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod searchRange, entrySelector, rangeShift = getSearchRange(segCount, 2) 7817842e56b97ce677b83bdab09cda48bc2d89ac75aJust 7828da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray = array.array("H", endCode + [0] + startCode) 783ec5f5150e2c9ba53696fbddae504a004398b367aBehdad Esfahbod idDeltaArray = array.array("H", idDelta) 7848da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray = array.array("H", idRangeOffset + glyphIndexArray) 785180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 7868da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray.byteswap() 7874c777d3465d16db968dc3f58a1f30444eed732a3Behdad Esfahbod idDeltaArray.byteswap() 7888da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray.byteswap() 7894c777d3465d16db968dc3f58a1f30444eed732a3Behdad Esfahbod data = charCodeArray.tostring() + idDeltaArray.tostring() + restArray.tostring() 790d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 7917842e56b97ce677b83bdab09cda48bc2d89ac75aJust length = struct.calcsize(cmap_format_4_format) + len(data) 7920cd79a5642101821d66392e3bd0e3c445e97f09bjvr header = struct.pack(cmap_format_4_format, self.format, length, self.language, 7937842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2, searchRange, entrySelector, rangeShift) 794d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return header + data 7957842e56b97ce677b83bdab09cda48bc2d89ac75aJust 7963a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 7970cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 798d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 799d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 800d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 801d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8027842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 803b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8047842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 805d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap, attrsMap, dummyContent = element 806180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if nameMap != "map": 807d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 0, "Unrecognized keyword in cmap subtable" 808d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrsMap["code"])] = attrsMap["name"] 8097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8117842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_6(CmapSubtable): 8127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8137842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 814d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 815d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 8169e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 817d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 818d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 8199e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 820d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 821d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 822d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode, entryCount = struct.unpack(">HH", data[:4]) 8237842e56b97ce677b83bdab09cda48bc2d89ac75aJust firstCode = int(firstCode) 824d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[4:] 825f6b1563e0dc4e396264d62598cac856b0959c0f7Just #assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!! 8267842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = array.array("H") 82743fa4be9483ec7cfc2f3c183be8bed746862b7f3Just glyphIndexArray.fromstring(data[:2 * int(entryCount)]) 828180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8297842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray.byteswap() 830d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 831d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8327842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 833d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 834d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIndexArray) 83597dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(firstCode, firstCode + lenArray)) 836d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 837d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 838e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenArray, glyphIndexArray )) 839d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 840d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 841e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, glyphIndexArray )) 842e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 8437842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8447842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 845d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 846d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 847d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 848c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod codes = list(cmap.keys()) 849d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if codes: # yes, there are empty cmap tables. 85097dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod codes = list(range(codes[0], codes[-1] + 1)) 851d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = codes[0] 85213a08d0c3a59402459875155b7dbd194787fb229Behdad Esfahbod valueList = [cmap.get(code, ".notdef") for code in codes] 853d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 8548da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray = array.array("H", valueList) 855180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8568da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray.byteswap() 857d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = glyphIndexArray.tostring() 858d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 8595f6418d9e1fa15a89dcec29cdc433ba2c99732c3Behdad Esfahbod data = b"" 860d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = 0 8617842e56b97ce677b83bdab09cda48bc2d89ac75aJust header = struct.pack(">HHHHH", 862d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6, len(data) + 10, self.language, firstCode, len(codes)) 8637842e56b97ce677b83bdab09cda48bc2d89ac75aJust return header + data 8647842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8653a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8660cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 867d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 868d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 869d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 870d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8717842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 872b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8737842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 8747842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 875180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 8767842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 877d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 8787842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8797842e56b97ce677b83bdab09cda48bc2d89ac75aJust 88051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12_or_13(CmapSubtable): 881924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 882d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __init__(self, format): 883d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = format 884d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = 0 885d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 886d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 887d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 888d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 889924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16]) 89051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader assert len(data) == (16 + nGroups*12) == (length), "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 891924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.format = format 892924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.reserved = reserved 893924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.length = length 894924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = language 895924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.nGroups = nGroups 896d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[16:] 897d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 898d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 899d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 900d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 901d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 9029e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 903d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 904d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 9059e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 906d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 907d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 908d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 909d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 910d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 911d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(self.nGroups): 912d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] ) 913d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 12 914d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGroup = 1 + endCharCode - startCharCode 915d56eebfa96bd07ebc3e6d076b62f96d97d569c99Behdad Esfahbod charCodes.extend(list(range(startCharCode, endCharCode +1))) 916d56eebfa96bd07ebc3e6d076b62f96d97d569c99Behdad Esfahbod gids.extend(self._computeGIDs(glyphID, lenGroup)) 917d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 918d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 919d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 920d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 921d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 922e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 923d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 924d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 925e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 926e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 927924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 928924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def compile(self, ttFont): 929d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 93051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data 931c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 932d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 933c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod names = list(self.cmap.values()) 934d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 935d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 936e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 937d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 938dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 939d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 940e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 941d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 942d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 12 tables 943d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 944d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 945d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 946d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 947d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 948d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 949d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 950d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 951d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 952d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 953d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 954d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 955d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 956d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 957d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 958924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr cmap = {} # code:glyphID mapping 959e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 960924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 961924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr charCodes.sort() 962d299b55d14fa77411140c0cc1c2524583b4ffa58jvr index = 0 963924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCodes[0] 964924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startGlyphID = cmap[startCharCode] 96551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader lastGlyphID = startGlyphID - self._format_step 966d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = startCharCode - 1 9670cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = 0 968d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [] 969d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxIndex = len(charCodes) 970d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(maxIndex): 971d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[index] 972924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr glyphID = cmap[charCode] 97351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode): 974d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 975924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCode 976d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startGlyphID = glyphID 977924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr nGroups = nGroups + 1 978d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastGlyphID = glyphID 979d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = charCode 980d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 9810cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = nGroups + 1 98218316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 983d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lengthSubtable = len(data) +16 984d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == (nGroups*12) == (lengthSubtable-16) 985d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data 986924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 987924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def toXML(self, writer, ttFont): 988924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.begintag(self.__class__.__name__, [ 989924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platformID", self.platformID), 990924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platEncID", self.platEncID), 991924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("format", self.format), 992924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("reserved", self.reserved), 993924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("length", self.length), 994924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("language", self.language), 995924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("nGroups", self.nGroups), 996924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ]) 997924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 998ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 999a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1000924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.endtag(self.__class__.__name__) 1001924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1002924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 10033a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1004d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = safeEval(attrs["format"]) 1005d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = safeEval(attrs["reserved"]) 1006d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = safeEval(attrs["length"]) 1007924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = safeEval(attrs["language"]) 1008d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.nGroups = safeEval(attrs["nGroups"]) 1009d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 1010d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1011d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 1012d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1013924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr for element in content: 1014b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 1015924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1016924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr name, attrs, content = element 1017180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 1018924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1019d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 1020924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1021924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 102251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12(cmap_format_12_or_13): 102351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 102451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 102551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 1 102651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 102751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 102897dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod return list(range(startingGlyph, startingGlyph + numberOfGlyphs)) 102951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 103051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 103151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode) 103251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 103351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 103451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_13(cmap_format_12_or_13): 103551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 103651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 103751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 0 103851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 103951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 104051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return [startingGlyph] * numberOfGlyphs 104151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 104351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode) 104451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 10460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtToUVS(threeByteString): 10472242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod data = b"\0" + threeByteString 10480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr val, = struct.unpack(">L", data) 10490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return val 10500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtFromUVS(val): 10522242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod assert 0 <= val < 0x1000000 10532242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod fourByteString = struct.pack(">L", val) 10542242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod return fourByteString[1:] 10550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 1056b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod 10570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrclass cmap_format_14(CmapSubtable): 10580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompileHeader(self, data, ttFont): 10600cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10]) 10610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = data[10:] 10620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = length 10630cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = numVarSelectorRecords 10640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.ttFont = ttFont 10650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.language = 0xFF # has no language. 10660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompile(self, data, ttFont): 10689e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None and ttFont.lazy: 10690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.decompileHeader(data, ttFont) 10700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10719e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 10720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = self.data 10730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 10750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = {} 10760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset = 0 10770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for n in range(self.numVarSelectorRecords): 10780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(">3sLL", data[recOffset:recOffset +11]) 10790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset += 11 10800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varUVS = cvtToUVS(uvs) 10810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defOVSOffset: 10820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = defOVSOffset - 10 10830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numValues, = struct.unpack(">L", data[startOffset:startOffset+4]) 10840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 10850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numValues): 10860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, addtlCnt = struct.unpack(">3sB", data[startOffset:startOffset+4]) 10870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 4 10880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr firstBaseUV = cvtToUVS(uv) 10890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = addtlCnt+1 109097dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod baseUVList = list(range(firstBaseUV, firstBaseUV+cnt)) 10910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphList = [None]*cnt 10920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = zip(baseUVList, glyphList) 10930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 10940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 10950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 1096fa5f2e85ab49c349468f5ae08f15163daa256a04Behdad Esfahbod uvsDict[varUVS] = list(localUVList) 10970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if nonDefUVSOffset: 10990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = nonDefUVSOffset - 10 11000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numRecs, = struct.unpack(">L", data[startOffset:startOffset+4]) 11010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = [] 11030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numRecs): 11040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, gid = struct.unpack(">3sH", data[startOffset:startOffset+5]) 11050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 5 11060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = cvtToUVS(uv) 11070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphName = self.ttFont.getGlyphName(gid) 11080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList.append( [uv, glyphName] ) 11090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS] = localUVList 11130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = uvsDict 11150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def toXML(self, writer, ttFont): 11170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.begintag(self.__class__.__name__, [ 11180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platformID", self.platformID), 11190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platEncID", self.platEncID), 11200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("format", self.format), 11210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("length", self.length), 11220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("numVarSelectorRecords", self.numVarSelectorRecords), 11230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ]) 11240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1126ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvList = uvsDict[uvs] 11299e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod uvList.sort(key=lambda item: (item[1] is not None, item[0], item[1])) 11300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in uvList: 11319e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if gname is None: 11320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = "None" 11330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr # I use the arg rather than th keyword syntax in order to preserve the attribute order. 11340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.simpletag("map", [ ("uvs",hex(uvs)), ("uv",hex(uv)), ("name", gname)] ) 11350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.endtag(self.__class__.__name__) 11370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11393a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 11400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.format = safeEval(attrs["format"]) 11410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = safeEval(attrs["length"]) 11420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = safeEval(attrs["numVarSelectorRecords"]) 1143b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.language = 0xFF # provide a value so that CmapSubtable.__lt__() won't fail 11440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "cmap"): 11450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "uvsDict"): 11470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = {} 11480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 11490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for element in content: 1151b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 11520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr name, attrs, content = element 1154180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 11550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs = safeEval(attrs["uvs"]) 11570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = safeEval(attrs["uv"]) 11580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = attrs["name"] 11590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == "None": 11600cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = None 11610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs].append( [uv, gname]) 11630cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs] = [ [uv, gname] ] 11650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def compile(self, ttFont): 11680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if self.data: 11690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) + self.data 11700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1172ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = len(uvsList) 11740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset = 10 + self.numVarSelectorRecords*11 # current value is end of VarSelectorRecords block. 11750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = [] 11760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords =[] 11770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr entryList = uvsDict[uvs] 11790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11809e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod defList = [entry for entry in entryList if entry[1] is None] 11810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defList: 1182e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod defList = [entry[0] for entry in defList] 11830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = offset 11840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defList.sort() 11850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defList[0] 11870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = -1 11880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs = [] 11890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for defEntry in defList: 11900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt +=1 11910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if (lastUV+cnt) != defEntry: 11920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt-1) 11930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defEntry 11940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 11950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = 0 11960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt) 11980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 11990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numDefRecs = len(defRecs) 12010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numDefRecs)) 12020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.extend(defRecs) 12030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numDefRecs*4 12040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = 0 12060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12079e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod ndefList = [entry for entry in entryList if entry[1] is not None] 12080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if ndefList: 12090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = offset 12100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndefList.sort() 12110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numNonDefRecs = len(ndefList) 12120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numNonDefRecs)) 12130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numNonDefRecs*5 12140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in ndefList: 12160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gid = ttFont.getGlyphID(gname) 12170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid) 12180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(ndrec) 12190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = 0 12210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset) 12230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords.append(vrec) 12240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 122518316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(varSelectorRecords) + bytesjoin(data) 12260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = 10 + len(data) 12270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) 12280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = headerdata + data 12290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return self.data 12310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12337842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_unknown(CmapSubtable): 12347842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1235a84b28d934fb697755823c62799f4b65e2b92237jvr def toXML(self, writer, ttFont): 1236d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmapName = self.__class__.__name__[:12] + str(self.format) 1237d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.begintag(cmapName, [ 1238a84b28d934fb697755823c62799f4b65e2b92237jvr ("platformID", self.platformID), 1239a84b28d934fb697755823c62799f4b65e2b92237jvr ("platEncID", self.platEncID), 1240a84b28d934fb697755823c62799f4b65e2b92237jvr ]) 1241a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1242d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.dumphex(self.data) 1243d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.endtag(cmapName) 1244a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1245a84b28d934fb697755823c62799f4b65e2b92237jvr 12463a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1247d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = readHex(content) 1248d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1249d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1250d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 1251427f9802bccf942f51567170e72a71ac14443c71jvr self.language = 0 # dummy value 12527842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.data = data 12537842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1254d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 1255d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 1256d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 12579e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 1258d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 1259d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 12609e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 12617842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1262d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def compile(self, ttFont): 1263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 1264d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return self.data 1265d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1266d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return None 12677842e56b97ce677b83bdab09cda48bc2d89ac75aJust 12687842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_classes = { 12697842e56b97ce677b83bdab09cda48bc2d89ac75aJust 0: cmap_format_0, 12707842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2: cmap_format_2, 12717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4: cmap_format_4, 12727842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6: cmap_format_6, 1273924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 12: cmap_format_12, 127451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 13: cmap_format_13, 12750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 14: cmap_format_14, 12767842e56b97ce677b83bdab09cda48bc2d89ac75aJust } 1277