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 { thickness: f64, raise: f64 },
40
41    /// Empty space (kern).
42    Kern,
43
44    /// A fraction: numerator over denominator with optional bar.
45    Fraction {
46        numer: Box<LayoutBox>,
47        denom: Box<LayoutBox>,
48        numer_shift: f64,
49        denom_shift: f64,
50        bar_thickness: f64,
51        numer_scale: f64,
52        denom_scale: f64,
53    },
54
55    /// Superscript/subscript layout.
56    SupSub {
57        base: Box<LayoutBox>,
58        sup: Option<Box<LayoutBox>>,
59        sub: Option<Box<LayoutBox>>,
60        sup_shift: f64,
61        sub_shift: f64,
62        sup_scale: f64,
63        sub_scale: f64,
64        /// When true, place scripts centered on the base width (e.g. `\overbrace` / `\underbrace`).
65        center_scripts: bool,
66        /// Italic correction of the base character (em). Superscript x is offset by this amount
67        /// beyond base.width, matching KaTeX's margin-right on italic math symbols.
68        italic_correction: f64,
69        /// Horizontal kern (em) applied to the subscript: KaTeX uses `margin-left: -base.italic` on
70        /// `SymbolNode` bases so subscripts are not pushed out by the base's italic correction.
71        sub_h_kern: 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        /// KaTeX `accentunder.js`: extra em gap between base bottom and under-accent (e.g. 0.12 for `\\utilde`).
107        under_gap_em: f64,
108    },
109
110    /// A stretchy delimiter (\left, \right) wrapping inner content.
111    LeftRight {
112        left: Box<LayoutBox>,
113        right: Box<LayoutBox>,
114        inner: Box<LayoutBox>,
115    },
116
117    /// A matrix/array: rows × columns of cells.
118    Array {
119        cells: Vec<Vec<LayoutBox>>,
120        col_widths: Vec<f64>,
121        /// Per-column alignment: b'l', b'c', or b'r'.
122        col_aligns: Vec<u8>,
123        row_heights: Vec<f64>,
124        row_depths: Vec<f64>,
125        col_gap: f64,
126        offset: f64,
127        /// Extra x padding before the first column (= arraycolsep when hskip_before_and_after is true).
128        content_x_offset: f64,
129        /// For each column boundary (0 = before col 0, ..., num_cols = after last col),
130        /// the vertical rule separator type: None = no rule, Some(false) = solid '|', Some(true) = dashed ':'.
131        col_separators: Vec<Option<bool>>,
132        /// For each row boundary (0 = before row 0, ..., num_rows = after last row),
133        /// the list of hlines: false = solid, true = dashed.
134        hlines_before_row: Vec<Vec<bool>>,
135        /// Thickness of array rules in em.
136        rule_thickness: f64,
137        /// Gap between consecutive \hline or \hdashline rules (= \doublerulesep, in em).
138        double_rule_sep: f64,
139        /// Width of the cell grid including `content_x_offset` padding (em); excludes tag column.
140        array_inner_width: f64,
141        /// Horizontal gap between grid and tag column (em).
142        tag_gap_em: f64,
143        /// Width reserved for tags; tags are right-aligned in this column (em).
144        tag_col_width: f64,
145        /// Per-row tag layout; length matches number of rows.
146        row_tags: Vec<Option<LayoutBox>>,
147        /// When true, tags sit left of the grid (leqno-style).
148        tags_left: bool,
149    },
150
151    /// An SVG-style path (arrows, braces, etc.).
152    SvgPath {
153        commands: Vec<PathCommand>,
154        fill: bool,
155    },
156
157    /// A framed/colored box (fbox, colorbox, fcolorbox).
158    /// body is the inner content; padding and border add to the outer dimensions.
159    Framed {
160        body: Box<LayoutBox>,
161        padding: f64,
162        border_thickness: f64,
163        has_border: bool,
164        bg_color: Option<Color>,
165        border_color: Color,
166    },
167
168    /// A raised/lowered box (raisebox).
169    /// shift > 0 moves content up, shift < 0 moves content down.
170    RaiseBox { body: Box<LayoutBox>, shift: f64 },
171
172    /// A scaled box (for \scriptstyle, \scriptscriptstyle in inline context).
173    /// The child is rendered at child_scale relative to the parent.
174    Scaled {
175        body: Box<LayoutBox>,
176        child_scale: f64,
177    },
178
179    /// Actuarial angle \angl{body}: path (horizontal roof + vertical bar) and body share the same baseline.
180    Angl {
181        path_commands: Vec<PathCommand>,
182        body: Box<LayoutBox>,
183    },
184
185    /// \overline{body}: body with a horizontal rule drawn above it.
186    /// The rule sits `2 * rule_thickness` above the body's top (clearance), and is `rule_thickness` thick.
187    Overline {
188        body: Box<LayoutBox>,
189        rule_thickness: f64,
190    },
191
192    /// \underline{body}: body with a horizontal rule drawn below it.
193    /// The rule sits `2 * rule_thickness` below the body's bottom (clearance), and is `rule_thickness` thick.
194    Underline {
195        body: Box<LayoutBox>,
196        rule_thickness: f64,
197        offset: f64,
198    },
199
200    /// Bussproofs-style proof tree with absolutely placed child boxes and inference rules.
201    ProofTree {
202        children: Vec<PlacedBox>,
203        rules: Vec<ProofRule>,
204    },
205
206    /// Empty placeholder.
207    Empty,
208}
209
210#[derive(Debug, Clone)]
211pub struct PlacedBox {
212    pub box_: LayoutBox,
213    pub x: f64,
214    pub baseline_y: f64,
215}
216
217#[derive(Debug, Clone)]
218pub struct ProofRule {
219    pub x: f64,
220    pub y: f64,
221    pub width: f64,
222    pub thickness: f64,
223    pub dashed: bool,
224}
225
226/// A child element in a vertical box.
227#[derive(Debug, Clone)]
228pub struct VBoxChild {
229    pub kind: VBoxChildKind,
230    pub shift: f64,
231}
232
233#[derive(Debug, Clone)]
234pub enum VBoxChildKind {
235    Box(Box<LayoutBox>),
236    Kern(f64),
237}
238
239impl LayoutBox {
240    pub fn new_empty() -> Self {
241        Self {
242            width: 0.0,
243            height: 0.0,
244            depth: 0.0,
245            content: BoxContent::Empty,
246            color: Color::BLACK,
247        }
248    }
249
250    pub fn new_kern(width: f64) -> Self {
251        Self {
252            width,
253            height: 0.0,
254            depth: 0.0,
255            content: BoxContent::Kern,
256            color: Color::BLACK,
257        }
258    }
259
260    pub fn new_rule(width: f64, height: f64, depth: f64, thickness: f64, raise: f64) -> Self {
261        Self {
262            width,
263            height,
264            depth,
265            content: BoxContent::Rule { thickness, raise },
266            color: Color::BLACK,
267        }
268    }
269
270    pub fn total_height(&self) -> f64 {
271        self.height + self.depth
272    }
273
274    pub fn with_color(mut self, color: Color) -> Self {
275        self.color = color;
276        self
277    }
278
279    /// Adjust height/depth for a delimiter to match a target size.
280    pub fn with_adjusted_delim(mut self, height: f64, depth: f64) -> Self {
281        self.height = height;
282        self.depth = depth;
283        self
284    }
285}