Skip to content

Fonts and themes

Fonts

fonts

Font identity and exact advance widths.

Line breaking is only as good as its measurements, so widths come from the font file itself (via Pillow, which shapes with HarfBuzz when available) or from reportlab's built-in metrics for the standard PDF families. An approximate advance model was rejected: the error compounds word by word and reopens the very gaps justification just closed.

UNITS_PER_EM module-attribute

UNITS_PER_EM = 1000

BOLD module-attribute

BOLD = 600

Font dataclass

Font(
    family: str,
    ascent: float,
    descent: float,
    advance: Callable[[str], float],
    weight: int = 400,
    italic: bool = False,
)

A face that can both name itself in SVG and measure its own text.

family instance-attribute
family: str
ascent instance-attribute
ascent: float
descent instance-attribute
descent: float
advance instance-attribute
advance: Callable[[str], float]
weight class-attribute instance-attribute
weight: int = 400
italic class-attribute instance-attribute
italic: bool = False
width
width(text: str, size: float) -> float

How wide text renders at size, in the figure's units.

TextStyle dataclass

TextStyle(font: Font, size: float, fill: str = 'ink', tracking: float = 0.0)

How a run of text is set — everything measuring and drawing both need.

font instance-attribute
font: Font
size instance-attribute
size: float
fill class-attribute instance-attribute
fill: str = 'ink'
tracking class-attribute instance-attribute
tracking: float = 0.0
space property
space: float

The width of one interword space.

width
width(text: str) -> float

How wide text renders in this style, tracking included.

load_font cached

load_font(
    path: Path | str,
    *,
    family: str | None = None,
    weight: int = 400,
    italic: bool = False,
    index: int = 0,
) -> Font

Load a real font file.

family overrides the name written into the SVG; index picks a face out of a collection (.ttc), where the bold and the regular share one file.

builtin_font cached

builtin_font(
    family: str = "Helvetica", weight: int = 400, *, italic: bool = False
) -> Font

A standard PDF face — no font file needed, works on any machine.

find_font

find_font(stem: str) -> Path

Locate stem in the usual font directories, e.g. SourceSansPro-Regular.

Themes

theme

One object holding the typography, palette and spacing a figure inherits.

Blocks name colours rather than spelling them out — fill="surface" or accent="red" — so a figure can be restyled by swapping the theme instead of editing every call. A literal #rrggbb is always accepted too.

ACCENTS module-attribute

ACCENTS = {
    "red": "#a8412f",
    "amber": "#a8752a",
    "green": "#4f7a4a",
    "blue": "#3f6a86",
    "grey": "#7a7a7a",
}

DEFAULT module-attribute

DEFAULT = Theme()

Theme dataclass

Theme(
    body: Font = builtin_font(),
    bold: Font = (lambda: builtin_font(weight=700))(),
    italic: Font = (lambda: builtin_font(italic=True))(),
    size: float = 11.0,
    leading: float = 1.38,
    ink: str = "#1a1a1a",
    muted: str = "#5f5f5f",
    accent: str = "#1f4e5f",
    surface: str = "#f4f3f0",
    rule: str = "#e0ded9",
    page: str = "#ffffff",
    radius: float = 5.0,
    pad: float = 14.0,
    gap: float = 12.0,
)
body class-attribute instance-attribute
body: Font = field(default_factory=builtin_font)
bold class-attribute instance-attribute
bold: Font = field(default_factory=lambda: builtin_font(weight=700))
italic class-attribute instance-attribute
italic: Font = field(default_factory=lambda: builtin_font(italic=True))
size class-attribute instance-attribute
size: float = 11.0
leading class-attribute instance-attribute
leading: float = 1.38
ink class-attribute instance-attribute
ink: str = '#1a1a1a'
muted class-attribute instance-attribute
muted: str = '#5f5f5f'
accent class-attribute instance-attribute
accent: str = '#1f4e5f'
surface class-attribute instance-attribute
surface: str = '#f4f3f0'
rule class-attribute instance-attribute
rule: str = '#e0ded9'
page class-attribute instance-attribute
page: str = '#ffffff'
radius class-attribute instance-attribute
radius: float = 5.0
pad class-attribute instance-attribute
pad: float = 14.0
gap class-attribute instance-attribute
gap: float = 12.0
font_for
font_for(weight: int, *, italic: bool) -> Font

The face this theme uses for a weight and slant.

style
style(
    size: float | None = None,
    *,
    weight: int = 400,
    italic: bool = False,
    fill: str = "ink",
    tracking: float = 0.0,
) -> TextStyle

A style resolved against the theme — the size falls back to its own.

color
color(value: str) -> str

Resolve a palette name, a status name, or a literal colour.

tint

tint(color: str, amount: float = 0.86) -> str

Mix color toward white — 0 leaves it alone, 1 returns white.

Images

images

Pictures, embedded rather than linked.

A figure has to survive being emailed, so an image becomes a data URI inside the SVG instead of a path to something that will not travel with it. The intrinsic size is read here too: a logo knows its own proportions, and the layout should not have to be told them.

Picture dataclass

Picture(href: str, width: float, height: float)

An embedded image and the proportions it wants to keep.

href instance-attribute
href: str
width instance-attribute
width: float
height instance-attribute
height: float
ratio property
ratio: float

load_image cached

load_image(path: Path | str) -> Picture

Read an image and its size, ready to be dropped into a figure.