Skip to main content

ratex_types/
display_item.rs

1use serde::{Deserialize, Serialize};
2
3use crate::color::Color;
4use crate::path_command::PathCommand;
5
6/// The final output of the layout engine: a flat list of drawing commands
7/// with absolute coordinates, ready for platform renderers.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct DisplayList {
10    pub items: Vec<DisplayItem>,
11    pub width: f64,
12    pub height: f64,
13    pub depth: f64,
14}
15
16/// A single drawing instruction with absolute position.
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(tag = "type")]
19pub enum DisplayItem {
20    /// Draw a glyph outline at the given position.
21    GlyphPath {
22        x: f64,
23        y: f64,
24        scale: f64,
25        font: String,
26        char_code: u32,
27        /// Placeholder bounding-box paths from layout. Not used by any renderer
28        /// (all platforms draw glyphs via font + char_code). Skipped during
29        /// serialization to reduce JSON payload size.
30        #[serde(skip_serializing, default)]
31        commands: Vec<PathCommand>,
32        color: Color,
33    },
34    /// Draw a horizontal line (fraction bars, overlines, etc.).
35    Line {
36        x: f64,
37        y: f64,
38        width: f64,
39        thickness: f64,
40        color: Color,
41        /// If true, render as a dashed line (for \hdashline).
42        #[serde(default)]
43        dashed: bool,
44    },
45    /// Draw a filled rectangle (\colorbox backgrounds).
46    Rect {
47        x: f64,
48        y: f64,
49        width: f64,
50        height: f64,
51        color: Color,
52    },
53    /// Draw an arbitrary SVG-style path (radical signs, large delimiters).
54    Path {
55        x: f64,
56        y: f64,
57        commands: Vec<PathCommand>,
58        fill: bool,
59        color: Color,
60    },
61}
62
63impl DisplayList {
64    pub fn new() -> Self {
65        Self {
66            items: Vec::new(),
67            width: 0.0,
68            height: 0.0,
69            depth: 0.0,
70        }
71    }
72
73    pub fn total_height(&self) -> f64 {
74        self.height + self.depth
75    }
76}
77
78impl Default for DisplayList {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_display_list_new() {
90        let dl = DisplayList::new();
91        assert!(dl.items.is_empty());
92        assert!((dl.width - 0.0).abs() < f64::EPSILON);
93        assert!((dl.total_height() - 0.0).abs() < f64::EPSILON);
94    }
95
96    #[test]
97    fn test_serde_roundtrip() {
98        let dl = DisplayList {
99            items: vec![
100                DisplayItem::Line {
101                    x: 0.0,
102                    y: 5.0,
103                    width: 10.0,
104                    thickness: 0.04,
105                    color: Color::BLACK,
106                    dashed: false,
107                },
108                DisplayItem::Rect {
109                    x: 0.0,
110                    y: 0.0,
111                    width: 10.0,
112                    height: 10.0,
113                    color: Color::rgb(1.0, 0.0, 0.0),
114                },
115                DisplayItem::Path {
116                    x: 0.0,
117                    y: 0.0,
118                    commands: vec![
119                        PathCommand::MoveTo { x: 0.0, y: 0.0 },
120                        PathCommand::LineTo { x: 5.0, y: 5.0 },
121                        PathCommand::Close,
122                    ],
123                    fill: false,
124                    color: Color::BLACK,
125                },
126            ],
127            width: 10.0,
128            height: 5.0,
129            depth: 5.0,
130        };
131
132        let json = serde_json::to_string(&dl).unwrap();
133        let dl2: DisplayList = serde_json::from_str(&json).unwrap();
134
135        assert_eq!(dl.items.len(), dl2.items.len());
136        assert!((dl.width - dl2.width).abs() < f64::EPSILON);
137        assert!((dl.height - dl2.height).abs() < f64::EPSILON);
138        assert!((dl.depth - dl2.depth).abs() < f64::EPSILON);
139    }
140
141    #[test]
142    fn test_total_height() {
143        let dl = DisplayList {
144            items: vec![],
145            width: 10.0,
146            height: 3.0,
147            depth: 2.0,
148        };
149        assert!((dl.total_height() - 5.0).abs() < f64::EPSILON);
150    }
151}