# Copyright 2013 Google, Inc. All Rights Reserved. # # Google Author(s): Behdad Esfahbod from fontTools.misc.py23 import bytesjoin from fontTools.misc.textTools import safeEval from . import DefaultTable import array from collections import namedtuple import struct import sys class table_C_P_A_L_(DefaultTable.DefaultTable): NO_NAME_ID = 0xFFFF DEFAULT_PALETTE_TYPE = 0 def __init__(self, tag=None): DefaultTable.DefaultTable.__init__(self, tag) self.palettes = [] self.paletteTypes = [] self.paletteLabels = [] self.paletteEntryLabels = [] def decompile(self, data, ttFont): self.version, self.numPaletteEntries, numPalettes, numColorRecords, goffsetFirstColorRecord = struct.unpack(">HHHHL", data[:12]) assert (self.version <= 1), "Version of CPAL table is higher than I know how to handle" self.palettes = [] pos = 12 for i in range(numPalettes): startIndex = struct.unpack(">H", data[pos:pos+2])[0] assert (startIndex + self.numPaletteEntries <= numColorRecords) pos += 2 palette = [] ppos = goffsetFirstColorRecord + startIndex * 4 for j in range(self.numPaletteEntries): palette.append( Color(*struct.unpack(">BBBB", data[ppos:ppos+4])) ) ppos += 4 self.palettes.append(palette) if self.version == 0: offsetToPaletteTypeArray = 0 offsetToPaletteLabelArray = 0 offsetToPaletteEntryLabelArray = 0 else: pos = 12 + numPalettes * 2 (offsetToPaletteTypeArray, offsetToPaletteLabelArray, offsetToPaletteEntryLabelArray) = ( struct.unpack(">LLL", data[pos:pos+12])) self.paletteTypes = self._decompileUInt32Array( data, offsetToPaletteTypeArray, numPalettes, default=self.DEFAULT_PALETTE_TYPE) self.paletteLabels = self._decompileUInt16Array( data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID) self.paletteEntryLabels = self._decompileUInt16Array( data, offsetToPaletteEntryLabelArray, self.numPaletteEntries, default=self.NO_NAME_ID) def _decompileUInt16Array(self, data, offset, numElements, default=0): if offset == 0: return [default] * numElements result = array.array("H", data[offset : offset + 2 * numElements]) if sys.byteorder != "big": result.byteswap() assert len(result) == numElements, result return result.tolist() def _decompileUInt32Array(self, data, offset, numElements, default=0): if offset == 0: return [default] * numElements result = array.array("I", data[offset : offset + 4 * numElements]) if sys.byteorder != "big": result.byteswap() assert len(result) == numElements, result return result.tolist() def compile(self, ttFont): colorRecordIndices, colorRecords = self._compileColorRecords() paletteTypes = self._compilePaletteTypes() paletteLabels = self._compilePaletteLabels() paletteEntryLabels = self._compilePaletteEntryLabels() numColorRecords = len(colorRecords) // 4 offsetToFirstColorRecord = 12 + len(colorRecordIndices) if self.version >= 1: offsetToFirstColorRecord += 12 header = struct.pack(">HHHHL", self.version, self.numPaletteEntries, len(self.palettes), numColorRecords, offsetToFirstColorRecord) if self.version == 0: dataList = [header, colorRecordIndices, colorRecords] else: pos = offsetToFirstColorRecord + len(colorRecords) if len(paletteTypes) == 0: offsetToPaletteTypeArray = 0 else: offsetToPaletteTypeArray = pos pos += len(paletteTypes) if len(paletteLabels) == 0: offsetToPaletteLabelArray = 0 else: offsetToPaletteLabelArray = pos pos += len(paletteLabels) if len(paletteEntryLabels) == 0: offsetToPaletteEntryLabelArray = 0 else: offsetToPaletteEntryLabelArray = pos pos += len(paletteLabels) header1 = struct.pack(">LLL", offsetToPaletteTypeArray, offsetToPaletteLabelArray, offsetToPaletteEntryLabelArray) dataList = [header, colorRecordIndices, header1, colorRecords, paletteTypes, paletteLabels, paletteEntryLabels] return bytesjoin(dataList) def _compilePalette(self, palette): assert(len(palette) == self.numPaletteEntries) pack = lambda c: struct.pack(">BBBB", c.blue, c.green, c.red, c.alpha) return bytesjoin([pack(color) for color in palette]) def _compileColorRecords(self): colorRecords, colorRecordIndices, pool = [], [], {} for palette in self.palettes: packedPalette = self._compilePalette(palette) if packedPalette in pool: index = pool[packedPalette] else: index = len(colorRecords) colorRecords.append(packedPalette) pool[packedPalette] = index colorRecordIndices.append(struct.pack(">H", index * self.numPaletteEntries)) return bytesjoin(colorRecordIndices), bytesjoin(colorRecords) def _compilePaletteTypes(self): if self.version == 0 or not any(self.paletteTypes): return b'' assert len(self.paletteTypes) == len(self.palettes) result = bytesjoin([struct.pack(">I", ptype) for ptype in self.paletteTypes]) assert len(result) == 4 * len(self.palettes) return result def _compilePaletteLabels(self): if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels): return b'' assert len(self.paletteLabels) == len(self.palettes) result = bytesjoin([struct.pack(">H", label) for label in self.paletteLabels]) assert len(result) == 2 * len(self.palettes) return result def _compilePaletteEntryLabels(self): if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteEntryLabels): return b'' assert len(self.paletteEntryLabels) == self.numPaletteEntries result = bytesjoin([struct.pack(">H", label) for label in self.paletteEntryLabels]) assert len(result) == 2 * self.numPaletteEntries return result def toXML(self, writer, ttFont): numPalettes = len(self.palettes) paletteLabels = {i: nameID for (i, nameID) in enumerate(self.paletteLabels)} paletteTypes = {i: typ for (i, typ) in enumerate(self.paletteTypes)} writer.simpletag("version", value=self.version) writer.newline() writer.simpletag("numPaletteEntries", value=self.numPaletteEntries) writer.newline() for index, palette in enumerate(self.palettes): attrs = {"index": index} paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE) paletteLabel = paletteLabels.get(index, self.NO_NAME_ID) if self.version > 0 and paletteLabel != self.NO_NAME_ID: attrs["label"] = paletteLabel if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE: attrs["type"] = paletteType writer.begintag("palette", **attrs) writer.newline() if (self.version > 0 and paletteLabel != self.NO_NAME_ID and ttFont and "name" in ttFont): name = ttFont["name"].getDebugName(paletteLabel) if name is not None: writer.comment(name) writer.newline() assert(len(palette) == self.numPaletteEntries) for cindex, color in enumerate(palette): color.toXML(writer, ttFont, cindex) writer.endtag("palette") writer.newline() if self.version > 0 and not all(l == self.NO_NAME_ID for l in self.paletteEntryLabels): writer.begintag("paletteEntryLabels") writer.newline() for index, label in enumerate(self.paletteEntryLabels): if label != self.NO_NAME_ID: writer.simpletag("label", index=index, value=label) if (self.version > 0 and label and ttFont and "name" in ttFont): name = ttFont["name"].getDebugName(label) if name is not None: writer.comment(name) writer.newline() writer.endtag("paletteEntryLabels") writer.newline() def fromXML(self, name, attrs, content, ttFont): if name == "palette": self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID))) self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE))) palette = [] for element in content: if isinstance(element, str): continue attrs = element[1] color = Color.fromHex(attrs["value"]) palette.append(color) self.palettes.append(palette) elif name == "paletteEntryLabels": colorLabels = {} for element in content: if isinstance(element, str): continue elementName, elementAttr, _ = element if elementName == "label": labelIndex = safeEval(elementAttr["index"]) nameID = safeEval(elementAttr["value"]) colorLabels[labelIndex] = nameID self.paletteEntryLabels = [ colorLabels.get(i, self.NO_NAME_ID) for i in range(self.numPaletteEntries)] elif "value" in attrs: value = safeEval(attrs["value"]) setattr(self, name, value) if name == "numPaletteEntries": self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries class Color(namedtuple("Color", "blue green red alpha")): def hex(self): return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha) def __repr__(self): return self.hex() def toXML(self, writer, ttFont, index=None): writer.simpletag("color", value=self.hex(), index=index) writer.newline() @classmethod def fromHex(cls, value): if value[0] == '#': value = value[1:] red = int(value[0:2], 16) green = int(value[2:4], 16) blue = int(value[4:6], 16) alpha = int(value[6:8], 16) if len (value) >= 8 else 0xFF return cls(red=red, green=green, blue=blue, alpha=alpha) @classmethod def fromRGBA(cls, red, green, blue, alpha): return cls(red=red, green=green, blue=blue, alpha=alpha)