1use std::iter;
2use std::ops::{Deref, DerefMut};
3
4use smallvec::SmallVec;
5use unicode_math_class::MathClass;
6
7use super::item::{MathItem, RawMathItem};
8use super::multiline::{AlignedRow, split_at_align};
9use crate::foundations::StyleChain;
10use crate::math::{MEDIUM, MathSize, THICK, THIN};
11
12pub(crate) enum GroupResult<'a> {
14 Flat(Vec<MathItem<'a>>),
17 Multiline(Vec<AlignedRow<'a>>),
20}
21
22pub(crate) fn process_group<'a, I>(
30 items: I,
31 styles: StyleChain<'a>,
32 closing: bool,
33 pad: bool,
34 split: bool,
35) -> GroupResult<'a>
36where
37 I: IntoIterator<Item = RawMathItem<'a>>,
38 I::IntoIter: ExactSizeIterator,
39{
40 let preprocessed = preprocess(items, closing, false);
41 if preprocessed.linebreaks > 0 || (split && preprocessed.has_align) {
42 let mut row = Vec::new();
43 let mut rows: Vec<_> = preprocessed
44 .items
45 .into_iter()
46 .chain(iter::once(RawMathItem::Linebreak))
47 .filter_map(|item| match item {
48 RawMathItem::Linebreak => Some(split_at_align(row.drain(..), styles)),
49 other => {
50 row.push(other);
51 None
52 }
53 })
54 .collect();
55
56 if pad {
57 let ncols = rows.iter().map(AlignedRow::len).max().unwrap_or_default();
58 for row in &mut rows {
59 row.pad_to(ncols, styles);
60 }
61 }
62
63 GroupResult::Multiline(rows)
64 } else {
65 GroupResult::Flat(
66 preprocessed
67 .items
68 .into_iter()
69 .filter(|item| !matches!(item, RawMathItem::Align))
70 .map(RawMathItem::into_item)
71 .collect::<Option<_>>()
72 .unwrap(),
73 )
74 }
75}
76
77pub(crate) struct TableCellResult<'a> {
79 pub sub_columns: AlignedRow<'a>,
81 pub had_linebreaks: bool,
83}
84
85pub(crate) fn process_table_cell<'a, I>(
87 items: I,
88 styles: StyleChain<'a>,
89) -> TableCellResult<'a>
90where
91 I: IntoIterator<Item = RawMathItem<'a>>,
92 I::IntoIter: ExactSizeIterator,
93{
94 let preprocessed = preprocess(items, false, true);
95 let sub_columns = if preprocessed.has_align {
96 split_at_align(preprocessed.items, styles)
97 } else {
98 AlignedRow::new(vec![MathItem::wrap(
99 preprocessed
100 .items
101 .into_iter()
102 .map(RawMathItem::into_item)
103 .collect::<Option<_>>()
104 .unwrap(),
105 styles,
106 )])
107 };
108 TableCellResult {
109 sub_columns,
110 had_linebreaks: preprocessed.had_linebreaks,
111 }
112}
113
114struct Preprocessed<'a> {
116 items: SmallVec<[RawMathItem<'a>; 8]>,
117 had_linebreaks: bool,
118 has_align: bool,
119 linebreaks: u32,
120}
121
122fn preprocess<'a, I>(items: I, closing: bool, strip_linebreaks: bool) -> Preprocessed<'a>
136where
137 I: IntoIterator<Item = RawMathItem<'a>>,
138 I::IntoIter: ExactSizeIterator,
139{
140 let iter = items.into_iter();
141 let mut resolved = MathBuffer::with_capacity(iter.len());
142
143 let mut last: Option<usize> = None;
144 let mut space: Option<MathItem> = None;
145 let mut had_linebreaks = false;
146 let mut has_align = false;
147 let mut linebreaks: u32 = 0;
148
149 for item in iter {
150 match item {
151 RawMathItem::Item(MathItem::Tag(_)) => {
153 resolved.push(item);
154 continue;
155 }
156 RawMathItem::Item(MathItem::Space) => {
158 if last.is_some() {
159 space = item.into_item();
160 }
161 continue;
162 }
163
164 RawMathItem::Item(MathItem::Spacing(width, font_size, weak)) => {
166 last = None;
167 space = None;
168
169 if weak {
170 let Some(resolved_last) = resolved.last_mut() else {
171 continue;
172 };
173 if let RawMathItem::Item(MathItem::Spacing(
174 prev_width,
175 prev_font_size,
176 true,
177 )) = resolved_last
178 {
179 if prev_width.at(*prev_font_size) < width.at(font_size) {
180 *prev_width = width;
181 *prev_font_size = font_size;
182 }
183 continue;
184 }
185 }
186
187 resolved.push(item);
188 continue;
189 }
190
191 RawMathItem::Align => {
193 has_align = true;
194 resolved.push(item);
195 continue;
196 }
197
198 RawMathItem::Linebreak => {
200 had_linebreaks = true;
201 if strip_linebreaks {
202 continue;
203 }
204 linebreaks += 1;
205 resolved.push(item);
206 space = None;
207 last = None;
208 continue;
209 }
210
211 _ => {}
212 }
213
214 let mut item = item.into_item().unwrap();
215
216 if item.class() == MathClass::Vary
219 && let Some(RawMathItem::Item(prev)) = last.map(|i| &resolved[i])
220 && matches!(
221 prev.class(),
222 MathClass::Normal
223 | MathClass::Alphabetic
224 | MathClass::Closing
225 | MathClass::Fence
226 )
227 {
228 item.set_class(MathClass::Binary);
229 }
230
231 if !item.is_ignorant() {
233 if let Some(i) = last
234 && let RawMathItem::Item(ref mut prev) = resolved[i]
235 && let Some(s) = spacing(prev, space.take(), &mut item)
236 {
237 resolved.insert(i + 1, RawMathItem::Item(s));
238 }
239
240 last = Some(resolved.len());
241 }
242
243 resolved.push(RawMathItem::Item(item));
244 }
245
246 if closing
248 && let Some(RawMathItem::Item(item)) = resolved.last_mut()
249 && item.rclass() == MathClass::Punctuation
250 && item.size().is_none_or(|s| s > MathSize::Script)
251 {
252 item.set_rspace(Some(THIN))
253 } else if let Some(idx) = resolved.last_index()
254 && let RawMathItem::Item(MathItem::Spacing(_, _, true)) = resolved.0[idx]
255 {
256 resolved.0.remove(idx);
257 }
258
259 if !closing
261 && let Some(idx) = resolved.last_index()
262 && matches!(resolved.0[idx], RawMathItem::Linebreak)
263 {
264 resolved.0.remove(idx);
265 linebreaks -= 1;
266 }
267
268 Preprocessed {
269 items: resolved.0,
270 had_linebreaks,
271 has_align,
272 linebreaks,
273 }
274}
275
276fn spacing<'a>(
278 l: &mut MathItem,
279 space: Option<MathItem<'a>>,
280 r: &mut MathItem,
281) -> Option<MathItem<'a>> {
282 use MathClass::*;
283
284 let script = |f: &MathItem| f.size().is_some_and(|s| s <= MathSize::Script);
285
286 match (l.rclass(), r.lclass()) {
287 (_, Punctuation) => {}
290 (Punctuation, _) if !script(l) => l.set_rspace(Some(THIN)),
291
292 (Opening, _) | (_, Closing) => {}
294
295 (Relation, Relation) => {}
298 (Relation, _) if !script(l) => l.set_rspace(Some(THICK)),
299 (_, Relation) if !script(r) => r.set_lspace(Some(THICK)),
300
301 (Binary, _) if !script(l) => l.set_rspace(Some(MEDIUM)),
303 (_, Binary) if !script(r) => r.set_lspace(Some(MEDIUM)),
304
305 (Large, Opening | Fence) => {}
308 (Large, _) => l.set_rspace(Some(THIN)),
309
310 (_, Large) => r.set_lspace(Some(THIN)),
311
312 _ if (l.is_spaced() || r.is_spaced()) => return space,
314
315 _ => {}
316 };
317
318 None
319}
320
321struct MathBuffer<'a>(SmallVec<[RawMathItem<'a>; 8]>);
324
325impl<'a> MathBuffer<'a> {
326 fn with_capacity(size: usize) -> Self {
328 Self(SmallVec::with_capacity(size))
329 }
330
331 fn last_mut(&mut self) -> Option<&mut RawMathItem<'a>> {
333 self.0.iter_mut().rev().find(|i| !i.is_ignorant())
334 }
335
336 fn last_index(&self) -> Option<usize> {
338 self.0.iter().rposition(|i| !i.is_ignorant())
339 }
340}
341
342impl<'a> Deref for MathBuffer<'a> {
343 type Target = SmallVec<[RawMathItem<'a>; 8]>;
344
345 fn deref(&self) -> &Self::Target {
346 &self.0
347 }
348}
349
350impl<'a> DerefMut for MathBuffer<'a> {
351 fn deref_mut(&mut self) -> &mut Self::Target {
352 &mut self.0
353 }
354}