from fontTools.misc import sstruct from fontTools.misc.textTools import readHex, safeEval import struct sbixGlyphHeaderFormat = """ > originOffsetX: h # The x-value of the point in the glyph relative to its # lower-left corner which corresponds to the origin of # the glyph on the screen, that is the point on the # baseline at the left edge of the glyph. originOffsetY: h # The y-value of the point in the glyph relative to its # lower-left corner which corresponds to the origin of # the glyph on the screen, that is the point on the # baseline at the left edge of the glyph. graphicType: 4s # e.g. "png " """ sbixGlyphHeaderFormatSize = sstruct.calcsize(sbixGlyphHeaderFormat) class Glyph(object): def __init__(self, glyphName=None, referenceGlyphName=None, originOffsetX=0, originOffsetY=0, graphicType=None, imageData=None, rawdata=None, gid=0): self.gid = gid self.glyphName = glyphName self.referenceGlyphName = referenceGlyphName self.originOffsetX = originOffsetX self.originOffsetY = originOffsetY self.rawdata = rawdata self.graphicType = graphicType self.imageData = imageData # fix self.graphicType if it is null terminated or too short if self.graphicType is not None: if self.graphicType[-1] == "\0": self.graphicType = self.graphicType[:-1] if len(self.graphicType) > 4: from fontTools import ttLib raise ttLib.TTLibError("Glyph.graphicType must not be longer than 4 characters.") elif len(self.graphicType) < 4: # pad with spaces self.graphicType += " "[:(4 - len(self.graphicType))] def decompile(self, ttFont): self.glyphName = ttFont.getGlyphName(self.gid) if self.rawdata is None: from fontTools import ttLib raise ttLib.TTLibError("No table data to decompile") if len(self.rawdata) > 0: if len(self.rawdata) < sbixGlyphHeaderFormatSize: from fontTools import ttLib #print "Glyph %i header too short: Expected %x, got %x." % (self.gid, sbixGlyphHeaderFormatSize, len(self.rawdata)) raise ttLib.TTLibError("Glyph header too short.") sstruct.unpack(sbixGlyphHeaderFormat, self.rawdata[:sbixGlyphHeaderFormatSize], self) if self.graphicType == "dupe": # this glyph is a reference to another glyph's image data gid, = struct.unpack(">H", self.rawdata[sbixGlyphHeaderFormatSize:]) self.referenceGlyphName = ttFont.getGlyphName(gid) else: self.imageData = self.rawdata[sbixGlyphHeaderFormatSize:] self.referenceGlyphName = None # clean up del self.rawdata del self.gid def compile(self, ttFont): if self.glyphName is None: from fontTools import ttLib raise ttLib.TTLibError("Can't compile Glyph without glyph name") # TODO: if ttFont has no maxp, cmap etc., ignore glyph names and compile by index? # (needed if you just want to compile the sbix table on its own) self.gid = struct.pack(">H", ttFont.getGlyphID(self.glyphName)) if self.graphicType is None: self.rawdata = b"" else: self.rawdata = sstruct.pack(sbixGlyphHeaderFormat, self) + self.imageData def toXML(self, xmlWriter, ttFont): if self.graphicType == None: # TODO: ignore empty glyphs? # a glyph data entry is required for each glyph, # but empty ones can be calculated at compile time xmlWriter.simpletag("glyph", name=self.glyphName) xmlWriter.newline() return xmlWriter.begintag("glyph", graphicType=self.graphicType, name=self.glyphName, originOffsetX=self.originOffsetX, originOffsetY=self.originOffsetY, ) xmlWriter.newline() if self.graphicType == "dupe": # graphicType == "dupe" is a reference to another glyph id. xmlWriter.simpletag("ref", glyphname=self.referenceGlyphName) else: xmlWriter.begintag("hexdata") xmlWriter.newline() xmlWriter.dumphex(self.imageData) xmlWriter.endtag("hexdata") xmlWriter.newline() xmlWriter.endtag("glyph") xmlWriter.newline() def fromXML(self, name, attrs, content, ttFont): if name == "ref": # glyph is a "dupe", i.e. a reference to another glyph's image data. # in this case imageData contains the glyph id of the reference glyph # get glyph id from glyphname self.imageData = struct.pack(">H", ttFont.getGlyphID(safeEval("'''" + attrs["glyphname"] + "'''"))) elif name == "hexdata": self.imageData = readHex(content) else: from fontTools import ttLib raise ttLib.TTLibError("can't handle '%s' element" % name)