1from fontTools.misc import sstruct 2from fontTools.misc.textTools import safeEval 3from .sbixGlyph import Glyph 4import struct 5 6sbixStrikeHeaderFormat = """ 7 > 8 ppem: H # The PPEM for which this strike was designed (e.g., 9, 9 # 12, 24) 10 resolution: H # The screen resolution (in dpi) for which this strike 11 # was designed (e.g., 72) 12""" 13 14sbixGlyphDataOffsetFormat = """ 15 > 16 glyphDataOffset: L # Offset from the beginning of the strike data record 17 # to data for the individual glyph 18""" 19 20sbixStrikeHeaderFormatSize = sstruct.calcsize(sbixStrikeHeaderFormat) 21sbixGlyphDataOffsetFormatSize = sstruct.calcsize(sbixGlyphDataOffsetFormat) 22 23 24class Strike(object): 25 def __init__(self, rawdata=None, ppem=0, resolution=72): 26 self.data = rawdata 27 self.ppem = ppem 28 self.resolution = resolution 29 self.glyphs = {} 30 31 def decompile(self, ttFont): 32 if self.data is None: 33 from fontTools import ttLib 34 35 raise ttLib.TTLibError 36 if len(self.data) < sbixStrikeHeaderFormatSize: 37 from fontTools import ttLib 38 39 raise ( 40 ttLib.TTLibError, 41 "Strike header too short: Expected %x, got %x.", 42 ) % (sbixStrikeHeaderFormatSize, len(self.data)) 43 44 # read Strike header from raw data 45 sstruct.unpack( 46 sbixStrikeHeaderFormat, self.data[:sbixStrikeHeaderFormatSize], self 47 ) 48 49 # calculate number of glyphs 50 (firstGlyphDataOffset,) = struct.unpack( 51 ">L", 52 self.data[ 53 sbixStrikeHeaderFormatSize : sbixStrikeHeaderFormatSize 54 + sbixGlyphDataOffsetFormatSize 55 ], 56 ) 57 self.numGlyphs = ( 58 firstGlyphDataOffset - sbixStrikeHeaderFormatSize 59 ) // sbixGlyphDataOffsetFormatSize - 1 60 # ^ -1 because there's one more offset than glyphs 61 62 # build offset list for single glyph data offsets 63 self.glyphDataOffsets = [] 64 for i in range( 65 self.numGlyphs + 1 66 ): # + 1 because there's one more offset than glyphs 67 start = i * sbixGlyphDataOffsetFormatSize + sbixStrikeHeaderFormatSize 68 (current_offset,) = struct.unpack( 69 ">L", self.data[start : start + sbixGlyphDataOffsetFormatSize] 70 ) 71 self.glyphDataOffsets.append(current_offset) 72 73 # iterate through offset list and slice raw data into glyph data records 74 for i in range(self.numGlyphs): 75 current_glyph = Glyph( 76 rawdata=self.data[ 77 self.glyphDataOffsets[i] : self.glyphDataOffsets[i + 1] 78 ], 79 gid=i, 80 ) 81 current_glyph.decompile(ttFont) 82 self.glyphs[current_glyph.glyphName] = current_glyph 83 del self.glyphDataOffsets 84 del self.numGlyphs 85 del self.data 86 87 def compile(self, ttFont): 88 self.glyphDataOffsets = b"" 89 self.bitmapData = b"" 90 91 glyphOrder = ttFont.getGlyphOrder() 92 93 # first glyph starts right after the header 94 currentGlyphDataOffset = ( 95 sbixStrikeHeaderFormatSize 96 + sbixGlyphDataOffsetFormatSize * (len(glyphOrder) + 1) 97 ) 98 for glyphName in glyphOrder: 99 if glyphName in self.glyphs: 100 # we have glyph data for this glyph 101 current_glyph = self.glyphs[glyphName] 102 else: 103 # must add empty glyph data record for this glyph 104 current_glyph = Glyph(glyphName=glyphName) 105 current_glyph.compile(ttFont) 106 current_glyph.glyphDataOffset = currentGlyphDataOffset 107 self.bitmapData += current_glyph.rawdata 108 currentGlyphDataOffset += len(current_glyph.rawdata) 109 self.glyphDataOffsets += sstruct.pack( 110 sbixGlyphDataOffsetFormat, current_glyph 111 ) 112 113 # add last "offset", really the end address of the last glyph data record 114 dummy = Glyph() 115 dummy.glyphDataOffset = currentGlyphDataOffset 116 self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, dummy) 117 118 # pack header 119 self.data = sstruct.pack(sbixStrikeHeaderFormat, self) 120 # add offsets and image data after header 121 self.data += self.glyphDataOffsets + self.bitmapData 122 123 def toXML(self, xmlWriter, ttFont): 124 xmlWriter.begintag("strike") 125 xmlWriter.newline() 126 xmlWriter.simpletag("ppem", value=self.ppem) 127 xmlWriter.newline() 128 xmlWriter.simpletag("resolution", value=self.resolution) 129 xmlWriter.newline() 130 glyphOrder = ttFont.getGlyphOrder() 131 for i in range(len(glyphOrder)): 132 if glyphOrder[i] in self.glyphs: 133 self.glyphs[glyphOrder[i]].toXML(xmlWriter, ttFont) 134 # TODO: what if there are more glyph data records than (glyf table) glyphs? 135 xmlWriter.endtag("strike") 136 xmlWriter.newline() 137 138 def fromXML(self, name, attrs, content, ttFont): 139 if name in ["ppem", "resolution"]: 140 setattr(self, name, safeEval(attrs["value"])) 141 elif name == "glyph": 142 if "graphicType" in attrs: 143 myFormat = safeEval("'''" + attrs["graphicType"] + "'''") 144 else: 145 myFormat = None 146 if "glyphname" in attrs: 147 myGlyphName = safeEval("'''" + attrs["glyphname"] + "'''") 148 elif "name" in attrs: 149 myGlyphName = safeEval("'''" + attrs["name"] + "'''") 150 else: 151 from fontTools import ttLib 152 153 raise ttLib.TTLibError("Glyph must have a glyph name.") 154 if "originOffsetX" in attrs: 155 myOffsetX = safeEval(attrs["originOffsetX"]) 156 else: 157 myOffsetX = 0 158 if "originOffsetY" in attrs: 159 myOffsetY = safeEval(attrs["originOffsetY"]) 160 else: 161 myOffsetY = 0 162 current_glyph = Glyph( 163 glyphName=myGlyphName, 164 graphicType=myFormat, 165 originOffsetX=myOffsetX, 166 originOffsetY=myOffsetY, 167 ) 168 for element in content: 169 if isinstance(element, tuple): 170 name, attrs, content = element 171 current_glyph.fromXML(name, attrs, content, ttFont) 172 current_glyph.compile(ttFont) 173 self.glyphs[current_glyph.glyphName] = current_glyph 174 else: 175 from fontTools import ttLib 176 177 raise ttLib.TTLibError("can't handle '%s' element" % name) 178