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.