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