4 Sizes and Transforms
dxtr edited this page 2026-10-18 16:25:41 +02:00
This file contains ambiguous Unicode characters

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.

Sizes and transforms

A scalable face must have a size before it can load scaled glyphs. A bitmap-only face, such as a PCF/BDF font or a color emoji font with CBDT or sbix strikes, selects one of its fixed strikes instead.

Size, strike and transform are setf places. FreeType cannot report back exactly what it was given, because it rounds pixels per em, so the face remembers what you set. Setting any of these places notifies the face's dependents with face-changed.

Size

face-size face → width, height · setf

The size in pixels per em, as two values. Both are nil if no size has been set, or if a strike has been selected since.

Set it to a positive real, or to a list (width height) of them: the type face-size-designator. Fractional sizes are kept to 1/64 of a pixel. Setting the size deselects any strike.

(setf (ft2:face-size face) 32)
(ft2:face-size face)                    ; => 32, 32
(setf (ft2:face-size face) '(20 24))    ; condensed
(setf (ft2:face-size face) 21/2)        ; 10.5 px/em

Points: convert with pixels = points × dpi / 72. For example, 12 pt at 96 dpi is (setf (ft2:face-size face) (* 12 96/72)), which is 16 px/em.

For a fractional size, size-metrics-x-scale is exact (21/4096 for 10.5 px with DejaVu's 2048 units per em), and glyphs scale by 10.5. size-metrics-x-ppem, an integer, reports 11.

On a face that is not scalable, setting the size fails with :invalid-pixel-size and a hint to use face-strike. Sizes that are not positive reals signal a type-error.

Strikes

face-strike face → index or nil · setf

The index into face-strikes of the strike selected as the face's size, or nil. FreeType never selects a strike by itself, not even for a font with only one, so a bitmap-only face starts with nil. Setting it selects that strike and deselects any face-size.

(setf (ft2:face-strike emoji) 0)        ; Noto Color Emoji: its only strike, 109 px/em

A strike that does not exist signals :invalid-argument before FreeType is called:

(setf (ft2:face-strike dejavu) 0)
;; error: The face has no bitmap strikes, so strike 0 does not exist.
;;        Set the size of a scalable face with FACE-SIZE.

face-strikes face → list of strike

The strikes the face provides. The list is empty for a purely scalable face.

strike is a read-only struct:

Accessor Meaning
strike-width average glyph width of the strike, in whole pixels
strike-height vertical distance between two baselines, in whole pixels
strike-size nominal size, in pixels (rational)
strike-x-ppem, strike-y-ppem pixels per em (rational)

To pick the strike closest to a wanted size:

(defun select-nearest-strike (face pixels)
  (let ((sizes (ft2:face-strikes face)))
    (setf (ft2:face-strike face)
          (position (reduce (lambda (a b)
                              (if (<= (abs (- (ft2:strike-y-ppem a) pixels))
                                      (abs (- (ft2:strike-y-ppem b) pixels)))
                                  a b))
                            sizes)
                    sizes))))

face-size-metrics face → size-metrics

The face's global metrics scaled to the current size:

(setf (ft2:face-size face) 32)
(ft2:face-size-metrics face)
;; => #S(SIZE-METRICS :X-PPEM 32 :Y-PPEM 32 :X-SCALE 1/64 :Y-SCALE 1/64
;;                    :ASCENDER 30 :DESCENDER -8 :HEIGHT 37 :MAX-ADVANCE 60)
Accessor Meaning
size-metrics-x-ppem, -y-ppem integer pixels per em
size-metrics-x-scale, -y-scale pixels per font unit. Multiply a font-unit distance by it. For DejaVu Sans at 32 px, 32/2048 = 1/64
size-metrics-ascender scaled ascender, in pixels. For hinted fonts FreeType rounds it outwards to whole pixels
size-metrics-descender scaled descender, in pixels (negative)
size-metrics-height baseline-to-baseline distance, in pixels
size-metrics-max-advance largest advance, in pixels

Converting a font-unit value from the face:

(* (ft2:face-underline-position face)
   (ft2:size-metrics-y-scale (ft2:face-size-metrics face)))

Transforms

FreeType applies an affine transform to every glyph as it is loaded into the face's slot. It is two places, read and set independently.

face-transform-matrix face → list · setf

A list (xx xy yx yy) of reals, the type transform-matrix, which maps (x, y) to (xx·x + xy·y, yx·x + yy·y). It is rounded to 16.16 when handed to FreeType. (1 0 0 1) is the identity, and setting nil restores it.

face-transform-delta face → list · setf

A list (x y) in pixels, the type transform-delta, rounded to 1/64, added after the matrix. (0 0) is the default, and setting nil restores it.

;; Synthetic oblique: shear x by 0.2 per unit of height.
(setf (ft2:face-transform-matrix face) '(1 1/5 0 1))
(ft2:char-bitmap face #\l)        ; slanted
(setf (ft2:face-transform-matrix face) nil)

;; Rotate 90° counter-clockwise.
(setf (ft2:face-transform-matrix face) '(0 -1 1 0))

;; Subpixel pen position: shift by a quarter pixel before rasterising.
(setf (ft2:face-transform-delta face) '(1/4 0))

Setting one place leaves the other as it is. Values of the wrong shape, such as a three-element matrix, signal a type-error.

The transform also changes glyph-advance. It does not change glyph-metrics, which FreeType always reports untransformed. Pass :ignore-transform in a load's :flags to skip the transform for that load.

To transform one copied glyph rather than every load, use transform-glyph on a glyph object.