colorLib: allow to build Paint, ColorLine, Color from dict
This commit is contained in:
parent
0149f40588
commit
5629d5d8d9
@ -1,7 +1,8 @@
|
||||
import collections
|
||||
import copy
|
||||
import enum
|
||||
from functools import partial
|
||||
from typing import Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Union
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Union
|
||||
from fontTools.ttLib.tables import C_O_L_R_
|
||||
from fontTools.ttLib.tables import C_P_A_L_
|
||||
from fontTools.ttLib.tables import _n_a_m_e
|
||||
@ -16,17 +17,22 @@ from .errors import ColorLibError
|
||||
|
||||
|
||||
# TODO move type aliases to colorLib.types?
|
||||
_LayerTuple = Tuple[str, Union[int, ot.Paint]]
|
||||
_Kwargs = Mapping[str, Any]
|
||||
_PaintInput = Union[int, _Kwargs, ot.Paint]
|
||||
_LayerTuple = Tuple[str, _PaintInput]
|
||||
_LayersList = Sequence[_LayerTuple]
|
||||
_ColorGlyphsDict = Dict[str, _LayersList]
|
||||
_ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
|
||||
_Number = Union[int, float]
|
||||
_VariableScalar = Union[_Number, VariableValue, Tuple[_Number, int]]
|
||||
_ColorTuple = Tuple[int, VariableValue]
|
||||
_ColorStopTuple = Tuple[_VariableScalar, Union[int, _ColorTuple, ot.Color]]
|
||||
_ScalarInput = Union[_Number, VariableValue, Tuple[_Number, int]]
|
||||
_ColorInput = Union[int, _Kwargs, ot.Color]
|
||||
_ColorStopTuple = Tuple[_ScalarInput, _ColorInput]
|
||||
_ColorStopsList = Sequence[Union[_ColorStopTuple, ot.ColorStop]]
|
||||
_PointTuple = Tuple[_VariableScalar, _VariableScalar]
|
||||
_AffineTuple = Tuple[_VariableScalar, _VariableScalar, _VariableScalar, _VariableScalar]
|
||||
_ColorLineInput = Union[_Kwargs, ot.ColorLine]
|
||||
_PointTuple = Tuple[_ScalarInput, _ScalarInput]
|
||||
_PointInput = Union[_PointTuple, ot.Point]
|
||||
_AffineTuple = Tuple[_ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput]
|
||||
_AffineInput = Union[_AffineTuple, ot.Affine2x2]
|
||||
|
||||
|
||||
def populateCOLRv0(
|
||||
@ -43,9 +49,12 @@ def populateCOLRv0(
|
||||
glyphMap: a map from glyph names to glyph indices, as returned from
|
||||
TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
|
||||
"""
|
||||
colorGlyphItems = colorGlyphsV0.items()
|
||||
if glyphMap:
|
||||
colorGlyphItems = sorted(colorGlyphItems, key=lambda item: glyphMap[item[0]])
|
||||
if glyphMap is not None:
|
||||
colorGlyphItems = sorted(
|
||||
colorGlyphsV0.items(), key=lambda item: glyphMap[item[0]]
|
||||
)
|
||||
else:
|
||||
colorGlyphItems = colorGlyphsV0.items()
|
||||
baseGlyphRecords = []
|
||||
layerRecords = []
|
||||
for baseGlyph, layers in colorGlyphItems:
|
||||
@ -301,7 +310,7 @@ def _splitSolidAndGradientGlyphs(
|
||||
_DEFAULT_TRANSPARENCY = VariableFloat(0.0)
|
||||
|
||||
|
||||
def _to_variable_value(value: _VariableScalar, cls=VariableValue) -> VariableValue:
|
||||
def _to_variable_value(value: _ScalarInput, cls=VariableValue) -> VariableValue:
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
try:
|
||||
@ -317,7 +326,7 @@ _to_variable_int = partial(_to_variable_value, cls=VariableInt)
|
||||
|
||||
|
||||
def buildColor(
|
||||
paletteIndex: int, transparency: _VariableScalar = _DEFAULT_TRANSPARENCY
|
||||
paletteIndex: int, transparency: _ScalarInput = _DEFAULT_TRANSPARENCY
|
||||
) -> ot.Color:
|
||||
self = ot.Color()
|
||||
self.PaletteIndex = int(paletteIndex)
|
||||
@ -326,7 +335,7 @@ def buildColor(
|
||||
|
||||
|
||||
def buildSolidColorPaint(
|
||||
paletteIndex: int, transparency: _VariableScalar = _DEFAULT_TRANSPARENCY
|
||||
paletteIndex: int, transparency: _ScalarInput = _DEFAULT_TRANSPARENCY
|
||||
) -> ot.Paint:
|
||||
self = ot.Paint()
|
||||
self.Format = 1
|
||||
@ -334,37 +343,46 @@ def buildSolidColorPaint(
|
||||
return self
|
||||
|
||||
|
||||
def buildColorStop(
|
||||
offset: _VariableScalar, color: Union[int, _ColorTuple, ot.Color]
|
||||
) -> ot.ColorStop:
|
||||
def buildColorStop(offset: _ScalarInput, color: _ColorInput) -> ot.ColorStop:
|
||||
self = ot.ColorStop()
|
||||
self.StopOffset = _to_variable_float(offset)
|
||||
|
||||
if isinstance(color, int):
|
||||
color = buildColor(paletteIndex=color)
|
||||
elif not isinstance(color, ot.Color):
|
||||
color = buildColor(*color)
|
||||
color = buildColor(**color)
|
||||
self.Color = color
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def _to_extend_mode(v):
|
||||
if isinstance(v, ExtendMode):
|
||||
return v
|
||||
elif isinstance(v, str):
|
||||
try:
|
||||
return getattr(ExtendMode, v.upper())
|
||||
except AttributeError:
|
||||
raise ValueError(f"{v!r} is not a valid ExtendMode")
|
||||
return ExtendMode(v)
|
||||
|
||||
|
||||
def buildColorLine(
|
||||
colorStops: _ColorStopsList, extend: ExtendMode = ExtendMode.PAD
|
||||
stops: _ColorStopsList, extend: ExtendMode = ExtendMode.PAD
|
||||
) -> ot.ColorLine:
|
||||
self = ot.ColorLine()
|
||||
self.Extend = ExtendMode(extend)
|
||||
self.StopCount = len(colorStops)
|
||||
self.Extend = _to_extend_mode(extend)
|
||||
self.StopCount = len(stops)
|
||||
self.ColorStop = [
|
||||
stop
|
||||
if isinstance(stop, ot.ColorStop)
|
||||
else buildColorStop(offset=stop[0], color=stop[1])
|
||||
for stop in colorStops
|
||||
for stop in stops
|
||||
]
|
||||
return self
|
||||
|
||||
|
||||
def buildPoint(x: _VariableScalar, y: _VariableScalar) -> ot.Point:
|
||||
def buildPoint(x: _ScalarInput, y: _ScalarInput) -> ot.Point:
|
||||
self = ot.Point()
|
||||
# positions are encoded as Int16 so round to int
|
||||
self.x = _to_variable_int(x)
|
||||
@ -372,7 +390,7 @@ def buildPoint(x: _VariableScalar, y: _VariableScalar) -> ot.Point:
|
||||
return self
|
||||
|
||||
|
||||
def _to_variable_point(pt: Union[_PointTuple, ot.Point]) -> ot.Point:
|
||||
def _to_variable_point(pt: _PointInput) -> ot.Point:
|
||||
if isinstance(pt, ot.Point):
|
||||
return pt
|
||||
if isinstance(pt, tuple):
|
||||
@ -380,18 +398,23 @@ def _to_variable_point(pt: Union[_PointTuple, ot.Point]) -> ot.Point:
|
||||
raise TypeError(pt)
|
||||
|
||||
|
||||
def _to_color_line(obj):
|
||||
if isinstance(obj, ot.ColorLine):
|
||||
return obj
|
||||
elif isinstance(obj, collections.abc.Mapping):
|
||||
return buildColorLine(**obj)
|
||||
raise TypeError(obj)
|
||||
|
||||
|
||||
def buildLinearGradientPaint(
|
||||
colorLine: Union[_ColorStopsList, ot.ColorLine],
|
||||
p0: Union[_PointTuple, ot.Point],
|
||||
p1: Union[_PointTuple, ot.Point],
|
||||
p2: Optional[Union[_PointTuple, ot.Point]] = None,
|
||||
colorLine: _ColorLineInput,
|
||||
p0: _PointInput,
|
||||
p1: _PointInput,
|
||||
p2: Optional[_PointInput] = None,
|
||||
) -> ot.Paint:
|
||||
self = ot.Paint()
|
||||
self.Format = 2
|
||||
|
||||
if not isinstance(colorLine, ot.ColorLine):
|
||||
colorLine = buildColorLine(colorStops=colorLine)
|
||||
self.ColorLine = colorLine
|
||||
self.ColorLine = _to_color_line(colorLine)
|
||||
|
||||
if p2 is None:
|
||||
p2 = copy.copy(p1)
|
||||
@ -402,7 +425,7 @@ def buildLinearGradientPaint(
|
||||
|
||||
|
||||
def buildAffine2x2(
|
||||
xx: _VariableScalar, xy: _VariableScalar, yx: _VariableScalar, yy: _VariableScalar
|
||||
xx: _ScalarInput, xy: _ScalarInput, yx: _ScalarInput, yy: _ScalarInput
|
||||
) -> ot.Affine2x2:
|
||||
self = ot.Affine2x2()
|
||||
locs = locals()
|
||||
@ -413,20 +436,17 @@ def buildAffine2x2(
|
||||
|
||||
|
||||
def buildRadialGradientPaint(
|
||||
colorLine: Union[_ColorStopsList, ot.ColorLine],
|
||||
c0: Union[_PointTuple, ot.Point],
|
||||
c1: Union[_PointTuple, ot.Point],
|
||||
r0: _VariableScalar,
|
||||
r1: _VariableScalar,
|
||||
affine: Optional[Union[_AffineTuple, ot.Affine2x2]] = None,
|
||||
colorLine: _ColorLineInput,
|
||||
c0: _PointInput,
|
||||
c1: _PointInput,
|
||||
r0: _ScalarInput,
|
||||
r1: _ScalarInput,
|
||||
affine: Optional[_AffineInput] = None,
|
||||
) -> ot.Paint:
|
||||
|
||||
self = ot.Paint()
|
||||
self.Format = 3
|
||||
|
||||
if not isinstance(colorLine, ot.ColorLine):
|
||||
colorLine = buildColorLine(colorStops=colorLine)
|
||||
self.ColorLine = colorLine
|
||||
self.ColorLine = _to_color_line(colorLine)
|
||||
|
||||
for i, pt in [(0, c0), (1, c1)]:
|
||||
setattr(self, f"c{i}", _to_variable_point(pt))
|
||||
@ -442,15 +462,21 @@ def buildRadialGradientPaint(
|
||||
return self
|
||||
|
||||
|
||||
def buildLayerV1Record(
|
||||
layerGlyph: str, paint: Union[int, ot.Paint]
|
||||
) -> ot.LayerV1Record:
|
||||
def _to_ot_paint(paint: _PaintInput) -> ot.Paint:
|
||||
if isinstance(paint, ot.Paint):
|
||||
return paint
|
||||
elif isinstance(paint, int):
|
||||
paletteIndex = paint
|
||||
return buildSolidColorPaint(paletteIndex)
|
||||
elif isinstance(paint, collections.abc.Mapping):
|
||||
return buildPaint(**paint)
|
||||
raise TypeError(f"expected int, Mapping or ot.Paint, found {type(paint.__name__)}")
|
||||
|
||||
|
||||
def buildLayerV1Record(layerGlyph: str, paint: _PaintInput) -> ot.LayerV1Record:
|
||||
self = ot.LayerV1Record()
|
||||
self.LayerGlyph = layerGlyph
|
||||
if isinstance(paint, int):
|
||||
paletteIndex = paint
|
||||
paint = buildSolidColorPaint(paletteIndex)
|
||||
self.Paint = paint
|
||||
self.Paint = _to_ot_paint(paint)
|
||||
return self
|
||||
|
||||
|
||||
@ -486,9 +512,12 @@ def buildBaseGlyphV1Array(
|
||||
colorGlyphs: Union[_ColorGlyphsDict, Dict[str, ot.LayerV1Array]],
|
||||
glyphMap: Optional[Mapping[str, int]] = None,
|
||||
) -> ot.BaseGlyphV1Array:
|
||||
colorGlyphItems = colorGlyphs.items()
|
||||
if glyphMap:
|
||||
colorGlyphItems = sorted(colorGlyphItems, key=lambda item: glyphMap[item[0]])
|
||||
if glyphMap is not None:
|
||||
colorGlyphItems = sorted(
|
||||
colorGlyphs.items(), key=lambda item: glyphMap[item[0]]
|
||||
)
|
||||
else:
|
||||
colorGlyphItems = colorGlyphs.items()
|
||||
records = [
|
||||
buildBaseGlyphV1Record(baseGlyph, layers)
|
||||
for baseGlyph, layers in colorGlyphItems
|
||||
@ -497,3 +526,17 @@ def buildBaseGlyphV1Array(
|
||||
self.BaseGlyphCount = len(records)
|
||||
self.BaseGlyphV1Record = records
|
||||
return self
|
||||
|
||||
|
||||
_PAINT_BUILDERS = {
|
||||
1: buildSolidColorPaint,
|
||||
2: buildLinearGradientPaint,
|
||||
3: buildRadialGradientPaint,
|
||||
}
|
||||
|
||||
|
||||
def buildPaint(format: int, **kwargs) -> ot.Paint:
|
||||
try:
|
||||
return _PAINT_BUILDERS[format](**kwargs)
|
||||
except KeyError:
|
||||
raise NotImplementedError(format)
|
||||
|
@ -258,6 +258,9 @@ def test_buildColorLine():
|
||||
(cs.StopOffset.value, cs.Color.PaletteIndex) for cs in cline.ColorStop
|
||||
] == stops
|
||||
|
||||
cline = builder.buildColorLine(stops, extend="pad")
|
||||
assert cline.Extend == builder.ExtendMode.PAD
|
||||
|
||||
cline = builder.buildColorLine(stops, extend=builder.ExtendMode.REPEAT)
|
||||
assert cline.Extend == builder.ExtendMode.REPEAT
|
||||
|
||||
@ -271,10 +274,19 @@ def test_buildColorLine():
|
||||
(cs.StopOffset.value, cs.Color.PaletteIndex) for cs in cline.ColorStop
|
||||
] == stops
|
||||
|
||||
stops = [((0.0, 1), (0, (0.5, 2))), ((1.0, 3), (1, (0.3, 4)))]
|
||||
stops = [
|
||||
((0.0, 1), {"paletteIndex": 0, "transparency": (0.5, 2)}),
|
||||
((1.0, 3), {"paletteIndex": 1, "transparency": (0.3, 4)}),
|
||||
]
|
||||
cline = builder.buildColorLine(stops)
|
||||
assert [
|
||||
(tuple(cs.StopOffset), (cs.Color.PaletteIndex, tuple(cs.Color.Transparency)))
|
||||
(
|
||||
cs.StopOffset,
|
||||
{
|
||||
"paletteIndex": cs.Color.PaletteIndex,
|
||||
"transparency": cs.Color.Transparency,
|
||||
},
|
||||
)
|
||||
for cs in cline.ColorStop
|
||||
] == stops
|
||||
|
||||
@ -327,7 +339,7 @@ def test_buildLinearGradientPaint():
|
||||
assert gradient.p2 == gradient.p1
|
||||
assert gradient.p2 is not gradient.p1
|
||||
|
||||
gradient = builder.buildLinearGradientPaint(color_stops, p0, p1)
|
||||
gradient = builder.buildLinearGradientPaint({"stops": color_stops}, p0, p1)
|
||||
assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
|
||||
assert gradient.ColorLine.ColorStop == color_stops
|
||||
|
||||
@ -357,18 +369,18 @@ def test_buildRadialGradientPaint():
|
||||
assert gradient.r1 == r1
|
||||
assert gradient.Affine is None
|
||||
|
||||
gradient = builder.buildRadialGradientPaint(color_stops, c0, c1, r0, r1)
|
||||
gradient = builder.buildRadialGradientPaint({"stops": color_stops}, c0, c1, r0, r1)
|
||||
assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
|
||||
assert gradient.ColorLine.ColorStop == color_stops
|
||||
|
||||
matrix = builder.buildAffine2x2(2.0, 0.0, 0.0, 2.0)
|
||||
gradient = builder.buildRadialGradientPaint(
|
||||
color_stops, c0, c1, r0, r1, affine=matrix
|
||||
color_line, c0, c1, r0, r1, affine=matrix
|
||||
)
|
||||
assert gradient.Affine == matrix
|
||||
|
||||
gradient = builder.buildRadialGradientPaint(
|
||||
color_stops, c0, c1, r0, r1, affine=(2.0, 0.0, 0.0, 2.0)
|
||||
color_line, c0, c1, r0, r1, affine=(2.0, 0.0, 0.0, 2.0)
|
||||
)
|
||||
assert gradient.Affine == matrix
|
||||
|
||||
@ -386,7 +398,9 @@ def test_buildLayerV1Record():
|
||||
|
||||
layer = builder.buildLayerV1Record(
|
||||
"a",
|
||||
builder.buildLinearGradientPaint([(0.0, 3), (1.0, 4)], (100, 200), (150, 250)),
|
||||
builder.buildLinearGradientPaint(
|
||||
{"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250)
|
||||
),
|
||||
)
|
||||
assert layer.Paint.Format == 2
|
||||
assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
|
||||
@ -401,7 +415,17 @@ def test_buildLayerV1Record():
|
||||
layer = builder.buildLayerV1Record(
|
||||
"a",
|
||||
builder.buildRadialGradientPaint(
|
||||
[(0.0, 5), (0.5, (6, 0.8)), (1.0, 7)], (50, 50), (75, 75), 30, 10
|
||||
{
|
||||
"stops": [
|
||||
(0.0, 5),
|
||||
(0.5, {"paletteIndex": 6, "transparency": 0.8}),
|
||||
(1.0, 7),
|
||||
]
|
||||
},
|
||||
(50, 50),
|
||||
(75, 75),
|
||||
30,
|
||||
10,
|
||||
),
|
||||
)
|
||||
assert layer.Paint.Format == 3
|
||||
@ -420,25 +444,71 @@ def test_buildLayerV1Record():
|
||||
assert layer.Paint.r1.value == 10
|
||||
|
||||
|
||||
def test_buildLayerV1Record_from_dict():
|
||||
layer = builder.buildLayerV1Record("a", {"format": 1, "paletteIndex": 0})
|
||||
assert layer.LayerGlyph == "a"
|
||||
assert layer.Paint.Format == 1
|
||||
assert layer.Paint.Color.PaletteIndex == 0
|
||||
|
||||
layer = builder.buildLayerV1Record(
|
||||
"a",
|
||||
{
|
||||
"format": 2,
|
||||
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
|
||||
"p0": (0, 0),
|
||||
"p1": (10, 10),
|
||||
},
|
||||
)
|
||||
assert layer.Paint.Format == 2
|
||||
assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
|
||||
|
||||
layer = builder.buildLayerV1Record(
|
||||
"a",
|
||||
{
|
||||
"format": 3,
|
||||
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
|
||||
"c0": (0, 0),
|
||||
"c1": (10, 10),
|
||||
"r0": 4,
|
||||
"r1": 0,
|
||||
},
|
||||
)
|
||||
assert layer.Paint.Format == 3
|
||||
assert layer.Paint.r0.value == 4
|
||||
|
||||
|
||||
def test_buildLayerV1Array():
|
||||
layers = [
|
||||
("a", 1),
|
||||
("b", builder.buildSolidColorPaint(2, 0.5)),
|
||||
("b", {"format": 1, "paletteIndex": 2, "transparency": 0.5}),
|
||||
(
|
||||
"c",
|
||||
builder.buildLinearGradientPaint(
|
||||
[(0.0, 3), (1.0, 4)], (100, 200), (150, 250)
|
||||
),
|
||||
{
|
||||
"format": 2,
|
||||
"colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "repeat"},
|
||||
"p0": (100, 200),
|
||||
"p1": (150, 250),
|
||||
},
|
||||
),
|
||||
(
|
||||
"d",
|
||||
builder.buildRadialGradientPaint(
|
||||
[(0.0, 5), (0.5, (6, 0.8)), (1.0, 7)], (50, 50), (75, 75), 30, 10
|
||||
),
|
||||
{
|
||||
"format": 3,
|
||||
"colorLine": {
|
||||
"stops": [
|
||||
(0.0, 5),
|
||||
(0.5, {"paletteIndex": 6, "transparency": 0.8}),
|
||||
(1.0, 7),
|
||||
]
|
||||
},
|
||||
"c0": (50, 50),
|
||||
"c1": (75, 75),
|
||||
"r0": 30,
|
||||
"r1": 10,
|
||||
},
|
||||
),
|
||||
builder.buildLayerV1Record("e", builder.buildSolidColorPaint(8)),
|
||||
]
|
||||
|
||||
layersArray = builder.buildLayerV1Array(layers)
|
||||
|
||||
assert layersArray.LayerCount == len(layersArray.LayerV1Record)
|
||||
@ -460,12 +530,17 @@ def test_buildBaseGlyphV1Array():
|
||||
colorGlyphs = {
|
||||
"a": [("b", 0), ("c", 1)],
|
||||
"d": [
|
||||
("e", builder.buildSolidColorPaint(2, transparency=0.8)),
|
||||
("e", {"format": 1, "paletteIndex": 2, "transparency": 0.8}),
|
||||
(
|
||||
"f",
|
||||
builder.buildRadialGradientPaint(
|
||||
[(0.0, 3), (1.0, 4)], (0, 0), (0, 0), 10, 0
|
||||
),
|
||||
{
|
||||
"format": 3,
|
||||
"colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "reflect"},
|
||||
"c0": (0, 0),
|
||||
"c1": (0, 0),
|
||||
"r0": 10,
|
||||
"r1": 0,
|
||||
},
|
||||
),
|
||||
],
|
||||
"g": builder.buildLayerV1Array([("h", 5)]),
|
||||
|
Loading…
x
Reference in New Issue
Block a user