imagine.font
import imagine.font
imaginelifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelledimagine.font.*needsimport 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(), soechoandprint()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— handlesize(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,.otfor.ttcfile.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, asmeasure()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