17842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format.
27842e56b97ce677b83bdab09cda48bc2d89ac75aJust
37842e56b97ce677b83bdab09cda48bc2d89ac75aJustDefines two public classes:
47842e56b97ce677b83bdab09cda48bc2d89ac75aJust	SFNTReader
57842e56b97ce677b83bdab09cda48bc2d89ac75aJust	SFNTWriter
67842e56b97ce677b83bdab09cda48bc2d89ac75aJust
77842e56b97ce677b83bdab09cda48bc2d89ac75aJust(Normally you don't have to use these classes explicitly; they are
87842e56b97ce677b83bdab09cda48bc2d89ac75aJustused automatically by ttLib.TTFont.)
97842e56b97ce677b83bdab09cda48bc2d89ac75aJust
107842e56b97ce677b83bdab09cda48bc2d89ac75aJustThe reading and writing of sfnt files is separated in two distinct
117842e56b97ce677b83bdab09cda48bc2d89ac75aJustclasses, since whenever to number of tables changes or whenever
127842e56b97ce677b83bdab09cda48bc2d89ac75aJusta table's length chages you need to rewrite the whole file anyway.
137842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""
147842e56b97ce677b83bdab09cda48bc2d89ac75aJust
151ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import
167ed91eca1eaa96b79eae780778e89bb9ec44c1eeBehdad Esfahbodfrom fontTools.misc.py23 import *
1730e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc import sstruct
1862dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbodfrom fontTools.ttLib import getSearchRange
1930e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport struct
207842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2104b3204dd168571635133d430a68175297282b1fjvr
22e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass SFNTReader(object):
237842e56b97ce677b83bdab09cda48bc2d89ac75aJust
247e91e776c9d10d3b295de06ee7f665d8106306d8pabs	def __init__(self, file, checkChecksums=1, fontNumber=-1):
257842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file = file
26ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr		self.checkChecksums = checkChecksums
2758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
2858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.flavor = None
2958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.flavorData = None
3058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.DirectoryEntry = SFNTDirectoryEntry
3158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.sfntVersion = self.file.read(4)
3258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.file.seek(0)
33ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		if self.sfntVersion == b"ttcf":
3458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(ttcHeaderFormat, self.file.read(ttcHeaderSize), self)
357e91e776c9d10d3b295de06ee7f665d8106306d8pabs			assert self.Version == 0x00010000 or self.Version == 0x00020000, "unrecognized TTC version 0x%08x" % self.Version
367e91e776c9d10d3b295de06ee7f665d8106306d8pabs			if not 0 <= fontNumber < self.numFonts:
377e91e776c9d10d3b295de06ee7f665d8106306d8pabs				from fontTools import ttLib
38cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod				raise ttLib.TTLibError("specify a font number between 0 and %d (inclusive)" % (self.numFonts - 1))
397e91e776c9d10d3b295de06ee7f665d8106306d8pabs			offsetTable = struct.unpack(">%dL" % self.numFonts, self.file.read(self.numFonts * 4))
407e91e776c9d10d3b295de06ee7f665d8106306d8pabs			if self.Version == 0x00020000:
417e91e776c9d10d3b295de06ee7f665d8106306d8pabs				pass # ignoring version 2.0 signatures
427e91e776c9d10d3b295de06ee7f665d8106306d8pabs			self.file.seek(offsetTable[fontNumber])
4358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
44ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		elif self.sfntVersion == b"wOFF":
4558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.flavor = "woff"
4658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.DirectoryEntry = WOFFDirectoryEntry
4758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(woffDirectoryFormat, self.file.read(woffDirectorySize), self)
4858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		else:
4958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
50ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		self.sfntVersion = Tag(self.sfntVersion)
5158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
52ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
537842e56b97ce677b83bdab09cda48bc2d89ac75aJust			from fontTools import ttLib
54cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod			raise ttLib.TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
557842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.tables = {}
567842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(self.numTables):
5758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			entry = self.DirectoryEntry()
58ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			entry.fromFile(self.file)
596338375bd8b6d8a6888972c3cabc9a30431c152fBehdad Esfahbod			self.tables[Tag(entry.tag)] = entry
6058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
6158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		# Load flavor data if any
6258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		if self.flavor == "woff":
6358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.flavorData = WOFFFlavorData(self)
6458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
657842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def has_key(self, tag):
66bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod		return tag in self.tables
67bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod
68bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod	__contains__ = has_key
697842e56b97ce677b83bdab09cda48bc2d89ac75aJust
707842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def keys(self):
717842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return self.tables.keys()
727842e56b97ce677b83bdab09cda48bc2d89ac75aJust
737842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __getitem__(self, tag):
747842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""Fetch the raw table data."""
75ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		entry = self.tables[Tag(tag)]
7658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		data = entry.loadData (self.file)
77ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr		if self.checkChecksums:
787842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if tag == 'head':
797842e56b97ce677b83bdab09cda48bc2d89ac75aJust				# Beh: we have to special-case the 'head' table.
80821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod				checksum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
817842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
82ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr				checksum = calcChecksum(data)
83ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			if self.checkChecksums > 1:
847842e56b97ce677b83bdab09cda48bc2d89ac75aJust				# Be obnoxious, and barf when it's wrong
857842e56b97ce677b83bdab09cda48bc2d89ac75aJust				assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
86180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod			elif checksum != entry.checkSum:
877842e56b97ce677b83bdab09cda48bc2d89ac75aJust				# Be friendly, and just print a warning.
883ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod				print("bad checksum for '%s' table" % tag)
897842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return data
907842e56b97ce677b83bdab09cda48bc2d89ac75aJust
91f70746325687393330f7a765814a0fe78f11f847jvr	def __delitem__(self, tag):
92ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		del self.tables[Tag(tag)]
93f70746325687393330f7a765814a0fe78f11f847jvr
947842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def close(self):
957842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.close()
967842e56b97ce677b83bdab09cda48bc2d89ac75aJust
977842e56b97ce677b83bdab09cda48bc2d89ac75aJust
98e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass SFNTWriter(object):
997842e56b97ce677b83bdab09cda48bc2d89ac75aJust
100b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
101b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		     flavor=None, flavorData=None):
1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file = file
1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.numTables = numTables
104ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod		self.sfntVersion = Tag(sfntVersion)
105b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.flavor = flavor
106b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.flavorData = flavorData
107b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
108b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if self.flavor == "woff":
109b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.directoryFormat = woffDirectoryFormat
110b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.directorySize = woffDirectorySize
111b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.DirectoryEntry = WOFFDirectoryEntry
112b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
113b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.signature = "wOFF"
114b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		else:
115b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			assert not self.flavor,  "Unknown flavor '%s'" % self.flavor
116b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.directoryFormat = sfntDirectoryFormat
117b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.directorySize = sfntDirectorySize
118b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.DirectoryEntry = SFNTDirectoryEntry
119b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
12062dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod			self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables, 16)
121b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
122b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.nextTableOffset = self.directorySize + numTables * self.DirectoryEntry.formatSize
1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# clear out directory area
1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(self.nextTableOffset)
125b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		# make sure we're actually where we want to be. (old cStringIO bug)
126821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod		self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.tables = {}
1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __setitem__(self, tag, data):
1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""Write raw table data to disk."""
131b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		reuse = False
132bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod		if tag in self.tables:
1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust			# We've written this table to file before. If the length
13404b3204dd168571635133d430a68175297282b1fjvr			# of the data is still the same, we allow overwriting it.
1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry = self.tables[tag]
136b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			assert not hasattr(entry.__class__, 'encodeData')
137180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod			if len(data) != entry.length:
1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust				from fontTools import ttLib
139cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod				raise ttLib.TTLibError("cannot rewrite '%s' table: length does not match directory entry" % tag)
140b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			reuse = True
1417842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
142b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			entry = self.DirectoryEntry()
1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry.tag = tag
144b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
145b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if tag == 'head':
146821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod			entry.checkSum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
147b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.headTable = data
148b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			entry.uncompressed = True
149b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		else:
150b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			entry.checkSum = calcChecksum(data)
151b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
152b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		entry.offset = self.nextTableOffset
153b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		entry.saveData (self.file, data)
154b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
155b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if not reuse:
156b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
157b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
158c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# Add NUL bytes to pad the table data to a 4-byte boundary.
159c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# Don't depend on f.seek() as we need to add the padding even if no
160c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# subsequent write follows (seek is lazy), ie. after the final table
161c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# in the font.
162821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod		self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
163c63ac64007caf769f1e6a267403280264d4ae7bdjvr		assert self.nextTableOffset == self.file.tell()
1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1657842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.tables[tag] = entry
1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust
16728ae1962292b66ad67117aef2a99d5735a70b779jvr	def close(self):
1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""All tables must have been written to disk. Now write the
1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust		directory.
1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""
171ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod		tables = sorted(self.tables.items())
172180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod		if len(tables) != self.numTables:
1737842e56b97ce677b83bdab09cda48bc2d89ac75aJust			from fontTools import ttLib
174cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod			raise ttLib.TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(tables)))
175b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
176b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if self.flavor == "woff":
177ac4672e4510a79a56c4983ad28b42724c30ea9d1Behdad Esfahbod			self.signature = b"wOFF"
178b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.reserved = 0
179b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
180b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.totalSfntSize = 12
181b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.totalSfntSize += 16 * len(tables)
182b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			for tag, entry in tables:
183b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.totalSfntSize += (entry.origLength + 3) & ~3
184b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
185b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			data = self.flavorData if self.flavorData else WOFFFlavorData()
1869e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod			if data.majorVersion is not None and data.minorVersion is not None:
187b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.majorVersion = data.majorVersion
188b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.minorVersion = data.minorVersion
189b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			else:
190b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				if hasattr(self, 'headTable'):
191b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod					self.majorVersion, self.minorVersion = struct.unpack(">HH", self.headTable[4:8])
192b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				else:
193b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod					self.majorVersion = self.minorVersion = 0
194b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			if data.metaData:
195b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.metaOrigLength = len(data.metaData)
196b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.file.seek(0,2)
197b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.metaOffset = self.file.tell()
198153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod				import zlib
199b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				compressedMetaData = zlib.compress(data.metaData)
200b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.metaLength = len(compressedMetaData)
201b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.file.write(compressedMetaData)
202b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			else:
203b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.metaOffset = self.metaLength = self.metaOrigLength = 0
204b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			if data.privData:
205b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.file.seek(0,2)
206b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				off = self.file.tell()
207b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				paddedOff = (off + 3) & ~3
208b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.file.write('\0' * (paddedOff - off))
209b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.privOffset = self.file.tell()
210b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.privLength = len(data.privData)
211b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.file.write(data.privData)
212b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			else:
213b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				self.privOffset = self.privLength = 0
214b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
215b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.file.seek(0,2)
216b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.length = self.file.tell()
217b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
218b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		else:
219b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			assert not self.flavor,  "Unknown flavor '%s'" % self.flavor
220b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			pass
2217842e56b97ce677b83bdab09cda48bc2d89ac75aJust
222b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		directory = sstruct.pack(self.directoryFormat, self)
2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust
224b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.file.seek(self.directorySize)
225f509c0f0707a85184525243dfb6efeba043dc793jvr		seenHead = 0
2267842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for tag, entry in tables:
227f509c0f0707a85184525243dfb6efeba043dc793jvr			if tag == "head":
228f509c0f0707a85184525243dfb6efeba043dc793jvr				seenHead = 1
229ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			directory = directory + entry.toString()
230f509c0f0707a85184525243dfb6efeba043dc793jvr		if seenHead:
23191bca4244286fb519c93fe92329da96b0e6f32eejvr			self.writeMasterChecksum(directory)
2327842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(0)
2337842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.write(directory)
23491bca4244286fb519c93fe92329da96b0e6f32eejvr
23591bca4244286fb519c93fe92329da96b0e6f32eejvr	def _calcMasterChecksum(self, directory):
2367842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# calculate checkSumAdjustment
237c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod		tags = list(self.tables.keys())
23891bca4244286fb519c93fe92329da96b0e6f32eejvr		checksums = []
2397842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(len(tags)):
24091bca4244286fb519c93fe92329da96b0e6f32eejvr			checksums.append(self.tables[tags[i]].checkSum)
24191bca4244286fb519c93fe92329da96b0e6f32eejvr
242b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		# TODO(behdad) I'm fairly sure the checksum for woff is not working correctly.
243b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		# Haven't debugged.
244b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if self.DirectoryEntry != SFNTDirectoryEntry:
245b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			# Create a SFNT directory for checksum calculation purposes
24662dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod			self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables, 16)
247b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			directory = sstruct.pack(sfntDirectoryFormat, self)
248ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod			tables = sorted(self.tables.items())
249b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			for tag, entry in tables:
250b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				sfntEntry = SFNTDirectoryEntry()
251b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				for item in ['tag', 'checkSum', 'offset', 'length']:
252b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod					setattr(sfntEntry, item, getattr(entry, item))
253b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod				directory = directory + sfntEntry.toString()
254b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
2557842e56b97ce677b83bdab09cda48bc2d89ac75aJust		directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
2567842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert directory_end == len(directory)
25791bca4244286fb519c93fe92329da96b0e6f32eejvr
25891bca4244286fb519c93fe92329da96b0e6f32eejvr		checksums.append(calcChecksum(directory))
25991bca4244286fb519c93fe92329da96b0e6f32eejvr		checksum = sum(checksums) & 0xffffffff
2607842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# BiboAfba!
26191bca4244286fb519c93fe92329da96b0e6f32eejvr		checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
26291bca4244286fb519c93fe92329da96b0e6f32eejvr		return checksumadjustment
26391bca4244286fb519c93fe92329da96b0e6f32eejvr
26491bca4244286fb519c93fe92329da96b0e6f32eejvr	def writeMasterChecksum(self, directory):
26591bca4244286fb519c93fe92329da96b0e6f32eejvr		checksumadjustment = self._calcMasterChecksum(directory)
2667842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# write the checksum to the file
2677842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(self.tables['head'].offset + 8)
2680e2aecec53da493c44d6a5c253910a9475da218apabs		self.file.write(struct.pack(">L", checksumadjustment))
2691ebda677eb6061e809c422fc6d3b483f965a8281jvr
2707842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2717842e56b97ce677b83bdab09cda48bc2d89ac75aJust# -- sfnt directory helpers and cruft
2727842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2737e91e776c9d10d3b295de06ee7f665d8106306d8pabsttcHeaderFormat = """
2747e91e776c9d10d3b295de06ee7f665d8106306d8pabs		> # big endian
2757e91e776c9d10d3b295de06ee7f665d8106306d8pabs		TTCTag:                  4s # "ttcf"
2767e91e776c9d10d3b295de06ee7f665d8106306d8pabs		Version:                 L  # 0x00010000 or 0x00020000
2777e91e776c9d10d3b295de06ee7f665d8106306d8pabs		numFonts:                L  # number of fonts
2787e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# OffsetTable[numFonts]: L  # array with offsets from beginning of file
2797e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# ulDsigTag:             L  # version 2.0 only
2807e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# ulDsigLength:          L  # version 2.0 only
2817e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# ulDsigOffset:          L  # version 2.0 only
2827e91e776c9d10d3b295de06ee7f665d8106306d8pabs"""
2837e91e776c9d10d3b295de06ee7f665d8106306d8pabs
2847e91e776c9d10d3b295de06ee7f665d8106306d8pabsttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
2857e91e776c9d10d3b295de06ee7f665d8106306d8pabs
2867842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryFormat = """
2877842e56b97ce677b83bdab09cda48bc2d89ac75aJust		> # big endian
288b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		sfntVersion:    4s
289b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		numTables:      H    # number of tables
290b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		searchRange:    H    # (max2 <= numTables)*16
291b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		entrySelector:  H    # log2(max2 <= numTables)
292b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		rangeShift:     H    # numTables*16-searchRange
2937842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""
2947842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2957842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
2967842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2977842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryEntryFormat = """
2987842e56b97ce677b83bdab09cda48bc2d89ac75aJust		> # big endian
299b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		tag:            4s
3000e2aecec53da493c44d6a5c253910a9475da218apabs		checkSum:       L
3010e2aecec53da493c44d6a5c253910a9475da218apabs		offset:         L
3020e2aecec53da493c44d6a5c253910a9475da218apabs		length:         L
3037842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""
3047842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3057842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
3067842e56b97ce677b83bdab09cda48bc2d89ac75aJust
30758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectoryFormat = """
30858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		> # big endian
30958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		signature:      4s   # "wOFF"
31058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		sfntVersion:    4s
31158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		length:         L    # total woff file size
31258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		numTables:      H    # number of tables
31358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		reserved:       H    # set to 0
31458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		totalSfntSize:  L    # uncompressed size
31558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		majorVersion:   H    # major version of WOFF file
31658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		minorVersion:   H    # minor version of WOFF file
31758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		metaOffset:     L    # offset to metadata block
31858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		metaLength:     L    # length of compressed metadata
31958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		metaOrigLength: L    # length of uncompressed metadata
32058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		privOffset:     L    # offset to private data block
32158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		privLength:     L    # length of private data block
32258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod"""
32358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
32458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
32558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
32658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectoryEntryFormat = """
32758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		> # big endian
32858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		tag:            4s
32958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		offset:         L
33058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		length:         L    # compressed length
33158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		origLength:     L    # original length
332b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		checkSum:       L    # original checksum
33358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod"""
33458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
33558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
33658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
33758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
338e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass DirectoryEntry(object):
3397842e56b97ce677b83bdab09cda48bc2d89ac75aJust
340b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	def __init__(self):
341b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.uncompressed = False # if True, always embed entry raw
342b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
343ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	def fromFile(self, file):
34458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		sstruct.unpack(self.format, file.read(self.formatSize), self)
3457842e56b97ce677b83bdab09cda48bc2d89ac75aJust
346ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	def fromString(self, str):
34758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		sstruct.unpack(self.format, str, self)
3487842e56b97ce677b83bdab09cda48bc2d89ac75aJust
349ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	def toString(self):
35058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return sstruct.pack(self.format, self)
3517842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3527842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __repr__(self):
3537842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if hasattr(self, "tag"):
35458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
3557842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
35658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			return "<%s at %x>" % (self.__class__.__name__, id(self))
35758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
35858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def loadData(self, file):
35958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		file.seek(self.offset)
36058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		data = file.read(self.length)
36158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		assert len(data) == self.length
362b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if hasattr(self.__class__, 'decodeData'):
363b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			data = self.decodeData(data)
364b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		return data
365b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
366b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	def saveData(self, file, data):
367b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if hasattr(self.__class__, 'encodeData'):
368b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			data = self.encodeData(data)
369b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.length = len(data)
370b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		file.seek(self.offset)
371b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		file.write(data)
37258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
37358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def decodeData(self, rawData):
37458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return rawData
37558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
376b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	def encodeData(self, data):
377b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		return data
378b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
37958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass SFNTDirectoryEntry(DirectoryEntry):
38058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
38158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	format = sfntDirectoryEntryFormat
38258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	formatSize = sfntDirectoryEntrySize
38358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
38458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass WOFFDirectoryEntry(DirectoryEntry):
38558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
38658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	format = woffDirectoryEntryFormat
38758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	formatSize = woffDirectoryEntrySize
388b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	zlibCompressionLevel = 6
38958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
39058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def decodeData(self, rawData):
39158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		import zlib
39258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		if self.length == self.origLength:
39358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			data = rawData
39458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		else:
39558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			assert self.length < self.origLength
39658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			data = zlib.decompress(rawData)
39758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			assert len (data) == self.origLength
39858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return data
39958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
400b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	def encodeData(self, data):
401b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		import zlib
402b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		self.origLength = len(data)
403b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if not self.uncompressed:
404b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			compressedData = zlib.compress(data, self.zlibCompressionLevel)
405b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		if self.uncompressed or len(compressedData) >= self.origLength:
406b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			# Encode uncompressed
407b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			rawData = data
408b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.length = self.origLength
409b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		else:
410b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			rawData = compressedData
411b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod			self.length = len(rawData)
412b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod		return rawData
413b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
41458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass WOFFFlavorData():
41558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
416b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod	Flavor = 'woff'
417b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod
41858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def __init__(self, reader=None):
41958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.majorVersion = None
42058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.minorVersion = None
42158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.metaData = None
42258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.privData = None
42358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		if reader:
42458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.majorVersion = reader.majorVersion
42558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.minorVersion = reader.minorVersion
42658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			if reader.metaLength:
42758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				reader.file.seek(reader.metaOffset)
428153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod				rawData = reader.file.read(reader.metaLength)
42958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				assert len(rawData) == reader.metaLength
430153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod				import zlib
43158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				data = zlib.decompress(rawData)
43258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				assert len(data) == reader.metaOrigLength
43358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				self.metaData = data
43458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			if reader.privLength:
43558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				reader.file.seek(reader.privOffset)
436153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod				data = reader.file.read(reader.privLength)
43758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				assert len(data) == reader.privLength
43858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				self.privData = data
4397842e56b97ce677b83bdab09cda48bc2d89ac75aJust
4407842e56b97ce677b83bdab09cda48bc2d89ac75aJust
44191bca4244286fb519c93fe92329da96b0e6f32eejvrdef calcChecksum(data):
4427842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""Calculate the checksum for an arbitrary block of data.
4437842e56b97ce677b83bdab09cda48bc2d89ac75aJust	Optionally takes a 'start' argument, which allows you to
4447842e56b97ce677b83bdab09cda48bc2d89ac75aJust	calculate a checksum in chunks by feeding it a previous
4457842e56b97ce677b83bdab09cda48bc2d89ac75aJust	result.
4467842e56b97ce677b83bdab09cda48bc2d89ac75aJust
4477842e56b97ce677b83bdab09cda48bc2d89ac75aJust	If the data length is not a multiple of four, it assumes
4487842e56b97ce677b83bdab09cda48bc2d89ac75aJust	it is to be padded with null byte.
44991bca4244286fb519c93fe92329da96b0e6f32eejvr
450821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod		>>> print calcChecksum(b"abcd")
45191bca4244286fb519c93fe92329da96b0e6f32eejvr		1633837924
452821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod		>>> print calcChecksum(b"abcdxyz")
45391bca4244286fb519c93fe92329da96b0e6f32eejvr		3655064932
4547842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
4557842e56b97ce677b83bdab09cda48bc2d89ac75aJust	remainder = len(data) % 4
4567842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if remainder:
457821572c9a92d338a7ecbb4261c08ce378eb5434dBehdad Esfahbod		data += b"\0" * (4 - remainder)
45891bca4244286fb519c93fe92329da96b0e6f32eejvr	value = 0
45991bca4244286fb519c93fe92329da96b0e6f32eejvr	blockSize = 4096
46091bca4244286fb519c93fe92329da96b0e6f32eejvr	assert blockSize % 4 == 0
46197dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod	for i in range(0, len(data), blockSize):
46291bca4244286fb519c93fe92329da96b0e6f32eejvr		block = data[i:i+blockSize]
46391bca4244286fb519c93fe92329da96b0e6f32eejvr		longs = struct.unpack(">%dL" % (len(block) // 4), block)
46491bca4244286fb519c93fe92329da96b0e6f32eejvr		value = (value + sum(longs)) & 0xffffffff
46591bca4244286fb519c93fe92329da96b0e6f32eejvr	return value
4667842e56b97ce677b83bdab09cda48bc2d89ac75aJust
4677842e56b97ce677b83bdab09cda48bc2d89ac75aJust
46891bca4244286fb519c93fe92329da96b0e6f32eejvrif __name__ == "__main__":
46991bca4244286fb519c93fe92329da96b0e6f32eejvr    import doctest
47091bca4244286fb519c93fe92329da96b0e6f32eejvr    doctest.testmod()
471