_c_m_a_p.py revision 32c10eecffb4923e0721c395e4b80fb732543f18
132c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbodfrom __future__ import print_function, division 230e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import * 330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.textTools import safeEval, readHex 42b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbodfrom . import DefaultTable 530e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport sys 67842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport struct 77842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport array 8d299b55d14fa77411140c0cc1c2524583b4ffa58jvrimport operator 97842e56b97ce677b83bdab09cda48bc2d89ac75aJust 107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 117842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass table__c_m_a_p(DefaultTable.DefaultTable): 127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 137842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getcmap(self, platformID, platEncID): 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust for subtable in self.tables: 157842e56b97ce677b83bdab09cda48bc2d89ac75aJust if (subtable.platformID == platformID and 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust subtable.platEncID == platEncID): 177842e56b97ce677b83bdab09cda48bc2d89ac75aJust return subtable 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust return None # not found 197842e56b97ce677b83bdab09cda48bc2d89ac75aJust 207842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 217842e56b97ce677b83bdab09cda48bc2d89ac75aJust tableVersion, numSubTables = struct.unpack(">HH", data[:4]) 227842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = int(tableVersion) 237842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = tables = [] 24d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets = {} 257842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(numSubTables): 267842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID, offset = struct.unpack( 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust ">HHl", data[4+i*8:4+(i+1)*8]) 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust platformID, platEncID = int(platformID), int(platEncID) 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust format, length = struct.unpack(">HH", data[offset:offset+4]) 3051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if format in [8,10,12,13]: 31924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length = struct.unpack(">HHL", data[offset:offset+8]) 320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr elif format in [14]: 330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length = struct.unpack(">HL", data[offset:offset+6]) 340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 352db352c748a933d85264deb102036137f06b840fjvr if not length: 363ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("Error: cmap subtable is reported as having zero length: platformID %s, platEncID %s, format %s offset %s. Skipping table." % (platformID, platEncID,format, offset)) 372db352c748a933d85264deb102036137f06b840fjvr continue 38bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 397842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 407842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = platformID 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = platEncID 44d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that by default we decompile only the subtable header info; 45d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # any other data gets decompiled only when an attribute of the 46d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # subtable is referenced. 47d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.decompileHeader(data[offset:offset+int(length)], ttFont) 48bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if offset in seenOffsets: 49d299b55d14fa77411140c0cc1c2524583b4ffa58jvr table.cmap = tables[seenOffsets[offset]].cmap 50d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 51d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seenOffsets[offset] = i 527842e56b97ce677b83bdab09cda48bc2d89ac75aJust tables.append(table) 537842e56b97ce677b83bdab09cda48bc2d89ac75aJust 547842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 557842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables.sort() # sort according to the spec; see CmapSubtable.__cmp__() 567842e56b97ce677b83bdab09cda48bc2d89ac75aJust numSubTables = len(self.tables) 577842e56b97ce677b83bdab09cda48bc2d89ac75aJust totalOffset = 4 + 8 * numSubTables 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = struct.pack(">HH", self.tableVersion, numSubTables) 597842e56b97ce677b83bdab09cda48bc2d89ac75aJust tableData = "" 60d299b55d14fa77411140c0cc1c2524583b4ffa58jvr seen = {} # Some tables are the same object reference. Don't compile them twice. 61d299b55d14fa77411140c0cc1c2524583b4ffa58jvr done = {} # Some tables are different objects, but compile to the same data chunk 627842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 63d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 64d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] 65d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 66d299b55d14fa77411140c0cc1c2524583b4ffa58jvr chunk = table.compile(ttFont) 67bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if chunk in done: 68d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = done[chunk] 69d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 70d299b55d14fa77411140c0cc1c2524583b4ffa58jvr offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(tableData) 71d299b55d14fa77411140c0cc1c2524583b4ffa58jvr tableData = tableData + chunk 727842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset) 737842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data + tableData 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 757842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.simpletag("tableVersion", version=self.tableVersion) 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 787842e56b97ce677b83bdab09cda48bc2d89ac75aJust for table in self.tables: 797842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.toXML(writer, ttFont) 807842e56b97ce677b83bdab09cda48bc2d89ac75aJust 813a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 827842e56b97ce677b83bdab09cda48bc2d89ac75aJust if name == "tableVersion": 837842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tableVersion = safeEval(attrs["version"]) 847842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 85180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name[:12] != "cmap_format_": 867842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not hasattr(self, "tables"): 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = [] 890cd79a5642101821d66392e3bd0e3c445e97f09bjvr format = safeEval(name[12:]) 90bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if format not in cmap_classes: 917842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_format_unknown(format) 927842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 937842e56b97ce677b83bdab09cda48bc2d89ac75aJust table = cmap_classes[format](format) 947842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platformID = safeEval(attrs["platformID"]) 957842e56b97ce677b83bdab09cda48bc2d89ac75aJust table.platEncID = safeEval(attrs["platEncID"]) 963a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod table.fromXML(name, attrs, content, ttFont) 977842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables.append(table) 987842e56b97ce677b83bdab09cda48bc2d89ac75aJust 997842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1007842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass CmapSubtable: 1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __init__(self, format): 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.format = format 104d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 105d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 106d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 107d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __getattr__(self, attr): 108d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow lazy decompilation of subtables. 109d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if attr[:2] == '__': # don't handle requests for member functions like '__lt__' 110cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 111d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data == None: 112cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 113d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompile(None, None) # use saved data. 114d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None # Once this table has been decompiled, make sure we don't 115d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # just return the original data. Also avoids recursion when 116d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # called with an attribute that the cmap subtable doesn't have. 117d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return getattr(self, attr) 1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 119d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 120d299b55d14fa77411140c0cc1c2524583b4ffa58jvr format, length, language = struct.unpack(">HHH", data[:6]) 121d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == length, "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 122d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = int(format) 123d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = int(length) 124d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.language = int(language) 125d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[6:] 126d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 127d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust def toXML(self, writer, ttFont): 1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.begintag(self.__class__.__name__, [ 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platformID", self.platformID), 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust ("platEncID", self.platEncID), 132a84b28d934fb697755823c62799f4b65e2b92237jvr ("language", self.language), 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust ]) 1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 135ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 136a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.endtag(self.__class__.__name__) 1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer.newline() 139a84b28d934fb697755823c62799f4b65e2b92237jvr 140a84b28d934fb697755823c62799f4b65e2b92237jvr def _writeCodes(self, codes, writer): 141d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (self.platformID, self.platEncID) == (3, 1) or (self.platformID, self.platEncID) == (3, 10) or self.platformID == 0: 142a84b28d934fb697755823c62799f4b65e2b92237jvr from fontTools.unicode import Unicode 143a84b28d934fb697755823c62799f4b65e2b92237jvr isUnicode = 1 144a84b28d934fb697755823c62799f4b65e2b92237jvr else: 145a84b28d934fb697755823c62799f4b65e2b92237jvr isUnicode = 0 146a84b28d934fb697755823c62799f4b65e2b92237jvr for code, name in codes: 147a84b28d934fb697755823c62799f4b65e2b92237jvr writer.simpletag("map", code=hex(code), name=name) 148a84b28d934fb697755823c62799f4b65e2b92237jvr if isUnicode: 149a84b28d934fb697755823c62799f4b65e2b92237jvr writer.comment(Unicode[code]) 150a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1527842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __cmp__(self, other): 153ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod if not isinstance(self, type(other)): return cmp(type(self), type(other)) 15496b321c8aea4dc64110d15a541c6f85152ae19cfBehdad Esfahbod 1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust # implemented so that list.sort() sorts according to the cmap spec. 1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust selfTuple = ( 15794118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platformID", None), 15894118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "platEncID", None), 15994118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(self, "language", None), 16094118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod self.__dict__) 1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust otherTuple = ( 16294118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platformID", None), 16394118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "platEncID", None), 16494118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod getattr(other, "language", None), 16594118dcea43bbc618b35774d9c9913738fa5e97aBehdad Esfahbod other.__dict__) 1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust return cmp(selfTuple, otherTuple) 1677842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1697842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_0(CmapSubtable): 1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 172d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 173d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 174d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 175d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 176d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 17717012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 178d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 179d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 262 == self.length, "Format 0 cmap subtable not 262 bytes" 1807842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIdArray = array.array("B") 181d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphIdArray.fromstring(self.data) 1827842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 183d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIdArray) 18497dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(lenArray)) 185d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = map(self.ttFont.getGlyphName, glyphIdArray) 186e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 187d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1887842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1897842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 190d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 191d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", 0, 262, self.language) + self.data 192d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 193ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod charCodeList = sorted(self.cmap.items()) 194d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [entry[0] for entry in charCodeList] 195d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = [entry[1] for entry in charCodeList] 19697dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod assert charCodes == list(range(256)) 197d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 198d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1998da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIdArray = array.array("B", valueList) 2000cd79a5642101821d66392e3bd0e3c445e97f09bjvr data = struct.pack(">HHH", 0, 262, self.language) + glyphIdArray.tostring() 2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert len(data) == 262 2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 2037842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2043a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 2050cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 206d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 207d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 208d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 2097842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 210b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 2117842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 213180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 2147842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 215d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 2167842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust 218bafa66e665afa581b58391585f1792578a4d3d2djvrsubHeaderFormat = ">HHhH" 219bafa66e665afa581b58391585f1792578a4d3d2djvrclass SubHeader: 220bafa66e665afa581b58391585f1792578a4d3d2djvr def __init__(self): 221bafa66e665afa581b58391585f1792578a4d3d2djvr self.firstCode = None 222bafa66e665afa581b58391585f1792578a4d3d2djvr self.entryCount = None 223bafa66e665afa581b58391585f1792578a4d3d2djvr self.idDelta = None 224bafa66e665afa581b58391585f1792578a4d3d2djvr self.idRangeOffset = None 225bafa66e665afa581b58391585f1792578a4d3d2djvr self.glyphIndexArray = [] 226bafa66e665afa581b58391585f1792578a4d3d2djvr 2277842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_2(CmapSubtable): 2287842e56b97ce677b83bdab09cda48bc2d89ac75aJust 229d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, subHeader): 230d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 231d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # find the minGI which is not zero. 232d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = subHeader.glyphIndexArray[0] 233d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for gid in subHeader.glyphIndexArray: 234d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (gid != 0) and (gid < minGI): 235d299b55d14fa77411140c0cc1c2524583b4ffa58jvr minGI = gid 236d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 237d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K. 238d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We would like to pick an idDelta such that the first glyphArray GID is 1, 239d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # so that we are more likely to be able to combine glypharray GID subranges. 240d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem when minGI is > 32K 241d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 242d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 243d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the 244d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 245d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 246d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (minGI > 1): 247d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if minGI > 0x7FFF: 248d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = -(0x10000 - minGI) -1 249d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 250d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = minGI -1 251d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = subHeader.idDelta 252d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(subHeader.entryCount): 253d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = subHeader.glyphIndexArray[i] 254d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid > 0: 255d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray[i] = gid - idDelta 256d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 257d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 2587842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 259d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 260d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 261d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 262d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 263d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 26417012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 265d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 266d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 267bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys = [] 268bafa66e665afa581b58391585f1792578a4d3d2djvr maxSubHeaderindex = 0 269bafa66e665afa581b58391585f1792578a4d3d2djvr # get the key array, and determine the number of subHeaders. 270d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys = array.array("H") 271d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.fromstring(data[:512]) 272d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[512:] 273180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 274d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allKeys.byteswap() 27532c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod subHeaderKeys = [ key//8 for key in allKeys] 276d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxSubHeaderindex = max(subHeaderKeys) 2777842e56b97ce677b83bdab09cda48bc2d89ac75aJust 278bafa66e665afa581b58391585f1792578a4d3d2djvr #Load subHeaders 279bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 280d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 281bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(maxSubHeaderindex + 1): 282bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 283bafa66e665afa581b58391585f1792578a4d3d2djvr (subHeader.firstCode, subHeader.entryCount, subHeader.idDelta, \ 284d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset) = struct.unpack(subHeaderFormat, data[pos:pos + 8]) 285d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 8 286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giDataPos = pos + subHeader.idRangeOffset-2 287d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList = array.array("H") 288d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2]) 289180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 290d299b55d14fa77411140c0cc1c2524583b4ffa58jvr giList.byteswap() 291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = giList 292bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 293bafa66e665afa581b58391585f1792578a4d3d2djvr # How this gets processed. 294bafa66e665afa581b58391585f1792578a4d3d2djvr # Charcodes may be one or two bytes. 295bafa66e665afa581b58391585f1792578a4d3d2djvr # The first byte of a charcode is mapped through the subHeaderKeys, to select 296bafa66e665afa581b58391585f1792578a4d3d2djvr # a subHeader. For any subheader but 0, the next byte is then mapped through the 297bafa66e665afa581b58391585f1792578a4d3d2djvr # selected subheader. If subheader Index 0 is selected, then the byte itself is 298bafa66e665afa581b58391585f1792578a4d3d2djvr # mapped through the subheader, and there is no second byte. 299bafa66e665afa581b58391585f1792578a4d3d2djvr # Then assume that the subsequent byte is the first byte of the next charcode,and repeat. 300bafa66e665afa581b58391585f1792578a4d3d2djvr # 301bafa66e665afa581b58391585f1792578a4d3d2djvr # Each subheader references a range in the glyphIndexArray whose length is entryCount. 302bafa66e665afa581b58391585f1792578a4d3d2djvr # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray 303bafa66e665afa581b58391585f1792578a4d3d2djvr # referenced by another subheader. 304bafa66e665afa581b58391585f1792578a4d3d2djvr # The only subheader that will be referenced by more than one first-byte value is the subheader 305bafa66e665afa581b58391585f1792578a4d3d2djvr # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef: 306bafa66e665afa581b58391585f1792578a4d3d2djvr # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx} 307bafa66e665afa581b58391585f1792578a4d3d2djvr # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex. 308bafa66e665afa581b58391585f1792578a4d3d2djvr # A subheader specifies a subrange within (0...256) by the 309bafa66e665afa581b58391585f1792578a4d3d2djvr # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero 310bafa66e665afa581b58391585f1792578a4d3d2djvr # (e.g. glyph not in font). 311bafa66e665afa581b58391585f1792578a4d3d2djvr # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar). 312bafa66e665afa581b58391585f1792578a4d3d2djvr # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by 313bafa66e665afa581b58391585f1792578a4d3d2djvr # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the 314bafa66e665afa581b58391585f1792578a4d3d2djvr # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex. 315bafa66e665afa581b58391585f1792578a4d3d2djvr # Example for Logocut-Medium 316bafa66e665afa581b58391585f1792578a4d3d2djvr # first byte of charcode = 129; selects subheader 1. 317bafa66e665afa581b58391585f1792578a4d3d2djvr # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252} 318bafa66e665afa581b58391585f1792578a4d3d2djvr # second byte of charCode = 66 319bafa66e665afa581b58391585f1792578a4d3d2djvr # the index offset = 66-64 = 2. 320bafa66e665afa581b58391585f1792578a4d3d2djvr # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is: 321bafa66e665afa581b58391585f1792578a4d3d2djvr # [glyphIndexArray index], [subrange array index] = glyphIndex 322bafa66e665afa581b58391585f1792578a4d3d2djvr # [256], [0]=1 from charcode [129, 64] 323bafa66e665afa581b58391585f1792578a4d3d2djvr # [257], [1]=2 from charcode [129, 65] 324bafa66e665afa581b58391585f1792578a4d3d2djvr # [258], [2]=3 from charcode [129, 66] 325bafa66e665afa581b58391585f1792578a4d3d2djvr # [259], [3]=4 from charcode [129, 67] 326d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero, 327d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # add it to the glyphID to get the final glyphIndex 328bafa66e665afa581b58391585f1792578a4d3d2djvr # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew! 329bafa66e665afa581b58391585f1792578a4d3d2djvr 330bafa66e665afa581b58391585f1792578a4d3d2djvr self.data = "" 331d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 332d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 333bafa66e665afa581b58391585f1792578a4d3d2djvr for firstByte in range(256): 334bafa66e665afa581b58391585f1792578a4d3d2djvr subHeadindex = subHeaderKeys[firstByte] 335bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = subHeaderList[subHeadindex] 336bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeadindex == 0: 337bafa66e665afa581b58391585f1792578a4d3d2djvr if (firstByte < subHeader.firstCode) or (firstByte >= subHeader.firstCode + subHeader.entryCount): 338d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 339bafa66e665afa581b58391585f1792578a4d3d2djvr else: 340bafa66e665afa581b58391585f1792578a4d3d2djvr charCode = firstByte 341bafa66e665afa581b58391585f1792578a4d3d2djvr offsetIndex = firstByte - subHeader.firstCode 342bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 343bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 344d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 345d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 346d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue # gi is notdef. 347d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 348bafa66e665afa581b58391585f1792578a4d3d2djvr else: 349bafa66e665afa581b58391585f1792578a4d3d2djvr if subHeader.entryCount: 350d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodeOffset = firstByte * 256 + subHeader.firstCode 351bafa66e665afa581b58391585f1792578a4d3d2djvr for offsetIndex in range(subHeader.entryCount): 352d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodeOffset + offsetIndex 353bafa66e665afa581b58391585f1792578a4d3d2djvr gi = subHeader.glyphIndexArray[offsetIndex] 354bafa66e665afa581b58391585f1792578a4d3d2djvr if gi != 0: 355d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gi = (gi + subHeader.idDelta) % 0x10000 356d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 357d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 358d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[charCode] = gi 359d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not subHeader.entryCount, then all char codes with this first byte are 360d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the 361d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same as mapping it to .notdef. 362d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # cmap values are GID's. 363d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 364c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod gids = list(cmap.values()) 365c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(cmap.keys()) 366d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 367d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 368e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 369d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 370d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 371e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 372e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 373d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 374bafa66e665afa581b58391585f1792578a4d3d2djvr 3757842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 376d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 377d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 378bafa66e665afa581b58391585f1792578a4d3d2djvr kEmptyTwoCharCodeRange = -1 379d299b55d14fa77411140c0cc1c2524583b4ffa58jvr notdefGI = 0 380d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 381ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.cmap.items()) 382d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [item[0] for item in items] 383d299b55d14fa77411140c0cc1c2524583b4ffa58jvr names = [item[1] for item in items] 384d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 385d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 386d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 387e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 388d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 389d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap(rebuild=1) 390d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 391e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 392d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 393d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 2 tables 394d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 395d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 396d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 397d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 398d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 399d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 400d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 401d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 402d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 403d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 404d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 405d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 406d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 407d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 408d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 409d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Process the (char code to gid) item list in char code order. 410d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # By definition, all one byte char codes map to subheader 0. 411d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # 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, 412d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # which defines all char codes in its range to map to notdef) unless proven otherwise. 413d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Note that since the char code items are processed in char code order, all the char codes with the 414d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # same first byte are in sequential order. 415bafa66e665afa581b58391585f1792578a4d3d2djvr 416d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys = [ kEmptyTwoCharCodeRange for x in range(256)] # list of indices into subHeaderList. 417bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList = [] 418d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 419d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up 420d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with a cmap where all the one byte char codes map to notdef, 421d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # with the result that the subhead 0 would not get created just by processing the item list. 422d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[0] 423d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode > 255: 424d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = SubHeader() 425d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.firstCode = 0 426d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 0 427d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idDelta = 0 428d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 429d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 430d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 431bafa66e665afa581b58391585f1792578a4d3d2djvr 432bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = -1 433d299b55d14fa77411140c0cc1c2524583b4ffa58jvr items = zip(charCodes, gids) 434d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode, gid in items: 435d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if gid == 0: 436d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 437bafa66e665afa581b58391585f1792578a4d3d2djvr firstbyte = charCode >> 8 438bafa66e665afa581b58391585f1792578a4d3d2djvr secondByte = charCode & 0x00FF 439d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 440d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if firstbyte != lastFirstByte: # Need to update the current subhead, and start a new one. 441bafa66e665afa581b58391585f1792578a4d3d2djvr if lastFirstByte > -1: 442d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of current subheader. 443d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 444d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 445d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero 446d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # for the indices matching the char codes. 447d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lastFirstByte == 0: 448d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subHeader.entryCount): 449d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = subHeader.firstCode + index 450d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[charCode] = 0 451d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 452bafa66e665afa581b58391585f1792578a4d3d2djvr assert (subHeader.entryCount == len(subHeader.glyphIndexArray)), "Error - subhead entry count does not match len of glyphID subrange." 453bafa66e665afa581b58391585f1792578a4d3d2djvr # init new subheader 454bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 455bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = secondByte 456d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.entryCount = 1 457d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 458d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderList.append(subHeader) 459d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeaderKeys[firstbyte] = len(subHeaderList) -1 460bafa66e665afa581b58391585f1792578a4d3d2djvr lastFirstByte = firstbyte 461bafa66e665afa581b58391585f1792578a4d3d2djvr else: 462d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # need to fill in with notdefs all the code points between the last charCode and the current charCode. 463bafa66e665afa581b58391585f1792578a4d3d2djvr codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount) 464bafa66e665afa581b58391585f1792578a4d3d2djvr for i in range(codeDiff): 465d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(notdefGI) 466d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray.append(gid) 467bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = subHeader.entryCount + codeDiff + 1 468d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 469d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # fix GI's and iDelta of last subheader that we we added to the subheader array. 470d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.setIDDelta(subHeader) 471d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 472d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges. 473bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader = SubHeader() 474bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.firstCode = 0 475bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.entryCount = 0 476bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idDelta = 0 477bafa66e665afa581b58391585f1792578a4d3d2djvr subHeader.idRangeOffset = 2 478bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderList.append(subHeader) 479bafa66e665afa581b58391585f1792578a4d3d2djvr emptySubheadIndex = len(subHeaderList) - 1 480bafa66e665afa581b58391585f1792578a4d3d2djvr for index in range(256): 481d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeaderKeys[index] == kEmptyTwoCharCodeRange: 482bafa66e665afa581b58391585f1792578a4d3d2djvr subHeaderKeys[index] = emptySubheadIndex 483bafa66e665afa581b58391585f1792578a4d3d2djvr # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the 484d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray, 485bafa66e665afa581b58391585f1792578a4d3d2djvr # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with 486bafa66e665afa581b58391585f1792578a4d3d2djvr # charcode 0 and GID 0. 487bafa66e665afa581b58391585f1792578a4d3d2djvr 488bafa66e665afa581b58391585f1792578a4d3d2djvr idRangeOffset = (len(subHeaderList)-1)*8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset. 489d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subheadRangeLen = len(subHeaderList) -1 # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2. 490d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(subheadRangeLen): 491d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader = subHeaderList[index] 492d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = 0 493d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for j in range(index): 494d299b55d14fa77411140c0cc1c2524583b4ffa58jvr prevSubhead = subHeaderList[j] 495d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if prevSubhead.glyphIndexArray == subHeader.glyphIndexArray: # use the glyphIndexArray subarray 496d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = prevSubhead.idRangeOffset - (index-j)*8 497d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.glyphIndexArray = [] 498d299b55d14fa77411140c0cc1c2524583b4ffa58jvr break 499d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if subHeader.idRangeOffset == 0: # didn't find one. 500d299b55d14fa77411140c0cc1c2524583b4ffa58jvr subHeader.idRangeOffset = idRangeOffset 501d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = (idRangeOffset - 8) + subHeader.entryCount*2 # one less subheader, one more subArray. 502d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 503d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idRangeOffset = idRangeOffset - 8 # one less subheader 504d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 505bafa66e665afa581b58391585f1792578a4d3d2djvr # Now we can write out the data! 506bafa66e665afa581b58391585f1792578a4d3d2djvr length = 6 + 512 + 8*len(subHeaderList) # header, 256 subHeaderKeys, and subheader array. 507bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 508d299b55d14fa77411140c0cc1c2524583b4ffa58jvr length = length + len(subhead.glyphIndexArray)*2 # We can't use subhead.entryCount, as some of the subhead may share subArrays. 509d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [struct.pack(">HHH", 2, length, self.language)] 510bafa66e665afa581b58391585f1792578a4d3d2djvr for index in subHeaderKeys: 511d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", index*8)) 512bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList: 513d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(subHeaderFormat, subhead.firstCode, subhead.entryCount, subhead.idDelta, subhead.idRangeOffset)) 514bafa66e665afa581b58391585f1792578a4d3d2djvr for subhead in subHeaderList[:-1]: 515bafa66e665afa581b58391585f1792578a4d3d2djvr for gi in subhead.glyphIndexArray: 516d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">H", gi)) 517d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = "".join(dataList) 518bafa66e665afa581b58391585f1792578a4d3d2djvr assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length) 519bafa66e665afa581b58391585f1792578a4d3d2djvr return data 520d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 521d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 5223a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 5230cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 524d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 525d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 526d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 527d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 528bafa66e665afa581b58391585f1792578a4d3d2djvr for element in content: 529b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 530bafa66e665afa581b58391585f1792578a4d3d2djvr continue 531bafa66e665afa581b58391585f1792578a4d3d2djvr name, attrs, content = element 532180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 533bafa66e665afa581b58391585f1792578a4d3d2djvr continue 534d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 5357842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5367842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5377842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_format_4_format = ">7H" 5387842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5391f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF. 5401f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 reservedPad # This value should be zero 5411f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 startCode[segCount] # Starting character code for each segment 5421f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idDelta[segCount] # Delta for all character codes in segment 5431f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0 5441f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr#uint16 glyphIndexArray[variable] # Glyph index array 5457842e56b97ce677b83bdab09cda48bc2d89ac75aJust 546542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvrdef splitRange(startCode, endCode, cmap): 5471f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Try to split a range of character codes into subranges with consecutive 5481f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # glyph IDs in such a way that the cmap4 subtable can be stored "most" 5491f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # efficiently. I can't prove I've got the optimal solution, but it seems 5501f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # to do well with the fonts I tested: none became bigger, many became smaller. 551542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if startCode == endCode: 552542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 553542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 554542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = cmap[startCode] 555542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = startCode 556542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = None 557542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 5581f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = [] 559542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5601f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Gather subranges in which the glyph IDs are consecutive. 561542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for code in range(startCode + 1, endCode + 1): 562542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphID = cmap[code] 563542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 564542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if glyphID - 1 == lastID: 565542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder is None or not inOrder: 566542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 1 567542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = lastCode 568542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 569542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 570542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr inOrder = 0 5711f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 572542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr orderedBegin = None 573542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 574542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastID = glyphID 575542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = code 576542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 577542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if inOrder: 5781f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((orderedBegin, lastCode)) 579542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert lastCode == endCode 580542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5811f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Now filter out those new subranges that would only make the data bigger. 5821f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # A new segment cost 8 bytes, not using a new segment costs 2 bytes per 5831f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # character. 5841f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges = [] 5851f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 586542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode and e == endCode: 587542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr break # the whole range, we're fine 588542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if b == startCode or e == endCode: 589542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 4 # split costs one more segment 590542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr else: 591542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr threshold = 8 # split costs two more segments 592542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr if (e - b + 1) > threshold: 5931f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr newRanges.append((b, e)) 5941f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges = newRanges 595542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5961f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if not subRanges: 597542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return [], [endCode] 598542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 5991f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[0][0] != startCode: 6001f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(0, (startCode, subRanges[0][0] - 1)) 6011f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[-1][1] != endCode: 6021f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.append((subRanges[-1][1] + 1, endCode)) 6031f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr 6041f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Fill the "holes" in the segments list -- those are the segments in which 6051f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # the glyph IDs are _not_ consecutive. 606542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = 1 6071f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr while i < len(subRanges): 6081f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr if subRanges[i-1][1] + 1 != subRanges[i][0]: 6091f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr subRanges.insert(i, (subRanges[i-1][1] + 1, subRanges[i][0] - 1)) 610542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 611542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr i = i + 1 612542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6131f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr # Transform the ranges into startCode/endCode lists. 614542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start = [] 615542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end = [] 6161f8a4bb02ac37fb92f3dbe9eca5d66ecee6755f3jvr for b, e in subRanges: 617542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.append(b) 618542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr end.append(e) 619542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr start.pop(0) 620542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 621542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr assert len(start) + 1 == len(end) 622542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr return start, end 623542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 624542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr 6257842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_4(CmapSubtable): 6267842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6277842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 628d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 629d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 630d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 631d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(self.data[offset:offset+int(length)], ttFont) 632d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 63317012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 634d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 635d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 636d299b55d14fa77411140c0cc1c2524583b4ffa58jvr (segCountX2, searchRange, entrySelector, rangeShift) = \ 637d299b55d14fa77411140c0cc1c2524583b4ffa58jvr struct.unpack(">4H", data[:8]) 638d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[8:] 63932c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod segCount = segCountX2 // 2 6407842e56b97ce677b83bdab09cda48bc2d89ac75aJust 641542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = array.array("H") 642d299b55d14fa77411140c0cc1c2524583b4ffa58jvr allCodes.fromstring(data) 643d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 644d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 645180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 646542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes.byteswap() 6477842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6487842e56b97ce677b83bdab09cda48bc2d89ac75aJust # divide the data 649542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr endCode = allCodes[:segCount] 650542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount+1:] # the +1 is skipping the reservedPad field 651542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr startCode = allCodes[:segCount] 652542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 653542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idDelta = allCodes[:segCount] 654542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr allCodes = allCodes[segCount:] 655542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr idRangeOffset = allCodes[:segCount] 656542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray = allCodes[segCount:] 657d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGIArray = len(glyphIndexArray) 658d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6597842e56b97ce677b83bdab09cda48bc2d89ac75aJust # build 2-byte character mapping 660d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 661d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 6627842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(startCode) - 1): # don't do 0xffff! 66397dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod rangeCharCodes = list(range(startCode[i], endCode[i] + 1)) 664d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = charCodes + rangeCharCodes 665d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in rangeCharCodes: 6667842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeOffset = idRangeOffset[i] 6677842e56b97ce677b83bdab09cda48bc2d89ac75aJust if rangeOffset == 0: 6687842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = charCode + idDelta[i] 6697842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6707842e56b97ce677b83bdab09cda48bc2d89ac75aJust # *someone* needs to get killed. 67132c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod index = idRangeOffset[i] // 2 + (charCode - startCode[i]) + i - len(idRangeOffset) 672d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 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) 673180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if glyphIndexArray[index] != 0: # if not missing glyph 6747842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = glyphIndexArray[index] + idDelta[i] 6757842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6767842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = 0 # missing glyph 677d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(glyphID % 0x10000) 678d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 679d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 680d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 681d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 682d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 683e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 684d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 685d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 686e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 687e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 688d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 689d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 690d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 691d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def setIDDelta(self, idDelta): 692d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1. 693d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # idDelta is a short, and must be between -32K and 32K 694d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # startCode can be between 0 and 64K-1, and the first glyph index can be between 1 and 64K-1 695d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # This means that we have a problem because we can need to assign to idDelta values 696d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # between -(64K-2) and 64K -1. 697d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Since the final gi is reconstructed from the glyphArray GID by: 698d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # (short)finalGID = (gid + idDelta) % 0x10000), 699d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we can get from a startCode of 0 to a final GID of 64 -1K by subtracting 1, and casting the 700d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # negative number to an unsigned short. 701d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Similarly , we can get from a startCode of 64K-1 to a final GID of 1 by adding 2, because of 702d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # the modulo arithmetic. 703d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 704d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if idDelta > 0x7FFF: 705d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta - 0x10000 706d299b55d14fa77411140c0cc1c2524583b4ffa58jvr elif idDelta < -0x7FFF: 707d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta = idDelta + 0x10000 708d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 709d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return idDelta 710d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 711d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 7127842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 713d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 714d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 715d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 716ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr from fontTools.ttLib.sfnt import maxPowerOfTwo 7177842e56b97ce677b83bdab09cda48bc2d89ac75aJust 718c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 719d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 720d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if lenCharCodes == 0: 721d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [0xffff] 722d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [0xffff] 723d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 7242db352c748a933d85264deb102036137f06b840fjvr charCodes.sort() 725e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) 726d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 727d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 728e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 729d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 730d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap(rebuild=1) 731d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 732e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 733d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 734d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 4 tables 735d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 736d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 737d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 738d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 739d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 740d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 741d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 742d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 743d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 744d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 745d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 746d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 747d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 748d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 749d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = {} # code:glyphID mapping 750e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 7517842e56b97ce677b83bdab09cda48bc2d89ac75aJust 752d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Build startCode and endCode lists. 753d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # Split the char codes in ranges of consecutive char codes, then split 754d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # each range in more ranges of consecutive/not consecutive glyph IDs. 755d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # See splitRange(). 756d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCodes[0] 757d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode = [] 758d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode = [lastCode] 759d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for charCode in charCodes[1:]: # skip the first code, it's the first start code 760d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if charCode == lastCode + 1: 761d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCode = charCode 762d299b55d14fa77411140c0cc1c2524583b4ffa58jvr continue 763d299b55d14fa77411140c0cc1c2524583b4ffa58jvr start, end = splitRange(startCode[-1], lastCode, cmap) 764d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.extend(start) 765d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.extend(end) 766d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(charCode) 767542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr lastCode = charCode 768d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(lastCode) 769d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCode.append(0xffff) 770d299b55d14fa77411140c0cc1c2524583b4ffa58jvr endCode.append(0xffff) 7717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 772542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr # build up rest of cruft 7737842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta = [] 7747842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset = [] 7757842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = [] 7767842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(endCode)-1): # skip the closing codes (0xffff) 7777842e56b97ce677b83bdab09cda48bc2d89ac75aJust indices = [] 778542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr for charCode in range(startCode[i], endCode[i] + 1): 779542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr indices.append(cmap[charCode]) 78097dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod if (indices == list(range(indices[0], indices[0] + len(indices)))): 781d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDeltaTemp = self.setIDDelta(indices[0] - startCode[i]) 782d299b55d14fa77411140c0cc1c2524583b4ffa58jvr idDelta.append( idDeltaTemp) 7837842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7847842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 7857842e56b97ce677b83bdab09cda48bc2d89ac75aJust # someone *definitely* needs to get killed. 7867842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(0) 7877842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i)) 788542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr glyphIndexArray.extend(indices) 7897842e56b97ce677b83bdab09cda48bc2d89ac75aJust idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef 7907842e56b97ce677b83bdab09cda48bc2d89ac75aJust idRangeOffset.append(0) 7917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 7927842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Insane. 7937842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCount = len(endCode) 7947842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2 = segCount * 2 795542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr maxExponent = maxPowerOfTwo(segCount) 796542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr searchRange = 2 * (2 ** maxExponent) 797542b9510e6a8909e35e99a5279b7c2ec57c78e3cjvr entrySelector = maxExponent 7987842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeShift = 2 * segCount - searchRange 7997842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8008da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray = array.array("H", endCode + [0] + startCode) 8018da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray = array.array("h", idDelta) 8028da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray = array.array("H", idRangeOffset + glyphIndexArray) 803180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8048da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod charCodeArray.byteswap() 8058da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod idDeltaeArray.byteswap() 8068da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod restArray.byteswap() 807d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = charCodeArray.tostring() + idDeltaeArray.tostring() + restArray.tostring() 808d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8097842e56b97ce677b83bdab09cda48bc2d89ac75aJust length = struct.calcsize(cmap_format_4_format) + len(data) 8100cd79a5642101821d66392e3bd0e3c445e97f09bjvr header = struct.pack(cmap_format_4_format, self.format, length, self.language, 8117842e56b97ce677b83bdab09cda48bc2d89ac75aJust segCountX2, searchRange, entrySelector, rangeShift) 812d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return header + data 8137842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8143a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8150cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 816d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 817d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 818d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 819d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8207842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 821b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8227842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 823d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap, attrsMap, dummyContent = element 824180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if nameMap != "map": 825d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert 0, "Unrecognized keyword in cmap subtable" 826d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrsMap["code"])] = attrsMap["name"] 8277842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8287842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8297842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_6(CmapSubtable): 8307842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8317842e56b97ce677b83bdab09cda48bc2d89ac75aJust def decompile(self, data, ttFont): 832d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 833d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 834d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 835d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 836d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 83717012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 838d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 839d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 840d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode, entryCount = struct.unpack(">HH", data[:4]) 8417842e56b97ce677b83bdab09cda48bc2d89ac75aJust firstCode = int(firstCode) 842d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = data[4:] 843f6b1563e0dc4e396264d62598cac856b0959c0f7Just #assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!! 8447842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray = array.array("H") 84543fa4be9483ec7cfc2f3c183be8bed746862b7f3Just glyphIndexArray.fromstring(data[:2 * int(entryCount)]) 846180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8477842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphIndexArray.byteswap() 848d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 849d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8507842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.cmap = cmap = {} 851d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 852d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenArray = len(glyphIndexArray) 85397dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes = list(range(firstCode, firstCode + lenArray)) 854d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 855d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 856e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenArray, glyphIndexArray )) 857d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 858d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 859e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, glyphIndexArray )) 860e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenArray, charCodes, names)) 8617842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8627842e56b97ce677b83bdab09cda48bc2d89ac75aJust def compile(self, ttFont): 863d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 864d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHH", self.format, self.length, self.language) + self.data 865d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 866c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod codes = list(cmap.keys()) 867d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if codes: # yes, there are empty cmap tables. 86897dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod codes = list(range(codes[0], codes[-1] + 1)) 869d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = codes[0] 87013a08d0c3a59402459875155b7dbd194787fb229Behdad Esfahbod valueList = [cmap.get(code, ".notdef") for code in codes] 871d299b55d14fa77411140c0cc1c2524583b4ffa58jvr valueList = map(ttFont.getGlyphID, valueList) 8728da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray = array.array("H", valueList) 873180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 8748da8242d614d8e57f5c1d1730436d455777449b5Behdad Esfahbod glyphIndexArray.byteswap() 875d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = glyphIndexArray.tostring() 876d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 877d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = "" 878d299b55d14fa77411140c0cc1c2524583b4ffa58jvr firstCode = 0 8797842e56b97ce677b83bdab09cda48bc2d89ac75aJust header = struct.pack(">HHHHH", 880d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 6, len(data) + 10, self.language, firstCode, len(codes)) 8817842e56b97ce677b83bdab09cda48bc2d89ac75aJust return header + data 8827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8833a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 8840cd79a5642101821d66392e3bd0e3c445e97f09bjvr self.language = safeEval(attrs["language"]) 885d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 886d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 887d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 888d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 8897842e56b97ce677b83bdab09cda48bc2d89ac75aJust for element in content: 890b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 8917842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 8927842e56b97ce677b83bdab09cda48bc2d89ac75aJust name, attrs, content = element 893180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 8947842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 895d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 8967842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8977842e56b97ce677b83bdab09cda48bc2d89ac75aJust 89851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12_or_13(CmapSubtable): 899924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 900d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def __init__(self, format): 901d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = format 902d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = 0 903d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = None 904d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = None 905d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 906d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 907924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16]) 90851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader assert len(data) == (16 + nGroups*12) == (length), "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length) 909924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.format = format 910924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.reserved = reserved 911924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.length = length 912924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = language 913924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.nGroups = nGroups 914d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data[16:] 915d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.ttFont = ttFont 916d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 917d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 918d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 919d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 920d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 921d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 922d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 92317012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 924d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 925d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = self.data # decompileHeader assigns the data after the header to self.data 926d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCodes = [] 927d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 928d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos = 0 929d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for i in range(self.nGroups): 930d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] ) 931d299b55d14fa77411140c0cc1c2524583b4ffa58jvr pos += 12 932d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenGroup = 1 + endCharCode - startCharCode 93397dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod charCodes += list(range(startCharCode, endCharCode +1)) 93451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader gids += self._computeGIDs(glyphID, lenGroup) 935d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = data = None 936d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = cmap = {} 937d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCmap = len(gids) 938d299b55d14fa77411140c0cc1c2524583b4ffa58jvr glyphOrder = self.ttFont.getGlyphOrder() 939d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 940e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids )) 941d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except IndexError: 942d299b55d14fa77411140c0cc1c2524583b4ffa58jvr getGlyphName = self.ttFont.getGlyphName 943e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod names = list(map(getGlyphName, gids )) 944e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*lenCmap, charCodes, names)) 945924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 946924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def compile(self, ttFont): 947d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 94851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data 949c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod charCodes = list(self.cmap.keys()) 950d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lenCharCodes = len(charCodes) 951c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod names = list(self.cmap.values()) 952d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap() 953d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 954e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 955d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 956d299b55d14fa77411140c0cc1c2524583b4ffa58jvr nameMap = ttFont.getReverseGlyphMap(rebuild=1) 957d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 958e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) 959d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 960d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # allow virtual GIDs in format 12 tables 961d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids = [] 962d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for name in names: 963d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 964d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = nameMap[name] 965d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except KeyError: 966d299b55d14fa77411140c0cc1c2524583b4ffa58jvr try: 967d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if (name[:3] == 'gid'): 968d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = eval(name[3:]) 969d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 970d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gid = ttFont.getGlyphID(name) 971d299b55d14fa77411140c0cc1c2524583b4ffa58jvr except: 972d299b55d14fa77411140c0cc1c2524583b4ffa58jvr raise KeyError(name) 973d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 974d299b55d14fa77411140c0cc1c2524583b4ffa58jvr gids.append(gid) 975d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 976924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr cmap = {} # code:glyphID mapping 977e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) 978924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 979924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr charCodes.sort() 980d299b55d14fa77411140c0cc1c2524583b4ffa58jvr index = 0 981924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCodes[0] 982924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startGlyphID = cmap[startCharCode] 98351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader lastGlyphID = startGlyphID - self._format_step 984d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = startCharCode - 1 9850cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = 0 986d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList = [] 987d299b55d14fa77411140c0cc1c2524583b4ffa58jvr maxIndex = len(charCodes) 988d299b55d14fa77411140c0cc1c2524583b4ffa58jvr for index in range(maxIndex): 989d299b55d14fa77411140c0cc1c2524583b4ffa58jvr charCode = charCodes[index] 990924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr glyphID = cmap[charCode] 99151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode): 992d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 993924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr startCharCode = charCode 994d299b55d14fa77411140c0cc1c2524583b4ffa58jvr startGlyphID = glyphID 995924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr nGroups = nGroups + 1 996d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastGlyphID = glyphID 997d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lastCharCode = charCode 998d299b55d14fa77411140c0cc1c2524583b4ffa58jvr dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)) 9990cd79a5642101821d66392e3bd0e3c445e97f09bjvr nGroups = nGroups + 1 1000d299b55d14fa77411140c0cc1c2524583b4ffa58jvr data = "".join(dataList) 1001d299b55d14fa77411140c0cc1c2524583b4ffa58jvr lengthSubtable = len(data) +16 1002d299b55d14fa77411140c0cc1c2524583b4ffa58jvr assert len(data) == (nGroups*12) == (lengthSubtable-16) 1003d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data 1004924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1005924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr def toXML(self, writer, ttFont): 1006924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.begintag(self.__class__.__name__, [ 1007924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platformID", self.platformID), 1008924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("platEncID", self.platEncID), 1009924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("format", self.format), 1010924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("reserved", self.reserved), 1011924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("length", self.length), 1012924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("language", self.language), 1013924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ("nGroups", self.nGroups), 1014924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr ]) 1015924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1016ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod codes = sorted(self.cmap.items()) 1017a84b28d934fb697755823c62799f4b65e2b92237jvr self._writeCodes(codes, writer) 1018924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.endtag(self.__class__.__name__) 1019924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr writer.newline() 1020924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 10213a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1022d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.format = safeEval(attrs["format"]) 1023d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.reserved = safeEval(attrs["reserved"]) 1024d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.length = safeEval(attrs["length"]) 1025924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr self.language = safeEval(attrs["language"]) 1026d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.nGroups = safeEval(attrs["nGroups"]) 1027d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if not hasattr(self, "cmap"): 1028d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1029d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap = self.cmap 1030d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1031924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr for element in content: 1032b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 1033924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1034924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr name, attrs, content = element 1035180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 1036924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr continue 1037d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmap[safeEval(attrs["code"])] = attrs["name"] 1038924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 1039924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 104051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_12(cmap_format_12_or_13): 104151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 104251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 104351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 1 104451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 104697dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod return list(range(startingGlyph, startingGlyph + numberOfGlyphs)) 104751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 104851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 104951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode) 105051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournaderclass cmap_format_13(cmap_format_12_or_13): 105351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def __init__(self, format): 105451a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader cmap_format_12_or_13.__init__(self, format) 105551a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader self._format_step = 0 105651a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 105751a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _computeGIDs(self, startingGlyph, numberOfGlyphs): 105851a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return [startingGlyph] * numberOfGlyphs 105951a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106051a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode): 106151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode) 106251a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 106351a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 10640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtToUVS(threeByteString): 1065180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 10660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = "\0" +threeByteString 10670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = threeByteString + "\0" 10690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr val, = struct.unpack(">L", data) 10700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return val 10710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cvtFromUVS(val): 1073180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if sys.byteorder != "big": 10740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr threeByteString = struct.pack(">L", val)[1:] 10750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 10760cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr threeByteString = struct.pack(">L", val)[:3] 10770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return threeByteString 10780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrdef cmpUVSListEntry(first, second): 10800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv1, glyphName1 = first 10810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv2, glyphName2 = second 10820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if (glyphName1 == None) and (glyphName2 != None): 10840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return -1 10850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr elif (glyphName2 == None) and (glyphName1 != None): 10860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return 1 10870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10880cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ret = cmp(uv1, uv2) 10890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if ret: 10900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return ret 10910cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return cmp(glyphName1, glyphName2) 10920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvrclass cmap_format_14(CmapSubtable): 10950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 10960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompileHeader(self, data, ttFont): 10970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10]) 10980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = data[10:] 10990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = length 11000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = numVarSelectorRecords 11010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.ttFont = ttFont 11020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.language = 0xFF # has no language. 11030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def decompile(self, data, ttFont): 11057ab0cb0b09f67f69990c47944100010435cc3b6aBehdad Esfahbod if data != None and ttFont != None and ttFont.lazy: 11060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.decompileHeader(data, ttFont) 11070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 110817012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 11090cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = self.data 11100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = {} 11130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset = 0 11140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for n in range(self.numVarSelectorRecords): 11150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(">3sLL", data[recOffset:recOffset +11]) 11160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr recOffset += 11 11170cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varUVS = cvtToUVS(uvs) 11180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defOVSOffset: 11190cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = defOVSOffset - 10 11200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numValues, = struct.unpack(">L", data[startOffset:startOffset+4]) 11210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numValues): 11230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, addtlCnt = struct.unpack(">3sB", data[startOffset:startOffset+4]) 11240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 4 11250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr firstBaseUV = cvtToUVS(uv) 11260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = addtlCnt+1 112797dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod baseUVList = list(range(firstBaseUV, firstBaseUV+cnt)) 11280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphList = [None]*cnt 11290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = zip(baseUVList, glyphList) 11300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 1133fa5f2e85ab49c349468f5ae08f15163daa256a04Behdad Esfahbod uvsDict[varUVS] = list(localUVList) 11340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if nonDefUVSOffset: 11360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset = nonDefUVSOffset - 10 11370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numRecs, = struct.unpack(">L", data[startOffset:startOffset+4]) 11380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset +=4 11390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList = [] 11400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for r in range(numRecs): 11410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv, gid = struct.unpack(">3sH", data[startOffset:startOffset+5]) 11420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr startOffset += 5 11430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = cvtToUVS(uv) 11440cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr glyphName = self.ttFont.getGlyphName(gid) 11450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr localUVList.append( [uv, glyphName] ) 11460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS].extend(localUVList) 11480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 11490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[varUVS] = localUVList 11500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = uvsDict 11520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def toXML(self, writer, ttFont): 11540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.begintag(self.__class__.__name__, [ 11550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platformID", self.platformID), 11560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("platEncID", self.platEncID), 11570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("format", self.format), 11580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("length", self.length), 11590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ("numVarSelectorRecords", self.numVarSelectorRecords), 11600cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ]) 11610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1163ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 11640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 11650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvList = uvsDict[uvs] 11660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvList.sort(cmpUVSListEntry) 11670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in uvList: 11680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == None: 11690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = "None" 11700cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr # I use the arg rather than th keyword syntax in order to preserve the attribute order. 11710cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.simpletag("map", [ ("uvs",hex(uvs)), ("uv",hex(uv)), ("name", gname)] ) 11720cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11730cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.endtag(self.__class__.__name__) 11740cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr writer.newline() 11750cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11763a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 11770cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.format = safeEval(attrs["format"]) 11780cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = safeEval(attrs["length"]) 11790cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = safeEval(attrs["numVarSelectorRecords"]) 11800cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.language = 0xFF # provide a value so that CmapSubtable.__cmp__() won't fail 11810cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "cmap"): 11820cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail. 11830cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if not hasattr(self, "uvsDict"): 11840cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.uvsDict = {} 11850cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 11860cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 11870cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for element in content: 1188b774f9f684c5a0f91f5fa177c9a461968789123fBehdad Esfahbod if not isinstance(element, tuple): 11890cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11900cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr name, attrs, content = element 1191180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if name != "map": 11920cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr continue 11930cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvs = safeEval(attrs["uvs"]) 11940cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uv = safeEval(attrs["uv"]) 11950cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = attrs["name"] 11960cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if gname == "None": 11970cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gname = None 11980cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr try: 11990cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs].append( [uv, gname]) 12000cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr except KeyError: 12010cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict[uvs] = [ [uv, gname] ] 12020cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12030cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12040cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr def compile(self, ttFont): 12050cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if self.data: 12060cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) + self.data 12070cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12080cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr uvsDict = self.uvsDict 1209ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod uvsList = sorted(uvsDict.keys()) 12100cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.numVarSelectorRecords = len(uvsList) 12110cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset = 10 + self.numVarSelectorRecords*11 # current value is end of VarSelectorRecords block. 12120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = [] 12130cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords =[] 12140cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uvs in uvsList: 12150cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr entryList = uvsDict[uvs] 12160cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 121728aeabb08b2656cb240063865c37f192532badf5Behdad Esfahbod defList = [entry for entry in entryList if entry[1] == None] 12180cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if defList: 1219e5ca79699d00fdf7ac6eaceaed372aea8d6bc1fdBehdad Esfahbod defList = [entry[0] for entry in defList] 12200cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = offset 12210cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defList.sort() 12220cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12230cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defList[0] 12240cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = -1 12250cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs = [] 12260cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for defEntry in defList: 12270cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt +=1 12280cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if (lastUV+cnt) != defEntry: 12290cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt-1) 12300cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr lastUV = defEntry 12310cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12320cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr cnt = 0 12330cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12340cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt) 12350cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defRecs.append(rec) 12360cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12370cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numDefRecs = len(defRecs) 12380cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numDefRecs)) 12390cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.extend(defRecs) 12400cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numDefRecs*4 12410cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12420cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr defOVSOffset = 0 12430cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 124428aeabb08b2656cb240063865c37f192532badf5Behdad Esfahbod ndefList = [entry for entry in entryList if entry[1] != None] 12450cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr if ndefList: 12460cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = offset 12470cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndefList.sort() 12480cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr numNonDefRecs = len(ndefList) 12490cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(struct.pack(">L", numNonDefRecs)) 12500cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr offset += 4 + numNonDefRecs*5 12510cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12520cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr for uv, gname in ndefList: 12530cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr gid = ttFont.getGlyphID(gname) 12540cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid) 12550cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data.append(ndrec) 12560cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr else: 12570cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr nonDefUVSOffset = 0 12580cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12590cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset) 12600cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr varSelectorRecords.append(vrec) 12610cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12620cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr data = "".join(varSelectorRecords) + "".join(data) 12630cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.length = 10 + len(data) 12640cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) 12650cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr self.data = headerdata + data 12660cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12670cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr return self.data 12680cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12690cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 12707842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass cmap_format_unknown(CmapSubtable): 12717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1272a84b28d934fb697755823c62799f4b65e2b92237jvr def toXML(self, writer, ttFont): 1273d299b55d14fa77411140c0cc1c2524583b4ffa58jvr cmapName = self.__class__.__name__[:12] + str(self.format) 1274d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.begintag(cmapName, [ 1275a84b28d934fb697755823c62799f4b65e2b92237jvr ("platformID", self.platformID), 1276a84b28d934fb697755823c62799f4b65e2b92237jvr ("platEncID", self.platEncID), 1277a84b28d934fb697755823c62799f4b65e2b92237jvr ]) 1278a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1279d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.dumphex(self.data) 1280d299b55d14fa77411140c0cc1c2524583b4ffa58jvr writer.endtag(cmapName) 1281a84b28d934fb697755823c62799f4b65e2b92237jvr writer.newline() 1282a84b28d934fb697755823c62799f4b65e2b92237jvr 12833a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 1284d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.data = readHex(content) 1285d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.cmap = {} 1286d299b55d14fa77411140c0cc1c2524583b4ffa58jvr 1287d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompileHeader(self, data, ttFont): 1288427f9802bccf942f51567170e72a71ac14443c71jvr self.language = 0 # dummy value 12897842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.data = data 12907842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1291d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def decompile(self, data, ttFont): 1292d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None. 1293d299b55d14fa77411140c0cc1c2524583b4ffa58jvr # If not, someone is calling the subtable decompile() directly, and must provide both args. 1294d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if data != None and ttFont != None: 1295d299b55d14fa77411140c0cc1c2524583b4ffa58jvr self.decompileHeader(data[offset:offset+int(length)], ttFont) 1296d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 129717012aabbbc595da0e0c809fff09408bef6b758epabs assert (data == None and ttFont == None), "Need both data and ttFont arguments" 12987842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1299d299b55d14fa77411140c0cc1c2524583b4ffa58jvr def compile(self, ttFont): 1300d299b55d14fa77411140c0cc1c2524583b4ffa58jvr if self.data: 1301d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return self.data 1302d299b55d14fa77411140c0cc1c2524583b4ffa58jvr else: 1303d299b55d14fa77411140c0cc1c2524583b4ffa58jvr return None 13047842e56b97ce677b83bdab09cda48bc2d89ac75aJust 13057842e56b97ce677b83bdab09cda48bc2d89ac75aJustcmap_classes = { 13067842e56b97ce677b83bdab09cda48bc2d89ac75aJust 0: cmap_format_0, 13077842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2: cmap_format_2, 13087842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4: cmap_format_4, 13097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6: cmap_format_6, 1310924e4e274e18508a70b2a8b150f6e9be033d3a3ejvr 12: cmap_format_12, 131151a17826be4fb43d1f6ad5ada94207d8e18fc458Roozbeh Pournader 13: cmap_format_13, 13120cb8a08d89ecd3126aac5a528997bfd0d41e8b10jvr 14: cmap_format_14, 13137842e56b97ce677b83bdab09cda48bc2d89ac75aJust } 1314