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