Table of Contents
- FreeType bridge: cl-harfbuzz/ft2
- API
- make-ft2-font face → ft2-font
- with-ft2-font (var face) &body body
- ft2-font (class)
- ft2-font-face font → cl-ft2 face
- How an ft2-font differs from a plain font
- It works in pixels
- It follows the face
- Variations go through cl-ft2
- Lifetimes
- The glyph slot
- Threads
- Agreement with cl-ft2
- Hinting
- Windows
FreeType bridge: cl-harfbuzz/ft2
The optional system cl-harfbuzz/ft2 (package cl-harfbuzz/ft2) builds HarfBuzz fonts on cl-ft2 faces through HarfBuzz's hb-ft integration. Shaping then uses FreeType's glyph data at the face's current size, so the positions it returns line up with the bitmaps cl-ft2 renders.
(asdf:load-system :cl-harfbuzz/ft2)
(defpackage #:my-text
(:use #:cl)
(:local-nicknames (#:hb #:cl-harfbuzz) (#:ft2 #:cl-ft2)))
(in-package #:my-text)
The examples below use those local nicknames. Plain cl-harfbuzz does not load cl-ft2. Use the bridge when you render with cl-ft2; use plain fonts when you only need shaping or outlines (cl-textrender's layout, for example, does not use FreeType).
API
make-ft2-font face → ft2-font
A new font for the cl-ft2 face, scaled to the face's current size. Set the size (ft2:face-size or a strike) first.
with-ft2-font (var face) &body body
Binds var to (make-ft2-font face) and destroys it on exit.
ft2-font (class)
A subclass of hb:font, so every function that takes a font takes it: shape, shape-text, glyph-outline, glyph-paint, font-glyph-extents, font-extents, font-metric, the variation accessors, and so on.
ft2-font-face font → cl-ft2 face
The cl-ft2 face the font is built on.
How an ft2-font differs from a plain font
It works in pixels
Shaped positions, glyph advances and extents, font extents, metrics, outlines and paint coordinates are in pixels at the face's size, as exact rationals: the same units cl-ft2 uses.
(ft2:with-library (lib)
(ft2:with-face (face lib "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
(setf (ft2:face-size face) 32)
(cl-harfbuzz/ft2:with-ft2-font (font face)
(list (map 'list #'hb:shaped-glyph-x-advance (hb:shape-text font "AVa"))
(multiple-value-list (hb:font-extents font))))))
;; => ((635/32 621/32 1255/64) (1901/64 -483/64 0))
Internally its HarfBuzz scale is the FreeType size in 26.6 units ((hb:font-scale font) → 2048, 2048 at 32 px), and cl-harfbuzz divides by 64.
Shaping is unhinted: the advances are FreeType's unhinted advances at that size, plus GPOS adjustments. They are exact and fractional. Round the pen position yourself if you want to snap glyphs to whole pixels, or render with cl-ft2 at subpixel offsets.
It follows the face
When the cl-ft2 face's size, transform or variation coordinates change, cl-ft2 notifies the font (ft2:face-changed) and the bridge calls hb_ft_font_changed, so the font picks up the change:
(setf (ft2:face-size face) 16)
(hb:font-scale font) ; => 1024, 1024
Do not set hb:font-scale on an ft2-font: change the face's size.
Variations go through cl-ft2
font-design-coordinates, font-axis-coordinate, font-normalized-coordinates and font-named-instance read and write the cl-ft2 face, so the two libraries never disagree. Both number named instances from 0. See Font variations.
Lifetimes
The font is registered as a dependent of the cl-ft2 face (cl-ft2: Face dependents):
ft2:destroy-faceorft2:destroy-librarydestroys the font first, so it never points at a freedFT_Face. Afterwardsfont-live-pis false and using it signalsharfbuzz-error.hb:destroy-fontunregisters it from the face.
(let* ((face (ft2:open-face lib path))
(font (cl-harfbuzz/ft2:make-ft2-font face)))
(ft2:destroy-face face)
(hb:font-live-p font)) ; => NIL
This differs from plain fonts, which outlive their hb:face.
The glyph slot
hb-ft loads glyphs into the FreeType face's glyph slot while shaping, replacing whatever glyph cl-ft2 last loaded there.
ft2:glyph-index-bitmap,ft2:char-bitmapand the like load the glyph they render, so they are unaffected.- If you call
ft2:load-glyphand then read the slot (ft2:glyph-advance,ft2:glyph-outline,ft2:render-glyph, …), do not shape in between: load the glyph after shaping.
Threads
An ft2-font follows cl-ft2's rule, not HarfBuzz's: like its face, it must not be used from two threads at once.
Agreement with cl-ft2
The test suite checks that, at the same size:
- shaped advances equal cl-ft2's unhinted advance plus unfitted kerning,
glyph-outlineequalsft2:glyph-outlineloaded with:no-hinting,font-glyph-extentsequals cl-ft2'sglyph-control-boxfor the same glyph,font-extentsequals the face's ascender scaled to pixels.
Hinting
hb-ft shapes with unhinted metrics, which matches rasterizing with light (vertical-only) hinting, as cl-textrender does. If you render with full hinting, hinted advances can differ from shaped ones by up to a pixel per glyph. Position glyphs with the shaped advances anyway, or shape with hinted metrics through the C API (hb_ft_font_set_load_flags, not bound by cl-harfbuzz/ffi).
Windows
hb-ft is built into cl-harfbuzz's bundled harfbuzz.dll, which delay-loads freetype.dll; cl-ft2 loads that DLL before the bridge first calls into it. A delivered application ships both DLLs next to its executable. See Installation.