rpic_core/ir.rs
1//! Intermediate representation: the placed-primitive tree produced by the
2//! evaluator and consumed by the render backends. All coordinates are absolute,
3//! in pic units (inches), y pointing up.
4
5use crate::diagnostic::Diagnostic;
6use crate::geom::{Bbox, Point};
7
8/// A fully evaluated drawing.
9#[derive(Debug, Clone, PartialEq)]
10pub struct Drawing {
11 pub shapes: Vec<Shape>,
12 /// Render layer per shape. Lower layers are emitted first; equal layers keep
13 /// source order and shape ids stable.
14 pub shape_layers: Vec<i32>,
15 /// rpic extension: CSS class hook per shape, emitted on the shape's
16 /// `<g id="sN">` group. Inert unless the host document styles it; `None`
17 /// keeps the group byte-identical to classic output.
18 pub shape_classes: Vec<Option<String>>,
19 pub bbox: Bbox,
20 /// Global `linethick` in points after picture-wide sizing, used only for
21 /// dpic-style backend prelude padding. Per-shape strokes keep their own
22 /// unscaled point thickness.
23 pub prelude_thick: f64,
24 /// Extra canvas whitespace in inches. This is an rpic extension inspired by
25 /// Pikchr: it affects native backend framing only, not pic geometry.
26 pub canvas_margin: CanvasMargin,
27 pub anims: Vec<Anim>,
28 /// Lines emitted by pic `print` statements, without trailing newlines.
29 pub diagnostics: Vec<String>,
30 /// Non-fatal compiler warnings for accepted but likely unintended input.
31 pub warnings: Vec<Diagnostic>,
32}
33
34/// Extra whitespace around the rendered canvas, in internal inches.
35#[derive(Debug, Clone, Copy, PartialEq, Default)]
36pub struct CanvasMargin {
37 pub top: f64,
38 pub right: f64,
39 pub bottom: f64,
40 pub left: f64,
41}
42
43impl CanvasMargin {
44 pub fn horizontal(self) -> f64 {
45 self.left + self.right
46 }
47
48 pub fn vertical(self) -> f64 {
49 self.top + self.bottom
50 }
51
52 pub fn scale_by(&mut self, factor: f64) {
53 self.top *= factor;
54 self.right *= factor;
55 self.bottom *= factor;
56 self.left *= factor;
57 }
58}
59
60/// A resolved animation entry. `shape` indexes into [`Drawing::shapes`]; the
61/// SVG backend gives that shape the id `s{shape}` so the player can target it.
62#[derive(Debug, Clone, PartialEq)]
63pub struct Anim {
64 pub shape: usize,
65 pub effect: String,
66 /// Absolute start time in seconds.
67 pub start: f64,
68 pub duration: f64,
69}
70
71/// Line dash style.
72#[derive(Debug, Clone, Copy, PartialEq, Default)]
73pub enum Dash {
74 #[default]
75 Solid,
76 /// Dash/gap base length in inches.
77 Dashed(f64),
78 /// Explicit dot spacing in inches; `None` means dpic's stroke-relative default.
79 Dotted(Option<f64>),
80}
81
82/// Fill specification.
83#[derive(Debug, Clone, PartialEq)]
84pub enum Fill {
85 /// Gray level, pic convention: 0 = black, 1 = white.
86 Gray(f64),
87 /// A named/explicit color.
88 Color(String),
89}
90
91/// Hatch fill pattern.
92#[derive(Debug, Clone, PartialEq)]
93pub struct Hatch {
94 pub cross: bool,
95 /// Line angle in degrees, measured in pic coordinates.
96 pub angle: f64,
97 /// Distance between hatch lines in inches.
98 pub sep: f64,
99 /// Hatch stroke width in points.
100 pub width: f64,
101 pub color: String,
102}
103
104/// Two-stop linear gradient fill (rpic extension, PSTricks-inspired).
105#[derive(Debug, Clone, PartialEq)]
106pub struct Gradient {
107 pub from: String,
108 pub to: String,
109 /// Angle in degrees, measured in pic coordinates: 0 = left to right,
110 /// 90 = bottom to top (matching how `hatchangle` measures).
111 pub angle: f64,
112}
113
114/// Visual style shared by all shapes.
115#[derive(Debug, Clone, PartialEq)]
116pub struct Style {
117 /// Stroke color (CSS), or `None` to use the default.
118 pub stroke: Option<String>,
119 pub fill: Option<Fill>,
120 pub hatch: Option<Hatch>,
121 pub gradient: Option<Gradient>,
122 /// Fill opacity, applied only to filled or hatched regions.
123 pub fill_opacity: Option<f64>,
124 /// Whether open paths/splines/arcs should emit a filled area. `color` on an
125 /// open object only changes the stroke; `fill` and `shaded` fill.
126 pub fill_open: bool,
127 pub dash: Dash,
128 /// Stroke thickness in points; `None` = backend default.
129 pub thick: Option<f64>,
130 /// Invisible (used by `move` and `invis`): geometry is still available for
131 /// placement and anchors, but is not drawn.
132 pub invis: bool,
133 /// Invisible geometry that still contributes to output bounds. Dpic uses
134 /// this for `move`; explicit `invis` helpers remain bounds-neutral.
135 pub invis_bounds: bool,
136 /// Arrowhead dimensions in inches (`arrowht`/`arrowwid`), used when this
137 /// shape carries arrowheads.
138 pub arrow_ht: f64,
139 pub arrow_wid: f64,
140 /// Filled (solid triangle, `arrowhead=2`) vs open (two strokes,
141 /// `arrowhead=0`).
142 pub arrow_filled: bool,
143}
144
145impl Default for Style {
146 fn default() -> Self {
147 Style {
148 stroke: None,
149 fill: None,
150 hatch: None,
151 gradient: None,
152 fill_opacity: None,
153 fill_open: false,
154 dash: Dash::Solid,
155 thick: None,
156 invis: false,
157 invis_bounds: false,
158 arrow_ht: 0.1,
159 arrow_wid: 0.05,
160 arrow_filled: true,
161 }
162 }
163}
164
165/// Which ends of a path carry an arrowhead.
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
167pub enum Arrowheads {
168 #[default]
169 None,
170 Start,
171 End,
172 Both,
173}
174
175/// A line of attached text with placement hints.
176#[derive(Debug, Clone, PartialEq)]
177pub struct TextLine {
178 pub s: String,
179 /// rpic `texlabels` extension: when set, this line is a typeset math
180 /// formula and `s` keeps the original literal for fallback/diagnostics.
181 pub math: Option<crate::math::MathSpan>,
182 /// horizontal: -1 = ljust, 0 = center, +1 = rjust.
183 pub halign: i8,
184 /// vertical: +1 = above, 0 = center, -1 = below.
185 pub valign: i8,
186 /// Extra text-position offset in inches (`textoffset`).
187 pub text_offset: f64,
188}
189
190/// A placed drawing primitive.
191#[derive(Debug, Clone, PartialEq)]
192pub enum Shape {
193 Box {
194 c: Point,
195 w: f64,
196 h: f64,
197 rad: f64,
198 style: Style,
199 text: Vec<TextLine>,
200 },
201 Circle {
202 c: Point,
203 r: f64,
204 style: Style,
205 text: Vec<TextLine>,
206 },
207 Ellipse {
208 c: Point,
209 w: f64,
210 h: f64,
211 style: Style,
212 text: Vec<TextLine>,
213 },
214 /// Straight polyline (line / arrow / move).
215 Path {
216 pts: Vec<Point>,
217 /// rpic extension: this path was closed with the `close` attribute.
218 closed: bool,
219 arrows: Arrowheads,
220 style: Style,
221 text: Vec<TextLine>,
222 },
223 Spline {
224 pts: Vec<Point>,
225 /// dpic spline tension. `None` = the classic pic quadratic B-spline
226 /// (straight first/last half-segments, tangent at segment midpoints);
227 /// `Some(t)` = dpic's tensioned cubic spline through `t`.
228 tension: Option<f64>,
229 arrows: Arrowheads,
230 style: Style,
231 text: Vec<TextLine>,
232 },
233 Arc {
234 c: Point,
235 r: f64,
236 /// start and end angles in radians.
237 a0: f64,
238 a1: f64,
239 cw: bool,
240 arrows: Arrowheads,
241 style: Style,
242 text: Vec<TextLine>,
243 },
244 Brace {
245 a: Point,
246 b: Point,
247 cubics: Vec<[Point; 4]>,
248 label_at: Point,
249 style: Style,
250 text: Vec<TextLine>,
251 },
252 Text {
253 at: Point,
254 text: Vec<TextLine>,
255 bbox: Bbox,
256 w: f64,
257 h: f64,
258 standalone: bool,
259 },
260}