A modern reimplementation of the pic picture-drawing language in Rust —
SVG / PNG / PDF output, GSAP animation, and a native circuit-element library.
Even the logo is a pic program: assets/logo.pic, rendered by rpic.
📖 Documentation: rpic.dev — language tour, every extension with live examples, spec and the pic-family history.
The figure above is rendered by
rpicfrom this source — the very example Brian Kernighan used to introduce pic in 1982:ellipse "document"; arrow; box "PIC"; arrow box "TBL/EQN" "(optional)" dashed; arrow box "TROFF"; arrow; ellipse "typesetter"
rpic keeps Kernighan's original pic paradigm — you describe a drawing by
"walking around a plane dropping primitives", with relative positioning,
default dimensions, compass corners, ordinals, blocks and macros — and brings it
to the modern web era:
- SVG / PNG / PDF, all pure-Rust — no troff, no LaTeX, no ImageMagick, no system libraries.
- Fast: single-digit-millisecond cold renders, flat with diagram size —
~140× faster than mermaid-cli in a docs pipeline
(benchmark, reproducible via
tools/bench). - A declarative animation layer (
animate …) that plays in the browser with GSAP — enter/exit effects (fade, pop, draw, slide), motion along a path, colour highlights, shape morphs, block staggering and a scroll-scrub hint, all emitted as a plain JSON timeline. - A native circuit-element library (79 elements) — a from-scratch
re-imagining of
circuit_macros. - Real typography: per-string
bold/italic/mono, any font family and size,rotatedlabels, and nativergb()/#hexcolours — all feeding the layout sofitand bounding boxes stay correct. - Built for editors: structured diagnostics with exact spans and
did-you-mean hints, per-object geometry in the
--jsonoutput (objects: kind, bbox, source span), and a fixed-canvas mode for a stable viewBox — a base visual editors can build on without DOM heuristics. - Safe on untrusted input: the parser and evaluator are hardened against
malformed or adversarial source — bounded recursion and expression depth,
configurable loop/shape budgets, and XML-escaped output — so an app can
compile pasted or shared
.picwithout crashing, hanging, or emitting unsafe SVG. Every binding, down to the C ABI, turns a fault into an error rather than aborting the host. - One core, many targets: a native CLI, WebAssembly, and bindings for Python, R, JavaScript/TypeScript, and C.
The compatibility rule is deliberately Kernighan-first: the 1982 paper and
manual set the language philosophy and the meaning of classic pic constructs.
When the texts leave room for interpretation, dpic is the practical oracle:
we compare against dpic -v, keep its well-tested geometry and macro behavior
in view, and document any intentional rpic extension separately.
.PS
boxht = 0.3; boxwid = 0.6
A: box "input"
arrow
B: box "process" fill 0.9
arrow
ellipse "output"
arc -> from A.n to last ellipse.n
.PEPrimitives: box circle ellipse arc line arrow move spline + text. Positioning:
named labels, compass corners (.n .ne .center …), ordinals (last,
2nd last box), with … at, fractions (1/3 between A and B), blocks [ … ].
Programmability: define macros with $1…$9, for, if, sprintf,
environment variables.
Explicit rpic extensions — margin, canvas, fit, behind, close, brace,
hatch, gradient, opacity, class hooks, dot, thin strokes, font
attributes (bold/italic/mono/font/fontsize/big/small), rotated &
aligned labels, rgb()/0xRRGGBB colour literals (also held in variables or
computed), texlabels
(KaTeX-grade TeX math in labels, rendered natively) and the animate layer —
are opt-in and inert for classic pic/dpic-compatible input. Each has a page with live
examples at rpic.dev; the design
notes live in docs/extensions.md.
rpic diagram.pic # SVG to stdout
rpic --png --scale 2 -o out.png diagram.pic
rpic --pdf -o out.pdf diagram.pic
rpic -c circuit.pic # load the circuit-element library
rpic -t paper.pic # typeset $…$ labels as TeX math
rpic --json diagram.pic # {svg, animations, diagnostics, warnings, objects}
rpic --html -o page.html animated.pic # self-contained page: SVG + GSAP player inline--html writes a single page that plays the drawing's
animate timeline on open: the
SVG, the manifest and the player are inlined, and GSAP (plus only the
plugins the used effects need) is pulled from a pinned, integrity-checked CDN
tag. A drawing with no animations becomes a plain static page with no
scripts at all.
A gallery of diagrams from the dpic distribution — including reproductions of
Brian Kernighan's original pic-manual figures — rendered by rpic itself lives
in examples/dpic/. Each .pic is paired with its rendered
.svg, with full credits and a parity matrix in
examples/dpic/STATUS.md.
rpic --svg examples/dpic/manual/man16.pic -o man16.svgexamples/figuras/ collects circuit_macros figures from
André Leite's personal collection, rendered by rpic.
examples/lib3d/ shows 3D drawings (axonometric projection,
à la circuit_macros' lib3D) rendered to flat SVG.
A declarative extension, faithful to pic's style:
box "A"; arrow; box "B"
animate 1st box with "pop" for 0.4
animate 1st arrow with "draw"
animate 2nd box with "fade" after 1st arrow delay 0.2The full effect palette:
| Effect | What it does |
|---|---|
fade / pop / draw |
opacity / scale-in / stroke-on entrances |
slide |
translate in from a compass direction (from left) |
move |
travel along another object's path (along L) |
highlight |
recolour + pulse the outline (to "crimson") |
morph |
tween the outline into another shape (into B) |
type |
reveal a label a character (or by word) at a time — a typewriter |
scramble |
glyphs cycle through random characters and resolve — a decode reveal |
wiggle |
a quick oscillating shake that returns to rest — "look here" (wiggles 6) |
"Describe the picture." at (0,0); animate last with "type" for 1.4
box "SECRET" fit at (0,-0.6); animate last with "scramble" for 1.2Any effect can play as an exit with out, loop with repeat/yoyo,
take a custom ease, or fan across a block's children with stagger;
animate scroll hints the host to scrub the timeline on scroll. Timing is
sequential by default, or absolute (at) / relative (after), with an optional
delay. Separately, draggable <obj> [inertia] [bounds B] [x|y] makes an
object grabbable in the browser (GSAP Draggable) — interaction rather than a
timeline, so it's its own directive.
This compiles to an SVG plus a flat JSON manifest ({id, effect, start, duration, …}, and interactions for draggable); the browser layer turns it
into a GSAP timeline. Full reference:
rpic.dev/docs/extensions/animate.
Try it in the playground:
./web/build.sh && (cd web && python3 -m http.server 8080) # http://localhost:8080Enable with -c, or in-source with copy "circuits" (the analog of
texlabels = 1 for -t). Two-terminal elements take two named points;
centered devices take one and expose their terminals as variables.
.PS
SW:(0,0); NW:(0,1.4); NE:(2.6,1.4); SE:(2.6,0)
battery(SW,NW); resistor(NW,NE); capacitor(NE,SE); inductor(SE,SW)
.PE79 elements across analog parts, distinctive & IEEE logic gates, BJT/MOSFET/
JFET transistors, op-amps, sources, meters, transducers, grounds and
annotations. See crates/core/src/std/circuits.pic.
# from source (any platform)
cargo install --git https://github.com/milkway/rpic-lang rpic-cli
# Homebrew (macOS / Linux)
brew install milkway/rpic/rpic
# Scoop (Windows)
scoop install https://raw.githubusercontent.com/milkway/rpic-lang/main/packaging/scoop/rpic.json
# Debian/Ubuntu — download the .deb from the Releases page, then:
sudo dpkg -i rpic_*.debPrebuilt binaries for macOS / Linux / Windows are attached to each
release. See
CHANGELOG.md for what changed in each version.
Python — bindings/python
pip install rpiclang # distribution name; the module is `rpic`import rpic
svg = rpic.render_svg('box "hi"; arrow; circle "x"')
open("out.png", "wb").write(rpic.render_png('box "hi"', scale=2.0))
bundle = rpic.compile('box\nanimate last box with "pop"')
# bundle["diagnostics"] = pic `print` output; bundle["warnings"] = structured
# warnings; errors raise rpic.CompileError with the diagnostic on `exc.info`R — milkway/rpic-r (separate repo)
remotes::install_github("milkway/rpic-r")
rpic::rpic_svg('A:(0,0); B:(2,0)\nresistor(A,B)', circuits = TRUE)
rpic::rpic_register_knitr() # ```{rpic} chunks in R Markdown / QuartoJavaScript / TypeScript — bindings/js
import * as rpic from '@strategicprojects/rpic';
await rpic.ready(); // or ready(undefined, { math: true })
const { svg, animations, diagnostics, warnings, objects } = rpic.compile('box "A"; arrow; box "B"');
rpic.animate(stage, animations, gsap); // GSAP timelineThe default wasm is lean; ready(undefined, { math: true }) lazy-loads a
math-enabled build so texlabels typeset $…$ labels in the browser.
Compile errors throw with structured err.errorInfo (span, kind,
did-you-mean hint) for editor integrations.
cargo build --release # CLI in target/release/rpic
cargo test # full test suite| Path | What |
|---|---|
crates/core |
engine: lexer, parser, eval, IR, SVG backend, std/circuits.pic |
crates/render |
PNG/PDF (resvg, svg2pdf) |
crates/cli |
the rpic binary |
crates/capi |
stable C ABI (rpic.h) |
crates/wasm |
WebAssembly bindings |
bindings/{python,js} |
Python & JS/TS bindings (R lives at milkway/rpic-r) |
web/ |
browser playground (GSAP) |
packaging/ |
deb / Homebrew / Scoop config |
If you use rpic in academic work, please cite it via its
Zenodo record. The concept DOI
10.5281/zenodo.21209915 always
resolves to the latest release; each version also has its own DOI. GitHub's
“Cite this repository” reads CITATION.cff for BibTeX/APA.
@software{leite_rpic,
author = {Leite, André},
title = {{rpic: the pic picture-drawing language, reimplemented in Rust}},
publisher = {Zenodo},
doi = {10.5281/zenodo.21209915},
url = {https://rpic.dev}
}rpic stands on the shoulders of giants — see ACKNOWLEDGMENTS.md:
Brian W. Kernighan (pic), Dwight Aplevich
(dpic,
circuit_macros), and
D. Richard Hipp (pikchr).