RPIC

Bindings

The same engine everywhere: the Rust core compiles to native libraries and WASM, so every binding produces identical output.

Language Package Install
Python rpiclang pip install rpiclang
JavaScript / WASM @strategicprojects/rpic npm i @strategicprojects/rpic
R rpic remotes::install_github("milkway/rpic-r")
C rpic-capi link librpic

Python

rpiclang ships prebuilt wheels (macOS arm64, Linux, Windows) with the full engine โ€” SVG, PNG, PDF and the math renderer included:

import rpic

svg = rpic.render_svg('box "hello" fit')
png = rpic.render_png('circle "grid" hatch', scale=2)          # bytes
pdf = rpic.render_pdf('A:(0,0); B:(2,0)\nresistor(A,B)', circuits=True)

# TeX math labels, exactly like `rpic -t`
svg = rpic.render_svg('box "$e^{j\\pi}+1=0$" fit', texlabels=True)

# the parsed bundle: svg + animation manifest + diagnostics + warnings
bundle = rpic.compile('box\nanimate last box with "pop"')

# `copy "file"` includes resolve relative to `base`
svg = rpic.render_svg('copy "shim.pic"\nbox', base="figs/")

# compiling untrusted source? fence or disable filesystem includes
svg = rpic.render_svg(src, base="jobs/42", include_policy="sandboxed")  # or "deny"

With include_policy="sandboxed", only files inside base resolve โ€” absolute paths and ../symlink escapes are structured errors (kind: "include_denied"); "deny" turns off filesystem includes entirely. The embedded copy "circuits" library always works. The default ("unrestricted") is the CLI behavior, right for local trusted use.

Compile errors raise rpic.CompileError (a ValueError subclass) carrying the same structured diagnostic the JS binding exposes โ€” exc.info is a dict with line/col/end_col/file/kind/found/expected/hint, positions always relative to your own source:

try:
    rpic.render_svg("bxo", circuits=True)
except rpic.CompileError as exc:
    exc.info["line"]  # 1 โ€” your line 1, even with the circuit library loaded
    exc.info["hint"]  # "did you mean `box`?"

JavaScript / WASM

The npm package runs the engine as WebAssembly โ€” browser or Node โ€” and ships the GSAP animation player used by the animate extension:

import { ready, compile, renderSvg, animate } from '@strategicprojects/rpic';

await ready();                          // fetch + init the .wasm once
const { svg, animations } = compile(src, { circuits: true, texlabels: true });
stage.innerHTML = svg;
animate(stage, animations, gsap);       // GSAP timeline on the stable s<N> ids

The default WASM build ships without the math renderer (size budget) โ€” with texlabels, $โ€ฆ$ labels fall back to literal text plus a diagnostic. Since v0.6.0 the package also ships a math-enabled build as a second, lazily fetched artifact; opt in at init and math typesets exactly like the CLI:

await ready(undefined, { math: true });  // fetches pkg/rpic_wasm_math_bg.wasm
renderSvg('box "$-\\frac{T}{2}$" fit', { texlabels: true });

Escaping note (all bindings): TeX commands take a single backslash in the pic source โ€” $\frac{T}{2}$. In a JS/Python/R string literal that is written "$\\frac{T}{2}$". A doubled backslash in the pic source (\\) is a TeX line break, so "$\\\\frac{โ€ฆ}$" typesets a broken multi-line label.

Errors and warnings for editors

Compile errors throw with a readable message plus structured fields, and the bundle carries non-fatal warnings (unknown attribute words, unknown animate effects) โ€” positions are always relative to your source, even with circuits: true; file names a copy include when the problem is inside one (null = your own input):

try {
  compile('bxo', { circuits: true });
} catch (err) {
  err.errorInfo; // { message, line: 1, col: 1, end_col: 4, file: null,
                 //   kind: "expected_token", found: "`bxo`",
                 //   expected: "an object", hint: "did you mean `box`?" }
}
compile('box "a" dashd').warnings; // [{ kind: "ignored_attribute", hint: "did you mean `dashed`?", โ€ฆ }]

R

The rpic R package wraps the same core via extendr โ€” aligned with the engine at 0.6.x, with its own pkgdown site and vignettes:

library(rpic)
svg <- rpic_svg('box "hello" fit')
rpic_png('circle rad 0.4 gradient "gold" "white"', "out.png", scale = 2)
rpic_svg('A:(0,0); B:(2,0)\nresistor(A,B)', circuits = TRUE)

# TeX math labels, exactly like `rpic -t`
rpic_svg('box "$-\\frac{T}{2}$" fit', texlabels = TRUE)

# knitr / Quarto engine for ```{rpic} chunks
rpic_register_knitr()

Compile errors are classed rpic_error conditions carrying the same structured diagnostic as the other bindings (NA = absent field):

tryCatch(
  rpic_svg("bxo", circuits = TRUE),
  rpic_error = function(e) list(line = e$info$line, hint = e$info$hint)
)
#> $line 1    $hint "did you mean `box`?"

C

rpic-capi exposes a minimal ABI for embedding anywhere C reaches. String results from rpic_render_svg and rpic_compile_json are released with rpic_free_string; byte buffers from rpic_render_png and rpic_render_pdf are released with rpic_free_bytes. Pass circuits as 0 or 1:

#include <stdio.h>
#include "rpic.h"

int main(void) {
    char *svg = rpic_render_svg(".PS\nbox \"embedded\"\n.PE", 0);
    if (svg == NULL) {
        return 1;
    }

    puts(svg);
    rpic_free_string(svg);

    size_t png_len = 0;
    unsigned char *png = rpic_render_png("circle \"bytes\"", 2.0, 0, &png_len);
    if (png != NULL) {
        printf("png bytes: %zu\n", png_len);
        rpic_free_bytes(png, png_len);
    }

    return 0;
}

CLI as an FFI of last resort

Everything the bindings do is also one process away: rpic --json file.pic emits {svg, animations, diagnostics, warnings, objects} on success and {error, error_info} on failure โ€” error_info is the same structured diagnostic the bindings surface (message, line, col, end_col, file, kind, found, expected, hint). This documentation site drives its live examples exactly that way.