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}