_c_m_a_p.py revision 273a90074ac209d67b5e2cb8ea510cd6c2b10272
132c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbodfrom __future__ import print_function, division 230e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import * 330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.textTools import safeEval, readHex 42b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbodfrom . import DefaultTable 556da965344d25223d41e202aa7fd463dc6228752Behdad Esfahbodfrom functools import total_ordering 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 10156da965344d25223d41e202aa7fd463dc6228752Behdad Esfahbod@total_ordering 102e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass CmapSubtable(object): 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __init__(self, format): 1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.format = format 106d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 107d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 108d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 109d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __getattr__(self, attr): 110d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow lazy decompilation of subtables. 111d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if attr[:2] == '__': # don't handle requests for member functions like '__lt__' 112cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 1139e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if self.data is None: 114cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 115d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompile(None, None) # use saved data. 116d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None # Once this table has been decompiled, make sure we don't 117d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # just return the original data. Also avoids recursion when 118d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # called with an attribute that the cmap subtable doesn't have. 119d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return getattr(self, attr) 1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 121d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 122d299b55d14fa77411140c0cc1c2524583b4ffa58jvr format, length, language = struct.unpack(">HHH", data[:6]) 123d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == length, "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 124d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = int(format) 125d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = int(length) 126d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.language = int(language) 127d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[6:] 128d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 129d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.begintag(self.__class__.__name__, [ 1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platformID", self.platformID), 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platEncID", self.platEncID), 134a84b28d934fb697755823c62799f4b65e2b92237jvr ("language", self.language), 1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust ]) 1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 137ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 138a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.endtag(self.__class__.__name__) 1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 141a84b28d934fb697755823c62799f4b65e2b92237jvr 142a84b28d934fb697755823c62799f4b65e2b92237jvr def _writeCodes(self, codes, writer): 143d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (self.platformID, self.platEncID) == (3, 1) or (self.platformID, self.platEncID) == (3, 10) or self.platformID == 0: 144a84b28d934fb697755823c62799f4b65e2b92237jvr from fontTools.unicode import Unicode 145a84b28d934fb697755823c62799f4b65e2b92237jvr isUnicode = 1 146a84b28d934fb697755823c62799f4b65e2b92237jvr else: 147a84b28d934fb697755823c62799f4b65e2b92237jvr isUnicode = 0 148a84b28d934fb697755823c62799f4b65e2b92237jvr for code, name in codes: 149a84b28d934fb697755823c62799f4b65e2b92237jvr writer.simpletag("map", code=hex(code), name=name) 150a84b28d934fb697755823c62799f4b65e2b92237jvr if isUnicode: 151a84b28d934fb697755823c62799f4b65e2b92237jvr writer.comment(Unicode[code]) 152a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust 154b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod def __lt__(self, other): 155b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod if not isinstance(other, CmapSubtable): 156273a90074ac209d67b5e2cb8ea510cd6c2b10272Behdad Esfahbod return NotImplemented 15796b321c8aea4dc64110d15a541c6f85152ae19cfBehdad Esfahbod 158b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod # implemented so that list.sort() sorts according to the spec. 1597842e56b97ce677b83bdab09cda48bc2d89ac75aJust selfTuple = ( 16094118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platformID", None), 16194118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platEncID", None), 16294118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "language", None), 16394118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod self.__dict__) 1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust otherTuple = ( 16594118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platformID", None), 16694118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platEncID", None), 16794118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "language", None), 16894118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod other.__dict__) 169b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod return selfTuple < otherTuple 1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1727842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_0(CmapSubtable): 1737842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 175d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 176d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 1779e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 178d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 179d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1809e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 181d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 182d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 262 == self.length, "Format 0 cmap subtable not 262 bytes" 1837842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIdArray = array.array("B") 184d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphIdArray.fromstring(self.data) 1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 186d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIdArray) 18797dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(lenArray)) 188d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = map(self.ttFont.getGlyphName, glyphIdArray) 189e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 190d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1927842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 193d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 194d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", 0, 262, self.language) + self.data 195d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 196ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod charCodeList = sorted(self.cmap.items()) 197d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [entry[0] for entry in charCodeList] 198d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = [entry[1] for entry in charCodeList] 19997dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod assert charCodes == list(range(256)) 200d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 201d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2028da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIdArray = array.array("B", valueList) 2030cd79a5642101821d66392e3bd0e3c445e97f09bjvr data = struct.pack(">HHH", 0, 262, self.language) + glyphIdArray.tostring() 2047842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert len(data) == 262 2057842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2073a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 2080cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 209d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 210d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 211d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 213b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 2147842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 216180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 218d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 2197842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 221bafa66e665afa581b58391585f1792578a4d3d2djvrsubHeaderFormat = ">HHhH" 222e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass SubHeader(object): 223bafa66e665afa581b58391585f1792578a4d3d2djvr def __init__(self): 224bafa66e665afa581b58391585f1792578a4d3d2djvr self.firstCode = None 225bafa66e665afa581b58391585f1792578a4d3d2djvr self.entryCount = None 226bafa66e665afa581b58391585f1792578a4d3d2djvr self.idDelta = None 227bafa66e665afa581b58391585f1792578a4d3d2djvr self.idRangeOffset = None 228bafa66e665afa581b58391585f1792578a4d3d2djvr self.glyphIndexArray = [] 229bafa66e665afa581b58391585f1792578a4d3d2djvr 2307842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_2(CmapSubtable): 2317842e56b97ce677b83bdab09cda48bc2d89ac75aJust 232d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, subHeader): 233d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 234d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # find the minGI which is not zero. 235d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = subHeader.glyphIndexArray[0] 236d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for gid in subHeader.glyphIndexArray: 237d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (gid != 0) and (gid < minGI): 238d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = gid 239d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 240d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K. 241d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We would like to pick an idDelta such that the first glyphArray GID is 1, 242d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # so that we are more likely to be able to combine glypharray GID subranges. 243d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem when minGI is > 32K 244d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 245d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 246d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the 247d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 248d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 249d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (minGI > 1): 250d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if minGI > 0x7FFF: 251d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = -(0x10000 - minGI) -1 252d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 253d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = minGI -1 254d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = subHeader.idDelta 255d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(subHeader.entryCount): 256d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = subHeader.glyphIndexArray[i] 257d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid > 0: 258d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray[i] = gid - idDelta 259d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 260d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2617842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 262d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 2649e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 265d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 266d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 2679e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 268d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 269d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 270bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys = [] 271bafa66e665afa581b58391585f1792578a4d3d2djvr maxSubHeaderindex = 0 272bafa66e665afa581b58391585f1792578a4d3d2djvr # get the key array, and determine the number of subHeaders. 273d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys = array.array("H") 274d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.fromstring(data[:512]) 275d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[512:] 276180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 277d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.byteswap() 27832c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod subHeaderKeys = [ key//8 for key in allKeys] 279d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxSubHeaderindex = max(subHeaderKeys) 2807842e56b97ce677b83bdab09cda48bc2d89ac75aJust 281bafa66e665afa581b58391585f1792578a4d3d2djvr #Load subHeaders 282bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 283d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 284bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(maxSubHeaderindex + 1): 285bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 286bafa66e665afa581b58391585f1792578a4d3d2djvr (subHeader.firstCode, subHeader.entryCount, subHeader.idDelta, \ 287d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset) = struct.unpack(subHeaderFormat, data[pos:pos + 8]) 288d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 8 289d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giDataPos = pos + subHeader.idRangeOffset-2 290d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList = array.array("H") 291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2]) 292180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 293d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.byteswap() 294d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = giList 295bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 296bafa66e665afa581b58391585f1792578a4d3d2djvr # How this gets processed. 297bafa66e665afa581b58391585f1792578a4d3d2djvr # Charcodes may be one or two bytes. 298bafa66e665afa581b58391585f1792578a4d3d2djvr # The first byte of a charcode is mapped through the subHeaderKeys, to select 299bafa66e665afa581b58391585f1792578a4d3d2djvr # a subHeader. For any subheader but 0, the next byte is then mapped through the 300bafa66e665afa581b58391585f1792578a4d3d2djvr # selected subheader. If subheader Index 0 is selected, then the byte itself is 301bafa66e665afa581b58391585f1792578a4d3d2djvr # mapped through the subheader, and there is no second byte. 302bafa66e665afa581b58391585f1792578a4d3d2djvr # Then assume that the subsequent byte is the first byte of the next charcode,and repeat. 303bafa66e665afa581b58391585f1792578a4d3d2djvr # 304bafa66e665afa581b58391585f1792578a4d3d2djvr # Each subheader references a range in the glyphIndexArray whose length is entryCount. 305bafa66e665afa581b58391585f1792578a4d3d2djvr # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray 306bafa66e665afa581b58391585f1792578a4d3d2djvr # referenced by another subheader. 307bafa66e665afa581b58391585f1792578a4d3d2djvr # The only subheader that will be referenced by more than one first-byte value is the subheader 308bafa66e665afa581b58391585f1792578a4d3d2djvr # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef: 309bafa66e665afa581b58391585f1792578a4d3d2djvr # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx} 310bafa66e665afa581b58391585f1792578a4d3d2djvr # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex. 311bafa66e665afa581b58391585f1792578a4d3d2djvr # A subheader specifies a subrange within (0...256) by the 312bafa66e665afa581b58391585f1792578a4d3d2djvr # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero 313bafa66e665afa581b58391585f1792578a4d3d2djvr # (e.g. glyph not in font). 314bafa66e665afa581b58391585f1792578a4d3d2djvr # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar). 315bafa66e665afa581b58391585f1792578a4d3d2djvr # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by 316bafa66e665afa581b58391585f1792578a4d3d2djvr # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the 317bafa66e665afa581b58391585f1792578a4d3d2djvr # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex. 318bafa66e665afa581b58391585f1792578a4d3d2djvr # Example for Logocut-Medium 319bafa66e665afa581b58391585f1792578a4d3d2djvr # first byte of charcode = 129; selects subheader 1. 320bafa66e665afa581b58391585f1792578a4d3d2djvr # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252} 321bafa66e665afa581b58391585f1792578a4d3d2djvr # second byte of charCode = 66 322bafa66e665afa581b58391585f1792578a4d3d2djvr # the index offset = 66-64 = 2. 323bafa66e665afa581b58391585f1792578a4d3d2djvr # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is: 324bafa66e665afa581b58391585f1792578a4d3d2djvr # [glyphIndexArray index], [subrange array index] = glyphIndex 325bafa66e665afa581b58391585f1792578a4d3d2djvr # [256], [0]=1 from charcode [129, 64] 326bafa66e665afa581b58391585f1792578a4d3d2djvr # [257], [1]=2 from charcode [129, 65] 327bafa66e665afa581b58391585f1792578a4d3d2djvr # [258], [2]=3 from charcode [129, 66] 328bafa66e665afa581b58391585f1792578a4d3d2djvr # [259], [3]=4 from charcode [129, 67] 329d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero, 330d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # add it to the glyphID to get the final glyphIndex 331bafa66e665afa581b58391585f1792578a4d3d2djvr # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew! 332bafa66e665afa581b58391585f1792578a4d3d2djvr 3335f6418d9e1fa15a89dcec29cdc433ba2c99732c3Behdad Esfahbod self.data = b"" 334d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 335d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 336bafa66e665afa581b58391585f1792578a4d3d2djvr for firstByte in range(256): 337bafa66e665afa581b58391585f1792578a4d3d2djvr subHeadindex = subHeaderKeys[firstByte] 338bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = subHeaderList[subHeadindex] 339bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeadindex == 0: 340bafa66e665afa581b58391585f1792578a4d3d2djvr if (firstByte < subHeader.firstCode) or (firstByte >= subHeader.firstCode + subHeader.entryCount): 341d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 342bafa66e665afa581b58391585f1792578a4d3d2djvr else: 343bafa66e665afa581b58391585f1792578a4d3d2djvr charCode = firstByte 344bafa66e665afa581b58391585f1792578a4d3d2djvr offsetIndex = firstByte - subHeader.firstCode 345bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 346bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 347d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 348d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 349d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 350d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 351bafa66e665afa581b58391585f1792578a4d3d2djvr else: 352bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeader.entryCount: 353d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodeOffset = firstByte * 256 + subHeader.firstCode 354bafa66e665afa581b58391585f1792578a4d3d2djvr for offsetIndex in range(subHeader.entryCount): 355d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodeOffset + offsetIndex 356bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 357bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 358d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 359d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 360d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 361d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 362d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not subHeader.entryCount, then all char codes with this first byte are 363d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the 364d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same as mapping it to .notdef. 365d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # cmap values are GID's. 366d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 367c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod gids = list(cmap.values()) 368c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(cmap.keys()) 369d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 370d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 371e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 372d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 373d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 374e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 375e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 376d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 377bafa66e665afa581b58391585f1792578a4d3d2djvr 3787842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 379d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 380d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 381bafa66e665afa581b58391585f1792578a4d3d2djvr kEmptyTwoCharCodeRange = -1 382d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 383d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 384ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.cmap.items()) 385d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [item[0] for item in items] 386d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = [item[1] for item in items] 387d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 388d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 389d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 390e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 391d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 392dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 393d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 394e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 395d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 396d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 2 tables 397d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 398d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 399d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 400d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 401d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 402d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 403d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 404d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 405d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 406d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 407d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 408d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 409d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 410d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 411d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 412d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Process the (char code to gid) item list in char code order. 413d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # By definition, all one byte char codes map to subheader 0. 414d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # 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, 415d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # which defines all char codes in its range to map to notdef) unless proven otherwise. 416d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that since the char code items are processed in char code order, all the char codes with the 417d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same first byte are in sequential order. 418bafa66e665afa581b58391585f1792578a4d3d2djvr 419d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys = [ kEmptyTwoCharCodeRange for x in range(256)] # list of indices into subHeaderList. 420bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 421d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 422d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up 423d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with a cmap where all the one byte char codes map to notdef, 424d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with the result that the subhead 0 would not get created just by processing the item list. 425d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[0] 426d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode > 255: 427d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = SubHeader() 428d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.firstCode = 0 429d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 0 430d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 431d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 432d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 433d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 434bafa66e665afa581b58391585f1792578a4d3d2djvr 435bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = -1 436d299b55d14fa77411140c0cc1c2524583b4ffa58jvr items = zip(charCodes, gids) 437d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode, gid in items: 438d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid == 0: 439d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 440bafa66e665afa581b58391585f1792578a4d3d2djvr firstbyte = charCode >> 8 441bafa66e665afa581b58391585f1792578a4d3d2djvr secondByte = charCode & 0x00FF 442d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 443d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if firstbyte != lastFirstByte: # Need to update the current subhead, and start a new one. 444bafa66e665afa581b58391585f1792578a4d3d2djvr if lastFirstByte > -1: 445d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of current subheader. 446d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 447d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 448d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero 449d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # for the indices matching the char codes. 450d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lastFirstByte == 0: 451d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subHeader.entryCount): 452d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = subHeader.firstCode + index 453d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[charCode] = 0 454d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 455bafa66e665afa581b58391585f1792578a4d3d2djvr assert (subHeader.entryCount == len(subHeader.glyphIndexArray)), "Error - subhead entry count does not match len of glyphID subrange." 456bafa66e665afa581b58391585f1792578a4d3d2djvr # init new subheader 457bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 458bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = secondByte 459d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 1 460d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 461d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 462d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[firstbyte] = len(subHeaderList) -1 463bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = firstbyte 464bafa66e665afa581b58391585f1792578a4d3d2djvr else: 465d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # need to fill in with notdefs all the code points between the last charCode and the current charCode. 466bafa66e665afa581b58391585f1792578a4d3d2djvr codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount) 467bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(codeDiff): 468d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(notdefGI) 469d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 470bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = subHeader.entryCount + codeDiff + 1 471d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 472d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of last subheader that we we added to the subheader array. 473d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 474d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 475d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges. 476bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 477bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = 0 478bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = 0 479bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idDelta = 0 480bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idRangeOffset = 2 481bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 482bafa66e665afa581b58391585f1792578a4d3d2djvr emptySubheadIndex = len(subHeaderList) - 1 483bafa66e665afa581b58391585f1792578a4d3d2djvr for index in range(256): 484d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeaderKeys[index] == kEmptyTwoCharCodeRange: 485bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys[index] = emptySubheadIndex 486bafa66e665afa581b58391585f1792578a4d3d2djvr # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the 487d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray, 488bafa66e665afa581b58391585f1792578a4d3d2djvr # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with 489bafa66e665afa581b58391585f1792578a4d3d2djvr # charcode 0 and GID 0. 490bafa66e665afa581b58391585f1792578a4d3d2djvr 491bafa66e665afa581b58391585f1792578a4d3d2djvr idRangeOffset = (len(subHeaderList)-1)*8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset. 492d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subheadRangeLen = len(subHeaderList) -1 # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2. 493d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subheadRangeLen): 494d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = subHeaderList[index] 495d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 496d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for j in range(index): 497d299b55d14fa77411140c0cc1c2524583b4ffa58jvr prevSubhead = subHeaderList[j] 498d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if prevSubhead.glyphIndexArray == subHeader.glyphIndexArray: # use the glyphIndexArray subarray 499d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = prevSubhead.idRangeOffset - (index-j)*8 500d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = [] 501d299b55d14fa77411140c0cc1c2524583b4ffa58jvr break 502d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeader.idRangeOffset == 0: # didn't find one. 503d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = idRangeOffset 504d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = (idRangeOffset - 8) + subHeader.entryCount*2 # one less subheader, one more subArray. 505d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 506d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = idRangeOffset - 8 # one less subheader 507d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 508bafa66e665afa581b58391585f1792578a4d3d2djvr # Now we can write out the data! 509bafa66e665afa581b58391585f1792578a4d3d2djvr length = 6 + 512 + 8*len(subHeaderList) # header, 256 subHeaderKeys, and subheader array. 510bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 511d299b55d14fa77411140c0cc1c2524583b4ffa58jvr length = length + len(subhead.glyphIndexArray)*2 # We can't use subhead.entryCount, as some of the subhead may share subArrays. 512d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [struct.pack(">HHH", 2, length, self.language)] 513bafa66e665afa581b58391585f1792578a4d3d2djvr for index in subHeaderKeys: 514d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", index*8)) 515bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList: 516d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(subHeaderFormat, subhead.firstCode, subhead.entryCount, subhead.idDelta, subhead.idRangeOffset)) 517bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 518bafa66e665afa581b58391585f1792578a4d3d2djvr for gi in subhead.glyphIndexArray: 519d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", gi)) 52018316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 521bafa66e665afa581b58391585f1792578a4d3d2djvr assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length) 522bafa66e665afa581b58391585f1792578a4d3d2djvr return data 523d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 524d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 5253a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 5260cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 527d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 528d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 529d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 530d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 531bafa66e665afa581b58391585f1792578a4d3d2djvr for element in content: 532b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 533bafa66e665afa581b58391585f1792578a4d3d2djvr continue 534bafa66e665afa581b58391585f1792578a4d3d2djvr name, attrs, content = element 535180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 536bafa66e665afa581b58391585f1792578a4d3d2djvr continue 537d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 5387842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5397842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5407842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_format_4_format = ">7H" 5417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5421f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF. 5431f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 reservedPad # This value should be zero 5441f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 startCode[segCount] # Starting character code for each segment 5451f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idDelta[segCount] # Delta for all character codes in segment 5461f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0 5471f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 glyphIndexArray[variable] # Glyph index array 5487842e56b97ce677b83bdab09cda48bc2d89ac75aJust 549542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvrdef splitRange(startCode, endCode, cmap): 5501f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Try to split a range of character codes into subranges with consecutive 5511f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # glyph IDs in such a way that the cmap4 subtable can be stored "most" 5521f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # efficiently. I can't prove I've got the optimal solution, but it seems 5531f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # to do well with the fonts I tested: none became bigger, many became smaller. 554542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if startCode == endCode: 555542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 556542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 557542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = cmap[startCode] 558542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = startCode 559542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = None 560542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 5611f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = [] 562542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5631f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Gather subranges in which the glyph IDs are consecutive. 564542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for code in range(startCode + 1, endCode + 1): 565542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphID = cmap[code] 566542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 567542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if glyphID - 1 == lastID: 568542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder is None or not inOrder: 569542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 1 570542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = lastCode 571542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 572542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 573542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 0 5741f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 575542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 576542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 577542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = glyphID 578542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = code 579542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 580542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 5811f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 582542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert lastCode == endCode 583542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5841f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Now filter out those new subranges that would only make the data bigger. 5851f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # A new segment cost 8 bytes, not using a new segment costs 2 bytes per 5861f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # character. 5871f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges = [] 5881f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 589542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode and e == endCode: 590542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr break # the whole range, we're fine 591542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode or e == endCode: 592542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 4 # split costs one more segment 593542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 594542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 8 # split costs two more segments 595542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if (e - b + 1) > threshold: 5961f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges.append((b, e)) 5971f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = newRanges 598542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5991f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if not subRanges: 600542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 601542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6021f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[0][0] != startCode: 6031f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(0, (startCode, subRanges[0][0] - 1)) 6041f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[-1][1] != endCode: 6051f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((subRanges[-1][1] + 1, endCode)) 6061f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr 6071f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Fill the "holes" in the segments list -- those are the segments in which 6081f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # the glyph IDs are _not_ consecutive. 609542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = 1 6101f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr while i < len(subRanges): 6111f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[i-1][1] + 1 != subRanges[i][0]: 6121f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(i, (subRanges[i-1][1] + 1, subRanges[i][0] - 1)) 613542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 614542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 615542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6161f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Transform the ranges into startCode/endCode lists. 617542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start = [] 618542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end = [] 6191f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 620542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.append(b) 621542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end.append(e) 622542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.pop(0) 623542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 624542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert len(start) + 1 == len(end) 625542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return start, end 626542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 627542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6287842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_4(CmapSubtable): 6297842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6307842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 631d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 632d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 6339e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 634d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(self.data[offset:offset+int(length)], ttFont) 635d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 6369e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 637d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 638d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 639d299b55d14fa77411140c0cc1c2524583b4ffa58jvr (segCountX2, searchRange, entrySelector, rangeShift) = \ 640d299b55d14fa77411140c0cc1c2524583b4ffa58jvr struct.unpack(">4H", data[:8]) 641d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[8:] 64232c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod segCount = segCountX2 // 2 6437842e56b97ce677b83bdab09cda48bc2d89ac75aJust 644542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = array.array("H") 645d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allCodes.fromstring(data) 646d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 647d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 648180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 649542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes.byteswap() 6507842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6517842e56b97ce677b83bdab09cda48bc2d89ac75aJust # divide the data 652542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr endCode = allCodes[:segCount] 653542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount+1:] # the +1 is skipping the reservedPad field 654542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr startCode = allCodes[:segCount] 655542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 656542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idDelta = allCodes[:segCount] 657542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 658542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idRangeOffset = allCodes[:segCount] 659542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray = allCodes[segCount:] 660d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGIArray = len(glyphIndexArray) 661d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6627842e56b97ce677b83bdab09cda48bc2d89ac75aJust # build 2-byte character mapping 663d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 664d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 6657842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(startCode) - 1): # don't do 0xffff! 66697dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod rangeCharCodes = list(range(startCode[i], endCode[i] + 1)) 667d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = charCodes + rangeCharCodes 668d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in rangeCharCodes: 6697842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeOffset = idRangeOffset[i] 6707842e56b97ce677b83bdab09cda48bc2d89ac75aJust if rangeOffset == 0: 6717842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = charCode + idDelta[i] 6727842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6737842e56b97ce677b83bdab09cda48bc2d89ac75aJust # *someone* needs to get killed. 67432c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod index = idRangeOffset[i] // 2 + (charCode - startCode[i]) + i - len(idRangeOffset) 675d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 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) 676180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if glyphIndexArray[index] != 0: # if not missing glyph 6777842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = glyphIndexArray[index] + idDelta[i] 6787842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6797842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = 0 # missing glyph 680d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(glyphID % 0x10000) 681d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 682d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 683d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 684d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 685d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 686e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 687d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 688d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 689e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 690e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 691d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 692d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 693d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 694d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, idDelta): 695d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 696d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K 697d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # startCode can be between 0 and 64K-1, and the first glyph index can be between 1 and 64K-1 698d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem because we can need to assign to idDelta values 699d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # between -(64K-2) and 64K -1. 700d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 701d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 702d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a startCode of 0 to a final GID of 64 -1K by subtracting 1, and casting the 703d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 704d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Similarly , we can get from a startCode of 64K-1 to a final GID of 1 by adding 2, because of 705d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # the modulo arithmetic. 706d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 707d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if idDelta > 0x7FFF: 708d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta - 0x10000 709d299b55d14fa77411140c0cc1c2524583b4ffa58jvr elif idDelta < -0x7FFF: 710d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta + 0x10000 711d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 712d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return idDelta 713d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 714d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 7157842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 716d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 717d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 718d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 719ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr from fontTools.ttLib.sfnt import maxPowerOfTwo 7207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 721c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 722d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 723d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lenCharCodes == 0: 724d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [0xffff] 725d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [0xffff] 726d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 7272db352c748a933d85264deb102036137f06b840fjvr charCodes.sort() 728e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) 729d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 730d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 731e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 732d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 733dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 734d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 735e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 736d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 737d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 4 tables 738d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 739d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 740d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 741d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 742d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 743d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 744d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 745d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 746d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 747d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 748d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 749d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 750d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 751d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 752d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = {} # code:glyphID mapping 753e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 7547842e56b97ce677b83bdab09cda48bc2d89ac75aJust 755d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Build startCode and endCode lists. 756d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Split the char codes in ranges of consecutive char codes, then split 757d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # each range in more ranges of consecutive/not consecutive glyph IDs. 758d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # See splitRange(). 759d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCodes[0] 760d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [] 761d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [lastCode] 762d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in charCodes[1:]: # skip the first code, it's the first start code 763d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode == lastCode + 1: 764d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCode 765d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 766d299b55d14fa77411140c0cc1c2524583b4ffa58jvr start, end = splitRange(startCode[-1], lastCode, cmap) 767d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.extend(start) 768d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.extend(end) 769d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(charCode) 770542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = charCode 771d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(lastCode) 772d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(0xffff) 773d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(0xffff) 7747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 775542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr # build up rest of cruft 7767842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta = [] 7777842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset = [] 7787842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = [] 7797842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(endCode)-1): # skip the closing codes (0xffff) 7807842e56b97ce677b83bdab09cda48bc2d89ac75aJust indices = [] 781542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for charCode in range(startCode[i], endCode[i] + 1): 782542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr indices.append(cmap[charCode]) 78397dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod if (indices == list(range(indices[0], indices[0] + len(indices)))): 784d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDeltaTemp = self.setIDDelta(indices[0] - startCode[i]) 785d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta.append( idDeltaTemp) 7867842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7877842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 7887842e56b97ce677b83bdab09cda48bc2d89ac75aJust # someone *definitely* needs to get killed. 7897842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(0) 7907842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i)) 791542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray.extend(indices) 7927842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef 7937842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7947842e56b97ce677b83bdab09cda48bc2d89ac75aJust 7957842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Insane. 7967842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCount = len(endCode) 7977842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2 = segCount * 2 798542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr maxExponent = maxPowerOfTwo(segCount) 799542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr searchRange = 2 * (2 ** maxExponent) 800542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr entrySelector = maxExponent 8017842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeShift = 2 * segCount - searchRange 8027842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8038da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray = array.array("H", endCode + [0] + startCode) 8048da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray = array.array("h", idDelta) 8058da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray = array.array("H", idRangeOffset + glyphIndexArray) 806180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8078da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray.byteswap() 8088da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray.byteswap() 8098da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray.byteswap() 810d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = charCodeArray.tostring() + idDeltaeArray.tostring() + restArray.tostring() 811d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8127842e56b97ce677b83bdab09cda48bc2d89ac75aJust length = struct.calcsize(cmap_format_4_format) + len(data) 8130cd79a5642101821d66392e3bd0e3c445e97f09bjvr header = struct.pack(cmap_format_4_format, self.format, length, self.language, 8147842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2, searchRange, entrySelector, rangeShift) 815d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return header + data 8167842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8173a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8180cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 819d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 820d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 821d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 822d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8237842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 824b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8257842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 826d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap, attrsMap, dummyContent = element 827180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if nameMap != "map": 828d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 0, "Unrecognized keyword in cmap subtable" 829d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrsMap["code"])] = attrsMap["name"] 8307842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8317842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8327842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_6(CmapSubtable): 8337842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8347842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 835d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 836d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 8379e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 838d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 839d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 8409e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 841d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 842d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 843d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode, entryCount = struct.unpack(">HH", data[:4]) 8447842e56b97ce677b83bdab09cda48bc2d89ac75aJust firstCode = int(firstCode) 845d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[4:] 846f6b1563e0dc4e396264d62598cac856b0959c0f7Just #assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!! 8477842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = array.array("H") 84843fa4be9483ec7cfc2f3c183be8bed746862b7f3Just glyphIndexArray.fromstring(data[:2 * int(entryCount)]) 849180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8507842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray.byteswap() 851d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 852d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8537842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 854d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 855d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIndexArray) 85697dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(firstCode, firstCode + lenArray)) 857d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 858d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 859e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenArray, glyphIndexArray )) 860d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 861d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 862e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, glyphIndexArray )) 863e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 8647842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8657842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 866d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 867d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 868d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 869c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod codes = list(cmap.keys()) 870d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if codes: # yes, there are empty cmap tables. 87197dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod codes = list(range(codes[0], codes[-1] + 1)) 872d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = codes[0] 87313a08d0c3a59402459875155b7dbd194787fb229Behdad Esfahbod valueList = [cmap.get(code, ".notdef") for code in codes] 874d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 8758da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray = array.array("H", valueList) 876180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8778da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray.byteswap() 878d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = glyphIndexArray.tostring() 879d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 8805f6418d9e1fa15a89dcec29cdc433ba2c99732c3Behdad Esfahbod data = b"" 881d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = 0 8827842e56b97ce677b83bdab09cda48bc2d89ac75aJust header = struct.pack(">HHHHH", 883d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6, len(data) + 10, self.language, firstCode, len(codes)) 8847842e56b97ce677b83bdab09cda48bc2d89ac75aJust return header + data 8857842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8863a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8870cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 888d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 889d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 890d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 891d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8927842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 893b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8947842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 8957842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 896180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 8977842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 898d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 8997842e56b97ce677b83bdab09cda48bc2d89ac75aJust 9007842e56b97ce677b83bdab09cda48bc2d89ac75aJust 90151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12_or_13(CmapSubtable): 902924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 903d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __init__(self, format): 904d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = format 905d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = 0 906d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 907d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 908d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 909d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 910924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16]) 91151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader assert len(data) == (16 + nGroups*12) == (length), "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 912924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.format = format 913924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.reserved = reserved 914924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.length = length 915924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = language 916924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.nGroups = nGroups 917d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[16:] 918d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 919d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 920d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 921d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 922d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 9239e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 924d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 925d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 9269e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 927d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 928d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 929d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 930d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 931d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 932d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(self.nGroups): 933d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] ) 934d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 12 935d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGroup = 1 + endCharCode - startCharCode 93697dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes += list(range(startCharCode, endCharCode +1)) 93751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader gids += self._computeGIDs(glyphID, lenGroup) 938d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 939d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 940d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 941d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 942d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 943e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 944d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 945d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 946e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 947e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 948924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 949924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def compile(self, ttFont): 950d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 95151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data 952c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 953d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 954c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod names = list(self.cmap.values()) 955d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 956d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 957e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 958d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 959dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod nameMap = ttFont.getReverseGlyphMap(rebuild=True) 960d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 961e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 962d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 963d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 12 tables 964d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 965d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 966d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 967d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 968d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 969d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 970d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 971d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 972d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 973d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 974d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 975d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 976d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 977d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 978d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 979924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr cmap = {} # code:glyphID mapping 980e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 981924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 982924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr charCodes.sort() 983d299b55d14fa77411140c0cc1c2524583b4ffa58jvr index = 0 984924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCodes[0] 985924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startGlyphID = cmap[startCharCode] 98651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader lastGlyphID = startGlyphID - self._format_step 987d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = startCharCode - 1 9880cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = 0 989d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [] 990d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxIndex = len(charCodes) 991d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(maxIndex): 992d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[index] 993924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr glyphID = cmap[charCode] 99451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode): 995d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 996924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCode 997d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startGlyphID = glyphID 998924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr nGroups = nGroups + 1 999d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastGlyphID = glyphID 1000d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = charCode 1001d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 10020cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = nGroups + 1 100318316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(dataList) 1004d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lengthSubtable = len(data) +16 1005d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == (nGroups*12) == (lengthSubtable-16) 1006d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data 1007924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1008924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def toXML(self, writer, ttFont): 1009924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.begintag(self.__class__.__name__, [ 1010924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platformID", self.platformID), 1011924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platEncID", self.platEncID), 1012924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("format", self.format), 1013924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("reserved", self.reserved), 1014924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("length", self.length), 1015924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("language", self.language), 1016924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("nGroups", self.nGroups), 1017924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ]) 1018924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1019ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 1020a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1021924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.endtag(self.__class__.__name__) 1022924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1023924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 10243a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1025d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = safeEval(attrs["format"]) 1026d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = safeEval(attrs["reserved"]) 1027d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = safeEval(attrs["length"]) 1028924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = safeEval(attrs["language"]) 1029d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.nGroups = safeEval(attrs["nGroups"]) 1030d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 1031d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1032d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 1033d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1034924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr for element in content: 1035b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 1036924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1037924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr name, attrs, content = element 1038180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 1039924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1040d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 1041924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1042924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 104351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12(cmap_format_12_or_13): 104451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 104551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 104651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 1 104751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 104997dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod return list(range(startingGlyph, startingGlyph + numberOfGlyphs)) 105051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 105251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode) 105351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_13(cmap_format_12_or_13): 105651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 105751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 105851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 0 105951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 106151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return [startingGlyph] * numberOfGlyphs 106251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 106451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode) 106551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 10670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtToUVS(threeByteString): 10682242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod data = b"\0" + threeByteString 10690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr val, = struct.unpack(">L", data) 10700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return val 10710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtFromUVS(val): 10732242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod assert 0 <= val < 0x1000000 10742242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod fourByteString = struct.pack(">L", val) 10752242b26742caaadd015c999a5ea7e0758c1ce621Behdad Esfahbod return fourByteString[1:] 10760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 1077b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod 10780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrclass cmap_format_14(CmapSubtable): 10790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompileHeader(self, data, ttFont): 10810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10]) 10820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = data[10:] 10830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = length 10840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = numVarSelectorRecords 10850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.ttFont = ttFont 10860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.language = 0xFF # has no language. 10870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompile(self, data, ttFont): 10899e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None and ttFont.lazy: 10900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.decompileHeader(data, ttFont) 10910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10929e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 10930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = self.data 10940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 10960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = {} 10970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset = 0 10980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for n in range(self.numVarSelectorRecords): 10990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(">3sLL", data[recOffset:recOffset +11]) 11000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset += 11 11010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varUVS = cvtToUVS(uvs) 11020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defOVSOffset: 11030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = defOVSOffset - 10 11040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numValues, = struct.unpack(">L", data[startOffset:startOffset+4]) 11050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numValues): 11070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, addtlCnt = struct.unpack(">3sB", data[startOffset:startOffset+4]) 11080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 4 11090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr firstBaseUV = cvtToUVS(uv) 11100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = addtlCnt+1 111197dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod baseUVList = list(range(firstBaseUV, firstBaseUV+cnt)) 11120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphList = [None]*cnt 11130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = zip(baseUVList, glyphList) 11140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 1117fa5f2e85ab49c349468f5ae08f15163daa256a04Behdad Esfahbod uvsDict[varUVS] = list(localUVList) 11180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if nonDefUVSOffset: 11200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = nonDefUVSOffset - 10 11210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numRecs, = struct.unpack(">L", data[startOffset:startOffset+4]) 11220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = [] 11240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numRecs): 11250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, gid = struct.unpack(">3sH", data[startOffset:startOffset+5]) 11260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 5 11270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = cvtToUVS(uv) 11280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphName = self.ttFont.getGlyphName(gid) 11290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList.append( [uv, glyphName] ) 11300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS] = localUVList 11340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = uvsDict 11360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def toXML(self, writer, ttFont): 11380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.begintag(self.__class__.__name__, [ 11390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platformID", self.platformID), 11400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platEncID", self.platEncID), 11410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("format", self.format), 11420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("length", self.length), 11430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("numVarSelectorRecords", self.numVarSelectorRecords), 11440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ]) 11450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1147ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvList = uvsDict[uvs] 11509e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod uvList.sort(key=lambda item: (item[1] is not None, item[0], item[1])) 11510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in uvList: 11529e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if gname is None: 11530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = "None" 11540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr # I use the arg rather than th keyword syntax in order to preserve the attribute order. 11550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.simpletag("map", [ ("uvs",hex(uvs)), ("uv",hex(uv)), ("name", gname)] ) 11560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.endtag(self.__class__.__name__) 11580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11603a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 11610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.format = safeEval(attrs["format"]) 11620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = safeEval(attrs["length"]) 11630cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = safeEval(attrs["numVarSelectorRecords"]) 1164b7fd2e19138b177403689bdd6989cfd2402aa2b3Behdad Esfahbod self.language = 0xFF # provide a value so that CmapSubtable.__lt__() won't fail 11650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "cmap"): 11660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "uvsDict"): 11680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = {} 11690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 11700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for element in content: 1172b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 11730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr name, attrs, content = element 1175180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 11760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs = safeEval(attrs["uvs"]) 11780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = safeEval(attrs["uv"]) 11790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = attrs["name"] 11800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == "None": 11810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = None 11820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs].append( [uv, gname]) 11840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs] = [ [uv, gname] ] 11860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def compile(self, ttFont): 11890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if self.data: 11900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) + self.data 11910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1193ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = len(uvsList) 11950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset = 10 + self.numVarSelectorRecords*11 # current value is end of VarSelectorRecords block. 11960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = [] 11970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords =[] 11980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr entryList = uvsDict[uvs] 12000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12019e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod defList = [entry for entry in entryList if entry[1] is None] 12020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defList: 1203e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod defList = [entry[0] for entry in defList] 12040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = offset 12050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defList.sort() 12060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defList[0] 12080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = -1 12090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs = [] 12100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for defEntry in defList: 12110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt +=1 12120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if (lastUV+cnt) != defEntry: 12130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt-1) 12140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defEntry 12150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = 0 12170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt) 12190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numDefRecs = len(defRecs) 12220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numDefRecs)) 12230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.extend(defRecs) 12240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numDefRecs*4 12250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = 0 12270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12289e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod ndefList = [entry for entry in entryList if entry[1] is not None] 12290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if ndefList: 12300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = offset 12310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndefList.sort() 12320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numNonDefRecs = len(ndefList) 12330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numNonDefRecs)) 12340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numNonDefRecs*5 12350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in ndefList: 12370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gid = ttFont.getGlyphID(gname) 12380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid) 12390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(ndrec) 12400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = 0 12420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset) 12440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords.append(vrec) 12450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 124618316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod data = bytesjoin(varSelectorRecords) + bytesjoin(data) 12470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = 10 + len(data) 12480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) 12490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = headerdata + data 12500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return self.data 12520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12547842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_unknown(CmapSubtable): 12557842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1256a84b28d934fb697755823c62799f4b65e2b92237jvr def toXML(self, writer, ttFont): 1257d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmapName = self.__class__.__name__[:12] + str(self.format) 1258d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.begintag(cmapName, [ 1259a84b28d934fb697755823c62799f4b65e2b92237jvr ("platformID", self.platformID), 1260a84b28d934fb697755823c62799f4b65e2b92237jvr ("platEncID", self.platEncID), 1261a84b28d934fb697755823c62799f4b65e2b92237jvr ]) 1262a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.dumphex(self.data) 1264d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.endtag(cmapName) 1265a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1266a84b28d934fb697755823c62799f4b65e2b92237jvr 12673a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1268d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = readHex(content) 1269d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1270d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1271d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 1272427f9802bccf942f51567170e72a71ac14443c71jvr self.language = 0 # dummy value 12737842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.data = data 12747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1275d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 1276d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 1277d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 12789e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if data is not None and ttFont is not None: 1279d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 1280d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 12819e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod assert (data is None and ttFont is None), "Need both data and ttFont arguments" 12827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1283d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def compile(self, ttFont): 1284d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 1285d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return self.data 1286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1287d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return None 12887842e56b97ce677b83bdab09cda48bc2d89ac75aJust 12897842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_classes = { 12907842e56b97ce677b83bdab09cda48bc2d89ac75aJust 0: cmap_format_0, 12917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2: cmap_format_2, 12927842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4: cmap_format_4, 12937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6: cmap_format_6, 1294924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 12: cmap_format_12, 129551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 13: cmap_format_13, 12960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 14: cmap_format_14, 12977842e56b97ce677b83bdab09cda48bc2d89ac75aJust } 1298