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