Upgrade fonttools to 4.20.0 am: 20c690aa8c am: 6e1560f5dc am: d47a0690f7

Original change: https://android-review.googlesource.com/c/platform/external/fonttools/+/1592900

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I652a9d7b79547d55c06727fe308fb54fada2619a
diff --git a/Doc/source/ttLib/ttFont.rst b/Doc/source/ttLib/ttFont.rst
index 77882cd..a571050 100644
--- a/Doc/source/ttLib/ttFont.rst
+++ b/Doc/source/ttLib/ttFont.rst
@@ -5,4 +5,5 @@
 .. automodule:: fontTools.ttLib.ttFont
    :inherited-members:
    :members:
-   :undoc-members:
\ No newline at end of file
+   :undoc-members:
+   :private-members:
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 16ee8a4..19040e4 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -4,6 +4,6 @@
 
 log = logging.getLogger(__name__)
 
-version = __version__ = "4.19.1"
+version = __version__ = "4.20.0"
 
 __all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py
index 998ab60..821244a 100644
--- a/Lib/fontTools/colorLib/builder.py
+++ b/Lib/fontTools/colorLib/builder.py
@@ -25,7 +25,6 @@
 from fontTools.ttLib.tables import C_O_L_R_
 from fontTools.ttLib.tables import C_P_A_L_
 from fontTools.ttLib.tables import _n_a_m_e
-from fontTools.ttLib.tables.otBase import BaseTable
 from fontTools.ttLib.tables import otTables as ot
 from fontTools.ttLib.tables.otTables import (
     ExtendMode,
@@ -36,6 +35,11 @@
 )
 from .errors import ColorLibError
 from .geometry import round_start_circle_stable_containment
+from .table_builder import (
+    convertTupleClass,
+    BuildCallback,
+    TableBuilder,
+)
 
 
 # TODO move type aliases to colorLib.types?
@@ -45,21 +49,87 @@
 _PaintInputList = Sequence[_PaintInput]
 _ColorGlyphsDict = Dict[str, Union[_PaintInputList, _PaintInput]]
 _ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
-_Number = Union[int, float]
-_ScalarInput = Union[_Number, VariableValue, Tuple[_Number, int]]
-_ColorStopTuple = Tuple[_ScalarInput, int]
-_ColorStopInput = Union[_ColorStopTuple, _Kwargs, ot.ColorStop]
-_ColorStopsList = Sequence[_ColorStopInput]
-_ExtendInput = Union[int, str, ExtendMode]
-_CompositeInput = Union[int, str, CompositeMode]
-_ColorLineInput = Union[_Kwargs, ot.ColorLine]
-_PointTuple = Tuple[_ScalarInput, _ScalarInput]
-_AffineTuple = Tuple[
-    _ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput, _ScalarInput
-]
-_AffineInput = Union[_AffineTuple, ot.Affine2x3]
+
 
 MAX_PAINT_COLR_LAYER_COUNT = 255
+_DEFAULT_ALPHA = VariableFloat(1.0)
+_MAX_REUSE_LEN = 32
+
+
+def _beforeBuildPaintVarRadialGradient(paint, source, srcMapFn=lambda v: v):
+    # normalize input types (which may or may not specify a varIdx)
+    x0 = convertTupleClass(VariableFloat, source["x0"])
+    y0 = convertTupleClass(VariableFloat, source["y0"])
+    r0 = convertTupleClass(VariableFloat, source["r0"])
+    x1 = convertTupleClass(VariableFloat, source["x1"])
+    y1 = convertTupleClass(VariableFloat, source["y1"])
+    r1 = convertTupleClass(VariableFloat, source["r1"])
+
+    # TODO apparently no builder_test confirms this works (?)
+
+    # avoid abrupt change after rounding when c0 is near c1's perimeter
+    c = round_start_circle_stable_containment(
+        (x0.value, y0.value), r0.value, (x1.value, y1.value), r1.value
+    )
+    x0, y0 = x0._replace(value=c.centre[0]), y0._replace(value=c.centre[1])
+    r0 = r0._replace(value=c.radius)
+
+    # update source to ensure paint is built with corrected values
+    source["x0"] = srcMapFn(x0)
+    source["y0"] = srcMapFn(y0)
+    source["r0"] = srcMapFn(r0)
+    source["x1"] = srcMapFn(x1)
+    source["y1"] = srcMapFn(y1)
+    source["r1"] = srcMapFn(r1)
+
+    return paint, source
+
+
+def _beforeBuildPaintRadialGradient(paint, source):
+    return _beforeBuildPaintVarRadialGradient(paint, source, lambda v: v.value)
+
+
+def _defaultColorIndex():
+    colorIndex = ot.ColorIndex()
+    colorIndex.Alpha = _DEFAULT_ALPHA.value
+    return colorIndex
+
+
+def _defaultVarColorIndex():
+    colorIndex = ot.VarColorIndex()
+    colorIndex.Alpha = _DEFAULT_ALPHA
+    return colorIndex
+
+
+def _defaultColorLine():
+    colorLine = ot.ColorLine()
+    colorLine.Extend = ExtendMode.PAD
+    return colorLine
+
+
+def _defaultVarColorLine():
+    colorLine = ot.VarColorLine()
+    colorLine.Extend = ExtendMode.PAD
+    return colorLine
+
+
+def _buildPaintCallbacks():
+    return {
+        (
+            BuildCallback.BEFORE_BUILD,
+            ot.Paint,
+            ot.PaintFormat.PaintRadialGradient,
+        ): _beforeBuildPaintRadialGradient,
+        (
+            BuildCallback.BEFORE_BUILD,
+            ot.Paint,
+            ot.PaintFormat.PaintVarRadialGradient,
+        ): _beforeBuildPaintVarRadialGradient,
+        (BuildCallback.CREATE_DEFAULT, ot.ColorIndex): _defaultColorIndex,
+        (BuildCallback.CREATE_DEFAULT, ot.VarColorIndex): _defaultVarColorIndex,
+        (BuildCallback.CREATE_DEFAULT, ot.ColorLine): _defaultColorLine,
+        (BuildCallback.CREATE_DEFAULT, ot.VarColorLine): _defaultVarColorLine,
+    }
 
 
 def populateCOLRv0(
@@ -112,7 +182,6 @@
     varStore: Optional[ot.VarStore] = None,
 ) -> C_O_L_R_.table_C_O_L_R_:
     """Build COLR table from color layers mapping.
-
     Args:
         colorGlyphs: map of base glyph name to, either list of (layer glyph name,
             color palette index) tuples for COLRv0; or a single Paint (dict) or
@@ -124,7 +193,6 @@
         glyphMap: a map from glyph names to glyph indices, as returned from
             TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
         varStore: Optional ItemVarationStore for deltas associated with v1 layer.
-
     Return:
         A new COLR table.
     """
@@ -161,7 +229,7 @@
     self.version = colr.Version = version
 
     if version == 0:
-        self._fromOTTable(colr)
+        self.ColorLayers = self._decompileColorLayersV0(colr)
     else:
         colr.VarStore = varStore
         self.table = colr
@@ -295,8 +363,6 @@
 # COLR v1 tables
 # See draft proposal at: https://github.com/googlefonts/colr-gradients-spec
 
-_DEFAULT_ALPHA = VariableFloat(1.0)
-
 
 def _is_colrv0_layer(layer: Any) -> bool:
     # Consider as COLRv0 layer any sequence of length 2 (be it tuple or list) in which
@@ -328,124 +394,15 @@
     return colorGlyphsV0, colorGlyphsV1
 
 
-def _to_variable_value(
-    value: _ScalarInput,
-    cls: Type[VariableValue] = VariableFloat,
-    minValue: Optional[_Number] = None,
-    maxValue: Optional[_Number] = None,
-) -> VariableValue:
-    if not isinstance(value, cls):
-        try:
-            it = iter(value)
-        except TypeError:  # not iterable
-            value = cls(value)
-        else:
-            value = cls._make(it)
-    if minValue is not None and value.value < minValue:
-        raise OverflowError(f"{cls.__name__}: {value.value} < {minValue}")
-    if maxValue is not None and value.value > maxValue:
-        raise OverflowError(f"{cls.__name__}: {value.value} < {maxValue}")
-    return value
-
-
-_to_variable_f16dot16_float = partial(
-    _to_variable_value,
-    cls=VariableFloat,
-    minValue=-(2 ** 15),
-    maxValue=fixedToFloat(2 ** 31 - 1, 16),
-)
-_to_variable_f2dot14_float = partial(
-    _to_variable_value,
-    cls=VariableFloat,
-    minValue=-2.0,
-    maxValue=fixedToFloat(2 ** 15 - 1, 14),
-)
-_to_variable_int16 = partial(
-    _to_variable_value,
-    cls=VariableInt,
-    minValue=-(2 ** 15),
-    maxValue=2 ** 15 - 1,
-)
-_to_variable_uint16 = partial(
-    _to_variable_value,
-    cls=VariableInt,
-    minValue=0,
-    maxValue=2 ** 16,
-)
-
-
-def buildColorIndex(
-    paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA
-) -> ot.ColorIndex:
-    self = ot.ColorIndex()
-    self.PaletteIndex = int(paletteIndex)
-    self.Alpha = _to_variable_f2dot14_float(alpha)
-    return self
-
-
-def buildColorStop(
-    offset: _ScalarInput,
-    paletteIndex: int,
-    alpha: _ScalarInput = _DEFAULT_ALPHA,
-) -> ot.ColorStop:
-    self = ot.ColorStop()
-    self.StopOffset = _to_variable_f2dot14_float(offset)
-    self.Color = buildColorIndex(paletteIndex, alpha)
-    return self
-
-
-def _to_enum_value(v: Union[str, int, T], enumClass: Type[T]) -> T:
-    if isinstance(v, enumClass):
-        return v
-    elif isinstance(v, str):
-        try:
-            return getattr(enumClass, v.upper())
-        except AttributeError:
-            raise ValueError(f"{v!r} is not a valid {enumClass.__name__}")
-    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(
-    stops: _ColorStopsList, extend: _ExtendInput = ExtendMode.PAD
-) -> ot.ColorLine:
-    self = ot.ColorLine()
-    self.Extend = _to_extend_mode(extend)
-    self.StopCount = len(stops)
-    self.ColorStop = [
-        stop
-        if isinstance(stop, ot.ColorStop)
-        else buildColorStop(**stop)
-        if isinstance(stop, collections.abc.Mapping)
-        else buildColorStop(*stop)
-        for stop in stops
-    ]
-    return self
-
-
-def _to_color_line(obj):
-    if isinstance(obj, ot.ColorLine):
-        return obj
-    elif isinstance(obj, collections.abc.Mapping):
-        return buildColorLine(**obj)
-    raise TypeError(obj)
-
-
 def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
     # TODO feels like something itertools might have already
     for lbound in range(num_layers):
-        # TODO may want a max length to limit scope of search
         # Reuse of very large #s of layers is relatively unlikely
         # +2: we want sequences of at least 2
         # otData handles single-record duplication
-        for ubound in range(lbound + 2, num_layers + 1):
+        for ubound in range(
+            lbound + 2, min(num_layers + 1, lbound + 2 + _MAX_REUSE_LEN)
+        ):
             yield (lbound, ubound)
 
 
@@ -463,6 +420,17 @@
         self.tuples = {}
         self.keepAlive = []
 
+        # We need to intercept construction of PaintColrLayers
+        callbacks = _buildPaintCallbacks()
+        callbacks[
+            (
+                BuildCallback.BEFORE_BUILD,
+                ot.Paint,
+                ot.PaintFormat.PaintColrLayers,
+            )
+        ] = self._beforeBuildPaintColrLayers
+        self.tableBuilder = TableBuilder(callbacks)
+
     def _paint_tuple(self, paint: ot.Paint):
         # start simple, who even cares about cyclic graphs or interesting field types
         def _tuple_safe(value):
@@ -488,217 +456,87 @@
     def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
         return tuple(self._paint_tuple(p) for p in paints)
 
-    def buildPaintSolid(
-        self, paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA
-    ) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintSolid)
-        ot_paint.Color = buildColorIndex(paletteIndex, alpha)
-        return ot_paint
+    # COLR layers is unusual in that it modifies shared state
+    # so we need a callback into an object
+    def _beforeBuildPaintColrLayers(self, dest, source):
+        paint = ot.Paint()
+        paint.Format = int(ot.PaintFormat.PaintColrLayers)
+        self.slices.append(paint)
 
-    def buildPaintLinearGradient(
-        self,
-        colorLine: _ColorLineInput,
-        p0: _PointTuple,
-        p1: _PointTuple,
-        p2: Optional[_PointTuple] = None,
-    ) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintLinearGradient)
-        ot_paint.ColorLine = _to_color_line(colorLine)
+        # Sketchy gymnastics: a sequence input will have dropped it's layers
+        # into NumLayers; get it back
+        if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
+            layers = source["NumLayers"]
+        else:
+            layers = source["Layers"]
 
-        if p2 is None:
-            p2 = copy.copy(p1)
-        for i, (x, y) in enumerate((p0, p1, p2)):
-            setattr(ot_paint, f"x{i}", _to_variable_int16(x))
-            setattr(ot_paint, f"y{i}", _to_variable_int16(y))
+        # Convert maps seqs or whatever into typed objects
+        layers = [self.buildPaint(l) for l in layers]
 
-        return ot_paint
-
-    def buildPaintRadialGradient(
-        self,
-        colorLine: _ColorLineInput,
-        c0: _PointTuple,
-        c1: _PointTuple,
-        r0: _ScalarInput,
-        r1: _ScalarInput,
-    ) -> ot.Paint:
-
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintRadialGradient)
-        ot_paint.ColorLine = _to_color_line(colorLine)
-
-        # normalize input types (which may or may not specify a varIdx)
-        x0, y0 = _to_variable_value(c0[0]), _to_variable_value(c0[1])
-        r0 = _to_variable_value(r0)
-        x1, y1 = _to_variable_value(c1[0]), _to_variable_value(c1[1])
-        r1 = _to_variable_value(r1)
-
-        # avoid abrupt change after rounding when c0 is near c1's perimeter
-        c = round_start_circle_stable_containment(
-            (x0.value, y0.value), r0.value, (x1.value, y1.value), r1.value
-        )
-        x0, y0 = x0._replace(value=c.centre[0]), y0._replace(value=c.centre[1])
-        r0 = r0._replace(value=c.radius)
-
-        for i, (x, y, r) in enumerate(((x0, y0, r0), (x1, y1, r1))):
-            # rounding happens here as floats are converted to integers
-            setattr(ot_paint, f"x{i}", _to_variable_int16(x))
-            setattr(ot_paint, f"y{i}", _to_variable_int16(y))
-            setattr(ot_paint, f"r{i}", _to_variable_uint16(r))
-
-        return ot_paint
-
-    def buildPaintGlyph(self, glyph: str, paint: _PaintInput) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintGlyph)
-        ot_paint.Glyph = glyph
-        ot_paint.Paint = self.buildPaint(paint)
-        return ot_paint
-
-    def buildPaintColrGlyph(self, glyph: str) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintColrGlyph)
-        ot_paint.Glyph = glyph
-        return ot_paint
-
-    def buildPaintTransform(
-        self, transform: _AffineInput, paint: _PaintInput
-    ) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintTransform)
-        if not isinstance(transform, ot.Affine2x3):
-            transform = buildAffine2x3(transform)
-        ot_paint.Transform = transform
-        ot_paint.Paint = self.buildPaint(paint)
-        return ot_paint
-
-    def buildPaintTranslate(
-        self, paint: _PaintInput, dx: _ScalarInput, dy: _ScalarInput
-    ):
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintTranslate)
-        ot_paint.Paint = self.buildPaint(paint)
-        ot_paint.dx = _to_variable_f16dot16_float(dx)
-        ot_paint.dy = _to_variable_f16dot16_float(dy)
-        return ot_paint
-
-    def buildPaintRotate(
-        self,
-        paint: _PaintInput,
-        angle: _ScalarInput,
-        centerX: _ScalarInput,
-        centerY: _ScalarInput,
-    ) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintRotate)
-        ot_paint.Paint = self.buildPaint(paint)
-        ot_paint.angle = _to_variable_f16dot16_float(angle)
-        ot_paint.centerX = _to_variable_f16dot16_float(centerX)
-        ot_paint.centerY = _to_variable_f16dot16_float(centerY)
-        return ot_paint
-
-    def buildPaintSkew(
-        self,
-        paint: _PaintInput,
-        xSkewAngle: _ScalarInput,
-        ySkewAngle: _ScalarInput,
-        centerX: _ScalarInput,
-        centerY: _ScalarInput,
-    ) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintSkew)
-        ot_paint.Paint = self.buildPaint(paint)
-        ot_paint.xSkewAngle = _to_variable_f16dot16_float(xSkewAngle)
-        ot_paint.ySkewAngle = _to_variable_f16dot16_float(ySkewAngle)
-        ot_paint.centerX = _to_variable_f16dot16_float(centerX)
-        ot_paint.centerY = _to_variable_f16dot16_float(centerY)
-        return ot_paint
-
-    def buildPaintComposite(
-        self,
-        mode: _CompositeInput,
-        source: _PaintInput,
-        backdrop: _PaintInput,
-    ):
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintComposite)
-        ot_paint.SourcePaint = self.buildPaint(source)
-        ot_paint.CompositeMode = _to_composite_mode(mode)
-        ot_paint.BackdropPaint = self.buildPaint(backdrop)
-        return ot_paint
-
-    def buildColrLayers(self, paints: List[_PaintInput]) -> ot.Paint:
-        ot_paint = ot.Paint()
-        ot_paint.Format = int(ot.Paint.Format.PaintColrLayers)
-        self.slices.append(ot_paint)
-
-        paints = [
-            self.buildPaint(p)
-            for p in _build_n_ary_tree(paints, n=MAX_PAINT_COLR_LAYER_COUNT)
-        ]
+        # No reason to have a colr layers with just one entry
+        if len(layers) == 1:
+            return layers[0], {}
 
         # Look for reuse, with preference to longer sequences
+        # This may make the layer list smaller
         found_reuse = True
         while found_reuse:
             found_reuse = False
 
             ranges = sorted(
-                _reuse_ranges(len(paints)),
+                _reuse_ranges(len(layers)),
                 key=lambda t: (t[1] - t[0], t[1], t[0]),
                 reverse=True,
             )
             for lbound, ubound in ranges:
                 reuse_lbound = self.reusePool.get(
-                    self._as_tuple(paints[lbound:ubound]), -1
+                    self._as_tuple(layers[lbound:ubound]), -1
                 )
                 if reuse_lbound == -1:
                     continue
                 new_slice = ot.Paint()
-                new_slice.Format = int(ot.Paint.Format.PaintColrLayers)
+                new_slice.Format = int(ot.PaintFormat.PaintColrLayers)
                 new_slice.NumLayers = ubound - lbound
                 new_slice.FirstLayerIndex = reuse_lbound
-                paints = paints[:lbound] + [new_slice] + paints[ubound:]
+                layers = layers[:lbound] + [new_slice] + layers[ubound:]
                 found_reuse = True
                 break
 
-        ot_paint.NumLayers = len(paints)
-        ot_paint.FirstLayerIndex = len(self.layers)
-        self.layers.extend(paints)
+        # The layer list is now final; if it's too big we need to tree it
+        is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT
+        layers = _build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT)
 
-        # Register our parts for reuse
-        for lbound, ubound in _reuse_ranges(len(paints)):
-            self.reusePool[self._as_tuple(paints[lbound:ubound])] = (
-                lbound + ot_paint.FirstLayerIndex
-            )
+        # We now have a tree of sequences with Paint leaves.
+        # Convert the sequences into PaintColrLayers.
+        def listToColrLayers(layer):
+            if isinstance(layer, collections.abc.Sequence):
+                return self.buildPaint(
+                    {
+                        "Format": ot.PaintFormat.PaintColrLayers,
+                        "Layers": [listToColrLayers(l) for l in layer],
+                    }
+                )
+            return layer
 
-        return ot_paint
+        layers = [listToColrLayers(l) for l in layers]
+
+        paint.NumLayers = len(layers)
+        paint.FirstLayerIndex = len(self.layers)
+        self.layers.extend(layers)
+
+        # Register our parts for reuse provided we aren't a tree
+        # If we are a tree the leaves registered for reuse and that will suffice
+        if not is_tree:
+            for lbound, ubound in _reuse_ranges(len(layers)):
+                self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
+                    lbound + paint.FirstLayerIndex
+                )
+
+        # we've fully built dest; empty source prevents generalized build from kicking in
+        return paint, {}
 
     def buildPaint(self, paint: _PaintInput) -> ot.Paint:
-        if isinstance(paint, ot.Paint):
-            return paint
-        elif isinstance(paint, int):
-            paletteIndex = paint
-            return self.buildPaintSolid(paletteIndex)
-        elif isinstance(paint, tuple):
-            layerGlyph, paint = paint
-            return self.buildPaintGlyph(layerGlyph, paint)
-        elif isinstance(paint, list):
-            # implicit PaintColrLayers for a list of > 1
-            if len(paint) == 0:
-                raise ValueError("An empty list is hard to paint")
-            elif len(paint) == 1:
-                return self.buildPaint(paint[0])
-            else:
-                return self.buildColrLayers(paint)
-        elif isinstance(paint, collections.abc.Mapping):
-            kwargs = dict(paint)
-            fmt = kwargs.pop("format")
-            try:
-                return LayerV1ListBuilder._buildFunctions[fmt](self, **kwargs)
-            except KeyError:
-                raise NotImplementedError(fmt)
-        raise TypeError(f"Not sure what to do with {type(paint).__name__}: {paint!r}")
+        return self.tableBuilder.build(ot.Paint, paint)
 
     def build(self) -> ot.LayerV1List:
         layers = ot.LayerV1List()
@@ -707,31 +545,6 @@
         return layers
 
 
-LayerV1ListBuilder._buildFunctions = {
-    pf.value: getattr(LayerV1ListBuilder, "build" + pf.name)
-    for pf in ot.Paint.Format
-    if pf != ot.Paint.Format.PaintColrLayers
-}
-
-
-def buildAffine2x3(transform: _AffineTuple) -> ot.Affine2x3:
-    if len(transform) != 6:
-        raise ValueError(f"Expected 6-tuple of floats, found: {transform!r}")
-    self = ot.Affine2x3()
-    # COLRv1 Affine2x3 uses the same column-major order to serialize a 2D
-    # Affine Transformation as the one used by fontTools.misc.transform.
-    # However, for historical reasons, the labels 'xy' and 'yx' are swapped.
-    # Their fundamental meaning is the same though.
-    # COLRv1 Affine2x3 follows the names found in FreeType and Cairo.
-    # In all case, the second element in the 6-tuple correspond to the
-    # y-part of the x basis vector, and the third to the x-part of the y
-    # basis vector.
-    # See https://github.com/googlefonts/colr-gradients-spec/pull/85
-    for i, attr in enumerate(("xx", "yx", "xy", "yy", "dx", "dy")):
-        setattr(self, attr, _to_variable_f16dot16_float(transform[i]))
-    return self
-
-
 def buildBaseGlyphV1Record(
     baseGlyph: str, layerBuilder: LayerV1ListBuilder, paint: _PaintInput
 ) -> ot.BaseGlyphV1List:
