Skip to main content

Crate document_graphics

Crate document_graphics 

Source
Expand description

§document-graphics

Bounded Mermaid/SVG rendering, optional image decoding, and Go/chess boards for terminal and GUI applications. The crate returns straight RGBA pixels and logical dimensions. It has no UI framework, Markdown document model, application cache, broker or background service.

Mermaid supports flowcharts, sequence, class and state diagrams, ER attribute tables, pie charts, mind maps, timelines without sections, user journeys and quadrant charts. Bundled fonts cover Latin (including accents), Greek and Cyrillic labels. Unsupported syntax and unavailable glyphs return Ok(None) through the worker so callers can retain the original source. The strict document SVG renderer uses bundled KaTeX and Noto Sans fonts and rejects external resources, filters, reuse and foreign content.

§Scope

The library renders the graphical content of documents independently of their layout or user interface. Today it supports Mermaid diagrams, a restricted SVG subset, Go positions from SGF records (sgf feature), and standard chess from PGN/FEN records (chess feature), and bounded image decoding (images, image-worker and image-native features, available from 0.1.5). Other diagram formats and music notation are possible future modules, not implemented here. Applications own document layout, interaction, scheduling and cache budgets. Equation layout remains in katex-native.

New backends should remain optional and initialize only when used. Their own input limits and platform isolation requirements must be explicit. Future APIs may retain a bounded parsed model and render a selected fragment, position, page or tile without retaining an image for every item.

§Integration

Requires Rust 1.90 or newer. Version 0.1.6 adds transparent Mermaid canvases and is pending release; use a local override until publication.

[dependencies]
document-graphics = "0.1.6"

Handle the private worker argument before initializing the application:

use document_graphics::{RasterOptions, worker::{self, Worker}};
use std::{sync::atomic::AtomicBool, time::Duration};

fn main() -> std::io::Result<()> {
    if std::env::args().nth(1).as_deref() == Some("--preview-worker") {
        return worker::worker_main();
    }
    // Run blocking rendering on a background thread in interactive applications.
    let mut worker = Worker::spawn(&std::env::current_exe()?, "--preview-worker")?;
    let cancelled = AtomicBool::new(false);
    let _image = worker.render(
        "flowchart LR; Read-->Preview", false,
        RasterOptions::scaled(2.), Duration::from_secs(2), &cancelled,
    )?;
    // Move image.rgba into the application's image type; no pixel copy is needed.
    Ok(())
}

RasterOptions::fit((width, height)) fits within physical bounds without upscaling. RasterOptions::scaled(2.) requests Retina output while preserving logical SVG size. Applications can set both fields explicitly. Worker::render paints the theme’s canvas color. To composite onto a terminal or another host surface, use Worker::render_with_background with mermaid::Background::Transparent between the light/dark flag and raster options. Transparency is applied before rasterization, preserving antialiased edges and intentional node/table fills. The existing worker API retains its opaque default.

Mermaid input is limited to 16 KiB / 1,024 lines, 512 nodes and 1,024 edges. Its generated SVG is limited to 1 MiB, 16,384 XML nodes, 256 KiB of geometry, a 32,768-unit logical edge and 64 million logical square units. These logical dimensions do not allocate a full-size bitmap: output remains capped at one million pixels and an 8,192-pixel edge. Local SVG retains its 256 KiB source, 4,096 XML nodes, 64 KiB of geometry, 8,192-unit logical edge and four-million-unit area limits. Additional nesting, annotation and label limits apply before expensive work. Pie charts allow 64 slices, mind maps 128 nodes/depth 32, timelines 64 periods/128 events, journeys 32 actors with scores 1–5, and quadrant charts 128 points with coordinates 0–1. Unsupported timeline sections retain source. Nonfinite chart numbers are rejected.

Use svg::rasterize(source, options) for bounded local SVG. It accepts no file paths and does not fetch resources. Only the internal Mermaid route permits bounded arrow markers. Mermaid parsing and layout are private to the worker; there is no public in-process Mermaid entry point.

