Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

imagine.font

import imagine.font

imagine lifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelled imagine.font.* needs import imagine.font.

Font: a loaded TrueType or OpenType face, ready to measure and draw text with.

Glyph rasterisation and metrics come from the font file itself, so kerning and line height are whatever the designer specified rather than an approximation.

Classes

Font

class imagine.Font

A typeface at a particular size, ready to draw with.

A Font is immutable and cheap to copy: size() hands back another Font sharing the same parsed face, so keeping one font around and asking it for several sizes costs nothing extra.

import imagine { Image, Font, Color }

var title = Font.load('assets/Inter.ttf').size(32)
var body = title.size(14)

Image(400, 120, Color.WHITE)
  .text(20, 20, 'Quarterly report', title, '#111111')
  .text(20, 64, 'Revenue is up 12% year on year.', body, '#555555')
  .save('report.png')
What text layout does and does not do

Glyphs are positioned by advance width with kerning applied, and \n starts a new line. That covers Latin, Greek, Cyrillic and anything else written left to right without contextual shaping.

It does not do complex shaping: Arabic letters will not join, Indic clusters will not reorder, and ligatures are not substituted. Those need a shaping engine, and a standard library that pretended to do them would be worse than one that says plainly it does not.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

imagine.Font(handle, size, source)

Wraps an already-parsed face.

Not the way to get a Font; use load(), from_bytes(), system() or default(), all of which end up here.

Parameters

  • ptr — handle
  • size (number)
  • source (string)

Font.load()

imagine.Font.load(path: string, size) -> Font

Loads a TrueType or OpenType font from a file.

The parsed face is cached by absolute path, so loading the same file twice in one process parses it once.

Parameters

  • path (string) — A path to a .ttf, .otf or .ttc file.
  • size (?number) — The size in pixels - Default: 16.

Returns Font

Font.from_bytes()

imagine.Font.from_bytes(data: bytes, size) -> Font

Parses a font already held in memory.

Useful for a font embedded in the program, downloaded, or read out of an archive. Nothing is cached, since there is no path to key a cache on; call this once and keep the result.

Parameters

  • data (bytes)
  • size (?number) — The size in pixels - Default: 16.

Returns Font

Font.system()

imagine.Font.system(family, size) -> Font

Finds a font installed on this machine by family name.

The standard font directories for the platform are searched, and the match ignores case, spaces and hyphens, so 'DejaVu Sans', 'dejavusans' and 'DejaVuSans' all find the same file. A family with several weights matches whichever file the search reaches first, which is why load() is the right call when a specific weight matters.

Raises FontError when nothing matches.

Parameters

  • family (string)
  • size (?number) — The size in pixels - Default: 16.

Returns Font

Font.sans()

imagine.Font.sans(size) -> Font

Finds a reasonable sans-serif font installed on this machine.

A short list of the families most likely to be installed is tried in turn. This is a convenience for scripts and tests, not something to rely on for output that has to look the same everywhere: which font it lands on depends entirely on the machine, and a container image with no fonts installed has none to find.

Raises FontError when the machine has no usable font, with a message saying so rather than a parse failure.

Parameters

  • size (?number) — The size in pixels - Default: 16.

Returns Font

Font.builtin()

imagine.Font.builtin(size) -> StrokeFont

The built-in font, which is always available.

imagine ships no font file, so system() and sans() both depend on what happens to be installed and can fail. This one cannot: it is drawn from stroke geometry rather than loaded, and scales to any size.

The result is a [[imagine.StrokeFont]], not a Font. The two have the same interface, so anywhere a font is accepted either works. Read that class’s own documentation for what the built-in font is and is not suitable for.

var font = Font.builtin(20)

chart.text(12, 8, 'requests / second', font, '#334155')

Parameters

  • size (?number) — The size in pixels - Default: 16.

Returns StrokeFont

Font.size()

imagine.Font.size(pixels: number) -> Font

Returns the same typeface at a different size.

The parsed face is shared, so this is cheap enough to call in a loop.

Parameters

  • pixels (number) — The size in pixels.

Returns Font

Font.pixel_size()

imagine.Font.pixel_size() -> number

The size in pixels this font draws at.

Returns number

Font.name()

imagine.Font.name() -> string

The typeface’s own name, as recorded in the font file.

Returns string

Font.source()

imagine.Font.source() -> string

Where this font came from: an absolute path, or '<memory>' for one parsed from bytes.

Returns string

Font.metrics()

imagine.Font.metrics() -> dict

The font’s vertical metrics at its current size, in pixels.

The dictionary holds:

  • name: the typeface’s name.
  • ascent: how far the tallest glyphs rise above the baseline.
  • descent: how far descenders fall below it, as a negative number.
  • line_gap: the designer’s recommended extra space between lines.
  • line_height: ascent minus descent plus line gap, which is the distance between consecutive baselines at single spacing.

Returns dict

Font.measure()

imagine.Font.measure(text, options) -> dict

Measures a string without drawing it.

Returns a dictionary holding width and height in pixels, baseline (the first line’s baseline, measured down from the top of the box) and lines (how many lines the string has).

The box covers the text’s advance widths. A glyph can paint a fraction of a pixel outside it, which is why Image.text() allows for a little slack when it draws.

Parameters

  • text (string)
  • options (?dict) — tracking, line_height, align.

Returns dict

Font.wrap()

imagine.Font.wrap(text, width: number, options) -> string

Breaks a string into lines that fit within a width.

Returns the same text with newlines inserted, ready to hand to Image.text(). Newlines already in the text are kept as paragraph breaks, and runs of spaces are collapsed to one.

Words are kept whole where they can be. A single word too long for the width is broken between characters rather than allowed to overflow, since overflowing text in an image cannot be scrolled to.

var body = Font.load('Inter.ttf', 16)
var text = body.wrap(article, 520)

page.text(40, 120, text, body, '#334155')

Parameters

  • text (string)
  • width (number) — The maximum line width in pixels.
  • options (?dict) — tracking, as measure() takes.

Returns string

Font.render()

imagine.Font.render(text, options) -> dict

Rasterizes a string into an 8-bit coverage mask.

Returns a dictionary holding coverage (the mask, one byte per pixel), width, height and baseline. Drawing the mask through Canvas.draw_mask() is what Image.text() does; this is here for code that wants the mask itself, to use as a stencil or an alpha channel.

Parameters

  • text (string)
  • options (?dict) — tracking, line_height, align.

Returns dict

Font.to_string()

imagine.Font.to_string()

2026, Richard Ore and Zuri contributors