Skip to main content

takumi-py

takumi-py provides Python 3.10+ bindings for the Takumi Rust renderer.

The documentation source lives in docs/ and is built with Zensical. Use make docs-serve for local preview.

[!IMPORTANT] takumi-py is currently in a testing stage. APIs, wheel build targets, release automation, and exception types may still change while the Takumi core binding surface is completed; do not treat it as a stable production dependency yet.

The binding focuses on exposing practical Takumi core capabilities instead of copying the WASM/JS convenience layer. It currently supports:

  • Node Tree, HTML string, and Jinja template rendering into image bytes.
  • RenderOptions, including auto viewport, DPR, debug border, dithering, and time_ms.
  • Custom fonts, per-render image resources, font fallback families, language hints, and SVG output.
  • Raw row-major RGBA image sources and configurable resource and glyph cache budgets.
  • Rust-backed HTML parsing with configurable presets, Tailwind attribute mapping, and depth limits.
  • Layout measurement with a typed measured node tree result.
  • CSS and structured keyframe animation time sampling, sequence animation, and WebP/APNG/GIF animated encoders.
  • PEP 561 typing, with _core.pyi covering the public native binding surface.

It intentionally does not include Playwright fallback, remote fetch, abort signal support, data URL convenience APIs, a Node.js sidecar, or Takumi internal layout/cache/glyph types.

Development

git submodule update --init --recursive
uv sync --all-groups --all-extras
uv run maturin develop
make check

make check checks Ruff formatting and linting, ty, native stub/runtime parity with mypy.stubtest, pytest with coverage, and the Rust formatting/build checks.

Install From Source

git clone --recurse-submodules https://github.com/BalconyJH/takumi-py.git
cd takumi-py
uv sync --all-groups --all-extras
uv run maturin develop

For an existing non-recursive checkout, run git submodule update --init --recursive before syncing dependencies.

Node Tree

from pathlib import Path

from takumi_py import Renderer

renderer = Renderer()

png = renderer.render_node(
    {"type": "text", "text": "Hello from Python"},
    stylesheets=["span { font-size: 72px; color: black; }"],
    width=1200,
    height=630,
)

Path("out.png").write_bytes(png)

Render Options

from takumi_py import RenderOptions, Renderer

raw = Renderer().render_node(
    {
        "type": "container",
        "style": {
            "width": "240px",
            "height": "120px",
            "backgroundColor": "white",
        },
    },
    options=RenderOptions(
        width=None,
        height=None,
        format="raw",
        device_pixel_ratio=2.0,
        dithering="ordered-bayer",
    ),
)

HTML

from takumi_py import Renderer

html = """
<div class="card">
  <h1>Hello</h1>
</div>
"""

stylesheets = ["""
.card {
  width: 1200px;
  height: 630px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  background: #111827;
}
"""]

png = Renderer().render_html(
    html,
    stylesheets=stylesheets,
    width=1200,
    height=630,
)

HTML parsing is performed by Takumi's Rust parser. Use HtmlOptions when you need to disable Chromium presets, read Tailwind classes from a custom attribute, or cap parse depth:

from takumi_py import HtmlOptions, Renderer

png = Renderer().render_html(
    '<div class="w-[1200px] h-[630px]"></div>',
    html_options=HtmlOptions(
        presets="none",
        tailwind_property="class",
        max_depth=64,
    ),
    width=None,
    height=None,
)

Compiled nodes expose Takumi's image URL discovery API:

compiled = Renderer().compile_node(
    {"type": "image", "src": "https://example.com/logo.png"}
)

print(compiled.resource_urls())

resource_urls() follows Takumi's native image URL discovery semantics and reports HTTP(S) image references from image nodes and styles. It does not fetch those resources and does not report already-provided memory:// resources or byte buffers.

Measure

from takumi_py import Renderer

measured = Renderer().measure_node(
    {
        "type": "container",
        "style": {"width": "240px", "height": "120px"},
        "children": [{"type": "text", "text": "Hello"}],
    },
    width=240,
    height=120,
)

print(measured.width, measured.height)

Resources

from pathlib import Path

from takumi_py import FontResource, ImageResource, Renderer

