aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/ttLib/tables/S_V_G_.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/ttLib/tables/S_V_G_.py')
-rw-r--r--Lib/fontTools/ttLib/tables/S_V_G_.py370
1 files changed, 370 insertions, 0 deletions
diff --git a/Lib/fontTools/ttLib/tables/S_V_G_.py b/Lib/fontTools/ttLib/tables/S_V_G_.py
new file mode 100644
index 00000000..c3f00dd5
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/S_V_G_.py
@@ -0,0 +1,370 @@
+from __future__ import print_function, division, absolute_import
+from fontTools.misc.py23 import *
+from fontTools.misc import sstruct
+from . import DefaultTable
+try:
+ import xml.etree.cElementTree as ET
+except ImportError:
+ import xml.etree.ElementTree as ET
+import struct
+import re
+
+__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 "<version1/>" to the SVG element in the TTF file.
+
+Version 0 is the joint Adobe-Mozilla proposal, which supports color palettes.
+
+The XML format is:
+ <SVG>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[ <complete SVG doc> ]]
+ </svgDoc>
+...
+ <svgDoc endGlyphID="n" startGlyphID="m">
+ <![CDATA[ <complete SVG doc> ]]
+ </svgDoc>
+
+ <colorPalettes>
+ <colorParamUINameID>n</colorParamUINameID>
+ ...
+ <colorParamUINameID>m</colorParamUINameID>
+ <colorPalette uiNameID="n">
+ <colorRecord red="<int>" green="<int>" blue="<int>" alpha="<int>" />
+ ...
+ <colorRecord red="<int>" green="<int>" blue="<int>" alpha="<int>" />
+ </colorPalette>
+ ...
+ <colorPalette uiNameID="m">
+ <colorRecord red="<int> green="<int>" blue="<int>" alpha="<int>" />
+ ...
+ <colorRecord red=<int>" green="<int>" blue="<int>" alpha="<int>" />
+ </colorPalette>
+ </colorPalettes>
+</SVG>
+
+Color values must be less than 256.
+
+The number of color records in each </colorPalette> must be the same as
+the number of <colorParamUINameID> elements.
+
+"""
+
+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 = tostr(data[start:end], "utf-8")
+ 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(tobytes(doc, encoding="utf-8"))
+ entryList.extend(docList)
+ svgDocData = bytesjoin(entryList)
+
+ # get colorpalette info.
+ if self.colorPalettes is 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 = bytesjoin(dataList)
+
+ header = struct.pack(">HLL", version, offsetToSVGDocIndex, offsetToColorPalettes)
+ data = [header, svgDocData, palettesData]
+ data = bytesjoin(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(tobytes(doc, encoding="utf-8"))
+ dataList.extend(docList)
+ data = bytesjoin(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.writecdata(doc)
+ writer.newline()
+ writer.endtag("svgDoc")
+ writer.newline()
+
+ if (self.colorPalettes is not None) and (self.colorPalettes.numColorParams is not 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 = strjoin(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(object):
+ 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(object):
+ 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 isinstance(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(object):
+ 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 isinstance(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(object):
+ def __init__(self):
+ self.red = 255 # all are one byte values.
+ self.green = 255
+ self.blue = 255
+ self.alpha = 255