sfnt.py revision 28ae1962292b66ad67117aef2a99d5735a70b779
17842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format. 27842e56b97ce677b83bdab09cda48bc2d89ac75aJust 37842e56b97ce677b83bdab09cda48bc2d89ac75aJustDefines two public classes: 47842e56b97ce677b83bdab09cda48bc2d89ac75aJust SFNTReader 57842e56b97ce677b83bdab09cda48bc2d89ac75aJust SFNTWriter 67842e56b97ce677b83bdab09cda48bc2d89ac75aJust 77842e56b97ce677b83bdab09cda48bc2d89ac75aJust(Normally you don't have to use these classes explicitly; they are 87842e56b97ce677b83bdab09cda48bc2d89ac75aJustused automatically by ttLib.TTFont.) 97842e56b97ce677b83bdab09cda48bc2d89ac75aJust 107842e56b97ce677b83bdab09cda48bc2d89ac75aJustThe reading and writing of sfnt files is separated in two distinct 117842e56b97ce677b83bdab09cda48bc2d89ac75aJustclasses, since whenever to number of tables changes or whenever 127842e56b97ce677b83bdab09cda48bc2d89ac75aJusta table's length chages you need to rewrite the whole file anyway. 137842e56b97ce677b83bdab09cda48bc2d89ac75aJust""" 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust 157842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport struct, sstruct 167842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport Numeric 177842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport os 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1904b3204dd168571635133d430a68175297282b1fjvr 207842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass SFNTReader: 217842e56b97ce677b83bdab09cda48bc2d89ac75aJust 22ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr def __init__(self, file, checkChecksums=1): 237842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file = file 24ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr self.checkChecksums = checkChecksums 257842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = self.file.read(sfntDirectorySize) 267842e56b97ce677b83bdab09cda48bc2d89ac75aJust if len(data) <> sfntDirectorySize: 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust from fontTools import ttLib 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust raise ttLib.TTLibError, "Not a TrueType or OpenType font (not enough data)" 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust sstruct.unpack(sfntDirectoryFormat, data, self) 307842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.sfntVersion not in ("\000\001\000\000", "OTTO", "true"): 317842e56b97ce677b83bdab09cda48bc2d89ac75aJust from fontTools import ttLib 327842e56b97ce677b83bdab09cda48bc2d89ac75aJust raise ttLib.TTLibError, "Not a TrueType or OpenType font (bad sfntVersion)" 337842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = {} 347842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(self.numTables): 357842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry = SFNTDirectoryEntry() 36ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr entry.fromFile(self.file) 37ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr if entry.length > 0: 38ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr self.tables[entry.tag] = entry 39ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr else: 40ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr # Ignore zero-length tables. This doesn't seem to be documented, 41ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr # yet it's apparently how the Windows TT rasterizer behaves. 42ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr # Besides, at least one font has been sighted which actually 43ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr # *has* a zero-length table. 44ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr pass 457842e56b97ce677b83bdab09cda48bc2d89ac75aJust 467842e56b97ce677b83bdab09cda48bc2d89ac75aJust def has_key(self, tag): 477842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.tables.has_key(tag) 487842e56b97ce677b83bdab09cda48bc2d89ac75aJust 497842e56b97ce677b83bdab09cda48bc2d89ac75aJust def keys(self): 507842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.tables.keys() 517842e56b97ce677b83bdab09cda48bc2d89ac75aJust 527842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __getitem__(self, tag): 537842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Fetch the raw table data.""" 547842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry = self.tables[tag] 557842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(entry.offset) 567842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = self.file.read(entry.length) 57ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr if self.checkChecksums: 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust if tag == 'head': 597842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Beh: we have to special-case the 'head' table. 60ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr checksum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:]) 617842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 62ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr checksum = calcChecksum(data) 63ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr if self.checkChecksums > 1: 647842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Be obnoxious, and barf when it's wrong 657842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert checksum == entry.checksum, "bad checksum for '%s' table" % tag 667842e56b97ce677b83bdab09cda48bc2d89ac75aJust elif checksum <> entry.checkSum: 677842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Be friendly, and just print a warning. 687842e56b97ce677b83bdab09cda48bc2d89ac75aJust print "bad checksum for '%s' table" % tag 697842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 707842e56b97ce677b83bdab09cda48bc2d89ac75aJust 71f70746325687393330f7a765814a0fe78f11f847jvr def __delitem__(self, tag): 72f70746325687393330f7a765814a0fe78f11f847jvr del self.tables[tag] 73f70746325687393330f7a765814a0fe78f11f847jvr 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust def close(self): 757842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.close() 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust 787842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass SFNTWriter: 797842e56b97ce677b83bdab09cda48bc2d89ac75aJust 807842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __init__(self, file, numTables, sfntVersion="\000\001\000\000"): 817842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file = file 827842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.numTables = numTables 837842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.sfntVersion = sfntVersion 84ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables) 857842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.nextTableOffset = sfntDirectorySize + numTables * sfntDirectoryEntrySize 867842e56b97ce677b83bdab09cda48bc2d89ac75aJust # clear out directory area 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(self.nextTableOffset) 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust # make sure we're actually where we want to be. (XXX old cStringIO bug) 897842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.write('\0' * (self.nextTableOffset - self.file.tell())) 907842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = {} 917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 927842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __setitem__(self, tag, data): 937842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Write raw table data to disk.""" 947842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.tables.has_key(tag): 957842e56b97ce677b83bdab09cda48bc2d89ac75aJust # We've written this table to file before. If the length 9604b3204dd168571635133d430a68175297282b1fjvr # of the data is still the same, we allow overwriting it. 977842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry = self.tables[tag] 987842e56b97ce677b83bdab09cda48bc2d89ac75aJust if len(data) <> entry.length: 997842e56b97ce677b83bdab09cda48bc2d89ac75aJust from fontTools import ttLib 1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust raise ttLib.TTLibError, "cannot rewrite '%s' table: length does not match directory entry" % tag 1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry = SFNTDirectoryEntry() 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry.tag = tag 1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry.offset = self.nextTableOffset 1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust entry.length = len(data) 1067842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.nextTableOffset = self.nextTableOffset + ((len(data) + 3) & ~3) 1077842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(entry.offset) 1087842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.write(data) 1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(self.nextTableOffset) 1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust # make sure we're actually where we want to be. (XXX old cStringIO bug) 1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.write('\0' * (self.nextTableOffset - self.file.tell())) 1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust if tag == 'head': 114ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr entry.checkSum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:]) 1157842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 116ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr entry.checkSum = calcChecksum(data) 1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables[tag] = entry 1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 11928ae1962292b66ad67117aef2a99d5735a70b779jvr def close(self): 1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust """All tables must have been written to disk. Now write the 1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust directory. 1227842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust tables = self.tables.items() 1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust tables.sort() 1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust if len(tables) <> self.numTables: 1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust from fontTools import ttLib 1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust raise ttLib.TTLibError, "wrong number of tables; expected %d, found %d" % (self.numTables, len(tables)) 1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust directory = sstruct.pack(sfntDirectoryFormat, self) 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(sfntDirectorySize) 132f509c0f0707a85184525243dfb6efeba043dc793jvr seenHead = 0 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust for tag, entry in tables: 134f509c0f0707a85184525243dfb6efeba043dc793jvr if tag == "head": 135f509c0f0707a85184525243dfb6efeba043dc793jvr seenHead = 1 136ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr directory = directory + entry.toString() 137f509c0f0707a85184525243dfb6efeba043dc793jvr if seenHead: 138f509c0f0707a85184525243dfb6efeba043dc793jvr self.calcMasterChecksum(directory) 1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(0) 1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.write(directory) 1417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 142ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr def calcMasterChecksum(self, directory): 1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust # calculate checkSumAdjustment 1447842e56b97ce677b83bdab09cda48bc2d89ac75aJust tags = self.tables.keys() 1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust checksums = Numeric.zeros(len(tags)+1) 1467842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(tags)): 1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust checksums[i] = self.tables[tags[i]].checkSum 1487842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize 1507842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert directory_end == len(directory) 1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust 152ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr checksums[-1] = calcChecksum(directory) 1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust checksum = Numeric.add.reduce(checksums) 1547842e56b97ce677b83bdab09cda48bc2d89ac75aJust # BiboAfba! 15502e76e905f08c0dcb2fb36bd5566fb86da13a906jvr checksumadjustment = Numeric.array(0xb1b0afbaL - 0x100000000L, 15602e76e905f08c0dcb2fb36bd5566fb86da13a906jvr Numeric.Int32) - checksum 1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust # write the checksum to the file 1587842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.file.seek(self.tables['head'].offset + 8) 15958629637261cbc9ee7f536b040ce960882aa907ejvr self.file.write(struct.pack(">l", checksumadjustment)) 1607842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust# -- sfnt directory helpers and cruft 1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1647842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryFormat = """ 1657842e56b97ce677b83bdab09cda48bc2d89ac75aJust > # big endian 166b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr sfntVersion: 4s 167b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr numTables: H # number of tables 168b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr searchRange: H # (max2 <= numTables)*16 169b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr entrySelector: H # log2(max2 <= numTables) 170b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr rangeShift: H # numTables*16-searchRange 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust""" 1727842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1737842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat) 1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1757842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryEntryFormat = """ 1767842e56b97ce677b83bdab09cda48bc2d89ac75aJust > # big endian 177b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr tag: 4s 178b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr checkSum: l 179b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr offset: l 180b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr length: l 1817842e56b97ce677b83bdab09cda48bc2d89ac75aJust""" 1827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1837842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat) 1847842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1857842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass SFNTDirectoryEntry: 1867842e56b97ce677b83bdab09cda48bc2d89ac75aJust 187ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr def fromFile(self, file): 1887842e56b97ce677b83bdab09cda48bc2d89ac75aJust sstruct.unpack(sfntDirectoryEntryFormat, 1897842e56b97ce677b83bdab09cda48bc2d89ac75aJust file.read(sfntDirectoryEntrySize), self) 1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust 191ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr def fromString(self, str): 1927842e56b97ce677b83bdab09cda48bc2d89ac75aJust sstruct.unpack(sfntDirectoryEntryFormat, str, self) 1937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 194ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr def toString(self): 1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust return sstruct.pack(sfntDirectoryEntryFormat, self) 1967842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1977842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __repr__(self): 1987842e56b97ce677b83bdab09cda48bc2d89ac75aJust if hasattr(self, "tag"): 1997842e56b97ce677b83bdab09cda48bc2d89ac75aJust return "<SFNTDirectoryEntry '%s' at %x>" % (self.tag, id(self)) 2007842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust return "<SFNTDirectoryEntry at %x>" % id(self) 2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2037842e56b97ce677b83bdab09cda48bc2d89ac75aJust 204ea9dfa9fb28966175bf2275d20aeb62c3040c86djvrdef calcChecksum(data, start=0): 2057842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Calculate the checksum for an arbitrary block of data. 2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust Optionally takes a 'start' argument, which allows you to 2077842e56b97ce677b83bdab09cda48bc2d89ac75aJust calculate a checksum in chunks by feeding it a previous 2087842e56b97ce677b83bdab09cda48bc2d89ac75aJust result. 2097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2107842e56b97ce677b83bdab09cda48bc2d89ac75aJust If the data length is not a multiple of four, it assumes 2117842e56b97ce677b83bdab09cda48bc2d89ac75aJust it is to be padded with null byte. 2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 2137842e56b97ce677b83bdab09cda48bc2d89ac75aJust from fontTools import ttLib 2147842e56b97ce677b83bdab09cda48bc2d89ac75aJust remainder = len(data) % 4 2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust if remainder: 2167842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = data + '\0' * (4-remainder) 2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust a = Numeric.fromstring(struct.pack(">l", start) + data, Numeric.Int32) 2187842e56b97ce677b83bdab09cda48bc2d89ac75aJust if ttLib.endian <> "big": 2197842e56b97ce677b83bdab09cda48bc2d89ac75aJust a = a.byteswapped() 2207842e56b97ce677b83bdab09cda48bc2d89ac75aJust return Numeric.add.reduce(a) 2217842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2227842e56b97ce677b83bdab09cda48bc2d89ac75aJust 223ea9dfa9fb28966175bf2275d20aeb62c3040c86djvrdef maxPowerOfTwo(x): 2247842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Return the highest exponent of two, so that 2257842e56b97ce677b83bdab09cda48bc2d89ac75aJust (2 ** exponent) <= x 2267842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 2277842e56b97ce677b83bdab09cda48bc2d89ac75aJust exponent = 0 2287842e56b97ce677b83bdab09cda48bc2d89ac75aJust while x: 2297842e56b97ce677b83bdab09cda48bc2d89ac75aJust x = x >> 1 2307842e56b97ce677b83bdab09cda48bc2d89ac75aJust exponent = exponent + 1 231fdea99d2655f4d22542f94a8499e5a3308607776Just return max(exponent - 1, 0) 2327842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2337842e56b97ce677b83bdab09cda48bc2d89ac75aJust 234ea9dfa9fb28966175bf2275d20aeb62c3040c86djvrdef getSearchRange(n): 2357842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Calculate searchRange, entrySelector, rangeShift for the 2367842e56b97ce677b83bdab09cda48bc2d89ac75aJust sfnt directory. 'n' is the number of tables. 2377842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 2387842e56b97ce677b83bdab09cda48bc2d89ac75aJust # This stuff needs to be stored in the file, because? 2397842e56b97ce677b83bdab09cda48bc2d89ac75aJust import math 240ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr exponent = maxPowerOfTwo(n) 2417842e56b97ce677b83bdab09cda48bc2d89ac75aJust searchRange = (2 ** exponent) * 16 2427842e56b97ce677b83bdab09cda48bc2d89ac75aJust entrySelector = exponent 2437842e56b97ce677b83bdab09cda48bc2d89ac75aJust rangeShift = n * 16 - searchRange 2447842e56b97ce677b83bdab09cda48bc2d89ac75aJust return searchRange, entrySelector, rangeShift 2457842e56b97ce677b83bdab09cda48bc2d89ac75aJust 246