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        /// Italic correction of the base character (em). Superscript x is offset by this amount
70        /// beyond base.width, matching KaTeX's margin-right on italic math symbols.
71        italic_correction: f64,
72    },
73
74    /// A radical (square root).
75    Radical {
76        body: Box<LayoutBox>,
77        index: Option<Box<LayoutBox>>,
78        /// Horizontal offset (in em) of the surd/body from the left edge when index is present.
79        index_offset: f64,
80        /// `scriptscript` size relative to the surrounding math style (for drawing the index).
81        index_scale: f64,
82        rule_thickness: f64,
83        inner_height: f64,
84    },
85
86    /// An operator with limits above/below (e.g. \sum_{i=0}^{n}).
87    OpLimits {
88        base: Box<LayoutBox>,
89        sup: Option<Box<LayoutBox>>,
90        sub: Option<Box<LayoutBox>>,
91        base_shift: f64,
92        sup_kern: f64,
93        sub_kern: f64,
94        slant: f64,
95        sup_scale: f64,
96        sub_scale: f64,
97    },
98
99    /// An accent above or below its base.
100    Accent {
101        base: Box<LayoutBox>,
102        accent: Box<LayoutBox>,
103        clearance: f64,
104        skew: f64,
105        is_below: bool,
106    },
107
108    /// A stretchy delimiter (\left, \right) wrapping inner content.
109    LeftRight {
110        left: Box<LayoutBox>,
111        right: Box<LayoutBox>,
112        inner: Box<LayoutBox>,
113    },
114
115    /// A matrix/array: rows × columns of cells.
116    Array {
117        cells: Vec<Vec<LayoutBox>>,
118        col_widths: Vec<f64>,
119        /// Per-column alignment: b'l', b'c', or b'r'.
120        col_aligns: Vec<u8>,
121        row_heights: Vec<f64>,
122        row_depths: Vec<f64>,
123        col_gap: f64,
124        offset: f64,
125        /// Extra x padding before the first column (= arraycolsep when hskip_before_and_after is true).
126        content_x_offset: f64,
127        /// For each column boundary (0 = before col 0, ..., num_cols = after last col),
128        /// whether there is a vertical rule separator ('|').
129        col_separators: Vec<bool>,
130        /// For each row boundary (0 = before row 0, ..., num_rows = after last row),
131        /// the list of hlines: false = solid, true = dashed.
132        hlines_before_row: Vec<Vec<bool>>,
133        /// Thickness of array rules in em.
134        rule_thickness: f64,
135        /// Gap between consecutive \hline or \hdashline rules (= \doublerulesep, in em).
136        double_rule_sep: f64,
137    },
138
139    /// An SVG-style path (arrows, braces, etc.).
140    SvgPath {
141        commands: Vec<PathCommand>,
142        fill: bool,
143    },
144
145    /// A framed/colored box (fbox, colorbox, fcolorbox).
146    /// body is the inner content; padding and border add to the outer dimensions.
147    Framed {
148        body: Box<LayoutBox>,
149        padding: f64,
150        border_thickness: f64,
151        has_border: bool,
152        bg_color: Option<Color>,
153        border_color: Color,
154    },
155
156    /// A raised/lowered box (raisebox).
157    /// shift > 0 moves content up, shift < 0 moves content down.
158    RaiseBox {
159        body: Box<LayoutBox>,
160        shift: f64,
161    },
162
163    /// A scaled box (for \scriptstyle, \scriptscriptstyle in inline context).
164    /// The child is rendered at child_scale relative to the parent.
165    Scaled {
166        body: Box<LayoutBox>,
167        child_scale: f64,
168    },
169
170    /// Actuarial angle \angl{body}: path (horizontal roof + vertical bar) and body share the same baseline.
171    Angl {
172        path_commands: Vec<PathCommand>,
173        body: Box<LayoutBox>,
174    },
175
176    /// \overline{body}: body with a horizontal rule drawn above it.
177    /// The rule sits `2 * rule_thickness` above the body's top (clearance), and is `rule_thickness` thick.
178    Overline {
179        body: Box<LayoutBox>,
180        rule_thickness: f64,
181    },
182
183    /// \underline{body}: body with a horizontal rule drawn below it.
184    /// The rule sits `2 * rule_thickness` below the body's bottom (clearance), and is `rule_thickness` thick.
185    Underline {
186        body: Box<LayoutBox>,
187        rule_thickness: f64,
188    },
189
190    /// Empty placeholder.
191    Empty,
192}
193
194/// A child element in a vertical box.
195#[derive(Debug, Clone)]
196pub struct VBoxChild {
197    pub kind: VBoxChildKind,
198    pub shift: f64,
199}
200
201#[derive(Debug, Clone)]
202pub enum VBoxChildKind {
203    Box(Box<LayoutBox>),
204    Kern(f64),
205}
206
207impl LayoutBox {
208    pub fn new_empty() -> Self {
209        Self {
210            width: 0.0,
211            height: 0.0,
212            depth: 0.0,
213            content: BoxContent::Empty,
214            color: Color::BLACK,
215        }
216    }
217
218    pub fn new_kern(width: f64) -> Self {
219        Self {
220            width,
221            height: 0.0,
222            depth: 0.0,
223            content: BoxContent::Kern,
224            color: Color::BLACK,
225        }
226    }
227
228    pub fn new_rule(width: f64, height: f64, depth: f64, thickness: f64, raise: f64) -> Self {
229        Self {
230            width,
231            height,
232            depth,
233            content: BoxContent::Rule { thickness, raise },
234            color: Color::BLACK,
235        }
236    }
237
238    pub fn total_height(&self) -> f64 {
239        self.height + self.depth
240    }
241
242    pub fn with_color(mut self, color: Color) -> Self {
243        self.color = color;
244        self
245    }
246
247    /// Adjust height/depth for a delimiter to match a target size.
248    pub fn with_adjusted_delim(mut self, height: f64, depth: f64) -> Self {
249        self.height = height;
250        self.depth = depth;
251        self
252    }
253}