Skip to main content

ratex_layout/
layout_box.rs

1use ratex_types::color::Color;
2use ratex_types::path_command::PathCommand;
3
4/// A TeX box: the fundamental unit of layout.
5///
6/// Every mathematical element is represented as a box with three dimensions:
7/// - `width`: horizontal extent
8/// - `height`: ascent above baseline
9/// - `depth`: descent below baseline
10///
11/// All values are in **em** units relative to the current font size.
12#[derive(Debug, Clone)]
13pub struct LayoutBox {
14    pub width: f64,
15    pub height: f64,
16    pub depth: f64,
17    pub content: BoxContent,
18    pub color: Color,
19}
20
21/// What a LayoutBox contains.
22#[derive(Debug, Clone)]
23pub enum BoxContent {
24    /// Horizontal list of child boxes laid out left-to-right.
25    HBox(Vec<LayoutBox>),
26
27    /// Vertical list of child boxes laid out top-to-bottom.
28    VBox(Vec<VBoxChild>),
29
30    /// A single glyph character.
31    Glyph {
32        font_id: ratex_font::FontId,
33        char_code: u32,
34    },
35
36    /// Filled rectangle from `\rule[<raise>]{width}{height}`.
37    /// `thickness` is the ink height; `raise` is the distance (in em) from the baseline
38    /// to the bottom edge of the rectangle, positive toward the top of the line.
39    Rule {
40        thickness: f64,
41        raise: f64,
42    },
43
44    /// Empty space (kern).
45    Kern,
46
47    /// A fraction: numerator over denominator with optional bar.
48    Fraction {
49        numer: Box<LayoutBox>,
50        denom: Box<LayoutBox>,
51        numer_shift: f64,
52        denom_shift: f64,
53        bar_thickness: f64,
54        numer_scale: f64,
55        denom_scale: f64,
56    },
57
58    /// Superscript/subscript layout.
59    SupSub {
60        base: Box<LayoutBox>,
61        sup: Option<Box<LayoutBox>>,
62        sub: Option<Box<LayoutBox>>,
63        sup_shift: f64,
64        sub_shift: f64,
65        sup_scale: f64,
66        sub_scale: f64,
67        /// When true, place scripts centered on the base width (e.g. `\overbrace` / `\underbrace`).
68        center_scripts: bool,
69    },
70
71    /// A radical (square root).
72    Radical {
73        body: Box<LayoutBox>,
74        index: Option<Box<LayoutBox>>,
75        /// Horizontal offset (in em) of the surd/body from the left edge when index is present.
76        index_offset: f64,
77        rule_thickness: f64,
78        inner_height: f64,
79    },
80
81    /// An operator with limits above/below (e.g. \sum_{i=0}^{n}).
82    OpLimits {
83        base: Box<LayoutBox>,
84        sup: Option<Box<LayoutBox>>,
85        sub: Option<Box<LayoutBox>>,
86        base_shift: f64,
87        sup_kern: f64,
88        sub_kern: f64,
89        slant: f64,
90        sup_scale: f64,
91        sub_scale: f64,
92    },
93
94    /// An accent above or below its base.
95    Accent {
96        base: Box<LayoutBox>,
97        accent: Box<LayoutBox>,
98        clearance: f64,
99        skew: f64,
100        is_below: bool,
101    },
102
103    /// A stretchy delimiter (\left, \right) wrapping inner content.
104    LeftRight {
105        left: Box<LayoutBox>,
106        right: Box<LayoutBox>,
107        inner: Box<LayoutBox>,
108    },
109
110    /// A matrix/array: rows × columns of cells.
111    Array {
112        cells: Vec<Vec<LayoutBox>>,
113        col_widths: Vec<f64>,
114        /// Per-column alignment: b'l', b'c', or b'r'.
115        col_aligns: Vec<u8>,
116        row_heights: Vec<f64>,
117        row_depths: Vec<f64>,
118        col_gap: f64,
119        offset: f64,
120    },
121
122    /// An SVG-style path (arrows, braces, etc.).
123    SvgPath {
124        commands: Vec<PathCommand>,
125        fill: bool,
126    },
127
128    /// A framed/colored box (fbox, colorbox, fcolorbox).
129    /// body is the inner content; padding and border add to the outer dimensions.
130    Framed {
131        body: Box<LayoutBox>,
132        padding: f64,
133        border_thickness: f64,
134        has_border: bool,
135        bg_color: Option<Color>,
136        border_color: Color,
137    },
138
139    /// A raised/lowered box (raisebox).
140    /// shift > 0 moves content up, shift < 0 moves content down.
141    RaiseBox {
142        body: Box<LayoutBox>,
143        shift: f64,
144    },
145
146    /// A scaled box (for \scriptstyle, \scriptscriptstyle in inline context).
147    /// The child is rendered at child_scale relative to the parent.
148    Scaled {
149        body: Box<LayoutBox>,
150        child_scale: f64,
151    },
152
153    /// Actuarial angle \angl{body}: path (horizontal roof + vertical bar) and body share the same baseline.
154    Angl {
155        path_commands: Vec<PathCommand>,
156        body: Box<LayoutBox>,
157    },
158
159    /// \overline{body}: body with a horizontal rule drawn above it.
160    /// The rule sits `2 * rule_thickness` above the body's top (clearance), and is `rule_thickness` thick.
161    Overline {
162        body: Box<LayoutBox>,
163        rule_thickness: f64,
164    },
165
166    /// \underline{body}: body with a horizontal rule drawn below it.
167    /// The rule sits `2 * rule_thickness` below the body's bottom (clearance), and is `rule_thickness` thick.
168    Underline {
169        body: Box<LayoutBox>,
170        rule_thickness: f64,
171    },
172
173    /// Empty placeholder.
174    Empty,
175}
176
177/// A child element in a vertical box.
178#[derive(Debug, Clone)]
179pub struct VBoxChild {
180    pub kind: VBoxChildKind,
181    pub shift: f64,
182}
183
184#[derive(Debug, Clone)]
185pub enum VBoxChildKind {
186    Box(LayoutBox),
187    Kern(f64),
188}
189
190impl LayoutBox {
191    pub fn new_empty() -> Self {
192        Self {
193            width: 0.0,
194            height: 0.0,
195            depth: 0.0,
196            content: BoxContent::Empty,
197            color: Color::BLACK,
198        }
199    }
200
201    pub fn new_kern(width: f64) -> Self {
202        Self {
203            width,
204            height: 0.0,
205            depth: 0.0,
206            content: BoxContent::Kern,
207            color: Color::BLACK,
208        }
209    }
210
211    pub fn new_rule(width: f64, height: f64, depth: f64, thickness: f64, raise: f64) -> Self {
212        Self {
213            width,
214            height,
215            depth,
216            content: BoxContent::Rule { thickness, raise },
217            color: Color::BLACK,
218        }
219    }
220
221    pub fn total_height(&self) -> f64 {
222        self.height + self.depth
223    }
224
225    pub fn with_color(mut self, color: Color) -> Self {
226        self.color = color;
227        self
228    }
229
230    /// Adjust height/depth for a delimiter to match a target size.
231    pub fn with_adjusted_delim(mut self, height: f64, depth: f64) -> Self {
232        self.height = height;
233        self.depth = depth;
234        self
235    }
236}