blob: a194d774a7ff4bf93e934e2438067017cf32f1a9 [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
Behdad Esfahbod8413c102013-09-17 16:59:39 -040015import struct
Behdad Esfahbodbb0beb72013-11-27 14:37:28 -050016from fontTools.misc import sstruct
Behdad Esfahbod7ed91ec2013-11-27 15:16:28 -050017from fontTools.misc.py23 import *
Just7842e561999-12-16 21:34:53 +000018
jvr04b32042002-05-14 12:09:10 +000019
Just7842e561999-12-16 21:34:53 +000020class SFNTReader:
21
pabs37e91e772009-02-22 08:55:00 +000022 def __init__(self, file, checkChecksums=1, fontNumber=-1):
Just7842e561999-12-16 21:34:53 +000023 self.file = file
jvrea9dfa92002-05-12 17:14:50 +000024 self.checkChecksums = checkChecksums
Behdad Esfahbod58d74162013-08-15 15:30:55 -040025
26 self.flavor = None
27 self.flavorData = None
28 self.DirectoryEntry = SFNTDirectoryEntry
29 self.sfntVersion = self.file.read(4)
30 self.file.seek(0)
pabs37e91e772009-02-22 08:55:00 +000031 if self.sfntVersion == "ttcf":
Behdad Esfahbod58d74162013-08-15 15:30:55 -040032 sstruct.unpack(ttcHeaderFormat, self.file.read(ttcHeaderSize), self)
pabs37e91e772009-02-22 08:55:00 +000033 assert self.Version == 0x00010000 or self.Version == 0x00020000, "unrecognized TTC version 0x%08x" % self.Version
34 if not 0 <= fontNumber < self.numFonts:
35 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050036 raise ttLib.TTLibError("specify a font number between 0 and %d (inclusive)" % (self.numFonts - 1))
pabs37e91e772009-02-22 08:55:00 +000037 offsetTable = struct.unpack(">%dL" % self.numFonts, self.file.read(self.numFonts * 4))
38 if self.Version == 0x00020000:
39 pass # ignoring version 2.0 signatures
40 self.file.seek(offsetTable[fontNumber])
Behdad Esfahbod58d74162013-08-15 15:30:55 -040041 sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
42 elif self.sfntVersion == "wOFF":
43 self.flavor = "woff"
44 self.DirectoryEntry = WOFFDirectoryEntry
45 sstruct.unpack(woffDirectoryFormat, self.file.read(woffDirectorySize), self)
46 else:
47 sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
48
Just7842e561999-12-16 21:34:53 +000049 if self.sfntVersion not in ("\000\001\000\000", "OTTO", "true"):
50 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050051 raise ttLib.TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
Just7842e561999-12-16 21:34:53 +000052 self.tables = {}
53 for i in range(self.numTables):
Behdad Esfahbod58d74162013-08-15 15:30:55 -040054 entry = self.DirectoryEntry()
jvrea9dfa92002-05-12 17:14:50 +000055 entry.fromFile(self.file)
jvrce1d50a2002-05-12 17:02:50 +000056 if entry.length > 0:
57 self.tables[entry.tag] = entry
58 else:
59 # Ignore zero-length tables. This doesn't seem to be documented,
60 # yet it's apparently how the Windows TT rasterizer behaves.
61 # Besides, at least one font has been sighted which actually
62 # *has* a zero-length table.
63 pass
Behdad Esfahbod58d74162013-08-15 15:30:55 -040064
65 # Load flavor data if any
66 if self.flavor == "woff":
67 self.flavorData = WOFFFlavorData(self)
68
Just7842e561999-12-16 21:34:53 +000069 def has_key(self, tag):
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050070 return tag in self.tables
71
72 __contains__ = has_key
Just7842e561999-12-16 21:34:53 +000073
74 def keys(self):
75 return self.tables.keys()
76
77 def __getitem__(self, tag):
78 """Fetch the raw table data."""
79 entry = self.tables[tag]
Behdad Esfahbod58d74162013-08-15 15:30:55 -040080 data = entry.loadData (self.file)
jvrea9dfa92002-05-12 17:14:50 +000081 if self.checkChecksums:
Just7842e561999-12-16 21:34:53 +000082 if tag == 'head':
83 # Beh: we have to special-case the 'head' table.
jvrea9dfa92002-05-12 17:14:50 +000084 checksum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:])
Just7842e561999-12-16 21:34:53 +000085 else:
jvrea9dfa92002-05-12 17:14:50 +000086 checksum = calcChecksum(data)
87 if self.checkChecksums > 1:
Just7842e561999-12-16 21:34:53 +000088 # Be obnoxious, and barf when it's wrong
89 assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
Behdad Esfahbod180ace62013-11-27 02:40:30 -050090 elif checksum != entry.checkSum:
Just7842e561999-12-16 21:34:53 +000091 # Be friendly, and just print a warning.
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -050092 print("bad checksum for '%s' table" % tag)
Just7842e561999-12-16 21:34:53 +000093 return data
94
jvrf7074632002-05-04 22:04:02 +000095 def __delitem__(self, tag):
96 del self.tables[tag]
97
Just7842e561999-12-16 21:34:53 +000098 def close(self):
99 self.file.close()
100
101
102class SFNTWriter:
103
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400104 def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
105 flavor=None, flavorData=None):
Just7842e561999-12-16 21:34:53 +0000106 self.file = file
107 self.numTables = numTables
108 self.sfntVersion = sfntVersion
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400109 self.flavor = flavor
110 self.flavorData = flavorData
111
112 if self.flavor == "woff":
113 self.directoryFormat = woffDirectoryFormat
114 self.directorySize = woffDirectorySize
115 self.DirectoryEntry = WOFFDirectoryEntry
116
117 self.signature = "wOFF"
118 else:
119 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
120 self.directoryFormat = sfntDirectoryFormat
121 self.directorySize = sfntDirectorySize
122 self.DirectoryEntry = SFNTDirectoryEntry
123
124 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables)
125
126 self.nextTableOffset = self.directorySize + numTables * self.DirectoryEntry.formatSize
Just7842e561999-12-16 21:34:53 +0000127 # clear out directory area
128 self.file.seek(self.nextTableOffset)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400129 # make sure we're actually where we want to be. (old cStringIO bug)
Just7842e561999-12-16 21:34:53 +0000130 self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
131 self.tables = {}
132
133 def __setitem__(self, tag, data):
134 """Write raw table data to disk."""
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400135 reuse = False
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500136 if tag in self.tables:
Just7842e561999-12-16 21:34:53 +0000137 # We've written this table to file before. If the length
jvr04b32042002-05-14 12:09:10 +0000138 # of the data is still the same, we allow overwriting it.
Just7842e561999-12-16 21:34:53 +0000139 entry = self.tables[tag]
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400140 assert not hasattr(entry.__class__, 'encodeData')
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500141 if len(data) != entry.length:
Just7842e561999-12-16 21:34:53 +0000142 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500143 raise ttLib.TTLibError("cannot rewrite '%s' table: length does not match directory entry" % tag)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400144 reuse = True
Just7842e561999-12-16 21:34:53 +0000145 else:
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400146 entry = self.DirectoryEntry()
Just7842e561999-12-16 21:34:53 +0000147 entry.tag = tag
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400148
149 if tag == 'head':
150 entry.checkSum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:])
151 self.headTable = data
152 entry.uncompressed = True
153 else:
154 entry.checkSum = calcChecksum(data)
155
156 entry.offset = self.nextTableOffset
157 entry.saveData (self.file, data)
158
159 if not reuse:
160 self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
161
jvrc63ac642008-06-17 20:41:15 +0000162 # Add NUL bytes to pad the table data to a 4-byte boundary.
163 # Don't depend on f.seek() as we need to add the padding even if no
164 # subsequent write follows (seek is lazy), ie. after the final table
165 # in the font.
Just7842e561999-12-16 21:34:53 +0000166 self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
jvrc63ac642008-06-17 20:41:15 +0000167 assert self.nextTableOffset == self.file.tell()
Just7842e561999-12-16 21:34:53 +0000168
Just7842e561999-12-16 21:34:53 +0000169 self.tables[tag] = entry
170
jvr28ae1962004-11-16 10:37:59 +0000171 def close(self):
Just7842e561999-12-16 21:34:53 +0000172 """All tables must have been written to disk. Now write the
173 directory.
174 """
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500175 tables = sorted(self.tables.items())
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500176 if len(tables) != self.numTables:
Just7842e561999-12-16 21:34:53 +0000177 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500178 raise ttLib.TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(tables)))
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400179
180 if self.flavor == "woff":
181 self.signature = "wOFF"
182 self.reserved = 0
183
184 self.totalSfntSize = 12
185 self.totalSfntSize += 16 * len(tables)
186 for tag, entry in tables:
187 self.totalSfntSize += (entry.origLength + 3) & ~3
188
189 data = self.flavorData if self.flavorData else WOFFFlavorData()
190 if data.majorVersion != None and data.minorVersion != None:
191 self.majorVersion = data.majorVersion
192 self.minorVersion = data.minorVersion
193 else:
194 if hasattr(self, 'headTable'):
195 self.majorVersion, self.minorVersion = struct.unpack(">HH", self.headTable[4:8])
196 else:
197 self.majorVersion = self.minorVersion = 0
198 if data.metaData:
199 self.metaOrigLength = len(data.metaData)
200 self.file.seek(0,2)
201 self.metaOffset = self.file.tell()
202 compressedMetaData = zlib.compress(data.metaData)
203 self.metaLength = len(compressedMetaData)
204 self.file.write(compressedMetaData)
205 else:
206 self.metaOffset = self.metaLength = self.metaOrigLength = 0
207 if data.privData:
208 self.file.seek(0,2)
209 off = self.file.tell()
210 paddedOff = (off + 3) & ~3
211 self.file.write('\0' * (paddedOff - off))
212 self.privOffset = self.file.tell()
213 self.privLength = len(data.privData)
214 self.file.write(data.privData)
215 else:
216 self.privOffset = self.privLength = 0
217
218 self.file.seek(0,2)
219 self.length = self.file.tell()
220
221 else:
222 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
223 pass
Just7842e561999-12-16 21:34:53 +0000224
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400225 directory = sstruct.pack(self.directoryFormat, self)
Just7842e561999-12-16 21:34:53 +0000226
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400227 self.file.seek(self.directorySize)
jvrf509c0f2003-08-22 19:38:37 +0000228 seenHead = 0
Just7842e561999-12-16 21:34:53 +0000229 for tag, entry in tables:
jvrf509c0f2003-08-22 19:38:37 +0000230 if tag == "head":
231 seenHead = 1
jvrea9dfa92002-05-12 17:14:50 +0000232 directory = directory + entry.toString()
jvrf509c0f2003-08-22 19:38:37 +0000233 if seenHead:
jvr91bca422012-10-18 12:49:22 +0000234 self.writeMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000235 self.file.seek(0)
236 self.file.write(directory)
jvr91bca422012-10-18 12:49:22 +0000237
238 def _calcMasterChecksum(self, directory):
Just7842e561999-12-16 21:34:53 +0000239 # calculate checkSumAdjustment
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500240 tags = list(self.tables.keys())
jvr91bca422012-10-18 12:49:22 +0000241 checksums = []
Just7842e561999-12-16 21:34:53 +0000242 for i in range(len(tags)):
jvr91bca422012-10-18 12:49:22 +0000243 checksums.append(self.tables[tags[i]].checkSum)
244
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400245 # TODO(behdad) I'm fairly sure the checksum for woff is not working correctly.
246 # Haven't debugged.
247 if self.DirectoryEntry != SFNTDirectoryEntry:
248 # Create a SFNT directory for checksum calculation purposes
249 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables)
250 directory = sstruct.pack(sfntDirectoryFormat, self)
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500251 tables = sorted(self.tables.items())
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400252 for tag, entry in tables:
253 sfntEntry = SFNTDirectoryEntry()
254 for item in ['tag', 'checkSum', 'offset', 'length']:
255 setattr(sfntEntry, item, getattr(entry, item))
256 directory = directory + sfntEntry.toString()
257
Just7842e561999-12-16 21:34:53 +0000258 directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
259 assert directory_end == len(directory)
jvr91bca422012-10-18 12:49:22 +0000260
261 checksums.append(calcChecksum(directory))
262 checksum = sum(checksums) & 0xffffffff
Just7842e561999-12-16 21:34:53 +0000263 # BiboAfba!
jvr91bca422012-10-18 12:49:22 +0000264 checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
265 return checksumadjustment
266
267 def writeMasterChecksum(self, directory):
268 checksumadjustment = self._calcMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000269 # write the checksum to the file
270 self.file.seek(self.tables['head'].offset + 8)
pabs30e2aece2009-03-24 09:42:15 +0000271 self.file.write(struct.pack(">L", checksumadjustment))
jvr1ebda672008-03-08 20:29:30 +0000272
Just7842e561999-12-16 21:34:53 +0000273
274# -- sfnt directory helpers and cruft
275
pabs37e91e772009-02-22 08:55:00 +0000276ttcHeaderFormat = """
277 > # big endian
278 TTCTag: 4s # "ttcf"
279 Version: L # 0x00010000 or 0x00020000
280 numFonts: L # number of fonts
281 # OffsetTable[numFonts]: L # array with offsets from beginning of file
282 # ulDsigTag: L # version 2.0 only
283 # ulDsigLength: L # version 2.0 only
284 # ulDsigOffset: L # version 2.0 only
285"""
286
287ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
288
Just7842e561999-12-16 21:34:53 +0000289sfntDirectoryFormat = """
290 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000291 sfntVersion: 4s
292 numTables: H # number of tables
293 searchRange: H # (max2 <= numTables)*16
294 entrySelector: H # log2(max2 <= numTables)
295 rangeShift: H # numTables*16-searchRange
Just7842e561999-12-16 21:34:53 +0000296"""
297
298sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
299
300sfntDirectoryEntryFormat = """
301 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000302 tag: 4s
pabs30e2aece2009-03-24 09:42:15 +0000303 checkSum: L
304 offset: L
305 length: L
Just7842e561999-12-16 21:34:53 +0000306"""
307
308sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
309
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400310woffDirectoryFormat = """
311 > # big endian
312 signature: 4s # "wOFF"
313 sfntVersion: 4s
314 length: L # total woff file size
315 numTables: H # number of tables
316 reserved: H # set to 0
317 totalSfntSize: L # uncompressed size
318 majorVersion: H # major version of WOFF file
319 minorVersion: H # minor version of WOFF file
320 metaOffset: L # offset to metadata block
321 metaLength: L # length of compressed metadata
322 metaOrigLength: L # length of uncompressed metadata
323 privOffset: L # offset to private data block
324 privLength: L # length of private data block
325"""
326
327woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
328
329woffDirectoryEntryFormat = """
330 > # big endian
331 tag: 4s
332 offset: L
333 length: L # compressed length
334 origLength: L # original length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400335 checkSum: L # original checksum
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400336"""
337
338woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
339
340
341class DirectoryEntry:
Just7842e561999-12-16 21:34:53 +0000342
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400343 def __init__(self):
344 self.uncompressed = False # if True, always embed entry raw
345
jvrea9dfa92002-05-12 17:14:50 +0000346 def fromFile(self, file):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400347 sstruct.unpack(self.format, file.read(self.formatSize), self)
Just7842e561999-12-16 21:34:53 +0000348
jvrea9dfa92002-05-12 17:14:50 +0000349 def fromString(self, str):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400350 sstruct.unpack(self.format, str, self)
Just7842e561999-12-16 21:34:53 +0000351
jvrea9dfa92002-05-12 17:14:50 +0000352 def toString(self):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400353 return sstruct.pack(self.format, self)
Just7842e561999-12-16 21:34:53 +0000354
355 def __repr__(self):
356 if hasattr(self, "tag"):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400357 return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
Just7842e561999-12-16 21:34:53 +0000358 else:
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400359 return "<%s at %x>" % (self.__class__.__name__, id(self))
360
361 def loadData(self, file):
362 file.seek(self.offset)
363 data = file.read(self.length)
364 assert len(data) == self.length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400365 if hasattr(self.__class__, 'decodeData'):
366 data = self.decodeData(data)
367 return data
368
369 def saveData(self, file, data):
370 if hasattr(self.__class__, 'encodeData'):
371 data = self.encodeData(data)
372 self.length = len(data)
373 file.seek(self.offset)
374 file.write(data)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400375
376 def decodeData(self, rawData):
377 return rawData
378
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400379 def encodeData(self, data):
380 return data
381
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400382class SFNTDirectoryEntry(DirectoryEntry):
383
384 format = sfntDirectoryEntryFormat
385 formatSize = sfntDirectoryEntrySize
386
387class WOFFDirectoryEntry(DirectoryEntry):
388
389 format = woffDirectoryEntryFormat
390 formatSize = woffDirectoryEntrySize
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400391 zlibCompressionLevel = 6
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400392
393 def decodeData(self, rawData):
394 import zlib
395 if self.length == self.origLength:
396 data = rawData
397 else:
398 assert self.length < self.origLength
399 data = zlib.decompress(rawData)
400 assert len (data) == self.origLength
401 return data
402
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400403 def encodeData(self, data):
404 import zlib
405 self.origLength = len(data)
406 if not self.uncompressed:
407 compressedData = zlib.compress(data, self.zlibCompressionLevel)
408 if self.uncompressed or len(compressedData) >= self.origLength:
409 # Encode uncompressed
410 rawData = data
411 self.length = self.origLength
412 else:
413 rawData = compressedData
414 self.length = len(rawData)
415 return rawData
416
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400417class WOFFFlavorData():
418
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400419 Flavor = 'woff'
420
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400421 def __init__(self, reader=None):
422 self.majorVersion = None
423 self.minorVersion = None
424 self.metaData = None
425 self.privData = None
426 if reader:
427 self.majorVersion = reader.majorVersion
428 self.minorVersion = reader.minorVersion
429 if reader.metaLength:
430 reader.file.seek(reader.metaOffset)
431 rawData = read.file.read(reader.metaLength)
432 assert len(rawData) == reader.metaLength
433 data = zlib.decompress(rawData)
434 assert len(data) == reader.metaOrigLength
435 self.metaData = data
436 if reader.privLength:
437 reader.file.seek(reader.privOffset)
438 data = read.file.read(reader.privLength)
439 assert len(data) == reader.privLength
440 self.privData = data
Just7842e561999-12-16 21:34:53 +0000441
442
jvr91bca422012-10-18 12:49:22 +0000443def calcChecksum(data):
Just7842e561999-12-16 21:34:53 +0000444 """Calculate the checksum for an arbitrary block of data.
445 Optionally takes a 'start' argument, which allows you to
446 calculate a checksum in chunks by feeding it a previous
447 result.
448
449 If the data length is not a multiple of four, it assumes
450 it is to be padded with null byte.
jvr91bca422012-10-18 12:49:22 +0000451
452 >>> print calcChecksum("abcd")
453 1633837924
454 >>> print calcChecksum("abcdxyz")
455 3655064932
Just7842e561999-12-16 21:34:53 +0000456 """
Just7842e561999-12-16 21:34:53 +0000457 remainder = len(data) % 4
458 if remainder:
jvr91bca422012-10-18 12:49:22 +0000459 data += "\0" * (4 - remainder)
460 value = 0
461 blockSize = 4096
462 assert blockSize % 4 == 0
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500463 for i in range(0, len(data), blockSize):
jvr91bca422012-10-18 12:49:22 +0000464 block = data[i:i+blockSize]
465 longs = struct.unpack(">%dL" % (len(block) // 4), block)
466 value = (value + sum(longs)) & 0xffffffff
467 return value
Just7842e561999-12-16 21:34:53 +0000468
469
jvrea9dfa92002-05-12 17:14:50 +0000470def maxPowerOfTwo(x):
Just7842e561999-12-16 21:34:53 +0000471 """Return the highest exponent of two, so that
472 (2 ** exponent) <= x
473 """
474 exponent = 0
475 while x:
476 x = x >> 1
477 exponent = exponent + 1
Justfdea99d2000-08-23 12:34:44 +0000478 return max(exponent - 1, 0)
Just7842e561999-12-16 21:34:53 +0000479
480
jvrea9dfa92002-05-12 17:14:50 +0000481def getSearchRange(n):
Just7842e561999-12-16 21:34:53 +0000482 """Calculate searchRange, entrySelector, rangeShift for the
483 sfnt directory. 'n' is the number of tables.
484 """
485 # This stuff needs to be stored in the file, because?
486 import math
jvrea9dfa92002-05-12 17:14:50 +0000487 exponent = maxPowerOfTwo(n)
Just7842e561999-12-16 21:34:53 +0000488 searchRange = (2 ** exponent) * 16
489 entrySelector = exponent
490 rangeShift = n * 16 - searchRange
491 return searchRange, entrySelector, rangeShift
492
jvr91bca422012-10-18 12:49:22 +0000493
494if __name__ == "__main__":
495 import doctest
496 doctest.testmod()