Table of Contents
- Font variations
- Describing the axes
- face-variable-p face → boolean
- face-variation-axes face → list of var-axis
- face-named-instances face → list of named-instance
- Setting the variation
- font-axis-coordinate font tag → real
- font-design-coordinates font → list (generic)
- font-normalized-coordinates font → list (generic, read-only)
- font-named-instance font → index or nil (generic)
- Variable ft2-fonts
- Optical size
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.
Font variations
Variable fonts (OpenType fvar, gvar, CFF2, HVAR, MVAR, …) have axes such as weight (wght), width (wdth), slant (slnt), italic (ital) and optical size (opsz), and usually named instances such as "Bold" at fixed coordinates. The axes and instances belong to the face; the current position belongs to the font, so several fonts from one face can sit at different weights.
Variations affect everything: shaping (advances, kerning, rvrn substitutions), outlines, extents, font-metric and paint.
The examples use cl-ft2's variable test font: axes wght 100–900 (default 400) and wdth 75–125 (default 100), instances Thin, Regular, Black; glyph I has advance 300 / 400 / 600 at wght 100 / 400 / 900.
Describing the axes
face-variable-p face → boolean
True if the face has variation axes.
face-variation-axes face → list of var-axis
The axes in axis order, which is the order of coordinate lists. Empty for a static face.
(hb:face-variation-axes face)
;; => (#S(VAR-AXIS :TAG "wght" :NAME "Weight" :MINIMUM 100 :DEFAULT 400 :MAXIMUM 900 :HIDDEN-P NIL :INDEX 0)
;; #S(VAR-AXIS :TAG "wdth" :NAME "Width" :MINIMUM 75 :DEFAULT 100 :MAXIMUM 125 :HIDDEN-P NIL :INDEX 1))
var-axis is a read-only struct:
| Reader | Value |
|---|---|
var-axis-tag |
four-character tag, e.g. "wght" |
var-axis-name |
name from the name table, or nil |
var-axis-minimum, var-axis-default, var-axis-maximum |
range, in design coordinates |
var-axis-hidden-p |
true for axes not meant to be shown in user interfaces |
var-axis-index |
position in coordinate lists |
face-named-instances face → list of named-instance
(hb:face-named-instances face)
;; => (#S(NAMED-INSTANCE :NAME "Thin" :POSTSCRIPT-NAME NIL :COORDINATES (100 100) :INDEX 0)
;; #S(NAMED-INSTANCE :NAME "Regular" :POSTSCRIPT-NAME NIL :COORDINATES (400 100) :INDEX 1)
;; #S(NAMED-INSTANCE :NAME "Black" :POSTSCRIPT-NAME NIL :COORDINATES (900 100) :INDEX 2))
| Reader | Value |
|---|---|
named-instance-name |
subfamily name, e.g. "Bold" |
named-instance-postscript-name |
PostScript name, or nil if the font gives none |
named-instance-coordinates |
design coordinates in axis order |
named-instance-index |
0-based index, the value for (setf font-named-instance) |
Setting the variation
All of these are setf-able places on a font.
font-axis-coordinate font tag → real
One axis by tag. Setting it changes that axis, clamped to its range, and leaves the others where they are.
(hb:font-glyph-advance font i) ; => 400
(setf (hb:font-axis-coordinate font "wght") 900)
(hb:font-glyph-advance font i) ; => 600
An axis the font does not have signals harfbuzz-error:
The font has no "slnt" axis; its axes are "wght", "wdth".
Name axes by the tags of FACE-VARIATION-AXES.
font-design-coordinates font → list (generic)
All axes, in axis order. A static font gives (). Set from a list of reals:
- values are clamped to each axis's range,
- axes beyond the end of the list take their default,
- more values than axes signals
harfbuzz-error, - setting
nil(empty list) returns the font to its named instance, if one is set, or to the defaults.
(setf (hb:font-design-coordinates font) '(700 85)) ; every axis
(setf (hb:font-design-coordinates font) '(2000)) ; wght clamped to 900, wdth default
(hb:font-design-coordinates font) ; => (900 100)
(setf (hb:font-design-coordinates font) '()) ; back to the named instance
font-normalized-coordinates font → list (generic, read-only)
The same position normalized to −1 … 0 … 1 (minimum … default … maximum), after the font's avar mapping, one per axis. These are what the font's variation data is actually indexed by. Returned as exact rationals (HarfBuzz uses 2.14 fixed point).
(setf (hb:font-axis-coordinate font "wght") 900)
(hb:font-normalized-coordinates font) ; => (1 0)
font-named-instance font → index or nil (generic)
The index of the named instance the font was set to, or nil. Setting an index moves every axis to that instance's coordinates; setting nil clears the instance and returns every axis to its default. An index out of range signals harfbuzz-error.
(setf (hb:font-named-instance font) 1)
(list (hb:font-named-instance font) (hb:font-design-coordinates font)) ; => (1 (400 100))
(setf (hb:font-named-instance font) nil)
(list (hb:font-named-instance font) (hb:font-design-coordinates font)) ; => (NIL (400 100))
Named instances are numbered from 0, as in cl-ft2.
Variable ft2-fonts
For an ft2-font, the variation lives in the cl-ft2 face, which is what hb-ft reads. These four accessors therefore read and write the cl-ft2 face (ft2:face-design-coordinates, ft2:face-named-instance, …), so the two libraries never disagree, and a change made through cl-ft2 shows up in the HarfBuzz font too:
(setf (ft2:face-size face) 100)
(cl-harfbuzz/ft2:with-ft2-font (font face)
(setf (hb:font-axis-coordinate font "wght") 900)
(list (ft2:face-design-coordinates face)
(hb:font-design-coordinates font)
(hb:font-glyph-advance font (hb:font-char-glyph font #\I))))
;; => ((900 100) (900 100) 60) ; 600/1000 EM at 100 px
Argument checking is the same for both kinds of font. Because the variation is per face, all ft2-fonts on one cl-ft2 face share it.
Optical size
HarfBuzz does not move the opsz axis by itself. If a font has one, set it from your point size:
(when (member "opsz" (hb:face-variation-axes face) :key #'hb:var-axis-tag :test #'string=)
(setf (hb:font-axis-coordinate font "opsz") point-size))