Home | History | Annotate | Download | only in tables
      1 # Copyright 2013 Google, Inc. All Rights Reserved.
      2 #
      3 # Google Author(s): Behdad Esfahbod
      4 
      5 from __future__ import print_function, division, absolute_import
      6 from fontTools.misc.py23 import *
      7 from fontTools.misc.textTools import safeEval
      8 from . import DefaultTable
      9 import array
     10 from collections import namedtuple
     11 import struct
     12 import sys
     13 
     14 
     15 class 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 
    234 class 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