December 2019
We propose an extension of the COLR table to support greater graphic capabilities. The current version number of COLR table is 0. We propose this as COLR table format version 1.
The initial design proposal aimed to add support for gradient fills, in addition to the existing solid color fills, and to integrate variation into color formats in variable fonts. As the design work progressed, it seemed appropriate to introduce other capabilities:
- Different composition modes
- Affine transformations
- Flexible means of re-use of components to provide size reduction
While extending the design for these additional capabilities, it also seemed appropriate to change the basic structure for color glyph descriptions from a vector of layered elements to be a directed, acyclic graph.
It is our understanding that this brings the capabilities of COLR/CPAL to nearly match those of SVG Native for vector graphics. SVG Native allows embedding PNG and JPEG images while this proposal does not. We'd like to explore in the future, how COLR/CPAL can be mixed with sbix to address that limitation as well.
The aim is for version 1 of the COLR table to be incorporated into a future version of the OpenType spec and into an update of the ISO/IEC 14496-22 Open Font Format (OFF) standard.
Earlier revisions of this document provided many details regarding the proposed COLR extensions. These have since been moved and elaborated with full details in a separate document for submission to OFF—see the next section.
The COLR version 1 enhancements have been proposed for incorporation into the ISO/IEC 14496-22 Open Font Format standard. In ISO process, this would be done as an amendment (Amendment 2) of the current edition of that standard. Content for a working draft of that amendment has been prepared in a separate doc: Proposed changes to ISO/IEC 14496-22 (Amendment 2).
This section is NOT meant for ISO submissions
Cosimo (@anthrotype) and Rod (@rsheeter) have implemented nanoemoji to compile a set of SVGs into color font formats, including COLR v1.
color-fonts has a collection of sample color fonts.
Chrome Canary as of version 90.0.4421.5 and above supports COLRv1 fonts when switching on the COLR v1 flag. Chrome 98+ ship COLRv1 enabled by default, see https://developer.chrome.com/blog/colrv1-fonts/.
To enable the feature and experiment with it, follow these steps:
- Download and open Chrome Canary, make sure it's updated to a version equal or newer than 90.0.4421.5.
- Go to chrome://flags/#colr-v1-fonts and enable the feature.
Skia support is in tip-of-tree Skia, implementation details
FreeType support is in tip-of-tree FreeType, see freetype.h for API details.
The following provides a C++ implementation of the structures defined above.
// Base template types
template <typename T, typename Length=uint16>
struct ArrayOf
{
Length count;
T array[/*count*/];
};
// Index of the first variation record in the variation index map or
// variation store if no variation index map is present.
// A record with N variable fields finds them at VarIdxBase+0, ...,+N-1
// Use 0xFFFFFFFF to indicate no variation.
typedef uint32 VarIdxBase;
// Color structures
// The (Var)ColorStop alpha is multiplied into the alpha of the CPAL entry
// (converted to float -- divide by 255) looked up using paletteIndex to
// produce a final alpha.
struct ColorStop
{
F2DOT14 stopOffset;
uint16 paletteIndex;
F2DOT14 alpha; // Default 1.0. Values outside [0.,1.] reserved.
};
struct VarColorStop
{
F2DOT14 stopOffset; // VarIdx varIndexBase + 0
uint16 paletteIndex;
F2DOT14 alpha; // Default 1.0. Values outside [0.,1.] reserved.
// VarIdx varIndexBase + 1
VarIdxBase varIndexBase;
};
enum Extend : uint8
{
EXTEND_PAD = 0,
EXTEND_REPEAT = 1,
EXTEND_REFLECT = 2,
};
struct ColorLine
{
Extend extend;
ArrayOf<ColorStop> colorStops;
};
struct VarColorLine
{
Extend extend;
ArrayOf<VarColorStop> colorStops;
};
// Composition modes
// Compositing modes are taken from https://www.w3.org/TR/compositing-1/
// NOTE: a brief audit of major implementations suggests most support most
// or all of the specified modes.
enum CompositeMode : uint8
{
// Porter-Duff modes
// https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators
COMPOSITE_CLEAR = 0, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_clear
COMPOSITE_SRC = 1, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_src
COMPOSITE_DEST = 2, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dst
COMPOSITE_SRC_OVER = 3, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcover
COMPOSITE_DEST_OVER = 4, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstover
COMPOSITE_SRC_IN = 5, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin
COMPOSITE_DEST_IN = 6, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstin
COMPOSITE_SRC_OUT = 7, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcout
COMPOSITE_DEST_OUT = 8, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstout
COMPOSITE_SRC_ATOP = 9, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcatop
COMPOSITE_DEST_ATOP = 10, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstatop
COMPOSITE_XOR = 11, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_xor
COMPOSITE_PLUS = 12, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_plus
// Blend modes
// https://www.w3.org/TR/compositing-1/#blending
COMPOSITE_SCREEN = 13, // https://www.w3.org/TR/compositing-1/#blendingscreen
COMPOSITE_OVERLAY = 14, // https://www.w3.org/TR/compositing-1/#blendingoverlay
COMPOSITE_DARKEN = 15, // https://www.w3.org/TR/compositing-1/#blendingdarken
COMPOSITE_LIGHTEN = 16, // https://www.w3.org/TR/compositing-1/#blendinglighten
COMPOSITE_COLOR_DODGE = 17, // https://www.w3.org/TR/compositing-1/#blendingcolordodge
COMPOSITE_COLOR_BURN = 18, // https://www.w3.org/TR/compositing-1/#blendingcolorburn
COMPOSITE_HARD_LIGHT = 19, // https://www.w3.org/TR/compositing-1/#blendinghardlight
COMPOSITE_SOFT_LIGHT = 20, // https://www.w3.org/TR/compositing-1/#blendingsoftlight
COMPOSITE_DIFFERENCE = 21, // https://www.w3.org/TR/compositing-1/#blendingdifference
COMPOSITE_EXCLUSION = 22, // https://www.w3.org/TR/compositing-1/#blendingexclusion
COMPOSITE_MULTIPLY = 23, // https://www.w3.org/TR/compositing-1/#blendingmultiply
// Modes that, uniquely, do not operate on components
// https://www.w3.org/TR/compositing-1/#blendingnonseparable
COMPOSITE_HSL_HUE = 24, // https://www.w3.org/TR/compositing-1/#blendinghue
COMPOSITE_HSL_SATURATION = 25, // https://www.w3.org/TR/compositing-1/#blendingsaturation
COMPOSITE_HSL_COLOR = 26, // https://www.w3.org/TR/compositing-1/#blendingcolor
COMPOSITE_HSL_LUMINOSITY = 27, // https://www.w3.org/TR/compositing-1/#blendingluminosity
};
// Affine 2D transformations
// This is a standard 2x3 matrix for 2D affine transformation.
struct Affine2x3
{
Fixed xx;
Fixed yx;
Fixed xy;
Fixed yy;
Fixed dx;
Fixed dy;
};
struct VarAffine2x3
{
Fixed xx; // VarIdx varIndexBase + 0
Fixed yx; // VarIdx varIndexBase + 1
Fixed xy; // VarIdx varIndexBase + 2
Fixed yy; // VarIdx varIndexBase + 3
Fixed dx; // VarIdx varIndexBase + 4
Fixed dy; // VarIdx varIndexBase + 5
VarIdxBase varIndexBase;
};
// Paint tables
// Each layer is composited on top of previous with mode COMPOSITE_SRC_OVER.
// NOTE: uint8 size saves bytes in most cases and does not
// preclude use of large layer counts via PaintComposite or a tree
// of PaintColrLayers.
struct PaintColrLayers
{
uint8 format; // = 1
uint8 numLayers;
uint32 firstLayerIndex; // index into COLRv1::layerList
}
// The Paint(Var)Solid alpha is multiplied into the alpha of the CPAL entry
// (converted to float -- divide by 255) looked up using paletteIndex to
// produce a final alpha.
struct PaintSolid
{
uint8 format; // = 2
uint16 paletteIndex;
F2DOT14 alpha; // Default 1.0. Values outside [0.,1.] reserved.
};
struct PaintVarSolid
{
uint8 format; // = 3
uint16 paletteIndex;
F2DOT14 alpha; // Default 1.0. Values outside [0.,1.] reserved.
// VarIdx varIndexBase + 0
VarIdxBase varIndexBase;
};
struct PaintLinearGradient
{
uint8 format; // = 4
Offset24<ColorLine> colorLine;
FWORD x0;
FWORD y0;
FWORD x1;
FWORD y1;
FWORD x2; // Normal; Equal to (x1,y1) in simple cases.
FWORD y2;
};
struct PaintVarLinearGradient
{
uint8 format; // = 5
Offset24<VarColorLine> colorLine;
FWORD x0; // VarIdx varIndexBase + 0
FWORD y0; // VarIdx varIndexBase + 1
FWORD x1; // VarIdx varIndexBase + 2
FWORD y1; // VarIdx varIndexBase + 3
FWORD x2; // VarIdx varIndexBase + 4. Normal; Equal to (x1,y1) in simple cases.
FWORD y2; // VarIdx varIndexBase + 5
VarIdxBase varIndexBase;
};
struct PaintRadialGradient
{
uint8 format; // = 6
Offset24<ColorLine> colorLine;
FWORD x0;
FWORD y0;
UFWORD radius0;
FWORD x1;
FWORD y1;
UFWORD radius1;
};
struct PaintVarRadialGradient
{
uint8 format; // = 7
Offset24<VarColorLine> colorLine;
FWORD x0; // VarIdx varIndexBase + 0
FWORD y0; // VarIdx varIndexBase + 1
UFWORD radius0; // VarIdx varIndexBase + 2
FWORD x1; // VarIdx varIndexBase + 3
FWORD y1; // VarIdx varIndexBase + 4
UFWORD radius1; // VarIdx varIndexBase + 5
VarIdxBase varIndexBase;
};
struct PaintSweepGradient
{
uint8 format; // = 8
Offset24<ColorLine> colorLine;
FWORD centerX;
FWORD centerY;
F2DOT14 startAngle; // 180° in counter-clockwise degrees per 1.0 of value
F2DOT14 endAngle; // 180° in counter-clockwise degrees per 1.0 of value
};
struct PaintVarSweepGradient
{
uint8 format; // = 9
Offset24<VarColorLine> colorLine;
FWORD centerX; // VarIdx varIndexBase + 0
FWORD centerY; // VarIdx varIndexBase + 1
F2DOT14 startAngle; // VarIdx varIndexBase + 2. 180° in counter-clockwise degrees per 1.0 of value
F2DOT14 endAngle; // VarIdx varIndexBase + 3. 180° in counter-clockwise degrees per 1.0 of value
VarIdxBase varIndexBase;
};
// Paint a non-COLR glyph, filled as indicated by paint.
struct PaintGlyph
{
uint8 format; // = 10
Offset24<Paint> paint;
uint16 glyphID; // not a COLR-only gid
// shall be less than maxp.numGlyphs
}
struct PaintColrGlyph
{
uint8 format; // = 11
uint16 glyphID; // shall be a COLR gid
}
struct PaintTransform
{
uint8 format; // = 12
Offset24<Paint> paint;
Offset24<Affine2x3> transform;
};
struct PaintVarTransform
{
uint8 format; // = 13
Offset24<Paint> paint;
Offset24<VarAffine2x3> transform;
};
struct PaintTranslate
{
uint8 format; // = 14
Offset24<Paint> paint;
FWORD dx;
FWORD dy;
};
struct PaintVarTranslate
{
uint8 format; // = 15
Offset24<Paint> paint;
FWORD dx; // VarIdx varIndexBase + 0
FWORD dy; // VarIdx varIndexBase + 1
VarIdxBase varIndexBase;
};
struct PaintScale
{
uint8 format; // = 16
Offset24<Paint> paint;
F2DOT14 scaleX;
F2DOT14 scaleY;
};
struct PaintVarScale
{
uint8 format; // = 17
Offset24<Paint> paint;
F2DOT14 scaleX; // VarIdx varIndexBase + 0
F2DOT14 scaleY; // VarIdx varIndexBase + 1
VarIdxBase varIndexBase;
};
struct PaintScaleAroundCenter
{
uint8 format; // = 18
Offset24<Paint> paint;
F2DOT14 scaleX;
F2DOT14 scaleY;
FWORD centerX;
FWORD centerY;
};
struct PaintVarScaleAroundCenter
{
uint8 format; // = 19
Offset24<Paint> paint;
F2DOT14 scaleX; // VarIdx varIndexBase + 0
F2DOT14 scaleY; // VarIdx varIndexBase + 1
FWORD centerX; // VarIdx varIndexBase + 2
FWORD centerY; // VarIdx varIndexBase + 3
VarIdxBase varIndexBase;
};
struct PaintScaleUniform
{
uint8 format; // = 20
Offset24<Paint> paint;
F2DOT14 scale;
};
struct PaintVarScaleUniform
{
uint8 format; // = 21
Offset24<Paint> paint;
F2DOT14 scale; // VarIdx varIndexBase + 0
VarIdxBase varIndexBase;
};
struct PaintScaleUniformAroundCenter
{
uint8 format; // = 22
Offset24<Paint> paint;
F2DOT14 scale;
FWORD centerX;
FWORD centerY;
};
struct PaintVarScaleUniformAroundCenter
{
uint8 format; // = 23
Offset24<Paint> paint;
F2DOT14 scale; // VarIdx varIndexBase + 0
FWORD centerX; // VarIdx varIndexBase + 1
FWORD centerY; // VarIdx varIndexBase + 2
VarIdxBase varIndexBase;
};
struct PaintRotate
{
uint8 format; // = 24
Offset24<Paint> paint;
F2DOT14 angle; // 180° in counter-clockwise degrees per 1.0 of value
};
struct PaintVarRotate
{
uint8 format; // = 25
Offset24<Paint> paint;
F2DOT14 angle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
VarIdxBase varIndexBase;
};
struct PaintRotateAroundCenter
{
uint8 format; // = 26
Offset24<Paint> paint;
F2DOT14 angle; // 180° in counter-clockwise degrees per 1.0 of value
FWORD centerX;
FWORD centerY;
};
struct PaintVarRotateAroundCenter
{
uint8 format; // = 27
Offset24<Paint> paint;
F2DOT14 angle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
FWORD centerX; // VarIdx varIndexBase + 1
FWORD centerY; // VarIdx varIndexBase + 2
VarIdxBase varIndexBase;
};
struct PaintSkew
{
uint8 format; // = 28
Offset24<Paint> paint;
F2DOT14 xSkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
F2DOT14 ySkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
};
struct PaintVarSkew
{
uint8 format; // = 29
Offset24<Paint> paint;
F2DOT14 xSkewAngle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
F2DOT14 ySkewAngle; // VarIdx varIndexBase + 1. 180° in counter-clockwise degrees per 1.0 of value
VarIdxBase varIndexBase;
};
struct PaintSkewAroundCenter
{
uint8 format; // = 30
Offset24<Paint> paint;
F2DOT14 xSkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
F2DOT14 ySkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
FWORD centerX;
FWORD centerY;
};
struct PaintVarSkewAroundCenter
{
uint8 format; // = 31
Offset24<Paint> paint;
F2DOT14 xSkewAngle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
F2DOT14 ySkewAngle; // VarIdx varIndexBase + 1. 180° in counter-clockwise degrees per 1.0 of value
FWORD centerX; // VarIdx varIndexBase + 2
FWORD centerY; // VarIdx varIndexBase + 3
VarIdxBase varIndexBase;
};
struct PaintComposite
{
uint8 format; // = 32
Offset24<Paint> sourcePaint;
CompositeMode compositeMode; // If mode is unrecognized use COMPOSITE_CLEAR
Offset24<Paint> backdropPaint;
};
struct ClipBox {
uint8 format; // = 1
FWORD xMin;
FWORD yMin;
FWORD xMax;
FWORD yMax;
}
struct VarClipBox {
uint8 format; // = 2
FWORD xMin; // VarIdx varIndexBase + 0.
FWORD yMin; // VarIdx varIndexBase + 1.
FWORD xMax; // VarIdx varIndexBase + 2.
FWORD yMax; // VarIdx varIndexBase + 3.
VarIdxBase varIndexBase; // Set to 0 in non-variable fonts.
}
struct Clip {
uint16 startGlyphID; // first gid clip applies to
uint16 endGlyphID; // last gid clip applies to, inclusive
Offset24<ClipBox> clipBox; // Box or VarBox
}
struct BaseGlyphPaintRecord
{
uint16 glyphID;
Offset32<Paint> paint; // Typically PaintColrLayers
};
// Entries shall be sorted in ascending order of the `glyphID` field of the `BaseGlyphPaintRecord`s.
typedef ArrayOf<BaseGlyphPaintRecord, uint32> BaseGlyphList;
// Only layers accessed via PaintColrLayers (format 1) need be encoded here.
typedef ArrayOf<Offset32<Paint>, uint32> LayerList;
struct ClipList
{
uint8 format; // Set to 1.
ArrayOf<Clip, uint32> clips; // Clip records, sorted by startGlyphID
}
struct COLRv1
{
// Version-0 fields
uint16 version;
uint16 numBaseGlyphRecords;
Offset32<SortedUnsizedArrayOf<BaseGlyphRecord>> baseGlyphRecords;
Offset32<UnsizedArrayOf<LayerRecord>> layerRecords;
uint16 numLayerRecords;
// Version-1 additions
Offset32<BaseGlyphList> baseGlyphList;
Offset32<LayerList> layerList;
Offset32<ClipList> clipList; // May be NULL
Offset32<VarIdxMap> varIdxMap; // May be NULL
Offset32<ItemVariationStore> varStore;
};
Allocate a bitmap for the glyph, using the ClipBox for the glyph (if present) or
by traversing the paint graph to determine bounds.
0) Start at base glyph paint.
a) Paint a paint, switch:
1) PaintColrLayers
Paint each referenced layer by performing a)
2) PaintSolid
SkCanvas::drawColor with color configured
3) PaintLinearGradient
SkCanvas::drawPaint with liner gradient configured
(expected to be bounded by parent composite mode or clipped by current clip, check bounds?)
4) PaintRadialGradient
SkCanvas::drawPaint with radial gradient configured
(expected to be bounded by parent composite mode or clipped by current clip, check bounds?)
5) PaintGlyph
gid must not COLRv1
saveLayer
setClipPath to gid path
recurse to a)
restore
6) PaintColrGlyph
gid must be from
if gid on recursion blacklist, do nothing
recurse to 0) with different gid
7) PaintTransform
saveLayer()
apply transform
call a) for paint
restore
8) PaintTranslate
saveLayer()
apply transform
call a) for paint
restore
9) PaintScale
saveLayer()
apply transform
call a) for paint
restore
10) PaintRotate
saveLayer()
apply transform
call a) for paint
restore
11) PaintSkew
saveLayer()
apply transform
call a) for paint
restore
12) PaintComposite
saveLayer()
paint Paint for backdrop, call a)
saveLayer() with setting composite mode on SkPaint
paint Paint for src, call a)
restore with saved composite mode
restore the outer layer
HarfBuzz implementation will follow later. No major client relies on HarfBuzz for color fonts currently, but we certainly want to implement later as there are clients who like to remove FreeType dependency completely.
Thanks to Benjamin Wagner (@bungeman), Dave Crossland (@davelab6), and Roderick Sheeter (@rsheeter) for review and detailed feedback on earlier proposal.