diff --git a/Lib/fontTools/colorLib/table_builder.py b/Lib/fontTools/colorLib/table_builder.py
new file mode 100644
index 0000000..18e2de1
--- /dev/null
+++ b/Lib/fontTools/colorLib/table_builder.py
@@ -0,0 +1,234 @@
+"""
+colorLib.table_builder: Generic helper for filling in BaseTable derivatives from tuples and maps and such.
+
+"""
+
+import collections
+import enum
+from fontTools.ttLib.tables.otBase import (
+    BaseTable,
+    FormatSwitchingBaseTable,
+    UInt8FormatSwitchingBaseTable,
+)
+from fontTools.ttLib.tables.otConverters import (
+    ComputedInt,
+    SimpleValue,
+    Struct,
+    Short,
+    UInt8,
+    UShort,
+    VarInt16,
+    VarUInt16,
+    IntValue,
+    FloatValue,
+)
+from fontTools.misc.fixedTools import otRound
+
+
+class BuildCallback(enum.Enum):
+    """Keyed on (BEFORE_BUILD, class[, Format if available]).
+    Receives (dest, source).
+    Should return (dest, source), which can be new objects.
+    """
+
+    BEFORE_BUILD = enum.auto()
+
+    """Keyed on (AFTER_BUILD, class[, Format if available]).
+    Receives (dest).
+    Should return dest, which can be a new object.
+    """
+    AFTER_BUILD = enum.auto()
+
+    """Keyed on (CREATE_DEFAULT, class).
+    Receives no arguments.
+    Should return a new instance of class.
+    """
+    CREATE_DEFAULT = enum.auto()
+
+
+def _assignable(convertersByName):
+    return {k: v for k, v in convertersByName.items() if not isinstance(v, ComputedInt)}
+
+
+def convertTupleClass(tupleClass, value):
+    if isinstance(value, tupleClass):
+        return value
+    if isinstance(value, tuple):
+        return tupleClass(*value)
+    return tupleClass(value)
+
+
+def _isNonStrSequence(value):
+    return isinstance(value, collections.abc.Sequence) and not isinstance(value, str)
+
+
+def _set_format(dest, source):
+    if _isNonStrSequence(source):
+        assert len(source) > 0, f"{type(dest)} needs at least format from {source}"
+        dest.Format = source[0]
+        source = source[1:]
+    elif isinstance(source, collections.abc.Mapping):
+        assert "Format" in source, f"{type(dest)} needs at least Format from {source}"
+        dest.Format = source["Format"]
+    else:
+        raise ValueError(f"Not sure how to populate {type(dest)} from {source}")
+
+    assert isinstance(
+        dest.Format, collections.abc.Hashable
+    ), f"{type(dest)} Format is not hashable: {dest.Format}"
+    assert (
+        dest.Format in dest.convertersByName
+    ), f"{dest.Format} invalid Format of {cls}"
+
+    return source
+
+
+class TableBuilder:
+    """
+    Helps to populate things derived from BaseTable from maps, tuples, etc.
+
+    A table of lifecycle callbacks may be provided to add logic beyond what is possible
+    based on otData info for the target class. See BuildCallbacks.
+    """
+
+    def __init__(self, callbackTable=None):
+        if callbackTable is None:
+            callbackTable = {}
+        self._callbackTable = callbackTable
+
+    def _convert(self, dest, field, converter, value):
+        tupleClass = getattr(converter, "tupleClass", None)
+        enumClass = getattr(converter, "enumClass", None)
+
+        if tupleClass:
+            value = convertTupleClass(tupleClass, value)
+
+        elif enumClass:
+            if isinstance(value, enumClass):
+                pass
+            elif isinstance(value, str):
+                try:
+                    value = getattr(enumClass, value.upper())
+                except AttributeError:
+                    raise ValueError(f"{value} is not a valid {enumClass}")
+            else:
+                value = enumClass(value)
+
+        elif isinstance(converter, IntValue):
+            value = otRound(value)
+        elif isinstance(converter, FloatValue):
+            value = float(value)
+
+        elif isinstance(converter, Struct):
+            if converter.repeat:
+                if _isNonStrSequence(value):
+                    value = [self.build(converter.tableClass, v) for v in value]
+                else:
+                    value = [self.build(converter.tableClass, value)]
+                setattr(dest, converter.repeat, len(value))
+            else:
+                value = self.build(converter.tableClass, value)
+        elif callable(converter):
+            value = converter(value)
+
+        setattr(dest, field, value)
+
+    def build(self, cls, source):
+        assert issubclass(cls, BaseTable)
+
+        if isinstance(source, cls):
+            return source
+
+        callbackKey = (cls,)
+        dest = self._callbackTable.get(
+            (BuildCallback.CREATE_DEFAULT,) + callbackKey, lambda: cls()
+        )()
+        assert isinstance(dest, cls)
+
+        convByName = _assignable(cls.convertersByName)
+        skippedFields = set()
+
+        # For format switchers we need to resolve converters based on format
+        if issubclass(cls, FormatSwitchingBaseTable):
+            source = _set_format(dest, source)
+
+            convByName = _assignable(convByName[dest.Format])
+            skippedFields.add("Format")
+            callbackKey = (cls, dest.Format)
+
+        # Convert sequence => mapping so before thunk only has to handle one format
+        if _isNonStrSequence(source):
+            # Sequence (typically list or tuple) assumed to match fields in declaration order
+            assert len(source) <= len(
+                convByName
+            ), f"Sequence of {len(source)} too long for {cls}; expected <= {len(convByName)} values"
+            source = dict(zip(convByName.keys(), source))
+
+        dest, source = self._callbackTable.get(
+            (BuildCallback.BEFORE_BUILD,) + callbackKey, lambda d, s: (d, s)
+        )(dest, source)
+
+        if isinstance(source, collections.abc.Mapping):
+            for field, value in source.items():
+                if field in skippedFields:
+                    continue
+                converter = convByName.get(field, None)
+                if not converter:
+                    raise ValueError(
+                        f"Unrecognized field {field} for {cls}; expected one of {sorted(convByName.keys())}"
+                    )
+                self._convert(dest, field, converter, value)
+        else:
+            # let's try as a 1-tuple
+            dest = self.build(cls, (source,))
+
+        dest = self._callbackTable.get(
+            (BuildCallback.AFTER_BUILD,) + callbackKey, lambda d: d
+        )(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
new file mode 100644
index 0000000..43582bd
--- /dev/null
+++ b/Lib/fontTools/colorLib/unbuilder.py
@@ -0,0 +1,79 @@
+from fontTools.ttLib.tables import otTables as ot
+from .table_builder import TableUnbuilder
+
+
+def unbuildColrV1(layerV1List, baseGlyphV1List):
+    unbuilder = LayerV1ListUnbuilder(layerV1List.Paint)
+    return {
+        rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint)
+        for rec in baseGlyphV1List.BaseGlyphV1Record
+    }
+
+
+def _flatten(lst):
+    for el in lst:
+        if isinstance(el, list):
+            yield from _flatten(el)
+        else:
+            yield el
+
+
+class LayerV1ListUnbuilder:
+    def __init__(self, layers):
+        self.layers = layers
+
+        callbacks = {
+            (
+                ot.Paint,
+                ot.PaintFormat.PaintColrLayers,
+            ): self._unbuildPaintColrLayers,
+        }
+        self.tableUnbuilder = TableUnbuilder(callbacks)
+
+    def unbuildPaint(self, paint):
+        assert isinstance(paint, ot.Paint)
+        return self.tableUnbuilder.unbuild(paint)
+
+    def _unbuildPaintColrLayers(self, source):
+        assert source["Format"] == ot.PaintFormat.PaintColrLayers
+
+        layers = list(
+            _flatten(
+                [
+                    self.unbuildPaint(childPaint)
+                    for childPaint in self.layers[
+                        source["FirstLayerIndex"] : source["FirstLayerIndex"]
+                        + source["NumLayers"]
+                    ]
+                ]
+            )
+        )
+
+        if len(layers) == 1:
+            return layers[0]
+
+        return {"Format": source["Format"], "Layers": layers}
+
+
+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)
diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py
index 7ef9afd..6c2bfce 100644
--- a/Lib/fontTools/feaLib/ast.py
+++ b/Lib/fontTools/feaLib/ast.py
@@ -188,6 +188,21 @@
         return self.text
 
 
+class NullGlyph(Expression):
+    """The NULL glyph, used in glyph deletion substitutions."""
+
+    def __init__(self, location=None):
+        Expression.__init__(self, location)
+        #: The name itself as a string
+
+    def glyphSet(self):
+        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
+        return ()
+
+    def asFea(self, indent=""):
+        return "NULL"
+
+
 class GlyphName(Expression):
     """A single glyph name, such as ``cedilla``."""
 
@@ -1246,8 +1261,9 @@
                 res += " " + " ".join(map(asFea, self.suffix))
         else:
             res += asFea(self.glyph)
+        replacement = self.replacement or [ NullGlyph() ]
         res += " by "
-        res += " ".join(map(asFea, self.replacement))
+        res += " ".join(map(asFea, replacement))
         res += ";"
         return res
 
diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py
index 7439fbf..23a4961 100644
--- a/Lib/fontTools/feaLib/parser.py
+++ b/Lib/fontTools/feaLib/parser.py
@@ -314,10 +314,15 @@
                 location,
             )
 
-    def parse_glyphclass_(self, accept_glyphname):
+    def parse_glyphclass_(self, accept_glyphname, accept_null=False):
         # Parses a glyph class, either named or anonymous, or (if
-        # ``bool(accept_glyphname)``) a glyph name.
+        # ``bool(accept_glyphname)``) a glyph name. If ``bool(accept_null)`` then
+        # also accept the special NULL glyph.
         if accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID):
+            if accept_null and self.next_token_ == "NULL":
+                # If you want a glyph called NULL, you should escape it.
+                self.advance_lexer_()
+                return self.ast.NullGlyph(location=self.cur_token_location_)
             glyph = self.expect_glyph_()
             self.check_glyph_name_in_glyph_set(glyph)
             return self.ast.GlyphName(glyph, location=self.cur_token_location_)
@@ -375,7 +380,8 @@
                     self.expect_symbol_("-")
                     range_end = self.expect_cid_()
                     self.check_glyph_name_in_glyph_set(
-                        f"cid{range_start:05d}", f"cid{range_end:05d}",
+                        f"cid{range_start:05d}",
+                        f"cid{range_end:05d}",
                     )
                     glyphs.add_cid_range(
                         range_start,
@@ -804,7 +810,7 @@
         if self.next_token_ == "by":
             keyword = self.expect_keyword_("by")
             while self.next_token_ != ";":
-                gc = self.parse_glyphclass_(accept_glyphname=True)
+                gc = self.parse_glyphclass_(accept_glyphname=True, accept_null=True)
                 new.append(gc)
         elif self.next_token_ == "from":
             keyword = self.expect_keyword_("from")
@@ -837,6 +843,11 @@
 
         num_lookups = len([l for l in lookups if l is not None])
 
+        is_deletion = False
+        if len(new) == 1 and len(new[0].glyphSet()) == 0:
+            new = []  # Deletion
+            is_deletion = True
+
         # GSUB lookup type 1: Single substitution.
         # Format A: "substitute a by a.sc;"
         # Format B: "substitute [one.fitted one.oldstyle] by one;"
@@ -863,8 +874,10 @@
             not reverse
             and len(old) == 1
             and len(old[0].glyphSet()) == 1
-            and len(new) > 1
-            and max([len(n.glyphSet()) for n in new]) == 1
+            and (
+                (len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1)
+                or len(new) == 0
+            )
             and num_lookups == 0
         ):
             return self.ast.MultipleSubstStatement(
@@ -936,7 +949,7 @@
             )
 
         # If there are remaining glyphs to parse, this is an invalid GSUB statement
-        if len(new) != 0:
+        if len(new) != 0 or is_deletion:
             raise FeatureLibError("Invalid substitution statement", location)
 
         # GSUB lookup type 6: Chaining contextual substitution.
diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py
index f3fe92a..f4c943f 100644
--- a/Lib/fontTools/fontBuilder.py
+++ b/Lib/fontTools/fontBuilder.py
@@ -1,4 +1,3 @@
-
 __all__ = ["FontBuilder"]
 
 """
@@ -136,192 +135,192 @@
 from .ttLib.tables._n_a_m_e import NameRecord, makeName
 from .misc.timeTools import timestampNow
 import struct
+from collections import OrderedDict
 
 
 _headDefaults = dict(
-    tableVersion = 1.0,
-    fontRevision = 1.0,
-    checkSumAdjustment = 0,
-    magicNumber = 0x5F0F3CF5,
-    flags = 0x0003,
-    unitsPerEm = 1000,
-    created = 0,
-    modified = 0,
-    xMin = 0,
-    yMin = 0,
-    xMax = 0,
-    yMax = 0,
-    macStyle = 0,
-    lowestRecPPEM = 3,
-    fontDirectionHint = 2,
-    indexToLocFormat = 0,
-    glyphDataFormat = 0,
+    tableVersion=1.0,
+    fontRevision=1.0,
+    checkSumAdjustment=0,
+    magicNumber=0x5F0F3CF5,
+    flags=0x0003,
+    unitsPerEm=1000,
+    created=0,
+    modified=0,
+    xMin=0,
+    yMin=0,
+    xMax=0,
+    yMax=0,
+    macStyle=0,
+    lowestRecPPEM=3,
+    fontDirectionHint=2,
+    indexToLocFormat=0,
+    glyphDataFormat=0,
 )
 
 _maxpDefaultsTTF = dict(
-    tableVersion = 0x00010000,
-    numGlyphs = 0,
-    maxPoints = 0,
-    maxContours = 0,
-    maxCompositePoints = 0,
-    maxCompositeContours = 0,
-    maxZones = 2,
-    maxTwilightPoints = 0,
-    maxStorage = 0,
-    maxFunctionDefs = 0,
-    maxInstructionDefs = 0,
-    maxStackElements = 0,
-    maxSizeOfInstructions = 0,
-    maxComponentElements = 0,
-    maxComponentDepth = 0,
+    tableVersion=0x00010000,
+    numGlyphs=0,
+    maxPoints=0,
+    maxContours=0,
+    maxCompositePoints=0,
+    maxCompositeContours=0,
+    maxZones=2,
+    maxTwilightPoints=0,
+    maxStorage=0,
+    maxFunctionDefs=0,
+    maxInstructionDefs=0,
+    maxStackElements=0,
+    maxSizeOfInstructions=0,
+    maxComponentElements=0,
+    maxComponentDepth=0,
 )
 _maxpDefaultsOTF = dict(
-    tableVersion = 0x00005000,
-    numGlyphs = 0,
+    tableVersion=0x00005000,
+    numGlyphs=0,
 )
 
 _postDefaults = dict(
-    formatType = 3.0,
-    italicAngle = 0,
-    underlinePosition = 0,
-    underlineThickness = 0,
-    isFixedPitch = 0,
-    minMemType42 = 0,
-    maxMemType42 = 0,
-    minMemType1 = 0,
-    maxMemType1 = 0,
+    formatType=3.0,
+    italicAngle=0,
+    underlinePosition=0,
+    underlineThickness=0,
+    isFixedPitch=0,
+    minMemType42=0,
+    maxMemType42=0,
+    minMemType1=0,
+    maxMemType1=0,
 )
 
 _hheaDefaults = dict(
-    tableVersion = 0x00010000,
-    ascent = 0,
-    descent = 0,
-    lineGap = 0,
-    advanceWidthMax = 0,
-    minLeftSideBearing = 0,
-    minRightSideBearing = 0,
-    xMaxExtent = 0,
-    caretSlopeRise = 1,
-    caretSlopeRun = 0,
-    caretOffset = 0,
-    reserved0 = 0,
-    reserved1 = 0,
-    reserved2 = 0,
-    reserved3 = 0,
-    metricDataFormat = 0,
-    numberOfHMetrics = 0,
+    tableVersion=0x00010000,
+    ascent=0,
+    descent=0,
+    lineGap=0,
+    advanceWidthMax=0,
+    minLeftSideBearing=0,
+    minRightSideBearing=0,
+    xMaxExtent=0,
+    caretSlopeRise=1,
+    caretSlopeRun=0,
+    caretOffset=0,
+    reserved0=0,
+    reserved1=0,
+    reserved2=0,
+    reserved3=0,
+    metricDataFormat=0,
+    numberOfHMetrics=0,
 )
 
 _vheaDefaults = dict(
-    tableVersion = 0x00010000,
-    ascent = 0,
-    descent = 0,
-    lineGap = 0,
-    advanceHeightMax = 0,
-    minTopSideBearing = 0,
-    minBottomSideBearing = 0,
-    yMaxExtent = 0,
-    caretSlopeRise = 0,
-    caretSlopeRun = 0,
-    reserved0 = 0,
-    reserved1 = 0,
-    reserved2 = 0,
-    reserved3 = 0,
-    reserved4 = 0,
-    metricDataFormat = 0,
-    numberOfVMetrics = 0,
+    tableVersion=0x00010000,
+    ascent=0,
+    descent=0,
+    lineGap=0,
+    advanceHeightMax=0,
+    minTopSideBearing=0,
+    minBottomSideBearing=0,
+    yMaxExtent=0,
+    caretSlopeRise=0,
+    caretSlopeRun=0,
+    reserved0=0,
+    reserved1=0,
+    reserved2=0,
+    reserved3=0,
+    reserved4=0,
+    metricDataFormat=0,
+    numberOfVMetrics=0,
 )
 
 _nameIDs = dict(
-                         copyright = 0,
-                        familyName = 1,
-                         styleName = 2,
-              uniqueFontIdentifier = 3,
-                          fullName = 4,
-                           version = 5,
-                            psName = 6,
-                         trademark = 7,
-                      manufacturer = 8,
-                          designer = 9,
-                       description = 10,
-                         vendorURL = 11,
-                       designerURL = 12,
-                licenseDescription = 13,
-                    licenseInfoURL = 14,
-                        # reserved = 15,
-                 typographicFamily = 16,
-              typographicSubfamily = 17,
-                compatibleFullName = 18,
-                        sampleText = 19,
-         postScriptCIDFindfontName = 20,
-                     wwsFamilyName = 21,
-                  wwsSubfamilyName = 22,
-            lightBackgroundPalette = 23,
-             darkBackgroundPalette = 24,
-    variationsPostScriptNamePrefix = 25,
+    copyright=0,
+    familyName=1,
+    styleName=2,
+    uniqueFontIdentifier=3,
+    fullName=4,
+    version=5,
+    psName=6,
+    trademark=7,
+    manufacturer=8,
+    designer=9,
+    description=10,
+    vendorURL=11,
+    designerURL=12,
+    licenseDescription=13,
+    licenseInfoURL=14,
+    # reserved = 15,
+    typographicFamily=16,
+    typographicSubfamily=17,
+    compatibleFullName=18,
+    sampleText=19,
+    postScriptCIDFindfontName=20,
+    wwsFamilyName=21,
+    wwsSubfamilyName=22,
+    lightBackgroundPalette=23,
+    darkBackgroundPalette=24,
+    variationsPostScriptNamePrefix=25,
 )
 
 # to insert in setupNameTable doc string:
 # print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1])))
 
 _panoseDefaults = dict(
-    bFamilyType = 0,
-    bSerifStyle = 0,
-    bWeight = 0,
-    bProportion = 0,
-    bContrast = 0,
-    bStrokeVariation = 0,
-    bArmStyle = 0,
-    bLetterForm = 0,
-    bMidline = 0,
-    bXHeight = 0,
+    bFamilyType=0,
+    bSerifStyle=0,
+    bWeight=0,
+    bProportion=0,
+    bContrast=0,
+    bStrokeVariation=0,
+    bArmStyle=0,
+    bLetterForm=0,
+    bMidline=0,
+    bXHeight=0,
 )
 
 _OS2Defaults = dict(
-    version = 3,
-    xAvgCharWidth = 0,
-    usWeightClass = 400,
-    usWidthClass = 5,
-    fsType = 0x0004,  # default: Preview & Print embedding
-    ySubscriptXSize = 0,
-    ySubscriptYSize = 0,
-    ySubscriptXOffset = 0,
-    ySubscriptYOffset = 0,
-    ySuperscriptXSize = 0,
-    ySuperscriptYSize = 0,
-    ySuperscriptXOffset = 0,
-    ySuperscriptYOffset = 0,
-    yStrikeoutSize = 0,
-    yStrikeoutPosition = 0,
-    sFamilyClass = 0,
-    panose = _panoseDefaults,
-    ulUnicodeRange1 = 0,
-    ulUnicodeRange2 = 0,
-    ulUnicodeRange3 = 0,
-    ulUnicodeRange4 = 0,
-    achVendID = "????",
-    fsSelection = 0,
-    usFirstCharIndex = 0,
-    usLastCharIndex = 0,
-    sTypoAscender = 0,
-    sTypoDescender = 0,
-    sTypoLineGap = 0,
-    usWinAscent = 0,
-    usWinDescent = 0,
-    ulCodePageRange1 = 0,
-    ulCodePageRange2 = 0,
-    sxHeight = 0,
-    sCapHeight = 0,
-    usDefaultChar = 0,  # .notdef
-    usBreakChar = 32,   # space
-    usMaxContext = 0,
-    usLowerOpticalPointSize = 0,
-    usUpperOpticalPointSize = 0,
+    version=3,
+    xAvgCharWidth=0,
+    usWeightClass=400,
+    usWidthClass=5,
+    fsType=0x0004,  # default: Preview & Print embedding
+    ySubscriptXSize=0,
+    ySubscriptYSize=0,
+    ySubscriptXOffset=0,
+    ySubscriptYOffset=0,
+    ySuperscriptXSize=0,
+    ySuperscriptYSize=0,
+    ySuperscriptXOffset=0,
+    ySuperscriptYOffset=0,
+    yStrikeoutSize=0,
+    yStrikeoutPosition=0,
+    sFamilyClass=0,
+    panose=_panoseDefaults,
+    ulUnicodeRange1=0,
+    ulUnicodeRange2=0,
+    ulUnicodeRange3=0,
+    ulUnicodeRange4=0,
+    achVendID="????",
+    fsSelection=0,
+    usFirstCharIndex=0,
+    usLastCharIndex=0,
+    sTypoAscender=0,
+    sTypoDescender=0,
+    sTypoLineGap=0,
+    usWinAscent=0,
+    usWinDescent=0,
+    ulCodePageRange1=0,
+    ulCodePageRange2=0,
+    sxHeight=0,
+    sCapHeight=0,
+    usDefaultChar=0,  # .notdef
+    usBreakChar=32,  # space
+    usMaxContext=0,
+    usLowerOpticalPointSize=0,
+    usUpperOpticalPointSize=0,
 )
 
 
 class FontBuilder(object):
-
     def __init__(self, unitsPerEm=None, font=None, isTTF=True):
         """Initialize a FontBuilder instance.
 
@@ -395,7 +394,7 @@
         """
         subTables = []
         highestUnicode = max(cmapping)
-        if highestUnicode > 0xffff:
+        if highestUnicode > 0xFFFF:
             cmapping_3_1 = dict((k, v) for k, v in cmapping.items() if k < 0x10000)
             subTable_3_10 = buildCmapSubTable(cmapping, 12, 3, 10)
             subTables.append(subTable_3_10)
@@ -408,7 +407,9 @@
         except struct.error:
             # format 4 overflowed, fall back to format 12
             if not allowFallback:
-                raise ValueError("cmap format 4 subtable overflowed; sort glyph order by unicode to fix.")
+                raise ValueError(
+                    "cmap format 4 subtable overflowed; sort glyph order by unicode to fix."
+                )
             format = 12
             subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1)
         subTables.append(subTable_3_1)
@@ -489,17 +490,33 @@
         """
         if "xAvgCharWidth" not in values:
             gs = self.font.getGlyphSet()
-            widths = [gs[glyphName].width for glyphName in gs.keys() if gs[glyphName].width > 0]
+            widths = [
+                gs[glyphName].width
+                for glyphName in gs.keys()
+                if gs[glyphName].width > 0
+            ]
             values["xAvgCharWidth"] = int(round(sum(widths) / float(len(widths))))
         self._initTableWithValues("OS/2", _OS2Defaults, values)
