typst_library/math/ir/
multiline.rs1use std::vec;
2
3use typst_syntax::Span;
4use unicode_math_class::MathClass;
5
6use super::item::{FencedBody, FencedItem, MathItem, RawMathItem, SharedFenceSizing};
7use crate::foundations::StyleChain;
8
9#[derive(Debug)]
11pub struct AlignedRow<'a>(Vec<MathItem<'a>>);
12
13impl<'a> AlignedRow<'a> {
14 pub(crate) fn new(columns: Vec<MathItem<'a>>) -> Self {
16 Self(columns)
17 }
18
19 pub fn len(&self) -> usize {
21 self.0.len()
22 }
23
24 pub fn is_empty(&self) -> bool {
26 self.0.is_empty()
27 }
28
29 pub(super) fn pad_to(&mut self, length: usize, styles: StyleChain<'a>) {
31 while self.0.len() < length {
32 self.0.push(MathItem::wrap(Vec::new(), styles));
33 }
34 }
35
36 pub fn iter(&self) -> std::slice::Iter<'_, MathItem<'a>> {
38 self.0.iter()
39 }
40
41 pub fn get(&self, index: usize) -> Option<&MathItem<'a>> {
43 self.0.get(index)
44 }
45}
46
47impl<'a> IntoIterator for AlignedRow<'a> {
48 type Item = MathItem<'a>;
49 type IntoIter = vec::IntoIter<MathItem<'a>>;
50
51 fn into_iter(self) -> Self::IntoIter {
52 self.0.into_iter()
53 }
54}
55
56pub(super) fn expand_multiline_fence<'a>(
59 rows: Vec<AlignedRow<'a>>,
60 mut open: Option<MathItem<'a>>,
61 mut close: Option<MathItem<'a>>,
62 styles: StyleChain<'a>,
63 span: Span,
64) -> Vec<RawMathItem<'a>> {
65 let nrows = rows.len();
66 let mut bodies = Vec::new();
67 let mut row_lengths = Vec::with_capacity(nrows);
68
69 for row in rows {
70 row_lengths.push(row.len());
71 bodies.extend(row);
72 }
73
74 let ncells: usize = row_lengths.iter().sum();
75 let sizing = SharedFenceSizing::new(bodies, styles);
76
77 let mut result = Vec::with_capacity((2 * ncells).saturating_sub(1));
78 let mut body_idx = 0;
79 for (row_idx, &ncols) in row_lengths.iter().enumerate() {
80 if row_idx > 0 {
81 result.push(RawMathItem::Linebreak);
82 }
83
84 for col_idx in 0..ncols {
85 if col_idx > 0 {
86 result.push(RawMathItem::Align);
87 }
88
89 let is_first = row_idx == 0 && col_idx == 0;
90 let is_last = row_idx + 1 == nrows && col_idx + 1 == ncols;
91 result.push(
92 FencedItem::create(
93 open.take_if(|_| is_first),
94 close.take_if(|_| is_last),
95 FencedBody::shared(body_idx, sizing.clone()),
96 true,
97 styles,
98 span,
99 )
100 .into(),
101 );
102
103 body_idx += 1;
104 }
105 }
106
107 result
108}
109
110pub(crate) fn split_at_align<'a, I>(items: I, styles: StyleChain<'a>) -> AlignedRow<'a>
114where
115 I: IntoIterator<Item = RawMathItem<'a>>,
116{
117 let mut cols = vec![vec![]];
118
119 let mut at_boundary = false;
120 for raw in items {
121 match raw {
122 RawMathItem::Align => {
123 cols.push(Vec::new());
124 at_boundary = true;
125 }
126 RawMathItem::Linebreak => unreachable!(),
127 RawMathItem::Item(mut item) => {
128 if at_boundary && !item.is_ignorant() {
131 if cols.len().is_multiple_of(2)
132 && matches!(item.class(), MathClass::Relation | MathClass::Binary)
133 && let MathItem::Component(ref mut comp) = item
134 {
135 comp.props.align_form_infix = true;
136 }
137
138 at_boundary = false;
139 }
140
141 cols.last_mut().unwrap().push(item);
142 }
143 }
144 }
145
146 AlignedRow::new(cols.into_iter().map(|col| MathItem::wrap(col, styles)).collect())
147}