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 :doc:`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:
* :func:`~manimpango.render` accepts plain text and optional
:class:`~manimpango.TextSpan` objects. Spans are normalized into Pango
attributes for one layout, preserving shaping across styled ranges.
* :func:`~manimpango.render_markup` accepts raw Pango markup. Markup is
validated before it enters the same layout and SVG-rendering pipeline.
* Both functions return :class:`~manimpango.RenderedText`, an immutable object
containing the SVG and layout metadata in SVG user-space units.
* :func:`~manimpango.register_font` returns a reference-counted
:class:`~manimpango.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.**
.. list-table::
:header-rows: 1
:widths: 38 62
* - 0.6.x API or pattern
- 1.0 replacement
* - ``text2svg(settings, ..., file_name, ...)``
- :func:`~manimpango.render`, which returns
:class:`~manimpango.RenderedText`
* - ``MarkupUtils.text2svg(...)``
- :func:`~manimpango.render_markup`
* - ``MarkupUtils.validate(markup)``
- :func:`~manimpango.validate_markup`
* - Mutable ``TextSetting`` objects
- Immutable :class:`~manimpango.TextSpan` objects for plain text
* - ``register_font(path)`` returning a boolean and
``unregister_font(path)``
- :func:`~manimpango.register_font` returning a
:class:`~manimpango.FontRegistration` handle; call ``close()`` or use a
context manager
* - ``pango_version()`` and ``cairo_version()``
- :func:`~manimpango.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.
.. code-block:: python
# 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("Hello world")
``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
:class:`~manimpango.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.
.. code-block:: python
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 :class:`~manimpango.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:
.. code-block:: python
with manimpango.register_font("path/to/font.ttf") as registration:
result = manimpango.render("Example", font="My Font")
assert registration.closed
Missing files raise :class:`~manimpango.FontNotFoundError`; other backend
registration failures raise :class:`~manimpango.FontRegistrationError`.