Cosimo Lupo df672a7ae4
move PaintFormat enum outside of Paint class nested scope
When a TTFont is loaded with lazy=True, the otTables are only loaded upon BaseTable.__getattr__
when the requested attribute is not found in the instance __dict__.
Since the Paint.Format enum was defined at class level, every Paint instance, even when loaded
lazily, will have a 'Format' attribute and the magic decompile-on-missing-attribute will not
trigger, since the class attribute will be returned when the instance is missing one.

For this reason, and to not add further special cases, it's better to simply move this Paint.Format
enum class outside to the module level scope, and rename it PaintFormat.
2021-02-05 12:11:43 +00:00

214 lines
6.7 KiB
Python

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.PaintFormat
}
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)