colorLib: add generic TableUnbuilder, reverse of TableBuilder
This commit is contained in:
parent
cf4a4087be
commit
e542b60dde
@ -13,6 +13,7 @@ from fontTools.ttLib.tables.otBase import (
|
||||
from fontTools.ttLib.tables.otConverters import (
|
||||
ComputedInt,
|
||||
GlyphID,
|
||||
SimpleValue,
|
||||
Struct,
|
||||
Short,
|
||||
UInt8,
|
||||
@ -192,3 +193,48 @@ class TableBuilder:
|
||||
)(dest)
|
||||
|
||||
return dest
|
||||
|
||||
|
||||
class TableUnbuilder:
|
||||
def __init__(self, callbackTable=None):
|
||||
if callbackTable is None:
|
||||
callbackTable = {}
|
||||
self._callbackTable = callbackTable
|
||||
|
||||
def unbuild(self, table):
|
||||
assert isinstance(table, BaseTable)
|
||||
|
||||
source = {}
|
||||
|
||||
callbackKey = (type(table),)
|
||||
if isinstance(table, FormatSwitchingBaseTable):
|
||||
source["Format"] = int(table.Format)
|
||||
callbackKey += (table.Format,)
|
||||
|
||||
for converter in table.getConverters():
|
||||
if isinstance(converter, ComputedInt):
|
||||
continue
|
||||
value = getattr(table, converter.name)
|
||||
|
||||
tupleClass = getattr(converter, "tupleClass", None)
|
||||
enumClass = getattr(converter, "enumClass", None)
|
||||
if tupleClass:
|
||||
source[converter.name] = tuple(value)
|
||||
elif enumClass:
|
||||
source[converter.name] = value.name.lower()
|
||||
elif isinstance(converter, Struct):
|
||||
if converter.repeat:
|
||||
source[converter.name] = [self.unbuild(v) for v in value]
|
||||
else:
|
||||
source[converter.name] = self.unbuild(value)
|
||||
elif isinstance(converter, SimpleValue):
|
||||
# "simple" values (e.g. int, float, str) need no further un-building
|
||||
source[converter.name] = value
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Don't know how unbuild {value!r} with {converter!r}"
|
||||
)
|
||||
|
||||
source = self._callbackTable.get(callbackKey, lambda s: s)(source)
|
||||
|
||||
return source
|
||||
|
@ -1,47 +1,15 @@
|
||||
from fontTools.ttLib.tables import otTables as ot
|
||||
from .table_builder import TableUnbuilder
|
||||
|
||||
|
||||
def unbuildColrV1(layerV1List, baseGlyphV1List, ignoreVarIdx=False):
|
||||
unbuilder = LayerV1ListUnbuilder(layerV1List.Paint, ignoreVarIdx=ignoreVarIdx)
|
||||
def unbuildColrV1(layerV1List, baseGlyphV1List):
|
||||
unbuilder = LayerV1ListUnbuilder(layerV1List.Paint)
|
||||
return {
|
||||
rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint)
|
||||
for rec in baseGlyphV1List.BaseGlyphV1Record
|
||||
}
|
||||
|
||||
|
||||
def _unbuildVariableValue(v, ignoreVarIdx=False):
|
||||
return v.value if ignoreVarIdx else (v.value, v.varIdx)
|
||||
|
||||
|
||||
def unbuildColorStop(colorStop, ignoreVarIdx=False):
|
||||
return {
|
||||
"offset": _unbuildVariableValue(
|
||||
colorStop.StopOffset, ignoreVarIdx=ignoreVarIdx
|
||||
),
|
||||
"paletteIndex": colorStop.Color.PaletteIndex,
|
||||
"alpha": _unbuildVariableValue(
|
||||
colorStop.Color.Alpha, ignoreVarIdx=ignoreVarIdx
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def unbuildColorLine(colorLine, ignoreVarIdx=False):
|
||||
return {
|
||||
"stops": [
|
||||
unbuildColorStop(stop, ignoreVarIdx=ignoreVarIdx)
|
||||
for stop in colorLine.ColorStop
|
||||
],
|
||||
"extend": colorLine.Extend.name.lower(),
|
||||
}
|
||||
|
||||
|
||||
def unbuildAffine2x3(transform, ignoreVarIdx=False):
|
||||
return tuple(
|
||||
_unbuildVariableValue(getattr(transform, attr), ignoreVarIdx=ignoreVarIdx)
|
||||
for attr in ("xx", "yx", "xy", "yy", "dx", "dy")
|
||||
)
|
||||
|
||||
|
||||
def _flatten(lst):
|
||||
for el in lst:
|
||||
if isinstance(el, list):
|
||||
@ -51,142 +19,40 @@ def _flatten(lst):
|
||||
|
||||
|
||||
class LayerV1ListUnbuilder:
|
||||
def __init__(self, layers, ignoreVarIdx=False):
|
||||
def __init__(self, layers):
|
||||
self.layers = layers
|
||||
self.ignoreVarIdx = ignoreVarIdx
|
||||
|
||||
callbacks = {
|
||||
(
|
||||
ot.Paint,
|
||||
ot.PaintFormat.PaintColrLayers,
|
||||
): self._unbuildPaintColrLayers,
|
||||
}
|
||||
self.tableUnbuilder = TableUnbuilder(callbacks)
|
||||
|
||||
def unbuildPaint(self, paint):
|
||||
try:
|
||||
return self._unbuildFunctions[paint.Format](self, paint)
|
||||
except KeyError:
|
||||
raise ValueError(f"Unrecognized paint format: {paint.Format}")
|
||||
assert isinstance(paint, ot.Paint)
|
||||
return self.tableUnbuilder.unbuild(paint)
|
||||
|
||||
def unbuildVariableValue(self, value):
|
||||
return _unbuildVariableValue(value, ignoreVarIdx=self.ignoreVarIdx)
|
||||
def _unbuildPaintColrLayers(self, source):
|
||||
assert source["Format"] == ot.PaintFormat.PaintColrLayers
|
||||
|
||||
def unbuildPaintColrLayers(self, paint):
|
||||
return list(
|
||||
layers = list(
|
||||
_flatten(
|
||||
[
|
||||
self.unbuildPaint(childPaint)
|
||||
for childPaint in self.layers[
|
||||
paint.FirstLayerIndex : paint.FirstLayerIndex + paint.NumLayers
|
||||
source["FirstLayerIndex"] : source["FirstLayerIndex"]
|
||||
+ source["NumLayers"]
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def unbuildPaintSolid(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"paletteIndex": paint.Color.PaletteIndex,
|
||||
"alpha": self.unbuildVariableValue(paint.Color.Alpha),
|
||||
}
|
||||
if len(layers) == 1:
|
||||
return layers[0]
|
||||
|
||||
def unbuildPaintLinearGradient(self, paint):
|
||||
p0 = (self.unbuildVariableValue(paint.x0), self.unbuildVariableValue(paint.y0))
|
||||
p1 = (self.unbuildVariableValue(paint.x1), self.unbuildVariableValue(paint.y1))
|
||||
p2 = (self.unbuildVariableValue(paint.x2), self.unbuildVariableValue(paint.y2))
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"colorLine": unbuildColorLine(
|
||||
paint.ColorLine, ignoreVarIdx=self.ignoreVarIdx
|
||||
),
|
||||
"p0": p0,
|
||||
"p1": p1,
|
||||
"p2": p2,
|
||||
}
|
||||
|
||||
def unbuildPaintRadialGradient(self, paint):
|
||||
c0 = (self.unbuildVariableValue(paint.x0), self.unbuildVariableValue(paint.y0))
|
||||
r0 = self.unbuildVariableValue(paint.r0)
|
||||
c1 = (self.unbuildVariableValue(paint.x1), self.unbuildVariableValue(paint.y1))
|
||||
r1 = self.unbuildVariableValue(paint.r1)
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"colorLine": unbuildColorLine(
|
||||
paint.ColorLine, ignoreVarIdx=self.ignoreVarIdx
|
||||
),
|
||||
"c0": c0,
|
||||
"r0": r0,
|
||||
"c1": c1,
|
||||
"r1": r1,
|
||||
}
|
||||
|
||||
def unbuildPaintSweepGradient(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"colorLine": unbuildColorLine(
|
||||
paint.ColorLine, ignoreVarIdx=self.ignoreVarIdx
|
||||
),
|
||||
"centerX": self.unbuildVariableValue(paint.centerX),
|
||||
"centerY": self.unbuildVariableValue(paint.centerY),
|
||||
"startAngle": self.unbuildVariableValue(paint.startAngle),
|
||||
"endAngle": self.unbuildVariableValue(paint.endAngle),
|
||||
}
|
||||
|
||||
def unbuildPaintGlyph(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"glyph": paint.Glyph,
|
||||
"paint": self.unbuildPaint(paint.Paint),
|
||||
}
|
||||
|
||||
def unbuildPaintColrGlyph(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"glyph": paint.Glyph,
|
||||
}
|
||||
|
||||
def unbuildPaintTransform(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"transform": unbuildAffine2x3(
|
||||
paint.Transform, ignoreVarIdx=self.ignoreVarIdx
|
||||
),
|
||||
"paint": self.unbuildPaint(paint.Paint),
|
||||
}
|
||||
|
||||
def unbuildPaintTranslate(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"dx": self.unbuildVariableValue(paint.dx),
|
||||
"dy": self.unbuildVariableValue(paint.dy),
|
||||
"paint": self.unbuildPaint(paint.Paint),
|
||||
}
|
||||
|
||||
def unbuildPaintRotate(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"angle": self.unbuildVariableValue(paint.angle),
|
||||
"centerX": self.unbuildVariableValue(paint.centerX),
|
||||
"centerY": self.unbuildVariableValue(paint.centerY),
|
||||
"paint": self.unbuildPaint(paint.Paint),
|
||||
}
|
||||
|
||||
def unbuildPaintSkew(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"xSkewAngle": self.unbuildVariableValue(paint.xSkewAngle),
|
||||
"ySkewAngle": self.unbuildVariableValue(paint.ySkewAngle),
|
||||
"centerX": self.unbuildVariableValue(paint.centerX),
|
||||
"centerY": self.unbuildVariableValue(paint.centerY),
|
||||
"paint": self.unbuildPaint(paint.Paint),
|
||||
}
|
||||
|
||||
def unbuildPaintComposite(self, paint):
|
||||
return {
|
||||
"format": int(paint.Format),
|
||||
"mode": paint.CompositeMode.name.lower(),
|
||||
"source": self.unbuildPaint(paint.SourcePaint),
|
||||
"backdrop": self.unbuildPaint(paint.BackdropPaint),
|
||||
}
|
||||
|
||||
|
||||
LayerV1ListUnbuilder._unbuildFunctions = {
|
||||
pf.value: getattr(LayerV1ListUnbuilder, "unbuild" + pf.name)
|
||||
for pf in ot.PaintFormat
|
||||
}
|
||||
return {"Format": source["Format"], "Layers": layers}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -5,198 +5,206 @@ import pytest
|
||||
|
||||
|
||||
TEST_COLOR_GLYPHS = {
|
||||
"glyph00010": (
|
||||
ot.PaintFormat.PaintColrLayers,
|
||||
[
|
||||
"glyph00010": {
|
||||
"Format": int(ot.PaintFormat.PaintColrLayers),
|
||||
"Layers": [
|
||||
{
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00011",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintSolid),
|
||||
"Color": {
|
||||
"PaletteIndex": 2,
|
||||
"Alpha": 0.5,
|
||||
},
|
||||
"Color": {"PaletteIndex": 2, "Alpha": (0.5, 0)},
|
||||
},
|
||||
"Glyph": "glyph00011",
|
||||
},
|
||||
{
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00012",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintLinearGradient),
|
||||
"ColorLine": {
|
||||
"Extend": "repeat",
|
||||
"ColorStop": [
|
||||
{
|
||||
"StopOffset": 0.0,
|
||||
"Color": {"PaletteIndex": 3, "Alpha": 1.0},
|
||||
"StopOffset": (0.0, 0),
|
||||
"Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
|
||||
},
|
||||
{
|
||||
"StopOffset": 0.5,
|
||||
"Color": {"PaletteIndex": 4, "Alpha": 1.0},
|
||||
"StopOffset": (0.5, 0),
|
||||
"Color": {"PaletteIndex": 4, "Alpha": (1.0, 0)},
|
||||
},
|
||||
{
|
||||
"StopOffset": 1.0,
|
||||
"Color": {"PaletteIndex": 5, "Alpha": 1.0},
|
||||
"StopOffset": (1.0, 0),
|
||||
"Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
|
||||
},
|
||||
],
|
||||
"Extend": "repeat",
|
||||
},
|
||||
"x0": 1,
|
||||
"y0": 2,
|
||||
"x1": -3,
|
||||
"y1": -4,
|
||||
"x2": 5,
|
||||
"y2": 6,
|
||||
"x0": (1, 0),
|
||||
"y0": (2, 0),
|
||||
"x1": (-3, 0),
|
||||
"y1": (-4, 0),
|
||||
"x2": (5, 0),
|
||||
"y2": (6, 0),
|
||||
},
|
||||
"Glyph": "glyph00012",
|
||||
},
|
||||
{
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00013",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintTransform),
|
||||
"Transform": (-13.0, 14.0, 15.0, -17.0, 18.0, 19.0),
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintRadialGradient),
|
||||
"ColorLine": {
|
||||
"Extend": "pad",
|
||||
"ColorStop": [
|
||||
{
|
||||
"StopOffset": 0.0,
|
||||
"Color": {"PaletteIndex": 6, "Alpha": 1.0},
|
||||
"StopOffset": (0.0, 0),
|
||||
"Color": {"PaletteIndex": 6, "Alpha": (1.0, 0)},
|
||||
},
|
||||
{
|
||||
"StopOffset": 1.0,
|
||||
"Color": {
|
||||
"PaletteIndex": 7,
|
||||
"Alpha": 0.4,
|
||||
},
|
||||
"StopOffset": (1.0, 0),
|
||||
"Color": {"PaletteIndex": 7, "Alpha": (0.4, 0)},
|
||||
},
|
||||
],
|
||||
"Extend": "pad",
|
||||
},
|
||||
"x0": 7,
|
||||
"y0": 8,
|
||||
"r0": 9,
|
||||
"x1": 10,
|
||||
"y1": 11,
|
||||
"r1": 12,
|
||||
"x0": (7, 0),
|
||||
"y0": (8, 0),
|
||||
"r0": (9, 0),
|
||||
"x1": (10, 0),
|
||||
"y1": (11, 0),
|
||||
"r1": (12, 0),
|
||||
},
|
||||
"Transform": {
|
||||
"xx": (-13.0, 0),
|
||||
"yx": (14.0, 0),
|
||||
"xy": (15.0, 0),
|
||||
"yy": (-17.0, 0),
|
||||
"dx": (18.0, 0),
|
||||
"dy": (19.0, 0),
|
||||
},
|
||||
},
|
||||
"Glyph": "glyph00013",
|
||||
},
|
||||
{
|
||||
"Format": int(ot.PaintFormat.PaintTranslate),
|
||||
"dx": 257.0,
|
||||
"dy": 258.0,
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintRotate),
|
||||
"angle": 45.0,
|
||||
"centerX": 255.0,
|
||||
"centerY": 256.0,
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintSkew),
|
||||
"xSkewAngle": -11.0,
|
||||
"ySkewAngle": 5.0,
|
||||
"centerX": 253.0,
|
||||
"centerY": 254.0,
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00011",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintSolid),
|
||||
"Color": {
|
||||
"PaletteIndex": 2,
|
||||
"Alpha": 0.5,
|
||||
},
|
||||
"Color": {"PaletteIndex": 2, "Alpha": (0.5, 0)},
|
||||
},
|
||||
"Glyph": "glyph00011",
|
||||
},
|
||||
"xSkewAngle": (-11.0, 0),
|
||||
"ySkewAngle": (5.0, 0),
|
||||
"centerX": (253.0, 0),
|
||||
"centerY": (254.0, 0),
|
||||
},
|
||||
"angle": (45.0, 0),
|
||||
"centerX": (255.0, 0),
|
||||
"centerY": (256.0, 0),
|
||||
},
|
||||
"dx": (257.0, 0),
|
||||
"dy": (258.0, 0),
|
||||
},
|
||||
],
|
||||
),
|
||||
},
|
||||
"glyph00014": {
|
||||
"Format": int(ot.PaintFormat.PaintComposite),
|
||||
"CompositeMode": "src_over",
|
||||
"SourcePaint": {
|
||||
"Format": int(ot.PaintFormat.PaintColrGlyph),
|
||||
"Glyph": "glyph00010",
|
||||
},
|
||||
"CompositeMode": "src_over",
|
||||
"BackdropPaint": {
|
||||
"Format": int(ot.PaintFormat.PaintTransform),
|
||||
"Transform": (1.0, 0.0, 0.0, 1.0, 300.0, 0.0),
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintColrGlyph),
|
||||
"Glyph": "glyph00010",
|
||||
},
|
||||
"Transform": {
|
||||
"xx": (1.0, 0),
|
||||
"yx": (0.0, 0),
|
||||
"xy": (0.0, 0),
|
||||
"yy": (1.0, 0),
|
||||
"dx": (300.0, 0),
|
||||
"dy": (0.0, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
"glyph00015": {
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00011",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintSweepGradient),
|
||||
"ColorLine": {
|
||||
"ColorStop": [
|
||||
{"StopOffset": 0.0, "Color": {"PaletteIndex": 3, "Alpha": 1.0}},
|
||||
{"StopOffset": 1.0, "Color": {"PaletteIndex": 5, "Alpha": 1.0}},
|
||||
],
|
||||
"Extend": "pad",
|
||||
"ColorStop": [
|
||||
{
|
||||
"StopOffset": (0.0, 0),
|
||||
"Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
|
||||
},
|
||||
"centerX": 259,
|
||||
"centerY": 300,
|
||||
"startAngle": 45.0,
|
||||
"endAngle": 135.0,
|
||||
{
|
||||
"StopOffset": (1.0, 0),
|
||||
"Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
|
||||
},
|
||||
],
|
||||
},
|
||||
"glyph00016": (
|
||||
ot.PaintFormat.PaintColrLayers,
|
||||
[
|
||||
"centerX": (259, 0),
|
||||
"centerY": (300, 0),
|
||||
"startAngle": (45.0, 0),
|
||||
"endAngle": (135.0, 0),
|
||||
},
|
||||
"Glyph": "glyph00011",
|
||||
},
|
||||
"glyph00016": {
|
||||
"Format": int(ot.PaintFormat.PaintColrLayers),
|
||||
"Layers": [
|
||||
{
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00011",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintSolid),
|
||||
"Color": {
|
||||
"PaletteIndex": 2,
|
||||
"Alpha": 0.5,
|
||||
},
|
||||
"Color": {"PaletteIndex": 2, "Alpha": (0.5, 0)},
|
||||
},
|
||||
"Glyph": "glyph00011",
|
||||
},
|
||||
{
|
||||
"Format": int(ot.PaintFormat.PaintGlyph),
|
||||
"Glyph": "glyph00012",
|
||||
"Paint": {
|
||||
"Format": int(ot.PaintFormat.PaintLinearGradient),
|
||||
"ColorLine": {
|
||||
"Extend": "repeat",
|
||||
"ColorStop": [
|
||||
{
|
||||
"StopOffset": 0.0,
|
||||
"Color": {"PaletteIndex": 3, "Alpha": 1.0},
|
||||
"StopOffset": (0.0, 0),
|
||||
"Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
|
||||
},
|
||||
{
|
||||
"StopOffset": 0.5,
|
||||
"Color": {"PaletteIndex": 4, "Alpha": 1.0},
|
||||
"StopOffset": (0.5, 0),
|
||||
"Color": {"PaletteIndex": 4, "Alpha": (1.0, 0)},
|
||||
},
|
||||
{
|
||||
"StopOffset": 1.0,
|
||||
"Color": {"PaletteIndex": 5, "Alpha": 1.0},
|
||||
"StopOffset": (1.0, 0),
|
||||
"Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
|
||||
},
|
||||
],
|
||||
"Extend": "repeat",
|
||||
},
|
||||
"x0": 1,
|
||||
"y0": 2,
|
||||
"x1": -3,
|
||||
"y1": -4,
|
||||
"x2": 5,
|
||||
"y2": 6,
|
||||
"x0": (1, 0),
|
||||
"y0": (2, 0),
|
||||
"x1": (-3, 0),
|
||||
"y1": (-4, 0),
|
||||
"x2": (5, 0),
|
||||
"y2": (6, 0),
|
||||
},
|
||||
"Glyph": "glyph00012",
|
||||
},
|
||||
],
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_unbuildColrV1():
|
||||
layersV1, baseGlyphsV1 = buildColrV1(TEST_COLOR_GLYPHS)
|
||||
colorGlyphs = unbuildColrV1(layersV1, baseGlyphsV1, ignoreVarIdx=True)
|
||||
colorGlyphs = unbuildColrV1(layersV1, baseGlyphsV1)
|
||||
assert colorGlyphs == TEST_COLOR_GLYPHS
|
||||
|
Loading…
x
Reference in New Issue
Block a user