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
|
||||
)
|
||||
|
||||
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):
|
||||
subTable = cmap_classes[format](format)
|
||||
|
@ -13,6 +13,9 @@ 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 = []
|
||||
@ -45,24 +48,25 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
offsetToPaletteEntryLabelArray) = (
|
||||
struct.unpack(">LLL", data[pos:pos+12]))
|
||||
self.paletteTypes = self._decompileUInt32Array(
|
||||
data, offsetToPaletteTypeArray, numPalettes)
|
||||
data, offsetToPaletteTypeArray, numPalettes,
|
||||
default=self.DEFAULT_PALETTE_TYPE)
|
||||
self.paletteLabels = self._decompileUInt16Array(
|
||||
data, offsetToPaletteLabelArray, numPalettes)
|
||||
data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID)
|
||||
self.paletteEntryLabels = self._decompileUInt16Array(
|
||||
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:
|
||||
return [0] * numElements
|
||||
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):
|
||||
def _decompileUInt32Array(self, data, offset, numElements, default=0):
|
||||
if offset == 0:
|
||||
return [0] * numElements
|
||||
return [default] * numElements
|
||||
result = array.array("I", data[offset : offset + 4 * numElements])
|
||||
if sys.byteorder != "big": result.byteswap()
|
||||
assert len(result) == numElements, result
|
||||
@ -136,7 +140,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
return result
|
||||
|
||||
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''
|
||||
assert len(self.paletteLabels) == len(self.palettes)
|
||||
result = bytesjoin([struct.pack(">H", label)
|
||||
@ -145,7 +149,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
return result
|
||||
|
||||
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''
|
||||
assert len(self.paletteEntryLabels) == self.numPaletteEntries
|
||||
result = bytesjoin([struct.pack(">H", label)
|
||||
@ -165,15 +169,15 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
writer.newline()
|
||||
for index, palette in enumerate(self.palettes):
|
||||
attrs = {"index": index}
|
||||
paletteType = paletteTypes.get(index)
|
||||
paletteLabel = paletteLabels.get(index)
|
||||
if self.version > 0 and paletteLabel is not None:
|
||||
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 is not None:
|
||||
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 and
|
||||
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:
|
||||
@ -184,11 +188,11 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
color.toXML(writer, ttFont, cindex)
|
||||
writer.endtag("palette")
|
||||
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.newline()
|
||||
for index, label in enumerate(self.paletteEntryLabels):
|
||||
if label:
|
||||
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)
|
||||
@ -200,8 +204,8 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
|
||||
def fromXML(self, name, attrs, content, ttFont):
|
||||
if name == "palette":
|
||||
self.paletteLabels.append(int(attrs.get("label", "0")))
|
||||
self.paletteTypes.append(int(attrs.get("type", "0")))
|
||||
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, basestring):
|
||||
@ -221,13 +225,13 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
|
||||
nameID = safeEval(elementAttr["value"])
|
||||
colorLabels[labelIndex] = nameID
|
||||
self.paletteEntryLabels = [
|
||||
colorLabels.get(i, 0)
|
||||
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 = [0] * self.numPaletteEntries
|
||||
self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
|
||||
|
||||
|
||||
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(repr(cpal.palettes),
|
||||
'[[#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):
|
||||
cpal = newTable('CPAL')
|
||||
@ -80,9 +77,6 @@ class CPALTest(unittest.TestCase):
|
||||
'[#223344FF, #99887711, #55555555]',
|
||||
'[#223344FF, #99887711, #FFFFFFFF]',
|
||||
'[#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):
|
||||
cpal = newTable('CPAL')
|
||||
@ -92,9 +86,10 @@ class CPALTest(unittest.TestCase):
|
||||
self.assertEqual([repr(p) for p in cpal.palettes], [
|
||||
'[#CAFECAFE, #22110033, #66554477]', # RGBA
|
||||
'[#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.paletteEntryLabels, [0, 0, 0])
|
||||
self.assertEqual(cpal.paletteEntryLabels,
|
||||
[cpal.NO_NAME_ID] * cpal.numPaletteEntries)
|
||||
|
||||
def test_decompile_v1(self):
|
||||
cpal = newTable('CPAL')
|
||||
@ -194,9 +189,6 @@ class CPALTest(unittest.TestCase):
|
||||
self.assertEqual(cpal.version, 0)
|
||||
self.assertEqual(cpal.numPaletteEntries, 2)
|
||||
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):
|
||||
cpal = newTable('CPAL')
|
||||
@ -218,7 +210,8 @@ class CPALTest(unittest.TestCase):
|
||||
'[[#12345678, #FEDCBA98, #CAFECAFE]]')
|
||||
self.assertEqual(cpal.paletteLabels, [259])
|
||||
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__":
|
||||
|
Loading…
x
Reference in New Issue
Block a user