use unicode_math_class::MathClass;
use crate::diag::SourceResult;
use crate::foundations::{elem, func, Content, NativeElement, Packed, Smart, StyleChain};
use crate::layout::{Abs, Axis, Em, Length, Rel};
use crate::math::{
stretch_fragment, EquationElem, LayoutMath, MathContext, MathFragment, Scaled,
};
use crate::text::TextElem;
pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
#[elem(title = "Left/Right", LayoutMath)]
pub struct LrElem {
pub size: Smart<Rel<Length>>,
#[required]
#[parse(
let mut arguments = args.all::<Content>()?.into_iter();
let mut body = arguments.next().unwrap_or_default();
arguments.for_each(|arg| body += TextElem::packed(',') + arg);
body
)]
pub body: Content,
}
impl LayoutMath for Packed<LrElem> {
#[typst_macros::time(name = "math.lr", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut body = self.body();
if let Some(equation) = body.to_packed::<EquationElem>() {
body = equation.body();
}
if let Some(lr) = body.to_packed::<LrElem>() {
if lr.size(styles).is_auto() {
body = lr.body();
}
}
let mut fragments = ctx.layout_into_fragments(body, styles)?;
let axis = scaled!(ctx, styles, axis_height);
let max_extent = fragments
.iter()
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
.max()
.unwrap_or_default();
let relative_to = 2.0 * max_extent;
let height = self.size(styles);
match fragments.as_mut_slice() {
[one] => scale(ctx, styles, one, relative_to, height, None),
[first, .., last] => {
scale(ctx, styles, first, relative_to, height, Some(MathClass::Opening));
scale(ctx, styles, last, relative_to, height, Some(MathClass::Closing));
}
_ => {}
}
for fragment in &mut fragments {
if let MathFragment::Variant(ref mut variant) = fragment {
if variant.mid_stretched == Some(false) {
variant.mid_stretched = Some(true);
scale(
ctx,
styles,
fragment,
relative_to,
height,
Some(MathClass::Large),
);
}
}
}
let original_len = fragments.len();
let mut index = 0;
fragments.retain(|fragment| {
index += 1;
(index != 2 && index + 1 != original_len)
|| !matches!(fragment, MathFragment::Spacing(_, true))
});
ctx.extend(fragments);
Ok(())
}
}
#[elem(LayoutMath)]
pub struct MidElem {
#[required]
pub body: Content,
}
impl LayoutMath for Packed<MidElem> {
#[typst_macros::time(name = "math.mid", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut fragments = ctx.layout_into_fragments(self.body(), styles)?;
for fragment in &mut fragments {
match fragment {
MathFragment::Glyph(glyph) => {
let mut new = glyph.clone().into_variant();
new.mid_stretched = Some(false);
new.class = MathClass::Fence;
*fragment = MathFragment::Variant(new);
}
MathFragment::Variant(variant) => {
variant.mid_stretched = Some(false);
variant.class = MathClass::Fence;
}
_ => {}
}
}
ctx.extend(fragments);
Ok(())
}
}
fn scale(
ctx: &mut MathContext,
styles: StyleChain,
fragment: &mut MathFragment,
relative_to: Abs,
height: Smart<Rel<Length>>,
apply: Option<MathClass>,
) {
if matches!(
fragment.class(),
MathClass::Opening | MathClass::Closing | MathClass::Fence
) {
let short_fall = DELIM_SHORT_FALL.at(fragment.font_size().unwrap_or_default());
stretch_fragment(
ctx,
styles,
fragment,
Some(Axis::Y),
Some(relative_to),
height,
short_fall,
);
if let Some(class) = apply {
fragment.set_class(class);
}
}
}
#[func]
pub fn floor(
#[named]
size: Option<Smart<Rel<Length>>>,
body: Content,
) -> Content {
delimited(body, '⌊', '⌋', size)
}
#[func]
pub fn ceil(
#[named]
size: Option<Smart<Rel<Length>>>,
body: Content,
) -> Content {
delimited(body, '⌈', '⌉', size)
}
#[func]
pub fn round(
#[named]
size: Option<Smart<Rel<Length>>>,
body: Content,
) -> Content {
delimited(body, '⌊', '⌉', size)
}
#[func]
pub fn abs(
#[named]
size: Option<Smart<Rel<Length>>>,
body: Content,
) -> Content {
delimited(body, '|', '|', size)
}
#[func]
pub fn norm(
#[named]
size: Option<Smart<Rel<Length>>>,
body: Content,
) -> Content {
delimited(body, '‖', '‖', size)
}
fn delimited(
body: Content,
left: char,
right: char,
size: Option<Smart<Rel<Length>>>,
) -> Content {
let span = body.span();
let mut elem = LrElem::new(Content::sequence([
TextElem::packed(left),
body,
TextElem::packed(right),
]));
if let Some(size) = size {
elem.push_size(size);
}
elem.pack().spanned(span)
}