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 raise ttLib.TTLibError 35 if len(self.data) < sbixStrikeHeaderFormatSize: 36 from fontTools import ttLib 37 raise(ttLib.TTLibError, "Strike header too short: Expected %x, got %x.") \ 38 % (sbixStrikeHeaderFormatSize, len(self.data)) 39 40 # read Strike header from raw data 41 sstruct.unpack(sbixStrikeHeaderFormat, self.data[:sbixStrikeHeaderFormatSize], self) 42 43 # calculate number of glyphs 44 firstGlyphDataOffset, = struct.unpack(">L", \ 45 self.data[sbixStrikeHeaderFormatSize:sbixStrikeHeaderFormatSize + sbixGlyphDataOffsetFormatSize]) 46 self.numGlyphs = (firstGlyphDataOffset - sbixStrikeHeaderFormatSize) // sbixGlyphDataOffsetFormatSize - 1 47 # ^ -1 because there's one more offset than glyphs 48 49 # build offset list for single glyph data offsets 50 self.glyphDataOffsets = [] 51 for i in range(self.numGlyphs + 1): # + 1 because there's one more offset than glyphs 52 start = i * sbixGlyphDataOffsetFormatSize + sbixStrikeHeaderFormatSize 53 current_offset, = struct.unpack(">L", self.data[start:start + sbixGlyphDataOffsetFormatSize]) 54 self.glyphDataOffsets.append(current_offset) 55 56 # iterate through offset list and slice raw data into glyph data records 57 for i in range(self.numGlyphs): 58 current_glyph = Glyph(rawdata=self.data[self.glyphDataOffsets[i]:self.glyphDataOffsets[i+1]], gid=i) 59 current_glyph.decompile(ttFont) 60 self.glyphs[current_glyph.glyphName] = current_glyph 61 del self.glyphDataOffsets 62 del self.numGlyphs 63 del self.data 64 65 def compile(self, ttFont): 66 self.glyphDataOffsets = b"" 67 self.bitmapData = b"" 68 69 glyphOrder = ttFont.getGlyphOrder() 70 71 # first glyph starts right after the header 72 currentGlyphDataOffset = sbixStrikeHeaderFormatSize + sbixGlyphDataOffsetFormatSize * (len(glyphOrder) + 1) 73 for glyphName in glyphOrder: 74 if glyphName in self.glyphs: 75 # we have glyph data for this glyph 76 current_glyph = self.glyphs[glyphName] 77 else: 78 # must add empty glyph data record for this glyph 79 current_glyph = Glyph(glyphName=glyphName) 80 current_glyph.compile(ttFont) 81 current_glyph.glyphDataOffset = currentGlyphDataOffset 82 self.bitmapData += current_glyph.rawdata 83 currentGlyphDataOffset += len(current_glyph.rawdata) 84 self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, current_glyph) 85 86 # add last "offset", really the end address of the last glyph data record 87 dummy = Glyph() 88 dummy.glyphDataOffset = currentGlyphDataOffset 89 self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, dummy) 90 91 # pack header 92 self.data = sstruct.pack(sbixStrikeHeaderFormat, self) 93 # add offsets and image data after header 94 self.data += self.glyphDataOffsets + self.bitmapData 95 96 def toXML(self, xmlWriter, ttFont): 97 xmlWriter.begintag("strike") 98 xmlWriter.newline() 99 xmlWriter.simpletag("ppem", value=self.ppem) 100 xmlWriter.newline() 101 xmlWriter.simpletag("resolution", value=self.resolution) 102 xmlWriter.newline() 103 glyphOrder = ttFont.getGlyphOrder() 104 for i in range(len(glyphOrder)): 105 if glyphOrder[i] in self.glyphs: 106 self.glyphs[glyphOrder[i]].toXML(xmlWriter, ttFont) 107 # TODO: what if there are more glyph data records than (glyf table) glyphs? 108 xmlWriter.endtag("strike") 109 xmlWriter.newline() 110 111 def fromXML(self, name, attrs, content, ttFont): 112 if name in ["ppem", "resolution"]: 113 setattr(self, name, safeEval(attrs["value"])) 114 elif name == "glyph": 115 if "graphicType" in attrs: 116 myFormat = safeEval("'''" + attrs["graphicType"] + "'''") 117 else: 118 myFormat = None 119 if "glyphname" in attrs: 120 myGlyphName = safeEval("'''" + attrs["glyphname"] + "'''") 121 elif "name" in attrs: 122 myGlyphName = safeEval("'''" + attrs["name"] + "'''") 123 else: 124 from fontTools import ttLib 125 raise ttLib.TTLibError("Glyph must have a glyph name.") 126 if "originOffsetX" in attrs: 127 myOffsetX = safeEval(attrs["originOffsetX"]) 128 else: 129 myOffsetX = 0 130 if "originOffsetY" in attrs: 131 myOffsetY = safeEval(attrs["originOffsetY"]) 132 else: 133 myOffsetY = 0 134 current_glyph = Glyph( 135 glyphName=myGlyphName, 136 graphicType=myFormat, 137 originOffsetX=myOffsetX, 138 originOffsetY=myOffsetY, 139 ) 140 for element in content: 141 if isinstance(element, tuple): 142 name, attrs, content = element 143 current_glyph.fromXML(name, attrs, content, ttFont) 144 current_glyph.compile(ttFont) 145 self.glyphs[current_glyph.glyphName] = current_glyph 146 else: 147 from fontTools import ttLib 148 raise ttLib.TTLibError("can't handle '%s' element" % name) 149