renderer = Renderer(load_default_fonts=False)
families = renderer.register_font(
    FontResource(
        Path("Inter-Regular.woff2").read_bytes(),
        name="Inter",
        weight=400,
        style="normal",
        generic_family="sans-serif",
    )
)

png = renderer.render_node(
    {"type": "image", "src": "memory://logo", "width": 128, "height": 128},
    width=128,
    height=128,
    images=[
        ImageResource(
            "memory://logo",
            Path("logo.svg").read_bytes(),
            cache="none",
        )
    ],
    font_families=families,
    lang="en",
)

fetched_resources, load_font, load_fonts, persistent_images, put_persistent_image, and clear_image_store remain available as deprecated compatibility shims for the v0.2 line. New code should pass images per render and use register_font / register_fonts.

register_font returns the family names registered by Takumi. Pass that list as font_families when you want a render call to use those families as its fallback stack. lang accepts a BCP-47 language tag and is forwarded to Takumi's locale-aware text shaping and line-breaking. Invalid render-level language tags raise ValueError consistently across render, measure, SVG, and animation APIs.

The render-level lang option is not injected as a node attribute, so it does not make CSS :lang() selectors match. Takumi's selector matcher follows the HTML language-determination model and walks actual node metadata or HTML attributes. If CSS needs :lang(...), set lang on the HTML element or node that should establish the language:

renderer.render_html(
    '<section lang="zh-Hant"><div class="headline">你好</div></section>',
    stylesheets=[
        '.headline:lang(zh-Hant) { font-family: "Noto Sans TC"; }',
    ],
)

renderer.render_node(
    {
        "type": "container",
        "lang": "ja",
        "children": [{"type": "text", "text": "こんにちは"}],
    },
    stylesheets=[':lang(ja) { font-family: "Noto Sans JP"; }'],
)

ImageResource.cache accepts "auto" or "none" and is forwarded to Takumi's native resource cache. Tuple resources like ("memory://logo", data) remain accepted and default to "auto".

Image nodes can also consume raw row-major RGBA pixels without image decoding:

from takumi_py import RawRgbaImage, Renderer

source: RawRgbaImage = {
    "width": 2,
    "height": 2,
    "data": bytes([255, 0, 0, 128] * 4),
}

png = Renderer().render_node(
    {"type": "image", "src": source, "width": 2, "height": 2},
    width=2,
    height=2,
)

The byte length must equal width * height * 4. Input uses straight alpha by default; set premultiplied=True only when the RGB channels are already multiplied by alpha.

Renderer(cache_max_bytes=...) controls the renderer-local resource cache for decoded images, scaled rasters, SVG rasters, and related render resources. The default is 16 MiB; 0 disables retention. Glyph masks and outlines use a separate process-wide cache. Call set_glyph_cache_max_bytes(...) before the process's first render when the default 8 MiB glyph budget is too small for the workload.

FontResource accepts Takumi v2 descriptor fields:

FontResource(
    font_bytes,
    name="Inter",
    weight=700,
    style="italic",
    subset_of="Brand Sans",
    generic_family="sans-serif",
)

style uses CSS font-style syntax such as "normal", "italic", or "oblique 12deg". Invalid style values raise FontError during registration. When provided, weight must be a finite number from 1 through 1000; invalid weight overrides raise ValueError.

SVG

from takumi_py import Renderer

svg = Renderer().render_svg_html(
    """
    <div class="card">Hello</div>
    """,
    stylesheets=[".card { width: 1200px; height: 630px; color: black; }"],
    width=1200,
    height=630,
)

Animation

from takumi_py import AnimationScene, RenderOptions, Renderer

renderer = Renderer()

frame = renderer.render_html(
    """
    <div class="box"></div>
    """,
    stylesheets=["""
    @keyframes fade {
      from { opacity: 0; }
      to { opacity: 1; }
    }
    .box {
      width: 64px;
      height: 64px;
      background: black;
      animation: fade 1000ms both;
    }
    """],
    width=64,
    height=64,
    time_ms=500,
)

structured_frame = renderer.render_node(
    {
        "type": "container",
        "className": "box",
    },
    stylesheets=[
        ".box { width: 64px; height: 64px; animation: fade 1000ms both; }"
    ],
    keyframes={
        "fade": {
            "from": {"opacity": 0},
            "to": {"opacity": 1},
        }
    },
    width=64,
    height=64,
    time_ms=500,
)

