Core concepts
Objects and ownership
cl-ft2 has four kinds of object. Each wraps a FreeType handle:
| Class | Wraps | Made by | Destroyed by |
|---|---|---|---|
library |
FT_Library |
make-library, with-library |
destroy-library |
face |
FT_Face |
open-face, with-face |
destroy-face |
glyph |
FT_Glyph |
copy-glyph, with-glyph |
destroy-glyph |
stroker |
FT_Stroker |
make-stroker, with-stroker |
destroy-stroker |
A library owns everything created in it. destroy-library first destroys every face, glyph and stroker that is still live, then the library itself. Destroying a library therefore never leaves another object holding a dangling pointer.
A glyph does not depend on the face it was copied from. It belongs to the face's library, so it outlives destroy-face but not destroy-library.
Destruction is explicit. cl-ft2 does not use finalizers. Use the with- macros, which destroy in an unwind-protect, or call the destroy- functions yourself. Each destroy- function is idempotent, so destroying an object twice is harmless.
Using a destroyed object
Every operation checks that its object is still live. A destroyed object signals a freetype-error and never touches freed memory:
| Object | Error name |
|---|---|
| library | :invalid-library-handle |
| face | :invalid-face-handle |
| glyph, stroker | :invalid-handle |
library-live-p, face-live-p, glyph-live-p and stroker-live-p test liveness without signalling. A destroyed object prints as #<FACE destroyed>, #<GLYPH destroyed>, and so on.
The glyph slot
FreeType gives each face one glyph slot. load-glyph and load-char overwrite it, and these readers report on the glyph loaded last:
glyph-index, glyph-format, glyph-metrics, glyph-advance, glyph-linear-advance, glyph-outline, glyph-control-box, render-glyph, glyph-bitmap
So the slot holds one glyph at a time. To keep a glyph, either copy out what you need (glyph-bitmap and glyph-outline return fresh Lisp data), or make a standalone glyph object with copy-glyph.
Naming: glyph-… and glyph-index-…
The names follow one rule:
glyph-…functions take a glyph source: either a face, meaning the glyph in its slot, or aglyphobject. These are all of the readers above, pluscopy-glyph. They are generic functions.glyph-index-…functions take a face and a glyph index, and leave the slot alone:glyph-index-bitmap,glyph-index-advanceandglyph-index-name. (glyph-index-bitmapdoes reuse the slot to render.)
Lookups that produce a glyph index are char-index (from a character) and name-index (from a glyph name).
Other code that uses the face's FT_Face also loads into the same slot. For example, shaping with a cl-harfbuzz/ft2 font replaces whatever glyph cl-ft2 loaded last.
Units
cl-ft2 converts FreeType's fixed-point numbers into exact rationals, so no precision is lost to floating point:
| FreeType | Meaning | cl-ft2 |
|---|---|---|
FT_F26Dot6, scaled FT_Pos |
1/64 pixel | rational pixels: 807/64 = 12.609375 |
FT_Fixed (16.16) |
scale factors, linear advances, variation coordinates | rationals: 1/2, 650 |
unscaled FT_Pos, FT_Short |
font units | integers |
Use (float x) when you need a float. Rounding happens only on input: cl-ft2 rounds the reals you pass to the nearest 1/64 (positions, char sizes) or 1/65536 (matrices, coordinates).
Which unit you get depends on what you ask for:
| Value | Unit |
|---|---|
Face-wide metrics: face-ascender, face-descender, face-height, face-bounding-box, face-max-advance-…, face-underline-… |
font units (see face-units-per-em) |
face-size-metrics: ascender, descender, height, max-advance |
pixels at the current size |
size-metrics-x-scale, -y-scale |
pixels per font unit: multiply a font-unit distance by it |
Glyph metrics, glyph-advance, outlines, control boxes |
pixels, or font units if the glyph was loaded with :no-scale |
glyph-linear-advance |
pixels, or font units if loaded with :linear-design |
glyph-index-advance |
pixels, or font units with :no-scale in :flags |
kerning |
pixels (:default, :unfitted), or font units (:unscaled) |
Bitmap width, height, left, top |
whole pixels |
cl-ft2 remembers the flags of the most recent load on each face, so glyph-slot readers always use the right unit. A glyph object remembers whether it was copied from a :no-scale load, and its outline, control box, advance, transform-glyph delta and stroker radius use the same unit.
Coordinate directions
- Outlines, metrics and control boxes are Y-up. The baseline is y = 0, ascent is positive and descent is negative, as in the font itself.
- Bitmaps are stored top row first.
(aref data row column)with row 0 at the top. cl-ft2 normalises FreeType's pitch, so you never see bottom-up rows. bitmap-topcounts up from the baseline to the top row. To place a bitmap on a Y-down canvas with the pen at(pen-x, baseline-y), its top-left pixel goes at(pen-x + left, baseline-y - top).
Sizes and the current state of a face
A face carries mutable state that affects every later load:
- the size (
face-size, orface-strikefor a bitmap strike) - the transform (
face-transform-matrix,face-transform-delta) - the variation coordinates, for variable fonts (see Font variations)
- the current charmap (
(setf face-charmap))
To render one font at two sizes at once, open the font twice. Two faces are independent, and opening the same file twice is cheap.
Threads
FreeType objects are not thread-safe. Use one library per thread, with its own faces, or serialise all access to a library and everything made in it. Glyphs and bitmaps that have been copied into Lisp (bitmap, outline lists, metric structs) are plain Lisp data and can be shared freely, as are outline objects of your own; stroke-outline and render-outline use a stroker and a library, under the same rules.
Input validation
Bad arguments are reported before they reach FreeType:
- An unknown keyword (load flag, render mode, encoding, kerning mode, line cap, …) signals a standard
type-errorthat lists the valid keywords. - An argument of the wrong type, such as a negative size or a glyph where a face belongs, signals a
type-error. Every exported function declares the types of its arguments and results, so SBCL also warns at compile time about a constant argument of the wrong type. - A glyph index out of range, a strike that does not exist, an outline asked of a bitmap, or a variation on a font without axes signals a
freetype-errorwithfreetype-error-functionset tonil. See Errors.