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 },
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 },
107
108 /// A stretchy delimiter (\left, \right) wrapping inner content.
109 LeftRight {
110 left: Box<LayoutBox>,
111 right: Box<LayoutBox>,
112 inner: Box<LayoutBox>,
113 },
114
115 /// A matrix/array: rows × columns of cells.
116 Array {
117 cells: Vec<Vec<LayoutBox>>,
118 col_widths: Vec<f64>,
119 /// Per-column alignment: b'l', b'c', or b'r'.
120 col_aligns: Vec<u8>,
121 row_heights: Vec<f64>,
122 row_depths: Vec<f64>,
123 col_gap: f64,
124 offset: f64,
125 /// Extra x padding before the first column (= arraycolsep when hskip_before_and_after is true).
126 content_x_offset: f64,
127 /// For each column boundary (0 = before col 0, ..., num_cols = after last col),
128 /// whether there is a vertical rule separator ('|').
129 col_separators: Vec<bool>,
130 /// For each row boundary (0 = before row 0, ..., num_rows = after last row),
131 /// the list of hlines: false = solid, true = dashed.
132 hlines_before_row: Vec<Vec<bool>>,
133 /// Thickness of array rules in em.
134 rule_thickness: f64,
135 /// Gap between consecutive \hline or \hdashline rules (= \doublerulesep, in em).
136 double_rule_sep: f64,
137 },
138
139 /// An SVG-style path (arrows, braces, etc.).
140 SvgPath {
141 commands: Vec<PathCommand>,
142 fill: bool,
143 },
144
145 /// A framed/colored box (fbox, colorbox, fcolorbox).
146 /// body is the inner content; padding and border add to the outer dimensions.
147 Framed {
148 body: Box<LayoutBox>,
149 padding: f64,
150 border_thickness: f64,
151 has_border: bool,
152 bg_color: Option<Color>,
153 border_color: Color,
154 },
155
156 /// A raised/lowered box (raisebox).
157 /// shift > 0 moves content up, shift < 0 moves content down.
158 RaiseBox {
159 body: Box<LayoutBox>,
160 shift: f64,
161 },
162
163 /// A scaled box (for \scriptstyle, \scriptscriptstyle in inline context).
164 /// The child is rendered at child_scale relative to the parent.
165 Scaled {
166 body: Box<LayoutBox>,
167 child_scale: f64,
168 },
169
170 /// Actuarial angle \angl{body}: path (horizontal roof + vertical bar) and body share the same baseline.
171 Angl {
172 path_commands: Vec<PathCommand>,
173 body: Box<LayoutBox>,
174 },
175
176 /// \overline{body}: body with a horizontal rule drawn above it.
177 /// The rule sits `2 * rule_thickness` above the body's top (clearance), and is `rule_thickness` thick.
178 Overline {
179 body: Box<LayoutBox>,
180 rule_thickness: f64,
181 },
182
183 /// \underline{body}: body with a horizontal rule drawn below it.
184 /// The rule sits `2 * rule_thickness` below the body's bottom (clearance), and is `rule_thickness` thick.
185 Underline {
186 body: Box<LayoutBox>,
187 rule_thickness: f64,
188 },
189
190 /// Empty placeholder.
191 Empty,
192}
193
194/// A child element in a vertical box.
195#[derive(Debug, Clone)]
196pub struct VBoxChild {
197 pub kind: VBoxChildKind,
198 pub shift: f64,
199}
200
201#[derive(Debug, Clone)]
202pub enum VBoxChildKind {
203 Box(Box<LayoutBox>),
204 Kern(f64),
205}
206
207impl LayoutBox {
208 pub fn new_empty() -> Self {
209 Self {
210 width: 0.0,
211 height: 0.0,
212 depth: 0.0,
213 content: BoxContent::Empty,
214 color: Color::BLACK,
215 }
216 }
217
218 pub fn new_kern(width: f64) -> Self {
219 Self {
220 width,
221 height: 0.0,
222 depth: 0.0,
223 content: BoxContent::Kern,
224 color: Color::BLACK,
225 }
226 }
227
228 pub fn new_rule(width: f64, height: f64, depth: f64, thickness: f64, raise: f64) -> Self {
229 Self {
230 width,
231 height,
232 depth,
233 content: BoxContent::Rule { thickness, raise },
234 color: Color::BLACK,
235 }
236 }
237
238 pub fn total_height(&self) -> f64 {
239 self.height + self.depth
240 }
241
242 pub fn with_color(mut self, color: Color) -> Self {
243 self.color = color;
244 self
245 }
246
247 /// Adjust height/depth for a delimiter to match a target size.
248 pub fn with_adjusted_delim(mut self, height: f64, depth: f64) -> Self {
249 self.height = height;
250 self.depth = depth;
251 self
252 }
253}