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