1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
5from fontTools.misc.textTools import safeEval
6from . import DefaultTable
7
8
9class table_C_O_L_R_(DefaultTable.DefaultTable):
10
11	""" This table is structured so that you can treat it like a dictionary keyed by glyph name.
12	ttFont['COLR'][<glyphName>] will return the color layers for any glyph
13	ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph.
14	"""
15
16	@staticmethod
17	def _decompileColorLayersV0(table):
18		if not table.LayerRecordArray:
19			return {}
20		colorLayerLists = {}
21		layerRecords = table.LayerRecordArray.LayerRecord
22		numLayerRecords = len(layerRecords)
23		for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
24			baseGlyph = baseRec.BaseGlyph
25			firstLayerIndex = baseRec.FirstLayerIndex
26			numLayers = baseRec.NumLayers
27			assert (firstLayerIndex + numLayers <= numLayerRecords)
28			layers = []
29			for i in range(firstLayerIndex, firstLayerIndex+numLayers):
30				layerRec = layerRecords[i]
31				layers.append(
32					LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex)
33				)
34			colorLayerLists[baseGlyph] = layers
35		return colorLayerLists
36
37	def _toOTTable(self, ttFont):
38		from . import otTables
39		from fontTools.colorLib.builder import populateCOLRv0
40
41		tableClass = getattr(otTables, self.tableTag)
42		table = tableClass()
43		table.Version = self.version
44
45		populateCOLRv0(
46			table,
47			{
48				baseGlyph: [(layer.name, layer.colorID) for layer in layers]
49				for baseGlyph, layers in self.ColorLayers.items()
50			},
51			glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
52		)
53		return table
54
55	def decompile(self, data, ttFont):
56		from .otBase import OTTableReader
57		from . import otTables
58
59		# We use otData to decompile, but we adapt the decompiled otTables to the
60		# existing COLR v0 API for backward compatibility.
61		reader = OTTableReader(data, tableTag=self.tableTag)
62		tableClass = getattr(otTables, self.tableTag)
63		table = tableClass()
64		table.decompile(reader, ttFont)
65
66		self.version = table.Version
67		if self.version == 0:
68			self.ColorLayers = self._decompileColorLayersV0(table)
69		else:
70			# for new versions, keep the raw otTables around
71			self.table = table
72
73	def compile(self, ttFont):
74		from .otBase import OTTableWriter
75
76		if hasattr(self, "table"):
77			table = self.table
78		else:
79			table = self._toOTTable(ttFont)
80
81		writer = OTTableWriter(tableTag=self.tableTag)
82		table.compile(writer, ttFont)
83		return writer.getAllData()
84
85	def toXML(self, writer, ttFont):
86		if hasattr(self, "table"):
87			self.table.toXML2(writer, ttFont)
88		else:
89			writer.simpletag("version", value=self.version)
90			writer.newline()
91			for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
92				writer.begintag("ColorGlyph", name=baseGlyph)
93				writer.newline()
94				for layer in self.ColorLayers[baseGlyph]:
95					layer.toXML(writer, ttFont)
96				writer.endtag("ColorGlyph")
97				writer.newline()
98
99	def fromXML(self, name, attrs, content, ttFont):
100		if name == "version":  # old COLR v0 API
101			setattr(self, name, safeEval(attrs["value"]))
102		elif name == "ColorGlyph":
103			if not hasattr(self, "ColorLayers"):
104				self.ColorLayers = {}
105			glyphName = attrs["name"]
106			for element in content:
107				if isinstance(element, str):
108					continue
109			layers = []
110			for element in content:
111				if isinstance(element, str):
112					continue
113				layer = LayerRecord()
114				layer.fromXML(element[0], element[1], element[2], ttFont)
115				layers.append (layer)
116			self.ColorLayers[glyphName] = layers
117		else:  # new COLR v1 API
118			from . import otTables
119
120			if not hasattr(self, "table"):
121				tableClass = getattr(otTables, self.tableTag)
122				self.table = tableClass()
123			self.table.fromXML(name, attrs, content, ttFont)
124			self.table.populateDefaults()
125			self.version = self.table.Version
126
127	def __getitem__(self, glyphName):
128		if not isinstance(glyphName, str):
129			raise TypeError(f"expected str, found {type(glyphName).__name__}")
130		return self.ColorLayers[glyphName]
131
132	def __setitem__(self, glyphName, value):
133		if not isinstance(glyphName, str):
134			raise TypeError(f"expected str, found {type(glyphName).__name__}")
135		if value is not None:
136			self.ColorLayers[glyphName] = value
137		elif glyphName in self.ColorLayers:
138			del self.ColorLayers[glyphName]
139
140	def __delitem__(self, glyphName):
141		del self.ColorLayers[glyphName]
142
143class LayerRecord(object):
144
145	def __init__(self, name=None, colorID=None):
146		self.name = name
147		self.colorID = colorID
148
149	def toXML(self, writer, ttFont):
150		writer.simpletag("layer", name=self.name, colorID=self.colorID)
151		writer.newline()
152
153	def fromXML(self, eltname, attrs, content, ttFont):
154		for (name, value) in attrs.items():
155			if name == "name":
156				setattr(self, name, value)
157			else:
158				setattr(self, name, safeEval(value))
159