• 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 bytesjoin, safeEval
6from . import DefaultTable
7import array
8from collections import namedtuple
9import struct
10import sys
11
12
13class table_C_P_A_L_(DefaultTable.DefaultTable):
14
15	NO_NAME_ID = 0xFFFF
16	DEFAULT_PALETTE_TYPE = 0
17
18	def __init__(self, tag=None):
19		DefaultTable.DefaultTable.__init__(self, tag)
20		self.palettes = []
21		self.paletteTypes = []
22		self.paletteLabels = []
23		self.paletteEntryLabels = []
24
25	def decompile(self, data, ttFont):
26		self.version, self.numPaletteEntries, numPalettes, numColorRecords, goffsetFirstColorRecord = struct.unpack(">HHHHL", data[:12])
27		assert (self.version <= 1), "Version of CPAL table is higher than I know how to handle"
28		self.palettes = []
29		pos = 12
30		for i in range(numPalettes):
31			startIndex = struct.unpack(">H", data[pos:pos+2])[0]
32			assert (startIndex + self.numPaletteEntries <= numColorRecords)
33			pos += 2
34			palette = []
35			ppos = goffsetFirstColorRecord + startIndex * 4
36			for j in range(self.numPaletteEntries):
37				palette.append( Color(*struct.unpack(">BBBB", data[ppos:ppos+4])) )
38				ppos += 4
39			self.palettes.append(palette)
40		if self.version == 0:
41			offsetToPaletteTypeArray = 0
42			offsetToPaletteLabelArray = 0
43			offsetToPaletteEntryLabelArray = 0
44		else:
45			pos = 12 + numPalettes * 2
46			(offsetToPaletteTypeArray, offsetToPaletteLabelArray,
47			offsetToPaletteEntryLabelArray) = (
48				struct.unpack(">LLL", data[pos:pos+12]))
49		self.paletteTypes = self._decompileUInt32Array(
50			data, offsetToPaletteTypeArray, numPalettes,
51			default=self.DEFAULT_PALETTE_TYPE)
52		self.paletteLabels = self._decompileUInt16Array(
53			data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID)
54		self.paletteEntryLabels = self._decompileUInt16Array(
55			data, offsetToPaletteEntryLabelArray,
56			self.numPaletteEntries, default=self.NO_NAME_ID)
57
58	def _decompileUInt16Array(self, data, offset, numElements, default=0):
59		if offset == 0:
60			return [default] * numElements
61		result = array.array("H", data[offset : offset + 2 * numElements])
62		if sys.byteorder != "big": result.byteswap()
63		assert len(result) == numElements, result
64		return result.tolist()
65
66	def _decompileUInt32Array(self, data, offset, numElements, default=0):
67		if offset == 0:
68			return [default] * numElements
69		result = array.array("I", data[offset : offset + 4 * numElements])
70		if sys.byteorder != "big": result.byteswap()
71		assert len(result) == numElements, result
72		return result.tolist()
73
74	def compile(self, ttFont):
75		colorRecordIndices, colorRecords = self._compileColorRecords()
76		paletteTypes = self._compilePaletteTypes()
77		paletteLabels = self._compilePaletteLabels()
78		paletteEntryLabels = self._compilePaletteEntryLabels()
79		numColorRecords = len(colorRecords) // 4
80		offsetToFirstColorRecord = 12 + len(colorRecordIndices)
81		if self.version >= 1:
82			offsetToFirstColorRecord += 12
83		header = struct.pack(">HHHHL", self.version,
84                                     self.numPaletteEntries, len(self.palettes),
85                                     numColorRecords, offsetToFirstColorRecord)
86		if self.version == 0:
87			dataList = [header, colorRecordIndices, colorRecords]
88		else:
89			pos = offsetToFirstColorRecord + len(colorRecords)
90			if len(paletteTypes) == 0:
91				offsetToPaletteTypeArray = 0
92			else:
93				offsetToPaletteTypeArray = pos
94				pos += len(paletteTypes)
95			if len(paletteLabels) == 0:
96				offsetToPaletteLabelArray = 0
97			else:
98				offsetToPaletteLabelArray = pos
99				pos += len(paletteLabels)
100			if len(paletteEntryLabels) == 0:
101				offsetToPaletteEntryLabelArray = 0
102			else:
103				offsetToPaletteEntryLabelArray = pos
104				pos += len(paletteLabels)
105			header1 = struct.pack(">LLL",
106				offsetToPaletteTypeArray,
107				offsetToPaletteLabelArray,
108				offsetToPaletteEntryLabelArray)
109			dataList = [header, colorRecordIndices, header1,
110				    colorRecords, paletteTypes, paletteLabels,
111                                    paletteEntryLabels]
112		return bytesjoin(dataList)
113
114	def _compilePalette(self, palette):
115		assert(len(palette) == self.numPaletteEntries)
116		pack = lambda c: struct.pack(">BBBB", c.blue, c.green, c.red, c.alpha)
117		return bytesjoin([pack(color) for color in palette])
118
119	def _compileColorRecords(self):
120		colorRecords, colorRecordIndices, pool = [], [], {}
121		for palette in self.palettes:
122			packedPalette = self._compilePalette(palette)
123			if packedPalette in pool:
124				index = pool[packedPalette]
125			else:
126				index = len(colorRecords)
127				colorRecords.append(packedPalette)
128				pool[packedPalette] = index
129			colorRecordIndices.append(struct.pack(">H", index * self.numPaletteEntries))
130		return bytesjoin(colorRecordIndices), bytesjoin(colorRecords)
131
132	def _compilePaletteTypes(self):
133		if self.version == 0 or not any(self.paletteTypes):
134			return b''
135		assert len(self.paletteTypes) == len(self.palettes)
136		result = bytesjoin([struct.pack(">I", ptype)
137                                    for ptype in self.paletteTypes])
138		assert len(result) == 4 * len(self.palettes)
139		return result
140
141	def _compilePaletteLabels(self):
142		if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
143			return b''
144		assert len(self.paletteLabels) == len(self.palettes)
145		result = bytesjoin([struct.pack(">H", label)
146                                    for label in self.paletteLabels])
147		assert len(result) == 2 * len(self.palettes)
148		return result
149
150	def _compilePaletteEntryLabels(self):
151		if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
152			return b''
153		assert len(self.paletteEntryLabels) == self.numPaletteEntries
154		result = bytesjoin([struct.pack(">H", label)
155                                    for label in self.paletteEntryLabels])
156		assert len(result) == 2 * self.numPaletteEntries
157		return result
158
159	def toXML(self, writer, ttFont):
160		numPalettes = len(self.palettes)
161		paletteLabels = {i: nameID
162				for (i, nameID) in enumerate(self.paletteLabels)}
163		paletteTypes = {i: typ for (i, typ) in enumerate(self.paletteTypes)}
164		writer.simpletag("version", value=self.version)
165		writer.newline()
166		writer.simpletag("numPaletteEntries",
167				 value=self.numPaletteEntries)
168		writer.newline()
169		for index, palette in enumerate(self.palettes):
170			attrs = {"index": index}
171			paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
172			paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
173			if self.version > 0 and paletteLabel != self.NO_NAME_ID:
174				attrs["label"] = paletteLabel
175			if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
176				attrs["type"] = paletteType
177			writer.begintag("palette", **attrs)
178			writer.newline()
179			if (self.version > 0 and paletteLabel != self.NO_NAME_ID and
180			    ttFont and "name" in ttFont):
181				name = ttFont["name"].getDebugName(paletteLabel)
182				if name is not None:
183					writer.comment(name)
184					writer.newline()
185			assert(len(palette) == self.numPaletteEntries)
186			for cindex, color in enumerate(palette):
187				color.toXML(writer, ttFont, cindex)
188			writer.endtag("palette")
189			writer.newline()
190		if self.version > 0 and not all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
191			writer.begintag("paletteEntryLabels")
192			writer.newline()
193			for index, label in enumerate(self.paletteEntryLabels):
194				if label != self.NO_NAME_ID:
195					writer.simpletag("label", index=index, value=label)
196					if (self.version > 0 and label and ttFont and "name" in ttFont):
197						name = ttFont["name"].getDebugName(label)
198						if name is not None:
199							writer.comment(name)
200					writer.newline()
201			writer.endtag("paletteEntryLabels")
202			writer.newline()
203
204	def fromXML(self, name, attrs, content, ttFont):
205		if name == "palette":
206			self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
207			self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
208			palette = []
209			for element in content:
210				if isinstance(element, str):
211					continue
212				attrs = element[1]
213				color = Color.fromHex(attrs["value"])
214				palette.append(color)
215			self.palettes.append(palette)
216		elif name == "paletteEntryLabels":
217			colorLabels = {}
218			for element in content:
219				if isinstance(element, str):
220					continue
221				elementName, elementAttr, _ = element
222				if elementName == "label":
223					labelIndex = safeEval(elementAttr["index"])
224					nameID = safeEval(elementAttr["value"])
225					colorLabels[labelIndex] = nameID
226			self.paletteEntryLabels = [
227				colorLabels.get(i, self.NO_NAME_ID)
228				for i in range(self.numPaletteEntries)]
229		elif "value" in attrs:
230			value = safeEval(attrs["value"])
231			setattr(self, name, value)
232			if name == "numPaletteEntries":
233				self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
234
235
236class Color(namedtuple("Color", "blue green red alpha")):
237
238	def hex(self):
239		return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha)
240
241	def __repr__(self):
242		return self.hex()
243
244	def toXML(self, writer, ttFont, index=None):
245		writer.simpletag("color", value=self.hex(), index=index)
246		writer.newline()
247
248	@classmethod
249	def fromHex(cls, value):
250		if value[0] == '#':
251			value = value[1:]
252		red = int(value[0:2], 16)
253		green = int(value[2:4], 16)
254		blue = int(value[4:6], 16)
255		alpha = int(value[6:8], 16) if len (value) >= 8 else 0xFF
256		return cls(red=red, green=green, blue=blue, alpha=alpha)
257
258	@classmethod
259	def fromRGBA(cls, red, green, blue, alpha):
260		return cls(red=red, green=green, blue=blue, alpha=alpha)
261