Table of Contents
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Color glyphs
OpenType has four ways to store color glyphs:
| Format | Tables | What it is | Typical fonts |
|---|---|---|---|
| COLRv0 | COLR + CPAL |
a stack of glyph outlines, each filled with one palette color | older emoji, icon fonts |
| COLRv1 | COLR + CPAL |
a paint graph: gradients, transforms, clips, compositing | Noto Color Emoji (COLRv1 build), modern icon fonts |
| SVG | SVG |
an SVG document per glyph (or range of glyphs) | Twemoji Mozilla, EmojiOne |
| bitmaps | CBDT/CBLC, sbix |
PNG images in fixed-size strikes | Noto Color Emoji (CBDT build), Apple Color Emoji |
cl-harfbuzz gives you one uniform interface, glyph-paint, which describes any glyph (color or not) as a list of drawing operations, and also direct access to the tables.
glyph-paint
glyph-paint font glyph &key (palette 0) (foreground '(0 0 0 255)) → list of operations
Walks the glyph's color description with hb_font_paint_glyph and returns the operations a 2D graphics library needs to draw it, in order. It works for COLRv1, COLRv0, embedded PNG and SVG, and plain glyphs. It does not rasterize; replay the operations with your renderer (Cairo, Skia, a GPU path renderer, …).
Operations
| Operation | Meaning |
|---|---|
(:push-transform xx yx xy yy dx dy) |
multiply the current transform by the affine matrix x' = xx·x + xy·y + dx, y' = yx·x + yy·y + dy |
(:pop-transform) |
restore the previous transform |
(:push-clip-glyph glyph) |
intersect the clip with the outline of glyph (get it with glyph-outline) |
(:push-clip-rectangle xmin ymin xmax ymax) |
intersect the clip with a rectangle |
(:pop-clip) |
restore the previous clip |
(:color color foreground-p) |
fill the current clip with a solid color |
(:linear-gradient stops extend x0 y0 x1 y1 x2 y2) |
fill with a linear gradient from p0 to p1; p2 sets the rotation (the gradient is perpendicular to p0–p2) |
(:radial-gradient stops extend x0 y0 r0 x1 y1 r1) |
fill with a two-circle radial gradient |
(:sweep-gradient stops extend x0 y0 start-angle end-angle) |
fill with a conic gradient around (x0, y0); angles in radians, counter-clockwise from the positive x axis |
(:image octets width height format slant extents) |
draw an image; format is "png", "svg" or "BGRA" |
(:push-group) |
start drawing into a new transparent layer |
(:pop-group composite-mode) |
composite that layer onto the one below with composite-mode |
Pushes and pops are balanced.
Values:
- color:
(r g b a)octets, the typecolor, which foreground is too. foreground-p is true when the color stands for the text color; it has already been replaced by foreground, but you may substitute your own (e.g. to recolor by state). - stops: a list of
(offset color foreground-p), offset typically 0 to 1. - extend:
:pad,:repeator:reflect, what happens outside the stops. - composite-mode:
:src-over(normal),:clear,:src,:dest,:dest-over,:src-in,:dest-in,:src-out,:dest-out,:src-atop,:dest-atop,:xor,:plus,:screen,:overlay,:darken,:lighten,:color-dodge,:color-burn,:hard-light,:soft-light,:difference,:exclusion,:multiply,:hsl-hue,:hsl-saturation,:hsl-color,:hsl-luminosity. - Positions, radii, translations, clip rectangles and image extents are in the font's units (y up); transform matrix coefficients and angles are unitless. Image width and height are in image pixels; extents
(x-bearing y-bearing width height)place the image in font units, and slant is a synthetic slant to apply.
Examples
A plain glyph is its own outline filled with the foreground color:
(hb:glyph-paint font (hb:font-char-glyph font #\A))
;; => ((:PUSH-CLIP-GLYPH 36) (:COLOR (0 0 0 255) T) (:POP-CLIP))
(hb:glyph-paint font glyph :foreground '(200 30 30 255))
;; => ((:PUSH-CLIP-GLYPH 5) (:COLOR (200 30 30 255) T) (:POP-CLIP))
COLRv0 layers (from the test font: a box in palette color 0, a bar in color 1):
(hb:glyph-paint font a)
;; => ((:PUSH-CLIP-GLYPH 1) (:COLOR (255 0 0 255) NIL) (:POP-CLIP)
;; (:PUSH-CLIP-GLYPH 2) (:COLOR (0 0 255 255) NIL) (:POP-CLIP))
(hb:glyph-paint font a :palette 1)
;; => ((:PUSH-CLIP-GLYPH 1) (:COLOR (0 255 0 255) NIL) (:POP-CLIP)
;; (:PUSH-CLIP-GLYPH 2) (:COLOR (0 0 0 128) NIL) (:POP-CLIP))
COLRv1 linear gradient:
(hb:glyph-paint font b)
;; => ((:PUSH-TRANSFORM 1 0 0 1 0 0) (:PUSH-TRANSFORM 1 0 0 1 0 0)
;; (:PUSH-CLIP-GLYPH 1) (:PUSH-TRANSFORM 1 0 0 1 0 0)
;; (:LINEAR-GRADIENT ((0 (255 0 0 255) NIL) (1 (0 0 255 255) NIL)) :PAD 0 0 500 0 0 500)
;; (:POP-TRANSFORM) (:POP-CLIP) (:POP-TRANSFORM) (:POP-TRANSFORM))
Replaying
A renderer keeps a stack of transforms, clips and layers:
(defun replay (font operations canvas)
(dolist (op operations)
(destructuring-bind (kind &rest args) op
(ecase kind
(:push-transform (canvas-save canvas) (apply #'canvas-transform canvas args))
(:pop-transform (canvas-restore canvas))
(:push-clip-glyph
(canvas-save canvas)
(canvas-clip-path canvas (hb:glyph-outline font (first args))))
(:push-clip-rectangle (canvas-save canvas) (apply #'canvas-clip-rect canvas args))
(:pop-clip (canvas-restore canvas))
(:color (canvas-fill-clip canvas (first args)))
((:linear-gradient :radial-gradient :sweep-gradient)
(canvas-fill-gradient canvas kind args))
(:image (canvas-draw-image canvas args))
(:push-group (canvas-push-layer canvas))
(:pop-group (canvas-pop-layer canvas (first args)))))))
(canvas-… stands for your graphics library.) Remember y is up.
Palettes
palette selects the CPAL palette (default 0). Fonts may flag palettes as suited to light or dark backgrounds in their name table (:light-background, :dark-background name IDs). An out-of-range palette falls back to palette 0.
Color tables directly
Face tests
| Function | True if the face has |
|---|---|
face-color-layers-p face |
COLRv0 layers |
face-color-paint-p face |
COLRv1 paint graphs |
face-svg-p face |
SVG glyph documents |
face-png-p face |
PNG bitmaps (CBDT or sbix) |
face-color-palettes face → list of palettes
Each palette is a list of (r g b a) colors; empty for a face without CPAL.
(hb:face-color-palettes face)
;; => (((255 0 0 255) (0 0 255 255))
;; ((0 255 0 255) (0 0 0 128)))
glyph-color-layers font glyph → list of (layer-glyph . color-index)
The glyph's COLRv0 layers, bottom first. color-index indexes a palette, or is :foreground for the text color. Empty for a glyph without layers.
(hb:glyph-color-layers font a) ; => ((1 . 0) (2 . 1))
glyph-color-paint-p font glyph → boolean
True if the glyph has a COLRv1 paint graph.
glyph-svg font glyph → octets or nil
The SVG document covering the glyph, as octets, possibly gzip-compressed (check for the 1F 8B magic). One document may cover several glyphs; the glyph's element has the id glyphN.
glyph-png font glyph → octets or nil
The PNG image from the bitmap strike that best matches the font's ppem (font-ppem); set the ppem before calling.