mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Add calc module
This commit is contained in:
parent
1ea0a93325
commit
0287b98ef3
@ -79,7 +79,7 @@ impl TableNode {
|
|||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #table(
|
/// #table(
|
||||||
/// fill: (col, _) => if odd(col) { luma(240) } else { white },
|
/// fill: (col, _) => if calc.odd(col) { luma(240) } else { white },
|
||||||
/// align: (col, row) =>
|
/// align: (col, row) =>
|
||||||
/// if row == 0 { center }
|
/// if row == 0 { center }
|
||||||
/// else if col == 0 { left }
|
/// else if col == 0 { left }
|
||||||
|
@ -1,15 +1,45 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use typst::model::{Module, Scope};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// A module with computational functions.
|
||||||
|
pub fn calc() -> Module {
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.def_func::<AbsFunc>("abs");
|
||||||
|
scope.def_func::<PowFunc>("pow");
|
||||||
|
scope.def_func::<SqrtFunc>("sqrt");
|
||||||
|
scope.def_func::<SinFunc>("sin");
|
||||||
|
scope.def_func::<CosFunc>("cos");
|
||||||
|
scope.def_func::<TanFunc>("tan");
|
||||||
|
scope.def_func::<AsinFunc>("asin");
|
||||||
|
scope.def_func::<AcosFunc>("acos");
|
||||||
|
scope.def_func::<AtanFunc>("atan");
|
||||||
|
scope.def_func::<SinhFunc>("sinh");
|
||||||
|
scope.def_func::<CoshFunc>("cosh");
|
||||||
|
scope.def_func::<TanhFunc>("tanh");
|
||||||
|
scope.def_func::<LogFunc>("log");
|
||||||
|
scope.def_func::<FloorFunc>("floor");
|
||||||
|
scope.def_func::<CeilFunc>("ceil");
|
||||||
|
scope.def_func::<RoundFunc>("round");
|
||||||
|
scope.def_func::<ClampFunc>("clamp");
|
||||||
|
scope.def_func::<MinFunc>("min");
|
||||||
|
scope.def_func::<MaxFunc>("max");
|
||||||
|
scope.def_func::<EvenFunc>("even");
|
||||||
|
scope.def_func::<OddFunc>("odd");
|
||||||
|
scope.def_func::<ModFunc>("mod");
|
||||||
|
Module::new("calc").with_scope(scope)
|
||||||
|
}
|
||||||
|
|
||||||
/// # Absolute
|
/// # Absolute
|
||||||
/// The absolute value of a numeric value.
|
/// The absolute value of a numeric value.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #abs(-5) \
|
/// #calc.abs(-5) \
|
||||||
/// #abs(5pt - 2cm) \
|
/// #calc.abs(5pt - 2cm) \
|
||||||
/// #abs(2fr)
|
/// #calc.abs(2fr)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
@ -37,13 +67,482 @@ castable! {
|
|||||||
v: Fr => Self(Value::Fraction(v.abs())),
|
v: Fr => Self(Value::Fraction(v.abs())),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Power
|
||||||
|
/// Raise a value to some exponent.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.pow(2, 3)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - base: Num (positional, required)
|
||||||
|
/// The base of the power.
|
||||||
|
/// - exponent: Num (positional, required)
|
||||||
|
/// The exponent of the power. Must be non-negative.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn pow(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let base = args.expect::<Num>("base")?;
|
||||||
|
let exponent = args
|
||||||
|
.expect::<Spanned<Num>>("exponent")
|
||||||
|
.and_then(|n| match n.v {
|
||||||
|
Num::Int(i) if i > u32::MAX as i64 => bail!(n.span, "exponent too large"),
|
||||||
|
Num::Int(i) if i >= 0 => Ok(n),
|
||||||
|
Num::Float(f) if f >= 0.0 => Ok(n),
|
||||||
|
_ => bail!(n.span, "exponent must be non-negative"),
|
||||||
|
})?
|
||||||
|
.v;
|
||||||
|
|
||||||
|
Ok(base.apply2(exponent, |a, b| a.pow(b as u32), |a, b| a.powf(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Square Root
|
||||||
|
/// The square root of a number.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.sqrt(16) \
|
||||||
|
/// #calc.sqrt(2.5)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number whose square root to calculate. Must be non-negative.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Spanned<Num>>("value")?;
|
||||||
|
if value.v.is_negative() {
|
||||||
|
bail!(value.span, "cannot take square root of negative number");
|
||||||
|
}
|
||||||
|
Ok(Value::Float(value.v.float().sqrt()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Sine
|
||||||
|
/// Calculate the sine of an angle. When called with an integer or a number,
|
||||||
|
/// they will be interpreted as radians.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #assert(calc.sin(90deg) == calc.sin(-270deg))
|
||||||
|
/// #calc.sin(1.5) \
|
||||||
|
/// #calc.sin(90deg)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - angle: AngleLike (positional, required)
|
||||||
|
/// The angle whose sine to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn sin(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
||||||
|
Ok(Value::Float(match arg {
|
||||||
|
AngleLike::Angle(a) => a.sin(),
|
||||||
|
AngleLike::Int(n) => (n as f64).sin(),
|
||||||
|
AngleLike::Float(n) => n.sin(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Cosine
|
||||||
|
/// Calculate the cosine of an angle. When called with an integer or a number,
|
||||||
|
/// they will be interpreted as radians.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.cos(90deg)
|
||||||
|
/// #calc.cos(1.5) \
|
||||||
|
/// #calc.cos(90deg)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - angle: AngleLike (positional, required)
|
||||||
|
/// The angle whose cosine to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn cos(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
||||||
|
Ok(Value::Float(match arg {
|
||||||
|
AngleLike::Angle(a) => a.cos(),
|
||||||
|
AngleLike::Int(n) => (n as f64).cos(),
|
||||||
|
AngleLike::Float(n) => n.cos(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Tangent
|
||||||
|
/// Calculate the tangent of an angle. When called with an integer or a number,
|
||||||
|
/// they will be interpreted as radians.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.tan(1.5) \
|
||||||
|
/// #calc.tan(90deg)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - angle: AngleLike (positional, required)
|
||||||
|
/// The angle whose tangent to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn tan(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
||||||
|
Ok(Value::Float(match arg {
|
||||||
|
AngleLike::Angle(a) => a.tan(),
|
||||||
|
AngleLike::Int(n) => (n as f64).tan(),
|
||||||
|
AngleLike::Float(n) => n.tan(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Arcsine
|
||||||
|
/// Calculate the arcsine of a number.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.asin(0) \
|
||||||
|
/// #calc.asin(1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number whose arcsine to calculate. Must be between -1 and 1.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn asin(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
|
||||||
|
let val = v.float();
|
||||||
|
if val < -1.0 || val > 1.0 {
|
||||||
|
bail!(span, "arcsin must be between -1 and 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Angle(Angle::rad(val.asin())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Arccosine
|
||||||
|
/// Calculate the arccosine of a number.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.acos(0) \
|
||||||
|
/// #calc.acos(1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number whose arccosine to calculate. Must be between -1 and 1.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn acos(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
|
||||||
|
let val = v.float();
|
||||||
|
if val < -1.0 || val > 1.0 {
|
||||||
|
bail!(span, "arccos must be between -1 and 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Angle(Angle::rad(val.acos())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Arctangent
|
||||||
|
/// Calculate the arctangent of a number.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.atan(0) \
|
||||||
|
/// #calc.atan(1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number whose arctangent to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn atan(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
||||||
|
Ok(Value::Angle(Angle::rad(value.float().atan())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Hyperbolic sine
|
||||||
|
/// Calculate the hyperbolic sine of an angle. When called with an integer or
|
||||||
|
/// a number, they will be interpreted as radians.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.sinh(0) \
|
||||||
|
/// #calc.sinh(45deg)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - angle: AngleLike (positional, required)
|
||||||
|
/// The angle whose hyperbolic sine to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn sinh(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
||||||
|
Ok(Value::Float(match arg {
|
||||||
|
AngleLike::Angle(a) => a.to_rad(),
|
||||||
|
AngleLike::Int(n) => (n as f64).sinh(),
|
||||||
|
AngleLike::Float(n) => n.sinh(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Hyperbolic cosine
|
||||||
|
/// Calculate the hyperbolic cosine of an angle. When called with an integer or
|
||||||
|
/// a number, they will be interpreted as radians.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.cosh(0) \
|
||||||
|
/// #calc.cosh(45deg)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - angle: AngleLike (positional, required)
|
||||||
|
/// The angle whose hyperbolic cosine to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn cosh(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
||||||
|
Ok(Value::Float(match arg {
|
||||||
|
AngleLike::Angle(a) => a.to_rad(),
|
||||||
|
AngleLike::Int(n) => (n as f64).cosh(),
|
||||||
|
AngleLike::Float(n) => n.cosh(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Hyperbolic tangent
|
||||||
|
/// Calculate the hyperbolic tangent of an angle. When called with an integer or
|
||||||
|
/// a number, they will be interpreted as radians.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.tanh(0) \
|
||||||
|
/// #calc.tanh(45deg)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - angle: AngleLike (positional, required)
|
||||||
|
/// The angle whose hyperbolic tangent to calculate.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn tanh(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
||||||
|
Ok(Value::Float(match arg {
|
||||||
|
AngleLike::Angle(a) => a.to_rad(),
|
||||||
|
AngleLike::Int(n) => (n as f64).tanh(),
|
||||||
|
AngleLike::Float(n) => n.tanh(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Logarithm
|
||||||
|
/// Calculate the logarithm of a number.
|
||||||
|
/// If the base is not specified, the logarithm is calculated in base 10.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #calc.log(100) \
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number whose logarithm to calculate.
|
||||||
|
/// - base: Num (named)
|
||||||
|
/// The base of the logarithm.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn log(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Num>("value")?;
|
||||||
|
let base = args.named::<Num>("base")?.unwrap_or_else(|| Num::Int(10));
|
||||||
|
|
||||||
|
Ok(value.apply2(base, |a, b| a.ilog(b) as i64, |a, b| a.log(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Round down
|
||||||
|
/// Round a number down to the nearest integer.
|
||||||
|
/// If the number is already an integer, it is returned unchanged.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #assert(calc.floor(3.14) == 3)
|
||||||
|
/// #assert(calc.floor(3) == 3)
|
||||||
|
/// #calc.floor(500.1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number to round down.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
||||||
|
Ok(match value {
|
||||||
|
Num::Int(n) => Value::Int(n),
|
||||||
|
Num::Float(n) => Value::Int(n.floor() as i64),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Round up
|
||||||
|
/// Round a number up to the nearest integer.
|
||||||
|
/// If the number is already an integer, it is returned unchanged.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #assert(calc.ceil(3.14) == 4)
|
||||||
|
/// #assert(calc.ceil(3) == 3)
|
||||||
|
/// #calc.ceil(500.1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number to round up.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
||||||
|
Ok(match value {
|
||||||
|
Num::Int(n) => Value::Int(n),
|
||||||
|
Num::Float(n) => Value::Int(n.ceil() as i64),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Round
|
||||||
|
/// Round a number to the nearest integer.
|
||||||
|
/// Optionally, a number of decimal places can be specified.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #assert(calc.round(3.14) == 3)
|
||||||
|
/// #assert(calc.round(3.5) == 4)
|
||||||
|
/// #calc.round(3.1415, digits: 2)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number to round.
|
||||||
|
/// - digits: i64 (named)
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn round(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Num>("value")?;
|
||||||
|
let digits = args.named::<Spanned<i64>>("digits").and_then(|n| match n {
|
||||||
|
Some(Spanned { v, span }) if v < 0 => {
|
||||||
|
bail!(span, "digits must be non-negative")
|
||||||
|
}
|
||||||
|
Some(Spanned { v, span }) if v > i32::MAX as i64 => {
|
||||||
|
bail!(span, "digits must be less than {}", i32::MAX)
|
||||||
|
}
|
||||||
|
Some(Spanned { v, .. }) => Ok(v as i32),
|
||||||
|
None => Ok(0),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(match value {
|
||||||
|
Num::Int(n) if digits == 0 => Value::Int(n),
|
||||||
|
_ => {
|
||||||
|
let n = value.float();
|
||||||
|
let factor = 10.0_f64.powi(digits) as f64;
|
||||||
|
Value::Float((n * factor).round() / factor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Clamp
|
||||||
|
/// Clamp a number between a minimum and maximum value.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #assert(calc.clamp(5, 0, 10) == 5)
|
||||||
|
/// #assert(calc.clamp(5, 6, 10) == 6)
|
||||||
|
/// #calc.clamp(5, 0, 4)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - value: Num (positional, required)
|
||||||
|
/// The number to clamp.
|
||||||
|
/// - min: Num (positional, required)
|
||||||
|
/// The inclusive minimum value.
|
||||||
|
/// - max: Num (positional, required)
|
||||||
|
/// The inclusive maximum value.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// calculate
|
||||||
|
#[func]
|
||||||
|
pub fn clamp(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let value = args.expect::<Num>("value")?;
|
||||||
|
let min = args.expect::<Num>("min")?;
|
||||||
|
let max = args.expect::<Spanned<Num>>("max")?;
|
||||||
|
|
||||||
|
if max.v.float() < min.float() {
|
||||||
|
bail!(max.span, "max must be greater than or equal to min")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(value.apply3(
|
||||||
|
min,
|
||||||
|
max.v,
|
||||||
|
|v, min, max| {
|
||||||
|
if v < min {
|
||||||
|
min
|
||||||
|
} else if v > max {
|
||||||
|
max
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|v, min, max| {
|
||||||
|
if v < min {
|
||||||
|
min
|
||||||
|
} else if v > max {
|
||||||
|
max
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// # Minimum
|
/// # Minimum
|
||||||
/// The minimum of a sequence of values.
|
/// The minimum of a sequence of values.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #min(1, -3, -5, 20, 3, 6) \
|
/// #calc.min(1, -3, -5, 20, 3, 6) \
|
||||||
/// #min("typst", "in", "beta")
|
/// #calc.min("typst", "in", "beta")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
@ -65,8 +564,8 @@ pub fn min(args: &mut Args) -> SourceResult<Value> {
|
|||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #max(1, -3, -5, 20, 3, 6) \
|
/// #calc.max(1, -3, -5, 20, 3, 6) \
|
||||||
/// #max("typst", "in", "beta")
|
/// #calc.max("typst", "in", "beta")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
@ -109,9 +608,9 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
|
|||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #even(4) \
|
/// #calc.even(4) \
|
||||||
/// #even(5) \
|
/// #calc.even(5) \
|
||||||
/// #range(10).filter(even)
|
/// #range(10).filter(calc.even)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
@ -132,9 +631,9 @@ pub fn even(args: &mut Args) -> SourceResult<Value> {
|
|||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #odd(4) \
|
/// #calc.odd(4) \
|
||||||
/// #odd(5) \
|
/// #calc.odd(5) \
|
||||||
/// #range(10).filter(odd)
|
/// #range(10).filter(calc.odd)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@ -156,15 +655,15 @@ pub fn odd(args: &mut Args) -> SourceResult<Value> {
|
|||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #mod(20, 6) \
|
/// #calc.mod(20, 6) \
|
||||||
/// #mod(1.75, 0.5)
|
/// #calc.mod(1.75, 0.5)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - dividend: ToMod (positional, required)
|
/// - dividend: Num (positional, required)
|
||||||
/// The dividend of the modulus.
|
/// The dividend of the modulus.
|
||||||
///
|
///
|
||||||
/// - divisor: ToMod (positional, required)
|
/// - divisor: Num (positional, required)
|
||||||
/// The divisor of the modulus.
|
/// The divisor of the modulus.
|
||||||
///
|
///
|
||||||
/// - returns: integer or float
|
/// - returns: integer or float
|
||||||
@ -200,10 +699,69 @@ pub fn mod_(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A value which can be passed to the `mod` function.
|
/// A value which can be passed to the `mod` function.
|
||||||
struct ToMod;
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
enum Num {
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Num {
|
||||||
|
fn apply2(
|
||||||
|
self,
|
||||||
|
other: Self,
|
||||||
|
int: impl FnOnce(i64, i64) -> i64,
|
||||||
|
float: impl FnOnce(f64, f64) -> f64,
|
||||||
|
) -> Value {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Int(a), Self::Int(b)) => Value::Int(int(a, b)),
|
||||||
|
(a, b) => Value::Float(float(a.float(), b.float())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply3(
|
||||||
|
self,
|
||||||
|
other: Self,
|
||||||
|
third: Self,
|
||||||
|
int: impl FnOnce(i64, i64, i64) -> i64,
|
||||||
|
float: impl FnOnce(f64, f64, f64) -> f64,
|
||||||
|
) -> Value {
|
||||||
|
match (self, other, third) {
|
||||||
|
(Self::Int(a), Self::Int(b), Self::Int(c)) => Value::Int(int(a, b, c)),
|
||||||
|
(a, b, c) => Value::Float(float(a.float(), b.float(), c.float())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn float(self) -> f64 {
|
||||||
|
match self {
|
||||||
|
Self::Int(v) => v as f64,
|
||||||
|
Self::Float(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_negative(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Int(v) => v < 0,
|
||||||
|
Self::Float(v) => v < 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
ToMod,
|
Num,
|
||||||
_: i64 => Self,
|
v: i64 => Self::Int(v),
|
||||||
_: f64 => Self,
|
v: f64 => Self::Float(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A value that can be passed to a trigonometric function.
|
||||||
|
enum AngleLike {
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
Angle(Angle),
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
AngleLike,
|
||||||
|
v: i64 => Self::Int(v),
|
||||||
|
v: f64 => Self::Float(v),
|
||||||
|
v: Angle => Self::Angle(v),
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,13 @@ use self::layout::LayoutRoot;
|
|||||||
pub fn build() -> Library {
|
pub fn build() -> Library {
|
||||||
let sym = text::sym();
|
let sym = text::sym();
|
||||||
let math = math::module(&sym);
|
let math = math::module(&sym);
|
||||||
let global = global(sym, math.clone());
|
let calc = compute::calc();
|
||||||
|
let global = global(sym, math.clone(), calc);
|
||||||
Library { global, math, styles: styles(), items: items() }
|
Library { global, math, styles: styles(), items: items() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct the module with global definitions.
|
/// Construct the module with global definitions.
|
||||||
fn global(sym: Module, math: Module) -> Module {
|
fn global(sym: Module, math: Module, calc: Module) -> Module {
|
||||||
let mut global = Scope::deduplicating();
|
let mut global = Scope::deduplicating();
|
||||||
|
|
||||||
// Basics.
|
// Basics.
|
||||||
@ -106,12 +107,6 @@ fn global(sym: Module, math: Module) -> Module {
|
|||||||
global.def_func::<compute::LabelFunc>("label");
|
global.def_func::<compute::LabelFunc>("label");
|
||||||
global.def_func::<compute::RegexFunc>("regex");
|
global.def_func::<compute::RegexFunc>("regex");
|
||||||
global.def_func::<compute::RangeFunc>("range");
|
global.def_func::<compute::RangeFunc>("range");
|
||||||
global.def_func::<compute::AbsFunc>("abs");
|
|
||||||
global.def_func::<compute::MinFunc>("min");
|
|
||||||
global.def_func::<compute::MaxFunc>("max");
|
|
||||||
global.def_func::<compute::EvenFunc>("even");
|
|
||||||
global.def_func::<compute::OddFunc>("odd");
|
|
||||||
global.def_func::<compute::ModFunc>("mod");
|
|
||||||
global.def_func::<compute::ReadFunc>("read");
|
global.def_func::<compute::ReadFunc>("read");
|
||||||
global.def_func::<compute::CsvFunc>("csv");
|
global.def_func::<compute::CsvFunc>("csv");
|
||||||
global.def_func::<compute::JsonFunc>("json");
|
global.def_func::<compute::JsonFunc>("json");
|
||||||
@ -119,6 +114,9 @@ fn global(sym: Module, math: Module) -> Module {
|
|||||||
global.def_func::<compute::LoremFunc>("lorem");
|
global.def_func::<compute::LoremFunc>("lorem");
|
||||||
global.def_func::<compute::NumberingFunc>("numbering");
|
global.def_func::<compute::NumberingFunc>("numbering");
|
||||||
|
|
||||||
|
// Calc.
|
||||||
|
global.define("calc", calc);
|
||||||
|
|
||||||
// Colors.
|
// Colors.
|
||||||
global.define("black", Color::BLACK);
|
global.define("black", Color::BLACK);
|
||||||
global.define("gray", Color::GRAY);
|
global.define("gray", Color::GRAY);
|
||||||
|
@ -64,6 +64,11 @@ impl Angle {
|
|||||||
pub fn cos(self) -> f64 {
|
pub fn cos(self) -> f64 {
|
||||||
self.to_rad().cos()
|
self.to_rad().cos()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the tangent of this angle in radians.
|
||||||
|
pub fn tan(self) -> f64 {
|
||||||
|
self.to_rad().tan()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Numeric for Angle {
|
impl Numeric for Angle {
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
spacing: 0.65em - 3pt,
|
spacing: 0.65em - 3pt,
|
||||||
tight: false,
|
tight: false,
|
||||||
numbering: n => text(
|
numbering: n => text(
|
||||||
fill: (red, green, blue).at(mod(n, 3)),
|
fill: (red, green, blue).at(calc.mod(n, 3)),
|
||||||
numbering("A", n),
|
numbering("A", n),
|
||||||
),
|
),
|
||||||
[Red], [Green], [Blue],
|
[Red], [Green], [Blue],
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
#set page(height: 70pt)
|
#set page(height: 70pt)
|
||||||
#set table(fill: (x, y) => if even(x + y) { rgb("aaa") })
|
#set table(fill: (x, y) => if calc.even(x + y) { rgb("aaa") })
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (1fr,) * 3,
|
columns: (1fr,) * 3,
|
||||||
|
@ -148,8 +148,8 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test the `filter` method.
|
// Test the `filter` method.
|
||||||
#test(().filter(even), ())
|
#test(().filter(calc.even), ())
|
||||||
#test((1, 2, 3, 4).filter(even), (2, 4))
|
#test((1, 2, 3, 4).filter(calc.even), (2, 4))
|
||||||
#test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1))
|
#test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
#while x < 8 {
|
#while x < 8 {
|
||||||
i += 1
|
i += 1
|
||||||
if mod(i, 3) == 0 {
|
if calc.mod(i, 3) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
x += i
|
x += i
|
||||||
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
#let x = for i in range(5) {
|
#let x = for i in range(5) {
|
||||||
"a"
|
"a"
|
||||||
if mod(i, 3) == 0 {
|
if calc.mod(i, 3) == 0 {
|
||||||
"_"
|
"_"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,9 @@
|
|||||||
// Test spreading array and dictionary.
|
// Test spreading array and dictionary.
|
||||||
#{
|
#{
|
||||||
let more = (3, -3, 6, 10)
|
let more = (3, -3, 6, 10)
|
||||||
test(min(1, 2, ..more), -3)
|
test(calc.min(1, 2, ..more), -3)
|
||||||
test(max(..more, 9), 10)
|
test(calc.max(..more, 9), 10)
|
||||||
test(max(..more, 11), 11)
|
test(calc.max(..more, 11), 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
#{
|
#{
|
||||||
@ -56,8 +56,8 @@
|
|||||||
#f(..for x in () [])
|
#f(..for x in () [])
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 8-14 cannot spread string
|
// Error: 13-19 cannot spread string
|
||||||
#min(.."nope")
|
#calc.min(.."nope")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 10-14 expected identifier, found boolean
|
// Error: 10-14 expected identifier, found boolean
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
let caps = match.captures
|
let caps = match.captures
|
||||||
time += 60 * int(caps.at(0)) + int(caps.at(1))
|
time += 60 * int(caps.at(0)) + int(caps.at(1))
|
||||||
}
|
}
|
||||||
str(int(time / 60)) + ":" + str(mod(time, 60))
|
str(int(time / 60)) + ":" + str(calc.mod(time, 60))
|
||||||
}
|
}
|
||||||
|
|
||||||
#test(timesum(""), "0:0")
|
#test(timesum(""), "0:0")
|
||||||
|
@ -30,55 +30,55 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test the `abs` function.
|
// Test the `abs` function.
|
||||||
#test(abs(-3), 3)
|
#test(calc.abs(-3), 3)
|
||||||
#test(abs(3), 3)
|
#test(calc.abs(3), 3)
|
||||||
#test(abs(-0.0), 0.0)
|
#test(calc.abs(-0.0), 0.0)
|
||||||
#test(abs(0.0), -0.0)
|
#test(calc.abs(0.0), -0.0)
|
||||||
#test(abs(-3.14), 3.14)
|
#test(calc.abs(-3.14), 3.14)
|
||||||
#test(abs(50%), 50%)
|
#test(calc.abs(50%), 50%)
|
||||||
#test(abs(-25%), 25%)
|
#test(calc.abs(-25%), 25%)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 6-17 expected integer, float, length, angle, ratio, or fraction, found string
|
// Error: 11-22 expected integer, float, length, angle, ratio, or fraction, found string
|
||||||
#abs("no number")
|
#calc.abs("no number")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `even` and `odd` functions.
|
// Test the `even` and `odd` functions.
|
||||||
#test(even(2), true)
|
#test(calc.even(2), true)
|
||||||
#test(odd(2), false)
|
#test(calc.odd(2), false)
|
||||||
#test(odd(-1), true)
|
#test(calc.odd(-1), true)
|
||||||
#test(even(-11), false)
|
#test(calc.even(-11), false)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `mod` function.
|
// Test the `mod` function.
|
||||||
#test(mod(1, 1), 0)
|
#test(calc.mod(1, 1), 0)
|
||||||
#test(mod(5, 3), 2)
|
#test(calc.mod(5, 3), 2)
|
||||||
#test(mod(5, -3), 2)
|
#test(calc.mod(5, -3), 2)
|
||||||
#test(mod(22.5, 10), 2.5)
|
#test(calc.mod(22.5, 10), 2.5)
|
||||||
#test(mod(9, 4.5), 0)
|
#test(calc.mod(9, 4.5), 0)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 9-10 divisor must not be zero
|
// Error: 14-15 divisor must not be zero
|
||||||
#mod(5, 0)
|
#calc.mod(5, 0)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 11-14 divisor must not be zero
|
// Error: 16-19 divisor must not be zero
|
||||||
#mod(3.0, 0.0)
|
#calc.mod(3.0, 0.0)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `min` and `max` functions.
|
// Test the `min` and `max` functions.
|
||||||
#test(min(2, -4), -4)
|
#test(calc.min(2, -4), -4)
|
||||||
#test(min(3.5, 1e2, -0.1, 3), -0.1)
|
#test(calc.min(3.5, 1e2, -0.1, 3), -0.1)
|
||||||
#test(max(-3, 11), 11)
|
#test(calc.max(-3, 11), 11)
|
||||||
#test(min("hi"), "hi")
|
#test(calc.min("hi"), "hi")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 5-7 missing argument: value
|
// Error: 10-12 missing argument: value
|
||||||
#min()
|
#calc.min()
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 9-13 cannot compare integer and string
|
// Error: 14-18 cannot compare integer and string
|
||||||
#min(1, "hi")
|
#calc.min(1, "hi")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `range` function.
|
// Test the `range` function.
|
||||||
|
@ -19,7 +19,7 @@ It is the east, and Juliet is the sun.
|
|||||||
#set block(spacing: 100pt)
|
#set block(spacing: 100pt)
|
||||||
#show table: set block(above: 5pt, below: 5pt)
|
#show table: set block(above: 5pt, below: 5pt)
|
||||||
Hello
|
Hello
|
||||||
#table(columns: 4, fill: (x, y) => if odd(x + y) { silver })[A][B][C][D]
|
#table(columns: 4, fill: (x, y) => if calc.odd(x + y) { silver })[A][B][C][D]
|
||||||
|
|
||||||
---
|
---
|
||||||
// While we're at it, test the larger block spacing wins.
|
// While we're at it, test the larger block spacing wins.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user