blob: 95679e99fbacbbc75576264fc0384a7e56a70047 [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 Esfahbod1ae29592014-01-14 15:07:50 +080015from __future__ import print_function, division, absolute_import
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)
Behdad Esfahbod63383752014-01-30 16:48:11 -050058 self.tables[Tag(entry.tag)] = entry
Behdad Esfahbod58d74162013-08-15 15:30:55 -040059
60 # Load flavor data if any
61 if self.flavor == "woff":
62 self.flavorData = WOFFFlavorData(self)
63
Just7842e561999-12-16 21:34:53 +000064 def has_key(self, tag):
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050065 return tag in self.tables
66
67 __contains__ = has_key
Just7842e561999-12-16 21:34:53 +000068
69 def keys(self):
70 return self.tables.keys()
71
72 def __getitem__(self, tag):
73 """Fetch the raw table data."""
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050074 entry = self.tables[Tag(tag)]
Behdad Esfahbod58d74162013-08-15 15:30:55 -040075 data = entry.loadData (self.file)
jvrea9dfa92002-05-12 17:14:50 +000076 if self.checkChecksums:
Just7842e561999-12-16 21:34:53 +000077 if tag == 'head':
78 # Beh: we have to special-case the 'head' table.
Behdad Esfahbod821572c2013-11-27 21:09:03 -050079 checksum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
Just7842e561999-12-16 21:34:53 +000080 else:
jvrea9dfa92002-05-12 17:14:50 +000081 checksum = calcChecksum(data)
82 if self.checkChecksums > 1:
Just7842e561999-12-16 21:34:53 +000083 # Be obnoxious, and barf when it's wrong
84 assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
Behdad Esfahbod180ace62013-11-27 02:40:30 -050085 elif checksum != entry.checkSum:
Just7842e561999-12-16 21:34:53 +000086 # Be friendly, and just print a warning.
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -050087 print("bad checksum for '%s' table" % tag)
Just7842e561999-12-16 21:34:53 +000088 return data
89
jvrf7074632002-05-04 22:04:02 +000090 def __delitem__(self, tag):
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050091 del self.tables[Tag(tag)]
jvrf7074632002-05-04 22:04:02 +000092
Just7842e561999-12-16 21:34:53 +000093 def close(self):
94 self.file.close()
95
96
Behdad Esfahbode388db52013-11-28 14:26:58 -050097class SFNTWriter(object):
Just7842e561999-12-16 21:34:53 +000098
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -040099 def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
100 flavor=None, flavorData=None):
Just7842e561999-12-16 21:34:53 +0000101 self.file = file
102 self.numTables = numTables
Behdad Esfahbodac4672e2013-11-27 16:44:53 -0500103 self.sfntVersion = Tag(sfntVersion)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400104 self.flavor = flavor
105 self.flavorData = flavorData
106
107 if self.flavor == "woff":
108 self.directoryFormat = woffDirectoryFormat
109 self.directorySize = woffDirectorySize
110 self.DirectoryEntry = WOFFDirectoryEntry
111
112 self.signature = "wOFF"
113 else:
114 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
115 self.directoryFormat = sfntDirectoryFormat
116 self.directorySize = sfntDirectorySize
117 self.DirectoryEntry = SFNTDirectoryEntry
118
119 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables)
120
121 self.nextTableOffset = self.directorySize + numTables * self.DirectoryEntry.formatSize
Just7842e561999-12-16 21:34:53 +0000122 # clear out directory area
123 self.file.seek(self.nextTableOffset)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400124 # make sure we're actually where we want to be. (old cStringIO bug)
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500125 self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
Just7842e561999-12-16 21:34:53 +0000126 self.tables = {}
127
128 def __setitem__(self, tag, data):
129 """Write raw table data to disk."""
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400130 reuse = False
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500131 if tag in self.tables:
Just7842e561999-12-16 21:34:53 +0000132 # We've written this table to file before. If the length
jvr04b32042002-05-14 12:09:10 +0000133 # of the data is still the same, we allow overwriting it.
Just7842e561999-12-16 21:34:53 +0000134 entry = self.tables[tag]
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400135 assert not hasattr(entry.__class__, 'encodeData')
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500136 if len(data) != entry.length:
Just7842e561999-12-16 21:34:53 +0000137 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500138 raise ttLib.TTLibError("cannot rewrite '%s' table: length does not match directory entry" % tag)
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400139 reuse = True
Just7842e561999-12-16 21:34:53 +0000140 else:
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400141 entry = self.DirectoryEntry()
Just7842e561999-12-16 21:34:53 +0000142 entry.tag = tag
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400143
144 if tag == 'head':
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500145 entry.checkSum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400146 self.headTable = data
147 entry.uncompressed = True
148 else:
149 entry.checkSum = calcChecksum(data)
150
151 entry.offset = self.nextTableOffset
152 entry.saveData (self.file, data)
153
154 if not reuse:
155 self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
156
jvrc63ac642008-06-17 20:41:15 +0000157 # Add NUL bytes to pad the table data to a 4-byte boundary.
158 # Don't depend on f.seek() as we need to add the padding even if no
159 # subsequent write follows (seek is lazy), ie. after the final table
160 # in the font.
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500161 self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
jvrc63ac642008-06-17 20:41:15 +0000162 assert self.nextTableOffset == self.file.tell()
Just7842e561999-12-16 21:34:53 +0000163
Just7842e561999-12-16 21:34:53 +0000164 self.tables[tag] = entry
165
jvr28ae1962004-11-16 10:37:59 +0000166 def close(self):
Just7842e561999-12-16 21:34:53 +0000167 """All tables must have been written to disk. Now write the
168 directory.
169 """
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500170 tables = sorted(self.tables.items())
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500171 if len(tables) != self.numTables:
Just7842e561999-12-16 21:34:53 +0000172 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500173 raise ttLib.TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(tables)))
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400174
175 if self.flavor == "woff":
Behdad Esfahbodac4672e2013-11-27 16:44:53 -0500176 self.signature = b"wOFF"
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400177 self.reserved = 0
178
179 self.totalSfntSize = 12
180 self.totalSfntSize += 16 * len(tables)
181 for tag, entry in tables:
182 self.totalSfntSize += (entry.origLength + 3) & ~3
183
184 data = self.flavorData if self.flavorData else WOFFFlavorData()
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500185 if data.majorVersion is not None and data.minorVersion is not None:
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400186 self.majorVersion = data.majorVersion
187 self.minorVersion = data.minorVersion
188 else:
189 if hasattr(self, 'headTable'):
190 self.majorVersion, self.minorVersion = struct.unpack(">HH", self.headTable[4:8])
191 else:
192 self.majorVersion = self.minorVersion = 0
193 if data.metaData:
194 self.metaOrigLength = len(data.metaData)
195 self.file.seek(0,2)
196 self.metaOffset = self.file.tell()
Behdad Esfahbod153ec402013-12-04 01:15:46 -0500197 import zlib
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400198 compressedMetaData = zlib.compress(data.metaData)
199 self.metaLength = len(compressedMetaData)
200 self.file.write(compressedMetaData)
201 else:
202 self.metaOffset = self.metaLength = self.metaOrigLength = 0
203 if data.privData:
204 self.file.seek(0,2)
205 off = self.file.tell()
206 paddedOff = (off + 3) & ~3
207 self.file.write('\0' * (paddedOff - off))
208 self.privOffset = self.file.tell()
209 self.privLength = len(data.privData)
210 self.file.write(data.privData)
211 else:
212 self.privOffset = self.privLength = 0
213
214 self.file.seek(0,2)
215 self.length = self.file.tell()
216
217 else:
218 assert not self.flavor, "Unknown flavor '%s'" % self.flavor
219 pass
Just7842e561999-12-16 21:34:53 +0000220
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400221 directory = sstruct.pack(self.directoryFormat, self)
Just7842e561999-12-16 21:34:53 +0000222
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400223 self.file.seek(self.directorySize)
jvrf509c0f2003-08-22 19:38:37 +0000224 seenHead = 0
Just7842e561999-12-16 21:34:53 +0000225 for tag, entry in tables:
jvrf509c0f2003-08-22 19:38:37 +0000226 if tag == "head":
227 seenHead = 1
jvrea9dfa92002-05-12 17:14:50 +0000228 directory = directory + entry.toString()
jvrf509c0f2003-08-22 19:38:37 +0000229 if seenHead:
jvr91bca422012-10-18 12:49:22 +0000230 self.writeMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000231 self.file.seek(0)
232 self.file.write(directory)
jvr91bca422012-10-18 12:49:22 +0000233
234 def _calcMasterChecksum(self, directory):
Just7842e561999-12-16 21:34:53 +0000235 # calculate checkSumAdjustment
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500236 tags = list(self.tables.keys())
jvr91bca422012-10-18 12:49:22 +0000237 checksums = []
Just7842e561999-12-16 21:34:53 +0000238 for i in range(len(tags)):
jvr91bca422012-10-18 12:49:22 +0000239 checksums.append(self.tables[tags[i]].checkSum)
240
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400241 # TODO(behdad) I'm fairly sure the checksum for woff is not working correctly.
242 # Haven't debugged.
243 if self.DirectoryEntry != SFNTDirectoryEntry:
244 # Create a SFNT directory for checksum calculation purposes
245 self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables)
246 directory = sstruct.pack(sfntDirectoryFormat, self)
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500247 tables = sorted(self.tables.items())
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400248 for tag, entry in tables:
249 sfntEntry = SFNTDirectoryEntry()
250 for item in ['tag', 'checkSum', 'offset', 'length']:
251 setattr(sfntEntry, item, getattr(entry, item))
252 directory = directory + sfntEntry.toString()
253
Just7842e561999-12-16 21:34:53 +0000254 directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
255 assert directory_end == len(directory)
jvr91bca422012-10-18 12:49:22 +0000256
257 checksums.append(calcChecksum(directory))
258 checksum = sum(checksums) & 0xffffffff
Just7842e561999-12-16 21:34:53 +0000259 # BiboAfba!
jvr91bca422012-10-18 12:49:22 +0000260 checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
261 return checksumadjustment
262
263 def writeMasterChecksum(self, directory):
264 checksumadjustment = self._calcMasterChecksum(directory)
Just7842e561999-12-16 21:34:53 +0000265 # write the checksum to the file
266 self.file.seek(self.tables['head'].offset + 8)
pabs30e2aece2009-03-24 09:42:15 +0000267 self.file.write(struct.pack(">L", checksumadjustment))
jvr1ebda672008-03-08 20:29:30 +0000268
Just7842e561999-12-16 21:34:53 +0000269
270# -- sfnt directory helpers and cruft
271
pabs37e91e772009-02-22 08:55:00 +0000272ttcHeaderFormat = """
273 > # big endian
274 TTCTag: 4s # "ttcf"
275 Version: L # 0x00010000 or 0x00020000
276 numFonts: L # number of fonts
277 # OffsetTable[numFonts]: L # array with offsets from beginning of file
278 # ulDsigTag: L # version 2.0 only
279 # ulDsigLength: L # version 2.0 only
280 # ulDsigOffset: L # version 2.0 only
281"""
282
283ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
284
Just7842e561999-12-16 21:34:53 +0000285sfntDirectoryFormat = """
286 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000287 sfntVersion: 4s
288 numTables: H # number of tables
289 searchRange: H # (max2 <= numTables)*16
290 entrySelector: H # log2(max2 <= numTables)
291 rangeShift: H # numTables*16-searchRange
Just7842e561999-12-16 21:34:53 +0000292"""
293
294sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
295
296sfntDirectoryEntryFormat = """
297 > # big endian
jvrb0e5f292002-05-13 11:21:48 +0000298 tag: 4s
pabs30e2aece2009-03-24 09:42:15 +0000299 checkSum: L
300 offset: L
301 length: L
Just7842e561999-12-16 21:34:53 +0000302"""
303
304sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
305
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400306woffDirectoryFormat = """
307 > # big endian
308 signature: 4s # "wOFF"
309 sfntVersion: 4s
310 length: L # total woff file size
311 numTables: H # number of tables
312 reserved: H # set to 0
313 totalSfntSize: L # uncompressed size
314 majorVersion: H # major version of WOFF file
315 minorVersion: H # minor version of WOFF file
316 metaOffset: L # offset to metadata block
317 metaLength: L # length of compressed metadata
318 metaOrigLength: L # length of uncompressed metadata
319 privOffset: L # offset to private data block
320 privLength: L # length of private data block
321"""
322
323woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
324
325woffDirectoryEntryFormat = """
326 > # big endian
327 tag: 4s
328 offset: L
329 length: L # compressed length
330 origLength: L # original length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400331 checkSum: L # original checksum
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400332"""
333
334woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
335
336
Behdad Esfahbode388db52013-11-28 14:26:58 -0500337class DirectoryEntry(object):
Just7842e561999-12-16 21:34:53 +0000338
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400339 def __init__(self):
340 self.uncompressed = False # if True, always embed entry raw
341
jvrea9dfa92002-05-12 17:14:50 +0000342 def fromFile(self, file):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400343 sstruct.unpack(self.format, file.read(self.formatSize), self)
Just7842e561999-12-16 21:34:53 +0000344
jvrea9dfa92002-05-12 17:14:50 +0000345 def fromString(self, str):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400346 sstruct.unpack(self.format, str, self)
Just7842e561999-12-16 21:34:53 +0000347
jvrea9dfa92002-05-12 17:14:50 +0000348 def toString(self):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400349 return sstruct.pack(self.format, self)
Just7842e561999-12-16 21:34:53 +0000350
351 def __repr__(self):
352 if hasattr(self, "tag"):
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400353 return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
Just7842e561999-12-16 21:34:53 +0000354 else:
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400355 return "<%s at %x>" % (self.__class__.__name__, id(self))
356
357 def loadData(self, file):
358 file.seek(self.offset)
359 data = file.read(self.length)
360 assert len(data) == self.length
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400361 if hasattr(self.__class__, 'decodeData'):
362 data = self.decodeData(data)
363 return data
364
365 def saveData(self, file, data):
366 if hasattr(self.__class__, 'encodeData'):
367 data = self.encodeData(data)
368 self.length = len(data)
369 file.seek(self.offset)
370 file.write(data)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400371
372 def decodeData(self, rawData):
373 return rawData
374
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400375 def encodeData(self, data):
376 return data
377
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400378class SFNTDirectoryEntry(DirectoryEntry):
379
380 format = sfntDirectoryEntryFormat
381 formatSize = sfntDirectoryEntrySize
382
383class WOFFDirectoryEntry(DirectoryEntry):
384
385 format = woffDirectoryEntryFormat
386 formatSize = woffDirectoryEntrySize
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400387 zlibCompressionLevel = 6
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400388
389 def decodeData(self, rawData):
390 import zlib
391 if self.length == self.origLength:
392 data = rawData
393 else:
394 assert self.length < self.origLength
395 data = zlib.decompress(rawData)
396 assert len (data) == self.origLength
397 return data
398
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400399 def encodeData(self, data):
400 import zlib
401 self.origLength = len(data)
402 if not self.uncompressed:
403 compressedData = zlib.compress(data, self.zlibCompressionLevel)
404 if self.uncompressed or len(compressedData) >= self.origLength:
405 # Encode uncompressed
406 rawData = data
407 self.length = self.origLength
408 else:
409 rawData = compressedData
410 self.length = len(rawData)
411 return rawData
412
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400413class WOFFFlavorData():
414
Behdad Esfahbodb0dc6df2013-08-15 17:39:16 -0400415 Flavor = 'woff'
416
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400417 def __init__(self, reader=None):
418 self.majorVersion = None
419 self.minorVersion = None
420 self.metaData = None
421 self.privData = None
422 if reader:
423 self.majorVersion = reader.majorVersion
424 self.minorVersion = reader.minorVersion
425 if reader.metaLength:
426 reader.file.seek(reader.metaOffset)
Behdad Esfahbod153ec402013-12-04 01:15:46 -0500427 rawData = reader.file.read(reader.metaLength)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400428 assert len(rawData) == reader.metaLength
Behdad Esfahbod153ec402013-12-04 01:15:46 -0500429 import zlib
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400430 data = zlib.decompress(rawData)
431 assert len(data) == reader.metaOrigLength
432 self.metaData = data
433 if reader.privLength:
434 reader.file.seek(reader.privOffset)
Behdad Esfahbod153ec402013-12-04 01:15:46 -0500435 data = reader.file.read(reader.privLength)
Behdad Esfahbod58d74162013-08-15 15:30:55 -0400436 assert len(data) == reader.privLength
437 self.privData = data
Just7842e561999-12-16 21:34:53 +0000438
439
jvr91bca422012-10-18 12:49:22 +0000440def calcChecksum(data):
Just7842e561999-12-16 21:34:53 +0000441 """Calculate the checksum for an arbitrary block of data.
442 Optionally takes a 'start' argument, which allows you to
443 calculate a checksum in chunks by feeding it a previous
444 result.
445
446 If the data length is not a multiple of four, it assumes
447 it is to be padded with null byte.
jvr91bca422012-10-18 12:49:22 +0000448
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500449 >>> print calcChecksum(b"abcd")
jvr91bca422012-10-18 12:49:22 +0000450 1633837924
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500451 >>> print calcChecksum(b"abcdxyz")
jvr91bca422012-10-18 12:49:22 +0000452 3655064932
Just7842e561999-12-16 21:34:53 +0000453 """
Just7842e561999-12-16 21:34:53 +0000454 remainder = len(data) % 4
455 if remainder:
Behdad Esfahbod821572c2013-11-27 21:09:03 -0500456 data += b"\0" * (4 - remainder)
jvr91bca422012-10-18 12:49:22 +0000457 value = 0
458 blockSize = 4096
459 assert blockSize % 4 == 0
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500460 for i in range(0, len(data), blockSize):
jvr91bca422012-10-18 12:49:22 +0000461 block = data[i:i+blockSize]
462 longs = struct.unpack(">%dL" % (len(block) // 4), block)
463 value = (value + sum(longs)) & 0xffffffff
464 return value
Just7842e561999-12-16 21:34:53 +0000465
466
jvrea9dfa92002-05-12 17:14:50 +0000467def maxPowerOfTwo(x):
Just7842e561999-12-16 21:34:53 +0000468 """Return the highest exponent of two, so that
469 (2 ** exponent) <= x
470 """
471 exponent = 0
472 while x:
473 x = x >> 1
474 exponent = exponent + 1
Justfdea99d2000-08-23 12:34:44 +0000475 return max(exponent - 1, 0)
Just7842e561999-12-16 21:34:53 +0000476
477
jvrea9dfa92002-05-12 17:14:50 +0000478def getSearchRange(n):
Just7842e561999-12-16 21:34:53 +0000479 """Calculate searchRange, entrySelector, rangeShift for the
480 sfnt directory. 'n' is the number of tables.
481 """
482 # This stuff needs to be stored in the file, because?
jvrea9dfa92002-05-12 17:14:50 +0000483 exponent = maxPowerOfTwo(n)
Just7842e561999-12-16 21:34:53 +0000484 searchRange = (2 ** exponent) * 16
485 entrySelector = exponent
Behdad Esfahbod9fed9522014-05-27 15:54:04 -0400486 rangeShift = max(0, n * 16 - searchRange)
Just7842e561999-12-16 21:34:53 +0000487 return searchRange, entrySelector, rangeShift
488
jvr91bca422012-10-18 12:49:22 +0000489
490if __name__ == "__main__":
491 import doctest
492 doctest.testmod()