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