sfnt.py revision 58d7416124dc0ebaa3faccb1b77dd5f7926a628a
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
159be387c94ff8199f8031b7f11f06c52cce5ccf6djvrimport sys
167842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport struct, sstruct
177842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport os
187842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1904b3204dd168571635133d430a68175297282b1fjvr
207842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass SFNTReader:
217842e56b97ce677b83bdab09cda48bc2d89ac75aJust
227e91e776c9d10d3b295de06ee7f665d8106306d8pabs	def __init__(self, file, checkChecksums=1, fontNumber=-1):
237842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file = file
24ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr		self.checkChecksums = checkChecksums
2558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
2658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.flavor = None
2758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.flavorData = None
2858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.DirectoryEntry = SFNTDirectoryEntry
2958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.sfntVersion = self.file.read(4)
3058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.file.seek(0)
317e91e776c9d10d3b295de06ee7f665d8106306d8pabs		if self.sfntVersion == "ttcf":
3258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(ttcHeaderFormat, self.file.read(ttcHeaderSize), self)
337e91e776c9d10d3b295de06ee7f665d8106306d8pabs			assert self.Version == 0x00010000 or self.Version == 0x00020000, "unrecognized TTC version 0x%08x" % self.Version
347e91e776c9d10d3b295de06ee7f665d8106306d8pabs			if not 0 <= fontNumber < self.numFonts:
357e91e776c9d10d3b295de06ee7f665d8106306d8pabs				from fontTools import ttLib
367e91e776c9d10d3b295de06ee7f665d8106306d8pabs				raise ttLib.TTLibError, "specify a font number between 0 and %d (inclusive)" % (self.numFonts - 1)
377e91e776c9d10d3b295de06ee7f665d8106306d8pabs			offsetTable = struct.unpack(">%dL" % self.numFonts, self.file.read(self.numFonts * 4))
387e91e776c9d10d3b295de06ee7f665d8106306d8pabs			if self.Version == 0x00020000:
397e91e776c9d10d3b295de06ee7f665d8106306d8pabs				pass # ignoring version 2.0 signatures
407e91e776c9d10d3b295de06ee7f665d8106306d8pabs			self.file.seek(offsetTable[fontNumber])
4158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
4258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		elif self.sfntVersion == "wOFF":
4358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.flavor = "woff"
4458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.DirectoryEntry = WOFFDirectoryEntry
4558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(woffDirectoryFormat, self.file.read(woffDirectorySize), self)
4658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		else:
4758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			sstruct.unpack(sfntDirectoryFormat, self.file.read(sfntDirectorySize), self)
4858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
497842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if self.sfntVersion not in ("\000\001\000\000", "OTTO", "true"):
507842e56b97ce677b83bdab09cda48bc2d89ac75aJust			from fontTools import ttLib
517842e56b97ce677b83bdab09cda48bc2d89ac75aJust			raise ttLib.TTLibError, "Not a TrueType or OpenType font (bad sfntVersion)"
527842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.tables = {}
537842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(self.numTables):
5458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			entry = self.DirectoryEntry()
55ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			entry.fromFile(self.file)
56ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr			if entry.length > 0:
57ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr				self.tables[entry.tag] = entry
58ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr			else:
59ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr				# Ignore zero-length tables. This doesn't seem to be documented,
60ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr				# yet it's apparently how the Windows TT rasterizer behaves.
61ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr				# Besides, at least one font has been sighted which actually
62ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr				# *has* a zero-length table.
63ce1d50aca8642b52b28deb0d0cb4f3e8594e574ejvr				pass
6458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
6558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		# Load flavor data if any
6658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		if self.flavor == "woff":
6758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.flavorData = WOFFFlavorData(self)
6858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
697842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def has_key(self, tag):
707842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return self.tables.has_key(tag)
717842e56b97ce677b83bdab09cda48bc2d89ac75aJust
727842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def keys(self):
737842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return self.tables.keys()
747842e56b97ce677b83bdab09cda48bc2d89ac75aJust
757842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __getitem__(self, tag):
767842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""Fetch the raw table data."""
777842e56b97ce677b83bdab09cda48bc2d89ac75aJust		entry = self.tables[tag]
7858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		data = entry.loadData (self.file)
79ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr		if self.checkChecksums:
807842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if tag == 'head':
817842e56b97ce677b83bdab09cda48bc2d89ac75aJust				# Beh: we have to special-case the 'head' table.
82ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr				checksum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:])
837842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
84ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr				checksum = calcChecksum(data)
85ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			if self.checkChecksums > 1:
867842e56b97ce677b83bdab09cda48bc2d89ac75aJust				# Be obnoxious, and barf when it's wrong
877842e56b97ce677b83bdab09cda48bc2d89ac75aJust				assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
887842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif checksum <> entry.checkSum:
897842e56b97ce677b83bdab09cda48bc2d89ac75aJust				# Be friendly, and just print a warning.
907842e56b97ce677b83bdab09cda48bc2d89ac75aJust				print "bad checksum for '%s' table" % tag
917842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return data
927842e56b97ce677b83bdab09cda48bc2d89ac75aJust
93f70746325687393330f7a765814a0fe78f11f847jvr	def __delitem__(self, tag):
94f70746325687393330f7a765814a0fe78f11f847jvr		del self.tables[tag]
95f70746325687393330f7a765814a0fe78f11f847jvr
967842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def close(self):
977842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.close()
987842e56b97ce677b83bdab09cda48bc2d89ac75aJust
997842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1007842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass SFNTWriter:
1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __init__(self, file, numTables, sfntVersion="\000\001\000\000"):
1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file = file
1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.numTables = numTables
1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.sfntVersion = sfntVersion
106ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr		self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables)
1077842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.nextTableOffset = sfntDirectorySize + numTables * sfntDirectoryEntrySize
1087842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# clear out directory area
1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(self.nextTableOffset)
1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# make sure we're actually where we want to be. (XXX old cStringIO bug)
1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.tables = {}
1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1147842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __setitem__(self, tag, data):
1157842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""Write raw table data to disk."""
1167842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if self.tables.has_key(tag):
1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust			# We've written this table to file before. If the length
11804b3204dd168571635133d430a68175297282b1fjvr			# of the data is still the same, we allow overwriting it.
1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry = self.tables[tag]
1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if len(data) <> entry.length:
1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust				from fontTools import ttLib
1227842e56b97ce677b83bdab09cda48bc2d89ac75aJust				raise ttLib.TTLibError, "cannot rewrite '%s' table: length does not match directory entry" % tag
1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry = SFNTDirectoryEntry()
1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry.tag = tag
1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry.offset = self.nextTableOffset
1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust			entry.length = len(data)
1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.nextTableOffset = self.nextTableOffset + ((len(data) + 3) & ~3)
1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(entry.offset)
1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.write(data)
131c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# Add NUL bytes to pad the table data to a 4-byte boundary.
132c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# Don't depend on f.seek() as we need to add the padding even if no
133c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# subsequent write follows (seek is lazy), ie. after the final table
134c63ac64007caf769f1e6a267403280264d4ae7bdjvr		# in the font.
1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
136c63ac64007caf769f1e6a267403280264d4ae7bdjvr		assert self.nextTableOffset == self.file.tell()
1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if tag == 'head':
139ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			entry.checkSum = calcChecksum(data[:8] + '\0\0\0\0' + data[12:])
1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
141ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			entry.checkSum = calcChecksum(data)
1427842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.tables[tag] = entry
1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust
14428ae1962292b66ad67117aef2a99d5735a70b779jvr	def close(self):
1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""All tables must have been written to disk. Now write the
1467842e56b97ce677b83bdab09cda48bc2d89ac75aJust		directory.
1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust		"""
1487842e56b97ce677b83bdab09cda48bc2d89ac75aJust		tables = self.tables.items()
1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust		tables.sort()
1507842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if len(tables) <> self.numTables:
1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust			from fontTools import ttLib
1527842e56b97ce677b83bdab09cda48bc2d89ac75aJust			raise ttLib.TTLibError, "wrong number of tables; expected %d, found %d" % (self.numTables, len(tables))
1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1547842e56b97ce677b83bdab09cda48bc2d89ac75aJust		directory = sstruct.pack(sfntDirectoryFormat, self)
1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(sfntDirectorySize)
157f509c0f0707a85184525243dfb6efeba043dc793jvr		seenHead = 0
1587842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for tag, entry in tables:
159f509c0f0707a85184525243dfb6efeba043dc793jvr			if tag == "head":
160f509c0f0707a85184525243dfb6efeba043dc793jvr				seenHead = 1
161ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr			directory = directory + entry.toString()
162f509c0f0707a85184525243dfb6efeba043dc793jvr		if seenHead:
16391bca4244286fb519c93fe92329da96b0e6f32eejvr			self.writeMasterChecksum(directory)
1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(0)
1657842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.write(directory)
16691bca4244286fb519c93fe92329da96b0e6f32eejvr
16791bca4244286fb519c93fe92329da96b0e6f32eejvr	def _calcMasterChecksum(self, directory):
1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# calculate checkSumAdjustment
1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust		tags = self.tables.keys()
17091bca4244286fb519c93fe92329da96b0e6f32eejvr		checksums = []
1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(len(tags)):
17291bca4244286fb519c93fe92329da96b0e6f32eejvr			checksums.append(self.tables[tags[i]].checkSum)
17391bca4244286fb519c93fe92329da96b0e6f32eejvr
1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust		directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
1757842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert directory_end == len(directory)
17691bca4244286fb519c93fe92329da96b0e6f32eejvr
17791bca4244286fb519c93fe92329da96b0e6f32eejvr		checksums.append(calcChecksum(directory))
17891bca4244286fb519c93fe92329da96b0e6f32eejvr		checksum = sum(checksums) & 0xffffffff
1797842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# BiboAfba!
18091bca4244286fb519c93fe92329da96b0e6f32eejvr		checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
18191bca4244286fb519c93fe92329da96b0e6f32eejvr		return checksumadjustment
18291bca4244286fb519c93fe92329da96b0e6f32eejvr
18391bca4244286fb519c93fe92329da96b0e6f32eejvr	def writeMasterChecksum(self, directory):
18491bca4244286fb519c93fe92329da96b0e6f32eejvr		checksumadjustment = self._calcMasterChecksum(directory)
1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust		# write the checksum to the file
1867842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.seek(self.tables['head'].offset + 8)
1870e2aecec53da493c44d6a5c253910a9475da218apabs		self.file.write(struct.pack(">L", checksumadjustment))
1881ebda677eb6061e809c422fc6d3b483f965a8281jvr
1897842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust# -- sfnt directory helpers and cruft
1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1927e91e776c9d10d3b295de06ee7f665d8106306d8pabsttcHeaderFormat = """
1937e91e776c9d10d3b295de06ee7f665d8106306d8pabs		> # big endian
1947e91e776c9d10d3b295de06ee7f665d8106306d8pabs		TTCTag:                  4s # "ttcf"
1957e91e776c9d10d3b295de06ee7f665d8106306d8pabs		Version:                 L  # 0x00010000 or 0x00020000
1967e91e776c9d10d3b295de06ee7f665d8106306d8pabs		numFonts:                L  # number of fonts
1977e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# OffsetTable[numFonts]: L  # array with offsets from beginning of file
1987e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# ulDsigTag:             L  # version 2.0 only
1997e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# ulDsigLength:          L  # version 2.0 only
2007e91e776c9d10d3b295de06ee7f665d8106306d8pabs		# ulDsigOffset:          L  # version 2.0 only
2017e91e776c9d10d3b295de06ee7f665d8106306d8pabs"""
2027e91e776c9d10d3b295de06ee7f665d8106306d8pabs
2037e91e776c9d10d3b295de06ee7f665d8106306d8pabsttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
2047e91e776c9d10d3b295de06ee7f665d8106306d8pabs
2057842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryFormat = """
2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust		> # big endian
207b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		sfntVersion:    4s
208b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		numTables:      H    # number of tables
209b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		searchRange:    H    # (max2 <= numTables)*16
210b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		entrySelector:  H    # log2(max2 <= numTables)
211b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		rangeShift:     H    # numTables*16-searchRange
2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""
2137842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2147842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2167842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryEntryFormat = """
2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust		> # big endian
218b0e5f299ffc5794f1960e694a855a05fc9cea01ejvr		tag:            4s
2190e2aecec53da493c44d6a5c253910a9475da218apabs		checkSum:       L
2200e2aecec53da493c44d6a5c253910a9475da218apabs		offset:         L
2210e2aecec53da493c44d6a5c253910a9475da218apabs		length:         L
2227842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""
2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2247842e56b97ce677b83bdab09cda48bc2d89ac75aJustsfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
2257842e56b97ce677b83bdab09cda48bc2d89ac75aJust
22658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectoryFormat = """
22758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		> # big endian
22858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		signature:      4s   # "wOFF"
22958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		sfntVersion:    4s
23058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		length:         L    # total woff file size
23158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		numTables:      H    # number of tables
23258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		reserved:       H    # set to 0
23358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		totalSfntSize:  L    # uncompressed size
23458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		majorVersion:   H    # major version of WOFF file
23558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		minorVersion:   H    # minor version of WOFF file
23658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		metaOffset:     L    # offset to metadata block
23758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		metaLength:     L    # length of compressed metadata
23858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		metaOrigLength: L    # length of uncompressed metadata
23958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		privOffset:     L    # offset to private data block
24058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		privLength:     L    # length of private data block
24158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod"""
24258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
24358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
24458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
24558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectoryEntryFormat = """
24658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		> # big endian
24758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		tag:            4s
24858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		offset:         L
24958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		length:         L    # compressed length
25058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		origLength:     L    # original length
25158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		checksum:       L    # original checksum
25258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod"""
25358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
25458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad EsfahbodwoffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
25558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
25658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
25758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass DirectoryEntry:
2587842e56b97ce677b83bdab09cda48bc2d89ac75aJust
259ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	def fromFile(self, file):
26058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		sstruct.unpack(self.format, file.read(self.formatSize), self)
2617842e56b97ce677b83bdab09cda48bc2d89ac75aJust
262ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	def fromString(self, str):
26358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		sstruct.unpack(self.format, str, self)
2647842e56b97ce677b83bdab09cda48bc2d89ac75aJust
265ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	def toString(self):
26658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return sstruct.pack(self.format, self)
2677842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2687842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __repr__(self):
2697842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if hasattr(self, "tag"):
27058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
2717842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
27258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			return "<%s at %x>" % (self.__class__.__name__, id(self))
27358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
27458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def loadData(self, file):
27558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		file.seek(self.offset)
27658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		data = file.read(self.length)
27758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		assert len(data) == self.length
27858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return self.decodeData (data)
27958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
28058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def decodeData(self, rawData):
28158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return rawData
28258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
28358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass SFNTDirectoryEntry(DirectoryEntry):
28458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
28558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	format = sfntDirectoryEntryFormat
28658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	formatSize = sfntDirectoryEntrySize
28758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
28858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass WOFFDirectoryEntry(DirectoryEntry):
28958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
29058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	format = woffDirectoryEntryFormat
29158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	formatSize = woffDirectoryEntrySize
29258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
29358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def decodeData(self, rawData):
29458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		import zlib
29558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		if self.length == self.origLength:
29658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			data = rawData
29758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		else:
29858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			assert self.length < self.origLength
29958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			data = zlib.decompress(rawData)
30058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			assert len (data) == self.origLength
30158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		return data
30258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
30358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbodclass WOFFFlavorData():
30458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod
30558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod	def __init__(self, reader=None):
30658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.majorVersion = None
30758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.minorVersion = None
30858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.metaData = None
30958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		self.privData = None
31058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod		if reader:
31158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.majorVersion = reader.majorVersion
31258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			self.minorVersion = reader.minorVersion
31358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			if reader.metaLength:
31458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				reader.file.seek(reader.metaOffset)
31558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				rawData = read.file.read(reader.metaLength)
31658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				assert len(rawData) == reader.metaLength
31758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				data = zlib.decompress(rawData)
31858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				assert len(data) == reader.metaOrigLength
31958d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				self.metaData = data
32058d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod			if reader.privLength:
32158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				reader.file.seek(reader.privOffset)
32258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				data = read.file.read(reader.privLength)
32358d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				assert len(data) == reader.privLength
32458d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod				self.privData = data
3257842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3267842e56b97ce677b83bdab09cda48bc2d89ac75aJust
32791bca4244286fb519c93fe92329da96b0e6f32eejvrdef calcChecksum(data):
3287842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""Calculate the checksum for an arbitrary block of data.
3297842e56b97ce677b83bdab09cda48bc2d89ac75aJust	Optionally takes a 'start' argument, which allows you to
3307842e56b97ce677b83bdab09cda48bc2d89ac75aJust	calculate a checksum in chunks by feeding it a previous
3317842e56b97ce677b83bdab09cda48bc2d89ac75aJust	result.
3327842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3337842e56b97ce677b83bdab09cda48bc2d89ac75aJust	If the data length is not a multiple of four, it assumes
3347842e56b97ce677b83bdab09cda48bc2d89ac75aJust	it is to be padded with null byte.
33591bca4244286fb519c93fe92329da96b0e6f32eejvr
33691bca4244286fb519c93fe92329da96b0e6f32eejvr		>>> print calcChecksum("abcd")
33791bca4244286fb519c93fe92329da96b0e6f32eejvr		1633837924
33891bca4244286fb519c93fe92329da96b0e6f32eejvr		>>> print calcChecksum("abcdxyz")
33991bca4244286fb519c93fe92329da96b0e6f32eejvr		3655064932
3407842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
3417842e56b97ce677b83bdab09cda48bc2d89ac75aJust	remainder = len(data) % 4
3427842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if remainder:
34391bca4244286fb519c93fe92329da96b0e6f32eejvr		data += "\0" * (4 - remainder)
34491bca4244286fb519c93fe92329da96b0e6f32eejvr	value = 0
34591bca4244286fb519c93fe92329da96b0e6f32eejvr	blockSize = 4096
34691bca4244286fb519c93fe92329da96b0e6f32eejvr	assert blockSize % 4 == 0
34791bca4244286fb519c93fe92329da96b0e6f32eejvr	for i in xrange(0, len(data), blockSize):
34891bca4244286fb519c93fe92329da96b0e6f32eejvr		block = data[i:i+blockSize]
34991bca4244286fb519c93fe92329da96b0e6f32eejvr		longs = struct.unpack(">%dL" % (len(block) // 4), block)
35091bca4244286fb519c93fe92329da96b0e6f32eejvr		value = (value + sum(longs)) & 0xffffffff
35191bca4244286fb519c93fe92329da96b0e6f32eejvr	return value
3527842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3537842e56b97ce677b83bdab09cda48bc2d89ac75aJust
354ea9dfa9fb28966175bf2275d20aeb62c3040c86djvrdef maxPowerOfTwo(x):
3557842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""Return the highest exponent of two, so that
3567842e56b97ce677b83bdab09cda48bc2d89ac75aJust	(2 ** exponent) <= x
3577842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
3587842e56b97ce677b83bdab09cda48bc2d89ac75aJust	exponent = 0
3597842e56b97ce677b83bdab09cda48bc2d89ac75aJust	while x:
3607842e56b97ce677b83bdab09cda48bc2d89ac75aJust		x = x >> 1
3617842e56b97ce677b83bdab09cda48bc2d89ac75aJust		exponent = exponent + 1
362fdea99d2655f4d22542f94a8499e5a3308607776Just	return max(exponent - 1, 0)
3637842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3647842e56b97ce677b83bdab09cda48bc2d89ac75aJust
365ea9dfa9fb28966175bf2275d20aeb62c3040c86djvrdef getSearchRange(n):
3667842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""Calculate searchRange, entrySelector, rangeShift for the
3677842e56b97ce677b83bdab09cda48bc2d89ac75aJust	sfnt directory. 'n' is the number of tables.
3687842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
3697842e56b97ce677b83bdab09cda48bc2d89ac75aJust	# This stuff needs to be stored in the file, because?
3707842e56b97ce677b83bdab09cda48bc2d89ac75aJust	import math
371ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr	exponent = maxPowerOfTwo(n)
3727842e56b97ce677b83bdab09cda48bc2d89ac75aJust	searchRange = (2 ** exponent) * 16
3737842e56b97ce677b83bdab09cda48bc2d89ac75aJust	entrySelector = exponent
3747842e56b97ce677b83bdab09cda48bc2d89ac75aJust	rangeShift = n * 16 - searchRange
3757842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return searchRange, entrySelector, rangeShift
3767842e56b97ce677b83bdab09cda48bc2d89ac75aJust
37791bca4244286fb519c93fe92329da96b0e6f32eejvr
37891bca4244286fb519c93fe92329da96b0e6f32eejvrif __name__ == "__main__":
37991bca4244286fb519c93fe92329da96b0e6f32eejvr    import doctest
38091bca4244286fb519c93fe92329da96b0e6f32eejvr    doctest.testmod()
381