7 Faces
dxtr edited this page 2026-10-18 16:20:21 +02:00

Faces

A face is one typeface of a font file or in-memory font: its tables and everything that does not depend on size.

Opening

make-face source &key (index 0) → face

Opens face index of source, which is

  • a native filename string ("/usr/share/fonts/…/DejaVuSans.ttf"),
  • a pathname (converted with uiop:native-namestring), or
  • a vector of octets (unsigned-byte 8), which is copied, so you may reuse or drop the vector afterwards.

These three make up the type font-source, which face-count and with-face take too; anything else is a type-error.

TrueType, OpenType (CFF and CFF2) and collections (.ttc, .otc) are accepted. WOFF and WOFF2 are not: HarfBuzz does not decompress them, and face-count returns 0 for them. make-face recognises them and says so. Convert web fonts to plain TrueType/OpenType first, e.g. with fontTools.

Signals harfbuzz-error if the file cannot be read, the data is not a font, or index is not below the number of faces:

(hb:make-face "/no/such.ttf")
;; Could not read the font file /no/such.ttf: it does not exist or cannot be read.
;; Check the path and the file's permissions.
;; (HarfBuzz: hb_blob_create_from_file_or_fail failed)

(hb:make-face "font.woff2")
;; font.woff2 is a WOFF2 font, which HarfBuzz cannot read.
;; Convert it to TrueType or OpenType first, for example with fontTools.

(hb:make-face path :index 3)
;; The font has 1 face, so face 3 does not exist.
;; Pass an index below FACE-COUNT of the font.

with-face (var source &key index) &body body

Binds var to (make-face source :index index) and destroys it on exit.

face-count source → integer

The number of faces in source (same kinds as make-face): 1 for a plain font, more for a collection, 0 for data that is not a font. It does not signal for data that is not a font, so it is a cheap "is this a font?" test. (A file that cannot be read still signals.)

(loop for i below (hb:face-count "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc")
      collect (hb:with-face (f "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" :index i)
                (hb:face-name f :full-name)))

destroy-face face

Releases the face. Fonts made from it remain usable. Destroying an already destroyed face does nothing.

face-live-p face → boolean

True unless the face has been destroyed.

Properties

Function Returns DejaVu Sans
face-index face index within its file 0
face-units-per-em face units per EM 2048
face-glyph-count face number of glyphs 6253
face-table-tags face list of table tags, in file order ("FFTM" "GDEF" "GPOS" "GSUB" "MATH" "OS/2" "cmap" "cvt " …)
face-table face tag the raw table as an octet vector, or nil if absent (length (hb:face-table face "head")) → 54; (hb:face-table face "COLR") → nil

Its tag is a tag, one to four printable ASCII characters; anything else is a type-error. face-table copies the table, so the vector is yours to keep. It is the way to read tables cl-harfbuzz does not parse (OS/2, head, post, MATH, STAT, …) with your own parser.

More face data is on other pages:

Escape hatch

face-handle face → foreign pointer

The raw hb_face_t* for use with cl-harfbuzz/ffi. Valid until destroy-face. Signals harfbuzz-error if the face is destroyed. Do not destroy the pointer yourself.

Printing

#<CL-HARFBUZZ:FACE 6253 glyphs {120422CCE3}>
#<CL-HARFBUZZ:FACE destroyed {12046521E3}>