7 Glyph Objects and Strokers
dxtr edited this page 2026-10-18 16:25:41 +02:00
This file contains ambiguous Unicode characters

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.

Glyph objects and strokers

The face's glyph slot holds one glyph and is overwritten by every load. A glyph object is a standalone copy (FT_Glyph) that you can keep, transform, stroke and render later.

Glyph objects

copy-glyph source → glyph

Returns a new glyph holding a copy of source:

  • From a face, it copies the image in the face's glyph slot, outline or bitmap. The glyph also keeps the slot's glyph index, metrics and linear advance, and remembers whether the glyph was loaded with :no-scale, so its coordinates stay in the same units.
  • From a glyph, it returns an independent copy.

destroy-glyph glyph

Destroys glyph. Destroying it again is harmless.

with-glyph (var source) &body body

Binds var to (copy-glyph source) around body and calls destroy-glyph on exit.

glyph-live-p glyph → boolean · glyph-library glyph → library

A glyph belongs to the library of the face it came from, and destroy-library destroys it. It does not depend on the face, so it stays valid after destroy-face.

A glyph prints with its format: #<GLYPH outline {…}>, #<GLYPH bitmap {…}>, or #<GLYPH destroyed {…}>.

Shared readers

These generic functions accept a glyph as well as a face:

Function On a glyph
glyph-index the index it was loaded as
glyph-format :outline, :bitmap, …
glyph-metrics the metrics it was loaded with
glyph-advance the advance it was loaded with, in pixels (font units for :no-scale)
glyph-linear-advance the unhinted advance it was loaded with
glyph-outline contours; see Outlines. Requires :outline format
glyph-control-box xmin, ymin, xmax, ymax. Works for outlines and bitmaps
render-glyph converts the glyph to a bitmap in place
glyph-bitmap a bitmap copy; requires :bitmap format
copy-glyph an independent copy

The index, metrics and advances describe the glyph as loaded. transform-glyph, stroke-glyph and render-glyph change its image but not these values.

render-glyph glyph &key (mode :normal) origin → glyph

Replaces the glyph's outline with a bitmap rendered in mode (see render modes). After this, glyph-format is :bitmap, and the outline is gone. Use copy-glyph first to keep it. origin, a list (x y) in pixels, the type transform-delta, shifts the outline before rendering, which is useful for subpixel positioning:

(ft2:render-glyph glyph :origin '(1/4 0))   ; render at a quarter-pixel offset

transform-glyph glyph &key matrix delta → glyph

Transforms an outline glyph in place. matrix is (xx xy yx yy), a transform-matrix as for face-transform-matrix. delta is (x y), a transform-delta, in pixels (font units for :no-scale), added after the matrix. Either may be nil.

(setf (ft2:face-size face) 16)
(ft2:load-char face #\A)
(ft2:with-glyph (g face)
  (ft2:transform-glyph g :matrix '(2 0 0 2) :delta '(1/2 0))   ; double size, shift ½ px
  (ft2:glyph-control-box g))
;; => 3/4, 0, 177/8, 24

The transform does not change the glyph's advance.

Strokers

A stroker turns an outline into the outline of its stroke, for outlined text, borders, halos and synthetic bold.

make-stroker library &key (radius 1) (line-cap :round) (line-join :round) (miter-limit 4) → stroker

  • radius: half the stroke width, a non-negative real. It is in the units of the glyph being stroked: pixels, or font units for :no-scale glyphs. cl-ft2 converts it on every stroke-glyph, so one stroker works on glyphs in either unit.
  • line-cap: how open subpaths end: :butt, :round or :square, the type line-cap. Glyph contours are closed, so this rarely matters.
  • line-join: how corners join: :round, :bevel, :miter-variable or :miter-fixed, the type line-join. These are FreeType's joins; cl-svgread's line-join type holds SVG's.
  • miter-limit: for mitered joins, the largest miter length as a multiple of radius, measured from the corner to the miter's tip. A longer miter is beveled with :miter-fixed, as in PostScript, PDF and SVG's miter, and clipped at that length with :miter-variable, as in XPS and SVG 2's miter-clip. It is the same number as SVG's stroke-miterlimit.

destroy-stroker stroker · with-stroker (var library &rest args) &body body

Destroy. with-stroker passes args on to make-stroker.

Readers

stroker-live-p, stroker-library, stroker-radius, stroker-line-cap, stroker-line-join, stroker-miter-limit. A stroker prints as #<STROKER radius 3/2 round round {…}>.

stroke-glyph glyph stroker &key (border :both) → glyph

Replaces the outline glyph with its stroke, in place.

border Result
:both the full stroke: a band of width 2 × radius centred on every contour
:outside only the border on the outer side of each contour. Filled, it covers the glyph grown by radius on every side: a synthetic bold or a halo
:inside only the border on the inner side of each contour

These keywords make up the type stroke-border.

Where the bands of neighbouring contours overlap, as on a stem narrower than 2 × radius, they merge into solid ink.

The glyph must be an outline. Render it afterwards with render-glyph.

stroke-outline outline stroker &key open → outline

Strokes an outline of your own, open paths or closed, and returns the stroke as a new outline, for render-outline to rasterize. The radius is in the outline's pixels.

Examples

Outlined text

(setf (ft2:face-size face) 32)
(ft2:load-char face #\A)
(ft2:with-glyph (glyph face)
  (ft2:with-stroker (stroker lib :radius 3/2 :line-join :round)
    (ft2:stroke-glyph glyph stroker)
    (ft2:glyph-bitmap (ft2:render-glyph glyph))))
;; => a 26 × 27 bitmap with LEFT -2 and TOP 25

Text with a halo or border

Render the :outside stroke and the plain glyph separately. Draw the stroke in the border color, then the plain glyph in the text color over it, aligning the two bitmaps with their left and top:

(defun bordered-glyph (face lib char radius)
  "Return two bitmaps for CHAR: its border and its fill."
  (ft2:load-char face char)
  (values (ft2:with-glyph (g face)
            (ft2:with-stroker (s lib :radius radius)
              (ft2:stroke-glyph g s :border :outside)
              (ft2:glyph-bitmap (ft2:render-glyph g))))
          (ft2:with-glyph (g face)
            (ft2:glyph-bitmap (ft2:render-glyph g)))))

For hollow letters, draw only the :both stroke, with a radius below half the stem width so the inside stays open.

Synthetic bold

(ft2:load-char face #\A)
(ft2:with-glyph (g face)
  (ft2:with-stroker (s lib :radius 1/2)
    (ft2:stroke-glyph g s :border :outside)   ; grow by ½ px on every side
    (ft2:glyph-bitmap (ft2:render-glyph g))))

Stroking does not change the advance. When you embolden, add about 2 × radius to the pen advance yourself.

Keeping glyphs in a cache

(defvar *cache* (make-hash-table))

(defun cached-glyph (face index)
  (or (gethash index *cache*)
      (progn (ft2:load-glyph face index)
             (setf (gethash index *cache*) (ft2:copy-glyph face)))))

Destroy cached glyphs with destroy-glyph, or let destroy-library destroy them all. For rendered pixels, caching bitmap structs is simpler: they are plain Lisp data and need no destroying.