-        if not ("ulUnicodeRange1" in values or "ulUnicodeRange2" in values or
-                "ulUnicodeRange3" in values or "ulUnicodeRange3" in values):
-            assert "cmap" in self.font, "the 'cmap' table must be setup before the 'OS/2' table"
+        if not (
+            "ulUnicodeRange1" in values
+            or "ulUnicodeRange2" in values
+            or "ulUnicodeRange3" in values
+            or "ulUnicodeRange3" in values
+        ):
+            assert (
+                "cmap" in self.font
+            ), "the 'cmap' table must be setup before the 'OS/2' table"
             self.font["OS/2"].recalcUnicodeRanges(self.font)
 
     def setupCFF(self, psName, fontInfo, charStringsDict, privateDict):
-        from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \
-                GlobalSubrsIndex, PrivateDict
+        from .cffLib import (
+            CFFFontSet,
+            TopDictIndex,
+            TopDict,
+            CharStrings,
+            GlobalSubrsIndex,
+            PrivateDict,
+        )
 
         assert not self.isTTF
         self.font.sfntVersion = "OTTO"
@@ -528,7 +545,9 @@
             scale = 1 / self.font["head"].unitsPerEm
             topDict.FontMatrix = [scale, 0, 0, scale, 0, 0]
 
-        charStrings = CharStrings(None, topDict.charset, globalSubrs, private, fdSelect, fdArray)
+        charStrings = CharStrings(
+            None, topDict.charset, globalSubrs, private, fdSelect, fdArray
+        )
         for glyphName, charString in charStringsDict.items():
             charString.private = private
             charString.globalSubrs = globalSubrs
@@ -541,8 +560,16 @@
         self.font["CFF "].cff = fontSet
 
     def setupCFF2(self, charStringsDict, fdArrayList=None, regions=None):
-        from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \
-                GlobalSubrsIndex, PrivateDict, FDArrayIndex, FontDict
+        from .cffLib import (
+            CFFFontSet,
+            TopDictIndex,
+            TopDict,
+            CharStrings,
+            GlobalSubrsIndex,
+            PrivateDict,
+            FDArrayIndex,
+            FontDict,
+        )
 
         assert not self.isTTF
         self.font.sfntVersion = "OTTO"
@@ -628,10 +655,40 @@
             self.calcGlyphBounds()
 
     def setupFvar(self, axes, instances):
+        """Adds an font variations table to the font.
+
+        Args:
+            axes (list): See below.
+            instances (list): See below.
+
+        ``axes`` should be a list of axes, with each axis either supplied as
+        a py:class:`.designspaceLib.AxisDescriptor` object, or a tuple in the
+        format ```tupletag, minValue, defaultValue, maxValue, name``.
+        The ``name`` is either a string, or a dict, mapping language codes
+        to strings, to allow localized name table entries.
+
+        ```instances`` should be a list of instances, with each instance either
+        supplied as a py:class:`.designspaceLib.InstanceDescriptor` object, or a
+        dict with keys ``location`` (mapping of axis tags to float values),
+        ``stylename`` and (optionally) ``postscriptfontname``.
+        The ``stylename`` is either a string, or a dict, mapping language codes
+        to strings, to allow localized name table entries.
+        """
+
         addFvar(self.font, axes, instances)
 
+    def setupAvar(self, axes):
+        """Adds an axis variations table to the font.
+
+        Args:
+            axes (list): A list of py:class:`.designspaceLib.AxisDescriptor` objects.
+        """
+        from .varLib import _add_avar
+
+        _add_avar(self.font, OrderedDict(enumerate(axes)))  # Only values are used
+
     def setupGvar(self, variations):
-        gvar = self.font["gvar"] = newTable('gvar')
+        gvar = self.font["gvar"] = newTable("gvar")
         gvar.version = 1
         gvar.reserved = 0
         gvar.variations = variations
@@ -650,7 +707,7 @@
         The `metrics` argument must be a dict, mapping glyph names to
         `(width, leftSidebearing)` tuples.
         """
-        self.setupMetrics('hmtx', metrics)
+        self.setupMetrics("hmtx", metrics)
 
     def setupVerticalMetrics(self, metrics):
         """Create a new `vmtx` table, for horizontal metrics.
@@ -658,7 +715,7 @@
         The `metrics` argument must be a dict, mapping glyph names to
         `(height, topSidebearing)` tuples.
         """
-        self.setupMetrics('vmtx', metrics)
+        self.setupMetrics("vmtx", metrics)
 
     def setupMetrics(self, tableTag, metrics):
         """See `setupHorizontalMetrics()` and `setupVerticalMetrics()`."""
@@ -699,8 +756,14 @@
                     bag[vorg] = 1
                 else:
                     bag[vorg] += 1
-            defaultVerticalOrigin = sorted(bag, key=lambda vorg: bag[vorg], reverse=True)[0]
-        self._initTableWithValues("VORG", {}, dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin))
+            defaultVerticalOrigin = sorted(
+                bag, key=lambda vorg: bag[vorg], reverse=True
+            )[0]
+        self._initTableWithValues(
+            "VORG",
+            {},
+            dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin),
+        )
         vorgTable = self.font["VORG"]
         vorgTable.majorVersion = 1
         vorgTable.minorVersion = 0
@@ -711,7 +774,7 @@
         """Create a new `post` table and initialize it with default values,
         which can be overridden by keyword arguments.
         """
-        isCFF2 = 'CFF2' in self.font
+        isCFF2 = "CFF2" in self.font
         postTable = self._initTableWithValues("post", _postDefaults, values)
         if (self.isTTF or isCFF2) and keepGlyphNames:
             postTable.formatType = 2.0
@@ -735,10 +798,10 @@
         happy. This does not properly sign the font.
         """
         values = dict(
-            ulVersion = 1,
-            usFlag = 0,
-            usNumSigs = 0,
-            signatureRecords = [],
+            ulVersion=1,
+            usFlag=0,
+            usNumSigs=0,
+            signatureRecords=[],
         )
         self._initTableWithValues("DSIG", {}, values)
 
@@ -754,7 +817,10 @@
         `fontTools.feaLib` for details.
         """
         from .feaLib.builder import addOpenTypeFeaturesFromString
-        addOpenTypeFeaturesFromString(self.font, features, filename=filename, tables=tables)
+
+        addOpenTypeFeaturesFromString(
+            self.font, features, filename=filename, tables=tables
+        )
 
     def addFeatureVariations(self, conditionalSubstitutions, featureTag="rvrn"):
         """Add conditional substitutions to a Variable Font.
@@ -770,14 +836,17 @@
             self.font, conditionalSubstitutions, featureTag=featureTag
         )
 
-    def setupCOLR(self, colorLayers):
+    def setupCOLR(self, colorLayers, version=None, varStore=None):
         """Build new COLR table using color layers dictionary.
 
         Cf. `fontTools.colorLib.builder.buildCOLR`.
         """
         from fontTools.colorLib.builder import buildCOLR
 
-        self.font["COLR"] = buildCOLR(colorLayers)
+        glyphMap = self.font.getReverseGlyphMap()
+        self.font["COLR"] = buildCOLR(
+            colorLayers, version=version, glyphMap=glyphMap, varStore=varStore
+        )
 
     def setupCPAL(
         self,
@@ -800,7 +869,7 @@
             paletteTypes=paletteTypes,
             paletteLabels=paletteLabels,
             paletteEntryLabels=paletteEntryLabels,
-            nameTable=self.font.get("name")
+            nameTable=self.font.get("name"),
         )
 
     def setupStat(self, axes, locations=None, elidedFallbackName=2):
@@ -810,6 +879,7 @@
         the arguments.
         """
         from .otlLib.builder import buildStatTable
+
         buildStatTable(self.font, axes, locations, elidedFallbackName)
 
 
@@ -823,32 +893,58 @@
 
 
 def addFvar(font, axes, instances):
-    from .misc.py23 import Tag, tounicode
     from .ttLib.tables._f_v_a_r import Axis, NamedInstance
+    from .designspaceLib import AxisDescriptor
 
     assert axes
 
-    fvar = newTable('fvar')
-    nameTable = font['name']
+    fvar = newTable("fvar")
+    nameTable = font["name"]
 
-    for tag, minValue, defaultValue, maxValue, name in axes:
+    for axis_def in axes:
         axis = Axis()
-        axis.axisTag = Tag(tag)
-        axis.minValue, axis.defaultValue, axis.maxValue = minValue, defaultValue, maxValue
-        axis.axisNameID = nameTable.addName(tounicode(name))
+
+        if isinstance(axis_def, tuple):
+            (
+                axis.axisTag,
+                axis.minValue,
+                axis.defaultValue,
+                axis.maxValue,
+                name,
+            ) = axis_def
+        else:
+            (axis.axisTag, axis.minValue, axis.defaultValue, axis.maxValue, name) = (
+                axis_def.tag,
+                axis_def.minimum,
+                axis_def.default,
+                axis_def.maximum,
+                axis_def.name,
+            )
+
+        if isinstance(name, str):
+            name = dict(en=name)
+
+        axis.axisNameID = nameTable.addMultilingualName(name, ttFont=font)
         fvar.axes.append(axis)
 
     for instance in instances:
-        coordinates = instance['location']
-        name = tounicode(instance['stylename'])
-        psname = instance.get('postscriptfontname')
+        if isinstance(instance, dict):
+            coordinates = instance["location"]
+            name = instance["stylename"]
+            psname = instance.get("postscriptfontname")
+        else:
+            coordinates = instance.location
+            name = instance.localisedStyleName or instance.styleName
+            psname = instance.postScriptFontName
+
+        if isinstance(name, str):
+            name = dict(en=name)
 
         inst = NamedInstance()
-        inst.subfamilyNameID = nameTable.addName(name)
+        inst.subfamilyNameID = nameTable.addMultilingualName(name, ttFont=font)
         if psname is not None:
-            psname = tounicode(psname)
             inst.postscriptNameID = nameTable.addName(psname)
         inst.coordinates = coordinates
         fvar.instances.append(inst)
 
-    font['fvar'] = fvar
+    font["fvar"] = fvar
diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py
index 81b2418..e76ced7 100644
--- a/Lib/fontTools/misc/arrayTools.py
+++ b/Lib/fontTools/misc/arrayTools.py
@@ -313,9 +313,9 @@
     __rmul__ = __mul__
 
     def __truediv__(self, other):
-        return Vector(self._scalarOp(other, operator.div), keep=True)
+        return Vector(self._scalarOp(other, operator.truediv), keep=True)
     def __itruediv__(self, other):
-        self.values = self._scalarOp(other, operator.div)
+        self.values = self._scalarOp(other, operator.truediv)
         return self
 
     def __pos__(self):
diff --git a/Lib/fontTools/pens/ttGlyphPen.py b/Lib/fontTools/pens/ttGlyphPen.py
index 0b64cb3..866298b 100644
--- a/Lib/fontTools/pens/ttGlyphPen.py
+++ b/Lib/fontTools/pens/ttGlyphPen.py
@@ -15,22 +15,34 @@
 class TTGlyphPen(LoggingPen):
     """Pen used for drawing to a TrueType glyph.
 
-    If `handleOverflowingTransforms` is True, the components' transform values
-    are checked that they don't overflow the limits of a F2Dot14 number:
-    -2.0 <= v < +2.0. If any transform value exceeds these, the composite
-    glyph is decomposed.
-    An exception to this rule is done for values that are very close to +2.0
-    (both for consistency with the -2.0 case, and for the relative frequency
-    these occur in real fonts). When almost +2.0 values occur (and all other
-    values are within the range -2.0 <= x <= +2.0), they are clamped to the
-    maximum positive value that can still be encoded as an F2Dot14: i.e.
-    1.99993896484375.
-    If False, no check is done and all components are translated unmodified
-    into the glyf table, followed by an inevitable `struct.error` once an
-    attempt is made to compile them.
+    This pen can be used to construct or modify glyphs in a TrueType format
+    font. After using the pen to draw, use the ``.glyph()`` method to retrieve
+    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
     """
 
     def __init__(self, glyphSet, handleOverflowingTransforms=True):
+        """Construct a new pen.
+
+        Args:
+            glyphSet (ttLib._TTGlyphSet): A glyphset object, used to resolve components.
+            handleOverflowingTransforms (bool): See below.
+
+        If ``handleOverflowingTransforms`` is True, the components' transform values
+        are checked that they don't overflow the limits of a F2Dot14 number:
+        -2.0 <= v < +2.0. If any transform value exceeds these, the composite
+        glyph is decomposed.
+
+        An exception to this rule is done for values that are very close to +2.0
+        (both for consistency with the -2.0 case, and for the relative frequency
+        these occur in real fonts). When almost +2.0 values occur (and all other
+        values are within the range -2.0 <= x <= +2.0), they are clamped to the
+        maximum positive value that can still be encoded as an F2Dot14: i.e.
+        1.99993896484375.
+
+        If False, no check is done and all components are translated unmodified
+        into the glyf table, followed by an inevitable ``struct.error`` once an
+        attempt is made to compile them.
+        """
         self.glyphSet = glyphSet
         self.handleOverflowingTransforms = handleOverflowingTransforms
         self.init()
@@ -136,6 +148,7 @@
         return components
 
     def glyph(self, componentFlags=0x4):
+        """Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph."""
         assert self._isClosed(), "Didn't close last contour."
 
         components = self._buildComponents(componentFlags)
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index 82605d5..8162c09 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -14,7 +14,7 @@
 import struct
 import array
 import logging
-from collections import Counter
+from collections import Counter, defaultdict
 from types import MethodType
 
 __usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
@@ -1983,27 +1983,130 @@
 	else:
 		assert False, "unknown 'prop' format %s" % prop.Format
 
+def _paint_glyph_names(paint, colr):
+	result = set()
+
+	def callback(paint):
+		if paint.Format in {
+			otTables.PaintFormat.PaintGlyph,
+			otTables.PaintFormat.PaintColrGlyph,
+		}:
+			result.add(paint.Glyph)
+
+	paint.traverse(colr, callback)
+	return result
+
 @_add_method(ttLib.getTableClass('COLR'))
 def closure_glyphs(self, s):
+	if self.version > 0:
+		# on decompiling COLRv1, we only keep around the raw otTables
+		# but for subsetting we need dicts with fully decompiled layers;
+		# we store them temporarily in the C_O_L_R_ instance and delete
+		# them after we have finished subsetting.
+		self.ColorLayers = self._decompileColorLayersV0(self.table)
+		self.ColorLayersV1 = {
+			rec.BaseGlyph: rec.Paint
+			for rec in self.table.BaseGlyphV1List.BaseGlyphV1Record
+		}
+
 	decompose = s.glyphs
 	while decompose:
 		layers = set()
 		for g in decompose:
-			for l in self.ColorLayers.get(g, []):
-				layers.add(l.name)
+			for layer in self.ColorLayers.get(g, []):
+				layers.add(layer.name)
+
+			if self.version > 0:
+				paint = self.ColorLayersV1.get(g)
+				if paint is not None:
+					layers.update(_paint_glyph_names(paint, self.table))
+
 		layers -= s.glyphs
 		s.glyphs.update(layers)
 		decompose = layers
 
 @_add_method(ttLib.getTableClass('COLR'))
 def subset_glyphs(self, s):
-	self.ColorLayers = {g: self.ColorLayers[g] for g in s.glyphs if g in self.ColorLayers}
-	return bool(self.ColorLayers)
+	from fontTools.colorLib.unbuilder import unbuildColrV1
+	from fontTools.colorLib.builder import buildColrV1, populateCOLRv0
 
-# TODO: prune unused palettes
+	self.ColorLayers = {g: self.ColorLayers[g] for g in s.glyphs if g in self.ColorLayers}
+	if self.version == 0:
+		return bool(self.ColorLayers)
+
+	colorGlyphsV1 = unbuildColrV1(self.table.LayerV1List, self.table.BaseGlyphV1List)
+	self.table.LayerV1List, self.table.BaseGlyphV1List = buildColrV1(
+		{g: colorGlyphsV1[g] for g in colorGlyphsV1 if g in s.glyphs}
+	)
+	del self.ColorLayersV1
+
+	layersV0 = self.ColorLayers
+	if not self.table.BaseGlyphV1List.BaseGlyphV1Record:
+		# no more COLRv1 glyphs: downgrade to version 0
+		self.version = 0
+		del self.table
+		return bool(layersV0)
+
+	if layersV0:
+		populateCOLRv0(
+			self.table,
+			{
+				g: [(layer.name, layer.colorID) for layer in layersV0[g]]
+				for g in layersV0
+			},
+		)
+	del self.ColorLayers
+
+	# TODO: also prune ununsed varIndices in COLR.VarStore
+	return True
+
 @_add_method(ttLib.getTableClass('CPAL'))
 def prune_post_subset(self, font, options):
-	return True
+	colr = font.get("COLR")
+	if not colr:  # drop CPAL if COLR was subsetted to empty
+		return False
+
+	colors_by_index = defaultdict(list)
+
+	def collect_colors_by_index(paint):
+		if hasattr(paint, "Color"):  # either solid colors...
+			colors_by_index[paint.Color.PaletteIndex].append(paint.Color)
+		elif hasattr(paint, "ColorLine"):  # ... or gradient color stops
+			for stop in paint.ColorLine.ColorStop:
+				colors_by_index[stop.Color.PaletteIndex].append(stop.Color)
+
+	if colr.version == 0:
+		for layers in colr.ColorLayers.values():
+			for layer in layers:
+				colors_by_index[layer.colorID].append(layer)
+	else:
+		if colr.table.LayerRecordArray:
+			for layer in colr.table.LayerRecordArray.LayerRecord:
+				colors_by_index[layer.PaletteIndex].append(layer)
+		for record in colr.table.BaseGlyphV1List.BaseGlyphV1Record:
+			record.Paint.traverse(colr.table, collect_colors_by_index)
+
+	retained_palette_indices = set(colors_by_index.keys())
+	for palette in self.palettes:
+		palette[:] = [c for i, c in enumerate(palette) if i in retained_palette_indices]
+		assert len(palette) == len(retained_palette_indices)
+
+	for new_index, old_index in enumerate(sorted(retained_palette_indices)):
+		for record in colors_by_index[old_index]:
+			if hasattr(record, "colorID"):  # v0
+				record.colorID = new_index
+			elif hasattr(record, "PaletteIndex"):  # v1
+				record.PaletteIndex = new_index
+			else:
+				raise AssertionError(record)
+
+	self.numPaletteEntries = len(self.palettes[0])
+
+	if self.version == 1:
+		self.paletteEntryLabels = [
+			label for i, label in self.paletteEntryLabels if i in retained_palette_indices
+		]
+	return bool(self.numPaletteEntries)
 
 @_add_method(otTables.MathGlyphConstruction)
 def closure_glyphs(self, glyphs):
diff --git a/Lib/fontTools/ttLib/tables/C_O_L_R_.py b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
index 7a9442d..db49052 100644
--- a/Lib/fontTools/ttLib/tables/C_O_L_R_.py
+++ b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
@@ -14,9 +14,11 @@
 	ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph.
 	"""
 
-	def _fromOTTable(self, table):
-		self.version = 0
-		self.ColorLayers = colorLayerLists = {}
+	@staticmethod
+	def _decompileColorLayersV0(table):
+		if not table.LayerRecordArray:
+			return {}
+		colorLayerLists = {}
 		layerRecords = table.LayerRecordArray.LayerRecord
 		numLayerRecords = len(layerRecords)
 		for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
@@ -31,6 +33,7 @@
 					LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex)
 				)
 			colorLayerLists[baseGlyph] = layers
+		return colorLayerLists
 
 	def _toOTTable(self, ttFont):
 		from . import otTables
@@ -61,12 +64,12 @@
 		table = tableClass()
 		table.decompile(reader, ttFont)
 
-		if table.Version == 0:
-			self._fromOTTable(table)
+		self.version = table.Version
+		if self.version == 0:
+			self.ColorLayers = self._decompileColorLayersV0(table)
 		else:
 			# for new versions, keep the raw otTables around
 			self.table = table
-			self.version = table.Version
 
 	def compile(self, ttFont):
 		from .otBase import OTTableWriter
@@ -120,6 +123,7 @@
 				self.table = tableClass()
 			self.table.fromXML(name, attrs, content, ttFont)
 			self.table.populateDefaults()
+			self.version = self.table.Version
 
 	def __getitem__(self, glyphName):
 		if not isinstance(glyphName, str):
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index 1b27841..96d461a 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -59,14 +59,20 @@
 				converterClass = Struct
 			else:
 				converterClass = eval(tp, tableNamespace, converterMapping)
-		if tp in ('MortChain', 'MortSubtable', 'MorxChain'):
+
+		conv = converterClass(name, repeat, aux)
+
+		if conv.tableClass:
+			# A "template" such as OffsetTo(AType) knowss the table class already
+			tableClass = conv.tableClass
+		elif tp in ('MortChain', 'MortSubtable', 'MorxChain'):
 			tableClass = tableNamespace.get(tp)
 		else:
 			tableClass = tableNamespace.get(tableName)
-		if tableClass is not None:
-			conv = converterClass(name, repeat, aux, tableClass=tableClass)
-		else:
-			conv = converterClass(name, repeat, aux)
+
+		if not conv.tableClass:
+			conv.tableClass = tableClass
+
 		if name in ["SubTable", "ExtSubTable", "SubStruct"]:
 			conv.lookupTypes = tableNamespace['lookupTypes']
 			# also create reverse mapping
diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py
index a6f9619..28b40c4 100755
--- a/Lib/fontTools/ttLib/tables/otData.py
+++ b/Lib/fontTools/ttLib/tables/otData.py
@@ -1588,7 +1588,24 @@
 		('LOffset', 'Paint', 'LayerCount', 0, 'Array of offsets to Paint tables, from the start of the LayerV1List table.'),
 	]),
 
