123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- # Copyright 2013 Google, Inc. All Rights Reserved.
- #
- # Google Author(s): Behdad Esfahbod
- from fontTools.misc.textTools import bytesjoin, 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)
|