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 },
70
71 /// A radical (square root).
72 Radical {
73 body: Box<LayoutBox>,
74 index: Option<Box<LayoutBox>>,
75 /// Horizontal offset (in em) of the surd/body from the left edge when index is present.
76 index_offset: f64,
77 rule_thickness: f64,
78 inner_height: f64,
79 },
80
81 /// An operator with limits above/below (e.g. \sum_{i=0}^{n}).
82 OpLimits {
83 base: Box<LayoutBox>,
84 sup: Option<Box<LayoutBox>>,
85 sub: Option<Box<LayoutBox>>,
86 base_shift: f64,
87 sup_kern: f64,
88 sub_kern: f64,
89 slant: f64,
90 sup_scale: f64,
91 sub_scale: f64,
92 },
93
94 /// An accent above or below its base.
95 Accent {
96 base: Box<LayoutBox>,
97 accent: Box<LayoutBox>,
98 clearance: f64,
99 skew: f64,
100 is_below: bool,
101 },
102
103 /// A stretchy delimiter (\left, \right) wrapping inner content.
104 LeftRight {
105 left: Box<LayoutBox>,
106 right: Box<LayoutBox>,
107 inner: Box<LayoutBox>,
108 },
109
110 /// A matrix/array: rows × columns of cells.
111 Array {
112 cells: Vec<Vec<LayoutBox>>,
113 col_widths: Vec<f64>,
114 /// Per-column alignment: b'l', b'c', or b'r'.
115 col_aligns: Vec<u8>,
116 row_heights: Vec<f64>,
117 row_depths: Vec<f64>,
118 col_gap: f64,
119 offset: f64,
120 },
121
122 /// An SVG-style path (arrows, braces, etc.).
123 SvgPath {
124 commands: Vec<PathCommand>,
125 fill: bool,
126 },
127
128 /// A framed/colored box (fbox, colorbox, fcolorbox).
129 /// body is the inner content; padding and border add to the outer dimensions.
130 Framed {
131 body: Box<LayoutBox>,
132 padding: f64,
133 border_thickness: f64,
134 has_border: bool,
135 bg_color: Option<Color>,
136 border_color: Color,
137 },
138
139 /// A raised/lowered box (raisebox).
140 /// shift > 0 moves content up, shift < 0 moves content down.
141 RaiseBox {
142 body: Box<LayoutBox>,
143 shift: f64,
144 },
145
146 /// A scaled box (for \scriptstyle, \scriptscriptstyle in inline context).
147 /// The child is rendered at child_scale relative to the parent.
148 Scaled {
149 body: Box<LayoutBox>,
150 child_scale: f64,
151 },
152
153 /// Actuarial angle \angl{body}: path (horizontal roof + vertical bar) and body share the same baseline.
154 Angl {
155 path_commands: Vec<PathCommand>,
156 body: Box<LayoutBox>,
157 },
158
159 /// \overline{body}: body with a horizontal rule drawn above it.
160 /// The rule sits `2 * rule_thickness` above the body's top (clearance), and is `rule_thickness` thick.
161 Overline {
162 body: Box<LayoutBox>,
163 rule_thickness: f64,
164 },
165
166 /// \underline{body}: body with a horizontal rule drawn below it.
167 /// The rule sits `2 * rule_thickness` below the body's bottom (clearance), and is `rule_thickness` thick.
168 Underline {
169 body: Box<LayoutBox>,
170 rule_thickness: f64,
171 },
172
173 /// Empty placeholder.
174 Empty,
175}
176
177/// A child element in a vertical box.
178#[derive(Debug, Clone)]
179pub struct VBoxChild {
180 pub kind: VBoxChildKind,
181 pub shift: f64,
182}
183
184#[derive(Debug, Clone)]
185pub enum VBoxChildKind {
186 Box(LayoutBox),
187 Kern(f64),
188}
189
190impl LayoutBox {
191 pub fn new_empty() -> Self {
192 Self {
193 width: 0.0,
194 height: 0.0,
195 depth: 0.0,
196 content: BoxContent::Empty,
197 color: Color::BLACK,
198 }
199 }
200
201 pub fn new_kern(width: f64) -> Self {
202 Self {
203 width,
204 height: 0.0,
205 depth: 0.0,
206 content: BoxContent::Kern,
207 color: Color::BLACK,
208 }
209 }
210
211 pub fn new_rule(width: f64, height: f64, depth: f64, thickness: f64, raise: f64) -> Self {
212 Self {
213 width,
214 height,
215 depth,
216 content: BoxContent::Rule { thickness, raise },
217 color: Color::BLACK,
218 }
219 }
220
221 pub fn total_height(&self) -> f64 {
222 self.height + self.depth
223 }
224
225 pub fn with_color(mut self, color: Color) -> Self {
226 self.color = color;
227 self
228 }
229
230 /// Adjust height/depth for a delimiter to match a target size.
231 pub fn with_adjusted_delim(mut self, height: f64, depth: f64) -> Self {
232 self.height = height;
233 self.depth = depth;
234 self
235 }
236}