Merge pull request #1826 from anthrotype/colorLib
colorLib: add buildCOLR and buildCPAL
This commit is contained in:
commit
909be35e57
0
Lib/fontTools/colorLib/__init__.py
Normal file
0
Lib/fontTools/colorLib/__init__.py
Normal file
147
Lib/fontTools/colorLib/builder.py
Normal file
147
Lib/fontTools/colorLib/builder.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import enum
|
||||||
|
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
from fontTools.ttLib.tables.C_O_L_R_ import LayerRecord, table_C_O_L_R_
|
||||||
|
from fontTools.ttLib.tables.C_P_A_L_ import Color, table_C_P_A_L_
|
||||||
|
from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e
|
||||||
|
from .errors import ColorLibError
|
||||||
|
|
||||||
|
|
||||||
|
def buildCOLR(colorLayers: Dict[str, List[Tuple[str, int]]]) -> table_C_O_L_R_:
|
||||||
|
"""Build COLR table from color layers mapping.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
colorLayers: : map of base glyph names to lists of (layer glyph names,
|
||||||
|
palette indices) tuples.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A new COLRv0 table.
|
||||||
|
"""
|
||||||
|
colorLayerLists = {}
|
||||||
|
for baseGlyphName, layers in colorLayers.items():
|
||||||
|
colorLayerLists[baseGlyphName] = [
|
||||||
|
LayerRecord(layerGlyphName, colorID) for layerGlyphName, colorID in layers
|
||||||
|
]
|
||||||
|
|
||||||
|
colr = table_C_O_L_R_()
|
||||||
|
colr.version = 0
|
||||||
|
colr.ColorLayers = colorLayerLists
|
||||||
|
return colr
|
||||||
|
|
||||||
|
|
||||||
|
class ColorPaletteType(enum.IntFlag):
|
||||||
|
USABLE_WITH_LIGHT_BACKGROUND = 0x0001
|
||||||
|
USABLE_WITH_DARK_BACKGROUND = 0x0002
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value):
|
||||||
|
# enforce reserved bits
|
||||||
|
if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0):
|
||||||
|
raise ValueError(f"{value} is not a valid {cls.__name__}")
|
||||||
|
return super()._missing_(value)
|
||||||
|
|
||||||
|
|
||||||
|
# None, 'abc' or {'en': 'abc', 'de': 'xyz'}
|
||||||
|
_OptionalLocalizedString = Union[None, str, Dict[str, str]]
|
||||||
|
|
||||||
|
|
||||||
|
def buildPaletteLabels(
|
||||||
|
labels: List[_OptionalLocalizedString], nameTable: table__n_a_m_e
|
||||||
|
) -> List[Optional[int]]:
|
||||||
|
return [
|
||||||
|
nameTable.addMultilingualName(l, mac=False)
|
||||||
|
if isinstance(l, dict)
|
||||||
|
else table_C_P_A_L_.NO_NAME_ID
|
||||||
|
if l is None
|
||||||
|
else nameTable.addMultilingualName({"en": l}, mac=False)
|
||||||
|
for l in labels
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def buildCPAL(
|
||||||
|
palettes: List[List[Tuple[float, float, float, float]]],
|
||||||
|
paletteTypes: Optional[List[ColorPaletteType]] = None,
|
||||||
|
paletteLabels: Optional[List[_OptionalLocalizedString]] = None,
|
||||||
|
paletteEntryLabels: Optional[List[_OptionalLocalizedString]] = None,
|
||||||
|
nameTable: Optional[table__n_a_m_e] = None,
|
||||||
|
) -> table_C_P_A_L_:
|
||||||
|
"""Build CPAL table from list of color palettes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats
|
||||||
|
in the range [0..1].
|
||||||
|
paletteTypes: optional list of ColorPaletteType, one for each palette.
|
||||||
|
paletteLabels: optional list of palette labels. Each lable can be either:
|
||||||
|
None (no label), a string (for for default English labels), or a
|
||||||
|
localized string (as a dict keyed with BCP47 language codes).
|
||||||
|
paletteEntryLabels: optional list of palette entry labels, one for each
|
||||||
|
palette entry (see paletteLabels).
|
||||||
|
nameTable: optional name table where to store palette and palette entry
|
||||||
|
labels. Required if either paletteLabels or paletteEntryLabels is set.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A new CPAL v0 or v1 table, if custom palette types or labels are specified.
|
||||||
|
"""
|
||||||
|
if len({len(p) for p in palettes}) != 1:
|
||||||
|
raise ColorLibError("color palettes have different lengths")
|
||||||
|
|
||||||
|
if (paletteLabels or paletteEntryLabels) and not nameTable:
|
||||||
|
raise TypeError(
|
||||||
|
"nameTable is required if palette or palette entries have labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
cpal = table_C_P_A_L_()
|
||||||
|
cpal.numPaletteEntries = len(palettes[0])
|
||||||
|
|
||||||
|
cpal.palettes = []
|
||||||
|
for i, palette in enumerate(palettes):
|
||||||
|
colors = []
|
||||||
|
for j, color in enumerate(palette):
|
||||||
|
if not isinstance(color, tuple) or len(color) != 4:
|
||||||
|
raise ColorLibError(
|
||||||
|
f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}"
|
||||||
|
)
|
||||||
|
if any(v > 1 or v < 0 for v in color):
|
||||||
|
raise ColorLibError(
|
||||||
|
f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}"
|
||||||
|
)
|
||||||
|
# input colors are RGBA, CPAL encodes them as BGRA
|
||||||
|
red, green, blue, alpha = color
|
||||||
|
colors.append(Color(*(round(v * 255) for v in (blue, green, red, alpha))))
|
||||||
|
cpal.palettes.append(colors)
|
||||||
|
|
||||||
|
if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)):
|
||||||
|
cpal.version = 1
|
||||||
|
|
||||||
|
if paletteTypes is not None:
|
||||||
|
if len(paletteTypes) != len(palettes):
|
||||||
|
raise ColorLibError(
|
||||||
|
f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}"
|
||||||
|
)
|
||||||
|
cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes]
|
||||||
|
else:
|
||||||
|
cpal.paletteTypes = [table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len(palettes)
|
||||||
|
|
||||||
|
if paletteLabels is not None:
|
||||||
|
if len(paletteLabels) != len(palettes):
|
||||||
|
raise ColorLibError(
|
||||||
|
f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}"
|
||||||
|
)
|
||||||
|
cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable)
|
||||||
|
else:
|
||||||
|
cpal.paletteLabels = [table_C_P_A_L_.NO_NAME_ID] * len(palettes)
|
||||||
|
|
||||||
|
if paletteEntryLabels is not None:
|
||||||
|
if len(paletteEntryLabels) != cpal.numPaletteEntries:
|
||||||
|
raise ColorLibError(
|
||||||
|
f"Expected {cpal.numPaletteEntries} paletteEntryLabels, "
|
||||||
|
f"got {len(paletteEntryLabels)}"
|
||||||
|
)
|
||||||
|
cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable)
|
||||||
|
else:
|
||||||
|
cpal.paletteEntryLabels = [
|
||||||
|
table_C_P_A_L_.NO_NAME_ID
|
||||||
|
] * cpal.numPaletteEntries
|
||||||
|
else:
|
||||||
|
cpal.version = 0
|
||||||
|
|
||||||
|
return cpal
|
3
Lib/fontTools/colorLib/errors.py
Normal file
3
Lib/fontTools/colorLib/errors.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
class ColorLibError(Exception):
|
||||||
|
pass
|
@ -768,6 +768,39 @@ class FontBuilder(object):
|
|||||||
self.font, conditionalSubstitutions, featureTag=featureTag
|
self.font, conditionalSubstitutions, featureTag=featureTag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def setupCOLR(self, colorLayers):
|
||||||
|
"""Build new COLR table using color layers dictionary.
|
||||||
|
|
||||||
|
Cf. `fontTools.colorLib.builder.buildCOLR`.
|
||||||
|
"""
|
||||||
|
from fontTools.colorLib.builder import buildCOLR
|
||||||
|
|
||||||
|
self.font["COLR"] = buildCOLR(colorLayers)
|
||||||
|
|
||||||
|
def setupCPAL(
|
||||||
|
self,
|
||||||
|
palettes,
|
||||||
|
paletteTypes=None,
|
||||||
|
paletteLabels=None,
|
||||||
|
paletteEntryLabels=None,
|
||||||
|
):
|
||||||
|
"""Build new CPAL table using list of palettes.
|
||||||
|
|
||||||
|
Optionally build CPAL v1 table using paletteTypes, paletteLabels and
|
||||||
|
paletteEntryLabels.
|
||||||
|
|
||||||
|
Cf. `fontTools.colorLib.builder.buildCPAL`.
|
||||||
|
"""
|
||||||
|
from fontTools.colorLib.builder import buildCPAL
|
||||||
|
|
||||||
|
self.font["CPAL"] = buildCPAL(
|
||||||
|
palettes,
|
||||||
|
paletteTypes=paletteTypes,
|
||||||
|
paletteLabels=paletteLabels,
|
||||||
|
paletteEntryLabels=paletteEntryLabels,
|
||||||
|
nameTable=self.font.get("name")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def buildCmapSubTable(cmapping, format, platformID, platEncID):
|
def buildCmapSubTable(cmapping, format, platformID, platEncID):
|
||||||
subTable = cmap_classes[format](format)
|
subTable = cmap_classes[format](format)
|
||||||
|
@ -13,6 +13,9 @@ import sys
|
|||||||
|
|
||||||
class table_C_P_A_L_(DefaultTable.DefaultTable):
|
class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||||
|
|
||||||
|
NO_NAME_ID = 0xFFFF
|
||||||
|
DEFAULT_PALETTE_TYPE = 0
|
||||||
|
|
||||||
def __init__(self, tag=None):
|
def __init__(self, tag=None):
|
||||||
DefaultTable.DefaultTable.__init__(self, tag)
|
DefaultTable.DefaultTable.__init__(self, tag)
|
||||||
self.palettes = []
|
self.palettes = []
|
||||||
@ -45,24 +48,25 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
offsetToPaletteEntryLabelArray) = (
|
offsetToPaletteEntryLabelArray) = (
|
||||||
struct.unpack(">LLL", data[pos:pos+12]))
|
struct.unpack(">LLL", data[pos:pos+12]))
|
||||||
self.paletteTypes = self._decompileUInt32Array(
|
self.paletteTypes = self._decompileUInt32Array(
|
||||||
data, offsetToPaletteTypeArray, numPalettes)
|
data, offsetToPaletteTypeArray, numPalettes,
|
||||||
|
default=self.DEFAULT_PALETTE_TYPE)
|
||||||
self.paletteLabels = self._decompileUInt16Array(
|
self.paletteLabels = self._decompileUInt16Array(
|
||||||
data, offsetToPaletteLabelArray, numPalettes)
|
data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID)
|
||||||
self.paletteEntryLabels = self._decompileUInt16Array(
|
self.paletteEntryLabels = self._decompileUInt16Array(
|
||||||
data, offsetToPaletteEntryLabelArray,
|
data, offsetToPaletteEntryLabelArray,
|
||||||
self.numPaletteEntries)
|
self.numPaletteEntries, default=self.NO_NAME_ID)
|
||||||
|
|
||||||
def _decompileUInt16Array(self, data, offset, numElements):
|
def _decompileUInt16Array(self, data, offset, numElements, default=0):
|
||||||
if offset == 0:
|
if offset == 0:
|
||||||
return [0] * numElements
|
return [default] * numElements
|
||||||
result = array.array("H", data[offset : offset + 2 * numElements])
|
result = array.array("H", data[offset : offset + 2 * numElements])
|
||||||
if sys.byteorder != "big": result.byteswap()
|
if sys.byteorder != "big": result.byteswap()
|
||||||
assert len(result) == numElements, result
|
assert len(result) == numElements, result
|
||||||
return result.tolist()
|
return result.tolist()
|
||||||
|
|
||||||
def _decompileUInt32Array(self, data, offset, numElements):
|
def _decompileUInt32Array(self, data, offset, numElements, default=0):
|
||||||
if offset == 0:
|
if offset == 0:
|
||||||
return [0] * numElements
|
return [default] * numElements
|
||||||
result = array.array("I", data[offset : offset + 4 * numElements])
|
result = array.array("I", data[offset : offset + 4 * numElements])
|
||||||
if sys.byteorder != "big": result.byteswap()
|
if sys.byteorder != "big": result.byteswap()
|
||||||
assert len(result) == numElements, result
|
assert len(result) == numElements, result
|
||||||
@ -136,7 +140,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _compilePaletteLabels(self):
|
def _compilePaletteLabels(self):
|
||||||
if self.version == 0 or not any(self.paletteLabels):
|
if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
|
||||||
return b''
|
return b''
|
||||||
assert len(self.paletteLabels) == len(self.palettes)
|
assert len(self.paletteLabels) == len(self.palettes)
|
||||||
result = bytesjoin([struct.pack(">H", label)
|
result = bytesjoin([struct.pack(">H", label)
|
||||||
@ -145,7 +149,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _compilePaletteEntryLabels(self):
|
def _compilePaletteEntryLabels(self):
|
||||||
if self.version == 0 or not any(self.paletteEntryLabels):
|
if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
|
||||||
return b''
|
return b''
|
||||||
assert len(self.paletteEntryLabels) == self.numPaletteEntries
|
assert len(self.paletteEntryLabels) == self.numPaletteEntries
|
||||||
result = bytesjoin([struct.pack(">H", label)
|
result = bytesjoin([struct.pack(">H", label)
|
||||||
@ -165,15 +169,15 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
writer.newline()
|
writer.newline()
|
||||||
for index, palette in enumerate(self.palettes):
|
for index, palette in enumerate(self.palettes):
|
||||||
attrs = {"index": index}
|
attrs = {"index": index}
|
||||||
paletteType = paletteTypes.get(index)
|
paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
|
||||||
paletteLabel = paletteLabels.get(index)
|
paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
|
||||||
if self.version > 0 and paletteLabel is not None:
|
if self.version > 0 and paletteLabel != self.NO_NAME_ID:
|
||||||
attrs["label"] = paletteLabel
|
attrs["label"] = paletteLabel
|
||||||
if self.version > 0 and paletteType is not None:
|
if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
|
||||||
attrs["type"] = paletteType
|
attrs["type"] = paletteType
|
||||||
writer.begintag("palette", **attrs)
|
writer.begintag("palette", **attrs)
|
||||||
writer.newline()
|
writer.newline()
|
||||||
if (self.version > 0 and paletteLabel and
|
if (self.version > 0 and paletteLabel != self.NO_NAME_ID and
|
||||||
ttFont and "name" in ttFont):
|
ttFont and "name" in ttFont):
|
||||||
name = ttFont["name"].getDebugName(paletteLabel)
|
name = ttFont["name"].getDebugName(paletteLabel)
|
||||||
if name is not None:
|
if name is not None:
|
||||||
@ -184,11 +188,11 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
color.toXML(writer, ttFont, cindex)
|
color.toXML(writer, ttFont, cindex)
|
||||||
writer.endtag("palette")
|
writer.endtag("palette")
|
||||||
writer.newline()
|
writer.newline()
|
||||||
if self.version > 0 and any(self.paletteEntryLabels):
|
if self.version > 0 and not all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
|
||||||
writer.begintag("paletteEntryLabels")
|
writer.begintag("paletteEntryLabels")
|
||||||
writer.newline()
|
writer.newline()
|
||||||
for index, label in enumerate(self.paletteEntryLabels):
|
for index, label in enumerate(self.paletteEntryLabels):
|
||||||
if label:
|
if label != self.NO_NAME_ID:
|
||||||
writer.simpletag("label", index=index, value=label)
|
writer.simpletag("label", index=index, value=label)
|
||||||
if (self.version > 0 and label and ttFont and "name" in ttFont):
|
if (self.version > 0 and label and ttFont and "name" in ttFont):
|
||||||
name = ttFont["name"].getDebugName(label)
|
name = ttFont["name"].getDebugName(label)
|
||||||
@ -200,8 +204,8 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
|
|
||||||
def fromXML(self, name, attrs, content, ttFont):
|
def fromXML(self, name, attrs, content, ttFont):
|
||||||
if name == "palette":
|
if name == "palette":
|
||||||
self.paletteLabels.append(int(attrs.get("label", "0")))
|
self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
|
||||||
self.paletteTypes.append(int(attrs.get("type", "0")))
|
self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
|
||||||
palette = []
|
palette = []
|
||||||
for element in content:
|
for element in content:
|
||||||
if isinstance(element, basestring):
|
if isinstance(element, basestring):
|
||||||
@ -221,13 +225,13 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|||||||
nameID = safeEval(elementAttr["value"])
|
nameID = safeEval(elementAttr["value"])
|
||||||
colorLabels[labelIndex] = nameID
|
colorLabels[labelIndex] = nameID
|
||||||
self.paletteEntryLabels = [
|
self.paletteEntryLabels = [
|
||||||
colorLabels.get(i, 0)
|
colorLabels.get(i, self.NO_NAME_ID)
|
||||||
for i in range(self.numPaletteEntries)]
|
for i in range(self.numPaletteEntries)]
|
||||||
elif "value" in attrs:
|
elif "value" in attrs:
|
||||||
value = safeEval(attrs["value"])
|
value = safeEval(attrs["value"])
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
if name == "numPaletteEntries":
|
if name == "numPaletteEntries":
|
||||||
self.paletteEntryLabels = [0] * self.numPaletteEntries
|
self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
|
||||||
|
|
||||||
|
|
||||||
class Color(namedtuple("Color", "blue green red alpha")):
|
class Color(namedtuple("Color", "blue green red alpha")):
|
||||||
|
0
Tests/colorLib/__init__.py
Normal file
0
Tests/colorLib/__init__.py
Normal file
187
Tests/colorLib/builder_test.py
Normal file
187
Tests/colorLib/builder_test.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
from fontTools.ttLib import newTable
|
||||||
|
from fontTools.colorLib import builder
|
||||||
|
from fontTools.colorLib.errors import ColorLibError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildCOLR_v0():
|
||||||
|
color_layer_lists = {
|
||||||
|
"a": [("a.color0", 0), ("a.color1", 1)],
|
||||||
|
"b": [("b.color1", 1), ("b.color0", 0)],
|
||||||
|
}
|
||||||
|
|
||||||
|
colr = builder.buildCOLR(color_layer_lists)
|
||||||
|
|
||||||
|
assert colr.tableTag == "COLR"
|
||||||
|
assert colr.version == 0
|
||||||
|
assert colr.ColorLayers["a"][0].name == "a.color0"
|
||||||
|
assert colr.ColorLayers["a"][0].colorID == 0
|
||||||
|
assert colr.ColorLayers["a"][1].name == "a.color1"
|
||||||
|
assert colr.ColorLayers["a"][1].colorID == 1
|
||||||
|
assert colr.ColorLayers["b"][0].name == "b.color1"
|
||||||
|
assert colr.ColorLayers["b"][0].colorID == 1
|
||||||
|
assert colr.ColorLayers["b"][1].name == "b.color0"
|
||||||
|
assert colr.ColorLayers["b"][1].colorID == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildCPAL_v0():
|
||||||
|
palettes = [
|
||||||
|
[(0.68, 0.20, 0.32, 1.0), (0.45, 0.68, 0.21, 1.0)],
|
||||||
|
[(0.68, 0.20, 0.32, 0.6), (0.45, 0.68, 0.21, 0.6)],
|
||||||
|
[(0.68, 0.20, 0.32, 0.3), (0.45, 0.68, 0.21, 0.3)],
|
||||||
|
]
|
||||||
|
|
||||||
|
cpal = builder.buildCPAL(palettes)
|
||||||
|
|
||||||
|
assert cpal.tableTag == "CPAL"
|
||||||
|
assert cpal.version == 0
|
||||||
|
assert cpal.numPaletteEntries == 2
|
||||||
|
|
||||||
|
assert len(cpal.palettes) == 3
|
||||||
|
assert [tuple(c) for c in cpal.palettes[0]] == [
|
||||||
|
(82, 51, 173, 255),
|
||||||
|
(54, 173, 115, 255),
|
||||||
|
]
|
||||||
|
assert [tuple(c) for c in cpal.palettes[1]] == [
|
||||||
|
(82, 51, 173, 153),
|
||||||
|
(54, 173, 115, 153),
|
||||||
|
]
|
||||||
|
assert [tuple(c) for c in cpal.palettes[2]] == [
|
||||||
|
(82, 51, 173, 76),
|
||||||
|
(54, 173, 115, 76),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildCPAL_palettes_different_lengths():
|
||||||
|
with pytest.raises(ColorLibError, match="have different lengths"):
|
||||||
|
builder.buildCPAL([[(1, 1, 1, 1)], [(0, 0, 0, 1), (0.5, 0.5, 0.5, 1)]])
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildPaletteLabels():
|
||||||
|
name_table = newTable("name")
|
||||||
|
name_table.names = []
|
||||||
|
|
||||||
|
name_ids = builder.buildPaletteLabels(
|
||||||
|
[None, "hi", {"en": "hello", "de": "hallo"}], name_table
|
||||||
|
)
|
||||||
|
|
||||||
|
assert name_ids == [0xFFFF, 256, 257]
|
||||||
|
|
||||||
|
assert len(name_table.names) == 3
|
||||||
|
assert str(name_table.names[0]) == "hi"
|
||||||
|
assert name_table.names[0].nameID == 256
|
||||||
|
|
||||||
|
assert str(name_table.names[1]) == "hallo"
|
||||||
|
assert name_table.names[1].nameID == 257
|
||||||
|
|
||||||
|
assert str(name_table.names[2]) == "hello"
|
||||||
|
assert name_table.names[2].nameID == 257
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_CPAL_v1_types_no_labels():
|
||||||
|
palettes = [
|
||||||
|
[(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
|
||||||
|
[(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
|
||||||
|
[(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
|
||||||
|
]
|
||||||
|
paletteTypes = [
|
||||||
|
builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND,
|
||||||
|
builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
|
||||||
|
builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND
|
||||||
|
| builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
|
||||||
|
]
|
||||||
|
|
||||||
|
cpal = builder.buildCPAL(palettes, paletteTypes=paletteTypes)
|
||||||
|
|
||||||
|
assert cpal.tableTag == "CPAL"
|
||||||
|
assert cpal.version == 1
|
||||||
|
assert cpal.numPaletteEntries == 2
|
||||||
|
assert len(cpal.palettes) == 3
|
||||||
|
|
||||||
|
assert cpal.paletteTypes == paletteTypes
|
||||||
|
assert cpal.paletteLabels == [cpal.NO_NAME_ID] * len(palettes)
|
||||||
|
assert cpal.paletteEntryLabels == [cpal.NO_NAME_ID] * cpal.numPaletteEntries
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_CPAL_v1_labels():
|
||||||
|
palettes = [
|
||||||
|
[(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
|
||||||
|
[(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
|
||||||
|
[(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
|
||||||
|
]
|
||||||
|
paletteLabels = ["First", {"en": "Second", "it": "Seconda"}, None]
|
||||||
|
paletteEntryLabels = ["Foo", "Bar"]
|
||||||
|
|
||||||
|
with pytest.raises(TypeError, match="nameTable is required"):
|
||||||
|
builder.buildCPAL(palettes, paletteLabels=paletteLabels)
|
||||||
|
with pytest.raises(TypeError, match="nameTable is required"):
|
||||||
|
builder.buildCPAL(palettes, paletteEntryLabels=paletteEntryLabels)
|
||||||
|
|
||||||
|
name_table = newTable("name")
|
||||||
|
name_table.names = []
|
||||||
|
|
||||||
|
cpal = builder.buildCPAL(
|
||||||
|
palettes,
|
||||||
|
paletteLabels=paletteLabels,
|
||||||
|
paletteEntryLabels=paletteEntryLabels,
|
||||||
|
nameTable=name_table,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cpal.tableTag == "CPAL"
|
||||||
|
assert cpal.version == 1
|
||||||
|
assert cpal.numPaletteEntries == 2
|
||||||
|
assert len(cpal.palettes) == 3
|
||||||
|
|
||||||
|
assert cpal.paletteTypes == [cpal.DEFAULT_PALETTE_TYPE] * len(palettes)
|
||||||
|
assert cpal.paletteLabels == [256, 257, cpal.NO_NAME_ID]
|
||||||
|
assert cpal.paletteEntryLabels == [258, 259]
|
||||||
|
|
||||||
|
assert name_table.getDebugName(256) == "First"
|
||||||
|
assert name_table.getDebugName(257) == "Second"
|
||||||
|
assert name_table.getDebugName(258) == "Foo"
|
||||||
|
assert name_table.getDebugName(259) == "Bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_ColorPaletteType():
|
||||||
|
with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
|
||||||
|
builder.ColorPaletteType(-1)
|
||||||
|
with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
|
||||||
|
builder.ColorPaletteType(4)
|
||||||
|
with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
|
||||||
|
builder.ColorPaletteType("abc")
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildCPAL_v1_invalid_args_length():
|
||||||
|
with pytest.raises(ColorLibError, match="Expected 2 paletteTypes, got 1"):
|
||||||
|
builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, 1, 1)]], paletteTypes=[1])
|
||||||
|
|
||||||
|
with pytest.raises(ColorLibError, match="Expected 2 paletteLabels, got 1"):
|
||||||
|
builder.buildCPAL(
|
||||||
|
[[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
|
||||||
|
paletteLabels=["foo"],
|
||||||
|
nameTable=newTable("name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ColorLibError, match="Expected 1 paletteEntryLabels, got 0"):
|
||||||
|
cpal = builder.buildCPAL(
|
||||||
|
[[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
|
||||||
|
paletteEntryLabels=[],
|
||||||
|
nameTable=newTable("name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildCPAL_invalid_color():
|
||||||
|
with pytest.raises(
|
||||||
|
ColorLibError,
|
||||||
|
match=r"In palette\[0\]\[1\]: expected \(R, G, B, A\) tuple, got \(1, 1, 1\)",
|
||||||
|
):
|
||||||
|
builder.buildCPAL([[(1, 1, 1, 1), (1, 1, 1)]])
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ColorLibError,
|
||||||
|
match=(
|
||||||
|
r"palette\[1\]\[0\] has invalid out-of-range "
|
||||||
|
r"\[0..1\] color: \(1, 1, -1, 2\)"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, -1, 2)]])
|
@ -66,9 +66,6 @@ class CPALTest(unittest.TestCase):
|
|||||||
self.assertEqual(cpal.numPaletteEntries, 2)
|
self.assertEqual(cpal.numPaletteEntries, 2)
|
||||||
self.assertEqual(repr(cpal.palettes),
|
self.assertEqual(repr(cpal.palettes),
|
||||||
'[[#000000FF, #66CCFFFF], [#000000FF, #800000FF]]')
|
'[[#000000FF, #66CCFFFF], [#000000FF, #800000FF]]')
|
||||||
self.assertEqual(cpal.paletteLabels, [0, 0])
|
|
||||||
self.assertEqual(cpal.paletteTypes, [0, 0])
|
|
||||||
self.assertEqual(cpal.paletteEntryLabels, [0, 0])
|
|
||||||
|
|
||||||
def test_decompile_v0_sharingColors(self):
|
def test_decompile_v0_sharingColors(self):
|
||||||
cpal = newTable('CPAL')
|
cpal = newTable('CPAL')
|
||||||
@ -80,9 +77,6 @@ class CPALTest(unittest.TestCase):
|
|||||||
'[#223344FF, #99887711, #55555555]',
|
'[#223344FF, #99887711, #55555555]',
|
||||||
'[#223344FF, #99887711, #FFFFFFFF]',
|
'[#223344FF, #99887711, #FFFFFFFF]',
|
||||||
'[#223344FF, #99887711, #55555555]'])
|
'[#223344FF, #99887711, #55555555]'])
|
||||||
self.assertEqual(cpal.paletteLabels, [0, 0, 0, 0])
|
|
||||||
self.assertEqual(cpal.paletteTypes, [0, 0, 0, 0])
|
|
||||||
self.assertEqual(cpal.paletteEntryLabels, [0, 0, 0])
|
|
||||||
|
|
||||||
def test_decompile_v1_noLabelsNoTypes(self):
|
def test_decompile_v1_noLabelsNoTypes(self):
|
||||||
cpal = newTable('CPAL')
|
cpal = newTable('CPAL')
|
||||||
@ -92,9 +86,10 @@ class CPALTest(unittest.TestCase):
|
|||||||
self.assertEqual([repr(p) for p in cpal.palettes], [
|
self.assertEqual([repr(p) for p in cpal.palettes], [
|
||||||
'[#CAFECAFE, #22110033, #66554477]', # RGBA
|
'[#CAFECAFE, #22110033, #66554477]', # RGBA
|
||||||
'[#59413127, #42424242, #13330037]'])
|
'[#59413127, #42424242, #13330037]'])
|
||||||
self.assertEqual(cpal.paletteLabels, [0, 0])
|
self.assertEqual(cpal.paletteLabels, [cpal.NO_NAME_ID] * len(cpal.palettes))
|
||||||
self.assertEqual(cpal.paletteTypes, [0, 0])
|
self.assertEqual(cpal.paletteTypes, [0, 0])
|
||||||
self.assertEqual(cpal.paletteEntryLabels, [0, 0, 0])
|
self.assertEqual(cpal.paletteEntryLabels,
|
||||||
|
[cpal.NO_NAME_ID] * cpal.numPaletteEntries)
|
||||||
|
|
||||||
def test_decompile_v1(self):
|
def test_decompile_v1(self):
|
||||||
cpal = newTable('CPAL')
|
cpal = newTable('CPAL')
|
||||||
@ -194,9 +189,6 @@ class CPALTest(unittest.TestCase):
|
|||||||
self.assertEqual(cpal.version, 0)
|
self.assertEqual(cpal.version, 0)
|
||||||
self.assertEqual(cpal.numPaletteEntries, 2)
|
self.assertEqual(cpal.numPaletteEntries, 2)
|
||||||
self.assertEqual(repr(cpal.palettes), '[[#12345678, #FEDCBA98]]')
|
self.assertEqual(repr(cpal.palettes), '[[#12345678, #FEDCBA98]]')
|
||||||
self.assertEqual(cpal.paletteLabels, [0])
|
|
||||||
self.assertEqual(cpal.paletteTypes, [0])
|
|
||||||
self.assertEqual(cpal.paletteEntryLabels, [0, 0])
|
|
||||||
|
|
||||||
def test_fromXML_v1(self):
|
def test_fromXML_v1(self):
|
||||||
cpal = newTable('CPAL')
|
cpal = newTable('CPAL')
|
||||||
@ -218,7 +210,8 @@ class CPALTest(unittest.TestCase):
|
|||||||
'[[#12345678, #FEDCBA98, #CAFECAFE]]')
|
'[[#12345678, #FEDCBA98, #CAFECAFE]]')
|
||||||
self.assertEqual(cpal.paletteLabels, [259])
|
self.assertEqual(cpal.paletteLabels, [259])
|
||||||
self.assertEqual(cpal.paletteTypes, [2])
|
self.assertEqual(cpal.paletteTypes, [2])
|
||||||
self.assertEqual(cpal.paletteEntryLabels, [0, 262, 0])
|
self.assertEqual(cpal.paletteEntryLabels,
|
||||||
|
[cpal.NO_NAME_ID, 262, cpal.NO_NAME_ID])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user