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    },
143
144    /// An SVG-style path (arrows, braces, etc.).
145    SvgPath {
146        commands: Vec<PathCommand>,
147        fill: bool,
148    },
149
150    /// A framed/colored box (fbox, colorbox, fcolorbox).
151    /// body is the inner content; padding and border add to the outer dimensions.
152    Framed {
153        body: Box<LayoutBox>,
154        padding: f64,
155        border_thickness: f64,
156        has_border: bool,
157        bg_color: Option<Color>,
158        border_color: Color,
159    },
160
161    /// A raised/lowered box (raisebox).
162    /// shift > 0 moves content up, shift < 0 moves content down.
163    RaiseBox {
164        body: Box<LayoutBox>,
165        shift: f64,
166    },
167
168    /// A scaled box (for \scriptstyle, \scriptscriptstyle in inline context).
169    /// The child is rendered at child_scale relative to the parent.
170    Scaled {
171        body: Box<LayoutBox>,
172        child_scale: f64,
173    },
174
175    /// Actuarial angle \angl{body}: path (horizontal roof + vertical bar) and body share the same baseline.
176    Angl {
177        path_commands: Vec<PathCommand>,
178        body: Box<LayoutBox>,
179    },
180
181    /// \overline{body}: body with a horizontal rule drawn above it.
182    /// The rule sits `2 * rule_thickness` above the body's top (clearance), and is `rule_thickness` thick.
183    Overline {
184        body: Box<LayoutBox>,
185        rule_thickness: f64,
186    },
187
188    /// \underline{body}: body with a horizontal rule drawn below it.
189    /// The rule sits `2 * rule_thickness` below the body's bottom (clearance), and is `rule_thickness` thick.
190    Underline {
191        body: Box<LayoutBox>,
192        rule_thickness: f64,
193    },
194
195    /// Empty placeholder.
196    Empty,
197}
198
199/// A child element in a vertical box.
200#[derive(Debug, Clone)]
201pub struct VBoxChild {
202    pub kind: VBoxChildKind,
203    pub shift: f64,
204}
205
206#[derive(Debug, Clone)]
207pub enum VBoxChildKind {
208    Box(Box<LayoutBox>),
209    Kern(f64),
210}
211
212impl LayoutBox {
213    pub fn new_empty() -> Self {
214        Self {
215            width: 0.0,
216            height: 0.0,
217            depth: 0.0,
218            content: BoxContent::Empty,
219            color: Color::BLACK,
220        }
221    }
222
223    pub fn new_kern(width: f64) -> Self {
224        Self {
225            width,
226            height: 0.0,
227            depth: 0.0,
228            content: BoxContent::Kern,
229            color: Color::BLACK,
230        }
231    }
232
233    pub fn new_rule(width: f64, height: f64, depth: f64, thickness: f64, raise: f64) -> Self {
234        Self {
235            width,
236            height,
237            depth,
238            content: BoxContent::Rule { thickness, raise },
239            color: Color::BLACK,
240        }
241    }
242
243    pub fn total_height(&self) -> f64 {
244        self.height + self.depth
245    }
246
247    pub fn with_color(mut self, color: Color) -> Self {
248        self.color = color;
249        self
250    }
251
252    /// Adjust height/depth for a delimiter to match a target size.
253    pub fn with_adjusted_delim(mut self, height: f64, depth: f64) -> Self {
254        self.height = height;
255        self.depth = depth;
256        self
257    }
258}