C_P_A_L_.py 11 KB

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