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