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