blob: da5c79d0e50c05edf593d4968575919a92179a1e [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 Esfahbod32c10ee2013-11-27 17:46:17 -050015from __future__ import print_function, division
Behdad Esfahbod7ed91ec2013-11-27 15:16:28 -050016from fontTools.misc.py23 import *
Behdad Esfahbod30e691e2013-11-27 17:27:45 -050017from fontTools.misc import sstruct
18import struct
Just7842e561999-12-16 21:34:53 +000019
jvr04b32042002-05-14 12:09:10 +000020
Behdad Esfahbode388db52013-11-28 14:26:58 -050021class SFNTReader(object):
Just7842e561999-12-16 21:34:53 +000022
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)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050032 if self.sfntVersion == b"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)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050043 elif self.sfntVersion == b"wOFF":
Behdad Esfahbod58d74162013-08-15 15:30:55 -040044 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)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050049 self.sfntVersion = Tag(self.sfntVersion)
Behdad Esfahbod58d74162013-08-15 15:30:55 -040050
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050051 if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
Just7842e561999-12-16 21:34:53 +000052 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050053 raise ttLib.TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
Just7842e561999-12-16 21:34:53 +000054 self.tables = {}
55 for i in range(self.numTables):
Behdad Esfahbod58d74162013-08-15 15:30:55 -040056 entry = self.DirectoryEntry()
jvrea9dfa92002-05-12 17:14:50 +000057 entry.fromFile(self.file)
jvrce1d50a2002-05-12 17:02:50 +000058 if entry.length > 0:
Behdad Esfahbod5cf40082013-11-27 19:51:59 -050059 self.tables[Tag(entry.tag)] = entry
jvrce1d50a2002-05-12 17:02:50 +000060 else:
61 # Ignore zero-length tables. This doesn't seem to be documented,
62 # yet it's apparently how the Windows TT rasterizer behaves.
63 # Besides, at least one font has been sighted which actually
64 # *has* a zero-length table.
65 pass
Behdad Esfahbod58d74162013-08-15 15:30:55 -040066
67 # Load flavor data if any
68 if self.flavor == "woff":
69 self.flavorData = WOFFFlavorData(self)
70
Just7842e561999-12-16 21:34:53 +000071 def has_key(self, tag):
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050072 return tag in self.tables
73
74 __contains__ = has_key
Just7842e561999-12-16 21:34:53 +000075
76 def keys(self):
77 return self.tables.keys()
78
79 def __getitem__(self, tag):
80 """Fetch the raw table data."""
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050081 entry = self.tables[Tag(tag)]
Behdad Esfahbod58d74162013-08-15 15:30:55 -040082 data = entry.loadData (self.file)
jvrea9dfa92002-05-12 17:14:50 +000083 if self.checkChecksums:
Just7842e561999-12-16 21:34:53 +000084 if tag == 'head':
85 # Beh: we have to special-case the 'head' table.
Behdad Esfahbod821572c2013-11-27 21:09:03 -050086 checksum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
Just7842e561999-12-16 21:34:53 +000087 else:
jvrea9dfa92002-05-12 17:14:50 +000088 checksum = calcChecksum(data)
89 if self.checkChecksums > 1:
Just7842e561999-12-16 21:34:53 +000090 # Be obnoxious, and barf when it's wrong
91 assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
Behdad Esfahbod180ace62013-11-27 02:40:30 -050092 elif checksum != entry.checkSum:
Just7842e561999-12-16 21:34:53 +000093 # Be friendly, and just print a warning.
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -050094 print("bad checksum for '%s' table" % tag)
Just7842e561999-12-16 21:34:53 +000095 return data
96
jvrf7074632002-05-04 22:04:02 +000097 def __delitem__(self, tag):
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050098 del self.tables[Tag(tag)]
jvrf7074632002-05-04 22:04:02 +000099
Just7842e561999-12-16 21:34:53 +0000100 def close(self):
101 self.file.close()
102
103
Behdad Esfahbode388db52013-11-28 14:26:58 -0500104class SFNTWriter(object):
Just7842e561999-12-16 21:34:53 +0000105
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400106 def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
107 flavor=None, flavorData=None):
Just7842e561999-12-16 21:34:53 +0000108 self.file = file
109 self.numTables = numTables
Behdad Esfahbodac4672e2013-11-27 16:44:53 -0500110 self.sfntVersion = Tag(sfntVersion)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400111 self.flavor = flavor
112 self.flavorData = flavorData
113
114 if self.flavor == "woff":
115 self.directoryFormat = woffDirectoryFormat
116 self.directorySize = woffDirectorySize
117 self.DirectoryEntry = WOFFDirectoryEntry
118
119 self.signature = "wOFF"
120 else:
121 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
122 self.directoryFormat = sfntDirectoryFormat
123 self.directorySize = sfntDirectorySize
124 self.DirectoryEntry = SFNTDirectoryEntry
125
126 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables)
127
128 self.nextTableOffset = self.directorySize + numTables * self.DirectoryEntry.formatSize
Just7842e561999-12-16 21:34:53 +0000129 # clear out directory area
130 self.file.seek(self.nextTableOffset)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400131 # make sure we're actually where we want to be. (old cStringIO bug)
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500132 self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
Just7842e561999-12-16 21:34:53 +0000133 self.tables = {}
134
135 def __setitem__(self, tag, data):
136 """Write raw table data to disk."""
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400137 reuse = False
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500138 if tag in self.tables:
Just7842e561999-12-16 21:34:53 +0000139 # We've written this table to file before. If the length
jvr04b32042002-05-14 12:09:10 +0000140 # of the data is still the same, we allow overwriting it.
Just7842e561999-12-16 21:34:53 +0000141 entry = self.tables[tag]
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400142 assert not hasattr(entry.__class__, 'encodeData')
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500143 if len(data) != entry.length:
Just7842e561999-12-16 21:34:53 +0000144 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500145 raise ttLib.TTLibError("cannot rewrite '%s' table: length does not match directory entry" % tag)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400146 reuse = True
Just7842e561999-12-16 21:34:53 +0000147 else:
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400148 entry = self.DirectoryEntry()
Just7842e561999-12-16 21:34:53 +0000149 entry.tag = tag
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400150
151 if tag == 'head':
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500152 entry.checkSum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400153 self.headTable = data
154 entry.uncompressed = True
155 else:
156 entry.checkSum = calcChecksum(data)
157
158 entry.offset = self.nextTableOffset
159 entry.saveData (self.file, data)
160
161 if not reuse:
162 self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
163
jvrc63ac642008-06-17 20:41:15 +0000164 # Add NUL bytes to pad the table data to a 4-byte boundary.
165 # Don't depend on f.seek() as we need to add the padding even if no
166 # subsequent write follows (seek is lazy), ie. after the final table
167 # in the font.
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500168 self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
jvrc63ac642008-06-17 20:41:15 +0000169 assert self.nextTableOffset == self.file.tell()
Just7842e561999-12-16 21:34:53 +0000170
Just7842e561999-12-16 21:34:53 +0000171 self.tables[tag] = entry
172
jvr28ae1962004-11-16 10:37:59 +0000173 def close(self):
Just7842e561999-12-16 21:34:53 +0000174 """All tables must have been written to disk. Now write the
175 directory.
176 """
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500177 tables = sorted(self.tables.items())
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":
Behdad Esfahbodac4672e2013-11-27 16:44:53 -0500183 self.signature = b"wOFF"
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400184 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
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500242 tags = list(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)
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500253 tables = sorted(self.tables.items())
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400254 for tag, entry in tables:
255 sfntEntry = SFNTDirectoryEntry()
256 for item in ['tag', 'checkSum', 'offset', 'length']:
257 setattr(sfntEntry, item, getattr(entry, item))
258 directory = directory + sfntEntry.toString()
259
Just7842e561999-12-16 21:34:53 +0000260 directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
261 assert directory_end == len(directory)
jvr91bca422012-10-18 12:49:22 +0000262
263 checksums.append(calcChecksum(directory))
264 checksum = sum(checksums) & 0xffffffff
Just7842e561999-12-16 21:34:53 +0000265 # BiboAfba!
jvr91bca422012-10-18 12:49:22 +0000266 checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
267 return checksumadjustment
268
269 def writeMasterChecksum(self, directory):
270 checksumadjustment = self._calcMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000271 # write the checksum to the file
272 self.file.seek(self.tables['head'].offset + 8)
pabs30e2aece2009-03-24 09:42:15 +0000273 self.file.write(struct.pack(">L", checksumadjustment))
jvr1ebda672008-03-08 20:29:30 +0000274
Just7842e561999-12-16 21:34:53 +0000275
276# -- sfnt directory helpers and cruft
277
pabs37e91e772009-02-22 08:55:00 +0000278ttcHeaderFormat = """
279 > # big endian
280 TTCTag: 4s # "ttcf"
281 Version: L # 0x00010000 or 0x00020000
282 numFonts: L # number of fonts
283 # OffsetTable[numFonts]: L # array with offsets from beginning of file
284 # ulDsigTag: L # version 2.0 only
285 # ulDsigLength: L # version 2.0 only
286 # ulDsigOffset: L # version 2.0 only
287"""
288
289ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
290
Just7842e561999-12-16 21:34:53 +0000291sfntDirectoryFormat = """
292 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000293 sfntVersion: 4s
294 numTables: H # number of tables
295 searchRange: H # (max2 <= numTables)*16
296 entrySelector: H # log2(max2 <= numTables)
297 rangeShift: H # numTables*16-searchRange
Just7842e561999-12-16 21:34:53 +0000298"""
299
300sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
301
302sfntDirectoryEntryFormat = """
303 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000304 tag: 4s
pabs30e2aece2009-03-24 09:42:15 +0000305 checkSum: L
306 offset: L
307 length: L
Just7842e561999-12-16 21:34:53 +0000308"""
309
310sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
311
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400312woffDirectoryFormat = """
313 > # big endian
314 signature: 4s # "wOFF"
315 sfntVersion: 4s
316 length: L # total woff file size
317 numTables: H # number of tables
318 reserved: H # set to 0
319 totalSfntSize: L # uncompressed size
320 majorVersion: H # major version of WOFF file
321 minorVersion: H # minor version of WOFF file
322 metaOffset: L # offset to metadata block
323 metaLength: L # length of compressed metadata
324 metaOrigLength: L # length of uncompressed metadata
325 privOffset: L # offset to private data block
326 privLength: L # length of private data block
327"""
328
329woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
330
331woffDirectoryEntryFormat = """
332 > # big endian
333 tag: 4s
334 offset: L
335 length: L # compressed length
336 origLength: L # original length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400337 checkSum: L # original checksum
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400338"""
339
340woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
341
342
Behdad Esfahbode388db52013-11-28 14:26:58 -0500343class DirectoryEntry(object):
Just7842e561999-12-16 21:34:53 +0000344
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400345 def __init__(self):
346 self.uncompressed = False # if True, always embed entry raw
347
jvrea9dfa92002-05-12 17:14:50 +0000348 def fromFile(self, file):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400349 sstruct.unpack(self.format, file.read(self.formatSize), self)
Just7842e561999-12-16 21:34:53 +0000350
jvrea9dfa92002-05-12 17:14:50 +0000351 def fromString(self, str):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400352 sstruct.unpack(self.format, str, self)
Just7842e561999-12-16 21:34:53 +0000353
jvrea9dfa92002-05-12 17:14:50 +0000354 def toString(self):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400355 return sstruct.pack(self.format, self)
Just7842e561999-12-16 21:34:53 +0000356
357 def __repr__(self):
358 if hasattr(self, "tag"):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400359 return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
Just7842e561999-12-16 21:34:53 +0000360 else:
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400361 return "<%s at %x>" % (self.__class__.__name__, id(self))
362
363 def loadData(self, file):
364 file.seek(self.offset)
365 data = file.read(self.length)
366 assert len(data) == self.length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400367 if hasattr(self.__class__, 'decodeData'):
368 data = self.decodeData(data)
369 return data
370
371 def saveData(self, file, data):
372 if hasattr(self.__class__, 'encodeData'):
373 data = self.encodeData(data)
374 self.length = len(data)
375 file.seek(self.offset)
376 file.write(data)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400377
378 def decodeData(self, rawData):
379 return rawData
380
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400381 def encodeData(self, data):
382 return data
383
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400384class SFNTDirectoryEntry(DirectoryEntry):
385
386 format = sfntDirectoryEntryFormat
387 formatSize = sfntDirectoryEntrySize
388
389class WOFFDirectoryEntry(DirectoryEntry):
390
391 format = woffDirectoryEntryFormat
392 formatSize = woffDirectoryEntrySize
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400393 zlibCompressionLevel = 6
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400394
395 def decodeData(self, rawData):
396 import zlib
397 if self.length == self.origLength:
398 data = rawData
399 else:
400 assert self.length < self.origLength
401 data = zlib.decompress(rawData)
402 assert len (data) == self.origLength
403 return data
404
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400405 def encodeData(self, data):
406 import zlib
407 self.origLength = len(data)
408 if not self.uncompressed:
409 compressedData = zlib.compress(data, self.zlibCompressionLevel)
410 if self.uncompressed or len(compressedData) >= self.origLength:
411 # Encode uncompressed
412 rawData = data
413 self.length = self.origLength
414 else:
415 rawData = compressedData
416 self.length = len(rawData)
417 return rawData
418
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400419class WOFFFlavorData():
420
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400421 Flavor = 'woff'
422
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400423 def __init__(self, reader=None):
424 self.majorVersion = None
425 self.minorVersion = None
426 self.metaData = None
427 self.privData = None
428 if reader:
429 self.majorVersion = reader.majorVersion
430 self.minorVersion = reader.minorVersion
431 if reader.metaLength:
432 reader.file.seek(reader.metaOffset)
433 rawData = read.file.read(reader.metaLength)
434 assert len(rawData) == reader.metaLength
435 data = zlib.decompress(rawData)
436 assert len(data) == reader.metaOrigLength
437 self.metaData = data
438 if reader.privLength:
439 reader.file.seek(reader.privOffset)
440 data = read.file.read(reader.privLength)
441 assert len(data) == reader.privLength
442 self.privData = data
Just7842e561999-12-16 21:34:53 +0000443
444
jvr91bca422012-10-18 12:49:22 +0000445def calcChecksum(data):
Just7842e561999-12-16 21:34:53 +0000446 """Calculate the checksum for an arbitrary block of data.
447 Optionally takes a 'start' argument, which allows you to
448 calculate a checksum in chunks by feeding it a previous
449 result.
450
451 If the data length is not a multiple of four, it assumes
452 it is to be padded with null byte.
jvr91bca422012-10-18 12:49:22 +0000453
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500454 >>> print calcChecksum(b"abcd")
jvr91bca422012-10-18 12:49:22 +0000455 1633837924
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500456 >>> print calcChecksum(b"abcdxyz")
jvr91bca422012-10-18 12:49:22 +0000457 3655064932
Just7842e561999-12-16 21:34:53 +0000458 """
Just7842e561999-12-16 21:34:53 +0000459 remainder = len(data) % 4
460 if remainder:
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500461 data += b"\0" * (4 - remainder)
jvr91bca422012-10-18 12:49:22 +0000462 value = 0
463 blockSize = 4096
464 assert blockSize % 4 == 0
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500465 for i in range(0, len(data), blockSize):
jvr91bca422012-10-18 12:49:22 +0000466 block = data[i:i+blockSize]
467 longs = struct.unpack(">%dL" % (len(block) // 4), block)
468 value = (value + sum(longs)) & 0xffffffff
469 return value
Just7842e561999-12-16 21:34:53 +0000470
471
jvrea9dfa92002-05-12 17:14:50 +0000472def maxPowerOfTwo(x):
Just7842e561999-12-16 21:34:53 +0000473 """Return the highest exponent of two, so that
474 (2 ** exponent) <= x
475 """
476 exponent = 0
477 while x:
478 x = x >> 1
479 exponent = exponent + 1
Justfdea99d2000-08-23 12:34:44 +0000480 return max(exponent - 1, 0)
Just7842e561999-12-16 21:34:53 +0000481
482
jvrea9dfa92002-05-12 17:14:50 +0000483def getSearchRange(n):
Just7842e561999-12-16 21:34:53 +0000484 """Calculate searchRange, entrySelector, rangeShift for the
485 sfnt directory. 'n' is the number of tables.
486 """
487 # This stuff needs to be stored in the file, because?
488 import math
jvrea9dfa92002-05-12 17:14:50 +0000489 exponent = maxPowerOfTwo(n)
Just7842e561999-12-16 21:34:53 +0000490 searchRange = (2 ** exponent) * 16
491 entrySelector = exponent
492 rangeShift = n * 16 - searchRange
493 return searchRange, entrySelector, rangeShift
494
jvr91bca422012-10-18 12:49:22 +0000495
496if __name__ == "__main__":
497 import doctest
498 doctest.testmod()