1use crate::node::{Node, NodeKind, StackKind};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub enum Style {
18 Display,
20 Text,
22 Script,
24 ScriptScript,
26}
27
28impl Style {
29 #[inline(always)]
32 #[must_use]
33 pub const fn is_script(self) -> bool {
34 matches!(self, Self::Script | Self::ScriptScript)
35 }
36
37 #[inline(always)]
40 #[must_use]
41 pub const fn size_factor(self) -> f64 {
42 match self {
43 Self::Display | Self::Text => 1.0,
44 Self::Script => 0.7,
45 Self::ScriptScript => 0.5,
46 }
47 }
48
49 #[inline(always)]
51 #[must_use]
52 pub const fn sup(self) -> Self {
53 match self {
54 Self::Display | Self::Text => Self::Script,
55 Self::Script | Self::ScriptScript => Self::ScriptScript,
56 }
57 }
58
59 #[inline(always)]
61 #[must_use]
62 pub const fn num(self) -> Self {
63 match self {
64 Self::Display => Self::Text,
65 Self::Text => Self::Script,
66 Self::Script | Self::ScriptScript => Self::ScriptScript,
67 }
68 }
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74pub struct StyleCtx {
75 pub style: Style,
77 pub cramped: bool,
80}
81
82impl StyleCtx {
83 #[inline(always)]
85 #[must_use]
86 pub const fn new(style: Style) -> Self {
87 Self {
88 style,
89 cramped: false,
90 }
91 }
92
93 #[inline(always)]
95 #[must_use]
96 pub const fn display() -> Self {
97 Self::new(Style::Display)
98 }
99
100 #[inline(always)]
103 #[must_use]
104 pub const fn sup(self) -> Self {
105 Self {
106 style: self.style.sup(),
107 cramped: self.cramped,
108 }
109 }
110
111 #[inline(always)]
113 #[must_use]
114 pub const fn sub(self) -> Self {
115 Self {
116 style: self.style.sup(),
117 cramped: true,
118 }
119 }
120
121 #[inline(always)]
124 #[must_use]
125 pub const fn num(self) -> Self {
126 Self {
127 style: self.style.num(),
128 cramped: self.cramped,
129 }
130 }
131
132 #[inline(always)]
135 #[must_use]
136 pub const fn den(self) -> Self {
137 Self {
138 style: self.style.num(),
139 cramped: true,
140 }
141 }
142
143 #[inline(always)]
145 #[must_use]
146 pub const fn cramp(self) -> Self {
147 Self {
148 style: self.style,
149 cramped: true,
150 }
151 }
152
153 #[inline(always)]
155 #[must_use]
156 pub const fn size_factor(self) -> f64 {
157 self.style.size_factor()
158 }
159}
160
161pub fn style_walk<'a, F>(node: &'a Node, ctx: StyleCtx, visit: &mut F)
184where
185 F: FnMut(&'a Node, StyleCtx),
186{
187 visit(node, ctx);
188 match &node.kind {
189 NodeKind::List(items) => walk_list(items, ctx, visit),
190 NodeKind::Scripts { base, sub, sup, .. } => {
191 if let Some(b) = base {
192 style_walk(b, ctx, visit);
193 }
194 if let Some(s) = sup {
195 style_walk(s, ctx.sup(), visit);
196 }
197 if let Some(s) = sub {
198 style_walk(s, ctx.sub(), visit);
199 }
200 }
201 NodeKind::Frac { num, den, spec } => {
202 let eff = match spec.forced_style {
203 Some(forced) => StyleCtx {
204 style: forced,
205 cramped: ctx.cramped,
206 },
207 None => ctx,
208 };
209 style_walk(num, eff.num(), visit);
210 style_walk(den, eff.den(), visit);
211 }
212 NodeKind::Radical { index, radicand } => {
213 if let Some(ix) = index {
214 style_walk(
215 ix,
216 StyleCtx {
217 style: Style::ScriptScript,
218 cramped: ctx.cramped,
219 },
220 visit,
221 );
222 }
223 style_walk(radicand, ctx.cramp(), visit);
224 }
225 NodeKind::Accent { base, .. } => style_walk(base, ctx.cramp(), visit),
226 NodeKind::LeftRight { body, .. } => walk_list(body, ctx, visit),
227 NodeKind::Text { body } | NodeKind::TextStyled { body, .. } => {
228 walk_list(body, ctx, visit);
229 }
230 NodeKind::MathIsland { body, display } => {
231 let style = if *display {
232 Style::Display
233 } else {
234 Style::Text
235 };
236 walk_list(body, StyleCtx::new(style), visit);
237 }
238 NodeKind::MathFont { body, .. } | NodeKind::Phantom { body, .. } => {
239 style_walk(body, ctx, visit);
240 }
241 NodeKind::Stack {
242 kind,
243 annotation,
244 base,
245 } => {
246 let ann_ctx = match kind {
247 StackKind::Underset => ctx.sub(),
248 StackKind::Stackrel | StackKind::Overset => ctx.sup(),
249 };
250 style_walk(annotation, ann_ctx, visit);
251 style_walk(base, ctx, visit);
252 }
253 NodeKind::XArrow { above, below, .. } => {
254 style_walk(above, ctx.sup(), visit);
257 if let Some(below) = below {
258 style_walk(below, ctx.sub(), visit);
259 }
260 }
261 NodeKind::AlignBlock { lines, .. } => {
262 for line in lines {
264 style_walk(line, ctx, visit);
265 }
266 }
267 NodeKind::Environment { name, rows, .. } => {
268 let cell_ctx = if name == "substack" {
269 StyleCtx::new(Style::Script)
272 } else if name.starts_with("align") {
273 ctx
274 } else {
275 StyleCtx::new(Style::Text)
276 };
277 for row in rows {
278 for cell in row {
279 style_walk(cell, cell_ctx, visit);
280 }
281 }
282 }
283 NodeKind::Symbol { .. }
284 | NodeKind::BigOp { .. }
285 | NodeKind::OpName { .. }
286 | NodeKind::SizedDelim { .. }
287 | NodeKind::TextRun { .. }
288 | NodeKind::StyleChange(_)
289 | NodeKind::SizeChange(_)
290 | NodeKind::AlignChange(_)
291 | NodeKind::LineSpacing(_)
292 | NodeKind::ColorChange(_)
293 | NodeKind::Space(_)
294 | NodeKind::Tie
295 | NodeKind::Linebreak
296 | NodeKind::AlignTab
297 | NodeKind::Fragment(_) => {}
298 }
299}
300
301fn walk_list<'a, F>(items: &'a [Node], mut ctx: StyleCtx, visit: &mut F)
305where
306 F: FnMut(&'a Node, StyleCtx),
307{
308 for item in items {
309 if let NodeKind::StyleChange(style) = &item.kind {
310 visit(item, ctx);
311 ctx = StyleCtx {
312 style: *style,
313 cramped: ctx.cramped,
314 };
315 continue;
316 }
317 style_walk(item, ctx, visit);
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn sup_chain() {
327 assert_eq!(Style::Display.sup(), Style::Script);
328 assert_eq!(Style::Text.sup(), Style::Script);
329 assert_eq!(Style::Script.sup(), Style::ScriptScript);
330 assert_eq!(Style::ScriptScript.sup(), Style::ScriptScript);
331 }
332
333 #[test]
334 fn num_chain() {
335 assert_eq!(Style::Display.num(), Style::Text);
336 assert_eq!(Style::Text.num(), Style::Script);
337 assert_eq!(Style::Script.num(), Style::ScriptScript);
338 assert_eq!(Style::ScriptScript.num(), Style::ScriptScript);
339 }
340
341 #[test]
342 fn sub_is_cramped_sup() {
343 let ctx = StyleCtx::display();
344 assert_eq!(ctx.sub().style, Style::Script);
345 assert!(ctx.sub().cramped);
346 assert!(!ctx.sup().cramped);
347 }
348
349 #[test]
350 fn den_is_cramped_num() {
351 let ctx = StyleCtx::new(Style::Text);
352 assert_eq!(ctx.den().style, Style::Script);
353 assert!(ctx.den().cramped);
354 }
355
356 #[test]
357 fn size_factors_are_cm_10_7_5() {
358 assert_eq!(Style::Display.size_factor(), 1.0);
359 assert_eq!(Style::Text.size_factor(), 1.0);
360 assert_eq!(Style::Script.size_factor(), 0.7);
361 assert_eq!(Style::ScriptScript.size_factor(), 0.5);
362 }
363}