Coverage for /usr/lib/python3/dist-packages/fontTools/ttLib/tables/C_P_A_L_.py: 14%

208 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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 NO_NAME_ID = 0xFFFF 

15 DEFAULT_PALETTE_TYPE = 0 

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 ( 

26 self.version, 

27 self.numPaletteEntries, 

28 numPalettes, 

29 numColorRecords, 

30 goffsetFirstColorRecord, 

31 ) = struct.unpack(">HHHHL", data[:12]) 

32 assert ( 

33 self.version <= 1 

34 ), "Version of CPAL table is higher than I know how to handle" 

35 self.palettes = [] 

36 pos = 12 

37 for i in range(numPalettes): 

38 startIndex = struct.unpack(">H", data[pos : pos + 2])[0] 

39 assert startIndex + self.numPaletteEntries <= numColorRecords 

40 pos += 2 

41 palette = [] 

42 ppos = goffsetFirstColorRecord + startIndex * 4 

43 for j in range(self.numPaletteEntries): 

44 palette.append(Color(*struct.unpack(">BBBB", data[ppos : ppos + 4]))) 

45 ppos += 4 

46 self.palettes.append(palette) 

47 if self.version == 0: 

48 offsetToPaletteTypeArray = 0 

49 offsetToPaletteLabelArray = 0 

50 offsetToPaletteEntryLabelArray = 0 

51 else: 

52 pos = 12 + numPalettes * 2 

53 ( 

54 offsetToPaletteTypeArray, 

55 offsetToPaletteLabelArray, 

56 offsetToPaletteEntryLabelArray, 

57 ) = struct.unpack(">LLL", data[pos : pos + 12]) 

58 self.paletteTypes = self._decompileUInt32Array( 

59 data, 

60 offsetToPaletteTypeArray, 

61 numPalettes, 

62 default=self.DEFAULT_PALETTE_TYPE, 

63 ) 

64 self.paletteLabels = self._decompileUInt16Array( 

65 data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID 

66 ) 

67 self.paletteEntryLabels = self._decompileUInt16Array( 

68 data, 

69 offsetToPaletteEntryLabelArray, 

70 self.numPaletteEntries, 

71 default=self.NO_NAME_ID, 

72 ) 

73 

74 def _decompileUInt16Array(self, data, offset, numElements, default=0): 

75 if offset == 0: 

76 return [default] * numElements 

77 result = array.array("H", data[offset : offset + 2 * numElements]) 

78 if sys.byteorder != "big": 

79 result.byteswap() 

80 assert len(result) == numElements, result 

81 return result.tolist() 

82 

83 def _decompileUInt32Array(self, data, offset, numElements, default=0): 

84 if offset == 0: 

85 return [default] * numElements 

86 result = array.array("I", data[offset : offset + 4 * numElements]) 

87 if sys.byteorder != "big": 

88 result.byteswap() 

89 assert len(result) == numElements, result 

90 return result.tolist() 

91 

92 def compile(self, ttFont): 

93 colorRecordIndices, colorRecords = self._compileColorRecords() 

94 paletteTypes = self._compilePaletteTypes() 

95 paletteLabels = self._compilePaletteLabels() 

96 paletteEntryLabels = self._compilePaletteEntryLabels() 

97 numColorRecords = len(colorRecords) // 4 

98 offsetToFirstColorRecord = 12 + len(colorRecordIndices) 

99 if self.version >= 1: 

100 offsetToFirstColorRecord += 12 

101 header = struct.pack( 

102 ">HHHHL", 

103 self.version, 

104 self.numPaletteEntries, 

105 len(self.palettes), 

106 numColorRecords, 

107 offsetToFirstColorRecord, 

108 ) 

109 if self.version == 0: 

110 dataList = [header, colorRecordIndices, colorRecords] 

111 else: 

112 pos = offsetToFirstColorRecord + len(colorRecords) 

113 if len(paletteTypes) == 0: 

114 offsetToPaletteTypeArray = 0 

115 else: 

116 offsetToPaletteTypeArray = pos 

117 pos += len(paletteTypes) 

118 if len(paletteLabels) == 0: 

119 offsetToPaletteLabelArray = 0 

120 else: 

121 offsetToPaletteLabelArray = pos 

122 pos += len(paletteLabels) 

123 if len(paletteEntryLabels) == 0: 

124 offsetToPaletteEntryLabelArray = 0 

125 else: 

126 offsetToPaletteEntryLabelArray = pos 

127 pos += len(paletteLabels) 

128 header1 = struct.pack( 

129 ">LLL", 

130 offsetToPaletteTypeArray, 

131 offsetToPaletteLabelArray, 

132 offsetToPaletteEntryLabelArray, 

133 ) 

134 dataList = [ 

135 header, 

136 colorRecordIndices, 

137 header1, 

138 colorRecords, 

139 paletteTypes, 

140 paletteLabels, 

141 paletteEntryLabels, 

142 ] 

143 return bytesjoin(dataList) 

144 

145 def _compilePalette(self, palette): 

146 assert len(palette) == self.numPaletteEntries 

147 pack = lambda c: struct.pack(">BBBB", c.blue, c.green, c.red, c.alpha) 

148 return bytesjoin([pack(color) for color in palette]) 

149 

150 def _compileColorRecords(self): 

151 colorRecords, colorRecordIndices, pool = [], [], {} 

152 for palette in self.palettes: 

153 packedPalette = self._compilePalette(palette) 

154 if packedPalette in pool: 

155 index = pool[packedPalette] 

156 else: 

157 index = len(colorRecords) 

158 colorRecords.append(packedPalette) 

