blob: 71155728d39f4aa79ac841333c558a687c980d6c [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)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050031 if self.sfntVersion == b"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)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050042 elif self.sfntVersion == b"wOFF":
Behdad Esfahbod58d74162013-08-15 15:30:55 -040043 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)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050048 self.sfntVersion = Tag(self.sfntVersion)
Behdad Esfahbod58d74162013-08-15 15:30:55 -040049
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050050 if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
Just7842e561999-12-16 21:34:53 +000051 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."""
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050080 entry = self.tables[Tag(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.
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -050093 print("bad checksum for '%s' table" % tag)
Just7842e561999-12-16 21:34:53 +000094 return data
95
jvrf7074632002-05-04 22:04:02 +000096 def __delitem__(self, tag):
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050097 del self.tables[Tag(tag)]
jvrf7074632002-05-04 22:04:02 +000098
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
Behdad Esfahbodac4672e2013-11-27 16:44:53 -0500109 self.sfntVersion = Tag(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 """
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500176 tables = sorted(self.tables.items())
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500177 if len(tables) != self.numTables:
Just7842e561999-12-16 21:34:53 +0000178 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500179 raise ttLib.TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(tables)))
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400180
181 if self.flavor == "woff":
Behdad Esfahbodac4672e2013-11-27 16:44:53 -0500182 self.signature = b"wOFF"
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400183 self.reserved = 0
184
185 self.totalSfntSize = 12
186 self.totalSfntSize += 16 * len(tables)
187 for tag, entry in tables:
188 self.totalSfntSize += (entry.origLength + 3) & ~3
189
190 data = self.flavorData if self.flavorData else WOFFFlavorData()
191 if data.majorVersion != None and data.minorVersion != None:
192 self.majorVersion = data.majorVersion
193 self.minorVersion = data.minorVersion
194 else:
195 if hasattr(self, 'headTable'):
196 self.majorVersion, self.minorVersion = struct.unpack(">HH", self.headTable[4:8])
197 else:
198 self.majorVersion = self.minorVersion = 0
199 if data.metaData:
200 self.metaOrigLength = len(data.metaData)
201 self.file.seek(0,2)
202 self.metaOffset = self.file.tell()
203 compressedMetaData = zlib.compress(data.metaData)
204 self.metaLength = len(compressedMetaData)
205 self.file.write(compressedMetaData)
206 else:
207 self.metaOffset = self.metaLength = self.metaOrigLength = 0
208 if data.privData:
209 self.file.seek(0,2)
210 off = self.file.tell()
211 paddedOff = (off + 3) & ~3
212 self.file.write('\0' * (paddedOff - off))
213 self.privOffset = self.file.tell()
214 self.privLength = len(data.privData)
215 self.file.write(data.privData)
216 else:
217 self.privOffset = self.privLength = 0
218
219 self.file.seek(0,2)
220 self.length = self.file.tell()
221
222 else:
223 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
224 pass
Just7842e561999-12-16 21:34:53 +0000225
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400226 directory = sstruct.pack(self.directoryFormat, self)
Just7842e561999-12-16 21:34:53 +0000227
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400228 self.file.seek(self.directorySize)
jvrf509c0f2003-08-22 19:38:37 +0000229 seenHead = 0
Just7842e561999-12-16 21:34:53 +0000230 for tag, entry in tables:
jvrf509c0f2003-08-22 19:38:37 +0000231 if tag == "head":
232 seenHead = 1
jvrea9dfa92002-05-12 17:14:50 +0000233 directory = directory + entry.toString()
jvrf509c0f2003-08-22 19:38:37 +0000234 if seenHead:
jvr91bca422012-10-18 12:49:22 +0000235 self.writeMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000236 self.file.seek(0)
237 self.file.write(directory)
jvr91bca422012-10-18 12:49:22 +0000238
239 def _calcMasterChecksum(self, directory):
Just7842e561999-12-16 21:34:53 +0000240 # calculate checkSumAdjustment
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500241 tags = list(self.tables.keys())
jvr91bca422012-10-18 12:49:22 +0000242 checksums = []
Just7842e561999-12-16 21:34:53 +0000243 for i in range(len(tags)):
jvr91bca422012-10-18 12:49:22 +0000244 checksums.append(self.tables[tags[i]].checkSum)
245
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400246 # TODO(behdad) I'm fairly sure the checksum for woff is not working correctly.
247 # Haven't debugged.
248 if self.DirectoryEntry != SFNTDirectoryEntry:
249 # Create a SFNT directory for checksum calculation purposes
250 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables)
251 directory = sstruct.pack(sfntDirectoryFormat, self)
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500252 tables = sorted(self.tables.items())
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400253 for tag, entry in tables:
254 sfntEntry = SFNTDirectoryEntry()
255 for item in ['tag', 'checkSum', 'offset', 'length']:
256 setattr(sfntEntry, item, getattr(entry, item))
257 directory = directory + sfntEntry.toString()
258
Just7842e561999-12-16 21:34:53 +0000259 directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
260 assert directory_end == len(directory)
jvr91bca422012-10-18 12:49:22 +0000261
262 checksums.append(calcChecksum(directory))
263 checksum = sum(checksums) & 0xffffffff
Just7842e561999-12-16 21:34:53 +0000264 # BiboAfba!
jvr91bca422012-10-18 12:49:22 +0000265 checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
266 return checksumadjustment
267
268 def writeMasterChecksum(self, directory):
269 checksumadjustment = self._calcMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000270 # write the checksum to the file
271 self.file.seek(self.tables['head'].offset + 8)
pabs30e2aece2009-03-24 09:42:15 +0000272 self.file.write(struct.pack(">L", checksumadjustment))
jvr1ebda672008-03-08 20:29:30 +0000273
Just7842e561999-12-16 21:34:53 +0000274
275# -- sfnt directory helpers and cruft
276
pabs37e91e772009-02-22 08:55:00 +0000277ttcHeaderFormat = """
278 > # big endian
279 TTCTag: 4s # "ttcf"
280 Version: L # 0x00010000 or 0x00020000
281 numFonts: L # number of fonts
282 # OffsetTable[numFonts]: L # array with offsets from beginning of file
283 # ulDsigTag: L # version 2.0 only
284 # ulDsigLength: L # version 2.0 only
285 # ulDsigOffset: L # version 2.0 only
286"""
287
288ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
289
Just7842e561999-12-16 21:34:53 +0000290sfntDirectoryFormat = """
291 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000292 sfntVersion: 4s
293 numTables: H # number of tables
294 searchRange: H # (max2 <= numTables)*16
295 entrySelector: H # log2(max2 <= numTables)
296 rangeShift: H # numTables*16-searchRange
Just7842e561999-12-16 21:34:53 +0000297"""
298
299sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
300
301sfntDirectoryEntryFormat = """
302 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000303 tag: 4s
pabs30e2aece2009-03-24 09:42:15 +0000304 checkSum: L
305 offset: L
306 length: L
Just7842e561999-12-16 21:34:53 +0000307"""
308
309sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
310
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400311woffDirectoryFormat = """
312 > # big endian
313 signature: 4s # "wOFF"
314 sfntVersion: 4s
315 length: L # total woff file size
316 numTables: H # number of tables
317 reserved: H # set to 0
318 totalSfntSize: L # uncompressed size
319 majorVersion: H # major version of WOFF file
320 minorVersion: H # minor version of WOFF file
321 metaOffset: L # offset to metadata block
322 metaLength: L # length of compressed metadata
323 metaOrigLength: L # length of uncompressed metadata
324 privOffset: L # offset to private data block
325 privLength: L # length of private data block
326"""
327
328woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
329
330woffDirectoryEntryFormat = """
331 > # big endian
332 tag: 4s
333 offset: L
334 length: L # compressed length
335 origLength: L # original length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400336 checkSum: L # original checksum
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400337"""
338
339woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
340
341
342class DirectoryEntry:
Just7842e561999-12-16 21:34:53 +0000343
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400344 def __init__(self):
345 self.uncompressed = False # if True, always embed entry raw
346
jvrea9dfa92002-05-12 17:14:50 +0000347 def fromFile(self, file):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400348 sstruct.unpack(self.format, file.read(self.formatSize), self)
Just7842e561999-12-16 21:34:53 +0000349
jvrea9dfa92002-05-12 17:14:50 +0000350 def fromString(self, str):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400351 sstruct.unpack(self.format, str, self)
Just7842e561999-12-16 21:34:53 +0000352
jvrea9dfa92002-05-12 17:14:50 +0000353 def toString(self):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400354 return sstruct.pack(self.format, self)
Just7842e561999-12-16 21:34:53 +0000355
356 def __repr__(self):
357 if hasattr(self, "tag"):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400358 return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
Just7842e561999-12-16 21:34:53 +0000359 else:
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400360 return "<%s at %x>" % (self.__class__.__name__, id(self))
361
362 def loadData(self, file):
363 file.seek(self.offset)
364 data = file.read(self.length)
365 assert len(data) == self.length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400366 if hasattr(self.__class__, 'decodeData'):
367 data = self.decodeData(data)
368 return data
369
370 def saveData(self, file, data):
371 if hasattr(self.__class__, 'encodeData'):
372 data = self.encodeData(data)
373 self.length = len(data)
374 file.seek(self.offset)
375 file.write(data)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400376
377 def decodeData(self, rawData):
378 return rawData
379
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400380 def encodeData(self, data):
381 return data
382
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400383class SFNTDirectoryEntry(DirectoryEntry):
384
385 format = sfntDirectoryEntryFormat
386 formatSize = sfntDirectoryEntrySize
387
388class WOFFDirectoryEntry(DirectoryEntry):
389
390 format = woffDirectoryEntryFormat
391 formatSize = woffDirectoryEntrySize
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400392 zlibCompressionLevel = 6
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400393
394 def decodeData(self, rawData):
395 import zlib
396 if self.length == self.origLength:
397 data = rawData
398 else:
399 assert self.length < self.origLength
400 data = zlib.decompress(rawData)
401 assert len (data) == self.origLength
402 return data
403
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400404 def encodeData(self, data):
405 import zlib
406 self.origLength = len(data)
407 if not self.uncompressed:
408 compressedData = zlib.compress(data, self.zlibCompressionLevel)
409 if self.uncompressed or len(compressedData) >= self.origLength:
410 # Encode uncompressed
411 rawData = data
412 self.length = self.origLength
413 else:
414 rawData = compressedData
415 self.length = len(rawData)
416 return rawData
417
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400418class WOFFFlavorData():
419
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400420 Flavor = 'woff'
421
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400422 def __init__(self, reader=None):
423 self.majorVersion = None
424 self.minorVersion = None
425 self.metaData = None
426 self.privData = None
427 if reader:
428 self.majorVersion = reader.majorVersion
429 self.minorVersion = reader.minorVersion
430 if reader.metaLength:
431 reader.file.seek(reader.metaOffset)
432 rawData = read.file.read(reader.metaLength)
433 assert len(rawData) == reader.metaLength
434 data = zlib.decompress(rawData)
435 assert len(data) == reader.metaOrigLength
436 self.metaData = data
437 if reader.privLength:
438 reader.file.seek(reader.privOffset)
439 data = read.file.read(reader.privLength)
440 assert len(data) == reader.privLength
441 self.privData = data
Just7842e561999-12-16 21:34:53 +0000442
443
jvr91bca422012-10-18 12:49:22 +0000444def calcChecksum(data):
Just7842e561999-12-16 21:34:53 +0000445 """Calculate the checksum for an arbitrary block of data.
446 Optionally takes a 'start' argument, which allows you to
447 calculate a checksum in chunks by feeding it a previous
448 result.
449
450 If the data length is not a multiple of four, it assumes
451 it is to be padded with null byte.
jvr91bca422012-10-18 12:49:22 +0000452
453 >>> print calcChecksum("abcd")
454 1633837924
455 >>> print calcChecksum("abcdxyz")
456 3655064932
Just7842e561999-12-16 21:34:53 +0000457 """
Just7842e561999-12-16 21:34:53 +0000458 remainder = len(data) % 4
459 if remainder:
jvr91bca422012-10-18 12:49:22 +0000460 data += "\0" * (4 - remainder)
461 value = 0
462 blockSize = 4096
463 assert blockSize % 4 == 0
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500464 for i in range(0, len(data), blockSize):
jvr91bca422012-10-18 12:49:22 +0000465 block = data[i:i+blockSize]
466 longs = struct.unpack(">%dL" % (len(block) // 4), block)
467 value = (value + sum(longs)) & 0xffffffff
468 return value
Just7842e561999-12-16 21:34:53 +0000469
470
jvrea9dfa92002-05-12 17:14:50 +0000471def maxPowerOfTwo(x):
Just7842e561999-12-16 21:34:53 +0000472 """Return the highest exponent of two, so that
473 (2 ** exponent) <= x
474 """
475 exponent = 0
476 while x:
477 x = x >> 1
478 exponent = exponent + 1
Justfdea99d2000-08-23 12:34:44 +0000479 return max(exponent - 1, 0)
Just7842e561999-12-16 21:34:53 +0000480
481
jvrea9dfa92002-05-12 17:14:50 +0000482def getSearchRange(n):
Just7842e561999-12-16 21:34:53 +0000483 """Calculate searchRange, entrySelector, rangeShift for the
484 sfnt directory. 'n' is the number of tables.
485 """
486 # This stuff needs to be stored in the file, because?
487 import math
jvrea9dfa92002-05-12 17:14:50 +0000488 exponent = maxPowerOfTwo(n)
Just7842e561999-12-16 21:34:53 +0000489 searchRange = (2 ** exponent) * 16
490 entrySelector = exponent
491 rangeShift = n * 16 - searchRange
492 return searchRange, entrySelector, rangeShift
493
jvr91bca422012-10-18 12:49:22 +0000494
495if __name__ == "__main__":
496 import doctest
497 doctest.testmod()