From e542b60dde969cf93acf8cc2f545c3144b1ba84c Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 10 Feb 2021 18:44:28 +0000 Subject: [PATCH] colorLib: add generic TableUnbuilder, reverse of TableBuilder --- Lib/fontTools/colorLib/table_builder.py | 46 ++++++ Lib/fontTools/colorLib/unbuilder.py | 178 +++-------------------- Tests/colorLib/unbuilder_test.py | 184 ++++++++++++------------ 3 files changed, 164 insertions(+), 244 deletions(-) diff --git a/Lib/fontTools/colorLib/table_builder.py b/Lib/fontTools/colorLib/table_builder.py index b80229754..322673466 100644 --- a/Lib/fontTools/colorLib/table_builder.py +++ b/Lib/fontTools/colorLib/table_builder.py @@ -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 diff --git a/Lib/fontTools/colorLib/unbuilder.py b/Lib/fontTools/colorLib/unbuilder.py index 1c29d5dd2..43582bde3 100644 --- a/Lib/fontTools/colorLib/unbuilder.py +++ b/Lib/fontTools/colorLib/unbuilder.py @@ -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__": diff --git a/Tests/colorLib/unbuilder_test.py b/Tests/colorLib/unbuilder_test.py index fb22abcdb..6728720f6 100644 --- a/Tests/colorLib/unbuilder_test.py +++ b/Tests/colorLib/unbuilder_test.py @@ -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)}, + }, + { + "StopOffset": (1.0, 0), + "Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)}, + }, + ], }, - "centerX": 259, - "centerY": 300, - "startAngle": 45.0, - "endAngle": 135.0, + "centerX": (259, 0), + "centerY": (300, 0), + "startAngle": (45.0, 0), + "endAngle": (135.0, 0), }, + "Glyph": "glyph00011", }, - "glyph00016": ( - ot.PaintFormat.PaintColrLayers, - [ + "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