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}