From b471ac7d590abd2398ce25193b4e4df373bf2e9c Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 11 Sep 2023 14:40:22 +0200 Subject: [PATCH] First-class types Makes types first-class values. --- Cargo.lock | 4 +- crates/typst-library/Cargo.toml | 2 +- crates/typst-library/src/compute/calc.rs | 295 ++--- crates/typst-library/src/compute/construct.rs | 1015 ----------------- crates/typst-library/src/compute/data.rs | 474 ++++---- .../typst-library/src/compute/foundations.rs | 208 ++-- crates/typst-library/src/compute/mod.rs | 35 +- crates/typst-library/src/layout/align.rs | 48 +- crates/typst-library/src/layout/columns.rs | 18 +- crates/typst-library/src/layout/container.rs | 68 +- crates/typst-library/src/layout/enum.rs | 49 +- crates/typst-library/src/layout/flow.rs | 55 +- crates/typst-library/src/layout/grid.rs | 7 +- crates/typst-library/src/layout/hide.rs | 7 +- crates/typst-library/src/layout/list.rs | 36 +- crates/typst-library/src/layout/measure.rs | 25 +- crates/typst-library/src/layout/mod.rs | 79 +- crates/typst-library/src/layout/pad.rs | 10 +- crates/typst-library/src/layout/page.rs | 68 +- crates/typst-library/src/layout/par.rs | 54 +- crates/typst-library/src/layout/place.rs | 26 +- crates/typst-library/src/layout/repeat.rs | 11 +- crates/typst-library/src/layout/spacing.rs | 16 +- crates/typst-library/src/layout/stack.rs | 25 +- crates/typst-library/src/layout/table.rs | 54 +- crates/typst-library/src/layout/terms.rs | 34 +- crates/typst-library/src/layout/transform.rs | 45 +- crates/typst-library/src/lib.rs | 72 +- crates/typst-library/src/math/accent.rs | 7 +- crates/typst-library/src/math/align.rs | 5 +- crates/typst-library/src/math/attach.rs | 35 +- crates/typst-library/src/math/cancel.rs | 26 +- crates/typst-library/src/math/class.rs | 7 +- crates/typst-library/src/math/frac.rs | 20 +- .../src/math/{delimited.rs => lr.rs} | 32 +- crates/typst-library/src/math/matrix.rs | 47 +- crates/typst-library/src/math/mod.rs | 128 +-- crates/typst-library/src/math/op.rs | 9 +- crates/typst-library/src/math/root.rs | 16 +- crates/typst-library/src/math/row.rs | 83 +- crates/typst-library/src/math/style.rs | 73 +- crates/typst-library/src/math/underover.rs | 44 +- crates/typst-library/src/meta/bibliography.rs | 37 +- crates/typst-library/src/meta/context.rs | 83 +- crates/typst-library/src/meta/counter.rs | 373 +++--- crates/typst-library/src/meta/document.rs | 5 +- crates/typst-library/src/meta/figure.rs | 82 +- crates/typst-library/src/meta/footnote.rs | 46 +- crates/typst-library/src/meta/heading.rs | 37 +- crates/typst-library/src/meta/link.rs | 26 +- crates/typst-library/src/meta/metadata.rs | 15 +- crates/typst-library/src/meta/mod.rs | 49 +- crates/typst-library/src/meta/numbering.rs | 9 +- crates/typst-library/src/meta/outline.rs | 73 +- crates/typst-library/src/meta/query.rs | 49 +- crates/typst-library/src/meta/reference.rs | 30 +- crates/typst-library/src/meta/state.rs | 236 ++-- crates/typst-library/src/prelude.rs | 8 +- crates/typst-library/src/shared/ext.rs | 6 +- crates/typst-library/src/symbols/emoji.rs | 2 +- crates/typst-library/src/symbols/mod.rs | 6 +- crates/typst-library/src/symbols/sym.rs | 2 +- crates/typst-library/src/text/deco.rs | 66 +- crates/typst-library/src/text/misc.rs | 68 +- crates/typst-library/src/text/mod.rs | 68 +- crates/typst-library/src/text/quotes.rs | 13 +- crates/typst-library/src/text/raw.rs | 35 +- crates/typst-library/src/text/shift.rs | 14 +- crates/typst-library/src/visualize/image.rs | 119 +- crates/typst-library/src/visualize/line.rs | 52 +- crates/typst-library/src/visualize/mod.rs | 37 +- crates/typst-library/src/visualize/path.rs | 39 +- crates/typst-library/src/visualize/polygon.rs | 176 ++- crates/typst-library/src/visualize/shape.rs | 106 +- crates/typst-syntax/Cargo.toml | 2 +- crates/typst/Cargo.toml | 2 +- crates/typst/src/diag.rs | 12 +- crates/typst/src/doc.rs | 29 +- crates/typst/src/eval/args.rs | 62 +- crates/typst/src/eval/array.rs | 827 ++++++++++---- crates/typst/src/eval/auto.rs | 18 +- crates/typst/src/eval/bool.rs | 15 + crates/typst/src/eval/bytes.rs | 141 ++- crates/typst/src/eval/cast.rs | 126 +- crates/typst/src/eval/datetime.rs | 601 +++++++--- crates/typst/src/eval/dict.rs | 172 ++- crates/typst/src/eval/duration.rs | 84 +- crates/typst/src/eval/fields.rs | 84 +- crates/typst/src/eval/float.rs | 60 + crates/typst/src/eval/func.rs | 394 +++++-- crates/typst/src/eval/int.rs | 155 ++- crates/typst/src/eval/library.rs | 35 +- crates/typst/src/eval/methods.rs | 485 +------- crates/typst/src/eval/mod.rs | 193 ++-- crates/typst/src/eval/module.rs | 27 +- crates/typst/src/eval/none.rs | 41 +- crates/typst/src/eval/ops.rs | 59 +- crates/typst/src/eval/plugin.rs | 122 +- crates/typst/src/eval/scope.rs | 104 +- crates/typst/src/eval/str.rs | 643 ++++++++--- crates/typst/src/eval/symbol.rs | 102 +- crates/typst/src/eval/ty.rs | 165 +++ crates/typst/src/eval/value.rs | 274 ++--- .../{external_graphics_state.rs => extg.rs} | 3 +- crates/typst/src/export/pdf/mod.rs | 6 +- crates/typst/src/export/pdf/outline.rs | 2 +- crates/typst/src/export/pdf/page.rs | 14 +- crates/typst/src/export/render.rs | 6 +- crates/typst/src/export/svg.rs | 6 +- crates/typst/src/geom/align.rs | 531 +++++---- crates/typst/src/geom/angle.rs | 54 +- crates/typst/src/geom/axes.rs | 23 +- crates/typst/src/geom/color.rs | 274 ++++- crates/typst/src/geom/corners.rs | 8 +- crates/typst/src/geom/dir.rs | 84 +- crates/typst/src/geom/ellipse.rs | 2 +- crates/typst/src/geom/fr.rs | 30 +- crates/typst/src/geom/length.rs | 98 +- crates/typst/src/geom/mod.rs | 8 +- crates/typst/src/geom/ratio.rs | 28 +- crates/typst/src/geom/rel.rs | 21 +- crates/typst/src/geom/rounded.rs | 6 +- crates/typst/src/geom/shape.rs | 4 +- crates/typst/src/geom/sides.rs | 20 +- crates/typst/src/geom/smart.rs | 12 +- crates/typst/src/geom/stroke.rs | 187 +-- crates/typst/src/ide/complete.rs | 169 +-- crates/typst/src/ide/tooltip.rs | 11 +- crates/typst/src/image.rs | 2 +- crates/typst/src/lib.rs | 2 +- crates/typst/src/model/content.rs | 245 ++-- crates/typst/src/model/element.rs | 232 ++-- crates/typst/src/model/introspect.rs | 51 +- crates/typst/src/model/label.rs | 32 + crates/typst/src/model/mod.rs | 4 +- crates/typst/src/model/realize.rs | 6 +- crates/typst/src/model/selector.rs | 226 ++-- crates/typst/src/model/styles.rs | 45 +- crates/typst/src/util/fmt.rs | 78 ++ crates/typst/src/util/mod.rs | 127 +-- 140 files changed, 6932 insertions(+), 6098 deletions(-) delete mode 100644 crates/typst-library/src/compute/construct.rs rename crates/typst-library/src/math/{delimited.rs => lr.rs} (88%) create mode 100644 crates/typst/src/eval/bool.rs create mode 100644 crates/typst/src/eval/float.rs create mode 100644 crates/typst/src/eval/ty.rs rename crates/typst/src/export/pdf/{external_graphics_state.rs => extg.rs} (99%) create mode 100644 crates/typst/src/util/fmt.rs diff --git a/Cargo.lock b/Cargo.lock index 95e0511ed..feb932b01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,9 +625,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecow" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c5051925c54d9a42c8652313b5358a7432eed209466b443ed5220431243a14" +checksum = "1d1990d053cf6edf3f030682dba3b0eb65ef01fabb2686072765d8a17d6728e8" dependencies = [ "serde", ] diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml index 68ba73087..040a44058 100644 --- a/crates/typst-library/Cargo.toml +++ b/crates/typst-library/Cargo.toml @@ -22,7 +22,7 @@ az = "1.2" chinese-number = { version = "0.7.2", default-features = false, features = ["number-to-chinese"] } comemo = "0.3" csv = "1" -ecow = "0.1" +ecow = { version = "0.1.2", features = ["serde"] } hayagriva = "0.3.2" hypher = "0.1.3" icu_properties = { version = "1.2.0", features = ["serde"] } diff --git a/crates/typst-library/src/compute/calc.rs b/crates/typst-library/src/compute/calc.rs index 83ecac5d6..9043e1f15 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::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); + scope.define_func::(); 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, - /// The callsite span. - span: Span, ) -> SourceResult { 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, /// The callsite span. span: Span, + /// The exponent of the power. + exponent: Spanned, ) -> SourceResult { 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, @@ -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, @@ -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, @@ -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, /// The base of the logarithm. May not be zero. #[named] #[default(Spanned::new(10.0, Span::detached()))] base: Spanned, - /// The callsite span. - span: Span, ) -> SourceResult { 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, /// The callsite span. span: Span, + /// The number whose logarithm to calculate. Must be strictly positive. + value: Spanned, ) -> SourceResult { 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 { /// 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 { /// 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>, - /// The callsite span. - span: Span, ) -> SourceResult { 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>, - /// The callsite span. - span: Span, ) -> SourceResult { 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 6ea8bd826..000000000 --- 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 { - let mut args = args; - Ok(if let Some(string) = args.find::>()? { - 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, - /// The month of the datetime. - #[named] - month: Option, - /// The day of the datetime. - #[named] - day: Option, - /// The hour of the datetime. - #[named] - hour: Option, - /// The minute of the datetime. - #[named] - minute: Option, - /// The second of the datetime. - #[named] - second: Option, -) -> StrResult { - 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, - /// The virtual machine. - vt: &mut Vt, -) -> StrResult { - 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, - /// The color space to mix in. By default, this happens in a perceptual - /// color space (Oklab). - #[named] - #[default(ColorSpace::Oklab)] - space: ColorSpace, -) -> StrResult { - 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>, - /// The callsite span. - span: Span, -) -> SourceResult { - 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() -/// ``` -/// -/// 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, -) -> SourceResult { - 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, -) -> SourceResult { - Regex::new(®ex.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(::error(value)), - }) - .collect::, _>>()? - .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 : set text(blue) -/// #show label("b"): set text(red) -/// -/// = Heading -/// *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 { - let mut args = args; - let first = args.expect::("end")?; - let (start, end) = match args.eat::()? { - 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, - /// The virtual machine. - vm: &mut Vm, -) -> SourceResult { - 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 222b14d35..dadf0bed3 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::(); + global.define_func::(); + global.define_func::(); + global.define_func::(); + global.define_func::(); + global.define_func::(); + global.define_func::(); +} + /// 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, /// The encoding to read the file with. @@ -34,8 +43,6 @@ pub fn read( #[named] #[default(Some(Encoding::Utf8))] encoding: Option, - /// The virtual machine. - vm: &mut Vm, ) -> SourceResult { let Spanned { v: path, span } = path; let id = vm.resolve_path(&path).at(span)?; @@ -101,7 +108,7 @@ impl From 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 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, /// 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 { 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, - /// The delimiter that separates columns in the CSV file. - /// Must be a single ASCII character. - #[named] - #[default] - delimiter: Delimiter, -) -> SourceResult { - 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(); +#[scope] +impl csv { + /// Reads structured data from a CSV string/bytes. + #[func(title = "Decode CSV")] + pub fn decode( + /// CSV data. + data: Spanned, + /// The delimiter that separates columns in the CSV file. + /// Must be a single ASCII character. + #[named] + #[default] + delimiter: Delimiter, + ) -> SourceResult { + 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)) + 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, /// The virtual machine. vm: &mut Vm, + /// Path to a JSON file. + path: Spanned, ) -> SourceResult { 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, -) -> SourceResult { - 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, - /// Whether to pretty print the JSON with newlines and indentation. - #[named] - #[default(true)] - pretty: bool, -) -> SourceResult { - let Spanned { v: value, span } = value; - if pretty { - serde_json::to_string_pretty(&value) - } else { - serde_json::to_string(&value) +#[scope] +impl json { + /// Reads structured data from a JSON string/bytes. + #[func(title = "Decode JSON")] + pub fn decode( + /// JSON data. + data: Spanned, + ) -> SourceResult { + 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. + #[func(title = "Encode JSON")] + pub fn encode( + /// Value to be encoded. + value: Spanned, + /// Whether to pretty print the JSON with newlines and indentation. + #[named] + #[default(true)] + pretty: bool, + ) -> SourceResult { + 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, /// The virtual machine. vm: &mut Vm, + /// Path to a TOML file. + path: Spanned, ) -> SourceResult { 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, -) -> SourceResult { - 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, + ) -> SourceResult { + 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, - /// Whether to pretty-print the resulting TOML. - #[named] - #[default(true)] - pretty: bool, -) -> SourceResult { - 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, + /// Whether to pretty-print the resulting TOML. + #[named] + #[default(true)] + pretty: bool, + ) -> SourceResult { + 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, /// The virtual machine. vm: &mut Vm, + /// Path to a YAML file. + path: Spanned, ) -> SourceResult { 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, -) -> SourceResult { - 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, + ) -> SourceResult { + 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, -) -> SourceResult { - 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, + ) -> SourceResult { + 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, /// The virtual machine. vm: &mut Vm, + /// Path to a CBOR file. + path: Spanned, ) -> SourceResult { 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, -) -> SourceResult { - 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, + ) -> SourceResult { + 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, -) -> SourceResult { - 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, + ) -> SourceResult { + 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, /// The virtual machine. vm: &mut Vm, + /// Path to an XML file. + path: Spanned, ) -> SourceResult { 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, -) -> SourceResult { - 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, + ) -> SourceResult { + 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 3d07a3af6..dad05717d 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::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_func::(); + global.define_func::(); + global.define_func::(); + global.define_func::(); } /// 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, -) -> StrResult { - 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, + ) -> StrResult { + 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, -) -> StrResult { - 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, + ) -> StrResult { + 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 { 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 ca95f7b72..9e8976532 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); } diff --git a/crates/typst-library/src/layout/align.rs b/crates/typst-library/src/layout/align.rs index 5f7e8bc04..f080f677b 100644 --- a/crates/typst-library/src/layout/align.rs +++ b/crates/typst-library/src/layout/align.rs @@ -2,7 +2,7 @@ use crate::prelude::*; /// Aligns content horizontally and vertically. /// -/// ## Example { #example } +/// # Example /// ```example /// #set align(center) /// @@ -11,43 +11,9 @@ use crate::prelude::*; /// Not left nor right, it stands alone \ /// A work of art, a visual throne /// ``` -/// -/// Display: Align -/// Category: layout -#[element(Show)] +#[elem(Show)] pub struct AlignElem { - /// The alignment along both axes. - /// - /// Possible values for horizontal alignments are: - /// - `start` - /// - `end` - /// - `left` - /// - `center` - /// - `right` - /// - /// The `start` and `end` alignments are relative to the current [text - /// direction]($func/text.dir). - /// - /// Possible values for vertical alignments are: - /// - `top` - /// - `horizon` - /// - `bottom` - /// - /// You can use the `axis` method on a single-axis alignment to obtain - /// whether it is `{"horizontal"}` or `{"vertical"}`. You can also use the - /// `inv` method to obtain its inverse alignment. For example, - /// `{top.axis()}` is `{"vertical"}`, while `{top.inv()}` is equal to - /// `{bottom}`. - /// - /// To align along both axes at the same time, add the two alignments using - /// the `+` operator to get a `2d alignment`. For example, `top + right` - /// aligns the content to the top right corner. - /// - /// For 2d alignments, the `x` and `y` fields hold their horizontal and - /// vertical components, respectively. Additionally, you can use the `inv` - /// method to obtain a 2d alignment with both components inverted. For - /// instance, `{(top + right).x}` is `right`, `{(top + right).y}` is `top`, - /// and `{(top + right).inv()}` is equal to `bottom + left`. + /// The [alignment]($alignment) along both axes. /// /// ```example /// #set page(height: 6cm) @@ -61,8 +27,8 @@ pub struct AlignElem { /// ``` #[positional] #[fold] - #[default(Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top)))] - pub alignment: Axes>, + #[default] + pub alignment: Align, /// The content to align. #[required] @@ -72,8 +38,6 @@ pub struct AlignElem { impl Show for AlignElem { #[tracing::instrument(name = "AlignElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self - .body() - .styled(Self::set_alignment(self.alignment(styles).map(Some)))) + Ok(self.body().styled(Self::set_alignment(self.alignment(styles)))) } } diff --git a/crates/typst-library/src/layout/columns.rs b/crates/typst-library/src/layout/columns.rs index 6645ba9e3..961bedc5c 100644 --- a/crates/typst-library/src/layout/columns.rs +++ b/crates/typst-library/src/layout/columns.rs @@ -10,9 +10,9 @@ use crate::text::TextElem; /// necessary. /// /// If you need to insert columns across your whole document, you can use the -/// [`{page}` function's `columns` parameter]($func/page.columns) instead. +/// [`{page}` function's `columns` parameter]($page.columns) instead. /// -/// ## Example { #example } +/// # Example /// ```example /// = Towards Advanced Deep Learning /// @@ -32,10 +32,7 @@ use crate::text::TextElem; /// increasingly been used to solve a /// variety of problems. /// ``` -/// -/// Display: Columns -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct ColumnsElem { /// The number of columns. #[positional] @@ -132,11 +129,11 @@ impl Layout for ColumnsElem { /// Forces a column break. /// -/// The function will behave like a [page break]($func/pagebreak) when used in a +/// The function will behave like a [page break]($pagebreak) when used in a /// single column layout or the last column on a page. Otherwise, content after /// the column break will be placed in the next column. /// -/// ## Example { #example } +/// # Example /// ```example /// #set page(columns: 2) /// Preliminary findings from our @@ -153,10 +150,7 @@ impl Layout for ColumnsElem { /// understanding of the fundamental /// laws of nature. /// ``` -/// -/// Display: Column Break -/// Category: layout -#[element(Behave)] +#[elem(title = "Column Break", Behave)] pub struct ColbreakElem { /// If `{true}`, the column break is skipped if the current column is /// already empty. diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs index c79669d0b..e966398f4 100644 --- a/crates/typst-library/src/layout/container.rs +++ b/crates/typst-library/src/layout/container.rs @@ -11,7 +11,7 @@ use crate::prelude::*; /// elements into a paragraph. Boxes take the size of their contents by default /// but can also be sized explicitly. /// -/// ## Example { #example } +/// # Example /// ```example /// Refer to the docs /// #box( @@ -20,15 +20,12 @@ use crate::prelude::*; /// ) /// for more information. /// ``` -/// -/// Display: Box -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct BoxElem { /// The width of the box. /// - /// Boxes can have [fractional]($type/fraction) widths, as the example - /// below demonstrates. + /// Boxes can have [fractional]($fraction) widths, as the example below + /// demonstrates. /// /// _Note:_ Currently, only boxes and only their widths might be fractionally /// sized within paragraphs. Support for fractionally sized images, shapes, @@ -51,23 +48,29 @@ pub struct BoxElem { pub baseline: Rel, /// The box's background color. See the - /// [rectangle's documentation]($func/rect.fill) for more details. + /// [rectangle's documentation]($rect.fill) for more details. pub fill: Option, /// The box's border color. See the - /// [rectangle's documentation]($func/rect.stroke) for more details. + /// [rectangle's documentation]($rect.stroke) for more details. #[resolve] #[fold] - pub stroke: Sides>>, + pub stroke: Sides>>, - /// How much to round the box's corners. See the [rectangle's - /// documentation]($func/rect.radius) for more details. + /// How much to round the box's corners. See the + /// [rectangle's documentation]($rect.radius) for more details. #[resolve] #[fold] pub radius: Corners>>, - /// How much to pad the box's content. See the [rectangle's - /// documentation]($func/rect.inset) for more details. + /// How much to pad the box's content. + /// + /// _Note:_ When the box contains text, its exact size depends on the + /// current [text edges]($text.top-edge). + /// + /// ```example + /// #rect(inset: 0pt)[Tight] + /// ``` #[resolve] #[fold] pub inset: Sides>>, @@ -76,7 +79,7 @@ pub struct BoxElem { /// /// This is useful to prevent padding from affecting line layout. For a /// generalized version of the example below, see the documentation for the - /// [raw text's block parameter]($func/raw.block). + /// [raw text's block parameter]($raw.block). /// /// ```example /// An inline @@ -119,8 +122,7 @@ impl Layout for BoxElem { let expand = sizing.as_ref().map(Smart::is_custom); let size = sizing .resolve(styles) - .zip(regions.base()) - .map(|(s, b)| s.map(|v| v.relative_to(b))) + .zip_map(regions.base(), |s, b| s.map(|v| v.relative_to(b))) .unwrap_or(regions.base()); // Apply inset. @@ -151,7 +153,7 @@ impl Layout for BoxElem { // Prepare fill and stroke. let fill = self.fill(styles); - let stroke = self.stroke(styles).map(|s| s.map(PartialStroke::unwrap_or_default)); + let stroke = self.stroke(styles).map(|s| s.map(Stroke::unwrap_or_default)); // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { @@ -172,7 +174,7 @@ impl Layout for BoxElem { /// Such a container can be used to separate content, size it, and give it a /// background or border. /// -/// ## Examples { #examples } +/// # Examples /// With a block, you can give a background to content while still allowing it /// to break across multiple pages. /// ```example @@ -196,10 +198,7 @@ impl Layout for BoxElem { /// = Blocky /// More text. /// ``` -/// -/// Display: Block -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct BlockElem { /// The block's width. /// @@ -215,7 +214,7 @@ pub struct BlockElem { pub width: Smart>, /// The block's height. When the height is larger than the remaining space - /// on a page and [`breakable`]($func/block.breakable) is `{true}`, the + /// on a page and [`breakable`]($block.breakable) is `{true}`, the /// block will continue on the next page with the remaining height. /// /// ```example @@ -244,29 +243,29 @@ pub struct BlockElem { pub breakable: bool, /// The block's background color. See the - /// [rectangle's documentation]($func/rect.fill) for more details. + /// [rectangle's documentation]($rect.fill) for more details. pub fill: Option, /// The block's border color. See the - /// [rectangle's documentation]($func/rect.stroke) for more details. + /// [rectangle's documentation]($rect.stroke) for more details. #[resolve] #[fold] - pub stroke: Sides>>, + pub stroke: Sides>>, - /// How much to round the block's corners. See the [rectangle's - /// documentation]($func/rect.radius) for more details. + /// How much to round the block's corners. See the + /// [rectangle's documentation]($rect.radius) for more details. #[resolve] #[fold] pub radius: Corners>>, - /// How much to pad the block's content. See the [rectangle's - /// documentation]($func/rect.inset) for more details. + /// How much to pad the block's content. See the + /// [box's documentation]($box.inset) for more details. #[resolve] #[fold] pub inset: Sides>>, /// How much to expand the block's size without affecting the layout. See - /// the [rectangle's documentation]($func/rect.outset) for more details. + /// the [box's documentation]($box.outset) for more details. #[resolve] #[fold] pub outset: Sides>>, @@ -352,8 +351,7 @@ impl Layout for BlockElem { let mut expand = sizing.as_ref().map(Smart::is_custom); let mut size = sizing .resolve(styles) - .zip(regions.base()) - .map(|(s, b)| s.map(|v| v.relative_to(b))) + .zip_map(regions.base(), |s, b| s.map(|v| v.relative_to(b))) .unwrap_or(regions.base()); // Layout the child. @@ -418,7 +416,7 @@ impl Layout for BlockElem { // Prepare fill and stroke. let fill = self.fill(styles); - let stroke = self.stroke(styles).map(|s| s.map(PartialStroke::unwrap_or_default)); + let stroke = self.stroke(styles).map(|s| s.map(Stroke::unwrap_or_default)); // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { diff --git a/crates/typst-library/src/layout/enum.rs b/crates/typst-library/src/layout/enum.rs index 8f23b6dc7..c505f189d 100644 --- a/crates/typst-library/src/layout/enum.rs +++ b/crates/typst-library/src/layout/enum.rs @@ -11,7 +11,7 @@ use super::GridLayouter; /// /// Displays a sequence of items vertically and numbers them consecutively. /// -/// ## Example { #example } +/// # Example /// ```example /// Automatically numbered: /// + Preparations @@ -41,8 +41,8 @@ use super::GridLayouter; /// + Don't forget step two /// ``` /// -/// You can also use [`enum.item`]($func/enum.item) to programmatically -/// customize the number of each item in the enumeration: +/// You can also use [`enum.item`]($enum.item) to programmatically customize the +/// number of each item in the enumeration: /// /// ```example /// #enum( @@ -52,7 +52,7 @@ use super::GridLayouter; /// ) /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// This functions also has dedicated syntax: /// /// - Starting a line with a plus sign creates an automatically numbered @@ -63,18 +63,11 @@ use super::GridLayouter; /// Enumeration items can contain multiple paragraphs and other block-level /// content. All content that is indented more than an item's marker becomes /// part of that item. -/// -/// Display: Numbered List -/// Category: layout -#[element(Layout)] -#[scope( - scope.define("item", EnumItem::func()); - scope -)] +#[elem(scope, title = "Numbered List", Layout)] pub struct EnumElem { /// If this is `{false}`, the items are spaced apart with - /// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal - /// [leading]($func/par.leading) instead. This makes the enumeration more + /// [enum spacing]($enum.spacing). If it is `{true}`, they use normal + /// [leading]($par.leading) instead. This makes the enumeration more /// compact, which can look better if the items are short. /// /// In markup mode, the value of this parameter is determined based on @@ -95,7 +88,7 @@ pub struct EnumElem { pub tight: bool, /// How to number the enumeration. Accepts a - /// [numbering pattern or function]($func/numbering). + /// [numbering pattern or function]($numbering). /// /// If the numbering pattern contains multiple counting symbols, they apply /// to nested enums. If given a function, the function receives one argument @@ -153,7 +146,7 @@ pub struct EnumElem { /// The spacing between the items of a wide (non-tight) enumeration. /// - /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). + /// If set to `{auto}`, uses the spacing [below blocks]($block.below). pub spacing: Smart, /// The horizontal alignment that enum numbers should have. @@ -177,8 +170,8 @@ pub struct EnumElem { /// 16. Sixteen /// 32. Thirty two /// ```` - #[default(HorizontalAlign(GenAlign::End))] - pub number_align: HorizontalAlign, + #[default(HAlign::End)] + pub number_align: HAlign, /// The numbered list's items. /// @@ -201,6 +194,12 @@ pub struct EnumElem { parents: Parent, } +#[scope] +impl EnumElem { + #[elem] + type EnumItem; +} + impl Layout for EnumElem { #[tracing::instrument(name = "EnumElem::layout", skip_all)] fn layout( @@ -225,11 +224,10 @@ impl Layout for EnumElem { let full = self.full(styles); // Horizontally align based on the given respective parameter. - // Vertically align to the top to avoid inheriting 'horizon' or - // 'bottom' alignment from the context and having the number be - // displaced in relation to the item it refers to. - let number_align: Axes> = - Axes::new(self.number_align(styles).into(), Align::Top.into()).map(Some); + // Vertically align to the top to avoid inheriting `horizon` or `bottom` + // alignment from the context and having the number be displaced in + // relation to the item it refers to. + let number_align = self.number_align(styles) + VAlign::Top; for item in self.children() { number = item.number(styles).unwrap_or(number); @@ -278,10 +276,7 @@ impl Layout for EnumElem { } /// An enumeration item. -/// -/// Display: Numbered List Item -/// Category: layout -#[element] +#[elem(name = "item", title = "Numbered List Item")] pub struct EnumItem { /// The item's number. #[positional] diff --git a/crates/typst-library/src/layout/flow.rs b/crates/typst-library/src/layout/flow.rs index 4ce78c94e..fe6e9398a 100644 --- a/crates/typst-library/src/layout/flow.rs +++ b/crates/typst-library/src/layout/flow.rs @@ -14,10 +14,7 @@ use crate::visualize::{ /// /// This element is responsible for layouting both the top-level content flow /// and the contents of boxes. -/// -/// Display: Flow -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct FlowElem { /// The children that will be arranges into a flow. #[variadic] @@ -62,7 +59,7 @@ impl Layout for FlowElem { frame.meta(styles, true); layouter.items.push(FlowItem::Frame { frame, - aligns: Axes::new(Align::Top, Align::Left), + align: Axes::splat(FixedAlign::Start), sticky: true, movable: false, }); @@ -128,12 +125,12 @@ enum FlowItem { /// A frame for a layouted block, how to align it, whether it sticks to the /// item after it (for orphan prevention), and whether it is movable /// (to keep it together with its footnotes). - Frame { frame: Frame, aligns: Axes, sticky: bool, movable: bool }, + Frame { frame: Frame, align: Axes, sticky: bool, movable: bool }, /// An absolutely placed frame. Placed { frame: Frame, - x_align: Align, - y_align: Smart>, + x_align: FixedAlign, + y_align: Smart>, delta: Axes>, float: bool, clearance: Abs, @@ -209,7 +206,7 @@ impl<'a> FlowLayouter<'a> { par: &ParElem, styles: StyleChain, ) -> SourceResult<()> { - let aligns = AlignElem::alignment_in(styles).resolve(styles); + let align = AlignElem::alignment_in(styles).resolve(styles); let leading = ParElem::leading_in(styles); let consecutive = self.last_was_par; let lines = par @@ -242,7 +239,7 @@ impl<'a> FlowLayouter<'a> { self.layout_item( vt, - FlowItem::Frame { frame, aligns, sticky: false, movable: true }, + FlowItem::Frame { frame, align, sticky: false, movable: true }, )?; } @@ -258,11 +255,11 @@ impl<'a> FlowLayouter<'a> { content: &dyn Layout, styles: StyleChain, ) -> SourceResult<()> { - let aligns = AlignElem::alignment_in(styles).resolve(styles); + let align = AlignElem::alignment_in(styles).resolve(styles); let sticky = BlockElem::sticky_in(styles); let pod = Regions::one(self.regions.base(), Axes::splat(false)); let frame = content.layout(vt, styles, pod)?.into_frame(); - self.layout_item(vt, FlowItem::Frame { frame, aligns, sticky, movable: true })?; + self.layout_item(vt, FlowItem::Frame { frame, align, sticky, movable: true })?; self.last_was_par = false; Ok(()) } @@ -278,10 +275,10 @@ impl<'a> FlowLayouter<'a> { let clearance = placed.clearance(styles); let alignment = placed.alignment(styles); let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles); - let x_align = alignment.map_or(Align::Center, |aligns| { - aligns.x.unwrap_or(GenAlign::Start).resolve(styles) + let x_align = alignment.map_or(FixedAlign::Center, |align| { + align.x().unwrap_or_default().resolve(styles) }); - let y_align = alignment.map(|align| align.y.resolve(styles)); + let y_align = alignment.map(|align| align.y().map(VAlign::fix)); let frame = placed.layout(vt, styles, self.regions)?.into_frame(); let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance }; self.layout_item(vt, item) @@ -309,7 +306,7 @@ impl<'a> FlowLayouter<'a> { } // How to align the block. - let aligns = if let Some(align) = block.to::() { + let align = if let Some(align) = block.to::() { align.alignment(styles) } else if let Some((_, local)) = block.to_styled() { AlignElem::alignment_in(styles.chain(local)) @@ -332,7 +329,7 @@ impl<'a> FlowLayouter<'a> { self.finish_region(vt)?; } - let item = FlowItem::Frame { frame, aligns, sticky, movable: false }; + let item = FlowItem::Frame { frame, align, sticky, movable: false }; self.layout_item(vt, item)?; } @@ -404,14 +401,14 @@ impl<'a> FlowLayouter<'a> { - (frame.height() + clearance) / 2.0) / self.regions.full; let better_align = - if ratio <= 0.5 { Align::Bottom } else { Align::Top }; + if ratio <= 0.5 { FixedAlign::End } else { FixedAlign::Start }; *y_align = Smart::Custom(Some(better_align)); } // Add some clearance so that the float doesn't touch the main // content. frame.size_mut().y += clearance; - if *y_align == Smart::Custom(Some(Align::Bottom)) { + if *y_align == Smart::Custom(Some(FixedAlign::End)) { frame.translate(Point::with_y(clearance)); } @@ -459,8 +456,10 @@ impl<'a> FlowLayouter<'a> { } FlowItem::Placed { float: false, .. } => {} FlowItem::Placed { frame, float: true, y_align, .. } => match y_align { - Smart::Custom(Some(Align::Top)) => float_top_height += frame.height(), - Smart::Custom(Some(Align::Bottom)) => { + Smart::Custom(Some(FixedAlign::Start)) => { + float_top_height += frame.height() + } + Smart::Custom(Some(FixedAlign::End)) => { float_bottom_height += frame.height() } _ => {} @@ -486,7 +485,7 @@ impl<'a> FlowLayouter<'a> { } let mut output = Frame::new(size); - let mut ruler = Align::Top; + let mut ruler = FixedAlign::Start; let mut float_top_offset = Abs::zero(); let mut offset = float_top_height; let mut float_bottom_offset = Abs::zero(); @@ -502,9 +501,9 @@ impl<'a> FlowLayouter<'a> { let remaining = self.initial.y - used.y; offset += v.share(fr, remaining); } - FlowItem::Frame { frame, aligns, .. } => { - ruler = ruler.max(aligns.y); - let x = aligns.x.position(size.x - frame.width()); + FlowItem::Frame { frame, align, .. } => { + ruler = ruler.max(align.y); + let x = align.x.position(size.x - frame.width()); let y = offset + ruler.position(size.y - used.y); let pos = Point::new(x, y); offset += frame.height(); @@ -514,12 +513,12 @@ impl<'a> FlowLayouter<'a> { let x = x_align.position(size.x - frame.width()); let y = if float { match y_align { - Smart::Custom(Some(Align::Top)) => { + Smart::Custom(Some(FixedAlign::Start)) => { let y = float_top_offset; float_top_offset += frame.height(); y } - Smart::Custom(Some(Align::Bottom)) => { + Smart::Custom(Some(FixedAlign::End)) => { let y = size.y - footnote_height - float_bottom_height + float_bottom_offset; float_bottom_offset += frame.height(); @@ -537,7 +536,7 @@ impl<'a> FlowLayouter<'a> { }; let pos = Point::new(x, y) - + delta.zip(size).map(|(d, s)| d.relative_to(s)).to_point(); + + delta.zip_map(size, Rel::relative_to).to_point(); output.push_frame(pos, frame); } diff --git a/crates/typst-library/src/layout/grid.rs b/crates/typst-library/src/layout/grid.rs index 4f5175e97..069625249 100644 --- a/crates/typst-library/src/layout/grid.rs +++ b/crates/typst-library/src/layout/grid.rs @@ -34,7 +34,7 @@ use super::Sizing; /// instead of an array. For example, `columns:` `{3}` is equivalent to /// `columns:` `{(auto, auto, auto)}`. /// -/// ## Example { #example } +/// # Example /// ```example /// #set text(10pt, style: "italic") /// #let cell = rect.with( @@ -58,10 +58,7 @@ use super::Sizing; /// cell[One more thing...], /// ) /// ``` -/// -/// Display: Grid -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct GridElem { /// The column sizes. /// diff --git a/crates/typst-library/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs index c6e83e0c2..7f17a7d73 100644 --- a/crates/typst-library/src/layout/hide.rs +++ b/crates/typst-library/src/layout/hide.rs @@ -7,15 +7,12 @@ use crate::prelude::*; /// content. It may also be useful to redact content because its arguments are /// not included in the output. /// -/// ## Example { #example } +/// # Example /// ```example /// Hello Jane \ /// #hide[Hello] Joe /// ``` -/// -/// Display: Hide -/// Category: layout -#[element(Show)] +#[elem(Show)] pub struct HideElem { /// The content to hide. #[required] diff --git a/crates/typst-library/src/layout/list.rs b/crates/typst-library/src/layout/list.rs index 8bb8744b5..a9dad85b6 100644 --- a/crates/typst-library/src/layout/list.rs +++ b/crates/typst-library/src/layout/list.rs @@ -9,7 +9,7 @@ use super::GridLayouter; /// Displays a sequence of items vertically, with each item introduced by a /// marker. /// -/// ## Example { #example } +/// # Example /// ```example /// Normal list. /// - Text @@ -30,24 +30,17 @@ use super::GridLayouter; /// ) /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// This functions also has dedicated syntax: Start a line with a hyphen, /// followed by a space to create a list item. A list item can contain multiple /// paragraphs and other block-level content. All content that is indented /// more than an item's marker becomes part of that item. -/// -/// Display: Bullet List -/// Category: layout -#[element(Layout)] -#[scope( - scope.define("item", ListItem::func()); - scope -)] +#[elem(scope, title = "Bullet List", Layout)] pub struct ListElem { - /// If this is `{false}`, the items are spaced apart with [list - /// spacing]($func/list.spacing). If it is `{true}`, they use normal - /// [leading]($func/par.leading) instead. This makes the list more compact, - /// which can look better if the items are short. + /// If this is `{false}`, the items are spaced apart with + /// [list spacing]($list.spacing). If it is `{true}`, they use normal + /// [leading]($par.leading) instead. This makes the list more compact, which + /// can look better if the items are short. /// /// In markup mode, the value of this parameter is determined based on /// whether items are separated with a blank line. If items directly follow @@ -98,7 +91,7 @@ pub struct ListElem { /// The spacing between the items of a wide (non-tight) list. /// - /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). + /// If set to `{auto}`, uses the spacing [below blocks]($block.below). pub spacing: Smart, /// The bullet list's children. @@ -120,6 +113,12 @@ pub struct ListElem { depth: Depth, } +#[scope] +impl ListElem { + #[elem] + type ListItem; +} + impl Layout for ListElem { #[tracing::instrument(name = "ListElem::layout", skip_all)] fn layout( @@ -142,7 +141,7 @@ impl Layout for ListElem { .marker(styles) .resolve(vt, depth)? // avoid '#set align' interference with the list - .aligned(Align::LEFT_TOP.into()); + .aligned(HAlign::Start + VAlign::Top); let mut cells = vec![]; for item in self.children() { @@ -170,10 +169,7 @@ impl Layout for ListElem { } /// A bullet list item. -/// -/// Display: Bullet List Item -/// Category: layout -#[element] +#[elem(name = "item", title = "Bullet List Item")] pub struct ListItem { /// The item's body. #[required] diff --git a/crates/typst-library/src/layout/measure.rs b/crates/typst-library/src/layout/measure.rs index eb8e509ec..d41b7f95f 100644 --- a/crates/typst-library/src/layout/measure.rs +++ b/crates/typst-library/src/layout/measure.rs @@ -2,13 +2,13 @@ use crate::prelude::*; /// Measures the layouted size of content. /// -/// The `measure` function lets you determine the layouted size of content. -/// Note that an infinite space is assumed, therefore the measured height/width -/// may not necessarily match the final height/width of the measured content. -/// If you want to measure in the current layout dimensions, you can combined -/// `measure` and [`layout`]($func/layout). +/// The `measure` function lets you determine the layouted size of content. Note +/// that an infinite space is assumed, therefore the measured height/width may +/// not necessarily match the final height/width of the measured content. If you +/// want to measure in the current layout dimensions, you can combine `measure` +/// and [`layout`]($layout). /// -/// # Example { #example } +/// # Example /// The same content can have a different size depending on the styles that /// are active when it is layouted. For example, in the example below /// `[#content]` is of course bigger when we increase the font size. @@ -21,8 +21,8 @@ use crate::prelude::*; /// ``` /// /// To do a meaningful measurement, you therefore first need to retrieve the -/// active styles with the [`style`]($func/style) function. You can then pass -/// them to the `measure` function. +/// active styles with the [`style`]($style) function. You can then pass them to +/// the `measure` function. /// /// ```example /// #let thing(body) = style(styles => { @@ -35,18 +35,15 @@ use crate::prelude::*; /// ``` /// /// The measure function returns a dictionary with the entries `width` and -/// `height`, both of type [`length`]($type/length). -/// -/// Display: Measure -/// Category: layout +/// `height`, both of type [`length`]($length). #[func] pub fn measure( + /// The virtual machine. + vm: &mut Vm, /// The content whose size to measure. content: Content, /// The styles with which to layout the content. styles: Styles, - /// The virtual machine. - vm: &mut Vm, ) -> SourceResult { let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); let styles = StyleChain::new(&styles); diff --git a/crates/typst-library/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs index 3334d5aa3..ace5cd6e2 100644 --- a/crates/typst-library/src/layout/mod.rs +++ b/crates/typst-library/src/layout/mod.rs @@ -10,7 +10,8 @@ mod fragment; mod grid; mod hide; mod list; -mod measure; +#[path = "measure.rs"] +mod measure_; mod pad; mod page; mod par; @@ -32,7 +33,7 @@ pub use self::fragment::*; pub use self::grid::*; pub use self::hide::*; pub use self::list::*; -pub use self::measure::*; +pub use self::measure_::*; pub use self::pad::*; pub use self::page::*; pub use self::par::*; @@ -57,7 +58,7 @@ use crate::math::{EquationElem, LayoutMath}; use crate::meta::DocumentElem; use crate::prelude::*; use crate::shared::BehavedBuilder; -use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem}; +use crate::text::{LinebreakElem, SmartquoteElem, SpaceElem, TextElem}; use crate::visualize::{ CircleElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, RectElem, SquareElem, @@ -65,43 +66,39 @@ use crate::visualize::{ /// Hook up all layout definitions. pub(super) fn define(global: &mut Scope) { - global.define("page", PageElem::func()); - global.define("pagebreak", PagebreakElem::func()); - global.define("v", VElem::func()); - global.define("par", ParElem::func()); - global.define("parbreak", ParbreakElem::func()); - global.define("h", HElem::func()); - global.define("box", BoxElem::func()); - global.define("block", BlockElem::func()); - global.define("list", ListElem::func()); - global.define("enum", EnumElem::func()); - global.define("terms", TermsElem::func()); - global.define("table", TableElem::func()); - global.define("stack", StackElem::func()); - global.define("grid", GridElem::func()); - global.define("columns", ColumnsElem::func()); - global.define("colbreak", ColbreakElem::func()); - global.define("place", PlaceElem::func()); - global.define("align", AlignElem::func()); - global.define("pad", PadElem::func()); - global.define("repeat", RepeatElem::func()); - global.define("move", MoveElem::func()); - global.define("scale", ScaleElem::func()); - global.define("rotate", RotateElem::func()); - global.define("hide", HideElem::func()); - global.define("measure", measure_func()); - global.define("ltr", Dir::LTR); - global.define("rtl", Dir::RTL); - global.define("ttb", Dir::TTB); - global.define("btt", Dir::BTT); - global.define("start", GenAlign::Start); - global.define("end", GenAlign::End); - global.define("left", GenAlign::Specific(Align::Left)); - global.define("center", GenAlign::Specific(Align::Center)); - global.define("right", GenAlign::Specific(Align::Right)); - global.define("top", GenAlign::Specific(Align::Top)); - global.define("horizon", GenAlign::Specific(Align::Horizon)); - global.define("bottom", GenAlign::Specific(Align::Bottom)); + global.category("layout"); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_type::>(); + global.define_type::(); + global.define_type::(); + global.define_type::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_elem::(); + global.define_func::(); } /// Root-level layout. @@ -598,7 +595,7 @@ impl<'a> ParBuilder<'a> { || content.is::() || content.is::() || content.is::() - || content.is::() + || content.is::() || content.to::().map_or(false, |elem| !elem.block(styles)) || content.is::() { diff --git a/crates/typst-library/src/layout/pad.rs b/crates/typst-library/src/layout/pad.rs index a3d5646ba..d1b0cb1fb 100644 --- a/crates/typst-library/src/layout/pad.rs +++ b/crates/typst-library/src/layout/pad.rs @@ -5,7 +5,7 @@ use crate::prelude::*; /// The spacing can be specified for each side individually, or for all sides at /// once by specifying a positional argument. /// -/// ## Example { #example } +/// # Example /// ```example /// #set align(center) /// @@ -13,10 +13,7 @@ use crate::prelude::*; /// _Typing speeds can be /// measured in words per minute._ /// ``` -/// -/// Display: Padding -/// Category: layout -#[element(Layout)] +#[elem(title = "Padding", Layout)] pub struct PadElem { /// The padding at the left side. #[parse( @@ -120,6 +117,5 @@ fn shrink(size: Size, padding: Sides>) -> Size { /// <=> (1 - p.rel) * w = s + p.abs /// <=> w = (s + p.abs) / (1 - p.rel) fn grow(size: Size, padding: Sides>) -> Size { - size.zip(padding.sum_by_axis()) - .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get())) + size.zip_map(padding.sum_by_axis(), |s, p| (s + p.abs).safe_div(1.0 - p.rel.get())) } diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 4ef90753f..d182a4172 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -1,6 +1,8 @@ use std::ptr; use std::str::FromStr; +use typst::eval::AutoValue; + use super::{AlignElem, ColumnsElem}; use crate::meta::{Counter, CounterKey, Numbering}; use crate::prelude::*; @@ -18,17 +20,14 @@ use crate::text::TextElem; /// The [Guide for Page Setup]($guides/page-setup-guide) explains how to use /// this and related functions to set up a document with many examples. /// -/// ## Example { #example } +/// # Example /// ```example /// >>> #set page(margin: auto) /// #set page("us-letter") /// /// There you go, US friends! /// ``` -/// -/// Display: Page -/// Category: layout -#[element] +#[elem] pub struct PageElem { /// A standard paper size to set width and height. #[external] @@ -59,9 +58,9 @@ pub struct PageElem { /// The height of the page. /// /// If this is set to `{auto}`, page breaks can only be triggered manually - /// by inserting a [page break]($func/pagebreak). Most examples throughout - /// this documentation use `{auto}` for the height of the page to - /// dynamically grow and shrink to fit their content. + /// by inserting a [page break]($pagebreak). Most examples throughout this + /// documentation use `{auto}` for the height of the page to dynamically + /// grow and shrink to fit their content. #[resolve] #[parse( args.named("height")? @@ -103,9 +102,9 @@ pub struct PageElem { /// - `bottom`: The bottom margin. /// - `left`: The left margin. /// - `inside`: The margin at the inner side of the page (where the - /// [binding]($func/page.binding) is). + /// [binding]($page.binding) is). /// - `outside`: The margin at the outer side of the page (opposite to the - /// [binding]($func/page.binding)). + /// [binding]($page.binding)). /// - `x`: The horizontal margins. /// - `y`: The vertical margins. /// - `rest`: The margins on all sides except those for which the @@ -132,7 +131,7 @@ pub struct PageElem { /// On which side the pages will be bound. /// - /// - `{auto}`: Equivalent to `left` if the [text direction]($func/text.dir) + /// - `{auto}`: Equivalent to `left` if the [text direction]($text.dir) /// is left-to-right and `right` if it is right-to-left. /// - `left`: Bound on the left side. /// - `right`: Bound on the right side. @@ -144,7 +143,7 @@ pub struct PageElem { /// How many columns the page has. /// /// If you need to insert columns into a page or other container, you can - /// also use the [`columns` function]($func/columns). + /// also use the [`columns` function]($columns). /// /// ```example:single /// #set page(columns: 2, height: 4.8cm) @@ -175,7 +174,7 @@ pub struct PageElem { /// ``` pub fill: Option, - /// How to [number]($func/numbering) the pages. + /// How to [number]($numbering) the pages. /// /// If an explicit `footer` (or `header` for top-aligned numbering) is /// given, the numbering is ignored. @@ -207,17 +206,17 @@ pub struct PageElem { /// /// #lorem(30) /// ``` - #[default(Align::Center.into())] + #[default(HAlign::Center + VAlign::Bottom)] #[parse({ - let spanned: Option>> = args.named("number-align")?; - if let Some(Spanned { v, span }) = spanned { - if matches!(v.y, Some(GenAlign::Specific(Align::Horizon))) { + let option: Option> = args.named("number-align")?; + if let Some(Spanned { v: align, span }) = option { + if align.y() == Some(VAlign::Horizon) { bail!(span, "page number cannot be `horizon`-aligned"); } } - spanned.map(|s| s.v) + option.map(|spanned| spanned.v) })] - pub number_align: Axes>, + pub number_align: Align, /// The page's header. Fills the top margin of each page. /// @@ -245,7 +244,7 @@ pub struct PageElem { /// /// For just a page number, the `numbering` property, typically suffices. If /// you want to create a custom footer, but still display the page number, - /// you can directly access the [page counter]($func/counter). + /// you can directly access the [page counter]($counter). /// /// ```example /// #set par(justify: true) @@ -406,14 +405,14 @@ impl PageElem { // We interpret the Y alignment as selecting header or footer // and then ignore it for aligning the actual number. - if let Some(x) = number_align.x { - counter = counter.aligned(Axes::with_x(Some(x))); + if let Some(x) = number_align.x() { + counter = counter.aligned(x.into()); } counter }); - if matches!(number_align.y, Some(GenAlign::Specific(Align::Top))) { + if matches!(number_align.y(), Some(VAlign::Top)) { header = header.or(numbering_marginal); } else { footer = footer.or(numbering_marginal); @@ -461,16 +460,16 @@ impl PageElem { let ascent = header_ascent.relative_to(margin.top); pos = Point::with_x(margin.left); area = Size::new(pw, margin.top - ascent); - align = Align::Bottom.into(); + align = Align::BOTTOM; } else if ptr::eq(marginal, &footer) { let descent = footer_descent.relative_to(margin.bottom); pos = Point::new(margin.left, size.y - margin.bottom + descent); area = Size::new(pw, margin.bottom - descent); - align = Align::Top.into(); + align = Align::TOP; } else { pos = Point::zero(); area = size; - align = Align::CENTER_HORIZON.into(); + align = HAlign::Center + VAlign::Horizon; }; let pod = Regions::one(area, Axes::splat(true)); @@ -626,12 +625,12 @@ impl Binding { cast! { Binding, self => match self { - Self::Left => GenAlign::Specific(Align::Left).into_value(), - Self::Right => GenAlign::Specific(Align::Right).into_value(), + Self::Left => Align::LEFT.into_value(), + Self::Right => Align::RIGHT.into_value(), }, - v: GenAlign => match v { - GenAlign::Specific(Align::Left) => Self::Left, - GenAlign::Specific(Align::Right) => Self::Right, + v: Align => match v { + Align::LEFT => Self::Left, + Align::RIGHT => Self::Right, _ => bail!("must be `left` or `right`"), }, } @@ -669,7 +668,7 @@ cast! { /// /// Must not be used inside any containers. /// -/// ## Example { #example } +/// # Example /// ```example /// The next page contains /// more details on compound theory. @@ -678,10 +677,7 @@ cast! { /// == Compound Theory /// In 1984, the first ... /// ``` -/// -/// Display: Page Break -/// Category: layout -#[element] +#[elem(title = "Page Break")] pub struct PagebreakElem { /// If `{true}`, the page break is skipped if the current page is already /// empty. diff --git a/crates/typst-library/src/layout/par.rs b/crates/typst-library/src/layout/par.rs index 39689477d..e28e661c5 100644 --- a/crates/typst-library/src/layout/par.rs +++ b/crates/typst-library/src/layout/par.rs @@ -16,7 +16,7 @@ use crate::layout::AlignElem; use crate::math::EquationElem; use crate::prelude::*; use crate::text::{ - is_gb_style, shape, LinebreakElem, Quoter, Quotes, ShapedText, SmartQuoteElem, + is_gb_style, shape, LinebreakElem, Quoter, Quotes, ShapedText, SmartquoteElem, SpaceElem, TextElem, }; @@ -26,7 +26,7 @@ use crate::text::{ /// properties, it can also be used to explicitly render its argument onto a /// paragraph of its own. /// -/// ## Example { #example } +/// # Example /// ```example /// #show par: set block(spacing: 0.65em) /// #set par( @@ -45,10 +45,7 @@ use crate::text::{ /// let $a$ be the smallest of the /// three integers. Then, we ... /// ``` -/// -/// Display: Paragraph -/// Category: layout -#[element(Construct)] +#[elem(title = "Paragraph", Construct)] pub struct ParElem { /// The spacing between lines. #[resolve] @@ -57,13 +54,13 @@ pub struct ParElem { /// Whether to justify text in its line. /// - /// Hyphenation will be enabled for justified paragraphs if the [text - /// property hyphenate]($func/text.hyphenate) is set to `{auto}` and the - /// current language is known. + /// Hyphenation will be enabled for justified paragraphs if the + /// [text function's `hyphenate` property]($text.hyphenate) is set to + /// `{auto}` and the current language is known. /// - /// Note that the current [alignment]($func/align) still has an effect on - /// the placement of the last line except if it ends with a [justified line - /// break]($func/linebreak.justify). + /// Note that the current [alignment]($align) still has an effect on the + /// placement of the last line except if it ends with a + /// [justified line break]($linebreak.justify). #[default(false)] pub justify: bool, @@ -88,7 +85,6 @@ pub struct ParElem { /// challenging to break in a visually /// pleasing way. /// ``` - #[default] pub linebreaks: Smart, /// The indent the first line of a paragraph should have. @@ -98,7 +94,7 @@ pub struct ParElem { /// /// By typographic convention, paragraph breaks are indicated either by some /// space between paragraphs or by indented first lines. Consider reducing - /// the [paragraph spacing]($func/block.spacing) to the [`leading`] when + /// the [paragraph spacing]($block.spacing) to the [`leading`] when /// using this property (e.g. using /// `[#show par: set block(spacing: 0.65em)]`). pub first_line_indent: Length, @@ -219,7 +215,7 @@ pub enum Linebreaks { /// [for loops]($scripting/#loops). Multiple consecutive /// paragraph breaks collapse into a single one. /// -/// ## Example { #example } +/// # Example /// ```example /// #for i in range(3) { /// [Blind text #i: ] @@ -228,13 +224,10 @@ pub enum Linebreaks { /// } /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// Instead of calling this function, you can insert a blank line into your /// markup to create a paragraph break. -/// -/// Display: Paragraph Break -/// Category: layout -#[element(Unlabellable)] +#[elem(title = "Paragraph Break", Unlabellable)] pub struct ParbreakElem {} impl Unlabellable for ParbreakElem {} @@ -266,8 +259,8 @@ struct Preparation<'a> { hyphenate: Option, /// The text language if it's the same for all children. lang: Option, - /// The paragraph's resolved alignment. - align: Align, + /// The paragraph's resolved horizontal alignment. + align: FixedAlign, /// Whether to justify the paragraph. justify: bool, /// The paragraph's hanging indent. @@ -550,7 +543,7 @@ fn collect<'a>( let first_line_indent = ParElem::first_line_indent_in(*styles); if !first_line_indent.is_zero() && consecutive - && AlignElem::alignment_in(*styles).x.resolve(*styles) + && AlignElem::alignment_in(*styles).resolve(*styles).x == TextElem::dir_in(*styles).start().into() { full.push(SPACING_REPLACE); @@ -593,15 +586,15 @@ fn collect<'a>( let c = if elem.justify(styles) { '\u{2028}' } else { '\n' }; full.push(c); Segment::Text(c.len_utf8()) - } else if let Some(elem) = child.to::() { + } else if let Some(elem) = child.to::() { let prev = full.len(); - if SmartQuoteElem::enabled_in(styles) { + if SmartquoteElem::enabled_in(styles) { let lang = TextElem::lang_in(styles); let region = TextElem::region_in(styles); let quotes = Quotes::from_lang( lang, region, - SmartQuoteElem::alternative_in(styles), + SmartquoteElem::alternative_in(styles), ); let peeked = iter.peek().and_then(|child| { let child = if let Some((child, _)) = child.to_styled() { @@ -611,7 +604,7 @@ fn collect<'a>( }; if let Some(elem) = child.to::() { elem.text().chars().next() - } else if child.is::() { + } else if child.is::() { Some('"') } else if child.is::() || child.is::() @@ -642,7 +635,7 @@ fn collect<'a>( }; if let Some(last) = full.chars().last() { - quoter.last(last, child.is::()); + quoter.last(last, child.is::()); } spans.push(segment.len(), child.span()); @@ -673,9 +666,10 @@ fn prepare<'a>( styles: StyleChain<'a>, region: Size, ) -> SourceResult> { + let dir = TextElem::dir_in(styles); let bidi = BidiInfo::new( text, - match TextElem::dir_in(styles) { + match dir { Dir::LTR => Some(BidiLevel::ltr()), Dir::RTL => Some(BidiLevel::rtl()), _ => None, @@ -734,7 +728,7 @@ fn prepare<'a>( styles, hyphenate: shared_get(styles, children, TextElem::hyphenate_in), lang: shared_get(styles, children, TextElem::lang_in), - align: AlignElem::alignment_in(styles).x.resolve(styles), + align: AlignElem::alignment_in(styles).resolve(styles).x, justify: ParElem::justify_in(styles), hang: ParElem::hanging_indent_in(styles), }) diff --git a/crates/typst-library/src/layout/place.rs b/crates/typst-library/src/layout/place.rs index 95c042ff4..39a38b16d 100644 --- a/crates/typst-library/src/layout/place.rs +++ b/crates/typst-library/src/layout/place.rs @@ -7,7 +7,7 @@ use crate::prelude::*; /// other content in the container. Page margins will be respected. /// /// -/// ## Example { #example } +/// # Example /// ```example /// #set page(height: 60pt) /// Hello, world! @@ -20,10 +20,7 @@ use crate::prelude::*; /// ), /// ) /// ``` -/// -/// Display: Place -/// Category: layout -#[element(Layout, Behave)] +#[elem(Layout, Behave)] pub struct PlaceElem { /// Relative to which position in the parent container to place the content. /// @@ -34,8 +31,8 @@ pub struct PlaceElem { /// that axis will be ignored, instead, the item will be placed in the /// origin of the axis. #[positional] - #[default(Smart::Custom(Axes::with_x(Some(GenAlign::Start))))] - pub alignment: Smart>>, + #[default(Smart::Custom(Align::START))] + pub alignment: Smart, /// Whether the placed element has floating layout. /// @@ -98,16 +95,7 @@ impl Layout for PlaceElem { let float = self.float(styles); let alignment = self.alignment(styles); - if float - && !matches!( - alignment, - Smart::Auto - | Smart::Custom(Axes { - y: Some(GenAlign::Specific(Align::Top | Align::Bottom)), - .. - }) - ) - { + if float && alignment.map_or(false, |align| align.y() == Some(VAlign::Horizon)) { bail!(self.span(), "floating placement must be `auto`, `top`, or `bottom`"); } else if !float && alignment.is_auto() { return Err("automatic positioning is only available for floating placement") @@ -115,9 +103,7 @@ impl Layout for PlaceElem { .at(self.span()); } - let child = self.body().aligned( - alignment.unwrap_or_else(|| Axes::with_x(Some(Align::Center.into()))), - ); + let child = self.body().aligned(alignment.unwrap_or_else(|| Align::CENTER)); let pod = Regions::one(base, Axes::splat(false)); let frame = child.layout(vt, styles, pod)?.into_frame(); diff --git a/crates/typst-library/src/layout/repeat.rs b/crates/typst-library/src/layout/repeat.rs index 646eb991c..41dede517 100644 --- a/crates/typst-library/src/layout/repeat.rs +++ b/crates/typst-library/src/layout/repeat.rs @@ -12,7 +12,7 @@ use super::AlignElem; /// Errors if there no bounds on the available space, as it would create /// infinite content. /// -/// ## Example { #example } +/// # Example /// ```example /// Sign on the dotted line: /// #box(width: 1fr, repeat[.]) @@ -23,10 +23,7 @@ use super::AlignElem; /// Berlin, the 22nd of December, 2022 /// ] /// ``` -/// -/// Display: Repeat -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct RepeatElem { /// The content to repeat. #[required] @@ -43,7 +40,7 @@ impl Layout for RepeatElem { ) -> SourceResult { let pod = Regions::one(regions.size, Axes::new(false, false)); let piece = self.body().layout(vt, styles, pod)?.into_frame(); - let align = AlignElem::alignment_in(styles).x.resolve(styles); + let align = AlignElem::alignment_in(styles).resolve(styles); let fill = regions.size.x; let width = piece.width(); @@ -64,7 +61,7 @@ impl Layout for RepeatElem { let mut offset = Abs::zero(); if count == 1.0 { - offset += align.position(remaining); + offset += align.x.position(remaining); } if width > Abs::zero() { diff --git a/crates/typst-library/src/layout/spacing.rs b/crates/typst-library/src/layout/spacing.rs index 69a5d952a..868b3d504 100644 --- a/crates/typst-library/src/layout/spacing.rs +++ b/crates/typst-library/src/layout/spacing.rs @@ -8,20 +8,17 @@ use crate::prelude::*; /// remaining space on the line is distributed among all fractional spacings /// according to their relative fractions. /// -/// ## Example { #example } +/// # Example /// ```example /// First #h(1cm) Second \ /// First #h(30%) Second \ /// First #h(2fr) Second #h(1fr) Third /// ``` /// -/// ## Mathematical Spacing { #math-spacing } +/// # Mathematical Spacing { #math-spacing } /// In [mathematical formulas]($category/math), you can additionally use these /// constants to add spacing between elements: `thin`, `med`, `thick`, `quad`. -/// -/// Display: Spacing (H) -/// Category: layout -#[element(Behave)] +#[elem(title = "Spacing (H)", Behave)] pub struct HElem { /// How much spacing to insert. #[required] @@ -79,7 +76,7 @@ impl Behave for HElem { /// the remaining space on the page is distributed among all fractional spacings /// according to their relative fractions. /// -/// ## Example { #example } +/// # Example /// ```example /// #grid( /// rows: 3cm, @@ -93,10 +90,7 @@ impl Behave for HElem { /// [A #v(1fr) B], /// ) /// ``` -/// -/// Display: Spacing (V) -/// Category: layout -#[element(Behave)] +#[elem(title = "Spacing (V)", Behave)] pub struct VElem { /// How much spacing to insert. #[required] diff --git a/crates/typst-library/src/layout/stack.rs b/crates/typst-library/src/layout/stack.rs index 52a2f289d..d3fcba8d2 100644 --- a/crates/typst-library/src/layout/stack.rs +++ b/crates/typst-library/src/layout/stack.rs @@ -6,7 +6,7 @@ use crate::prelude::*; /// The stack places a list of items along an axis, with optional spacing /// between each item. /// -/// ## Example { #example } +/// # Example /// ```example /// #stack( /// dir: ttb, @@ -15,10 +15,7 @@ use crate::prelude::*; /// rect(width: 90pt), /// ) /// ``` -/// -/// Display: Stack -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct StackElem { /// The direction along which the items are stacked. Possible values are: /// @@ -27,7 +24,7 @@ pub struct StackElem { /// - `{ttb}`: Top to bottom. /// - `{btt}`: Bottom to top. /// - /// You cab use the `start` and `end` methods to obtain the initial and + /// You can use the `start` and `end` methods to obtain the initial and /// final points (respectively) of a direction, as `alignment`. You can also /// use the `axis` method to determine whether a direction is /// `{"horizontal"}` or `{"vertical"}`. The `inv` method returns a @@ -141,7 +138,7 @@ enum StackItem { /// Fractional spacing between other items. Fractional(Fr), /// A frame for a layouted block. - Frame(Frame, Axes), + Frame(Frame, Axes), } impl<'a> StackLayouter<'a> { @@ -204,7 +201,7 @@ impl<'a> StackLayouter<'a> { } // Block-axis alignment of the `AlignElement` is respected by stacks. - let aligns = if let Some(align) = block.to::() { + let align = if let Some(align) = block.to::() { align.alignment(styles) } else if let Some((_, local)) = block.to_styled() { AlignElem::alignment_in(styles.chain(local)) @@ -230,7 +227,7 @@ impl<'a> StackLayouter<'a> { self.used.main += gen.main; self.used.cross.set_max(gen.cross); - self.items.push(StackItem::Frame(frame, aligns)); + self.items.push(StackItem::Frame(frame, align)); if i + 1 < len { self.finish_region(); @@ -259,18 +256,18 @@ impl<'a> StackLayouter<'a> { let mut output = Frame::new(size); let mut cursor = Abs::zero(); - let mut ruler: Align = self.dir.start().into(); + let mut ruler: FixedAlign = self.dir.start().into(); // Place all frames. for item in self.items.drain(..) { match item { StackItem::Absolute(v) => cursor += v, StackItem::Fractional(v) => cursor += v.share(self.fr, remaining), - StackItem::Frame(frame, aligns) => { + StackItem::Frame(frame, align) => { if self.dir.is_positive() { - ruler = ruler.max(aligns.get(self.axis)); + ruler = ruler.max(align.get(self.axis)); } else { - ruler = ruler.min(aligns.get(self.axis)); + ruler = ruler.min(align.get(self.axis)); } // Align along the main axis. @@ -285,7 +282,7 @@ impl<'a> StackLayouter<'a> { // Align along the cross axis. let other = self.axis.other(); - let cross = aligns + let cross = align .get(other) .position(size.get(other) - frame.size().get(other)); diff --git a/crates/typst-library/src/layout/table.rs b/crates/typst-library/src/layout/table.rs index a7bc8a0ec..4bbe79a66 100644 --- a/crates/typst-library/src/layout/table.rs +++ b/crates/typst-library/src/layout/table.rs @@ -9,13 +9,13 @@ use crate::prelude::*; /// Tables are used to arrange content in cells. Cells can contain arbitrary /// content, including multiple paragraphs and are specified in row-major order. /// Because tables are just grids with configurable cell properties, refer to -/// the [grid documentation]($func/grid) for more information on how to size the +/// the [grid documentation]($grid) for more information on how to size the /// table tracks. /// -/// To give a table a caption and make it [referenceable]($func/ref), put it -/// into a [figure]($func/figure). +/// To give a table a caption and make it [referenceable]($ref), put it into a +/// [figure]($figure). /// -/// ## Example { #example } +/// # Example /// ```example /// #table( /// columns: (1fr, auto, auto), @@ -34,34 +34,31 @@ use crate::prelude::*; /// [$a$: edge length] /// ) /// ``` -/// -/// Display: Table -/// Category: layout -#[element(Layout, LocalName, Figurable)] +#[elem(Layout, LocalName, Figurable)] pub struct TableElem { - /// The column sizes. See the [grid documentation]($func/grid) for more + /// The column sizes. See the [grid documentation]($grid) for more /// information on track sizing. pub columns: TrackSizings, - /// The row sizes. See the [grid documentation]($func/grid) for more - /// information on track sizing. + /// The row sizes. See the [grid documentation]($grid) for more information + /// on track sizing. pub rows: TrackSizings, - /// The gaps between rows & columns. See the [grid - /// documentation]($func/grid) for more information on gutters. + /// The gaps between rows & columns. See the [grid documentation]($grid) for + /// more information on gutters. #[external] pub gutter: TrackSizings, - /// The gaps between columns. Takes precedence over `gutter`. See the [grid - /// documentation]($func/grid) for more information on gutters. + /// The gaps between columns. Takes precedence over `gutter`. See the + /// [grid documentation]($grid) for more information on gutters. #[parse( let gutter = args.named("gutter")?; args.named("column-gutter")?.or_else(|| gutter.clone()) )] pub column_gutter: TrackSizings, - /// The gaps between rows. Takes precedence over `gutter`. See the [grid - /// documentation]($func/grid) for more information on gutters. + /// The gaps between rows. Takes precedence over `gutter`. See the + /// [grid documentation]($grid) for more information on gutters. #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] pub row_gutter: TrackSizings, @@ -102,20 +99,19 @@ pub struct TableElem { /// [A], [B], [C], /// ) /// ``` - pub align: Celled>>>, + pub align: Celled>, - /// How to stroke the cells. + /// How to [stroke]($stroke) the cells. /// - /// See the [line's documentation]($func/line.stroke) for more details. /// Strokes can be disabled by setting this to `{none}`. /// /// _Note:_ Richer stroke customization for individual cells is not yet - /// implemented, but will be in the future. In the meantime, you can use - /// the third-party [tablex library](https://github.com/PgBiel/typst-tablex/). + /// implemented, but will be in the future. In the meantime, you can use the + /// third-party [tablex library](https://github.com/PgBiel/typst-tablex/). #[resolve] #[fold] - #[default(Some(PartialStroke::default()))] - pub stroke: Option, + #[default(Some(Stroke::default()))] + pub stroke: Option, /// How much to pad the cells' content. #[default(Abs::pt(5.0).into())] @@ -158,7 +154,7 @@ impl Layout for TableElem { .collect::>()?; let fill = self.fill(styles); - let stroke = self.stroke(styles).map(PartialStroke::unwrap_or_default); + let stroke = self.stroke(styles).map(Stroke::unwrap_or_default); // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( @@ -268,8 +264,12 @@ impl Default for Celled { } impl Reflect for Celled { - fn describe() -> CastInfo { - T::describe() + Array::describe() + Func::describe() + fn input() -> CastInfo { + T::input() + Array::input() + Func::input() + } + + fn output() -> CastInfo { + T::output() + Array::output() + Func::output() } fn castable(value: &Value) -> bool { diff --git a/crates/typst-library/src/layout/terms.rs b/crates/typst-library/src/layout/terms.rs index d693f100c..07f17bb0f 100644 --- a/crates/typst-library/src/layout/terms.rs +++ b/crates/typst-library/src/layout/terms.rs @@ -8,29 +8,22 @@ use crate::prelude::*; /// descriptions span over multiple lines, they use hanging indent to /// communicate the visual hierarchy. /// -/// ## Example { #example } +/// # Example /// ```example /// / Ligature: A merged glyph. /// / Kerning: A spacing adjustment /// between two adjacent letters. /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// This function also has dedicated syntax: Starting a line with a slash, /// followed by a term, a colon and a description creates a term list item. -/// -/// Display: Term List -/// Category: layout -#[element(Layout)] -#[scope( - scope.define("item", TermItem::func()); - scope -)] +#[elem(scope, title = "Term List", Layout)] pub struct TermsElem { - /// If this is `{false}`, the items are spaced apart with [term list - /// spacing]($func/terms.spacing). If it is `{true}`, they use normal - /// [leading]($func/par.leading) instead. This makes the term list more - /// compact, which can look better if the items are short. + /// If this is `{false}`, the items are spaced apart with + /// [term list spacing]($terms.spacing). If it is `{true}`, they use normal + /// [leading]($par.leading) instead. This makes the term list more compact, + /// which can look better if the items are short. /// /// In markup mode, the value of this parameter is determined based on /// whether items are separated with a blank line. If items directly follow @@ -81,7 +74,7 @@ pub struct TermsElem { /// The spacing between the items of a wide (non-tight) term list. /// - /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). + /// If set to `{auto}`, uses the spacing [below blocks]($block.below). pub spacing: Smart, /// The term list's children. @@ -100,6 +93,12 @@ pub struct TermsElem { pub children: Vec, } +#[scope] +impl TermsElem { + #[elem] + type TermItem; +} + impl Layout for TermsElem { #[tracing::instrument(name = "TermsElem::layout", skip_all)] fn layout( @@ -138,10 +137,7 @@ impl Layout for TermsElem { } /// A term list item. -/// -/// Display: Term List Item -/// Category: layout -#[element] +#[elem(name = "item", title = "Term List Item")] pub struct TermItem { /// The term described by the list item. #[required] diff --git a/crates/typst-library/src/layout/transform.rs b/crates/typst-library/src/layout/transform.rs index a57a5edc7..012a146d0 100644 --- a/crates/typst-library/src/layout/transform.rs +++ b/crates/typst-library/src/layout/transform.rs @@ -8,7 +8,7 @@ use crate::prelude::*; /// it at the original positions. Containers will still be sized as if the /// content was not moved. /// -/// ## Example { #example } +/// # Example /// ```example /// #rect(inset: 0pt, move( /// dx: 6pt, dy: 6pt, @@ -20,10 +20,7 @@ use crate::prelude::*; /// ) /// )) /// ``` -/// -/// Display: Move -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct MoveElem { /// The horizontal displacement of the content. pub dx: Rel, @@ -47,7 +44,7 @@ impl Layout for MoveElem { let pod = Regions::one(regions.base(), Axes::splat(false)); let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); let delta = Axes::new(self.dx(styles), self.dy(styles)).resolve(styles); - let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s)); + let delta = delta.zip_map(regions.base(), Rel::relative_to); frame.translate(delta.to_point()); Ok(Fragment::frame(frame)) } @@ -58,7 +55,7 @@ impl Layout for MoveElem { /// Rotates an element by a given angle. The layout will act as if the element /// was not rotated. /// -/// ## Example { #example } +/// # Example /// ```example /// #stack( /// dir: ltr, @@ -67,10 +64,7 @@ impl Layout for MoveElem { /// .map(i => rotate(24deg * i)[X]), /// ) /// ``` -/// -/// Display: Rotate -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct RotateElem { /// The amount of rotation. /// @@ -96,10 +90,9 @@ pub struct RotateElem { /// #box(rotate(30deg, origin: top + left, square())) /// #box(rotate(30deg, origin: bottom + right, square())) /// ``` - #[resolve] #[fold] - #[default(Align::CENTER_HORIZON)] - pub origin: Axes>, + #[default(HAlign::Center + VAlign::Horizon)] + pub origin: Align, /// The content to rotate. #[required] @@ -116,8 +109,10 @@ impl Layout for RotateElem { ) -> SourceResult { let pod = Regions::one(regions.base(), Axes::splat(false)); let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let Axes { x, y } = - self.origin(styles).zip(frame.size()).map(|(o, s)| o.position(s)); + let Axes { x, y } = self + .origin(styles) + .resolve(styles) + .zip_map(frame.size(), FixedAlign::position); let ts = Transform::translate(x, y) .pre_concat(Transform::rotate(self.angle(styles))) .pre_concat(Transform::translate(-x, -y)); @@ -130,15 +125,12 @@ impl Layout for RotateElem { /// /// Lets you mirror content by specifying a negative scale on a single axis. /// -/// ## Example { #example } +/// # Example /// ```example /// #set align(center) /// #scale(x: -100%)[This is mirrored.] /// ``` -/// -/// Display: Scale -/// Category: layout -#[element(Layout)] +#[elem(Layout)] pub struct ScaleElem { /// The horizontal scaling factor. /// @@ -163,10 +155,9 @@ pub struct ScaleElem { /// A#box(scale(75%)[A])A \ /// B#box(scale(75%, origin: bottom + left)[B])B /// ``` - #[resolve] #[fold] - #[default(Align::CENTER_HORIZON)] - pub origin: Axes>, + #[default(HAlign::Center + VAlign::Horizon)] + pub origin: Align, /// The content to scale. #[required] @@ -183,8 +174,10 @@ impl Layout for ScaleElem { ) -> SourceResult { let pod = Regions::one(regions.base(), Axes::splat(false)); let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let Axes { x, y } = - self.origin(styles).zip(frame.size()).map(|(o, s)| o.position(s)); + let Axes { x, y } = self + .origin(styles) + .resolve(styles) + .zip_map(frame.size(), FixedAlign::position); let transform = Transform::translate(x, y) .pre_concat(Transform::scale(self.x(styles), self.y(styles))) .pre_concat(Transform::translate(-x, -y)); diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index e9bb72ced..bdb97f844 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -14,10 +14,9 @@ pub mod symbols; pub mod text; pub mod visualize; -use typst::diag::At; -use typst::eval::{LangItems, Library, Module, Scope}; -use typst::geom::Smart; -use typst::model::{Element, Styles}; +use typst::eval::{Array, LangItems, Library, Module, Scope}; +use typst::geom::{Align, Color, Dir, Smart}; +use typst::model::{NativeElement, Styles}; use self::layout::LayoutRoot; @@ -32,17 +31,53 @@ pub fn build() -> Library { #[tracing::instrument(skip_all)] fn global(math: Module) -> Module { let mut global = Scope::deduplicating(); - - // Categories. text::define(&mut global); + global.define_module(math); layout::define(&mut global); visualize::define(&mut global); meta::define(&mut global); - compute::define(&mut global); symbols::define(&mut global); - global.define("math", math); + compute::define(&mut global); + prelude(&mut global); + Module::new("global", global) +} - Module::new("global").with_scope(global) +/// Defines scoped values that are globally available, too. +fn prelude(global: &mut Scope) { + global.define("black", Color::BLACK); + global.define("gray", Color::GRAY); + global.define("silver", Color::SILVER); + global.define("white", Color::WHITE); + global.define("navy", Color::NAVY); + global.define("blue", Color::BLUE); + global.define("aqua", Color::AQUA); + global.define("teal", Color::TEAL); + global.define("eastern", Color::EASTERN); + global.define("purple", Color::PURPLE); + global.define("fuchsia", Color::FUCHSIA); + global.define("maroon", Color::MAROON); + global.define("red", Color::RED); + global.define("orange", Color::ORANGE); + global.define("yellow", Color::YELLOW); + global.define("olive", Color::OLIVE); + global.define("green", Color::GREEN); + global.define("lime", Color::LIME); + global.define("luma", Color::luma_data()); + global.define("rgb", Color::rgb_data()); + global.define("cmyk", Color::cmyk_data()); + global.define("range", Array::range_data()); + global.define("ltr", Dir::LTR); + global.define("rtl", Dir::RTL); + global.define("ttb", Dir::TTB); + global.define("btt", Dir::BTT); + global.define("start", Align::START); + global.define("left", Align::LEFT); + global.define("center", Align::CENTER); + global.define("right", Align::RIGHT); + global.define("end", Align::END); + global.define("top", Align::TOP); + global.define("horizon", Align::HORIZON); + global.define("bottom", Align::BOTTOM); } /// Construct the standard style map. @@ -59,9 +94,9 @@ fn items() -> LangItems { space: || text::SpaceElem::new().pack(), linebreak: || text::LinebreakElem::new().pack(), text: |text| text::TextElem::new(text).pack(), - text_func: text::TextElem::func(), + text_elem: text::TextElem::elem(), text_str: |content| Some(content.to::()?.text()), - smart_quote: |double| text::SmartQuoteElem::new().with_double(double).pack(), + smart_quote: |double| text::SmartquoteElem::new().with_double(double).pack(), parbreak: || layout::ParbreakElem::new().pack(), strong: |body| text::StrongElem::new(body).pack(), emph: |body| text::EmphElem::new(body).pack(), @@ -85,7 +120,7 @@ fn items() -> LangItems { }, bibliography_keys: meta::BibliographyElem::keys, heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(), - heading_func: meta::HeadingElem::func(), + heading_elem: meta::HeadingElem::elem(), list_item: |body| layout::ListItem::new(body).pack(), enum_item: |number, body| { let mut elem = layout::EnumItem::new(body); @@ -95,9 +130,6 @@ fn items() -> LangItems { elem.pack() }, term_item: |term, description| layout::TermItem::new(term, description).pack(), - rgb_func: compute::rgb_func(), - cmyk_func: compute::cmyk_func(), - luma_func: compute::luma_func(), equation: |body, block| math::EquationElem::new(body).with_block(block).pack(), math_align_point: || math::AlignPointElem::new().pack(), math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(), @@ -131,15 +163,5 @@ fn items() -> LangItems { math_root: |index, radicand| { math::RootElem::new(radicand).with_index(index).pack() }, - library_method: |vm, dynamic, method, args, span| { - if let Some(counter) = dynamic.downcast::().cloned() { - counter.call_method(vm, method, args, span) - } else if let Some(state) = dynamic.downcast::().cloned() { - state.call_method(vm, method, args, span) - } else { - Err(format!("type {} has no method `{method}`", dynamic.type_name())) - .at(span) - } - }, } } diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs index d1bee1987..c92f9585f 100644 --- a/crates/typst-library/src/math/accent.rs +++ b/crates/typst-library/src/math/accent.rs @@ -5,16 +5,13 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5); /// Attaches an accent to a base. /// -/// ## Example { #example } +/// # Example /// ```example /// $grave(a) = accent(a, `)$ \ /// $arrow(a) = accent(a, arrow)$ \ /// $tilde(a) = accent(a, \u{0303})$ /// ``` -/// -/// Display: Accent -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct AccentElem { /// The base to which the accent is applied. /// May consist of multiple letters. diff --git a/crates/typst-library/src/math/align.rs b/crates/typst-library/src/math/align.rs index aee89a893..bf81597c0 100644 --- a/crates/typst-library/src/math/align.rs +++ b/crates/typst-library/src/math/align.rs @@ -1,10 +1,7 @@ use super::*; /// A math alignment point: `&`, `&&`. -/// -/// Display: Alignment Point -/// Category: math -#[element(LayoutMath)] +#[elem(title = "Alignment Point", LayoutMath)] pub struct AlignPointElem {} impl LayoutMath for AlignPointElem { diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs index d74beafe9..c33b58e40 100644 --- a/crates/typst-library/src/math/attach.rs +++ b/crates/typst-library/src/math/attach.rs @@ -2,26 +2,13 @@ use super::*; /// A base with optional attachments. /// -/// ## Example { #example } /// ```example -/// // With syntax. -/// $ sum_(i=0)^n a_i = 2^(1+i) $ -/// -/// // With function call. /// $ attach( /// Pi, t: alpha, b: beta, /// tl: 1, tr: 2+3, bl: 4+5, br: 6, /// ) $ /// ``` -/// -/// ## Syntax { #syntax } -/// This function also has dedicated syntax for attachments after the base: Use -/// the underscore (`_`) to indicate a subscript i.e. bottom attachment and the -/// hat (`^`) to indicate a superscript i.e. top attachment. -/// -/// Display: Attachment -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct AttachElem { /// The base to which things are attached. #[required] @@ -86,19 +73,15 @@ impl LayoutMath for AttachElem { /// Grouped primes. /// -/// ## Example { #example } /// ```example /// $ a'''_b = a^'''_b $ /// ``` /// -/// ## Syntax +/// # Syntax /// This function has dedicated syntax: use apostrophes instead of primes. They /// will automatically attach to the previous element, moving superscripts to /// the next level. -/// -/// Display: Attachment -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct PrimesElem { /// The number of grouped primes. #[required] @@ -141,14 +124,10 @@ impl LayoutMath for PrimesElem { /// Forces a base to display attachments as scripts. /// -/// ## Example { #example } /// ```example /// $ scripts(sum)_1^2 != sum_1^2 $ /// ``` -/// -/// Display: Scripts -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct ScriptsElem { /// The base to attach the scripts to. #[required] @@ -167,14 +146,10 @@ impl LayoutMath for ScriptsElem { /// Forces a base to display attachments as limits. /// -/// ## Example { #example } /// ```example /// $ limits(A)_1^2 != A_1^2 $ /// ``` -/// -/// Display: Limits -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct LimitsElem { /// The base to attach the limits to. #[required] diff --git a/crates/typst-library/src/math/cancel.rs b/crates/typst-library/src/math/cancel.rs index f576a727c..d27031b9b 100644 --- a/crates/typst-library/src/math/cancel.rs +++ b/crates/typst-library/src/math/cancel.rs @@ -4,17 +4,14 @@ use super::*; /// /// This is commonly used to show the elimination of a term. /// -/// ## Example { #example } +/// # Example /// ```example /// >>> #set page(width: 140pt) /// Here, we can simplify: /// $ (a dot b dot cancel(x)) / /// cancel(x) $ /// ``` -/// -/// Display: Cancel -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct CancelElem { /// The content over which the line should be placed. #[required] @@ -53,8 +50,8 @@ pub struct CancelElem { #[default(false)] pub cross: bool, - /// How to rotate the cancel line. See the [line's - /// documentation]($func/line.angle) for more details. + /// How to rotate the cancel line. See the + /// [line's documentation]($line.angle) for more details. /// /// ```example /// >>> #set page(width: 140pt) @@ -63,8 +60,7 @@ pub struct CancelElem { #[default(Angle::zero())] pub rotation: Angle, - /// How to stroke the cancel line. See the - /// [line's documentation]($func/line.stroke) for more details. + /// How to [stroke]($stroke) the cancel line. /// /// ```example /// >>> #set page(width: 140pt) @@ -79,12 +75,12 @@ pub struct CancelElem { /// ``` #[resolve] #[fold] - #[default(PartialStroke { + #[default(Stroke { // Default stroke has 0.5pt for better visuals. thickness: Smart::Custom(Abs::pt(0.5)), ..Default::default() })] - pub stroke: PartialStroke, + pub stroke: Stroke, } impl LayoutMath for CancelElem { @@ -99,7 +95,7 @@ impl LayoutMath for CancelElem { let span = self.span(); let length = self.length(styles).resolve(styles); - let stroke = self.stroke(styles).unwrap_or(Stroke { + let stroke = self.stroke(styles).unwrap_or(FixedStroke { paint: TextElem::fill_in(styles), ..Default::default() }); @@ -139,7 +135,7 @@ impl LayoutMath for CancelElem { /// Draws a cancel line. fn draw_cancel_line( length: Rel, - stroke: Stroke, + stroke: FixedStroke, invert: bool, angle: Angle, body_size: Size, @@ -172,8 +168,8 @@ fn draw_cancel_line( // (-width / 2, height / 2) with length components (width, -height) (sign is // inverted in the y-axis). After applying the scale, the line will have the // correct length and orientation (inverted if needed). - let start = Axes::new(-mid.x, mid.y).zip(scales).map(|(l, s)| l * s); - let delta = Axes::new(width, -height).zip(scales).map(|(l, s)| l * s); + let start = Axes::new(-mid.x, mid.y).zip_map(scales, |l, s| l * s); + let delta = Axes::new(width, -height).zip_map(scales, |l, s| l * s); let mut frame = Frame::new(body_size); frame.push( diff --git a/crates/typst-library/src/math/class.rs b/crates/typst-library/src/math/class.rs index 69635c629..fc8a6c798 100644 --- a/crates/typst-library/src/math/class.rs +++ b/crates/typst-library/src/math/class.rs @@ -5,7 +5,7 @@ use super::*; /// This is useful to treat certain symbols as if they were of a different /// class, e.g. to make a symbol behave like a relation. /// -/// ## Example { #example } +/// # Example /// ```example /// #let loves = math.class( /// "relation", @@ -14,10 +14,7 @@ use super::*; /// /// $x loves y and y loves 5$ /// ``` -/// -/// Display: Class -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct ClassElem { /// The class to apply to the content. #[required] diff --git a/crates/typst-library/src/math/frac.rs b/crates/typst-library/src/math/frac.rs index cf1d38e9e..6a2962032 100644 --- a/crates/typst-library/src/math/frac.rs +++ b/crates/typst-library/src/math/frac.rs @@ -4,21 +4,18 @@ const FRAC_AROUND: Em = Em::new(0.1); /// A mathematical fraction. /// -/// ## Example { #example } +/// # Example /// ```example /// $ 1/2 < (x+1)/2 $ /// $ ((x+1)) / 2 = frac(a, b) $ /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// This function also has dedicated syntax: Use a slash to turn neighbouring /// expressions into a fraction. Multiple atoms can be grouped into a single /// expression using round grouping parenthesis. Such parentheses are removed /// from the output, but you can nest multiple to force them. -/// -/// Display: Fraction -/// Category: math -#[element(LayoutMath)] +#[elem(title = "Fraction", LayoutMath)] pub struct FracElem { /// The fraction's numerator. #[required] @@ -38,14 +35,11 @@ impl LayoutMath for FracElem { /// A binomial expression. /// -/// ## Example { #example } +/// # Example /// ```example /// $ binom(n, k) $ /// ``` -/// -/// Display: Binomial -/// Category: math -#[element(LayoutMath)] +#[elem(title = "Binomial", LayoutMath)] pub struct BinomElem { /// The binomial's upper index. #[required] @@ -135,10 +129,10 @@ fn layout( frame.push( line_pos, FrameItem::Shape( - Geometry::Line(Point::with_x(line_width)).stroked(Stroke { + Geometry::Line(Point::with_x(line_width)).stroked(FixedStroke { paint: TextElem::fill_in(ctx.styles()), thickness, - ..Stroke::default() + ..FixedStroke::default() }), span, ), diff --git a/crates/typst-library/src/math/delimited.rs b/crates/typst-library/src/math/lr.rs similarity index 88% rename from crates/typst-library/src/math/delimited.rs rename to crates/typst-library/src/math/lr.rs index 25ecf623e..0d3c855e4 100644 --- a/crates/typst-library/src/math/delimited.rs +++ b/crates/typst-library/src/math/lr.rs @@ -7,16 +7,7 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); /// /// While matched delimiters scale by default, this can be used to scale /// unmatched delimiters and to control the delimiter scaling more precisely. -/// -/// ## Example { #example } -/// ```example -/// $ lr(]a, b/2]) $ -/// $ lr(]sum_(x=1)^n] x, size: #50%) $ -/// ``` -/// -/// Display: Left/Right -/// Category: math -#[element(LayoutMath)] +#[elem(title = "Left/Right", LayoutMath)] pub struct LrElem { /// The size of the brackets, relative to the height of the wrapped content. pub size: Smart>, @@ -107,13 +98,9 @@ fn scale( /// Floors an expression. /// -/// ## Example { #example } /// ```example /// $ floor(x/2) $ /// ``` -/// -/// Display: Floor -/// Category: math #[func] pub fn floor( /// The expression to floor. @@ -124,13 +111,9 @@ pub fn floor( /// Ceils an expression. /// -/// ## Example { #example } /// ```example /// $ ceil(x/2) $ /// ``` -/// -/// Display: Ceil -/// Category: math #[func] pub fn ceil( /// The expression to ceil. @@ -141,13 +124,9 @@ pub fn ceil( /// Rounds an expression. /// -/// ## Example { #example } /// ```example /// $ round(x/2) $ /// ``` -/// -/// Display: Round -/// Category: math #[func] pub fn round( /// The expression to round. @@ -158,14 +137,9 @@ pub fn round( /// Takes the absolute value of an expression. /// -/// ## Example { #example } /// ```example /// $ abs(x/2) $ /// ``` -/// -/// -/// Display: Abs -/// Category: math #[func] pub fn abs( /// The expression to take the absolute value of. @@ -176,13 +150,9 @@ pub fn abs( /// Takes the norm of an expression. /// -/// ## Example { #example } /// ```example /// $ norm(x/2) $ /// ``` -/// -/// Display: Norm -/// Category: math #[func] pub fn norm( /// The expression to take the norm of. diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs index c913592d5..abb0da353 100644 --- a/crates/typst-library/src/math/matrix.rs +++ b/crates/typst-library/src/math/matrix.rs @@ -12,15 +12,12 @@ const DEFAULT_STROKE_THICKNESS: Em = Em::new(0.05); /// /// Content in the vector's elements can be aligned with the `&` symbol. /// -/// ## Example { #example } +/// # Example /// ```example /// $ vec(a, b, c) dot vec(1, 2, 3) /// = a + 2b + 3c $ /// ``` -/// -/// Display: Vector -/// Category: math -#[element(LayoutMath)] +#[elem(title = "Vector", LayoutMath)] pub struct VecElem { /// The delimiter to use. /// @@ -40,7 +37,7 @@ impl LayoutMath for VecElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let delim = self.delim(ctx.styles()); - let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; + let frame = layout_vec_body(ctx, &self.children(), FixedAlign::Center)?; layout_delimiters( ctx, frame, @@ -61,7 +58,7 @@ impl LayoutMath for VecElem { /// /// Content in cells that are in the same row can be aligned with the `&` symbol. /// -/// ## Example { #example } +/// # Example /// ```example /// $ mat( /// 1, 2, ..., 10; @@ -70,10 +67,7 @@ impl LayoutMath for VecElem { /// 10, 10, ..., 10; /// ) $ /// ``` -/// -/// Display: Matrix -/// Category: math -#[element(LayoutMath)] +#[elem(title = "Matrix", LayoutMath)] pub struct MatElem { /// The delimiter to use. /// @@ -102,10 +96,8 @@ pub struct MatElem { /// drawn after the second column of the matrix. Accepts either an /// integer for a single line, or an array of integers /// for multiple lines. - /// - `stroke`: How to stroke the line. See the - /// [line's documentation]($func/line.stroke) - /// for more details. If set to `{auto}`, takes on a thickness of - /// 0.05em and square line caps. + /// - `stroke`: How to [stroke]($stroke) the line. If set to `{auto}`, + /// takes on a thickness of 0.05em and square line caps. /// /// ```example /// $ mat(1, 0, 1; 0, 1, 2; augment: #2) $ @@ -204,7 +196,7 @@ impl LayoutMath for MatElem { /// /// Content across different branches can be aligned with the `&` symbol. /// -/// ## Example { #example } +/// # Example /// ```example /// $ f(x, y) := cases( /// 1 "if" (x dot y)/2 <= 0, @@ -213,10 +205,7 @@ impl LayoutMath for MatElem { /// 4 "else", /// ) $ /// ``` -/// -/// Display: Cases -/// Category: math -#[element(LayoutMath)] +#[elem(LayoutMath)] pub struct CasesElem { /// The delimiter to use. /// @@ -236,7 +225,7 @@ impl LayoutMath for CasesElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let delim = self.delim(ctx.styles()); - let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; + let frame = layout_vec_body(ctx, &self.children(), FixedAlign::Start)?; layout_delimiters(ctx, frame, Some(delim.open()), None, self.span()) } } @@ -289,7 +278,7 @@ impl Delimiter { fn layout_vec_body( ctx: &mut MathContext, column: &[Content], - align: Align, + align: FixedAlign, ) -> SourceResult { let gap = ROW_GAP.scaled(ctx); ctx.style(ctx.style.for_denominator()); @@ -319,7 +308,7 @@ fn layout_mat_body( // look correct by default at all matrix sizes. // The line cap is also set to square because it looks more "correct". let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.scaled(ctx); - let default_stroke = Stroke { + let default_stroke = FixedStroke { thickness: default_stroke_thickness, line_cap: LineCap::Square, ..Default::default() @@ -383,7 +372,7 @@ fn layout_mat_body( let mut y = Abs::zero(); for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) { - let cell = cell.into_aligned_frame(ctx, &points, Align::Center); + let cell = cell.into_aligned_frame(ctx, &points, FixedAlign::Center); let pos = Point::new( if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x }, y + ascent - cell.ascent(), @@ -429,7 +418,7 @@ fn layout_mat_body( Ok(frame) } -fn line_item(length: Abs, vertical: bool, stroke: Stroke, span: Span) -> FrameItem { +fn line_item(length: Abs, vertical: bool, stroke: FixedStroke, span: Span) -> FrameItem { let line_geom = if vertical { Geometry::Line(Point::with_y(length)) } else { @@ -482,14 +471,14 @@ fn layout_delimiters( /// Parameters specifying how augmentation lines /// should be drawn on a matrix. #[derive(Default, Clone, Hash)] -pub struct Augment { +pub struct Augment { pub hline: Offsets, pub vline: Offsets, - pub stroke: Smart>, + pub stroke: Smart>, } impl Augment { - fn stroke_or(&self, fallback: Stroke) -> Stroke { + fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke { match &self.stroke { Smart::Custom(v) => v.clone().unwrap_or(fallback), _ => fallback, @@ -543,7 +532,7 @@ cast! { let vline = dict.take("vline").ok().map(Offsets::from_value) .transpose().unwrap_or_default().unwrap_or_default(); - let stroke = dict.take("stroke").ok().map(PartialStroke::from_value) + let stroke = dict.take("stroke").ok().map(Stroke::from_value) .transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto); Augment { hline, vline, stroke } diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 5d32af64b..578064bae 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -7,9 +7,9 @@ mod align; mod attach; mod cancel; mod class; -mod delimited; mod frac; mod fragment; +mod lr; mod matrix; mod op; mod root; @@ -24,8 +24,8 @@ pub use self::align::*; pub use self::attach::*; pub use self::cancel::*; pub use self::class::*; -pub use self::delimited::*; pub use self::frac::*; +pub use self::lr::*; pub use self::matrix::*; pub use self::op::*; pub use self::root::*; @@ -57,79 +57,64 @@ use crate::text::{ /// Create a module with all math definitions. pub fn module() -> Module { let mut math = Scope::deduplicating(); - math.define("equation", EquationElem::func()); - math.define("text", TextElem::func()); + math.category("math"); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::(); + math.define_func::