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.
Core concepts
Faces, fonts and buffers
| Object | HarfBuzz type | What it is | Made by |
|---|---|---|---|
face |
hb_face_t |
One typeface of a font file or octet vector: its tables, glyph count, units per EM, names, axes, palettes. Size-independent. | make-face, with-face |
font |
hb_font_t |
A face at a scale, ppem, point size and variation. Shaping, metrics, outlines and paint take a font. | make-font, with-font, or make-ft2-font |
buffer |
hb_buffer_t |
Text to shape, its properties (direction, script, language), and after shaping the glyphs. | make-buffer, with-buffer |
shaped-glyph |
(hb_glyph_info_t + hb_glyph_position_t) |
One output glyph, copied into a Lisp struct. | buffer-glyphs, shape-text |
The name says what a function takes: face-* functions take a face (face-name, face-variation-axes, face-color-palettes), font-* functions a font (font-metric, font-glyph-advance), buffer-* functions a buffer, and glyph-* functions a font and a glyph index (glyph-outline, glyph-paint, glyph-color-layers, glyph-svg, glyph-png). From a font, font-face gives its face.
Units
Everything a font reports (shaped advances and offsets, glyph advances and extents, font extents, font-metric, outline coordinates, paint coordinates) is in the font's units:
| Font | Units | Values |
|---|---|---|
plain font, default scale |
font units (scale = face-units-per-em) |
integers |
plain font after (setf font-scale) |
scale units per EM | integers (HarfBuzz rounds) |
ft2-font |
pixels at the cl-ft2 face's size | exact rationals (26.6 values divided by 64) |
Internally a font has a scale divisor: 1 for a plain font, 64 for an ft2-font, whose HarfBuzz scale is in 26.6 fixed point. cl-harfbuzz divides by it, which is why ft2-font results are rationals such as 385/16 and match cl-ft2's own units exactly.
Outline and paint coordinates come from HarfBuzz as floats; they are returned as integers when integral, and otherwise as floats (for a plain font) or rationals (for an ft2-font).
Coordinate system
y is up, as in the font: a positive y-offset raises a glyph, font-glyph-extents returns ymax above ymin, with ymin negative for descenders, and font-extents returns a positive ascender and negative descender. Flip y yourself when drawing in screen space.
Advances are along the direction of the text: horizontal text has x-advance set and y-advance 0; vertical text (:ttb) the reverse, with negative y-advance (down).
Clusters
Each shaped glyph's cluster is the character index in the text of the first character it came from. buffer-add-text assigns each character its index in the string you pass, including the context before :start, so clusters index straight into your string.
- One character → several glyphs (decomposition, e.g. Indic split vowels): several glyphs share a cluster.
- Several characters → one glyph (ligature): the next glyph's cluster skips ahead. In
"office"the ligature has cluster 1 and the next glyph cluster 4, so the ligature covers characters 1–3. - Marks attach to their base; with the default cluster level they are merged into the base's cluster.
Clusters are how you map between text and glyphs: hit testing, caret placement, selection highlighting, and applying per-character styles. buffer-cluster-level controls how much HarfBuzz merges them; see Shaping.
Visual order
Glyphs come out in visual order, left to right (top to bottom for vertical text). For a right-to-left run that means reversed text order, so clusters decrease:
(map 'list #'hb:shaped-glyph-cluster (hb:shape-text font "سلام")) ; => (3 1 0)
Always lay out by walking the vector forwards and adding advances; never reverse it yourself.
Runs
shape handles one run: a stretch of text with one direction, one script, one language and one font. A paragraph mixing English and Arabic, or Latin and Han that needs a fallback font, has to be split first:
- bidi resolution and reordering of runs (cl-icu),
- script itemization (cl-icu),
- font fallback (your code, or cl-textrender),
then each run is shaped separately, passing the whole paragraph as context (:start/:end) so joining and contextual forms are right at run edges. See Cookbook.
Representations
| Concept | Lisp form | Example |
|---|---|---|
| OpenType tag (table, feature, script, axis) | string of 1–4 printable ASCII characters, padded with spaces; the type tag |
"kern", "GSUB", "wght", "cvt " |
| script (ISO 15924) | 4-letter string; the type script |
"Latn", "Arab", "Deva", "Hani" |
| language (BCP 47) | string | "en", "ar", "sr", "zh-Hant" |
| direction | keyword | :ltr, :rtl, :ttb, :btt; nil = unset |
| feature setting | HarfBuzz feature string | "-liga", "kern=0", "aalt=2", "smcp[3:5]" |
| color | list of octets | (r g b a), e.g. (255 0 0 255) |
| glyph | glyph index (integer) | 36 |
| flags | list of keywords | (:bot :eot), (:unsafe-to-break) |
Note that script tags in the layout tables (face-script-tags) are OpenType script tags, lowercase like "latn", "arab", which differ from ISO 15924 codes for some scripts. Buffer scripts are ISO 15924.
Lifetimes
Faces, fonts and buffers are released explicitly with destroy-face, destroy-font and destroy-buffer, or by the with- macros. There are no finalizers.
-
HarfBuzz reference-counts its objects, so a font stays usable after its face is destroyed, and
font-facestill returns a usable face for it:(let* ((face (hb:make-face path)) (font (hb:make-font face))) (hb:destroy-face face) (hb:shape-text font "ok") ; works (hb:face-units-per-em (hb:font-face font))) ; => 2048 -
Destroying twice is harmless.
-
Using a destroyed object signals
harfbuzz-error;face-live-p,font-live-pandbuffer-live-ptest for it. The printed form saysdestroyed. -
An
ft2-fontis also destroyed when its cl-ft2 face or library is destroyed. See FreeType bridge. -
Raw pointers from
face-handle,font-handleandbuffer-handleare valid until the object is destroyed.
Threads
- Faces and fonts may be shared between threads (HarfBuzz's own guarantee for immutable use). Do not change a font's scale or variation while another thread shapes with it.
- Buffers must not be shared. Give each thread its own, or use
shape-text, which makes a fresh buffer per call. - An
ft2-fontinherits cl-ft2's rule: its face, and therefore the font, must not be used from two threads at once. glyph-outlineandglyph-paintcollect results through a special variable bound per call, so concurrent calls on different threads are fine.
Determinism
Neither shape-text nor a buffer's guess-buffer-properties takes the language from the process locale. Without an explicit language, text is shaped with the font's default forms (language "und", undetermined), so results do not depend on the machine. Set :language or buffer-language for language-specific forms.