options_frame = renderer.render_node(
    {"type": "container", "className": "box"},
    stylesheets=[
        ".box { width: 64px; height: 64px; animation: fade 1000ms both; }"
    ],
    options=RenderOptions(
        width=64,
        height=64,
        time_ms=500,
        keyframes={
            "fade": {
                "from": {"opacity": 0},
                "to": {"opacity": 1},
            }
        },
    ),
)

webp = renderer.render_animation(
    [
        AnimationScene(
            {
                "type": "container",
                "style": {
                    "width": "100%",
                    "height": "100%",
                    "backgroundColor": "black",
                },
            },
            duration_ms=100,
        ),
        AnimationScene(
            {
                "type": "container",
                "style": {
                    "width": "100%",
                    "height": "100%",
                    "backgroundColor": "white",
                },
            },
            duration_ms=100,
        ),
    ],
    width=64,
    height=64,
    fps=20,
)

Animation scene and raw-frame duration_ms values must be positive. A zero duration raises ValueError instead of producing an empty or silently skipped frame.

Takumi v2 Migration

takumi-py now targets Takumi v2. The main resource model changed from a renderer-level global context to explicit per-render resources:

  • Use images=[ImageResource(...)] instead of fetched_resources.
  • Use register_font / register_fonts instead of load_font / load_fonts.
  • Pass font_families and lang on render calls when you need deterministic font fallback or locale-aware shaping.
  • Use HTML or node lang attributes, not the render-level lang option, when CSS selectors depend on :lang(...).
  • ImageResource.cache is forwarded to the native image cache for per-render, constructor, and deprecated persistent-image resources.
  • Image nodes accept RawRgbaImage sources for already decoded row-major RGBA pixels.
  • Renderer(cache_max_bytes=...) controls its resource cache, while set_glyph_cache_max_bytes(...) controls the process-wide glyph cache before first use.
  • FontResource accepts Takumi v2 descriptor fields: name, weight, style, subset_of, and generic_family.
  • The built-in fallback font follows Takumi v2: a Latin Geist subset marked as last resort, so caller-registered fonts win ordinary fallback selection.
  • HtmlOptions exposes Takumi's Rust from_html parser options.
  • CompiledNode.resource_urls() wraps Takumi's image URL discovery and reports HTTP(S) image/style references for callers that want to prepare resources before rendering.
  • Pass keyframes=... or RenderOptions(keyframes=...) to use Takumi's structured keyframe input without embedding @keyframes CSS text.
  • WebP defaults to lossless when neither quality nor lossless is specified. Passing both quality and lossless=True is rejected.
  • SVG output is available through render_svg_node, render_svg_html, render_svg_template, and render_svg_compiled.
  • HTML parsing is handled by Takumi's Rust parser. Inline style attributes are parsed with the HTML payload; pass document-level CSS explicitly through stylesheets=[...] on HTML render, measure, SVG, or template calls.

Takumi v2 also changes several CSS defaults to be closer to the Web platform. If an old image shifts, check for implicit defaults such as position, border/outline width, transform-origin, object-position, and SVG currentColor inheritance before treating it as a binding regression.

Jinja

from pathlib import Path

from takumi_py import FontResource, Renderer, TemplateRenderer


def uppercase(value: str) -> str:
    return value.upper()


configured_renderer = Renderer(load_default_fonts=False)
families = configured_renderer.register_font(
    FontResource(
        Path("Inter-Regular.woff2").read_bytes(),
        name="Inter",
        generic_family="sans-serif",
    )
)
renderer = TemplateRenderer(
    "examples/templates",
    filters={"uppercase": uppercase},
    renderer=configured_renderer,
)
stylesheets = ["""
.card {
  width: 1200px;
  height: 630px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 64px;
  background: #111827;
  color: white;
}
"""]

png = renderer.render(
    "card.html.jinja",
    {
        "title": "takumi-py",
        "subtitle": "HTML / Jinja to image",
    },
    stylesheets=stylesheets,
    font_families=families,
    width=1200,
    height=630,
)

