blob: 035e51a38608bc2e90ef6e6a966bd379daa7423f [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format.
2
3Defines two public classes:
4 SFNTReader
5 SFNTWriter
6
7(Normally you don't have to use these classes explicitly; they are
8used automatically by ttLib.TTFont.)
9
10The reading and writing of sfnt files is separated in two distinct
11classes, since whenever to number of tables changes or whenever
12a table's length chages you need to rewrite the whole file anyway.
13"""
14
jvr9be387c2008-03-01 11:43:01 +000015import sys
Behdad Esfahbod8413c102013-09-17 16:59:39 -040016import struct
17from fontTools.misc import sstruct
Just7842e561999-12-16 21:34:53 +000018import os
19
jvr04b32042002-05-14 12:09:10 +000020
Just7842e561999-12-16 21:34:53 +000021class SFNTReader:
22
pabs37e91e772009-02-22 08:55:00 +000023 def __init__(self, file, checkChecksums=1, fontNumber=-1):
Just7842e561999-12-16 21:34:53 +000024 self.file = file
jvrea9dfa92002-05-12 17:14:50 +000025 self.checkChecksums = checkChecksums
Behdad Esfahbod58d74162013-08-15 15:30:55 -040026
27 self.flavor = None
28 self.flavorData = None
29 self.DirectoryEntry = SFNTDirectoryEntry
30 self.sfntVersion = self.file.read(4)
31 self.file.seek(0)
pabs37e91e772009-02-22 08:55:00 +000032 if self.sfntVersion == "ttcf":
Behdad Esfahbod58d74162013-08-15 15:30:55 -040033 sstruct.unpack(ttcHeaderFormat, self.file.read(ttcHeaderSize), self)
pabs37e91e772009-02-22 08:55:00 +000034 assert self.Version == 0x00010000 or self.Version == 0x00020000, "unrecognized TTC version 0x%08x" % self.Version
35 if not 0 <= fontNumber < self.numFonts:
36 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050037 raise ttLib.TTLibError("specify a font number between 0 and %d (inclusive)" % (self.numFonts - 1))
pabs37e91e772009-02-22 08:55:00 +000038 offsetTable = struct.unpack(">%dL" % self.numFonts, self.file.read(self.numFonts * 4))
39 if self.Version == 0x00020000:
40 pass # ignoring version 2.0 signatures
41 self.file.seek(offsetTable[fontNumber])
Behdad Esfahbod58d74162013-08-15 15:30:55 -040042 sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
43 elif self.sfntVersion == "wOFF":
44 self.flavor = "woff"
45 self.DirectoryEntry = WOFFDirectoryEntry
46 sstruct.unpack(woffDirectoryFormat, self.file.read(woffDirectorySize), self)
47 else:
48 sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
49
Just7842e561999-12-16 21:34:53 +000050 if self.sfntVersion not in ("\000\001\000\000", "OTTO", "true"):
51 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050052 raise ttLib.TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
Just7842e561999-12-16 21:34:53 +000053 self.tables = {}
54 for i in range(self.numTables):
Behdad Esfahbod58d74162013-08-15 15:30:55 -040055 entry = self.DirectoryEntry()
jvrea9dfa92002-05-12 17:14:50 +000056 entry.fromFile(self.file)
jvrce1d50a2002-05-12 17:02:50 +000057 if entry.length > 0:
58 self.tables[entry.tag] = entry
59 else:
60 # Ignore zero-length tables. This doesn't seem to be documented,
61 # yet it's apparently how the Windows TT rasterizer behaves.
62 # Besides, at least one font has been sighted which actually
63 # *has* a zero-length table.
64 pass
Behdad Esfahbod58d74162013-08-15 15:30:55 -040065
66 # Load flavor data if any
67 if self.flavor == "woff":
68 self.flavorData = WOFFFlavorData(self)
69
Just7842e561999-12-16 21:34:53 +000070 def has_key(self, tag):
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050071 return tag in self.tables
72
73 __contains__ = has_key
Just7842e561999-12-16 21:34:53 +000074
75 def keys(self):
76 return self.tables.keys()
77
78 def __getitem__(self, tag):
79 """Fetch the raw table data."""
80 entry = self.tables[tag]
Behdad Esfahbod58d74162013-08-15 15:30:55 -040081 data = entry.loadData (self.file)
jvrea9dfa92002-05-12 17:14:50 +000082 if self.checkChecksums:
Just7842e561999-12-16 21:34:53 +000083 if tag == 'head':
84 # Beh: we have to special-case the 'head' table.
jvrea9dfa92002-05-12 17:14:50 +000085 checksum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:])
Just7842e561999-12-16 21:34:53 +000086 else:
jvrea9dfa92002-05-12 17:14:50 +000087 checksum = calcChecksum(data)
88 if self.checkChecksums > 1:
Just7842e561999-12-16 21:34:53 +000089 # Be obnoxious, and barf when it's wrong
90 assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
Behdad Esfahbod180ace62013-11-27 02:40:30 -050091 elif checksum != entry.checkSum:
Just7842e561999-12-16 21:34:53 +000092 # Be friendly, and just print a warning.
93 print "bad checksum for '%s' table" % tag
94 return data
95
jvrf7074632002-05-04 22:04:02 +000096 def __delitem__(self, tag):
97 del self.tables[tag]
98
Just7842e561999-12-16 21:34:53 +000099 def close(self):
100 self.file.close()
101
102
103class SFNTWriter:
104
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400105 def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
106 flavor=None, flavorData=None):
Just7842e561999-12-16 21:34:53 +0000107 self.file = file
108 self.numTables = numTables
109 self.sfntVersion = sfntVersion
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400110 self.flavor = flavor
111 self.flavorData = flavorData
112
113 if self.flavor == "woff":
114 self.directoryFormat = woffDirectoryFormat
115 self.directorySize = woffDirectorySize
116 self.DirectoryEntry = WOFFDirectoryEntry
117
118 self.signature = "wOFF"
119 else:
120 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
121 self.directoryFormat = sfntDirectoryFormat
122 self.directorySize = sfntDirectorySize
123 self.DirectoryEntry = SFNTDirectoryEntry
124
125 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables)
126
127 self.nextTableOffset = self.directorySize + numTables * self.DirectoryEntry.formatSize
Just7842e561999-12-16 21:34:53 +0000128 # clear out directory area
129 self.file.seek(self.nextTableOffset)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400130 # make sure we're actually where we want to be. (old cStringIO bug)
Just7842e561999-12-16 21:34:53 +0000131 self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
132 self.tables = {}
133
134 def __setitem__(self, tag, data):
135 """Write raw table data to disk."""
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400136 reuse = False
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500137 if tag in self.tables:
Just7842e561999-12-16 21:34:53 +0000138 # We've written this table to file before. If the length
jvr04b32042002-05-14 12:09:10 +0000139 # of the data is still the same, we allow overwriting it.
Just7842e561999-12-16 21:34:53 +0000140 entry = self.tables[tag]
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400141 assert not hasattr(entry.__class__, 'encodeData')
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500142 if len(data) != entry.length:
Just7842e561999-12-16 21:34:53 +0000143 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500144 raise ttLib.TTLibError("cannot rewrite '%s' table: length does not match directory entry" % tag)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400145 reuse = True
Just7842e561999-12-16 21:34:53 +0000146 else:
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400147 entry = self.DirectoryEntry()
Just7842e561999-12-16 21:34:53 +0000148 entry.tag = tag
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400149
150 if tag == 'head':
151 entry.checkSum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:])
152 self.headTable = data
153 entry.uncompressed = True
154 else:
155 entry.checkSum = calcChecksum(data)
156
157 entry.offset = self.nextTableOffset
158 entry.saveData (self.file, data)
159
160 if not reuse:
161 self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
162
jvrc63ac642008-06-17 20:41:15 +0000163 # Add NUL bytes to pad the table data to a 4-byte boundary.
164 # Don't depend on f.seek() as we need to add the padding even if no
165 # subsequent write follows (seek is lazy), ie. after the final table
166 # in the font.
Just7842e561999-12-16 21:34:53 +0000167 self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
jvrc63ac642008-06-17 20:41:15 +0000168 assert self.nextTableOffset == self.file.tell()
Just7842e561999-12-16 21:34:53 +0000169
Just7842e561999-12-16 21:34:53 +0000170 self.tables[tag] = entry
171
jvr28ae1962004-11-16 10:37:59 +0000172 def close(self):
Just7842e561999-12-16 21:34:53 +0000173 """All tables must have been written to disk. Now write the
174 directory.
175 """
176 tables = self.tables.items()
177 tables.sort()
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500178 if len(tables) != self.numTables:
Just7842e561999-12-16 21:34:53 +0000179 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500180 raise ttLib.TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(tables)))
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400181
182 if self.flavor == "woff":
183 self.signature = "wOFF"
184 self.reserved = 0
185
186 self.totalSfntSize = 12
187 self.totalSfntSize += 16 * len(tables)
188 for tag, entry in tables:
189 self.totalSfntSize += (entry.origLength + 3) & ~3
190
191 data = self.flavorData if self.flavorData else WOFFFlavorData()
192 if data.majorVersion != None and data.minorVersion != None:
193 self.majorVersion = data.majorVersion
194 self.minorVersion = data.minorVersion
195 else:
196 if hasattr(self, 'headTable'):
197 self.majorVersion, self.minorVersion = struct.unpack(">HH", self.headTable[4:8])
198 else:
199 self.majorVersion = self.minorVersion = 0
200 if data.metaData:
201 self.metaOrigLength = len(data.metaData)
202 self.file.seek(0,2)
203 self.metaOffset = self.file.tell()
204 compressedMetaData = zlib.compress(data.metaData)
205 self.metaLength = len(compressedMetaData)
206 self.file.write(compressedMetaData)
207 else:
208 self.metaOffset = self.metaLength = self.metaOrigLength = 0
209 if data.privData:
210 self.file.seek(0,2)
211 off = self.file.tell()
212 paddedOff = (off + 3) & ~3
213 self.file.write('\0' * (paddedOff - off))
214 self.privOffset = self.file.tell()
215 self.privLength = len(data.privData)
216 self.file.write(data.privData)
217 else:
218 self.privOffset = self.privLength = 0
219
220 self.file.seek(0,2)
221 self.length = self.file.tell()
222
223 else:
224 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
225 pass
Just7842e561999-12-16 21:34:53 +0000226
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400227 directory = sstruct.pack(self.directoryFormat, self)
Just7842e561999-12-16 21:34:53 +0000228
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400229 self.file.seek(self.directorySize)
jvrf509c0f2003-08-22 19:38:37 +0000230 seenHead = 0
Just7842e561999-12-16 21:34:53 +0000231 for tag, entry in tables:
jvrf509c0f2003-08-22 19:38:37 +0000232 if tag == "head":
233 seenHead = 1
jvrea9dfa92002-05-12 17:14:50 +0000234 directory = directory + entry.toString()
jvrf509c0f2003-08-22 19:38:37 +0000235 if seenHead:
jvr91bca422012-10-18 12:49:22 +0000236 self.writeMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000237 self.file.seek(0)
238 self.file.write(directory)
jvr91bca422012-10-18 12:49:22 +0000239
240 def _calcMasterChecksum(self, directory):
Just7842e561999-12-16 21:34:53 +0000241 # calculate checkSumAdjustment
242 tags = self.tables.keys()
jvr91bca422012-10-18 12:49:22 +0000243 checksums = []
Just7842e561999-12-16 21:34:53 +0000244 for i in range(len(tags)):
jvr91bca422012-10-18 12:49:22 +0000245 checksums.append(self.tables[tags[i]].checkSum)
246
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400247 # TODO(behdad) I'm fairly sure the checksum for woff is not working correctly.
248 # Haven't debugged.
249 if self.DirectoryEntry != SFNTDirectoryEntry:
250 # Create a SFNT directory for checksum calculation purposes
251 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables)
252 directory = sstruct.pack(sfntDirectoryFormat, self)
253 tables = self.tables.items()
254 tables.sort()
255 for tag, entry in tables:
256 sfntEntry = SFNTDirectoryEntry()
257 for item in ['tag', 'checkSum', 'offset', 'length']:
258 setattr(sfntEntry, item, getattr(entry, item))
259 directory = directory + sfntEntry.toString()
260
Just7842e561999-12-16 21:34:53 +0000261 directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
262 assert directory_end == len(directory)
jvr91bca422012-10-18 12:49:22 +0000263
264 checksums.append(calcChecksum(directory))
265 checksum = sum(checksums) & 0xffffffff
Just7842e561999-12-16 21:34:53 +0000266 # BiboAfba!
jvr91bca422012-10-18 12:49:22 +0000267 checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
268 return checksumadjustment
269
270 def writeMasterChecksum(self, directory):
271 checksumadjustment = self._calcMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000272 # write the checksum to the file
273 self.file.seek(self.tables['head'].offset + 8)
pabs30e2aece2009-03-24 09:42:15 +0000274 self.file.write(struct.pack(">L", checksumadjustment))
jvr1ebda672008-03-08 20:29:30 +0000275
Just7842e561999-12-16 21:34:53 +0000276
277# -- sfnt directory helpers and cruft
278
pabs37e91e772009-02-22 08:55:00 +0000279ttcHeaderFormat = """
280 > # big endian
281 TTCTag: 4s # "ttcf"
282 Version: L # 0x00010000 or 0x00020000
283 numFonts: L # number of fonts
284 # OffsetTable[numFonts]: L # array with offsets from beginning of file
285 # ulDsigTag: L # version 2.0 only
286 # ulDsigLength: L # version 2.0 only
287 # ulDsigOffset: L # version 2.0 only
288"""
289
290ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
291
Just7842e561999-12-16 21:34:53 +0000292sfntDirectoryFormat = """
293 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000294 sfntVersion: 4s
295 numTables: H # number of tables
296 searchRange: H # (max2 <= numTables)*16
297 entrySelector: H # log2(max2 <= numTables)
298 rangeShift: H # numTables*16-searchRange
Just7842e561999-12-16 21:34:53 +0000299"""
300
301sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
302
303sfntDirectoryEntryFormat = """
304 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000305 tag: 4s
pabs30e2aece2009-03-24 09:42:15 +0000306 checkSum: L
307 offset: L
308 length: L
Just7842e561999-12-16 21:34:53 +0000309"""
310
311sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
312
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400313woffDirectoryFormat = """
314 > # big endian
315 signature: 4s # "wOFF"
316 sfntVersion: 4s
317 length: L # total woff file size
318 numTables: H # number of tables
319 reserved: H # set to 0
320 totalSfntSize: L # uncompressed size
321 majorVersion: H # major version of WOFF file
322 minorVersion: H # minor version of WOFF file
323 metaOffset: L # offset to metadata block
324 metaLength: L # length of compressed metadata
325 metaOrigLength: L # length of uncompressed metadata
326 privOffset: L # offset to private data block
327 privLength: L # length of private data block
328"""
329
330woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
331
332woffDirectoryEntryFormat = """
333 > # big endian
334 tag: 4s
335 offset: L
336 length: L # compressed length
337 origLength: L # original length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400338 checkSum: L # original checksum
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400339"""
340
341woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
342
343
344class DirectoryEntry:
Just7842e561999-12-16 21:34:53 +0000345
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400346 def __init__(self):
347 self.uncompressed = False # if True, always embed entry raw
348
jvrea9dfa92002-05-12 17:14:50 +0000349 def fromFile(self, file):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400350 sstruct.unpack(self.format, file.read(self.formatSize), self)
Just7842e561999-12-16 21:34:53 +0000351
jvrea9dfa92002-05-12 17:14:50 +0000352 def fromString(self, str):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400353 sstruct.unpack(self.format, str, self)
Just7842e561999-12-16 21:34:53 +0000354
jvrea9dfa92002-05-12 17:14:50 +0000355 def toString(self):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400356 return sstruct.pack(self.format, self)
Just7842e561999-12-16 21:34:53 +0000357
358 def __repr__(self):
359 if hasattr(self, "tag"):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400360 return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
Just7842e561999-12-16 21:34:53 +0000361 else:
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400362 return "<%s at %x>" % (self.__class__.__name__, id(self))
363
364 def loadData(self, file):
365 file.seek(self.offset)
366 data = file.read(self.length)
367 assert len(data) == self.length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400368 if hasattr(self.__class__, 'decodeData'):
369 data = self.decodeData(data)
370 return data
371
372 def saveData(self, file, data):
373 if hasattr(self.__class__, 'encodeData'):
374 data = self.encodeData(data)
375 self.length = len(data)
376 file.seek(self.offset)
377 file.write(data)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400378
379 def decodeData(self, rawData):
380 return rawData
381
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400382 def encodeData(self, data):
383 return data
384
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400385class SFNTDirectoryEntry(DirectoryEntry):
386
387 format = sfntDirectoryEntryFormat
388 formatSize = sfntDirectoryEntrySize
389
390class WOFFDirectoryEntry(DirectoryEntry):
391
392 format = woffDirectoryEntryFormat
393 formatSize = woffDirectoryEntrySize
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400394 zlibCompressionLevel = 6
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400395
396 def decodeData(self, rawData):
397 import zlib
398 if self.length == self.origLength:
399 data = rawData
400 else:
401 assert self.length < self.origLength
402 data = zlib.decompress(rawData)
403 assert len (data) == self.origLength
404 return data
405
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400406 def encodeData(self, data):
407 import zlib
408 self.origLength = len(data)
409 if not self.uncompressed:
410 compressedData = zlib.compress(data, self.zlibCompressionLevel)
411 if self.uncompressed or len(compressedData) >= self.origLength:
412 # Encode uncompressed
413 rawData = data
414 self.length = self.origLength
415 else:
416 rawData = compressedData
417 self.length = len(rawData)
418 return rawData
419
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400420class WOFFFlavorData():
421
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400422 Flavor = 'woff'
423
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400424 def __init__(self, reader=None):
425 self.majorVersion = None
426 self.minorVersion = None
427 self.metaData = None
428 self.privData = None
429 if reader:
430 self.majorVersion = reader.majorVersion
431 self.minorVersion = reader.minorVersion
432 if reader.metaLength:
433 reader.file.seek(reader.metaOffset)
434 rawData = read.file.read(reader.metaLength)
435 assert len(rawData) == reader.metaLength
436 data = zlib.decompress(rawData)
437 assert len(data) == reader.metaOrigLength
438 self.metaData = data
439 if reader.privLength:
440 reader.file.seek(reader.privOffset)
441 data = read.file.read(reader.privLength)
442 assert len(data) == reader.privLength
443 self.privData = data
Just7842e561999-12-16 21:34:53 +0000444
445
jvr91bca422012-10-18 12:49:22 +0000446def calcChecksum(data):
Just7842e561999-12-16 21:34:53 +0000447 """Calculate the checksum for an arbitrary block of data.
448 Optionally takes a 'start' argument, which allows you to
449 calculate a checksum in chunks by feeding it a previous
450 result.
451
452 If the data length is not a multiple of four, it assumes
453 it is to be padded with null byte.
jvr91bca422012-10-18 12:49:22 +0000454
455 >>> print calcChecksum("abcd")
456 1633837924
457 >>> print calcChecksum("abcdxyz")
458 3655064932
Just7842e561999-12-16 21:34:53 +0000459 """
Just7842e561999-12-16 21:34:53 +0000460 remainder = len(data) % 4
461 if remainder:
jvr91bca422012-10-18 12:49:22 +0000462 data += "\0" * (4 - remainder)
463 value = 0
464 blockSize = 4096
465 assert blockSize % 4 == 0
466 for i in xrange(0, len(data), blockSize):
467 block = data[i:i+blockSize]
468 longs = struct.unpack(">%dL" % (len(block) // 4), block)
469 value = (value + sum(longs)) & 0xffffffff
470 return value
Just7842e561999-12-16 21:34:53 +0000471
472
jvrea9dfa92002-05-12 17:14:50 +0000473def maxPowerOfTwo(x):
Just7842e561999-12-16 21:34:53 +0000474 """Return the highest exponent of two, so that
475 (2 ** exponent) <= x
476 """
477 exponent = 0
478 while x:
479 x = x >> 1
480 exponent = exponent + 1
Justfdea99d2000-08-23 12:34:44 +0000481 return max(exponent - 1, 0)
Just7842e561999-12-16 21:34:53 +0000482
483
jvrea9dfa92002-05-12 17:14:50 +0000484def getSearchRange(n):
Just7842e561999-12-16 21:34:53 +0000485 """Calculate searchRange, entrySelector, rangeShift for the
486 sfnt directory. 'n' is the number of tables.
487 """
488 # This stuff needs to be stored in the file, because?
489 import math
jvrea9dfa92002-05-12 17:14:50 +0000490 exponent = maxPowerOfTwo(n)
Just7842e561999-12-16 21:34:53 +0000491 searchRange = (2 ** exponent) * 16
492 entrySelector = exponent
493 rangeShift = n * 16 - searchRange
494 return searchRange, entrySelector, rangeShift
495
jvr91bca422012-10-18 12:49:22 +0000496
497if __name__ == "__main__":
498 import doctest
499 doctest.testmod()