159 pool[packedPalette] = index 

160 colorRecordIndices.append(struct.pack(">H", index * self.numPaletteEntries)) 

161 return bytesjoin(colorRecordIndices), bytesjoin(colorRecords) 

162 

163 def _compilePaletteTypes(self): 

164 if self.version == 0 or not any(self.paletteTypes): 

165 return b"" 

166 assert len(self.paletteTypes) == len(self.palettes) 

167 result = bytesjoin([struct.pack(">I", ptype) for ptype in self.paletteTypes]) 

168 assert len(result) == 4 * len(self.palettes) 

169 return result 

170 

171 def _compilePaletteLabels(self): 

172 if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels): 

173 return b"" 

174 assert len(self.paletteLabels) == len(self.palettes) 

175 result = bytesjoin([struct.pack(">H", label) for label in self.paletteLabels]) 

176 assert len(result) == 2 * len(self.palettes) 

177 return result 

178 

179 def _compilePaletteEntryLabels(self): 

180 if self.version == 0 or all( 

181 l == self.NO_NAME_ID for l in self.paletteEntryLabels 

182 ): 

183 return b"" 

184 assert len(self.paletteEntryLabels) == self.numPaletteEntries 

185 result = bytesjoin( 

186 [struct.pack(">H", label) for label in self.paletteEntryLabels] 

187 ) 

188 assert len(result) == 2 * self.numPaletteEntries 

189 return result 

190 

191 def toXML(self, writer, ttFont): 

192 numPalettes = len(self.palettes) 

193 paletteLabels = {i: nameID for (i, nameID) in enumerate(self.paletteLabels)} 

194 paletteTypes = {i: typ for (i, typ) in enumerate(self.paletteTypes)} 

195 writer.simpletag("version", value=self.version) 

196 writer.newline() 

197 writer.simpletag("numPaletteEntries", value=self.numPaletteEntries) 

198 writer.newline() 

199 for index, palette in enumerate(self.palettes): 

200 attrs = {"index": index} 

201 paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE) 

202 paletteLabel = paletteLabels.get(index, self.NO_NAME_ID) 

203 if self.version > 0 and paletteLabel != self.NO_NAME_ID: 

204 attrs["label"] = paletteLabel 

205 if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE: 

206 attrs["type"] = paletteType 

207 writer.begintag("palette", **attrs) 

208 writer.newline() 

209 if ( 

210 self.version > 0 

211 and paletteLabel != self.NO_NAME_ID 

212 and ttFont 

213 and "name" in ttFont 

214 ): 

215 name = ttFont["name"].getDebugName(paletteLabel) 

216 if name is not None: 

217 writer.comment(name) 

218 writer.newline() 

219 assert len(palette) == self.numPaletteEntries 

220 for cindex, color in enumerate(palette): 

221 color.toXML(writer, ttFont, cindex) 

222 writer.endtag("palette") 

223 writer.newline() 

224 if self.version > 0 and not all( 

225 l == self.NO_NAME_ID for l in self.paletteEntryLabels 

226 ): 

227 writer.begintag("paletteEntryLabels") 

228 writer.newline() 

229 for index, label in enumerate(self.paletteEntryLabels): 

230 if label != self.NO_NAME_ID: 

231 writer.simpletag("label", index=index, value=label) 

232 if self.version > 0 and label and ttFont and "name" in ttFont: 

233 name = ttFont["name"].getDebugName(label) 

234 if name is not None: 

235 writer.comment(name) 

236 writer.newline() 

237 writer.endtag("paletteEntryLabels") 

238 writer.newline() 

239 

240 def fromXML(self, name, attrs, content, ttFont): 

241 if name == "palette": 

242 self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID))) 

243 self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE))) 

244 palette = [] 

245 for element in content: 

246 if isinstance(element, str): 

247 continue 

248 attrs = element[1] 

249 color = Color.fromHex(attrs["value"]) 

250 palette.append(color) 

251 self.palettes.append(palette) 

252 elif name == "paletteEntryLabels": 

253 colorLabels = {} 

254 for element in content: 

255 if isinstance(element, str): 

256 continue 

257 elementName, elementAttr, _ = element 

258 if elementName == "label": 

259 labelIndex = safeEval(elementAttr["index"]) 

260 nameID = safeEval(elementAttr["value"]) 

261 colorLabels[labelIndex] = nameID 

262 self.paletteEntryLabels = [ 

263 colorLabels.get(i, self.NO_NAME_ID) 

264 for i in range(self.numPaletteEntries) 

265 ] 

266 elif "value" in attrs: 

267 value = safeEval(attrs["value"]) 

268 setattr(self, name, value) 

269 if name == "numPaletteEntries": 

270 self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries 

271 

272 

273class Color(namedtuple("Color", "blue green red alpha")): 

274 def hex(self): 

275 return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha) 

276 

277 def __repr__(self): 

278 return self.hex() 

279 

280 def toXML(self, writer, ttFont, index=None): 

281 writer.simpletag("color", value=self.hex(), index=index) 

282 writer.newline() 

283 

284 @classmethod 

285 def fromHex(cls, value): 

286 if value[0] == "#": 

287 value = value[1:] 

288 red = int(value[0:2], 16) 

289 green = int(value[2:4], 16) 

290 blue = int(value[4:6], 16) 

291 alpha = int(value[6:8], 16) if len(value) >= 8 else 0xFF 

292 return cls(red=red, green=green, blue=blue, alpha=alpha) 

293 

294 @classmethod 

295 def fromRGBA(cls, red, green, blue, alpha): 

296 return cls(red=red, green=green, blue=blue, alpha=alpha)