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::geom::{Bbox, Point};
6
7/// A fully evaluated drawing.
8#[derive(Debug, Clone, PartialEq)]
9pub struct Drawing {
10 pub shapes: Vec<Shape>,
11 pub bbox: Bbox,
12 /// Global `linethick` in points after picture-wide sizing, used only for
13 /// dpic-style backend prelude padding. Per-shape strokes keep their own
14 /// unscaled point thickness.
15 pub prelude_thick: f64,
16 /// Extra canvas whitespace in inches. This is an rpic extension inspired by
17 /// Pikchr: it affects native backend framing only, not pic geometry.
18 pub canvas_margin: CanvasMargin,
19 pub anims: Vec<Anim>,
20 /// Lines emitted by pic `print` statements, without trailing newlines.
21 pub diagnostics: Vec<String>,
22}
23
24/// Extra whitespace around the rendered canvas, in internal inches.
25#[derive(Debug, Clone, Copy, PartialEq, Default)]
26pub struct CanvasMargin {
27 pub top: f64,
28 pub right: f64,
29 pub bottom: f64,
30 pub left: f64,
31}
32
33impl CanvasMargin {
34 pub fn horizontal(self) -> f64 {
35 self.left + self.right
36 }
37
38 pub fn vertical(self) -> f64 {
39 self.top + self.bottom
40 }
41
42 pub fn scale_by(&mut self, factor: f64) {
43 self.top *= factor;
44 self.right *= factor;
45 self.bottom *= factor;
46 self.left *= factor;
47 }
48}
49
50/// A resolved animation entry. `shape` indexes into [`Drawing::shapes`]; the
51/// SVG backend gives that shape the id `s{shape}` so the player can target it.
52#[derive(Debug, Clone, PartialEq)]
53pub struct Anim {
54 pub shape: usize,
55 pub effect: String,
56 /// Absolute start time in seconds.
57 pub start: f64,
58 pub duration: f64,
59}
60
61/// Line dash style.
62#[derive(Debug, Clone, Copy, PartialEq, Default)]
63pub enum Dash {
64 #[default]
65 Solid,
66 /// Dash/gap base length in inches.
67 Dashed(f64),
68 /// Explicit dot spacing in inches; `None` means dpic's stroke-relative default.
69 Dotted(Option<f64>),
70}
71
72/// Fill specification.
73#[derive(Debug, Clone, PartialEq)]
74pub enum Fill {
75 /// Gray level, pic convention: 0 = black, 1 = white.
76 Gray(f64),
77 /// A named/explicit color.
78 Color(String),
79}
80
81/// Visual style shared by all shapes.
82#[derive(Debug, Clone, PartialEq)]
83pub struct Style {
84 /// Stroke color (CSS), or `None` to use the default.
85 pub stroke: Option<String>,
86 pub fill: Option<Fill>,
87 /// Whether open paths/splines/arcs should emit a filled area. `color` on an
88 /// open object only changes the stroke; `fill` and `shaded` fill.
89 pub fill_open: bool,
90 pub dash: Dash,
91 /// Stroke thickness in points; `None` = backend default.
92 pub thick: Option<f64>,
93 /// Invisible (used by `move` and `invis`): geometry is still available for
94 /// placement and anchors, but is not drawn.
95 pub invis: bool,
96 /// Invisible geometry that still contributes to output bounds. Dpic uses
97 /// this for `move`; explicit `invis` helpers remain bounds-neutral.
98 pub invis_bounds: bool,
99 /// Arrowhead dimensions in inches (`arrowht`/`arrowwid`), used when this
100 /// shape carries arrowheads.
101 pub arrow_ht: f64,
102 pub arrow_wid: f64,
103 /// Filled (solid triangle, `arrowhead=2`) vs open (two strokes,
104 /// `arrowhead=0`).
105 pub arrow_filled: bool,
106}
107
108impl Default for Style {
109 fn default() -> Self {
110 Style {
111 stroke: None,
112 fill: None,
113 fill_open: false,
114 dash: Dash::Solid,
115 thick: None,
116 invis: false,
117 invis_bounds: false,
118 arrow_ht: 0.1,
119 arrow_wid: 0.05,
120 arrow_filled: true,
121 }
122 }
123}
124
125/// Which ends of a path carry an arrowhead.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
127pub enum Arrowheads {
128 #[default]
129 None,
130 Start,
131 End,
132 Both,
133}
134
135/// A line of attached text with placement hints.
136#[derive(Debug, Clone, PartialEq)]
137pub struct TextLine {
138 pub s: String,
139 /// horizontal: -1 = ljust, 0 = center, +1 = rjust.
140 pub halign: i8,
141 /// vertical: +1 = above, 0 = center, -1 = below.
142 pub valign: i8,
143 /// Extra text-position offset in inches (`textoffset`).
144 pub text_offset: f64,
145}
146
147/// A placed drawing primitive.
148#[derive(Debug, Clone, PartialEq)]
149pub enum Shape {
150 Box {
151 c: Point,
152 w: f64,
153 h: f64,
154 rad: f64,
155 style: Style,
156 text: Vec<TextLine>,
157 },
158 Circle {
159 c: Point,
160 r: f64,
161 style: Style,
162 text: Vec<TextLine>,
163 },
164 Ellipse {
165 c: Point,
166 w: f64,
167 h: f64,
168 style: Style,
169 text: Vec<TextLine>,
170 },
171 /// Straight polyline (line / arrow / move).
172 Path {
173 pts: Vec<Point>,
174 arrows: Arrowheads,
175 style: Style,
176 text: Vec<TextLine>,
177 },
178 Spline {
179 pts: Vec<Point>,
180 /// dpic spline tension. `None` = the classic pic quadratic B-spline
181 /// (straight first/last half-segments, tangent at segment midpoints);
182 /// `Some(t)` = dpic's tensioned cubic spline through `t`.
183 tension: Option<f64>,
184 arrows: Arrowheads,
185 style: Style,
186 text: Vec<TextLine>,
187 },
188 Arc {
189 c: Point,
190 r: f64,
191 /// start and end angles in radians.
192 a0: f64,
193 a1: f64,
194 cw: bool,
195 arrows: Arrowheads,
196 style: Style,
197 text: Vec<TextLine>,
198 },
199 Text {
200 at: Point,
201 text: Vec<TextLine>,
202 bbox: Bbox,
203 w: f64,
204 h: f64,
205 standalone: bool,
206 },
207}