mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
208 lines
4.5 KiB
Rust
208 lines
4.5 KiB
Rust
use super::*;
|
|
|
|
/// How much less high scaled delimiters can be than what they wrap.
|
|
pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
|
|
|
/// Scales delimiters.
|
|
///
|
|
/// While matched delimiters scale by default, this can be used to scale
|
|
/// unmatched delimiters and to control the delimiter scaling more precisely.
|
|
///
|
|
/// ## Example
|
|
/// ```example
|
|
/// $ lr(]a, b/2]) $
|
|
/// $ lr(]sum_(x=1)^n] x, size: #50%) $
|
|
/// ```
|
|
///
|
|
/// Display: Left/Right
|
|
/// Category: math
|
|
#[element(LayoutMath)]
|
|
pub struct LrElem {
|
|
/// The size of the brackets, relative to the height of the wrapped content.
|
|
///
|
|
/// Defaults to `{100%}`.
|
|
pub size: Smart<Rel<Length>>,
|
|
|
|
/// The delimited content, including the delimiters.
|
|
#[required]
|
|
#[parse(
|
|
let mut body = Content::empty();
|
|
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
|
if i > 0 {
|
|
body += TextElem::packed(',');
|
|
}
|
|
body += arg;
|
|
}
|
|
body
|
|
)]
|
|
pub body: Content,
|
|
}
|
|
|
|
impl LayoutMath for LrElem {
|
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
|
let mut body = self.body();
|
|
if let Some(elem) = body.to::<LrElem>() {
|
|
if elem.size(ctx.styles()).is_auto() {
|
|
body = elem.body();
|
|
}
|
|
}
|
|
|
|
let mut fragments = ctx.layout_fragments(&body)?;
|
|
let axis = scaled!(ctx, axis_height);
|
|
let max_extent = fragments
|
|
.iter()
|
|
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
|
|
.max()
|
|
.unwrap_or_default();
|
|
|
|
let height = self
|
|
.size(ctx.styles())
|
|
.unwrap_or(Rel::one())
|
|
.resolve(ctx.styles())
|
|
.relative_to(2.0 * max_extent);
|
|
|
|
match fragments.as_mut_slice() {
|
|
[one] => scale(ctx, one, height, None),
|
|
[first, .., last] => {
|
|
scale(ctx, first, height, Some(MathClass::Opening));
|
|
scale(ctx, last, height, Some(MathClass::Closing));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
ctx.extend(fragments);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Scale a math fragment to a height.
|
|
fn scale(
|
|
ctx: &mut MathContext,
|
|
fragment: &mut MathFragment,
|
|
height: Abs,
|
|
apply: Option<MathClass>,
|
|
) {
|
|
if matches!(
|
|
fragment.class(),
|
|
Some(MathClass::Opening | MathClass::Closing | MathClass::Fence)
|
|
) {
|
|
let glyph = match fragment {
|
|
MathFragment::Glyph(glyph) => glyph.clone(),
|
|
MathFragment::Variant(variant) => {
|
|
GlyphFragment::new(ctx, variant.c, variant.span)
|
|
}
|
|
_ => return,
|
|
};
|
|
|
|
let short_fall = DELIM_SHORT_FALL.scaled(ctx);
|
|
*fragment =
|
|
MathFragment::Variant(glyph.stretch_vertical(ctx, height, short_fall));
|
|
|
|
if let Some(class) = apply {
|
|
fragment.set_class(class);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Floor an expression.
|
|
///
|
|
/// ## Example
|
|
/// ```example
|
|
/// $ floor(x/2) $
|
|
/// ```
|
|
///
|
|
/// Display: Floor
|
|
/// Category: math
|
|
/// Returns: content
|
|
#[func]
|
|
pub fn floor(
|
|
/// The expression to floor.
|
|
body: Content,
|
|
) -> Value {
|
|
delimited(body, '⌊', '⌋')
|
|
}
|
|
|
|
/// Ceil an expression.
|
|
///
|
|
/// ## Example
|
|
/// ```example
|
|
/// $ ceil(x/2) $
|
|
/// ```
|
|
///
|
|
/// Display: Ceil
|
|
/// Category: math
|
|
/// Returns: content
|
|
#[func]
|
|
pub fn ceil(
|
|
/// The expression to ceil.
|
|
body: Content,
|
|
) -> Value {
|
|
delimited(body, '⌈', '⌉')
|
|
}
|
|
|
|
/// Round an expression.
|
|
///
|
|
/// ## Example
|
|
/// ```example
|
|
/// $ round(x/2) $
|
|
/// ```
|
|
///
|
|
/// Display: Round
|
|
/// Category: math
|
|
/// Returns: content
|
|
#[func]
|
|
pub fn round(
|
|
/// The expression to round.
|
|
body: Content,
|
|
) -> Value {
|
|
delimited(body, '⌊', '⌉')
|
|
}
|
|
|
|
/// Take the absolute value of an expression.
|
|
///
|
|
/// ## Example
|
|
/// ```example
|
|
/// $ abs(x/2) $
|
|
/// ```
|
|
///
|
|
///
|
|
/// Display: Abs
|
|
/// Category: math
|
|
/// Returns: content
|
|
#[func]
|
|
pub fn abs(
|
|
/// The expression to take the absolute value of.
|
|
body: Content,
|
|
) -> Value {
|
|
delimited(body, '|', '|')
|
|
}
|
|
|
|
/// Take the norm of an expression.
|
|
///
|
|
/// ## Example
|
|
/// ```example
|
|
/// $ norm(x/2) $
|
|
/// ```
|
|
///
|
|
/// Display: Norm
|
|
/// Category: math
|
|
/// Returns: content
|
|
#[func]
|
|
pub fn norm(
|
|
/// The expression to take the norm of.
|
|
body: Content,
|
|
) -> Value {
|
|
delimited(body, '‖', '‖')
|
|
}
|
|
|
|
fn delimited(body: Content, left: char, right: char) -> Value {
|
|
LrElem::new(Content::sequence([
|
|
TextElem::packed(left),
|
|
body,
|
|
TextElem::packed(right),
|
|
]))
|
|
.pack()
|
|
.into()
|
|
}
|