shapdf/
lib.rs

1//! # `shapdf` = `shape` + `pdf`
2//! Create Shapes into PDF
3//!
4//! - **🌐 Try it online:** [shapdf.wqzhao.org](https://shapdf.wqzhao.org)
5//! - **📚 Documentation:** [docs.rs/shapdf](https://docs.rs/shapdf)
6//! - **💻 Source:** [github.com/Teddy-van-Jerry/shapdf](https://github.com/Teddy-van-Jerry/shapdf)
7//!
8//! ## Motivation
9//! - Efficient programmable generation of shapes in PDF (rather than slow compilation of LaTeX [Ti*k*Z](https://tikz.dev/) or Typst [CeTZ](https://cetz-package.github.io/));
10//! - Minimal dependencies in Rust, relying mostly on **PDF primitives**;
11//! - A lightweight solution for machine generation of simple graphics.
12//!
13//! ## Capabilities
14//! - [x] Shapes
15//!   - [x] Line
16//!   - [x] Circle (filled)
17//!   - [x] Rectangle (filled)
18//!   - [ ] Polygon
19//! - [ ] Text
20//! - [x] Color
21//! - [ ] Opacity
22//! - [x] Rotation & Anchor
23//! - [x] PDF Stream Compression (feature `compress`)
24//! - [x] CLI for declarative scripts
25//! - [x] WebAssembly
26//! - [x] Python Bindings
27//!
28//! More features are coming soon!
29//!
30//! ## Example
31//! The usage of this library is quite simple:
32//! ```rust
33//! use shapdf::*;
34//! use std::error::Error;
35//!
36//! fn main() -> Result<(), Box<dyn Error>> {
37//!     let mut generator = Generator::new("output/shapes.pdf".into());
38//!     generator.add_page(); // use the default page size (US letter)
39//!     generator
40//!         .circle(Mm(20.), Mm(20.), Mm(10.))
41//!         .with_color(NamedColor("blue"))
42//!         .draw();
43//!     generator
44//!         .line(Pt(500.), Pt(600.), Pt(300.), Pt(400.))
45//!         .with_width(Mm(10.))
46//!         .with_cap_type(CapType::Round)
47//!         .with_color(NamedColor("red"))
48//!         .draw();
49//!     generator.add_page_letter();
50//!     generator
51//!         .rectangle(Mm(80.), Mm(180.), Mm(50.), Mm(30.))
52//!         .with_anchor(Anchor::Center)
53//!         .with_angle(Degree(30.))
54//!         .draw();
55//!     generator
56//!         .circle(Mm(80.), Mm(180.), Mm(1.))
57//!         .with_color(NamedColor("green"))
58//!         .draw();
59//!     generator.add_page_a4();
60//!     generator.write_pdf()?;
61//!     println!("PDF generated successfully!");
62//!     Ok(())
63//! }
64//! ```
65//! More examples are available in the [`examples`](examples) directory.
66//!
67//! ## CLI Usage
68//! The binary reads declarative `.shapdf` scripts and renders them to PDF.
69//!
70//! Install via `cargo install shapdf` (use `cargo install --path .` when working from a local checkout).
71//!
72//! - `shapdf <script.shapdf>` renders the file to `<script>.pdf` in place.
73//! - `shapdf --output output/shape.pdf -` reads the script from `stdin` (e.g. piped from another program).
74//! - Sample scripts and helpers live in [`examples/cli/`](examples/cli/):
75//!   - [`sample_shapes.shapdf`](examples/cli/sample_shapes.shapdf): demonstrates multiple pages, colors, anchors, and rotations.
76//!   - [`generate_sample.sh`](examples/cli/generate_sample.sh): runs the CLI against the script file and writes `sample_shapes.pdf` beside it.
77//!   - [`generate_stdin.sh`](examples/cli/generate_stdin.sh): inlines the same script, pipes it over `stdin`, and produces `sample_shapes_inline.pdf`.
78//!
79//! Library consumers can also call `shapdf::render_script_to_pdf(script, output_path)` to execute a script string directly.
80//!
81//! ### `.shapdf` Script Syntax
82//! - Lines are `command [args] [key=value ...]`; blank lines or those starting with `#`/`//` are ignored.
83//! - Supported commands:
84//!   - `page default|letter|letter-landscape|a4|a4-landscape`
85//!   - `page size <width> <height>` (accepts `mm`, `cm`, `in`, `pt`)
86//!   - `set default_page_size <width> <height>`
87//!   - `set default_width <length>`
88//!   - `set default_color <color>` (named colors, `#RRGGBB`, `rgb(r,g,b)`, or `gray(v)`)
89//!   - `set default_cap butt|round|square`
90//!   - `set default_angle <value>` (`deg` default, or `rad`)
91//!   - `line <x1> <y1> <x2> <y2> [width=...] [color=...] [cap=...]`
92//!   - `circle <x> <y> <radius> [color=...]`
93//! - `rectangle <x> <y> <width> <height> [color=...] [anchor=...] [angle=...]`
94//! - The first drawing command automatically inserts a default page if none was added.
95//!
96//! ### WebAssembly & Web Editor
97//!
98//! **Try the online editor:** [shapdf.wqzhao.org](https://shapdf.wqzhao.org)
99//!
100//! **Using WASM in your own project:**
101//! - Build the library with `--features wasm` to enable `shapdf::render_script_to_bytes(script)` for in-memory rendering.
102//! - The returned `Vec<u8>` contains the PDF bytes, ready to serve or download in a web context.
103//! - See [`examples/shapdf.wqzhao.org`](examples/shapdf.wqzhao.org) for the full React/TypeScript web editor implementation.
104//!
105//! ## Python Bindings
106//!
107//! Python bindings are available via the `pyshapdf` package:
108//!
109//! ```sh
110//! pip install pyshapdf
111//! ```
112//!
113//! ```python
114//! import pyshapdf
115//!
116//! script = """
117//! page letter
118//! circle 100mm 150mm 20mm color=blue
119//! rectangle 50mm 50mm 40mm 30mm color=green angle=45deg anchor=center
120//! """
121//!
122//! pyshapdf.render_script(script, "output.pdf")
123//! ```
124//!
125//! See [`python/README.md`](python/README.md) for full documentation, examples, and API reference.
126//!
127//! ## Implementation Facts
128//! - Filled circle is actually implemented using [a zero-length line with the rounded line cap](https://stackoverflow.com/a/46897816/15080514).
129//!
130//! ## License
131//! This project is distributed under the [GPL-3.0 License](LICENSE).
132//!
133//! © 2025 [Teddy van Jerry](https://github.com/Teddy-van-Jerry) ([Wuqiong Zhao](https://wqzhao.org))
134
135mod generator;
136mod script;
137mod shapes;
138mod units;
139
140#[cfg(feature = "wasm")]
141use wasm_bindgen::prelude::*;
142
143pub use generator::*;
144#[cfg(feature = "wasm")]
145pub use script::render_script_to_bytes;
146#[cfg(not(feature = "wasm"))]
147pub use script::render_script_to_pdf;
148pub use script::{
149    execute_instructions, parse_script, ExecutionError, Instruction, InstructionKind, ParseError,
150};
151
152#[cfg(feature = "wasm")]
153#[wasm_bindgen]
154pub fn render_script(script: &str) -> Result<Vec<u8>, JsValue> {
155    render_script_to_bytes(script).map_err(|err| JsValue::from_str(&err.to_string()))
156}