The expanded Mermaid limits are available from 0.1.2, using the published document-graphics-mermaid backend. They retain the existing worker memory and raster budgets.

Version 0.1.3 pins document-graphics-mermaid 0.1.3 for faster flowchart route comparisons and improved quadrant-axis label contrast. The public API, worker protocol, feature defaults and resource limits are unchanged.

§SGF Go records

Enable the optional sgf feature (available from 0.1.1). It adds no dependencies, network access or runtime installation requirements. Parse a bounded collection, reconstruct the selected board, and render it off the application’s UI thread:

use document_graphics::{sgf::{Collection, RenderOptions, rasterize}, RasterOptions};
use std::sync::{Arc, atomic::AtomicBool};
let cancel = AtomicBool::new(false);
let source: Arc<[u8]> = Arc::from(&b"(;FF[4]GM[1]SZ[9];B[dd];W[ee])"[..]);
let collection = Collection::parse(source, &cancel)?;
let position = collection.position(2, &cancel)?;
let image = rasterize(&position, RenderOptions::default(), RasterOptions::scaled(2.), &cancel)?;

The compact index retains source ranges and tree links, including unknown properties; it does not store a board or bitmap for every node. Position::advance replays a direct child incrementally. Applications choose which positions to cache. Captures, passes, setup stones, handicap, move numbers, common board marks, labels, arrows and lines are supported. Replay follows SGF positions without checking competitive legality, ko or turn order. Comments and game information are exposed as text for the caller’s UI, not embedded in the diagram.

Limits: 8 MiB source, 100,000 nodes, 1,024 games, 256 levels of variation nesting, 300,000 properties (128 per node), 500,000 values, 128 KiB per value, boards up to 52 × 52, 512 marks and 512 lines per position, and 32 characters per board label. Position reconstruction has a 20-million-step work limit. SVG and raster limits also apply, so dense annotated boards can exceed the diagram budget. UTF-8 and ISO-8859-1 are accepted; missing CA uses UTF-8 when valid, otherwise ISO-8859-1. Other games/encodings return an explicit error. VW cropping and legacy markup are retained in source but not interpreted. Board labels use bundled fonts.

Unlike Mermaid’s layout engine, this bounded, iterative parser and replay loop run in-process with cooperative cancellation, followed by the restricted SVG renderer. They are not isolated by the Mermaid worker’s OS sandbox. Callers must schedule them off-thread, coalesce requests and retain readable source on failure. There is no hard wall-clock deadline on this in-process API.

The repository’s sgf_probe example reads an SGF directory, checks every node and renders roots and leaves without modifying any input:

cargo run --release --features sgf --example sgf_probe -- path/to/sgf-directory

§Chess records

Enable the optional chess feature for standard-chess PGN collections and FEN positions. It adds the MIT-licensed cozy-chess move generator and its types crate. There is no engine, network access, runtime installation or system-font requirement. Chess piece silhouettes are original vector paths under this crate’s MIT license.

use document_graphics::{chess::{Collection, Format, RenderOptions, rasterize}, RasterOptions};
use std::sync::{Arc, atomic::AtomicBool};
let cancel = AtomicBool::new(false);
let source: Arc<[u8]> = Arc::from(&b"1.e4 e5 (1...c5 {Sicilian}) 2.Nf3 *"[..]);
let collection = Collection::parse(source, Format::Pgn, &cancel)?;
let position = collection.position(2, &cancel)?;
let image = rasterize(&position, RenderOptions::default(), RasterOptions::scaled(2.), &cancel)?;

SAN moves, castling, en passant, promotions, disambiguation, nested variations, comments, annotation glyphs, tags, results and FEN starting positions are supported. Format::Fen accepts one standard six-field FEN per nonempty line. UTF-8 text falls back to Latin-1 for older PGN annotations. Chess variants are rejected; engine annotations and drawing extensions remain comment text.

Parsing builds a compact source/tree index, with one initial board per game. It does not validate every move eagerly. Collection::position replays the requested line with legal moves; Position::advance handles a direct child. Illegal or ambiguous moves return an error rather than a guessed position. Applications own navigation, comments, cache policy and draw adjudication. RenderOptions::flipped changes perspective; the last move is highlighted.

