BlockDiag family renderer
One Cloudflare Python Worker renders BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag, and RackDiag directly from pinned upstream Python libraries. The request hostname selects the engine. Request bodies cannot select a different engine.
The Worker imports renderer libraries directly and does not execute a command, spawn a process, call Java, or fetch the Fly compatibility origin. Remote and filesystem-backed images are rejected. SVG is sanitized before presentation metadata, padding, backgrounds, and framing are applied.
The six engines share one Worker because they form one compatible upstream dependency family. The four source trees provide the same parser, builder, SVG drawer, font, and node-renderer model. They retain six hostnames, so the hostname remains the only engine selector. The shared deployment avoids six copies of Pillow, the font, and the registration table. One request cannot select another engine.
Reproduce locally
Run these commands from this directory:
uv sync
uv run pytest -q
uv run pywrangler sync
uv run pywrangler dev --port 8788
The Worker build is Pyodide-based. pywrangler sync is therefore the
important packaging check; ordinary uv sync only checks the CPython test
environment. Deploy only after both checks pass:
uv run pywrangler deploy
For a local request, route the hostname to the development port, for example:
curl --resolve blockdiag.render.diagram.zip:8788:127.0.0.1 \
-H 'Content-Type: application/json' \
--data '{"source":"blockdiag { a -> b; }"}' \
http://blockdiag.render.diagram.zip:8788/v1/svg
The request body must not contain an engine field. Wrangler's local custom
domain emulation resolves every configured hostname as the first route. Use the
CPython six-engine example suite locally. Smoke all six real hostnames after
deployment. Do not treat a local Host header as proof of per-host dispatch.
Upstream parity reference
When an upstream comparison is needed, run the tagged Kroki image directly. It is an external reference and is not part of DiagramZip's build or runtime:
docker run --rm -p 8000:8000 yuzutech/kroki:0.32.1
Send the same fixtures to the reference image and to the Worker development port, then compare structural SVG coverage and record engine-specific losses. Pixel equality is not a gate for this extraction.
Pinned upstream revisions and their licenses are recorded in
THIRD_PARTY_NOTICES.md and uv.lock.
Boundaries and known losses
The request protocol enforces a 1 MiB body limit and a 512 KiB source limit. It
allows at most 64 options and 4 KiB per option. Padding ranges from 0 through
256. The sanitized SVG limit is 4 MiB. Only SVG output is exposed. Remote URLs
and filesystem images are rejected before they can be fetched. Returned SVG is parsed with
defusedxml; scripts, event handlers, active elements, external references,
unsafe CSS, and unsafe embedded resources are removed.
Supported renderer options currently include size=WIDTHxHEIGHT, antialias,
no-transparency, and no-doctype. The two raster-only flags, antialias and
no-transparency, intentionally have no effect on SVG. The common protocol
validates other upstream command-line options but does not pass them to a CLI.
They remain documented loss until a direct API mapping is added. The Worker
uses the bundled DejaVu Serif font path from the Docker reference. This path
provides deterministic text metrics. Font fallback can differ on a larger host.
The vendored runtime replaces upstream importlib.metadata entry-point
discovery with an explicit registration table. It installs the SVG drawer,
all bundled node renderers (including PacketDiag), and only the built-in
attributes, autoclass, and autolane plugins. This keeps the Worker
reproducible and prevents arbitrary installed plugins from entering the
rendering process.
The upstream libraries expose process-wide mutable renderer and plugin
registries. Registration is one-time startup work. Each render and its cleanup
are synchronous with no await boundary. Requests cannot interleave while one
isolate changes those registries. Cloudflare can run multiple isolates. Each
isolate has its own Python runtime and registries. Keep rendering synchronous
until the registries become request-local.