summaryrefslogtreecommitdiff
path: root/library/src/compute
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/compute')
-rw-r--r--library/src/compute/calc.rs1024
-rw-r--r--library/src/compute/construct.rs743
-rw-r--r--library/src/compute/data.rs492
-rw-r--r--library/src/compute/foundations.rs215
-rw-r--r--library/src/compute/mod.rs39
5 files changed, 0 insertions, 2513 deletions
diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs
deleted file mode 100644
index 81715007..00000000
--- a/library/src/compute/calc.rs
+++ /dev/null
@@ -1,1024 +0,0 @@
-//! Calculations and processing of numeric values.
-
-use std::cmp;
-use std::cmp::Ordering;
-use std::ops::{Div, Rem};
-
-use typst::eval::{Module, Scope};
-
-use crate::prelude::*;
-
-/// A module with computational functions.
-pub fn module() -> Module {
- let mut scope = Scope::new();
- scope.define("abs", abs_func());
- scope.define("pow", pow_func());
- scope.define("exp", exp_func());
- scope.define("sqrt", sqrt_func());
- scope.define("sin", sin_func());
- scope.define("cos", cos_func());
- scope.define("tan", tan_func());
- scope.define("asin", asin_func());
- scope.define("acos", acos_func());
- scope.define("atan", atan_func());
- scope.define("atan2", atan2_func());
- scope.define("sinh", sinh_func());
- scope.define("cosh", cosh_func());
- scope.define("tanh", tanh_func());
- scope.define("log", log_func());
- scope.define("ln", ln_func());
- scope.define("fact", fact_func());
- scope.define("perm", perm_func());
- scope.define("binom", binom_func());
- scope.define("gcd", gcd_func());
- scope.define("lcm", lcm_func());
- scope.define("floor", floor_func());
- scope.define("ceil", ceil_func());
- scope.define("trunc", trunc_func());
- scope.define("fract", fract_func());
- scope.define("round", round_func());
- scope.define("clamp", clamp_func());
- scope.define("min", min_func());
- scope.define("max", max_func());
- scope.define("even", even_func());
- scope.define("odd", odd_func());
- scope.define("rem", rem_func());
- scope.define("quo", quo_func());
- scope.define("inf", f64::INFINITY);
- scope.define("nan", f64::NAN);
- scope.define("pi", std::f64::consts::PI);
- scope.define("e", std::f64::consts::E);
- Module::new("calc").with_scope(scope)
-}
-
-/// Calculates the absolute value of a numeric value.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.abs(-5) \
-/// #calc.abs(5pt - 2cm) \
-/// #calc.abs(2fr)
-/// ```
-///
-/// Display: Absolute
-/// Category: calculate
-#[func]
-pub fn abs(
- /// The value whose absolute value to calculate.
- value: ToAbs,
-) -> Value {
- value.0
-}
-
-/// A value of which the absolute value can be taken.
-pub struct ToAbs(Value);
-
-cast! {
- ToAbs,
- v: i64 => Self(v.abs().into_value()),
- v: f64 => Self(v.abs().into_value()),
- v: Length => Self(Value::Length(v.try_abs()
- .ok_or("cannot take absolute value of this length")?)),
- v: Angle => Self(Value::Angle(v.abs())),
- v: Ratio => Self(Value::Ratio(v.abs())),
- v: Fr => Self(Value::Fraction(v.abs())),
-}
-
-/// Raises a value to some exponent.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.pow(2, 3)
-/// ```
-///
-/// Display: Power
-/// Category: calculate
-#[func]
-pub fn pow(
- /// The base of the power.
- base: Num,
- /// The exponent of the power.
- exponent: Spanned<Num>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<Num> {
- match exponent.v {
- _ if exponent.v.float() == 0.0 && base.float() == 0.0 => {
- bail!(span, "zero to the power of zero is undefined")
- }
- Num::Int(i) if i32::try_from(i).is_err() => {
- bail!(exponent.span, "exponent is too large")
- }
- Num::Float(f) if !f.is_normal() && f != 0.0 => {
- bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN")
- }
- _ => {}
- };
-
- let result = match (base, exponent.v) {
- (Num::Int(a), Num::Int(b)) if b >= 0 => a
- .checked_pow(b as u32)
- .map(Num::Int)
- .ok_or("the result is too large")
- .at(span)?,
- (a, b) => Num::Float(if a.float() == std::f64::consts::E {
- b.float().exp()
- } else if a.float() == 2.0 {
- b.float().exp2()
- } else if let Num::Int(b) = b {
- a.float().powi(b as i32)
- } else {
- a.float().powf(b.float())
- }),
- };
-
- if result.float().is_nan() {
- bail!(span, "the result is not a real number")
- }
-
- Ok(result)
-}
-
-/// Raises a value to some exponent of e.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.exp(1)
-/// ```
-///
-/// Display: Exponential
-/// Category: calculate
-#[func]
-pub fn exp(
- /// The exponent of the power.
- exponent: Spanned<Num>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<f64> {
- match exponent.v {
- Num::Int(i) if i32::try_from(i).is_err() => {
- bail!(exponent.span, "exponent is too large")
- }
- Num::Float(f) if !f.is_normal() && f != 0.0 => {
- bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN")
- }
- _ => {}
- };
-
- let result = exponent.v.float().exp();
- if result.is_nan() {
- bail!(span, "the result is not a real number")
- }
-
- Ok(result)
-}
-
-/// Extracts the square root of a number.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.sqrt(16) \
-/// #calc.sqrt(2.5)
-/// ```
-///
-/// Display: Square Root
-/// Category: calculate
-#[func]
-pub fn sqrt(
- /// The number whose square root to calculate. Must be non-negative.
- value: Spanned<Num>,
-) -> SourceResult<f64> {
- if value.v.float() < 0.0 {
- bail!(value.span, "cannot take square root of negative number");
- }
- Ok(value.v.float().sqrt())
-}
-
-/// Calculates the sine of an angle.
-///
-/// When called with an integer or a float, they will be interpreted as
-/// radians.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.sin(90deg) == calc.sin(-270deg))
-/// #calc.sin(1.5) \
-/// #calc.sin(90deg)
-/// ```
-///
-/// Display: Sine
-/// Category: calculate
-#[func]
-pub fn sin(
- /// The angle whose sine to calculate.
- angle: AngleLike,
-) -> f64 {
- match angle {
- AngleLike::Angle(a) => a.sin(),
- AngleLike::Int(n) => (n as f64).sin(),
- AngleLike::Float(n) => n.sin(),
- }
-}
-
-/// Calculates the cosine of an angle.
-///
-/// When called with an integer or a float, they will be interpreted as
-/// radians.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.cos(90deg) \
-/// #calc.cos(1.5) \
-/// #calc.cos(90deg)
-/// ```
-///
-/// Display: Cosine
-/// Category: calculate
-#[func]
-pub fn cos(
- /// The angle whose cosine to calculate.
- angle: AngleLike,
-) -> f64 {
- match angle {
- AngleLike::Angle(a) => a.cos(),
- AngleLike::Int(n) => (n as f64).cos(),
- AngleLike::Float(n) => n.cos(),
- }
-}
-
-/// Calculates the tangent of an angle.
-///
-/// When called with an integer or a float, they will be interpreted as
-/// radians.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.tan(1.5) \
-/// #calc.tan(90deg)
-/// ```
-///
-/// Display: Tangent
-/// Category: calculate
-#[func]
-pub fn tan(
- /// The angle whose tangent to calculate.
- angle: AngleLike,
-) -> f64 {
- match angle {
- AngleLike::Angle(a) => a.tan(),
- AngleLike::Int(n) => (n as f64).tan(),
- AngleLike::Float(n) => n.tan(),
- }
-}
-
-/// Calculates the arcsine of a number.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.asin(0) \
-/// #calc.asin(1)
-/// ```
-///
-/// Display: Arcsine
-/// Category: calculate
-#[func]
-pub fn asin(
- /// The number whose arcsine to calculate. Must be between -1 and 1.
- value: Spanned<Num>,
-) -> SourceResult<Angle> {
- let val = value.v.float();
- if val < -1.0 || val > 1.0 {
- bail!(value.span, "value must be between -1 and 1");
- }
- Ok(Angle::rad(val.asin()))
-}
-
-/// Calculates the arccosine of a number.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.acos(0) \
-/// #calc.acos(1)
-/// ```
-///
-/// Display: Arccosine
-/// Category: calculate
-#[func]
-pub fn acos(
- /// The number whose arcsine to calculate. Must be between -1 and 1.
- value: Spanned<Num>,
-) -> SourceResult<Angle> {
- let val = value.v.float();
- if val < -1.0 || val > 1.0 {
- bail!(value.span, "value must be between -1 and 1");
- }
- Ok(Angle::rad(val.acos()))
-}
-
-/// Calculates the arctangent of a number.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.atan(0) \
-/// #calc.atan(1)
-/// ```
-///
-/// Display: Arctangent
-/// Category: calculate
-#[func]
-pub fn atan(
- /// The number whose arctangent to calculate.
- value: Num,
-) -> Angle {
- Angle::rad(value.float().atan())
-}
-
-/// Calculates the four-quadrant arctangent of a coordinate.
-///
-/// The arguments are `(x, y)`, not `(y, x)`.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.atan2(1, 1) \
-/// #calc.atan2(-2, -3)
-/// ```
-///
-/// Display: Four-quadrant Arctangent
-/// Category: calculate
-#[func]
-pub fn atan2(
- /// The X coordinate.
- x: Num,
- /// The Y coordinate.
- y: Num,
-) -> Angle {
- Angle::rad(f64::atan2(y.float(), x.float()))
-}
-
-/// Calculates the hyperbolic sine of an angle.
-///
-/// When called with an integer or a float, they will be interpreted as radians.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.sinh(0) \
-/// #calc.sinh(45deg)
-/// ```
-///
-/// Display: Hyperbolic sine
-/// Category: calculate
-#[func]
-pub fn sinh(
- /// The angle whose hyperbolic sine to calculate.
- angle: AngleLike,
-) -> f64 {
- match angle {
- AngleLike::Angle(a) => a.to_rad().sinh(),
- AngleLike::Int(n) => (n as f64).sinh(),
- AngleLike::Float(n) => n.sinh(),
- }
-}
-
-/// Calculates the hyperbolic cosine of an angle.
-///
-/// When called with an integer or a float, they will be interpreted as radians.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.cosh(0) \
-/// #calc.cosh(45deg)
-/// ```
-///
-/// Display: Hyperbolic cosine
-/// Category: calculate
-#[func]
-pub fn cosh(
- /// The angle whose hyperbolic cosine to calculate.
- angle: AngleLike,
-) -> f64 {
- match angle {
- AngleLike::Angle(a) => a.to_rad().cosh(),
- AngleLike::Int(n) => (n as f64).cosh(),
- AngleLike::Float(n) => n.cosh(),
- }
-}
-
-/// Calculates the hyperbolic tangent of an angle.
-///
-/// When called with an integer or a float, they will be interpreted as radians.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.tanh(0) \
-/// #calc.tanh(45deg)
-/// ```
-///
-/// Display: Hyperbolic tangent
-/// Category: calculate
-#[func]
-pub fn tanh(
- /// The angle whose hyperbolic tangent to calculate.
- angle: AngleLike,
-) -> f64 {
- match angle {
- AngleLike::Angle(a) => a.to_rad().tanh(),
- AngleLike::Int(n) => (n as f64).tanh(),
- AngleLike::Float(n) => n.tanh(),
- }
-}
-
-/// Calculates the logarithm of a number.
-///
-/// If the base is not specified, the logarithm is calculated in base 10.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.log(100)
-/// ```
-///
-/// Display: Logarithm
-/// Category: calculate
-#[func]
-pub fn log(
- /// The number whose logarithm to calculate. Must be strictly positive.
- value: Spanned<Num>,
- /// The base of the logarithm. May not be zero.
- #[named]
- #[default(Spanned::new(10.0, Span::detached()))]
- base: Spanned<f64>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<f64> {
- let number = value.v.float();
- if number <= 0.0 {
- bail!(value.span, "value must be strictly positive")
- }
-
- if !base.v.is_normal() {
- bail!(base.span, "base may not be zero, NaN, infinite, or subnormal")
- }
-
- let result = if base.v == std::f64::consts::E {
- number.ln()
- } else if base.v == 2.0 {
- number.log2()
- } else if base.v == 10.0 {
- number.log10()
- } else {
- number.log(base.v)
- };
-
- if result.is_infinite() || result.is_nan() {
- bail!(span, "the result is not a real number")
- }
-
- Ok(result)
-}
-
-/// Calculates the natural logarithm of a number.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.ln(calc.e)
-/// ```
-///
-/// Display: Natural Logarithm
-/// Category: calculate
-#[func]
-pub fn ln(
- /// The number whose logarithm to calculate. Must be strictly positive.
- value: Spanned<Num>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<f64> {
- let number = value.v.float();
- if number <= 0.0 {
- bail!(value.span, "value must be strictly positive")
- }
-
- let result = number.ln();
- if result.is_infinite() {
- bail!(span, "result close to -inf")
- }
-
- Ok(result)
-}
-
-/// Calculates the factorial of a number.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.fact(5)
-/// ```
-///
-/// Display: Factorial
-/// Category: calculate
-#[func]
-pub fn fact(
- /// The number whose factorial to calculate. Must be non-negative.
- number: u64,
-) -> StrResult<i64> {
- Ok(fact_impl(1, number).ok_or("the result is too large")?)
-}
-
-/// Calculates a permutation.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.perm(10, 5)
-/// ```
-///
-/// Display: Permutation
-/// Category: calculate
-#[func]
-pub fn perm(
- /// The base number. Must be non-negative.
- base: u64,
- /// The number of permutations. Must be non-negative.
- numbers: u64,
-) -> StrResult<i64> {
- // By convention.
- if base < numbers {
- return Ok(0);
- }
-
- Ok(fact_impl(base - numbers + 1, base).ok_or("the result is too large")?)
-}
-
-/// Calculates the product of a range of numbers. Used to calculate
-/// permutations. Returns None if the result is larger than `i64::MAX`
-fn fact_impl(start: u64, end: u64) -> Option<i64> {
- // By convention
- if end + 1 < start {
- return Some(0);
- }
-
- let real_start: u64 = cmp::max(1, start);
- let mut count: u64 = 1;
- for i in real_start..=end {
- count = count.checked_mul(i)?;
- }
-
- count.try_into().ok()
-}
-
-/// Calculates a binomial coefficient.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.binom(10, 5)
-/// ```
-///
-/// Display: Binomial
-/// Category: calculate
-#[func]
-pub fn binom(
- /// The upper coefficient. Must be non-negative.
- n: u64,
- /// The lower coefficient. Must be non-negative.
- k: u64,
-) -> StrResult<i64> {
- Ok(binom_impl(n, k).ok_or("the result is too large")?)
-}
-
-/// Calculates a binomial coefficient, with `n` the upper coefficient and `k`
-/// the lower coefficient. Returns `None` if the result is larger than
-/// `i64::MAX`
-fn binom_impl(n: u64, k: u64) -> Option<i64> {
- if k > n {
- return Some(0);
- }
-
- // By symmetry
- let real_k = cmp::min(n - k, k);
- if real_k == 0 {
- return Some(1);
- }
-
- let mut result: u64 = 1;
- for i in 0..real_k {
- result = result.checked_mul(n - i)?.checked_div(i + 1)?;
- }
-
- result.try_into().ok()
-}
-
-/// Calculates the greatest common divisor of two integers.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.gcd(7, 42)
-/// ```
-///
-/// Display: Greatest Common Divisor
-/// Category: calculate
-#[func]
-pub fn gcd(
- /// The first integer.
- a: i64,
- /// The second integer.
- b: i64,
-) -> i64 {
- let (mut a, mut b) = (a, b);
- while b != 0 {
- let temp = b;
- b = a % b;
- a = temp;
- }
-
- a.abs()
-}
-
-/// Calculates the least common multiple of two integers.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.lcm(96, 13)
-/// ```
-///
-/// Display: Least Common Multiple
-/// Category: calculate
-#[func]
-pub fn lcm(
- /// The first integer.
- a: i64,
- /// The second integer.
- b: i64,
-) -> StrResult<i64> {
- if a == b {
- return Ok(a.abs());
- }
-
- Ok(a.checked_div(gcd(a, b))
- .and_then(|gcd| gcd.checked_mul(b))
- .map(|v| v.abs())
- .ok_or("the return value is too large")?)
-}
-
-/// Rounds a number down to the nearest integer.
-///
-/// If the number is already an integer, it is returned unchanged.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.floor(3.14) == 3)
-/// #assert(calc.floor(3) == 3)
-/// #calc.floor(500.1)
-/// ```
-///
-/// Display: Round down
-/// Category: calculate
-#[func]
-pub fn floor(
- /// The number to round down.
- value: Num,
-) -> i64 {
- match value {
- Num::Int(n) => n,
- Num::Float(n) => n.floor() as i64,
- }
-}
-
-/// Rounds a number up to the nearest integer.
-///
-/// If the number is already an integer, it is returned unchanged.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.ceil(3.14) == 4)
-/// #assert(calc.ceil(3) == 3)
-/// #calc.ceil(500.1)
-/// ```
-///
-/// Display: Round up
-/// Category: calculate
-#[func]
-pub fn ceil(
- /// The number to round up.
- value: Num,
-) -> i64 {
- match value {
- Num::Int(n) => n,
- Num::Float(n) => n.ceil() as i64,
- }
-}
-
-/// Returns the integer part of a number.
-///
-/// If the number is already an integer, it is returned unchanged.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.trunc(3) == 3)
-/// #assert(calc.trunc(-3.7) == -3)
-/// #assert(calc.trunc(15.9) == 15)
-/// ```
-///
-/// Display: Truncate
-/// Category: calculate
-#[func]
-pub fn trunc(
- /// The number to truncate.
- value: Num,
-) -> i64 {
- match value {
- Num::Int(n) => n,
- Num::Float(n) => n.trunc() as i64,
- }
-}
-
-/// Returns the fractional part of a number.
-///
-/// If the number is an integer, returns `0`.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.fract(3) == 0)
-/// #calc.fract(-3.1)
-/// ```
-///
-/// Display: Fractional
-/// Category: calculate
-#[func]
-pub fn fract(
- /// The number to truncate.
- value: Num,
-) -> Num {
- match value {
- Num::Int(_) => Num::Int(0),
- Num::Float(n) => Num::Float(n.fract()),
- }
-}
-
-/// Rounds a number to the nearest integer.
-///
-/// Optionally, a number of decimal places can be specified.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.round(3.14) == 3)
-/// #assert(calc.round(3.5) == 4)
-/// #calc.round(3.1415, digits: 2)
-/// ```
-///
-/// Display: Round
-/// Category: calculate
-#[func]
-pub fn round(
- /// The number to round.
- value: Num,
- /// The number of decimal places.
- #[named]
- #[default(0)]
- digits: i64,
-) -> Num {
- match value {
- Num::Int(n) if digits == 0 => Num::Int(n),
- _ => {
- let n = value.float();
- let factor = 10.0_f64.powi(digits as i32);
- Num::Float((n * factor).round() / factor)
- }
- }
-}
-
-/// Clamps a number between a minimum and maximum value.
-///
-/// ## Example { #example }
-/// ```example
-/// #assert(calc.clamp(5, 0, 10) == 5)
-/// #assert(calc.clamp(5, 6, 10) == 6)
-/// #calc.clamp(5, 0, 4)
-/// ```
-///
-/// Display: Clamp
-/// Category: calculate
-#[func]
-pub fn clamp(
- /// The number to clamp.
- value: Num,
- /// The inclusive minimum value.
- min: Num,
- /// The inclusive maximum value.
- max: Spanned<Num>,
-) -> SourceResult<Num> {
- if max.v.float() < min.float() {
- bail!(max.span, "max must be greater than or equal to min")
- }
- Ok(value.apply3(min, max.v, i64::clamp, f64::clamp))
-}
-
-/// Determines the minimum of a sequence of values.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.min(1, -3, -5, 20, 3, 6) \
-/// #calc.min("typst", "in", "beta")
-/// ```
-///
-/// Display: Minimum
-/// Category: calculate
-#[func]
-pub fn min(
- /// The sequence of values from which to extract the minimum.
- /// Must not be empty.
- #[variadic]
- values: Vec<Spanned<Value>>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<Value> {
- minmax(span, values, Ordering::Less)
-}
-
-/// Determines the maximum of a sequence of values.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.max(1, -3, -5, 20, 3, 6) \
-/// #calc.max("typst", "in", "beta")
-/// ```
-///
-/// Display: Maximum
-/// Category: calculate
-#[func]
-pub fn max(
- /// The sequence of values from which to extract the maximum.
- /// Must not be empty.
- #[variadic]
- values: Vec<Spanned<Value>>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<Value> {
- minmax(span, values, Ordering::Greater)
-}
-
-/// Find the minimum or maximum of a sequence of values.
-fn minmax(
- span: Span,
- values: Vec<Spanned<Value>>,
- goal: Ordering,
-) -> SourceResult<Value> {
- let mut iter = values.into_iter();
- let Some(Spanned { v: mut extremum, ..}) = iter.next() else {
- bail!(span, "expected at least one value");
- };
-
- for Spanned { v, span } in iter {
- let ordering = typst::eval::ops::compare(&v, &extremum).at(span)?;
- if ordering == goal {
- extremum = v;
- }
- }
-
- Ok(extremum)
-}
-
-/// Determines whether an integer is even.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.even(4) \
-/// #calc.even(5) \
-/// #range(10).filter(calc.even)
-/// ```
-///
-/// Display: Even
-/// Category: calculate
-#[func]
-pub fn even(
- /// The number to check for evenness.
- value: i64,
-) -> bool {
- value % 2 == 0
-}
-
-/// Determines whether an integer is odd.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.odd(4) \
-/// #calc.odd(5) \
-/// #range(10).filter(calc.odd)
-/// ```
-///
-/// Display: Odd
-/// Category: calculate
-#[func]
-pub fn odd(
- /// The number to check for oddness.
- value: i64,
-) -> bool {
- value % 2 != 0
-}
-
-/// Calculates the remainder of two numbers.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.rem(20, 6) \
-/// #calc.rem(1.75, 0.5)
-/// ```
-///
-/// Display: Remainder
-/// Category: calculate
-#[func]
-pub fn rem(
- /// The dividend of the remainder.
- dividend: Num,
- /// The divisor of the remainder.
- divisor: Spanned<Num>,
-) -> SourceResult<Num> {
- if divisor.v.float() == 0.0 {
- bail!(divisor.span, "divisor must not be zero");
- }
- Ok(dividend.apply2(divisor.v, Rem::rem, Rem::rem))
-}
-
-/// Calculates the quotient of two numbers.
-///
-/// ## Example { #example }
-/// ```example
-/// #calc.quo(14, 5) \
-/// #calc.quo(3.46, 0.5)
-/// ```
-///
-/// Display: Quotient
-/// Category: calculate
-#[func]
-pub fn quo(
- /// The dividend of the quotient.
- dividend: Num,
- /// The divisor of the quotient.
- divisor: Spanned<Num>,
-) -> SourceResult<i64> {
- if divisor.v.float() == 0.0 {
- bail!(divisor.span, "divisor must not be zero");
- }
-
- Ok(floor(dividend.apply2(divisor.v, Div::div, Div::div)))
-}
-
-/// A value which can be passed to functions that work with integers and floats.
-#[derive(Debug, Copy, Clone)]
-pub enum Num {
- Int(i64),
- Float(f64),
-}
-
-impl Num {
- pub fn apply2(
- self,
- other: Self,
- int: impl FnOnce(i64, i64) -> i64,
- float: impl FnOnce(f64, f64) -> f64,
- ) -> Num {
- match (self, other) {
- (Self::Int(a), Self::Int(b)) => Num::Int(int(a, b)),
- (a, b) => Num::Float(float(a.float(), b.float())),
- }
- }
-
- pub fn apply3(
- self,
- other: Self,
- third: Self,
- int: impl FnOnce(i64, i64, i64) -> i64,
- float: impl FnOnce(f64, f64, f64) -> f64,
- ) -> Num {
- match (self, other, third) {
- (Self::Int(a), Self::Int(b), Self::Int(c)) => Num::Int(int(a, b, c)),
- (a, b, c) => Num::Float(float(a.float(), b.float(), c.float())),
- }
- }
-
- pub fn float(self) -> f64 {
- match self {
- Self::Int(v) => v as f64,
- Self::Float(v) => v,
- }
- }
-}
-
-cast! {
- Num,
- self => match self {
- Self::Int(v) => v.into_value(),
- Self::Float(v) => v.into_value(),
- },
- v: i64 => Self::Int(v),
- v: f64 => Self::Float(v),
-}
-
-/// A value that can be passed to a trigonometric function.
-pub enum AngleLike {
- Int(i64),
- Float(f64),
- Angle(Angle),
-}
-
-cast! {
- AngleLike,
- v: i64 => Self::Int(v),
- v: f64 => Self::Float(v),
- v: Angle => Self::Angle(v),
-}
diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs
deleted file mode 100644
index 956212ee..00000000
--- a/library/src/compute/construct.rs
+++ /dev/null
@@ -1,743 +0,0 @@
-use std::num::NonZeroI64;
-use std::str::FromStr;
-
-use time::{Month, PrimitiveDateTime};
-
-use typst::eval::{Datetime, Regex};
-
-use crate::prelude::*;
-
-/// Converts a value to an integer.
-///
-/// - Booleans are converted to `0` or `1`.
-/// - Floats are floored to the next 64-bit integer.
-/// - Strings are parsed in base 10.
-///
-/// ## Example { #example }
-/// ```example
-/// #int(false) \
-/// #int(true) \
-/// #int(2.7) \
-/// #{ int("27") + int("4") }
-/// ```
-///
-/// Display: Integer
-/// Category: construct
-#[func]
-pub fn int(
- /// The value that should be converted to an integer.
- value: ToInt,
-) -> i64 {
- value.0
-}
-
-/// A value that can be cast to an integer.
-pub struct ToInt(i64);
-
-cast! {
- ToInt,
- v: bool => Self(v as i64),
- v: i64 => Self(v),
- v: f64 => Self(v as i64),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid integer: {}", v))?),
-}
-
-/// Converts a value to a float.
-///
-/// - Booleans are converted to `0.0` or `1.0`.
-/// - Integers are converted to the closest 64-bit float.
-/// - Ratios are divided by 100%.
-/// - Strings are parsed in base 10 to the closest 64-bit float.
-/// Exponential notation is supported.
-///
-/// ## Example { #example }
-/// ```example
-/// #float(false) \
-/// #float(true) \
-/// #float(4) \
-/// #float(40%) \
-/// #float("2.7") \
-/// #float("1e5")
-/// ```
-///
-/// Display: Float
-/// Category: construct
-#[func]
-pub fn float(
- /// The value that should be converted to a float.
- value: ToFloat,
-) -> f64 {
- value.0
-}
-
-/// A value that can be cast to a float.
-pub struct ToFloat(f64);
-
-cast! {
- ToFloat,
- v: bool => Self(v as i64 as f64),
- v: i64 => Self(v as f64),
- v: f64 => Self(v),
- v: Ratio => Self(v.get()),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid float: {}", v))?),
-}
-
-/// Creates a grayscale color.
-///
-/// ## Example { #example }
-/// ```example
-/// #for x in range(250, step: 50) {
-/// box(square(fill: luma(x)))
-/// }
-/// ```
-///
-/// Display: Luma
-/// Category: construct
-#[func]
-pub fn luma(
- /// The gray component.
- gray: Component,
-) -> Color {
- LumaColor::new(gray.0).into()
-}
-
-/// Creates an RGB(A) color.
-///
-/// The color is specified in the sRGB color space.
-///
-/// _Note:_ While you can specify transparent colors and Typst's preview will
-/// render them correctly, the PDF export does not handle them properly at the
-/// moment. This will be fixed in the future.
-///
-/// ## Example { #example }
-/// ```example
-/// #square(fill: rgb("#b1f2eb"))
-/// #square(fill: rgb(87, 127, 230))
-/// #square(fill: rgb(25%, 13%, 65%))
-/// ```
-///
-/// Display: RGB
-/// Category: construct
-#[func]
-pub fn rgb(
- /// The color in hexadecimal notation.
- ///
- /// Accepts three, four, six or eight hexadecimal digits and optionally
- /// a leading hashtag.
- ///
- /// If this string is given, the individual components should not be given.
- ///
- /// ```example
- /// #text(16pt, rgb("#239dad"))[
- /// *Typst*
- /// ]
- /// ```
- #[external]
- hex: EcoString,
- /// The red component.
- #[external]
- red: Component,
- /// The green component.
- #[external]
- green: Component,
- /// The blue component.
- #[external]
- blue: Component,
- /// The alpha component.
- #[external]
- alpha: Component,
- /// The arguments.
- args: Args,
-) -> SourceResult<Color> {
- let mut args = args;
- Ok(if let Some(string) = args.find::<Spanned<EcoString>>()? {
- match RgbaColor::from_str(&string.v) {
- Ok(color) => color.into(),
- Err(msg) => bail!(string.span, "{msg}"),
- }
- } else {
- let Component(r) = args.expect("red component")?;
- let Component(g) = args.expect("green component")?;
- let Component(b) = args.expect("blue component")?;
- let Component(a) = args.eat()?.unwrap_or(Component(255));
- RgbaColor::new(r, g, b, a).into()
- })
-}
-
-/// An integer or ratio component.
-pub struct Component(u8);
-
-cast! {
- Component,
- v: i64 => match v {
- 0 ..= 255 => Self(v as u8),
- _ => bail!("number must be between 0 and 255"),
- },
- v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- bail!("ratio must be between 0% and 100%");
- },
-}
-
-/// Creates a new datetime.
-///
-/// You can specify the [datetime]($type/datetime) using a year, month, day,
-/// hour, minute, and second. You can also get the current date with
-/// [`datetime.today`]($func/datetime.today).
-///
-/// ## Example
-/// ```example
-/// #let date = datetime(
-/// year: 2012,
-/// month: 8,
-/// day: 3,
-/// )
-///
-/// #date.display() \
-/// #date.display(
-/// "[day].[month].[year]"
-/// )
-/// ```
-///
-/// ## Format
-/// _Note_: Depending on which components of the datetime you specify, Typst
-/// will store it in one of the following three ways:
-/// * If you specify year, month and day, Typst will store just a date.
-/// * If you specify hour, minute and second, Typst will store just a time.
-/// * If you specify all of year, month, day, hour, minute and second, Typst
-/// will store a full datetime.
-///
-/// Depending on how it is stored, the [`display`]($type/datetime.display)
-/// method will choose a different formatting by default.
-///
-/// Display: Datetime
-/// Category: construct
-#[func]
-#[scope(
- scope.define("today", datetime_today_func());
- scope
-)]
-pub fn datetime(
- /// The year of the datetime.
- #[named]
- year: Option<YearComponent>,
- /// The month of the datetime.
- #[named]
- month: Option<MonthComponent>,
- /// The day of the datetime.
- #[named]
- day: Option<DayComponent>,
- /// The hour of the datetime.
- #[named]
- hour: Option<HourComponent>,
- /// The minute of the datetime.
- #[named]
- minute: Option<MinuteComponent>,
- /// The second of the datetime.
- #[named]
- second: Option<SecondComponent>,
-) -> StrResult<Datetime> {
- let time = match (hour, minute, second) {
- (Some(hour), Some(minute), Some(second)) => {
- match time::Time::from_hms(hour.0, minute.0, second.0) {
- Ok(time) => Some(time),
- Err(_) => bail!("time is invalid"),
- }
- }
- (None, None, None) => None,
- _ => bail!("time is incomplete"),
- };
-
- let date = match (year, month, day) {
- (Some(year), Some(month), Some(day)) => {
- match time::Date::from_calendar_date(year.0, month.0, day.0) {
- Ok(date) => Some(date),
- Err(_) => bail!("date is invalid"),
- }
- }
- (None, None, None) => None,
- _ => bail!("date is incomplete"),
- };
-
- Ok(match (date, time) {
- (Some(date), Some(time)) => {
- Datetime::Datetime(PrimitiveDateTime::new(date, time))
- }
- (Some(date), None) => Datetime::Date(date),
- (None, Some(time)) => Datetime::Time(time),
- (None, None) => {
- bail!("at least one of date or time must be fully specified")
- }
- })
-}
-
-pub struct YearComponent(i32);
-pub struct MonthComponent(Month);
-pub struct DayComponent(u8);
-pub struct HourComponent(u8);
-pub struct MinuteComponent(u8);
-pub struct SecondComponent(u8);
-
-cast! {
- YearComponent,
- v: i32 => Self(v),
-}
-
-cast! {
- MonthComponent,
- v: u8 => Self(Month::try_from(v).map_err(|_| "month is invalid")?)
-}
-
-cast! {
- DayComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- HourComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- MinuteComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- SecondComponent,
- v: u8 => Self(v),
-}
-
-/// Returns the current date.
-///
-/// Refer to the documentation of the [`display`]($type/datetime.display) method
-/// for details on how to affect the formatting of the date.
-///
-/// ## Example
-/// ```example
-/// Today's date is
-/// #datetime.today().display().
-/// ```
-///
-/// Display: Today
-/// Category: construct
-#[func]
-pub fn datetime_today(
- /// An offset to apply to the current UTC date. If set to `{auto}`, the
- /// offset will be the local offset.
- #[named]
- #[default]
- offset: Smart<i64>,
- /// The virtual machine.
- vt: &mut Vt,
-) -> StrResult<Datetime> {
- Ok(vt
- .world
- .today(offset.as_custom())
- .ok_or("unable to get the current date")?)
-}
-
-/// Creates a CMYK color.
-///
-/// This is useful if you want to target a specific printer. The conversion
-/// to RGB for display preview might differ from how your printer reproduces
-/// the color.
-///
-/// ## Example { #example }
-/// ```example
-/// #square(
-/// fill: cmyk(27%, 0%, 3%, 5%)
-/// )
-/// ````
-///
-/// Display: CMYK
-/// Category: construct
-#[func]
-pub fn cmyk(
- /// The cyan component.
- cyan: RatioComponent,
- /// The magenta component.
- magenta: RatioComponent,
- /// The yellow component.
- yellow: RatioComponent,
- /// The key component.
- key: RatioComponent,
-) -> Color {
- CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into()
-}
-
-/// A component that must be a ratio.
-pub struct RatioComponent(u8);
-
-cast! {
- RatioComponent,
- v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- bail!("ratio must be between 0% and 100%");
- },
-}
-
-/// Creates a custom symbol with modifiers.
-///
-/// ## Example { #example }
-/// ```example
-/// #let envelope = symbol(
-/// "🖂",
-/// ("stamped", "🖃"),
-/// ("stamped.pen", "🖆"),
-/// ("lightning", "🖄"),
-/// ("fly", "🖅"),
-/// )
-///
-/// #envelope
-/// #envelope.stamped
-/// #envelope.stamped.pen
-/// #envelope.lightning
-/// #envelope.fly
-/// ```
-///
-/// Display: Symbol
-/// Category: construct
-#[func]
-pub fn symbol(
- /// The variants of the symbol.
- ///
- /// Can be a just a string consisting of a single character for the
- /// modifierless variant or an array with two strings specifying the modifiers
- /// and the symbol. Individual modifiers should be separated by dots. When
- /// displaying a symbol, Typst selects the first from the variants that have
- /// all attached modifiers and the minimum number of other modifiers.
- #[variadic]
- variants: Vec<Spanned<Variant>>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<Symbol> {
- let mut list = Vec::new();
- if variants.is_empty() {
- bail!(span, "expected at least one variant");
- }
- for Spanned { v, span } in variants {
- if list.iter().any(|(prev, _)| &v.0 == prev) {
- bail!(span, "duplicate variant");
- }
- list.push((v.0, v.1));
- }
- Ok(Symbol::runtime(list.into_boxed_slice()))
-}
-
-/// A value that can be cast to a symbol.
-pub struct Variant(EcoString, char);
-
-cast! {
- Variant,
- c: char => Self(EcoString::new(), c),
- array: Array => {
- let mut iter = array.into_iter();
- match (iter.next(), iter.next(), iter.next()) {
- (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
- _ => bail!("point array must contain exactly two entries"),
- }
- },
-}
-
-/// Converts a value to a string.
-///
-/// - Integers are formatted in base 10. This can be overridden with the
-/// optional `base` parameter.
-/// - Floats are formatted in base 10 and never in exponential notation.
-/// - From labels the name is extracted.
-///
-/// If you wish to convert from and to Unicode code points, see
-/// [`str.to-unicode`]($func/str.to-unicode) and
-/// [`str.from-unicode`]($func/str.from-unicode).
-///
-/// ## Example { #example }
-/// ```example
-/// #str(10) \
-/// #str(4000, base: 16) \
-/// #str(2.7) \
-/// #str(1e8) \
-/// #str(<intro>)
-/// ```
-///
-/// Display: String
-/// Category: construct
-#[func]
-#[scope(
- scope.define("to-unicode", str_to_unicode_func());
- scope.define("from-unicode", str_from_unicode_func());
- scope
-)]
-pub fn str(
- /// The value that should be converted to a string.
- value: ToStr,
- /// The base (radix) to display integers in, between 2 and 36.
- #[named]
- #[default(Spanned::new(10, Span::detached()))]
- base: Spanned<i64>,
-) -> SourceResult<Str> {
- Ok(match value {
- ToStr::Str(s) => {
- if base.v != 10 {
- bail!(base.span, "base is only supported for integers");
- }
- s
- }
- ToStr::Int(n) => {
- if base.v < 2 || base.v > 36 {
- bail!(base.span, "base must be between 2 and 36");
- }
- int_to_base(n, base.v).into()
- }
- })
-}
-
-/// A value that can be cast to a string.
-pub enum ToStr {
- /// A string value ready to be used as-is.
- Str(Str),
- /// An integer about to be formatted in a given base.
- Int(i64),
-}
-
-cast! {
- ToStr,
- v: i64 => Self::Int(v),
- v: f64 => Self::Str(format_str!("{}", v)),
- v: Label => Self::Str(v.0.into()),
- v: Str => Self::Str(v),
-}
-
-/// Format an integer in a base.
-fn int_to_base(mut n: i64, base: i64) -> EcoString {
- if n == 0 {
- return "0".into();
- }
-
- // In Rust, `format!("{:x}", -14i64)` is not `-e` but `fffffffffffffff2`.
- // So we can only use the built-in for decimal, not bin/oct/hex.
- if base == 10 {
- return eco_format!("{n}");
- }
-
- // The largest output is `to_base(i64::MIN, 2)`, which is 65 chars long.
- const SIZE: usize = 65;
- let mut digits = [b'\0'; SIZE];
- let mut i = SIZE;
-
- // It's tempting to take the absolute value, but this will fail for i64::MIN.
- // Instead, we turn n negative, as -i64::MAX is perfectly representable.
- let negative = n < 0;
- if n > 0 {
- n = -n;
- }
-
- while n != 0 {
- let digit = char::from_digit(-(n % base) as u32, base as u32);
- i -= 1;
- digits[i] = digit.unwrap_or('?') as u8;
- n /= base;
- }
-
- if negative {
- i -= 1;
- digits[i] = b'-';
- }
-
- std::str::from_utf8(&digits[i..]).unwrap_or_default().into()
-}
-
-/// Converts a character into its corresponding code point.
-///
-/// ## Example
-/// ```example
-/// #str.to-unicode("a") \
-/// #"a\u{0300}".codepoints().map(str.to-unicode)
-/// ```
-///
-/// Display: String To Unicode
-/// Category: construct
-#[func]
-pub fn str_to_unicode(
- /// The character that should be converted.
- value: char,
-) -> u32 {
- value.into()
-}
-
-/// Converts a Unicode code point into its corresponding string.
-///
-/// ```example
-/// #str.from-unicode(97)
-/// ```
-///
-/// Display: String From Unicode
-/// Category: construct
-#[func]
-pub fn str_from_unicode(
- /// The code point that should be converted.
- value: CodePoint,
-) -> Str {
- format_str!("{}", value.0)
-}
-
-/// The numeric representation of a single unicode code point.
-pub struct CodePoint(char);
-
-cast! {
- CodePoint,
- v: i64 => {
- Self(v.try_into().ok().and_then(|v: u32| v.try_into().ok()).ok_or_else(
- || eco_format!("{:#x} is not a valid codepoint", v),
- )?)
- },
-}
-
-/// Creates a label from a string.
-///
-/// Inserting a label into content attaches it to the closest previous element
-/// that is not a space. Then, the element can be [referenced]($func/ref) and
-/// styled through the label.
-///
-/// ## Example { #example }
-/// ```example
-/// #show <a>: set text(blue)
-/// #show label("b"): set text(red)
-///
-/// = Heading <a>
-/// *Strong* #label("b")
-/// ```
-///
-/// ## Syntax { #syntax }
-/// This function also has dedicated syntax: You can create a label by enclosing
-/// its name in angle brackets. This works both in markup and code.
-///
-/// Display: Label
-/// Category: construct
-#[func]
-pub fn label(
- /// The name of the label.
- name: EcoString,
-) -> Label {
- Label(name)
-}
-
-/// Creates a regular expression from a string.
-///
-/// The result can be used as a
-/// [show rule selector]($styling/#show-rules) and with
-/// [string methods]($type/string) like `find`, `split`, and `replace`.
-///
-/// See [the specification of the supported syntax](https://docs.rs/regex/latest/regex/#syntax).
-///
-/// ## Example { #example }
-/// ```example
-/// // Works with show rules.
-/// #show regex("\d+"): set text(red)
-///
-/// The numbers 1 to 10.
-///
-/// // Works with string methods.
-/// #("a,b;c"
-/// .split(regex("[,;]")))
-/// ```
-///
-/// Display: Regex
-/// Category: construct
-#[func]
-pub fn regex(
- /// The regular expression as a string.
- ///
- /// Most regex escape sequences just work because they are not valid Typst
- /// escape sequences. To produce regex escape sequences that are also valid in
- /// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim
- /// backslash, you would need to write `{regex("\\\\")}`.
- ///
- /// If you need many escape sequences, you can also create a raw element
- /// and extract its text to use it for your regular expressions:
- /// ```{regex(`\d+\.\d+\.\d+`.text)}```.
- regex: Spanned<EcoString>,
-) -> SourceResult<Regex> {
- Regex::new(&regex.v).at(regex.span)
-}
-
-/// Creates an array consisting of consecutive integers.
-///
-/// If you pass just one positional parameter, it is interpreted as the `end` of
-/// the range. If you pass two, they describe the `start` and `end` of the
-/// range.
-///
-/// ## Example { #example }
-/// ```example
-/// #range(5) \
-/// #range(2, 5) \
-/// #range(20, step: 4) \
-/// #range(21, step: 4) \
-/// #range(5, 2, step: -1)
-/// ```
-///
-/// Display: Range
-/// Category: construct
-#[func]
-pub fn range(
- /// The start of the range (inclusive).
- #[external]
- #[default]
- start: i64,
- /// The end of the range (exclusive).
- #[external]
- end: i64,
- /// The distance between the generated numbers.
- #[named]
- #[default(NonZeroI64::new(1).unwrap())]
- step: NonZeroI64,
- /// The arguments.
- args: Args,
-) -> SourceResult<Array> {
- let mut args = args;
- let first = args.expect::<i64>("end")?;
- let (start, end) = match args.eat::<i64>()? {
- Some(second) => (first, second),
- None => (0, first),
- };
-
- let step = step.get();
-
- let mut x = start;
- let mut array = Array::new();
-
- while x.cmp(&end) == 0.cmp(&step) {
- array.push(Value::Int(x));
- x += step;
- }
-
- Ok(array)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_to_base() {
- assert_eq!(&int_to_base(0, 10), "0");
- assert_eq!(&int_to_base(0, 16), "0");
- assert_eq!(&int_to_base(0, 36), "0");
- assert_eq!(
- &int_to_base(i64::MAX, 2),
- "111111111111111111111111111111111111111111111111111111111111111"
- );
- assert_eq!(
- &int_to_base(i64::MIN, 2),
- "-1000000000000000000000000000000000000000000000000000000000000000"
- );
- assert_eq!(&int_to_base(i64::MAX, 10), "9223372036854775807");
- assert_eq!(&int_to_base(i64::MIN, 10), "-9223372036854775808");
- assert_eq!(&int_to_base(i64::MAX, 16), "7fffffffffffffff");
- assert_eq!(&int_to_base(i64::MIN, 16), "-8000000000000000");
- assert_eq!(&int_to_base(i64::MAX, 36), "1y2p0ij32e8e7");
- assert_eq!(&int_to_base(i64::MIN, 36), "-1y2p0ij32e8e8");
- }
-}
diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs
deleted file mode 100644
index 6e3a298e..00000000
--- a/library/src/compute/data.rs
+++ /dev/null
@@ -1,492 +0,0 @@
-use typst::diag::{format_xml_like_error, FileError};
-use typst::eval::Datetime;
-
-use crate::prelude::*;
-
-/// Reads plain text from a file.
-///
-/// The file will be read and returned as a string.
-///
-/// ## Example { #example }
-/// ```example
-/// #let text = read("data.html")
-///
-/// An example for a HTML file:\
-/// #raw(text, lang: "html")
-/// ```
-///
-/// Display: Read
-/// Category: data-loading
-#[func]
-pub fn read(
- /// Path to a file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Str> {
- let Spanned { v: path, span } = path;
- let id = vm.location().join(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- let text = std::str::from_utf8(&data)
- .map_err(|_| "file is not valid utf-8")
- .at(span)?;
- Ok(text.into())
-}
-
-/// Reads structured data from a CSV file.
-///
-/// The CSV file will be read and parsed into a 2-dimensional array of strings:
-/// Each row in the CSV file will be represented as an array of strings, and all
-/// rows will be collected into a single array. Header rows will not be
-/// stripped.
-///
-/// ## Example { #example }
-/// ```example
-/// #let results = csv("data.csv")
-///
-/// #table(
-/// columns: 2,
-/// [*Condition*], [*Result*],
-/// ..results.flatten(),
-/// )
-/// ```
-///
-/// Display: CSV
-/// Category: data-loading
-#[func]
-pub fn csv(
- /// Path to a CSV file.
- path: Spanned<EcoString>,
- /// The delimiter that separates columns in the CSV file.
- /// Must be a single ASCII character.
- #[named]
- #[default]
- delimiter: Delimiter,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Array> {
- let Spanned { v: path, span } = path;
- let id = vm.location().join(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
-
- let mut builder = csv::ReaderBuilder::new();
- builder.has_headers(false);
- builder.delimiter(delimiter.0 as u8);
-
- let mut reader = builder.from_reader(data.as_slice());
- let mut array = Array::new();
-
- for (line, result) in reader.records().enumerate() {
- // Original solution use line from error, but that is incorrect with
- // `has_headers` set to `false`. See issue:
- // https://github.com/BurntSushi/rust-csv/issues/184
- let line = line + 1; // Counting lines from 1
- let row = result.map_err(|err| format_csv_error(err, line)).at(span)?;
- let sub = row.into_iter().map(|field| field.into_value()).collect();
- array.push(Value::Array(sub))
- }
-
- Ok(array)
-}
-
-/// The delimiter to use when parsing CSV files.
-pub struct Delimiter(char);
-
-impl Default for Delimiter {
- fn default() -> Self {
- Self(',')
- }
-}
-
-cast! {
- Delimiter,
- self => self.0.into_value(),
- v: EcoString => {
- let mut chars = v.chars();
- let first = chars.next().ok_or("delimiter must not be empty")?;
- if chars.next().is_some() {
- bail!("delimiter must be a single character");
- }
-
- if !first.is_ascii() {
- bail!("delimiter must be an ASCII character");
- }
-
- Self(first)
- },
-}
-
-/// Format the user-facing CSV error message.
-fn format_csv_error(error: csv::Error, line: usize) -> EcoString {
- match error.kind() {
- csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
- csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
- eco_format!(
- "failed to parse csv file: found {len} instead of {expected_len} fields in line {line}"
- )
- }
- _ => "failed to parse csv file".into(),
- }
-}
-
-/// Reads structured data from a JSON file.
-///
-/// The file must contain a valid JSON object or array. JSON objects will be
-/// converted into Typst dictionaries, and JSON arrays will be converted into
-/// Typst arrays. Strings and booleans will be converted into the Typst
-/// equivalents, `null` will be converted into `{none}`, and numbers will be
-/// converted to floats or integers depending on whether they are whole numbers.
-///
-/// The function returns a dictionary or an array, depending on the JSON file.
-///
-/// The JSON files in the example contain objects with the keys `temperature`,
-/// `unit`, and `weather`.
-///
-/// ## Example { #example }
-/// ```example
-/// #let forecast(day) = block[
-/// #box(square(
-/// width: 2cm,
-/// inset: 8pt,
-/// fill: if day.weather == "sunny" {
-/// yellow
-/// } else {
-/// aqua
-/// },
-/// align(
-/// bottom + right,
-/// strong(day.weather),
-/// ),
-/// ))
-/// #h(6pt)
-/// #set text(22pt, baseline: -8pt)
-/// #day.temperature °#day.unit
-/// ]
-///
-/// #forecast(json("monday.json"))
-/// #forecast(json("tuesday.json"))
-/// ```
-///
-/// Display: JSON
-/// Category: data-loading
-#[func]
-pub fn json(
- /// Path to a JSON file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Value> {
- let Spanned { v: path, span } = path;
- let id = vm.location().join(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- let value: serde_json::Value =
- serde_json::from_slice(&data).map_err(format_json_error).at(span)?;
- Ok(convert_json(value))
-}
-
-/// Convert a JSON value to a Typst value.
-fn convert_json(value: serde_json::Value) -> Value {
- match value {
- serde_json::Value::Null => Value::None,
- serde_json::Value::Bool(v) => v.into_value(),
- serde_json::Value::Number(v) => match v.as_i64() {
- Some(int) => int.into_value(),
- None => v.as_f64().unwrap_or(f64::NAN).into_value(),
- },
- serde_json::Value::String(v) => v.into_value(),
- serde_json::Value::Array(v) => {
- v.into_iter().map(convert_json).collect::<Array>().into_value()
- }
- serde_json::Value::Object(v) => v
- .into_iter()
- .map(|(key, value)| (key.into(), convert_json(value)))
- .collect::<Dict>()
- .into_value(),
- }
-}
-
-/// Format the user-facing JSON error message.
-fn format_json_error(error: serde_json::Error) -> EcoString {
- assert!(error.is_syntax() || error.is_eof());
- eco_format!("failed to parse json file: syntax error in line {}", error.line())
-}
-
-/// Reads structured data from a TOML file.
-///
-/// The file must contain a valid TOML table. TOML tables will be
-/// converted into Typst dictionaries, and TOML arrays will be converted into
-/// Typst arrays. Strings and booleans will be converted into the Typst
-/// equivalents and numbers will be converted to floats or integers depending on
-/// whether they are whole numbers. For the time being, datetimes will be
-/// converted to strings as Typst does not have a built-in datetime yet.
-///
-/// The TOML file in the example consists of a table with the keys `title`,
-/// `version`, and `authors`.
-///
-/// ## Example { #example }
-/// ```example
-/// #let details = toml("details.toml")
-///
-/// Title: #details.title \
-/// Version: #details.version \
-/// Authors: #(details.authors
-/// .join(", ", last: " and "))
-/// ```
-///
-/// Display: TOML
-/// Category: data-loading
-#[func]
-pub fn toml(
- /// Path to a TOML file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Value> {
- let Spanned { v: path, span } = path;
- let id = vm.location().join(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
-
- let raw = std::str::from_utf8(&data)
- .map_err(|_| "file is not valid utf-8")
- .at(span)?;
-
- let value: toml::Value = toml::from_str(raw).map_err(format_toml_error).at(span)?;
- Ok(convert_toml(value))
-}
-
-/// Convert a TOML value to a Typst value.
-fn convert_toml(value: toml::Value) -> Value {
- match value {
- toml::Value::String(v) => v.into_value(),
- toml::Value::Integer(v) => v.into_value(),
- toml::Value::Float(v) => v.into_value(),
- toml::Value::Boolean(v) => v.into_value(),
- toml::Value::Array(v) => {
- v.into_iter().map(convert_toml).collect::<Array>().into_value()
- }
- toml::Value::Table(v) => v
- .into_iter()
- .map(|(key, value)| (key.into(), convert_toml(value)))
- .collect::<Dict>()
- .into_value(),
- toml::Value::Datetime(v) => match (v.date, v.time) {
- (None, None) => Value::None,
- (Some(date), None) => {
- Datetime::from_ymd(date.year as i32, date.month, date.day).into_value()
- }
- (None, Some(time)) => {
- Datetime::from_hms(time.hour, time.minute, time.second).into_value()
- }
- (Some(date), Some(time)) => Datetime::from_ymd_hms(
- date.year as i32,
- date.month,
- date.day,
- time.hour,
- time.minute,
- time.second,
- )
- .into_value(),
- },
- }
-}
-
-/// Format the user-facing TOML error message.
-fn format_toml_error(error: toml::de::Error) -> EcoString {
- if let Some(range) = error.span() {
- eco_format!(
- "failed to parse toml file: {}, index {}-{}",
- error.message(),
- range.start,
- range.end
- )
- } else {
- eco_format!("failed to parse toml file: {}", error.message())
- }
-}
-
-/// Reads structured data from a YAML file.
-///
-/// The file must contain a valid YAML object or array. YAML mappings will be
-/// converted into Typst dictionaries, and YAML sequences will be converted into
-/// Typst arrays. Strings and booleans will be converted into the Typst
-/// equivalents, null-values (`null`, `~` or empty ``) will be converted into
-/// `{none}`, and numbers will be converted to floats or integers depending on
-/// whether they are whole numbers.
-///
-/// Note that mapping keys that are not a string cause the entry to be
-/// discarded.
-///
-/// Custom YAML tags are ignored, though the loaded value will still be
-/// present.
-///
-/// The function returns a dictionary or value or an array, depending on
-/// the YAML file.
-///
-/// The YAML files in the example contain objects with authors as keys,
-/// each with a sequence of their own submapping with the keys
-/// "title" and "published"
-///
-/// ## Example { #example }
-/// ```example
-/// #let bookshelf(contents) = {
-/// for (author, works) in contents {
-/// author
-/// for work in works [
-/// - #work.title (#work.published)
-/// ]
-/// }
-/// }
-///
-/// #bookshelf(
-/// yaml("scifi-authors.yaml")
-/// )
-/// ```
-///
-/// Display: YAML
-/// Category: data-loading
-#[func]
-pub fn yaml(
- /// Path to a YAML file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Value> {
- let Spanned { v: path, span } = path;
- let id = vm.location().join(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- let value: serde_yaml::Value =
- serde_yaml::from_slice(&data).map_err(format_yaml_error).at(span)?;
- Ok(convert_yaml(value))
-}
-
-/// Convert a YAML value to a Typst value.
-fn convert_yaml(value: serde_yaml::Value) -> Value {
- match value {
- serde_yaml::Value::Null => Value::None,
- serde_yaml::Value::Bool(v) => v.into_value(),
- serde_yaml::Value::Number(v) => match v.as_i64() {
- Some(int) => int.into_value(),
- None => v.as_f64().unwrap_or(f64::NAN).into_value(),
- },
- serde_yaml::Value::String(v) => v.into_value(),
- serde_yaml::Value::Sequence(v) => {
- v.into_iter().map(convert_yaml).collect::<Array>().into_value()
- }
- serde_yaml::Value::Mapping(v) => v
- .into_iter()
- .map(|(key, value)| (convert_yaml_key(key), convert_yaml(value)))
- .filter_map(|(key, value)| key.map(|key| (key, value)))
- .collect::<Dict>()
- .into_value(),
- }
-}
-
-/// Converts an arbitrary YAML mapping key into a Typst Dict Key.
-/// Currently it only does so for strings, everything else
-/// returns None
-fn convert_yaml_key(key: serde_yaml::Value) -> Option<Str> {
- match key {
- serde_yaml::Value::String(v) => Some(Str::from(v)),
- _ => None,
- }
-}
-
-/// Format the user-facing YAML error message.
-fn format_yaml_error(error: serde_yaml::Error) -> EcoString {
- eco_format!("failed to parse yaml file: {}", error.to_string().trim())
-}
-
-/// Reads structured data from an XML file.
-///
-/// The XML file is parsed into an array of dictionaries and strings. XML nodes
-/// can be elements or strings. Elements are represented as dictionaries with
-/// the the following keys:
-///
-/// - `tag`: The name of the element as a string.
-/// - `attrs`: A dictionary of the element's attributes as strings.
-/// - `children`: An array of the element's child nodes.
-///
-/// The XML file in the example contains a root `news` tag with multiple
-/// `article` tags. Each article has a `title`, `author`, and `content` tag. The
-/// `content` tag contains one or more paragraphs, which are represented as `p`
-/// tags.
-///
-/// ## Example { #example }
-/// ```example
-/// #let find-child(elem, tag) = {
-/// elem.children
-/// .find(e => "tag" in e and e.tag == tag)
-/// }
-///
-/// #let article(elem) = {
-/// let title = find-child(elem, "title")
-/// let author = find-child(elem, "author")
-/// let pars = find-child(elem, "content")
-///
-/// heading(title.children.first())
-/// text(10pt, weight: "medium")[
-/// Published by
-/// #author.children.first()
-/// ]
-///
-/// for p in pars.children {
-/// if (type(p) == "dictionary") {
-/// parbreak()
-/// p.children.first()
-/// }
-/// }
-/// }
-///
-/// #let data = xml("example.xml")
-/// #for elem in data.first().children {
-/// if (type(elem) == "dictionary") {
-/// article(elem)
-/// }
-/// }
-/// ```
-///
-/// Display: XML
-/// Category: data-loading
-#[func]
-pub fn xml(
- /// Path to an XML file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Value> {
- let Spanned { v: path, span } = path;
- let id = vm.location().join(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?;
- let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
- Ok(convert_xml(document.root()))
-}
-
-/// Convert an XML node to a Typst value.
-fn convert_xml(node: roxmltree::Node) -> Value {
- if node.is_text() {
- return node.text().unwrap_or_default().into_value();
- }
-
- let children: Array = node.children().map(convert_xml).collect();
- if node.is_root() {
- return Value::Array(children);
- }
-
- let tag: Str = node.tag_name().name().into();
- let attrs: Dict = node
- .attributes()
- .map(|attr| (attr.name().into(), attr.value().into_value()))
- .collect();
-
- Value::Dict(dict! {
- "tag" => tag,
- "attrs" => attrs,
- "children" => children,
- })
-}
-
-/// Format the user-facing XML error message.
-fn format_xml_error(error: roxmltree::Error) -> EcoString {
- format_xml_like_error("xml file", error)
-}
diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs
deleted file mode 100644
index f83d71a0..00000000
--- a/library/src/compute/foundations.rs
+++ /dev/null
@@ -1,215 +0,0 @@
-use crate::prelude::*;
-
-/// Determines the type of a value.
-///
-/// Returns the name of the value's type.
-///
-/// ## Example { #example }
-/// ```example
-/// #type(12) \
-/// #type(14.7) \
-/// #type("hello") \
-/// #type(none) \
-/// #type([Hi]) \
-/// #type(x => x + 1)
-/// ```
-///
-/// Display: Type
-/// Category: foundations
-#[func]
-pub fn type_(
- /// The value whose type's to determine.
- value: Value,
-) -> Str {
- value.type_name().into()
-}
-
-/// Returns the string representation of a value.
-///
-/// When inserted into content, most values are displayed as this representation
-/// in monospace with syntax-highlighting. The exceptions are `{none}`,
-/// integers, floats, strings, content, and functions.
-///
-/// **Note:** This function is for debugging purposes. Its output should not be
-/// considered stable and may change at any time!
-///
-/// ## Example { #example }
-/// ```example
-/// #none vs #repr(none) \
-/// #"hello" vs #repr("hello") \
-/// #(1, 2) vs #repr((1, 2)) \
-/// #[*Hi*] vs #repr([*Hi*])
-/// ```
-///
-/// Display: Representation
-/// Category: foundations
-#[func]
-pub fn repr(
- /// The value whose string representation to produce.
- value: Value,
-) -> Str {
- value.repr()
-}
-
-/// Fails with an error.
-///
-/// ## Example { #example }
-/// The code below produces the error `panicked with: "this is wrong"`.
-/// ```typ
-/// #panic("this is wrong")
-/// ```
-///
-/// Display: Panic
-/// Category: foundations
-#[func]
-pub fn panic(
- /// The values to panic with.
- #[variadic]
- values: Vec<Value>,
-) -> StrResult<Never> {
- let mut msg = EcoString::from("panicked");
- if !values.is_empty() {
- msg.push_str(" with: ");
- for (i, value) in values.iter().enumerate() {
- if i > 0 {
- msg.push_str(", ");
- }
- msg.push_str(&value.repr());
- }
- }
- Err(msg)
-}
-
-/// Ensures that a condition is fulfilled.
-///
-/// Fails with an error if the condition is not fulfilled. Does not
-/// produce any output in the document.
-///
-/// If you wish to test equality between two values, see
-/// [`assert.eq`]($func/assert.eq) and [`assert.ne`]($func/assert.ne).
-///
-/// ## Example { #example }
-/// ```typ
-/// #assert(1 < 2, message: "math broke")
-/// ```
-///
-/// Display: Assert
-/// Category: foundations
-#[func]
-#[scope(
- scope.define("eq", assert_eq_func());
- scope.define("ne", assert_ne_func());
- scope
-)]
-pub fn assert(
- /// The condition that must be true for the assertion to pass.
- condition: bool,
- /// The error message when the assertion fails.
- #[named]
- message: Option<EcoString>,
-) -> StrResult<NoneValue> {
- if !condition {
- if let Some(message) = message {
- bail!("assertion failed: {message}");
- } else {
- bail!("assertion failed");
- }
- }
- Ok(NoneValue)
-}
-
-/// Ensures that two values are equal.
-///
-/// Fails with an error if the first value is not equal to the second. Does not
-/// produce any output in the document.
-///
-/// ## Example { #example }
-/// ```typ
-/// #assert.eq(10, 10)
-/// ```
-///
-/// Display: Assert Equals
-/// Category: foundations
-#[func]
-pub fn assert_eq(
- /// The first value to compare.
- left: Value,
-
- /// The second value to compare.
- right: Value,
-
- /// An optional message to display on error instead of the representations
- /// of the compared values.
- #[named]
- message: Option<EcoString>,
-) -> StrResult<NoneValue> {
- if left != right {
- if let Some(message) = message {
- bail!("equality assertion failed: {message}");
- } else {
- bail!("equality assertion failed: value {left:?} was not equal to {right:?}");
- }
- }
- Ok(NoneValue)
-}
-
-/// Ensures that two values are not equal.
-///
-/// Fails with an error if the first value is equal to the second. Does not
-/// produce any output in the document.
-///
-/// ## Example { #example }
-/// ```typ
-/// #assert.ne(3, 4)
-/// ```
-///
-/// Display: Assert Not Equals
-/// Category: foundations
-#[func]
-pub fn assert_ne(
- /// The first value to compare.
- left: Value,
-
- /// The second value to compare.
- right: Value,
-
- /// An optional message to display on error instead of the representations
- /// of the compared values.
- #[named]
- message: Option<EcoString>,
-) -> StrResult<NoneValue> {
- if left == right {
- if let Some(message) = message {
- bail!("inequality assertion failed: {message}");
- } else {
- bail!("inequality assertion failed: value {left:?} was equal to {right:?}");
- }
- }
- Ok(NoneValue)
-}
-
-/// Evaluates a string as Typst code.
-///
-/// This function should only be used as a last resort.
-///
-/// ## Example { #example }
-/// ```example
-/// #eval("1 + 1") \
-/// #eval("(1, 2, 3, 4)").len() \
-/// #eval("[*Strong text*]")
-/// ```
-///
-/// Display: Evaluate
-/// Category: foundations
-#[func]
-pub fn eval(
- /// A string of Typst code to evaluate.
- ///
- /// The code in the string cannot interact with the file system.
- source: Spanned<String>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Value> {
- let Spanned { v: text, span } = source;
- typst::eval::eval_string(vm.world(), &text, span)
-}
diff --git a/library/src/compute/mod.rs b/library/src/compute/mod.rs
deleted file mode 100644
index e9e4870c..00000000
--- a/library/src/compute/mod.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-//! Computational functions.
-
-pub mod calc;
-mod construct;
-mod data;
-mod foundations;
-
-pub use self::construct::*;
-pub use self::data::*;
-pub use self::foundations::*;
-
-use crate::prelude::*;
-
-/// Hook up all compute definitions.
-pub(super) fn define(global: &mut Scope) {
- global.define("type", type_func());
- global.define("repr", repr_func());
- global.define("panic", panic_func());
- global.define("assert", assert_func());
- global.define("eval", eval_func());
- global.define("int", int_func());
- global.define("float", float_func());
- global.define("luma", luma_func());
- global.define("rgb", rgb_func());
- global.define("cmyk", cmyk_func());
- global.define("datetime", datetime_func());
- global.define("symbol", symbol_func());
- global.define("str", str_func());
- global.define("label", label_func());
- global.define("regex", regex_func());
- global.define("range", range_func());
- global.define("read", read_func());
- global.define("csv", csv_func());
- global.define("json", json_func());
- global.define("toml", toml_func());
- global.define("yaml", yaml_func());
- global.define("xml", xml_func());
- global.define("calc", calc::module());
-}