Face dependents
Other C libraries can build objects that point into a face's FT_Face. The motivating case is HarfBuzz's hb_ft_font_create_referenced, which cl-harfbuzz/ft2 uses. Such an object has two needs:
- It must be destroyed before the
FT_Faceit points into, whether that happens throughdestroy-faceor throughdestroy-library. - It may need to know when the face's scaling changes: size, transform or variation coordinates.
cl-ft2 provides both through a small protocol.
Protocol
register-face-dependent face dependent → dependent
Registers dependent, any Lisp object, on face. From then on, cl-ft2 calls the two generic functions below on it. Registering the same object twice has no extra effect. The face must be live.
unregister-face-dependent face dependent
Stops the notifications, for example because the dependent was released first. Unregistering an object that is not registered is harmless.
release-face-dependent dependent face (generic, required)
Called by destroy-face, and so also by destroy-library, before the FT_Face is destroyed, while face-handle is still valid. Release the foreign object here. Every dependent class must have a method. There is no default, so a missing method is an error when the face is destroyed rather than a silent dangling pointer.
After the dependents are released, the face clears its list, so you do not need to call unregister-face-dependent from inside this method.
face-changed dependent face (generic, optional)
Called right after the face's size, transform or variation coordinates change, by setting face-size, face-strike, face-transform-matrix, face-transform-delta, face-design-coordinates, face-axis-coordinate, face-normalized-coordinates or face-named-instance. The default method does nothing.
Example: the HarfBuzz bridge
This is how cl-harfbuzz/ft2 ties a HarfBuzz font to a cl-ft2 face, abridged:
(defclass ft2-font (cl-harfbuzz:font)
((ft2-face :initarg :ft2-face :reader ft2-font-face)))
(defun make-ft2-font (face)
(let ((handle (cl-harfbuzz/ffi:hb-ft-font-create-referenced (cl-ft2:face-handle face))))
(cl-ft2:register-face-dependent
face (make-instance 'ft2-font :handle handle :ft2-face face :scale-divisor 64))))
;; Destroyed explicitly first? Stop listening.
(defmethod cl-harfbuzz:destroy-font :before ((font ft2-font))
(cl-ft2:unregister-face-dependent (ft2-font-face font) font))
;; Face closing? Destroy the hb_font_t while the FT_Face still exists.
(defmethod cl-ft2:release-face-dependent ((font ft2-font) face)
(declare (ignore face))
(cl-harfbuzz:destroy-font font))
;; Size, transform or variations changed? Resynchronise HarfBuzz.
(defmethod cl-ft2:face-changed ((font ft2-font) face)
(declare (ignore face))
(when (cl-harfbuzz:font-live-p font)
(cl-harfbuzz/ffi:hb-ft-font-changed (cl-harfbuzz:font-handle font))))
With this in place:
(ft2:with-library (lib)
(ft2:with-face (face lib path)
(setf (ft2:face-size face) 32)
(cl-harfbuzz/ft2:with-ft2-font (font face)
;; shape with FONT; glyph indexes go to ft2:glyph-index-bitmap
(setf (ft2:face-axis-coordinate face "wght") 700) ; FONT follows automatically
…)))
Shaping through hb-ft loads glyphs into the face's glyph slot, so do not hold slot state across a shaping call.
Writing your own
A minimal dependent that caches per-size data and drops it when the face changes:
(defclass metrics-cache ()
((table :initform (make-hash-table) :reader cache-table)))
(defmethod ft2:release-face-dependent ((cache metrics-cache) face)
(declare (ignore face))
(clrhash (cache-table cache)))
(defmethod ft2:face-changed ((cache metrics-cache) face)
(declare (ignore face))
(clrhash (cache-table cache)))
(defun attach-cache (face)
(ft2:register-face-dependent face (make-instance 'metrics-cache)))