1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
5from __future__ import print_function, division, absolute_import
6from fontTools.misc.py23 import *
7from fontTools.misc.textTools import safeEval
8from . import DefaultTable
9import operator
10import struct
11
12
13class table_C_O_L_R_(DefaultTable.DefaultTable):
14
15	""" This table is structured so that you can treat it like a dictionary keyed by glyph name.
16	ttFont['COLR'][<glyphName>] will return the color layers for any glyph
17	ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph.
18	"""
19
20	def decompile(self, data, ttFont):
21		self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
22		self.version, numBaseGlyphRecords, offsetBaseGlyphRecord, offsetLayerRecord, numLayerRecords = struct.unpack(">HHLLH", data[:14])
23		assert (self.version == 0), "Version of COLR table is higher than I know how to handle"
24		glyphOrder = ttFont.getGlyphOrder()
25		gids = []
26		layerLists = []
27		glyphPos = offsetBaseGlyphRecord
28		for i in range(numBaseGlyphRecords):
29			gid, firstLayerIndex, numLayers = struct.unpack(">HHH", data[glyphPos:glyphPos+6])
30			glyphPos += 6
31			gids.append(gid)
32			assert (firstLayerIndex + numLayers <= numLayerRecords)
33			layerPos = offsetLayerRecord + firstLayerIndex * 4
34			layers = []
35			for j in range(numLayers):
36				layerGid, colorID = struct.unpack(">HH", data[layerPos:layerPos+4])
37				try:
38					layerName = glyphOrder[layerGid]
39				except IndexError:
40					layerName = self.getGlyphName(layerGid)
41				layerPos += 4
42				layers.append(LayerRecord(layerName, colorID))
43			layerLists.append(layers)
44
45		self.ColorLayers = colorLayerLists = {}
46		try:
47			names = list(map(operator.getitem, [glyphOrder]*numBaseGlyphRecords, gids))
48		except IndexError:
49			getGlyphName = self.getGlyphName
50			names = list(map(getGlyphName, gids ))
51
52		list(map(operator.setitem, [colorLayerLists]*numBaseGlyphRecords, names, layerLists))
53
54
55	def compile(self, ttFont):
56		ordered = []
57		ttFont.getReverseGlyphMap(rebuild=True)
58		glyphNames = self.ColorLayers.keys()
59		for glyphName in glyphNames:
60			try:
61				gid = ttFont.getGlyphID(glyphName)
62			except:
63				assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
64			ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
65		ordered.sort()
66
67		glyphMap = []
68		layerMap = []
69		for (gid, glyphName, layers) in ordered:
70			glyphMap.append(struct.pack(">HHH", gid, len(layerMap), len(layers)))
71			for layer in layers:
72				layerMap.append(struct.pack(">HH", ttFont.getGlyphID(layer.name), layer.colorID))
73
74		dataList = [struct.pack(">HHLLH", self.version, len(glyphMap), 14, 14+6*len(glyphMap), len(layerMap))]
75		dataList.extend(glyphMap)
76		dataList.extend(layerMap)
77		data = bytesjoin(dataList)
78		return data
79
80	def toXML(self, writer, ttFont):
81		writer.simpletag("version", value=self.version)
82		writer.newline()
83		ordered = []
84		glyphNames = self.ColorLayers.keys()
85		for glyphName in glyphNames:
86			try:
87				gid = ttFont.getGlyphID(glyphName)
88			except:
89				assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
90			ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
91		ordered.sort()
92		for entry in ordered:
93			writer.begintag("ColorGlyph", name=entry[1])
94			writer.newline()
95			for layer in entry[2]:
96				layer.toXML(writer, ttFont)
97			writer.endtag("ColorGlyph")
98			writer.newline()
99
100	def fromXML(self, name, attrs, content, ttFont):
101		if not hasattr(self, "ColorLayers"):
102			self.ColorLayers = {}
103		self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
104		if name == "ColorGlyph":
105			glyphName = attrs["name"]
106			for element in content:
107				if isinstance(element, basestring):
108					continue
109			layers = []
110			for element in content:
111				if isinstance(element, basestring):
112					continue
113				layer = LayerRecord()
114				layer.fromXML(element[0], element[1], element[2], ttFont)
115				layers.append (layer)
116			operator.setitem(self, glyphName, layers)
117		elif "value" in attrs:
118			setattr(self, name, safeEval(attrs["value"]))
119
120
121	def __getitem__(self, glyphSelector):
122		if isinstance(glyphSelector, int):
123			# its a gid, convert to glyph name
124			glyphSelector = self.getGlyphName(glyphSelector)
125
126		if glyphSelector not in self.ColorLayers:
127			return None
128
129		return self.ColorLayers[glyphSelector]
130
131	def __setitem__(self, glyphSelector, value):
132		if isinstance(glyphSelector, int):
133			# its a gid, convert to glyph name
134			glyphSelector = self.getGlyphName(glyphSelector)
135
136		if  value:
137			self.ColorLayers[glyphSelector] = value
138		elif glyphSelector in self.ColorLayers:
139			del self.ColorLayers[glyphSelector]
140
141	def __delitem__(self, glyphSelector):
142		del self.ColorLayers[glyphSelector]
143
144class LayerRecord(object):
145
146	def __init__(self, name = None, colorID = None):
147		self.name = name
148		self.colorID = colorID
149
150	def toXML(self, writer, ttFont):
151		writer.simpletag("layer", name=self.name, colorID=self.colorID)
152		writer.newline()
153
154	def fromXML(self, eltname, attrs, content, ttFont):
155		for (name, value) in attrs.items():
156			if name == "name":
157				if isinstance(value, int):
158					value = ttFont.getGlyphName(value)
159				setattr(self, name, value)
160			else:
161				setattr(self, name, safeEval(value))
162