+	# COLRv1 Affine2x3 uses the same column-major order to serialize a 2D
+	# Affine Transformation as the one used by fontTools.misc.transform.
+	# However, for historical reasons, the labels 'xy' and 'yx' are swapped.
+	# Their fundamental meaning is the same though.
+	# COLRv1 Affine2x3 follows the names found in FreeType and Cairo.
+	# In all case, the second element in the 6-tuple correspond to the
+	# y-part of the x basis vector, and the third to the x-part of the y
+	# basis vector.
+	# See https://github.com/googlefonts/colr-gradients-spec/pull/85
 	('Affine2x3', [
+		('Fixed', 'xx', None, None, 'x-part of x basis vector'),
+		('Fixed', 'yx', None, None, 'y-part of x basis vector'),
+		('Fixed', 'xy', None, None, 'x-part of y basis vector'),
+		('Fixed', 'yy', None, None, 'y-part of y basis vector'),
+		('Fixed', 'dx', None, None, 'Translation in x direction'),
+		('Fixed', 'dy', None, None, 'Translation in y direction'),
+	]),
+	('VarAffine2x3', [
 		('VarFixed', 'xx', None, None, 'x-part of x basis vector'),
 		('VarFixed', 'yx', None, None, 'y-part of x basis vector'),
 		('VarFixed', 'xy', None, None, 'x-part of y basis vector'),
@@ -1599,34 +1616,66 @@
 
 	('ColorIndex', [
 		('uint16', 'PaletteIndex', None, None, 'Index value to use with a selected color palette.'),
+		('F2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved'),
+	]),
+	('VarColorIndex', [
+		('uint16', 'PaletteIndex', None, None, 'Index value to use with a selected color palette.'),
 		('VarF2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved'),
 	]),
 
 	('ColorStop', [
-		('VarF2Dot14', 'StopOffset', None, None, ''),
+		('F2Dot14', 'StopOffset', None, None, ''),
 		('ColorIndex', 'Color', None, None, ''),
 	]),
+	('VarColorStop', [
+		('VarF2Dot14', 'StopOffset', None, None, ''),
+		('VarColorIndex', 'Color', None, None, ''),
+	]),
 
 	('ColorLine', [
 		('ExtendMode', 'Extend', None, None, 'Enum {PAD = 0, REPEAT = 1, REFLECT = 2}'),
 		('uint16', 'StopCount', None, None, 'Number of Color stops.'),
 		('ColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'),
 	]),
+	('VarColorLine', [
+		('ExtendMode', 'Extend', None, None, 'Enum {PAD = 0, REPEAT = 1, REFLECT = 2}'),
+		('uint16', 'StopCount', None, None, 'Number of Color stops.'),
+		('VarColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'),
+	]),
 
+	# PaintColrLayers
 	('PaintFormat1', [
 		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 1'),
 		('uint8', 'NumLayers', None, None, 'Number of offsets to Paint to read from LayerV1List.'),
 		('uint32', 'FirstLayerIndex', None, None, 'Index into LayerV1List.'),
 	]),
 
+	# PaintSolid
 	('PaintFormat2', [
 		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 2'),
 		('ColorIndex', 'Color', None, None, 'A solid color paint.'),
 	]),
-
+	# PaintVarSolid
 	('PaintFormat3', [
 		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 3'),
-		('Offset24', 'ColorLine', None, None, 'Offset (from beginning of Paint table) to ColorLine subtable.'),
+		('VarColorIndex', 'Color', None, None, 'A solid color paint.'),
+	]),
+
+	# PaintLinearGradient
+	('PaintFormat4', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 4'),
+		('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintLinearGradient table) to ColorLine subtable.'),
+		('int16', 'x0', None, None, ''),
+		('int16', 'y0', None, None, ''),
+		('int16', 'x1', None, None, ''),
+		('int16', 'y1', None, None, ''),
+		('int16', 'x2', None, None, ''),
+		('int16', 'y2', None, None, ''),
+	]),
+	# PaintVarLinearGradient
+	('PaintFormat5', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 5'),
+		('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarLinearGradient table) to VarColorLine subtable.'),
 		('VarInt16', 'x0', None, None, ''),
 		('VarInt16', 'y0', None, None, ''),
 		('VarInt16', 'x1', None, None, ''),
@@ -1635,9 +1684,21 @@
 		('VarInt16', 'y2', None, None, ''),
 	]),
 
-	('PaintFormat4', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 4'),
-		('Offset24', 'ColorLine', None, None, 'Offset (from beginning of Paint table) to ColorLine subtable.'),
+	# PaintRadialGradient
+	('PaintFormat6', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 6'),
+		('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintRadialGradient table) to ColorLine subtable.'),
+		('int16', 'x0', None, None, ''),
+		('int16', 'y0', None, None, ''),
+		('uint16', 'r0', None, None, ''),
+		('int16', 'x1', None, None, ''),
+		('int16', 'y1', None, None, ''),
+		('uint16', 'r1', None, None, ''),
+	]),
+	# PaintVarRadialGradient
+	('PaintFormat7', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 7'),
+		('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarRadialGradient table) to VarColorLine subtable.'),
 		('VarInt16', 'x0', None, None, ''),
 		('VarInt16', 'y0', None, None, ''),
 		('VarUInt16', 'r0', None, None, ''),
@@ -1646,49 +1707,105 @@
 		('VarUInt16', 'r1', None, None, ''),
 	]),
 
-	('PaintFormat5', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 5'),
+	# PaintSweepGradient
+	('PaintFormat8', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 8'),
+		('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintSweepGradient table) to ColorLine subtable.'),
+		('int16', 'centerX', None, None, 'Center x coordinate.'),
+		('int16', 'centerY', None, None, 'Center y coordinate.'),
+		('Fixed', 'startAngle', None, None, 'Start of the angular range of the gradient.'),
+		('Fixed', 'endAngle', None, None, 'End of the angular range of the gradient.'),
+	]),
+	# PaintVarSweepGradient
+	('PaintFormat9', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'),
+		('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarSweepGradient table) to VarColorLine subtable.'),
+		('VarInt16', 'centerX', None, None, 'Center x coordinate.'),
+		('VarInt16', 'centerY', None, None, 'Center y coordinate.'),
+		('VarFixed', 'startAngle', None, None, 'Start of the angular range of the gradient.'),
+		('VarFixed', 'endAngle', None, None, 'End of the angular range of the gradient.'),
+	]),
+
+	# PaintGlyph
+	('PaintFormat10', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'),
 		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintGlyph table) to Paint subtable.'),
 		('GlyphID', 'Glyph', None, None, 'Glyph ID for the source outline.'),
 	]),
 
-	('PaintFormat6', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 6'),
+	# PaintColrGlyph
+	('PaintFormat11', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'),
 		('GlyphID', 'Glyph', None, None, 'Virtual glyph ID for a BaseGlyphV1List base glyph.'),
 	]),
 
-	('PaintFormat7', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 7'),
-		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransformed table) to Paint subtable.'),
-		('Affine2x3', 'Transform', None, None, 'Offset (from beginning of PaintTrasformed table) to Affine2x3 subtable.'),
+	# PaintTransform
+	('PaintFormat12', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 12'),
+		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransform table) to Paint subtable.'),
+		('Affine2x3', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'),
+	]),
+	# PaintVarTransform
+	('PaintFormat13', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 13'),
+		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarTransform table) to Paint subtable.'),
+		('VarAffine2x3', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'),
 	]),
 
-	('PaintFormat8', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 8'),
+	# PaintTranslate
+	('PaintFormat14', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 14'),
 		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTranslate table) to Paint subtable.'),
+		('Fixed', 'dx', None, None, 'Translation in x direction.'),
+		('Fixed', 'dy', None, None, 'Translation in y direction.'),
+	]),
+	# PaintVarTranslate
+	('PaintFormat15', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 15'),
+		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarTranslate table) to Paint subtable.'),
 		('VarFixed', 'dx', None, None, 'Translation in x direction.'),
 		('VarFixed', 'dy', None, None, 'Translation in y direction.'),
 	]),
 
-	('PaintFormat9', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'),
+	# PaintRotate
+	('PaintFormat16', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 16'),
 		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintRotate table) to Paint subtable.'),
+		('Fixed', 'angle', None, None, ''),
+		('Fixed', 'centerX', None, None, ''),
+		('Fixed', 'centerY', None, None, ''),
+	]),
+	# PaintVarRotate
+	('PaintFormat17', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 17'),
+		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarRotate table) to Paint subtable.'),
 		('VarFixed', 'angle', None, None, ''),
 		('VarFixed', 'centerX', None, None, ''),
 		('VarFixed', 'centerY', None, None, ''),
 	]),
 
-	('PaintFormat10', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'),
+	# PaintSkew
+	('PaintFormat18', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 18'),
 		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintSkew table) to Paint subtable.'),
+		('Fixed', 'xSkewAngle', None, None, ''),
+		('Fixed', 'ySkewAngle', None, None, ''),
+		('Fixed', 'centerX', None, None, ''),
+		('Fixed', 'centerY', None, None, ''),
+	]),
+	# PaintVarSkew
+	('PaintFormat19', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 19'),
+		('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarSkew table) to Paint subtable.'),
 		('VarFixed', 'xSkewAngle', None, None, ''),
 		('VarFixed', 'ySkewAngle', None, None, ''),
 		('VarFixed', 'centerX', None, None, ''),
 		('VarFixed', 'centerY', None, None, ''),
 	]),
 
