ErisPulse-Takumi
ErisPulse-Takumi is a third-party image rendering module maintained by ccd2s, based on takumi-py, allowing the Bot to render HTML, node trees, Jinja templates, SVG, and animations into images. The module includes built-in Chinese and English fonts (Noto Sans SC / Roboto / Source Code Pro), requiring no additional configuration.
Important
Takumi is not a built-in feature of the ErisPulse framework and needs to be installed separately:
epsdk install Takumi
Applicable scenarios:
- Rendering data/statistics into card images
- Rendering Markdown / long text into well-formatted images, avoiding platform style differences
- Generating SVG / animations to achieve dynamic visual effects
- Mixed Chinese and English text and image (built-in fonts ready to use out of the box)
Installation and Activation
epsdk install Takumi
After installation, the module is automatically loaded. Confirm activation in the configuration:
[Takumi]
enabled = true
Quick Start
After the module is automatically loaded, it can be obtained through the module manager or using the sdk shortcut:
from ErisPulse import sdk
takumi = sdk.module.get("Takumi")
# Equivalent syntax: takumi = sdk.Takumi
Render HTML
png = takumi.render_html(
"""
<div class="card">
<h1>Hello, ErisPulse</h1>
<p>Rendered by Takumi</p>
</div>
""",
stylesheets=["""
.card {
width: 800px;
padding: 48px;
color: white;
background: #111827;
font-family: "Noto Sans SC";
}
"""],
width=800,
height=None, # Auto height based on content
lang="en",
)
Render Node Tree
png = takumi.render_node(
{
"type": "text",
"text": "Both Chinese and English can be rendered directly",
"style": {"fontSize": 48, "color": "#111827"},
},
width=800,
height=None,
lang="en",
)
png is a bytes object, which can be sent using event.reply(png, method="Image") (see Sending Rendered Results).
Rendering API
sdk.Takumi proxies all capabilities of the underlying takumi_py.Renderer: all rendering, measurement, SVG, animation, and template methods are directly callable on sdk.Takumi. For these methods, the module automatically injects the built-in font fallback stack (takumi.families) at the time of invocation, eliminating the need to manually pass font_families; however, explicit input is respected if provided.
Method Overview
| Category | Method | Return | Description |
|---|---|---|---|
| Static Rendering | render_html(html, ...) |
bytes |
Render HTML string |
render_node(node, ...) |
bytes |
Render node tree (dict) | |
render_template(name, ctx, ...) |
bytes |
Render Jinja template | |
render_compiled(node, ...) |
bytes |
Render pre-compiled node | |
| SVG Output | render_svg_html(html, ...) |
str |
Output SVG (HTML input) |
render_svg_node(node, ...) |
str |
Output SVG (node tree input) | |
render_svg_template(name, ctx, ...) |
str |
Output SVG (template input) | |
render_svg_compiled(node, ...) |
str |
Output SVG (pre-compiled input) | |
| Animation | render_animation(scenes, ...) |
bytes |
Encode multi-frame animation |
render_sequence_at_time(scenes, time_ms, ...) |
bytes |
Extract frame at a specific time from sequence | |
| Measurement | measure_node(node, ...) |
dict |
Measure node tree layout |
measure_html(html, ...) |
dict |
Measure HTML layout | |
measure_compiled(node, ...) |
dict |
Measure pre-compiled node | |
| Compilation | compile_node(node) |
CompiledNode |
Compile node tree |
compile_html(html, ...) |
CompiledNode |
Compile HTML | |
| Font | register_font(font) |
list[str] |
Register custom font, return family list |
register_fonts(fonts) |
list[str] |
Batch register fonts |
CompiledNodeexposes aresource_urls()method, allowing pre-discovery of HTTP(S) image references, facilitating resource preparation in advance.
Common Parameters
The following parameters apply to static rendering and SVG methods (animation methods have additional parameters such as fps, see corresponding examples):
| Parameter | Type | Default | Description |
|---|---|---|---|
stylesheets |
list[str] |
None |
List of document-level CSS strings; inline style is still parsed with HTML |
width |
int | None |
1200 |
Viewport width (in pixels); None infers width from layout |
height |
int | None |
630 |
Canvas height (in pixels); None auto-sizes content (see Viewport and Output Format) |
lang |
str | None |
None |
BCP-47 language tag (e.g., zh-CN), affects text shaping and line breaking |
font_families |
list[str] |
Auto-injected | Font fallback stack; convenience methods auto-inject built-in fonts |
format |
str |
"png" |
Output format (see Viewport and Output Format) |
device_pixel_ratio |
float |
1.0 |
Device pixel ratio, controls output resolution |
time_ms |
int |
0 |
Animation sampling time (in milliseconds) |
dithering |
str |
"none" |
Dithering algorithm: none / ordered-bayer / floyd-steinberg |
quality |
int | None |
None |
Lossy encoding quality |
lossless |
bool | None |
None |
Whether to use lossless encoding |
images |
list |
None |
Image resources for this render (ImageResource or (src, bytes) tuple) |
keyframes |
Mapping |
None |
Structured keyframes, no need to write @keyframes |
options |
RenderOptions |
— | Aggregate parameters via RenderOptions(...), fields match the above table |
Full field definitions are available in takumi_py.RenderOptions.
Node Tree Example
png = takumi.render_node(
{
"type": "container",
"style": {"padding": "32px", "backgroundColor": "#111827"},
"children": [
{"type": "text", "text": "Title", "style": {"fontSize": 32, "color": "white"}},
{"type": "text", "text": "Body", "style": {"fontSize": 18, "color": "#9ca3af"}},
],
},
width=800,
height=None,
lang="zh-CN",
)
Jinja Template Example
png = takumi.render_template(
"card.html.jinja",
{"title": "Takumi", "subtitle": "Jinja to image"},
stylesheets=["""
.card {
width: 800px;
padding: 48px;
color: white;
background: #111827;
}
"""],
width=800,
height=None,
lang="zh-CN",
)
Custom Jinja filters can be injected via
filters={...}, or a fulljinja2.Environmentcan be passed viaenvironment=.... Template directory and environment configuration are detailed in takumi-py template documentation.
SVG Output Example
svg = takumi.render_svg_html(
'<div class="card">Hello</div>',
stylesheets=[".card { width: 800px; color: black; }"],
width=800,
height=None,
)
Animation Example
from takumi_py import AnimationScene
webp = takumi.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,
format="webp",
)
Each frame is constructed using
AnimationScene(node, duration_ms=...), whereduration_msmust be a positive number.
Viewport and Output Format
Output Format
| Scenario | format value |
|---|---|
| Static image | png (default) / jpeg / jpg / webp / ico / raw |
| Animation | webp (default) / apng / gif |
format="raw" returns a row-major RGBA byte stream, for custom pixel-level processing.
About width and height
The roles of width and height are asymmetrical:
widthis the viewport width, text and layout wrap and reflow according to it. Should be fixed to a specific value (e.g.800), otherwise the canvas will stretch to the natural width of the content, text will not wrap, and the size will be out of control.heightis the canvas height, which grows with the content. The default value ofheightis630; whenheight=Noneis passed, Takumi will automatically expand the canvas height based on the content (auto viewport).
Tip
Recommended combination: fixed width + height=None. Only when a fixed canvas size or clipping effect is needed, should a specific height be passed.
Note
Either width or height can technically be passed as None to let it be inferred by the layout (e.g. when node itself has already declared its size); when both are provided, the output size is determined.
Fonts
Built-in Fonts
| Font | Family | Category |
|---|---|---|
| Noto Sans SC | Noto Sans SC |
sans-serif |
| Roboto | Roboto |
sans-serif |
| Roboto Italic | Roboto |
sans-serif (italic) |
| Source Code Pro | Source Code Pro |
monospace |
| Source Code Pro Italic | Source Code Pro |
monospace (italic) |
Module attributes:
| Attribute | Description |
|---|---|
takumi.fonts |
List of built-in font file names |
takumi.families |
List of registered font families |
Automatic Injection
All rendering, measurement, SVG, animation, and template methods on sdk.Takumi automatically inject takumi.families as the font fallback stack. If you directly call takumi.renderer (native instance) or create an independent instance via create_renderer(), you must manually pass font_families=takumi.families.
Custom Fonts
from takumi_py import FontResource
families = takumi.renderer.register_font(
FontResource(
font_bytes,
name="MyFont",
weight=400,
style="normal",
generic_family="sans-serif",
)
)
register_font returns a list of registered family names, which can be passed as font_families in subsequent rendering.
Renderer Instances
Native Renderer
takumi.renderer is the raw takumi_py.Renderer instance. When called directly, font_families must be passed manually:
png = takumi.renderer.render_html(
"<div>Hello</div>",
font_families=takumi.families,
lang="zh-CN",
)
Standalone Renderer
For scenarios requiring isolated font/image/resource caching (long-lifecycle processes, multi-tenant scenarios), you can create a standalone Renderer, which automatically registers built-in fonts:
renderer = takumi.create_renderer(cache_max_bytes=64 * 1024 * 1024)
png = renderer.render_html(
"<div>Standalone Renderer</div>",
font_families=takumi.families,
width=800,
height=None,
lang="zh-CN",
)
create_renderer() accepts constructor parameters from takumi_py.Renderer:
| Parameter | Type | Default | Description |
|---|---|---|---|
load_default_fonts |
bool |
False |
Whether to load fonts bundled with takumi-py (built-in fonts are always loaded) |
fonts |
list[FontResource] |
None |
Additional custom fonts to register |
cache_max_bytes |
int | None |
None |
Maximum resource cache size (in bytes); 0 disables caching |
persistent_images |
list |
None |
Persistent image resources |
Standalone instances bypass the module proxy, so if you want to retain a unified built-in font fallback stack, you must explicitly pass
font_families=takumi.families. Iffont_familiesis explicitly passed, the module respects the caller's setting and does not inject the default fallback stack;RenderOptions(font_families=...)is also valid.
Sending Rendered Results
The rendered image is in bytes format and can be sent directly via event reply:
from ErisPulse import sdk
takumi = sdk.Takumi
png = takumi.render_html("<div>hello</div>", lang="en")
# Method 1: Reply using Image method
await event.reply(png, method="Image")
# Method 2: Reply using OneBot12 message segment
from ErisPulse.Core.Event import MessageBuilder
await event.reply_ob12(
MessageBuilder().image(png).build()
)
The adapter handles the image encapsulation for different platforms. See MessageBuilder Detailed Explanation and Send Method Specification for more information.
Configuration
[Takumi]
enabled = true
Related Links
- PyPI: https://pypi.org/project/ErisPulse-Takumi/
- Repository: https://github.com/ccd2s/ErisPulse-Takumi (author @ccd2s)
- Underlying Engine: https://github.com/BalconyJH/takumi-py
- takumi-py Documentation: https://github.com/BalconyJH/takumi-py/blob/main/docs/en/index.md