_c_m_a_p.py revision 470d610eb2cba2629889b00575742921079bc1cb
11ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import 230e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import * 330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.textTools import safeEval, readHex 40f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbodfrom fontTools.unicode import Unicode 52b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbodfrom . import DefaultTable 630e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport sys 77842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport struct 87842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport array 9d299b55d14fa77411140c0cc1c2524583b4ffa58jvrimport operator 107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 117842e56b97ce677b83bdab09cda48bc2d89ac75aJust 127842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass table__c_m_a_p(DefaultTable.DefaultTable): 137842e56b97ce677b83bdab09cda48bc2d89ac75aJust 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getcmap(self, platformID, platEncID): 157842e56b97ce677b83bdab09cda48bc2d89ac75aJust for subtable in self.tables: 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust if (subtable.platformID == platformID and 177842e56b97ce677b83bdab09cda48bc2d89ac75aJust subtable.platEncID == platEncID): 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust return subtable 197842e56b97ce677b83bdab09cda48bc2d89ac75aJust return None # not found 207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 217842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 227842e56b97ce677b83bdab09cda48bc2d89ac75aJust tableVersion, numSubTables = struct.unpack(">HH", data[:4]) 237842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = int(tableVersion) 247842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = tables = [] 25d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets = {} 267842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(numSubTables): 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID, offset = struct.unpack( 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust ">HHl", data[4+i*8:4+(i+1)*8]) 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID = int(platformID), int(platEncID) 307842e56b97ce677b83bdab09cda48bc2d89ac75aJust format, length = struct.unpack(">HH", data[offset:offset+4]) 3151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if format in [8,10,12,13]: 32924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length = struct.unpack(">HHL", data[offset:offset+8]) 330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr elif format in [14]: 340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length = struct.unpack(">HL", data[offset:offset+6]) 350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 362db352c748a933d85264deb102036137f06b840fjvr if not length: 373ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad 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)) 382db352c748a933d85264deb102036137f06b840fjvr continue 39bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 407842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = platformID 447842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = platEncID 45d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that by default we decompile only the subtable header info; 46d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # any other data gets decompiled only when an attribute of the 47d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # subtable is referenced. 48d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.decompileHeader(data[offset:offset+int(length)], ttFont) 49bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if offset in seenOffsets: 50d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.cmap = tables[seenOffsets[offset]].cmap 51d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 52d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets[offset] = i 537842e56b97ce677b83bdab09cda48bc2d89ac75aJust tables.append(table) 547842e56b97ce677b83bdab09cda48bc2d89ac75aJust 557842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 56b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__() 577842e56b97ce677b83bdab09cda48bc2d89ac75aJust numSubTables = len(self.tables) 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust totalOffset = 4 + 8 * numSubTables 597842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = struct.pack(">HH", self.tableVersion, numSubTables) 60821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod tableData = b"" 61d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seen = {} # Some tables are the same object reference. Don't compile them twice. 62d299b55d14fa77411140c0cc1c2524583b4ffa58jvr done = {} # Some tables are different objects, but compile to the same data chunk 637842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 64d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 65d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] 66d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 67d299b55d14fa77411140c0cc1c2524583b4ffa58jvr chunk = table.compile(ttFont) 68bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if chunk in done: 69d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = done[chunk] 70d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 71d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(tableData) 72d299b55d14fa77411140c0cc1c2524583b4ffa58jvr tableData = tableData + chunk 737842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset) 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data + tableData 757842e56b97ce677b83bdab09cda48bc2d89ac75aJust 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.simpletag("tableVersion", version=self.tableVersion) 787842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 797842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 807842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.toXML(writer, ttFont) 817842e56b97ce677b83bdab09cda48bc2d89ac75aJust 823a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 837842e56b97ce677b83bdab09cda48bc2d89ac75aJust if name == "tableVersion": 847842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = safeEval(attrs["version"]) 857842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 86180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name[:12] != "cmap_format_": 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not hasattr(self, "tables"): 897842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = [] 900cd79a5642101821d66392e3bd0e3c445e97f09bjvr format = safeEval(name[12:]) 91bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 927842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 937842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 947842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 957842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = safeEval(attrs["platformID"]) 967842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = safeEval(attrs["platEncID"]) 973a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod table.fromXML(name, attrs, content, ttFont) 987842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables.append(table) 997842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust 101e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass CmapSubtable(object): 1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __init__(self, format): 1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.format = format 105d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 106d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 107d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 108d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __getattr__(self, attr): 109d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow lazy decompilation of subtables. 110d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if attr[:2] == '__': # don't handle requests for member functions like '__lt__' 111cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 1129e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if self.data is None: 113cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 114d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompile(None, None) # use saved data. 115d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None # Once this table has been decompiled, make sure we don't 116d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # just return the original data. Also avoids recursion when 117d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # called with an attribute that the cmap subtable doesn't have. 118d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return getattr(self, attr) 1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust 120d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 121d299b55d14fa77411140c0cc1c2524583b4ffa58jvr format, length, language = struct.unpack(">HHH", data[:6]) 122d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == length, "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 123d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = int(format) 124d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = int(length) 125d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.language = int(language) 126d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[6:] 127d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 128d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.begintag(self.__class__.__name__, [ 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platformID", self.platformID), 1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platEncID", self.platEncID), 133a84b28d934fb697755823c62799f4b65e2b92237jvr ("language", self.language), 1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust ]) 1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 136ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 137a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.endtag(self.__class__.__name__) 1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 140a84b28d934fb697755823c62799f4b65e2b92237jvr 1410f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod def isUnicode(self): 1420f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod return (self.platformID == 0 or 1430f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod (self.platformID == 3 and self.platEncID in [1, 10])) 1440f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod 1450f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod def isSymbol(self): 1460f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod return self.platformID == 3 and self.platEncID == 0 1470f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod 148a84b28d934fb697755823c62799f4b65e2b92237jvr def _writeCodes(self, codes, writer): 1490f74e80d59cf5426b3003ab955729c6d909860a6Behdad Esfahbod isUnicode = self.isUnicode() 150a84b28d934fb697755823c62799f4b65e2b92237jvr for code, name in codes: 151a84b28d934fb697755823c62799f4b65e2b92237jvr writer.simpletag("map", code=hex(code), name=name) 152a84b28d934fb697755823c62799f4b65e2b92237jvr if isUnicode: 153a84b28d934fb697755823c62799f4b65e2b92237jvr writer.comment(Unicode[code]) 154a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust 156b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod def __lt__(self, other): 157b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod if not isinstance(other, CmapSubtable): 158273a90074ac209d67b5e2cb8ea510cd6c2b10272Behdad Esfahbod return NotImplemented 15996b321c8aea4dc64110d15a541c6f85152ae19cfBehdad Esfahbod 160b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod # implemented so that list.sort() sorts according to the spec. 1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust selfTuple = ( 16294118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platformID", None), 16394118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platEncID", None), 16494118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "language", None), 16594118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod self.__dict__) 1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust otherTuple = ( 16794118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platformID", None), 16894118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platEncID", None), 16994118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "language", None), 17094118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod other.__dict__) 171b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod return selfTuple < otherTuple 1727842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1737842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1747842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_0(CmapSubtable): 1757842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1767842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 177d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 178d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 1799e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 180d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 181d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1829e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 183d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 184d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 262 == self.length, "Format 0 cmap subtable not 262 bytes" 1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIdArray = array.array("B") 186d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphIdArray.fromstring(self.data) 1877842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 188d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIdArray) 18997dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(lenArray)) 190d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = map(self.ttFont.getGlyphName, glyphIdArray) 191e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 192d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1947842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 195d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 196d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", 0, 262, self.language) + self.data 197d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 198ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod charCodeList = sorted(self.cmap.items()) 199d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [entry[0] for entry in charCodeList] 200d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = [entry[1] for entry in charCodeList] 20197dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod assert charCodes == list(range(256)) 202d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 203d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2048da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIdArray = array.array("B", valueList) 2050cd79a5642101821d66392e3bd0e3c445e97f09bjvr data = struct.pack(">HHH", 0, 262, self.language) + glyphIdArray.tostring() 2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert len(data) == 262 2077842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 2087842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2093a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 2100cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 211d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 212d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 213d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 2147842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 215b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 2167842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 218180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 2197842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 220d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 2217842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2227842e56b97ce677b83bdab09cda48bc2d89ac75aJust 223bafa66e665afa581b58391585f1792578a4d3d2djvrsubHeaderFormat = ">HHhH" 224e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass SubHeader(object): 225bafa66e665afa581b58391585f1792578a4d3d2djvr def __init__(self): 226bafa66e665afa581b58391585f1792578a4d3d2djvr self.firstCode = None 227bafa66e665afa581b58391585f1792578a4d3d2djvr self.entryCount = None 228bafa66e665afa581b58391585f1792578a4d3d2djvr self.idDelta = None 229bafa66e665afa581b58391585f1792578a4d3d2djvr self.idRangeOffset = None 230bafa66e665afa581b58391585f1792578a4d3d2djvr self.glyphIndexArray = [] 231bafa66e665afa581b58391585f1792578a4d3d2djvr 2327842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_2(CmapSubtable): 2337842e56b97ce677b83bdab09cda48bc2d89ac75aJust 234d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, subHeader): 235d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 236d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # find the minGI which is not zero. 237d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = subHeader.glyphIndexArray[0] 238d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for gid in subHeader.glyphIndexArray: 239d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (gid != 0) and (gid < minGI): 240d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = gid 241d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 242d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K. 243d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We would like to pick an idDelta such that the first glyphArray GID is 1, 244d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # so that we are more likely to be able to combine glypharray GID subranges. 245d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem when minGI is > 32K 246d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 247d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 248d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the 249d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 250d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 251d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (minGI > 1): 252d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if minGI > 0x7FFF: 253d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = -(0x10000 - minGI) -1 254d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 255d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = minGI -1 256d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = subHeader.idDelta 257d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(subHeader.entryCount): 258d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = subHeader.glyphIndexArray[i] 259d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid > 0: 260d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray[i] = gid - idDelta 261d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 262d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2637842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 264d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 265d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 2669e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 267d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 268d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 2699e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 270d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 271d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 272bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys = [] 273bafa66e665afa581b58391585f1792578a4d3d2djvr maxSubHeaderindex = 0 274bafa66e665afa581b58391585f1792578a4d3d2djvr # get the key array, and determine the number of subHeaders. 275d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys = array.array("H") 276d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.fromstring(data[:512]) 277d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[512:] 278180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 279d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.byteswap() 28032c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod subHeaderKeys = [ key//8 for key in allKeys] 281d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxSubHeaderindex = max(subHeaderKeys) 2827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 283bafa66e665afa581b58391585f1792578a4d3d2djvr #Load subHeaders 284bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 285d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 286bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(maxSubHeaderindex + 1): 287bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 288bafa66e665afa581b58391585f1792578a4d3d2djvr (subHeader.firstCode, subHeader.entryCount, subHeader.idDelta, \ 289d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset) = struct.unpack(subHeaderFormat, data[pos:pos + 8]) 290d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 8 291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giDataPos = pos + subHeader.idRangeOffset-2 292d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList = array.array("H") 293d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2]) 294180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 295d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.byteswap() 296d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = giList 297bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 298bafa66e665afa581b58391585f1792578a4d3d2djvr # How this gets processed. 299bafa66e665afa581b58391585f1792578a4d3d2djvr # Charcodes may be one or two bytes. 300bafa66e665afa581b58391585f1792578a4d3d2djvr # The first byte of a charcode is mapped through the subHeaderKeys, to select 301bafa66e665afa581b58391585f1792578a4d3d2djvr # a subHeader. For any subheader but 0, the next byte is then mapped through the 302bafa66e665afa581b58391585f1792578a4d3d2djvr # selected subheader. If subheader Index 0 is selected, then the byte itself is 303bafa66e665afa581b58391585f1792578a4d3d2djvr # mapped through the subheader, and there is no second byte. 304bafa66e665afa581b58391585f1792578a4d3d2djvr # Then assume that the subsequent byte is the first byte of the next charcode,and repeat. 305bafa66e665afa581b58391585f1792578a4d3d2djvr # 306bafa66e665afa581b58391585f1792578a4d3d2djvr # Each subheader references a range in the glyphIndexArray whose length is entryCount. 307bafa66e665afa581b58391585f1792578a4d3d2djvr # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray 308bafa66e665afa581b58391585f1792578a4d3d2djvr # referenced by another subheader. 309bafa66e665afa581b58391585f1792578a4d3d2djvr # The only subheader that will be referenced by more than one first-byte value is the subheader 310bafa66e665afa581b58391585f1792578a4d3d2djvr # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef: 311bafa66e665afa581b58391585f1792578a4d3d2djvr # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx} 312bafa66e665afa581b58391585f1792578a4d3d2djvr # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex. 313bafa66e665afa581b58391585f1792578a4d3d2djvr # A subheader specifies a subrange within (0...256) by the 314bafa66e665afa581b58391585f1792578a4d3d2djvr # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero 315bafa66e665afa581b58391585f1792578a4d3d2djvr # (e.g. glyph not in font). 316bafa66e665afa581b58391585f1792578a4d3d2djvr # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar). 317bafa66e665afa581b58391585f1792578a4d3d2djvr # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by 318bafa66e665afa581b58391585f1792578a4d3d2djvr # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the 319bafa66e665afa581b58391585f1792578a4d3d2djvr # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex. 320bafa66e665afa581b58391585f1792578a4d3d2djvr # Example for Logocut-Medium 321bafa66e665afa581b58391585f1792578a4d3d2djvr # first byte of charcode = 129; selects subheader 1. 322bafa66e665afa581b58391585f1792578a4d3d2djvr # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252} 323bafa66e665afa581b58391585f1792578a4d3d2djvr # second byte of charCode = 66 324bafa66e665afa581b58391585f1792578a4d3d2djvr # the index offset = 66-64 = 2. 325bafa66e665afa581b58391585f1792578a4d3d2djvr # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is: 326bafa66e665afa581b58391585f1792578a4d3d2djvr # [glyphIndexArray index], [subrange array index] = glyphIndex 327bafa66e665afa581b58391585f1792578a4d3d2djvr # [256], [0]=1 from charcode [129, 64] 328bafa66e665afa581b58391585f1792578a4d3d2djvr # [257], [1]=2 from charcode [129, 65] 329bafa66e665afa581b58391585f1792578a4d3d2djvr # [258], [2]=3 from charcode [129, 66] 330bafa66e665afa581b58391585f1792578a4d3d2djvr # [259], [3]=4 from charcode [129, 67] 331d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero, 332d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # add it to the glyphID to get the final glyphIndex 333bafa66e665afa581b58391585f1792578a4d3d2djvr # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew! 334bafa66e665afa581b58391585f1792578a4d3d2djvr 3355f6418d9e1fa15a89dcec29cdc433ba2c99732c3Behdad Esfahbod self.data = b"" 336d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 337d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 338bafa66e665afa581b58391585f1792578a4d3d2djvr for firstByte in range(256): 339bafa66e665afa581b58391585f1792578a4d3d2djvr subHeadindex = subHeaderKeys[firstByte] 340bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = subHeaderList[subHeadindex] 341bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeadindex == 0: 342bafa66e665afa581b58391585f1792578a4d3d2djvr if (firstByte < subHeader.firstCode) or (firstByte >= subHeader.firstCode + subHeader.entryCount): 343d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 344bafa66e665afa581b58391585f1792578a4d3d2djvr else: 345bafa66e665afa581b58391585f1792578a4d3d2djvr charCode = firstByte 346bafa66e665afa581b58391585f1792578a4d3d2djvr offsetIndex = firstByte - subHeader.firstCode 347bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 348bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 349d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 350d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 351d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 352d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 353bafa66e665afa581b58391585f1792578a4d3d2djvr else: 354bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeader.entryCount: 355d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodeOffset = firstByte * 256 + subHeader.firstCode 356bafa66e665afa581b58391585f1792578a4d3d2djvr for offsetIndex in range(subHeader.entryCount): 357d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodeOffset + offsetIndex 358bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 359bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 360d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 361d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 362d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 363d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 364d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not subHeader.entryCount, then all char codes with this first byte are 365d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the 366d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same as mapping it to .notdef. 367d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # cmap values are GID's. 368d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 369c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod gids = list(cmap.values()) 370c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(cmap.keys()) 371d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 372d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 373e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 374d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 375d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 376e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 377e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 378d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 379bafa66e665afa581b58391585f1792578a4d3d2djvr 3807842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 381d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 382d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 383bafa66e665afa581b58391585f1792578a4d3d2djvr kEmptyTwoCharCodeRange = -1 384d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 385d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 386ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.cmap.items()) 387d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [item[0] for item in items] 388d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = [item[1] for item in items] 389d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 390d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 391d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 392e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 393d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 394dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 395d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 396e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 397d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 398d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 2 tables 399d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 400d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 401d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 402d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 403d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 404d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 405d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 406d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 407d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 408d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 409d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 410d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 411d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 412d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 413d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 414d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Process the (char code to gid) item list in char code order. 415d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # By definition, all one byte char codes map to subheader 0. 416d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # 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, 417d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # which defines all char codes in its range to map to notdef) unless proven otherwise. 418d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that since the char code items are processed in char code order, all the char codes with the 419d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same first byte are in sequential order. 420bafa66e665afa581b58391585f1792578a4d3d2djvr 421d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys = [ kEmptyTwoCharCodeRange for x in range(256)] # list of indices into subHeaderList. 422bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 423d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 424d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up 425d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with a cmap where all the one byte char codes map to notdef, 426d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with the result that the subhead 0 would not get created just by processing the item list. 427d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[0] 428d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode > 255: 429d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = SubHeader() 430d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.firstCode = 0 431d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 0 432d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 433d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 434d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 435d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 436bafa66e665afa581b58391585f1792578a4d3d2djvr 437bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = -1 438d299b55d14fa77411140c0cc1c2524583b4ffa58jvr items = zip(charCodes, gids) 439d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode, gid in items: 440d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid == 0: 441d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 442bafa66e665afa581b58391585f1792578a4d3d2djvr firstbyte = charCode >> 8 443bafa66e665afa581b58391585f1792578a4d3d2djvr secondByte = charCode & 0x00FF 444d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 445d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if firstbyte != lastFirstByte: # Need to update the current subhead, and start a new one. 446bafa66e665afa581b58391585f1792578a4d3d2djvr if lastFirstByte > -1: 447d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of current subheader. 448d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 449d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 450d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero 451d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # for the indices matching the char codes. 452d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lastFirstByte == 0: 453d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subHeader.entryCount): 454d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = subHeader.firstCode + index 455d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[charCode] = 0 456d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 457bafa66e665afa581b58391585f1792578a4d3d2djvr assert (subHeader.entryCount == len(subHeader.glyphIndexArray)), "Error - subhead entry count does not match len of glyphID subrange." 458bafa66e665afa581b58391585f1792578a4d3d2djvr # init new subheader 459bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 460bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = secondByte 461d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 1 462d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 463d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 464d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[firstbyte] = len(subHeaderList) -1 465bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = firstbyte 466bafa66e665afa581b58391585f1792578a4d3d2djvr else: 467d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # need to fill in with notdefs all the code points between the last charCode and the current charCode. 468bafa66e665afa581b58391585f1792578a4d3d2djvr codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount) 469bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(codeDiff): 470d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(notdefGI) 471d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 472bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = subHeader.entryCount + codeDiff + 1 473d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 474d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of last subheader that we we added to the subheader array. 475d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 476d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 477d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges. 478bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 479bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = 0 480bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = 0 481bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idDelta = 0 482bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idRangeOffset = 2 483bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 484bafa66e665afa581b58391585f1792578a4d3d2djvr emptySubheadIndex = len(subHeaderList) - 1 485bafa66e665afa581b58391585f1792578a4d3d2djvr for index in range(256): 486d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeaderKeys[index] == kEmptyTwoCharCodeRange: 487bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys[index] = emptySubheadIndex 488bafa66e665afa581b58391585f1792578a4d3d2djvr # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the 489d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray, 490bafa66e665afa581b58391585f1792578a4d3d2djvr # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with 491bafa66e665afa581b58391585f1792578a4d3d2djvr # charcode 0 and GID 0. 492bafa66e665afa581b58391585f1792578a4d3d2djvr 493bafa66e665afa581b58391585f1792578a4d3d2djvr idRangeOffset = (len(subHeaderList)-1)*8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset. 494d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subheadRangeLen = len(subHeaderList) -1 # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2. 495d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subheadRangeLen): 496d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = subHeaderList[index] 497d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 498d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for j in range(index): 499d299b55d14fa77411140c0cc1c2524583b4ffa58jvr prevSubhead = subHeaderList[j] 500d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if prevSubhead.glyphIndexArray == subHeader.glyphIndexArray: # use the glyphIndexArray subarray 501d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = prevSubhead.idRangeOffset - (index-j)*8 502d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = [] 503d299b55d14fa77411140c0cc1c2524583b4ffa58jvr break 504d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeader.idRangeOffset == 0: # didn't find one. 505d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = idRangeOffset 506d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = (idRangeOffset - 8) + subHeader.entryCount*2 # one less subheader, one more subArray. 507d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 508d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = idRangeOffset - 8 # one less subheader 509d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 510bafa66e665afa581b58391585f1792578a4d3d2djvr # Now we can write out the data! 511bafa66e665afa581b58391585f1792578a4d3d2djvr length = 6 + 512 + 8*len(subHeaderList) # header, 256 subHeaderKeys, and subheader array. 512bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 513d299b55d14fa77411140c0cc1c2524583b4ffa58jvr length = length + len(subhead.glyphIndexArray)*2 # We can't use subhead.entryCount, as some of the subhead may share subArrays. 514d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [struct.pack(">HHH", 2, length, self.language)] 515bafa66e665afa581b58391585f1792578a4d3d2djvr for index in subHeaderKeys: 516d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", index*8)) 517bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList: 518d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(subHeaderFormat, subhead.firstCode, subhead.entryCount, subhead.idDelta, subhead.idRangeOffset)) 519bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 520bafa66e665afa581b58391585f1792578a4d3d2djvr for gi in subhead.glyphIndexArray: 521d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", gi)) 52218316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 523bafa66e665afa581b58391585f1792578a4d3d2djvr assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length) 524bafa66e665afa581b58391585f1792578a4d3d2djvr return data 525d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 526d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 5273a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 5280cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 529d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 530d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 531d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 532d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 533bafa66e665afa581b58391585f1792578a4d3d2djvr for element in content: 534b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 535bafa66e665afa581b58391585f1792578a4d3d2djvr continue 536bafa66e665afa581b58391585f1792578a4d3d2djvr name, attrs, content = element 537180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 538bafa66e665afa581b58391585f1792578a4d3d2djvr continue 539d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 5407842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5427842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_format_4_format = ">7H" 5437842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5441f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF. 5451f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 reservedPad # This value should be zero 5461f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 startCode[segCount] # Starting character code for each segment 5471f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idDelta[segCount] # Delta for all character codes in segment 5481f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0 5491f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 glyphIndexArray[variable] # Glyph index array 5507842e56b97ce677b83bdab09cda48bc2d89ac75aJust 551542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvrdef splitRange(startCode, endCode, cmap): 5521f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Try to split a range of character codes into subranges with consecutive 5531f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # glyph IDs in such a way that the cmap4 subtable can be stored "most" 5541f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # efficiently. I can't prove I've got the optimal solution, but it seems 5551f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # to do well with the fonts I tested: none became bigger, many became smaller. 556542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if startCode == endCode: 557542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 558542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 559542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = cmap[startCode] 560542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = startCode 561542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = None 562542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 5631f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = [] 564542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5651f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Gather subranges in which the glyph IDs are consecutive. 566542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for code in range(startCode + 1, endCode + 1): 567542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphID = cmap[code] 568542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 569542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if glyphID - 1 == lastID: 570542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder is None or not inOrder: 571542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 1 572542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = lastCode 573542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 574542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 575542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 0 5761f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 577542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 578542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 579542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = glyphID 580542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = code 581542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 582542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 5831f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 584542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert lastCode == endCode 585542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5861f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Now filter out those new subranges that would only make the data bigger. 5871f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # A new segment cost 8 bytes, not using a new segment costs 2 bytes per 5881f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # character. 5891f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges = [] 5901f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 591542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode and e == endCode: 592542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr break # the whole range, we're fine 593542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode or e == endCode: 594542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 4 # split costs one more segment 595542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 596542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 8 # split costs two more segments 597542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if (e - b + 1) > threshold: 5981f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges.append((b, e)) 5991f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = newRanges 600542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6011f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if not subRanges: 602542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 603542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6041f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[0][0] != startCode: 6051f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(0, (startCode, subRanges[0][0] - 1)) 6061f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[-1][1] != endCode: 6071f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((subRanges[-1][1] + 1, endCode)) 6081f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr 6091f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Fill the "holes" in the segments list -- those are the segments in which 6101f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # the glyph IDs are _not_ consecutive. 611542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = 1 6121f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr while i < len(subRanges): 6131f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[i-1][1] + 1 != subRanges[i][0]: 6141f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(i, (subRanges[i-1][1] + 1, subRanges[i][0] - 1)) 615542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 616542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 617542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6181f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Transform the ranges into startCode/endCode lists. 619542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start = [] 620542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end = [] 6211f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 622542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.append(b) 623542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end.append(e) 624542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.pop(0) 625542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 626542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert len(start) + 1 == len(end) 627542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return start, end 628542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 629542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6307842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_4(CmapSubtable): 6317842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6327842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 633d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 634d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 6359e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 636d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(self.data[offset:offset+int(length)], ttFont) 637d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 6389e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 639d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 640d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 641d299b55d14fa77411140c0cc1c2524583b4ffa58jvr (segCountX2, searchRange, entrySelector, rangeShift) = \ 642d299b55d14fa77411140c0cc1c2524583b4ffa58jvr struct.unpack(">4H", data[:8]) 643d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[8:] 64432c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod segCount = segCountX2 // 2 6457842e56b97ce677b83bdab09cda48bc2d89ac75aJust 646542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = array.array("H") 647d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allCodes.fromstring(data) 648d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 649d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 650180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 651542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes.byteswap() 6527842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6537842e56b97ce677b83bdab09cda48bc2d89ac75aJust # divide the data 654542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr endCode = allCodes[:segCount] 655542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount+1:] # the +1 is skipping the reservedPad field 656542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr startCode = allCodes[:segCount] 657542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 658542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idDelta = allCodes[:segCount] 659542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 660542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idRangeOffset = allCodes[:segCount] 661542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray = allCodes[segCount:] 662d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGIArray = len(glyphIndexArray) 663d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6647842e56b97ce677b83bdab09cda48bc2d89ac75aJust # build 2-byte character mapping 665d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 666d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 6677842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(startCode) - 1): # don't do 0xffff! 6682db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod start = startCode[i] 6692db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod delta = idDelta[i] 6702db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod rangeOffset = idRangeOffset[i] 6712db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod # *someone* needs to get killed. 6722db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod partial = rangeOffset // 2 - start + i - len(idRangeOffset) 6732db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod 67497dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod rangeCharCodes = list(range(startCode[i], endCode[i] + 1)) 6750d182bfb8078665313280db759b782c3144f65faBehdad Esfahbod charCodes.extend(rangeCharCodes) 676470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod if rangeOffset == 0: 677470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod gids.extend([(charCode + delta) & 0xFFFF for charCode in rangeCharCodes]) 678470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod else: 679470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod for charCode in rangeCharCodes: 6802db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod index = charCode + partial 681d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 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) 682180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if glyphIndexArray[index] != 0: # if not missing glyph 6832db5eca0df1ff21f4c6bb5dee44512bf02e3f203Behdad Esfahbod glyphID = glyphIndexArray[index] + delta 6847842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6857842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = 0 # missing glyph 686470d610eb2cba2629889b00575742921079bc1cbBehdad Esfahbod gids.append(glyphID & 0xFFFF) 687d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 688d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 689d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 690d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 691d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 692e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 693d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 694d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 695e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 696e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 697d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 698d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 699d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 700d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, idDelta): 701d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 702d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K 703d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # startCode can be between 0 and 64K-1, and the first glyph index can be between 1 and 64K-1 704d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem because we can need to assign to idDelta values 705d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # between -(64K-2) and 64K -1. 706d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 707d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 708d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a startCode of 0 to a final GID of 64 -1K by subtracting 1, and casting the 709d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 710d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Similarly , we can get from a startCode of 64K-1 to a final GID of 1 by adding 2, because of 711d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # the modulo arithmetic. 712d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 713d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if idDelta > 0x7FFF: 714d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta - 0x10000 715d299b55d14fa77411140c0cc1c2524583b4ffa58jvr elif idDelta < -0x7FFF: 716d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta + 0x10000 717d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 718d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return idDelta 719d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 720d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 7217842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 722d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 723d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 724d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 725ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr from fontTools.ttLib.sfnt import maxPowerOfTwo 7267842e56b97ce677b83bdab09cda48bc2d89ac75aJust 727c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 728d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 729d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lenCharCodes == 0: 730d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [0xffff] 731d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [0xffff] 732d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 7332db352c748a933d85264deb102036137f06b840fjvr charCodes.sort() 734e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) 735d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 736d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 737e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 738d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 739dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 740d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 741e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 742d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 743d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 4 tables 744d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 745d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 746d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 747d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 748d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 749d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 750d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 751d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 752d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 753d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 754d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 755d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 756d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 757d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 758d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = {} # code:glyphID mapping 759e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 7607842e56b97ce677b83bdab09cda48bc2d89ac75aJust 761d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Build startCode and endCode lists. 762d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Split the char codes in ranges of consecutive char codes, then split 763d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # each range in more ranges of consecutive/not consecutive glyph IDs. 764d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # See splitRange(). 765d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCodes[0] 766d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [] 767d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [lastCode] 768d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in charCodes[1:]: # skip the first code, it's the first start code 769d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode == lastCode + 1: 770d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCode 771d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 772d299b55d14fa77411140c0cc1c2524583b4ffa58jvr start, end = splitRange(startCode[-1], lastCode, cmap) 773d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.extend(start) 774d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.extend(end) 775d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(charCode) 776542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = charCode 777d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(lastCode) 778d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(0xffff) 779d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(0xffff) 7807842e56b97ce677b83bdab09cda48bc2d89ac75aJust 781542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr # build up rest of cruft 7827842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta = [] 7837842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset = [] 7847842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = [] 7857842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(endCode)-1): # skip the closing codes (0xffff) 7867842e56b97ce677b83bdab09cda48bc2d89ac75aJust indices = [] 787542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for charCode in range(startCode[i], endCode[i] + 1): 788542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr indices.append(cmap[charCode]) 78997dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod if (indices == list(range(indices[0], indices[0] + len(indices)))): 790d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDeltaTemp = self.setIDDelta(indices[0] - startCode[i]) 791d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta.append( idDeltaTemp) 7927842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7937842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 7947842e56b97ce677b83bdab09cda48bc2d89ac75aJust # someone *definitely* needs to get killed. 7957842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(0) 7967842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i)) 797542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray.extend(indices) 7987842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef 7997842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 8007842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8017842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Insane. 8027842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCount = len(endCode) 8037842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2 = segCount * 2 804542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr maxExponent = maxPowerOfTwo(segCount) 805542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr searchRange = 2 * (2 ** maxExponent) 806542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr entrySelector = maxExponent 8077842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeShift = 2 * segCount - searchRange 8087842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8098da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray = array.array("H", endCode + [0] + startCode) 8108da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray = array.array("h", idDelta) 8118da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray = array.array("H", idRangeOffset + glyphIndexArray) 812180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8138da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray.byteswap() 8148da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray.byteswap() 8158da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray.byteswap() 816d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = charCodeArray.tostring() + idDeltaeArray.tostring() + restArray.tostring() 817d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8187842e56b97ce677b83bdab09cda48bc2d89ac75aJust length = struct.calcsize(cmap_format_4_format) + len(data) 8190cd79a5642101821d66392e3bd0e3c445e97f09bjvr header = struct.pack(cmap_format_4_format, self.format, length, self.language, 8207842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2, searchRange, entrySelector, rangeShift) 821d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return header + data 8227842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8233a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8240cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 825d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 826d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 827d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 828d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8297842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 830b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8317842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 832d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap, attrsMap, dummyContent = element 833180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if nameMap != "map": 834d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 0, "Unrecognized keyword in cmap subtable" 835d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrsMap["code"])] = attrsMap["name"] 8367842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8377842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8387842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_6(CmapSubtable): 8397842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8407842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 841d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 842d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 8439e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 844d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 845d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 8469e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 847d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 848d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 849d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode, entryCount = struct.unpack(">HH", data[:4]) 8507842e56b97ce677b83bdab09cda48bc2d89ac75aJust firstCode = int(firstCode) 851d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[4:] 852f6b1563e0dc4e396264d62598cac856b0959c0f7Just #assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!! 8537842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = array.array("H") 85443fa4be9483ec7cfc2f3c183be8bed746862b7f3Just glyphIndexArray.fromstring(data[:2 * int(entryCount)]) 855180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8567842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray.byteswap() 857d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 858d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8597842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 860d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 861d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIndexArray) 86297dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(firstCode, firstCode + lenArray)) 863d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 864d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 865e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenArray, glyphIndexArray )) 866d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 867d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 868e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, glyphIndexArray )) 869e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 8707842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8717842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 872d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 873d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 874d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 875c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod codes = list(cmap.keys()) 876d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if codes: # yes, there are empty cmap tables. 87797dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod codes = list(range(codes[0], codes[-1] + 1)) 878d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = codes[0] 87913a08d0c3a59402459875155b7dbd194787fb229Behdad Esfahbod valueList = [cmap.get(code, ".notdef") for code in codes] 880d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 8818da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray = array.array("H", valueList) 882180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8838da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray.byteswap() 884d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = glyphIndexArray.tostring() 885d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 8865f6418d9e1fa15a89dcec29cdc433ba2c99732c3Behdad Esfahbod data = b"" 887d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = 0 8887842e56b97ce677b83bdab09cda48bc2d89ac75aJust header = struct.pack(">HHHHH", 889d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6, len(data) + 10, self.language, firstCode, len(codes)) 8907842e56b97ce677b83bdab09cda48bc2d89ac75aJust return header + data 8917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8923a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8930cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 894d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 895d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 896d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 897d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8987842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 899b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 9007842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 9017842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 902180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 9037842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 904d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 9057842e56b97ce677b83bdab09cda48bc2d89ac75aJust 9067842e56b97ce677b83bdab09cda48bc2d89ac75aJust 90751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12_or_13(CmapSubtable): 908924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 909d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __init__(self, format): 910d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = format 911d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = 0 912d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 913d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 914d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 915d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 916924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16]) 91751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader assert len(data) == (16 + nGroups*12) == (length), "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 918924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.format = format 919924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.reserved = reserved 920924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.length = length 921924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = language 922924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.nGroups = nGroups 923d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[16:] 924d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 925d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 926d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 927d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 928d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 9299e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 930d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 931d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 9329e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 933d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 934d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 935d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 936d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 937d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 938d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(self.nGroups): 939d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] ) 940d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 12 941d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGroup = 1 + endCharCode - startCharCode 94297dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes += list(range(startCharCode, endCharCode +1)) 94351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader gids += self._computeGIDs(glyphID, lenGroup) 944d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 945d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 946d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 947d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 948d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 949e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 950d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 951d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 952e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 953e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 954924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 955924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def compile(self, ttFont): 956d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 95751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data 958c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 959d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 960c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod names = list(self.cmap.values()) 961d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 962d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 963e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 964d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 965dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 966d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 967e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 968d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 969d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 12 tables 970d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 971d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 972d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 973d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 974d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 975d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 976d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 977d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 978d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 979d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 980d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 981d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 982d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 983d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 984d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 985924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr cmap = {} # code:glyphID mapping 986e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 987924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 988924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr charCodes.sort() 989d299b55d14fa77411140c0cc1c2524583b4ffa58jvr index = 0 990924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCodes[0] 991924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startGlyphID = cmap[startCharCode] 99251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader lastGlyphID = startGlyphID - self._format_step 993d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = startCharCode - 1 9940cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = 0 995d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [] 996d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxIndex = len(charCodes) 997d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(maxIndex): 998d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[index] 999924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr glyphID = cmap[charCode] 100051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode): 1001d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 1002924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCode 1003d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startGlyphID = glyphID 1004924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr nGroups = nGroups + 1 1005d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastGlyphID = glyphID 1006d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = charCode 1007d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 10080cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = nGroups + 1 100918316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 1010d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lengthSubtable = len(data) +16 1011d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == (nGroups*12) == (lengthSubtable-16) 1012d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data 1013924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1014924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def toXML(self, writer, ttFont): 1015924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.begintag(self.__class__.__name__, [ 1016924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platformID", self.platformID), 1017924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platEncID", self.platEncID), 1018924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("format", self.format), 1019924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("reserved", self.reserved), 1020924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("length", self.length), 1021924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("language", self.language), 1022924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("nGroups", self.nGroups), 1023924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ]) 1024924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1025ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 1026a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1027924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.endtag(self.__class__.__name__) 1028924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1029924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 10303a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1031d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = safeEval(attrs["format"]) 1032d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = safeEval(attrs["reserved"]) 1033d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = safeEval(attrs["length"]) 1034924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = safeEval(attrs["language"]) 1035d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.nGroups = safeEval(attrs["nGroups"]) 1036d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 1037d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1038d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 1039d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1040924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr for element in content: 1041b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 1042924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1043924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr name, attrs, content = element 1044180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 1045924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1046d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 1047924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1048924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 104951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12(cmap_format_12_or_13): 105051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 105151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 105251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 1 105351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 105597dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod return list(range(startingGlyph, startingGlyph + numberOfGlyphs)) 105651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 105851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode) 105951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_13(cmap_format_12_or_13): 106251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 106351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 106451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 0 106551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 106751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return [startingGlyph] * numberOfGlyphs 106851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 107051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode) 107151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 107251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 10730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtToUVS(threeByteString): 10742242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod data = b"\0" + threeByteString 10750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr val, = struct.unpack(">L", data) 10760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return val 10770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtFromUVS(val): 10792242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod assert 0 <= val < 0x1000000 10802242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod fourByteString = struct.pack(">L", val) 10812242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod return fourByteString[1:] 10820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 1083b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod 10840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrclass cmap_format_14(CmapSubtable): 10850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompileHeader(self, data, ttFont): 10870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10]) 10880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = data[10:] 10890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = length 10900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = numVarSelectorRecords 10910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.ttFont = ttFont 10920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.language = 0xFF # has no language. 10930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompile(self, data, ttFont): 10959e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None and ttFont.lazy: 10960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.decompileHeader(data, ttFont) 10970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10989e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 10990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = self.data 11000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = {} 11030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset = 0 11040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for n in range(self.numVarSelectorRecords): 11050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(">3sLL", data[recOffset:recOffset +11]) 11060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset += 11 11070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varUVS = cvtToUVS(uvs) 11080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defOVSOffset: 11090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = defOVSOffset - 10 11100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numValues, = struct.unpack(">L", data[startOffset:startOffset+4]) 11110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numValues): 11130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, addtlCnt = struct.unpack(">3sB", data[startOffset:startOffset+4]) 11140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 4 11150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr firstBaseUV = cvtToUVS(uv) 11160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = addtlCnt+1 111797dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod baseUVList = list(range(firstBaseUV, firstBaseUV+cnt)) 11180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphList = [None]*cnt 11190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = zip(baseUVList, glyphList) 11200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 1123fa5f2e85ab49c349468f5ae08f15163daa256a04Behdad Esfahbod uvsDict[varUVS] = list(localUVList) 11240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if nonDefUVSOffset: 11260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = nonDefUVSOffset - 10 11270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numRecs, = struct.unpack(">L", data[startOffset:startOffset+4]) 11280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = [] 11300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numRecs): 11310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, gid = struct.unpack(">3sH", data[startOffset:startOffset+5]) 11320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 5 11330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = cvtToUVS(uv) 11340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphName = self.ttFont.getGlyphName(gid) 11350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList.append( [uv, glyphName] ) 11360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS] = localUVList 11400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = uvsDict 11420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def toXML(self, writer, ttFont): 11440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.begintag(self.__class__.__name__, [ 11450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platformID", self.platformID), 11460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platEncID", self.platEncID), 11470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("format", self.format), 11480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("length", self.length), 11490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("numVarSelectorRecords", self.numVarSelectorRecords), 11500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ]) 11510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1153ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvList = uvsDict[uvs] 11569e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod uvList.sort(key=lambda item: (item[1] is not None, item[0], item[1])) 11570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in uvList: 11589e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if gname is None: 11590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = "None" 11600cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr # I use the arg rather than th keyword syntax in order to preserve the attribute order. 11610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.simpletag("map", [ ("uvs",hex(uvs)), ("uv",hex(uv)), ("name", gname)] ) 11620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11630cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.endtag(self.__class__.__name__) 11640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11663a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 11670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.format = safeEval(attrs["format"]) 11680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = safeEval(attrs["length"]) 11690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = safeEval(attrs["numVarSelectorRecords"]) 1170b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.language = 0xFF # provide a value so that CmapSubtable.__lt__() won't fail 11710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "cmap"): 11720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "uvsDict"): 11740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = {} 11750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 11760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for element in content: 1178b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 11790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr name, attrs, content = element 1181180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 11820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs = safeEval(attrs["uvs"]) 11840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = safeEval(attrs["uv"]) 11850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = attrs["name"] 11860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == "None": 11870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = None 11880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs].append( [uv, gname]) 11900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs] = [ [uv, gname] ] 11920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def compile(self, ttFont): 11950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if self.data: 11960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) + self.data 11970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1199ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 12000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = len(uvsList) 12010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset = 10 + self.numVarSelectorRecords*11 # current value is end of VarSelectorRecords block. 12020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = [] 12030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords =[] 12040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 12050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr entryList = uvsDict[uvs] 12060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12079e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod defList = [entry for entry in entryList if entry[1] is None] 12080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defList: 1209e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod defList = [entry[0] for entry in defList] 12100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = offset 12110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defList.sort() 12120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defList[0] 12140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = -1 12150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs = [] 12160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for defEntry in defList: 12170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt +=1 12180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if (lastUV+cnt) != defEntry: 12190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt-1) 12200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defEntry 12210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = 0 12230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt) 12250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numDefRecs = len(defRecs) 12280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numDefRecs)) 12290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.extend(defRecs) 12300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numDefRecs*4 12310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = 0 12330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12349e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod ndefList = [entry for entry in entryList if entry[1] is not None] 12350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if ndefList: 12360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = offset 12370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndefList.sort() 12380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numNonDefRecs = len(ndefList) 12390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numNonDefRecs)) 12400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numNonDefRecs*5 12410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in ndefList: 12430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gid = ttFont.getGlyphID(gname) 12440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid) 12450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(ndrec) 12460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = 0 12480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset) 12500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords.append(vrec) 12510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 125218316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(varSelectorRecords) + bytesjoin(data) 12530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = 10 + len(data) 12540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) 12550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = headerdata + data 12560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return self.data 12580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12607842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_unknown(CmapSubtable): 12617842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1262a84b28d934fb697755823c62799f4b65e2b92237jvr def toXML(self, writer, ttFont): 1263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmapName = self.__class__.__name__[:12] + str(self.format) 1264d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.begintag(cmapName, [ 1265a84b28d934fb697755823c62799f4b65e2b92237jvr ("platformID", self.platformID), 1266a84b28d934fb697755823c62799f4b65e2b92237jvr ("platEncID", self.platEncID), 1267a84b28d934fb697755823c62799f4b65e2b92237jvr ]) 1268a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1269d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.dumphex(self.data) 1270d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.endtag(cmapName) 1271a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1272a84b28d934fb697755823c62799f4b65e2b92237jvr 12733a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1274d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = readHex(content) 1275d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1276d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1277d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 1278427f9802bccf942f51567170e72a71ac14443c71jvr self.language = 0 # dummy value 12797842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.data = data 12807842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1281d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 1282d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 1283d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 12849e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 1285d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 1286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 12879e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 12887842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1289d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def compile(self, ttFont): 1290d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 1291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return self.data 1292d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1293d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return None 12947842e56b97ce677b83bdab09cda48bc2d89ac75aJust 12957842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_classes = { 12967842e56b97ce677b83bdab09cda48bc2d89ac75aJust 0: cmap_format_0, 12977842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2: cmap_format_2, 12987842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4: cmap_format_4, 12997842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6: cmap_format_6, 1300924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 12: cmap_format_12, 130151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 13: cmap_format_13, 13020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 14: cmap_format_14, 13037842e56b97ce677b83bdab09cda48bc2d89ac75aJust } 1304