Glyph Outlines
Renderers that draw from outlines start from a glyph's contours: Slug-style
curve renderers, MSDF generators, vector output such as SVG and PDF, and
meshes for 3D text. glyph-outline gives them.
Getting an outline
(tr:with-font (font "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" :size 1)
(tr:glyph-outline font (tr:font-glyph-id font #\l)))
;; => #S(CL-TEXTRENDER:OUTLINE
;; :CONTOURS (((:LINE 0.09423828 0.7597656 0.18408203 0.7597656)
;; (:LINE 0.18408203 0.7597656 0.18408203 0.0)
;; (:LINE 0.18408203 0.0 0.09423828 0.0)
;; (:LINE 0.09423828 0.0 0.09423828 0.7597656)))
;; :XMIN 0.09423828 :YMIN 0.0 :XMAX 0.18408203 :YMAX 0.7597656)
In the rest of this page, l-outline is that outline.
An outline is in layout units of the font's size, in font orientation (y up), relative to the glyph's origin on the baseline. Make a font of size 1, as here, to get outlines in ems. To draw one at a glyph's position, see Coordinates and Units.
A glyph ID the font does not have gives the outline of its .notdef,
glyph 0, usually a box: see Fonts.
Contours and segments
outline-contours is a list of contours. Each contour is a closed list of
segments, each with its start point given explicitly:
(:line x0 y0 x1 y1)
(:quadratic x0 y0 cx cy x1 y1)
(:cubic x0 y0 c1x c1y c2x c2y x1 y1)
Each segment starts where the previous one ended, and the last ends where
the first started; a contour the font left open is closed with a line.
TrueType fonts (.ttf) give lines and quadratics; CFF fonts (.otf) give
lines and cubics.
outline-xmin, outline-ymin, outline-xmax and outline-ymax bound
every point, control points included, so the box always contains the ink.
A glyph without ink, such as a space, has no contours
(outline-empty-p), and all four bounds are zero.
Quadratics only
Some renderers, such as Slug, take only quadratic curves. With :quadratic t, glyph-outline replaces each cubic with quadratics within
:tolerance layout units of it, a thousandth of an em by default:
(tr:with-font (font "/usr/share/fonts/opentype/urw-base35/URWBookman-Light.otf" :size 1)
(let ((o (tr:font-glyph-id font #\o)))
(list (mapcar #'length (tr:outline-contours (tr:glyph-outline font o)))
(mapcar #'length (tr:outline-contours
(tr:glyph-outline font o :quadratic t))))))
;; => ((6 4) (10 8))
The cubic is cut into equal pieces, as few as keep every piece within the
tolerance, and each piece becomes one quadratic. TrueType outlines are
already quadratic, so :quadratic changes nothing for them.
Packed for Slug
outline-quadratics packs an outline into one vector of single-floats,
six per curve, x0 y0 cx cy x1 y1, contour after contour. Lines become
quadratics with their control point at their middle:
(tr:outline-quadratics l-outline)
;; => #(0.09423828 0.7597656 0.13916016 0.7597656 0.18408203 0.7597656
;; 0.18408203 0.7597656 0.18408203 0.3798828 0.18408203 0.0
;; 0.18408203 0.0 0.13916016 0.0 0.09423828 0.0
;; 0.09423828 0.0 0.09423828 0.3798828 0.09423828 0.7597656)
An outline with cubics needs :tolerance to pack, or outline-quadratics
signals textrender-error; make it with :quadratic t instead.
cl-slug builds Slug's band and curve
textures from glyph shapes like these.
Orientation
TrueType glyphs draw their outer contours clockwise and their holes counterclockwise; CFF glyphs do the opposite. Fill rules don't care, but signed distance fields take their sign from the direction, so an MSDF generator wants every glyph the same way round.
outline-signed-areareturns the area the outline encloses, positive where outer contours run counterclockwise (with y up), negative where clockwise. Holes count against their contours. It is exact for all three segment kinds.outline-orientationreturns:clockwiseor:counterclockwisefrom that sign, ornilfor an empty outline.orient-outlinereturns the outline running in the direction you ask for. If it ran the other way, every contour is reversed, which keeps holes opposite to their outer contours. It returns the same outline if it already runs that way.
(tr:outline-orientation l-outline) ; DejaVu Sans, TrueType
;; => :CLOCKWISE
(tr:outline-signed-area l-outline)
;; => -0.06826019
(tr:outline-contours (tr:orient-outline l-outline :counterclockwise))
;; => (((:LINE 0.09423828 0.7597656 0.09423828 0.0)
;; (:LINE 0.09423828 0.0 0.18408203 0.0)
;; (:LINE 0.18408203 0.0 0.18408203 0.7597656)
;; (:LINE 0.18408203 0.7597656 0.09423828 0.7597656)))
orient-outline assumes the glyph is consistent with itself, as font
glyphs are; it does not repair contours that disagree with each other.
(cl-sdf resolves either winding and
overlapping contours itself, so the distance field
atlas needs no orienting.)
Caching outlines
Computing an outline asks HarfBuzz for the glyph each time. A renderer that draws the same glyphs every frame asks the font to keep them:
(tr:cached-quadratics font glyph-id) ; => #(x0 y0 cx cy x1 y1 ...)
(tr:cached-outline font glyph-id) ; => OUTLINE, for MSDF
cached-outlinereturns the outline asglyph-outlinedoes with the default tolerance;:quadratic tasks for the quadratic form.cached-quadraticsreturns the packed quadratics, from the quadratic outline.- Each is computed on the first request, and the same object is returned after, without allocating. Do not modify it.
- The font keeps them, with its other caches, and they go with it. Two fonts, even of one face, keep their own.
- Both may be called from any thread.