Skip to content

Diagram renderers

PlantUML and Mermaid each expose a narrow renderer Protocol with one render(diagram: str) -> bytes method, and each has its own make_renderer() factory that maps a backend name from scribpy.yml to a concrete implementation. The two diagram types are otherwise independent packages — scribpy.core.plantuml and scribpy.core.mermaid — that happen to share the same architecture and the same diagram-encoding helper.

uml diagram

Same simple name, different packages

Both packages define a class literally called KrokiRenderer (scribpy.core.plantuml.kroki.KrokiRenderer and scribpy.core.mermaid.kroki.KrokiRenderer). They are unrelated classes that happen to share a name and a Kroki-calling strategy — always refer to them by their fully qualified module path, not the bare class name.

Why this design

Both diagram types use the same three-layer shape — Protocol, make_renderer() factory, one module per backend — which is the Strategy + Registry pattern applied twice. The engine (render_diagram_blocks() in scribpy.core.diagram_blocks, see Assembly pipeline) never branches on backend name; it only calls make_renderer(backend_name, ...) and then .render(diagram). Adding a new backend — a self-hosted Kroki instance, a different PlantUML deployment — means writing one new class that satisfies the Protocol and registering it in that package's _BACKENDS dict; no existing renderer class or the pipeline itself needs to change.

diagram_encoding.encode_diagram() is deliberately generic: it knows zlib compression (level 9) and URL-safe base64 encoding, and nothing about HTTP, Kroki, or PlantUML. kroki_http.kroki_render() is the one place that knows Kroki's URL shape (https://kroki.io/<language>/png/<encoded>), its timeout, and its User-Agent header — both plantuml.kroki.KrokiRenderer and mermaid.kroki.KrokiRenderer call into it with their own language string and domain exception class. This isolation means a future non-Kroki web backend does not have to touch the encoding module, and the encoding module can be tested with zero network mocking.

Backend selection

Diagram type scribpy.yml key Backend name Class Default
PlantUML build.plantuml_backend plantuml_server scribpy.core.plantuml.server.PlantUmlServerRenderer yes
PlantUML build.plantuml_backend kroki, web (alias) scribpy.core.plantuml.kroki.KrokiRenderer no
PlantUML build.plantuml_backend local scribpy.core.plantuml.local.LocalRenderer no — raises NotImplementedError
Mermaid build.mermaid_backend kroki, web (alias) scribpy.core.mermaid.kroki.KrokiRenderer yes
Mermaid build.mermaid_backend mermaid_cli, local (alias) scribpy.core.mermaid.cli.MermaidCliRenderer no

PlantUML defaults to plantuml_server, using build.plantuml_server_url (default https://www.plantuml.com/plantuml). PlantUmlServerRenderer encodes the UTF-8 diagram source with PlantUML's own hexadecimal ~h encoding (not the Kroki zlib+base64url scheme) and calls <server_url>/png/<encoded>; the same class serves both the public server and self-hosted PlantUML Server instances.

Mermaid defaults to kroki. The optional mermaid_cli backend (aliased as local) calls the executable named by build.mermaid_command (default mmdc) — the official @mermaid-js/mermaid-cli tool, which must be installed separately with Node.js. Each render runs in an isolated temporary directory with an argument list built without a shell (asyncio.create_subprocess_exec), and is bounded by a 60-second timeout.

PlantUML's local backend exists only so the factory and manifest validation can reference the name "local" without error; LocalRenderer.render() unconditionally raises NotImplementedError telling the caller to use kroki or plantuml_server instead. A real local-JAR implementation is future work.

Example

from scribpy.core.plantuml.renderer import make_renderer as make_plantuml_renderer
from scribpy.core.mermaid.renderer import make_renderer as make_mermaid_renderer

plantuml_renderer = make_plantuml_renderer(
    "plantuml_server",
    server_url="https://www.plantuml.com/plantuml",
)
png_bytes = plantuml_renderer.render("@startuml\nA -> B\n@enduml")

mermaid_renderer = make_mermaid_renderer("kroki")
png_bytes = mermaid_renderer.render("graph TD; A-->B;")

Both factories raise ValueError for an unknown backend name, listing the known backends in the error message.