1use rust_latex_parser::{AccentKind, EqNode, MathFontKind, MatrixKind};
2
3use crate::mathfont;
4use crate::rendered_block::RenderedBlock;
5
6pub fn layout(node: &EqNode) -> RenderedBlock {
8 match node {
9 EqNode::Text(s) => layout_text(s),
10 EqNode::Space(pts) => layout_space(*pts),
11 EqNode::Seq(children) => layout_seq(children),
12 EqNode::Frac(num, den) => layout_frac(num, den),
13 EqNode::Sup(base, sup) => layout_sup(base, sup),
14 EqNode::Sub(base, sub) => layout_sub(base, sub),
15 EqNode::SupSub(base, sup, sub) => layout_supsub(base, sup, sub),
16 EqNode::Sqrt(body) => layout_sqrt(body),
17 EqNode::BigOp {
18 symbol,
19 lower,
20 upper,
21 } => layout_bigop(symbol, lower, upper),
22 EqNode::Accent(body, kind) => layout_accent(body, kind),
23 EqNode::Limit { name, lower } => layout_limit(name, lower),
24 EqNode::TextBlock(s) => RenderedBlock::from_text(s),
25 EqNode::MathFont { kind, content } => layout_mathfont(kind, content),
26 EqNode::Delimited {
27 left,
28 right,
29 content,
30 } => layout_delimited(left, right, content),
31 EqNode::Matrix { kind, rows } => layout_matrix(kind, rows),
32 EqNode::Cases { rows } => layout_cases(rows),
33 EqNode::Binom(top, bottom) => layout_binom(top, bottom),
34 EqNode::Brace {
35 content,
36 label,
37 over,
38 } => layout_brace(content, label, over),
39 EqNode::StackRel {
40 base,
41 annotation,
42 over,
43 } => layout_stackrel(base, annotation, over),
44 }
45}
46
47fn layout_text(s: &str) -> RenderedBlock {
48 RenderedBlock::from_text(s)
49}
50
51fn to_superscript_char(ch: char) -> Option<char> {
53 match ch {
54 '0' => Some('⁰'),
55 '1' => Some('¹'),
56 '2' => Some('²'),
57 '3' => Some('³'),
58 '4' => Some('⁴'),
59 '5' => Some('⁵'),
60 '6' => Some('⁶'),
61 '7' => Some('⁷'),
62 '8' => Some('⁸'),
63 '9' => Some('⁹'),
64 '+' => Some('⁺'),
65 '-' => Some('⁻'),
66 '=' => Some('⁼'),
67 '(' => Some('⁽'),
68 ')' => Some('⁾'),
69 'n' => Some('ⁿ'),
70 'i' => Some('ⁱ'),
71 _ => None,
72 }
73}
74
75fn to_subscript_char(ch: char) -> Option<char> {
77 match ch {
78 '0' => Some('₀'),
79 '1' => Some('₁'),
80 '2' => Some('₂'),
81 '3' => Some('₃'),
82 '4' => Some('₄'),
83 '5' => Some('₅'),
84 '6' => Some('₆'),
85 '7' => Some('₇'),
86 '8' => Some('₈'),
87 '9' => Some('₉'),
88 '+' => Some('₊'),
89 '-' => Some('₋'),
90 '=' => Some('₌'),
91 '(' => Some('₍'),
92 ')' => Some('₎'),
93 'a' => Some('ₐ'),
94 'e' => Some('ₑ'),
95 'h' => Some('ₕ'),
96 'i' => Some('ᵢ'),
97 'j' => Some('ⱼ'),
98 'k' => Some('ₖ'),
99 'l' => Some('ₗ'),
100 'm' => Some('ₘ'),
101 'n' => Some('ₙ'),
102 'o' => Some('ₒ'),
103 'p' => Some('ₚ'),
104 'r' => Some('ᵣ'),
105 's' => Some('ₛ'),
106 't' => Some('ₜ'),
107 'u' => Some('ᵤ'),
108 'v' => Some('ᵥ'),
109 'x' => Some('ₓ'),
110 _ => None,
111 }
112}
113
114fn try_unicode_superscript(node: &EqNode) -> Option<String> {
117 let text = extract_flat_text(node)?;
118 text.chars().map(to_superscript_char).collect()
119}
120
121fn try_unicode_subscript(node: &EqNode) -> Option<String> {
123 let text = extract_flat_text(node)?;
124 text.chars().map(to_subscript_char).collect()
125}
126
127fn extract_flat_text(node: &EqNode) -> Option<String> {
129 match node {
130 EqNode::Text(s) => Some(s.clone()),
131 EqNode::Seq(children) => {
132 let mut result = String::new();
133 for child in children {
134 match child {
135 EqNode::Text(s) => result.push_str(s),
136 EqNode::Space(_) => {} _ => return None,
138 }
139 }
140 if result.is_empty() {
141 None
142 } else {
143 Some(result)
144 }
145 }
146 _ => None,
147 }
148}
149
150fn layout_space(pts: f32) -> RenderedBlock {
154 if pts <= 0.0 || pts < 2.0 {
155 RenderedBlock::empty()
156 } else if pts >= 18.0 {
157 RenderedBlock::from_text(" ")
159 } else {
160 RenderedBlock::from_char(' ')
161 }
162}
163
164fn is_space_like(node: &EqNode) -> bool {
166 match node {
167 EqNode::Space(_) => true,
168 EqNode::Text(s) => s.chars().all(|c| c == ' '),
169 _ => false,
170 }
171}
172
173fn layout_seq(children: &[EqNode]) -> RenderedBlock {
174 let flat = flatten_seq(children);
176 let mut result = RenderedBlock::empty();
178 let mut prev_was_space = false;
179 for child in &flat {
180 if is_space_like(child) {
181 if !prev_was_space {
182 prev_was_space = true;
183 result = result.beside(&RenderedBlock::from_char(' '));
184 }
185 continue;
186 }
187 prev_was_space = false;
188 let block = layout(child);
189 result = result.beside(&block);
190 }
191 result
192}
193
194fn trim_node(node: &EqNode) -> EqNode {
197 match node {
198 EqNode::Seq(children) => {
199 let trimmed: Vec<EqNode> = children
200 .iter()
201 .map(|c| match c {
202 EqNode::Text(s) => EqNode::Text(s.trim().to_string()),
203 other => other.clone(),
204 })
205 .filter(|c| !is_space_like(c) || !matches!(c, EqNode::Text(s) if s.is_empty()))
206 .collect();
207 let start = trimmed.iter().position(|c| !is_space_like(c)).unwrap_or(0);
209 let end = trimmed
210 .iter()
211 .rposition(|c| !is_space_like(c))
212 .map_or(0, |i| i + 1);
213 if start >= end {
214 return EqNode::Seq(vec![]);
215 }
216 EqNode::Seq(trimmed[start..end].to_vec())
217 }
218 EqNode::Text(s) => EqNode::Text(s.trim().to_string()),
219 other => other.clone(),
220 }
221}
222
223fn flatten_seq(children: &[EqNode]) -> Vec<&EqNode> {
225 let mut result = Vec::new();
226 for child in children {
227 if let EqNode::Seq(inner) = child {
228 result.extend(flatten_seq(inner));
229 } else {
230 result.push(child);
231 }
232 }
233 result
234}
235
236fn layout_frac(num: &EqNode, den: &EqNode) -> RenderedBlock {
237 let num_block = layout(num);
238 let den_block = layout(den);
239
240 let bar_width = num_block.width().max(den_block.width()) + 2; let bar = RenderedBlock::hline('─', bar_width);
242
243 let num_centered = num_block.center_in(bar_width);
244 let den_centered = den_block.center_in(bar_width);
245
246 let top = RenderedBlock::above(&num_centered, &bar, 0);
248 let baseline_row = top.height() - 1; RenderedBlock::above(&top, &den_centered, baseline_row)
250}
251
252fn layout_sup(base: &EqNode, sup: &EqNode) -> RenderedBlock {
253 if let Some(sup_text) = try_unicode_superscript(sup) {
255 let base_block = layout(base);
256 let sup_block = RenderedBlock::from_text(&sup_text);
257 return base_block.beside(&sup_block);
258 }
259
260 let base_block = layout(base);
261 let sup_block = layout(sup);
262
263 let can_overlap = base_block.height() > 1;
264 let sup_above = if can_overlap {
265 sup_block.height().saturating_sub(1)
266 } else {
267 sup_block.height()
268 };
269
270 let rows = build_sup_sub_grid(
271 base_block.cells(),
272 base_block.width(),
273 base_block.baseline(),
274 sup_block.cells(),
275 sup_block.width(),
276 None,
277 0,
278 );
279
280 let total_height = rows.len();
281 let baseline = sup_above + base_block.baseline();
282
283 RenderedBlock::new(rows, baseline.min(total_height.saturating_sub(1)))
284}
285
286fn layout_sub(base: &EqNode, sub: &EqNode) -> RenderedBlock {
287 if let Some(sub_text) = try_unicode_subscript(sub) {
289 let base_block = layout(base);
290 let sub_block = RenderedBlock::from_text(&sub_text);
291 return base_block.beside(&sub_block);
292 }
293
294 let base_block = layout(base);
295 let sub_block = layout(sub);
296
297 let rows = build_sup_sub_grid(
298 base_block.cells(),
299 base_block.width(),
300 base_block.baseline(),
301 &[],
302 0,
303 Some((sub_block.cells(), sub_block.width())),
304 0,
305 );
306
307 let baseline = base_block.baseline();
308 let total_height = rows.len();
309
310 RenderedBlock::new(rows, baseline.min(total_height.saturating_sub(1)))
311}
312
313fn layout_supsub(base: &EqNode, sup: &EqNode, sub: &EqNode) -> RenderedBlock {
314 let sup_inline = try_unicode_superscript(sup);
316 let sub_inline = try_unicode_subscript(sub);
317
318 if let (Some(sup_text), Some(sub_text)) = (&sup_inline, &sub_inline) {
319 let base_block = layout(base);
320 let scripts = format!("{}{}", sup_text, sub_text);
321 return base_block.beside(&RenderedBlock::from_text(&scripts));
326 }
327
328 let base_block = layout(base);
330 let sup_block = layout(sup);
331 let sub_block = layout(sub);
332
333 let can_overlap_sup = base_block.height() > 1;
334 let sup_above = if can_overlap_sup {
335 sup_block.height().saturating_sub(1)
336 } else {
337 sup_block.height()
338 };
339
340 let rows = build_sup_sub_grid(
341 base_block.cells(),
342 base_block.width(),
343 base_block.baseline(),
344 sup_block.cells(),
345 sup_block.width(),
346 Some((sub_block.cells(), sub_block.width())),
347 0,
348 );
349
350 let total_height = rows.len();
351 let baseline = sup_above + base_block.baseline();
352
353 RenderedBlock::new(rows, baseline.min(total_height.saturating_sub(1)))
354}
355
356fn build_sup_sub_grid(
376 base_cells: &[Vec<String>],
377 base_width: usize,
378 _base_baseline: usize,
379 sup_cells: &[Vec<String>],
380 sup_width: usize,
381 sub: Option<(&[Vec<String>], usize)>,
382 _sub_baseline: usize,
383) -> Vec<Vec<String>> {
384 let base_height = base_cells.len();
385 let sup_height = sup_cells.len();
386 let (sub_cells, sub_width) = sub.unwrap_or((&[], 0));
387 let sub_height = sub_cells.len();
388 let has_sup = sup_height > 0;
389 let has_sub = sub_height > 0;
390
391 let script_width = sup_width.max(sub_width);
392
393 let can_overlap_sup = has_sup && base_height > 1;
396 let can_overlap_sub = has_sub && base_height > 1 && !(has_sup && base_height <= 2);
397
398 let sup_above = if can_overlap_sup {
399 sup_height.saturating_sub(1)
400 } else {
401 sup_height
402 };
403
404 let sub_below = if can_overlap_sub {
405 sub_height.saturating_sub(1)
406 } else {
407 sub_height
408 };
409
410 let total_height = sup_above + base_height + sub_below;
411 let mut rows = Vec::with_capacity(total_height);
412
413 let empty_script = || std::iter::repeat_n(" ".to_string(), script_width);
414
415 fn append_script_row(
417 row: &mut Vec<String>,
418 cells: &[Vec<String>],
419 idx: usize,
420 script_width: usize,
421 ) {
422 if idx < cells.len() {
423 row.extend(cells[idx].iter().cloned());
424 let used = cells[idx].len();
425 row.extend(std::iter::repeat_n(
426 " ".to_string(),
427 script_width.saturating_sub(used),
428 ));
429 } else {
430 row.extend(std::iter::repeat_n(" ".to_string(), script_width));
431 }
432 }
433
434 for r in 0..sup_above {
436 let mut row = vec![" ".to_string(); base_width];
437 append_script_row(&mut row, sup_cells, r, script_width);
438 rows.push(row);
439 }
440
441 for (r, base_row) in base_cells.iter().enumerate().take(base_height) {
443 let mut row = base_row.clone();
444
445 let sup_idx = if can_overlap_sup {
447 sup_above + r
448 } else {
449 usize::MAX
450 };
451 let sub_overlap_start = if can_overlap_sub {
453 base_height.saturating_sub(sub_height)
454 } else {
455 usize::MAX
456 };
457 let sub_idx = if r >= sub_overlap_start && can_overlap_sub {
458 r - sub_overlap_start
459 } else {
460 usize::MAX
461 };
462
463 if sup_idx < sup_height {
464 append_script_row(&mut row, sup_cells, sup_idx, script_width);
465 } else if sub_idx < sub_height {
466 append_script_row(&mut row, sub_cells, sub_idx, script_width);
467 } else {
468 row.extend(empty_script());
469 }
470
471 rows.push(row);
472 }
473
474 let sub_start = if can_overlap_sub {
476 sub_height.min(base_height)
477 } else {
478 0
479 };
480 for r in sub_start..sub_height {
481 let mut row = vec![" ".to_string(); base_width];
482 append_script_row(&mut row, sub_cells, r, script_width);
483 rows.push(row);
484 }
485
486 rows
487}
488
489fn layout_sqrt(body: &EqNode) -> RenderedBlock {
490 let body_block = layout(body);
491 let body_h = body_block.height();
492 let body_w = body_block.width();
493
494 if body_h == 1 {
503 let mut rows = Vec::with_capacity(2);
505 let mut top = vec![" ".to_string()];
507 top.extend(std::iter::repeat_n("─".to_string(), body_w));
508 rows.push(top);
509 let mut bot = vec!["√".to_string()];
511 bot.extend(body_block.cells()[0].iter().cloned());
512 rows.push(bot);
513 RenderedBlock::new(rows, 1) } else {
515 let mut rows = Vec::with_capacity(body_h + 1);
517
518 let mut top = vec![" ".to_string()];
520 top.extend(std::iter::repeat_n("─".to_string(), body_w));
521 rows.push(top);
522
523 for r in 0..body_h {
525 let radical_char = if r == body_h - 1 { "√" } else { "│" };
526 let mut row = vec![radical_char.to_string()];
527 row.extend(body_block.cells()[r].iter().cloned());
528 rows.push(row);
529 }
530
531 let baseline = 1 + body_block.baseline();
532 RenderedBlock::new(rows, baseline)
533 }
534}
535
536fn build_bigop_symbol(symbol: &str) -> RenderedBlock {
538 match symbol {
539 "∫" => {
540 let rows = vec![
542 vec!["⌠".to_string()],
543 vec!["⎮".to_string()],
544 vec!["⌡".to_string()],
545 ];
546 RenderedBlock::new(rows, 1) }
548 "∬" => {
549 let rows = vec![
550 vec!["⌠".to_string(), "⌠".to_string()],
551 vec!["⎮".to_string(), "⎮".to_string()],
552 vec!["⌡".to_string(), "⌡".to_string()],
553 ];
554 RenderedBlock::new(rows, 1)
555 }
556 "∮" => {
557 let rows = vec![
559 vec!["⌠".to_string()],
560 vec!["⎮".to_string()],
561 vec!["⌡".to_string()],
562 ];
563 RenderedBlock::new(rows, 1)
564 }
565 _ => {
566 RenderedBlock::from_text(symbol)
568 }
569 }
570}
571
572fn layout_bigop(
573 symbol: &str,
574 lower: &Option<Box<EqNode>>,
575 upper: &Option<Box<EqNode>>,
576) -> RenderedBlock {
577 let op_block = build_bigop_symbol(symbol);
578
579 let upper_block = upper.as_ref().map(|u| layout(u));
580 let lower_block = lower.as_ref().map(|l| layout(l));
581
582 let max_width = [
583 op_block.width(),
584 upper_block.as_ref().map_or(0, |b| b.width()),
585 lower_block.as_ref().map_or(0, |b| b.width()),
586 ]
587 .into_iter()
588 .max()
589 .unwrap_or(1);
590
591 let op_centered = op_block.center_in(max_width);
592
593 let mut result = if let Some(ub) = &upper_block {
594 let ub_centered = ub.center_in(max_width);
595 let baseline = ub_centered.height(); RenderedBlock::above(&ub_centered, &op_centered, baseline)
597 } else {
598 op_centered.clone()
599 };
600
601 let op_mid = upper_block.as_ref().map_or(0, |b| b.height()) + op_block.height() / 2;
603
604 if let Some(lb) = &lower_block {
605 let lb_centered = lb.center_in(max_width);
606 result = RenderedBlock::above(&result, &lb_centered, op_mid);
607 }
608
609 RenderedBlock::new(result.cells().to_vec(), op_mid)
610}
611
612fn layout_accent(body: &EqNode, kind: &AccentKind) -> RenderedBlock {
613 let body_block = layout(body);
614 let w = body_block.width();
615
616 let accent_block = match kind {
617 AccentKind::Bar => {
618 RenderedBlock::hline('‾', w)
620 }
621 AccentKind::Hat => {
622 if w <= 1 {
623 RenderedBlock::from_char('^')
624 } else if w <= 3 {
625 RenderedBlock::from_text("/\\").center_in(w)
626 } else {
627 let inner = w.saturating_sub(2);
629 let hat_str: String = std::iter::once('/')
630 .chain(std::iter::repeat_n('‾', inner))
631 .chain(std::iter::once('\\'))
632 .collect();
633 RenderedBlock::from_text(&hat_str)
634 }
635 }
636 AccentKind::Tilde => {
637 if w <= 1 {
638 RenderedBlock::from_char('~')
639 } else {
640 RenderedBlock::hline('~', w)
642 }
643 }
644 AccentKind::Vec => {
645 if w <= 1 {
646 RenderedBlock::from_char('→')
647 } else {
648 let shaft = w.saturating_sub(1);
650 let arrow_str: String = std::iter::repeat_n('─', shaft)
651 .chain(std::iter::once('→'))
652 .collect();
653 RenderedBlock::from_text(&arrow_str)
654 }
655 }
656 AccentKind::Dot => RenderedBlock::from_char('˙').center_in(w),
657 AccentKind::DoubleDot => RenderedBlock::from_text("¨").center_in(w),
658 };
659
660 let baseline = accent_block.height() + body_block.baseline();
661 RenderedBlock::above(&accent_block, &body_block, baseline)
662}
663
664fn layout_limit(name: &str, lower: &Option<Box<EqNode>>) -> RenderedBlock {
665 let name_block = RenderedBlock::from_text(name);
666
667 if let Some(low) = lower {
668 let low_block = layout(low);
669 let max_width = name_block.width().max(low_block.width());
670 let name_centered = name_block.center_in(max_width);
671 let low_centered = low_block.center_in(max_width);
672 let baseline = name_centered.height() - 1;
673 RenderedBlock::above(&name_centered, &low_centered, baseline)
674 } else {
675 name_block
676 }
677}
678
679fn layout_mathfont(kind: &MathFontKind, content: &EqNode) -> RenderedBlock {
680 if let Some(text) = extract_flat_text(content) {
682 let mapped = mathfont::map_str(kind, &text);
683 RenderedBlock::from_text(&mapped)
684 } else {
685 layout(content)
687 }
688}
689
690fn layout_delimited(left: &str, right: &str, content: &EqNode) -> RenderedBlock {
691 let content_block = layout(content);
692 let h = content_block.height();
693
694 let left_block = build_delimiter(left, h);
695 let right_block = build_delimiter(right, h);
696
697 left_block.beside(&content_block).beside(&right_block)
698}
699
700fn build_delimiter(delim: &str, height: usize) -> RenderedBlock {
702 if delim == "." || delim.is_empty() {
703 return RenderedBlock::new(vec![vec![" ".to_string()]; height], height / 2);
705 }
706
707 if height <= 1 {
708 return RenderedBlock::new(vec![vec![delim.to_string()]], 0);
709 }
710
711 let (top, mid, bot) = match delim {
712 "(" => ("⎛", "⎜", "⎝"),
713 ")" => ("⎞", "⎟", "⎠"),
714 "[" => ("⎡", "⎢", "⎣"),
715 "]" => ("⎤", "⎥", "⎦"),
716 "{" => ("⎧", "⎨", "⎩"),
717 "}" => ("⎫", "⎬", "⎭"),
718 "|" => ("│", "│", "│"),
719 "‖" => ("‖", "‖", "‖"),
720 _ => (delim, delim, delim),
721 };
722
723 let mut rows = Vec::with_capacity(height);
724 rows.push(vec![top.to_string()]);
725 for _ in 1..height.saturating_sub(1) {
726 rows.push(vec![mid.to_string()]);
727 }
728 if height > 1 {
729 rows.push(vec![bot.to_string()]);
730 }
731
732 RenderedBlock::new(rows, height / 2)
733}
734
735fn layout_matrix(kind: &MatrixKind, matrix_rows: &[Vec<EqNode>]) -> RenderedBlock {
736 if matrix_rows.is_empty() {
737 return RenderedBlock::empty();
738 }
739
740 let rendered: Vec<Vec<RenderedBlock>> = matrix_rows
742 .iter()
743 .map(|row| row.iter().map(|cell| layout(&trim_node(cell))).collect())
744 .collect();
745
746 let num_cols = rendered.iter().map(|r| r.len()).max().unwrap_or(0);
747
748 let mut col_widths = vec![0usize; num_cols];
750 for row in &rendered {
751 for (c, cell) in row.iter().enumerate() {
752 col_widths[c] = col_widths[c].max(cell.width());
753 }
754 }
755
756 let col_sep = 2; let separator = RenderedBlock::from_text(&" ".repeat(col_sep));
760
761 let mut row_blocks: Vec<RenderedBlock> = Vec::new();
762
763 for row in &rendered {
764 let mut row_block = RenderedBlock::empty();
765 for (c, cell) in row.iter().enumerate() {
766 let padded = cell.center_in(col_widths[c]);
767 if !row_block.is_empty() {
768 row_block = row_block.beside(&separator);
769 }
770 row_block = row_block.beside(&padded);
771 }
772 for w in col_widths.iter().take(num_cols).skip(row.len()) {
774 row_block = row_block.beside(&separator);
775 row_block = row_block.beside(&RenderedBlock::from_text(&" ".repeat(*w)));
776 }
777 row_blocks.push(row_block);
778 }
779
780 let grid_width = row_blocks.iter().map(|r| r.width()).max().unwrap_or(0);
782
783 let mut grid = RenderedBlock::empty();
784 for row_block in &row_blocks {
785 let padded = row_block.center_in(grid_width);
786 if grid.is_empty() {
787 grid = padded;
788 } else {
789 let baseline = grid.height() / 2; grid = RenderedBlock::above(&grid, &padded, baseline);
791 }
792 }
793
794 let total_height = grid.height();
796 let grid = RenderedBlock::new(grid.cells().to_vec(), total_height / 2);
797
798 let (left, right) = match kind {
800 MatrixKind::Paren => ("(", ")"),
801 MatrixKind::Bracket => ("[", "]"),
802 MatrixKind::Brace => ("{", "}"),
803 MatrixKind::VBar => ("|", "|"),
804 MatrixKind::DoubleVBar => ("‖", "‖"),
805 MatrixKind::Plain => ("", ""),
806 };
807
808 if left.is_empty() {
809 grid
810 } else {
811 let left_d = build_delimiter(left, total_height);
812 let right_d = build_delimiter(right, total_height);
813 left_d.beside(&grid).beside(&right_d)
814 }
815}
816
817fn layout_cases(rows: &[(EqNode, Option<EqNode>)]) -> RenderedBlock {
818 let rendered: Vec<RenderedBlock> = rows
820 .iter()
821 .map(|(val, cond)| {
822 let val_block = layout(val);
823 if let Some(c) = cond {
824 let cond_block = layout(c);
825 val_block
826 .beside(&RenderedBlock::from_text(" if "))
827 .beside(&cond_block)
828 } else {
829 val_block
830 }
831 })
832 .collect();
833
834 let max_width = rendered.iter().map(|b| b.width()).max().unwrap_or(0);
835 let mut grid = RenderedBlock::empty();
836 for row_block in &rendered {
837 let padded = RenderedBlock::new(row_block.cells().to_vec(), row_block.baseline());
838 let full_row = RenderedBlock::new(
840 padded
841 .cells()
842 .iter()
843 .map(|r| {
844 let mut r = r.clone();
845 r.extend(std::iter::repeat_n(
846 " ".to_string(),
847 max_width.saturating_sub(r.len()),
848 ));
849 r
850 })
851 .collect(),
852 padded.baseline(),
853 );
854 if grid.is_empty() {
855 grid = full_row;
856 } else {
857 grid = RenderedBlock::above(&grid, &full_row, grid.height() / 2);
858 }
859 }
860
861 let total_height = grid.height();
862 let grid = RenderedBlock::new(grid.cells().to_vec(), total_height / 2);
863 let left_brace = build_delimiter("{", total_height);
864 left_brace.beside(&grid)
865}
866
867fn layout_binom(top: &EqNode, bottom: &EqNode) -> RenderedBlock {
868 let top_block = layout(top);
870 let bot_block = layout(bottom);
871
872 let inner_width = top_block.width().max(bot_block.width());
873 let top_centered = top_block.center_in(inner_width);
874 let bot_centered = bot_block.center_in(inner_width);
875
876 let baseline = top_centered.height();
877 let stacked = RenderedBlock::above(&top_centered, &bot_centered, baseline - 1);
878
879 let h = stacked.height();
880 let left = build_delimiter("(", h);
881 let right = build_delimiter(")", h);
882 left.beside(&stacked).beside(&right)
883}
884
885fn layout_brace(content: &EqNode, label: &Option<Box<EqNode>>, over: &bool) -> RenderedBlock {
886 let content_block = layout(content);
887 let w = content_block.width();
888
889 let brace_str = if *over { "⏞" } else { "⏟" };
891 let brace_block = RenderedBlock::hline(brace_str.chars().next().unwrap(), w);
892
893 if let Some(lbl) = label {
894 let label_block = layout(lbl).center_in(w);
895 if *over {
896 let top = RenderedBlock::above(&label_block, &brace_block, label_block.height());
897 let baseline = top.height() + content_block.baseline();
898 RenderedBlock::above(&top, &content_block, baseline)
899 } else {
900 let bottom = RenderedBlock::above(&brace_block, &label_block, 0);
901 let baseline = content_block.baseline();
902 RenderedBlock::above(&content_block, &bottom, baseline)
903 }
904 } else if *over {
905 let baseline = brace_block.height() + content_block.baseline();
906 RenderedBlock::above(&brace_block, &content_block, baseline)
907 } else {
908 let baseline = content_block.baseline();
909 RenderedBlock::above(&content_block, &brace_block, baseline)
910 }
911}
912
913fn layout_stackrel(base: &EqNode, annotation: &EqNode, over: &bool) -> RenderedBlock {
914 let base_block = layout(base);
915 let ann_block = layout(annotation);
916 let w = base_block.width().max(ann_block.width());
917 let base_centered = base_block.center_in(w);
918 let ann_centered = ann_block.center_in(w);
919
920 if *over {
921 let baseline = ann_centered.height() + base_block.baseline();
922 RenderedBlock::above(&ann_centered, &base_centered, baseline)
923 } else {
924 let baseline = base_block.baseline();
925 RenderedBlock::above(&base_centered, &ann_centered, baseline)
926 }
927}