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