Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc import sstruct
      4 from . import DefaultTable
      5 try:
      6     import xml.etree.cElementTree as ET
      7 except ImportError:
      8     import xml.etree.ElementTree as ET
      9 import struct
     10 import re
     11 
     12 __doc__="""
     13 Compiles/decompiles version 0 and 1 SVG tables from/to XML.
     14 
     15 Version 1 is the first SVG definition, implemented in Mozilla before Aug 2013, now deprecated.
     16 This module will decompile this correctly, but will compile a version 1 table
     17 only if you add the secret element "<version1/>" to the SVG element in the TTF file.
     18 
     19 Version 0 is the joint Adobe-Mozilla proposal, which supports color palettes.
     20 
     21 The 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 
     49 Color values must be less than 256. 
     50 
     51 The number of color records in each </colorPalette> must be the same as
     52 the number of <colorParamUINameID> elements.
     53 
     54 """
     55 
     56 XML = ET.XML
     57 XMLElement = ET.Element
     58 xmlToString = ET.tostring
     59 
     60 SVG_format_0 = """
     61 	>   # big endian
     62 	version:                  H
     63 	offsetToSVGDocIndex:      L
     64 	offsetToColorPalettes:    L
     65 """
     66 
     67 SVG_format_0Size = sstruct.calcsize(SVG_format_0)
     68 
     69 SVG_format_1 = """
     70 	>   # big endian
     71 	version:                  H
     72 	numIndicies:              H
     73 """
     74 
     75 SVG_format_1Size = sstruct.calcsize(SVG_format_1)
     76 
     77 doc_index_entry_format_0 = """
     78 	>   # big endian
     79 	startGlyphID:             H
     80 	endGlyphID:               H
     81 	svgDocOffset:             L
     82 	svgDocLength:             L
     83 """
     84 
     85 doc_index_entry_format_0Size = sstruct.calcsize(doc_index_entry_format_0)
     86 
     87 colorRecord_format_0 = """
     88 	red:                      B
     89 	green:                    B
     90 	blue:                     B
     91 	alpha:                    B
     92 """
     93 
     94 
     95 class 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 
    310 class 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 
    320 class 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 
    346 class 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 
    365 class 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