2020-02-17 18:25:45 +00:00
|
|
|
import enum
|
|
|
|
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
2020-02-17 12:11:32 +00:00
|
|
|
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_
|
2020-02-17 18:25:45 +00:00
|
|
|
from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e
|
2020-02-10 17:31:14 +00:00
|
|
|
from .errors import ColorLibError
|
|
|
|
|
|
|
|
|
2020-02-17 12:11:32 +00:00
|
|
|
def buildCOLR(colorLayers: Dict[str, List[Tuple[str, int]]]) -> table_C_O_L_R_:
|
2020-02-10 17:31:14 +00:00
|
|
|
"""Build COLR table from color layers mapping.
|
|
|
|
|
|
|
|
Args:
|
2020-02-17 12:11:32 +00:00
|
|
|
colorLayers: : map of base glyph names to lists of (layer glyph names,
|
|
|
|
palette indices) tuples.
|
2020-02-10 17:31:14 +00:00
|
|
|
|
|
|
|
Return:
|
|
|
|
A new COLRv0 table.
|
|
|
|
"""
|
|
|
|
colorLayerLists = {}
|
|
|
|
for baseGlyphName, layers in colorLayers.items():
|
|
|
|
colorLayerLists[baseGlyphName] = [
|
|
|
|
LayerRecord(layerGlyphName, colorID) for layerGlyphName, colorID in layers
|
|
|
|
]
|
|
|
|
|
2020-02-17 12:11:32 +00:00
|
|
|
colr = table_C_O_L_R_()
|
2020-02-10 17:31:14 +00:00
|
|
|
colr.version = 0
|
|
|
|
colr.ColorLayers = colorLayerLists
|
|
|
|
return colr
|
|
|
|
|
|
|
|
|
2020-02-17 18:25:45 +00:00
|
|
|
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
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2020-02-17 12:11:32 +00:00
|
|
|
def buildCPAL(
|
2020-02-17 18:25:45 +00:00
|
|
|
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,
|
2020-02-17 12:11:32 +00:00
|
|
|
) -> table_C_P_A_L_:
|
2020-02-10 17:31:14 +00:00
|
|
|
"""Build CPAL table from list of color palettes.
|
|
|
|
|
|
|
|
Args:
|
2020-02-17 18:25:45 +00:00
|
|
|
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.
|
2020-02-10 17:31:14 +00:00
|
|
|
|
|
|
|
Return:
|
2020-02-17 18:25:45 +00:00
|
|
|
A new CPAL v0 or v1 table, if custom palette types or labels are specified.
|
2020-02-10 17:31:14 +00:00
|
|
|
"""
|
|
|
|
if len({len(p) for p in palettes}) != 1:
|
|
|
|
raise ColorLibError("color palettes have different lengths")
|
2020-02-17 18:25:45 +00:00
|
|
|
|
|
|
|
if (paletteLabels or paletteEntryLabels) and not nameTable:
|
|
|
|
raise TypeError(
|
|
|
|
"nameTable is required if palette or palette entries have labels"
|
|
|
|
)
|
|
|
|
|
2020-02-17 12:11:32 +00:00
|
|
|
cpal = table_C_P_A_L_()
|
2020-02-10 17:31:14 +00:00
|
|
|
cpal.numPaletteEntries = len(palettes[0])
|
2020-02-17 18:25:45 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-02-10 17:31:14 +00:00
|
|
|
return cpal
|