-	('PaintFormat11', [
-		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'),
+	# PaintComposite
+	('PaintFormat20', [
+		('uint8', 'PaintFormat', None, None, 'Format identifier-format = 20'),
 		('LOffset24To(Paint)', 'SourcePaint', None, None, 'Offset (from beginning of PaintComposite table) to source Paint subtable.'),
 		('CompositeMode', 'CompositeMode', None, None, 'A CompositeMode enumeration value.'),
 		('LOffset24To(Paint)', 'BackdropPaint', None, None, 'Offset (from beginning of PaintComposite table) to backdrop Paint subtable.'),
diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index 7f42921..008909b 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -1324,24 +1324,34 @@
 	HSL_LUMINOSITY = 26
 
 
-class Paint(getFormatSwitchingBaseTableClass("uint8")):
+class PaintFormat(IntEnum):
+	PaintColrLayers = 1
+	PaintSolid = 2
+	PaintVarSolid = 3,
+	PaintLinearGradient = 4
+	PaintVarLinearGradient = 5
+	PaintRadialGradient = 6
+	PaintVarRadialGradient = 7
+	PaintSweepGradient = 8
+	PaintVarSweepGradient = 9
+	PaintGlyph = 10
+	PaintColrGlyph = 11
+	PaintTransform = 12
+	PaintVarTransform = 13
+	PaintTranslate = 14
+	PaintVarTranslate = 15
+	PaintRotate = 16
+	PaintVarRotate = 17
+	PaintSkew = 18
+	PaintVarSkew = 19
+	PaintComposite = 20
 
-	class Format(IntEnum):
-		PaintColrLayers = 1
-		PaintSolid = 2
-		PaintLinearGradient = 3
-		PaintRadialGradient = 4
-		PaintGlyph = 5
-		PaintColrGlyph = 6
-		PaintTransform = 7
-		PaintTranslate = 8
-		PaintRotate = 9
-		PaintSkew = 10
-		PaintComposite = 11
+
+class Paint(getFormatSwitchingBaseTableClass("uint8")):
 
 	def getFormatName(self):
 		try:
-			return self.__class__.Format(self.Format).name
+			return PaintFormat(self.Format).name
 		except ValueError:
 			raise NotImplementedError(f"Unknown Paint format: {self.Format}")
 
@@ -1357,6 +1367,40 @@
 		xmlWriter.endtag(tableName)
 		xmlWriter.newline()
 
+	def getChildren(self, colr):
+		if self.Format == PaintFormat.PaintColrLayers:
+			return colr.LayerV1List.Paint[
+				self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers
+			]
+
+		if self.Format == PaintFormat.PaintColrGlyph:
+			for record in colr.BaseGlyphV1List.BaseGlyphV1Record:
+				if record.BaseGlyph == self.Glyph:
+					return [record.Paint]
+			else:
+				raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphV1List")
+
+		children = []
+		for conv in self.getConverters():
+			if conv.tableClass is not None and issubclass(conv.tableClass, type(self)):
+				children.append(getattr(self, conv.name))
+
+		return children
+
+	def traverse(self, colr: COLR, callback):
+		"""Depth-first traversal of graph rooted at self, callback on each node."""
+		if not callable(callback):
+			raise TypeError("callback must be callable")
+		stack = [self]
+		visited = set()
+		while stack:
+			current = stack.pop()
+			if id(current) in visited:
+				continue
+			callback(current)
+			visited.add(id(current))
+			stack.extend(reversed(current.getChildren(colr)))
+
 
 # For each subtable format there is a class. However, we don't really distinguish
 # between "field name" and "format name": often these are the same. Yet there's
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py
index ed1ec5e..811cf00 100644
--- a/Lib/fontTools/ttLib/ttFont.py
+++ b/Lib/fontTools/ttLib/ttFont.py
@@ -700,6 +700,13 @@
 	"""
 
 	def __init__(self, ttFont, glyphs, glyphType):
+		"""Construct a new glyphset.
+
+		Args:
+			font (TTFont): The font object (used to get metrics).
+			glyphs (dict): A dictionary mapping glyph names to ``_TTGlyph`` objects.
+			glyphType (class): Either ``_TTGlyphCFF`` or ``_TTGlyphGlyf``.
+		"""
 		self._glyphs = glyphs
 		self._hmtx = ttFont['hmtx']
 		self._vmtx = ttFont['vmtx'] if 'vmtx' in ttFont else None
@@ -740,6 +747,13 @@
 	"""
 
 	def __init__(self, glyphset, glyph, horizontalMetrics, verticalMetrics=None):
+		"""Construct a new _TTGlyph.
+
+		Args:
+			glyphset (_TTGlyphSet): A glyphset object used to resolve components.
+			glyph (ttLib.tables._g_l_y_f.Glyph): The glyph object.
+			horizontalMetrics (int, int): The glyph's width and left sidebearing.
+		"""
 		self._glyphset = glyphset
 		self._glyph = glyph
 		self.width, self.lsb = horizontalMetrics
@@ -749,7 +763,7 @@
 			self.height, self.tsb = None, None
 
 	def draw(self, pen):
-		"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
+		"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
 		how that works.
 		"""
 		self._glyph.draw(pen)
diff --git a/METADATA b/METADATA
index 478bd7e..6bb4a20 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,6 @@
+# *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS.  PLEASE
+#     CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
+#     DEPENDING ON IT IN YOUR PROJECT. ***
 name: "fonttools"
 description: "fontTools is a library for manipulating fonts, written in Python."
 third_party {
@@ -7,13 +10,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://github.com/fonttools/fonttools/archive/4.19.1.zip"
+    value: "https://github.com/fonttools/fonttools/archive/4.20.0.zip"
   }
-  version: "4.19.1"
+  version: "4.20.0"
   license_type: BY_EXCEPTION_ONLY
   last_upgrade_date {
     year: 2021
-    month: 1
-    day: 28
+    month: 2
+    day: 17
   }
 }
diff --git a/NEWS.rst b/NEWS.rst
index 776deef..393795c 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,22 @@
+4.20.0 (released 2021-02-15)
+----------------------------
+
+- [COLRv1] Added ``unbuildColrV1`` to deconstruct COLRv1 otTables to raw json-able
+  data structure; it does the reverse of ``buildColrV1`` (#2171).
+- [feaLib] Allow ``sub X by NULL`` sequence to delete a glyph (#2170).
+- [arrayTools] Fixed ``Vector`` division (#2173).
+- [COLRv1] Define new ``PaintSweepGradient`` (#2172).
+- [otTables] Moved ``Paint.Format`` enum class outside of ``Paint`` class definition,
+  now named ``PaintFormat``. It was clashing with paint instance ``Format`` attribute
+  and thus was breaking lazy load of COLR table which relies on magic ``__getattr__``
+  (#2175).
+- [COLRv1] Replace hand-coded builder functions with otData-driven dynamic
+  implementation (#2181).
+- [COLRv1] Define additional static (non-variable) Paint formats (#2181).
+- [fontBuilder] Allow ``setupFvar`` to optionally take ``designspaceLib.AxisDescriptor``
+  objects. Added new ``setupAvar`` method. Support localised names for axes and
+  named instances (#2185).
+
 4.19.1 (released 2021-01-28)
 ----------------------------
 
diff --git a/Tests/colorLib/builder_test.py b/Tests/colorLib/builder_test.py
index 43ec96a..81da281 100644
--- a/Tests/colorLib/builder_test.py
+++ b/Tests/colorLib/builder_test.py
@@ -3,11 +3,20 @@
 from fontTools.colorLib import builder
 from fontTools.colorLib.geometry import round_start_circle_stable_containment, Circle
 from fontTools.colorLib.builder import LayerV1ListBuilder, _build_n_ary_tree
+from fontTools.colorLib.table_builder import TableBuilder
 from fontTools.colorLib.errors import ColorLibError
 import pytest
 from typing import List
 
 
+def _build(cls, source):
+    return LayerV1ListBuilder().tableBuilder.build(cls, source)
+
+
+def _buildPaint(source):
+    return LayerV1ListBuilder().buildPaint(source)
+
+
 def test_buildCOLR_v0():
     color_layer_lists = {
         "a": [("a.color0", 0), ("a.color1", 1)],
@@ -222,191 +231,361 @@
         builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, -1, 2)]])
 
 
-def test_buildColorIndex():
-    c = builder.buildColorIndex(0)
-    assert c.PaletteIndex == 0
+def test_buildColorIndex_Minimal():
+    c = _build(ot.ColorIndex, 1)
+    assert c.PaletteIndex == 1
+    assert c.Alpha == 1.0
+
+
+def test_buildVarColorIndex_Minimal():
+    c = _build(ot.VarColorIndex, 1)
+    assert c.PaletteIndex == 1
     assert c.Alpha.value == 1.0
     assert c.Alpha.varIdx == 0
 
-    c = builder.buildColorIndex(1, alpha=0.5)
-    assert c.PaletteIndex == 1
-    assert c.Alpha.value == 0.5
-    assert c.Alpha.varIdx == 0
 
-    c = builder.buildColorIndex(3, alpha=builder.VariableFloat(0.5, varIdx=2))
+def test_buildColorIndex():
+    c = _build(ot.ColorIndex, (1, 0.5))
+    assert c.PaletteIndex == 1
+    assert c.Alpha == 0.5
+
+
+def test_buildVarColorIndex():
+    c = _build(ot.VarColorIndex, (3, builder.VariableFloat(0.5, varIdx=2)))
     assert c.PaletteIndex == 3
     assert c.Alpha.value == 0.5
     assert c.Alpha.varIdx == 2
 
 
 def test_buildPaintSolid():
-    p = LayerV1ListBuilder().buildPaintSolid(0)
-    assert p.Format == ot.Paint.Format.PaintSolid
+    p = _buildPaint((ot.PaintFormat.PaintSolid, 0))
+    assert p.Format == ot.PaintFormat.PaintSolid
     assert p.Color.PaletteIndex == 0
-    assert p.Color.Alpha.value == 1.0
-    assert p.Color.Alpha.varIdx == 0
+    assert p.Color.Alpha == 1.0
 
-    p = LayerV1ListBuilder().buildPaintSolid(1, alpha=0.5)
-    assert p.Format == ot.Paint.Format.PaintSolid
+
+def test_buildPaintSolid_Alpha():
+    p = _buildPaint((ot.PaintFormat.PaintSolid, (1, 0.5)))
+    assert p.Format == ot.PaintFormat.PaintSolid
     assert p.Color.PaletteIndex == 1
-    assert p.Color.Alpha.value == 0.5
-    assert p.Color.Alpha.varIdx == 0
+    assert p.Color.Alpha == 0.5
 
-    p = LayerV1ListBuilder().buildPaintSolid(
-        3, alpha=builder.VariableFloat(0.5, varIdx=2)
+
+def test_buildPaintVarSolid():
+    p = _buildPaint(
+        (ot.PaintFormat.PaintVarSolid, (3, builder.VariableFloat(0.5, varIdx=2)))
     )
-    assert p.Format == ot.Paint.Format.PaintSolid
+    assert p.Format == ot.PaintFormat.PaintVarSolid
     assert p.Color.PaletteIndex == 3
     assert p.Color.Alpha.value == 0.5
     assert p.Color.Alpha.varIdx == 2
 
 
-def test_buildColorStop():
-    s = builder.buildColorStop(0.1, 2)
+def test_buildVarColorStop_DefaultAlpha():
+    s = _build(ot.ColorStop, (0.1, 2))
+    assert s.StopOffset == 0.1
+    assert s.Color.PaletteIndex == 2
+    assert s.Color.Alpha == builder._DEFAULT_ALPHA.value
+
+
+def test_buildVarColorStop_DefaultAlpha():
+    s = _build(ot.VarColorStop, (0.1, 2))
     assert s.StopOffset == builder.VariableFloat(0.1)
     assert s.Color.PaletteIndex == 2
     assert s.Color.Alpha == builder._DEFAULT_ALPHA
 
-    s = builder.buildColorStop(offset=0.2, paletteIndex=3, alpha=0.4)
-    assert s.StopOffset == builder.VariableFloat(0.2)
-    assert s.Color == builder.buildColorIndex(3, alpha=0.4)
 
-    s = builder.buildColorStop(
-        offset=builder.VariableFloat(0.0, varIdx=1),
-        paletteIndex=0,
-        alpha=builder.VariableFloat(0.3, varIdx=2),
+def test_buildColorStop():
+    s = _build(
+        ot.ColorStop, {"StopOffset": 0.2, "Color": {"PaletteIndex": 3, "Alpha": 0.4}}
+    )
+    assert s.StopOffset == 0.2
+    assert s.Color == _build(ot.ColorIndex, (3, 0.4))
+
+
+def test_buildColorStop_Variable():
+    s = _build(
+        ot.VarColorStop,
+        {
+            "StopOffset": builder.VariableFloat(0.0, varIdx=1),
+            "Color": {
+                "PaletteIndex": 0,
+                "Alpha": builder.VariableFloat(0.3, varIdx=2),
+            },
+        },
     )
     assert s.StopOffset == builder.VariableFloat(0.0, varIdx=1)
     assert s.Color.PaletteIndex == 0
     assert s.Color.Alpha == builder.VariableFloat(0.3, varIdx=2)
 
 
-def test_buildColorLine():
+def test_buildColorLine_StopList():
     stops = [(0.0, 0), (0.5, 1), (1.0, 2)]
 
-    cline = builder.buildColorLine(stops)
+    cline = _build(ot.ColorLine, {"ColorStop": stops})
     assert cline.Extend == builder.ExtendMode.PAD
     assert cline.StopCount == 3
-    assert [
-        (cs.StopOffset.value, cs.Color.PaletteIndex) for cs in cline.ColorStop
-    ] == stops
+    assert [(cs.StopOffset, cs.Color.PaletteIndex) for cs in cline.ColorStop] == stops
 
-    cline = builder.buildColorLine(stops, extend="pad")
+    cline = _build(ot.ColorLine, {"Extend": "pad", "ColorStop": stops})
     assert cline.Extend == builder.ExtendMode.PAD
 
-    cline = builder.buildColorLine(stops, extend=builder.ExtendMode.REPEAT)
+    cline = _build(
+        ot.ColorLine, {"ColorStop": stops, "Extend": builder.ExtendMode.REPEAT}
+    )
     assert cline.Extend == builder.ExtendMode.REPEAT
 
-    cline = builder.buildColorLine(stops, extend=builder.ExtendMode.REFLECT)
+    cline = _build(
+        ot.ColorLine, {"ColorStop": stops, "Extend": builder.ExtendMode.REFLECT}
+    )
     assert cline.Extend == builder.ExtendMode.REFLECT
 
-    cline = builder.buildColorLine([builder.buildColorStop(*s) for s in stops])
-    assert [
-        (cs.StopOffset.value, cs.Color.PaletteIndex) for cs in cline.ColorStop
-    ] == stops
+    cline = _build(
+        ot.ColorLine, {"ColorStop": [_build(ot.ColorStop, s) for s in stops]}
+    )
+    assert [(cs.StopOffset, cs.Color.PaletteIndex) for cs in cline.ColorStop] == stops
 
+
+def test_buildVarColorLine_StopMap():
     stops = [
-        {"offset": (0.0, 1), "paletteIndex": 0, "alpha": (0.5, 2)},
-        {"offset": (1.0, 3), "paletteIndex": 1, "alpha": (0.3, 4)},
+        {"StopOffset": (0.0, (1,)), "Color": {"PaletteIndex": 0, "Alpha": (0.5, 2)}},
+        {"StopOffset": (1.0, (3,)), "Color": {"PaletteIndex": 1, "Alpha": (0.3, 4)}},
     ]
-    cline = builder.buildColorLine(stops)
+    cline = _build(ot.VarColorLine, {"ColorStop": stops})
     assert [
         {
-            "offset": cs.StopOffset,
-            "paletteIndex": cs.Color.PaletteIndex,
-            "alpha": cs.Color.Alpha,
+            "StopOffset": cs.StopOffset,
+            "Color": {
+                "PaletteIndex": cs.Color.PaletteIndex,
+                "Alpha": cs.Color.Alpha,
+            },
         }
         for cs in cline.ColorStop
     ] == stops
 
 
+def checkBuildAffine2x3(cls, resultMapFn):
+    matrix = _build(cls, (1.5, 0, 0.5, 2.0, 1.0, -3.0))
+    assert matrix.xx == resultMapFn(1.5)
+    assert matrix.yx == resultMapFn(0.0)
+    assert matrix.xy == resultMapFn(0.5)
+    assert matrix.yy == resultMapFn(2.0)
+    assert matrix.dx == resultMapFn(1.0)
+    assert matrix.dy == resultMapFn(-3.0)
+
+
 def test_buildAffine2x3():
-    matrix = builder.buildAffine2x3((1.5, 0, 0.5, 2.0, 1.0, -3.0))
-    assert matrix.xx == builder.VariableFloat(1.5)
-    assert matrix.yx == builder.VariableFloat(0.0)
-    assert matrix.xy == builder.VariableFloat(0.5)
-    assert matrix.yy == builder.VariableFloat(2.0)
-    assert matrix.dx == builder.VariableFloat(1.0)
-    assert matrix.dy == builder.VariableFloat(-3.0)
+    checkBuildAffine2x3(ot.Affine2x3, lambda v: v)
+
+
+def test_buildVarAffine2x3():
+    checkBuildAffine2x3(ot.VarAffine2x3, builder.VariableFloat)
+
+
+def _sample_stops(cls):
+    return [
+        _build(cls, (0.0, 0)),
+        _build(cls, (0.5, 1)),
+        _build(cls, (1.0, (2, 0.8))),
+    ]
+
+
+def _is_var(fmt):
+    return fmt.name.startswith("PaintVar")
+
+
+def checkBuildPaintLinearGradient(fmt):
+    if _is_var(fmt):
+        inputMapFn = builder.VariableInt
+        outputMapFn = lambda v: v.value
+        color_stops = _sample_stops(ot.VarColorStop)
+    else:
+        inputMapFn = outputMapFn = lambda v: v
+        color_stops = _sample_stops(ot.ColorStop)
+
+    x0, y0, x1, y1, x2, y2 = tuple(inputMapFn(v) for v in (1, 2, 3, 4, 5, 6))
+    gradient = _buildPaint(
+        {
+            "Format": fmt,
+            "ColorLine": {"ColorStop": color_stops},
+            "x0": x0,
+            "y0": y0,
+            "x1": x1,
+            "y1": y1,
+            "x2": x2,
+            "y2": y2,
+        },
+    )
+    assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
+    assert gradient.ColorLine.ColorStop == color_stops
+
+    gradient = _buildPaint(gradient)
+    assert (outputMapFn(gradient.x0), outputMapFn(gradient.y0)) == (1, 2)
+    assert (outputMapFn(gradient.x1), outputMapFn(gradient.y1)) == (3, 4)
+    assert (outputMapFn(gradient.x2), outputMapFn(gradient.y2)) == (5, 6)
 
 
 def test_buildPaintLinearGradient():
-    layerBuilder = LayerV1ListBuilder()
-    color_stops = [
-        builder.buildColorStop(0.0, 0),
-        builder.buildColorStop(0.5, 1),
-        builder.buildColorStop(1.0, 2, alpha=0.8),
-    ]
-    color_line = builder.buildColorLine(color_stops, extend=builder.ExtendMode.REPEAT)
-    p0 = (builder.VariableInt(100), builder.VariableInt(200))
-    p1 = (builder.VariableInt(150), builder.VariableInt(250))
+    assert not _is_var(ot.PaintFormat.PaintLinearGradient)
+    checkBuildPaintLinearGradient(ot.PaintFormat.PaintLinearGradient)
 
-    gradient = layerBuilder.buildPaintLinearGradient(color_line, p0, p1)
-    assert gradient.Format == 3
+
+def test_buildVarPaintLinearGradient():
+    assert _is_var(ot.PaintFormat.PaintVarLinearGradient)
+    checkBuildPaintLinearGradient(ot.PaintFormat.PaintVarLinearGradient)
+
+
+def checkBuildPaintRadialGradient(fmt):
+    if _is_var(fmt):
+        inputMapFn = builder.VariableInt
+        outputMapFn = lambda v: v
+        color_stops = _sample_stops(ot.VarColorStop)
+        line_cls = ot.VarColorLine
+    else:
+        inputMapFn = outputMapFn = lambda v: v
+        color_stops = _sample_stops(ot.ColorStop)
+        line_cls = ot.ColorLine
+
+    color_line = _build(
+        line_cls, {"ColorStop": color_stops, "Extend": builder.ExtendMode.REPEAT}
+    )
+    c0 = (inputMapFn(100), inputMapFn(200))
+    c1 = (inputMapFn(150), inputMapFn(250))
+    r0 = inputMapFn(10)
+    r1 = inputMapFn(5)
+
+    gradient = _build(ot.Paint, (fmt, color_line, *c0, r0, *c1, r1))
+    assert gradient.Format == fmt
     assert gradient.ColorLine == color_line
-    assert (gradient.x0, gradient.y0) == p0
-    assert (gradient.x1, gradient.y1) == p1
-    assert (gradient.x2, gradient.y2) == p1
+    assert (outputMapFn(gradient.x0), outputMapFn(gradient.y0)) == c0
+    assert (outputMapFn(gradient.x1), outputMapFn(gradient.y1)) == c1
+    assert outputMapFn(gradient.r0) == r0
+    assert outputMapFn(gradient.r1) == r1
 
-    gradient = layerBuilder.buildPaintLinearGradient({"stops": color_stops}, p0, p1)
+    gradient = _build(
+        ot.Paint,
+        {
+            "Format": fmt,
+            "ColorLine": {"ColorStop": color_stops},
+            "x0": c0[0],
+            "y0": c0[1],
+            "x1": c1[0],
+            "y1": c1[1],
+            "r0": r0,
+            "r1": r1,
+        },
+    )
     assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
     assert gradient.ColorLine.ColorStop == color_stops
-
-    gradient = layerBuilder.buildPaintLinearGradient(color_line, p0, p1, p2=(150, 230))
-    assert (gradient.x2.value, gradient.y2.value) == (150, 230)
-    assert (gradient.x2, gradient.y2) != (gradient.x1, gradient.y1)
+    assert (outputMapFn(gradient.x0), outputMapFn(gradient.y0)) == c0
+    assert (outputMapFn(gradient.x1), outputMapFn(gradient.y1)) == c1
+    assert outputMapFn(gradient.r0) == r0
+    assert outputMapFn(gradient.r1) == r1
 
 
 def test_buildPaintRadialGradient():
-    layerBuilder = LayerV1ListBuilder()
-    color_stops = [
-        builder.buildColorStop(0.0, 0),
-        builder.buildColorStop(0.5, 1),
-        builder.buildColorStop(1.0, 2, alpha=0.8),
-    ]
-    color_line = builder.buildColorLine(color_stops, extend=builder.ExtendMode.REPEAT)
-    c0 = (builder.VariableInt(100), builder.VariableInt(200))
-    c1 = (builder.VariableInt(150), builder.VariableInt(250))
-    r0 = builder.VariableInt(10)
-    r1 = builder.VariableInt(5)
+    assert not _is_var(ot.PaintFormat.PaintRadialGradient)
+    checkBuildPaintRadialGradient(ot.PaintFormat.PaintRadialGradient)
 
-    gradient = layerBuilder.buildPaintRadialGradient(color_line, c0, c1, r0, r1)
-    assert gradient.Format == ot.Paint.Format.PaintRadialGradient
-    assert gradient.ColorLine == color_line
-    assert (gradient.x0, gradient.y0) == c0
-    assert (gradient.x1, gradient.y1) == c1
-    assert gradient.r0 == r0
-    assert gradient.r1 == r1
 
-    gradient = layerBuilder.buildPaintRadialGradient(
-        {"stops": color_stops}, c0, c1, r0, r1
+def test_buildPaintVarRadialGradient():
+    assert _is_var(ot.PaintFormat.PaintVarRadialGradient)
+    checkBuildPaintRadialGradient(ot.PaintFormat.PaintVarRadialGradient)
+
+
+def checkPaintSweepGradient(fmt):
+    if _is_var(fmt):
+        outputMapFn = lambda v: v.value
+    else:
+        outputMapFn = lambda v: v
+
+    paint = _buildPaint(
+        {
+            "Format": fmt,
+            "ColorLine": {
+                "ColorStop": (
+                    (0.0, 0),
+                    (0.5, 1),
+                    (1.0, (2, 0.8)),
+                )
+            },
+            "centerX": 127,
+            "centerY": 129,
+            "startAngle": 15,
+            "endAngle": 42,
+        }
     )
-    assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
-    assert gradient.ColorLine.ColorStop == color_stops
+
+    assert paint.Format == fmt
+    assert outputMapFn(paint.centerX) == 127
+    assert outputMapFn(paint.centerY) == 129
+    assert outputMapFn(paint.startAngle) == 15
+    assert outputMapFn(paint.endAngle) == 42
+
+
+def test_buildPaintSweepGradient():
+    assert not _is_var(ot.PaintFormat.PaintSweepGradient)
+    checkPaintSweepGradient(ot.PaintFormat.PaintSweepGradient)
+
+
+def test_buildPaintVarSweepGradient():
+    assert _is_var(ot.PaintFormat.PaintVarSweepGradient)
+    checkPaintSweepGradient(ot.PaintFormat.PaintVarSweepGradient)
 
 
 def test_buildPaintGlyph_Solid():
-    layerBuilder = LayerV1ListBuilder()
-    layer = layerBuilder.buildPaintGlyph("a", 2)
-    assert layer.Glyph == "a"
-    assert layer.Paint.Format == ot.Paint.Format.PaintSolid
-    assert layer.Paint.Color.PaletteIndex == 2
-
-    layer = layerBuilder.buildPaintGlyph("a", layerBuilder.buildPaintSolid(3, 0.9))
-    assert layer.Paint.Format == ot.Paint.Format.PaintSolid
-    assert layer.Paint.Color.PaletteIndex == 3
-    assert layer.Paint.Color.Alpha.value == 0.9
-
-
-def test_buildPaintGlyph_LinearGradient():
-    layerBuilder = LayerV1ListBuilder()
-    layer = layerBuilder.buildPaintGlyph(
-        "a",
-        layerBuilder.buildPaintLinearGradient(
-            {"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250)
+    layer = _build(
+        ot.Paint,
+        (
+            ot.PaintFormat.PaintGlyph,
+            (
+                ot.PaintFormat.PaintSolid,
+                2,
+            ),
+            "a",
         ),
     )
-    assert layer.Paint.Format == ot.Paint.Format.PaintLinearGradient
+    assert layer.Format == ot.PaintFormat.PaintGlyph
+    assert layer.Glyph == "a"
+    assert layer.Paint.Format == ot.PaintFormat.PaintSolid
+    assert layer.Paint.Color.PaletteIndex == 2
+
+    layer = _build(
+        ot.Paint,
+        (
+            ot.PaintFormat.PaintGlyph,
+            (
+                ot.PaintFormat.PaintSolid,
+                (3, 0.9),
+            ),
+            "a",
+        ),
+    )
+    assert layer.Paint.Format == ot.PaintFormat.PaintSolid
+    assert layer.Paint.Color.PaletteIndex == 3
+    assert layer.Paint.Color.Alpha == 0.9
+
+
+def test_buildPaintGlyph_VarLinearGradient():
+    layer = _build(
+        ot.Paint,
+        {
+            "Format": ot.PaintFormat.PaintGlyph,
+            "Glyph": "a",
+            "Paint": {
+                "Format": ot.PaintFormat.PaintVarLinearGradient,
+                "ColorLine": {"ColorStop": [(0.0, 3), (1.0, 4)]},
+                "x0": 100,
+                "y0": 200,
+                "x1": 150,
+                "y1": 250,
+            },
+        },
+    )
+
+    assert layer.Format == ot.PaintFormat.PaintGlyph
+    assert layer.Glyph == "a"
+    assert layer.Paint.Format == ot.PaintFormat.PaintVarLinearGradient
     assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
     assert layer.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 3
     assert layer.Paint.ColorLine.ColorStop[1].StopOffset.value == 1.0
@@ -418,235 +597,387 @@
 
 
 def test_buildPaintGlyph_RadialGradient():
-    layerBuilder = LayerV1ListBuilder()
-    layer = layerBuilder.buildPaintGlyph(
-        "a",
-        layerBuilder.buildPaintRadialGradient(
-            {
-                "stops": [
-                    (0.0, 5),
-                    {"offset": 0.5, "paletteIndex": 6, "alpha": 0.8},
-                    (1.0, 7),
-                ]
-            },
-            (50, 50),
-            (75, 75),
-            30,
-            10,
+    layer = _build(
+        ot.Paint,
+        (
+            int(ot.PaintFormat.PaintGlyph),
+            (
+                ot.PaintFormat.PaintRadialGradient,
+                (
+                    "pad",
+                    [
+                        (0.0, 5),
+                        {"StopOffset": 0.5, "Color": {"PaletteIndex": 6, "Alpha": 0.8}},
+                        (1.0, 7),
+                    ],
+                ),
+                50,
+                50,
+                30,
+                75,
+                75,
+                10,
+            ),
+            "a",
         ),
     )
-    assert layer.Paint.Format == ot.Paint.Format.PaintRadialGradient
-    assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
+    assert layer.Format == ot.PaintFormat.PaintGlyph
+    assert layer.Paint.Format == ot.PaintFormat.PaintRadialGradient
+    assert layer.Paint.ColorLine.ColorStop[0].StopOffset == 0.0
     assert layer.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 5
-    assert layer.Paint.ColorLine.ColorStop[1].StopOffset.value == 0.5
+    assert layer.Paint.ColorLine.ColorStop[1].StopOffset == 0.5
     assert layer.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 6
-    assert layer.Paint.ColorLine.ColorStop[1].Color.Alpha.value == 0.8
-    assert layer.Paint.ColorLine.ColorStop[2].StopOffset.value == 1.0
+    assert layer.Paint.ColorLine.ColorStop[1].Color.Alpha == 0.8
+    assert layer.Paint.ColorLine.ColorStop[2].StopOffset == 1.0
     assert layer.Paint.ColorLine.ColorStop[2].Color.PaletteIndex == 7
-    assert layer.Paint.x0.value == 50
-    assert layer.Paint.y0.value == 50
-    assert layer.Paint.r0.value == 30
-    assert layer.Paint.x1.value == 75
-    assert layer.Paint.y1.value == 75
-    assert layer.Paint.r1.value == 10
+    assert layer.Paint.x0 == 50
+    assert layer.Paint.y0 == 50
+    assert layer.Paint.r0 == 30
+    assert layer.Paint.x1 == 75
+    assert layer.Paint.y1 == 75
+    assert layer.Paint.r1 == 10
 
 
 def test_buildPaintGlyph_Dict_Solid():
-    layerBuilder = LayerV1ListBuilder()
-    layer = layerBuilder.buildPaintGlyph("a", {"format": 2, "paletteIndex": 0})
+    layer = _build(
+        ot.Paint,
+        (
+            int(ot.PaintFormat.PaintGlyph),
+            (int(ot.PaintFormat.PaintSolid), 1),
+            "a",
+        ),
+    )
+    assert layer.Format == ot.PaintFormat.PaintGlyph
+    assert layer.Format == ot.PaintFormat.PaintGlyph
     assert layer.Glyph == "a"
-    assert layer.Paint.Format == ot.Paint.Format.PaintSolid
-    assert layer.Paint.Color.PaletteIndex == 0
+    assert layer.Paint.Format == ot.PaintFormat.PaintSolid
+    assert layer.Paint.Color.PaletteIndex == 1
 
 
-def test_buildPaintGlyph_Dict_LinearGradient():
-    layerBuilder = LayerV1ListBuilder()
-    layer = layerBuilder.buildPaintGlyph(
-        "a",
+def test_buildPaintGlyph_Dict_VarLinearGradient():
+    layer = _build(
+        ot.Paint,
         {
-            "format": 3,
-            "colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
-            "p0": (0, 0),
-            "p1": (10, 10),
+            "Format": ot.PaintFormat.PaintGlyph,
+            "Glyph": "a",
+            "Paint": {
+                "Format": int(ot.PaintFormat.PaintVarLinearGradient),
+                "ColorLine": {"ColorStop": [(0.0, 0), (1.0, 1)]},
+                "x0": 0,
+                "y0": 0,
+                "x1": 10,
+                "y1": 10,
+            },
         },
     )
-    assert layer.Paint.Format == ot.Paint.Format.PaintLinearGradient
+    assert layer.Format == ot.PaintFormat.PaintGlyph
+    assert layer.Glyph == "a"
+    assert layer.Paint.Format == ot.PaintFormat.PaintVarLinearGradient
     assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
 
 
 def test_buildPaintGlyph_Dict_RadialGradient():
-    layerBuilder = LayerV1ListBuilder()
-    layer = layerBuilder.buildPaintGlyph(
-        "a",
+    layer = _buildPaint(
         {
-            "format": 4,
-            "colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
-            "c0": (0, 0),
-            "c1": (10, 10),
-            "r0": 4,
-            "r1": 0,
+            "Glyph": "a",
+            "Paint": {
+                "Format": int(ot.PaintFormat.PaintRadialGradient),
+                "ColorLine": {"ColorStop": [(0.0, 0), (1.0, 1)]},
+                "x0": 0,
+                "y0": 0,
+                "r0": 4,
+                "x1": 10,
+                "y1": 10,
+                "r1": 0,
+            },
+            "Format": int(ot.PaintFormat.PaintGlyph),
         },
     )
-    assert layer.Paint.Format == ot.Paint.Format.PaintRadialGradient
-    assert layer.Paint.r0.value == 4
+    assert layer.Paint.Format == ot.PaintFormat.PaintRadialGradient
+    assert layer.Paint.r0 == 4
 
 
 def test_buildPaintColrGlyph():
-    paint = LayerV1ListBuilder().buildPaintColrGlyph("a")
-    assert paint.Format == ot.Paint.Format.PaintColrGlyph
+    paint = _buildPaint((int(ot.PaintFormat.PaintColrGlyph), "a"))
+    assert paint.Format == ot.PaintFormat.PaintColrGlyph
     assert paint.Glyph == "a"
 
 
-def test_buildPaintTransform():
-    layerBuilder = LayerV1ListBuilder()
-    paint = layerBuilder.buildPaintTransform(
-        transform=builder.buildAffine2x3((1, 2, 3, 4, 5, 6)),
-        paint=layerBuilder.buildPaintGlyph(
-            glyph="a",
-            paint=layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0),
+def checkBuildPaintTransform(fmt):
+    if _is_var(fmt):
+        inputMapFn = builder.VariableFloat
+        outputMapFn = lambda v: v.value
+        affine_cls = ot.VarAffine2x3
+    else:
+        inputMapFn = outputMapFn = lambda v: v
+        affine_cls = ot.Affine2x3
+
+    paint = _buildPaint(
+        (
+            int(fmt),
+            (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, (0, 1.0)), "a"),
+            _build(affine_cls, (1, 2, 3, 4, 5, 6)),
         ),
     )
 
-    assert paint.Format == ot.Paint.Format.PaintTransform
-    assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
-    assert paint.Paint.Paint.Format == ot.Paint.Format.PaintSolid
+    assert paint.Format == fmt
+    assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+    assert paint.Paint.Paint.Format == ot.PaintFormat.PaintSolid
 
-    assert paint.Transform.xx.value == 1.0
-    assert paint.Transform.yx.value == 2.0
-    assert paint.Transform.xy.value == 3.0
-    assert paint.Transform.yy.value == 4.0
-    assert paint.Transform.dx.value == 5.0
-    assert paint.Transform.dy.value == 6.0
+    assert outputMapFn(paint.Transform.xx) == 1.0
+    assert outputMapFn(paint.Transform.yx) == 2.0
+    assert outputMapFn(paint.Transform.xy) == 3.0
+    assert outputMapFn(paint.Transform.yy) == 4.0
+    assert outputMapFn(paint.Transform.dx) == 5.0
+    assert outputMapFn(paint.Transform.dy) == 6.0
 
-    paint = layerBuilder.buildPaintTransform(
-        (1, 0, 0, 0.3333, 10, 10),
+    paint = _build(
+        ot.Paint,
         {
-            "format": 4,
-            "colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
-            "c0": (100, 100),
-            "c1": (100, 100),
-            "r0": 0,
-            "r1": 50,
+            "Format": fmt,
+            "Transform": (1, 2, 3, 0.3333, 10, 10),
+            "Paint": {
+                "Format": int(ot.PaintFormat.PaintRadialGradient),
+                "ColorLine": {"ColorStop": [(0.0, 0), (1.0, 1)]},
+                "x0": 100,
+                "y0": 101,
+                "x1": 102,
+                "y1": 103,
+                "r0": 0,
+                "r1": 50,
+            },
         },
     )
 
-    assert paint.Format == ot.Paint.Format.PaintTransform
-    assert paint.Transform.xx.value == 1.0
-    assert paint.Transform.yx.value == 0.0
-    assert paint.Transform.xy.value == 0.0
-    assert paint.Transform.yy.value == 0.3333
-    assert paint.Transform.dx.value == 10
-    assert paint.Transform.dy.value == 10
-    assert paint.Paint.Format == ot.Paint.Format.PaintRadialGradient
+    assert paint.Format == fmt
+    assert outputMapFn(paint.Transform.xx) == 1.0
+    assert outputMapFn(paint.Transform.yx) == 2.0
+    assert outputMapFn(paint.Transform.xy) == 3.0
+    assert outputMapFn(paint.Transform.yy) == 0.3333
+    assert outputMapFn(paint.Transform.dx) == 10
+    assert outputMapFn(paint.Transform.dy) == 10
+    assert paint.Paint.Format == ot.PaintFormat.PaintRadialGradient
+
+
+def test_buildPaintTransform():
+    assert not _is_var(ot.PaintFormat.PaintTransform)
+    checkBuildPaintTransform(ot.PaintFormat.PaintTransform)
+
+
+def test_buildPaintVarTransform():
+    assert _is_var(ot.PaintFormat.PaintVarTransform)
+    checkBuildPaintTransform(ot.PaintFormat.PaintVarTransform)
 
 
 def test_buildPaintComposite():
-    layerBuilder = LayerV1ListBuilder()
-    composite = layerBuilder.buildPaintComposite(
-        mode=ot.CompositeMode.SRC_OVER,
-        source={
-            "format": 11,
-            "mode": "src_over",
-            "source": {"format": 5, "glyph": "c", "paint": 2},
-            "backdrop": {"format": 5, "glyph": "b", "paint": 1},
+    composite = _build(
+        ot.Paint,
+        {
+            "Format": int(ot.PaintFormat.PaintComposite),
+            "CompositeMode": "src_over",
+            "SourcePaint": {
+                "Format": ot.PaintFormat.PaintComposite,
+                "CompositeMode": "src_over",
+                "SourcePaint": {
+                    "Format": int(ot.PaintFormat.PaintGlyph),
+                    "Glyph": "c",
+                    "Paint": (ot.PaintFormat.PaintSolid, 2),
+                },
+                "BackdropPaint": {
+                    "Format": int(ot.PaintFormat.PaintGlyph),
+                    "Glyph": "b",
+                    "Paint": (ot.PaintFormat.PaintSolid, 1),
+                },
+            },
+            "BackdropPaint": {
+                "Format": ot.PaintFormat.PaintGlyph,
+                "Glyph": "a",
+                "Paint": {
+                    "Format": ot.PaintFormat.PaintSolid,
+                    "Color": (0, 1.0),
+                },
+            },
         },
-        backdrop=layerBuilder.buildPaintGlyph(
-            "a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
-        ),
     )
 
-    assert composite.Format == ot.Paint.Format.PaintComposite
-    assert composite.SourcePaint.Format == ot.Paint.Format.PaintComposite
-    assert composite.SourcePaint.SourcePaint.Format == ot.Paint.Format.PaintGlyph
+    assert composite.Format == ot.PaintFormat.PaintComposite
+    assert composite.SourcePaint.Format == ot.PaintFormat.PaintComposite
+    assert composite.SourcePaint.SourcePaint.Format == ot.PaintFormat.PaintGlyph
     assert composite.SourcePaint.SourcePaint.Glyph == "c"
-    assert composite.SourcePaint.SourcePaint.Paint.Format == ot.Paint.Format.PaintSolid
+    assert composite.SourcePaint.SourcePaint.Paint.Format == ot.PaintFormat.PaintSolid
     assert composite.SourcePaint.SourcePaint.Paint.Color.PaletteIndex == 2
     assert composite.SourcePaint.CompositeMode == ot.CompositeMode.SRC_OVER
-    assert composite.SourcePaint.BackdropPaint.Format == ot.Paint.Format.PaintGlyph
+    assert composite.SourcePaint.BackdropPaint.Format == ot.PaintFormat.PaintGlyph
     assert composite.SourcePaint.BackdropPaint.Glyph == "b"
-    assert (
-        composite.SourcePaint.BackdropPaint.Paint.Format == ot.Paint.Format.PaintSolid
-    )
+    assert composite.SourcePaint.BackdropPaint.Paint.Format == ot.PaintFormat.PaintSolid
     assert composite.SourcePaint.BackdropPaint.Paint.Color.PaletteIndex == 1
     assert composite.CompositeMode == ot.CompositeMode.SRC_OVER
-    assert composite.BackdropPaint.Format == ot.Paint.Format.PaintGlyph
+    assert composite.BackdropPaint.Format == ot.PaintFormat.PaintGlyph
     assert composite.BackdropPaint.Glyph == "a"
-    assert composite.BackdropPaint.Paint.Format == ot.Paint.Format.PaintSolid
+    assert composite.BackdropPaint.Paint.Format == ot.PaintFormat.PaintSolid
     assert composite.BackdropPaint.Paint.Color.PaletteIndex == 0
 
 
-def test_buildPaintTranslate():
-    layerBuilder = LayerV1ListBuilder()
-    paint = layerBuilder.buildPaintTranslate(
-        paint=layerBuilder.buildPaintGlyph(
-            "a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
-        ),
-        dx=123,
-        dy=-345,
+def checkBuildPaintTranslate(fmt):
+    if _is_var(fmt):
+        inputMapFn = builder.VariableInt
+        outputMapFn = lambda v: v.value
+    else:
+        inputMapFn = outputMapFn = lambda v: v
+
+    paint = _build(
+        ot.Paint,
+        {
+            "Format": fmt,
+            "Paint": (
+                ot.PaintFormat.PaintGlyph,
+                (ot.PaintFormat.PaintSolid, (0, 1.0)),
+                "a",
+            ),
+            "dx": 123,
+            "dy": -345,
+        },
     )
 
-    assert paint.Format == ot.Paint.Format.PaintTranslate
-    assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
-    assert paint.dx.value == 123
-    assert paint.dy.value == -345
+    assert paint.Format == fmt
+    assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+    assert outputMapFn(paint.dx) == 123
+    assert outputMapFn(paint.dy) == -345
+
+
+def test_buildPaintTranslate():
+    assert not _is_var(ot.PaintFormat.PaintTranslate)
+    checkBuildPaintTranslate(ot.PaintFormat.PaintTranslate)
+
+
+def test_buildPaintVarTranslate():
+    assert _is_var(ot.PaintFormat.PaintVarTranslate)
+    checkBuildPaintTranslate(ot.PaintFormat.PaintVarTranslate)
+
+
+def checkBuildPaintRotate(fmt):
+    if _is_var(fmt):
+        inputMapFn = builder.VariableInt
+        outputMapFn = lambda v: v.value
+    else:
+        inputMapFn = outputMapFn = lambda v: v
+
+    paint = _build(
+        ot.Paint,
+        {
+            "Format": fmt,
+            "Paint": (
+                ot.PaintFormat.PaintGlyph,
+                (ot.PaintFormat.PaintSolid, (0, 1.0)),
+                "a",
+            ),
+            "angle": 15,
+            "centerX": 127,
+            "centerY": 129,
+        },
+    )
+
+    assert paint.Format == fmt
+    assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+    assert outputMapFn(paint.angle) == 15
+    assert outputMapFn(paint.centerX) == 127
+    assert outputMapFn(paint.centerY) == 129
 
 
 def test_buildPaintRotate():
-    layerBuilder = LayerV1ListBuilder()
-    paint = layerBuilder.buildPaintRotate(
-        paint=layerBuilder.buildPaintGlyph(
-            "a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
-        ),
-        angle=15,
-        centerX=127,
-        centerY=129,
+    assert not _is_var(ot.PaintFormat.PaintRotate)
+    checkBuildPaintRotate(ot.PaintFormat.PaintRotate)
+
+
+def test_buildPaintVarRotate():
+    assert _is_var(ot.PaintFormat.PaintVarRotate)
+    checkBuildPaintRotate(ot.PaintFormat.PaintVarRotate)
+
+
+def checkBuildPaintSkew(fmt):
+    if _is_var(fmt):
+        inputMapFn = builder.VariableInt
+        outputMapFn = lambda v: v.value
+    else:
+        inputMapFn = outputMapFn = lambda v: v
+
+    paint = _build(
+        ot.Paint,
+        {
+            "Format": fmt,
+            "Paint": (
+                ot.PaintFormat.PaintGlyph,
+                (ot.PaintFormat.PaintSolid, (0, 1.0)),
+                "a",
+            ),
+            "xSkewAngle": 15,
+            "ySkewAngle": 42,
+            "centerX": 127,
+            "centerY": 129,
+        },
     )
 
-    assert paint.Format == ot.Paint.Format.PaintRotate
-    assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
-    assert paint.angle.value == 15
-    assert paint.centerX.value == 127
-    assert paint.centerY.value == 129
+    assert paint.Format == fmt
+    assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+    assert outputMapFn(paint.xSkewAngle) == 15
+    assert outputMapFn(paint.ySkewAngle) == 42
+    assert outputMapFn(paint.centerX) == 127
+    assert outputMapFn(paint.centerY) == 129
 
 
 def test_buildPaintSkew():
-    layerBuilder = LayerV1ListBuilder()
-    paint = layerBuilder.buildPaintSkew(
-        paint=layerBuilder.buildPaintGlyph(
-            "a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
-        ),
-        xSkewAngle=15,
-        ySkewAngle=42,
-        centerX=127,
-        centerY=129,
-    )
+    assert not _is_var(ot.PaintFormat.PaintSkew)
+    checkBuildPaintSkew(ot.PaintFormat.PaintSkew)
 
-    assert paint.Format == ot.Paint.Format.PaintSkew
-    assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
-    assert paint.xSkewAngle.value == 15
-    assert paint.ySkewAngle.value == 42
-    assert paint.centerX.value == 127
-    assert paint.centerY.value == 129
+
+def test_buildPaintVarSkew():
+    assert _is_var(ot.PaintFormat.PaintVarSkew)
+    checkBuildPaintSkew(ot.PaintFormat.PaintVarSkew)
 
 
 def test_buildColrV1():
     colorGlyphs = {
-        "a": [("b", 0), ("c", 1)],
-        "d": [
-            ("e", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
-            (
-                "f",
-                {
-                    "format": 4,
-                    "colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "reflect"},
-                    "c0": (0, 0),
-                    "c1": (0, 0),
-                    "r0": 10,
-                    "r1": 0,
-                },
-            ),
-        ],
-        "g": [("h", 5)],
+        "a": (
+            ot.PaintFormat.PaintColrLayers,
+            [
+                (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 0), "b"),
+                (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintVarSolid, 1), "c"),
+            ],
+        ),
+        "d": (
+            ot.PaintFormat.PaintColrLayers,
+            [
+                (
+                    ot.PaintFormat.PaintGlyph,
+                    {
+                        "Format": int(ot.PaintFormat.PaintSolid),
+                        "Color": {"PaletteIndex": 2, "Alpha": 0.8},
+                    },
+                    "e",
+                ),
+                (
+                    ot.PaintFormat.PaintGlyph,
+                    {
+                        "Format": int(ot.PaintFormat.PaintVarRadialGradient),
+                        "ColorLine": {
+                            "ColorStop": [(0.0, 3), (1.0, 4)],
+                            "Extend": "reflect",
+                        },
+                        "x0": 0,
+                        "y0": 0,
+                        "x1": 0,
+                        "y1": 0,
+                        "r0": 10,
+                        "r1": 0,
+                    },
+                    "f",
+                ),
+            ],
+        ),
+        "g": (
+            ot.PaintFormat.PaintColrLayers,
+            [(ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 5), "h")],
+        ),
     }
     glyphMap = {
         ".notdef": 0,
@@ -677,35 +1008,38 @@
 def test_buildColrV1_more_than_255_paints():
     num_paints = 364
     colorGlyphs = {
-        "a": [
-            {
-                "format": 5,  # PaintGlyph
-                "paint": 0,
-                "glyph": name,
-            }
-            for name in (f"glyph{i}" for i in range(num_paints))
-        ],
+        "a": (
+            ot.PaintFormat.PaintColrLayers,
+            [
+                {
+                    "Format": int(ot.PaintFormat.PaintGlyph),
+                    "Paint": (ot.PaintFormat.PaintSolid, 0),
+                    "Glyph": name,
+                }
+                for name in (f"glyph{i}" for i in range(num_paints))
+            ],
+        ),
     }
     layers, baseGlyphs = builder.buildColrV1(colorGlyphs)
     paints = layers.Paint
 
     assert len(paints) == num_paints + 1
 
-    assert all(paints[i].Format == ot.Paint.Format.PaintGlyph for i in range(255))
+    assert all(paints[i].Format == ot.PaintFormat.PaintGlyph for i in range(255))
 
-    assert paints[255].Format == ot.Paint.Format.PaintColrLayers
+    assert paints[255].Format == ot.PaintFormat.PaintColrLayers
     assert paints[255].FirstLayerIndex == 0
     assert paints[255].NumLayers == 255
 
     assert all(
-        paints[i].Format == ot.Paint.Format.PaintGlyph
+        paints[i].Format == ot.PaintFormat.PaintGlyph
         for i in range(256, num_paints + 1)
     )
 
     assert baseGlyphs.BaseGlyphCount == len(colorGlyphs)
     assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "a"
     assert (
-        baseGlyphs.BaseGlyphV1Record[0].Paint.Format == ot.Paint.Format.PaintColrLayers
+        baseGlyphs.BaseGlyphV1Record[0].Paint.Format == ot.PaintFormat.PaintColrLayers
     )
     assert baseGlyphs.BaseGlyphV1Record[0].Paint.FirstLayerIndex == 255
     assert baseGlyphs.BaseGlyphV1Record[0].Paint.NumLayers == num_paints + 1 - 255
@@ -727,9 +1061,7 @@
     assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]}
     assert not colorGlyphsV1
 
-    colorGlyphs = {
-        "a": [("b", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=0.0))]
-    }
+    colorGlyphs = {"a": (ot.PaintFormat.PaintGlyph, 0, "b")}
 
     colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
 
@@ -775,32 +1107,35 @@
 
 
 def test_build_layerv1list_empty():
-    # Nobody uses PaintColrLayers (format 8), no layerlist
+    # Nobody uses PaintColrLayers, no layerlist
     colr = builder.buildCOLR(
         {
-            "a": {
-                "format": 5,  # PaintGlyph
-                "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
-                "glyph": "b",
-            },
-            # A list of 1 shouldn't become a PaintColrLayers
-            "b": [
-                {
-                    "format": 5,  # PaintGlyph
-                    "paint": {
-                        "format": 3,
-                        "colorLine": {
-                            "stops": [(0.0, 2), (1.0, 3)],
-                            "extend": "reflect",
-                        },
-                        "p0": (1, 2),
-                        "p1": (3, 4),
-                        "p2": (2, 2),
+            # BaseGlyph, tuple form
+            "a": (
+                int(ot.PaintFormat.PaintGlyph),
+                (2, (2, 0.8)),
+                "b",
+            ),
+            # BaseGlyph, map form
+            "b": {
+                "Format": int(ot.PaintFormat.PaintGlyph),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintLinearGradient),
+                    "ColorLine": {
+                        "ColorStop": [(0.0, 2), (1.0, 3)],
+                        "Extend": "reflect",
                     },
-                    "glyph": "bb",
-                }
-            ],
-        }
+                    "x0": 1,
+                    "y0": 2,
+                    "x1": 3,
+                    "y1": 4,
+                    "x2": 2,
+                    "y2": 2,
+                },
+                "Glyph": "bb",
+            },
+        },
+        version=1,
     )
 
     assertIsColrV1(colr)
@@ -818,9 +1153,9 @@
     # semi-readable assertions on a LayerV1List order.
     result = []
     for paint in paints:
-        if paint.Format == int(ot.Paint.Format.PaintGlyph):
+        if paint.Format == int(ot.PaintFormat.PaintGlyph):
             result.append(paint.Glyph)
-        elif paint.Format == int(ot.Paint.Format.PaintColrLayers):
+        elif paint.Format == int(ot.PaintFormat.PaintColrLayers):
             result.append(
                 f"Layers[{paint.FirstLayerIndex}:{paint.FirstLayerIndex+paint.NumLayers}]"
             )
@@ -830,35 +1165,42 @@
 def test_build_layerv1list_simple():
     # Two colr glyphs, each with two layers the first of which is common
     # All layers use the same solid paint
-    solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8}
+    solid_paint = {"Format": 2, "Color": {"PaletteIndex": 2, "Alpha": 0.8}}
     backdrop = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "back",
+        "Format": int(ot.PaintFormat.PaintGlyph),
+        "Paint": solid_paint,
+        "Glyph": "back",
     }
     a_foreground = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "a_fore",
+        "Format": int(ot.PaintFormat.PaintGlyph),
+        "Paint": solid_paint,
+        "Glyph": "a_fore",
     }
     b_foreground = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "b_fore",
+        "Format": int(ot.PaintFormat.PaintGlyph),
+        "Paint": solid_paint,
+        "Glyph": "b_fore",
     }
 
-    # list => PaintColrLayers, which means contents should be in LayerV1List
+    # list => PaintColrLayers, contents should land in LayerV1List
     colr = builder.buildCOLR(
         {
-            "a": [
-                backdrop,
-                a_foreground,
-            ],
-            "b": [
-                backdrop,
-                b_foreground,
-            ],
-        }
+            "a": (
+                ot.PaintFormat.PaintColrLayers,
+                [
+                    backdrop,
+                    a_foreground,
+                ],
+            ),
+            "b": {
+                "Format": ot.PaintFormat.PaintColrLayers,
+                "Layers": [
+                    backdrop,
+                    b_foreground,
+                ],
+            },
+        },
+        version=1,
     )
 
     assertIsColrV1(colr)
@@ -879,47 +1221,51 @@
 
 def test_build_layerv1list_with_sharing():
     # Three colr glyphs, each with two layers in common
-    solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8}
+    solid_paint = {"Format": 2, "Color": (2, 0.8)}
     backdrop = [
         {
-            "format": 5,  # PaintGlyph
-            "paint": solid_paint,
-            "glyph": "back1",
+            "Format": int(ot.PaintFormat.PaintGlyph),
+            "Paint": solid_paint,
+            "Glyph": "back1",
         },
         {
-            "format": 5,  # PaintGlyph
-            "paint": solid_paint,
-            "glyph": "back2",
+            "Format": ot.PaintFormat.PaintGlyph,
+            "Paint": solid_paint,
+            "Glyph": "back2",
         },
     ]
     a_foreground = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "a_fore",
+        "Format": ot.PaintFormat.PaintGlyph,
+        "Paint": solid_paint,
+        "Glyph": "a_fore",
     }
     b_background = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "b_back",
+        "Format": ot.PaintFormat.PaintGlyph,
+        "Paint": solid_paint,
+        "Glyph": "b_back",
     }
     b_foreground = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "b_fore",
+        "Format": ot.PaintFormat.PaintGlyph,
+        "Paint": solid_paint,
+        "Glyph": "b_fore",
     }
     c_background = {
-        "format": 5,  # PaintGlyph
-        "paint": solid_paint,
-        "glyph": "c_back",
+        "Format": ot.PaintFormat.PaintGlyph,
+        "Paint": solid_paint,
+        "Glyph": "c_back",
     }
 
     # list => PaintColrLayers, which means contents should be in LayerV1List
     colr = builder.buildCOLR(
         {
-            "a": backdrop + [a_foreground],
-            "b": [b_background] + backdrop + [b_foreground],
-            "c": [c_background] + backdrop,
-        }
+            "a": (ot.PaintFormat.PaintColrLayers, backdrop + [a_foreground]),
+            "b": (
+                ot.PaintFormat.PaintColrLayers,
+                [b_background] + backdrop + [b_foreground],
+            ),
+            "c": (ot.PaintFormat.PaintColrLayers, [c_background] + backdrop),
+        },
+        version=1,
     )
 
     assertIsColrV1(colr)
@@ -951,9 +1297,12 @@
 def test_build_layerv1list_with_overlaps():
     paints = [
         {
-            "format": 5,  # PaintGlyph
-            "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
-            "glyph": c,
+            "Format": ot.PaintFormat.PaintGlyph,
+            "Paint": {
+                "Format": ot.PaintFormat.PaintSolid,
+                "Color": {"PaletteIndex": 2, "Alpha": 0.8},
+            },
+            "Glyph": c,
         }
         for c in "abcdefghi"
     ]
@@ -961,10 +1310,11 @@
     # list => PaintColrLayers, which means contents should be in LayerV1List
     colr = builder.buildCOLR(
         {
-            "a": paints[0:4],
-            "b": paints[0:6],
-            "c": paints[2:8],
-        }
+            "a": (ot.PaintFormat.PaintColrLayers, paints[0:4]),
+            "b": (ot.PaintFormat.PaintColrLayers, paints[0:6]),
+            "c": (ot.PaintFormat.PaintColrLayers, paints[2:8]),
+        },
+        version=1,
     )
 
     assertIsColrV1(colr)
@@ -994,6 +1344,26 @@
     assert colr.table.LayerV1List.LayerCount == 11
 
 
+def test_explicit_version_1():
+    colr = builder.buildCOLR(
+        {
+            "a": (
+                ot.PaintFormat.PaintColrLayers,
+                [
+                    (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 0), "b"),
+                    (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 1), "c"),
+                ],
+            )
+        },
+        version=1,
+    )
+    assert colr.version == 1
+    assert not hasattr(colr, "ColorLayers")
+    assert hasattr(colr, "table")
+    assert isinstance(colr.table, ot.COLR)
+    assert colr.table.VarStore is None
+
+
 class BuildCOLRTest(object):
     def test_automatic_version_all_solid_color_glyphs(self):
         colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]})
@@ -1005,38 +1375,55 @@
     def test_automatic_version_no_solid_color_glyphs(self):
         colr = builder.buildCOLR(
             {
-                "a": [
-                    (
-                        "b",
-                        {
-                            "format": 4,
-                            "colorLine": {
-                                "stops": [(0.0, 0), (1.0, 1)],
-                                "extend": "repeat",
+                "a": (
+                    ot.PaintFormat.PaintColrLayers,
+                    [
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            {
+                                "Format": int(ot.PaintFormat.PaintRadialGradient),
+                                "ColorLine": {
+                                    "ColorStop": [(0.0, 0), (1.0, 1)],
+                                    "Extend": "repeat",
+                                },
+                                "x0": 1,
+                                "y0": 0,
+                                "x1": 10,
+                                "y1": 0,
+                                "r0": 4,
+                                "r1": 2,
                             },
-                            "c0": (1, 0),
-                            "c1": (10, 0),
-                            "r0": 4,
-                            "r1": 2,
-                        },
-                    ),
-                    ("c", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
-                ],
-                "d": [
-                    (
-                        "e",
+                            "b",
+                        ),
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            {"Format": 2, "Color": {"PaletteIndex": 2, "Alpha": 0.8}},
+                            "c",
+                        ),
+                    ],
+                ),
+                "d": (
+                    ot.PaintFormat.PaintColrLayers,
+                    [
                         {
-                            "format": 3,
-                            "colorLine": {
-                                "stops": [(0.0, 2), (1.0, 3)],
-                                "extend": "reflect",
+                            "Format": ot.PaintFormat.PaintGlyph,
+                            "Glyph": "e",
+                            "Paint": {
+                                "Format": ot.PaintFormat.PaintLinearGradient,
+                                "ColorLine": {
+                                    "ColorStop": [(0.0, 2), (1.0, 3)],
+                                    "Extend": "reflect",
+                                },
+                                "x0": 1,
+                                "y0": 2,
+                                "x1": 3,
+                                "y1": 4,
+                                "x2": 2,
+                                "y2": 2,
                             },
-                            "p0": (1, 2),
-                            "p1": (3, 4),
-                            "p2": (2, 2),
-                        },
-                    ),
-                ],
+                        }
+                    ],
+                ),
             }
         )
         assertIsColrV1(colr)
@@ -1049,19 +1436,30 @@
         colr = builder.buildCOLR(
             {
                 "a": [("b", 0), ("c", 1)],
-                "d": [
-                    (
-                        "e",
-                        {
-                            "format": 3,
-                            "colorLine": {"stops": [(0.0, 2), (1.0, 3)]},
-                            "p0": (1, 2),
-                            "p1": (3, 4),
-                            "p2": (2, 2),
-                        },
-                    ),
-                    ("f", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
-                ],
+                "d": (
+                    ot.PaintFormat.PaintColrLayers,
+                    [
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            {
+                                "Format": ot.PaintFormat.PaintLinearGradient,
+                                "ColorLine": {"ColorStop": [(0.0, 2), (1.0, 3)]},
+                                "x0": 1,
+                                "y0": 2,
+                                "x1": 3,
+                                "y1": 4,
+                                "x2": 2,
+                                "y2": 2,
+                            },
+                            "e",
+                        ),
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            (ot.PaintFormat.PaintSolid, (2, 0.8)),
+                            "f",
+                        ),
+                    ],
+                ),
             }
         )
         assertIsColrV1(colr)
@@ -1087,13 +1485,55 @@
         assert hasattr(colr, "ColorLayers")
 
     def test_explicit_version_1(self):
-        colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]}, version=1)
+        colr = builder.buildCOLR(
+            {
+                "a": (
+                    ot.PaintFormat.PaintColrLayers,
+                    [
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            (ot.PaintFormat.PaintSolid, 0),
+                            "b",
+                        ),
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            (ot.PaintFormat.PaintSolid, 1),
+                            "c",
+                        ),
+                    ],
+                )
+            },
+            version=1,
+        )
         assert colr.version == 1
         assert not hasattr(colr, "ColorLayers")
         assert hasattr(colr, "table")
         assert isinstance(colr.table, ot.COLR)
         assert colr.table.VarStore is None
 
+    def test_paint_one_colr_layers(self):
+        # A set of one layers should flip to just that layer
+        colr = builder.buildCOLR(
+            {
+                "a": (
+                    ot.PaintFormat.PaintColrLayers,
+                    [
+                        (
+                            ot.PaintFormat.PaintGlyph,
+                            (ot.PaintFormat.PaintSolid, 0),
+                            "b",
+                        ),
+                    ],
+                )
+            },
+        )
+
+        assert len(colr.table.LayerV1List.Paint) == 0, "PaintColrLayers should be gone"
+        assert colr.table.BaseGlyphV1List.BaseGlyphCount == 1
+        paint = colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].Paint
+        assert paint.Format == ot.PaintFormat.PaintGlyph
+        assert paint.Paint.Format == ot.PaintFormat.PaintSolid
+
 
 class TrickyRadialGradientTest:
     @staticmethod
diff --git a/Tests/colorLib/table_builder_test.py b/Tests/colorLib/table_builder_test.py
new file mode 100644
index 0000000..d0a76f5
--- /dev/null
+++ b/Tests/colorLib/table_builder_test.py
@@ -0,0 +1,15 @@
+from fontTools.ttLib.tables import otTables  # trigger setup to occur
+from fontTools.ttLib.tables.otConverters import UShort
+from fontTools.colorLib.table_builder import TableBuilder
+import pytest
+
+
+class WriteMe:
+    value = None
+
+
+def test_intValue_otRound():
+    dest = WriteMe()
+    converter = UShort("value", None, None)
+    TableBuilder()._convert(dest, "value", converter, 85.6)
+    assert dest.value == 86, "Should have used otRound"
diff --git a/Tests/colorLib/unbuilder_test.py b/Tests/colorLib/unbuilder_test.py
new file mode 100644
index 0000000..81169e0
--- /dev/null
+++ b/Tests/colorLib/unbuilder_test.py
@@ -0,0 +1,210 @@
+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.PaintFormat.PaintColrLayers),
+        "Layers": [
+            {
+                "Format": int(ot.PaintFormat.PaintGlyph),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintSolid),
+                    "Color": {"PaletteIndex": 2, "Alpha": 0.5},
+                },
+                "Glyph": "glyph00011",
+            },
+            {
+                "Format": int(ot.PaintFormat.PaintGlyph),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintVarLinearGradient),
+                    "ColorLine": {
+                        "Extend": "repeat",
+                        "ColorStop": [
+                            {
+                                "StopOffset": (0.0, 0),
+                                "Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
+                            },
+                            {
+                                "StopOffset": (0.5, 0),
+                                "Color": {"PaletteIndex": 4, "Alpha": (1.0, 0)},
+                            },
+                            {
+                                "StopOffset": (1.0, 0),
+                                "Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
+                            },
+                        ],
+                    },
+                    "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),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintVarTransform),
+                    "Paint": {
+                        "Format": int(ot.PaintFormat.PaintRadialGradient),
+                        "ColorLine": {
+                            "Extend": "pad",
+                            "ColorStop": [
+                                {
+                                    "StopOffset": 0,
+                                    "Color": {"PaletteIndex": 6, "Alpha": 1.0},
+                                },
+                                {
+                                    "StopOffset": 1.0,
+                                    "Color": {"PaletteIndex": 7, "Alpha": 0.4},
+                                },
+                            ],
+                        },
+                        "x0": 7,
+                        "y0": 8,
+                        "r0": 9,
+                        "x1": 10,
+                        "y1": 11,
+                        "r1": 12,
+                    },
+                    "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.PaintVarTranslate),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintRotate),
+                    "Paint": {
+                        "Format": int(ot.PaintFormat.PaintVarSkew),
+                        "Paint": {
+                            "Format": int(ot.PaintFormat.PaintGlyph),
+                            "Paint": {
+                                "Format": int(ot.PaintFormat.PaintSolid),
+                                "Color": {"PaletteIndex": 2, "Alpha": 0.5},
+                            },
+                            "Glyph": "glyph00011",
+                        },
+                        "xSkewAngle": (-11.0, 0),
+                        "ySkewAngle": (5.0, 0),
+                        "centerX": (253.0, 0),
+                        "centerY": (254.0, 0),
+                    },
+                    "angle": 45.0,
+                    "centerX": 255.0,
+                    "centerY": 256.0,
+                },
+                "dx": (257.0, 0),
+                "dy": (258.0, 0),
+            },
+        ],
+    },
+    "glyph00014": {
+        "Format": int(ot.PaintFormat.PaintComposite),
+        "SourcePaint": {
+            "Format": int(ot.PaintFormat.PaintColrGlyph),
+            "Glyph": "glyph00010",
+        },
+        "CompositeMode": "src_over",
+        "BackdropPaint": {
+            "Format": int(ot.PaintFormat.PaintTransform),
+            "Paint": {
+                "Format": int(ot.PaintFormat.PaintColrGlyph),
+                "Glyph": "glyph00010",
+            },
+            "Transform": {
+                "xx": 1.0,
+                "yx": 0.0,
+                "xy": 0.0,
+                "yy": 1.0,
+                "dx": 300.0,
+                "dy": 0.0,
+            },
+        },
+    },
+    "glyph00015": {
+        "Format": int(ot.PaintFormat.PaintGlyph),
+        "Paint": {
+            "Format": int(ot.PaintFormat.PaintSweepGradient),
+            "ColorLine": {
+                "Extend": "pad",
+                "ColorStop": [
+                    {
+                        "StopOffset": 0.0,
+                        "Color": {"PaletteIndex": 3, "Alpha": 1.0},
+                    },
+                    {
+                        "StopOffset": 1.0,
+                        "Color": {"PaletteIndex": 5, "Alpha": 1.0},
+                    },
+                ],
+            },
+            "centerX": 259,
+            "centerY": 300,
+            "startAngle": 45.0,
+            "endAngle": 135.0,
+        },
+        "Glyph": "glyph00011",
+    },
+    "glyph00016": {
+        "Format": int(ot.PaintFormat.PaintColrLayers),
+        "Layers": [
+            {
+                "Format": int(ot.PaintFormat.PaintGlyph),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintVarSolid),
+                    "Color": {"PaletteIndex": 2, "Alpha": (0.5, 0)},
+                },
+                "Glyph": "glyph00011",
+            },
+            {
+                "Format": int(ot.PaintFormat.PaintGlyph),
+                "Paint": {
+                    "Format": int(ot.PaintFormat.PaintVarLinearGradient),
+                    "ColorLine": {
+                        "Extend": "repeat",
+                        "ColorStop": [
+                            {
+                                "StopOffset": (0.0, 0),
+                                "Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
+                            },
+                            {
+                                "StopOffset": (0.5, 0),
+                                "Color": {"PaletteIndex": 4, "Alpha": (1.0, 0)},
+                            },
+                            {
+                                "StopOffset": (1.0, 0),
+                                "Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
+                            },
+                        ],
+                    },
+                    "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)
+    assert colorGlyphs == TEST_COLOR_GLYPHS
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
index 151cd89..279e8ca 100644
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -73,7 +73,7 @@
         LigatureSubtable AlternateSubtable MultipleSubstSubtable 
         SingleSubstSubtable aalt_chain_contextual_subst AlternateChained 
         MultipleLookupsPerGlyph MultipleLookupsPerGlyph2 GSUB_6_formats
-        GSUB_5_formats
+        GSUB_5_formats delete_glyph
     """.split()
 
     def __init__(self, methodName):
diff --git a/Tests/feaLib/data/delete_glyph.fea b/Tests/feaLib/data/delete_glyph.fea
new file mode 100644
index 0000000..36e0f0f
--- /dev/null
+++ b/Tests/feaLib/data/delete_glyph.fea
@@ -0,0 +1,3 @@
+feature test {
+	sub a by NULL;
+} test;
diff --git a/Tests/feaLib/data/delete_glyph.ttx b/Tests/feaLib/data/delete_glyph.ttx
new file mode 100644
index 0000000..777f6e3
--- /dev/null
+++ b/Tests/feaLib/data/delete_glyph.ttx
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+  <GSUB>
+    <Version value="0x00010000"/>
+    <ScriptList>
+      <!-- ScriptCount=1 -->
+      <ScriptRecord index="0">
+        <ScriptTag value="DFLT"/>
+        <Script>
+          <DefaultLangSys>
+            <ReqFeatureIndex value="65535"/>
+            <!-- FeatureCount=1 -->
+            <FeatureIndex index="0" value="0"/>
+          </DefaultLangSys>
+          <!-- LangSysCount=0 -->
+        </Script>
+      </ScriptRecord>
+    </ScriptList>
+    <FeatureList>
+      <!-- FeatureCount=1 -->
+      <FeatureRecord index="0">
+        <FeatureTag value="test"/>
+        <Feature>
+          <!-- LookupCount=1 -->
+          <LookupListIndex index="0" value="0"/>
+        </Feature>
+      </FeatureRecord>
+    </FeatureList>
+    <LookupList>
+      <!-- LookupCount=1 -->
+      <Lookup index="0">
+        <LookupType value="2"/>
+        <LookupFlag value="0"/>
+        <!-- SubTableCount=1 -->
+        <MultipleSubst index="0">
+          <Substitution in="a" out=""/>
+        </MultipleSubst>
+      </Lookup>
+    </LookupList>
+  </GSUB>
+
+</ttFont>
diff --git a/Tests/fontBuilder/data/test_var.otf.ttx b/Tests/fontBuilder/data/test_var.otf.ttx
index ccf64dc..09246e5 100644
--- a/Tests/fontBuilder/data/test_var.otf.ttx
+++ b/Tests/fontBuilder/data/test_var.otf.ttx
@@ -141,9 +141,6 @@
       Test Axis
     </namerecord>
     <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      TotallyNormal
-    </namerecord>
-    <namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
       TotallyTested
     </namerecord>
     <namerecord nameID="1" platformID="1" platEncID="0" langID="0x4" unicode="True">
@@ -165,9 +162,6 @@
       Test Axis
     </namerecord>
     <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
-      TotallyNormal
-    </namerecord>
-    <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
       TotallyTested
     </namerecord>
     <namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
@@ -290,12 +284,12 @@
     </Axis>
 
     <!-- TotallyNormal -->
-    <NamedInstance flags="0x0" subfamilyNameID="257">
+    <NamedInstance flags="0x0" subfamilyNameID="2">
       <coord axis="TEST" value="0.0"/>
     </NamedInstance>
 
     <!-- TotallyTested -->
-    <NamedInstance flags="0x0" subfamilyNameID="258">
+    <NamedInstance flags="0x0" subfamilyNameID="257">
       <coord axis="TEST" value="100.0"/>
     </NamedInstance>
   </fvar>
diff --git a/Tests/fontBuilder/data/test_var.ttf.ttx b/Tests/fontBuilder/data/test_var.ttf.ttx
index ed8fd30..781bb64 100644
--- a/Tests/fontBuilder/data/test_var.ttf.ttx
+++ b/Tests/fontBuilder/data/test_var.ttf.ttx
@@ -199,12 +199,9 @@
       Down
     </namerecord>
     <namerecord nameID="260" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      TotallyNormal
-    </namerecord>
-    <namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
       Right Up
     </namerecord>
-    <namerecord nameID="262" platformID="1" platEncID="0" langID="0x0" unicode="True">
+    <namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
       Neutral
     </namerecord>
     <namerecord nameID="1" platformID="1" platEncID="0" langID="0x4" unicode="True">
@@ -235,12 +232,9 @@
       Down
     </namerecord>
     <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
-      TotallyNormal
-    </namerecord>
-    <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
       Right Up
     </namerecord>
-    <namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
+    <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
       Neutral
     </namerecord>
     <namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
@@ -400,7 +394,7 @@
       <AxisValue index="0" Format="1">
         <AxisIndex value="0"/>
         <Flags value="2"/>
-        <ValueNameID value="262"/>  <!-- Neutral -->
+        <ValueNameID value="261"/>  <!-- Neutral -->
         <Value value="0.0"/>
       </AxisValue>
       <AxisValue index="1" Format="1">
@@ -412,7 +406,7 @@
       <AxisValue index="2" Format="1">
         <AxisIndex value="1"/>
         <Flags value="2"/>
-        <ValueNameID value="262"/>  <!-- Neutral -->
+        <ValueNameID value="261"/>  <!-- Neutral -->
         <Value value="0.0"/>
       </AxisValue>
       <AxisValue index="3" Format="1">
@@ -424,7 +418,7 @@
       <AxisValue index="4" Format="1">
         <AxisIndex value="2"/>
         <Flags value="2"/>
-        <ValueNameID value="262"/>  <!-- Neutral -->
+        <ValueNameID value="261"/>  <!-- Neutral -->
         <Value value="0.0"/>
       </AxisValue>
       <AxisValue index="5" Format="1">
@@ -436,7 +430,7 @@
       <AxisValue index="6" Format="1">
         <AxisIndex value="3"/>
         <Flags value="2"/>
-        <ValueNameID value="262"/>  <!-- Neutral -->
+        <ValueNameID value="261"/>  <!-- Neutral -->
         <Value value="0.0"/>
       </AxisValue>
       <AxisValue index="7" Format="1">
@@ -492,7 +486,7 @@
     </Axis>
 
     <!-- TotallyNormal -->
-    <NamedInstance flags="0x0" subfamilyNameID="260">
+    <NamedInstance flags="0x0" subfamilyNameID="2">
       <coord axis="LEFT" value="0.0"/>
       <coord axis="RGHT" value="0.0"/>
       <coord axis="UPPP" value="0.0"/>
@@ -500,7 +494,7 @@
     </NamedInstance>
 
     <!-- Right Up -->
-    <NamedInstance flags="0x0" subfamilyNameID="261">
+    <NamedInstance flags="0x0" subfamilyNameID="260">
       <coord axis="LEFT" value="0.0"/>
       <coord axis="RGHT" value="100.0"/>
       <coord axis="UPPP" value="100.0"/>
diff --git a/Tests/misc/arrayTools_test.py b/Tests/misc/arrayTools_test.py
index 127f153..73e0ab1 100644
--- a/Tests/misc/arrayTools_test.py
+++ b/Tests/misc/arrayTools_test.py
@@ -1,7 +1,7 @@
 from fontTools.misc.py23 import *
 from fontTools.misc.py23 import round3
 from fontTools.misc.arrayTools import (
-    calcBounds, calcIntBounds, updateBounds, pointInRect, pointsInRect,
+    Vector, calcBounds, calcIntBounds, updateBounds, pointInRect, pointsInRect,
     vectorLength, asInt16, normRect, scaleRect, offsetRect, insetRect,
     sectRect, unionRect, rectCenter, intRect)
 import math
@@ -88,3 +88,14 @@
 
 def test_intRect():
     assert intRect((0.9, 2.9, 3.1, 4.1)) == (0, 2, 4, 5)
+
+
+def test_Vector():
+    v = Vector([100, 200])
+    assert v == Vector([100, 200])
+    assert v == [100, 200]
+    assert v + Vector([1, 2]) == [101, 202]
+    assert v - Vector([1, 2]) == [99, 198]
+    assert v * 2 == [200, 400]
+    assert v * 0.5 == [50, 100]
+    assert v / 2 == [50, 100]
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index d37634f..370f9b6 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -3,7 +3,9 @@
 from fontTools.misc.testTools import getXML
 from fontTools import subset
 from fontTools.fontBuilder import FontBuilder
+from fontTools.pens.ttGlyphPen import TTGlyphPen
 from fontTools.ttLib import TTFont, newTable
+from fontTools.ttLib.tables import otTables as ot
 from fontTools.misc.loggingTools import CapturingLogHandler
 import difflib
 import logging
@@ -930,5 +932,256 @@
     assert all(loc == 0 for loc in loca)
 
 
+@pytest.fixture
+def colrv1_path(tmp_path):
+    base_glyph_names = ["uni%04X" % i for i in range(0xE000, 0xE000 + 10)]
+    layer_glyph_names = ["glyph%05d" % i for i in range(10, 20)]
+    glyph_order = [".notdef"] + base_glyph_names + layer_glyph_names
+
+    pen = TTGlyphPen(glyphSet=None)
+    pen.moveTo((0, 0))
+    pen.lineTo((0, 500))
+    pen.lineTo((500, 500))
+    pen.lineTo((500, 0))
+    pen.closePath()
+    glyph = pen.glyph()
+    glyphs = {g: glyph for g in glyph_order}
+
+    fb = FontBuilder(unitsPerEm=1024, isTTF=True)
+    fb.setupGlyphOrder(glyph_order)
+    fb.setupCharacterMap({int(name[3:], 16): name for name in base_glyph_names})
+    fb.setupGlyf(glyphs)
+    fb.setupHorizontalMetrics({g: (500, 0) for g in glyph_order})
+    fb.setupHorizontalHeader()
+    fb.setupOS2()
+    fb.setupPost()
+    fb.setupNameTable({"familyName": "TestCOLRv1", "styleName": "Regular"})
+
+    fb.setupCOLR(
+        {
+            "uniE000": (
+                ot.PaintFormat.PaintColrLayers,
+                [
+                    {
+                        "Format": ot.PaintFormat.PaintGlyph,
+                        "Paint": (ot.PaintFormat.PaintSolid, 0),
+                        "Glyph": "glyph00010",
+                    },
+                    {
+                        "Format": ot.PaintFormat.PaintGlyph,
+                        "Paint": (ot.PaintFormat.PaintSolid, (2, 0.3)),
+                        "Glyph": "glyph00011",
+                    },
+                ],
+            ),
+            "uniE001": (
+                ot.PaintFormat.PaintColrLayers,
+                [
+                    {
+                        "Format": ot.PaintFormat.PaintTransform,
+                        "Paint": {
+                            "Format": ot.PaintFormat.PaintGlyph,
+                            "Paint": {
+                                "Format": ot.PaintFormat.PaintRadialGradient,
+                                "x0": 250,
+                                "y0": 250,
+                                "r0": 250,
+                                "x1": 200,
+                                "y1": 200,
+                                "r1": 0,
+                                "ColorLine": {
+                                    "ColorStop": [(0.0, 1), (1.0, 2)],
+                                    "Extend": "repeat",
+                                },
+                            },
+                            "Glyph": "glyph00012",
+                        },
+                        "Transform": (0.7071, 0.7071, -0.7071, 0.7071, 0, 0),
+                    },
+                    {
+                        "Format": ot.PaintFormat.PaintGlyph,
+                        "Paint": (ot.PaintFormat.PaintSolid, (1, 0.5)),
+                        "Glyph": "glyph00013",
+                    },
+                ],
+            ),
+            "uniE002": (
+                ot.PaintFormat.PaintColrLayers,
+                [
+                    {
+                        "Format": ot.PaintFormat.PaintGlyph,
+                        "Paint": {
+                            "Format": ot.PaintFormat.PaintLinearGradient,
+                            "x0": 0,
+                            "y0": 0,
+                            "x1": 500,
+                            "y1": 500,
+                            "x2": -500,
+                            "y2": 500,
+                            "ColorLine": {"ColorStop": [(0.0, 1), (1.0, 2)]},
+                        },
+                        "Glyph": "glyph00014",
+                    },
+                    {
+                        "Format": ot.PaintFormat.PaintTransform,
+                        "Paint": {
+                            "Format": ot.PaintFormat.PaintGlyph,
+                            "Paint": (ot.PaintFormat.PaintSolid, 1),
+                            "Glyph": "glyph00015",
+                        },
+                        "Transform": (1, 0, 0, 1, 400, 400),
+                    },
+                ],
+            ),
+            "uniE003": {
+                "Format": ot.PaintFormat.PaintRotate,
+                "Paint": {
+                    "Format": ot.PaintFormat.PaintColrGlyph,
+                    "Glyph": "uniE001",
+                },
+                "angle": 45,
+                "centerX": 250,
+                "centerY": 250,
+            },
+            "uniE004": [
+                ("glyph00016", 1),
+                ("glyph00017", 2),
+            ],
+        },
+    )
+    fb.setupCPAL(
+        [
+            [
+                (1.0, 0.0, 0.0, 1.0),  # red
+                (0.0, 1.0, 0.0, 1.0),  # green
+                (0.0, 0.0, 1.0, 1.0),  # blue
+            ],
+        ],
+    )
+
+    output_path = tmp_path / "TestCOLRv1.ttf"
+    fb.save(output_path)
+
+    return output_path
+
+
+def test_subset_COLRv1_and_CPAL(colrv1_path):
+    subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
+
+    subset.main(
+        [
+            str(colrv1_path),
+            "--glyph-names",
+            f"--output-file={subset_path}",
+            "--unicodes=E002,E003,E004",
+        ]
+    )
+    subset_font = TTFont(subset_path)
+
+    glyph_set = set(subset_font.getGlyphOrder())
+
+    # uniE000 and its children are excluded from subset
+    assert "uniE000" not in glyph_set
+    assert "glyph00010" not in glyph_set
+    assert "glyph00011" not in glyph_set
+
+    # uniE001 and children are pulled in indirectly as PaintColrGlyph by uniE003
+    assert "uniE001" in glyph_set
+    assert "glyph00012" in glyph_set
+    assert "glyph00013" in glyph_set
+
+    assert "uniE002" in glyph_set
+    assert "glyph00014" in glyph_set
+    assert "glyph00015" in glyph_set
+
+    assert "uniE003" in glyph_set
+
+    assert "uniE004" in glyph_set
+    assert "glyph00016" in glyph_set
+    assert "glyph00017" in glyph_set
+
+    assert "COLR" in subset_font
+    colr = subset_font["COLR"].table
+    assert colr.Version == 1
+    assert len(colr.BaseGlyphRecordArray.BaseGlyphRecord) == 1
+    assert len(colr.BaseGlyphV1List.BaseGlyphV1Record) == 3  # was 4
+
+    base = colr.BaseGlyphV1List.BaseGlyphV1Record[0]
+    assert base.BaseGlyph == "uniE001"
+    layers = colr.LayerV1List.Paint[
+        base.Paint.FirstLayerIndex: base.Paint.FirstLayerIndex + base.Paint.NumLayers
+    ]
+    assert len(layers) == 2
+    # check v1 palette indices were remapped
+    assert layers[0].Paint.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 0
+    assert layers[0].Paint.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 1
+    assert layers[1].Paint.Color.PaletteIndex == 0
+
+    baseRecV0 = colr.BaseGlyphRecordArray.BaseGlyphRecord[0]
+    assert baseRecV0.BaseGlyph == "uniE004"
+    layersV0 = colr.LayerRecordArray.LayerRecord
+    assert len(layersV0) == 2
+    # check v0 palette indices were remapped
+    assert layersV0[0].PaletteIndex == 0
+    assert layersV0[1].PaletteIndex == 1
+
+    assert "CPAL" in subset_font
+    cpal = subset_font["CPAL"]
+    assert [
+        tuple(v / 255 for v in (c.red, c.green, c.blue, c.alpha))
+        for c in cpal.palettes[0]
+    ] == [
+        # the first color 'red' was pruned
+        (0.0, 1.0, 0.0, 1.0),  # green
+        (0.0, 0.0, 1.0, 1.0),  # blue
+    ]
+
+
+def test_subset_COLRv1_and_CPAL_drop_empty(colrv1_path):
+    subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
+
+    subset.main(
+        [
+            str(colrv1_path),
+            "--glyph-names",
+            f"--output-file={subset_path}",
+            "--glyphs=glyph00010",
+        ]
+    )
+    subset_font = TTFont(subset_path)
+
+    glyph_set = set(subset_font.getGlyphOrder())
+
+    assert "glyph00010" in glyph_set
+    assert "uniE000" not in glyph_set
+
+    assert "COLR" not in subset_font
+    assert "CPAL" not in subset_font
+
+
+def test_subset_COLRv1_downgrade_version(colrv1_path):
+    subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
+
+    subset.main(
+        [
+            str(colrv1_path),
+            "--glyph-names",
+            f"--output-file={subset_path}",
+            "--unicodes=E004",
+        ]
+    )
+    subset_font = TTFont(subset_path)
+
+    assert set(subset_font.getGlyphOrder()) == {
+        ".notdef",
+        "uniE004",
+        "glyph00016",
+        "glyph00017",
+    }
+
+    assert "COLR" in subset_font
+    assert subset_font["COLR"].version == 0
+
+
 if __name__ == "__main__":
     sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/C_O_L_R_test.py b/Tests/ttLib/tables/C_O_L_R_test.py
index 7f3f71e..4855f58 100644
--- a/Tests/ttLib/tables/C_O_L_R_test.py
+++ b/Tests/ttLib/tables/C_O_L_R_test.py
@@ -46,13 +46,15 @@
 def diff_binary_fragments(font_bytes, expected_fragments):
     pos = 0
     prev_desc = ""
+    errors = 0
     for expected_bytes, description in expected_fragments:
         actual_bytes = font_bytes[pos : pos + len(expected_bytes)]
-        assert (
-            actual_bytes == expected_bytes
-        ), f'{description} (previous "{prev_desc}", bytes: {str(font_bytes[pos:pos+16])}'
+        if actual_bytes != expected_bytes:
+            print(f'{description} (previous "{prev_desc}", actual_bytes: {"".join("%02x" % v for v in actual_bytes)} bytes: {str(font_bytes[pos:pos+16])}')
+            errors += 1
         pos += len(expected_bytes)
         prev_desc = description
+    assert errors == 0
     assert pos == len(
         font_bytes
     ), f"Leftover font bytes, used {pos} of {len(font_bytes)}"
@@ -106,115 +108,135 @@
     (b"\x00\x00\x00 ", "Offset to LayerRecordArray from beginning of table (32)"),
     (b"\x00\x03", "LayerRecordCount (3)"),
     (b"\x00\x00\x00,", "Offset to BaseGlyphV1List from beginning of table (44)"),
-    (b"\x00\x00\x00\x81", "Offset to LayerV1List from beginning of table (129)"),
+    (b"\x00\x00\x00\xac", "Offset to LayerV1List from beginning of table (172)"),
     (b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"),
     (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
     (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"),
-    (b"\x00\x04", "BaseGlyphRecord[0].NumLayers (4)"),
+    (b"\x00\x03", "BaseGlyphRecord[0].NumLayers (3)"),
     (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"),
     (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"),
     (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"),
     (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"),
     (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"),
     (b"\x00\x02", "LayerRecord[2].PaletteIndex (2)"),
-    (b"\x00\x00\x00\x02", "BaseGlyphV1List.BaseGlyphCount (2)"),
+    # BaseGlyphV1List
+    (b"\x00\x00\x00\x03", "BaseGlyphV1List.BaseGlyphCount (3)"),
     (b"\x00\n", "BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph (10)"),
     (
-        b"\x00\x00\x00\x10",
-        "Offset to Paint table from beginning of BaseGlyphV1List (16)",
-    ),
-    (b"\x00\x0e", "BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14)"),
-    (
         b"\x00\x00\x00\x16",
         "Offset to Paint table from beginning of BaseGlyphV1List (22)",
     ),
+    (b"\x00\x0e", "BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14)"),
+    (
+        b"\x00\x00\x00\x1c",
+        "Offset to Paint table from beginning of BaseGlyphV1List (28)",
+    ),
+    (b"\x00\x0f", "BaseGlyphV1List.BaseGlyphV1Record[2].BaseGlyph (15)"),
+    (
+        b"\x00\x00\x00\x5b",
+        "Offset to Paint table from beginning of BaseGlyphV1List (91)",
+    ),
+    # BaseGlyphV1Record[0]
     (b"\x01", "BaseGlyphV1Record[0].Paint.Format (1)"),
     (b"\x04", "BaseGlyphV1Record[0].Paint.NumLayers (4)"),
     (b"\x00\x00\x00\x00", "BaseGlyphV1Record[0].Paint.FirstLayerIndex (0)"),
-    (b"\x0B", "BaseGlyphV1Record[1].Paint.Format (11)"),
+    # BaseGlyphV1Record[1]
+    (b"\x14", "BaseGlyphV1Record[1].Paint.Format (20)"),
     (b"\x00\x00<", "Offset to SourcePaint from beginning of PaintComposite (60)"),
     (b"\x03", "BaseGlyphV1Record[1].Paint.CompositeMode [SRC_OVER] (3)"),
     (b"\x00\x00\x08", "Offset to BackdropPaint from beginning of PaintComposite (8)"),
-    (b"\x07", "BaseGlyphV1Record[1].Paint.BackdropPaint.Format (7)"),
-    (b"\x00\x00\x34", "Offset to Paint from beginning of PaintTransform (52)"),
+    (b"\x0d", "BaseGlyphV1Record[1].Paint.BackdropPaint.Format (13)"),
+    (b"\x00\x00\x34", "Offset to Paint from beginning of PaintVarTransform (52)"),
     (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (1.0)"),
     (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (0.0)"),
     (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.yx.value (0.0)"),
     (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (1.0)"),
     (b"\x01\x2c\x00\x00\x00\x00\x00\x00", "Affine2x3.dx.value (300.0)"),
     (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.dy.value (0.0)"),
-    (b"\x06", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (6)"),
+    (b"\x0b", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (11)"),
     (b"\x00\n", "BaseGlyphV1Record[1].Paint.SourcePaint.Glyph (10)"),
+    # BaseGlyphV1Record[2]
+    (b"\x0a", "BaseGlyphV1Record[2].Paint.Format (10)"),
+    (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
+    (b"\x00\x0b", "BaseGlyphV1Record[2].Paint.Glyph (11)"),
+    (b"\x08", "BaseGlyphV1Record[2].Paint.Paint.Format (8)"),
+    (b"\x00\x00\x10", "Offset to ColorLine from beginning of PaintSweepGradient (16)"),
+    (b"\x01\x03", "centerX (259)"),
+    (b"\x01\x2c", "centerY (300)"),
+    (b"\x00\x2d\x00\x00", "startAngle (45.0)"),
+    (b"\x00\x87\x00\x00", "endAngle (135.0)"),
+    (b"\x00", "ColorLine.Extend (0; pad)"),
+    (b"\x00\x02", "ColorLine.StopCount (2)"),
+    (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset (0.0)"),
+    (b"\x00\x03", "ColorLine.ColorStop[0].Color.PaletteIndex (3)"),
+    (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha (1.0)"),
+    (b"@\x00", "ColorLine.ColorStop[1].StopOffset (1.0)"),
+    (b"\x00\x05", "ColorLine.ColorStop[1].Color.PaletteIndex (5)"),
+    (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha (1.0)"),
+    # LayerV1List
     (b"\x00\x00\x00\x04", "LayerV1List.LayerCount (4)"),
     (
         b"\x00\x00\x00\x14",
         "First Offset to Paint table from beginning of LayerV1List (20)",
     ),
     (
-        b"\x00\x00\x00\x1a",
-        "Second Offset to Paint table from beginning of LayerV1List (26)",
+        b"\x00\x00\x00\x23",
+        "Second Offset to Paint table from beginning of LayerV1List (35)",
     ),
     (
-        b"\x00\x00\x00u",
-        "Third Offset to Paint table from beginning of LayerV1List (117)",
+        b"\x00\x00\x00\x4e",
+        "Third Offset to Paint table from beginning of LayerV1List (78)",
     ),
     (
-        b"\x00\x00\x00\xf6",
-        "Fourth Offset to Paint table from beginning of LayerV1List (246)",
+        b"\x00\x00\x00\xb7",
+        "Fourth Offset to Paint table from beginning of LayerV1List (183)",
     ),
     # PaintGlyph glyph00011
-    (b"\x05", "LayerV1List.Paint[0].Format (5)"),
-    (b"\x00\x01<", "Offset24 to Paint subtable from beginning of PaintGlyph (316)"),
+    (b"\x0a", "LayerV1List.Paint[0].Format (10)"),
+    (b"\x00\x00\x06", "Offset24 to Paint subtable from beginning of PaintGlyph (6)"),
     (b"\x00\x0b", "LayerV1List.Paint[0].Glyph (glyph00011)"),
+    # PaintVarSolid
+    (b"\x03", "LayerV1List.Paint[0].Paint.Format (3)"),
+    (b"\x00\x02", "Paint.Color.PaletteIndex (2)"),
+    (b" \x00", "Paint.Color.Alpha.value (0.5)"),
+    (b"\x00\x00\x00\x00", "Paint.Color.Alpha.varIdx (0)"),
     # PaintGlyph glyph00012
-    (b"\x05", "LayerV1List.Paint[1].Format (5)"),
+    (b"\x0a", "LayerV1List.Paint[1].Format (10)"),
     (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
     (b"\x00\x0c", "LayerV1List.Paint[1].Glyph (glyph00012)"),
-    (b"\x03", "LayerV1List.Paint[1].Paint.Format (3)"),
-    (b"\x00\x00(", "Offset to ColorLine from beginning of PaintLinearGradient (40)"),
-    (b"\x00\x01", "Paint.x0.value (1)"),
-    (b"\x00\x00\x00\x00", "Paint.x0.varIdx (0)"),
-    (b"\x00\x02", "Paint.y0.value (2)"),
-    (b"\x00\x00\x00\x00", "Paint.y0.varIdx (0)"),
-    (b"\xff\xfd", "Paint.x1.value (-3)"),
-    (b"\x00\x00\x00\x00", "Paint.x1.varIdx (0)"),
-    (b"\xff\xfc", "Paint.y1.value (-4)"),
-    (b"\x00\x00\x00\x00", "Paint.y1.varIdx (0)"),
-    (b"\x00\x05", "Paint.x2.value (5)"),
-    (b"\x00\x00\x00\x00", "Paint.x2.varIdx (0)"),
-    (b"\x00\x06", "Paint.y2.value (6)"),
-    (b"\x00\x00\x00\x00", "Paint.y2.varIdx (0)"),
+    (b"\x04", "LayerV1List.Paint[1].Paint.Format (4)"),
+    (b"\x00\x00\x10", "Offset to ColorLine from beginning of PaintLinearGradient (16)"),
+    (b"\x00\x01", "Paint.x0 (1)"),
+    (b"\x00\x02", "Paint.y0 (2)"),
+    (b"\xff\xfd", "Paint.x1 (-3)"),
+    (b"\xff\xfc", "Paint.y1 (-4)"),
+    (b"\x00\x05", "Paint.x2 (5)"),
+    (b"\x00\x06", "Paint.y2 (6)"),
     (b"\x01", "ColorLine.Extend (1; repeat)"),
     (b"\x00\x03", "ColorLine.StopCount (3)"),
-    (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset.value (0.0)"),
-    (b"\x00\x00\x00\x00", "ColorLine.ColorStop[0].StopOffset.varIdx (0)"),
+    (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset (0.0)"),
     (b"\x00\x03", "ColorLine.ColorStop[0].Color.PaletteIndex (3)"),
-    (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha.value (1.0)"),
-    (b"\x00\x00\x00\x00", "ColorLine.ColorStop[0].Color.Alpha.varIdx (0)"),
-    (b" \x00", "ColorLine.ColorStop[1].StopOffset.value (0.5)"),
-    (b"\x00\x00\x00\x00", "ColorLine.ColorStop[1].StopOffset.varIdx (0)"),
+    (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha (1.0)"),
+    (b" \x00", "ColorLine.ColorStop[1].StopOffset (0.5)"),
     (b"\x00\x04", "ColorLine.ColorStop[1].Color.PaletteIndex (4)"),
-    (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha.value (1.0)"),
-    (b"\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.varIdx (0)"),
-    (b"@\x00", "ColorLine.ColorStop[2].StopOffset.value (1.0)"),
-    (b"\x00\x00\x00\x00", "ColorLine.ColorStop[2].StopOffset.varIdx (0)"),
+    (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha (1.0)"),
+    (b"@\x00", "ColorLine.ColorStop[2].StopOffset (1.0)"),
     (b"\x00\x05", "ColorLine.ColorStop[2].Color.PaletteIndex (5)"),
-    (b"@\x00", "ColorLine.ColorStop[2].Color.Alpha.value (1.0)"),
-    (b"\x00\x00\x00\x00", "ColorLine.ColorStop[2].Color.Alpha.varIdx (0)"),
+    (b"@\x00", "ColorLine.ColorStop[2].Color.Alpha (1.0)"),
     # PaintGlyph glyph00013
-    (b"\x05", "LayerV1List.Paint[2].Format (5)"),
+    (b"\x0a", "LayerV1List.Paint[2].Format (10)"),
     (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
     (b"\x00\r", "LayerV1List.Paint[2].Glyph (13)"),
-    (b"\x07", "LayerV1List.Paint[2].Paint.Format (5)"),
-    (b"\x00\x00\x34", "Offset to Paint subtable from beginning of PaintTransform (52)"),
-    (b"\xff\xf3\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (-13)"),
-    (b"\x00\x0e\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (14)"),
-    (b"\x00\x0f\x00\x00\x00\x00\x00\x00", "Affine2x3.yx.value (15)"),
-    (b"\xff\xef\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (-17)"),
-    (b"\x00\x12\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (18)"),
-    (b"\x00\x13\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (19)"),
-    (b"\x04", "LayerV1List.Paint[2].Paint.Paint.Format (4)"),
-    (b"\x00\x00(", "Offset to ColorLine from beginning of PaintRadialGradient (40)"),
+    (b"\x0c", "LayerV1List.Paint[2].Paint.Format (12)"),
+    (b"\x00\x00\x1c", "Offset to Paint subtable from beginning of PaintTransform (28)"),
+    (b"\xff\xf3\x00\x00", "Affine2x3.xx (-13)"),
+    (b"\x00\x0e\x00\x00", "Affine2x3.xy (14)"),
+    (b"\x00\x0f\x00\x00", "Affine2x3.yx (15)"),
+    (b"\xff\xef\x00\x00", "Affine2x3.yy (-17)"),
+    (b"\x00\x12\x00\x00", "Affine2x3.yy (18)"),
+    (b"\x00\x13\x00\x00", "Affine2x3.yy (19)"),
+    (b"\x07", "LayerV1List.Paint[2].Paint.Paint.Format (7)"),
+    (b"\x00\x00(", "Offset to ColorLine from beginning of PaintVarRadialGradient (40)"),
     (b"\x00\x07\x00\x00\x00\x00", "Paint.x0.value (7)"),
     (b"\x00\x08\x00\x00\x00\x00", "Paint.y0.value (8)"),
     (b"\x00\t\x00\x00\x00\x00", "Paint.r0.value (9)"),
@@ -230,32 +252,31 @@
     (b"\x00\x07", "ColorLine.ColorStop[1].Color.PaletteIndex (7)"),
     (b"\x19\x9a\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.value (0.4)"),
     # PaintTranslate
-    (b"\x08", "LayerV1List.Paint[3].Format (8)"),
-    (b"\x00\x00\x14", "Offset to Paint subtable from beginning of PaintTranslate (20)"),
-    (b"\x01\x01\x00\x00\x00\x00\x00\x00", "dx.value (257)"),
-    (b"\x01\x02\x00\x00\x00\x00\x00\x00", "dy.value (258)"),
+    (b"\x0e", "LayerV1List.Paint[3].Format (14)"),
+    (b"\x00\x00\x0c", "Offset to Paint subtable from beginning of PaintTranslate (12)"),
+    (b"\x01\x01\x00\x00", "dx (257)"),
+    (b"\x01\x02\x00\x00", "dy (258)"),
     # PaintRotate
-    (b"\x09", "LayerV1List.Paint[3].Paint.Format (9)"),
-    (b"\x00\x00\x1c", "Offset to Paint subtable from beginning of PaintRotate (28)"),
-    (b"\x00\x2d\x00\x00\x00\x00\x00\x00", "angle.value (45)"),
-    (b"\x00\xff\x00\x00\x00\x00\x00\x00", "centerX.value (255)"),
-    (b"\x01\x00\x00\x00\x00\x00\x00\x00", "centerY.value (256)"),
+    (b"\x10", "LayerV1List.Paint[3].Paint.Format (16)"),
+    (b"\x00\x00\x10", "Offset to Paint subtable from beginning of PaintRotate (16)"),
+    (b"\x00\x2d\x00\x00", "angle (45)"),
+    (b"\x00\xff\x00\x00", "centerX (255)"),
+    (b"\x01\x00\x00\x00", "centerY (256)"),
     # PaintSkew
-    (b"\x0a", "LayerV1List.Paint[3].Paint.Paint.Format (10)"),
-    (b"\x00\x00\x24", "Offset to Paint subtable from beginning of PaintSkew (36)"),
-    (b"\xff\xf5\x00\x00\x00\x00\x00\x00", "xSkewAngle (-11)"),
-    (b"\x00\x05\x00\x00\x00\x00\x00\x00", "ySkewAngle (5)"),
-    (b"\x00\xfd\x00\x00\x00\x00\x00\x00", "centerX.value (253)"),
-    (b"\x00\xfe\x00\x00\x00\x00\x00\x00", "centerY.value (254)"),
+    (b"\x12", "LayerV1List.Paint[3].Paint.Paint.Format (18)"),
+    (b"\x00\x00\x14", "Offset to Paint subtable from beginning of PaintSkew (20)"),
+    (b"\xff\xf5\x00\x00", "xSkewAngle (-11)"),
+    (b"\x00\x05\x00\x00", "ySkewAngle (5)"),
+    (b"\x00\xfd\x00\x00", "centerX.value (253)"),
+    (b"\x00\xfe\x00\x00", "centerY.value (254)"),
     # PaintGlyph
-    (b"\x05", "LayerV1List.Paint[2].Format (5)"),
+    (b"\x0a", "LayerV1List.Paint[3].Paint.Paint.Paint.Format (10)"),
     (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
     (b"\x00\x0b", "LayerV1List.Paint[2].Glyph (11)"),
     # PaintSolid
-    (b"\x02", "LayerV1List.Paint[0].Paint.Format (2)"),
+    (b"\x02", "LayerV1List.Paint[0].Paint.Paint.Paint.Paint.Format (2)"),
     (b"\x00\x02", "Paint.Color.PaletteIndex (2)"),
-    (b" \x00", "Paint.Color.Alpha.value (0.5)"),
-    (b"\x00\x00\x00\x00", "Paint.Color.Alpha.varIdx (0)"),
+    (b" \x00", "Paint.Color.Alpha (0.5)"),
 )
 
 COLR_V1_DATA = b"".join(t[0] for t in COLR_V1_SAMPLE)
@@ -267,7 +288,7 @@
     '  <BaseGlyphRecord index="0">',
     '    <BaseGlyph value="glyph00006"/>',
     '    <FirstLayerIndex value="0"/>',
-    '    <NumLayers value="4"/>',
+    '    <NumLayers value="3"/>',
     "  </BaseGlyphRecord>",
     "</BaseGlyphRecordArray>",
     "<LayerRecordArray>",
@@ -286,7 +307,7 @@
     "</LayerRecordArray>",
     "<!-- LayerRecordCount=3 -->",
     "<BaseGlyphV1List>",
-    "  <!-- BaseGlyphCount=2 -->",
+    "  <!-- BaseGlyphCount=3 -->",
     '  <BaseGlyphV1Record index="0">',
     '    <BaseGlyph value="glyph00010"/>',
     '    <Paint Format="1"><!-- PaintColrLayers -->',
@@ -296,13 +317,13 @@
     "  </BaseGlyphV1Record>",
     '  <BaseGlyphV1Record index="1">',
     '    <BaseGlyph value="glyph00014"/>',
-    '    <Paint Format="11"><!-- PaintComposite -->',
-    '      <SourcePaint Format="6"><!-- PaintColrGlyph -->',
+    '    <Paint Format="20"><!-- PaintComposite -->',
+    '      <SourcePaint Format="11"><!-- PaintColrGlyph -->',
     '        <Glyph value="glyph00010"/>',
     "      </SourcePaint>",
     '      <CompositeMode value="src_over"/>',
-    '      <BackdropPaint Format="7"><!-- PaintTransform -->',
-    '        <Paint Format="6"><!-- PaintColrGlyph -->',
+    '      <BackdropPaint Format="13"><!-- PaintVarTransform -->',
+    '        <Paint Format="11"><!-- PaintColrGlyph -->',
     '          <Glyph value="glyph00010"/>',
     "        </Paint>",
     "        <Transform>",
@@ -316,11 +337,41 @@
     "      </BackdropPaint>",
     "    </Paint>",
     "  </BaseGlyphV1Record>",
+    '  <BaseGlyphV1Record index="2">',
+    '    <BaseGlyph value="glyph00015"/>',
+    '    <Paint Format="10"><!-- PaintGlyph -->',
+    '      <Paint Format="8"><!-- PaintSweepGradient -->',
+    "        <ColorLine>",
+    '          <Extend value="pad"/>',
+    "          <!-- StopCount=2 -->",
+    '          <ColorStop index="0">',
+    '            <StopOffset value="0.0"/>',
+    "            <Color>",
+    '              <PaletteIndex value="3"/>',
+    '              <Alpha value="1.0"/>',
+    "            </Color>",
+    "          </ColorStop>",
+    '          <ColorStop index="1">',
+    '            <StopOffset value="1.0"/>',
+    "            <Color>",
+    '              <PaletteIndex value="5"/>',
+    '              <Alpha value="1.0"/>',
+    "            </Color>",
+    "          </ColorStop>",
+    "        </ColorLine>",
+    '        <centerX value="259"/>',
+    '        <centerY value="300"/>',
+    '        <startAngle value="45.0"/>',
+    '        <endAngle value="135.0"/>',
+    "      </Paint>",
+    '      <Glyph value="glyph00011"/>',
+    "    </Paint>",
+    "  </BaseGlyphV1Record>",
     "</BaseGlyphV1List>",
     "<LayerV1List>",
     "  <!-- LayerCount=4 -->",
-    '  <Paint index="0" Format="5"><!-- PaintGlyph -->',
-    '    <Paint Format="2"><!-- PaintSolid -->',
+    '  <Paint index="0" Format="10"><!-- PaintGlyph -->',
+    '    <Paint Format="3"><!-- PaintVarSolid -->',
     "      <Color>",
     '        <PaletteIndex value="2"/>',
     '        <Alpha value="0.5"/>',
@@ -328,8 +379,8 @@
     "    </Paint>",
     '    <Glyph value="glyph00011"/>',
     "  </Paint>",
-    '  <Paint index="1" Format="5"><!-- PaintGlyph -->',
-    '    <Paint Format="3"><!-- PaintLinearGradient -->',
+    '  <Paint index="1" Format="10"><!-- PaintGlyph -->',
+    '    <Paint Format="4"><!-- PaintLinearGradient -->',
     "      <ColorLine>",
     '        <Extend value="repeat"/>',
     "        <!-- StopCount=3 -->",
@@ -364,9 +415,9 @@
     "    </Paint>",
     '    <Glyph value="glyph00012"/>',
     "  </Paint>",
-    '  <Paint index="2" Format="5"><!-- PaintGlyph -->',
-    '    <Paint Format="7"><!-- PaintTransform -->',
-    '      <Paint Format="4"><!-- PaintRadialGradient -->',
+    '  <Paint index="2" Format="10"><!-- PaintGlyph -->',
+    '    <Paint Format="12"><!-- PaintTransform -->',
+    '      <Paint Format="7"><!-- PaintVarRadialGradient -->',
     "        <ColorLine>",
     '          <Extend value="pad"/>',
     "          <!-- StopCount=2 -->",
@@ -403,10 +454,10 @@
     "    </Paint>",
     '    <Glyph value="glyph00013"/>',
     "  </Paint>",
-    '  <Paint index="3" Format="8"><!-- PaintTranslate -->',
-    '    <Paint Format="9"><!-- PaintRotate -->',
-    '      <Paint Format="10"><!-- PaintSkew -->',
-    '        <Paint Format="5"><!-- PaintGlyph -->',
+    '  <Paint index="3" Format="14"><!-- PaintTranslate -->',
+    '    <Paint Format="16"><!-- PaintRotate -->',
+    '      <Paint Format="18"><!-- PaintSkew -->',
+    '        <Paint Format="10"><!-- PaintGlyph -->',
     '          <Paint Format="2"><!-- PaintSolid -->',
     "            <Color>",
     '              <PaletteIndex value="2"/>',
diff --git a/setup.cfg b/setup.cfg
index 7691af5..75ddb71 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 4.19.1
+current_version = 4.20.0
 commit = True
 tag = False
 tag_name = {new_version}
diff --git a/setup.py b/setup.py
index 7f13122..e3e7b52 100755
--- a/setup.py
+++ b/setup.py
@@ -441,7 +441,7 @@
 
 setup_params = dict(
 	name="fonttools",
-	version="4.19.1",
+	version="4.20.0",
 	description="Tools to manipulate font files",
 	author="Just van Rossum",
 	author_email="just@letterror.com",