use std::iter;
use std::ops::{Deref, DerefMut};
use smallvec::SmallVec;
use unicode_math_class::MathClass;
use super::item::{MathItem, RawMathItem};
use super::multiline::{AlignedRow, split_at_align};
use crate::foundations::StyleChain;
use crate::math::{MEDIUM, MathSize, THICK, THIN};
pub(crate) enum GroupResult<'a> {
Flat(Vec<MathItem<'a>>),
Multiline(Vec<AlignedRow<'a>>),
}
pub(crate) fn process_group<'a, I>(
items: I,
styles: StyleChain<'a>,
closing: bool,
pad: bool,
) -> GroupResult<'a>
where
I: IntoIterator<Item = RawMathItem<'a>>,
I::IntoIter: ExactSizeIterator,
{
let preprocessed = preprocess(items, closing, false);
if preprocessed.linebreaks > 0 {
let mut row = Vec::new();
let mut rows: Vec<_> = preprocessed
.items
.into_iter()
.chain(iter::once(RawMathItem::Linebreak))
.filter_map(|item| match item {
RawMathItem::Linebreak => Some(split_at_align(row.drain(..), styles)),
other => {
row.push(other);
None
}
})
.collect();
if pad {
let ncols = rows.iter().map(AlignedRow::len).max().unwrap_or_default();
for row in &mut rows {
row.pad_to(ncols, styles);
}
}
GroupResult::Multiline(rows)
} else {
GroupResult::Flat(
preprocessed
.items
.into_iter()
.filter(|item| !matches!(item, RawMathItem::Align))
.map(RawMathItem::into_item)
.collect::<Option<_>>()
.unwrap(),
)
}
}
pub(crate) struct TableCellResult<'a> {
pub sub_columns: AlignedRow<'a>,
pub had_linebreaks: bool,
}
pub(crate) fn process_table_cell<'a, I>(
items: I,
styles: StyleChain<'a>,
) -> TableCellResult<'a>
where
I: IntoIterator<Item = RawMathItem<'a>>,
I::IntoIter: ExactSizeIterator,
{
let preprocessed = preprocess(items, false, true);
let sub_columns = if preprocessed.has_align {
split_at_align(preprocessed.items, styles)
} else {
AlignedRow::new(vec![MathItem::wrap(
preprocessed
.items
.into_iter()
.map(RawMathItem::into_item)
.collect::<Option<_>>()
.unwrap(),
styles,
)])
};
TableCellResult {
sub_columns,
had_linebreaks: preprocessed.had_linebreaks,
}
}
struct Preprocessed<'a> {
items: SmallVec<[RawMathItem<'a>; 8]>,
had_linebreaks: bool,
has_align: bool,
linebreaks: u32,
}
fn preprocess<'a, I>(items: I, closing: bool, strip_linebreaks: bool) -> Preprocessed<'a>
where
I: IntoIterator<Item = RawMathItem<'a>>,
I::IntoIter: ExactSizeIterator,
{
let iter = items.into_iter();
let mut resolved = MathBuffer::with_capacity(iter.len());
let mut last: Option<usize> = None;
let mut space: Option<MathItem> = None;
let mut had_linebreaks = false;
let mut has_align = false;
let mut linebreaks: u32 = 0;
for item in iter {
match item {
RawMathItem::Item(MathItem::Tag(_)) => {
resolved.push(item);
continue;
}
RawMathItem::Item(MathItem::Space) => {
if last.is_some() {
space = item.into_item();
}
continue;
}
RawMathItem::Item(MathItem::Spacing(width, font_size, weak)) => {
last = None;
space = None;
if weak {
let Some(resolved_last) = resolved.last_mut() else {
continue;
};
if let RawMathItem::Item(MathItem::Spacing(
prev_width,
prev_font_size,
true,
)) = resolved_last
{
if prev_width.at(*prev_font_size) < width.at(font_size) {
*prev_width = width;
*prev_font_size = font_size;
}
continue;
}
}
resolved.push(item);
continue;
}
RawMathItem::Align => {
has_align = true;
resolved.push(item);
continue;
}
RawMathItem::Linebreak => {
had_linebreaks = true;
if strip_linebreaks {
continue;
}
linebreaks += 1;
resolved.push(item);
space = None;
last = None;
continue;
}
_ => {}
}
let mut item = item.into_item().unwrap();
if item.class() == MathClass::Vary
&& let Some(RawMathItem::Item(prev)) = last.map(|i| &resolved[i])
&& matches!(
prev.class(),
MathClass::Normal
| MathClass::Alphabetic
| MathClass::Closing
| MathClass::Fence
)
{
item.set_class(MathClass::Binary);
}
if !item.is_ignorant() {
if let Some(i) = last
&& let RawMathItem::Item(ref mut prev) = resolved[i]
&& let Some(s) = spacing(prev, space.take(), &mut item)
{
resolved.insert(i + 1, RawMathItem::Item(s));
}
last = Some(resolved.len());
}
resolved.push(RawMathItem::Item(item));
}
if closing
&& let Some(RawMathItem::Item(item)) = resolved.last_mut()
&& item.rclass() == MathClass::Punctuation
&& item.size().is_none_or(|s| s > MathSize::Script)
{
item.set_rspace(Some(THIN))
} else if let Some(idx) = resolved.last_index()
&& let RawMathItem::Item(MathItem::Spacing(_, _, true)) = resolved.0[idx]
{
resolved.0.remove(idx);
}
if !closing
&& let Some(idx) = resolved.last_index()
&& matches!(resolved.0[idx], RawMathItem::Linebreak)
{
resolved.0.remove(idx);
linebreaks -= 1;
}
Preprocessed {
items: resolved.0,
had_linebreaks,
has_align,
linebreaks,
}
}
fn spacing<'a>(
l: &mut MathItem,
space: Option<MathItem<'a>>,
r: &mut MathItem,
) -> Option<MathItem<'a>> {
use MathClass::*;
let script = |f: &MathItem| f.size().is_some_and(|s| s <= MathSize::Script);
match (l.rclass(), r.lclass()) {
(_, Punctuation) => {}
(Punctuation, _) if !script(l) => l.set_rspace(Some(THIN)),
(Opening, _) | (_, Closing) => {}
(Relation, Relation) => {}
(Relation, _) if !script(l) => l.set_rspace(Some(THICK)),
(_, Relation) if !script(r) => r.set_lspace(Some(THICK)),
(Binary, _) if !script(l) => l.set_rspace(Some(MEDIUM)),
(_, Binary) if !script(r) => r.set_lspace(Some(MEDIUM)),
(Large, Opening | Fence) => {}
(Large, _) => l.set_rspace(Some(THIN)),
(_, Large) => r.set_lspace(Some(THIN)),
_ if (l.is_spaced() || r.is_spaced()) => return space,
_ => {}
};
None
}
struct MathBuffer<'a>(SmallVec<[RawMathItem<'a>; 8]>);
impl<'a> MathBuffer<'a> {
fn with_capacity(size: usize) -> Self {
Self(SmallVec::with_capacity(size))
}
fn last_mut(&mut self) -> Option<&mut RawMathItem<'a>> {
self.0.iter_mut().rev().find(|i| !i.is_ignorant())
}
fn last_index(&self) -> Option<usize> {
self.0.iter().rposition(|i| !i.is_ignorant())
}
}
impl<'a> Deref for MathBuffer<'a> {
type Target = SmallVec<[RawMathItem<'a>; 8]>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DerefMut for MathBuffer<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}