summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/compute
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/compute')
-rw-r--r--crates/typst-library/src/compute/calc.rs295
-rw-r--r--crates/typst-library/src/compute/construct.rs1015
-rw-r--r--crates/typst-library/src/compute/data.rs474
-rw-r--r--crates/typst-library/src/compute/foundations.rs208
-rw-r--r--crates/typst-library/src/compute/mod.rs35
5 files changed, 394 insertions, 1633 deletions
diff --git a/crates/typst-library/src/compute/calc.rs b/crates/typst-library/src/compute/calc.rs
index 83ecac5d..9043e1f1 100644
--- a/crates/typst-library/src/compute/calc.rs
+++ b/crates/typst-library/src/compute/calc.rs
@@ -8,62 +8,65 @@ use typst::eval::{Module, Scope};
use crate::prelude::*;
-/// A module with computational functions.
-pub fn module() -> Module {
+/// Hook up all calculation definitions.
+pub(super) fn define(global: &mut Scope) {
+ global.category("calculate");
+ global.define_module(module());
+}
+
+/// A module with calculation definitions.
+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.category("calculate");
+ scope.define_func::<abs>();
+ scope.define_func::<pow>();
+ scope.define_func::<exp>();
+ scope.define_func::<sqrt>();
+ scope.define_func::<sin>();
+ scope.define_func::<cos>();
+ scope.define_func::<tan>();
+ scope.define_func::<asin>();
+ scope.define_func::<acos>();
+ scope.define_func::<atan>();
+ scope.define_func::<atan2>();
+ scope.define_func::<sinh>();
+ scope.define_func::<cosh>();
+ scope.define_func::<tanh>();
+ scope.define_func::<log>();
+ scope.define_func::<ln>();
+ scope.define_func::<fact>();
+ scope.define_func::<perm>();
+ scope.define_func::<binom>();
+ scope.define_func::<gcd>();
+ scope.define_func::<lcm>();
+ scope.define_func::<floor>();
+ scope.define_func::<ceil>();
+ scope.define_func::<trunc>();
+ scope.define_func::<fract>();
+ scope.define_func::<round>();
+ scope.define_func::<clamp>();
+ scope.define_func::<min>();
+ scope.define_func::<max>();
+ scope.define_func::<even>();
+ scope.define_func::<odd>();
+ scope.define_func::<rem>();
+ scope.define_func::<quo>();
scope.define("inf", f64::INFINITY);
scope.define("nan", f64::NAN);
scope.define("pi", std::f64::consts::PI);
scope.define("tau", std::f64::consts::TAU);
scope.define("e", std::f64::consts::E);
- Module::new("calc").with_scope(scope)
+ Module::new("calc", 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]
+#[func(title = "Absolute")]
pub fn abs(
/// The value whose absolute value to calculate.
value: ToAbs,
@@ -87,21 +90,17 @@ cast! {
/// Raises a value to some exponent.
///
-/// ## Example { #example }
/// ```example
/// #calc.pow(2, 3)
/// ```
-///
-/// Display: Power
-/// Category: calculate
-#[func]
+#[func(title = "Power")]
pub fn pow(
+ /// The callsite span.
+ span: Span,
/// 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 => {
@@ -142,19 +141,15 @@ pub fn pow(
/// Raises a value to some exponent of e.
///
-/// ## Example { #example }
/// ```example
/// #calc.exp(1)
/// ```
-///
-/// Display: Exponential
-/// Category: calculate
-#[func]
+#[func(title = "Exponential")]
pub fn exp(
- /// The exponent of the power.
- exponent: Spanned<Num>,
/// The callsite span.
span: Span,
+ /// The exponent of the power.
+ exponent: Spanned<Num>,
) -> SourceResult<f64> {
match exponent.v {
Num::Int(i) if i32::try_from(i).is_err() => {
@@ -176,15 +171,11 @@ pub fn exp(
/// Extracts the square root of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.sqrt(16) \
/// #calc.sqrt(2.5)
/// ```
-///
-/// Display: Square Root
-/// Category: calculate
-#[func]
+#[func(title = "Square Root")]
pub fn sqrt(
/// The number whose square root to calculate. Must be non-negative.
value: Spanned<Num>,
@@ -200,16 +191,12 @@ pub fn sqrt(
/// 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]
+#[func(title = "Sine")]
pub fn sin(
/// The angle whose sine to calculate.
angle: AngleLike,
@@ -226,16 +213,12 @@ pub fn sin(
/// 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]
+#[func(title = "Cosine")]
pub fn cos(
/// The angle whose cosine to calculate.
angle: AngleLike,
@@ -252,15 +235,11 @@ pub fn cos(
/// 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]
+#[func(title = "Tangent")]
pub fn tan(
/// The angle whose tangent to calculate.
angle: AngleLike,
@@ -274,15 +253,11 @@ pub fn tan(
/// Calculates the arcsine of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.asin(0) \
/// #calc.asin(1)
/// ```
-///
-/// Display: Arcsine
-/// Category: calculate
-#[func]
+#[func(title = "Arcsine")]
pub fn asin(
/// The number whose arcsine to calculate. Must be between -1 and 1.
value: Spanned<Num>,
@@ -296,15 +271,11 @@ pub fn asin(
/// Calculates the arccosine of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.acos(0) \
/// #calc.acos(1)
/// ```
-///
-/// Display: Arccosine
-/// Category: calculate
-#[func]
+#[func(title = "Arccosine")]
pub fn acos(
/// The number whose arcsine to calculate. Must be between -1 and 1.
value: Spanned<Num>,
@@ -318,15 +289,11 @@ pub fn acos(
/// Calculates the arctangent of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.atan(0) \
/// #calc.atan(1)
/// ```
-///
-/// Display: Arctangent
-/// Category: calculate
-#[func]
+#[func(title = "Arctangent")]
pub fn atan(
/// The number whose arctangent to calculate.
value: Num,
@@ -338,15 +305,11 @@ pub fn atan(
///
/// 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]
+#[func(title = "Four-quadrant Arctangent")]
pub fn atan2(
/// The X coordinate.
x: Num,
@@ -358,15 +321,11 @@ pub fn atan2(
/// Calculates the hyperbolic sine of a hyperbolic angle.
///
-/// ## Example { #example }
/// ```example
/// #calc.sinh(0) \
/// #calc.sinh(1.5)
/// ```
-///
-/// Display: Hyperbolic sine
-/// Category: calculate
-#[func]
+#[func(title = "Hyperbolic Sine")]
pub fn sinh(
/// The hyperbolic angle whose hyperbolic sine to calculate.
value: f64,
@@ -376,15 +335,11 @@ pub fn sinh(
/// Calculates the hyperbolic cosine of a hyperbolic angle.
///
-/// ## Example { #example }
/// ```example
/// #calc.cosh(0) \
/// #calc.cosh(1.5)
/// ```
-///
-/// Display: Hyperbolic cosine
-/// Category: calculate
-#[func]
+#[func(title = "Hyperbolic Cosine")]
pub fn cosh(
/// The hyperbolic angle whose hyperbolic cosine to calculate.
value: f64,
@@ -394,15 +349,11 @@ pub fn cosh(
/// Calculates the hyperbolic tangent of an hyperbolic angle.
///
-/// ## Example { #example }
/// ```example
/// #calc.tanh(0) \
/// #calc.tanh(1.5)
/// ```
-///
-/// Display: Hyperbolic tangent
-/// Category: calculate
-#[func]
+#[func(title = "Hyperbolic Tangent")]
pub fn tanh(
/// The hyperbolic angle whose hyperbolic tangent to calculate.
value: f64,
@@ -414,23 +365,19 @@ pub fn tanh(
///
/// If the base is not specified, the logarithm is calculated in base 10.
///
-/// ## Example { #example }
/// ```example
/// #calc.log(100)
/// ```
-///
-/// Display: Logarithm
-/// Category: calculate
-#[func]
+#[func(title = "Logarithm")]
pub fn log(
+ /// The callsite span.
+ span: Span,
/// 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 {
@@ -460,19 +407,15 @@ pub fn log(
/// Calculates the natural logarithm of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.ln(calc.e)
/// ```
-///
-/// Display: Natural Logarithm
-/// Category: calculate
-#[func]
+#[func(title = "Natural Logarithm")]
pub fn ln(
- /// The number whose logarithm to calculate. Must be strictly positive.
- value: Spanned<Num>,
/// The callsite span.
span: Span,
+ /// The number whose logarithm to calculate. Must be strictly positive.
+ value: Spanned<Num>,
) -> SourceResult<f64> {
let number = value.v.float();
if number <= 0.0 {
@@ -489,14 +432,10 @@ pub fn ln(
/// Calculates the factorial of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.fact(5)
/// ```
-///
-/// Display: Factorial
-/// Category: calculate
-#[func]
+#[func(title = "Factorial")]
pub fn fact(
/// The number whose factorial to calculate. Must be non-negative.
number: u64,
@@ -506,14 +445,10 @@ pub fn fact(
/// Calculates a permutation.
///
-/// ## Example { #example }
/// ```example
/// #calc.perm(10, 5)
/// ```
-///
-/// Display: Permutation
-/// Category: calculate
-#[func]
+#[func(title = "Permutation")]
pub fn perm(
/// The base number. Must be non-negative.
base: u64,
@@ -547,14 +482,10 @@ fn fact_impl(start: u64, end: u64) -> Option<i64> {
/// Calculates a binomial coefficient.
///
-/// ## Example { #example }
/// ```example
/// #calc.binom(10, 5)
/// ```
-///
-/// Display: Binomial
-/// Category: calculate
-#[func]
+#[func(title = "Binomial")]
pub fn binom(
/// The upper coefficient. Must be non-negative.
n: u64,
@@ -588,14 +519,10 @@ fn binom_impl(n: u64, k: u64) -> Option<i64> {
/// Calculates the greatest common divisor of two integers.
///
-/// ## Example { #example }
/// ```example
/// #calc.gcd(7, 42)
/// ```
-///
-/// Display: Greatest Common Divisor
-/// Category: calculate
-#[func]
+#[func(title = "Greatest Common Divisor")]
pub fn gcd(
/// The first integer.
a: i64,
@@ -614,14 +541,10 @@ pub fn gcd(
/// Calculates the least common multiple of two integers.
///
-/// ## Example { #example }
/// ```example
/// #calc.lcm(96, 13)
/// ```
-///
-/// Display: Least Common Multiple
-/// Category: calculate
-#[func]
+#[func(title = "Least Common Multiple")]
pub fn lcm(
/// The first integer.
a: i64,
@@ -642,15 +565,11 @@ pub fn lcm(
///
/// 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.
@@ -666,15 +585,11 @@ pub fn floor(
///
/// 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.
@@ -690,16 +605,12 @@ pub fn ceil(
///
/// 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]
+#[func(title = "Truncate")]
pub fn trunc(
/// The number to truncate.
value: Num,
@@ -714,15 +625,11 @@ pub fn trunc(
///
/// 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]
+#[func(title = "Fractional")]
pub fn fract(
/// The number to truncate.
value: Num,
@@ -737,15 +644,11 @@ pub fn fract(
///
/// 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.
@@ -767,15 +670,11 @@ pub fn round(
/// 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.
@@ -793,44 +692,36 @@ pub fn 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]
+#[func(title = "Minimum")]
pub fn min(
+ /// The callsite span.
+ span: Span,
/// 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]
+#[func(title = "Maximum")]
pub fn max(
+ /// The callsite span.
+ span: Span,
/// 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)
}
@@ -858,15 +749,11 @@ fn minmax(
/// 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.
@@ -877,15 +764,11 @@ pub fn even(
/// 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.
@@ -896,15 +779,11 @@ pub fn odd(
/// Calculates the remainder of two numbers.
///
-/// ## Example { #example }
/// ```example
/// #calc.rem(20, 6) \
/// #calc.rem(1.75, 0.5)
/// ```
-///
-/// Display: Remainder
-/// Category: calculate
-#[func]
+#[func(title = "Remainder")]
pub fn rem(
/// The dividend of the remainder.
dividend: Num,
@@ -919,15 +798,11 @@ pub fn 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]
+#[func(title = "Quotient")]
pub fn quo(
/// The dividend of the quotient.
dividend: Num,
@@ -949,7 +824,7 @@ pub enum Num {
}
impl Num {
- pub fn apply2(
+ fn apply2(
self,
other: Self,
int: impl FnOnce(i64, i64) -> i64,
@@ -961,7 +836,7 @@ impl Num {
}
}
- pub fn apply3(
+ fn apply3(
self,
other: Self,
third: Self,
@@ -974,7 +849,7 @@ impl Num {
}
}
- pub fn float(self) -> f64 {
+ fn float(self) -> f64 {
match self {
Self::Int(v) => v as f64,
Self::Float(v) => v,
diff --git a/crates/typst-library/src/compute/construct.rs b/crates/typst-library/src/compute/construct.rs
deleted file mode 100644
index 6ea8bd82..00000000
--- a/crates/typst-library/src/compute/construct.rs
+++ /dev/null
@@ -1,1015 +0,0 @@
-use std::num::NonZeroI64;
-use std::str::FromStr;
-
-use time::{Month, PrimitiveDateTime};
-
-use typst::eval::{Bytes, Datetime, Duration, Module, Plugin, Reflect, 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: f64 => Self(v as i64),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid integer: {}", v))?),
- v: i64 => Self(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: Ratio => Self(v.get()),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid float: {}", v))?),
- v: f64 => Self(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.
-///
-/// ## 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 new duration.
-///
-/// You can specify the [duration]($type/duration) using weeks, days, hours,
-/// minutes and seconds. You can also get a duration by subtracting two
-/// [datetimes]($type/datetime).
-///
-/// ## Example
-/// ```example
-/// #duration(
-/// days: 3,
-/// hours: 12,
-/// ).hours()
-/// ```
-///
-/// Display: Duration
-/// Category: construct
-#[func]
-pub fn duration(
- /// The number of seconds.
- #[named]
- #[default(0)]
- seconds: i64,
- /// The number of minutes.
- #[named]
- #[default(0)]
- minutes: i64,
- /// The number of hours.
- #[named]
- #[default(0)]
- hours: i64,
- /// The number of days.
- #[named]
- #[default(0)]
- days: i64,
- /// The number of weeks.
- #[named]
- #[default(0)]
- weeks: i64,
-) -> Duration {
- Duration::from(
- time::Duration::seconds(seconds)
- + time::Duration::minutes(minutes)
- + time::Duration::hours(hours)
- + time::Duration::days(days)
- + time::Duration::weeks(weeks),
- )
-}
-
-/// 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%");
- },
-}
-
-/// A module with functions operating on colors.
-pub fn color_module() -> Module {
- let mut scope = Scope::new();
- scope.define("mix", mix_func());
- Module::new("color").with_scope(scope)
-}
-
-/// Create a color by mixing two or more colors.
-///
-/// ## Example { #example }
-/// ```example
-/// #set block(height: 20pt, width: 100%)
-/// #block(fill: color.mix(red, blue))
-/// #block(fill: color.mix(red, blue, space: "srgb"))
-/// #block(fill: color.mix((red, 70%), (blue, 30%)))
-/// #block(fill: color.mix(red, blue, white))
-/// ```
-///
-/// _Note:_ This function must be specified as `color.mix`, not just `mix`.
-/// Currently, `color` is a module, but it is designed to be forward compatible
-/// with a future `color` type.
-///
-/// Display: Mix
-/// Category: construct
-#[func]
-pub fn mix(
- /// The colors, optionally with weights, specified as a pair (array of
- /// length two) of color and weight (float or ratio).
- ///
- /// The weights do not need to add to `{100%}`, they are relative to the
- /// sum of all weights.
- #[variadic]
- colors: Vec<WeightedColor>,
- /// The color space to mix in. By default, this happens in a perceptual
- /// color space (Oklab).
- #[named]
- #[default(ColorSpace::Oklab)]
- space: ColorSpace,
-) -> StrResult<Color> {
- Color::mix(colors, space)
-}
-
-/// 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.
-/// - Bytes are decoded as UTF-8.
-///
-/// 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: Bytes => Self::Str(
- std::str::from_utf8(&v)
- .map_err(|_| "bytes are not valid utf-8")?
- .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 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)
-}
-
-/// Converts a value to bytes.
-///
-/// - Strings are encoded in UTF-8.
-/// - Arrays of integers between `{0}` and `{255}` are converted directly. The
-/// dedicated byte representation is much more efficient than the array
-/// representation and thus typically used for large byte buffers (e.g. image
-/// data).
-///
-/// ```example
-/// #bytes("Hello 😃") \
-/// #bytes((123, 160, 22, 0))
-/// ```
-///
-/// Display: Bytes
-/// Category: construct
-#[func]
-pub fn bytes(
- /// The value that should be converted to bytes.
- value: ToBytes,
-) -> Bytes {
- value.0
-}
-
-/// A value that can be cast to bytes.
-pub struct ToBytes(Bytes);
-
-cast! {
- ToBytes,
- v: Str => Self(v.as_bytes().into()),
- v: Array => Self(v.iter()
- .map(|v| match v {
- Value::Int(byte @ 0..=255) => Ok(*byte as u8),
- Value::Int(_) => bail!("number must be between 0 and 255"),
- value => Err(<u8 as Reflect>::error(value)),
- })
- .collect::<Result<Vec<u8>, _>>()?
- .into()
- ),
- v: Bytes => Self(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)
-}
-
-/// Converts a value to an array.
-///
-/// Note that this function is only intended for conversion of a collection-like
-/// value to an array, not for creation of an array from individual items. Use
-/// the array syntax `(1, 2, 3)` (or `(1,)` for a single-element array) instead.
-///
-/// ```example
-/// #let hi = "Hello 😃"
-/// #array(bytes(hi))
-/// ```
-///
-/// Display: Array
-/// Category: construct
-#[func]
-pub fn array(
- /// The value that should be converted to an array.
- value: ToArray,
-) -> Array {
- value.0
-}
-
-/// A value that can be cast to bytes.
-pub struct ToArray(Array);
-
-cast! {
- ToArray,
- v: Bytes => Self(v.iter().map(|&b| Value::Int(b as i64)).collect()),
- v: Array => Self(v),
-}
-
-/// 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)
-}
-
-/// Loads a WebAssembly plugin.
-///
-/// This is **advanced functionality** and not to be confused with
-/// [Typst packages]($scripting/#packages).
-///
-/// Typst is capable of interfacing with plugins compiled to WebAssembly. Plugin
-/// functions may accept multiple [byte buffers]($type/bytes) as arguments and
-/// return a single byte buffer. They should typically be wrapped in idiomatic
-/// Typst functions that perform the necessary conversions between native Typst
-/// types and bytes.
-///
-/// Plugins run in isolation from your system, which means that printing,
-/// reading files, or anything like that will not be supported for security
-/// reasons. To run as a plugin, a program needs to be compiled to a 32-bit
-/// shared WebAssembly library. Many compilers will use the
-/// [WASI ABI](https://wasi.dev/) by default or as their only option (e.g.
-/// emscripten), which allows printing, reading files, etc. This ABI will not
-/// directly work with Typst. You will either need to compile to a different
-/// target or [stub all functions](https://github.com/astrale-sharp/wasm-minimal-protocol/blob/master/wasi-stub).
-///
-/// ## Example { #example }
-/// ```example
-/// #let myplugin = plugin("hello.wasm")
-/// #let concat(a, b) = str(
-/// myplugin.concatenate(
-/// bytes(a),
-/// bytes(b),
-/// )
-/// )
-///
-/// #concat("hello", "world")
-/// ```
-///
-/// ## Protocol { #protocol }
-/// To be used as a plugin, a WebAssembly module must conform to the following
-/// protocol:
-///
-/// ### Exports { #exports }
-/// A plugin module can export functions to make them callable from Typst. To
-/// conform to the protocol, an exported function should:
-///
-/// - Take `n` 32-bit integer arguments `a_1`, `a_2`, ..., `a_n` (interpreted as
-/// lengths, so `usize/size_t` may be preferable), and return one 32-bit
-/// integer.
-///
-/// - The function should first allocate a buffer `buf` of length
-/// `a_1 + a_2 + ... + a_n`, and then call
-/// `wasm_minimal_protocol_write_args_to_buffer(buf.ptr)`.
-///
-/// - The `a_1` first bytes of the buffer now constitute the first argument, the
-/// `a_2` next bytes the second argument, and so on.
-///
-/// - The function can now do its job with the arguments and produce an output
-/// buffer. Before returning, it should call
-/// `wasm_minimal_protocol_send_result_to_host` to send its result back to the
-/// host.
-///
-/// - To signal success, the function should return `0`.
-///
-/// - To signal an error, the function should return `1`. The written buffer is
-/// then interpreted as an UTF-8 encoded error message.
-///
-/// ### Imports { #imports }
-/// Plugin modules need to import two functions that are provided by the runtime.
-/// (Types and functions are described using WAT syntax.)
-///
-/// - `(import "typst_env" "wasm_minimal_protocol_write_args_to_buffer" (func (param i32)))`
-///
-/// Writes the arguments for the current function into a plugin-allocated
-/// buffer. When a plugin function is called, it
-/// [receives the lengths](#exported-functions) of its input buffers as
-/// arguments. It should then allocate a buffer whose capacity is at least the
-/// sum of these lengths. It should then call this function with a `ptr` to
-/// the buffer to fill it with the arguments, one after another.
-///
-/// - `(import "typst_env" "wasm_minimal_protocol_send_result_to_host" (func (param i32 i32)))`
-///
-/// Sends the output of the current function to the host (Typst). The first
-/// parameter shall be a pointer to a buffer (`ptr`), while the second is the
-/// length of that buffer (`len`). The memory pointed at by `ptr` can be freed
-/// immediately after this function returns. If the message should be
-/// interpreted as an error message, it should be encoded as UTF-8.
-///
-/// ## Resources { #resources }
-/// For more resources, check out the
-/// [wasm-minimal-protocol repository](https://github.com/astrale-sharp/wasm-minimal-protocol).
-/// It contains:
-///
-/// - A list of example plugin implementations and a test runner for these
-/// examples
-/// - Wrappers to help you write your plugin in Rust (Zig wrapper in
-/// development)
-/// - A stubber for WASI
-///
-/// Display: Plugin
-/// Category: construct
-#[func]
-pub fn plugin(
- /// Path to a WebAssembly file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Plugin> {
- let Spanned { v: path, span } = path;
- let id = vm.resolve_path(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- Plugin::new(data).at(span)
-}
-
-#[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/crates/typst-library/src/compute/data.rs b/crates/typst-library/src/compute/data.rs
index 222b14d3..dadf0bed 100644
--- a/crates/typst-library/src/compute/data.rs
+++ b/crates/typst-library/src/compute/data.rs
@@ -4,15 +4,25 @@ use typst::syntax::is_newline;
use crate::prelude::*;
+/// Hook up all data loading definitions.
+pub(super) fn define(global: &mut Scope) {
+ global.category("data-loading");
+ global.define_func::<read>();
+ global.define_func::<csv>();
+ global.define_func::<json>();
+ global.define_func::<toml>();
+ global.define_func::<yaml>();
+ global.define_func::<cbor>();
+ global.define_func::<xml>();
+}
+
/// Reads plain text or data from a file.
///
-/// By default, the file will be read as UTF-8 and returned as a
-/// [string]($type/string).
+/// By default, the file will be read as UTF-8 and returned as a [string]($str).
///
-/// If you specify `{encoding: none}`, this returns raw [bytes]($type/bytes)
-/// instead.
+/// If you specify `{encoding: none}`, this returns raw [bytes]($bytes) instead.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// An example for a HTML file: \
/// #let text = read("data.html")
@@ -21,11 +31,10 @@ use crate::prelude::*;
/// Raw bytes:
/// #read("tiger.jpg", encoding: none)
/// ```
-///
-/// Display: Read
-/// Category: data-loading
#[func]
pub fn read(
+ /// The virtual machine.
+ vm: &mut Vm,
/// Path to a file.
path: Spanned<EcoString>,
/// The encoding to read the file with.
@@ -34,8 +43,6 @@ pub fn read(
#[named]
#[default(Some(Encoding::Utf8))]
encoding: Option<Encoding>,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Readable> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
@@ -101,7 +108,7 @@ impl From<Readable> for Bytes {
/// rows will be collected into a single array. Header rows will not be
/// stripped.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let results = csv("data.csv")
///
@@ -111,15 +118,10 @@ impl From<Readable> for Bytes {
/// ..results.flatten(),
/// )
/// ```
-///
-/// Display: CSV
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", csv_decode_func());
- scope
-)]
+#[func(scope, title = "CSV")]
pub fn csv(
+ /// The virtual machine.
+ vm: &mut Vm,
/// Path to a CSV file.
path: Spanned<EcoString>,
/// The delimiter that separates columns in the CSV file.
@@ -127,47 +129,45 @@ pub fn csv(
#[named]
#[default]
delimiter: Delimiter,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Array> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- csv_decode(Spanned::new(Readable::Bytes(data), span), delimiter)
+ self::csv::decode(Spanned::new(Readable::Bytes(data), span), delimiter)
}
-/// Reads structured data from a CSV string/bytes.
-///
-/// Display: Decode CSV
-/// Category: data-loading
-#[func]
-pub fn csv_decode(
- /// CSV data.
- data: Spanned<Readable>,
- /// The delimiter that separates columns in the CSV file.
- /// Must be a single ASCII character.
- #[named]
- #[default]
- delimiter: Delimiter,
-) -> SourceResult<Array> {
- let Spanned { v: data, span } = data;
- 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))
- }
+#[scope]
+impl csv {
+ /// Reads structured data from a CSV string/bytes.
+ #[func(title = "Decode CSV")]
+ pub fn decode(
+ /// CSV data.
+ data: Spanned<Readable>,
+ /// The delimiter that separates columns in the CSV file.
+ /// Must be a single ASCII character.
+ #[named]
+ #[default]
+ delimiter: Delimiter,
+ ) -> SourceResult<Array> {
+ let Spanned { v: data, span } = data;
+ 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)
+ Ok(array)
+ }
}
/// The delimiter to use when parsing CSV files.
@@ -198,10 +198,10 @@ cast! {
}
/// Format the user-facing CSV error message.
-fn format_csv_error(err: csv::Error, line: usize) -> EcoString {
+fn format_csv_error(err: ::csv::Error, line: usize) -> EcoString {
match err.kind() {
- csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
- csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
+ ::csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
+ ::csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
eco_format!(
"failed to parse CSV (found {len} instead of \
{expected_len} fields in line {line})"
@@ -224,7 +224,7 @@ fn format_csv_error(err: csv::Error, line: usize) -> EcoString {
/// The JSON files in the example contain objects with the keys `temperature`,
/// `unit`, and `weather`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let forecast(day) = block[
/// #box(square(
@@ -248,64 +248,53 @@ fn format_csv_error(err: csv::Error, line: usize) -> EcoString {
/// #forecast(json("monday.json"))
/// #forecast(json("tuesday.json"))
/// ```
-///
-/// Display: JSON
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", json_decode_func());
- scope.define("encode", json_encode_func());
- scope
-)]
+#[func(scope, title = "JSON")]
pub fn json(
- /// Path to a JSON file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a JSON file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- json_decode(Spanned::new(Readable::Bytes(data), span))
+ json::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from a JSON string/bytes.
-///
-/// Display: JSON
-/// Category: data-loading
-#[func]
-pub fn json_decode(
- /// JSON data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- serde_json::from_slice(data.as_slice())
- .map_err(|err| eco_format!("failed to parse JSON ({err})"))
- .at(span)
-}
+#[scope]
+impl json {
+ /// Reads structured data from a JSON string/bytes.
+ #[func(title = "Decode JSON")]
+ pub fn decode(
+ /// JSON data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ serde_json::from_slice(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse JSON ({err})"))
+ .at(span)
+ }
-/// Encodes structured data into a JSON string.
-///
-/// Display: Encode JSON
-/// Category: data-loading
-#[func]
-pub fn json_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
- /// Whether to pretty print the JSON with newlines and indentation.
- #[named]
- #[default(true)]
- pretty: bool,
-) -> SourceResult<Str> {
- let Spanned { v: value, span } = value;
- if pretty {
- serde_json::to_string_pretty(&value)
- } else {
- serde_json::to_string(&value)
+ /// Encodes structured data into a JSON string.
+ #[func(title = "Encode JSON")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ /// Whether to pretty print the JSON with newlines and indentation.
+ #[named]
+ #[default(true)]
+ pretty: bool,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ if pretty {
+ serde_json::to_string_pretty(&value)
+ } else {
+ serde_json::to_string(&value)
+ }
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
+ .at(span)
}
- .map(|v| v.into())
- .map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
- .at(span)
}
/// Reads structured data from a TOML file.
@@ -319,7 +308,7 @@ pub fn json_encode(
/// The TOML file in the example consists of a table with the keys `title`,
/// `version`, and `authors`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let details = toml("details.toml")
///
@@ -328,67 +317,56 @@ pub fn json_encode(
/// Authors: #(details.authors
/// .join(", ", last: " and "))
/// ```
-///
-/// Display: TOML
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", toml_decode_func());
- scope.define("encode", toml_encode_func());
- scope
-)]
+#[func(scope, title = "TOML")]
pub fn toml(
- /// Path to a TOML file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a TOML file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- toml_decode(Spanned::new(Readable::Bytes(data), span))
+ toml::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from a TOML string/bytes.
-///
-/// Display: Decode TOML
-/// Category: data-loading
-#[func]
-pub fn toml_decode(
- /// TOML data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- let raw = std::str::from_utf8(data.as_slice())
- .map_err(|_| "file is not valid utf-8")
- .at(span)?;
- toml::from_str(raw)
- .map_err(|err| format_toml_error(err, raw))
- .at(span)
-}
+#[scope]
+impl toml {
+ /// Reads structured data from a TOML string/bytes.
+ #[func(title = "Decode TOML")]
+ pub fn decode(
+ /// TOML data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ let raw = std::str::from_utf8(data.as_slice())
+ .map_err(|_| "file is not valid utf-8")
+ .at(span)?;
+ ::toml::from_str(raw)
+ .map_err(|err| format_toml_error(err, raw))
+ .at(span)
+ }
-/// Encodes structured data into a TOML string.
-///
-/// Display: Encode TOML
-/// Category: data-loading
-#[func]
-pub fn toml_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
- /// Whether to pretty-print the resulting TOML.
- #[named]
- #[default(true)]
- pretty: bool,
-) -> SourceResult<Str> {
- let Spanned { v: value, span } = value;
- if pretty { toml::to_string_pretty(&value) } else { toml::to_string(&value) }
- .map(|v| v.into())
- .map_err(|err| eco_format!("failed to encode value as TOML ({err})"))
- .at(span)
+ /// Encodes structured data into a TOML string.
+ #[func(title = "Encode TOML")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ /// Whether to pretty-print the resulting TOML.
+ #[named]
+ #[default(true)]
+ pretty: bool,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ if pretty { ::toml::to_string_pretty(&value) } else { ::toml::to_string(&value) }
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as TOML ({err})"))
+ .at(span)
+ }
}
/// Format the user-facing TOML error message.
-fn format_toml_error(error: toml::de::Error, raw: &str) -> EcoString {
+fn format_toml_error(error: ::toml::de::Error, raw: &str) -> EcoString {
if let Some(head) = error.span().and_then(|range| raw.get(..range.start)) {
let line = head.lines().count();
let column = 1 + head.chars().rev().take_while(|&c| !is_newline(c)).count();
@@ -415,7 +393,7 @@ fn format_toml_error(error: toml::de::Error, raw: &str) -> EcoString {
/// each with a sequence of their own submapping with the keys
/// "title" and "published"
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let bookshelf(contents) = {
/// for (author, works) in contents {
@@ -430,56 +408,45 @@ fn format_toml_error(error: toml::de::Error, raw: &str) -> EcoString {
/// yaml("scifi-authors.yaml")
/// )
/// ```
-///
-/// Display: YAML
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", yaml_decode_func());
- scope.define("encode", yaml_encode_func());
- scope
-)]
+#[func(scope, title = "YAML")]
pub fn yaml(
- /// Path to a YAML file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a YAML file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- yaml_decode(Spanned::new(Readable::Bytes(data), span))
+ yaml::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from a YAML string/bytes.
-///
-/// Display: Decode YAML
-/// Category: data-loading
-#[func]
-pub fn yaml_decode(
- /// YAML data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- serde_yaml::from_slice(data.as_slice())
- .map_err(|err| eco_format!("failed to parse YAML ({err})"))
- .at(span)
-}
+#[scope]
+impl yaml {
+ /// Reads structured data from a YAML string/bytes.
+ #[func(title = "Decode YAML")]
+ pub fn decode(
+ /// YAML data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ serde_yaml::from_slice(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse YAML ({err})"))
+ .at(span)
+ }
-/// Encode structured data into a YAML string.
-///
-/// Display: Encode YAML
-/// Category: data-loading
-#[func]
-pub fn yaml_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
-) -> SourceResult<Str> {
- let Spanned { v: value, span } = value;
- serde_yaml::to_string(&value)
- .map(|v| v.into())
- .map_err(|err| eco_format!("failed to encode value as YAML ({err})"))
- .at(span)
+ /// Encode structured data into a YAML string.
+ #[func(title = "Encode YAML")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ serde_yaml::to_string(&value)
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as YAML ({err})"))
+ .at(span)
+ }
}
/// Reads structured data from a CBOR file.
@@ -490,57 +457,46 @@ pub fn yaml_encode(
/// 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.
-///
-/// Display: CBOR
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", cbor_decode_func());
- scope.define("encode", cbor_encode_func());
- scope
-)]
+#[func(scope, title = "CBOR")]
pub fn cbor(
- /// Path to a CBOR file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a CBOR file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- cbor_decode(Spanned::new(data, span))
+ cbor::decode(Spanned::new(data, span))
}
-/// Reads structured data from CBOR bytes.
-///
-/// Display: Decode CBOR
-/// Category: data-loading
-#[func]
-pub fn cbor_decode(
- /// cbor data.
- data: Spanned<Bytes>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- ciborium::from_reader(data.as_slice())
- .map_err(|err| eco_format!("failed to parse CBOR ({err})"))
- .at(span)
-}
+#[scope]
+impl cbor {
+ /// Reads structured data from CBOR bytes.
+ #[func(title = "Decode CBOR")]
+ pub fn decode(
+ /// cbor data.
+ data: Spanned<Bytes>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ ciborium::from_reader(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse CBOR ({err})"))
+ .at(span)
+ }
-/// Encode structured data into CBOR bytes.
-///
-/// Display: Encode CBOR
-/// Category: data-loading
-#[func]
-pub fn cbor_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
-) -> SourceResult<Bytes> {
- let Spanned { v: value, span } = value;
- let mut res = Vec::new();
- ciborium::into_writer(&value, &mut res)
- .map(|_| res.into())
- .map_err(|err| eco_format!("failed to encode value as CBOR ({err})"))
- .at(span)
+ /// Encode structured data into CBOR bytes.
+ #[func(title = "Encode CBOR")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ ) -> SourceResult<Bytes> {
+ let Spanned { v: value, span } = value;
+ let mut res = Vec::new();
+ ciborium::into_writer(&value, &mut res)
+ .map(|_| res.into())
+ .map_err(|err| eco_format!("failed to encode value as CBOR ({err})"))
+ .at(span)
+ }
}
/// Reads structured data from an XML file.
@@ -558,7 +514,7 @@ pub fn cbor_encode(
/// `content` tag contains one or more paragraphs, which are represented as `p`
/// tags.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let find-child(elem, tag) = {
/// elem.children
@@ -591,41 +547,35 @@ pub fn cbor_encode(
/// }
/// }
/// ```
-///
-/// Display: XML
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", xml_decode_func());
- scope
-)]
+#[func(scope, title = "XML")]
pub fn xml(
- /// Path to an XML file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to an XML file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- xml_decode(Spanned::new(Readable::Bytes(data), span))
+ xml::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from an XML string/bytes.
-///
-/// Display: Decode XML
-/// Category: data-loading
-#[func]
-pub fn xml_decode(
- /// XML data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- let text = std::str::from_utf8(data.as_slice())
- .map_err(FileError::from)
- .at(span)?;
- let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
- Ok(convert_xml(document.root()))
+#[scope]
+impl xml {
+ /// Reads structured data from an XML string/bytes.
+ #[func(title = "Decode XML")]
+ pub fn decode(
+ /// XML data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ let text = std::str::from_utf8(data.as_slice())
+ .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.
diff --git a/crates/typst-library/src/compute/foundations.rs b/crates/typst-library/src/compute/foundations.rs
index 3d07a3af..dad05717 100644
--- a/crates/typst-library/src/compute/foundations.rs
+++ b/crates/typst-library/src/compute/foundations.rs
@@ -1,29 +1,32 @@
-use typst::eval::EvalMode;
+use typst::eval::{
+ Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex,
+};
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()
+/// Hook up all foundational definitions.
+pub(super) fn define(global: &mut Scope) {
+ global.category("foundations");
+ global.define_type::<bool>();
+ global.define_type::<i64>();
+ global.define_type::<f64>();
+ global.define_type::<Str>();
+ global.define_type::<Bytes>();
+ global.define_type::<Content>();
+ global.define_type::<Array>();
+ global.define_type::<Dict>();
+ global.define_type::<Func>();
+ global.define_type::<Args>();
+ global.define_type::<Type>();
+ global.define_type::<Module>();
+ global.define_type::<Regex>();
+ global.define_type::<Datetime>();
+ global.define_type::<Duration>();
+ global.define_type::<Plugin>();
+ global.define_func::<repr>();
+ global.define_func::<panic>();
+ global.define_func::<assert>();
+ global.define_func::<eval>();
}
/// Returns the string representation of a value.
@@ -35,17 +38,14 @@ pub fn type_(
/// **Note:** This function is for debugging purposes. Its output should not be
/// considered stable and may change at any time!
///
-/// ## Example { #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]
+#[func(title = "Representation")]
pub fn repr(
/// The value whose string representation to produce.
value: Value,
@@ -55,16 +55,12 @@ pub fn repr(
/// Fails with an error.
///
-/// ## Example { #example }
+/// # Example
/// The code below produces the error `panicked with: "this is wrong"`.
/// ```typ
/// #panic("this is wrong")
/// ```
-///
-/// Display: Panic
-/// Category: foundations
-/// Keywords: error
-#[func]
+#[func(keywords = ["error"])]
pub fn panic(
/// The values to panic with.
#[variadic]
@@ -89,21 +85,13 @@ pub fn panic(
/// 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).
+/// [`assert.eq`]($assert.eq) and [`assert.ne`]($assert.ne).
///
-/// ## Example { #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
-)]
+#[func(scope)]
pub fn assert(
/// The condition that must be true for the assertion to pass.
condition: bool,
@@ -121,91 +109,83 @@ pub fn assert(
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:?}");
+#[scope]
+impl assert {
+ /// 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.
+ ///
+ /// ```typ
+ /// #assert.eq(10, 10)
+ /// ```
+ #[func(title = "Assert Equal")]
+ pub fn 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)
}
- 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:?}");
+ /// 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.
+ ///
+ /// ```typ
+ /// #assert.ne(3, 4)
+ /// ```
+ #[func(title = "Assert Not Equal")]
+ pub fn 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)
}
- Ok(NoneValue)
}
/// Evaluates a string as Typst code.
///
/// This function should only be used as a last resort.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #eval("1 + 1") \
/// #eval("(1, 2, 3, 4)").len() \
/// #eval("*Markup!*", mode: "markup") \
/// ```
-///
-/// Display: Evaluate
-/// Category: foundations
-#[func]
+#[func(title = "Evaluate")]
pub fn eval(
+ /// The virtual machine.
+ vm: &mut Vm,
/// A string of Typst code to evaluate.
///
/// The code in the string cannot interact with the file system.
@@ -235,8 +215,6 @@ pub fn eval(
#[named]
#[default]
scope: Dict,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Value> {
let Spanned { v: text, span } = source;
let dict = scope;
diff --git a/crates/typst-library/src/compute/mod.rs b/crates/typst-library/src/compute/mod.rs
index ca95f7b7..9e897653 100644
--- a/crates/typst-library/src/compute/mod.rs
+++ b/crates/typst-library/src/compute/mod.rs
@@ -1,11 +1,10 @@
//! Computational functions.
pub mod calc;
-mod construct;
+
mod data;
mod foundations;
-pub use self::construct::*;
pub use self::data::*;
pub use self::foundations::*;
@@ -13,33 +12,7 @@ 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("color", color_module());
- global.define("datetime", datetime_func());
- global.define("duration", duration_func());
- global.define("symbol", symbol_func());
- global.define("str", str_func());
- global.define("bytes", bytes_func());
- global.define("label", label_func());
- global.define("regex", regex_func());
- global.define("array", array_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("cbor", cbor_func());
- global.define("xml", xml_func());
- global.define("calc", calc::module());
- global.define("plugin", plugin_func());
+ self::foundations::define(global);
+ self::data::define(global);
+ self::calc::define(global);
}