summaryrefslogtreecommitdiff
path: root/crates/typst-library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-09-11 14:40:22 +0200
committerLaurenz <laurmaedje@gmail.com>2023-09-11 14:40:22 +0200
commitb471ac7d590abd2398ce25193b4e4df373bf2e9c (patch)
treeb5f7a6fdc807ee3340a4f42b0ad3cc563fe45429 /crates/typst-library
parent8f36fca68447a5d42a3d54b5fac7e5546ee244be (diff)
First-class types
Makes types first-class values.
Diffstat (limited to 'crates/typst-library')
-rw-r--r--crates/typst-library/Cargo.toml2
-rw-r--r--crates/typst-library/src/compute/calc.rs295
-rw-r--r--crates/typst-library/src/compute/construct.rs1015
-rw-r--r--crates/typst-library/src/compute/data.rs474
-rw-r--r--crates/typst-library/src/compute/foundations.rs208
-rw-r--r--crates/typst-library/src/compute/mod.rs35
-rw-r--r--crates/typst-library/src/layout/align.rs48
-rw-r--r--crates/typst-library/src/layout/columns.rs18
-rw-r--r--crates/typst-library/src/layout/container.rs68
-rw-r--r--crates/typst-library/src/layout/enum.rs49
-rw-r--r--crates/typst-library/src/layout/flow.rs55
-rw-r--r--crates/typst-library/src/layout/grid.rs7
-rw-r--r--crates/typst-library/src/layout/hide.rs7
-rw-r--r--crates/typst-library/src/layout/list.rs36
-rw-r--r--crates/typst-library/src/layout/measure.rs25
-rw-r--r--crates/typst-library/src/layout/mod.rs79
-rw-r--r--crates/typst-library/src/layout/pad.rs10
-rw-r--r--crates/typst-library/src/layout/page.rs68
-rw-r--r--crates/typst-library/src/layout/par.rs54
-rw-r--r--crates/typst-library/src/layout/place.rs26
-rw-r--r--crates/typst-library/src/layout/repeat.rs11
-rw-r--r--crates/typst-library/src/layout/spacing.rs16
-rw-r--r--crates/typst-library/src/layout/stack.rs25
-rw-r--r--crates/typst-library/src/layout/table.rs54
-rw-r--r--crates/typst-library/src/layout/terms.rs34
-rw-r--r--crates/typst-library/src/layout/transform.rs45
-rw-r--r--crates/typst-library/src/lib.rs72
-rw-r--r--crates/typst-library/src/math/accent.rs7
-rw-r--r--crates/typst-library/src/math/align.rs5
-rw-r--r--crates/typst-library/src/math/attach.rs35
-rw-r--r--crates/typst-library/src/math/cancel.rs26
-rw-r--r--crates/typst-library/src/math/class.rs7
-rw-r--r--crates/typst-library/src/math/frac.rs20
-rw-r--r--crates/typst-library/src/math/lr.rs (renamed from crates/typst-library/src/math/delimited.rs)32
-rw-r--r--crates/typst-library/src/math/matrix.rs47
-rw-r--r--crates/typst-library/src/math/mod.rs130
-rw-r--r--crates/typst-library/src/math/op.rs9
-rw-r--r--crates/typst-library/src/math/root.rs16
-rw-r--r--crates/typst-library/src/math/row.rs71
-rw-r--r--crates/typst-library/src/math/style.rs73
-rw-r--r--crates/typst-library/src/math/underover.rs44
-rw-r--r--crates/typst-library/src/meta/bibliography.rs37
-rw-r--r--crates/typst-library/src/meta/context.rs83
-rw-r--r--crates/typst-library/src/meta/counter.rs373
-rw-r--r--crates/typst-library/src/meta/document.rs5
-rw-r--r--crates/typst-library/src/meta/figure.rs82
-rw-r--r--crates/typst-library/src/meta/footnote.rs46
-rw-r--r--crates/typst-library/src/meta/heading.rs37
-rw-r--r--crates/typst-library/src/meta/link.rs26
-rw-r--r--crates/typst-library/src/meta/metadata.rs15
-rw-r--r--crates/typst-library/src/meta/mod.rs49
-rw-r--r--crates/typst-library/src/meta/numbering.rs9
-rw-r--r--crates/typst-library/src/meta/outline.rs73
-rw-r--r--crates/typst-library/src/meta/query.rs49
-rw-r--r--crates/typst-library/src/meta/reference.rs30
-rw-r--r--crates/typst-library/src/meta/state.rs236
-rw-r--r--crates/typst-library/src/prelude.rs8
-rw-r--r--crates/typst-library/src/shared/ext.rs6
-rw-r--r--crates/typst-library/src/symbols/emoji.rs2
-rw-r--r--crates/typst-library/src/symbols/mod.rs6
-rw-r--r--crates/typst-library/src/symbols/sym.rs2
-rw-r--r--crates/typst-library/src/text/deco.rs66
-rw-r--r--crates/typst-library/src/text/misc.rs68
-rw-r--r--crates/typst-library/src/text/mod.rs68
-rw-r--r--crates/typst-library/src/text/quotes.rs13
-rw-r--r--crates/typst-library/src/text/raw.rs35
-rw-r--r--crates/typst-library/src/text/shift.rs14
-rw-r--r--crates/typst-library/src/visualize/image.rs119
-rw-r--r--crates/typst-library/src/visualize/line.rs52
-rw-r--r--crates/typst-library/src/visualize/mod.rs37
-rw-r--r--crates/typst-library/src/visualize/path.rs39
-rw-r--r--crates/typst-library/src/visualize/polygon.rs176
-rw-r--r--crates/typst-library/src/visualize/shape.rs106
73 files changed, 1748 insertions, 3577 deletions
diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml
index 68ba7308..040a4405 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 83ecac5d..9043e1f1 100644
--- a/crates/typst-library/src/compute/calc.rs
+++ b/crates/typst-library/src/compute/calc.rs
@@ -8,62 +8,65 @@ use typst::eval::{Module, Scope};
use crate::prelude::*;
-/// A module with computational functions.
-pub fn module() -> Module {
+/// Hook up all calculation definitions.
+pub(super) fn define(global: &mut Scope) {
+ global.category("calculate");
+ global.define_module(module());
+}
+
+/// A module with calculation definitions.
+fn module() -> Module {
let mut scope = Scope::new();
- scope.define("abs", abs_func());
- scope.define("pow", pow_func());
- scope.define("exp", exp_func());
- scope.define("sqrt", sqrt_func());
- scope.define("sin", sin_func());
- scope.define("cos", cos_func());
- scope.define("tan", tan_func());
- scope.define("asin", asin_func());
- scope.define("acos", acos_func());
- scope.define("atan", atan_func());
- scope.define("atan2", atan2_func());
- scope.define("sinh", sinh_func());
- scope.define("cosh", cosh_func());
- scope.define("tanh", tanh_func());
- scope.define("log", log_func());
- scope.define("ln", ln_func());
- scope.define("fact", fact_func());
- scope.define("perm", perm_func());
- scope.define("binom", binom_func());
- scope.define("gcd", gcd_func());
- scope.define("lcm", lcm_func());
- scope.define("floor", floor_func());
- scope.define("ceil", ceil_func());
- scope.define("trunc", trunc_func());
- scope.define("fract", fract_func());
- scope.define("round", round_func());
- scope.define("clamp", clamp_func());
- scope.define("min", min_func());
- scope.define("max", max_func());
- scope.define("even", even_func());
- scope.define("odd", odd_func());
- scope.define("rem", rem_func());
- scope.define("quo", quo_func());
+ scope.category("calculate");
+ scope.define_func::<abs>();
+ scope.define_func::<pow>();
+ scope.define_func::<exp>();
+ scope.define_func::<sqrt>();
+ scope.define_func::<sin>();
+ scope.define_func::<cos>();
+ scope.define_func::<tan>();
+ scope.define_func::<asin>();
+ scope.define_func::<acos>();
+ scope.define_func::<atan>();
+ scope.define_func::<atan2>();
+ scope.define_func::<sinh>();
+ scope.define_func::<cosh>();
+ scope.define_func::<tanh>();
+ scope.define_func::<log>();
+ scope.define_func::<ln>();
+ scope.define_func::<fact>();
+ scope.define_func::<perm>();
+ scope.define_func::<binom>();
+ scope.define_func::<gcd>();
+ scope.define_func::<lcm>();
+ scope.define_func::<floor>();
+ scope.define_func::<ceil>();
+ scope.define_func::<trunc>();
+ scope.define_func::<fract>();
+ scope.define_func::<round>();
+ scope.define_func::<clamp>();
+ scope.define_func::<min>();
+ scope.define_func::<max>();
+ scope.define_func::<even>();
+ scope.define_func::<odd>();
+ scope.define_func::<rem>();
+ scope.define_func::<quo>();
scope.define("inf", f64::INFINITY);
scope.define("nan", f64::NAN);
scope.define("pi", std::f64::consts::PI);
scope.define("tau", std::f64::consts::TAU);
scope.define("e", std::f64::consts::E);
- Module::new("calc").with_scope(scope)
+ Module::new("calc", scope)
}
/// Calculates the absolute value of a numeric value.
///
-/// ## Example { #example }
/// ```example
/// #calc.abs(-5) \
/// #calc.abs(5pt - 2cm) \
/// #calc.abs(2fr)
/// ```
-///
-/// Display: Absolute
-/// Category: calculate
-#[func]
+#[func(title = "Absolute")]
pub fn abs(
/// The value whose absolute value to calculate.
value: ToAbs,
@@ -87,21 +90,17 @@ cast! {
/// Raises a value to some exponent.
///
-/// ## Example { #example }
/// ```example
/// #calc.pow(2, 3)
/// ```
-///
-/// Display: Power
-/// Category: calculate
-#[func]
+#[func(title = "Power")]
pub fn pow(
+ /// The callsite span.
+ span: Span,
/// The base of the power.
base: Num,
/// The exponent of the power.
exponent: Spanned<Num>,
- /// The callsite span.
- span: Span,
) -> SourceResult<Num> {
match exponent.v {
_ if exponent.v.float() == 0.0 && base.float() == 0.0 => {
@@ -142,19 +141,15 @@ pub fn pow(
/// Raises a value to some exponent of e.
///
-/// ## Example { #example }
/// ```example
/// #calc.exp(1)
/// ```
-///
-/// Display: Exponential
-/// Category: calculate
-#[func]
+#[func(title = "Exponential")]
pub fn exp(
- /// The exponent of the power.
- exponent: Spanned<Num>,
/// The callsite span.
span: Span,
+ /// The exponent of the power.
+ exponent: Spanned<Num>,
) -> SourceResult<f64> {
match exponent.v {
Num::Int(i) if i32::try_from(i).is_err() => {
@@ -176,15 +171,11 @@ pub fn exp(
/// Extracts the square root of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.sqrt(16) \
/// #calc.sqrt(2.5)
/// ```
-///
-/// Display: Square Root
-/// Category: calculate
-#[func]
+#[func(title = "Square Root")]
pub fn sqrt(
/// The number whose square root to calculate. Must be non-negative.
value: Spanned<Num>,
@@ -200,16 +191,12 @@ pub fn sqrt(
/// When called with an integer or a float, they will be interpreted as
/// radians.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.sin(90deg) == calc.sin(-270deg))
/// #calc.sin(1.5) \
/// #calc.sin(90deg)
/// ```
-///
-/// Display: Sine
-/// Category: calculate
-#[func]
+#[func(title = "Sine")]
pub fn sin(
/// The angle whose sine to calculate.
angle: AngleLike,
@@ -226,16 +213,12 @@ pub fn sin(
/// When called with an integer or a float, they will be interpreted as
/// radians.
///
-/// ## Example { #example }
/// ```example
/// #calc.cos(90deg) \
/// #calc.cos(1.5) \
/// #calc.cos(90deg)
/// ```
-///
-/// Display: Cosine
-/// Category: calculate
-#[func]
+#[func(title = "Cosine")]
pub fn cos(
/// The angle whose cosine to calculate.
angle: AngleLike,
@@ -252,15 +235,11 @@ pub fn cos(
/// When called with an integer or a float, they will be interpreted as
/// radians.
///
-/// ## Example { #example }
/// ```example
/// #calc.tan(1.5) \
/// #calc.tan(90deg)
/// ```
-///
-/// Display: Tangent
-/// Category: calculate
-#[func]
+#[func(title = "Tangent")]
pub fn tan(
/// The angle whose tangent to calculate.
angle: AngleLike,
@@ -274,15 +253,11 @@ pub fn tan(
/// Calculates the arcsine of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.asin(0) \
/// #calc.asin(1)
/// ```
-///
-/// Display: Arcsine
-/// Category: calculate
-#[func]
+#[func(title = "Arcsine")]
pub fn asin(
/// The number whose arcsine to calculate. Must be between -1 and 1.
value: Spanned<Num>,
@@ -296,15 +271,11 @@ pub fn asin(
/// Calculates the arccosine of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.acos(0) \
/// #calc.acos(1)
/// ```
-///
-/// Display: Arccosine
-/// Category: calculate
-#[func]
+#[func(title = "Arccosine")]
pub fn acos(
/// The number whose arcsine to calculate. Must be between -1 and 1.
value: Spanned<Num>,
@@ -318,15 +289,11 @@ pub fn acos(
/// Calculates the arctangent of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.atan(0) \
/// #calc.atan(1)
/// ```
-///
-/// Display: Arctangent
-/// Category: calculate
-#[func]
+#[func(title = "Arctangent")]
pub fn atan(
/// The number whose arctangent to calculate.
value: Num,
@@ -338,15 +305,11 @@ pub fn atan(
///
/// The arguments are `(x, y)`, not `(y, x)`.
///
-/// ## Example { #example }
/// ```example
/// #calc.atan2(1, 1) \
/// #calc.atan2(-2, -3)
/// ```
-///
-/// Display: Four-quadrant Arctangent
-/// Category: calculate
-#[func]
+#[func(title = "Four-quadrant Arctangent")]
pub fn atan2(
/// The X coordinate.
x: Num,
@@ -358,15 +321,11 @@ pub fn atan2(
/// Calculates the hyperbolic sine of a hyperbolic angle.
///
-/// ## Example { #example }
/// ```example
/// #calc.sinh(0) \
/// #calc.sinh(1.5)
/// ```
-///
-/// Display: Hyperbolic sine
-/// Category: calculate
-#[func]
+#[func(title = "Hyperbolic Sine")]
pub fn sinh(
/// The hyperbolic angle whose hyperbolic sine to calculate.
value: f64,
@@ -376,15 +335,11 @@ pub fn sinh(
/// Calculates the hyperbolic cosine of a hyperbolic angle.
///
-/// ## Example { #example }
/// ```example
/// #calc.cosh(0) \
/// #calc.cosh(1.5)
/// ```
-///
-/// Display: Hyperbolic cosine
-/// Category: calculate
-#[func]
+#[func(title = "Hyperbolic Cosine")]
pub fn cosh(
/// The hyperbolic angle whose hyperbolic cosine to calculate.
value: f64,
@@ -394,15 +349,11 @@ pub fn cosh(
/// Calculates the hyperbolic tangent of an hyperbolic angle.
///
-/// ## Example { #example }
/// ```example
/// #calc.tanh(0) \
/// #calc.tanh(1.5)
/// ```
-///
-/// Display: Hyperbolic tangent
-/// Category: calculate
-#[func]
+#[func(title = "Hyperbolic Tangent")]
pub fn tanh(
/// The hyperbolic angle whose hyperbolic tangent to calculate.
value: f64,
@@ -414,23 +365,19 @@ pub fn tanh(
///
/// If the base is not specified, the logarithm is calculated in base 10.
///
-/// ## Example { #example }
/// ```example
/// #calc.log(100)
/// ```
-///
-/// Display: Logarithm
-/// Category: calculate
-#[func]
+#[func(title = "Logarithm")]
pub fn log(
+ /// The callsite span.
+ span: Span,
/// The number whose logarithm to calculate. Must be strictly positive.
value: Spanned<Num>,
/// The base of the logarithm. May not be zero.
#[named]
#[default(Spanned::new(10.0, Span::detached()))]
base: Spanned<f64>,
- /// The callsite span.
- span: Span,
) -> SourceResult<f64> {
let number = value.v.float();
if number <= 0.0 {
@@ -460,19 +407,15 @@ pub fn log(
/// Calculates the natural logarithm of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.ln(calc.e)
/// ```
-///
-/// Display: Natural Logarithm
-/// Category: calculate
-#[func]
+#[func(title = "Natural Logarithm")]
pub fn ln(
- /// The number whose logarithm to calculate. Must be strictly positive.
- value: Spanned<Num>,
/// The callsite span.
span: Span,
+ /// The number whose logarithm to calculate. Must be strictly positive.
+ value: Spanned<Num>,
) -> SourceResult<f64> {
let number = value.v.float();
if number <= 0.0 {
@@ -489,14 +432,10 @@ pub fn ln(
/// Calculates the factorial of a number.
///
-/// ## Example { #example }
/// ```example
/// #calc.fact(5)
/// ```
-///
-/// Display: Factorial
-/// Category: calculate
-#[func]
+#[func(title = "Factorial")]
pub fn fact(
/// The number whose factorial to calculate. Must be non-negative.
number: u64,
@@ -506,14 +445,10 @@ pub fn fact(
/// Calculates a permutation.
///
-/// ## Example { #example }
/// ```example
/// #calc.perm(10, 5)
/// ```
-///
-/// Display: Permutation
-/// Category: calculate
-#[func]
+#[func(title = "Permutation")]
pub fn perm(
/// The base number. Must be non-negative.
base: u64,
@@ -547,14 +482,10 @@ fn fact_impl(start: u64, end: u64) -> Option<i64> {
/// Calculates a binomial coefficient.
///
-/// ## Example { #example }
/// ```example
/// #calc.binom(10, 5)
/// ```
-///
-/// Display: Binomial
-/// Category: calculate
-#[func]
+#[func(title = "Binomial")]
pub fn binom(
/// The upper coefficient. Must be non-negative.
n: u64,
@@ -588,14 +519,10 @@ fn binom_impl(n: u64, k: u64) -> Option<i64> {
/// Calculates the greatest common divisor of two integers.
///
-/// ## Example { #example }
/// ```example
/// #calc.gcd(7, 42)
/// ```
-///
-/// Display: Greatest Common Divisor
-/// Category: calculate
-#[func]
+#[func(title = "Greatest Common Divisor")]
pub fn gcd(
/// The first integer.
a: i64,
@@ -614,14 +541,10 @@ pub fn gcd(
/// Calculates the least common multiple of two integers.
///
-/// ## Example { #example }
/// ```example
/// #calc.lcm(96, 13)
/// ```
-///
-/// Display: Least Common Multiple
-/// Category: calculate
-#[func]
+#[func(title = "Least Common Multiple")]
pub fn lcm(
/// The first integer.
a: i64,
@@ -642,15 +565,11 @@ pub fn lcm(
///
/// If the number is already an integer, it is returned unchanged.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.floor(3.14) == 3)
/// #assert(calc.floor(3) == 3)
/// #calc.floor(500.1)
/// ```
-///
-/// Display: Round down
-/// Category: calculate
#[func]
pub fn floor(
/// The number to round down.
@@ -666,15 +585,11 @@ pub fn floor(
///
/// If the number is already an integer, it is returned unchanged.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.ceil(3.14) == 4)
/// #assert(calc.ceil(3) == 3)
/// #calc.ceil(500.1)
/// ```
-///
-/// Display: Round up
-/// Category: calculate
#[func]
pub fn ceil(
/// The number to round up.
@@ -690,16 +605,12 @@ pub fn ceil(
///
/// If the number is already an integer, it is returned unchanged.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.trunc(3) == 3)
/// #assert(calc.trunc(-3.7) == -3)
/// #assert(calc.trunc(15.9) == 15)
/// ```
-///
-/// Display: Truncate
-/// Category: calculate
-#[func]
+#[func(title = "Truncate")]
pub fn trunc(
/// The number to truncate.
value: Num,
@@ -714,15 +625,11 @@ pub fn trunc(
///
/// If the number is an integer, returns `0`.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.fract(3) == 0)
/// #calc.fract(-3.1)
/// ```
-///
-/// Display: Fractional
-/// Category: calculate
-#[func]
+#[func(title = "Fractional")]
pub fn fract(
/// The number to truncate.
value: Num,
@@ -737,15 +644,11 @@ pub fn fract(
///
/// Optionally, a number of decimal places can be specified.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.round(3.14) == 3)
/// #assert(calc.round(3.5) == 4)
/// #calc.round(3.1415, digits: 2)
/// ```
-///
-/// Display: Round
-/// Category: calculate
#[func]
pub fn round(
/// The number to round.
@@ -767,15 +670,11 @@ pub fn round(
/// Clamps a number between a minimum and maximum value.
///
-/// ## Example { #example }
/// ```example
/// #assert(calc.clamp(5, 0, 10) == 5)
/// #assert(calc.clamp(5, 6, 10) == 6)
/// #calc.clamp(5, 0, 4)
/// ```
-///
-/// Display: Clamp
-/// Category: calculate
#[func]
pub fn clamp(
/// The number to clamp.
@@ -793,44 +692,36 @@ pub fn clamp(
/// Determines the minimum of a sequence of values.
///
-/// ## Example { #example }
/// ```example
/// #calc.min(1, -3, -5, 20, 3, 6) \
/// #calc.min("typst", "in", "beta")
/// ```
-///
-/// Display: Minimum
-/// Category: calculate
-#[func]
+#[func(title = "Minimum")]
pub fn min(
+ /// The callsite span.
+ span: Span,
/// The sequence of values from which to extract the minimum.
/// Must not be empty.
#[variadic]
values: Vec<Spanned<Value>>,
- /// The callsite span.
- span: Span,
) -> SourceResult<Value> {
minmax(span, values, Ordering::Less)
}
/// Determines the maximum of a sequence of values.
///
-/// ## Example { #example }
/// ```example
/// #calc.max(1, -3, -5, 20, 3, 6) \
/// #calc.max("typst", "in", "beta")
/// ```
-///
-/// Display: Maximum
-/// Category: calculate
-#[func]
+#[func(title = "Maximum")]
pub fn max(
+ /// The callsite span.
+ span: Span,
/// The sequence of values from which to extract the maximum.
/// Must not be empty.
#[variadic]
values: Vec<Spanned<Value>>,
- /// The callsite span.
- span: Span,
) -> SourceResult<Value> {
minmax(span, values, Ordering::Greater)
}
@@ -858,15 +749,11 @@ fn minmax(
/// Determines whether an integer is even.
///
-/// ## Example { #example }
/// ```example
/// #calc.even(4) \
/// #calc.even(5) \
/// #range(10).filter(calc.even)
/// ```
-///
-/// Display: Even
-/// Category: calculate
#[func]
pub fn even(
/// The number to check for evenness.
@@ -877,15 +764,11 @@ pub fn even(
/// Determines whether an integer is odd.
///
-/// ## Example { #example }
/// ```example
/// #calc.odd(4) \
/// #calc.odd(5) \
/// #range(10).filter(calc.odd)
/// ```
-///
-/// Display: Odd
-/// Category: calculate
#[func]
pub fn odd(
/// The number to check for oddness.
@@ -896,15 +779,11 @@ pub fn odd(
/// Calculates the remainder of two numbers.
///
-/// ## Example { #example }
/// ```example
/// #calc.rem(20, 6) \
/// #calc.rem(1.75, 0.5)
/// ```
-///
-/// Display: Remainder
-/// Category: calculate
-#[func]
+#[func(title = "Remainder")]
pub fn rem(
/// The dividend of the remainder.
dividend: Num,
@@ -919,15 +798,11 @@ pub fn rem(
/// Calculates the quotient of two numbers.
///
-/// ## Example { #example }
/// ```example
/// #calc.quo(14, 5) \
/// #calc.quo(3.46, 0.5)
/// ```
-///
-/// Display: Quotient
-/// Category: calculate
-#[func]
+#[func(title = "Quotient")]
pub fn quo(
/// The dividend of the quotient.
dividend: Num,
@@ -949,7 +824,7 @@ pub enum Num {
}
impl Num {
- pub fn apply2(
+ fn apply2(
self,
other: Self,
int: impl FnOnce(i64, i64) -> i64,
@@ -961,7 +836,7 @@ impl Num {
}
}
- pub fn apply3(
+ fn apply3(
self,
other: Self,
third: Self,
@@ -974,7 +849,7 @@ impl Num {
}
}
- pub fn float(self) -> f64 {
+ fn float(self) -> f64 {
match self {
Self::Int(v) => v as f64,
Self::Float(v) => v,
diff --git a/crates/typst-library/src/compute/construct.rs b/crates/typst-library/src/compute/construct.rs
deleted file mode 100644
index 6ea8bd82..00000000
--- a/crates/typst-library/src/compute/construct.rs
+++ /dev/null
@@ -1,1015 +0,0 @@
-use std::num::NonZeroI64;
-use std::str::FromStr;
-
-use time::{Month, PrimitiveDateTime};
-
-use typst::eval::{Bytes, Datetime, Duration, Module, Plugin, Reflect, Regex};
-
-use crate::prelude::*;
-
-/// Converts a value to an integer.
-///
-/// - Booleans are converted to `0` or `1`.
-/// - Floats are floored to the next 64-bit integer.
-/// - Strings are parsed in base 10.
-///
-/// ## Example { #example }
-/// ```example
-/// #int(false) \
-/// #int(true) \
-/// #int(2.7) \
-/// #{ int("27") + int("4") }
-/// ```
-///
-/// Display: Integer
-/// Category: construct
-#[func]
-pub fn int(
- /// The value that should be converted to an integer.
- value: ToInt,
-) -> i64 {
- value.0
-}
-
-/// A value that can be cast to an integer.
-pub struct ToInt(i64);
-
-cast! {
- ToInt,
- v: bool => Self(v as i64),
- v: f64 => Self(v as i64),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid integer: {}", v))?),
- v: i64 => Self(v),
-}
-
-/// Converts a value to a float.
-///
-/// - Booleans are converted to `0.0` or `1.0`.
-/// - Integers are converted to the closest 64-bit float.
-/// - Ratios are divided by 100%.
-/// - Strings are parsed in base 10 to the closest 64-bit float.
-/// Exponential notation is supported.
-///
-/// ## Example { #example }
-/// ```example
-/// #float(false) \
-/// #float(true) \
-/// #float(4) \
-/// #float(40%) \
-/// #float("2.7") \
-/// #float("1e5")
-/// ```
-///
-/// Display: Float
-/// Category: construct
-#[func]
-pub fn float(
- /// The value that should be converted to a float.
- value: ToFloat,
-) -> f64 {
- value.0
-}
-
-/// A value that can be cast to a float.
-pub struct ToFloat(f64);
-
-cast! {
- ToFloat,
- v: bool => Self(v as i64 as f64),
- v: i64 => Self(v as f64),
- v: Ratio => Self(v.get()),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid float: {}", v))?),
- v: f64 => Self(v),
-}
-
-/// Creates a grayscale color.
-///
-/// ## Example { #example }
-/// ```example
-/// #for x in range(250, step: 50) {
-/// box(square(fill: luma(x)))
-/// }
-/// ```
-///
-/// Display: Luma
-/// Category: construct
-#[func]
-pub fn luma(
- /// The gray component.
- gray: Component,
-) -> Color {
- LumaColor::new(gray.0).into()
-}
-
-/// Creates an RGB(A) color.
-///
-/// The color is specified in the sRGB color space.
-///
-/// ## Example { #example }
-/// ```example
-/// #square(fill: rgb("#b1f2eb"))
-/// #square(fill: rgb(87, 127, 230))
-/// #square(fill: rgb(25%, 13%, 65%))
-/// ```
-///
-/// Display: RGB
-/// Category: construct
-#[func]
-pub fn rgb(
- /// The color in hexadecimal notation.
- ///
- /// Accepts three, four, six or eight hexadecimal digits and optionally
- /// a leading hashtag.
- ///
- /// If this string is given, the individual components should not be given.
- ///
- /// ```example
- /// #text(16pt, rgb("#239dad"))[
- /// *Typst*
- /// ]
- /// ```
- #[external]
- hex: EcoString,
- /// The red component.
- #[external]
- red: Component,
- /// The green component.
- #[external]
- green: Component,
- /// The blue component.
- #[external]
- blue: Component,
- /// The alpha component.
- #[external]
- alpha: Component,
- /// The arguments.
- args: Args,
-) -> SourceResult<Color> {
- let mut args = args;
- Ok(if let Some(string) = args.find::<Spanned<EcoString>>()? {
- match RgbaColor::from_str(&string.v) {
- Ok(color) => color.into(),
- Err(msg) => bail!(string.span, "{msg}"),
- }
- } else {
- let Component(r) = args.expect("red component")?;
- let Component(g) = args.expect("green component")?;
- let Component(b) = args.expect("blue component")?;
- let Component(a) = args.eat()?.unwrap_or(Component(255));
- RgbaColor::new(r, g, b, a).into()
- })
-}
-
-/// An integer or ratio component.
-pub struct Component(u8);
-
-cast! {
- Component,
- v: i64 => match v {
- 0 ..= 255 => Self(v as u8),
- _ => bail!("number must be between 0 and 255"),
- },
- v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- bail!("ratio must be between 0% and 100%");
- },
-}
-
-/// Creates a new datetime.
-///
-/// You can specify the [datetime]($type/datetime) using a year, month, day,
-/// hour, minute, and second. You can also get the current date with
-/// [`datetime.today`]($func/datetime.today).
-///
-/// ## Example
-/// ```example
-/// #let date = datetime(
-/// year: 2012,
-/// month: 8,
-/// day: 3,
-/// )
-///
-/// #date.display() \
-/// #date.display(
-/// "[day].[month].[year]"
-/// )
-/// ```
-///
-/// ## Format
-/// _Note_: Depending on which components of the datetime you specify, Typst
-/// will store it in one of the following three ways:
-/// * If you specify year, month and day, Typst will store just a date.
-/// * If you specify hour, minute and second, Typst will store just a time.
-/// * If you specify all of year, month, day, hour, minute and second, Typst
-/// will store a full datetime.
-///
-/// Depending on how it is stored, the [`display`]($type/datetime.display)
-/// method will choose a different formatting by default.
-///
-/// Display: Datetime
-/// Category: construct
-#[func]
-#[scope(
- scope.define("today", datetime_today_func());
- scope
-)]
-pub fn datetime(
- /// The year of the datetime.
- #[named]
- year: Option<YearComponent>,
- /// The month of the datetime.
- #[named]
- month: Option<MonthComponent>,
- /// The day of the datetime.
- #[named]
- day: Option<DayComponent>,
- /// The hour of the datetime.
- #[named]
- hour: Option<HourComponent>,
- /// The minute of the datetime.
- #[named]
- minute: Option<MinuteComponent>,
- /// The second of the datetime.
- #[named]
- second: Option<SecondComponent>,
-) -> StrResult<Datetime> {
- let time = match (hour, minute, second) {
- (Some(hour), Some(minute), Some(second)) => {
- match time::Time::from_hms(hour.0, minute.0, second.0) {
- Ok(time) => Some(time),
- Err(_) => bail!("time is invalid"),
- }
- }
- (None, None, None) => None,
- _ => bail!("time is incomplete"),
- };
-
- let date = match (year, month, day) {
- (Some(year), Some(month), Some(day)) => {
- match time::Date::from_calendar_date(year.0, month.0, day.0) {
- Ok(date) => Some(date),
- Err(_) => bail!("date is invalid"),
- }
- }
- (None, None, None) => None,
- _ => bail!("date is incomplete"),
- };
-
- Ok(match (date, time) {
- (Some(date), Some(time)) => {
- Datetime::Datetime(PrimitiveDateTime::new(date, time))
- }
- (Some(date), None) => Datetime::Date(date),
- (None, Some(time)) => Datetime::Time(time),
- (None, None) => {
- bail!("at least one of date or time must be fully specified")
- }
- })
-}
-
-pub struct YearComponent(i32);
-pub struct MonthComponent(Month);
-pub struct DayComponent(u8);
-pub struct HourComponent(u8);
-pub struct MinuteComponent(u8);
-pub struct SecondComponent(u8);
-
-cast! {
- YearComponent,
- v: i32 => Self(v),
-}
-
-cast! {
- MonthComponent,
- v: u8 => Self(Month::try_from(v).map_err(|_| "month is invalid")?)
-}
-
-cast! {
- DayComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- HourComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- MinuteComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- SecondComponent,
- v: u8 => Self(v),
-}
-
-/// Returns the current date.
-///
-/// Refer to the documentation of the [`display`]($type/datetime.display) method
-/// for details on how to affect the formatting of the date.
-///
-/// ## Example
-/// ```example
-/// Today's date is
-/// #datetime.today().display().
-/// ```
-///
-/// Display: Today
-/// Category: construct
-#[func]
-pub fn datetime_today(
- /// An offset to apply to the current UTC date. If set to `{auto}`, the
- /// offset will be the local offset.
- #[named]
- #[default]
- offset: Smart<i64>,
- /// The virtual machine.
- vt: &mut Vt,
-) -> StrResult<Datetime> {
- Ok(vt
- .world
- .today(offset.as_custom())
- .ok_or("unable to get the current date")?)
-}
-
-/// Creates a new duration.
-///
-/// You can specify the [duration]($type/duration) using weeks, days, hours,
-/// minutes and seconds. You can also get a duration by subtracting two
-/// [datetimes]($type/datetime).
-///
-/// ## Example
-/// ```example
-/// #duration(
-/// days: 3,
-/// hours: 12,
-/// ).hours()
-/// ```
-///
-/// Display: Duration
-/// Category: construct
-#[func]
-pub fn duration(
- /// The number of seconds.
- #[named]
- #[default(0)]
- seconds: i64,
- /// The number of minutes.
- #[named]
- #[default(0)]
- minutes: i64,
- /// The number of hours.
- #[named]
- #[default(0)]
- hours: i64,
- /// The number of days.
- #[named]
- #[default(0)]
- days: i64,
- /// The number of weeks.
- #[named]
- #[default(0)]
- weeks: i64,
-) -> Duration {
- Duration::from(
- time::Duration::seconds(seconds)
- + time::Duration::minutes(minutes)
- + time::Duration::hours(hours)
- + time::Duration::days(days)
- + time::Duration::weeks(weeks),
- )
-}
-
-/// Creates a CMYK color.
-///
-/// This is useful if you want to target a specific printer. The conversion
-/// to RGB for display preview might differ from how your printer reproduces
-/// the color.
-///
-/// ## Example { #example }
-/// ```example
-/// #square(
-/// fill: cmyk(27%, 0%, 3%, 5%)
-/// )
-/// ```
-///
-/// Display: CMYK
-/// Category: construct
-#[func]
-pub fn cmyk(
- /// The cyan component.
- cyan: RatioComponent,
- /// The magenta component.
- magenta: RatioComponent,
- /// The yellow component.
- yellow: RatioComponent,
- /// The key component.
- key: RatioComponent,
-) -> Color {
- CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into()
-}
-
-/// A component that must be a ratio.
-pub struct RatioComponent(u8);
-
-cast! {
- RatioComponent,
- v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- bail!("ratio must be between 0% and 100%");
- },
-}
-
-/// A module with functions operating on colors.
-pub fn color_module() -> Module {
- let mut scope = Scope::new();
- scope.define("mix", mix_func());
- Module::new("color").with_scope(scope)
-}
-
-/// Create a color by mixing two or more colors.
-///
-/// ## Example { #example }
-/// ```example
-/// #set block(height: 20pt, width: 100%)
-/// #block(fill: color.mix(red, blue))
-/// #block(fill: color.mix(red, blue, space: "srgb"))
-/// #block(fill: color.mix((red, 70%), (blue, 30%)))
-/// #block(fill: color.mix(red, blue, white))
-/// ```
-///
-/// _Note:_ This function must be specified as `color.mix`, not just `mix`.
-/// Currently, `color` is a module, but it is designed to be forward compatible
-/// with a future `color` type.
-///
-/// Display: Mix
-/// Category: construct
-#[func]
-pub fn mix(
- /// The colors, optionally with weights, specified as a pair (array of
- /// length two) of color and weight (float or ratio).
- ///
- /// The weights do not need to add to `{100%}`, they are relative to the
- /// sum of all weights.
- #[variadic]
- colors: Vec<WeightedColor>,
- /// The color space to mix in. By default, this happens in a perceptual
- /// color space (Oklab).
- #[named]
- #[default(ColorSpace::Oklab)]
- space: ColorSpace,
-) -> StrResult<Color> {
- Color::mix(colors, space)
-}
-
-/// Creates a custom symbol with modifiers.
-///
-/// ## Example { #example }
-/// ```example
-/// #let envelope = symbol(
-/// "🖂",
-/// ("stamped", "🖃"),
-/// ("stamped.pen", "🖆"),
-/// ("lightning", "🖄"),
-/// ("fly", "🖅"),
-/// )
-///
-/// #envelope
-/// #envelope.stamped
-/// #envelope.stamped.pen
-/// #envelope.lightning
-/// #envelope.fly
-/// ```
-///
-/// Display: Symbol
-/// Category: construct
-#[func]
-pub fn symbol(
- /// The variants of the symbol.
- ///
- /// Can be a just a string consisting of a single character for the
- /// modifierless variant or an array with two strings specifying the modifiers
- /// and the symbol. Individual modifiers should be separated by dots. When
- /// displaying a symbol, Typst selects the first from the variants that have
- /// all attached modifiers and the minimum number of other modifiers.
- #[variadic]
- variants: Vec<Spanned<Variant>>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<Symbol> {
- let mut list = Vec::new();
- if variants.is_empty() {
- bail!(span, "expected at least one variant");
- }
- for Spanned { v, span } in variants {
- if list.iter().any(|(prev, _)| &v.0 == prev) {
- bail!(span, "duplicate variant");
- }
- list.push((v.0, v.1));
- }
- Ok(Symbol::runtime(list.into_boxed_slice()))
-}
-
-/// A value that can be cast to a symbol.
-pub struct Variant(EcoString, char);
-
-cast! {
- Variant,
- c: char => Self(EcoString::new(), c),
- array: Array => {
- let mut iter = array.into_iter();
- match (iter.next(), iter.next(), iter.next()) {
- (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
- _ => bail!("point array must contain exactly two entries"),
- }
- },
-}
-
-/// Converts a value to a string.
-///
-/// - Integers are formatted in base 10. This can be overridden with the
-/// optional `base` parameter.
-/// - Floats are formatted in base 10 and never in exponential notation.
-/// - From labels the name is extracted.
-/// - Bytes are decoded as UTF-8.
-///
-/// If you wish to convert from and to Unicode code points, see
-/// [`str.to-unicode`]($func/str.to-unicode) and
-/// [`str.from-unicode`]($func/str.from-unicode).
-///
-/// ## Example { #example }
-/// ```example
-/// #str(10) \
-/// #str(4000, base: 16) \
-/// #str(2.7) \
-/// #str(1e8) \
-/// #str(<intro>)
-/// ```
-///
-/// Display: String
-/// Category: construct
-#[func]
-#[scope(
- scope.define("to-unicode", str_to_unicode_func());
- scope.define("from-unicode", str_from_unicode_func());
- scope
-)]
-pub fn str(
- /// The value that should be converted to a string.
- value: ToStr,
- /// The base (radix) to display integers in, between 2 and 36.
- #[named]
- #[default(Spanned::new(10, Span::detached()))]
- base: Spanned<i64>,
-) -> SourceResult<Str> {
- Ok(match value {
- ToStr::Str(s) => {
- if base.v != 10 {
- bail!(base.span, "base is only supported for integers");
- }
- s
- }
- ToStr::Int(n) => {
- if base.v < 2 || base.v > 36 {
- bail!(base.span, "base must be between 2 and 36");
- }
- int_to_base(n, base.v).into()
- }
- })
-}
-
-/// A value that can be cast to a string.
-pub enum ToStr {
- /// A string value ready to be used as-is.
- Str(Str),
- /// An integer about to be formatted in a given base.
- Int(i64),
-}
-
-cast! {
- ToStr,
- v: i64 => Self::Int(v),
- v: f64 => Self::Str(format_str!("{}", v)),
- v: Label => Self::Str(v.0.into()),
- v: Bytes => Self::Str(
- std::str::from_utf8(&v)
- .map_err(|_| "bytes are not valid utf-8")?
- .into()
- ),
- v: Str => Self::Str(v),
-}
-
-/// Format an integer in a base.
-fn int_to_base(mut n: i64, base: i64) -> EcoString {
- if n == 0 {
- return "0".into();
- }
-
- // In Rust, `format!("{:x}", -14i64)` is not `-e` but `fffffffffffffff2`.
- // So we can only use the built-in for decimal, not bin/oct/hex.
- if base == 10 {
- return eco_format!("{n}");
- }
-
- // The largest output is `to_base(i64::MIN, 2)`, which is 65 chars long.
- const SIZE: usize = 65;
- let mut digits = [b'\0'; SIZE];
- let mut i = SIZE;
-
- // It's tempting to take the absolute value, but this will fail for i64::MIN.
- // Instead, we turn n negative, as -i64::MAX is perfectly representable.
- let negative = n < 0;
- if n > 0 {
- n = -n;
- }
-
- while n != 0 {
- let digit = char::from_digit(-(n % base) as u32, base as u32);
- i -= 1;
- digits[i] = digit.unwrap_or('?') as u8;
- n /= base;
- }
-
- if negative {
- i -= 1;
- digits[i] = b'-';
- }
-
- std::str::from_utf8(&digits[i..]).unwrap_or_default().into()
-}
-
-/// Converts a character into its corresponding code point.
-///
-/// ## Example
-/// ```example
-/// #str.to-unicode("a") \
-/// #"a\u{0300}".codepoints().map(str.to-unicode)
-/// ```
-///
-/// Display: String To Unicode
-/// Category: construct
-#[func]
-pub fn str_to_unicode(
- /// The character that should be converted.
- value: char,
-) -> u32 {
- value.into()
-}
-
-/// Converts a Unicode code point into its corresponding string.
-///
-/// ```example
-/// #str.from-unicode(97)
-/// ```
-///
-/// Display: String From Unicode
-/// Category: construct
-#[func]
-pub fn str_from_unicode(
- /// The code point that should be converted.
- value: CodePoint,
-) -> Str {
- format_str!("{}", value.0)
-}
-
-/// The numeric representation of a single unicode code point.
-pub struct CodePoint(char);
-
-cast! {
- CodePoint,
- v: i64 => {
- Self(v.try_into().ok().and_then(|v: u32| v.try_into().ok()).ok_or_else(
- || eco_format!("{:#x} is not a valid codepoint", v),
- )?)
- },
-}
-
-/// Creates a regular expression from a string.
-///
-/// The result can be used as a
-/// [show rule selector]($styling/#show-rules) and with
-/// [string methods]($type/string) like `find`, `split`, and `replace`.
-///
-/// See [the specification of the supported syntax](https://docs.rs/regex/latest/regex/#syntax).
-///
-/// ## Example { #example }
-/// ```example
-/// // Works with show rules.
-/// #show regex("\d+"): set text(red)
-///
-/// The numbers 1 to 10.
-///
-/// // Works with string methods.
-/// #("a,b;c"
-/// .split(regex("[,;]")))
-/// ```
-///
-/// Display: Regex
-/// Category: construct
-#[func]
-pub fn regex(
- /// The regular expression as a string.
- ///
- /// Most regex escape sequences just work because they are not valid Typst
- /// escape sequences. To produce regex escape sequences that are also valid in
- /// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim
- /// backslash, you would need to write `{regex("\\\\")}`.
- ///
- /// If you need many escape sequences, you can also create a raw element
- /// and extract its text to use it for your regular expressions:
- /// ```{regex(`\d+\.\d+\.\d+`.text)}```.
- regex: Spanned<EcoString>,
-) -> SourceResult<Regex> {
- Regex::new(&regex.v).at(regex.span)
-}
-
-/// Converts a value to bytes.
-///
-/// - Strings are encoded in UTF-8.
-/// - Arrays of integers between `{0}` and `{255}` are converted directly. The
-/// dedicated byte representation is much more efficient than the array
-/// representation and thus typically used for large byte buffers (e.g. image
-/// data).
-///
-/// ```example
-/// #bytes("Hello 😃") \
-/// #bytes((123, 160, 22, 0))
-/// ```
-///
-/// Display: Bytes
-/// Category: construct
-#[func]
-pub fn bytes(
- /// The value that should be converted to bytes.
- value: ToBytes,
-) -> Bytes {
- value.0
-}
-
-/// A value that can be cast to bytes.
-pub struct ToBytes(Bytes);
-
-cast! {
- ToBytes,
- v: Str => Self(v.as_bytes().into()),
- v: Array => Self(v.iter()
- .map(|v| match v {
- Value::Int(byte @ 0..=255) => Ok(*byte as u8),
- Value::Int(_) => bail!("number must be between 0 and 255"),
- value => Err(<u8 as Reflect>::error(value)),
- })
- .collect::<Result<Vec<u8>, _>>()?
- .into()
- ),
- v: Bytes => Self(v),
-}
-
-/// Creates a label from a string.
-///
-/// Inserting a label into content attaches it to the closest previous element
-/// that is not a space. Then, the element can be [referenced]($func/ref) and
-/// styled through the label.
-///
-/// ## Example { #example }
-/// ```example
-/// #show <a>: set text(blue)
-/// #show label("b"): set text(red)
-///
-/// = Heading <a>
-/// *Strong* #label("b")
-/// ```
-///
-/// ## Syntax { #syntax }
-/// This function also has dedicated syntax: You can create a label by enclosing
-/// its name in angle brackets. This works both in markup and code.
-///
-/// Display: Label
-/// Category: construct
-#[func]
-pub fn label(
- /// The name of the label.
- name: EcoString,
-) -> Label {
- Label(name)
-}
-
-/// Converts a value to an array.
-///
-/// Note that this function is only intended for conversion of a collection-like
-/// value to an array, not for creation of an array from individual items. Use
-/// the array syntax `(1, 2, 3)` (or `(1,)` for a single-element array) instead.
-///
-/// ```example
-/// #let hi = "Hello 😃"
-/// #array(bytes(hi))
-/// ```
-///
-/// Display: Array
-/// Category: construct
-#[func]
-pub fn array(
- /// The value that should be converted to an array.
- value: ToArray,
-) -> Array {
- value.0
-}
-
-/// A value that can be cast to bytes.
-pub struct ToArray(Array);
-
-cast! {
- ToArray,
- v: Bytes => Self(v.iter().map(|&b| Value::Int(b as i64)).collect()),
- v: Array => Self(v),
-}
-
-/// Creates an array consisting of consecutive integers.
-///
-/// If you pass just one positional parameter, it is interpreted as the `end` of
-/// the range. If you pass two, they describe the `start` and `end` of the
-/// range.
-///
-/// ## Example { #example }
-/// ```example
-/// #range(5) \
-/// #range(2, 5) \
-/// #range(20, step: 4) \
-/// #range(21, step: 4) \
-/// #range(5, 2, step: -1)
-/// ```
-///
-/// Display: Range
-/// Category: construct
-#[func]
-pub fn range(
- /// The start of the range (inclusive).
- #[external]
- #[default]
- start: i64,
- /// The end of the range (exclusive).
- #[external]
- end: i64,
- /// The distance between the generated numbers.
- #[named]
- #[default(NonZeroI64::new(1).unwrap())]
- step: NonZeroI64,
- /// The arguments.
- args: Args,
-) -> SourceResult<Array> {
- let mut args = args;
- let first = args.expect::<i64>("end")?;
- let (start, end) = match args.eat::<i64>()? {
- Some(second) => (first, second),
- None => (0, first),
- };
-
- let step = step.get();
-
- let mut x = start;
- let mut array = Array::new();
-
- while x.cmp(&end) == 0.cmp(&step) {
- array.push(Value::Int(x));
- x += step;
- }
-
- Ok(array)
-}
-
-/// Loads a WebAssembly plugin.
-///
-/// This is **advanced functionality** and not to be confused with
-/// [Typst packages]($scripting/#packages).
-///
-/// Typst is capable of interfacing with plugins compiled to WebAssembly. Plugin
-/// functions may accept multiple [byte buffers]($type/bytes) as arguments and
-/// return a single byte buffer. They should typically be wrapped in idiomatic
-/// Typst functions that perform the necessary conversions between native Typst
-/// types and bytes.
-///
-/// Plugins run in isolation from your system, which means that printing,
-/// reading files, or anything like that will not be supported for security
-/// reasons. To run as a plugin, a program needs to be compiled to a 32-bit
-/// shared WebAssembly library. Many compilers will use the
-/// [WASI ABI](https://wasi.dev/) by default or as their only option (e.g.
-/// emscripten), which allows printing, reading files, etc. This ABI will not
-/// directly work with Typst. You will either need to compile to a different
-/// target or [stub all functions](https://github.com/astrale-sharp/wasm-minimal-protocol/blob/master/wasi-stub).
-///
-/// ## Example { #example }
-/// ```example
-/// #let myplugin = plugin("hello.wasm")
-/// #let concat(a, b) = str(
-/// myplugin.concatenate(
-/// bytes(a),
-/// bytes(b),
-/// )
-/// )
-///
-/// #concat("hello", "world")
-/// ```
-///
-/// ## Protocol { #protocol }
-/// To be used as a plugin, a WebAssembly module must conform to the following
-/// protocol:
-///
-/// ### Exports { #exports }
-/// A plugin module can export functions to make them callable from Typst. To
-/// conform to the protocol, an exported function should:
-///
-/// - Take `n` 32-bit integer arguments `a_1`, `a_2`, ..., `a_n` (interpreted as
-/// lengths, so `usize/size_t` may be preferable), and return one 32-bit
-/// integer.
-///
-/// - The function should first allocate a buffer `buf` of length
-/// `a_1 + a_2 + ... + a_n`, and then call
-/// `wasm_minimal_protocol_write_args_to_buffer(buf.ptr)`.
-///
-/// - The `a_1` first bytes of the buffer now constitute the first argument, the
-/// `a_2` next bytes the second argument, and so on.
-///
-/// - The function can now do its job with the arguments and produce an output
-/// buffer. Before returning, it should call
-/// `wasm_minimal_protocol_send_result_to_host` to send its result back to the
-/// host.
-///
-/// - To signal success, the function should return `0`.
-///
-/// - To signal an error, the function should return `1`. The written buffer is
-/// then interpreted as an UTF-8 encoded error message.
-///
-/// ### Imports { #imports }
-/// Plugin modules need to import two functions that are provided by the runtime.
-/// (Types and functions are described using WAT syntax.)
-///
-/// - `(import "typst_env" "wasm_minimal_protocol_write_args_to_buffer" (func (param i32)))`
-///
-/// Writes the arguments for the current function into a plugin-allocated
-/// buffer. When a plugin function is called, it
-/// [receives the lengths](#exported-functions) of its input buffers as
-/// arguments. It should then allocate a buffer whose capacity is at least the
-/// sum of these lengths. It should then call this function with a `ptr` to
-/// the buffer to fill it with the arguments, one after another.
-///
-/// - `(import "typst_env" "wasm_minimal_protocol_send_result_to_host" (func (param i32 i32)))`
-///
-/// Sends the output of the current function to the host (Typst). The first
-/// parameter shall be a pointer to a buffer (`ptr`), while the second is the
-/// length of that buffer (`len`). The memory pointed at by `ptr` can be freed
-/// immediately after this function returns. If the message should be
-/// interpreted as an error message, it should be encoded as UTF-8.
-///
-/// ## Resources { #resources }
-/// For more resources, check out the
-/// [wasm-minimal-protocol repository](https://github.com/astrale-sharp/wasm-minimal-protocol).
-/// It contains:
-///
-/// - A list of example plugin implementations and a test runner for these
-/// examples
-/// - Wrappers to help you write your plugin in Rust (Zig wrapper in
-/// development)
-/// - A stubber for WASI
-///
-/// Display: Plugin
-/// Category: construct
-#[func]
-pub fn plugin(
- /// Path to a WebAssembly file.
- path: Spanned<EcoString>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Plugin> {
- let Spanned { v: path, span } = path;
- let id = vm.resolve_path(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- Plugin::new(data).at(span)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_to_base() {
- assert_eq!(&int_to_base(0, 10), "0");
- assert_eq!(&int_to_base(0, 16), "0");
- assert_eq!(&int_to_base(0, 36), "0");
- assert_eq!(
- &int_to_base(i64::MAX, 2),
- "111111111111111111111111111111111111111111111111111111111111111"
- );
- assert_eq!(
- &int_to_base(i64::MIN, 2),
- "-1000000000000000000000000000000000000000000000000000000000000000"
- );
- assert_eq!(&int_to_base(i64::MAX, 10), "9223372036854775807");
- assert_eq!(&int_to_base(i64::MIN, 10), "-9223372036854775808");
- assert_eq!(&int_to_base(i64::MAX, 16), "7fffffffffffffff");
- assert_eq!(&int_to_base(i64::MIN, 16), "-8000000000000000");
- assert_eq!(&int_to_base(i64::MAX, 36), "1y2p0ij32e8e7");
- assert_eq!(&int_to_base(i64::MIN, 36), "-1y2p0ij32e8e8");
- }
-}
diff --git a/crates/typst-library/src/compute/data.rs b/crates/typst-library/src/compute/data.rs
index 222b14d3..dadf0bed 100644
--- a/crates/typst-library/src/compute/data.rs
+++ b/crates/typst-library/src/compute/data.rs
@@ -4,15 +4,25 @@ use typst::syntax::is_newline;
use crate::prelude::*;
+/// Hook up all data loading definitions.
+pub(super) fn define(global: &mut Scope) {
+ global.category("data-loading");
+ global.define_func::<read>();
+ global.define_func::<csv>();
+ global.define_func::<json>();
+ global.define_func::<toml>();
+ global.define_func::<yaml>();
+ global.define_func::<cbor>();
+ global.define_func::<xml>();
+}
+
/// Reads plain text or data from a file.
///
-/// By default, the file will be read as UTF-8 and returned as a
-/// [string]($type/string).
+/// By default, the file will be read as UTF-8 and returned as a [string]($str).
///
-/// If you specify `{encoding: none}`, this returns raw [bytes]($type/bytes)
-/// instead.
+/// If you specify `{encoding: none}`, this returns raw [bytes]($bytes) instead.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// An example for a HTML file: \
/// #let text = read("data.html")
@@ -21,11 +31,10 @@ use crate::prelude::*;
/// Raw bytes:
/// #read("tiger.jpg", encoding: none)
/// ```
-///
-/// Display: Read
-/// Category: data-loading
#[func]
pub fn read(
+ /// The virtual machine.
+ vm: &mut Vm,
/// Path to a file.
path: Spanned<EcoString>,
/// The encoding to read the file with.
@@ -34,8 +43,6 @@ pub fn read(
#[named]
#[default(Some(Encoding::Utf8))]
encoding: Option<Encoding>,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Readable> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
@@ -101,7 +108,7 @@ impl From<Readable> for Bytes {
/// rows will be collected into a single array. Header rows will not be
/// stripped.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let results = csv("data.csv")
///
@@ -111,15 +118,10 @@ impl From<Readable> for Bytes {
/// ..results.flatten(),
/// )
/// ```
-///
-/// Display: CSV
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", csv_decode_func());
- scope
-)]
+#[func(scope, title = "CSV")]
pub fn csv(
+ /// The virtual machine.
+ vm: &mut Vm,
/// Path to a CSV file.
path: Spanned<EcoString>,
/// The delimiter that separates columns in the CSV file.
@@ -127,47 +129,45 @@ pub fn csv(
#[named]
#[default]
delimiter: Delimiter,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Array> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- csv_decode(Spanned::new(Readable::Bytes(data), span), delimiter)
+ self::csv::decode(Spanned::new(Readable::Bytes(data), span), delimiter)
}
-/// Reads structured data from a CSV string/bytes.
-///
-/// Display: Decode CSV
-/// Category: data-loading
-#[func]
-pub fn csv_decode(
- /// CSV data.
- data: Spanned<Readable>,
- /// The delimiter that separates columns in the CSV file.
- /// Must be a single ASCII character.
- #[named]
- #[default]
- delimiter: Delimiter,
-) -> SourceResult<Array> {
- let Spanned { v: data, span } = data;
- let mut builder = csv::ReaderBuilder::new();
- builder.has_headers(false);
- builder.delimiter(delimiter.0 as u8);
- let mut reader = builder.from_reader(data.as_slice());
- let mut array = Array::new();
-
- for (line, result) in reader.records().enumerate() {
- // Original solution use line from error, but that is incorrect with
- // `has_headers` set to `false`. See issue:
- // https://github.com/BurntSushi/rust-csv/issues/184
- let line = line + 1; // Counting lines from 1
- let row = result.map_err(|err| format_csv_error(err, line)).at(span)?;
- let sub = row.into_iter().map(|field| field.into_value()).collect();
- array.push(Value::Array(sub))
- }
+#[scope]
+impl csv {
+ /// Reads structured data from a CSV string/bytes.
+ #[func(title = "Decode CSV")]
+ pub fn decode(
+ /// CSV data.
+ data: Spanned<Readable>,
+ /// The delimiter that separates columns in the CSV file.
+ /// Must be a single ASCII character.
+ #[named]
+ #[default]
+ delimiter: Delimiter,
+ ) -> SourceResult<Array> {
+ let Spanned { v: data, span } = data;
+ let mut builder = ::csv::ReaderBuilder::new();
+ builder.has_headers(false);
+ builder.delimiter(delimiter.0 as u8);
+ let mut reader = builder.from_reader(data.as_slice());
+ let mut array = Array::new();
+
+ for (line, result) in reader.records().enumerate() {
+ // Original solution use line from error, but that is incorrect with
+ // `has_headers` set to `false`. See issue:
+ // https://github.com/BurntSushi/rust-csv/issues/184
+ let line = line + 1; // Counting lines from 1
+ let row = result.map_err(|err| format_csv_error(err, line)).at(span)?;
+ let sub = row.into_iter().map(|field| field.into_value()).collect();
+ array.push(Value::Array(sub))
+ }
- Ok(array)
+ Ok(array)
+ }
}
/// The delimiter to use when parsing CSV files.
@@ -198,10 +198,10 @@ cast! {
}
/// Format the user-facing CSV error message.
-fn format_csv_error(err: csv::Error, line: usize) -> EcoString {
+fn format_csv_error(err: ::csv::Error, line: usize) -> EcoString {
match err.kind() {
- csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
- csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
+ ::csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
+ ::csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
eco_format!(
"failed to parse CSV (found {len} instead of \
{expected_len} fields in line {line})"
@@ -224,7 +224,7 @@ fn format_csv_error(err: csv::Error, line: usize) -> EcoString {
/// The JSON files in the example contain objects with the keys `temperature`,
/// `unit`, and `weather`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let forecast(day) = block[
/// #box(square(
@@ -248,64 +248,53 @@ fn format_csv_error(err: csv::Error, line: usize) -> EcoString {
/// #forecast(json("monday.json"))
/// #forecast(json("tuesday.json"))
/// ```
-///
-/// Display: JSON
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", json_decode_func());
- scope.define("encode", json_encode_func());
- scope
-)]
+#[func(scope, title = "JSON")]
pub fn json(
- /// Path to a JSON file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a JSON file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- json_decode(Spanned::new(Readable::Bytes(data), span))
+ json::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from a JSON string/bytes.
-///
-/// Display: JSON
-/// Category: data-loading
-#[func]
-pub fn json_decode(
- /// JSON data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- serde_json::from_slice(data.as_slice())
- .map_err(|err| eco_format!("failed to parse JSON ({err})"))
- .at(span)
-}
+#[scope]
+impl json {
+ /// Reads structured data from a JSON string/bytes.
+ #[func(title = "Decode JSON")]
+ pub fn decode(
+ /// JSON data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ serde_json::from_slice(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse JSON ({err})"))
+ .at(span)
+ }
-/// Encodes structured data into a JSON string.
-///
-/// Display: Encode JSON
-/// Category: data-loading
-#[func]
-pub fn json_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
- /// Whether to pretty print the JSON with newlines and indentation.
- #[named]
- #[default(true)]
- pretty: bool,
-) -> SourceResult<Str> {
- let Spanned { v: value, span } = value;
- if pretty {
- serde_json::to_string_pretty(&value)
- } else {
- serde_json::to_string(&value)
+ /// Encodes structured data into a JSON string.
+ #[func(title = "Encode JSON")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ /// Whether to pretty print the JSON with newlines and indentation.
+ #[named]
+ #[default(true)]
+ pretty: bool,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ if pretty {
+ serde_json::to_string_pretty(&value)
+ } else {
+ serde_json::to_string(&value)
+ }
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
+ .at(span)
}
- .map(|v| v.into())
- .map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
- .at(span)
}
/// Reads structured data from a TOML file.
@@ -319,7 +308,7 @@ pub fn json_encode(
/// The TOML file in the example consists of a table with the keys `title`,
/// `version`, and `authors`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let details = toml("details.toml")
///
@@ -328,67 +317,56 @@ pub fn json_encode(
/// Authors: #(details.authors
/// .join(", ", last: " and "))
/// ```
-///
-/// Display: TOML
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", toml_decode_func());
- scope.define("encode", toml_encode_func());
- scope
-)]
+#[func(scope, title = "TOML")]
pub fn toml(
- /// Path to a TOML file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a TOML file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- toml_decode(Spanned::new(Readable::Bytes(data), span))
+ toml::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from a TOML string/bytes.
-///
-/// Display: Decode TOML
-/// Category: data-loading
-#[func]
-pub fn toml_decode(
- /// TOML data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- let raw = std::str::from_utf8(data.as_slice())
- .map_err(|_| "file is not valid utf-8")
- .at(span)?;
- toml::from_str(raw)
- .map_err(|err| format_toml_error(err, raw))
- .at(span)
-}
+#[scope]
+impl toml {
+ /// Reads structured data from a TOML string/bytes.
+ #[func(title = "Decode TOML")]
+ pub fn decode(
+ /// TOML data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ let raw = std::str::from_utf8(data.as_slice())
+ .map_err(|_| "file is not valid utf-8")
+ .at(span)?;
+ ::toml::from_str(raw)
+ .map_err(|err| format_toml_error(err, raw))
+ .at(span)
+ }
-/// Encodes structured data into a TOML string.
-///
-/// Display: Encode TOML
-/// Category: data-loading
-#[func]
-pub fn toml_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
- /// Whether to pretty-print the resulting TOML.
- #[named]
- #[default(true)]
- pretty: bool,
-) -> SourceResult<Str> {
- let Spanned { v: value, span } = value;
- if pretty { toml::to_string_pretty(&value) } else { toml::to_string(&value) }
- .map(|v| v.into())
- .map_err(|err| eco_format!("failed to encode value as TOML ({err})"))
- .at(span)
+ /// Encodes structured data into a TOML string.
+ #[func(title = "Encode TOML")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ /// Whether to pretty-print the resulting TOML.
+ #[named]
+ #[default(true)]
+ pretty: bool,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ if pretty { ::toml::to_string_pretty(&value) } else { ::toml::to_string(&value) }
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as TOML ({err})"))
+ .at(span)
+ }
}
/// Format the user-facing TOML error message.
-fn format_toml_error(error: toml::de::Error, raw: &str) -> EcoString {
+fn format_toml_error(error: ::toml::de::Error, raw: &str) -> EcoString {
if let Some(head) = error.span().and_then(|range| raw.get(..range.start)) {
let line = head.lines().count();
let column = 1 + head.chars().rev().take_while(|&c| !is_newline(c)).count();
@@ -415,7 +393,7 @@ fn format_toml_error(error: toml::de::Error, raw: &str) -> EcoString {
/// each with a sequence of their own submapping with the keys
/// "title" and "published"
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let bookshelf(contents) = {
/// for (author, works) in contents {
@@ -430,56 +408,45 @@ fn format_toml_error(error: toml::de::Error, raw: &str) -> EcoString {
/// yaml("scifi-authors.yaml")
/// )
/// ```
-///
-/// Display: YAML
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", yaml_decode_func());
- scope.define("encode", yaml_encode_func());
- scope
-)]
+#[func(scope, title = "YAML")]
pub fn yaml(
- /// Path to a YAML file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a YAML file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- yaml_decode(Spanned::new(Readable::Bytes(data), span))
+ yaml::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from a YAML string/bytes.
-///
-/// Display: Decode YAML
-/// Category: data-loading
-#[func]
-pub fn yaml_decode(
- /// YAML data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- serde_yaml::from_slice(data.as_slice())
- .map_err(|err| eco_format!("failed to parse YAML ({err})"))
- .at(span)
-}
+#[scope]
+impl yaml {
+ /// Reads structured data from a YAML string/bytes.
+ #[func(title = "Decode YAML")]
+ pub fn decode(
+ /// YAML data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ serde_yaml::from_slice(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse YAML ({err})"))
+ .at(span)
+ }
-/// Encode structured data into a YAML string.
-///
-/// Display: Encode YAML
-/// Category: data-loading
-#[func]
-pub fn yaml_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
-) -> SourceResult<Str> {
- let Spanned { v: value, span } = value;
- serde_yaml::to_string(&value)
- .map(|v| v.into())
- .map_err(|err| eco_format!("failed to encode value as YAML ({err})"))
- .at(span)
+ /// Encode structured data into a YAML string.
+ #[func(title = "Encode YAML")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ serde_yaml::to_string(&value)
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as YAML ({err})"))
+ .at(span)
+ }
}
/// Reads structured data from a CBOR file.
@@ -490,57 +457,46 @@ pub fn yaml_encode(
/// equivalents, null-values (`null`, `~` or empty ``) will be converted into
/// `{none}`, and numbers will be converted to floats or integers depending on
/// whether they are whole numbers.
-///
-/// Display: CBOR
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", cbor_decode_func());
- scope.define("encode", cbor_encode_func());
- scope
-)]
+#[func(scope, title = "CBOR")]
pub fn cbor(
- /// Path to a CBOR file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to a CBOR file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- cbor_decode(Spanned::new(data, span))
+ cbor::decode(Spanned::new(data, span))
}
-/// Reads structured data from CBOR bytes.
-///
-/// Display: Decode CBOR
-/// Category: data-loading
-#[func]
-pub fn cbor_decode(
- /// cbor data.
- data: Spanned<Bytes>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- ciborium::from_reader(data.as_slice())
- .map_err(|err| eco_format!("failed to parse CBOR ({err})"))
- .at(span)
-}
+#[scope]
+impl cbor {
+ /// Reads structured data from CBOR bytes.
+ #[func(title = "Decode CBOR")]
+ pub fn decode(
+ /// cbor data.
+ data: Spanned<Bytes>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ ciborium::from_reader(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse CBOR ({err})"))
+ .at(span)
+ }
-/// Encode structured data into CBOR bytes.
-///
-/// Display: Encode CBOR
-/// Category: data-loading
-#[func]
-pub fn cbor_encode(
- /// Value to be encoded.
- value: Spanned<Value>,
-) -> SourceResult<Bytes> {
- let Spanned { v: value, span } = value;
- let mut res = Vec::new();
- ciborium::into_writer(&value, &mut res)
- .map(|_| res.into())
- .map_err(|err| eco_format!("failed to encode value as CBOR ({err})"))
- .at(span)
+ /// Encode structured data into CBOR bytes.
+ #[func(title = "Encode CBOR")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ ) -> SourceResult<Bytes> {
+ let Spanned { v: value, span } = value;
+ let mut res = Vec::new();
+ ciborium::into_writer(&value, &mut res)
+ .map(|_| res.into())
+ .map_err(|err| eco_format!("failed to encode value as CBOR ({err})"))
+ .at(span)
+ }
}
/// Reads structured data from an XML file.
@@ -558,7 +514,7 @@ pub fn cbor_encode(
/// `content` tag contains one or more paragraphs, which are represented as `p`
/// tags.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #let find-child(elem, tag) = {
/// elem.children
@@ -591,41 +547,35 @@ pub fn cbor_encode(
/// }
/// }
/// ```
-///
-/// Display: XML
-/// Category: data-loading
-#[func]
-#[scope(
- scope.define("decode", xml_decode_func());
- scope
-)]
+#[func(scope, title = "XML")]
pub fn xml(
- /// Path to an XML file.
- path: Spanned<EcoString>,
/// The virtual machine.
vm: &mut Vm,
+ /// Path to an XML file.
+ path: Spanned<EcoString>,
) -> SourceResult<Value> {
let Spanned { v: path, span } = path;
let id = vm.resolve_path(&path).at(span)?;
let data = vm.world().file(id).at(span)?;
- xml_decode(Spanned::new(Readable::Bytes(data), span))
+ xml::decode(Spanned::new(Readable::Bytes(data), span))
}
-/// Reads structured data from an XML string/bytes.
-///
-/// Display: Decode XML
-/// Category: data-loading
-#[func]
-pub fn xml_decode(
- /// XML data.
- data: Spanned<Readable>,
-) -> SourceResult<Value> {
- let Spanned { v: data, span } = data;
- let text = std::str::from_utf8(data.as_slice())
- .map_err(FileError::from)
- .at(span)?;
- let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
- Ok(convert_xml(document.root()))
+#[scope]
+impl xml {
+ /// Reads structured data from an XML string/bytes.
+ #[func(title = "Decode XML")]
+ pub fn decode(
+ /// XML data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ let text = std::str::from_utf8(data.as_slice())
+ .map_err(FileError::from)
+ .at(span)?;
+ let document =
+ roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
+ Ok(convert_xml(document.root()))
+ }
}
/// Convert an XML node to a Typst value.
diff --git a/crates/typst-library/src/compute/foundations.rs b/crates/typst-library/src/compute/foundations.rs
index 3d07a3af..dad05717 100644
--- a/crates/typst-library/src/compute/foundations.rs
+++ b/crates/typst-library/src/compute/foundations.rs
@@ -1,29 +1,32 @@
-use typst::eval::EvalMode;
+use typst::eval::{
+ Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex,
+};
use crate::prelude::*;
-/// Determines the type of a value.
-///
-/// Returns the name of the value's type.
-///
-/// ## Example { #example }
-/// ```example
-/// #type(12) \
-/// #type(14.7) \
-/// #type("hello") \
-/// #type(none) \
-/// #type([Hi]) \
-/// #type(x => x + 1)
-/// ```
-///
-/// Display: Type
-/// Category: foundations
-#[func]
-pub fn type_(
- /// The value whose type's to determine.
- value: Value,
-) -> Str {
- value.type_name().into()
+/// Hook up all foundational definitions.
+pub(super) fn define(global: &mut Scope) {
+ global.category("foundations");
+ global.define_type::<bool>();
+ global.define_type::<i64>();
+ global.define_type::<f64>();
+ global.define_type::<Str>();
+ global.define_type::<Bytes>();
+ global.define_type::<Content>();
+ global.define_type::<Array>();
+ global.define_type::<Dict>();
+ global.define_type::<Func>();
+ global.define_type::<Args>();
+ global.define_type::<Type>();
+ global.define_type::<Module>();
+ global.define_type::<Regex>();
+ global.define_type::<Datetime>();
+ global.define_type::<Duration>();
+ global.define_type::<Plugin>();
+ global.define_func::<repr>();
+ global.define_func::<panic>();
+ global.define_func::<assert>();
+ global.define_func::<eval>();
}
/// Returns the string representation of a value.
@@ -35,17 +38,14 @@ pub fn type_(
/// **Note:** This function is for debugging purposes. Its output should not be
/// considered stable and may change at any time!
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #none vs #repr(none) \
/// #"hello" vs #repr("hello") \
/// #(1, 2) vs #repr((1, 2)) \
/// #[*Hi*] vs #repr([*Hi*])
/// ```
-///
-/// Display: Representation
-/// Category: foundations
-#[func]
+#[func(title = "Representation")]
pub fn repr(
/// The value whose string representation to produce.
value: Value,
@@ -55,16 +55,12 @@ pub fn repr(
/// Fails with an error.
///
-/// ## Example { #example }
+/// # Example
/// The code below produces the error `panicked with: "this is wrong"`.
/// ```typ
/// #panic("this is wrong")
/// ```
-///
-/// Display: Panic
-/// Category: foundations
-/// Keywords: error
-#[func]
+#[func(keywords = ["error"])]
pub fn panic(
/// The values to panic with.
#[variadic]
@@ -89,21 +85,13 @@ pub fn panic(
/// produce any output in the document.
///
/// If you wish to test equality between two values, see
-/// [`assert.eq`]($func/assert.eq) and [`assert.ne`]($func/assert.ne).
+/// [`assert.eq`]($assert.eq) and [`assert.ne`]($assert.ne).
///
-/// ## Example { #example }
+/// # Example
/// ```typ
/// #assert(1 < 2, message: "math broke")
/// ```
-///
-/// Display: Assert
-/// Category: foundations
-#[func]
-#[scope(
- scope.define("eq", assert_eq_func());
- scope.define("ne", assert_ne_func());
- scope
-)]
+#[func(scope)]
pub fn assert(
/// The condition that must be true for the assertion to pass.
condition: bool,
@@ -121,91 +109,83 @@ pub fn assert(
Ok(NoneValue)
}
-/// Ensures that two values are equal.
-///
-/// Fails with an error if the first value is not equal to the second. Does not
-/// produce any output in the document.
-///
-/// ## Example { #example }
-/// ```typ
-/// #assert.eq(10, 10)
-/// ```
-///
-/// Display: Assert Equals
-/// Category: foundations
-#[func]
-pub fn assert_eq(
- /// The first value to compare.
- left: Value,
-
- /// The second value to compare.
- right: Value,
-
- /// An optional message to display on error instead of the representations
- /// of the compared values.
- #[named]
- message: Option<EcoString>,
-) -> StrResult<NoneValue> {
- if left != right {
- if let Some(message) = message {
- bail!("equality assertion failed: {message}");
- } else {
- bail!("equality assertion failed: value {left:?} was not equal to {right:?}");
+#[scope]
+impl assert {
+ /// Ensures that two values are equal.
+ ///
+ /// Fails with an error if the first value is not equal to the second. Does not
+ /// produce any output in the document.
+ ///
+ /// ```typ
+ /// #assert.eq(10, 10)
+ /// ```
+ #[func(title = "Assert Equal")]
+ pub fn eq(
+ /// The first value to compare.
+ left: Value,
+ /// The second value to compare.
+ right: Value,
+ /// An optional message to display on error instead of the representations
+ /// of the compared values.
+ #[named]
+ message: Option<EcoString>,
+ ) -> StrResult<NoneValue> {
+ if left != right {
+ if let Some(message) = message {
+ bail!("equality assertion failed: {message}");
+ } else {
+ bail!("equality assertion failed: value {left:?} was not equal to {right:?}");
+ }
}
+ Ok(NoneValue)
}
- Ok(NoneValue)
-}
-/// Ensures that two values are not equal.
-///
-/// Fails with an error if the first value is equal to the second. Does not
-/// produce any output in the document.
-///
-/// ## Example { #example }
-/// ```typ
-/// #assert.ne(3, 4)
-/// ```
-///
-/// Display: Assert Not Equals
-/// Category: foundations
-#[func]
-pub fn assert_ne(
- /// The first value to compare.
- left: Value,
-
- /// The second value to compare.
- right: Value,
-
- /// An optional message to display on error instead of the representations
- /// of the compared values.
- #[named]
- message: Option<EcoString>,
-) -> StrResult<NoneValue> {
- if left == right {
- if let Some(message) = message {
- bail!("inequality assertion failed: {message}");
- } else {
- bail!("inequality assertion failed: value {left:?} was equal to {right:?}");
+ /// Ensures that two values are not equal.
+ ///
+ /// Fails with an error if the first value is equal to the second. Does not
+ /// produce any output in the document.
+ ///
+ /// ```typ
+ /// #assert.ne(3, 4)
+ /// ```
+ #[func(title = "Assert Not Equal")]
+ pub fn ne(
+ /// The first value to compare.
+ left: Value,
+ /// The second value to compare.
+ right: Value,
+ /// An optional message to display on error instead of the representations
+ /// of the compared values.
+ #[named]
+ message: Option<EcoString>,
+ ) -> StrResult<NoneValue> {
+ if left == right {
+ if let Some(message) = message {
+ bail!("inequality assertion failed: {message}");
+ } else {
+ bail!(
+ "inequality assertion failed: value {left:?} was equal to {right:?}"
+ );
+ }
}
+ Ok(NoneValue)
}
- Ok(NoneValue)
}
/// Evaluates a string as Typst code.
///
/// This function should only be used as a last resort.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #eval("1 + 1") \
/// #eval("(1, 2, 3, 4)").len() \
/// #eval("*Markup!*", mode: "markup") \
/// ```
-///
-/// Display: Evaluate
-/// Category: foundations
-#[func]
+#[func(title = "Evaluate")]
pub fn eval(
+ /// The virtual machine.
+ vm: &mut Vm,
/// A string of Typst code to evaluate.
///
/// The code in the string cannot interact with the file system.
@@ -235,8 +215,6 @@ pub fn eval(
#[named]
#[default]
scope: Dict,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Value> {
let Spanned { v: text, span } = source;
let dict = scope;
diff --git a/crates/typst-library/src/compute/mod.rs b/crates/typst-library/src/compute/mod.rs
index ca95f7b7..9e897653 100644
--- a/crates/typst-library/src/compute/mod.rs
+++ b/crates/typst-library/src/compute/mod.rs
@@ -1,11 +1,10 @@
//! Computational functions.
pub mod calc;
-mod construct;
+
mod data;
mod foundations;
-pub use self::construct::*;
pub use self::data::*;
pub use self::foundations::*;
@@ -13,33 +12,7 @@ use crate::prelude::*;
/// Hook up all compute definitions.
pub(super) fn define(global: &mut Scope) {
- global.define("type", type_func());
- global.define("repr", repr_func());
- global.define("panic", panic_func());
- global.define("assert", assert_func());
- global.define("eval", eval_func());
- global.define("int", int_func());
- global.define("float", float_func());
- global.define("luma", luma_func());
- global.define("rgb", rgb_func());
- global.define("cmyk", cmyk_func());
- global.define("color", color_module());
- global.define("datetime", datetime_func());
- global.define("duration", duration_func());
- global.define("symbol", symbol_func());
- global.define("str", str_func());
- global.define("bytes", bytes_func());
- global.define("label", label_func());
- global.define("regex", regex_func());
- global.define("array", array_func());
- global.define("range", range_func());
- global.define("read", read_func());
- global.define("csv", csv_func());
- global.define("json", json_func());
- global.define("toml", toml_func());
- global.define("yaml", yaml_func());
- global.define("cbor", cbor_func());
- global.define("xml", xml_func());
- global.define("calc", calc::module());
- global.define("plugin", plugin_func());
+ self::foundations::define(global);
+ self::data::define(global);
+ self::calc::define(global);
}
diff --git a/crates/typst-library/src/layout/align.rs b/crates/typst-library/src/layout/align.rs
index 5f7e8bc0..f080f677 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<Option<GenAlign>>,
+ #[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<Content> {
- 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 6645ba9e..961bedc5 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 c79669d0..e966398f 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<Length>,
/// 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<Paint>,
/// 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<Option<Option<PartialStroke>>>,
+ pub stroke: Sides<Option<Option<Stroke>>>,
- /// 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<Option<Rel<Length>>>,
- /// 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<Option<Rel<Length>>>,
@@ -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<Rel<Length>>,
/// 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<Paint>,
/// 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<Option<Option<PartialStroke>>>,
+ pub stroke: Sides<Option<Option<Stroke>>>,
- /// 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<Option<Rel<Length>>>,
- /// 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<Option<Rel<Length>>>,
/// 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<Option<Rel<Length>>>,
@@ -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 8f23b6dc..c505f189 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<Spacing>,
/// 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<Option<GenAlign>> =
- 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 4ce78c94..fe6e9398 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<Align>, sticky: bool, movable: bool },
+ Frame { frame: Frame, align: Axes<FixedAlign>, sticky: bool, movable: bool },
/// An absolutely placed frame.
Placed {
frame: Frame,
- x_align: Align,
- y_align: Smart<Option<Align>>,
+ x_align: FixedAlign,
+ y_align: Smart<Option<FixedAlign>>,
delta: Axes<Rel<Abs>>,
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::<AlignElem>() {
+ let align = if let Some(align) = block.to::<AlignElem>() {
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 4f5175e9..06962524 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 c6e83e0c..7f17a7d7 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 8bb8744b..a9dad85b 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<Spacing>,
/// 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 eb8e509e..d41b7f95 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<Dict> {
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 3334d5aa..ace5cd6e 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::<Length>();
+ global.define_type::<Angle>();
+ global.define_type::<Ratio>();
+ global.define_type::<Rel<Length>>();
+ global.define_type::<Fr>();
+ global.define_type::<Dir>();
+ global.define_type::<Align>();
+ global.define_elem::<PageElem>();
+ global.define_elem::<PagebreakElem>();
+ global.define_elem::<VElem>();
+ global.define_elem::<ParElem>();
+ global.define_elem::<ParbreakElem>();
+ global.define_elem::<HElem>();
+ global.define_elem::<BoxElem>();
+ global.define_elem::<BlockElem>();
+ global.define_elem::<ListElem>();
+ global.define_elem::<EnumElem>();
+ global.define_elem::<TermsElem>();
+ global.define_elem::<TableElem>();
+ global.define_elem::<StackElem>();
+ global.define_elem::<GridElem>();
+ global.define_elem::<ColumnsElem>();
+ global.define_elem::<ColbreakElem>();
+ global.define_elem::<PlaceElem>();
+ global.define_elem::<AlignElem>();
+ global.define_elem::<PadElem>();
+ global.define_elem::<RepeatElem>();
+ global.define_elem::<MoveElem>();
+ global.define_elem::<ScaleElem>();
+ global.define_elem::<RotateElem>();
+ global.define_elem::<HideElem>();
+ global.define_func::<measure>();
}
/// Root-level layout.
@@ -598,7 +595,7 @@ impl<'a> ParBuilder<'a> {
|| content.is::<TextElem>()
|| content.is::<HElem>()
|| content.is::<LinebreakElem>()
- || content.is::<SmartQuoteElem>()
+ || content.is::<SmartquoteElem>()
|| content.to::<EquationElem>().map_or(false, |elem| !elem.block(styles))
|| content.is::<BoxElem>()
{
diff --git a/crates/typst-library/src/layout/pad.rs b/crates/typst-library/src/layout/pad.rs
index a3d5646b..d1b0cb1f 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<Rel<Abs>>) -> Size {
/// <=> (1 - p.rel) * w = s + p.abs
/// <=> w = (s + p.abs) / (1 - p.rel)
fn grow(size: Size, padding: Sides<Rel<Abs>>) -> 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 4ef90753..d182a417 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<Paint>,
- /// 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<Spanned<Axes<_>>> = args.named("number-align")?;
- if let Some(Spanned { v, span }) = spanned {
- if matches!(v.y, Some(GenAlign::Specific(Align::Horizon))) {
+ let option: Option<Spanned<Align>> = 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<Option<GenAlign>>,
+ 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 39689477..e28e661c 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<Linebreaks>,
/// 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<bool>,
/// The text language if it's the same for all children.
lang: Option<Lang>,
- /// 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::<SmartQuoteElem>() {
+ } else if let Some(elem) = child.to::<SmartquoteElem>() {
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::<TextElem>() {
elem.text().chars().next()
- } else if child.is::<SmartQuoteElem>() {
+ } else if child.is::<SmartquoteElem>() {
Some('"')
} else if child.is::<SpaceElem>()
|| child.is::<HElem>()
@@ -642,7 +635,7 @@ fn collect<'a>(
};
if let Some(last) = full.chars().last() {
- quoter.last(last, child.is::<SmartQuoteElem>());
+ quoter.last(last, child.is::<SmartquoteElem>());
}
spans.push(segment.len(), child.span());
@@ -673,9 +666,10 @@ fn prepare<'a>(
styles: StyleChain<'a>,
region: Size,
) -> SourceResult<Preparation<'a>> {
+ 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 95c042ff..39a38b16 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<Axes<Option<GenAlign>>>,
+ #[default(Smart::Custom(Align::START))]
+ pub alignment: Smart<Align>,
/// 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 646eb991..41dede51 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<Fragment> {
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 69a5d952..868b3d50 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 52a2f289..d3fcba8d 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<Align>),
+ Frame(Frame, Axes<FixedAlign>),
}
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::<AlignElem>() {
+ let align = if let Some(align) = block.to::<AlignElem>() {
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 a7bc8a0e..4bbe79a6 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<Smart<Axes<Option<GenAlign>>>>,
+ pub align: Celled<Smart<Align>>,
- /// 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<PartialStroke>,
+ #[default(Some(Stroke::default()))]
+ pub stroke: Option<Stroke>,
/// How much to pad the cells' content.
#[default(Abs::pt(5.0).into())]
@@ -158,7 +154,7 @@ impl Layout for TableElem {
.collect::<SourceResult<_>>()?;
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<T: Default> Default for Celled<T> {
}
impl<T: Reflect> Reflect for Celled<T> {
- 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 d693f100..07f17bb0 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<Spacing>,
/// The term list's children.
@@ -100,6 +93,12 @@ pub struct TermsElem {
pub children: Vec<TermItem>,
}
+#[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 a57a5edc..012a146d 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<Length>,
@@ -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<Option<GenAlign>>,
+ #[default(HAlign::Center + VAlign::Horizon)]
+ pub origin: Align,
/// The content to rotate.
#[required]
@@ -116,8 +109,10 @@ impl Layout for RotateElem {
) -> SourceResult<Fragment> {
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<Option<GenAlign>>,
+ #[default(HAlign::Center + VAlign::Horizon)]
+ pub origin: Align,
/// The content to scale.
#[required]
@@ -183,8 +174,10 @@ impl Layout for ScaleElem {
) -> SourceResult<Fragment> {
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 e9bb72ce..bdb97f84 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::TextElem>()?.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::<meta::Counter>().cloned() {
- counter.call_method(vm, method, args, span)
- } else if let Some(state) = dynamic.downcast::<meta::State>().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 d1bee198..c92f9585 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 aee89a89..bf81597c 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 d74beafe..c33b58e4 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 f576a727..d27031b9 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<Abs>,
- 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 69635c62..fc8a6c79 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 cf1d38e9..6a296203 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
index 25ecf623..0d3c855e 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<Rel<Length>>,
@@ -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 c913592d..abb0da35 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<Frame> {
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<T = Length> {
+pub struct Augment<T: Numeric = Length> {
pub hline: Offsets,
pub vline: Offsets,
- pub stroke: Smart<PartialStroke<T>>,
+ pub stroke: Smart<Stroke<T>>,
}
impl Augment<Abs> {
- 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 5d32af64..578064ba 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());
-
- // Grouping.
- math.define("lr", LrElem::func());
- math.define("abs", abs_func());
- math.define("norm", norm_func());
- math.define("floor", floor_func());
- math.define("ceil", ceil_func());
- math.define("round", round_func());
-
- // Attachments and accents.
- math.define("attach", AttachElem::func());
- math.define("scripts", ScriptsElem::func());
- math.define("limits", LimitsElem::func());
- math.define("accent", AccentElem::func());
- math.define("underline", UnderlineElem::func());
- math.define("overline", OverlineElem::func());
- math.define("underbrace", UnderbraceElem::func());
- math.define("overbrace", OverbraceElem::func());
- math.define("underbracket", UnderbracketElem::func());
- math.define("overbracket", OverbracketElem::func());
- math.define("cancel", CancelElem::func());
-
- // Fractions and matrix-likes.
- math.define("frac", FracElem::func());
- math.define("binom", BinomElem::func());
- math.define("vec", VecElem::func());
- math.define("mat", MatElem::func());
- math.define("cases", CasesElem::func());
-
- // Roots.
- math.define("sqrt", sqrt_func());
- math.define("root", RootElem::func());
-
- // Styles.
- math.define("upright", upright_func());
- math.define("bold", bold_func());
- math.define("italic", italic_func());
- math.define("serif", serif_func());
- math.define("sans", sans_func());
- math.define("cal", cal_func());
- math.define("frak", frak_func());
- math.define("mono", mono_func());
- math.define("bb", bb_func());
-
- math.define("display", display_func());
- math.define("inline", inline_func());
- math.define("script", script_func());
- math.define("sscript", sscript_func());
-
- math.define("class", ClassElem::func());
-
- // Text operators.
- math.define("op", OpElem::func());
+ math.category("math");
+ math.define_elem::<EquationElem>();
+ math.define_elem::<TextElem>();
+ math.define_elem::<LrElem>();
+ math.define_elem::<AttachElem>();
+ math.define_elem::<ScriptsElem>();
+ math.define_elem::<LimitsElem>();
+ math.define_elem::<AccentElem>();
+ math.define_elem::<UnderlineElem>();
+ math.define_elem::<OverlineElem>();
+ math.define_elem::<UnderbraceElem>();
+ math.define_elem::<OverbraceElem>();
+ math.define_elem::<UnderbracketElem>();
+ math.define_elem::<OverbracketElem>();
+ math.define_elem::<CancelElem>();
+ math.define_elem::<FracElem>();
+ math.define_elem::<BinomElem>();
+ math.define_elem::<VecElem>();
+ math.define_elem::<MatElem>();
+ math.define_elem::<CasesElem>();
+ math.define_elem::<RootElem>();
+ math.define_elem::<ClassElem>();
+ math.define_elem::<OpElem>();
+ math.define_func::<abs>();
+ math.define_func::<norm>();
+ math.define_func::<floor>();
+ math.define_func::<ceil>();
+ math.define_func::<round>();
+ math.define_func::<sqrt>();
+ math.define_func::<upright>();
+ math.define_func::<bold>();
+ math.define_func::<italic>();
+ math.define_func::<serif>();
+ math.define_func::<sans>();
+ math.define_func::<cal>();
+ math.define_func::<frak>();
+ math.define_func::<mono>();
+ math.define_func::<bb>();
+ math.define_func::<display>();
+ math.define_func::<inline>();
+ math.define_func::<script>();
+ math.define_func::<sscript>();
+
+ // Text operators, spacings, and symbols.
op::define(&mut math);
-
- // Spacings.
spacing::define(&mut math);
-
- // Symbols.
for (name, symbol) in crate::symbols::SYM {
math.define(*name, symbol.clone());
}
- Module::new("math").with_scope(math)
+ Module::new("math", math)
}
/// A mathematical equation.
///
/// Can be displayed inline with text or as a separate block.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #set text(font: "New Computer Modern")
///
@@ -142,16 +127,13 @@ pub fn module() -> Module {
/// $ sum_(k=1)^n k = (n(n+1)) / 2 $
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: Write mathematical markup within
/// dollar signs to create an equation. Starting and ending the equation with at
/// least one space lifts it into a separate block that is centered
/// horizontally. For more details about math syntax, see the
/// [main math page]($category/math).
-///
-/// Display: Equation
-/// Category: math
-#[element(
+#[elem(
Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable,
Outlinable
)]
@@ -160,7 +142,7 @@ pub struct EquationElem {
#[default(false)]
pub block: bool,
- /// How to [number]($func/numbering) block-level equations.
+ /// How to [number]($numbering) block-level equations.
///
/// ```example
/// #set math.equation(numbering: "(1)")
@@ -216,9 +198,9 @@ impl Synthesize for EquationElem {
impl Show for EquationElem {
#[tracing::instrument(name = "EquationElem::show", skip_all)]
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = self.clone().pack().guarded(Guard::Base(Self::func()));
+ let mut realized = self.clone().pack().guarded(Guard::Base(Self::elem()));
if self.block(styles) {
- realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
+ realized = realized.aligned(Align::CENTER);
}
Ok(realized)
}
@@ -264,7 +246,7 @@ impl Layout for EquationElem {
if block {
if let Some(numbering) = self.numbering(styles) {
let pod = Regions::one(regions.base(), Axes::splat(false));
- let counter = Counter::of(Self::func())
+ let counter = Counter::of(Self::elem())
.display(Some(numbering), false)
.layout(vt, styles, pod)?
.into_frame();
@@ -277,7 +259,7 @@ impl Layout for EquationElem {
};
let height = frame.height().max(counter.height());
- frame.resize(Size::new(width, height), Align::CENTER_HORIZON);
+ frame.resize(Size::new(width, height), Axes::splat(FixedAlign::Center));
let x = if TextElem::dir_in(styles).is_positive() {
frame.width() - counter.width()
@@ -358,7 +340,7 @@ impl Refable for EquationElem {
}
fn counter(&self) -> Counter {
- Counter::of(Self::func())
+ Counter::of(Self::elem())
}
fn numbering(&self) -> Option<Numbering> {
diff --git a/crates/typst-library/src/math/op.rs b/crates/typst-library/src/math/op.rs
index 4016d24f..eed16465 100644
--- a/crates/typst-library/src/math/op.rs
+++ b/crates/typst-library/src/math/op.rs
@@ -4,23 +4,20 @@ use super::*;
/// A text operator in an equation.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// $ tan x = (sin x)/(cos x) $
/// $ op("custom",
/// limits: #true)_(n->oo) n $
/// ```
///
-/// ## Predefined Operators { #predefined }
+/// # Predefined Operators { #predefined }
/// Typst predefines the operators `arccos`, `arcsin`, `arctan`, `arg`, `cos`,
/// `cosh`, `cot`, `coth`, `csc`, `ctg`, `deg`, `det`, `dim`, `exp`, `gcd`,
/// `hom`, `id`, `im`, `inf`, `ker`, `lg`, `lim`, `liminf`, `limsup`, `ln`,
/// `log`, `max`, `min`, `mod`, `Pr`, `sec`, `sin`, `sinc`, `sinh`, `sup`,
/// `tan`, `tanh`, `tg` and `tr`.
-///
-/// Display: Text Operator
-/// Category: math
-#[element(LayoutMath)]
+#[elem(title = "Text Operator", LayoutMath)]
pub struct OpElem {
/// The operator's text.
#[required]
diff --git a/crates/typst-library/src/math/root.rs b/crates/typst-library/src/math/root.rs
index b889f1cb..03d0a212 100644
--- a/crates/typst-library/src/math/root.rs
+++ b/crates/typst-library/src/math/root.rs
@@ -2,14 +2,10 @@ use super::*;
/// A square root.
///
-/// ## Example { #example }
/// ```example
/// $ sqrt(3 - 2 sqrt(2)) = sqrt(2) - 1 $
/// ```
-///
-/// Display: Square Root
-/// Category: math
-#[func]
+#[func(title = "Square Root")]
pub fn sqrt(
/// The expression to take the square root of.
radicand: Content,
@@ -19,14 +15,10 @@ pub fn sqrt(
/// A general root.
///
-/// ## Example { #example }
/// ```example
/// $ root(3, x) $
/// ```
-///
-/// Display: Root
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct RootElem {
/// Which root of the radicand to take.
#[positional]
@@ -129,10 +121,10 @@ fn layout(
frame.push(
line_pos,
FrameItem::Shape(
- Geometry::Line(Point::with_x(radicand.width())).stroked(Stroke {
+ Geometry::Line(Point::with_x(radicand.width())).stroked(FixedStroke {
paint: TextElem::fill_in(ctx.styles()),
thickness,
- ..Stroke::default()
+ ..FixedStroke::default()
}),
span,
),
diff --git a/crates/typst-library/src/math/row.rs b/crates/typst-library/src/math/row.rs
index 687f82b8..cf3a8af2 100644
--- a/crates/typst-library/src/math/row.rs
+++ b/crates/typst-library/src/math/row.rs
@@ -121,7 +121,7 @@ impl MathRow {
pub fn into_frame(self, ctx: &MathContext) -> Frame {
let styles = ctx.styles();
- let align = AlignElem::alignment_in(styles).x.resolve(styles);
+ let align = AlignElem::alignment_in(styles).resolve(styles).x;
self.into_aligned_frame(ctx, &[], align)
}
@@ -137,53 +137,54 @@ impl MathRow {
self,
ctx: &MathContext,
points: &[Abs],
- align: Align,
+ align: FixedAlign,
) -> Frame {
- if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
- let leading = if ctx.style.size >= MathSize::Text {
- ParElem::leading_in(ctx.styles())
- } else {
- TIGHT_LEADING.scaled(ctx)
- };
+ if !self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
+ return self.into_line_frame(points, align);
+ }
- let mut rows: Vec<_> = self.rows();
+ let leading = if ctx.style.size >= MathSize::Text {
+ ParElem::leading_in(ctx.styles())
+ } else {
+ TIGHT_LEADING.scaled(ctx)
+ };
- if matches!(rows.last(), Some(row) if row.0.is_empty()) {
- rows.pop();
- }
+ let mut rows: Vec<_> = self.rows();
- let AlignmentResult { points, width } = alignments(&rows);
- let mut frame = Frame::new(Size::zero());
+ if matches!(rows.last(), Some(row) if row.0.is_empty()) {
+ rows.pop();
+ }
- for (i, row) in rows.into_iter().enumerate() {
- let sub = row.into_line_frame(&points, align);
- let size = frame.size_mut();
- if i > 0 {
- size.y += leading;
- }
+ let AlignmentResult { points, width } = alignments(&rows);
+ let mut frame = Frame::new(Size::zero());
- let mut pos = Point::with_y(size.y);
- if points.is_empty() {
- pos.x = align.position(width - sub.width());
- }
- size.y += sub.height();
- size.x.set_max(sub.width());
- frame.push_frame(pos, sub);
+ for (i, row) in rows.into_iter().enumerate() {
+ let sub = row.into_line_frame(&points, align);
+ let size = frame.size_mut();
+ if i > 0 {
+ size.y += leading;
}
- frame
- } else {
- self.into_line_frame(points, align)
+
+ let mut pos = Point::with_y(size.y);
+ if points.is_empty() {
+ pos.x = align.position(width - sub.width());
+ }
+ size.y += sub.height();
+ size.x.set_max(sub.width());
+ frame.push_frame(pos, sub);
}
+
+ frame
}
- fn into_line_frame(self, points: &[Abs], align: Align) -> Frame {
+ fn into_line_frame(self, points: &[Abs], align: FixedAlign) -> Frame {
let ascent = self.ascent();
let mut frame = Frame::new(Size::new(Abs::zero(), ascent + self.descent()));
frame.set_baseline(ascent);
let mut next_x = {
let mut widths = Vec::new();
- if !points.is_empty() && align != Align::Left {
+ if !points.is_empty() && align != FixedAlign::Start {
let mut width = Abs::zero();
for fragment in self.iter() {
if matches!(fragment, MathFragment::Align) {
@@ -201,8 +202,10 @@ impl MathRow {
let mut point_widths = points.iter().copied().zip(widths);
let mut alternator = LeftRightAlternator::Right;
move || match align {
- Align::Left => prev_points.next(),
- Align::Right => point_widths.next().map(|(point, width)| point - width),
+ FixedAlign::Start => prev_points.next(),
+ FixedAlign::End => {
+ point_widths.next().map(|(point, width)| point - width)
+ }
_ => point_widths
.next()
.zip(prev_points.next())
diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs
index 35a4cfdb..4d80a235 100644
--- a/crates/typst-library/src/math/style.rs
+++ b/crates/typst-library/src/math/style.rs
@@ -2,13 +2,9 @@ use super::*;
/// Bold font style in math.
///
-/// ## Example { #example }
/// ```example
/// $ bold(A) := B^+ $
/// ```
-///
-/// Display: Bold
-/// Category: math
#[func]
pub fn bold(
/// The content to style.
@@ -19,13 +15,9 @@ pub fn bold(
/// Upright (non-italic) font style in math.
///
-/// ## Example { #example }
/// ```example
/// $ upright(A) != A $
/// ```
-///
-/// Display: Upright
-/// Category: math
#[func]
pub fn upright(
/// The content to style.
@@ -37,9 +29,6 @@ pub fn upright(
/// Italic font style in math.
///
/// For roman letters and greek lowercase letters, this is already the default.
-///
-/// Display: Italic
-/// Category: math
#[func]
pub fn italic(
/// The content to style.
@@ -50,9 +39,6 @@ pub fn italic(
/// Serif (roman) font style in math.
///
/// This is already the default.
-///
-/// Display: Serif
-/// Category: math
#[func]
pub fn serif(
/// The content to style.
@@ -63,14 +49,10 @@ pub fn serif(
/// Sans-serif font style in math.
///
-/// ## Example { #example }
/// ```example
/// $ sans(A B C) $
/// ```
-///
-/// Display: Sans-serif
-/// Category: math
-#[func]
+#[func(title = "Sans Serif")]
pub fn sans(
/// The content to style.
body: Content,
@@ -80,14 +62,10 @@ pub fn sans(
/// Calligraphic font style in math.
///
-/// ## Example { #example }
/// ```example
/// Let $cal(P)$ be the set of ...
/// ```
-///
-/// Display: Calligraphic
-/// Category: math
-#[func]
+#[func(title = "Calligraphic")]
pub fn cal(
/// The content to style.
body: Content,
@@ -97,14 +75,10 @@ pub fn cal(
/// Fraktur font style in math.
///
-/// ## Example { #example }
/// ```example
/// $ frak(P) $
/// ```
-///
-/// Display: Fraktur
-/// Category: math
-#[func]
+#[func(title = "Fraktur")]
pub fn frak(
/// The content to style.
body: Content,
@@ -114,14 +88,10 @@ pub fn frak(
/// Monospace font style in math.
///
-/// ## Example { #example }
/// ```example
/// $ mono(x + y = z) $
/// ```
-///
-/// Display: Monospace
-/// Category: math
-#[func]
+#[func(title = "Monospace")]
pub fn mono(
/// The content to style.
body: Content,
@@ -134,16 +104,12 @@ pub fn mono(
/// For uppercase latin letters, blackboard bold is additionally available
/// through [symbols]($category/symbols/sym) of the form `NN` and `RR`.
///
-/// ## Example { #example }
/// ```example
/// $ bb(b) $
/// $ bb(N) = NN $
/// $ f: NN -> RR $
/// ```
-///
-/// Display: Blackboard Bold
-/// Category: math
-#[func]
+#[func(title = "Blackboard Bold")]
pub fn bb(
/// The content to style.
body: Content,
@@ -155,14 +121,10 @@ pub fn bb(
///
/// This is the normal size for block equations.
///
-/// ## Example { #example }
/// ```example
/// $sum_i x_i/2 = display(sum_i x_i/2)$
/// ```
-///
-/// Display: Display Size
-/// Category: math
-#[func]
+#[func(title = "Display Size")]
pub fn display(
/// The content to size.
body: Content,
@@ -182,15 +144,11 @@ pub fn display(
///
/// This is the normal size for inline equations.
///
-/// ## Example { #example }
/// ```example
/// $ sum_i x_i/2
/// = inline(sum_i x_i/2) $
/// ```
-///
-/// Display: Inline Size
-/// Category: math
-#[func]
+#[func(title = "Inline Size")]
pub fn inline(
/// The content to size.
body: Content,
@@ -210,14 +168,10 @@ pub fn inline(
///
/// This is the smaller size used in powers or sub- or superscripts.
///
-/// ## Example { #example }
/// ```example
/// $sum_i x_i/2 = script(sum_i x_i/2)$
/// ```
-///
-/// Display: Script Size
-/// Category: math
-#[func]
+#[func(title = "Script Size")]
pub fn script(
/// The content to size.
body: Content,
@@ -238,14 +192,10 @@ pub fn script(
/// This is the smallest size, used in second-level sub- and superscripts
/// (script of the script).
///
-/// ## Example { #example }
/// ```example
/// $sum_i x_i/2 = sscript(sum_i x_i/2)$
/// ```
-///
-/// Display: Script-Script Size
-/// Category: math
-#[func]
+#[func(title = "Script-Script Size")]
pub fn sscript(
/// The content to size.
body: Content,
@@ -262,10 +212,7 @@ pub fn sscript(
}
/// A font variant in math.
-///
-/// Display: Bold
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct MathStyleElem {
/// The content to style.
#[required]
diff --git a/crates/typst-library/src/math/underover.rs b/crates/typst-library/src/math/underover.rs
index 796c9ebc..3e8dba1a 100644
--- a/crates/typst-library/src/math/underover.rs
+++ b/crates/typst-library/src/math/underover.rs
@@ -11,14 +11,10 @@ enum LineKind {
/// A horizontal line under content.
///
-/// ## Example { #example }
/// ```example
/// $ underline(1 + 2 + ... + 5) $
/// ```
-///
-/// Display: Underline
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct UnderlineElem {
/// The content above the line.
#[required]
@@ -34,14 +30,10 @@ impl LayoutMath for UnderlineElem {
/// A horizontal line over content.
///
-/// ## Example { #example }
/// ```example
/// $ overline(1 + 2 + ... + 5) $
/// ```
-///
-/// Display: Overline
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct OverlineElem {
/// The content below the line.
#[required]
@@ -103,10 +95,10 @@ fn layout_underoverline(
frame.push(
line_pos,
FrameItem::Shape(
- Geometry::Line(Point::with_x(width)).stroked(Stroke {
+ Geometry::Line(Point::with_x(width)).stroked(FixedStroke {
paint: TextElem::fill_in(ctx.styles()),
thickness: bar_height,
- ..Stroke::default()
+ ..FixedStroke::default()
}),
span,
),
@@ -119,14 +111,10 @@ fn layout_underoverline(
/// A horizontal brace under content, with an optional annotation below.
///
-/// ## Example { #example }
/// ```example
/// $ underbrace(1 + 2 + ... + 5, "numbers") $
/// ```
-///
-/// Display: Underbrace
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct UnderbraceElem {
/// The content above the brace.
#[required]
@@ -154,14 +142,10 @@ impl LayoutMath for UnderbraceElem {
/// A horizontal brace over content, with an optional annotation above.
///
-/// ## Example { #example }
/// ```example
/// $ overbrace(1 + 2 + ... + 5, "numbers") $
/// ```
-///
-/// Display: Overbrace
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct OverbraceElem {
/// The content below the brace.
#[required]
@@ -189,14 +173,10 @@ impl LayoutMath for OverbraceElem {
/// A horizontal bracket under content, with an optional annotation below.
///
-/// ## Example { #example }
/// ```example
/// $ underbracket(1 + 2 + ... + 5, "numbers") $
/// ```
-///
-/// Display: Underbracket
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct UnderbracketElem {
/// The content above the bracket.
#[required]
@@ -224,14 +204,10 @@ impl LayoutMath for UnderbracketElem {
/// A horizontal bracket over content, with an optional annotation above.
///
-/// ## Example { #example }
/// ```example
/// $ overbracket(1 + 2 + ... + 5, "numbers") $
/// ```
-///
-/// Display: Overbracket
-/// Category: math
-#[element(LayoutMath)]
+#[elem(LayoutMath)]
pub struct OverbracketElem {
/// The content below the bracket.
#[required]
@@ -294,7 +270,7 @@ fn layout_underoverspreader(
baseline = rows.len() - 1;
}
- let frame = stack(ctx, rows, Align::Center, gap, baseline);
+ let frame = stack(ctx, rows, FixedAlign::Center, gap, baseline);
ctx.push(FrameFragment::new(ctx, frame).with_class(body_class));
Ok(())
@@ -307,7 +283,7 @@ fn layout_underoverspreader(
pub(super) fn stack(
ctx: &MathContext,
rows: Vec<MathRow>,
- align: Align,
+ align: FixedAlign,
gap: Abs,
baseline: usize,
) -> Frame {
diff --git a/crates/typst-library/src/meta/bibliography.rs b/crates/typst-library/src/meta/bibliography.rs
index d871db23..4f1cc32c 100644
--- a/crates/typst-library/src/meta/bibliography.rs
+++ b/crates/typst-library/src/meta/bibliography.rs
@@ -30,8 +30,8 @@ use crate::text::TextElem;
///
/// As soon as you add a bibliography somewhere in your document, you can start
/// citing things with reference syntax (`[@key]`) or explicit calls to the
-/// [citation]($func/cite) function (`[#cite("key")]`). The bibliography will
-/// only show entries for works that were referenced in the document.
+/// [citation]($cite) function (`[#cite("key")]`). The bibliography will only
+/// show entries for works that were referenced in the document.
///
/// # Example
/// ```example
@@ -43,10 +43,7 @@ use crate::text::TextElem;
///
/// #bibliography("works.bib")
/// ```
-///
-/// Display: Bibliography
-/// Category: meta
-#[element(Locatable, Synthesize, Show, Finalize, LocalName)]
+#[elem(Locatable, Synthesize, Show, Finalize, LocalName)]
pub struct BibliographyElem {
/// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file.
#[required]
@@ -78,8 +75,8 @@ pub struct BibliographyElem {
/// The title of the bibliography.
///
- /// - When set to `{auto}`, an appropriate title for the [text
- /// language]($func/text.lang) will be used. This is the default.
+ /// - When set to `{auto}`, an appropriate title for the
+ /// [text language]($text.lang) will be used. This is the default.
/// - When set to `{none}`, the bibliography will not have a title.
/// - A custom title can be set by passing content.
///
@@ -109,7 +106,7 @@ cast! {
impl BibliographyElem {
/// Find the document's bibliography.
pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> {
- let mut iter = introspector.query(&Self::func().select()).into_iter();
+ let mut iter = introspector.query(&Self::elem().select()).into_iter();
let Some(elem) = iter.next() else {
bail!("the document does not contain a bibliography");
};
@@ -124,7 +121,7 @@ impl BibliographyElem {
/// Whether the bibliography contains the given key.
pub fn has(vt: &Vt, key: &str) -> bool {
vt.introspector
- .query(&Self::func().select())
+ .query(&Self::elem().select())
.into_iter()
.flat_map(|elem| {
let elem = elem.to::<Self>().unwrap();
@@ -289,8 +286,8 @@ impl BibliographyStyle {
/// Cite a work from the bibliography.
///
-/// Before you starting citing, you need to add a
-/// [bibliography]($func/bibliography) somewhere in your document.
+/// Before you starting citing, you need to add a [bibliography]($bibliography)
+/// somewhere in your document.
///
/// # Example
/// ```example
@@ -304,13 +301,10 @@ impl BibliographyStyle {
/// ```
///
/// # Syntax
-/// This function indirectly has dedicated syntax. [References]($func/ref)
-/// can be used to cite works from the bibliography. The label then
-/// corresponds to the citation key.
-///
-/// Display: Citation
-/// Category: meta
-#[element(Locatable, Synthesize, Show)]
+/// This function indirectly has dedicated syntax. [References]($ref) can be
+/// used to cite works from the bibliography. The label then corresponds to the
+/// citation key.
+#[elem(Locatable, Synthesize, Show)]
pub struct CiteElem {
/// The citation keys that identify the elements that shall be cited in
/// the bibliography.
@@ -329,7 +323,6 @@ pub struct CiteElem {
///
/// #bibliography("works.bib")
/// ```
- #[positional]
pub supplement: Option<Content>,
/// Whether the citation should include brackets.
@@ -435,8 +428,8 @@ impl Works {
let citations = vt
.introspector
.query(&Selector::Or(eco_vec![
- RefElem::func().select(),
- CiteElem::func().select(),
+ RefElem::elem().select(),
+ CiteElem::elem().select(),
]))
.into_iter()
.map(|elem| match elem.to::<RefElem>() {
diff --git a/crates/typst-library/src/meta/context.rs b/crates/typst-library/src/meta/context.rs
index a42c6980..3a82a925 100644
--- a/crates/typst-library/src/meta/context.rs
+++ b/crates/typst-library/src/meta/context.rs
@@ -2,9 +2,9 @@ use crate::prelude::*;
/// Provides access to the location of content.
///
-/// This is useful in combination with [queries]($func/query),
-/// [counters]($func/counter), [state]($func/state), and [links]($func/link).
-/// See their documentation for more details.
+/// This is useful in combination with [queries]($query), [counters]($counter),
+/// [state]($state), and [links]($link). See their documentation for more
+/// details.
///
/// ```example
/// #locate(loc => [
@@ -12,44 +12,10 @@ use crate::prelude::*;
/// #loc.position()!
/// ])
/// ```
-///
-/// ## Methods
-/// ### page()
-/// Returns the page number for this location.
-///
-/// Note that this does not return the value of the [page counter]($func/counter)
-/// at this location, but the true page number (starting from one).
-///
-/// If you want to know the value of the page counter, use
-/// `{counter(page).at(loc)}` instead.
-///
-/// - returns: integer
-///
-/// ### position()
-/// Returns a dictionary with the page number and the x, y position for this
-/// location. The page number starts at one and the coordinates are measured
-/// from the top-left of the page.
-///
-/// If you only need the page number, use `page()` instead as it allows Typst
-/// to skip unnecessary work.
-///
-/// - returns: dictionary
-///
-/// ### page-numbering()
-/// Returns the page numbering pattern of the page at this location. This can be
-/// used when displaying the page counter in order to obtain the local numbering.
-/// This is useful if you are building custom indices or outlines.
-///
-/// If the page numbering is set to `none` at that location, this function returns `none`.
-///
-/// - returns: string or function or none
-///
-/// Display: Locate
-/// Category: meta
#[func]
pub fn locate(
- /// A function that receives a `location`. Its return value is displayed
- /// in the document.
+ /// A function that receives a [`location`]($location). Its return value is
+ /// displayed in the document.
///
/// This function is called once for each time the content returned by
/// `locate` appears in the document. That makes it possible to generate
@@ -60,10 +26,7 @@ pub fn locate(
}
/// Executes a `locate` call.
-///
-/// Display: Locate
-/// Category: special
-#[element(Locatable, Show)]
+#[elem(Locatable, Show)]
struct LocateElem {
/// The function to call with the location.
#[required]
@@ -83,9 +46,9 @@ impl Show for LocateElem {
/// Provides access to active styles.
///
/// The styles are currently opaque and only useful in combination with the
-/// [`measure`]($func/measure) function. See its documentation for more details.
-/// In the future, the provided styles might also be directly accessed to look
-/// up styles defined by [set rules]($styling/#set-rules).
+/// [`measure`]($measure) function. See its documentation for more details. In
+/// the future, the provided styles might also be directly accessed to look up
+/// styles defined by [set rules]($styling/#set-rules).
///
/// ```example
/// #let thing(body) = style(styles => {
@@ -96,9 +59,6 @@ impl Show for LocateElem {
/// #thing[Hey] \
/// #thing[Welcome]
/// ```
-///
-/// Display: Style
-/// Category: meta
#[func]
pub fn style(
/// A function to call with the styles. Its return value is displayed
@@ -113,10 +73,7 @@ pub fn style(
}
/// Executes a style access.
-///
-/// Display: Style
-/// Category: special
-#[element(Show)]
+#[elem(Show)]
struct StyleElem {
/// The function to call with the styles.
#[required]
@@ -134,10 +91,8 @@ impl Show for StyleElem {
/// (width and height).
///
/// The given function must accept a single parameter, `size`, which is a
-/// dictionary with keys `width` and `height`, both of type
-/// [`length`]($type/length).
+/// dictionary with keys `width` and `height`, both of type [`length`]($length).
///
-
/// ```example
/// #let text = lorem(30)
/// #layout(size => style(styles => [
@@ -155,9 +110,9 @@ impl Show for StyleElem {
/// and a height of `{400pt}`, then the specified function will be given the
/// parameter `{(width: 800pt, height: 400pt)}`. If it placed directly into the
/// page it receives the page's dimensions minus its margins. This is mostly
-/// useful in combination with [measurement]($func/measure).
+/// useful in combination with [measurement]($measure).
///
-/// You can also use this function to resolve [`ratio`]($type/ratio) to fixed
+/// You can also use this function to resolve [`ratio`]($ratio) to fixed
/// lengths. This might come in handy if you're building your own layout
/// abstractions.
///
@@ -170,16 +125,13 @@ impl Show for StyleElem {
///
/// Note that this function will provide an infinite width or height if one of
/// the page width or height is `auto`, respectively.
-///
-/// Display: Layout
-/// Category: meta
#[func]
pub fn layout(
/// A function to call with the outer container's size. Its return value is
/// displayed in the document.
///
- /// The container's size is given as a [dictionary]($type/dictionary) with
- /// the keys `width` and `height`.
+ /// The container's size is given as a [dictionary]($dictionary) with the
+ /// keys `width` and `height`.
///
/// This function is called once for each time the content returned by
/// `layout` appears in the document. That makes it possible to generate
@@ -190,10 +142,7 @@ pub fn layout(
}
/// Executes a `layout` call.
-///
-/// Display: Layout
-/// Category: special
-#[element(Layout)]
+#[elem(Layout)]
struct LayoutElem {
/// The function to call with the outer container's (or page's) size.
#[required]
diff --git a/crates/typst-library/src/meta/counter.rs b/crates/typst-library/src/meta/counter.rs
index 6c437469..a2a63e81 100644
--- a/crates/typst-library/src/meta/counter.rs
+++ b/crates/typst-library/src/meta/counter.rs
@@ -17,15 +17,14 @@ use crate::prelude::*;
/// headings, figures, and more. Moreover, you can define custom counters for
/// other things you want to count.
///
-/// ## Displaying a counter { #displaying }
+/// # Displaying a counter { #displaying }
/// To display the current value of the heading counter, you call the `counter`
/// function with the `key` set to `heading` and then call the `display` method
/// on the counter. To see any output, you also have to enable heading
-/// [numbering]($func/heading.numbering).
+/// [numbering]($heading.numbering).
///
/// The `display` method optionally takes an argument telling it how to format
-/// the counter. This can be a [numbering pattern or a
-/// function]($func/numbering).
+/// the counter. This can be a [numbering pattern or a function]($numbering).
///
/// ```example
/// #set heading(numbering: "1.")
@@ -41,7 +40,7 @@ use crate::prelude::*;
/// #counter(heading).display("I")
/// ```
///
-/// ## Modifying a counter { #modifying }
+/// # Modifying a counter { #modifying }
/// To modify a counter, you can use the `step` and `update` methods:
///
/// - The `step` method increases the value of the counter by one. Because
@@ -76,7 +75,6 @@ use crate::prelude::*;
/// Still at #counter(heading).display().
/// ```
///
-/// ## Custom counters { #custom-counters }
/// To define your own counter, call the `counter` function with a string as a
/// key. This key identifies the counter globally.
///
@@ -89,7 +87,7 @@ use crate::prelude::*;
/// #mine.display() \
/// ```
///
-/// ## How to step { #how-to-step }
+/// # How to step
/// When you define and use a custom counter, in general, you should first step
/// the counter and then display it. This way, the stepping behaviour of a
/// counter can depend on the element it is stepped for. If you were writing a
@@ -118,7 +116,7 @@ use crate::prelude::*;
/// they always start at zero. This way, they are at one for the first display
/// (which happens after the first step).
///
-/// ## Page counter { #page-counter }
+/// # Page counter
/// The page counter is special. It is automatically stepped at each pagebreak.
/// But like other counters, you can also step it manually. For example, you
/// could have Roman page numbers for your preface, then switch to Arabic page
@@ -145,7 +143,7 @@ use crate::prelude::*;
/// Arabic numbers.
/// ```
///
-/// ## Time travel { #time-travel }
+/// # Time travel
/// Counters can travel through time! You can find out the final value of the
/// counter before it is reached and even determine what the value was at any
/// particular location in the document.
@@ -177,16 +175,16 @@ use crate::prelude::*;
///
/// Let's dissect what happens in the example above:
///
-/// - We call [`locate`]($func/locate) to get access to the current location in
-/// the document. We then pass this location to our counter's `at` method to
-/// get its value at the current location. The `at` method always returns an
-/// array because counters can have multiple levels. As the counter starts at
-/// zero, the first value is thus `{(0,)}`.
+/// - We call [`locate`]($locate) to get access to the current location in the
+/// document. We then pass this location to our counter's `at` method to get
+/// its value at the current location. The `at` method always returns an array
+/// because counters can have multiple levels. As the counter starts at zero,
+/// the first value is thus `{(0,)}`.
///
-/// - We now [`query`]($func/query) the document for all elements with the
+/// - We now [`query`]($query) the document for all elements with the
/// `{<intro>}` label. The result is an array from which we extract the first
-/// (and only) element's [location]($type/content.location). We then look up
-/// the value of the counter at that location. The first update to the counter
+/// (and only) element's [location]($content.location). We then look up the
+/// value of the counter at that location. The first update to the counter
/// sets it to `{0 + 3 = 3}`. At the introduction heading, the value is thus
/// `{(3,)}`.
///
@@ -196,180 +194,31 @@ use crate::prelude::*;
/// which one doesn't matter. After the heading follow two calls to `step()`,
/// so the final value is `{(5,)}`.
///
-/// ## Other kinds of state { #other-state }
-/// The `counter` function is closely related to [state]($func/state) function.
-/// Read its documentation for more details on state management in Typst and
-/// why it doesn't just use normal variables for counters.
-///
-/// ## Methods
-/// ### display()
-/// Displays the value of the counter.
-///
-/// - numbering: string or function (positional)
-/// A [numbering pattern or a function]($func/numbering), which specifies how
-/// to display the counter. If given a function, that function receives each
-/// number of the counter as a separate argument. If the amount of numbers
-/// varies, e.g. for the heading argument, you can use an
-/// [argument sink]($type/arguments).
-///
-/// If this is omitted, displays the counter with the numbering style for the
-/// counted element or with the pattern `{"1.1"}` if no such style exists.
-///
-/// - both: boolean (named)
-/// If enabled, displays the current and final top-level count together. Both
-/// can be styled through a single numbering pattern. This is used by the page
-/// numbering property to display the current and total number of pages when a
-/// pattern like `{"1 / 1"}` is given.
-///
-/// - returns: content
-///
-/// ### step()
-/// Increases the value of the counter by one.
-///
-/// The update will be in effect at the position where the returned content is
-/// inserted into the document. If you don't put the output into the document,
-/// nothing happens! This would be the case, for example, if you write
-/// `{let _ = counter(page).step()}`. Counter updates are always applied in
-/// layout order and in that case, Typst wouldn't know when to step the counter.
-///
-/// - level: integer (named)
-/// The depth at which to step the counter. Defaults to `{1}`.
-///
-/// - returns: content
-///
-/// ### update()
-/// Updates the value of the counter.
-///
-/// Just like with `step`, the update only occurs if you put the resulting
-/// content into the document.
-///
-/// - value: integer or array or function (positional, required)
-/// If given an integer or array of integers, sets the counter to that value.
-/// If given a function, that function receives the previous counter value
-/// (with each number as a separate argument) and has to return the new
-/// value (integer or array).
-///
-/// - returns: content
-///
-/// ### at()
-/// Gets the value of the counter at the given location. Always returns an
-/// array of integers, even if the counter has just one number.
-///
-/// - location: location (positional, required)
-/// The location at which the counter value should be retrieved. A suitable
-/// location can be retrieved from [`locate`]($func/locate) or
-/// [`query`]($func/query).
-///
-/// - returns: array
-///
-/// ### final()
-/// Gets the value of the counter at the end of the document. Always returns an
-/// array of integers, even if the counter has just one number.
-///
-/// - location: location (positional, required)
-/// Can be an arbitrary location, as its value is irrelevant for the method's
-/// return value. Why is it required then? Typst has to evaluate parts of your
-/// code multiple times to determine all counter values. By only allowing this
-/// method within [`locate`]($func/locate) calls, the amount of code that can
-/// depend on the method's result is reduced. If you could call `final`
-/// directly at the top level of a module, the evaluation of the whole module
-/// and its exports could depend on the counter's value.
-///
-/// - returns: array
-///
-/// Display: Counter
-/// Category: meta
-#[func]
-pub fn counter(
- /// The key that identifies this counter.
- ///
- /// - If it is a string, creates a custom counter that is only affected by
- /// manual updates,
- /// - If this is a `{<label>}`, counts through all elements with that label,
- /// - If this is an element function or selector, counts through its elements,
- /// - If this is the [`page`]($func/page) function, counts through pages.
- key: CounterKey,
-) -> Counter {
- Counter::new(key)
-}
-
-/// Counts through pages, elements, and more.
+/// # Other kinds of state { #other-state }
+/// The `counter` type is closely related to [state]($state) type. Read its
+/// documentation for more details on state management in Typst and why it
+/// doesn't just use normal variables for counters.
+#[ty(scope)]
#[derive(Clone, PartialEq, Hash)]
pub struct Counter(CounterKey);
impl Counter {
- /// Create a new counter from a key.
- pub fn new(key: CounterKey) -> Self {
+ /// Create a new counter identified by a key.
+ pub fn new(key: CounterKey) -> Counter {
Self(key)
}
/// The counter for the given element.
- pub fn of(func: ElemFunc) -> Self {
- Self::new(CounterKey::Selector(Selector::Elem(func, None)))
- }
-
- /// Call a method on counter.
- #[tracing::instrument(skip(vm))]
- pub fn call_method(
- self,
- vm: &mut Vm,
- method: &str,
- mut args: Args,
- span: Span,
- ) -> SourceResult<Value> {
- let value = match method {
- "display" => self
- .display(args.eat()?, args.named("both")?.unwrap_or(false))
- .into_value(),
- "step" => self
- .update(CounterUpdate::Step(
- args.named("level")?.unwrap_or(NonZeroUsize::ONE),
- ))
- .into_value(),
- "update" => self.update(args.expect("value or function")?).into_value(),
- "at" => self.at(&mut vm.vt, args.expect("location")?)?.into_value(),
- "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into_value(),
- _ => bail!(span, "type counter has no method `{}`", method),
- };
- args.finish()?;
- Ok(value)
- }
-
- /// Display the current value of the counter.
- pub fn display(self, numbering: Option<Numbering>, both: bool) -> Content {
- DisplayElem::new(self, numbering, both).pack()
- }
-
- /// Get the value of the state at the given location.
- pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
- let sequence = self.sequence(vt)?;
- let offset = vt.introspector.query(&self.selector().before(location, true)).len();
- let (mut state, page) = sequence[offset].clone();
- if self.is_page() {
- let delta = vt.introspector.page(location).get().saturating_sub(page.get());
- state.step(NonZeroUsize::ONE, delta);
- }
-
- Ok(state)
- }
-
- /// Get the value of the state at the final location.
- pub fn final_(&self, vt: &mut Vt, _: Location) -> SourceResult<CounterState> {
- let sequence = self.sequence(vt)?;
- let (mut state, page) = sequence.last().unwrap().clone();
- if self.is_page() {
- let delta = vt.introspector.pages().get().saturating_sub(page.get());
- state.step(NonZeroUsize::ONE, delta);
- }
- Ok(state)
+ pub fn of(func: Element) -> Self {
+ Self::construct(CounterKey::Selector(Selector::Elem(func, None)))
}
- /// Get the current and final value of the state combined in one state.
+ /// Gets the current and final value of the state combined in one state.
pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
let sequence = self.sequence(vt)?;
let offset = vt
.introspector
- .query(&Selector::before(self.selector(), location, true))
+ .query(&self.selector().before(location.into(), true))
.len();
let (mut at_state, at_page) = sequence[offset].clone();
let (mut final_state, final_page) = sequence.last().unwrap().clone();
@@ -384,12 +233,7 @@ impl Counter {
Ok(CounterState(smallvec![at_state.first(), final_state.first()]))
}
- /// Produce content that performs a state update.
- pub fn update(self, update: CounterUpdate) -> Content {
- UpdateElem::new(self.0, update).pack()
- }
-
- /// Produce the whole sequence of counter states.
+ /// Produces the whole sequence of counter states.
///
/// This has to happen just once for all counters, cutting down the number
/// of counter updates from quadratic to linear.
@@ -462,7 +306,7 @@ impl Counter {
/// The selector relevant for this counter's updates.
fn selector(&self) -> Selector {
let mut selector =
- Selector::Elem(UpdateElem::func(), Some(dict! { "key" => self.0.clone() }));
+ Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.0.clone() }));
if let CounterKey::Selector(key) = &self.0 {
selector = Selector::Or(eco_vec![selector, key.clone()]);
@@ -477,6 +321,140 @@ impl Counter {
}
}
+#[scope]
+impl Counter {
+ /// Create a new counter identified by a key.
+ #[func(constructor)]
+ pub fn construct(
+ /// The key that identifies this counter.
+ ///
+ /// - If it is a string, creates a custom counter that is only affected
+ /// by manual updates,
+ /// - If this is a `{<label>}`, counts through all elements with that
+ /// label,
+ /// - If this is an element function or selector, counts through its
+ /// elements,
+ /// - If this is the [`page`]($page) function, counts through pages.
+ key: CounterKey,
+ ) -> Counter {
+ Self(key)
+ }
+
+ /// Displays the current value of the counter.
+ #[func]
+ pub fn display(
+ self,
+ /// A [numbering pattern or a function]($numbering), which specifies how
+ /// to display the counter. If given a function, that function receives
+ /// each number of the counter as a separate argument. If the amount of
+ /// numbers varies, e.g. for the heading argument, you can use an
+ /// [argument sink]($arguments).
+ ///
+ /// If this is omitted, displays the counter with the numbering style
+ /// for the counted element or with the pattern `{"1.1"}` if no such
+ /// style exists.
+ #[default]
+ numbering: Option<Numbering>,
+ /// If enabled, displays the current and final top-level count together.
+ /// Both can be styled through a single numbering pattern. This is used
+ /// by the page numbering property to display the current and total
+ /// number of pages when a pattern like `{"1 / 1"}` is given.
+ #[named]
+ #[default(false)]
+ both: bool,
+ ) -> Content {
+ DisplayElem::new(self, numbering, both).pack()
+ }
+
+ /// Increases the value of the counter by one.
+ ///
+ /// The update will be in effect at the position where the returned content
+ /// is inserted into the document. If you don't put the output into the
+ /// document, nothing happens! This would be the case, for example, if you
+ /// write `{let _ = counter(page).step()}`. Counter updates are always
+ /// applied in layout order and in that case, Typst wouldn't know when to
+ /// step the counter.
+ #[func]
+ pub fn step(
+ self,
+ /// The depth at which to step the counter. Defaults to `{1}`.
+ #[named]
+ #[default(NonZeroUsize::ONE)]
+ level: NonZeroUsize,
+ ) -> Content {
+ self.update(CounterUpdate::Step(level))
+ }
+
+ /// Updates the value of the counter.
+ ///
+ /// Just like with `step`, the update only occurs if you put the resulting
+ /// content into the document.
+ #[func]
+ pub fn update(
+ self,
+ /// If given an integer or array of integers, sets the counter to that
+ /// value. If given a function, that function receives the previous
+ /// counter value (with each number as a separate argument) and has to
+ /// return the new value (integer or array).
+ update: CounterUpdate,
+ ) -> Content {
+ UpdateElem::new(self.0, update).pack()
+ }
+
+ /// Gets the value of the counter at the given location. Always returns an
+ /// array of integers, even if the counter has just one number.
+ #[func]
+ pub fn at(
+ &self,
+ /// The virtual typesetter.
+ vt: &mut Vt,
+ /// The location at which the counter value should be retrieved. A
+ /// suitable location can be retrieved from [`locate`]($locate) or
+ /// [`query`]($query).
+ location: Location,
+ ) -> SourceResult<CounterState> {
+ let sequence = self.sequence(vt)?;
+ let offset = vt
+ .introspector
+ .query(&self.selector().before(location.into(), true))
+ .len();
+ let (mut state, page) = sequence[offset].clone();
+ if self.is_page() {
+ let delta = vt.introspector.page(location).get().saturating_sub(page.get());
+ state.step(NonZeroUsize::ONE, delta);
+ }
+
+ Ok(state)
+ }
+
+ /// Gets the value of the counter at the end of the document. Always returns
+ /// an array of integers, even if the counter has just one number.
+ #[func]
+ pub fn final_(
+ &self,
+ /// The virtual typesetter.
+ vt: &mut Vt,
+ /// Can be an arbitrary location, as its value is irrelevant for the
+ /// method's return value. Why is it required then? Typst has to
+ /// evaluate parts of your code multiple times to determine all counter
+ /// values. By only allowing this method within [`locate`]($locate)
+ /// calls, the amount of code that can depend on the method's result is
+ /// reduced. If you could call `final` directly at the top level of a
+ /// module, the evaluation of the whole module and its exports could
+ /// depend on the counter's value.
+ location: Location,
+ ) -> SourceResult<CounterState> {
+ let _ = location;
+ let sequence = self.sequence(vt)?;
+ let (mut state, page) = sequence.last().unwrap().clone();
+ if self.is_page() {
+ let delta = vt.introspector.pages().get().saturating_sub(page.get());
+ state.step(NonZeroUsize::ONE, delta);
+ }
+ Ok(state)
+ }
+}
+
impl Debug for Counter {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("counter(")?;
@@ -486,7 +464,7 @@ impl Debug for Counter {
}
cast! {
- type Counter: "counter",
+ type Counter,
}
/// Identifies a counter.
@@ -504,14 +482,14 @@ pub enum CounterKey {
cast! {
CounterKey,
self => match self {
- Self::Page => PageElem::func().into_value(),
+ Self::Page => PageElem::elem().into_value(),
Self::Selector(v) => v.into_value(),
Self::Str(v) => v.into_value(),
},
v: Str => Self::Str(v),
v: Label => Self::Selector(Selector::Label(v)),
- v: ElemFunc => {
- if v == PageElem::func() {
+ v: Element => {
+ if v == PageElem::elem() {
Self::Page
} else {
Self::Selector(LocatableSelector::from_value(v.into_value())?.0)
@@ -531,6 +509,7 @@ impl Debug for CounterKey {
}
/// An update to perform on a counter.
+#[ty]
#[derive(Clone, PartialEq, Hash)]
pub enum CounterUpdate {
/// Set the counter to the specified state.
@@ -548,7 +527,7 @@ impl Debug for CounterUpdate {
}
cast! {
- type CounterUpdate: "counter update",
+ type CounterUpdate,
v: CounterState => Self::Set(v),
v: Func => Self::Func(v),
}
@@ -612,10 +591,7 @@ cast! {
}
/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
+#[elem(Locatable, Show)]
struct DisplayElem {
/// The counter.
#[required]
@@ -643,11 +619,11 @@ impl Show for DisplayElem {
return None;
};
- if func == HeadingElem::func() {
+ if func == HeadingElem::elem() {
HeadingElem::numbering_in(styles)
- } else if func == FigureElem::func() {
+ } else if func == FigureElem::elem() {
FigureElem::numbering_in(styles)
- } else if func == EquationElem::func() {
+ } else if func == EquationElem::elem() {
EquationElem::numbering_in(styles)
} else {
None
@@ -667,10 +643,7 @@ impl Show for DisplayElem {
}
/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
+#[elem(Locatable, Show)]
struct UpdateElem {
/// The key that identifies the counter.
#[required]
diff --git a/crates/typst-library/src/meta/document.rs b/crates/typst-library/src/meta/document.rs
index db036e0a..66f8aeb5 100644
--- a/crates/typst-library/src/meta/document.rs
+++ b/crates/typst-library/src/meta/document.rs
@@ -17,10 +17,7 @@ use crate::prelude::*;
///
/// Note that metadata set with this function is not rendered within the
/// document. Instead, it is embedded in the compiled PDF file.
-///
-/// Display: Document
-/// Category: meta
-#[element(Construct, LayoutRoot)]
+#[elem(Construct, LayoutRoot)]
pub struct DocumentElem {
/// The document's title. This is often rendered as the title of the
/// PDF viewer window.
diff --git a/crates/typst-library/src/meta/figure.rs b/crates/typst-library/src/meta/figure.rs
index d63ae3a8..6e95dce7 100644
--- a/crates/typst-library/src/meta/figure.rs
+++ b/crates/typst-library/src/meta/figure.rs
@@ -15,7 +15,7 @@ use crate::visualize::ImageElem;
/// For example, figures containing images will be numbered separately from
/// figures containing tables.
///
-/// ## Examples { #examples }
+/// # Examples
/// The example below shows a basic figure with an image:
/// ```example
/// @glacier shows a glacier. Glaciers
@@ -27,9 +27,8 @@ use crate::visualize::ImageElem;
/// ) <glacier>
/// ```
///
-/// You can also insert [tables]($func/table) into figures to give them a
-/// caption. The figure will detect this and automatically use a separate
-/// counter.
+/// You can also insert [tables]($table) into figures to give them a caption.
+/// The figure will detect this and automatically use a separate counter.
///
/// ```example
/// #figure(
@@ -45,7 +44,7 @@ use crate::visualize::ImageElem;
/// This behaviour can be overridden by explicitly specifying the figure's
/// `kind`. All figures of the same kind share a common counter.
///
-/// ## Modifying the appearance { #modifying-appearance }
+/// # Modifying the appearance { #modifying-appearance }
/// You can completely customize the look of your figures with a [show
/// rule]($styling/#show-rules). In the example below, we show the figure's
/// caption above its body and display its supplement and counter after the
@@ -73,13 +72,10 @@ use crate::visualize::ImageElem;
/// If your figure is too large and its contents are breakable across pages
/// (e.g. if it contains a large table), then you can make the figure breakable
/// across pages as well by using `[#show figure: set block(breakable: true)]`
-/// (see the [block]($func/block) documentation for more information).
-///
-/// Display: Figure
-/// Category: meta
-#[element(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)]
+/// (see the [block]($block) documentation for more information).
+#[elem(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)]
pub struct FigureElem {
- /// The content of the figure. Often, an [image]($func/image).
+ /// The content of the figure. Often, an [image]($image).
#[required]
pub body: Content,
@@ -103,7 +99,7 @@ pub struct FigureElem {
/// )
/// #lorem(60)
/// ```
- pub placement: Option<Smart<VerticalAlign>>,
+ pub placement: Option<Smart<VAlign>>,
/// The figure's caption.
pub caption: Option<Content>,
@@ -122,8 +118,17 @@ pub struct FigureElem {
/// caption: [I'm down here],
/// )
/// ```
- #[default(VerticalAlign(GenAlign::Specific(Align::Bottom)))]
- pub caption_pos: VerticalAlign,
+ #[default(VAlign::Bottom)]
+ #[parse({
+ let option: Option<Spanned<VAlign>> = args.named("caption-pos")?;
+ if let Some(Spanned { v: align, span }) = option {
+ if align == VAlign::Horizon {
+ bail!(span, "expected `top` or `bottom`");
+ }
+ }
+ option.map(|spanned| spanned.v)
+ })]
+ pub caption_pos: VAlign,
/// The kind of figure this is.
///
@@ -133,7 +138,7 @@ pub struct FigureElem {
/// Setting this to something other than `{auto}` will override the
/// automatic detection. This can be useful if
/// - you wish to create a custom figure type that is not an
- /// [image]($func/image), a [table]($func/table) or [code]($func/raw),
+ /// [image]($image), a [table]($table) or [code]($raw),
/// - you want to force the figure to use a specific counter regardless of
/// its content.
///
@@ -155,8 +160,8 @@ pub struct FigureElem {
/// The figure's supplement.
///
/// If set to `{auto}`, the figure will try to automatically determine the
- /// correct supplement based on the `kind` and the active [text
- /// language]($func/text.lang). If you are using a custom figure type, you
+ /// correct supplement based on the `kind` and the active
+ /// [text language]($text.lang). If you are using a custom figure type, you
/// will need to manually specify the supplement.
///
/// If a function is specified, it is passed the first descendant of the
@@ -174,7 +179,7 @@ pub struct FigureElem {
pub supplement: Smart<Option<Supplement>>,
/// How to number the figure. Accepts a
- /// [numbering pattern or function]($func/numbering).
+ /// [numbering pattern or function]($numbering).
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
pub numbering: Option<Numbering>,
@@ -182,16 +187,15 @@ pub struct FigureElem {
#[default(Em::new(0.65).into())]
pub gap: Length,
- /// Whether the figure should appear in an [`outline`]($func/outline)
- /// of figures.
+ /// Whether the figure should appear in an [`outline`]($outline) of figures.
#[default(true)]
pub outlined: bool,
/// Convenience field to get access to the counter for this figure.
///
/// The counter only depends on the `kind`:
- /// - For (tables)[$func/table]: `{counter(figure.where(kind: table))}`
- /// - For (images)[$func/image]: `{counter(figure.where(kind: image))}`
+ /// - For (tables)[@table]: `{counter(figure.where(kind: table))}`
+ /// - For (images)[@image]: `{counter(figure.where(kind: image))}`
/// - For a custom kind: `{counter(figure.where(kind: kind))}`
///
/// These are the counters you'll need to modify if you want to skip a
@@ -210,16 +214,9 @@ impl Synthesize for FigureElem {
.query_first(Selector::can::<dyn Figurable>())
.cloned()
.map(|elem| FigureKind::Elem(elem.func()))
- .unwrap_or_else(|| FigureKind::Elem(ImageElem::func()))
+ .unwrap_or_else(|| FigureKind::Elem(ImageElem::elem()))
});
- let caption_pos =
- VerticalAlign(GenAlign::Specific(match self.caption_pos(styles) {
- VerticalAlign(GenAlign::Specific(Align::Top)) => Align::Top,
- VerticalAlign(GenAlign::Specific(Align::Bottom)) => Align::Bottom,
- _ => bail!(self.span(), "caption-pos can only be top or bottom"),
- }));
-
// Resolve the supplement.
let supplement = match self.supplement(styles) {
Smart::Auto => {
@@ -261,14 +258,14 @@ impl Synthesize for FigureElem {
// Construct the figure's counter.
let counter = Counter::new(CounterKey::Selector(Selector::Elem(
- Self::func(),
+ Self::elem(),
Some(dict! {
"kind" => kind.clone(),
}),
)));
self.push_placement(self.placement(styles));
- self.push_caption_pos(caption_pos);
+ self.push_caption_pos(self.caption_pos(styles));
self.push_caption(self.caption(styles));
self.push_kind(Smart::Custom(kind));
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
@@ -288,10 +285,7 @@ impl Show for FigureElem {
// Build the caption, if any.
if let Some(caption) = self.full_caption(vt)? {
let v = VElem::weak(self.gap(styles).into()).pack();
- realized = if matches!(
- self.caption_pos(styles),
- VerticalAlign(GenAlign::Specific(Align::Bottom))
- ) {
+ realized = if self.caption_pos(styles) == VAlign::Bottom {
realized + v + caption
} else {
caption + v + realized
@@ -302,15 +296,13 @@ impl Show for FigureElem {
realized = BlockElem::new()
.with_body(Some(realized))
.pack()
- .aligned(Axes::with_x(Some(Align::Center.into())));
+ .aligned(Align::CENTER);
// Wrap in a float.
if let Some(align) = self.placement(styles) {
realized = PlaceElem::new(realized)
.with_float(true)
- .with_alignment(align.map(|VerticalAlign(align)| {
- Axes::new(Some(Align::Center.into()), Some(align))
- }))
+ .with_alignment(align.map(|align| HAlign::Center + align))
.pack();
}
@@ -345,7 +337,7 @@ impl Refable for FigureElem {
}
fn counter(&self) -> Counter {
- self.counter().unwrap_or_else(|| Counter::of(Self::func()))
+ self.counter().unwrap_or_else(|| Counter::of(Self::elem()))
}
fn numbering(&self) -> Option<Numbering> {
@@ -379,8 +371,8 @@ impl FigureElem {
self.counter(),
self.numbering(StyleChain::default()),
) {
- let loc = self.0.location().unwrap();
- let numbers = counter.at(vt, loc)?.display(vt, &numbering)?;
+ let location = self.0.location().unwrap();
+ let numbers = counter.at(vt, location)?.display(vt, &numbering)?;
if !supplement.is_empty() {
supplement += TextElem::packed("\u{a0}");
@@ -397,7 +389,7 @@ impl FigureElem {
#[derive(Debug, Clone)]
pub enum FigureKind {
/// The kind is an element function.
- Elem(ElemFunc),
+ Elem(Element),
/// The kind is a name.
Name(EcoString),
}
@@ -408,7 +400,7 @@ cast! {
Self::Elem(v) => v.into_value(),
Self::Name(v) => v.into_value(),
},
- v: ElemFunc => Self::Elem(v),
+ v: Element => Self::Elem(v),
v: EcoString => Self::Name(v),
}
diff --git a/crates/typst-library/src/meta/footnote.rs b/crates/typst-library/src/meta/footnote.rs
index 848c0b7c..ed7242bb 100644
--- a/crates/typst-library/src/meta/footnote.rs
+++ b/crates/typst-library/src/meta/footnote.rs
@@ -34,11 +34,11 @@ cast! {
/// and can break across multiple pages.
///
/// To customize the appearance of the entry in the footnote listing, see
-/// [`footnote.entry`]($func/footnote.entry). The footnote itself is realized as
-/// a normal superscript, so you can use a set rule on the
-/// [`super`]($func/super) function to customize it.
+/// [`footnote.entry`]($footnote.entry). The footnote itself is realized as a
+/// normal superscript, so you can use a set rule on the [`super`]($super)
+/// function to customize it.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// Check the docs for more details.
/// #footnote[https://typst.app/docs]
@@ -46,7 +46,7 @@ cast! {
///
/// The footnote automatically attaches itself to the preceding word, even if
/// there is a space before it in the markup. To force space, you can use the
-/// string `[#" "]` or explicit [horizontal spacing]($func/h).
+/// string `[#" "]` or explicit [horizontal spacing]($h).
///
/// By giving a label to a footnote, you can have multiple references to it.
///
@@ -61,21 +61,14 @@ cast! {
/// apply to the footnote's content. See [here][issue] for more information.
///
/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
-///
-/// Display: Footnote
-/// Category: meta
-#[element(Locatable, Synthesize, Show, Count)]
-#[scope(
- scope.define("entry", FootnoteEntry::func());
- scope
-)]
+#[elem(scope, Locatable, Synthesize, Show, Count)]
pub struct FootnoteElem {
/// How to number footnotes.
///
/// By default, the footnote numbering continues throughout your document.
/// If you prefer per-page footnote numbering, you can reset the footnote
- /// [counter]($func/counter) in the page [header]($func/page.header). In the
- /// future, there might be a simpler way to achieve this.
+ /// [counter]($counter) in the page [header]($page.header). In the future,
+ /// there might be a simpler way to achieve this.
///
/// ```example
/// #set footnote(numbering: "*")
@@ -93,6 +86,12 @@ pub struct FootnoteElem {
pub body: FootnoteBody,
}
+#[scope]
+impl FootnoteElem {
+ #[elem]
+ type FootnoteEntry;
+}
+
impl FootnoteElem {
/// Creates a new footnote that the passed content as its body.
pub fn with_content(content: Content) -> Self {
@@ -145,7 +144,7 @@ impl Show for FootnoteElem {
Ok(vt.delayed(|vt| {
let loc = self.declaration_location(vt).at(self.span())?;
let numbering = self.numbering(styles);
- let counter = Counter::of(Self::func());
+ let counter = Counter::of(Self::elem());
let num = counter.at(vt, loc)?.display(vt, &numbering)?;
let sup = SuperElem::new(num).pack();
let hole = HElem::new(Abs::zero().into()).with_weak(true).pack();
@@ -168,11 +167,9 @@ impl Count for FootnoteElem {
///
/// _Note:_ Set and show rules for `footnote.entry` must be defined at the
/// beginning of the document in order to work correctly.
-/// See [here][issue] for more information.
-///
-/// [issue]: https://github.com/typst/typst/issues/1348#issuecomment-1566316463
+/// See [here](https://github.com/typst/typst/issues/1348#issuecomment-1566316463)
+/// for more information.
///
-/// ## Example { #example }
/// ```example
/// #show footnote.entry: set text(red)
///
@@ -180,10 +177,7 @@ impl Count for FootnoteElem {
/// #footnote[It's down here]
/// has red text!
/// ```
-///
-/// Display: Footnote Entry
-/// Category: meta
-#[element(Show, Finalize)]
+#[elem(name = "entry", title = "Footnote Entry", Show, Finalize)]
pub struct FootnoteEntry {
/// The footnote for this entry. It's location can be used to determine
/// the footnote counter state.
@@ -220,7 +214,7 @@ pub struct FootnoteEntry {
#[default(
LineElem::new()
.with_length(Ratio::new(0.3).into())
- .with_stroke(PartialStroke {
+ .with_stroke(Stroke {
thickness: Smart::Custom(Abs::pt(0.5).into()),
..Default::default()
})
@@ -273,7 +267,7 @@ impl Show for FootnoteEntry {
let note = self.note();
let number_gap = Em::new(0.05);
let numbering = note.numbering(StyleChain::default());
- let counter = Counter::of(FootnoteElem::func());
+ let counter = Counter::of(FootnoteElem::elem());
let loc = note.0.location().unwrap();
let num = counter.at(vt, loc)?.display(vt, &numbering)?;
let sup = SuperElem::new(num)
diff --git a/crates/typst-library/src/meta/heading.rs b/crates/typst-library/src/meta/heading.rs
index 84da7c03..edd2d0f0 100644
--- a/crates/typst-library/src/meta/heading.rs
+++ b/crates/typst-library/src/meta/heading.rs
@@ -17,14 +17,13 @@ use crate::text::{SpaceElem, TextElem, TextSize};
///
/// Typst can automatically number your headings for you. To enable numbering,
/// specify how you want your headings to be numbered with a
-/// [numbering pattern or function]($func/numbering).
+/// [numbering pattern or function]($numbering).
///
/// Independently from the numbering, Typst can also automatically generate an
-/// [outline]($func/outline) of all headings for you. To exclude one or more
-/// headings from this outline, you can set the `outlined` parameter to
-/// `{false}`.
+/// [outline]($outline) of all headings for you. To exclude one or more headings
+/// from this outline, you can set the `outlined` parameter to `{false}`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #set heading(numbering: "1.a)")
///
@@ -35,21 +34,18 @@ use crate::text::{SpaceElem, TextElem, TextSize};
/// To start, ...
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// Headings have dedicated syntax: They can be created by starting a line with
/// one or multiple equals signs, followed by a space. The number of equals
/// signs determines the heading's logical nesting depth.
-///
-/// Display: Heading
-/// Category: meta
-#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)]
+#[elem(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)]
pub struct HeadingElem {
/// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::ONE)]
pub level: NonZeroUsize,
/// How to number the heading. Accepts a
- /// [numbering pattern or function]($func/numbering).
+ /// [numbering pattern or function]($numbering).
///
/// ```example
/// #set heading(numbering: "1.a.")
@@ -78,11 +74,11 @@ pub struct HeadingElem {
/// ```
pub supplement: Smart<Option<Supplement>>,
- /// Whether the heading should appear in the [outline]($func/outline).
+ /// Whether the heading should appear in the [outline]($outline).
///
- /// Note that this property, if set to `{true}`, ensures the heading is
- /// also shown as a bookmark in the exported PDF's outline (when exporting
- /// to PDF). To change that behavior, use the `bookmarked` property.
+ /// Note that this property, if set to `{true}`, ensures the heading is also
+ /// shown as a bookmark in the exported PDF's outline (when exporting to
+ /// PDF). To change that behavior, use the `bookmarked` property.
///
/// ```example
/// #outline()
@@ -103,9 +99,8 @@ pub struct HeadingElem {
/// The default value of `{auto}` indicates that the heading will only
/// appear in the exported PDF's outline if its `outlined` property is set
/// to `{true}`, that is, if it would also be listed in Typst's
- /// [outline]($func/outline). Setting this property to either
- /// `{true}` (bookmark) or `{false}` (don't bookmark) bypasses that
- /// behavior.
+ /// [outline]($outline). Setting this property to either `{true}` (bookmark)
+ /// or `{false}` (don't bookmark) bypasses that behavior.
///
/// ```example
/// #heading[Normal heading]
@@ -149,7 +144,7 @@ impl Show for HeadingElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body();
if let Some(numbering) = self.numbering(styles) {
- realized = Counter::of(Self::func())
+ realized = Counter::of(Self::elem())
.display(Some(numbering), false)
.spanned(self.span())
+ HElem::new(Em::new(0.3).into()).with_weak(true).pack()
@@ -205,7 +200,7 @@ impl Refable for HeadingElem {
}
fn counter(&self) -> Counter {
- Counter::of(Self::func())
+ Counter::of(Self::elem())
}
fn numbering(&self) -> Option<Numbering> {
@@ -221,7 +216,7 @@ impl Outlinable for HeadingElem {
let mut content = self.body();
if let Some(numbering) = self.numbering(StyleChain::default()) {
- let numbers = Counter::of(Self::func())
+ let numbers = Counter::of(Self::elem())
.at(vt, self.0.location().unwrap())?
.display(vt, &numbering)?;
content = numbers + SpaceElem::new().pack() + content;
diff --git a/crates/typst-library/src/meta/link.rs b/crates/typst-library/src/meta/link.rs
index 2a53b84f..7b68b186 100644
--- a/crates/typst-library/src/meta/link.rs
+++ b/crates/typst-library/src/meta/link.rs
@@ -6,7 +6,7 @@ use crate::text::{Hyphenate, TextElem};
/// By default, links are not styled any different from normal text. However,
/// you can easily apply a style of your choice with a show rule.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #show link: underline
///
@@ -18,13 +18,10 @@ use crate::text::{Hyphenate, TextElem};
/// ]
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: Text that starts with `http://` or
/// `https://` is automatically turned into a link.
-///
-/// Display: Link
-/// Category: meta
-#[element(Show)]
+#[elem(Show)]
pub struct LinkElem {
/// The destination the link points to.
///
@@ -35,17 +32,16 @@ pub struct LinkElem {
///
/// - To link to another part of the document, `dest` can take one of three
/// forms:
- /// - A [label]($func/label) attached to an element. If you also want
- /// automatic text for the link based on the element, consider using
- /// a [reference]($func/ref) instead.
+ /// - A [label]($label) attached to an element. If you also want automatic
+ /// text for the link based on the element, consider using a
+ /// [reference]($ref) instead.
///
- /// - A [location]($func/locate) resulting from a [`locate`]($func/locate)
- /// call or [`query`]($func/query).
+ /// - A [location]($locate) resulting from a [`locate`]($locate) call or
+ /// [`query`]($query).
///
- /// - A dictionary with a `page` key of type [integer]($type/integer) and
- /// `x` and `y` coordinates of type [length]($type/length). Pages are
- /// counted from one, and the coordinates are relative to the page's top
- /// left corner.
+ /// - A dictionary with a `page` key of type [integer]($int) and `x` and
+ /// `y` coordinates of type [length]($length). Pages are counted from
+ /// one, and the coordinates are relative to the page's top left corner.
///
/// ```example
/// = Introduction <intro>
diff --git a/crates/typst-library/src/meta/metadata.rs b/crates/typst-library/src/meta/metadata.rs
index 3e08a9f7..b4ae64cb 100644
--- a/crates/typst-library/src/meta/metadata.rs
+++ b/crates/typst-library/src/meta/metadata.rs
@@ -2,11 +2,11 @@ use crate::prelude::*;
/// Exposes a value to the query system without producing visible content.
///
-/// This element can be retrieved with the [`query`]($func/query) function and
-/// from the command with [`typst query`]($reference/meta/query/#cli-queries).
-/// Its purpose is to expose an arbitrary value to the introspection system. To
-/// identify a metadata value among others, you can attach a
-/// [`label`]($func/label) to it and query for that label.
+/// This element can be retrieved with the [`query`]($query) function and from
+/// the command with [`typst query`]($reference/meta/query/#cli-queries). Its
+/// purpose is to expose an arbitrary value to the introspection system. To
+/// identify a metadata value among others, you can attach a [`label`]($label)
+/// to it and query for that label.
///
/// The `metadata` element is especially useful for command line queries because
/// it allows you to expose arbitrary values to the outside world.
@@ -20,10 +20,7 @@ use crate::prelude::*;
/// query(<note>, loc).first().value
/// })
/// ```
-///
-/// Display: Metadata
-/// Category: meta
-#[element(Behave, Show, Locatable)]
+#[elem(Behave, Show, Locatable)]
pub struct MetadataElem {
/// The value to embed into the document.
#[required]
diff --git a/crates/typst-library/src/meta/mod.rs b/crates/typst-library/src/meta/mod.rs
index bb6ac3d3..659cb5a3 100644
--- a/crates/typst-library/src/meta/mod.rs
+++ b/crates/typst-library/src/meta/mod.rs
@@ -9,9 +9,11 @@ mod footnote;
mod heading;
mod link;
mod metadata;
-mod numbering;
+#[path = "numbering.rs"]
+mod numbering_;
mod outline;
-mod query;
+#[path = "query.rs"]
+mod query_;
mod reference;
mod state;
@@ -24,9 +26,9 @@ pub use self::footnote::*;
pub use self::heading::*;
pub use self::link::*;
pub use self::metadata::*;
-pub use self::numbering::*;
+pub use self::numbering_::*;
pub use self::outline::*;
-pub use self::query::*;
+pub use self::query_::*;
pub use self::reference::*;
pub use self::state::*;
@@ -35,24 +37,27 @@ use crate::text::TextElem;
/// Hook up all meta definitions.
pub(super) fn define(global: &mut Scope) {
- global.define("document", DocumentElem::func());
- global.define("ref", RefElem::func());
- global.define("link", LinkElem::func());
- global.define("outline", OutlineElem::func());
- global.define("heading", HeadingElem::func());
- global.define("figure", FigureElem::func());
- global.define("footnote", FootnoteElem::func());
- global.define("cite", CiteElem::func());
- global.define("bibliography", BibliographyElem::func());
- global.define("locate", locate_func());
- global.define("style", style_func());
- global.define("layout", layout_func());
- global.define("counter", counter_func());
- global.define("numbering", numbering_func());
- global.define("state", state_func());
- global.define("query", query_func());
- global.define("selector", selector_func());
- global.define("metadata", MetadataElem::func());
+ global.category("meta");
+ global.define_type::<Label>();
+ global.define_type::<Selector>();
+ global.define_type::<Location>();
+ global.define_type::<Counter>();
+ global.define_type::<State>();
+ global.define_elem::<DocumentElem>();
+ global.define_elem::<RefElem>();
+ global.define_elem::<LinkElem>();
+ global.define_elem::<OutlineElem>();
+ global.define_elem::<HeadingElem>();
+ global.define_elem::<FigureElem>();
+ global.define_elem::<FootnoteElem>();
+ global.define_elem::<CiteElem>();
+ global.define_elem::<BibliographyElem>();
+ global.define_elem::<MetadataElem>();
+ global.define_func::<locate>();
+ global.define_func::<style>();
+ global.define_func::<layout>();
+ global.define_func::<numbering>();
+ global.define_func::<query>();
}
/// The named with which an element is referenced.
diff --git a/crates/typst-library/src/meta/numbering.rs b/crates/typst-library/src/meta/numbering.rs
index 8698f7b9..40308af0 100644
--- a/crates/typst-library/src/meta/numbering.rs
+++ b/crates/typst-library/src/meta/numbering.rs
@@ -16,7 +16,7 @@ use crate::text::Case;
/// number is substituted, their prefixes, and one suffix. The prefixes and the
/// suffix are repeated as-is.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #numbering("1.1)", 1, 2, 3) \
/// #numbering("1.a.i", 1, 2) \
@@ -29,11 +29,10 @@ use crate::text::Case;
/// 1, 2, 3,
/// )
/// ```
-///
-/// Display: Numbering
-/// Category: meta
#[func]
pub fn numbering(
+ /// The virtual machine.
+ vm: &mut Vm,
/// Defines how the numbering works.
///
/// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `い`, `イ`, `א`, `가`,
@@ -64,8 +63,6 @@ pub fn numbering(
/// given, the last counting symbol with its prefix is repeated.
#[variadic]
numbers: Vec<usize>,
- /// The virtual machine.
- vm: &mut Vm,
) -> SourceResult<Value> {
numbering.apply_vm(vm, &numbers)
}
diff --git a/crates/typst-library/src/meta/outline.rs b/crates/typst-library/src/meta/outline.rs
index 6fd4ebd0..8583d96f 100644
--- a/crates/typst-library/src/meta/outline.rs
+++ b/crates/typst-library/src/meta/outline.rs
@@ -16,7 +16,7 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// be displayed in the outline alongside its title or caption. By default this
/// generates a table of contents.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #outline()
///
@@ -27,13 +27,13 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// #lorem(10)
/// ```
///
-/// ## Alternative outlines { #alternative-outlines }
+/// # Alternative outlines
/// By setting the `target` parameter, the outline can be used to generate a
/// list of other kinds of elements than headings. In the example below, we list
/// all figures containing images by setting `target` to `{figure.where(kind:
/// image)}`. We could have also set it to just `figure`, but then the list
/// would also include figures containing tables or other material. For more
-/// details on the `where` selector, [see here]($type/content.where).
+/// details on the `where` selector, [see here]($function.where).
///
/// ```example
/// #outline(
@@ -47,25 +47,17 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// )
/// ```
///
-/// ## Styling the outline { #styling-the-outline }
+/// # Styling the outline
/// The outline element has several options for customization, such as its
-/// `title` and `indent` parameters. If desired, however, it is possible to
-/// have more control over the outline's look and style through the
-/// [`outline.entry`]($func/outline.entry) element.
-///
-/// Display: Outline
-/// Category: meta
-/// Keywords: Table of Contents
-#[element(Show, Finalize, LocalName)]
-#[scope(
- scope.define("entry", OutlineEntry::func());
- scope
-)]
+/// `title` and `indent` parameters. If desired, however, it is possible to have
+/// more control over the outline's look and style through the
+/// [`outline.entry`]($outline.entry) element.
+#[elem(scope, keywords = ["Table of Contents"], Show, Finalize, LocalName)]
pub struct OutlineElem {
/// The title of the outline.
///
/// - When set to `{auto}`, an appropriate title for the
- /// [text language]($func/text.lang) will be used. This is the default.
+ /// [text language]($text.lang) will be used. This is the default.
/// - When set to `{none}`, the outline will not have a title.
/// - A custom title can be set by passing content.
///
@@ -97,7 +89,7 @@ pub struct OutlineElem {
/// )
/// ```
#[default(LocatableSelector(Selector::Elem(
- HeadingElem::func(),
+ HeadingElem::elem(),
Some(dict! { "outlined" => true })
)))]
pub target: LocatableSelector,
@@ -125,19 +117,18 @@ pub struct OutlineElem {
/// - `{none}`: No indent
/// - `{auto}`: Indents the numbering of the nested entry with the title of
/// its parent entry. This only has an effect if the entries are numbered
- /// (e.g., via [heading numbering]($func/heading.numbering)).
- /// - [Relative length]($type/relative-length): Indents the item by this length
+ /// (e.g., via [heading numbering]($heading.numbering)).
+ /// - [Relative length]($relative): Indents the item by this length
/// multiplied by its nesting level. Specifying `{2em}`, for instance,
/// would indent top-level headings (not nested) by `{0em}`, second level
/// headings by `{2em}` (nested once), third-level headings by `{4em}`
/// (nested twice) and so on.
- /// - [Function]($type/function): You can completely customize this setting
- /// with a function. That function receives the nesting level as a
- /// parameter (starting at 0 for top-level headings/elements) and can
- /// return a relative length or content making up the indent. For example,
- /// `{n => n * 2em}` would be equivalent to just specifying `{2em}`,
- /// while `{n => [→ ] * n}` would indent with one arrow per nesting
- /// level.
+ /// - [Function]($function): You can completely customize this setting with
+ /// a function. That function receives the nesting level as a parameter
+ /// (starting at 0 for top-level headings/elements) and can return a
+ /// relative length or content making up the indent. For example,
+ /// `{n => n * 2em}` would be equivalent to just specifying `{2em}`, while
+ /// `{n => [→ ] * n}` would indent with one arrow per nesting level.
///
/// *Migration hints:* Specifying `{true}` (equivalent to `{auto}`) or
/// `{false}` (equivalent to `{none}`) for this option is deprecated and
@@ -184,6 +175,12 @@ pub struct OutlineElem {
pub fill: Option<Content>,
}
+#[scope]
+impl OutlineElem {
+ #[elem]
+ type OutlineEntry;
+}
+
impl Show for OutlineElem {
#[tracing::instrument(name = "OutlineElem::show", skip_all)]
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
@@ -391,11 +388,9 @@ cast! {
/// outlined element, its page number, and the filler content between both.
///
/// This element is intended for use with show rules to control the appearance
-/// of outlines.
-///
-/// ## Example { #example }
-/// The example below shows how to style entries for top-level sections to make
-/// them stand out.
+/// of outlines. To customize an entry's line, you can build it from scratch by
+/// accessing the `level`, `element`, `body`, `fill` and `page` fields on the
+/// entry.
///
/// ```example
/// #set heading(numbering: "1.")
@@ -416,13 +411,7 @@ cast! {
/// = Analysis
/// == Setup
/// ```
-///
-/// To completely customize an entry's line, you can also build it from scratch
-/// by accessing the `level`, `element`, `body`, `fill` and `page` fields on the entry.
-///
-/// Display: Outline Entry
-/// Category: meta
-#[element(Show)]
+#[elem(name = "entry", title = "Outline Entry", Show)]
pub struct OutlineEntry {
/// The nesting level of this outline entry. Starts at `{1}` for top-level
/// entries.
@@ -430,8 +419,8 @@ pub struct OutlineEntry {
pub level: NonZeroUsize,
/// The element this entry refers to. Its location will be available
- /// through the [`location`]($type/content.location) method on content
- /// and can be [linked]($func/link) to.
+ /// through the [`location`]($content.location) method on content
+ /// and can be [linked]($link) to.
#[required]
pub element: Content,
@@ -446,7 +435,7 @@ pub struct OutlineEntry {
/// located in. When `{none}`, empty space is inserted in that gap instead.
///
/// Note that, when using show rules to override outline entries, it is
- /// recommended to wrap the filling content in a [`box`]($func/box) with
+ /// recommended to wrap the filling content in a [`box`]($box) with
/// fractional width. For example, `{box(width: 1fr, repeat[-])}` would show
/// precisely as many `-` characters as necessary to fill a particular gap.
#[required]
diff --git a/crates/typst-library/src/meta/query.rs b/crates/typst-library/src/meta/query.rs
index eb520896..d6c600d7 100644
--- a/crates/typst-library/src/meta/query.rs
+++ b/crates/typst-library/src/meta/query.rs
@@ -4,10 +4,10 @@ use crate::prelude::*;
///
/// The `query` functions lets you search your document for elements of a
/// particular type or with a particular label. To use it, you first need to
-/// retrieve the current document location with the [`locate`]($func/locate)
+/// retrieve the current document location with the [`locate`]($locate)
/// function.
///
-/// ## Finding elements { #finding-elements }
+/// # Finding elements
/// In the example below, we create a custom page header that displays the text
/// "Typst Academy" in small capitals and the current section title. On the
/// first page, the section title is omitted because the header is before the
@@ -59,7 +59,7 @@ use crate::prelude::*;
/// #lorem(15)
/// ```
///
-/// ## A word of caution { #caution }
+/// # A word of caution { #caution }
/// To resolve all your queries, Typst evaluates and layouts parts of the
/// document multiple times. However, there is no guarantee that your queries
/// can actually be completely resolved. If you aren't careful a query can
@@ -73,9 +73,9 @@ use crate::prelude::*;
/// on. As we can see, the output has five headings. This is because Typst
/// simply gives up after five attempts.
///
-/// In general, you should try not to write queries that affect themselves.
-/// The same words of caution also apply to other introspection features like
-/// [counters]($func/counter) and [state]($func/state).
+/// In general, you should try not to write queries that affect themselves. The
+/// same words of caution also apply to other introspection features like
+/// [counters]($counter) and [state]($state).
///
/// ```example
/// = Real
@@ -86,11 +86,11 @@ use crate::prelude::*;
/// })
/// ```
///
-/// ## Command line queries { #command-line-queries }
+/// # Command line queries
/// You can also perform queries from the command line with the `typst query`
/// command. This command executes an arbitrary query on the document and
/// returns the resulting elements in serialized form. Consider the following
-/// `example.typ` file which contains some invisible [metadata]($func/metadata):
+/// `example.typ` file which contains some invisible [metadata]($metadata):
///
/// ```typ
/// #metadata("This is a note") <note>
@@ -125,32 +125,29 @@ use crate::prelude::*;
/// $ typst query example.typ "<note>" --field value --one
/// "This is a note"
/// ```
-///
-/// Display: Query
-/// Category: meta
#[func]
pub fn query(
+ /// The virtual machine.
+ vm: &mut Vm,
/// Can be an element function like a `heading` or `figure`, a `{<label>}`
/// or a more complex selector like `{heading.where(level: 1)}`.
///
/// Currently, only a subset of element functions is supported. Aside from
/// headings and figures, this includes equations, references and all
/// elements with an explicit label. As a result, you _can_ query for e.g.
- /// [`strong`]($func/strong) elements, but you will find only those that
- /// have an explicit label attached to them. This limitation will be
- /// resolved in the future.
+ /// [`strong`]($strong) elements, but you will find only those that have an
+ /// explicit label attached to them. This limitation will be resolved in the
+ /// future.
target: LocatableSelector,
/// Can be an arbitrary location, as its value is irrelevant for the
/// function's return value. Why is it required then? As noted before, Typst
/// has to evaluate parts of your code multiple times to determine the
/// values of all state. By only allowing this function within
- /// [`locate`]($func/locate) calls, the amount of code that can depend on
- /// the query's result is reduced. If you could call it directly at the top
+ /// [`locate`]($locate) calls, the amount of code that can depend on the
+ /// query's result is reduced. If you could call it directly at the top
/// level of a module, the evaluation of the whole module and its exports
/// could depend on the query's result.
location: Location,
- /// The virtual machine.
- vm: &mut Vm,
) -> Array {
let _ = location;
let vec = vm.vt.introspector.query(&target.0);
@@ -158,19 +155,3 @@ pub fn query(
.map(|elem| Value::Content(elem.into_inner()))
.collect()
}
-
-/// Turns a value into a selector. The following values are accepted:
-/// - An element function like a `heading` or `figure`.
-/// - A `{<label>}`.
-/// - A more complex selector like `{heading.where(level: 1)}`.
-///
-/// Display: Selector
-/// Category: meta
-#[func]
-pub fn selector(
- /// Can be an element function like a `heading` or `figure`, a `{<label>}`
- /// or a more complex selector like `{heading.where(level: 1)}`.
- target: Selector,
-) -> Selector {
- target
-}
diff --git a/crates/typst-library/src/meta/reference.rs b/crates/typst-library/src/meta/reference.rs
index 015157a8..7f05bfcc 100644
--- a/crates/typst-library/src/meta/reference.rs
+++ b/crates/typst-library/src/meta/reference.rs
@@ -9,20 +9,19 @@ use crate::text::TextElem;
/// Produces a textual reference to a label. For example, a reference to a
/// heading will yield an appropriate string such as "Section 1" for a reference
/// to the first heading. The references are also links to the respective
-/// element. Reference syntax can also be used to [cite]($func/cite) from a
+/// element. Reference syntax can also be used to [cite]($cite) from a
/// bibliography.
///
-/// Referenceable elements include [headings]($func/heading),
-/// [figures]($func/figure), [equations]($func/math.equation), and
-/// [footnotes]($func/footnote). To create a custom referenceable element like a
-/// theorem, you can create a figure of a custom [`kind`]($func/figure.kind) and
-/// write a show rule for it. In the future, there might be a more direct way to
-/// define a custom referenceable element.
+/// Referenceable elements include [headings]($heading), [figures]($figure),
+/// [equations]($math.equation), and [footnotes]($footnote). To create a custom
+/// referenceable element like a theorem, you can create a figure of a custom
+/// [`kind`]($figure.kind) and write a show rule for it. In the future, there
+/// might be a more direct way to define a custom referenceable element.
///
/// If you just want to link to a labelled element and not get an automatic
-/// textual reference, consider using the [`link`]($func/link) function instead.
+/// textual reference, consider using the [`link`]($link) function instead.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #set heading(numbering: "1.")
/// #set math.equation(numbering: "(1)")
@@ -46,7 +45,7 @@ use crate::text::TextElem;
/// #bibliography("works.bib")
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: A reference to a label can be
/// created by typing an `@` followed by the name of the label (e.g.
/// `[= Introduction <intro>]` can be referenced by typing `[@intro]`).
@@ -54,7 +53,7 @@ use crate::text::TextElem;
/// To customize the supplement, add content in square brackets after the
/// reference: `[@intro[Chapter]]`.
///
-/// ## Customization { #customization }
+/// # Customization
/// If you write a show rule for references, you can access the referenced
/// element through the `element` field of the reference. The `element` may
/// be `{none}` even if it exists if Typst hasn't discovered it yet, so you
@@ -83,10 +82,7 @@ use crate::text::TextElem;
/// In @beginning we prove @pythagoras.
/// $ a^2 + b^2 = c^2 $ <pythagoras>
/// ```
-///
-/// Display: Reference
-/// Category: meta
-#[element(Synthesize, Locatable, Show)]
+#[elem(title = "Reference", Synthesize, Locatable, Show)]
pub struct RefElem {
/// The target label that should be referenced.
#[required]
@@ -163,7 +159,7 @@ impl Show for RefElem {
let elem = elem.at(span)?;
- if elem.func() == FootnoteElem::func() {
+ if elem.func() == FootnoteElem::elem() {
return Ok(FootnoteElem::with_label(target).pack().spanned(span));
}
@@ -192,7 +188,7 @@ impl Show for RefElem {
.hint(eco_format!(
"you can enable {} numbering with `#set {}(numbering: \"1.\")`",
elem.func().name(),
- if elem.func() == EquationElem::func() {
+ if elem.func() == EquationElem::elem() {
"math.equation"
} else {
elem.func().name()
diff --git a/crates/typst-library/src/meta/state.rs b/crates/typst-library/src/meta/state.rs
index ee2a6e32..f7b5eb77 100644
--- a/crates/typst-library/src/meta/state.rs
+++ b/crates/typst-library/src/meta/state.rs
@@ -30,7 +30,7 @@ use crate::prelude::*;
/// #compute("x - 5")
/// ```
///
-/// ## State and document markup { #state-and-markup }
+/// # State and document markup { #state-and-markup }
/// Why does it do that? Because, in general, this kind of computation with side
/// effects is problematic in document markup and Typst is upfront about that.
/// For the results to make sense, the computation must proceed in the same
@@ -61,7 +61,7 @@ use crate::prelude::*;
/// `template` function and only then sees the `Outline`. Just counting up would
/// number the `Introduction` with `1` and the `Outline` with `2`.
///
-/// ## Managing state in Typst { #state-in-typst }
+/// # Managing state in Typst { #state-in-typst }
/// So what do we do instead? We use Typst's state management system. Calling
/// the `state` function with an identifying string key and an optional initial
/// value gives you a state value which exposes a few methods. The two most
@@ -122,14 +122,14 @@ use crate::prelude::*;
///
/// This example is of course a bit silly, but in practice this is often exactly
/// what you want! A good example are heading counters, which is why Typst's
-/// [counting system]($func/counter) is very similar to its state system.
+/// [counting system]($counter) is very similar to its state system.
///
-/// ## Time Travel { #time-travel }
+/// # Time Travel
/// By using Typst's state management system you also get time travel
-/// capabilities! By combining the state system with [`locate`]($func/locate)
-/// and [`query`]($func/query), we can find out what the value of the state will
-/// be at any position in the document from anywhere else. In particular, the
-/// `at` method gives us the value of the state at any location and the `final`
+/// capabilities! By combining the state system with [`locate`]($locate) and
+/// [`query`]($query), we can find out what the value of the state will be at
+/// any position in the document from anywhere else. In particular, the `at`
+/// method gives us the value of the state at any location and the `final`
/// methods gives us the value of the state at the end of the document.
///
/// ```example
@@ -156,7 +156,7 @@ use crate::prelude::*;
/// #compute("x - 5")
/// ```
///
-/// ## A word of caution { #caution }
+/// # A word of caution { #caution }
/// To resolve the values of all states, Typst evaluates parts of your code
/// multiple times. However, there is no guarantee that your state manipulation
/// can actually be completely resolved.
@@ -180,73 +180,7 @@ use crate::prelude::*;
/// `locate` calls or `display` calls of state or counters. Instead, pass a
/// function to `update` that determines the value of the state based on its
/// previous value.
-///
-/// ## Methods
-/// ### display()
-/// Displays the value of the state.
-///
-/// - format: function (positional)
-/// A function which receives the value of the state and can return arbitrary
-/// content which is then displayed. If this is omitted, the value is directly
-/// displayed.
-///
-/// - returns: content
-///
-/// ### update()
-/// Updates the value of the state.
-///
-/// The update will be in effect at the position where the returned content is
-/// inserted into the document. If you don't put the output into the document,
-/// nothing happens! This would be the case, for example, if you write
-/// `{let _ = state("key").update(7)}`. State updates are always applied in
-/// layout order and in that case, Typst wouldn't know when to update the state.
-///
-/// - value: any or function (positional, required)
-/// If given a non function-value, sets the state to that value. If given a
-/// function, that function receives the previous state and has to return the
-/// new state.
-///
-/// - returns: content
-///
-/// ### at()
-/// Gets the value of the state at the given location.
-///
-/// - location: location (positional, required)
-/// The location at which the state's value should be retrieved. A suitable
-/// location can be retrieved from [`locate`]($func/locate) or
-/// [`query`]($func/query).
-///
-/// - returns: any
-///
-/// ### final()
-/// Gets the value of the state at the end of the document.
-///
-/// - location: location (positional, required)
-/// Can be an arbitrary location, as its value is irrelevant for the method's
-/// return value. Why is it required then? As noted before, Typst has to
-/// evaluate parts of your code multiple times to determine the values of all
-/// state. By only allowing this method within [`locate`]($func/locate) calls,
-/// the amount of code that can depend on the method's result is reduced. If
-/// you could call `final` directly at the top level of a module, the
-/// evaluation of the whole module and its exports could depend on the state's
-/// value.
-///
-/// - returns: any
-///
-/// Display: State
-/// Category: meta
-#[func]
-pub fn state(
- /// The key that identifies this state.
- key: Str,
- /// The initial value of the state.
- #[default]
- init: Value,
-) -> State {
- State { key, init }
-}
-
-/// A state.
+#[ty(scope)]
#[derive(Clone, PartialEq, Hash)]
pub struct State {
/// The key that identifies the state.
@@ -256,49 +190,9 @@ pub struct State {
}
impl State {
- /// Call a method on a state.
- #[tracing::instrument(skip(vm))]
- pub fn call_method(
- self,
- vm: &mut Vm,
- method: &str,
- mut args: Args,
- span: Span,
- ) -> SourceResult<Value> {
- let value = match method {
- "display" => self.display(args.eat()?).into_value(),
- "at" => self.at(&mut vm.vt, args.expect("location")?)?,
- "final" => self.final_(&mut vm.vt, args.expect("location")?)?,
- "update" => self.update(args.expect("value or function")?).into_value(),
- _ => bail!(span, "type state has no method `{}`", method),
- };
- args.finish()?;
- Ok(value)
- }
-
- /// Display the current value of the state.
- pub fn display(self, func: Option<Func>) -> Content {
- DisplayElem::new(self, func).pack()
- }
-
- /// Get the value of the state at the given location.
- #[tracing::instrument(skip(self, vt))]
- pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> {
- let sequence = self.sequence(vt)?;
- let offset = vt.introspector.query(&self.selector().before(location, true)).len();
- Ok(sequence[offset].clone())
- }
-
- /// Get the value of the state at the final location.
- #[tracing::instrument(skip(self, vt))]
- pub fn final_(self, vt: &mut Vt, _: Location) -> SourceResult<Value> {
- let sequence = self.sequence(vt)?;
- Ok(sequence.last().unwrap().clone())
- }
-
- /// Produce content that performs a state update.
- pub fn update(self, update: StateUpdate) -> Content {
- UpdateElem::new(self.key, update).pack()
+ /// Create a new state identified by a key.
+ pub fn new(key: Str, init: Value) -> State {
+ Self { key, init }
}
/// Produce the whole sequence of states.
@@ -350,7 +244,94 @@ impl State {
/// The selector for this state's updates.
fn selector(&self) -> Selector {
- Selector::Elem(UpdateElem::func(), Some(dict! { "key" => self.key.clone() }))
+ Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.key.clone() }))
+ }
+}
+
+#[scope]
+impl State {
+ /// Create a new state identified by a key.
+ #[func(constructor)]
+ pub fn construct(
+ /// The key that identifies this state.
+ key: Str,
+ /// The initial value of the state.
+ #[default]
+ init: Value,
+ ) -> State {
+ Self::new(key, init)
+ }
+
+ /// Displays the current value of the state.
+ #[func]
+ pub fn display(
+ self,
+ /// A function which receives the value of the state and can return
+ /// arbitrary content which is then displayed. If this is omitted, the
+ /// value is directly displayed.
+ #[default]
+ func: Option<Func>,
+ ) -> Content {
+ DisplayElem::new(self, func).pack()
+ }
+
+ /// Update the value of the state.
+ ///
+ /// The update will be in effect at the position where the returned content
+ /// is inserted into the document. If you don't put the output into the
+ /// document, nothing happens! This would be the case, for example, if you
+ /// write `{let _ = state("key").update(7)}`. State updates are always
+ /// applied in layout order and in that case, Typst wouldn't know when to
+ /// update the state.
+ #[func]
+ pub fn update(
+ self,
+ /// If given a non function-value, sets the state to that value. If
+ /// given a function, that function receives the previous state and has
+ /// to return the new state.
+ update: StateUpdate,
+ ) -> Content {
+ UpdateElem::new(self.key, update).pack()
+ }
+
+ /// Get the value of the state at the given location.
+ #[func]
+ pub fn at(
+ &self,
+ /// The virtual typesetter.
+ vt: &mut Vt,
+ /// The location at which the state's value should be retrieved. A
+ /// suitable location can be retrieved from [`locate`]($locate) or
+ /// [`query`]($query).
+ location: Location,
+ ) -> SourceResult<Value> {
+ let sequence = self.sequence(vt)?;
+ let offset = vt
+ .introspector
+ .query(&self.selector().before(location.into(), true))
+ .len();
+ Ok(sequence[offset].clone())
+ }
+
+ /// Get the value of the state at the end of the document.
+ #[func]
+ pub fn final_(
+ &self,
+ /// The virtual typesetter.
+ vt: &mut Vt,
+ /// Can be an arbitrary location, as its value is irrelevant for the
+ /// method's return value. Why is it required then? As noted before,
+ /// Typst has to evaluate parts of your code multiple times to determine
+ /// the values of all state. By only allowing this method within
+ /// [`locate`]($locate) calls, the amount of code that can depend on the
+ /// method's result is reduced. If you could call `final` directly at
+ /// the top level of a module, the evaluation of the whole module and
+ /// its exports could depend on the state's value.
+ location: Location,
+ ) -> SourceResult<Value> {
+ let _ = location;
+ let sequence = self.sequence(vt)?;
+ Ok(sequence.last().unwrap().clone())
}
}
@@ -365,10 +346,11 @@ impl Debug for State {
}
cast! {
- type State: "state",
+ type State,
}
/// An update to perform on a state.
+#[ty]
#[derive(Clone, PartialEq, Hash)]
pub enum StateUpdate {
/// Set the state to the specified value.
@@ -384,16 +366,13 @@ impl Debug for StateUpdate {
}
cast! {
- type StateUpdate: "state update",
+ type StateUpdate,
v: Func => Self::Func(v),
v: Value => Self::Set(v),
}
/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
+#[elem(Locatable, Show)]
struct DisplayElem {
/// The state.
#[required]
@@ -419,10 +398,7 @@ impl Show for DisplayElem {
}
/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
+#[elem(Locatable, Show)]
struct UpdateElem {
/// The key that identifies the state.
#[required]
diff --git a/crates/typst-library/src/prelude.rs b/crates/typst-library/src/prelude.rs
index b4acbea0..aca5a064 100644
--- a/crates/typst-library/src/prelude.rs
+++ b/crates/typst-library/src/prelude.rs
@@ -15,15 +15,15 @@ pub use typst::diag::{bail, error, At, Hint, SourceResult, StrResult};
pub use typst::doc::*;
#[doc(no_inline)]
pub use typst::eval::{
- array, cast, dict, format_str, func, Args, Array, AutoValue, Cast, Dict, FromValue,
- Func, IntoValue, Never, NoneValue, Scope, Str, Symbol, Type, Value, Vm,
+ array, cast, dict, format_str, func, scope, ty, Args, Array, Bytes, Cast, Dict,
+ FromValue, Func, IntoValue, Scope, Str, Symbol, Type, Value, Vm,
};
#[doc(no_inline)]
pub use typst::geom::*;
#[doc(no_inline)]
pub use typst::model::{
- element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold,
- Introspector, Label, Locatable, LocatableSelector, Location, Locator, MetaElem,
+ elem, Behave, Behaviour, Construct, Content, Element, Finalize, Fold, Introspector,
+ Label, Locatable, LocatableSelector, Location, Locator, MetaElem, NativeElement,
PlainText, Resolve, Selector, Set, Show, StyleChain, StyleVec, Styles, Synthesize,
Unlabellable, Vt,
};
diff --git a/crates/typst-library/src/shared/ext.rs b/crates/typst-library/src/shared/ext.rs
index d7c80a30..e6431423 100644
--- a/crates/typst-library/src/shared/ext.rs
+++ b/crates/typst-library/src/shared/ext.rs
@@ -24,7 +24,7 @@ pub trait ContentExt {
fn backlinked(self, loc: Location) -> Self;
/// Set alignments for this content.
- fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self;
+ fn aligned(self, align: Align) -> Self;
/// Pad this content at the sides.
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
@@ -56,8 +56,8 @@ impl ContentExt for Content {
self.styled(MetaElem::set_data(vec![Meta::Elem(backlink)]))
}
- fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
- self.styled(AlignElem::set_alignment(aligns))
+ fn aligned(self, align: Align) -> Self {
+ self.styled(AlignElem::set_alignment(align))
}
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
diff --git a/crates/typst-library/src/symbols/emoji.rs b/crates/typst-library/src/symbols/emoji.rs
index 44bc3e14..b35dfcaa 100644
--- a/crates/typst-library/src/symbols/emoji.rs
+++ b/crates/typst-library/src/symbols/emoji.rs
@@ -6,7 +6,7 @@ pub fn emoji() -> Module {
for (name, symbol) in EMOJI {
scope.define(*name, symbol.clone());
}
- Module::new("emoji").with_scope(scope)
+ Module::new("emoji", scope)
}
/// A list of named emoji.
diff --git a/crates/typst-library/src/symbols/mod.rs b/crates/typst-library/src/symbols/mod.rs
index 5036aa11..0d288c3b 100644
--- a/crates/typst-library/src/symbols/mod.rs
+++ b/crates/typst-library/src/symbols/mod.rs
@@ -10,6 +10,8 @@ use crate::prelude::*;
/// Hook up all symbol definitions.
pub(super) fn define(global: &mut Scope) {
- global.define("sym", sym());
- global.define("emoji", emoji());
+ global.category("symbols");
+ global.define_type::<Symbol>();
+ global.define_module(sym());
+ global.define_module(emoji());
}
diff --git a/crates/typst-library/src/symbols/sym.rs b/crates/typst-library/src/symbols/sym.rs
index a9ec2485..bfc22ea4 100644
--- a/crates/typst-library/src/symbols/sym.rs
+++ b/crates/typst-library/src/symbols/sym.rs
@@ -6,7 +6,7 @@ pub fn sym() -> Module {
for (name, symbol) in SYM {
scope.define(*name, symbol.clone());
}
- Module::new("sym").with_scope(scope)
+ Module::new("sym", scope)
}
/// The list of general symbols.
diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs
index c97ef325..e34bf363 100644
--- a/crates/typst-library/src/text/deco.rs
+++ b/crates/typst-library/src/text/deco.rs
@@ -6,19 +6,15 @@ use crate::prelude::*;
/// Underlines text.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// This is #underline[important].
/// ```
-///
-/// Display: Underline
-/// Category: text
-#[element(Show)]
+#[elem(Show)]
pub struct UnderlineElem {
- /// How to stroke the line.
+ /// How to [stroke]($stroke) the line.
///
- /// See the [line's documentation]($func/line.stroke) for more details. If
- /// set to `{auto}`, takes on the text's color and a thickness defined in
+ /// If set to `{auto}`, takes on the text's color and a thickness defined in
/// the current font.
///
/// ```example
@@ -30,7 +26,7 @@ pub struct UnderlineElem {
/// ```
#[resolve]
#[fold]
- pub stroke: Smart<PartialStroke>,
+ pub stroke: Smart<Stroke>,
/// The position of the line relative to the baseline, read from the font
/// tables if `{auto}`.
@@ -85,19 +81,15 @@ impl Show for UnderlineElem {
/// Adds a line over text.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #overline[A line over text.]
/// ```
-///
-/// Display: Overline
-/// Category: text
-#[element(Show)]
+#[elem(Show)]
pub struct OverlineElem {
- /// How to stroke the line.
+ /// How to [stroke]($stroke) the line.
///
- /// See the [line's documentation]($func/line.stroke) for more details. If
- /// set to `{auto}`, takes on the text's color and a thickness defined in
+ /// If set to `{auto}`, takes on the text's color and a thickness defined in
/// the current font.
///
/// ```example
@@ -110,7 +102,7 @@ pub struct OverlineElem {
/// ```
#[resolve]
#[fold]
- pub stroke: Smart<PartialStroke>,
+ pub stroke: Smart<Stroke>,
/// The position of the line relative to the baseline. Read from the font
/// tables if `{auto}`.
@@ -170,23 +162,19 @@ impl Show for OverlineElem {
/// Strikes through text.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// This is #strike[not] relevant.
/// ```
-///
-/// Display: Strikethrough
-/// Category: text
-#[element(Show)]
+#[elem(title = "Strikethrough", Show)]
pub struct StrikeElem {
- /// How to stroke the line.
+ /// How to [stroke]($stroke) the line.
///
- /// See the [line's documentation]($func/line.stroke) for more details. If
- /// set to `{auto}`, takes on the text's color and a thickness defined in
+ /// If set to `{auto}`, takes on the text's color and a thickness defined in
/// the current font.
///
- /// _Note:_ Please don't use this for real redaction as you can still
- /// copy paste the text.
+ /// _Note:_ Please don't use this for real redaction as you can still copy
+ /// paste the text.
///
/// ```example
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
@@ -194,7 +182,7 @@ pub struct StrikeElem {
/// ```
#[resolve]
#[fold]
- pub stroke: Smart<PartialStroke>,
+ pub stroke: Smart<Stroke>,
/// The position of the line relative to the baseline. Read from the font
/// tables if `{auto}`.
@@ -240,14 +228,11 @@ impl Show for StrikeElem {
/// Highlights text with a background color.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// This is #highlight[important].
/// ```
-///
-/// Display: Highlight
-/// Category: text
-#[element(Show)]
+#[elem(Show)]
pub struct HighlightElem {
/// The color to highlight the text with.
/// (Default: 0xffff5f)
@@ -316,6 +301,7 @@ impl Show for HighlightElem {
/// Defines a line-based decoration that is positioned over, under or on top of text,
/// or highlights the text with a background.
+#[ty]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Decoration {
line: DecoLine,
@@ -332,15 +318,15 @@ impl Fold for Decoration {
}
cast! {
- type Decoration: "decoration",
+ type Decoration,
}
/// A kind of decorative line.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum DecoLine {
- Underline { stroke: PartialStroke<Abs>, offset: Smart<Abs>, evade: bool },
- Strikethrough { stroke: PartialStroke<Abs>, offset: Smart<Abs> },
- Overline { stroke: PartialStroke<Abs>, offset: Smart<Abs>, evade: bool },
+ Underline { stroke: Stroke<Abs>, offset: Smart<Abs>, evade: bool },
+ Strikethrough { stroke: Stroke<Abs>, offset: Smart<Abs> },
+ Overline { stroke: Stroke<Abs>, offset: Smart<Abs>, evade: bool },
Highlight { fill: Paint, top_edge: TopEdge, bottom_edge: BottomEdge },
}
@@ -378,10 +364,10 @@ pub(super) fn decorate(
};
let offset = offset.unwrap_or(-metrics.position.at(text.size)) - shift;
- let stroke = stroke.clone().unwrap_or(Stroke {
+ let stroke = stroke.clone().unwrap_or(FixedStroke {
paint: text.fill.clone(),
thickness: metrics.thickness.at(text.size),
- ..Stroke::default()
+ ..FixedStroke::default()
});
let gap_padding = 0.08 * text.size;
diff --git a/crates/typst-library/src/text/misc.rs b/crates/typst-library/src/text/misc.rs
index 811b027e..73657345 100644
--- a/crates/typst-library/src/text/misc.rs
+++ b/crates/typst-library/src/text/misc.rs
@@ -2,10 +2,7 @@ use super::TextElem;
use crate::prelude::*;
/// A text space.
-///
-/// Display: Space
-/// Category: text
-#[element(Behave, Unlabellable, PlainText)]
+#[elem(Behave, Unlabellable, PlainText)]
pub struct SpaceElem {}
impl Behave for SpaceElem {
@@ -28,21 +25,18 @@ impl PlainText for SpaceElem {
/// end of a paragraph is ignored, but more than one creates additional empty
/// lines.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// *Date:* 26.12.2022 \
/// *Topic:* Infrastructure Test \
/// *Severity:* High \
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: To insert a line break, simply write
/// a backslash followed by whitespace. This always creates an unjustified
/// break.
-///
-/// Display: Line Break
-/// Category: text
-#[element(Behave)]
+#[elem(title = "Line Break", Behave)]
pub struct LinebreakElem {
/// Whether to justify the line before the break.
///
@@ -71,7 +65,7 @@ impl Behave for LinebreakElem {
///
/// Increases the current font weight by a given `delta`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// This is *strong.* \
/// This is #strong[too.] \
@@ -80,15 +74,12 @@ impl Behave for LinebreakElem {
/// And this is *evermore.*
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: To strongly emphasize content,
/// simply enclose it in stars/asterisks (`*`). Note that this only works at
/// word boundaries. To strongly emphasize part of a word, you have to use the
/// function.
-///
-/// Display: Strong Emphasis
-/// Category: text
-#[element(Show)]
+#[elem(title = "Strong Emphasis", Show)]
pub struct StrongElem {
/// The delta to apply on the font weight.
///
@@ -131,12 +122,12 @@ impl Fold for Delta {
/// Emphasizes content by setting it in italics.
///
-/// - If the current [text style]($func/text.style) is `{"normal"}`,
-/// this turns it into `{"italic"}`.
-/// - If it is already `{"italic"}` or `{"oblique"}`,
-/// it turns it back to `{"normal"}`.
+/// - If the current [text style]($text.style) is `{"normal"}`, this turns it
+/// into `{"italic"}`.
+/// - If it is already `{"italic"}` or `{"oblique"}`, it turns it back to
+/// `{"normal"}`.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// This is _emphasized._ \
/// This is #emph[too.]
@@ -148,14 +139,11 @@ impl Fold for Delta {
/// This is _emphasized_ differently.
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: To emphasize content, simply
/// enclose it in underscores (`_`). Note that this only works at word
/// boundaries. To emphasize part of a word, you have to use the function.
-///
-/// Display: Emphasis
-/// Category: text
-#[element(Show)]
+#[elem(title = "Emphasis", Show)]
pub struct EmphElem {
/// The content to emphasize.
#[required]
@@ -189,16 +177,13 @@ impl Fold for Toggle {
/// Converts text or content to lowercase.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #lower("ABC") \
/// #lower[*My Text*] \
/// #lower[already low]
/// ```
-///
-/// Display: Lowercase
-/// Category: text
-#[func]
+#[func(title = "Lowercase")]
pub fn lower(
/// The text to convert to lowercase.
text: Caseable,
@@ -208,16 +193,13 @@ pub fn lower(
/// Converts text or content to uppercase.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #upper("abc") \
/// #upper[*my text*] \
/// #upper[ALREADY HIGH]
/// ```
-///
-/// Display: Uppercase
-/// Category: text
-#[func]
+#[func(title = "Uppercase")]
pub fn upper(
/// The text to convert to uppercase.
text: Caseable,
@@ -278,7 +260,7 @@ impl Case {
/// support selecting a dedicated smallcaps font as well as synthesizing
/// smallcaps from normal letters, but this is not yet implemented.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #set par(justify: true)
/// #set heading(numbering: "I.")
@@ -292,10 +274,7 @@ impl Case {
/// = Introduction
/// #lorem(40)
/// ```
-///
-/// Display: Small Capitals
-/// Category: text
-#[func]
+#[func(title = "Small Capitals")]
pub fn smallcaps(
/// The text to display to small capitals.
body: Content,
@@ -310,7 +289,7 @@ pub fn smallcaps(
/// the same but randomly chosen. As usual for blind texts, it does not make any
/// sense. Use it as a placeholder to try layouts.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// = Blind Text
/// #lorem(30)
@@ -318,10 +297,7 @@ pub fn smallcaps(
/// = More Blind Text
/// #lorem(15)
/// ```
-///
-/// Display: Blind Text
-/// Category: text
-#[func]
+#[func(keywords = ["Blind Text"])]
pub fn lorem(
/// The length of the blind text in words.
words: usize,
diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs
index 4f3c1591..8bce5e8a 100644
--- a/crates/typst-library/src/text/mod.rs
+++ b/crates/typst-library/src/text/mod.rs
@@ -23,22 +23,23 @@ use crate::prelude::*;
/// Hook up all text definitions.
pub(super) fn define(global: &mut Scope) {
- global.define("text", TextElem::func());
- global.define("linebreak", LinebreakElem::func());
- global.define("smartquote", SmartQuoteElem::func());
- global.define("strong", StrongElem::func());
- global.define("emph", EmphElem::func());
- global.define("lower", lower_func());
- global.define("upper", upper_func());
- global.define("smallcaps", smallcaps_func());
- global.define("sub", SubElem::func());
- global.define("super", SuperElem::func());
- global.define("underline", UnderlineElem::func());
- global.define("strike", StrikeElem::func());
- global.define("highlight", HighlightElem::func());
- global.define("overline", OverlineElem::func());
- global.define("raw", RawElem::func());
- global.define("lorem", lorem_func());
+ global.category("text");
+ global.define_elem::<TextElem>();
+ global.define_elem::<LinebreakElem>();
+ global.define_elem::<SmartquoteElem>();
+ global.define_elem::<StrongElem>();
+ global.define_elem::<EmphElem>();
+ global.define_elem::<SubElem>();
+ global.define_elem::<SuperElem>();
+ global.define_elem::<UnderlineElem>();
+ global.define_elem::<OverlineElem>();
+ global.define_elem::<StrikeElem>();
+ global.define_elem::<HighlightElem>();
+ global.define_elem::<RawElem>();
+ global.define_func::<lower>();
+ global.define_func::<upper>();
+ global.define_func::<smallcaps>();
+ global.define_func::<lorem>();
}
/// Customizes the look and layout of text in a variety of ways.
@@ -47,7 +48,7 @@ pub(super) fn define(global: &mut Scope) {
/// the set rule is often the simpler choice, calling the `text` function
/// directly can be useful when passing text as an argument to another function.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #set text(18pt)
/// With a set rule.
@@ -56,10 +57,7 @@ pub(super) fn define(global: &mut Scope) {
/// With a function call.
/// ])
/// ```
-///
-/// Display: Text
-/// Category: text
-#[element(Construct, PlainText)]
+#[elem(Construct, PlainText)]
pub struct TextElem {
/// A prioritized sequence of font families.
///
@@ -111,8 +109,8 @@ pub struct TextElem {
/// italic and oblique style is rarely observable.
///
/// If you want to emphasize your text, you should do so using the
- /// [emph]($func/emph) function instead. This makes it easy to adapt the
- /// style later if you change your mind about how to signify the emphasis.
+ /// [emph]($emph) function instead. This makes it easy to adapt the style
+ /// later if you change your mind about how to signify the emphasis.
///
/// ```example
/// #text(font: "Linux Libertine", style: "italic")[Italic]
@@ -126,7 +124,7 @@ pub struct TextElem {
/// that is closest in weight.
///
/// If you want to strongly emphasize your text, you should do so using the
- /// [strong]($func/strong) function instead. This makes it easy to adapt the
+ /// [strong]($strong) function instead. This makes it easy to adapt the
/// style later if you change your mind about how to signify the strong
/// emphasis.
///
@@ -147,7 +145,7 @@ pub struct TextElem {
/// the text if a condensed or expanded version of the font is available.
///
/// If you want to adjust the amount of space between characters instead of
- /// stretching the glyphs itself, use the [`tracking`]($func/text.tracking)
+ /// stretching the glyphs itself, use the [`tracking`]($text.tracking)
/// property instead.
///
/// ```example
@@ -196,7 +194,7 @@ pub struct TextElem {
/// the space character in the font.
///
/// If you want to adjust the amount of space between characters rather than
- /// words, use the [`tracking`]($func/text.tracking) property instead.
+ /// words, use the [`tracking`]($text.tracking) property instead.
///
/// ```example
/// #set text(spacing: 200%)
@@ -272,7 +270,7 @@ pub struct TextElem {
///
/// - The text processing pipeline can make more informed choices.
/// - Hyphenation will use the correct patterns for the language.
- /// - [Smart quotes]($func/smartquote) turns into the correct quotes for the
+ /// - [Smart quotes]($smartquote) turns into the correct quotes for the
/// language.
/// - And all other things which are language-aware.
///
@@ -327,13 +325,13 @@ pub struct TextElem {
/// - `{rtl}`: Layout text from right to left.
///
/// When writing in right-to-left scripts like Arabic or Hebrew, you should
- /// set the [text language]($func/text.lang) or direction. While individual
- /// runs of text are automatically layouted in the correct direction,
- /// setting the dominant direction gives the bidirectional reordering
- /// algorithm the necessary information to correctly place punctuation and
- /// inline objects. Furthermore, setting the direction affects the alignment
- /// values `start` and `end`, which are equivalent to `left` and `right` in
- /// `ltr` text and the other way around in `rtl` text.
+ /// set the [text language]($text.lang) or direction. While individual runs
+ /// of text are automatically layouted in the correct direction, setting the
+ /// dominant direction gives the bidirectional reordering algorithm the
+ /// necessary information to correctly place punctuation and inline objects.
+ /// Furthermore, setting the direction affects the alignment values `start`
+ /// and `end`, which are equivalent to `left` and `right` in `ltr` text and
+ /// the other way around in `rtl` text.
///
/// If you set this to `rtl` and experience bugs or in some way bad looking
/// output, please do get in touch with us through the
@@ -350,7 +348,7 @@ pub struct TextElem {
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
/// will be hyphenated if and only if justification is enabled.
///
- /// Setting the [text language]($func/text.lang) ensures that the correct
+ /// Setting the [text language]($text.lang) ensures that the correct
/// hyphenation patterns are used.
///
/// ```example
diff --git a/crates/typst-library/src/text/quotes.rs b/crates/typst-library/src/text/quotes.rs
index cf4a03d5..a47f7ed5 100644
--- a/crates/typst-library/src/text/quotes.rs
+++ b/crates/typst-library/src/text/quotes.rs
@@ -5,9 +5,9 @@ use crate::prelude::*;
/// A language-aware quote that reacts to its context.
///
/// Automatically turns into an appropriate opening or closing quote based on
-/// the active [text language]($func/text.lang).
+/// the active [text language]($text.lang).
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// "This is in quotes."
///
@@ -18,14 +18,11 @@ use crate::prelude::*;
/// "C'est entre guillemets."
/// ```
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax: The normal quote characters
/// (`'` and `"`). Typst automatically makes your quotes smart.
-///
-/// Display: Smart Quote
-/// Category: text
-#[element]
-pub struct SmartQuoteElem {
+#[elem]
+pub struct SmartquoteElem {
/// Whether this should be a double quote.
#[default(true)]
pub double: bool,
diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs
index a5699afd..1f46f94d 100644
--- a/crates/typst-library/src/text/raw.rs
+++ b/crates/typst-library/src/text/raw.rs
@@ -12,7 +12,7 @@ use typst::util::option_eq;
use unicode_segmentation::UnicodeSegmentation;
use super::{
- FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize,
+ FontFamily, FontList, Hyphenate, LinebreakElem, SmartquoteElem, TextElem, TextSize,
};
use crate::layout::BlockElem;
use crate::meta::{Figurable, LocalName};
@@ -23,7 +23,7 @@ use crate::prelude::*;
/// Displays the text verbatim and in a monospace font. This is typically used
/// to embed computer code into your document.
///
-/// ## Example { #example }
+/// # Example
/// ````example
/// Adding `rbx` to `rcx` gives
/// the desired result.
@@ -43,7 +43,7 @@ use crate::prelude::*;
/// also trimmed.
/// ````
///
-/// ## Syntax { #syntax }
+/// # Syntax
/// This function also has dedicated syntax. You can enclose text in 1 or 3+
/// backticks (`` ` ``) to make it raw. Two backticks produce empty raw text.
/// When you use three or more backticks, you can additionally specify a
@@ -57,10 +57,15 @@ use crate::prelude::*;
/// needed, start the text with a single space (which will be trimmed) or use
/// the single backtick syntax. If your text should start or end with a
/// backtick, put a space before or after it (it will be trimmed).
-///
-/// Display: Raw Text / Code
-/// Category: text
-#[element(Synthesize, Show, Finalize, LocalName, Figurable, PlainText)]
+#[elem(
+ title = "Raw Text / Code",
+ Synthesize,
+ Show,
+ Finalize,
+ LocalName,
+ Figurable,
+ PlainText
+)]
pub struct RawElem {
/// The raw text.
///
@@ -153,8 +158,8 @@ pub struct RawElem {
/// code = "centered"
/// ```
/// ````
- #[default(HorizontalAlign(GenAlign::Start))]
- pub align: HorizontalAlign,
+ #[default(HAlign::Start)]
+ pub align: HAlign,
/// One or multiple additional syntax definitions to load. The syntax
/// definitions should be in the
@@ -190,10 +195,10 @@ pub struct RawElem {
/// Applying a theme only affects the color of specifically highlighted
/// text. It does not consider the theme's foreground and background
/// properties, so that you retain control over the color of raw text. You
- /// can apply the foreground color yourself with the [`text`]($func/text)
- /// function and the background with a [filled block]($func/block.fill). You
- /// could also use the [`xml`]($func/xml) function to extract these
- /// properties from the theme.
+ /// can apply the foreground color yourself with the [`text`]($text)
+ /// function and the background with a [filled block]($block.fill). You
+ /// could also use the [`xml`]($xml) function to extract these properties
+ /// from the theme.
///
/// ````example
/// #set raw(theme: "halcyon.tmTheme")
@@ -340,7 +345,7 @@ impl Show for RawElem {
if self.block(styles) {
// Align the text before inserting it into the block.
- realized = realized.aligned(Axes::with_x(Some(self.align(styles).into())));
+ realized = realized.aligned(self.align(styles).into());
realized = BlockElem::new().with_body(Some(realized)).pack();
}
@@ -356,7 +361,7 @@ impl Finalize for RawElem {
styles.set(TextElem::set_size(TextSize(Em::new(0.8).into())));
styles
.set(TextElem::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")])));
- styles.set(SmartQuoteElem::set_enabled(false));
+ styles.set(SmartquoteElem::set_enabled(false));
realized.styled_with_map(styles)
}
}
diff --git a/crates/typst-library/src/text/shift.rs b/crates/typst-library/src/text/shift.rs
index 65e309e1..6cb4d895 100644
--- a/crates/typst-library/src/text/shift.rs
+++ b/crates/typst-library/src/text/shift.rs
@@ -5,14 +5,11 @@ use crate::prelude::*;
///
/// The text is rendered smaller and its baseline is lowered.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// Revenue#sub[yearly]
/// ```
-///
-/// Display: Subscript
-/// Category: text
-#[element(Show)]
+#[elem(title = "Subscript", Show)]
pub struct SubElem {
/// Whether to prefer the dedicated subscript characters of the font.
///
@@ -68,14 +65,11 @@ impl Show for SubElem {
///
/// The text is rendered smaller and its baseline is raised.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// 1#super[st] try!
/// ```
-///
-/// Display: Superscript
-/// Category: text
-#[element(Show)]
+#[elem(title = "Superscript", Show)]
pub struct SuperElem {
/// Whether to prefer the dedicated superscript characters of the font.
///
diff --git a/crates/typst-library/src/visualize/image.rs b/crates/typst-library/src/visualize/image.rs
index a06509dd..e6269198 100644
--- a/crates/typst-library/src/visualize/image.rs
+++ b/crates/typst-library/src/visualize/image.rs
@@ -18,7 +18,7 @@ use crate::text::families;
/// in the resulting PDF. Make sure to double-check embedded SVG images. If you
/// have an issue, also feel free to report it on [GitHub][gh-svg].
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #figure(
/// image("molecular.jpg", width: 80%),
@@ -30,14 +30,7 @@ use crate::text::families;
/// ```
///
/// [gh-svg]: https://github.com/typst/typst/issues?q=is%3Aopen+is%3Aissue+label%3Asvg
-///
-/// Display: Image
-/// Category: visualize
-#[element(Layout, LocalName, Figurable)]
-#[scope(
- scope.define("decode", image_decode_func());
- scope
-)]
+#[elem(scope, Layout, LocalName, Figurable)]
pub struct ImageElem {
/// Path to an image file.
#[required]
@@ -73,59 +66,58 @@ pub struct ImageElem {
pub fit: ImageFit,
}
-/// Decode a raster or vector graphic from bytes or a string.
-///
-/// ## Example { #example }
-/// ```example
-/// #let original = read("diagram.svg")
-/// #let changed = original.replace(
-/// "#2B80FF", // blue
-/// green.hex(),
-/// )
-///
-/// #image.decode(original)
-/// #image.decode(changed)
-/// ```
-///
-/// Display: Decode Image
-/// Category: visualize
-#[func]
-pub fn image_decode(
- /// The data to decode as an image. Can be a string for SVGs.
- data: Readable,
- /// The image's format. Detected automatically by default.
- #[named]
- format: Option<Smart<ImageFormat>>,
- /// The width of the image.
- #[named]
- width: Option<Smart<Rel<Length>>>,
- /// The height of the image.
- #[named]
- height: Option<Smart<Rel<Length>>>,
- /// A text describing the image.
- #[named]
- alt: Option<Option<EcoString>>,
- /// How the image should adjust itself to a given area.
- #[named]
- fit: Option<ImageFit>,
-) -> StrResult<Content> {
- let mut elem = ImageElem::new(EcoString::new(), data);
- if let Some(format) = format {
- elem.push_format(format);
- }
- if let Some(width) = width {
- elem.push_width(width);
- }
- if let Some(height) = height {
- elem.push_height(height);
- }
- if let Some(alt) = alt {
- elem.push_alt(alt);
- }
- if let Some(fit) = fit {
- elem.push_fit(fit);
+#[scope]
+impl ImageElem {
+ /// Decode a raster or vector graphic from bytes or a string.
+ ///
+ /// ```example
+ /// #let original = read("diagram.svg")
+ /// #let changed = original.replace(
+ /// "#2B80FF", // blue
+ /// green.to-hex(),
+ /// )
+ ///
+ /// #image.decode(original)
+ /// #image.decode(changed)
+ /// ```
+ #[func(title = "Decode Image")]
+ pub fn decode(
+ /// The data to decode as an image. Can be a string for SVGs.
+ data: Readable,
+ /// The image's format. Detected automatically by default.
+ #[named]
+ format: Option<Smart<ImageFormat>>,
+ /// The width of the image.
+ #[named]
+ width: Option<Smart<Rel<Length>>>,
+ /// The height of the image.
+ #[named]
+ height: Option<Smart<Rel<Length>>>,
+ /// A text describing the image.
+ #[named]
+ alt: Option<Option<EcoString>>,
+ /// How the image should adjust itself to a given area.
+ #[named]
+ fit: Option<ImageFit>,
+ ) -> StrResult<Content> {
+ let mut elem = ImageElem::new(EcoString::new(), data);
+ if let Some(format) = format {
+ elem.push_format(format);
+ }
+ if let Some(width) = width {
+ elem.push_width(width);
+ }
+ if let Some(height) = height {
+ elem.push_height(height);
+ }
+ if let Some(alt) = alt {
+ elem.push_alt(alt);
+ }
+ if let Some(fit) = fit {
+ elem.push_fit(fit);
+ }
+ Ok(elem.pack())
}
- Ok(elem.pack())
}
impl Layout for ImageElem {
@@ -175,8 +167,7 @@ impl Layout for ImageElem {
let sizing = Axes::new(self.width(styles), self.height(styles));
let region = sizing
- .zip(regions.base())
- .map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r)))
+ .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)))
.unwrap_or(regions.base());
let expand = sizing.as_ref().map(Smart::is_custom) | regions.expand;
@@ -217,7 +208,7 @@ impl Layout for ImageElem {
// process.
let mut frame = Frame::new(fitted);
frame.push(Point::zero(), FrameItem::Image(image, fitted, self.span()));
- frame.resize(target, Align::CENTER_HORIZON);
+ frame.resize(target, Axes::splat(FixedAlign::Center));
// Create a clipping group if only part of the image should be visible.
if fit == ImageFit::Cover && !target.fits(fitted) {
diff --git a/crates/typst-library/src/visualize/line.rs b/crates/typst-library/src/visualize/line.rs
index a476ffa7..9960a2d3 100644
--- a/crates/typst-library/src/visualize/line.rs
+++ b/crates/typst-library/src/visualize/line.rs
@@ -2,7 +2,7 @@ use crate::prelude::*;
/// A line from one point to another.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #set page(height: 100pt)
///
@@ -13,10 +13,7 @@ use crate::prelude::*;
/// stroke: 2pt + maroon,
/// )
/// ```
-///
-/// Display: Line
-/// Category: visualize
-#[element(Layout)]
+#[elem(Layout)]
pub struct LineElem {
/// The start point of the line.
///
@@ -37,42 +34,7 @@ pub struct LineElem {
/// respected if `end` is `none`.
pub angle: Angle,
- /// How to stroke the line. This can be:
- ///
- /// - A length specifying the stroke's thickness. The color is inherited,
- /// defaulting to black.
- /// - A color to use for the stroke. The thickness is inherited, defaulting
- /// to `{1pt}`.
- /// - A stroke combined from color and thickness using the `+` operator as
- /// in `{2pt + red}`.
- /// - A stroke described by a dictionary with any of the following keys:
- /// - `paint`: The [color]($type/color) to use for the stroke.
- /// - `thickness`: The stroke's thickness as a [length]($type/length).
- /// - `cap`: How the line terminates. One of `{"butt"}`, `{"round"}`, or
- /// `{"square"}`.
- /// - `join`: How sharp turns of a contour are rendered. One of
- /// `{"miter"}`, `{"round"}`, or `{"bevel"}`. Not applicable to lines
- /// but to [polygons]($func/polygon) or [paths]($func/path).
- /// - `miter-limit`: Number at which protruding sharp angles are rendered
- /// with a bevel instead. The higher the number, the sharper an angle
- /// can be before it is bevelled. Only applicable if `join` is
- /// `{"miter"}`. Defaults to `{4.0}`.
- /// - `dash`: The dash pattern to use. Can be any of the following:
- /// - One of the predefined patterns `{"solid"}`, `{"dotted"}`,
- /// `{"densely-dotted"}`, `{"loosely-dotted"}`, `{"dashed"}`,
- /// `{"densely-dashed"}`, `{"loosely-dashed"}`, `{"dash-dotted"}`,
- /// `{"densely-dash-dotted"}` or `{"loosely-dash-dotted"}`
- /// - An [array]($type/array) with alternating lengths for dashes and
- /// gaps. You can also use the string `{"dot"}` for a length equal to
- /// the line thickness.
- /// - A [dictionary]($type/dictionary) with the keys `array` (same as
- /// the array above), and `phase` (of type [length]($type/length)),
- /// which defines where in the pattern to start drawing.
- ///
- /// On a `stroke` object, you can access any of the fields mentioned in the
- /// dictionary format above. For example, `{(2pt + blue).thickness}` is
- /// `{2pt}`, `{(2pt + blue).miter-limit}` is `{4.0}` (the default), and so
- /// on.
+ /// How to [stroke]($stroke) the line.
///
/// ```example
/// #set line(length: 100%)
@@ -86,7 +48,7 @@ pub struct LineElem {
/// ```
#[resolve]
#[fold]
- pub stroke: PartialStroke,
+ pub stroke: Stroke,
}
impl Layout for LineElem {
@@ -97,10 +59,8 @@ impl Layout for LineElem {
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let resolve = |axes: Axes<Rel<Abs>>| {
- axes.zip(regions.base()).map(|(l, b)| l.relative_to(b))
- };
-
+ let resolve =
+ |axes: Axes<Rel<Abs>>| axes.zip_map(regions.base(), Rel::relative_to);
let start = resolve(self.start(styles));
let delta =
self.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
diff --git a/crates/typst-library/src/visualize/mod.rs b/crates/typst-library/src/visualize/mod.rs
index ea873f44..a013853f 100644
--- a/crates/typst-library/src/visualize/mod.rs
+++ b/crates/typst-library/src/visualize/mod.rs
@@ -16,30 +16,15 @@ use crate::prelude::*;
/// Hook up all visualize definitions.
pub(super) fn define(global: &mut Scope) {
- global.define("image", ImageElem::func());
- global.define("line", LineElem::func());
- global.define("rect", RectElem::func());
- global.define("square", SquareElem::func());
- global.define("ellipse", EllipseElem::func());
- global.define("circle", CircleElem::func());
- global.define("polygon", PolygonElem::func());
- global.define("path", PathElem::func());
- 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.category("visualize");
+ global.define_type::<Color>();
+ global.define_type::<Stroke>();
+ global.define_elem::<ImageElem>();
+ global.define_elem::<LineElem>();
+ global.define_elem::<RectElem>();
+ global.define_elem::<SquareElem>();
+ global.define_elem::<EllipseElem>();
+ global.define_elem::<CircleElem>();
+ global.define_elem::<PolygonElem>();
+ global.define_elem::<PathElem>();
}
diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs
index d78abce1..c252e95f 100644
--- a/crates/typst-library/src/visualize/path.rs
+++ b/crates/typst-library/src/visualize/path.rs
@@ -7,7 +7,7 @@ use PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
/// A path through a list of points, connected by Bezier curves.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #path(
/// fill: blue.lighten(80%),
@@ -18,26 +18,24 @@ use PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
/// ((50%, 0pt), (40pt, 0pt)),
/// )
/// ```
-///
-/// Display: Path
-/// Category: visualize
-#[element(Layout)]
+#[elem(Layout)]
pub struct PathElem {
- /// How to fill the path. See the
- /// [rectangle's documentation]($func/rect.fill) for more details.
+ /// How to fill the path.
+ ///
+ /// When setting a fill, the default stroke disappears. To create a
+ /// rectangle with both fill and stroke, you have to configure both.
///
- /// Currently all paths are filled according to the
- /// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
+ /// Currently all paths are filled according to the [non-zero winding
+ /// rule](https://en.wikipedia.org/wiki/Nonzero-rule).
pub fill: Option<Paint>,
- /// How to stroke the path. This can be:
+ /// How to [stroke]($stroke) the path. This can be:
///
- /// See the [line's documentation]($func/line.stroke) for more details. Can
- /// be set to `{none}` to disable the stroke or to `{auto}` for a stroke of
- /// `{1pt}` black if and if only if no fill is given.
+ /// Can be set to `{none}` to disable the stroke or to `{auto}` for a
+ /// stroke of `{1pt}` black if and if only if no fill is given.
#[resolve]
#[fold]
- pub stroke: Smart<Option<PartialStroke>>,
+ pub stroke: Smart<Option<Stroke>>,
/// Whether to close this path with one last bezier curve. This curve will
/// takes into account the adjacent control points. If you want to close
@@ -50,8 +48,8 @@ pub struct PathElem {
///
/// Each vertex can be defined in 3 ways:
///
- /// - A regular point, as given to the [`line`]($func/line) or
- /// [`polygon`]($func/polygon) function.
+ /// - A regular point, as given to the [`line`]($line) or
+ /// [`polygon`]($polygon) function.
/// - An array of two points, the first being the vertex and the second
/// being the control point. The control point is expressed relative to
/// the vertex and is mirrored to get the second control point. The given
@@ -60,7 +58,7 @@ pub struct PathElem {
/// the curve going out of this vertex.
/// - An array of three points, the first being the vertex and the next
/// being the control points (control point for curves coming in and out,
- /// respectively)
+ /// respectively).
#[variadic]
pub vertices: Vec<PathVertex>,
}
@@ -75,8 +73,7 @@ impl Layout for PathElem {
) -> SourceResult<Fragment> {
let resolve = |axes: Axes<Rel<Length>>| {
axes.resolve(styles)
- .zip(regions.base())
- .map(|(l, b)| l.relative_to(b))
+ .zip_map(regions.base(), Rel::relative_to)
.to_point()
};
@@ -136,9 +133,9 @@ impl Layout for PathElem {
// Prepare fill and stroke.
let fill = self.fill(styles);
let stroke = match self.stroke(styles) {
- Smart::Auto if fill.is_none() => Some(Stroke::default()),
+ Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
Smart::Auto => None,
- Smart::Custom(stroke) => stroke.map(PartialStroke::unwrap_or_default),
+ Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
};
let mut frame = Frame::new(size);
diff --git a/crates/typst-library/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs
index b244b2e9..9f573467 100644
--- a/crates/typst-library/src/visualize/polygon.rs
+++ b/crates/typst-library/src/visualize/polygon.rs
@@ -6,7 +6,7 @@ use crate::prelude::*;
///
/// The polygon is defined by its corner points and is closed automatically.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// #polygon(
/// fill: blue.lighten(80%),
@@ -17,37 +17,102 @@ use crate::prelude::*;
/// (0%, 2cm),
/// )
/// ```
-///
-/// Display: Polygon
-/// Category: visualize
-#[element(Layout)]
-#[scope(
- scope.define("regular", polygon_regular_func());
- scope
-)]
+#[elem(scope, Layout)]
pub struct PolygonElem {
- /// How to fill the polygon. See the
- /// [rectangle's documentation]($func/rect.fill) for more details.
+ /// How to fill the polygon.
+ ///
+ /// When setting a fill, the default stroke disappears. To create a
+ /// rectangle with both fill and stroke, you have to configure both.
///
/// Currently all polygons are filled according to the
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
pub fill: Option<Paint>,
- /// How to stroke the polygon. This can be:
+ /// How to [stroke]($stroke) the polygon. This can be:
///
- /// See the [line's documentation]($func/line.stroke) for more details. Can
- /// be set to `{none}` to disable the stroke or to `{auto}` for a stroke of
- /// `{1pt}` black if and if only if no fill is given.
+ /// Can be set to `{none}` to disable the stroke or to `{auto}` for a
+ /// stroke of `{1pt}` black if and if only if no fill is given.
#[resolve]
#[fold]
- pub stroke: Smart<Option<PartialStroke>>,
+ pub stroke: Smart<Option<Stroke>>,
/// The vertices of the polygon. Each point is specified as an array of two
- /// [relative lengths]($type/relative-length).
+ /// [relative lengths]($relative).
#[variadic]
pub vertices: Vec<Axes<Rel<Length>>>,
}
+#[scope]
+impl PolygonElem {
+ /// A regular polygon, defined by its size and number of vertices.
+ ///
+ /// ```example
+ /// #polygon.regular(
+ /// fill: blue.lighten(80%),
+ /// stroke: blue,
+ /// size: 30pt,
+ /// vertices: 3,
+ /// )
+ /// ```
+ #[func(title = "Regular Polygon")]
+ pub fn regular(
+ /// How to fill the polygon. See the general
+ /// [polygon's documentation]($polygon.fill) for more details.
+ #[named]
+ fill: Option<Option<Paint>>,
+
+ /// How to stroke the polygon. See the general
+ /// [polygon's documentation]($polygon.stroke) for more details.
+ #[named]
+ stroke: Option<Smart<Option<Stroke>>>,
+
+ /// The diameter of the [circumcircle](https://en.wikipedia.org/wiki/Circumcircle)
+ /// of the regular polygon.
+ #[named]
+ #[default(Em::one().into())]
+ size: Length,
+
+ /// The number of vertices in the polygon.
+ #[named]
+ #[default(3)]
+ vertices: u64,
+ ) -> Content {
+ let radius = size / 2.0;
+ let angle = |i: f64| {
+ 2.0 * PI * i / (vertices as f64) + PI * (1.0 / 2.0 - 1.0 / vertices as f64)
+ };
+ let (horizontal_offset, vertical_offset) = (0..=vertices)
+ .map(|v| {
+ (
+ (radius * angle(v as f64).cos()) + radius,
+ (radius * angle(v as f64).sin()) + radius,
+ )
+ })
+ .fold((radius, radius), |(min_x, min_y), (v_x, v_y)| {
+ (
+ if min_x < v_x { min_x } else { v_x },
+ if min_y < v_y { min_y } else { v_y },
+ )
+ });
+ let vertices = (0..=vertices)
+ .map(|v| {
+ let x = (radius * angle(v as f64).cos()) + radius - horizontal_offset;
+ let y = (radius * angle(v as f64).sin()) + radius - vertical_offset;
+ Axes::new(x, y).map(Rel::from)
+ })
+ .collect();
+
+ let mut elem = PolygonElem::new(vertices);
+ if let Some(fill) = fill {
+ elem.push_fill(fill);
+ }
+ if let Some(stroke) = stroke {
+ elem.push_stroke(stroke);
+ }
+ elem.pack()
+ }
+}
+
impl Layout for PolygonElem {
#[tracing::instrument(name = "PolygonElem::layout", skip_all)]
fn layout(
@@ -60,10 +125,7 @@ impl Layout for PolygonElem {
.vertices()
.iter()
.map(|c| {
- c.resolve(styles)
- .zip(regions.base())
- .map(|(l, b)| l.relative_to(b))
- .to_point()
+ c.resolve(styles).zip_map(regions.base(), Rel::relative_to).to_point()
})
.collect();
@@ -78,9 +140,9 @@ impl Layout for PolygonElem {
// Prepare fill and stroke.
let fill = self.fill(styles);
let stroke = match self.stroke(styles) {
- Smart::Auto if fill.is_none() => Some(Stroke::default()),
+ Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
Smart::Auto => None,
- Smart::Custom(stroke) => stroke.map(PartialStroke::unwrap_or_default),
+ Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
};
// Construct a closed path given all points.
@@ -97,71 +159,3 @@ impl Layout for PolygonElem {
Ok(Fragment::frame(frame))
}
}
-
-/// A regular polygon, defined by its size and number of vertices.
-///
-/// ## Example { #example }
-/// ```example
-/// #polygon.regular(
-/// fill: blue.lighten(80%),
-/// stroke: blue,
-/// size: 30pt,
-/// vertices: 3,
-/// )
-/// ```
-///
-/// Display: Regular Polygon
-/// Category: visualize
-#[func]
-pub fn polygon_regular(
- /// How to fill the polygon. See the general
- /// [polygon's documentation]($func/polygon.fill) for more details.
- #[named]
- fill: Option<Option<Paint>>,
-
- /// How to stroke the polygon. See the general
- /// [polygon's documentation]($func/polygon.stroke) for more details.
- #[named]
- stroke: Option<Smart<Option<PartialStroke>>>,
-
- /// The diameter of the circumcircle of the regular polygon (https://en.wikipedia.org/wiki/Circumcircle).
- #[named]
- #[default(Em::one().into())]
- size: Length,
-
- /// The number of vertices in the polygon.
- #[named]
- #[default(3)]
- vertices: u64,
-) -> Content {
- let radius = size / 2.0;
- let angle = |i: f64| {
- 2.0 * PI * i / (vertices as f64) + PI * (1.0 / 2.0 - 1.0 / vertices as f64)
- };
- let (horizontal_offset, vertical_offset) = (0..=vertices)
- .map(|v| {
- (
- (radius * angle(v as f64).cos()) + radius,
- (radius * angle(v as f64).sin()) + radius,
- )
- })
- .fold((radius, radius), |(min_x, min_y), (v_x, v_y)| {
- (if min_x < v_x { min_x } else { v_x }, if min_y < v_y { min_y } else { v_y })
- });
- let vertices = (0..=vertices)
- .map(|v| {
- let x = (radius * angle(v as f64).cos()) + radius - horizontal_offset;
- let y = (radius * angle(v as f64).sin()) + radius - vertical_offset;
- Axes::new(x, y).map(Rel::from)
- })
- .collect();
-
- let mut elem = PolygonElem::new(vertices);
- if let Some(fill) = fill {
- elem.push_fill(fill);
- }
- if let Some(stroke) = stroke {
- elem.push_stroke(stroke);
- }
- elem.pack()
-}
diff --git a/crates/typst-library/src/visualize/shape.rs b/crates/typst-library/src/visualize/shape.rs
index 6129b70b..64d1ece6 100644
--- a/crates/typst-library/src/visualize/shape.rs
+++ b/crates/typst-library/src/visualize/shape.rs
@@ -4,7 +4,7 @@ use crate::prelude::*;
/// A rectangle with optional content.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// // Without content.
/// #rect(width: 35%, height: 30pt)
@@ -15,10 +15,7 @@ use crate::prelude::*;
/// to fit the content.
/// ]
/// ```
-///
-/// Display: Rectangle
-/// Category: visualize
-#[element(Layout)]
+#[elem(title = "Rectangle", Layout)]
pub struct RectElem {
/// The rectangle's width, relative to its parent container.
pub width: Smart<Rel<Length>>,
@@ -41,8 +38,7 @@ pub struct RectElem {
/// - `{none}` to disable stroking
/// - `{auto}` for a stroke of `{1pt + black}` if and if only if no fill is
/// given.
- /// - Any kind of stroke that can also be used for
- /// [lines]($func/line.stroke).
+ /// - Any kind of [stroke]($stroke)
/// - A dictionary describing the stroke for each side inidvidually. The
/// dictionary can contain the following keys in order of precedence:
/// - `top`: The top stroke.
@@ -65,7 +61,7 @@ pub struct RectElem {
/// ```
#[resolve]
#[fold]
- pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
+ pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
/// How much to round the rectangle's corners, relative to the minimum of
/// the width and height divided by two. This can be:
@@ -106,20 +102,14 @@ pub struct RectElem {
pub radius: Corners<Option<Rel<Length>>>,
/// How much to pad the rectangle's content.
- ///
- /// _Note:_ When the rectangle contains text, its exact size depends on the
- /// current [text edges]($func/text.top-edge).
- ///
- /// ```example
- /// #rect(inset: 0pt)[Tight]
- /// ```
+ /// See the [box's documentation]($box.outset) for more details.
#[resolve]
#[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))]
pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the rectangle's size without affecting the layout.
- /// See the [box's documentation]($func/box.outset) for more details.
+ /// See the [box's documentation]($box.outset) for more details.
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
@@ -159,7 +149,7 @@ impl Layout for RectElem {
/// A square with optional content.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// // Without content.
/// #square(size: 40pt)
@@ -170,10 +160,7 @@ impl Layout for RectElem {
/// sized to fit.
/// ]
/// ```
-///
-/// Display: Square
-/// Category: visualize
-#[element(Layout)]
+#[elem(Layout)]
pub struct SquareElem {
/// The square's side length. This is mutually exclusive with `width` and
/// `height`.
@@ -203,31 +190,31 @@ pub struct SquareElem {
})]
pub height: Smart<Rel<Length>>,
- /// How to fill the square. See the
- /// [rectangle's documentation]($func/rect.fill) for more details.
+ /// How to fill the square. See the [rectangle's documentation]($rect.fill)
+ /// for more details.
pub fill: Option<Paint>,
- /// How to stroke the square. See the [rectangle's
- /// documentation]($func/rect.stroke) for more details.
+ /// How to stroke the square. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
#[resolve]
#[fold]
- pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
+ pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
- /// How much to round the square's corners. See the [rectangle's
- /// documentation]($func/rect.radius) for more details.
+ /// How much to round the square's corners. See the
+ /// [rectangle's documentation]($rect.radius) for more details.
#[resolve]
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
- /// How much to pad the square's content. See the [rectangle's
- /// documentation]($func/rect.inset) for more details.
+ /// How much to pad the square's content. See the
+ /// [box's documentation]($box.inset) for more details.
#[resolve]
#[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))]
pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the square'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<Option<Rel<Length>>>,
@@ -268,7 +255,7 @@ impl Layout for SquareElem {
/// An ellipse with optional content.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// // Without content.
/// #ellipse(width: 35%, height: 30pt)
@@ -280,10 +267,7 @@ impl Layout for SquareElem {
/// to fit the content.
/// ]
/// ```
-///
-/// Display: Ellipse
-/// Category: visualize
-#[element(Layout)]
+#[elem(Layout)]
pub struct EllipseElem {
/// The ellipse's width, relative to its parent container.
pub width: Smart<Rel<Length>>,
@@ -291,25 +275,25 @@ pub struct EllipseElem {
/// The ellipse's height, relative to its parent container.
pub height: Smart<Rel<Length>>,
- /// How to fill the ellipse. See the
- /// [rectangle's documentation]($func/rect.fill) for more details.
+ /// How to fill the ellipse. See the [rectangle's documentation]($rect.fill)
+ /// for more details.
pub fill: Option<Paint>,
- /// How to stroke the ellipse. See the [rectangle's
- /// documentation]($func/rect.stroke) for more details.
+ /// How to stroke the ellipse. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
#[resolve]
#[fold]
- pub stroke: Smart<Option<PartialStroke>>,
+ pub stroke: Smart<Option<Stroke>>,
- /// How much to pad the ellipse's content. See the [rectangle's
- /// documentation]($func/rect.inset) for more details.
+ /// How much to pad the ellipse's content. See the
+ /// [box's documentation]($box.inset) for more details.
#[resolve]
#[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))]
pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the ellipse'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<Option<Rel<Length>>>,
@@ -349,7 +333,7 @@ impl Layout for EllipseElem {
/// A circle with optional content.
///
-/// ## Example { #example }
+/// # Example
/// ```example
/// // Without content.
/// #circle(radius: 25pt)
@@ -361,10 +345,7 @@ impl Layout for EllipseElem {
/// sized to fit.
/// ]
/// ```
-///
-/// Display: Circle
-/// Category: visualize
-#[element(Layout)]
+#[elem(Layout)]
pub struct CircleElem {
/// The circle's radius. This is mutually exclusive with `width` and
/// `height`.
@@ -398,26 +379,26 @@ pub struct CircleElem {
})]
pub height: Smart<Rel<Length>>,
- /// How to fill the circle. See the
- /// [rectangle's documentation]($func/rect.fill) for more details.
+ /// How to fill the circle. See the [rectangle's documentation]($rect.fill)
+ /// for more details.
pub fill: Option<Paint>,
- /// How to stroke the circle. See the [rectangle's
- /// documentation]($func/rect.stroke) for more details.
+ /// How to stroke the circle. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
#[resolve]
#[fold]
#[default(Smart::Auto)]
- pub stroke: Smart<Option<PartialStroke>>,
+ pub stroke: Smart<Option<Stroke>>,
- /// How much to pad the circle's content. See the [rectangle's
- /// documentation]($func/rect.inset) for more details.
+ /// How much to pad the circle's content. See the
+ /// [box's documentation]($box.inset) for more details.
#[resolve]
#[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))]
pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the circle'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<Option<Rel<Length>>>,
@@ -464,15 +445,14 @@ fn layout(
body: &Option<Content>,
sizing: Axes<Smart<Rel<Length>>>,
fill: Option<Paint>,
- stroke: Smart<Sides<Option<PartialStroke<Abs>>>>,
+ stroke: Smart<Sides<Option<Stroke<Abs>>>>,
mut inset: Sides<Rel<Abs>>,
outset: Sides<Rel<Abs>>,
radius: Corners<Rel<Abs>>,
span: Span,
) -> SourceResult<Fragment> {
let resolved = sizing
- .zip(regions.base())
- .map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r)));
+ .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)));
let mut frame;
if let Some(child) = body {
@@ -517,11 +497,9 @@ fn layout(
// Prepare stroke.
let stroke = match stroke {
- Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
+ Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())),
Smart::Auto => Sides::splat(None),
- Smart::Custom(strokes) => {
- strokes.map(|s| s.map(PartialStroke::unwrap_or_default))
- }
+ Smart::Custom(strokes) => strokes.map(|s| s.map(Stroke::unwrap_or_default)),
};
// Add fill and/or stroke.