1from fontTools.misc import sstruct 2from fontTools.misc.textTools import readHex, safeEval 3import struct 4 5 6sbixGlyphHeaderFormat = """ 7 > 8 originOffsetX: h # The x-value of the point in the glyph relative to its 9 # lower-left corner which corresponds to the origin of 10 # the glyph on the screen, that is the point on the 11 # baseline at the left edge of the glyph. 12 originOffsetY: h # The y-value of the point in the glyph relative to its 13 # lower-left corner which corresponds to the origin of 14 # the glyph on the screen, that is the point on the 15 # baseline at the left edge of the glyph. 16 graphicType: 4s # e.g. "png " 17""" 18 19sbixGlyphHeaderFormatSize = sstruct.calcsize(sbixGlyphHeaderFormat) 20 21 22class Glyph(object): 23 def __init__( 24 self, 25 glyphName=None, 26 referenceGlyphName=None, 27 originOffsetX=0, 28 originOffsetY=0, 29 graphicType=None, 30 imageData=None, 31 rawdata=None, 32 gid=0, 33 ): 34 self.gid = gid 35 self.glyphName = glyphName 36 self.referenceGlyphName = referenceGlyphName 37 self.originOffsetX = originOffsetX 38 self.originOffsetY = originOffsetY 39 self.rawdata = rawdata 40 self.graphicType = graphicType 41 self.imageData = imageData 42 43 # fix self.graphicType if it is null terminated or too short 44 if self.graphicType is not None: 45 if self.graphicType[-1] == "\0": 46 self.graphicType = self.graphicType[:-1] 47 if len(self.graphicType) > 4: 48 from fontTools import ttLib 49 50 raise ttLib.TTLibError( 51 "Glyph.graphicType must not be longer than 4 characters." 52 ) 53 elif len(self.graphicType) < 4: 54 # pad with spaces 55 self.graphicType += " "[: (4 - len(self.graphicType))] 56 57 def is_reference_type(self): 58 """Returns True if this glyph is a reference to another glyph's image data.""" 59 return self.graphicType == "dupe" or self.graphicType == "flip" 60 61 def decompile(self, ttFont): 62 self.glyphName = ttFont.getGlyphName(self.gid) 63 if self.rawdata is None: 64 from fontTools import ttLib 65 66 raise ttLib.TTLibError("No table data to decompile") 67 if len(self.rawdata) > 0: 68 if len(self.rawdata) < sbixGlyphHeaderFormatSize: 69 from fontTools import ttLib 70 71 # print "Glyph %i header too short: Expected %x, got %x." % (self.gid, sbixGlyphHeaderFormatSize, len(self.rawdata)) 72 raise ttLib.TTLibError("Glyph header too short.") 73 74 sstruct.unpack( 75 sbixGlyphHeaderFormat, self.rawdata[:sbixGlyphHeaderFormatSize], self 76 ) 77 78 if self.is_reference_type(): 79 # this glyph is a reference to another glyph's image data 80 (gid,) = struct.unpack(">H", self.rawdata[sbixGlyphHeaderFormatSize:]) 81 self.referenceGlyphName = ttFont.getGlyphName(gid) 82 else: 83 self.imageData = self.rawdata[sbixGlyphHeaderFormatSize:] 84 self.referenceGlyphName = None 85 # clean up 86 del self.rawdata 87 del self.gid 88 89 def compile(self, ttFont): 90 if self.glyphName is None: 91 from fontTools import ttLib 92 93 raise ttLib.TTLibError("Can't compile Glyph without glyph name") 94 # TODO: if ttFont has no maxp, cmap etc., ignore glyph names and compile by index? 95 # (needed if you just want to compile the sbix table on its own) 96 self.gid = struct.pack(">H", ttFont.getGlyphID(self.glyphName)) 97 if self.graphicType is None: 98 rawdata = b"" 99 else: 100 rawdata = sstruct.pack(sbixGlyphHeaderFormat, self) 101 if self.is_reference_type(): 102 rawdata += struct.pack(">H", ttFont.getGlyphID(self.referenceGlyphName)) 103 else: 104 assert self.imageData is not None 105 rawdata += self.imageData 106 self.rawdata = rawdata 107 108 def toXML(self, xmlWriter, ttFont): 109 if self.graphicType is None: 110 # TODO: ignore empty glyphs? 111 # a glyph data entry is required for each glyph, 112 # but empty ones can be calculated at compile time 113 xmlWriter.simpletag("glyph", name=self.glyphName) 114 xmlWriter.newline() 115 return 116 xmlWriter.begintag( 117 "glyph", 118 graphicType=self.graphicType, 119 name=self.glyphName, 120 originOffsetX=self.originOffsetX, 121 originOffsetY=self.originOffsetY, 122 ) 123 xmlWriter.newline() 124 if self.is_reference_type(): 125 # this glyph is a reference to another glyph id. 126 xmlWriter.simpletag("ref", glyphname=self.referenceGlyphName) 127 else: 128 xmlWriter.begintag("hexdata") 129 xmlWriter.newline() 130 xmlWriter.dumphex(self.imageData) 131 xmlWriter.endtag("hexdata") 132 xmlWriter.newline() 133 xmlWriter.endtag("glyph") 134 xmlWriter.newline() 135 136 def fromXML(self, name, attrs, content, ttFont): 137 if name == "ref": 138 # this glyph i.e. a reference to another glyph's image data. 139 # in this case imageData contains the glyph id of the reference glyph 140 # get glyph id from glyphname 141 glyphname = safeEval("'''" + attrs["glyphname"] + "'''") 142 self.imageData = struct.pack(">H", ttFont.getGlyphID(glyphname)) 143 self.referenceGlyphName = glyphname 144 elif name == "hexdata": 145 self.imageData = readHex(content) 146 else: 147 from fontTools import ttLib 148 149 raise ttLib.TTLibError("can't handle '%s' element" % name) 150