| 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 |