Fonts

Font loading and text measurement live in the novasvg.fonts submodule. It is backed by the same font stack that paints text and <foreignObject> content, so a measurement can never disagree with what is rendered.

import novasvg.fonts as fonts

fonts.add_font_face_from_file("DejaVu Sans", False, False, "DejaVuSans.ttf")
face = fonts.get_font_face("DejaVu Sans", bold=False, italic=False)
font = fonts.Font(face, size=16.0)
font.measure_text("Hello, world!")   # width in pixels

Reference

class FontFace(*args, **kwargs)
advance_width_units(self, codepoint: int) → float

Advance width of a single Unicode codepoint, in raw font design units (unscaled).

codepoints(self) → list[int]

Every Unicode codepoint this face’s cmap maps to a glyph (see FontFace::codepoints() in font.h for exactly which cmap formats are covered – virtually every real-world font).

is_null(self) → bool

Check if the font face is null/invalid.

kern_advance_units(self, first: int, second: int) → float

Kerning adjustment between two consecutive codepoints, in raw font design units (unscaled) – see FontFace::kernAdvanceUnits() in font.h for why this is a per-pair query rather than a bulk table like codepoints().

property ascent_units

Ascent in raw font design units (unscaled).

property descent_units

Descent in raw font design units (unscaled).

property notdef_advance_width_units

The advance width used for any codepoint outside this font’s cmap (glyph id 0, the “.notdef” glyph) – what advance_width_units() itself already falls back to for such a codepoint.

property units_per_em

Raw font design-unit space this face’s own tables are defined in (e.g. head.unitsPerEm). Pair with advance_width_units()/ascent_units()/descent_units() to build a complete, size-independent advance table – e.g. to hand text-measurement data to a JS engine that can’t call back into Python per string (mermaidx’s v8_engine does exactly this).

class Font(*args, **kwargs)
glyph_boxes()

(self, text: str) -> list

Per-glyph (pen_x, ink_left, ink_right, ink_top, ink_bottom) in pixels (top/bottom relative to the baseline, y down) for every glyph that has an outline (spaces are skipped), using the same ligature + kerning walk as measure_text().

is_null(self) → bool

Check if the font is null/invalid.

measure_text(self, text: str) → float

Measure the advance width of a UTF-8 string, in pixels, at this font’s size.

property ascent

Ascent in pixels, at this font’s size.

property descent

Descent in pixels (typically negative), at this font’s size.

property face

The underlying FontFace.

property height

ascent - descent.

property line_gap

Recommended extra spacing between lines, in pixels.

property size

The pixel size this font was constructed with.

property x_height

Height of a lowercase ‘x’ glyph, in pixels.

add_font_face_from_file(family: str, bold: bool, italic: bool, filename: str) → bool

Add a font face from a file to the cache.

add_font_face_from_data(family: str, bold: bool, italic: bool, data: bytes) → bool

Add a font face from a memory buffer (bytes) to the cache.

get_font_face(family: str, bold: bool = False, italic: bool = False) → novasvg.FontFace

Look up a font face previously registered via add_font_face_from_file/_data (falling back to novasvg’s built-in generic-family table, e.g. “sans-serif”).

get_font_face_for_family_stack(family_stack: str, bold: bool = False, italic: bool = False) → novasvg.FontFace

Resolve a CSS-style comma-separated font-family stack (e.g. ‘“trebuchet ms”, verdana, arial, sans-serif’) name by name: a face registered or installed under that exact name, then a metric-compatible substitute from novasvg’s built-in table (case-insensitive, e.g. ‘arial’ -> Liberation Sans), then generic keywords. Names nothing matches are skipped; returns a null face if none resolve.

measure_foreign_object(html: str, font: novasvg.Font, max_width: float | None = None, family: str | None = None, bold: bool = False, italic: bool = False) → dict

Height/width (and line count/line-height) novasvg will use when painting <foreignObject> content, computed WITHOUT rendering – the exact same logic ForeignObjectSimple::render() itself uses (see measureForeignObjectContent() in svgelement.h), so a caller building the SVG’s own layout (e.g. mermaidx’s headless JS DOM shim sizing a node box for an HTML label) can ask novasvg what size it will need instead of guessing separately. Text soft-wraps only if the HTML’s own CSS white-space allows it (not nowrap/pre), at max_width (px) if given, else at the px width/max-width the HTML itself declares – the same rule render() applies at the foreignObject’s own width, so layout and paint agree. Bold and italic runs (<b>, <strong>, <i>, <em>, inline font-weight / font-style) are measured with the faces they are painted with when family is given: font is then the face of the base style (bold / italic say which it is) and the other variants are looked up in family exactly as SVG <text> looks them up. Without family every run is measured with font alone, as if it had no bold or italic in it. Returns {‘height’: float, ‘width’: float, ‘line_count’: int, ‘line_height’: float}; ‘width’ is the widest resulting line.