Registered filters are available to templates in the renderer's environment; for example, {{ title | uppercase }} uses the filter above. Injecting the configured Renderer preserves its registered font state for template renders. The standalone render_template_to_html helper accepts the same filters mapping when only the rendered HTML string is needed.

For complete Jinja control, pass environment=... instead of template_dir. The injected jinja2.Environment keeps its loader, globals, tests, extensions, undefined-value policy, bytecode cache, and other native configuration. The optional filters mapping is then added to that same environment, and renderer.environment exposes the exact instance in use.

Release

The version contract, exact-commit CI gates, automatic tag and publish pipeline, external repository settings, and failure recovery rules are maintained in the release guide.

Test Coverage

The Python test suite covers static rendering, the HTML adapter, templates, core fixtures, options, measurement, resources, animation, typing artifacts, and generated HTML fixtures from the upstream Takumi core test suite.

License

takumi-py is licensed under GPL-3.0-or-later. See LICENSE.

This repository includes takumi as a git submodule. takumi is licensed separately under MIT OR Apache-2.0; see THIRD_PARTY_NOTICES.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

takumi_py-0.3.0.tar.gz (6.7 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

takumi_py-0.3.0-cp310-abi3-win_amd64.whl (4.3 MB view details)

Uploaded CPython 3.10+Windows x86-64

takumi_py-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

takumi_py-0.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

takumi_py-0.3.0-cp310-abi3-macosx_11_0_arm64.whl (4.5 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file takumi_py-0.3.0.tar.gz.

File metadata

  • Download URL: takumi_py-0.3.0.tar.gz
  • Upload date:
  • Size: 6.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for takumi_py-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9488faab28a5f8fa6a4388f20ca23659165e41980e82b48de1784d9d467b2f7e
MD5 df056acaa83c1e0151535150209c2e1e
BLAKE2b-256 a847801fc2b3496b915f0ecd08d848812fb565d8dcdd0ed5ea9f22ab6d91f73c

See more details on using hashes here.

Provenance

The following attestation bundles were made for takumi_py-0.3.0.tar.gz:

Publisher: publish.yml on BalconyJH/takumi-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file takumi_py-0.3.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: takumi_py-0.3.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 4.3 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for takumi_py-0.3.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 3eec35de469f8fe6bfc2a81f1b8f395f94ed13d6e22b9410721b521b7dd22a3b
MD5 4280e0f682adb281a1d00d630c88fee4
BLAKE2b-256 eb3e0d2ba467d47779db9138c5fb1261284aba8409a84a3cd968e9480eb74d4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for takumi_py-0.3.0-cp310-abi3-win_amd64.whl:

Publisher: publish.yml on BalconyJH/takumi-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file takumi_py-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for takumi_py-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 89d061d8c3d3a7a9283e14a43bcd1b2d5745072d064499991261c37b7029cb57
MD5 ff74b0412f52d356c152510fac566f7a
BLAKE2b-256 1fe10bd18ab68d5a1c3466417701c0c6d3beec76076232341e18dcbb1a92113d

See more details on using hashes here.

Provenance

The following attestation bundles were made for takumi_py-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on BalconyJH/takumi-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file takumi_py-0.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for takumi_py-0.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b73e4a79c6ea1f01439c9800c15cb50ede0a76ebd5489702e8ecde1caaf684c6
MD5 5b551ea0ceca905715f58e1ad18f981a
BLAKE2b-256 9da79f32507e0cacd63d399c01f8839c89c67fd40b5d376f29cd638e52c0e827

See more details on using hashes here.

Provenance

The following attestation bundles were made for takumi_py-0.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish.yml on BalconyJH/takumi-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file takumi_py-0.3.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for takumi_py-0.3.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 695be994267c34e11a8eca7820b39ce5e2f21f32d42490e1f1a20b872cc7b7fc
MD5 49687fd69c7e0e19bde8674d4e9be14d
BLAKE2b-256 b60adf80a7432b62a9b91347bcaba1712b02f4307992ca028d6d1dcc12e061af

See more details on using hashes here.

Provenance

The following attestation bundles were made for takumi_py-0.3.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on BalconyJH/takumi-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

5 files

0.2.0

5 files

0.1.0

6 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page