Limits: 8 MiB source, 100,000 nodes, 1,024 games, 128 variation levels, 16,384 half-moves per line, 128 tags per game, 64 KiB per tag/comment budget per node, 128 annotations per node and 300,000 annotations total. Tokens are limited to 32 bytes. Output retains the existing one-million-pixel raster cap.

This bounded, iterative parser and replay run in-process with cooperative cancellation, followed by restricted SVG rendering. They do not use the Mermaid worker’s OS sandbox or hard deadline. Schedule them off the UI thread, coalesce obsolete requests and preserve readable source on failure.

The read-only corpus probe checks all positions and renders roots/leaves:

cargo run --release --features chess --example chess_probe -- path/to/chess-records

§Image decoding

All image features are disabled by default. They create no background threads, queues or caches until the consumer calls them.

FeatureAPI and behavior
imagesimages::decode accepts PNG/JPEG bytes and returns a fitted, orientation-corrected Raster. Portable Rust codecs; no file access or system fonts.
image-workerAdds images::worker::{Worker, worker_main}, metadata, regions, streaming PNG tiles and broader SVG in a disposable Unix helper. Portable raster codecs provide still images.
image-nativeIncludes image-worker; on macOS enables ImageIO/Core Graphics, color profiles, EXIF orientation, native codecs and GIF/WebP animation. On other Unix systems it uses the portable worker.

For small PNG/JPEG thumbnails:

use document_graphics::images::{decode, Format, Limits};
use std::sync::atomic::AtomicBool;
let bytes = std::fs::read("image.png")?; // The application bounds file access.
let raster = decode(&bytes, Format::Png, (800, 600), Limits::default(), &AtomicBool::new(false));

The default limits are 8 MiB encoded, 8,192 pixels per source edge, four million source pixels, 32 MiB decoder allocation and one million output pixels. Callers may lower or raise them within the documented hard ceilings. Decoding runs in-process; cancellation is checked around codec calls, not inside them. These allocation budgets are not a process RSS ceiling. Run decoding off UI threads.

For isolated decoding, route an application-private argument to images::worker::worker_main() before UI initialization. Off-thread, call Worker::spawn(executable, argument, &stable_file), then info(cancel) and render(regions, cancel). The caller supplies a stable regular file (typically an immutable snapshot); the library duplicates its descriptor, passes it to the child, and never resolves a document path. Info::overview(frame) requests a bounded thumbnail. Regions use physical output pixels and fixed-point scale (SCALE_UNIT means 1:1). RGBA buffers have straight alpha. Metadata includes dimensions, format, color profile/model, frame count and total plays; rendered frames include a bounded delay. Animation scheduling remains app-owned.

The worker accepts 256 MiB files, 100,000 pixels per source edge, two billion source pixels, and 10,000 frames. SVG source is limited to 32 MiB. Each request allows 16 regions, at most 1,600 pixels per edge and 4,194,304 total output pixels. Streaming PNG handles Adam7 without allocating the full decoded image. Other portable codecs use a 16-megapixel limit and 128 MiB decoder allocation budget; they provide still images without the native orientation/color pipeline. The portable worker detects PNG, JPEG, GIF, WebP, BMP, ICO, TIFF, QOI and PNM from file contents; TGA recognition requires the native codec. AVIF, HEIC and other native formats depend on installed macOS codecs. There are no codec downloads, external tools or network requests.

Each request has a ten-second deadline. Cancellation, protocol errors, decoding errors and deadlines stop and reap the child before returning; replace a failed handle. Dropping a handle also reaps it. Process creation is outside the request deadline. Children clear inherited environment variables, disable core dumps and use a twelve-second per-operation alarm. macOS samples worker RSS against 256 MiB and fails closed if its sandbox cannot be installed. That sandbox denies networking, file writes and child execution; codec/font reads remain allowed. Other Unix systems use a 512 MiB address-space limit, without an equivalent OS sandbox. Windows supports images::decode, but not this worker yet.

