S_V_G_.py revision 2b06aaa2a6bcd363c25fb0c43f6bb906906594bd
1__doc__=""" 2Compiles/decompiles version 0 and 1 SVG tables from/to XML. 3 4Version 1 is the first SVG definition, implemented in Mozilla before Aug 2013, now deprecated. 5This module will decompile this correctly, but will compile a version 1 table 6only if you add the secret element "<version1/>" to the SVG element in the TTF file. 7 8Version 0 is the joint Adobe-Mozilla proposal, which supports color palettes. 9 10The XML format is: 11 <SVG> 12 <svgDoc endGlyphID="1" startGlyphID="1"> 13 <![CDATA[ <complete SVG doc> ]] 14 </svgDoc> 15... 16 <svgDoc endGlyphID="n" startGlyphID="m"> 17 <![CDATA[ <complete SVG doc> ]] 18 </svgDoc> 19 20 <colorPalettes> 21 <colorParamUINameID>n</colorParamUINameID> 22 ... 23 <colorParamUINameID>m</colorParamUINameID> 24 <colorPalette uiNameID="n"> 25 <colorRecord red="<int>" green="<int>" blue="<int>" alpha="<int>" /> 26 ... 27 <colorRecord red="<int>" green="<int>" blue="<int>" alpha="<int>" /> 28 </colorPalette> 29 ... 30 <colorPalette uiNameID="m"> 31 <colorRecord red="<int> green="<int>" blue="<int>" alpha="<int>" /> 32 ... 33 <colorRecord red=<int>" green="<int>" blue="<int>" alpha="<int>" /> 34 </colorPalette> 35 </colorPalettes> 36</SVG> 37 38Color values must be less than 256. 39 40The number of color records in each </colorPalette> must be the same as 41the number of <colorParamUINameID> elements. 42 43""" 44 45from . import DefaultTable 46import struct 47from fontTools.misc import sstruct 48from fontTools.misc.textTools import safeEval 49try: 50 import xml.etree.cElementTree as ET 51except ImportError: 52 import xml.etree.ElementTree as ET 53import string 54import types 55import re 56 57XML = ET.XML 58XMLElement = ET.Element 59xmlToString = ET.tostring 60 61SVG_format_0 = """ 62 > # big endian 63 version: H 64 offsetToSVGDocIndex: L 65 offsetToColorPalettes: L 66""" 67 68SVG_format_0Size = sstruct.calcsize(SVG_format_0) 69 70SVG_format_1 = """ 71 > # big endian 72 version: H 73 numIndicies: H 74""" 75 76SVG_format_1Size = sstruct.calcsize(SVG_format_1) 77 78doc_index_entry_format_0 = """ 79 > # big endian 80 startGlyphID: H 81 endGlyphID: H 82 svgDocOffset: L 83 svgDocLength: L 84""" 85 86doc_index_entry_format_0Size = sstruct.calcsize(doc_index_entry_format_0) 87 88colorRecord_format_0 = """ 89 red: B 90 green: B 91 blue: B 92 alpha: B 93""" 94 95 96class table_S_V_G_(DefaultTable.DefaultTable): 97 98 def decompile(self, data, ttFont): 99 self.docList = None 100 self.colorPalettes = None 101 pos = 0 102 self.version = struct.unpack(">H", data[pos:pos+2])[0] 103 104 if self.version == 1: 105 self.decompile_format_1(data, ttFont) 106 else: 107 if self.version != 0: 108 print "Unknown SVG table version '%s'. Decompiling as version 0." % (self.version) 109 self.decompile_format_0(data, ttFont) 110 111 112 def decompile_format_0(self, data, ttFont): 113 dummy, data2 = sstruct.unpack2(SVG_format_0, data, self) 114 # read in SVG Documents Index 115 self.decompileEntryList(data) 116 117 # read in colorPalettes table. 118 self.colorPalettes = colorPalettes = ColorPalettes() 119 pos = self.offsetToColorPalettes 120 if pos > 0: 121 colorPalettes.numColorParams = numColorParams = struct.unpack(">H", data[pos:pos+2])[0] 122 if numColorParams > 0: 123 colorPalettes.colorParamUINameIDs = colorParamUINameIDs = [] 124 pos = pos + 2 125 i = 0 126 while i < numColorParams: 127 nameID = struct.unpack(">H", data[pos:pos+2])[0] 128 colorParamUINameIDs.append(nameID) 129 pos = pos + 2 130 i += 1 131 132 colorPalettes.numColorPalettes = numColorPalettes = struct.unpack(">H", data[pos:pos+2])[0] 133 pos = pos + 2 134 if numColorPalettes > 0: 135 colorPalettes.colorPaletteList = colorPaletteList = [] 136 i = 0 137 while i < numColorPalettes: 138 colorPalette = ColorPalette() 139 colorPaletteList.append(colorPalette) 140 colorPalette.uiNameID = struct.unpack(">H", data[pos:pos+2])[0] 141 pos = pos + 2 142 colorPalette.paletteColors = paletteColors = [] 143 j = 0 144 while j < numColorParams: 145 colorRecord, colorPaletteData = sstruct.unpack2(colorRecord_format_0, data[pos:], ColorRecord()) 146 paletteColors.append(colorRecord) 147 j += 1 148 pos += 4 149 i += 1 150 151 def decompile_format_1(self, data, ttFont): 152 pos = 2 153 self.numEntries = struct.unpack(">H", data[pos:pos+2])[0] 154 pos += 2 155 self.decompileEntryList(data, pos) 156 157 def decompileEntryList(self, data): 158 # data starts with the first entry of the entry list. 159 pos = subTableStart = self.offsetToSVGDocIndex 160 self.numEntries = numEntries = struct.unpack(">H", data[pos:pos+2])[0] 161 pos += 2 162 if self.numEntries > 0: 163 data2 = data[pos:] 164 self.docList = [] 165 self.entries = entries = [] 166 i = 0 167 while i < self.numEntries: 168 docIndexEntry, data2 = sstruct.unpack2(doc_index_entry_format_0, data2, DocumentIndexEntry()) 169 entries.append(docIndexEntry) 170 i += 1 171 172 for entry in entries: 173 start = entry.svgDocOffset + subTableStart 174 end = start + entry.svgDocLength 175 doc = data[start:end] 176 self.docList.append( [doc, entry.startGlyphID, entry.endGlyphID] ) 177 178 def compile(self, ttFont): 179 if hasattr(self, "version1"): 180 data = self.compileFormat1(ttFont) 181 else: 182 data = self.compileFormat0(ttFont) 183 return data 184 185 def compileFormat0(self, ttFont): 186 version = 0 187 offsetToSVGDocIndex = SVG_format_0Size # I start the SVGDocIndex right after the header. 188 # get SGVDoc info. 189 docList = [] 190 entryList = [] 191 numEntries = len(self.docList) 192 datum = struct.pack(">H",numEntries) 193 entryList.append(datum) 194 curOffset = len(datum) + doc_index_entry_format_0Size*numEntries 195 for doc, startGlyphID, endGlyphID in self.docList: 196 docOffset = curOffset 197 docLength = len(doc) 198 curOffset += docLength 199 entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength) 200 entryList.append(entry) 201 docList.append(doc) 202 entryList.extend(docList) 203 svgDocData = "".join(entryList) 204 205 # get colorpalette info. 206 if self.colorPalettes == None: 207 offsetToColorPalettes = 0 208 palettesData = "" 209 else: 210 offsetToColorPalettes = SVG_format_0Size + len(svgDocData) 211 dataList = [] 212 numColorParams = len(self.colorPalettes.colorParamUINameIDs) 213 datum = struct.pack(">H", numColorParams) 214 dataList.append(datum) 215 for uiNameId in self.colorPalettes.colorParamUINameIDs: 216 datum = struct.pack(">H", uiNameId) 217 dataList.append(datum) 218 numColorPalettes = len(self.colorPalettes.colorPaletteList) 219 datum = struct.pack(">H", numColorPalettes) 220 dataList.append(datum) 221 for colorPalette in self.colorPalettes.colorPaletteList: 222 datum = struct.pack(">H", colorPalette.uiNameID) 223 dataList.append(datum) 224 for colorRecord in colorPalette.paletteColors: 225 data = struct.pack(">BBBB", colorRecord.red, colorRecord.green, colorRecord.blue, colorRecord.alpha) 226 dataList.append(data) 227 palettesData = "".join(dataList) 228 229 header = struct.pack(">HLL", version, offsetToSVGDocIndex, offsetToColorPalettes) 230 data = [header, svgDocData, palettesData] 231 data = "".join(data) 232 return data 233 234 def compileFormat1(self, ttFont): 235 version = 1 236 numEntries = len(self.docList) 237 header = struct.pack(">HH", version, numEntries) 238 dataList = [header] 239 docList = [] 240 curOffset = SVG_format_1Size + doc_index_entry_format_0Size*numEntries 241 for doc, startGlyphID, endGlyphID in self.docList: 242 docOffset = curOffset 243 docLength = len(doc) 244 curOffset += docLength 245 entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength) 246 dataList.append(entry) 247 docList.append(doc) 248 dataList.extend(docList) 249 data = "".join(dataList) 250 return data 251 252 def toXML(self, writer, ttFont): 253 writer.newline() 254 for doc, startGID, endGID in self.docList: 255 writer.begintag("svgDoc", startGlyphID=startGID, endGlyphID=endGID) 256 writer.newline() 257 writer.writeraw("<![CDATA["+ doc + "]]>") 258 writer.newline() 259 writer.endtag("svgDoc") 260 writer.newline() 261 262 if (self.colorPalettes != None) and (self.colorPalettes.numColorParams != None): 263 writer.begintag("colorPalettes") 264 writer.newline() 265 for uiNameID in self.colorPalettes.colorParamUINameIDs: 266 writer.begintag("colorParamUINameID") 267 writer.writeraw(str(uiNameID)) 268 writer.endtag("colorParamUINameID") 269 writer.newline() 270 for colorPalette in self.colorPalettes.colorPaletteList: 271 writer.begintag("colorPalette", [("uiNameID", str(colorPalette.uiNameID))]) 272 writer.newline() 273 for colorRecord in colorPalette.paletteColors: 274 colorAttributes = [ 275 ("red", hex(colorRecord.red)), 276 ("green", hex(colorRecord.green)), 277 ("blue", hex(colorRecord.blue)), 278 ("alpha", hex(colorRecord.alpha)), 279 ] 280 writer.begintag("colorRecord", colorAttributes) 281 writer.endtag("colorRecord") 282 writer.newline() 283 writer.endtag("colorPalette") 284 writer.newline() 285 286 writer.endtag("colorPalettes") 287 writer.newline() 288 else: 289 writer.begintag("colorPalettes") 290 writer.endtag("colorPalettes") 291 writer.newline() 292 293 def fromXML(self, (name, attrs, content), ttFont): 294 import re 295 if name == "svgDoc": 296 if not hasattr(self, "docList"): 297 self.docList = [] 298 doc = "".join(content) 299 doc = doc.strip() 300 startGID = int(attrs["startGlyphID"]) 301 endGID = int(attrs["endGlyphID"]) 302 self.docList.append( [doc, startGID, endGID] ) 303 elif name == "colorPalettes": 304 self.colorPalettes = ColorPalettes() 305 self.colorPalettes.fromXML((name, attrs, content), ttFont) 306 if self.colorPalettes.numColorParams == 0: 307 self.colorPalettes = None 308 else: 309 print "Unknown", name, content 310 311class DocumentIndexEntry: 312 def __init__(self): 313 self.startGlyphID = None # USHORT 314 self.endGlyphID = None # USHORT 315 self.svgDocOffset = None # ULONG 316 self.svgDocLength = None # ULONG 317 318 def __repr__(self): 319 return "startGlyphID: %s, endGlyphID: %s, svgDocOffset: %s, svgDocLength: %s" % (self.startGlyphID, self.endGlyphID, self.svgDocOffset, self.svgDocLength) 320 321class ColorPalettes: 322 def __init__(self): 323 self.numColorParams = None # USHORT 324 self.colorParamUINameIDs = [] # list of name table name ID values that provide UI description of each color palette. 325 self.numColorPalettes = None # USHORT 326 self.colorPaletteList = [] # list of ColorPalette records 327 328 def fromXML(self, (name, attrs, content), ttFont): 329 for element in content: 330 if type(element) == type(""): 331 continue 332 name, attrib, content = element 333 if name == "colorParamUINameID": 334 uiNameID = int(content[0]) 335 self.colorParamUINameIDs.append(uiNameID) 336 elif name == "colorPalette": 337 colorPalette = ColorPalette() 338 self.colorPaletteList.append(colorPalette) 339 colorPalette.fromXML((name, attrib, content), ttFont) 340 341 self.numColorParams = len(self.colorParamUINameIDs) 342 self.numColorPalettes = len(self.colorPaletteList) 343 for colorPalette in self.colorPaletteList: 344 if len(colorPalette.paletteColors) != self.numColorParams: 345 raise ValueError("Number of color records in a colorPalette ('%s') does not match the number of colorParamUINameIDs elements ('%s')." % (len(colorPalette.paletteColors), self.numColorParams)) 346 347class ColorPalette: 348 def __init__(self): 349 self.uiNameID = None # USHORT. name table ID that describes user interface strings associated with this color palette. 350 self.paletteColors = [] # list of ColorRecords 351 352 def fromXML(self, (name, attrs, content), ttFont): 353 self.uiNameID = int(attrs["uiNameID"]) 354 for element in content: 355 if type(element) == type(""): 356 continue 357 name, attrib, content = element 358 if name == "colorRecord": 359 colorRecord = ColorRecord() 360 self.paletteColors.append(colorRecord) 361 colorRecord.red = eval(attrib["red"]) 362 colorRecord.green = eval(attrib["green"]) 363 colorRecord.blue = eval(attrib["blue"]) 364 colorRecord.alpha = eval(attrib["alpha"]) 365 366class ColorRecord: 367 def __init__(self): 368 self.red = 255 # all are one byte values. 369 self.green = 255 370 self.blue = 255 371 self.alpha = 255 372