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