__doc__="""
Compiles/decompiles version 0 and 1 SVG tables from/to XML.
Version 1 is the first SVG definition, implemented in Mozilla before Aug 2013, now deprecated.
This module will decompile this correctly, but will compile a version 1 table
only if you add the secret element "" to the SVG element in the TTF file.
Version 0 is the joint Adobe-Mozilla proposal, which supports color palettes.
The XML format is:
Color values must be less than 256.
The number of color records in each must be the same as
the number of elements.
"""
import DefaultTable
import struct
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
import string
import types
import re
XML = ET.XML
XMLElement = ET.Element
xmlToString = ET.tostring
SVG_format_0 = """
> # big endian
version: H
offsetToSVGDocIndex: L
offsetToColorPalettes: L
"""
SVG_format_0Size = sstruct.calcsize(SVG_format_0)
SVG_format_1 = """
> # big endian
version: H
numIndicies: H
"""
SVG_format_1Size = sstruct.calcsize(SVG_format_1)
doc_index_entry_format_0 = """
> # big endian
startGlyphID: H
endGlyphID: H
svgDocOffset: L
svgDocLength: L
"""
doc_index_entry_format_0Size = sstruct.calcsize(doc_index_entry_format_0)
colorRecord_format_0 = """
red: B
green: B
blue: B
alpha: B
"""
class table_S_V_G_(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
self.docList = None
self.colorPalettes = None
pos = 0
self.version = struct.unpack(">H", data[pos:pos+2])[0]
if self.version == 1:
self.decompile_format_1(data, ttFont)
else:
if self.version != 0:
print "Unknown SVG table version '%s'. Decompiling as version 0." % (self.version)
self.decompile_format_0(data, ttFont)
def decompile_format_0(self, data, ttFont):
dummy, data2 = sstruct.unpack2(SVG_format_0, data, self)
# read in SVG Documents Index
self.decompileEntryList(data)
# read in colorPalettes table.
self.colorPalettes = colorPalettes = ColorPalettes()
pos = self.offsetToColorPalettes
if pos > 0:
colorPalettes.numColorParams = numColorParams = struct.unpack(">H", data[pos:pos+2])[0]
if numColorParams > 0:
colorPalettes.colorParamUINameIDs = colorParamUINameIDs = []
pos = pos + 2
i = 0
while i < numColorParams:
nameID = struct.unpack(">H", data[pos:pos+2])[0]
colorParamUINameIDs.append(nameID)
pos = pos + 2
i += 1
colorPalettes.numColorPalettes = numColorPalettes = struct.unpack(">H", data[pos:pos+2])[0]
pos = pos + 2
if numColorPalettes > 0:
colorPalettes.colorPaletteList = colorPaletteList = []
i = 0
while i < numColorPalettes:
colorPalette = ColorPalette()
colorPaletteList.append(colorPalette)
colorPalette.uiNameID = struct.unpack(">H", data[pos:pos+2])[0]
pos = pos + 2
colorPalette.paletteColors = paletteColors = []
j = 0
while j < numColorParams:
colorRecord, colorPaletteData = sstruct.unpack2(colorRecord_format_0, data[pos:], ColorRecord())
paletteColors.append(colorRecord)
j += 1
pos += 4
i += 1
def decompile_format_1(self, data, ttFont):
pos = 2
self.numEntries = struct.unpack(">H", data[pos:pos+2])[0]
pos += 2
self.decompileEntryList(data, pos)
def decompileEntryList(self, data):
# data starts with the first entry of the entry list.
pos = subTableStart = self.offsetToSVGDocIndex
self.numEntries = numEntries = struct.unpack(">H", data[pos:pos+2])[0]
pos += 2
if self.numEntries > 0:
data2 = data[pos:]
self.docList = []
self.entries = entries = []
i = 0
while i < self.numEntries:
docIndexEntry, data2 = sstruct.unpack2(doc_index_entry_format_0, data2, DocumentIndexEntry())
entries.append(docIndexEntry)
i += 1
for entry in entries:
start = entry.svgDocOffset + subTableStart
end = start + entry.svgDocLength
doc = data[start:end]
self.docList.append( [doc, entry.startGlyphID, entry.endGlyphID] )
def compile(self, ttFont):
if hasattr(self, "version1"):
data = self.compileFormat1(ttFont)
else:
data = self.compileFormat0(ttFont)
return data
def compileFormat0(self, ttFont):
version = 0
offsetToSVGDocIndex = SVG_format_0Size # I start the SVGDocIndex right after the header.
# get SGVDoc info.
docList = []
entryList = []
numEntries = len(self.docList)
datum = struct.pack(">H",numEntries)
entryList.append(datum)
curOffset = len(datum) + doc_index_entry_format_0Size*numEntries
for doc, startGlyphID, endGlyphID in self.docList:
docOffset = curOffset
docLength = len(doc)
curOffset += docLength
entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength)
entryList.append(entry)
docList.append(doc)
entryList.extend(docList)
svgDocData = "".join(entryList)
# get colorpalette info.
if self.colorPalettes == None:
offsetToColorPalettes = 0
palettesData = ""
else:
offsetToColorPalettes = SVG_format_0Size + len(svgDocData)
dataList = []
numColorParams = len(self.colorPalettes.colorParamUINameIDs)
datum = struct.pack(">H", numColorParams)
dataList.append(datum)
for uiNameId in self.colorPalettes.colorParamUINameIDs:
datum = struct.pack(">H", uiNameId)
dataList.append(datum)
numColorPalettes = len(self.colorPalettes.colorPaletteList)
datum = struct.pack(">H", numColorPalettes)
dataList.append(datum)
for colorPalette in self.colorPalettes.colorPaletteList:
datum = struct.pack(">H", colorPalette.uiNameID)
dataList.append(datum)
for colorRecord in colorPalette.paletteColors:
data = struct.pack(">BBBB", colorRecord.red, colorRecord.green, colorRecord.blue, colorRecord.alpha)
dataList.append(data)
palettesData = "".join(dataList)
header = struct.pack(">HLL", version, offsetToSVGDocIndex, offsetToColorPalettes)
data = [header, svgDocData, palettesData]
data = "".join(data)
return data
def compileFormat1(self, ttFont):
version = 1
numEntries = len(self.docList)
header = struct.pack(">HH", version, numEntries)
dataList = [header]
docList = []
curOffset = SVG_format_1Size + doc_index_entry_format_0Size*numEntries
for doc, startGlyphID, endGlyphID in self.docList:
docOffset = curOffset
docLength = len(doc)
curOffset += docLength
entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength)
dataList.append(entry)
docList.append(doc)
dataList.extend(docList)
data = "".join(dataList)
return data
def toXML(self, writer, ttFont):
writer.newline()
for doc, startGID, endGID in self.docList:
writer.begintag("svgDoc", startGlyphID=startGID, endGlyphID=endGID)
writer.newline()
writer.writeraw("")
writer.newline()
writer.endtag("svgDoc")
writer.newline()
if (self.colorPalettes != None) and (self.colorPalettes.numColorParams != None):
writer.begintag("colorPalettes")
writer.newline()
for uiNameID in self.colorPalettes.colorParamUINameIDs:
writer.begintag("colorParamUINameID")
writer.writeraw(str(uiNameID))
writer.endtag("colorParamUINameID")
writer.newline()
for colorPalette in self.colorPalettes.colorPaletteList:
writer.begintag("colorPalette", [("uiNameID", str(colorPalette.uiNameID))])
writer.newline()
for colorRecord in colorPalette.paletteColors:
colorAttributes = [
("red", hex(colorRecord.red)),
("green", hex(colorRecord.green)),
("blue", hex(colorRecord.blue)),
("alpha", hex(colorRecord.alpha)),
]
writer.begintag("colorRecord", colorAttributes)
writer.endtag("colorRecord")
writer.newline()
writer.endtag("colorPalette")
writer.newline()
writer.endtag("colorPalettes")
writer.newline()
else:
writer.begintag("colorPalettes")
writer.endtag("colorPalettes")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
import re
if name == "svgDoc":
if not hasattr(self, "docList"):
self.docList = []
doc = "".join(content)
doc = doc.strip()
startGID = int(attrs["startGlyphID"])
endGID = int(attrs["endGlyphID"])
self.docList.append( [doc, startGID, endGID] )
elif name == "colorPalettes":
self.colorPalettes = ColorPalettes()
self.colorPalettes.fromXML((name, attrs, content), ttFont)
if self.colorPalettes.numColorParams == 0:
self.colorPalettes = None
else:
print "Unknown", name, content
class DocumentIndexEntry:
def __init__(self):
self.startGlyphID = None # USHORT
self.endGlyphID = None # USHORT
self.svgDocOffset = None # ULONG
self.svgDocLength = None # ULONG
def __repr__(self):
return "startGlyphID: %s, endGlyphID: %s, svgDocOffset: %s, svgDocLength: %s" % (self.startGlyphID, self.endGlyphID, self.svgDocOffset, self.svgDocLength)
class ColorPalettes:
def __init__(self):
self.numColorParams = None # USHORT
self.colorParamUINameIDs = [] # list of name table name ID values that provide UI description of each color palette.
self.numColorPalettes = None # USHORT
self.colorPaletteList = [] # list of ColorPalette records
def fromXML(self, (name, attrs, content), ttFont):
for element in content:
if type(element) == type(""):
continue
name, attrib, content = element
if name == "colorParamUINameID":
uiNameID = int(content[0])
self.colorParamUINameIDs.append(uiNameID)
elif name == "colorPalette":
colorPalette = ColorPalette()
self.colorPaletteList.append(colorPalette)
colorPalette.fromXML((name, attrib, content), ttFont)
self.numColorParams = len(self.colorParamUINameIDs)
self.numColorPalettes = len(self.colorPaletteList)
for colorPalette in self.colorPaletteList:
if len(colorPalette.paletteColors) != self.numColorParams:
raise ValueError("Number of color records in a colorPalette ('%s') does not match the number of colorParamUINameIDs elements ('%s')." % (len(colorPalette.paletteColors), self.numColorParams))
class ColorPalette:
def __init__(self):
self.uiNameID = None # USHORT. name table ID that describes user interface strings associated with this color palette.
self.paletteColors = [] # list of ColorRecords
def fromXML(self, (name, attrs, content), ttFont):
self.uiNameID = int(attrs["uiNameID"])
for element in content:
if type(element) == type(""):
continue
name, attrib, content = element
if name == "colorRecord":
colorRecord = ColorRecord()
self.paletteColors.append(colorRecord)
colorRecord.red = eval(attrib["red"])
colorRecord.green = eval(attrib["green"])
colorRecord.blue = eval(attrib["blue"])
colorRecord.alpha = eval(attrib["alpha"])
class ColorRecord:
def __init__(self):
self.red = 255 # all are one byte values.
self.green = 255
self.blue = 255
self.alpha = 255