Merge pull request #2171 from fonttools/colorLib-unbuilder

COLRv1: add functions to un-build COLR otTables to raw dicts
This commit is contained in:
Cosimo Lupo 2021-02-04 12:34:36 +00:00 committed by GitHub
commit f0374be5e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 372 additions and 0 deletions

View File

@ -0,0 +1,213 @@
from fontTools.ttLib.tables import otTables as ot
def unbuildColrV1(layerV1List, baseGlyphV1List, ignoreVarIdx=False):
unbuilder = LayerV1ListUnbuilder(layerV1List.Paint, ignoreVarIdx=ignoreVarIdx)
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):
yield from _flatten(el)
else:
yield el
class LayerV1ListUnbuilder:
def __init__(self, layers, ignoreVarIdx=False):
self.layers = layers
self.ignoreVarIdx = ignoreVarIdx
def unbuildPaint(self, paint):
try:
return self._unbuildFunctions[paint.Format](self, paint)
except KeyError:
raise ValueError(f"Unrecognized paint format: {paint.Format}")
def unbuildVariableValue(self, value):
return _unbuildVariableValue(value, ignoreVarIdx=self.ignoreVarIdx)
def unbuildPaintColrLayers(self, paint):
return list(
_flatten(
[
self.unbuildPaint(childPaint)
for childPaint in self.layers[
paint.FirstLayerIndex : paint.FirstLayerIndex + paint.NumLayers
]
]
)
)
def unbuildPaintSolid(self, paint):
return {
"format": int(paint.Format),
"paletteIndex": paint.Color.PaletteIndex,
"alpha": self.unbuildVariableValue(paint.Color.Alpha),
}
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.Paint.Format
}
if __name__ == "__main__":
from pprint import pprint
import sys
from fontTools.ttLib import TTFont
try:
fontfile = sys.argv[1]
except IndexError:
sys.exit("usage: fonttools colorLib.unbuilder FONTFILE")
font = TTFont(fontfile)
colr = font["COLR"]
if colr.version < 1:
sys.exit(f"error: No COLR table version=1 found in {fontfile}")
colorGlyphs = unbuildColrV1(
colr.table.LayerV1List,
colr.table.BaseGlyphV1List,
ignoreVarIdx=not colr.table.VarStore,
)
pprint(colorGlyphs)

View File

@ -0,0 +1,159 @@
from fontTools.ttLib.tables import otTables as ot
from fontTools.colorLib.builder import buildColrV1
from fontTools.colorLib.unbuilder import unbuildColrV1
import pytest
TEST_COLOR_GLYPHS = {
"glyph00010": [
{
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00011",
"paint": {
"format": int(ot.Paint.Format.PaintSolid),
"paletteIndex": 2,
"alpha": 0.5,
},
},
{
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00012",
"paint": {
"format": int(ot.Paint.Format.PaintLinearGradient),
"colorLine": {
"stops": [
{"offset": 0.0, "paletteIndex": 3, "alpha": 1.0},
{"offset": 0.5, "paletteIndex": 4, "alpha": 1.0},
{"offset": 1.0, "paletteIndex": 5, "alpha": 1.0},
],
"extend": "repeat",
},
"p0": (1, 2),
"p1": (-3, -4),
"p2": (5, 6),
},
},
{
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00013",
"paint": {
"format": int(ot.Paint.Format.PaintTransform),
"transform": (-13.0, 14.0, 15.0, -17.0, 18.0, 19.0),
"paint": {
"format": int(ot.Paint.Format.PaintRadialGradient),
"colorLine": {
"stops": [
{"offset": 0.0, "paletteIndex": 6, "alpha": 1.0},
{
"offset": 1.0,
"paletteIndex": 7,
"alpha": 0.4,
},
],
"extend": "pad",
},
"c0": (7, 8),
"r0": 9,
"c1": (10, 11),
"r1": 12,
},
},
},
{
"format": int(ot.Paint.Format.PaintTranslate),
"dx": 257.0,
"dy": 258.0,
"paint": {
"format": int(ot.Paint.Format.PaintRotate),
"angle": 45.0,
"centerX": 255.0,
"centerY": 256.0,
"paint": {
"format": int(ot.Paint.Format.PaintSkew),
"xSkewAngle": -11.0,
"ySkewAngle": 5.0,
"centerX": 253.0,
"centerY": 254.0,
"paint": {
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00011",
"paint": {
"format": int(ot.Paint.Format.PaintSolid),
"paletteIndex": 2,
"alpha": 0.5,
},
},
},
},
},
],
"glyph00014": {
"format": int(ot.Paint.Format.PaintComposite),
"mode": "src_over",
"source": {
"format": int(ot.Paint.Format.PaintColrGlyph),
"glyph": "glyph00010",
},
"backdrop": {
"format": int(ot.Paint.Format.PaintTransform),
"transform": (1.0, 0.0, 0.0, 1.0, 300.0, 0.0),
"paint": {
"format": int(ot.Paint.Format.PaintColrGlyph),
"glyph": "glyph00010",
},
},
},
"glyph00015": {
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00011",
"paint": {
"format": int(ot.Paint.Format.PaintSweepGradient),
"colorLine": {
"stops": [
{"offset": 0.0, "paletteIndex": 3, "alpha": 1.0},
{"offset": 1.0, "paletteIndex": 5, "alpha": 1.0},
],
"extend": "pad",
},
"centerX": 259,
"centerY": 300,
"startAngle": 45.0,
"endAngle": 135.0,
},
},
"glyph00016": [
{
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00011",
"paint": {
"format": int(ot.Paint.Format.PaintSolid),
"paletteIndex": 2,
"alpha": 0.5,
},
},
{
"format": int(ot.Paint.Format.PaintGlyph),
"glyph": "glyph00012",
"paint": {
"format": int(ot.Paint.Format.PaintLinearGradient),
"colorLine": {
"stops": [
{"offset": 0.0, "paletteIndex": 3, "alpha": 1.0},
{"offset": 0.5, "paletteIndex": 4, "alpha": 1.0},
{"offset": 1.0, "paletteIndex": 5, "alpha": 1.0},
],
"extend": "repeat",
},
"p0": (1, 2),
"p1": (-3, -4),
"p2": (5, 6),
},
},
],
}
def test_unbuildColrV1():
layersV1, baseGlyphsV1 = buildColrV1(TEST_COLOR_GLYPHS)
colorGlyphs = unbuildColrV1(layersV1, baseGlyphsV1, ignoreVarIdx=True)
assert colorGlyphs == TEST_COLOR_GLYPHS