use comemo::Track;
use typst_library::diag::{At, SourceResult};
use typst_library::engine::Engine;
use typst_library::foundations::{Context, Smart, StyleChain};
use typst_library::layout::{Abs, Angle, Frame, FrameItem, Point, Rel, Size, Transform};
use typst_library::math::CancelAngle;
use typst_library::math::ir::{CancelItem, MathProperties};
use typst_library::visualize::{FixedStroke, Geometry};
use typst_syntax::Span;
use super::MathContext;
use super::fragment::FrameFragment;
#[typst_macros::time(name = "math cancel layout", span = props.span)]
pub fn layout_cancel(
item: &CancelItem,
ctx: &mut MathContext,
styles: StyleChain,
props: &MathProperties,
) -> SourceResult<()> {
let body = ctx.layout_into_fragment(&item.base, styles)?;
let body_text_like = body.is_text_like();
let body_italics = body.italics_correction();
let body_attach = body.accent_attach();
let mut body = body.into_frame();
let body_size = body.size();
let first_line = draw_cancel_line(
ctx.engine,
item.length,
item.stroke.clone(),
item.invert_first_line,
&item.angle,
body_size,
styles,
props.span,
)?;
let center = body_size.to_point() / 2.0;
body.push_frame(center, first_line);
if item.cross {
let second_line = draw_cancel_line(
ctx.engine,
item.length,
item.stroke.clone(),
true,
&item.angle,
body_size,
styles,
props.span,
)?;
body.push_frame(center, second_line);
}
ctx.push(
FrameFragment::new(props, styles, body)
.with_italics_correction(body_italics)
.with_text_like(body_text_like)
.with_accent_attach(body_attach),
);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn draw_cancel_line(
engine: &mut Engine,
length_scale: Rel<Abs>,
stroke: FixedStroke,
invert: bool,
angle: &Smart<CancelAngle>,
body_size: Size,
styles: StyleChain,
span: Span,
) -> SourceResult<Frame> {
let default = default_angle(body_size);
let mut angle = match angle {
Smart::Auto => default,
Smart::Custom(angle) => match angle {
CancelAngle::Angle(v) => *v,
CancelAngle::Func(func) => func
.call(engine, Context::new(None, Some(styles)).track(), [default])?
.cast()
.at(span)?,
},
};
if invert {
angle *= -1.0;
}
let default_length = body_size.to_point().hypot();
let length = length_scale.relative_to(default_length);
let start = Point::new(Abs::zero(), length / 2.0);
let delta = Point::new(Abs::zero(), -length);
let mut frame = Frame::soft(body_size);
frame.push(start, FrameItem::Shape(Geometry::Line(delta).stroked(stroke), span));
frame.transform(Transform::rotate(angle));
Ok(frame)
}
fn default_angle(body: Size) -> Angle {
let (width, height) = (body.x, body.y);
Angle::atan(width / height)
}