colorLib: update builders to latest COLRv1 draft

LayerV1Record and Affine2x2 are gone.
LayerV1List now contains a list of Paint DAGs
Added 4 new Paint formats: PaintGlyph, PaintColorGlyph, PaintTransform
and PaintComposite
This commit is contained in:
Cosimo Lupo 2020-10-09 18:16:51 +01:00
parent 7f6a05b007
commit fdf6a5c1fc
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F
2 changed files with 148 additions and 135 deletions

View File

@ -6,13 +6,26 @@ import collections
import copy import copy
import enum import enum
from functools import partial from functools import partial
from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Union from typing import (
Any,
Dict,
Iterable,
List,
Mapping,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
)
from fontTools.ttLib.tables import C_O_L_R_ from fontTools.ttLib.tables import C_O_L_R_
from fontTools.ttLib.tables import C_P_A_L_ from fontTools.ttLib.tables import C_P_A_L_
from fontTools.ttLib.tables import _n_a_m_e from fontTools.ttLib.tables import _n_a_m_e
from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otTables import ( from fontTools.ttLib.tables.otTables import (
ExtendMode, ExtendMode,
CompositeMode,
VariableValue, VariableValue,
VariableFloat, VariableFloat,
VariableInt, VariableInt,
@ -21,11 +34,11 @@ from .errors import ColorLibError
# TODO move type aliases to colorLib.types? # TODO move type aliases to colorLib.types?
T = TypeVar("T")
_Kwargs = Mapping[str, Any] _Kwargs = Mapping[str, Any]
_PaintInput = Union[int, _Kwargs, ot.Paint] _PaintInput = Union[int, _Kwargs, ot.Paint, Tuple[str, "_PaintInput"]]
_LayerTuple = Tuple[str, _PaintInput] _PaintInputList = Sequence[_PaintInput]
_LayersList = Sequence[_LayerTuple] _ColorGlyphsDict = Dict[str, Union[_PaintInputList, ot.LayerV1List]]
_ColorGlyphsDict = Dict[str, _LayersList]
_ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]] _ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
_Number = Union[int, float] _Number = Union[int, float]
_ScalarInput = Union[_Number, VariableValue, Tuple[_Number, int]] _ScalarInput = Union[_Number, VariableValue, Tuple[_Number, int]]
@ -33,10 +46,13 @@ _ColorStopTuple = Tuple[_ScalarInput, int]
_ColorStopInput = Union[_ColorStopTuple, _Kwargs, ot.ColorStop] _ColorStopInput = Union[_ColorStopTuple, _Kwargs, ot.ColorStop]
_ColorStopsList = Sequence[_ColorStopInput] _ColorStopsList = Sequence[_ColorStopInput]
_ExtendInput = Union[int, str, ExtendMode] _ExtendInput = Union[int, str, ExtendMode]
_CompositeInput = Union[int, str, CompositeMode]
_ColorLineInput = Union[_Kwargs, ot.ColorLine] _ColorLineInput = Union[_Kwargs, ot.ColorLine]
_PointTuple = Tuple[_ScalarInput, _ScalarInput] _PointTuple = Tuple[_ScalarInput, _ScalarInput]
_AffineTuple = Tuple[_ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput] _AffineTuple = Tuple[
_AffineInput = Union[_AffineTuple, ot.Affine2x2] _ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput
]
_AffineInput = Union[_AffineTuple, ot.Affine2x3]
def populateCOLRv0( def populateCOLRv0(
@ -91,14 +107,13 @@ def buildCOLR(
"""Build COLR table from color layers mapping. """Build COLR table from color layers mapping.
Args: Args:
colorGlyphs: map of base glyph names to lists of (layer glyph names, colorGlyphs: map of base glyph name to, either list of (layer glyph name,
Paint) tuples. For COLRv0, a paint is simply the color palette index color palette index) tuples for COLRv0; or list of Paints (dicts)
(int); for COLRv1, paint can be either solid colors (with variable for COLRv1.
opacity), linear gradients or radial gradients.
version: the version of COLR table. If None, the version is determined version: the version of COLR table. If None, the version is determined
by the presence of gradients or variation data (varStore), which by the presence of COLRv1 paints or variation data (varStore), which
require version 1; otherwise, if there are only simple colors, version require version 1; otherwise, if all base glyphs use only simple color
0 is used. layers, version 0 is used.
glyphMap: a map from glyph names to glyph indices, as returned from glyphMap: a map from glyph names to glyph indices, as returned from
TTFont.getReverseGlyphMap(), to optionally sort base records by GID. TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
varStore: Optional ItemVarationStore for deltas associated with v1 layer. varStore: Optional ItemVarationStore for deltas associated with v1 layer.
@ -113,10 +128,9 @@ def buildCOLR(
if version in (None, 0) and not varStore: if version in (None, 0) and not varStore:
# split color glyphs into v0 and v1 and encode separately # split color glyphs into v0 and v1 and encode separately
colorGlyphsV0, colorGlyphsV1 = _splitSolidAndGradientGlyphs(colorGlyphs) colorGlyphsV0, colorGlyphsV1 = _split_color_glyphs_by_version(colorGlyphs)
if version == 0 and colorGlyphsV1: if version == 0 and colorGlyphsV1:
# TODO Derive "average" solid color from gradients? raise ValueError("Can't encode COLRv1 glyphs in COLRv0")
raise ValueError("Can't encode gradients in COLRv0")
else: else:
# unless explicitly requested for v1 or have variations, in which case # unless explicitly requested for v1 or have variations, in which case
# we encode all color glyph as v1 # we encode all color glyph as v1
@ -277,29 +291,16 @@ def buildCPAL(
_DEFAULT_ALPHA = VariableFloat(1.0) _DEFAULT_ALPHA = VariableFloat(1.0)
def _splitSolidAndGradientGlyphs( def _split_color_glyphs_by_version(
colorGlyphs: _ColorGlyphsDict, colorGlyphs: _ColorGlyphsDict,
) -> Tuple[Dict[str, List[Tuple[str, int]]], Dict[str, List[Tuple[str, ot.Paint]]]]: ) -> Tuple[_ColorGlyphsV0Dict, _ColorGlyphsDict]:
colorGlyphsV0 = {} colorGlyphsV0 = {}
colorGlyphsV1 = {} colorGlyphsV1 = {}
for baseGlyph, layers in colorGlyphs.items(): for baseGlyph, layers in colorGlyphs.items():
newLayers = [] if all(isinstance(l, tuple) and isinstance(l[1], int) for l in layers):
allSolidColors = True colorGlyphsV0[baseGlyph] = layers
for layerGlyph, paint in layers:
paint = _to_ot_paint(paint)
if (
paint.Format != 1
or paint.Color.Alpha.value != _DEFAULT_ALPHA.value
):
allSolidColors = False
newLayers.append((layerGlyph, paint))
if allSolidColors:
colorGlyphsV0[baseGlyph] = [
(layerGlyph, paint.Color.PaletteIndex)
for layerGlyph, paint in newLayers
]
else: else:
colorGlyphsV1[baseGlyph] = newLayers colorGlyphsV1[baseGlyph] = layers
# sanity check # sanity check
assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1)) assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1))
@ -351,15 +352,23 @@ def buildColorStop(
return self return self
def _to_extend_mode(v: _ExtendInput) -> ExtendMode: def _to_enum_value(v: Union[str, int, T], enumClass: Type[T]) -> T:
if isinstance(v, ExtendMode): if isinstance(v, enumClass):
return v return v
elif isinstance(v, str): elif isinstance(v, str):
try: try:
return getattr(ExtendMode, v.upper()) return getattr(enumClass, v.upper())
except AttributeError: except AttributeError:
raise ValueError(f"{v!r} is not a valid ExtendMode") raise ValueError(f"{v!r} is not a valid {enumClass.__name__}")
return ExtendMode(v) return enumClass(v)
def _to_extend_mode(v: _ExtendInput) -> ExtendMode:
return _to_enum_value(v, ExtendMode)
def _to_composite_mode(v: _CompositeInput) -> CompositeMode:
return _to_enum_value(v, CompositeMode)
def buildColorLine( def buildColorLine(
@ -406,12 +415,17 @@ def buildLinearGradientPaint(
return self return self
def buildAffine2x2( def buildAffine2x3(
xx: _ScalarInput, xy: _ScalarInput, yx: _ScalarInput, yy: _ScalarInput xx: _ScalarInput,
) -> ot.Affine2x2: xy: _ScalarInput,
self = ot.Affine2x2() yx: _ScalarInput,
yy: _ScalarInput,
dx: _ScalarInput,
dy: _ScalarInput,
) -> ot.Affine2x3:
self = ot.Affine2x3()
locs = locals() locs = locals()
for attr in ("xx", "xy", "yx", "yy"): for attr in ("xx", "xy", "yx", "yy", "dx", "dy"):
value = locs[attr] value = locs[attr]
setattr(self, attr, _to_variable_float(value)) setattr(self, attr, _to_variable_float(value))
return self return self
@ -423,7 +437,6 @@ def buildRadialGradientPaint(
c1: _PointTuple, c1: _PointTuple,
r0: _ScalarInput, r0: _ScalarInput,
r1: _ScalarInput, r1: _ScalarInput,
transform: Optional[_AffineInput] = None,
) -> ot.Paint: ) -> ot.Paint:
self = ot.Paint() self = ot.Paint()
@ -435,50 +448,86 @@ def buildRadialGradientPaint(
setattr(self, f"y{i}", _to_variable_int(y)) setattr(self, f"y{i}", _to_variable_int(y))
setattr(self, f"r{i}", _to_variable_int(r)) setattr(self, f"r{i}", _to_variable_int(r))
if transform is not None and not isinstance(transform, ot.Affine2x2):
transform = buildAffine2x2(*transform)
self.Transform = transform
return self return self
def _to_ot_paint(paint: _PaintInput) -> ot.Paint: def buildPaintGlyph(glyph: str, paint: _PaintInput) -> ot.Paint:
self = ot.Paint()
self.Format = 4
self.Glyph = glyph
self.Paint = buildPaint(paint)
return self
def buildPaintColorGlyph(glyph: str) -> ot.Paint:
self = ot.Paint()
self.Format = 5
self.Glyph = glyph
return self
def buildPaintTransform(transform: _AffineInput, paint: _PaintInput) -> ot.Paint:
self = ot.Paint()
self.Format = 6
if not isinstance(transform, ot.Affine2x3):
transform = buildAffine2x3(*transform)
self.Transform = transform
self.Paint = buildPaint(paint)
return self
def buildPaintComposite(
mode: _CompositeInput, source: _PaintInput, backdrop: _PaintInput
):
self = ot.Paint()
self.Format = 7
self.SourcePaint = buildPaint(source)
self.CompositeMode = _to_composite_mode(mode)
self.BackdropPaint = buildPaint(backdrop)
return self
_PAINT_BUILDERS = {
1: buildSolidColorPaint,
2: buildLinearGradientPaint,
3: buildRadialGradientPaint,
4: buildPaintGlyph,
5: buildPaintColorGlyph,
6: buildPaintTransform,
7: buildPaintComposite,
}
def buildPaint(paint: _PaintInput) -> ot.Paint:
if isinstance(paint, ot.Paint): if isinstance(paint, ot.Paint):
return paint return paint
elif isinstance(paint, int): elif isinstance(paint, int):
paletteIndex = paint paletteIndex = paint
return buildSolidColorPaint(paletteIndex) return buildSolidColorPaint(paletteIndex)
elif isinstance(paint, tuple):
layerGlyph, paint = paint
return buildPaintGlyph(layerGlyph, paint)
elif isinstance(paint, collections.abc.Mapping): elif isinstance(paint, collections.abc.Mapping):
return buildPaint(**paint) kwargs = dict(paint)
raise TypeError(f"expected int, Mapping or ot.Paint, found {type(paint.__name__)}") fmt = kwargs.pop("format")
try:
return _PAINT_BUILDERS[fmt](**kwargs)
except KeyError:
raise NotImplementedError(fmt)
raise TypeError(
f"expected int, Mapping or ot.Paint, found {type(paint).__name__}: {paint!r}"
)
def buildLayerV1Record(layerGlyph: str, paint: _PaintInput) -> ot.LayerV1Record: def buildLayerV1List(layers: _PaintInputList) -> ot.LayerV1List:
self = ot.LayerV1Record()
self.LayerGlyph = layerGlyph
self.Paint = _to_ot_paint(paint)
return self
def buildLayerV1List(
layers: Sequence[Union[_LayerTuple, ot.LayerV1Record]]
) -> ot.LayerV1List:
self = ot.LayerV1List() self = ot.LayerV1List()
self.LayerCount = len(layers) self.LayerCount = len(layers)
records = [] self.Paint = [buildPaint(layer) for layer in layers]
for layer in layers:
if isinstance(layer, ot.LayerV1Record):
record = layer
else:
layerGlyph, paint = layer
record = buildLayerV1Record(layerGlyph, paint)
records.append(record)
self.LayerV1Record = records
return self return self
def buildBaseGlyphV1Record( def buildBaseGlyphV1Record(
baseGlyph: str, layers: Union[_LayersList, ot.LayerV1List] baseGlyph: str, layers: Union[_PaintInputList, ot.LayerV1List]
) -> ot.BaseGlyphV1List: ) -> ot.BaseGlyphV1List:
self = ot.BaseGlyphV1Record() self = ot.BaseGlyphV1Record()
self.BaseGlyph = baseGlyph self.BaseGlyph = baseGlyph
@ -489,7 +538,7 @@ def buildBaseGlyphV1Record(
def buildBaseGlyphV1List( def buildBaseGlyphV1List(
colorGlyphs: Union[_ColorGlyphsDict, Dict[str, ot.LayerV1List]], colorGlyphs: _ColorGlyphsDict,
glyphMap: Optional[Mapping[str, int]] = None, glyphMap: Optional[Mapping[str, int]] = None,
) -> ot.BaseGlyphV1List: ) -> ot.BaseGlyphV1List:
if glyphMap is not None: if glyphMap is not None:
@ -506,17 +555,3 @@ def buildBaseGlyphV1List(
self.BaseGlyphCount = len(records) self.BaseGlyphCount = len(records)
self.BaseGlyphV1Record = records self.BaseGlyphV1Record = records
return self 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)

View File

@ -284,12 +284,14 @@ def test_buildColorLine():
] == stops ] == stops
def test_buildAffine2x2(): def test_buildAffine2x3():
matrix = builder.buildAffine2x2(1.5, 0, 0.5, 2.0) matrix = builder.buildAffine2x3(1.5, 0, 0.5, 2.0, 1.0, -3.0)
assert matrix.xx == builder.VariableFloat(1.5) assert matrix.xx == builder.VariableFloat(1.5)
assert matrix.xy == builder.VariableFloat(0.0) assert matrix.xy == builder.VariableFloat(0.0)
assert matrix.yx == builder.VariableFloat(0.5) assert matrix.yx == builder.VariableFloat(0.5)
assert matrix.yy == builder.VariableFloat(2.0) assert matrix.yy == builder.VariableFloat(2.0)
assert matrix.dx == builder.VariableFloat(1.0)
assert matrix.dy == builder.VariableFloat(-3.0)
def test_buildLinearGradientPaint(): def test_buildLinearGradientPaint():
@ -337,36 +339,24 @@ def test_buildRadialGradientPaint():
assert (gradient.x1, gradient.y1) == c1 assert (gradient.x1, gradient.y1) == c1
assert gradient.r0 == r0 assert gradient.r0 == r0
assert gradient.r1 == r1 assert gradient.r1 == r1
assert gradient.Transform is None
gradient = builder.buildRadialGradientPaint({"stops": 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.Extend == builder.ExtendMode.PAD
assert gradient.ColorLine.ColorStop == color_stops assert gradient.ColorLine.ColorStop == color_stops
matrix = builder.buildAffine2x2(2.0, 0.0, 0.0, 2.0)
gradient = builder.buildRadialGradientPaint(
color_line, c0, c1, r0, r1, transform=matrix
)
assert gradient.Transform == matrix
gradient = builder.buildRadialGradientPaint( def test_buildPaintGlyph():
color_line, c0, c1, r0, r1, transform=(2.0, 0.0, 0.0, 2.0) layer = builder.buildPaintGlyph("a", 2)
) assert layer.Glyph == "a"
assert gradient.Transform == matrix
def test_buildLayerV1Record():
layer = builder.buildLayerV1Record("a", 2)
assert layer.LayerGlyph == "a"
assert layer.Paint.Format == 1 assert layer.Paint.Format == 1
assert layer.Paint.Color.PaletteIndex == 2 assert layer.Paint.Color.PaletteIndex == 2
layer = builder.buildLayerV1Record("a", builder.buildSolidColorPaint(3, 0.9)) layer = builder.buildPaintGlyph("a", builder.buildSolidColorPaint(3, 0.9))
assert layer.Paint.Format == 1 assert layer.Paint.Format == 1
assert layer.Paint.Color.PaletteIndex == 3 assert layer.Paint.Color.PaletteIndex == 3
assert layer.Paint.Color.Alpha.value == 0.9 assert layer.Paint.Color.Alpha.value == 0.9
layer = builder.buildLayerV1Record( layer = builder.buildPaintGlyph(
"a", "a",
builder.buildLinearGradientPaint( builder.buildLinearGradientPaint(
{"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250) {"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250)
@ -382,7 +372,7 @@ def test_buildLayerV1Record():
assert layer.Paint.x1.value == 150 assert layer.Paint.x1.value == 150
assert layer.Paint.y1.value == 250 assert layer.Paint.y1.value == 250
layer = builder.buildLayerV1Record( layer = builder.buildPaintGlyph(
"a", "a",
builder.buildRadialGradientPaint( builder.buildRadialGradientPaint(
{ {
@ -414,13 +404,13 @@ def test_buildLayerV1Record():
assert layer.Paint.r1.value == 10 assert layer.Paint.r1.value == 10
def test_buildLayerV1Record_from_dict(): def test_buildPaintGlyph_from_dict():
layer = builder.buildLayerV1Record("a", {"format": 1, "paletteIndex": 0}) layer = builder.buildPaintGlyph("a", {"format": 1, "paletteIndex": 0})
assert layer.LayerGlyph == "a" assert layer.Glyph == "a"
assert layer.Paint.Format == 1 assert layer.Paint.Format == 1
assert layer.Paint.Color.PaletteIndex == 0 assert layer.Paint.Color.PaletteIndex == 0
layer = builder.buildLayerV1Record( layer = builder.buildPaintGlyph(
"a", "a",
{ {
"format": 2, "format": 2,
@ -432,7 +422,7 @@ def test_buildLayerV1Record_from_dict():
assert layer.Paint.Format == 2 assert layer.Paint.Format == 2
assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0 assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
layer = builder.buildLayerV1Record( layer = builder.buildPaintGlyph(
"a", "a",
{ {
"format": 3, "format": 3,
@ -477,12 +467,12 @@ def test_buildLayerV1List():
"r1": 10, "r1": 10,
}, },
), ),
builder.buildLayerV1Record("e", builder.buildSolidColorPaint(8)), builder.buildPaintGlyph("e", builder.buildSolidColorPaint(8)),
] ]
layers = builder.buildLayerV1List(layers) layers = builder.buildLayerV1List(layers)
assert layers.LayerCount == len(layers.LayerV1Record) assert layers.LayerCount == len(layers.Paint)
assert all(isinstance(l, ot.LayerV1Record) for l in layers.LayerV1Record) assert all(isinstance(l, ot.Paint) for l in layers.Paint)
def test_buildBaseGlyphV1Record(): def test_buildBaseGlyphV1Record():
@ -540,17 +530,17 @@ def test_buildBaseGlyphV1List():
assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g" assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g"
def test_splitSolidAndGradientGlyphs(): def test_split_color_glyphs_by_version():
colorGlyphs = { colorGlyphs = {
"a": [ "a": [
("b", 0), ("b", 0),
("c", 1), ("c", 1),
("d", {"format": 1, "paletteIndex": 2}), ("d", 2),
("e", builder.buildSolidColorPaint(paletteIndex=3)), ("e", 3),
] ]
} }
colorGlyphsV0, colorGlyphsV1 = builder._splitSolidAndGradientGlyphs(colorGlyphs) colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]} assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]}
assert not colorGlyphsV1 assert not colorGlyphsV1
@ -559,7 +549,7 @@ def test_splitSolidAndGradientGlyphs():
"a": [("b", builder.buildSolidColorPaint(paletteIndex=0, alpha=0.0))] "a": [("b", builder.buildSolidColorPaint(paletteIndex=0, alpha=0.0))]
} }
colorGlyphsV0, colorGlyphsV1 = builder._splitSolidAndGradientGlyphs(colorGlyphs) colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
assert not colorGlyphsV0 assert not colorGlyphsV0
assert colorGlyphsV1 == colorGlyphs assert colorGlyphsV1 == colorGlyphs
@ -580,23 +570,13 @@ def test_splitSolidAndGradientGlyphs():
], ],
} }
colorGlyphsV0, colorGlyphsV1 = builder._splitSolidAndGradientGlyphs(colorGlyphs) colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
assert colorGlyphsV0 == {"a": [("b", 0)]} assert colorGlyphsV0 == {"a": [("b", 0)]}
assert "a" not in colorGlyphsV1 assert "a" not in colorGlyphsV1
assert "c" in colorGlyphsV1 assert "c" in colorGlyphsV1
assert len(colorGlyphsV1["c"]) == 2 assert len(colorGlyphsV1["c"]) == 2
layer_d = colorGlyphsV1["c"][0]
assert layer_d[0] == "d"
assert isinstance(layer_d[1], ot.Paint)
assert layer_d[1].Format == 1
layer_e = colorGlyphsV1["c"][1]
assert layer_e[0] == "e"
assert isinstance(layer_e[1], ot.Paint)
assert layer_e[1].Format == 2
class BuildCOLRTest(object): class BuildCOLRTest(object):
def test_automatic_version_all_solid_color_glyphs(self): def test_automatic_version_all_solid_color_glyphs(self):
@ -691,9 +671,7 @@ class BuildCOLRTest(object):
colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].LayerV1List, ot.LayerV1List colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].LayerV1List, ot.LayerV1List
) )
assert ( assert (
colr.table.BaseGlyphV1List.BaseGlyphV1Record[0] colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].LayerV1List.Paint[0].Glyph
.LayerV1List.LayerV1Record[0]
.LayerGlyph
== "e" == "e"
) )