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::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}