Table of Contents
- Glyph objects and strokers
- Glyph objects
- copy-glyph source → glyph
- destroy-glyph glyph
- with-glyph (var source) &body body
- glyph-live-p glyph → boolean · glyph-library glyph → library
- Shared readers
- render-glyph glyph &key (mode :normal) origin → glyph
- transform-glyph glyph &key matrix delta → glyph
- Strokers
- make-stroker library &key (radius 1) (line-cap :round) (line-join :round) (miter-limit 4) → stroker
- destroy-stroker stroker · with-stroker (var library &rest args) &body body
- Readers
- stroke-glyph glyph stroker &key (border :both) → glyph
- stroke-outline outline stroker &key open → outline
- Examples
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-scaleglyphs. cl-ft2 converts it on everystroke-glyph, so one stroker works on glyphs in either unit.line-cap: how open subpaths end::butt,:roundor:square, the typeline-cap. Glyph contours are closed, so this rarely matters.line-join: how corners join::round,:bevel,:miter-variableor:miter-fixed, the typeline-join. These are FreeType's joins; cl-svgread'sline-jointype holds SVG's.miter-limit: for mitered joins, the largest miter length as a multiple ofradius, measured from the corner to the miter's tip. A longer miter is beveled with:miter-fixed, as in PostScript, PDF and SVG'smiter, and clipped at that length with:miter-variable, as in XPS and SVG 2'smiter-clip. It is the same number as SVG'sstroke-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.