OpenType has four ways to give glyphs colour. COLR builds colour glyphs
from the font’s own outlines, filled with colours from the CPAL palette
table; SVG embeds SVG documents; CBDT/CBLC
and sbix hold bitmaps (see bitmap
tables). This page covers the first two. Every colour glyph also has an ordinary
outline in glyf, CFF or CFF2 for software
without colour support. All numbers are big-endian.
COLR header
| Offset | Type | Name | Version | Description |
|---|---|---|---|---|
0 | uint16 | version | 0, 1 | 0 or 1. |
2 | uint16 | numBaseGlyphRecords | 0, 1 | Number of version 0 base glyphs. |
4 | Offset32 | baseGlyphRecordsOffset | 0, 1 | To the base glyph records. |
8 | Offset32 | layerRecordsOffset | 0, 1 | To the layer records. |
12 | uint16 | numLayerRecords | 0, 1 | Number of layer records. |
14 | Offset32 | baseGlyphListOffset | 1 | To the BaseGlyphList. |
18 | Offset32 | layerListOffset | 1 | To the LayerList, or NULL. |
22 | Offset32 | clipListOffset | 1 | To the ClipList, or NULL. |
26 | Offset32 | varIndexMapOffset | 1 | To a DeltaSetIndexMap, or NULL. |
30 | Offset32 | itemVariationStoreOffset | 1 | To an item variation store, or NULL. |
Offsets count from the start of COLR. A version 1 table can still hold
version 0 glyphs for older software.
Version 0: layers
| Record | Fields | Meaning |
|---|---|---|
| BaseGlyph (6 bytes) | uint16 glyphID, uint16 firstLayerIndex, uint16 numLayers | A colour glyph and its run of layers. Sorted by glyph ID. |
| Layer (4 bytes) | uint16 glyphID, uint16 paletteIndex | An outline glyph filled with one palette colour; 0xFFFF means the current text colour. |
Layers are drawn in order, the first at the bottom, each painted over the ones before.
Version 1: paint graphs
A version 1 colour glyph is a tree (strictly, a graph without cycles) of paint tables: fills (solid or gradient), shapes that clip them (glyph outlines), transforms and composites. Paint tables are reached by 24-bit offsets counted from the table that holds the offset.
| Structure | Fields | Meaning |
|---|---|---|
| BaseGlyphList | uint32 numBaseGlyphPaintRecords, then records of uint16 glyphID, Offset32 paintOffset | The root paint of each colour glyph, sorted by glyph ID; offsets from the start of the list. |
| LayerList | uint32 numLayers, Offset32 paintOffsets[numLayers] | Paints that PaintColrLayers uses in slices; offsets from the start of the list. |
| ClipList | uint8 format (1), uint32 numClips, then records of uint16 startGlyphID, uint16 endGlyphID, Offset24 clipBoxOffset | Bounding boxes for ranges of glyphs, sorted, not overlapping; offsets from the start of the ClipList. |
| ClipBox format 1 | uint8 format, FWORD xMin, yMin, xMax, yMax | A fixed clip box. |
| ClipBox format 2 | as format 1, uint32 varIndexBase | A variable clip box. |
| ColorStop | F2DOT14 stopOffset, uint16 paletteIndex, F2DOT14 alpha | One colour on a gradient line. |
| VarColorStop | as ColorStop, uint32 varIndexBase | Variable: +0 offset, +1 alpha. |
| ColorLine / VarColorLine | uint8 extend, uint16 numStops, stops | The colours of a gradient. |
extend: 0 EXTEND_PAD (continue the end colours), 1
EXTEND_REPEAT, 2 EXTEND_REFLECT; unknown values are treated
as pad.
Paint formats
Each paint starts with uint8 format. Even formats from 2 to 30 are
fixed; the odd “Var” format after each adds a uint32 varIndexBase at
the end and lets its numbers vary. Var formats may only be used when the table has an
item variation store.
| Format | Name | Fields after format |
|---|---|---|
| 1 | PaintColrLayers | uint8 numLayers, uint32 firstLayerIndex: a slice of the LayerList, drawn bottom up with source-over. |
| 2, 3 | PaintSolid, PaintVarSolid | uint16 paletteIndex, F2DOT14 alpha. |
| 4, 5 | PaintLinearGradient, PaintVarLinearGradient | Offset24 colorLineOffset, FWORD x0, y0, x1, y1, x2, y2: start point, end point and a rotation point that sets the angle of the colour bands. |
| 6, 7 | PaintRadialGradient, PaintVarRadialGradient | Offset24 colorLineOffset, FWORD x0, y0, UFWORD radius0, FWORD x1, y1, UFWORD radius1: start and end circles. |
| 8, 9 | PaintSweepGradient, PaintVarSweepGradient | Offset24 colorLineOffset, FWORD centerX, centerY, F2DOT14 startAngle, endAngle: angles anticlockwise, stored as (degrees / 180) − 1, so 0x4000 is 360°. |
| 10 | PaintGlyph | Offset24 paintOffset, uint16 glyphID: clip the child paint to that glyph’s plain outline. |
| 11 | PaintColrGlyph | uint16 glyphID: reuse another colour glyph from the BaseGlyphList. |
| 12, 13 | PaintTransform, PaintVarTransform | Offset24 paintOffset, Offset24 transformOffset to an Affine2x3 (VarAffine2x3): Fixed xx, yx, xy, yy, dx, dy, giving x′ = xx·x + xy·y + dx, y′ = yx·x + yy·y + dy. |
| 14, 15 | PaintTranslate, PaintVarTranslate | Offset24 paintOffset, FWORD dx, dy. |
| 16, 17 | PaintScale, PaintVarScale | Offset24 paintOffset, F2DOT14 scaleX, scaleY, about the origin. |
| 18, 19 | PaintScaleAroundCenter, var | As 16, plus FWORD centerX, centerY. |
| 20, 21 | PaintScaleUniform, var | Offset24 paintOffset, F2DOT14 scale. |
| 22, 23 | PaintScaleUniformAroundCenter, var | As 20, plus FWORD centerX, centerY. |
| 24, 25 | PaintRotate, PaintVarRotate | Offset24 paintOffset, F2DOT14 angle (1.0 = 180° anticlockwise, no bias). |
| 26, 27 | PaintRotateAroundCenter, var | As 24, plus FWORD centerX, centerY. |
| 28, 29 | PaintSkew, PaintVarSkew | Offset24 paintOffset, F2DOT14 xSkewAngle, ySkewAngle (1.0 = 180°). |
| 30, 31 | PaintSkewAroundCenter, var | As 28, plus FWORD centerX, centerY. |
| 32 | PaintComposite | Offset24 sourcePaintOffset, uint8 compositeMode, Offset24 backdropPaintOffset. |
In a Var format, the varying fields take variation indexes
varIndexBase + 0, + 1 … in the order they appear. Each index goes through
the DeltaSetIndexMap (an index past its end uses the last entry); with no map, the
index itself is the delta-set index, outer in the high 16 bits and inner in the low.
A varIndexBase of 0xFFFFFFFF means no variation. Deltas to
F2DOT14 and Fixed values are added to their raw integer
form, which is why COLR may need 32-bit (LONG_WORDS)
deltas.
Composite modes
The modes of the W3C Compositing and Blending specification; an unknown value is
treated as clear. Colours from CPAL are sRGB, and compositing is done on
linear-light values.
| Value | Name | Value | Name |
|---|---|---|---|
| 0 | COMPOSITE_CLEAR | 14 | COMPOSITE_OVERLAY |
| 1 | COMPOSITE_SRC | 15 | COMPOSITE_DARKEN |
| 2 | COMPOSITE_DEST | 16 | COMPOSITE_LIGHTEN |
| 3 | COMPOSITE_SRC_OVER | 17 | COMPOSITE_COLOR_DODGE |
| 4 | COMPOSITE_DEST_OVER | 18 | COMPOSITE_COLOR_BURN |
| 5 | COMPOSITE_SRC_IN | 19 | COMPOSITE_HARD_LIGHT |
| 6 | COMPOSITE_DEST_IN | 20 | COMPOSITE_SOFT_LIGHT |
| 7 | COMPOSITE_SRC_OUT | 21 | COMPOSITE_DIFFERENCE |
| 8 | COMPOSITE_DEST_OUT | 22 | COMPOSITE_EXCLUSION |
| 9 | COMPOSITE_SRC_ATOP | 23 | COMPOSITE_MULTIPLY |
| 10 | COMPOSITE_DEST_ATOP | 24 | COMPOSITE_HSL_HUE |
| 11 | COMPOSITE_XOR | 25 | COMPOSITE_HSL_SATURATION |
| 12 | COMPOSITE_PLUS | 26 | COMPOSITE_HSL_COLOR |
| 13 | COMPOSITE_SCREEN | 27 | COMPOSITE_HSL_LUMINOSITY |
0 to 12 are Porter–Duff operators, 13 to 23 separable blend modes, 24 to 27 non-separable ones.
CPAL
| Type | Name | Version | Description |
|---|---|---|---|
uint16 | version | 0, 1 | 0 or 1. |
uint16 | numPaletteEntries | 0, 1 | Colours in each palette. |
uint16 | numPalettes | 0, 1 | Number of palettes; palette 0 is the default. |
uint16 | numColorRecords | 0, 1 | Total colour records. |
Offset32 | colorRecordsArrayOffset | 0, 1 | From the start of CPAL. |
uint16 × numPalettes | colorRecordIndices | 0, 1 | Where each palette starts in the colour records; palettes may overlap. |
Offset32 | paletteTypesArrayOffset | 1 | A uint32 of flags per palette, or NULL. |
Offset32 | paletteLabelsArrayOffset | 1 | A name ID per palette (0xFFFF none), or NULL. |
Offset32 | paletteEntryLabelsArrayOffset | 1 | A name ID per palette entry (0xFFFF none), or NULL. |
A colour record is four bytes in the order blue, green, red, alpha, in sRGB, not
premultiplied. Palette type bit 0 (0x0001) means suitable on a light
background, bit 1 (0x0002) on a dark background; other bits are
reserved.
SVG
The SVG table holds SVG documents that draw some or all glyphs in
colour, with gradients and, if wanted, embedded PNG or JPEG images. It works with
TrueType, CFF or CFF2 outlines, and every SVG glyph must also have an ordinary outline
glyph with the same ID. Layout is unchanged: cmap, hmtx,
GSUB, GPOS and so on run as usual, and only the final drawing uses the SVG.
| Structure | Type | Name | Meaning |
|---|---|---|---|
| Header | uint16 | version | 0. |
Offset32 | svgDocumentListOffset | To the SVGDocumentList, from the start of the table; never 0. | |
uint32 | reserved | 0. | |
| SVGDocumentList | uint16 | numEntries | Number of records; never 0. |
| SVGDocumentRecord × count | documentRecords | 12 bytes each. | |
| SVGDocumentRecord | uint16 | startGlyphID | First glyph of the range. |
uint16 | endGlyphID | Last glyph of the range, inclusive. | |
Offset32 | svgDocOffset | To the document, from the start of the SVGDocumentList; never 0. | |
uint32 | svgDocLength | Length of the stored (possibly compressed) document; never 0. |
Records are sorted by startGlyphID; each range has start ≤ end and
starts after the previous record’s end, so ranges never overlap. Several records may
point to the same document, which lets one document draw glyphs in separate
ranges.
The documents
- Encoding. Plain UTF-8 text, or gzip (deflate method, so the data
starts
1F 8B 08); readers must accept both. - Language. SVG 1.1 in its XML form, so case-sensitive. The root
must declare
xmlns="http://www.w3.org/2000/svg", and alsoxmlns:xlink="http://www.w3.org/1999/xlink"ifxlink:hrefis used; no other XLink attribute and no other namespace. - Glyph IDs. For every glyph in a record’s range, the document has an
element with
id="glyphn", n in decimal without leading zeros. It is drawn as if it were inside<defs>and referenced by a<use>, so it may sit anywhere in the tree and can share definitions with other glyphs in the same document. - Coordinates. One SVG unit is one font unit and the origin is the
glyph origin, but y points down, so the glyph is normally drawn at negative y
(or moved there with a
viewBoxortransform). The initial viewport is the em square (unitsPerEmeach way); aviewBox,widthorheightof another size scales the drawing. Nothing is clipped to the viewport:clipis taken asautoandoverflowasvisible. - Metrics. Advances come from
hmtxandvmtxas for any glyph; side bearings andheadflag bit 1 are not used for SVG glyphs, and there is no stored bounding box. GPOS andkernvalues are in y-up units and must be converted. - Not allowed (and ignored if present):
<text>,<font>and related elements,<foreignObject>,<switch>,<script>,<a>,<view>, XSL, the unitsemandex, SVG inside<image>, colour profiles, thecontentStyleTypeattribute, CSS2 system colours andrgba().<desc>,<metadata>and<title>may appear but are ignored. - Optional for readers, so best avoided:
<style>andstyleattributes, CSS variables other than palette colours, media queries,calc(), CSS and SVG animation, filters, patterns, masks, markers, symbols, XML entities, images other than PNG and JPEG, and interactivity. Gradients, clipping paths and opacity must be supported, as must everything else in SVG 1.1. - Colour. RGB values, the SVG 1.1 colour keywords and
currentColor(set by the application, normally to the text colour). With aCPALtable, the application defines one CSS custom property per palette entry, named--color0,--color1and so on, and the document uses them asvar(--color0, fallback), only at the start of a colour value. The entry’s alpha is multiplied into the matchingfill-opacity,stroke-opacity,stop-opacityorflood-opacity. context-fill,context-strokeand the othercontext-*values, allowed by earlier versions, are deprecated.- Animations, where supported, cannot change advances; a static rendering (for printing) ignores them rather than taking the first frame.
Example
None of the fonts shipped with macOS uses COLR, CPAL or
SVG (Apple’s emoji font uses sbix), so here is a
minimal version 0 table written out by hand: one colour glyph, glyph 5, made of glyph
6 in palette colour 0 under glyph 7 in the text colour.
00 00 00 01 00 00 00 0E 00 00 00 14 00 02 header, version 0
00 05 00 00 00 02 base glyph 5: layers 0 and 1
00 06 00 00 layer 0: glyph 6, palette entry 0
00 07 FF FF layer 1: glyph 7, text colour
The header is 14 bytes, so the base glyph records start at 14 and the layer records
at 20. A matching CPAL entry 00 00 FF FF would make colour 0
opaque red (blue 0, green 0, red 255, alpha 255).