def _startElementHandler(self, name, attrs): stackSize = self.stackSize self.stackSize = stackSize + 1 if not stackSize: if name != "ttFont": raise TTXParseError("illegal root tag: %s" % name) sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) != 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: if hasattr(self.file, 'name'): # if file has a name, get its parent directory dirname = os.path.dirname(self.file.name) else: # else fall back to using the current working directory dirname = os.getcwd() subFile = os.path.join(dirname, subFile) subReader = XMLReader(subFile, self.ttFont, self.progress, self.quiet) subReader.read() self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setLabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: if not self.quiet: print(msg) if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == 'loca' and tag in self.ttFont: # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: self.currentTable = tableClass(tag) self.ttFont[tag] = self.currentTable self.contentStack.append([]) elif stackSize == 2: self.contentStack.append([]) self.root = (name, attrs, self.contentStack[-1]) else: l = [] self.contentStack[-1].append((name, attrs, l)) self.contentStack.append(l)
def _writeTableDirectory(self): if self.verbose: debugmsg("writing table directory") self.file.seek(woffHeaderSize) for tag, (index, entry, data) in sorted(self.tables.items()): entry = sstruct.pack(woffDirectoryEntryFormat, entry) self.file.write(entry)
def _checkTableConformance(self, entry, data): """ Check the conformance of the table directory entries. These must be checked because the origChecksum, origLength and compLength can be set by an outside caller. """ if self.verbose: debugmsg("checking conformance of '%s' table" % entry.tag) # origLength must be less than or equal to compLength if entry.origLength < entry.compLength: raise WOFFLibError( "origLength and compLength are not correct in the '%s' table entry." % entry.tag) # unpack the data as needed if entry.origLength > entry.compLength: origData = zlib.decompress(data) compData = data else: origData = data compData = data # the origLength entry must match the actual length if entry.origLength != len(origData): raise WOFFLibError( "origLength is not correct in the '%s' table entry." % entry.tag) # the checksum must be correct if entry.origChecksum != calcTableChecksum(entry.tag, origData): raise WOFFLibError( "origChecksum is not correct in the '%s' table entry." % entry.tag) # the compLength must be correct if entry.compLength != len(compData): raise WOFFLibError( "compLength is not correct in the '%s' table entry." % entry.tag)
def _checkTableConformance(self, entry, data): """ Check the conformance of the table directory entries. These must be checked because the origChecksum, origLength and compLength can be set by an outside caller. """ if self.verbose: debugmsg("checking conformance of '%s' table" % entry.tag) # origLength must be less than or equal to compLength if entry.origLength < entry.compLength: raise WOFFLibError("origLength and compLength are not correct in the '%s' table entry." % entry.tag) # unpack the data as needed if entry.origLength > entry.compLength: origData = zlib.decompress(data) compData = data else: origData = data compData = data # the origLength entry must match the actual length if entry.origLength != len(origData): raise WOFFLibError("origLength is not correct in the '%s' table entry." % entry.tag) # the checksum must be correct if entry.origChecksum != calcTableChecksum(entry.tag, origData): raise WOFFLibError("origChecksum is not correct in the '%s' table entry." % entry.tag) # the compLength must be correct if entry.compLength != len(compData): raise WOFFLibError("compLength is not correct in the '%s' table entry." % entry.tag)
def _prepTable(self, tag, data, origLength=None, origChecksum=None, compLength=None, entryOnly=False): # skip data prep if entryOnly: origLength = origLength origChecksum = calcTableChecksum(tag, data) compLength = 0 # prep the data else: # compress if compLength is None: origData = data origLength = len(origData) origChecksum = calcTableChecksum(tag, data) if self.verbose: debugmsg("compressing '%s' table" % tag) compData = zlib.compress(origData, self.compressionLevel) compLength = len(compData) if origLength <= compLength: data = origData compLength = origLength else: data = compData # make the directory entry entry = WOFFDirectoryEntry() entry.tag = tag entry.offset = 0 entry.origLength = origLength entry.origChecksum = origChecksum entry.compLength = compLength # return if entryOnly: return entry return entry, data
def _writePrivateData(self): if self.privateData is None: return if self.verbose: debugmsg("writing private data") if self.metadata is not None: self.privOffset = self.metadataEnd else: self.privOffset = self.tableDataEnd self.length += self.privLength self.file.seek(self.privOffset) self.file.write(self.privateData)
def startElementHandler(self, name, attrs): stackSize = self.stackSize self.stackSize = stackSize + 1 if not stackSize: if name <> "ttFont": raise TTXParseError, "illegal root tag: %s" % name sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) <> 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: subFile = os.path.join(os.path.dirname(self.fileName), subFile) importXML(self.ttFont, subFile, self.progress) self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setlabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: if not self.quiet: print msg if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif attrs.has_key("ERROR"): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == 'loca' and self.ttFont.has_key(tag): # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: self.currentTable = tableClass(tag) self.ttFont[tag] = self.currentTable self.contentStack.append([]) elif stackSize == 2: self.contentStack.append([]) self.root = (name, attrs, self.contentStack[-1]) else: list = [] self.contentStack[-1].append((name, attrs, list)) self.contentStack.append(list)
def setMetadata(self, data, metaOrigLength=None, metaLength=None): if not data: return if metaLength is None: if self.verbose: debugmsg("compressing metadata") metaOrigLength = len(data) data = zlib.compress(data, self.compressionLevel) metaLength = len(data) # set the header values self.metaOrigLength = metaOrigLength self.metaLength = metaLength # store self.metadata = data
def _writeTableData(self): d = woffHeaderSize + (woffDirectoryEntrySize * self.numTables) offset = woffHeaderSize + (woffDirectoryEntrySize * self.numTables) self.file.seek(offset) for tag in self._tableOrder(): if self.verbose: debugmsg("writing '%s' table" % tag) index, entry, data = self.tables[tag] data += "\0" * (calc4BytePaddedLength(entry.compLength) - entry.compLength ) # ensure byte alignment self.file.write(data) self.length += calc4BytePaddedLength(entry.compLength) # ensure byte alignment self.totalSFNTSize += calc4BytePaddedLength(entry.origLength) # ensure byte alignment # store the end for use by metadata or private data self.tableDataEnd = self.length
def startElementHandler(self, name, attrs): stackSize = self.stackSize self.stackSize = stackSize + 1 if not stackSize: if name <> "ttFont": raise TTXParseError, "illegal root tag: %s" % name sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) <> 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: subFile = os.path.join(os.path.dirname(self.fileName), subFile) importXML(self.ttFont, subFile, self.progress) self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setlabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: print msg if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif attrs.has_key("ERROR"): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == 'loca' and self.ttFont.has_key(tag): # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: self.currentTable = tableClass(tag) self.ttFont[tag] = self.currentTable self.contentStack.append([]) elif stackSize == 2: self.contentStack.append([]) self.root = (name, attrs, self.contentStack[-1]) else: list = [] self.contentStack[-1].append((name, attrs, list)) self.contentStack.append(list)
def _writeTableData(self): d = woffHeaderSize + (woffDirectoryEntrySize * self.numTables) offset = woffHeaderSize + (woffDirectoryEntrySize * self.numTables) self.file.seek(offset) for tag in self._tableOrder(): if self.verbose: debugmsg("writing '%s' table" % tag) index, entry, data = self.tables[tag] data += "\0" * (calc4BytePaddedLength(entry.compLength) - entry.compLength) # ensure byte alignment self.file.write(data) self.length += calc4BytePaddedLength( entry.compLength) # ensure byte alignment self.totalSFNTSize += calc4BytePaddedLength( entry.origLength) # ensure byte alignment # store the end for use by metadata or private data self.tableDataEnd = self.length
def _writeMetadata(self): if self.metadata is None: return if self.verbose: debugmsg("writing metadata") self.length += self.metaLength self.metaOffset = self.tableDataEnd self.file.seek(self.metaOffset) self.file.write(self.metadata) # store the end for use by private data self.metadataEnd = self.metaOffset + self.metaLength # if private data exists, pad to a four byte boundary if self.privateData is not None: padding = calc4BytePaddedLength(self.metaLength) - self.metaLength self.metadataEnd += padding self.length += padding padding = "\0" * padding if padding: self.file.write(padding)
def fromXML(self, name, attrs, content, ttFont): if name != "TTGlyph": return if not hasattr(self, "glyphs"): self.glyphs = {} if not hasattr(self, "glyphOrder"): self.glyphOrder = ttFont.getGlyphOrder() glyphName = attrs["name"] if ttFont.verbose: ttLib.debugmsg("unpacking glyph '%s'" % glyphName) glyph = Glyph() for attr in ['xMin', 'yMin', 'xMax', 'yMax']: setattr(glyph, attr, safeEval(attrs.get(attr, '0'))) self.glyphs[glyphName] = glyph for element in content: if not isinstance(element, tuple): continue name, attrs, content = element glyph.fromXML(name, attrs, content, ttFont) if not ttFont.recalcBBoxes: glyph.compact(self, 0)
def _handleHeadChecksum(self): if self.verbose: debugmsg("updating head checkSumAdjustment") # get the value tables = {} offset = sfntDirectorySize + (sfntDirectoryEntrySize * self.numTables) for (index, entry, data) in sorted(self.tables.values()): tables[entry.tag] = dict(offset=offset, length=entry.origLength, checkSum=entry.origChecksum) offset += calc4BytePaddedLength(entry.origLength) checkSumAdjustment = calcHeadCheckSumAdjustment(self.flavor, tables) # set the value in the head table index, entry, data = self.tables["head"] data = data[:8] + struct.pack(">L", checkSumAdjustment) + data[12:] # compress the data newEntry, data = self._prepTable("head", data) # update the entry data assert entry.origChecksum == newEntry.origChecksum entry.origLength = newEntry.origLength entry.compLength = newEntry.compLength # store self.tables["head"] = (index, entry, data)
def save(self, file, compressionLevel=9, recompressTables=False, reorderTables=True, recalculateHeadChecksum=True): """ Save a WOFF into file a file object specifified by the file argument.. Optionally, file can be a path and a new file will be created at that location. compressionLevel is the compression level to be used with zlib. This must be an int between 1 and 9. The default is 9, the highest compression, but slowest compression time. Set recompressTables to True if you want any already compressed tables to be decompressed and then recompressed using the level specified by compressionLevel. If you want the tables in the WOFF reordered following the suggested optimal table orderings described in the OTF/OFF sepecification, set reorderTables to True. Tables cannot be reordered if a DSIG table is in the font. If you change any of the SFNT data or reorder the tables, the head table checkSumAdjustment must be recalculated. If you are not changing any of the SFNT data, you can set recalculateHeadChecksum to False to prevent the recalculation. This must be set to False if the font contains a DSIG table. """ # if DSIG is to be written, the table order # must be completely specified. otherwise the # DSIG may not be valid after decoding the WOFF. tags = self.keys() if "GlyphOrder" in tags: tags.remove("GlyphOrder") if "DSIG" in tags: if self._tableOrder is None or (set(self._tableOrder) != set(tags)): raise WOFFLibError( "A complete table order must be supplied when saving a font with a 'DSIG' table." ) elif reorderTables: raise WOFFLibError( "Tables can not be reordered when a 'DSIG' table is in the font. Set reorderTables to False." ) elif recalculateHeadChecksum: raise WOFFLibError( "The 'head' table checkSumAdjustment can not be recalculated when a 'DSIG' table is in the font." ) # sort the tags if necessary if reorderTables: tags = sortedTagList(tags) # open a file if necessary closeStream = False if not hasattr(file, "write"): closeStream = True file = open(file, "wb") # write the table data if "GlyphOrder" in tags: tags.remove("GlyphOrder") numTables = len(tags) writer = WOFFWriter(file, numTables, flavor=self.flavor, majorVersion=self.majorVersion, minorVersion=self.minorVersion, compressionLevel=compressionLevel, recalculateHeadChecksum=recalculateHeadChecksum, verbose=self.verbose) for tag in tags: origData = None origLength = None origChecksum = None compLength = None # table is loaded if self.isLoaded(tag): origData = self.getTableData(tag) # table is in reader elif self.reader is not None: if recompressTables: origData = self.getTableData(tag) else: if self.verbose: debugmsg("Reading '%s' table from disk" % tag) origData, origLength, origChecksum, compLength = self.reader.getCompressedTableData( tag) # add to writer writer.setTable(tag, origData, origLength=origLength, origChecksum=origChecksum, compLength=compLength) # write the metadata metadata = None metaOrigLength = None metaLength = None if hasattr(self, "metadata"): declaration = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" tree = ElementTree.ElementTree(self.metadata) f = StringIO() tree.write(f, encoding="utf-8") metadata = f.getvalue() # make sure the metadata starts with the declaration if not metadata.startswith(declaration): metadata = declaration + metadata del f elif self.reader is not None: if recompressTables: metadata = self.reader.metadata else: metadata, metaOrigLength, metaLength = self.reader.getCompressedMetadata( ) if metadata: writer.setMetadata(metadata, metaOrigLength=metaOrigLength, metaLength=metaLength) # write the private data privData = self.privateData if privData: writer.setPrivateData(privData) # close the writer writer.close() # close the file if closeStream: file.close()
class table__g_l_y_f(DefaultTable.DefaultTable): def decompile(self, data, ttFont): loca = ttFont['loca'] last = int(loca[0]) self.glyphs = {} self.glyphOrder = glyphOrder = ttFont.getGlyphOrder() for i in range(0, len(loca) - 1): glyphName = glyphOrder[i] next = int(loca[i + 1]) glyphdata = data[last:next] if len(glyphdata) <> (next - last): raise ttLib.TTLibError, "not enough 'glyf' table data" glyph = Glyph(glyphdata) self.glyphs[glyphName] = glyph last = next # this should become a warning: #if len(data) > next: # raise ttLib.TTLibError, "too much 'glyf' table data" def compile(self, ttFont): if not hasattr(self, "glyphOrder"): self.glyphOrder = ttFont.getGlyphOrder() import string locations = [] currentLocation = 0 dataList = [] recalcBBoxes = ttFont.recalcBBoxes for glyphName in self.glyphOrder: glyph = self.glyphs[glyphName] glyphData = glyph.compile(self, recalcBBoxes) locations.append(currentLocation) currentLocation = currentLocation + len(glyphData) dataList.append(glyphData) locations.append(currentLocation) data = string.join(dataList, "") ttFont['loca'].set(locations) ttFont['maxp'].numGlyphs = len(self.glyphs) return data def toXML(self, writer, ttFont, progress=None): writer.newline() glyphNames = ttFont.getGlyphNames() writer.comment( "The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler." ) writer.newline() writer.newline() counter = 0 progressStep = 10 numGlyphs = len(glyphNames) for glyphName in glyphNames: if not counter % progressStep and progress is not None: progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName) progress.increment(progressStep / float(numGlyphs)) counter = counter + 1 glyph = self[glyphName] if glyph.numberOfContours: writer.begintag('TTGlyph', [ ("name", glyphName), ("xMin", glyph.xMin), ("yMin", glyph.yMin), ("xMax", glyph.xMax), ("yMax", glyph.yMax), ]) writer.newline() glyph.toXML(writer, ttFont) writer.endtag('TTGlyph') writer.newline() else: writer.simpletag('TTGlyph', name=glyphName) writer.comment("contains no outline data") writer.newline() writer.newline() def fromXML(self, (name, attrs, content), ttFont): if name <> "TTGlyph": return if not hasattr(self, "glyphs"): self.glyphs = {} if not hasattr(self, "glyphOrder"): self.glyphOrder = ttFont.getGlyphOrder() glyphName = attrs["name"] if ttFont.verbose: ttLib.debugmsg("unpacking glyph '%s'" % glyphName) glyph = Glyph() for attr in ['xMin', 'yMin', 'xMax', 'yMax']: setattr(glyph, attr, safeEval(attrs.get(attr, '0'))) self.glyphs[glyphName] = glyph for element in content: if type(element) <> TupleType: continue glyph.fromXML(element, ttFont) if not ttFont.recalcBBoxes: glyph.compact(self, 0)
def save(self, file, compressionLevel=9, recompressTables=False, reorderTables=True, recalculateHeadChecksum=True): """ Save a WOFF into file a file object specifified by the file argument.. Optionally, file can be a path and a new file will be created at that location. compressionLevel is the compression level to be used with zlib. This must be an int between 1 and 9. The default is 9, the highest compression, but slowest compression time. Set recompressTables to True if you want any already compressed tables to be decompressed and then recompressed using the level specified by compressionLevel. If you want the tables in the WOFF reordered following the suggested optimal table orderings described in the OTF/OFF sepecification, set reorderTables to True. Tables cannot be reordered if a DSIG table is in the font. If you change any of the SFNT data or reorder the tables, the head table checkSumAdjustment must be recalculated. If you are not changing any of the SFNT data, you can set recalculateHeadChecksum to False to prevent the recalculation. This must be set to False if the font contains a DSIG table. """ # if DSIG is to be written, the table order # must be completely specified. otherwise the # DSIG may not be valid after decoding the WOFF. tags = self.keys() if "GlyphOrder" in tags: tags.remove("GlyphOrder") if "DSIG" in tags: if self._tableOrder is None or (set(self._tableOrder) != set(tags)): raise WOFFLibError("A complete table order must be supplied when saving a font with a 'DSIG' table.") elif reorderTables: raise WOFFLibError("Tables can not be reordered when a 'DSIG' table is in the font. Set reorderTables to False.") elif recalculateHeadChecksum: raise WOFFLibError("The 'head' table checkSumAdjustment can not be recalculated when a 'DSIG' table is in the font.") # sort the tags if necessary if reorderTables: tags = sortedTagList(tags) # open a file if necessary closeStream = False if not hasattr(file, "write"): closeStream = True file = open(file, "wb") # write the table data if "GlyphOrder" in tags: tags.remove("GlyphOrder") numTables = len(tags) writer = WOFFWriter(file, numTables, flavor=self.flavor, majorVersion=self.majorVersion, minorVersion=self.minorVersion, compressionLevel=compressionLevel, recalculateHeadChecksum=recalculateHeadChecksum, verbose=self.verbose) for tag in tags: origData = None origLength = None origChecksum = None compLength = None # table is loaded if self.isLoaded(tag): origData = self.getTableData(tag) # table is in reader elif self.reader is not None: if recompressTables: origData = self.getTableData(tag) else: if self.verbose: debugmsg("Reading '%s' table from disk" % tag) origData, origLength, origChecksum, compLength = self.reader.getCompressedTableData(tag) # add to writer writer.setTable(tag, origData, origLength=origLength, origChecksum=origChecksum, compLength=compLength) # write the metadata metadata = None metaOrigLength = None metaLength = None if hasattr(self, "metadata"): declaration = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" tree = ElementTree.ElementTree(self.metadata) f = StringIO() tree.write(f, encoding="utf-8") metadata = f.getvalue() # make sure the metadata starts with the declaration if not metadata.startswith(declaration): metadata = declaration + metadata del f elif self.reader is not None: if recompressTables: metadata = self.reader.metadata else: metadata, metaOrigLength, metaLength = self.reader.getCompressedMetadata() if metadata: writer.setMetadata(metadata, metaOrigLength=metaOrigLength, metaLength=metaLength) # write the private data privData = self.privateData if privData: writer.setPrivateData(privData) # close the writer writer.close() # close the file if closeStream: file.close()