use std::iter::once; use crate::layout::AlignElem; use super::*; pub const TIGHT_LEADING: Em = Em::new(0.25); #[derive(Debug, Default, Clone)] pub struct MathRow(Vec); impl MathRow { pub fn new(fragments: Vec) -> Self { let iter = fragments.into_iter().peekable(); let mut last: Option = None; let mut space: Option = None; let mut resolved: Vec = vec![]; for mut fragment in iter { match fragment { // Keep space only if supported by spaced fragments. MathFragment::Space(_) => { if last.is_some() { space = Some(fragment); } continue; } // Explicit spacing disables automatic spacing. MathFragment::Spacing(_) => { last = None; space = None; resolved.push(fragment); continue; } // Alignment points are resolved later. MathFragment::Align => { resolved.push(fragment); continue; } // New line, new things. MathFragment::Linebreak => { resolved.push(fragment); space = None; last = None; continue; } _ => {} } // Convert variable operators into binary operators if something // precedes them and they are not preceded by a operator or comparator. if fragment.class() == Some(MathClass::Vary) && matches!( last.and_then(|i| resolved[i].class()), Some( MathClass::Normal | MathClass::Alphabetic | MathClass::Closing | MathClass::Fence ) ) { fragment.set_class(MathClass::Binary); } // Insert spacing between the last and this item. if let Some(i) = last { if let Some(s) = spacing(&resolved[i], space.take(), &fragment) { resolved.insert(i + 1, s); } } last = Some(resolved.len()); resolved.push(fragment); } Self(resolved) } pub fn iter(&self) -> std::slice::Iter<'_, MathFragment> { self.0.iter() } /// Extract the sublines of the row. /// /// It is very unintuitive, but in current state of things, a `MathRow` can /// contain several actual rows. That function deconstructs it to "single" /// rows. Hopefully this is only a temporary hack. pub fn rows(&self) -> Vec { self.0 .split(|frag| matches!(frag, MathFragment::Linebreak)) .map(|slice| Self(slice.to_vec())) .collect() } pub fn ascent(&self) -> Abs { self.iter().map(MathFragment::ascent).max().unwrap_or_default() } pub fn descent(&self) -> Abs { self.iter().map(MathFragment::descent).max().unwrap_or_default() } pub fn class(&self) -> MathClass { // Predict the class of the output of 'into_fragment' if self.0.len() == 1 { self.0 .first() .and_then(|fragment| fragment.class()) .unwrap_or(MathClass::Special) } else { // FrameFragment::new() (inside 'into_fragment' in this branch) defaults // to MathClass::Normal for its class. MathClass::Normal } } pub fn into_frame(self, ctx: &MathContext) -> Frame { let styles = ctx.styles(); let align = AlignElem::alignment_in(styles).resolve(styles).x; self.into_aligned_frame(ctx, &[], align) } pub fn into_fragment(self, ctx: &MathContext) -> MathFragment { if self.0.len() == 1 { self.0.into_iter().next().unwrap() } else { FrameFragment::new(ctx, self.into_frame(ctx)).into() } } pub fn into_aligned_frame( self, ctx: &MathContext, points: &[Abs], align: FixedAlign, ) -> Frame { if !self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) { return self.into_line_frame(points, align); } let leading = if ctx.style.size >= MathSize::Text { ParElem::leading_in(ctx.styles()) } else { TIGHT_LEADING.scaled(ctx) }; let mut rows: Vec<_> = self.rows(); if matches!(rows.last(), Some(row) if row.0.is_empty()) { rows.pop(); } let AlignmentResult { points, width } = alignments(&rows); let mut frame = Frame::soft(Size::zero()); for (i, row) in rows.into_iter().enumerate() { let sub = row.into_line_frame(&points, align); let size = frame.size_mut(); if i > 0 { size.y += leading; } let mut pos = Point::with_y(size.y); if points.is_empty() { pos.x = align.position(width - sub.width()); } size.y += sub.height(); size.x.set_max(sub.width()); frame.push_frame(pos, sub); } frame } fn into_line_frame(self, points: &[Abs], align: FixedAlign) -> Frame { let ascent = self.ascent(); let mut frame = Frame::soft(Size::new(Abs::zero(), ascent + self.descent())); frame.set_baseline(ascent); let mut next_x = { let mut widths = Vec::new(); if !points.is_empty() && align != FixedAlign::Start { let mut width = Abs::zero(); for fragment in self.iter() { if matches!(fragment, MathFragment::Align) { widths.push(width); width = Abs::zero(); } else { width += fragment.width(); } } widths.push(width); } let widths = widths; let mut prev_points = once(Abs::zero()).chain(points.iter().copied()); let mut point_widths = points.iter().copied().zip(widths); let mut alternator = LeftRightAlternator::Right; move || match align { FixedAlign::Start => prev_points.next(), FixedAlign::End => { point_widths.next().map(|(point, width)| point - width) } _ => point_widths .next() .zip(prev_points.next()) .zip(alternator.next()) .map(|(((point, width), prev_point), alternator)| match alternator { LeftRightAlternator::Left => prev_point, LeftRightAlternator::Right => point - width, }), } }; let mut x = next_x().unwrap_or_default(); for fragment in self.0.into_iter() { if matches!(fragment, MathFragment::Align) { x = next_x().unwrap_or(x); continue; } let y = ascent - fragment.ascent(); let pos = Point::new(x, y); x += fragment.width(); frame.push_frame(pos, fragment.into_frame()); } frame.size_mut().x = x; frame } } impl> From for MathRow { fn from(fragment: T) -> Self { Self(vec![fragment.into()]) } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum LeftRightAlternator { Left, Right, } impl Iterator for LeftRightAlternator { type Item = LeftRightAlternator; fn next(&mut self) -> Option { let r = Some(*self); match self { Self::Left => *self = Self::Right, Self::Right => *self = Self::Left, } r } }