Changelog

1.0

Architecture and breaking changes

ManimPango 1.0 is a hard break from the 0.6.x API: callers should migrate rather than expect a compatibility layer. This section summarizes the design surface for review; the API Reference is the authoritative API contract. The redesigned implementation was first made available for testing in 1.0.0a3.

The public API separates user-facing validation and immutable result models from the native Pango and Cairo implementation. Rendering follows one layout pipeline:

  • render() accepts plain text and optional TextSpan objects. Spans are normalized into Pango attributes for one layout, preserving shaping across styled ranges.

  • render_markup() accepts raw Pango markup. Markup is validated before it enters the same layout and SVG-rendering pipeline.

  • Both functions return RenderedText, an immutable object containing the SVG and layout metadata in SVG user-space units.

  • register_font() returns a reference-counted FontRegistration handle. The native backend registers the font and the renderer updates its visible font map when registration state changes.

This division keeps generic text rendering in ManimPango while leaving Manim-specific text selection and object integration to Manim.

Public API.

render() accepts plain text only. It has keyword-only options and returns an in-memory result instead of requiring an output path. Use render_markup() for raw Pango markup; structured spans and markup cannot be mixed in one call.

TextSpan offsets are half-open Python code-point ranges, just like string slices. Overlapping spans compose when they set different attributes; conflicting values for the same attribute raise ValueError rather than depending on span order.

All public geometry is represented by float values in SVG user-space units. RenderedText includes SVG text, overall dimensions and baseline, ink and logical bounds, and per-line text, ranges, bounds, and baselines.

Custom fonts have explicit ownership. A registration remains active until its handle is closed; use the handle as a context manager for scoped registration. Repeated registrations of the same normalized path are reference-counted.

Breaking changes from 0.6.x.

0.6.x API or pattern

1.0 replacement

text2svg(settings, ..., file_name, ...)

render(), which returns RenderedText

MarkupUtils.text2svg(...)

render_markup()

MarkupUtils.validate(markup)

validate_markup()

Mutable TextSetting objects

Immutable TextSpan objects for plain text

register_font(path) returning a boolean and unregister_font(path)

register_font() returning a FontRegistration handle; call close() or use a context manager

pango_version() and cairo_version()

get_version_info()

RenderedText.save(path) is the explicit opt-in for writing SVG. It writes UTF-8 content to an existing parent directory and propagates filesystem errors.

Scope.

Version 1.0 intentionally does not provide:

  • a compatibility facade for the 0.6.x utility classes and path-based font unregistration;

  • rendering backends other than SVG;

  • a Manim Text or MarkupText integration layer; or

  • Linux binary wheels. Linux users continue to install from source with the required native build dependencies.

These boundaries keep the package focused on a small, testable SVG text rendering interface. Future work can build on that interface without changing its core contracts.

Migration

render() accepts plain text only. Use render_markup() for raw Pango markup; plain and markup rendering are separate entry points. Use TextSpan objects with render() for range styling.

# Plain text with structured styling.
result = manimpango.render(
    "Hello world",
    spans=(
        manimpango.TextSpan(0, 5, foreground="#3366cc"),
        manimpango.TextSpan(6, 11, style=manimpango.Style.ITALIC),
    ),
)

# Raw Pango markup uses a different entry point.
marked = manimpango.render_markup("<b>Hello</b> <i>world</i>")

TextSpan.start and TextSpan.end are half-open Python code-point offsets. Overlapping spans compose when they affect different attributes; conflicting values for the same attribute raise ValueError.

Both rendering functions return a frozen, slotted RenderedText. Geometry is always expressed in SVG user-space units, not pixels or raw Pango units. The former synthetic line-spacing metadata value and mutable underscore-backed fields have been removed; line_spacing= remains a Pango layout multiplier.

result = manimpango.render("one\ntwo", width=200.0)

print(result.width, result.height, result.baseline)
print(result.ink_bounds, result.logical_bounds)
for line in result.lines:
    # Ranges exclude newline separators.
    print(line.text, line.start, line.end, line.bounds, line.baseline)

result.lines is a tuple of LineInfo instances. For markup, line ranges refer to parsed text rather than the markup source. Both layout bounds and per-line bounds use the same transformed coordinate system as the SVG.

RenderedText.save(path) writes UTF-8 SVG to path. The parent directory must already exist; missing parents raise FileNotFoundError.

Custom-font registration has explicit ownership:

with manimpango.register_font("path/to/font.ttf") as registration:
    result = manimpango.render("Example", font="My Font")

assert registration.closed

Missing files raise FontNotFoundError; other backend registration failures raise FontRegistrationError.