use std::cell::Cell;
use std::sync::LazyLock;
use codex::styling::{MathStyle, to_style};
use ecow::EcoString;
use typst_syntax::{Span, split_newlines};
use typst_utils::{LazyHash, SliceExt};
use unicode_segmentation::UnicodeSegmentation;
use super::item::*;
use super::multiline::{AlignedRow, expand_multiline_fence};
use super::process::{GroupResult, process_group, process_table_cell};
use crate::diag::{SourceResult, bail, warning};
use crate::engine::Engine;
use crate::foundations::{
Content, Packed, Style, StyleChain, Styles, SymbolElem, TargetElem,
};
use crate::introspection::{Locator, SplitLocator, TagElem};
use crate::layout::{Abs, Axes, BoxElem, FixedAlignment, HElem, Ratio, Rel, Spacing};
use crate::math::*;
use crate::routines::{Arenas, RealizationKind};
use crate::text::{
BottomEdge, BottomEdgeMetric, LinebreakElem, SpaceElem, TextElem, TopEdge,
TopEdgeMetric, is_default_ignorable,
};
use crate::visualize::FixedStroke;
static TEXT_BASE_LOCAL_STYLES: LazyLock<[LazyHash<Style>; 3]> = LazyLock::new(|| {
[
TextElem::top_edge.set(TopEdge::Metric(TopEdgeMetric::Bounds)),
TextElem::bottom_edge.set(BottomEdge::Metric(BottomEdgeMetric::Bounds)),
TextElem::overhang.set(false),
]
.map(|p| p.wrap())
});
pub(crate) struct MathResolver<'a, 'v, 'e> {
engine: &'v mut Engine<'e>,
locator: SplitLocator<'a>,
arenas: &'a Arenas,
items: Vec<RawMathItem<'a>>,
}
impl<'a, 'v, 'e> MathResolver<'a, 'v, 'e> {
pub(crate) fn new(
engine: &'v mut Engine<'e>,
locator: Locator<'a>,
arenas: &'a Arenas,
) -> Self {
Self {
engine,
locator: locator.split(),
arenas,
items: vec![],
}
}
fn store_styles(&self, styles: impl Into<Styles>) -> &'a Styles {
self.arenas.styles.alloc(styles.into())
}
fn chain_styles(
&self,
base: StyleChain<'a>,
new: impl Into<Styles>,
) -> StyleChain<'a> {
self.store_chain(base).chain(self.store_styles(new))
}
fn store_chain<'c>(&self, styles: StyleChain<'c>) -> &'a StyleChain<'c> {
self.arenas.bump.alloc(styles)
}
fn store(&self, content: Content) -> &'a Content {
self.arenas.content.alloc(content)
}
fn push(&mut self, item: impl Into<RawMathItem<'a>>) {
self.items.push(item.into());
}
fn resolve_into_items(
&mut self,
elem: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<usize> {
let start = self.items.len();
self.resolve_into_self(elem, styles)?;
Ok(start)
}
pub(crate) fn resolve_into_item(
&mut self,
elem: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<MathItem<'a>> {
let start = self.resolve_into_items(elem, styles)?;
let len = self.items.len() - start;
if len == 1 && matches!(self.items.last(), Some(RawMathItem::Item(_))) {
return Ok(self.items.pop().unwrap().into_item().unwrap());
}
Ok(match process_group(self.items.drain(start..), styles, false, true) {
GroupResult::Multiline(rows) => MultilineItem::create(rows, styles),
GroupResult::Flat(items) => MathItem::wrap(items, styles),
})
}
fn resolve_into_self(
&mut self,
content: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let pairs = (self.engine.library.routines.realize)(
RealizationKind::Math,
self.engine,
&mut self.locator,
self.arenas,
content,
styles,
)?;
for (elem, styles) in pairs {
resolve_realized(elem, self, styles)?;
}
Ok(())
}
}
fn resolve_realized<'a, 'v, 'e>(
elem: &'a Content,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
if let Some(elem) = elem.to_packed::<SymbolElem>() {
resolve_symbol(elem, ctx, styles)?;
} else if elem.is::<SpaceElem>() {
ctx.push(MathItem::Space);
} else if let Some(elem) = elem.to_packed::<TextElem>() {
resolve_text(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<AttachElem>() {
resolve_attach(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<LrElem>() {
resolve_lr(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<OpElem>() {
resolve_op(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<HElem>() {
resolve_h(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<OverlineElem>() {
resolve_overline(elem, ctx, styles)?;
} else if elem.is::<AlignPointElem>() {
ctx.push(RawMathItem::Align);
} else if let Some(elem) = elem.to_packed::<PrimesElem>() {
resolve_primes(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<TagElem>() {
ctx.push(MathItem::Tag(elem.tag.clone()));
} else if let Some(elem) = elem.to_packed::<ClassElem>() {
resolve_class(elem, ctx, styles)?;
} else if elem.is::<LinebreakElem>() {
ctx.push(RawMathItem::Linebreak);
} else if let Some(elem) = elem.to_packed::<FracElem>() {
resolve_frac(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<AccentElem>() {
resolve_accent(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<LimitsElem>() {
resolve_limits(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<StretchElem>() {
resolve_stretch(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<RootElem>() {
resolve_root(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<BoxElem>() {
let locator = ctx.locator.next(&elem.span());
ctx.push(BoxItem::create(elem, styles, locator));
} else if let Some(elem) = elem.to_packed::<MatElem>() {
resolve_mat(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<MidElem>() {
resolve_mid(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<UnderlineElem>() {
resolve_underline(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<CasesElem>() {
resolve_cases(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<UnderbraceElem>() {
resolve_underbrace(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<ScriptsElem>() {
resolve_scripts(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<CancelElem>() {
resolve_cancel(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<UnderbracketElem>() {
resolve_underbracket(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<VecElem>() {
resolve_vec(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<OverbraceElem>() {
resolve_overbrace(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<BinomElem>() {
resolve_binom(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<OverbracketElem>() {
resolve_overbracket(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<UnderparenElem>() {
resolve_underparen(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<OverparenElem>() {
resolve_overparen(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<UndershellElem>() {
resolve_undershell(elem, ctx, styles)?;
} else if let Some(elem) = elem.to_packed::<OvershellElem>() {
resolve_overshell(elem, ctx, styles)?;
} else if let Some(body) =
(ctx.engine.library.routines.html_mathml_body)(elem, styles)
{
resolve_mathml(elem, body, ctx, styles)?;
} else {
let locator = ctx.locator.next(&elem.span());
ctx.push(ExternalItem::create(elem, styles, locator));
}
Ok(())
}
fn resolve_mathml<'a, 'v, 'e>(
elem: &'a Content,
body: Option<&'a Content>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let body = if styles.get(TargetElem::target).is_html() {
body.map(|body| ctx.resolve_into_item(body, styles)).transpose()?
} else {
None
};
ctx.push(MathmlItem::create(elem, body, styles));
Ok(())
}
fn resolve_h(
elem: &Packed<HElem>,
ctx: &mut MathResolver,
styles: StyleChain,
) -> SourceResult<()> {
if let Spacing::Rel(rel) = elem.amount
&& rel.rel.is_zero()
{
ctx.push(MathItem::Spacing(
rel.abs,
styles.resolve(TextElem::size),
elem.weak.get(styles),
));
}
Ok(())
}
fn resolve_text<'a, 'v, 'e>(
elem: &Packed<TextElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let variant = styles.get(EquationElem::variant);
let bold = styles.get(EquationElem::bold);
let italic = styles.get(EquationElem::italic).or(Some(false));
let local_styles = ctx.store_chain(styles).chain(&*TEXT_BASE_LOCAL_STYLES);
let mut create_item = |text: &str| {
let mut decimal_count = 0;
let num = text.chars().all(|c| {
if c == '.' {
decimal_count += 1;
}
c.is_ascii_digit() || c == '.'
}) && decimal_count != text.len() && decimal_count <= 1; let styled_text: EcoString = text
.chars()
.flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
.collect();
if num {
NumberItem::create(styled_text, styles, elem.span())
} else {
TextItem::create(
styled_text,
local_styles,
elem.span(),
ctx.locator.next(&elem.span()),
)
}
};
let mut lines = split_newlines(&elem.text);
lines.pop_if(|x| x.is_empty());
let item = if let [text] = lines.as_slice() {
create_item(text)
} else {
let rows = lines
.into_iter()
.map(|line| AlignedRow::new(vec![create_item(line)]))
.collect();
MultilineItem::create(rows, styles).with_multiline_centering()
};
ctx.push(item);
Ok(())
}
fn resolve_symbol<'a, 'v, 'e>(
elem: &'a Packed<SymbolElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let variant = styles.get(EquationElem::variant);
let bold = styles.get(EquationElem::bold);
let italic = styles.get(EquationElem::italic);
for cluster in elem.text.graphemes(true) {
if cluster.chars().all(is_default_ignorable) {
continue;
}
let text: EcoString = cluster
.chars()
.flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
.collect();
let item = GlyphItem::create(text, styles, elem.span());
if item.class() == MathClass::Large && item.size().unwrap() == MathSize::Display {
let stretch = Stretch::new().with_y(StretchInfo::default());
item.replace_stretch(stretch);
}
ctx.push(item);
}
Ok(())
}
fn resolve_accent<'a, 'v, 'e>(
elem: &'a Packed<AccentElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let accent = elem.accent;
let position = if accent.is_bottom() { Position::Below } else { Position::Above };
let mut new_styles = Styles::new();
if position == Position::Above {
new_styles.apply(style_cramped().into());
if elem.dotless.get(styles) {
new_styles.apply(style_dtls().into());
}
}
let base_styles = ctx.chain_styles(styles, new_styles);
let base = ctx.resolve_into_item(&elem.base, base_styles)?;
let accent = ctx.store(SymbolElem::packed(accent.0).spanned(elem.span()));
let mut accent = ctx.resolve_into_item(accent, styles)?;
accent.set_class(MathClass::Diacritic);
let width = elem.size.resolve(styles);
accent.set_stretch(Stretch::new().with_x(StretchInfo::new(width, ACCENT_SHORT_FALL)));
ctx.push(AccentItem::create(
base,
accent,
position,
elem.dotless.get(styles),
false,
styles,
));
Ok(())
}
fn resolve_attach<'a, 'v, 'e>(
elem: &'a Packed<AttachElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let outer_attachments = &mut [const { AttachmentList::End }; 6];
let item = resolve_inner_attach(elem, outer_attachments, false, ctx, styles)?;
ctx.push(item);
Ok(())
}
enum AttachmentList<'a> {
Node {
data: Cell<Option<Content>>,
outer: &'a AttachmentList<'a>,
},
End,
}
impl<'a> AttachmentList<'a> {
fn inner(content: Option<Content>, outer: &'a Self) -> Self {
fn merge_inward(list: &AttachmentList) -> Option<Content> {
match list {
AttachmentList::Node { data, outer } => data.replace(merge_inward(outer)),
AttachmentList::End => None,
}
}
let data = content.or_else(|| merge_inward(outer));
AttachmentList::Node { data: Cell::new(data), outer }
}
fn unmerge(mut self: &Self, content: Content) {
let mut data = Some(content);
while data.is_some()
&& let AttachmentList::Node { data: outer_data, outer } = self
{
data = outer_data.replace(data);
self = outer;
}
assert!(data.is_none(), "only called if we previously merged inward");
}
}
fn resolve_inner_attach<'a, 'v, 'e>(
elem: &'a Packed<AttachElem>,
outer_attachments: &[AttachmentList; 6],
outer_t_inside_tr: bool,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<MathItem<'a>> {
let bumped_styles = ctx.store_chain(styles);
let sup_style = ctx.store_styles(style_for_superscript(styles));
let sup_style_chain = bumped_styles.chain(sup_style);
let sub_style = ctx.store_styles(style_for_subscript(styles));
let sub_style_chain = bumped_styles.chain(sub_style);
let (no_inner_tr, t_inside_tr);
let attachments = {
let [o_t, o_b, o_tl, o_tr, o_bl, o_br] = outer_attachments;
let tl = AttachmentList::inner(elem.tl.get_cloned(sup_style_chain), o_tl);
let elem_t = elem.t.get_cloned(sup_style_chain);
let elem_tr = elem.tr.get_cloned(sup_style_chain);
no_inner_tr = elem_tr.is_none();
t_inside_tr = no_inner_tr && (elem_t.is_some() || outer_t_inside_tr);
let t = AttachmentList::inner(elem_t, o_t);
let tr = AttachmentList::inner(elem_tr, o_tr);
let b = AttachmentList::inner(elem.b.get_cloned(sub_style_chain), o_b);
let bl = AttachmentList::inner(elem.bl.get_cloned(sub_style_chain), o_bl);
let br = AttachmentList::inner(elem.br.get_cloned(sub_style_chain), o_br);
[t, b, tl, tr, bl, br]
};
let mut base_elem = &elem.base;
while let Some(equation) = base_elem.to_packed::<EquationElem>() {
base_elem = &equation.body;
}
let base = if let Some(base_elem) = base_elem.to_packed::<AttachElem>() {
resolve_inner_attach(base_elem, &attachments, t_inside_tr, ctx, styles)?
} else {
ctx.resolve_into_item(base_elem, styles)?
};
let [t, b, tl, tr, bl, br] = attachments.map(|a| match a {
AttachmentList::Node { data: content, outer: _ } => content.into_inner(),
AttachmentList::End => None,
});
if [&t, &b, &tl, &tr, &bl, &br].iter().all(|a| a.is_none()) {
return Ok(base);
}
let limits = base.limits().active(styles);
let (t, tr) = match (t, tr) {
(Some(t), Some(tr)) if !limits => {
let primed = tr.is::<PrimesElem>();
if primed && no_inner_tr && t_inside_tr {
outer_attachments[3].unmerge(tr);
(None, Some(t))
} else if primed {
(None, Some(tr + t))
} else {
(Some(t), Some(tr))
}
}
(Some(t), None) if !limits => (None, Some(t)),
(t, tr) => (t, tr),
};
let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) };
macro_rules! layout {
($content:ident, $style_chain:ident) => {
$content
.map(|elem| ctx.resolve_into_item(ctx.store(elem), $style_chain))
.transpose()
};
}
let top = layout!(t, sup_style_chain)?;
let bottom = layout!(b, sub_style_chain)?;
let top_left = layout!(tl, sup_style_chain)?;
let bottom_left = layout!(bl, sub_style_chain)?;
let top_right = layout!(tr, sup_style_chain)?;
let bottom_right = layout!(br, sub_style_chain)?;
Ok(ScriptsItem::create(
base,
top,
bottom,
top_left,
bottom_left,
top_right,
bottom_right,
styles,
))
}
fn resolve_primes<'a, 'v, 'e>(
elem: &'a Packed<PrimesElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
match elem.count {
count @ 1..=4 => {
let c = match count {
1 => '′',
2 => '″',
3 => '‴',
4 => '⁗',
_ => unreachable!(),
};
let f = ctx.resolve_into_item(
ctx.store(SymbolElem::packed(c).spanned(elem.span())),
styles,
)?;
ctx.push(f);
}
count => {
ctx.push(PrimesItem::create(count, styles));
}
}
Ok(())
}
fn resolve_scripts<'a, 'v, 'e>(
elem: &'a Packed<ScriptsElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let mut item = ctx.resolve_into_item(&elem.body, styles)?;
item.set_limits(Limits::Never);
ctx.push(item);
Ok(())
}
fn resolve_limits<'a, 'v, 'e>(
elem: &'a Packed<LimitsElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let mut item = ctx.resolve_into_item(&elem.body, styles)?;
let limits = if elem.inline.get(styles) { Limits::Always } else { Limits::Display };
item.set_limits(limits);
ctx.push(item);
Ok(())
}
fn resolve_stretch<'a, 'v, 'e>(
elem: &'a Packed<StretchElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let item = ctx.resolve_into_item(&elem.body, styles)?;
let size = elem.size.get(styles);
let font_size = styles.resolve(TextElem::size);
item.update_stretch(StretchInfo::from_size(size, Em::zero(), font_size));
ctx.push(item);
Ok(())
}
fn resolve_cancel<'a, 'v, 'e>(
elem: &'a Packed<CancelElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let body = ctx.resolve_into_item(&elem.body, styles)?;
let length = elem.length.resolve(styles);
let stroke = elem.stroke.resolve(styles).unwrap_or(FixedStroke {
paint: styles.get_ref(TextElem::fill).as_decoration(),
..Default::default()
});
let invert = elem.inverted.get(styles);
let cross = elem.cross.get(styles);
let angle = elem.angle.get_ref(styles);
let invert_first_line = !cross && invert;
ctx.push(CancelItem::create(
body,
length,
stroke,
cross,
invert_first_line,
angle.clone(),
styles,
elem.span(),
));
Ok(())
}
fn resolve_frac<'a, 'v, 'e>(
elem: &'a Packed<FracElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
match elem.style.get(styles) {
FracStyle::Skewed => {
resolve_skewed_frac(ctx, styles, &elem.num, &elem.denom, elem.span())
}
FracStyle::Horizontal => resolve_horizontal_frac(
ctx,
styles,
&elem.num,
&elem.denom,
elem.span(),
elem.num_deparenthesized.get(styles),
elem.denom_deparenthesized.get(styles),
),
FracStyle::Vertical => resolve_vertical_frac_like(
ctx,
styles,
&elem.num,
std::slice::from_ref(&elem.denom),
false,
elem.span(),
),
}
}
fn resolve_binom<'a, 'v, 'e>(
elem: &'a Packed<BinomElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_vertical_frac_like(ctx, styles, &elem.upper, &elem.lower, true, elem.span())
}
fn resolve_vertical_frac_like<'a, 'v, 'e>(
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
num: &'a Content,
denom: &[Content],
binom: bool,
span: Span,
) -> SourceResult<()> {
let num_style = ctx.store_styles(style_for_numerator(styles));
let denom_style = ctx.store_styles(style_for_denominator(styles));
let bumped_styles = ctx.store_chain(styles);
let numerator = ctx.resolve_into_item(num, bumped_styles.chain(num_style))?;
let denominator = ctx.resolve_into_item(
ctx.store(Content::sequence(
denom
.iter()
.flat_map(|a| [SymbolElem::packed(',').spanned(span), a.clone()])
.skip(1),
)),
bumped_styles.chain(denom_style),
)?;
let frac =
FractionItem::create(numerator, denominator, !binom, FRAC_PADDING, styles, span);
if binom {
let stretch =
Stretch::new().with_y(StretchInfo::new(Rel::one(), DELIM_SHORT_FALL));
let open = ctx.resolve_into_item(
ctx.store(SymbolElem::packed('(').spanned(span)),
styles,
)?;
open.set_stretch(stretch);
let close = ctx.resolve_into_item(
ctx.store(SymbolElem::packed(')').spanned(span)),
styles,
)?;
close.set_stretch(stretch);
ctx.push(FencedItem::create(Some(open), Some(close), frac, false, styles, span));
} else {
ctx.push(frac);
}
Ok(())
}
fn resolve_horizontal_frac<'a, 'v, 'e>(
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
num: &'a Content,
denom: &'a Content,
span: Span,
num_deparen: bool,
denom_deparen: bool,
) -> SourceResult<()> {
let num = if num_deparen {
ctx.store(
LrElem::new(Content::sequence(vec![
SymbolElem::packed('('),
num.clone(),
SymbolElem::packed(')'),
]))
.pack(),
)
} else {
num
};
let num = ctx.resolve_into_item(num, styles)?;
ctx.push(num);
let mut slash =
ctx.resolve_into_item(ctx.store(SymbolElem::packed('/').spanned(span)), styles)?;
slash.set_class(MathClass::Binary);
slash.set_lspace(Some(Em::zero()));
slash.set_rspace(Some(Em::zero()));
ctx.push(slash);
let denom = if denom_deparen {
ctx.store(
LrElem::new(Content::sequence(vec![
SymbolElem::packed('('),
denom.clone(),
SymbolElem::packed(')'),
]))
.pack(),
)
} else {
denom
};
let denom = ctx.resolve_into_item(denom, styles)?;
ctx.push(denom);
Ok(())
}
fn resolve_skewed_frac<'a, 'v, 'e>(
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
num: &'a Content,
denom: &'a Content,
span: Span,
) -> SourceResult<()> {
let num_style = ctx.store_styles(style_for_numerator(styles));
let denom_style = ctx.store_styles(style_for_denominator(styles));
let bumped_styles = ctx.store_chain(styles);
let numerator = ctx.resolve_into_item(num, bumped_styles.chain(num_style))?;
let denominator = ctx.resolve_into_item(denom, bumped_styles.chain(denom_style))?;
let slash = ctx.resolve_into_item(
ctx.store(SymbolElem::packed('\u{2044}').spanned(span)),
styles,
)?;
slash.set_stretch(
Stretch::new().with_y(StretchInfo::new(Rel::one(), DELIM_SHORT_FALL)),
);
ctx.push(SkewedFractionItem::create(numerator, denominator, slash, styles, span));
Ok(())
}
fn resolve_lr<'a, 'v, 'e>(
elem: &'a Packed<LrElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let mut body = &elem.body;
if let Some(equation) = body.to_packed::<EquationElem>() {
body = &equation.body;
}
if let Some(lr) = body.to_packed::<LrElem>()
&& lr.size.get(styles).is_one()
{
body = &lr.body;
}
let start = ctx.resolve_into_items(body, styles)?;
let (start_idx, end_idx) =
ctx.items[start..].split_prefix_suffix(|f| f.is_ignorant());
let inner_range = (start + start_idx)..(start + end_idx);
let inner_items = &mut ctx.items[inner_range.clone()];
let size = elem.size.get(styles);
let font_size = styles.resolve(TextElem::size);
let stretch =
Stretch::new().with_y(StretchInfo::from_size(size, DELIM_SHORT_FALL, font_size));
let scale_if_delimiter = |item: &mut MathItem, apply: Option<MathClass>| {
if matches!(
item.class(),
MathClass::Opening | MathClass::Closing | MathClass::Fence
) {
item.set_stretch(stretch);
if let Some(class) = apply {
item.set_class(class);
}
}
};
match inner_items {
[one] => {
if let RawMathItem::Item(one) = one {
if let MathItem::Component(MathComponent {
kind: MathKind::Fenced(fenced),
..
}) = one
{
if let Some(open) = &fenced.open {
open.set_stretch(stretch);
}
if let Some(close) = &fenced.close {
close.set_stretch(stretch);
}
for item in fenced.body.as_slice() {
if item.mid_stretched() == Some(true) {
item.set_stretch(stretch);
}
}
} else {
let mut info =
StretchInfo::new(size.abs.at(font_size).into(), DELIM_SHORT_FALL);
if !size.is_one() {
info.requested_target = Some(size);
}
one.set_y_stretch(info);
}
}
return Ok(());
}
[first, .., last] => {
if let RawMathItem::Item(first) = first {
scale_if_delimiter(first, Some(MathClass::Opening));
}
if let RawMathItem::Item(last) = last {
scale_if_delimiter(last, Some(MathClass::Closing));
}
}
[] => {}
}
for item in inner_items.iter_mut() {
if let RawMathItem::Item(item) = item
&& item.mid_stretched() == Some(false)
{
item.set_mid_stretched(Some(true));
item.set_stretch(stretch);
}
}
let mut inner_items: Vec<_> = ctx.items.drain(inner_range).collect();
let mut index = 0;
let len = inner_items.len();
let opening_exists = inner_items.first().is_some_and(
|f| matches!(f, RawMathItem::Item(item) if item.class() == MathClass::Opening),
);
let closing_exists = inner_items.last().is_some_and(
|f| matches!(f, RawMathItem::Item(item) if item.class() == MathClass::Closing),
);
inner_items.retain(|item| {
let discard = (index == 1 && opening_exists
|| index + 2 == len && closing_exists)
&& matches!(item, RawMathItem::Item(MathItem::Spacing(_, _, true)));
index += 1;
!discard
});
let open = opening_exists.then(|| inner_items.remove(0).into_item().unwrap());
let close = closing_exists.then(|| inner_items.pop().unwrap().into_item().unwrap());
let insert_pos = start + start_idx;
match process_group(inner_items, styles, close.is_some(), false) {
GroupResult::Multiline(rows) => {
let items = expand_multiline_fence(rows, open, close, styles, elem.span());
ctx.items.splice(insert_pos..insert_pos, items);
}
GroupResult::Flat(items) => {
let body = GroupItem::create(items, styles);
let item = FencedItem::create(open, close, body, true, styles, elem.span());
ctx.items.insert(insert_pos, item.into());
}
}
Ok(())
}
fn resolve_mid<'a, 'v, 'e>(
elem: &'a Packed<MidElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let start = ctx.resolve_into_items(&elem.body, styles)?;
for item in &mut ctx.items[start..] {
if let RawMathItem::Item(item) = item {
item.set_mid_stretched(Some(false));
item.set_class(MathClass::Relation);
}
}
Ok(())
}
fn resolve_vec<'a, 'v, 'e>(
elem: &'a Packed<VecElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let span = elem.span();
let rows: Vec<Vec<&Content>> =
elem.children.iter().map(|child| vec![child]).collect();
let cells = resolve_cells(
ctx,
styles,
rows,
span,
elem.align.resolve(styles),
LeftRightAlternator::Right,
None,
Axes::with_y(elem.gap.resolve(styles)),
"elements",
)?;
let delim = elem.delim.get(styles);
resolve_delimiters(ctx, styles, cells, delim.open(), delim.close(), span)
}
fn resolve_mat<'a, 'v, 'e>(
elem: &'a Packed<MatElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let span = elem.span();
let rows: Vec<Vec<&Content>> =
elem.rows.iter().map(|row| row.iter().collect()).collect();
let nrows = rows.len();
let ncols = rows.first().map_or(0, |row| row.len());
let augment = elem.augment.resolve(styles);
if let Some(aug) = &augment {
for &offset in &aug.hline.0 {
if offset > nrows as isize || offset.unsigned_abs() > nrows {
bail!(
span,
"cannot draw a horizontal line at offset {offset} \
in a matrix with {nrows} rows",
);
}
}
for &offset in &aug.vline.0 {
if offset > ncols as isize || offset.unsigned_abs() > ncols {
bail!(
span,
"cannot draw a vertical line at offset {offset} \
in a matrix with {ncols} columns",
);
}
}
}
let cells = resolve_cells(
ctx,
styles,
rows,
span,
elem.align.resolve(styles),
LeftRightAlternator::Right,
augment,
Axes::new(elem.column_gap.resolve(styles), elem.row_gap.resolve(styles)),
"cells",
)?;
let delim = elem.delim.get(styles);
resolve_delimiters(ctx, styles, cells, delim.open(), delim.close(), span)
}
fn resolve_cases<'a, 'v, 'e>(
elem: &'a Packed<CasesElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let span = elem.span();
let rows: Vec<Vec<&Content>> =
elem.children.iter().map(|child| vec![child]).collect();
let cells = resolve_cells(
ctx,
styles,
rows,
span,
FixedAlignment::Start,
LeftRightAlternator::None,
None,
Axes::with_y(elem.gap.resolve(styles)),
"branches",
)?;
let delim = elem.delim.get(styles);
let (open, close) = if elem.reverse.get(styles) {
(None, delim.close())
} else {
(delim.open(), None)
};
resolve_delimiters(ctx, styles, cells, open, close, span)
}
#[allow(clippy::too_many_arguments)]
fn resolve_cells<'a, 'v, 'e>(
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
rows: Vec<Vec<&'a Content>>,
span: Span,
align: FixedAlignment,
alternator: LeftRightAlternator,
augment: Option<Augment<Abs>>,
gap: Axes<Rel<Abs>>,
children: &str,
) -> SourceResult<MathItem<'a>> {
let cell_styles = ctx.chain_styles(styles, style_for_denominator(styles));
let mut cells = Vec::with_capacity(rows.len());
for row in rows {
let mut resolved_row = Vec::with_capacity(row.len());
for cell in row {
let start = ctx.resolve_into_items(cell, cell_styles)?;
let processed = process_table_cell(ctx.items.drain(start..), cell_styles);
if processed.had_linebreaks {
ctx.engine.sink.warn(warning!(
cell.span(),
"linebreaks are ignored in {children}";
hint: "use commas instead to separate each line";
));
}
resolved_row.push(processed.sub_columns);
}
cells.push(resolved_row);
}
let ncols = cells.first().map_or(0, |row| row.len());
for c in 0..ncols {
let max = cells.iter().map(|row| row[c].len()).max().unwrap_or_default();
for row in &mut cells {
row[c].pad_to(max, cell_styles);
}
}
Ok(TableItem::create(cells, gap, augment, align, alternator, styles, span))
}
fn resolve_delimiters<'a, 'v, 'e>(
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
cells: MathItem<'a>,
left: Option<char>,
right: Option<char>,
span: Span,
) -> SourceResult<()> {
let target = Rel::new(Ratio::new(1.1), Abs::zero());
let stretch = Stretch::new().with_y(StretchInfo::new(target, DELIM_SHORT_FALL));
let open = left
.map(|c| {
ctx.resolve_into_item(ctx.store(SymbolElem::packed(c).spanned(span)), styles)
})
.transpose()?
.inspect(|x| x.set_stretch(stretch));
let close = right
.map(|c| {
ctx.resolve_into_item(ctx.store(SymbolElem::packed(c).spanned(span)), styles)
})
.transpose()?
.inspect(|x| x.set_stretch(stretch));
ctx.push(FencedItem::create(open, close, cells, false, styles, span));
Ok(())
}
fn resolve_class<'a, 'v, 'e>(
elem: &'a Packed<ClassElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let mut item = ctx.resolve_into_item(&elem.body, styles)?;
item.set_class(elem.class);
item.set_limits(Limits::for_class(elem.class));
ctx.push(item);
Ok(())
}
fn resolve_op<'a, 'v, 'e>(
elem: &'a Packed<OpElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let mut item = ctx.resolve_into_item(&elem.text, styles)?;
item.set_class(MathClass::Large);
item.set_limits(if elem.limits.get(styles) {
Limits::Display
} else {
Limits::Never
});
ctx.push(item);
Ok(())
}
fn resolve_root<'a, 'v, 'e>(
elem: &'a Packed<RootElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let cramped_styles = ctx.store_chain(ctx.chain_styles(styles, style_cramped()));
let radicand = ctx
.resolve_into_item(&elem.radicand, *cramped_styles)?
.with_multiline_centering();
let index = {
let sscript =
ctx.store_styles(EquationElem::size.set(MathSize::ScriptScript).wrap());
elem.index
.get_ref(styles)
.as_ref()
.map(|elem| ctx.resolve_into_item(elem, cramped_styles.chain(sscript)))
.transpose()?
};
let sqrt = ctx.resolve_into_item(
ctx.store(SymbolElem::packed('√').spanned(elem.span())),
styles,
)?;
sqrt.set_stretch(Stretch::new().with_y(StretchInfo::new(Rel::one(), Em::zero())));
ctx.push(RadicalItem::create(radicand, index, sqrt, styles, elem.span()));
Ok(())
}
fn resolve_underline<'a, 'v, 'e>(
elem: &'a Packed<UnderlineElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let base = ctx.resolve_into_item(&elem.body, styles)?;
ctx.push(LineItem::create(base, Position::Below, styles, elem.span()));
Ok(())
}
fn resolve_overline<'a, 'v, 'e>(
elem: &'a Packed<OverlineElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let cramped_styles = ctx.chain_styles(styles, style_cramped());
let base = ctx.resolve_into_item(&elem.body, cramped_styles)?;
ctx.push(LineItem::create(base, Position::Above, styles, elem.span()));
Ok(())
}
fn resolve_underbrace<'a, 'v, 'e>(
elem: &'a Packed<UnderbraceElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⏟',
Position::Below,
elem.span(),
)
}
fn resolve_overbrace<'a, 'v, 'e>(
elem: &'a Packed<OverbraceElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⏞',
Position::Above,
elem.span(),
)
}
fn resolve_underbracket<'a, 'v, 'e>(
elem: &'a Packed<UnderbracketElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⎵',
Position::Below,
elem.span(),
)
}
fn resolve_overbracket<'a, 'v, 'e>(
elem: &'a Packed<OverbracketElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⎴',
Position::Above,
elem.span(),
)
}
fn resolve_underparen<'a, 'v, 'e>(
elem: &'a Packed<UnderparenElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⏝',
Position::Below,
elem.span(),
)
}
fn resolve_overparen<'a, 'v, 'e>(
elem: &'a Packed<OverparenElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⏜',
Position::Above,
elem.span(),
)
}
fn resolve_undershell<'a, 'v, 'e>(
elem: &'a Packed<UndershellElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⏡',
Position::Below,
elem.span(),
)
}
fn resolve_overshell<'a, 'v, 'e>(
elem: &'a Packed<OvershellElem>,
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
) -> SourceResult<()> {
resolve_underoverspreader(
ctx,
styles,
&elem.body,
elem.annotation.get_ref(styles),
'⏠',
Position::Above,
elem.span(),
)
}
fn resolve_underoverspreader<'a, 'v, 'e>(
ctx: &mut MathResolver<'a, 'v, 'e>,
styles: StyleChain<'a>,
body: &'a Content,
annotation: &'a Option<Content>,
c: char,
position: Position,
span: Span,
) -> SourceResult<()> {
let base = ctx.resolve_into_item(body, styles)?;
let accent = ctx.store(SymbolElem::packed(c).spanned(span));
let mut accent = ctx.resolve_into_item(accent, styles)?;
accent.set_class(MathClass::Diacritic);
accent.set_stretch(Stretch::new().with_x(StretchInfo::new(Rel::one(), Em::zero())));
let base = AccentItem::create(base, accent, position, false, true, styles);
let Some(annotation) = annotation else {
ctx.push(base);
return Ok(());
};
let base = match position {
Position::Below => {
let under_styles = ctx.chain_styles(styles, style_for_subscript(styles));
let annotation = ctx.resolve_into_item(annotation, under_styles)?;
ScriptsItem::create(
base,
None,
Some(annotation),
None,
None,
None,
None,
styles,
)
}
Position::Above => {
let over_styles = ctx.chain_styles(styles, style_for_superscript(styles));
let annotation = ctx.resolve_into_item(annotation, over_styles)?;
ScriptsItem::create(
base,
Some(annotation),
None,
None,
None,
None,
None,
styles,
)
}
};
ctx.push(base);
Ok(())
}