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