Skip to main content

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