The two SVG APIs deliberately retain different policies:

  • svg::rasterize is the strict, in-process document subset with bundled fonts, small source/complexity limits, and no filters, reuse or external content.
  • The image worker accepts broader resvg SVG, including local <use> and system text fonts. It disables both external and data-image references. This decoder is private and can only be reached through the worker API, with its resource guards and platform restrictions.

Applications retain source/path policy, snapshot storage, queues, cache budgets, zoom, fitting, playback and idle retirement. Enabling a feature does not replace those policies or silently broaden the strict SVG entry point.

§Mermaid worker lifecycle

Applications choose cache sizes, scheduling, idle retirement and total invocation budgets. Each worker handles at most worker::MAX_REQUESTS (32) requests; check requests_completed() and replace the worker when needed. Rejected syntax does not break the worker. An I/O error, cancellation or deadline stops and reaps it before returning; an errored handle must be replaced. Dropping a healthy handle also stops and reaps its child. Process creation itself is outside the per-render deadline.

The child receives source, theme and raster options over bounded private pipes; its inherited environment is cleared. Unix workers disable core dumps and have an eight-second cumulative CPU backstop. On macOS the worker’s sandbox profile allows everything by default, then denies file-data reads, writes, networking and child processes; other operations, such as Mach service lookups, remain allowed. The parent samples resident memory against 256 MiB. Other platforms retain input/output bounds and deadlines, but do not yet have equivalent OS sandbox and resident-memory enforcement.

The library owns these mechanisms. A terminal app can retain a five-second total budget; a desktop app can reuse a worker through a bounded queue and retire it on idle. Neither policy is imposed on the other application.

The Mermaid and image clients share private cancellation/deadline guards, 20 ms macOS memory-sampling checks, and child termination/reaping. Their wire protocols, I/O loops, error reporting and sandbox profiles remain separate. Request limits, timeouts and application queue/cache policies are unchanged. Mermaid retains best-effort memory sampling; image requests fail if their worker cannot be monitored. A completed reply still has to pass its cancellation and deadline checks before the application receives it.

§Development

This is a standalone library repository, shared by mdviewer and Plastext. The Mermaid backend comes from the published document-graphics-mermaid crate, maintained in a separate fork. It provides caller-supplied font metrics, complete ER key badges, chart-label contrast fixes and bounded routing policies for large flowcharts. Its default features stay disabled: this library owns SVG rasterization and does not need the backend’s CLI or PNG renderer. All dependencies come from crates.io; no sibling checkout is required.

cargo fmt --all --check
cargo clippy --all-targets --all-features --no-deps --locked -- -D warnings
cargo test --all-features --locked
cargo +1.90.0 check --all-targets --all-features --locked

Shared renderer, SVG and protocol tests belong here. Application integration and policy tests stay with the consumers. On macOS, worker tests must run outside an enclosing sandbox so the child can install its own sandbox.

See release checks for testing changes in the consumers, packaging and consumer validation.

MIT; see LICENSE. katex-native includes KaTeX fonts under OFL-1.1. Noto Sans assets and their license/provenance are in fonts.

Modules§

chess
Bounded PGN collections and standard-chess FEN positions. Parsing, legal move replay and rendering perform no file or network access.
images
Bounded portable image decoding, with optional isolated region/native decoding. Byte decoding does not access files, fonts, networking or OS codec services.
mermaid
Bounded native Mermaid subset. Parsing and layout belong in the worker process.
sgf
Bounded SGF collections and Go positions. No file access or application cache.
svg
Restricted SVG rendering with bundled fonts and no external resources.
worker
Isolated rendering, shared wire protocol and process supervision.

Structs§

Raster
Straight RGBA pixels and unscaled SVG dimensions. Moving this value transfers its pixel buffer; callers need not copy it into an intermediate image format.
RasterOptions
Raster fitting is independent of logical layout. Bounds are physical pixels; scale is the requested physical pixels per logical SVG unit (1x or Retina). All output remains subject to the crate’s pixel, edge and source-size caps.

Constants§

MAX_EDGE
MAX_PIXELS