def __init__(self, file, numTables, sfntVersion="\000\001\000\000", flavor=None, flavorData=None): self.file = file self.numTables = numTables self.sfntVersion = Tag(sfntVersion) self.flavor = flavor self.flavorData = flavorData if self.flavor == "woff": self.directoryFormat = woffDirectoryFormat self.directorySize = woffDirectorySize self.DirectoryEntry = WOFFDirectoryEntry self.signature = "wOFF" # to calculate WOFF checksum adjustment, we also need the original SFNT offsets self.origNextTableOffset = sfntDirectorySize + numTables * sfntDirectoryEntrySize else: assert not self.flavor, "Unknown flavor '%s'" % self.flavor self.directoryFormat = sfntDirectoryFormat self.directorySize = sfntDirectorySize self.DirectoryEntry = SFNTDirectoryEntry self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables, 16) self.nextTableOffset = self.directorySize + numTables * self.DirectoryEntry.formatSize # clear out directory area self.file.seek(self.nextTableOffset) # make sure we're actually where we want to be. (old cStringIO bug) self.file.write(b'\0' * (self.nextTableOffset - self.file.tell())) self.tables = OrderedDict()
def _calcMasterChecksum(self, directory): # calculate checkSumAdjustment tags = list(self.tables.keys()) checksums = [] for i in range(len(tags)): checksums.append(self.tables[tags[i]].checkSum) if self.DirectoryEntry != SFNTDirectoryEntry: # Create a SFNT directory for checksum calculation purposes self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables, 16) directory = sstruct.pack(sfntDirectoryFormat, self) tables = sorted(self.tables.items()) for tag, entry in tables: sfntEntry = SFNTDirectoryEntry() sfntEntry.tag = entry.tag sfntEntry.checkSum = entry.checkSum sfntEntry.offset = entry.origOffset sfntEntry.length = entry.origLength directory = directory + sfntEntry.toString() directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize assert directory_end == len(directory) checksums.append(calcChecksum(directory)) checksum = sum(checksums) & 0xffffffff # BiboAfba! checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff return checksumadjustment
def _calcMasterChecksum(self, directory): # calculate checkSumAdjustment tags = list(self.tables.keys()) checksums = [] for i in range(len(tags)): checksums.append(self.tables[tags[i]].checkSum) # TODO(behdad) I'm fairly sure the checksum for woff is not working correctly. # Haven't debugged. if self.DirectoryEntry != SFNTDirectoryEntry: # Create a SFNT directory for checksum calculation purposes self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables, 16) directory = sstruct.pack(sfntDirectoryFormat, self) tables = sorted(self.tables.items()) for tag, entry in tables: sfntEntry = SFNTDirectoryEntry() for item in ['tag', 'checkSum', 'offset', 'length']: setattr(sfntEntry, item, getattr(entry, item)) directory = directory + sfntEntry.toString() directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize assert directory_end == len(directory) checksums.append(calcChecksum(directory)) checksum = sum(checksums) & 0xffffffff # BiboAfba! checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff return checksumadjustment
def __init__(self, file, numTables, sfntVersion="\000\001\000\000", flavor=None, flavorData=None): self.file = file self.numTables = numTables self.sfntVersion = Tag(sfntVersion) self.flavor = flavor self.flavorData = flavorData if self.flavor == "woff": self.directoryFormat = woffDirectoryFormat self.directorySize = woffDirectorySize self.DirectoryEntry = WOFFDirectoryEntry self.signature = "wOFF" # to calculate WOFF checksum adjustment, we also need the original SFNT offsets self.origNextTableOffset = sfntDirectorySize + numTables * sfntDirectoryEntrySize else: assert not self.flavor, "Unknown flavor '%s'" % self.flavor self.directoryFormat = sfntDirectoryFormat self.directorySize = sfntDirectorySize self.DirectoryEntry = SFNTDirectoryEntry from fontTools.ttLib import getSearchRange self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(numTables, 16) self.directoryOffset = self.file.tell() self.nextTableOffset = self.directoryOffset + self.directorySize + numTables * self.DirectoryEntry.formatSize # clear out directory area self.file.seek(self.nextTableOffset) # make sure we're actually where we want to be. (old cStringIO bug) self.file.write(b'\0' * (self.nextTableOffset - self.file.tell())) self.tables = OrderedDict()
def _testJunkAtTheBeginningOfTheFile(header): """ >>> test = dict(numTables=5, searchRange=64, entrySelector=2, rangeShift=16) >>> bool(_testJunkAtTheBeginningOfTheFile(test)) False >>> test = dict(numTables=5, searchRange=0, entrySelector=2, rangeShift=16) >>> bool(_testJunkAtTheBeginningOfTheFile(test)) True >>> test = dict(numTables=5, searchRange=64, entrySelector=0, rangeShift=16) >>> bool(_testJunkAtTheBeginningOfTheFile(test)) True >>> test = dict(numTables=5, searchRange=64, entrySelector=2, rangeShift=0) >>> bool(_testJunkAtTheBeginningOfTheFile(test)) True """ errors = [] numTables = header["numTables"] searchRange, entrySelector, rangeShift = getSearchRange(numTables) if header["searchRange"] != searchRange: errors.append("The searchRange value is incorrect.") if header["entrySelector"] != entrySelector: errors.append("The entrySelector value is incorrect.") if header["rangeShift"] != rangeShift: errors.append("The rangeShift value is incorrect.") return errors
def calcHeadCheckSumAdjustment(flavor, tables): numTables = len(tables) # build the sfnt header searchRange, entrySelector, rangeShift = getSearchRange(numTables) sfntDirectoryData = dict( sfntVersion=flavor, numTables=numTables, searchRange=searchRange, entrySelector=entrySelector, rangeShift=rangeShift ) # build the sfnt directory directory = sstruct.pack(sfntDirectoryFormat, sfntDirectoryData) for tag, entry in sorted(tables.items()): entry = tables[tag] sfntEntry = SFNTDirectoryEntry() sfntEntry.tag = tag sfntEntry.checkSum = entry["checkSum"] sfntEntry.offset = entry["offset"] sfntEntry.length = entry["length"] directory += sfntEntry.toString() # calculate the checkSumAdjustment checkSums = [entry["checkSum"] for entry in tables.values()] checkSums.append(calcChecksum(directory)) checkSumAdjustment = sum(checkSums) checkSumAdjustment = (0xB1B0AFBA - checkSumAdjustment) & 0xffffffff # done return checkSumAdjustment
def compile(self, ttFont): nPairs = len(self.kernTable) searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6) data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift) # yeehee! (I mean, turn names into indices) getGlyphID = ttFont.getGlyphID kernTable = sorted((getGlyphID(left), getGlyphID(right), value) for ((left,right),value) in self.kernTable.items()) for left, right, value in kernTable: data = data + struct.pack(">HHh", left, right, value) return struct.pack(">HHH", self.version, len(data) + 6, self.coverage) + data
def packSFNT(header, directory, tableData, flavor="cff", calcCheckSum=True, applyPadding=True, sortDirectory=True, searchRange=None, entrySelector=None, rangeShift=None): # update the checkSum if calcCheckSum: if flavor == "cff": f = "OTTO" else: f = "\000\001\000\000" calcHeadCheckSumAdjustmentSFNT(directory, tableData, flavor=f) # update the header cSearchRange, cEntrySelector, cRangeShift = getSearchRange(len(directory), 16) if searchRange is None: searchRange = cSearchRange if entrySelector is None: entrySelector = cEntrySelector if rangeShift is None: rangeShift = cRangeShift if flavor == "cff": header["sfntVersion"] = "OTTO" else: header["sfntVersion"] = "\000\001\000\000" header["searchRange"] = searchRange header["entrySelector"] = entrySelector header["rangeShift"] = rangeShift # version and num tables should already be set sfntData = sstruct.pack(sfntDirectoryFormat, header) # compile the directory sfntDirectoryEntries = {} entryOrder = [] for entry in directory: sfntEntry = SFNTDirectoryEntry() sfntEntry.tag = entry["tag"] sfntEntry.checkSum = entry["checksum"] sfntEntry.offset = entry["offset"] sfntEntry.length = entry["length"] sfntDirectoryEntries[entry["tag"]] = sfntEntry entryOrder.append(entry["tag"]) if sortDirectory: entryOrder = sorted(entryOrder) for tag in entryOrder: entry = sfntDirectoryEntries[tag] sfntData += entry.toString() # compile the data directory = [(entry["offset"], entry["tag"]) for entry in directory] for o, tag in sorted(directory): data = tableData[tag] if applyPadding: data = padData(data) sfntData += data # done return sfntData
def compile(self, ttFont): nPairs = len(self.kernTable) searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6) data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift) # yeehee! (I mean, turn names into indices) try: reverseOrder = ttFont.getReverseGlyphMap() kernTable = sorted((reverseOrder[left], reverseOrder[right], value) for ((left,right),value) in self.kernTable.items()) except KeyError: # Slower, but will not throw KeyError on invalid glyph id. getGlyphID = ttFont.getGlyphID kernTable = sorted((getGlyphID(left), getGlyphID(right), value) for ((left,right),value) in self.kernTable.items()) for left, right, value in kernTable: data = data + struct.pack(">HHh", left, right, value) return struct.pack(">HHH", self.version, len(data) + 6, self.coverage) + data
def calcHeadCheckSumAdjustmentSFNT(directory, tableData, flavor=None): """ Set the checkSumAdjustment in the head table data. Grumble. """ # if the flavor is None, guess. if flavor is None: flavor = "\000\001\000\000" for entry in directory: if entry["tag"] == "CFF ": flavor = "OTTO" break assert flavor in ("OTTO", "\000\001\000\000") # make the sfnt header searchRange, entrySelector, rangeShift = getSearchRange(len(directory), 16) sfntHeaderData = dict( sfntVersion=flavor, numTables=len(directory), searchRange=searchRange, entrySelector=entrySelector, rangeShift=rangeShift, ) sfntData = sstruct.pack(sfntDirectoryFormat, sfntHeaderData) # make a SFNT table directory directory = [(entry["tag"], entry) for entry in directory] for tag, entry in sorted(directory): sfntEntry = SFNTDirectoryEntry() sfntEntry.tag = entry["tag"] sfntEntry.checkSum = entry["checksum"] sfntEntry.offset = entry["offset"] sfntEntry.length = entry["length"] sfntData += sfntEntry.toString() # calculate the checksum sfntDataChecksum = calcChecksum(sfntData) # gather all of the checksums checksums = [entry["checksum"] for o, entry in directory] checksums.append(sfntDataChecksum) # calculate the checksum checkSumAdjustment = sum(checksums) checkSumAdjustment = (0xB1B0AFBA - checkSumAdjustment) & 0xFFFFFFFF # set the value in the head table headTableData = tableData["head"] newHeadTableData = headTableData[:8] newHeadTableData += struct.pack(">L", checkSumAdjustment) newHeadTableData += headTableData[12:] tableData["head"] = newHeadTableData
def calcHeadCheckSumAdjustmentSFNT(directory, tableData, flavor=None): """ Set the checkSumAdjustment in the head table data. Grumble. """ # if the flavor is None, guess. if flavor is None: flavor = "\000\001\000\000" for entry in directory: if entry["tag"] == "CFF ": flavor = "OTTO" break assert flavor in ("OTTO", "\000\001\000\000") # make the sfnt header searchRange, entrySelector, rangeShift = getSearchRange(len(directory), 16) sfntHeaderData = dict( sfntVersion=flavor, numTables=len(directory), searchRange=searchRange, entrySelector=entrySelector, rangeShift=rangeShift ) sfntData = sstruct.pack(sfntDirectoryFormat, sfntHeaderData) # make a SFNT table directory directory = [(entry["tag"], entry) for entry in directory] for tag, entry in sorted(directory): sfntEntry = SFNTDirectoryEntry() sfntEntry.tag = entry["tag"] sfntEntry.checkSum = entry["checksum"] sfntEntry.offset = entry["offset"] sfntEntry.length = entry["length"] sfntData += sfntEntry.toString() # calculate the checksum sfntDataChecksum = calcChecksum(sfntData) # gather all of the checksums checksums = [entry["checksum"] for o, entry in directory] checksums.append(sfntDataChecksum) # calculate the checksum checkSumAdjustment = sum(checksums) checkSumAdjustment = (0xB1B0AFBA - checkSumAdjustment) & 0xffffffff # set the value in the head table headTableData = tableData["head"] newHeadTableData = headTableData[:8] newHeadTableData += struct.pack(">L", checkSumAdjustment) newHeadTableData += headTableData[12:] tableData["head"] = newHeadTableData
def compile(self, ttFont): nPairs = len(self.kernTable) searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6) searchRange &= 0xFFFF data = struct.pack( ">HHHH", nPairs, searchRange, entrySelector, rangeShift) # yeehee! (I mean, turn names into indices) try: reverseOrder = ttFont.getReverseGlyphMap() kernTable = sorted( (reverseOrder[left], reverseOrder[right], value) for ((left, right), value) in self.kernTable.items()) except KeyError: # Slower, but will not throw KeyError on invalid glyph id. getGlyphID = ttFont.getGlyphID kernTable = sorted( (getGlyphID(left), getGlyphID(right), value) for ((left, right), value) in self.kernTable.items()) for left, right, value in kernTable: data = data + struct.pack(">HHh", left, right, value) if not self.apple: version = 0 length = len(data) + 6 if length >= 0x10000: log.warning('"kern" subtable overflow, ' 'truncating length value while preserving pairs.') length &= 0xFFFF header = struct.pack( ">HHBB", version, length, self.format, self.coverage) else: if self.tupleIndex is None: # sensible default when compiling a TTX from an old fonttools # or when inserting a Windows-style format 0 subtable into an # Apple version=1.0 kern table log.warning("'tupleIndex' is None; default to 0") self.tupleIndex = 0 length = len(data) + 8 header = struct.pack( ">LBBH", length, self.coverage, self.format, self.tupleIndex) return header + data
def _cmap_format_4_compile(self, ttFont): if self.data: return struct.pack(">HHH", self.format, self.length, self.language) + self.data charCodes = list(self.cmap.keys()) charCodes.sort() charCodes = charCodes[:256] lenCharCodes = len(charCodes) if lenCharCodes == 0: startCode = [0xffff] endCode = [0xffff] else: charCodes.sort() names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) nameMap = ttFont.getReverseGlyphMap() try: gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) except KeyError: nameMap = ttFont.getReverseGlyphMap(rebuild=True) try: gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) except KeyError: # allow virtual GIDs in format 4 tables gids = [] for name in names: try: gid = nameMap[name] except KeyError: try: if (name[:3] == 'gid'): gid = eval(name[3:]) else: gid = ttFont.getGlyphID(name) except: raise KeyError(name) gids.append(gid) cmap = {} # code:glyphID mapping list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) # Build startCode and endCode lists. # Split the char codes in ranges of consecutive char codes, then split # each range in more ranges of consecutive/not consecutive glyph IDs. # See splitRange(). lastCode = charCodes[0] endCode = [] startCode = [lastCode] for charCode in charCodes[1:]: # skip the first code, it's the first start code if charCode == lastCode + 1: lastCode = charCode continue start, end = splitRange(startCode[-1], lastCode, cmap) startCode.extend(start) endCode.extend(end) startCode.append(charCode) lastCode = charCode start, end = splitRange(startCode[-1], lastCode, cmap) startCode.extend(start) endCode.extend(end) startCode.append(0xffff) endCode.append(0xffff) #debug_len = len(endCode) #debug_pos = 10 #debug_start = min(debug_pos, debug_len) #debug_end = min(debug_pos+10, debug_len) # for i in range(debug_start, debug_end): # print('a: start/end {0}/{1}'.format(startCode[i], endCode[i] + 1)) # build up rest of cruft idDelta = [] idRangeOffset = [] glyphIndexArray = [] for i in range(len(endCode)-1): # skip the closing codes (0xffff) indices = [] for charCode in range(startCode[i], endCode[i] + 1): indices.append(cmap[charCode]) if (indices == list(range(indices[0], indices[0] + len(indices)))): idDelta.append((indices[0] - startCode[i]) % 0x10000) idRangeOffset.append(0) else: # someone *definitely* needs to get killed. idDelta.append(0) idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i)) glyphIndexArray.extend(indices) idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef idRangeOffset.append(0) # Insane. segCount = len(endCode) segCountX2 = segCount * 2 searchRange, entrySelector, rangeShift = getSearchRange(segCount, 2) charCodeArray = array.array("H", endCode + [0] + startCode) idDeltaArray = array.array("H", idDelta) restArray = array.array("H", idRangeOffset + glyphIndexArray) if sys.byteorder != "big": charCodeArray.byteswap() idDeltaArray.byteswap() restArray.byteswap() data = charCodeArray.tostring() + idDeltaArray.tostring() + restArray.tostring() length = struct.calcsize(_c_m_a_p.cmap_format_4_format) + len(data) header = struct.pack(_c_m_a_p.cmap_format_4_format, self.format, length, self.language, segCountX2, searchRange, entrySelector, rangeShift) return header + data
def getSFNTCollectionData(pathOrFiles, modifyNames=True, reverseNames=False, DSIG=False, duplicates=[], shared=[]): tables = [] offsets = {} fonts = [TTFont(pathOrFile) for pathOrFile in pathOrFiles] numFonts = len(fonts) header = dict( TTCTag="ttcf", Version=0x00010000, numFonts=numFonts, ) if DSIG: header["version"] = 0x00020000 fontData = sstruct.pack(ttcHeaderFormat, header) offset = ttcHeaderSize + (numFonts * struct.calcsize(">L")) if DSIG: offset += 3 * struct.calcsize(">L") for font in fonts: fontData += struct.pack(">L", offset) tags = [i for i in sorted(font.keys()) if len(i) == 4] offset += sfntDirectorySize + (len(tags) * sfntDirectoryEntrySize) if DSIG: data = "\0" * 4 tables.append(data) offset += len(data) fontData += struct.pack(">4s", "DSIG") fontData += struct.pack(">L", len(data)) fontData += struct.pack(">L", offset) for i, font in enumerate(fonts): # Make the name table unique if modifyNames: index = i if reverseNames: index = len(fonts) - i - 1 name = font["name"] for namerecord in name.names: nameID = namerecord.nameID string = namerecord.toUnicode() if nameID == 1: namerecord.string = "%s %d" % (string, index) elif nameID == 4: namerecord.string = string.replace("Regular", "%d Regular" % index) elif nameID == 6: namerecord.string = string.replace("-", "%d-" % index) tags = [i for i in sorted(font.keys()) if len(i) == 4] searchRange, entrySelector, rangeShift = getSearchRange(len(tags), 16) offsetTable = dict( sfntVersion=font.sfntVersion, numTables=len(tags), searchRange=searchRange, entrySelector=entrySelector, rangeShift=rangeShift, ) fontData += sstruct.pack(sfntDirectoryFormat, offsetTable) for tag in tags: data = font.getTableData(tag) checksum = calcTableChecksum(tag, data) entry = dict( tag=tag, offset=offset, length=len(data), checkSum=checksum, ) if (shared and tag not in shared) or tag in duplicates or data not in tables: tables.append(data) offsets[checksum] = offset offset += len(data) + calcPaddingLength(len(data)) else: entry["offset"] = offsets[checksum] fontData += sstruct.pack(sfntDirectoryEntryFormat, entry) for table in tables: fontData += padData(table) for font in fonts: font.close() return fontData