Documentation

This commit is contained in:
Laurenz 2022-12-20 16:08:16 +01:00
parent b8ffd3ad3d
commit f5f7df7247
47 changed files with 707 additions and 394 deletions

View File

@ -5,30 +5,48 @@ use crate::layout::{BlockNode, VNode};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{SpaceNode, TextNode, TextSize}; use crate::text::{SpaceNode, TextNode, TextSize};
/// # Heading
/// A section heading. /// A section heading.
/// ///
/// # Example /// With headings, you can structure your document into sections. Each heading
/// has a _level,_ which starts at one and is unbounded upwards. This level
/// indicates the logical role of the following content (section, subsection,
/// etc.) Top-level heading indicates a top-level section of the document (not
/// the document's title).
///
/// Typst can automatically number your headings for you. To enable numbering,
/// specify how you want your headings to be numbered with a [numbering
/// pattern](@numbering).
///
/// Independently from the numbering, Typst can also automatically generate an
/// [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
/// ``` /// ```
/// #set heading(numbering: "I.") /// #set heading(numbering: "1.a)")
/// ///
/// = Introduction /// = Introduction
/// In recent years, ... /// In recent years, ...
///
/// == Preliminaries
/// To start, ...
/// ``` /// ```
/// ///
/// # Syntax /// ## Syntax
/// Headings can be created by starting a line with one or multiple equals /// Headings have dedicated syntax: They can be created by starting a line with
/// signs. The number of equals signs determines the heading's logical nesting /// one or multiple equals signs, followed by a space. The number of equals
/// depth. /// signs determines the heading's logical nesting depth.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - title: Content (positional, required)
/// The heading's contents. /// The heading's title.
/// ///
/// - level: NonZeroUsize (named) /// - level: NonZeroUsize (named)
/// The logical nesting depth of the heading, starting from one. /// The logical nesting depth of the heading, starting from one.
/// ///
/// # Tags /// ## Category
/// - basics /// basics
#[func] #[func]
#[capable(Prepare, Show, Finalize)] #[capable(Prepare, Show, Finalize)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -37,12 +55,12 @@ pub struct HeadingNode {
/// default style, this controls the text size of the heading. /// default style, this controls the text size of the heading.
pub level: NonZeroUsize, pub level: NonZeroUsize,
/// The heading's contents. /// The heading's contents.
pub body: Content, pub title: Content,
} }
#[node] #[node]
impl HeadingNode { impl HeadingNode {
/// How to number the heading. /// How to number the heading. Accepts a [numbering pattern](@numbering).
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -72,7 +90,7 @@ impl HeadingNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self { Ok(Self {
body: args.expect("body")?, title: args.expect("title")?,
level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()),
} }
.pack()) .pack())
@ -81,7 +99,7 @@ impl HeadingNode {
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
"level" => Some(Value::Int(self.level.get() as i64)), "level" => Some(Value::Int(self.level.get() as i64)),
"body" => Some(Value::Content(self.body.clone())), "title" => Some(Value::Content(self.title.clone())),
_ => None, _ => None,
} }
} }
@ -118,7 +136,7 @@ impl Prepare for HeadingNode {
impl Show for HeadingNode { impl Show for HeadingNode {
fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
let mut realized = self.body.clone(); let mut realized = self.title.clone();
if let Some(Value::Str(numbering)) = this.field("numbers") { if let Some(Value::Str(numbering)) = this.field("numbers") {
realized = TextNode::packed(numbering) + SpaceNode.pack() + realized; realized = TextNode::packed(numbering) + SpaceNode.pack() + realized;
} }

View File

@ -3,9 +3,10 @@ use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{SpaceNode, TextNode}; use crate::text::{SpaceNode, TextNode};
/// # List
/// An unordered (bulleted) or ordered (numbered) list. /// An unordered (bulleted) or ordered (numbered) list.
/// ///
/// # Parameters /// ## Parameters
/// - items: Content (positional, variadic) /// - items: Content (positional, variadic)
/// The contents of the list items. /// The contents of the list items.
/// ///
@ -16,7 +17,7 @@ use crate::text::{SpaceNode, TextNode};
/// Makes the list more compact, if enabled. This looks better if the items /// Makes the list more compact, if enabled. This looks better if the items
/// fit into a single line each. /// fit into a single line each.
/// ///
/// # Example /// ### Example
/// ``` /// ```
/// #show columns.with(2) /// #show columns.with(2)
/// #list(tight: true)[Tight][List] /// #list(tight: true)[Tight][List]
@ -24,8 +25,8 @@ use crate::text::{SpaceNode, TextNode};
/// #list(tight: false)[Wide][List] /// #list(tight: false)[Wide][List]
/// ``` /// ```
/// ///
/// # Tags /// ## Category
/// - basics /// basics
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,9 +1,10 @@
use crate::layout::{GridNode, TrackSizing, TrackSizings}; use crate::layout::{GridNode, TrackSizing, TrackSizings};
use crate::prelude::*; use crate::prelude::*;
/// # Table
/// A table of items. /// A table of items.
/// ///
/// # Parameters /// ## Parameters
/// - cells: Content (positional, variadic) /// - cells: Content (positional, variadic)
/// The contents of the table cells. /// The contents of the table cells.
/// ///
@ -22,8 +23,8 @@ use crate::prelude::*;
/// - row-gutter: TrackSizings (named) /// - row-gutter: TrackSizings (named)
/// Defines the gaps between rows. Takes precedence over `gutter`. /// Defines the gaps between rows. Takes precedence over `gutter`.
/// ///
/// # Tags /// ## Category
/// - basics /// basics
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -2,14 +2,22 @@ use std::cmp::Ordering;
use crate::prelude::*; use crate::prelude::*;
/// # Absolute
/// The absolute value of a numeric value. /// The absolute value of a numeric value.
/// ///
/// # Parameters /// ## Example
/// ```
/// #abs(-5) \
/// #abs(5pt - 2cm) \
/// #abs(2fr)
/// ```
///
/// ## Parameters
/// - value: ToAbs (positional, required) /// - value: ToAbs (positional, required)
/// The value whose absolute value to calculate. /// The value whose absolute value to calculate.
/// ///
/// # Tags /// ## Category
/// - calculate /// calculate
#[func] #[func]
pub fn abs(args: &mut Args) -> SourceResult<Value> { pub fn abs(args: &mut Args) -> SourceResult<Value> {
Ok(args.expect::<ToAbs>("value")?.0) Ok(args.expect::<ToAbs>("value")?.0)
@ -22,32 +30,50 @@ castable! {
ToAbs, ToAbs,
v: i64 => Self(Value::Int(v.abs())), v: i64 => Self(Value::Int(v.abs())),
v: f64 => Self(Value::Float(v.abs())), v: f64 => Self(Value::Float(v.abs())),
v: Length => Self(Value::Length(v.try_abs()
.ok_or_else(|| "cannot take absolute value of this length")?)),
v: Angle => Self(Value::Angle(v.abs())), v: Angle => Self(Value::Angle(v.abs())),
v: Ratio => Self(Value::Ratio(v.abs())), v: Ratio => Self(Value::Ratio(v.abs())),
v: Fr => Self(Value::Fraction(v.abs())), v: Fr => Self(Value::Fraction(v.abs())),
} }
/// # Minimum
/// The minimum of a sequence of values. /// The minimum of a sequence of values.
/// ///
/// # Parameters /// ## Example
/// - values: Value (positional, variadic) /// ```
/// The sequence of values. /// #min(1, -3, -5, 20, 3, 6) \
/// #min("Typst", "in", "beta")
/// ```
/// ///
/// # Tags /// ## Parameters
/// - calculate /// - values: Value (positional, variadic)
/// The sequence of values from which to extract the minimum.
/// Must not be empty.
///
/// ## Category
/// calculate
#[func] #[func]
pub fn min(args: &mut Args) -> SourceResult<Value> { pub fn min(args: &mut Args) -> SourceResult<Value> {
minmax(args, Ordering::Less) minmax(args, Ordering::Less)
} }
/// # Maximum
/// The maximum of a sequence of values. /// The maximum of a sequence of values.
/// ///
/// # Parameters /// ## Example
/// - values: Value (positional, variadic) /// ```
/// The sequence of values. /// #max(1, -3, -5, 20, 3, 6) \
/// #max("Typst", "in", "beta")
/// ```
/// ///
/// # Tags /// ## Parameters
/// - calculate /// - values: Value (positional, variadic)
/// The sequence of values from which to extract the maximum.
/// Must not be empty.
///
/// ## Category
/// calculate
#[func] #[func]
pub fn max(args: &mut Args) -> SourceResult<Value> { pub fn max(args: &mut Args) -> SourceResult<Value> {
minmax(args, Ordering::Greater) minmax(args, Ordering::Greater)
@ -74,43 +100,67 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
Ok(extremum) Ok(extremum)
} }
/// # Even
/// Whether an integer is even. /// Whether an integer is even.
/// ///
/// # Parameters /// ## Example
/// ```
/// #even(4) \
/// #even(5) \
/// { range(10).filter(even) }
/// ```
///
/// ## Parameters
/// - value: i64 (positional, required) /// - value: i64 (positional, required)
/// The number to check for evenness. /// The number to check for evenness.
/// ///
/// # Tags /// ## Category
/// - calculate /// calculate
#[func] #[func]
pub fn even(args: &mut Args) -> SourceResult<Value> { pub fn even(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Bool(args.expect::<i64>("value")? % 2 == 0)) Ok(Value::Bool(args.expect::<i64>("value")? % 2 == 0))
} }
/// # Odd
/// Whether an integer is odd. /// Whether an integer is odd.
/// ///
/// # Parameters /// ## Example
/// ```
/// #odd(4) \
/// #odd(5) \
/// { range(10).filter(odd) }
/// ```
///
///
/// ## Parameters
/// - value: i64 (positional, required) /// - value: i64 (positional, required)
/// The number to check for oddness. /// The number to check for oddness.
/// ///
/// # Tags /// ## Category
/// - calculate /// calculate
#[func] #[func]
pub fn odd(args: &mut Args) -> SourceResult<Value> { pub fn odd(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Bool(args.expect::<i64>("value")? % 2 != 0)) Ok(Value::Bool(args.expect::<i64>("value")? % 2 != 0))
} }
/// # Modulus
/// The modulus of two numbers. /// The modulus of two numbers.
/// ///
/// # Parameters /// ## Example
/// ```
/// #mod(20, 6) \
/// #mod(1.75, 0.5)
/// ```
///
/// ## Parameters
/// - dividend: ToMod (positional, required) /// - dividend: ToMod (positional, required)
/// The dividend of the modulus. /// The dividend of the modulus.
/// ///
/// - divisor: ToMod (positional, required) /// - divisor: ToMod (positional, required)
/// The divisor of the modulus. /// The divisor of the modulus.
/// ///
/// # Tags /// ## Category
/// - calculate /// calculate
#[func] #[func]
pub fn mod_(args: &mut Args) -> SourceResult<Value> { pub fn mod_(args: &mut Args) -> SourceResult<Value> {
let Spanned { v: v1, span: span1 } = args.expect("dividend")?; let Spanned { v: v1, span: span1 } = args.expect("dividend")?;

View File

@ -4,14 +4,27 @@ use typst::model::Regex;
use crate::prelude::*; use crate::prelude::*;
/// # Integer
/// Convert a value to an integer. /// Convert a value to an integer.
/// ///
/// # Parameters /// - Booleans are converted to `0` or `1`.
/// - Floats are floored to the next 64-bit integer.
/// - Strings are parsed in base 10.
///
/// ## Example
/// ```
/// #int(false) \
/// #int(true) \
/// #int(2.7) \
/// { int("27") + int("4") }
/// ```
///
/// ## Parameters
/// - value: ToInt (positional, required) /// - value: ToInt (positional, required)
/// The value that should be converted to an integer. /// The value that should be converted to an integer.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn int(args: &mut Args) -> SourceResult<Value> { pub fn int(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Int(args.expect::<ToInt>("value")?.0)) Ok(Value::Int(args.expect::<ToInt>("value")?.0))
@ -28,14 +41,29 @@ castable! {
v: EcoString => Self(v.parse().map_err(|_| "not a valid integer")?), v: EcoString => Self(v.parse().map_err(|_| "not a valid integer")?),
} }
/// # Float
/// Convert a value to a float. /// Convert a value to a float.
/// ///
/// # Parameters /// - Booleans are converted to `0.0` or `1.0`.
/// - Integers are converted to the closest 64-bit float.
/// - Strings are parsed in base 10 to the closest 64-bit float.
/// Exponential notation is supported.
///
/// ## Example
/// ```
/// #float(false) \
/// #float(true) \
/// #float(4) \
/// #float("2.7") \
/// #float("1e5")
/// ```
///
/// ## Parameters
/// - value: ToFloat (positional, required) /// - value: ToFloat (positional, required)
/// The value that should be converted to a float. /// The value that should be converted to a float.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn float(args: &mut Args) -> SourceResult<Value> { pub fn float(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Float(args.expect::<ToFloat>("value")?.0)) Ok(Value::Float(args.expect::<ToFloat>("value")?.0))
@ -52,23 +80,45 @@ castable! {
v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?), v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?),
} }
/// # Luma
/// Create a grayscale color. /// Create a grayscale color.
/// ///
/// # Parameters /// ## Example
/// ```
/// #for x in range(250, step: 50) {
/// square(fill: luma(x))
/// }
/// ```
///
/// ## Parameters
/// - gray: Component (positional, required) /// - gray: Component (positional, required)
/// The gray component. /// The gray component.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn luma(args: &mut Args) -> SourceResult<Value> { pub fn luma(args: &mut Args) -> SourceResult<Value> {
let Component(luma) = args.expect("gray component")?; let Component(luma) = args.expect("gray component")?;
Ok(Value::Color(LumaColor::new(luma).into())) Ok(Value::Color(LumaColor::new(luma).into()))
} }
/// # RGBA
/// Create an RGB(A) color. /// Create an RGB(A) color.
/// ///
/// # Parameters /// The color is specified in the sRGB color space.
///
/// _Note:_ While you can specify transparent colors and Typst's preview will
/// render them correctly, the PDF export does not handle them properly at the
/// moment. This will be fixed in the future.
///
/// ## Example
/// ```
/// #square(fill: rgb("#b1f2eb"))
/// #square(fill: rgb(87, 127, 230))
/// #square(fill: rgb(25%, 13%, 65%))
/// ```
///
/// ## Parameters
/// - hex: EcoString (positional) /// - hex: EcoString (positional)
/// The color in hexademical notation. /// The color in hexademical notation.
/// ///
@ -77,10 +127,11 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
/// ///
/// If this string is given, the individual components should not be given. /// If this string is given, the individual components should not be given.
/// ///
/// # Example /// ### Example
/// ``` /// ```
/// #let color = rgb("#239dad") /// #text(16pt, rgb("#239dad"))[
/// #text(16pt, color)[*Typst*] /// *Typst*
/// ]
/// ``` /// ```
/// ///
/// - red: Component (positional) /// - red: Component (positional)
@ -95,8 +146,8 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
/// - alpha: Component (positional) /// - alpha: Component (positional)
/// The alpha component. /// The alpha component.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn rgb(args: &mut Args) -> SourceResult<Value> { pub fn rgb(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? { Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
@ -129,9 +180,21 @@ castable! {
}, },
} }
/// # CMYK
/// Create a CMYK color. /// Create a CMYK color.
/// ///
/// # Parameters /// 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
/// ```
/// #square(
/// fill: cmyk(27%, 0%, 3%, 5%)
/// )
/// ````
///
/// ## Parameters
/// - cyan: RatioComponent (positional, required) /// - cyan: RatioComponent (positional, required)
/// The cyan component. /// The cyan component.
/// ///
@ -144,8 +207,8 @@ castable! {
/// - key: RatioComponent (positional, required) /// - key: RatioComponent (positional, required)
/// The key component. /// The key component.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn cmyk(args: &mut Args) -> SourceResult<Value> { pub fn cmyk(args: &mut Args) -> SourceResult<Value> {
let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(c) = args.expect("cyan component")?;
@ -167,14 +230,27 @@ castable! {
}, },
} }
/// # String
/// Convert a value to a string. /// Convert a value to a string.
/// ///
/// # Parameters /// - Integers are formatted in base 10.
/// - Floats are formatted in base 10 and never in exponential notation.
/// - From labels the name is extracted.
///
/// ## Example
/// ```
/// #str(10) \
/// #str(2.7) \
/// #str(1e8) \
/// #str(<intro>)
/// ```
///
/// ## Parameters
/// - value: ToStr (positional, required) /// - value: ToStr (positional, required)
/// The value that should be converted to a string. /// The value that should be converted to a string.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn str(args: &mut Args) -> SourceResult<Value> { pub fn str(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Str(args.expect::<ToStr>("value")?.0)) Ok(Value::Str(args.expect::<ToStr>("value")?.0))
@ -191,36 +267,94 @@ castable! {
v: Str => Self(v), v: Str => Self(v),
} }
/// # Label
/// Create a label from a string. /// Create a label from a string.
/// ///
/// # Parameters /// Inserting a label into content attaches it to the closest previous element
/// that is not a space. Then, the element can be [referenced](@ref) and styled
/// through the label.
///
/// ## Example
/// ```
/// #show <a>: set text(blue)
/// #show label("b"): set text(red)
///
/// = Heading <a>
/// *Strong* #label("b")
/// ```
///
/// ## 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.
///
/// ## Parameters
/// - name: EcoString (positional, required) /// - name: EcoString (positional, required)
/// The name of the label. /// The name of the label.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn label(args: &mut Args) -> SourceResult<Value> { pub fn label(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Label(Label(args.expect("string")?))) Ok(Value::Label(Label(args.expect("string")?)))
} }
/// # Regex
/// Create a regular expression from a string. /// Create a regular expression from a string.
/// ///
/// # Parameters /// The result can be used as a show rule
/// - regex: EcoString (positional, required) /// [selector](/docs/reference/concepts/#selector) and with
/// The regular expression. /// [string methods](/docs/reference/concepts/#methods) like `find`, `split`,
/// and `replace`.
/// ///
/// # Tags /// [See here](https://docs.rs/regex/latest/regex/#syntax) for a specification
/// - create /// of the supported syntax.
///
/// ## 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("[,;]")) }
/// ```
///
/// ## Parameters
/// - regex: EcoString (positional, required)
/// 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("\\\\")}`.
///
/// ## Category
/// create
#[func] #[func]
pub fn regex(args: &mut Args) -> SourceResult<Value> { pub fn regex(args: &mut Args) -> SourceResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?; let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
Ok(Regex::new(&v).at(span)?.into()) Ok(Regex::new(&v).at(span)?.into())
} }
/// # Range
/// Create an array consisting of a sequence of numbers. /// Create an array consisting of a sequence of numbers.
/// ///
/// # Parameters /// 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
/// ```
/// #range(5) \
/// #range(2, 5) \
/// #range(20, step: 4) \
/// #range(21, step: 4) \
/// #range(5, 2, step: -1)
/// ```
///
/// ## Parameters
/// - start: i64 (positional) /// - start: i64 (positional)
/// The start of the range (inclusive). /// The start of the range (inclusive).
/// ///
@ -230,8 +364,8 @@ pub fn regex(args: &mut Args) -> SourceResult<Value> {
/// - step: i64 (named) /// - step: i64 (named)
/// The distance between the generated numbers. /// The distance between the generated numbers.
/// ///
/// # Tags /// ## Category
/// - create /// create
#[func] #[func]
pub fn range(args: &mut Args) -> SourceResult<Value> { pub fn range(args: &mut Args) -> SourceResult<Value> {
let first = args.expect::<i64>("end")?; let first = args.expect::<i64>("end")?;

View File

@ -4,6 +4,7 @@ use typst::diag::{format_xml_like_error, FileError};
use crate::prelude::*; use crate::prelude::*;
/// # CSV
/// Read structured data from a CSV file. /// Read structured data from a CSV file.
/// ///
/// The CSV file will be read and parsed into a 2-dimensional array of strings: /// The CSV file will be read and parsed into a 2-dimensional array of strings:
@ -11,7 +12,7 @@ use crate::prelude::*;
/// rows will be collected into a single array. Header rows will not be /// rows will be collected into a single array. Header rows will not be
/// stripped. /// stripped.
/// ///
/// # Example /// ## Example
/// ``` /// ```
/// #let results = csv("/data.csv") /// #let results = csv("/data.csv")
/// ///
@ -21,7 +22,8 @@ use crate::prelude::*;
/// ..results.flatten(), /// ..results.flatten(),
/// ) /// )
/// ``` /// ```
/// # Parameters ///
/// ## Parameters
/// - path: EcoString (positional, required) /// - path: EcoString (positional, required)
/// Path to a CSV file. /// Path to a CSV file.
/// - delimiter: Delimiter (named) /// - delimiter: Delimiter (named)
@ -29,8 +31,8 @@ use crate::prelude::*;
/// Must be a single ASCII character. /// Must be a single ASCII character.
/// Defaults to a comma. /// Defaults to a comma.
/// ///
/// # Tags /// ## Category
/// - data-loading /// data-loading
#[func] #[func]
pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: path, span } = let Spanned { v: path, span } =
@ -95,6 +97,7 @@ fn format_csv_error(error: csv::Error) -> String {
} }
} }
/// # JSON
/// Read structured data from a JSON file. /// Read structured data from a JSON file.
/// ///
/// The file must contain a valid JSON object or array. JSON objects will be /// The file must contain a valid JSON object or array. JSON objects will be
@ -108,7 +111,7 @@ fn format_csv_error(error: csv::Error) -> String {
/// The JSON files in the example contain a object with the keys `temperature`, /// The JSON files in the example contain a object with the keys `temperature`,
/// `unit`, and `weather`. /// `unit`, and `weather`.
/// ///
/// # Example /// ## Example
/// ``` /// ```
/// #let forecast(day) = block[ /// #let forecast(day) = block[
/// #square( /// #square(
@ -134,12 +137,12 @@ fn format_csv_error(error: csv::Error) -> String {
/// #forecast(json("/tuesday.json")) /// #forecast(json("/tuesday.json"))
/// ``` /// ```
/// ///
/// # Parameters /// ## Parameters
/// - path: EcoString (positional, required) /// - path: EcoString (positional, required)
/// Path to a JSON file. /// Path to a JSON file.
/// ///
/// # Tags /// ## Category
/// - data-loading /// data-loading
#[func] #[func]
pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: path, span } = let Spanned { v: path, span } =
@ -177,12 +180,10 @@ fn convert_json(value: serde_json::Value) -> Value {
/// Format the user-facing JSON error message. /// Format the user-facing JSON error message.
fn format_json_error(error: serde_json::Error) -> String { fn format_json_error(error: serde_json::Error) -> String {
assert!(error.is_syntax() || error.is_eof()); assert!(error.is_syntax() || error.is_eof());
format!( format!("failed to parse json file: syntax error in line {}", error.line())
"failed to parse json file: syntax error in line {}",
error.line()
)
} }
/// # XML
/// Read structured data from an XML file. /// Read structured data from an XML file.
/// ///
/// The XML file is parsed into an array of dictionaries and strings. XML nodes /// The XML file is parsed into an array of dictionaries and strings. XML nodes
@ -198,7 +199,7 @@ fn format_json_error(error: serde_json::Error) -> String {
/// `content` tag contains one or more paragraphs, which are represented as `p` /// `content` tag contains one or more paragraphs, which are represented as `p`
/// tags. /// tags.
/// ///
/// # Example /// ## Example
/// ``` /// ```
/// #let findChild(elem, tag) = { /// #let findChild(elem, tag) = {
/// elem.children /// elem.children
@ -232,12 +233,12 @@ fn format_json_error(error: serde_json::Error) -> String {
/// } /// }
/// ``` /// ```
/// ///
/// # Parameters /// ## Parameters
/// - path: EcoString (positional, required) /// - path: EcoString (positional, required)
/// Path to an XML file. /// Path to an XML file.
/// ///
/// # Tags /// ## Category
/// - data-loading /// data-loading
#[func] #[func]
pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: path, span } = let Spanned { v: path, span } =

View File

@ -4,40 +4,43 @@ use comemo::Track;
use typst::model; use typst::model;
use typst::syntax::Source; use typst::syntax::Source;
/// # Type
/// The name of a value's type. /// The name of a value's type.
/// ///
/// # Parameters /// ## Parameters
/// - value: Value (positional, required) /// - value: Value (positional, required)
/// The value whose type's to determine. /// The value whose type's to determine.
/// ///
/// # Tags /// ## Category
/// - foundations /// foundations
#[func] #[func]
pub fn type_(args: &mut Args) -> SourceResult<Value> { pub fn type_(args: &mut Args) -> SourceResult<Value> {
Ok(args.expect::<Value>("value")?.type_name().into()) Ok(args.expect::<Value>("value")?.type_name().into())
} }
/// # Representation
/// The string representation of a value. /// The string representation of a value.
/// ///
/// # Parameters /// ## Parameters
/// - value: Value (positional, required) /// - value: Value (positional, required)
/// The value whose string representation to produce. /// The value whose string representation to produce.
/// ///
/// # Tags /// ## Category
/// - foundations /// foundations
#[func] #[func]
pub fn repr(args: &mut Args) -> SourceResult<Value> { pub fn repr(args: &mut Args) -> SourceResult<Value> {
Ok(args.expect::<Value>("value")?.repr().into()) Ok(args.expect::<Value>("value")?.repr().into())
} }
/// # Assert
/// Ensure that a condition is fulfilled. /// Ensure that a condition is fulfilled.
/// ///
/// # Parameters /// ## Parameters
/// - condition: bool (positional, required) /// - condition: bool (positional, required)
/// The condition that must be true for the assertion to pass. /// The condition that must be true for the assertion to pass.
/// ///
/// # Tags /// ## Category
/// - foundations /// foundations
#[func] #[func]
pub fn assert(args: &mut Args) -> SourceResult<Value> { pub fn assert(args: &mut Args) -> SourceResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?; let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
@ -47,14 +50,15 @@ pub fn assert(args: &mut Args) -> SourceResult<Value> {
Ok(Value::None) Ok(Value::None)
} }
/// # Evaluate
/// Evaluate a string as Typst markup. /// Evaluate a string as Typst markup.
/// ///
/// # Parameters /// ## Parameters
/// - source: String (positional, required) /// - source: String (positional, required)
/// A string of Typst markup to evaluate. /// A string of Typst markup to evaluate.
/// ///
/// # Tags /// ## Category
/// - foundations /// foundations
#[func] #[func]
pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?; let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;

View File

@ -3,31 +3,33 @@ use std::str::FromStr;
use crate::prelude::*; use crate::prelude::*;
use crate::text::Case; use crate::text::Case;
/// # Blind Text
/// Create blind text. /// Create blind text.
/// ///
/// # Parameters /// ## Parameters
/// - words: usize (positional, required) /// - words: usize (positional, required)
/// The length of the blind text in words. /// The length of the blind text in words.
/// ///
/// # Tags /// ## Category
/// - utility /// utility
#[func] #[func]
pub fn lorem(args: &mut Args) -> SourceResult<Value> { pub fn lorem(args: &mut Args) -> SourceResult<Value> {
let words: usize = args.expect("number of words")?; let words: usize = args.expect("number of words")?;
Ok(Value::Str(lipsum::lipsum(words).into())) Ok(Value::Str(lipsum::lipsum(words).into()))
} }
/// # Numbering
/// Apply a numbering pattern to a sequence of numbers. /// Apply a numbering pattern to a sequence of numbers.
/// ///
/// # Parameters /// ## Parameters
/// - pattern: NumberingPattern (positional, required) /// - pattern: NumberingPattern (positional, required)
/// A string that defines how the numbering works. /// A string that defines how the numbering works.
/// ///
/// - numbers: NonZeroUsize (positional, variadic) /// - numbers: NonZeroUsize (positional, variadic)
/// The numbers to apply the pattern to. /// The numbers to apply the pattern to.
/// ///
/// # Tags /// ## Category
/// - utility /// utility
#[func] #[func]
pub fn numbering(args: &mut Args) -> SourceResult<Value> { pub fn numbering(args: &mut Args) -> SourceResult<Value> {
let pattern = args.expect::<NumberingPattern>("pattern")?; let pattern = args.expect::<NumberingPattern>("pattern")?;

View File

@ -1,16 +1,17 @@
use crate::prelude::*; use crate::prelude::*;
/// # Align
/// Align content horizontally and vertically. /// Align content horizontally and vertically.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to align. /// The content to align.
/// ///
/// - alignment: Axes<Option<GenAlign>> (positional, settable) /// - alignment: Axes<Option<GenAlign>> (positional, settable)
/// The alignment along both axes. /// The alignment along both axes.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable] #[capable]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,17 +1,18 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// # Columns
/// Separate a region into multiple equally sized columns. /// Separate a region into multiple equally sized columns.
/// ///
/// # Parameters /// ## Parameters
/// - count: usize (positional, required) /// - count: usize (positional, required)
/// The number of columns. /// The number of columns.
/// ///
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content that should be layouted into the columns. /// The content that should be layouted into the columns.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -112,15 +113,16 @@ impl Layout for ColumnsNode {
} }
} }
/// # Column Break
/// A column break. /// A column break.
/// ///
/// # Parameters /// ## Parameters
/// - weak: bool (named) /// - weak: bool (named)
/// If true, the column break is skipped if the current column is already /// If true, the column break is skipped if the current column is already
/// empty. /// empty.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Behave)] #[capable(Behave)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -2,9 +2,10 @@ use super::VNode;
use crate::layout::Spacing; use crate::layout::Spacing;
use crate::prelude::*; use crate::prelude::*;
/// # Box
/// An inline-level container that sizes content. /// An inline-level container that sizes content.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional) /// - body: Content (positional)
/// The contents of the box. /// The contents of the box.
/// ///
@ -14,8 +15,8 @@ use crate::prelude::*;
/// - height: Rel<Length> (named) /// - height: Rel<Length> (named)
/// The height of the box. /// The height of the box.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -75,9 +76,10 @@ impl Layout for BoxNode {
impl Inline for BoxNode {} impl Inline for BoxNode {}
/// # Block
/// A block-level container that places content into a separate flow. /// A block-level container that places content into a separate flow.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional) /// - body: Content (positional)
/// The contents of the block. /// The contents of the block.
/// ///
@ -92,8 +94,8 @@ impl Inline for BoxNode {}
/// The spacing between this block and the following one. Takes precedence /// The spacing between this block and the following one. Takes precedence
/// over `spacing`. /// over `spacing`.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -2,9 +2,10 @@ use crate::prelude::*;
use super::Spacing; use super::Spacing;
/// # Grid
/// Arrange content in a grid. /// Arrange content in a grid.
/// ///
/// # Parameters /// ## Parameters
/// - cells: Content (positional, variadic) /// - cells: Content (positional, variadic)
/// The contents of the table cells. /// The contents of the table cells.
/// ///
@ -23,8 +24,8 @@ use super::Spacing;
/// - row-gutter: TrackSizings (named) /// - row-gutter: TrackSizings (named)
/// Defines the gaps between rows. Takes precedence over `gutter`. /// Defines the gaps between rows. Takes precedence over `gutter`.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,13 +1,14 @@
use crate::prelude::*; use crate::prelude::*;
/// # Hide
/// Hide content without affecting layout. /// Hide content without affecting layout.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to hide. /// The content to hide.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,8 +1,9 @@
use crate::prelude::*; use crate::prelude::*;
/// # Padding
/// Pad content at the sides. /// Pad content at the sides.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to pad at the sides. /// The content to pad at the sides.
/// ///
@ -27,8 +28,8 @@ use crate::prelude::*;
/// - rest: Rel<Length> (named) /// - rest: Rel<Length> (named)
/// The padding for all sides. All other parameters take precedence over this. /// The padding for all sides. All other parameters take precedence over this.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -4,17 +4,18 @@ use super::ColumnsNode;
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// # Page
/// Layouts its child onto one or multiple pages. /// Layouts its child onto one or multiple pages.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The contents of the page(s). /// The contents of the page(s).
/// ///
/// - paper: Paper (positional, settable) /// - paper: Paper (positional, settable)
/// The paper size. /// The paper size.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable] #[capable]
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
@ -154,14 +155,15 @@ impl Debug for PageNode {
} }
} }
/// # Page Break
/// A page break. /// A page break.
/// ///
/// # Parameters /// ## Parameters
/// - weak: bool (named) /// - weak: bool (named)
/// If true, the page break is skipped if the current page is already empty. /// If true, the page break is skipped if the current page is already empty.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable] #[capable]
#[derive(Debug, Copy, Clone, Hash)] #[derive(Debug, Copy, Clone, Hash)]

View File

@ -11,14 +11,15 @@ use crate::text::{
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode, shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
}; };
/// # Paragraph
/// Arrange text, spacing and inline-level nodes into a paragraph. /// Arrange text, spacing and inline-level nodes into a paragraph.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The contents of the paragraph. /// The contents of the paragraph.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable] #[capable]
#[derive(Hash)] #[derive(Hash)]
@ -147,10 +148,11 @@ castable! {
"optimized" => Self::Optimized, "optimized" => Self::Optimized,
} }
/// # Paragraph Break
/// A paragraph break. /// A paragraph break.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Unlabellable)] #[capable(Unlabellable)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,8 +1,9 @@
use crate::prelude::*; use crate::prelude::*;
/// # Place
/// Place content at an absolute position. /// Place content at an absolute position.
/// ///
/// # Parameters /// ## Parameters
/// - alignment: Axes<Option<GenAlign>> (positional) /// - alignment: Axes<Option<GenAlign>> (positional)
/// Relative to which position in the parent container to place the content. /// Relative to which position in the parent container to place the content.
/// ///
@ -15,8 +16,8 @@ use crate::prelude::*;
/// - dy: Rel<Length> (named) /// - dy: Rel<Length> (named)
/// The vertical displacement of the placed content. /// The vertical displacement of the placed content.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout, Behave)] #[capable(Layout, Behave)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,13 +1,14 @@
use crate::prelude::*; use crate::prelude::*;
/// # Repeat
/// Repeats content to fill a line. /// Repeats content to fill a line.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to repeat. /// The content to repeat.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -2,9 +2,10 @@ use std::cmp::Ordering;
use crate::prelude::*; use crate::prelude::*;
/// # Spacing (H)
/// Horizontal spacing in a paragraph. /// Horizontal spacing in a paragraph.
/// ///
/// # Parameters /// ## Parameters
/// - amount: Spacing (positional, required) /// - amount: Spacing (positional, required)
/// How much spacing to insert. /// How much spacing to insert.
/// ///
@ -13,8 +14,8 @@ use crate::prelude::*;
/// Moreover, from multiple adjacent weak spacings all but the largest one /// Moreover, from multiple adjacent weak spacings all but the largest one
/// collapse. /// collapse.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Behave)] #[capable(Behave)]
#[derive(Debug, Copy, Clone, Hash)] #[derive(Debug, Copy, Clone, Hash)]
@ -63,9 +64,10 @@ impl Behave for HNode {
} }
} }
/// # Spacing (V)
/// Vertical spacing. /// Vertical spacing.
/// ///
/// # Parameters /// ## Parameters
/// - amount: Spacing (positional, required) /// - amount: Spacing (positional, required)
/// How much spacing to insert. /// How much spacing to insert.
/// ///
@ -74,8 +76,8 @@ impl Behave for HNode {
/// Moreover, from multiple adjacent weak spacings all but the largest one /// Moreover, from multiple adjacent weak spacings all but the largest one
/// collapse. /// collapse.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Behave)] #[capable(Behave)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)] #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]

View File

@ -3,9 +3,10 @@ use typst::model::StyledNode;
use super::{AlignNode, Spacing}; use super::{AlignNode, Spacing};
use crate::prelude::*; use crate::prelude::*;
/// # Stack
/// Arrange content and spacing along an axis. /// Arrange content and spacing along an axis.
/// ///
/// # Parameters /// ## Parameters
/// - items: StackChild (positional, variadic) /// - items: StackChild (positional, variadic)
/// The items to stack along an axis. /// The items to stack along an axis.
/// ///
@ -15,8 +16,8 @@ use crate::prelude::*;
/// - spacing: Spacing (named) /// - spacing: Spacing (named)
/// Spacing to insert between items where no explicit spacing was provided. /// Spacing to insert between items where no explicit spacing was provided.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout)] #[capable(Layout)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -2,9 +2,10 @@ use typst::geom::Transform;
use crate::prelude::*; use crate::prelude::*;
/// # Move
/// Move content without affecting layout. /// Move content without affecting layout.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to move. /// The content to move.
/// ///
@ -14,8 +15,8 @@ use crate::prelude::*;
/// - dy: Rel<Length> (named) /// - dy: Rel<Length> (named)
/// The vertical displacement of the content. /// The vertical displacement of the content.
/// ///
/// # Tags /// ## Category
/// - layout /// layout
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -58,64 +59,44 @@ impl Layout for MoveNode {
impl Inline for MoveNode {} impl Inline for MoveNode {}
/// Transform content without affecting layout. /// # Rotate
/// Rotate content with affecting layout.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to transform. /// The content to rotate.
/// ///
/// - angle: Angle (named) /// - angle: Angle (named)
/// The amount of rotation. /// The amount of rotation.
/// ///
/// - x: Ratio (named) /// ## Category
/// The horizontal scaling factor. /// layout
///
/// - y: Ratio (named)
/// The vertical scaling factor.
///
/// # Tags
/// - layout
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct TransformNode<const T: TransformKind> { pub struct RotateNode {
/// Transformation to apply to the content. /// The angle by which to rotate the node.
pub transform: Transform, pub angle: Angle,
/// The content that should be transformed. /// The content that should be rotated.
pub body: Content, pub body: Content,
} }
/// Rotate content without affecting layout.
pub type RotateNode = TransformNode<ROTATE>;
/// Scale content without affecting layout.
pub type ScaleNode = TransformNode<SCALE>;
#[node] #[node]
impl<const T: TransformKind> TransformNode<T> { impl RotateNode {
/// The origin of the transformation. /// The origin of the rotation.
#[property(resolve)] #[property(resolve)]
pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default(); pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default();
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let transform = match T { Ok(Self {
ROTATE => { angle: args.named_or_find("angle")?.unwrap_or_default(),
let angle = args.named_or_find("angle")?.unwrap_or_default(); body: args.expect("body")?,
Transform::rotate(angle) }
} .pack())
SCALE | _ => {
let all = args.find()?;
let sx = args.named("x")?.or(all).unwrap_or(Ratio::one());
let sy = args.named("y")?.or(all).unwrap_or(Ratio::one());
Transform::scale(sx, sy)
}
};
Ok(Self { transform, body: args.expect("body")? }.pack())
} }
} }
impl<const T: TransformKind> Layout for TransformNode<T> { impl Layout for RotateNode {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -127,7 +108,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y) let transform = Transform::translate(x, y)
.pre_concat(self.transform) .pre_concat(Transform::rotate(self.angle))
.pre_concat(Transform::translate(-x, -y)); .pre_concat(Transform::translate(-x, -y));
frame.transform(transform); frame.transform(transform);
} }
@ -135,15 +116,69 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
} }
} }
impl<const T: TransformKind> Inline for TransformNode<T> {} impl Inline for RotateNode {}
/// Kinds of transformations. /// # Scale
/// Scale content without affecting layout.
/// ///
/// The move transformation is handled separately. /// ## Parameters
pub type TransformKind = usize; /// - body: Content (positional, required)
/// The content to scale.
///
/// - x: Ratio (named)
/// The horizontal scaling factor.
///
/// - y: Ratio (named)
/// The vertical scaling factor.
///
/// ## Category
/// layout
#[func]
#[capable(Layout, Inline)]
#[derive(Debug, Hash)]
pub struct ScaleNode {
/// Scaling factor.
pub factor: Axes<Ratio>,
/// The content that should be scaled.
pub body: Content,
}
/// A rotational transformation. #[node]
const ROTATE: TransformKind = 1; impl ScaleNode {
/// The origin of the transformation.
#[property(resolve)]
pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default();
/// A scale transformation. fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
const SCALE: TransformKind = 2; let all = args.find()?;
let x = args.named("x")?.or(all).unwrap_or(Ratio::one());
let y = args.named("y")?.or(all).unwrap_or(Ratio::one());
Ok(Self {
factor: Axes::new(x, y),
body: args.expect("body")?,
}
.pack())
}
}
impl Layout for ScaleNode {
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let mut fragment = self.body.layout(vt, styles, regions)?;
for frame in &mut fragment {
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y)
.pre_concat(Transform::scale(self.factor.x, self.factor.y))
.pre_concat(Transform::translate(-x, -y));
frame.transform(transform);
}
Ok(fragment)
}
}
impl Inline for ScaleNode {}

View File

@ -196,7 +196,7 @@ fn items() -> LangItems {
}, },
link: |url| meta::LinkNode::from_url(url).pack(), link: |url| meta::LinkNode::from_url(url).pack(),
ref_: |target| meta::RefNode(target).pack(), ref_: |target| meta::RefNode(target).pack(),
heading: |level, body| basics::HeadingNode { level, body }.pack(), heading: |level, body| basics::HeadingNode { level, title: body }.pack(),
list_item: |body| basics::ListItem::List(Box::new(body)).pack(), list_item: |body| basics::ListItem::List(Box::new(body)).pack(),
enum_item: |number, body| basics::ListItem::Enum(number, Box::new(body)).pack(), enum_item: |number, body| basics::ListItem::Enum(number, Box::new(body)).pack(),
desc_item: |term, body| { desc_item: |term, body| {

View File

@ -1,13 +1,14 @@
use super::*; use super::*;
/// # Vector
/// A column vector. /// A column vector.
/// ///
/// # Parameters /// ## Parameters
/// - elements: Content (positional, variadic) /// - elements: Content (positional, variadic)
/// The elements of the vector. /// The elements of the vector.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -59,24 +60,25 @@ pub enum Delimiter {
castable! { castable! {
Delimiter, Delimiter,
/// Delimit matrices with parentheses. /// Delimit vector with parentheses.
"(" => Self::Paren, "(" => Self::Paren,
/// Delimit matrices with brackets. /// Delimit vector with brackets.
"[" => Self::Bracket, "[" => Self::Bracket,
/// Delimit matrices with curly braces. /// Delimit vector with curly braces.
"{" => Self::Brace, "{" => Self::Brace,
/// Delimit matrices with vertical bars. /// Delimit vector with vertical bars.
"|" => Self::Bar, "|" => Self::Bar,
} }
/// # Cases
/// A case distinction. /// A case distinction.
/// ///
/// # Parameters /// ## Parameters
/// - branches: Content (positional, variadic) /// - branches: Content (positional, variadic)
/// The branches of the case distinction. /// The branches of the case distinction.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -14,17 +14,18 @@ use self::tex::layout_tex;
use crate::prelude::*; use crate::prelude::*;
use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode}; use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
/// # Math
/// A piece of a mathematical formula. /// A piece of a mathematical formula.
/// ///
/// # Parameters /// ## Parameters
/// - items: Content (positional, variadic) /// - items: Content (positional, variadic)
/// The individual parts of the formula. /// The individual parts of the formula.
/// ///
/// - block: bool (named) /// - block: bool (named)
/// Whether the formula is displayed as a separate block. /// Whether the formula is displayed as a separate block.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Show, Layout, Inline, Texify)] #[capable(Show, Layout, Inline, Texify)]
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -259,14 +260,15 @@ impl Texify for Content {
} }
} }
/// # Atom
/// An atom in a math formula: `x`, `+`, `12`. /// An atom in a math formula: `x`, `+`, `12`.
/// ///
/// # Parameters /// ## Parameters
/// - text: EcoString (positional, required) /// - text: EcoString (positional, required)
/// The atom's text. /// The atom's text.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -305,17 +307,18 @@ impl Texify for AtomNode {
} }
} }
/// # Accent
/// An accented node. /// An accented node.
/// ///
/// # Parameters /// ## Parameters
/// - base: Content (positional, required) /// - base: Content (positional, required)
/// The base to which the accent is applied. /// The base to which the accent is applied.
/// ///
/// - accent: Content (positional, required) /// - accent: Content (positional, required)
/// The accent to apply to the base. /// The accent to apply to the base.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -390,17 +393,18 @@ impl Texify for AccNode {
} }
} }
/// # Fraction
/// A fraction. /// A fraction.
/// ///
/// # Parameters /// ## Parameters
/// - num: Content (positional, required) /// - num: Content (positional, required)
/// The fraction's numerator. /// The fraction's numerator.
/// ///
/// - denom: Content (positional, required) /// - denom: Content (positional, required)
/// The fraction's denominator. /// The fraction's denominator.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -431,17 +435,18 @@ impl Texify for FracNode {
} }
} }
/// # Binomial
/// A binomial. /// A binomial.
/// ///
/// # Parameters /// ## Parameters
/// - upper: Content (positional, required) /// - upper: Content (positional, required)
/// The binomial's upper index. /// The binomial's upper index.
/// ///
/// - lower: Content (positional, required) /// - lower: Content (positional, required)
/// The binomial's lower index. /// The binomial's lower index.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -472,9 +477,10 @@ impl Texify for BinomNode {
} }
} }
/// # Script
/// A sub- and/or superscript. /// A sub- and/or superscript.
/// ///
/// # Parameters /// ## Parameters
/// - base: Content (positional, required) /// - base: Content (positional, required)
/// The base to which the applies the sub- and/or superscript. /// The base to which the applies the sub- and/or superscript.
/// ///
@ -484,8 +490,8 @@ impl Texify for BinomNode {
/// - sup: Content (named) /// - sup: Content (named)
/// The superscript. /// The superscript.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -528,14 +534,15 @@ impl Texify for ScriptNode {
} }
} }
/// # Alignment Point
/// A math alignment point: `&`, `&&`. /// A math alignment point: `&`, `&&`.
/// ///
/// # Parameters /// ## Parameters
/// - index: usize (positional, required) /// - index: usize (positional, required)
/// The alignment point's index. /// The alignment point's index.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -554,14 +561,15 @@ impl Texify for AlignPointNode {
} }
} }
/// # Square Root
/// A square root. /// A square root.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The expression to take the square root of. /// The expression to take the square root of.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -583,14 +591,15 @@ impl Texify for SqrtNode {
} }
} }
/// # Floor
/// A floored expression. /// A floored expression.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The expression to floor. /// The expression to floor.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -612,14 +621,15 @@ impl Texify for FloorNode {
} }
} }
/// # Ceil
/// A ceiled expression. /// A ceiled expression.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The expression to ceil. /// The expression to ceil.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,13 +1,14 @@
use super::*; use super::*;
/// # Serif
/// Serif (roman) font style. /// Serif (roman) font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -29,14 +30,15 @@ impl Texify for SerifNode {
} }
} }
/// # Sans-serif
/// Sans-serif font style. /// Sans-serif font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -58,14 +60,15 @@ impl Texify for SansNode {
} }
} }
/// # Bold
/// Bold font style. /// Bold font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -87,14 +90,15 @@ impl Texify for BoldNode {
} }
} }
/// # Italic
/// Italic font style. /// Italic font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -116,14 +120,15 @@ impl Texify for ItalNode {
} }
} }
/// # Calligraphic
/// Calligraphic font style. /// Calligraphic font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -145,14 +150,15 @@ impl Texify for CalNode {
} }
} }
/// # Fraktur
/// Fraktur font style. /// Fraktur font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -174,14 +180,15 @@ impl Texify for FrakNode {
} }
} }
/// # Monospace
/// Monospace font style. /// Monospace font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -203,14 +210,15 @@ impl Texify for MonoNode {
} }
} }
/// # Doublestruck
/// Blackboard bold (double-struck) font style. /// Blackboard bold (double-struck) font style.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The piece of formula to style. /// The piece of formula to style.
/// ///
/// # Tags /// ## Category
/// - math /// math
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,6 +1,7 @@
use crate::layout::{LayoutRoot, PageNode}; use crate::layout::{LayoutRoot, PageNode};
use crate::prelude::*; use crate::prelude::*;
/// # Document
/// The root element of a document and its metadata. /// The root element of a document and its metadata.
/// ///
/// All documents are automatically wrapped in a `document` element. The main /// All documents are automatically wrapped in a `document` element. The main
@ -10,8 +11,8 @@ use crate::prelude::*;
/// The metadata set with this function is not rendered within the document. /// The metadata set with this function is not rendered within the document.
/// Instead, it is embedded in the compiled PDF file. /// Instead, it is embedded in the compiled PDF file.
/// ///
/// # Tags /// ## Category
/// - meta /// meta
#[func] #[func]
#[capable(LayoutRoot)] #[capable(LayoutRoot)]
#[derive(Hash)] #[derive(Hash)]

View File

@ -1,12 +1,13 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// # Link
/// Link to a URL or another location in the document. /// Link to a URL or another location in the document.
/// ///
/// The link function makes its positional `body` argument clickable and links /// The link function makes its positional `body` argument clickable and links
/// it to the destination specified by the `dest` argument. /// it to the destination specified by the `dest` argument.
/// ///
/// # Example /// ## Example
/// ``` /// ```
/// #show link: underline /// #show link: underline
/// ///
@ -16,7 +17,7 @@ use crate::text::TextNode;
/// ] /// ]
/// ``` /// ```
/// ///
/// # Parameters /// ## Parameters
/// - dest: Destination (positional, required) /// - dest: Destination (positional, required)
/// The destination the link points to. /// The destination the link points to.
/// ///
@ -30,7 +31,7 @@ use crate::text::TextNode;
/// coordinates of type `length`. Pages are counted from one, and the /// coordinates of type `length`. Pages are counted from one, and the
/// coordinates are relative to the page's top left corner. /// coordinates are relative to the page's top left corner.
/// ///
/// # Example /// ### Example
/// ``` /// ```
/// #link("mailto:hello@typst.app") \ /// #link("mailto:hello@typst.app") \
/// #link((page: 1, x: 0pt, y: 0pt))[ /// #link((page: 1, x: 0pt, y: 0pt))[
@ -43,8 +44,8 @@ use crate::text::TextNode;
/// The content that should become a link. If `dest` is an URL string, the /// The content that should become a link. If `dest` is an URL string, the
/// parameter can be omitted. In this case, the URL will be shown as the link. /// parameter can be omitted. In this case, the URL will be shown as the link.
/// ///
/// # Tags /// ## Category
/// - meta /// meta
#[func] #[func]
#[capable(Show, Finalize)] #[capable(Show, Finalize)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -3,13 +3,14 @@ use crate::layout::{BlockNode, HNode, HideNode, RepeatNode, Spacing};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{LinebreakNode, SpaceNode, TextNode}; use crate::text::{LinebreakNode, SpaceNode, TextNode};
/// Generate a section outline / table of contents. /// # Outline
/// A section outline / table of contents.
/// ///
/// This function generates a list of all headings in the /// This function generates a list of all headings in the document, up to a
/// document, up to a given depth. The [@heading] numbering will be reproduced /// given depth. The [heading](@heading) numbering will be reproduced within the
/// within the outline. /// outline.
/// ///
/// # Example /// ## Example
/// ``` /// ```
/// #outline() /// #outline()
/// ///
@ -20,8 +21,8 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode};
/// #lorem(10) /// #lorem(10)
/// ``` /// ```
/// ///
/// # Tags /// ## Category
/// - meta /// meta
#[func] #[func]
#[capable(Prepare, Show)] #[capable(Prepare, Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -31,8 +32,8 @@ pub struct OutlineNode;
impl OutlineNode { impl OutlineNode {
/// The title of the outline. /// The title of the outline.
/// ///
/// - When set to `{auto}`, an appropriate title for the [@text] language will /// - When set to `{auto}`, an appropriate title for the [text](@text)
/// be used. This is the default. /// language will be used. This is the default.
/// - When set to `{none}`, the outline will not have a title. /// - When set to `{none}`, the outline will not have a title.
/// - A custom title can be set by passing content. /// - A custom title can be set by passing content.
#[property(referenced)] #[property(referenced)]
@ -44,7 +45,7 @@ impl OutlineNode {
/// Whether to indent the subheadings to align the start of their numbering /// Whether to indent the subheadings to align the start of their numbering
/// with the title of their parents. This will only have an effect if a /// with the title of their parents. This will only have an effect if a
/// [@heading] numbering is set. /// [heading](@heading) numbering is set.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -114,7 +115,7 @@ impl Show for OutlineNode {
}); });
seq.push( seq.push(
HeadingNode { body, level: NonZeroUsize::new(1).unwrap() } HeadingNode { title: body, level: NonZeroUsize::new(1).unwrap() }
.pack() .pack()
.styled(HeadingNode::NUMBERING, None) .styled(HeadingNode::NUMBERING, None)
.styled(HeadingNode::OUTLINED, false), .styled(HeadingNode::OUTLINED, false),
@ -175,7 +176,7 @@ impl Show for OutlineNode {
}; };
// Add the numbering and section name. // Add the numbering and section name.
let start = numbering + heading.body.clone(); let start = numbering + heading.title.clone();
seq.push(start.linked(Destination::Internal(loc))); seq.push(start.linked(Destination::Internal(loc)));
// Add filler symbols between the section name and page number. // Add filler symbols between the section name and page number.

View File

@ -1,6 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// # Reference
/// A reference to a label. /// A reference to a label.
/// ///
/// *Note: This function is currently unimplemented.* /// *Note: This function is currently unimplemented.*
@ -10,17 +11,17 @@ use crate::text::TextNode;
/// 1" for a reference to the first heading's label. The references are also /// 1" for a reference to the first heading's label. The references are also
/// links to the respective labels. /// links to the respective labels.
/// ///
/// # Syntax /// ## Syntax
/// This function also has dedicated syntax: A reference to a label can be /// 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. `[= /// created by typing an `@` followed by the name of the label (e.g. `[=
/// Introduction <intro>]` can be referenced by typing `[@intro]`). /// Introduction <intro>]` can be referenced by typing `[@intro]`).
/// ///
/// # Parameters /// ## Parameters
/// - target: Label (positional, required) /// - target: Label (positional, required)
/// The label that should be referenced. /// The label that should be referenced.
/// ///
/// # Tags /// ## Category
/// - meta /// meta
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -4,14 +4,15 @@ use ttf_parser::{GlyphId, OutlineBuilder};
use super::TextNode; use super::TextNode;
use crate::prelude::*; use crate::prelude::*;
/// # Underline
/// Typeset underline, stricken-through or overlined text. /// Typeset underline, stricken-through or overlined text.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to decorate. /// The content to decorate.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,10 +1,11 @@
use super::TextNode; use super::TextNode;
use crate::prelude::*; use crate::prelude::*;
/// # Space
/// A text space. /// A text space.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Unlabellable, Behave)] #[capable(Unlabellable, Behave)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -25,14 +26,15 @@ impl Behave for SpaceNode {
} }
} }
/// # Line Break
/// A line break. /// A line break.
/// ///
/// # Parameters /// ## Parameters
/// - justify: bool (named) /// - justify: bool (named)
/// Whether to justify the line before the break. /// Whether to justify the line before the break.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Behave)] #[capable(Behave)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -54,14 +56,15 @@ impl Behave for LinebreakNode {
} }
} }
/// # Strong Emphasis
/// Strongly emphasizes content by increasing the font weight. /// Strongly emphasizes content by increasing the font weight.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to strongly emphasize. /// The content to strongly emphasize.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -107,14 +110,15 @@ impl Fold for Delta {
} }
} }
/// # Emphasis
/// Emphasizes content by flipping the italicness. /// Emphasizes content by flipping the italicness.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The content to emphasize. /// The content to emphasize.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -152,27 +156,29 @@ impl Fold for Toggle {
} }
} }
/// # Lowercase
/// Convert text or content to lowercase. /// Convert text or content to lowercase.
/// ///
/// # Parameters /// ## Parameters
/// - text: ToCase (positional, required) /// - text: ToCase (positional, required)
/// The text to convert to lowercase. /// The text to convert to lowercase.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
pub fn lower(args: &mut Args) -> SourceResult<Value> { pub fn lower(args: &mut Args) -> SourceResult<Value> {
case(Case::Lower, args) case(Case::Lower, args)
} }
/// # Uppercase
/// Convert text or content to uppercase. /// Convert text or content to uppercase.
/// ///
/// # Parameters /// ## Parameters
/// - text: ToCase (positional, required) /// - text: ToCase (positional, required)
/// The text to convert to uppercase. /// The text to convert to uppercase.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
pub fn upper(args: &mut Args) -> SourceResult<Value> { pub fn upper(args: &mut Args) -> SourceResult<Value> {
case(Case::Upper, args) case(Case::Upper, args)
@ -216,14 +222,15 @@ impl Case {
} }
} }
/// # Small Capitals
/// Display text in small capitals. /// Display text in small capitals.
/// ///
/// # Parameters /// ## Parameters
/// - text: Content (positional, required) /// - text: Content (positional, required)
/// The text to display to small capitals. /// The text to display to small capitals.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> { pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
let body: Content = args.expect("content")?; let body: Content = args.expect("content")?;

View File

@ -25,17 +25,18 @@ use typst::util::EcoString;
use crate::layout::ParNode; use crate::layout::ParNode;
use crate::prelude::*; use crate::prelude::*;
/// # Text
/// Stylable text. /// Stylable text.
/// ///
/// # Parameters /// ## Parameters
/// - family: EcoString (positional, variadic, settable) /// - family: EcoString (positional, variadic, settable)
/// A prioritized sequence of font families. /// A prioritized sequence of font families.
/// ///
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// Content in which all text is styled according to the other arguments. /// Content in which all text is styled according to the other arguments.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable] #[capable]
#[derive(Clone, Hash)] #[derive(Clone, Hash)]

View File

@ -2,14 +2,15 @@ use typst::syntax::is_newline;
use crate::prelude::*; use crate::prelude::*;
/// # Smart Quote
/// A smart quote. /// A smart quote.
/// ///
/// # Parameters /// ## Parameters
/// - double: bool (named) /// - double: bool (named)
/// Whether to produce a smart double quote. /// Whether to produce a smart double quote.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable] #[capable]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -6,17 +6,18 @@ use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
use crate::layout::BlockNode; use crate::layout::BlockNode;
use crate::prelude::*; use crate::prelude::*;
/// # Raw Text
/// Raw text with optional syntax highlighting. /// Raw text with optional syntax highlighting.
/// ///
/// # Parameters /// ## Parameters
/// - text: EcoString (positional, required) /// - text: EcoString (positional, required)
/// The raw text. /// The raw text.
/// ///
/// - block: bool (named) /// - block: bool (named)
/// Whether the raw text is displayed as a separate block. /// Whether the raw text is displayed as a separate block.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -4,6 +4,7 @@ use typst::util::EcoString;
use super::{variant, SpaceNode, TextNode, TextSize}; use super::{variant, SpaceNode, TextNode, TextSize};
use crate::prelude::*; use crate::prelude::*;
/// # Subscript
/// Sub- or superscript text. /// Sub- or superscript text.
/// ///
/// The text is rendered smaller and its baseline is raised/lowered. To provide /// The text is rendered smaller and its baseline is raised/lowered. To provide
@ -11,12 +12,12 @@ use crate::prelude::*;
/// superscript codepoints. If that fails, we fall back to rendering shrunk /// superscript codepoints. If that fails, we fall back to rendering shrunk
/// normal letters in a raised way. /// normal letters in a raised way.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The text to display in sub- or superscript. /// The text to display in sub- or superscript.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,14 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// # Symbol
/// A symbol identified by symmie notation. /// A symbol identified by symmie notation.
/// ///
/// # Parameters /// ## Parameters
/// - notation: EcoString (positional, required) /// - notation: EcoString (positional, required)
/// The symbols symmie notation. /// The symbols symmie notation.
/// ///
/// # Tags /// ## Category
/// - text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -4,16 +4,17 @@ use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
use crate::prelude::*; use crate::prelude::*;
/// # Image
/// Show a raster or vector graphic. /// Show a raster or vector graphic.
/// ///
/// Supported formats are PNG, JPEG, GIF and SVG. /// Supported formats are PNG, JPEG, GIF and SVG.
/// ///
/// # Parameters /// ## Parameters
/// - path: EcoString (positional, required) /// - path: EcoString (positional, required)
/// Path to an image file. /// Path to an image file.
/// ///
/// # Tags /// ## Category
/// - visualize /// visualize
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -1,10 +1,11 @@
use crate::prelude::*; use crate::prelude::*;
/// # Line
/// Display a line without affecting the layout. /// Display a line without affecting the layout.
/// ///
/// You should only provide either an endpoint or an angle and a length. /// You should only provide either an endpoint or an angle and a length.
/// ///
/// # Parameters /// ## Parameters
/// - origin: Axes<Rel<Length>> (named) /// - origin: Axes<Rel<Length>> (named)
/// The start point of the line. /// The start point of the line.
/// ///
@ -17,8 +18,8 @@ use crate::prelude::*;
/// - angle: Angle (named) /// - angle: Angle (named)
/// The angle at which the line points away from the origin. /// The angle at which the line points away from the origin.
/// ///
/// # Tags /// ## Category
/// - visualize /// visualize
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -2,9 +2,10 @@ use std::f64::consts::SQRT_2;
use crate::prelude::*; use crate::prelude::*;
/// # Rectangle
/// A sizable and fillable shape with optional content. /// A sizable and fillable shape with optional content.
/// ///
/// # Parameters /// ## Parameters
/// - body: Content (positional) /// - body: Content (positional)
/// The content to place into the shape. /// The content to place into the shape.
/// ///
@ -23,8 +24,8 @@ use crate::prelude::*;
/// - stroke: Smart<Sides<Option<PartialStroke>>> (named) /// - stroke: Smart<Sides<Option<PartialStroke>>> (named)
/// How to stroke the shape. /// How to stroke the shape.
/// ///
/// # Tags /// ## Category
/// - visualize /// visualize
#[func] #[func]
#[capable(Layout, Inline)] #[capable(Layout, Inline)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View File

@ -4,27 +4,34 @@ use super::*;
/// Expand the `#[func]` macro. /// Expand the `#[func]` macro.
pub fn func(item: syn::Item) -> Result<TokenStream> { pub fn func(item: syn::Item) -> Result<TokenStream> {
let mut docs = match &item { let docs = match &item {
syn::Item::Struct(item) => documentation(&item.attrs), syn::Item::Struct(item) => documentation(&item.attrs),
syn::Item::Enum(item) => documentation(&item.attrs), syn::Item::Enum(item) => documentation(&item.attrs),
syn::Item::Fn(item) => documentation(&item.attrs), syn::Item::Fn(item) => documentation(&item.attrs),
_ => String::new(), _ => String::new(),
}; };
let tags = tags(&mut docs); let first = docs.lines().next().unwrap();
let display = first.strip_prefix("# ").unwrap();
let display = display.trim();
let mut docs = docs[first.len()..].to_string();
let example = example(&mut docs, 2);
let params = params(&mut docs)?; let params = params(&mut docs)?;
let example = quote_option(example(&mut docs)); let syntax = quote_option(section(&mut docs, "Syntax", 2));
let syntax = quote_option(section(&mut docs, "Syntax")); let category = section(&mut docs, "Category", 2).expect("missing category");
let example = quote_option(example);
let docs = docs.trim(); let docs = docs.trim();
if docs.contains("# ") { if docs.contains("# ") {
bail!(item, "Documentation heading not recognized"); bail!(item, "unrecognized heading");
} }
let info = quote! { let info = quote! {
::typst::model::FuncInfo { ::typst::model::FuncInfo {
name, name,
tags: &[#(#tags),*], display: #display,
category: #category,
docs: #docs, docs: #docs,
example: #example, example: #example,
syntax: #syntax, syntax: #syntax,
@ -57,7 +64,7 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
impl::typst::model::FuncType for #ty { impl::typst::model::FuncType for #ty {
fn create_func(name: &'static str) -> ::typst::model::Func { fn create_func(name: &'static str) -> ::typst::model::Func {
::typst::model::Func::from_fn(name, #full, #info) ::typst::model::Func::from_fn(#full, #info)
} }
} }
}) })
@ -75,7 +82,7 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
impl #params ::typst::model::FuncType for #ident #args #clause { impl #params ::typst::model::FuncType for #ident #args #clause {
fn create_func(name: &'static str) -> ::typst::model::Func { fn create_func(name: &'static str) -> ::typst::model::Func {
::typst::model::Func::from_node::<Self>(name, #info) ::typst::model::Func::from_node::<Self>(#info)
} }
} }
}) })
@ -83,31 +90,27 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
} }
/// Extract a section. /// Extract a section.
pub fn section(docs: &mut String, title: &str) -> Option<String> { pub fn section(docs: &mut String, title: &str, level: usize) -> Option<String> {
let needle = format!("\n# {title}\n"); let hashtags = "#".repeat(level);
let needle = format!("\n{hashtags} {title}\n");
let start = docs.find(&needle)?; let start = docs.find(&needle)?;
let rest = &docs[start..]; let rest = &docs[start..];
let len = rest[1..].find("\n# ").map(|x| 1 + x).unwrap_or(rest.len()); let len = rest[1..]
.find("\n# ")
.or(rest[1..].find("\n## "))
.or(rest[1..].find("\n### "))
.map(|x| 1 + x)
.unwrap_or(rest.len());
let end = start + len; let end = start + len;
let section = docs[start + needle.len()..end].trim().to_owned(); let section = docs[start + needle.len()..end].trim().to_owned();
docs.replace_range(start..end, ""); docs.replace_range(start..end, "");
Some(section) Some(section)
} }
/// Parse the tag section.
fn tags(docs: &mut String) -> Vec<String> {
section(docs, "Tags")
.unwrap_or_default()
.lines()
.filter_map(|line| line.strip_prefix('-'))
.map(|s| s.trim().into())
.collect()
}
/// Parse the example section. /// Parse the example section.
pub fn example(docs: &mut String) -> Option<String> { pub fn example(docs: &mut String, level: usize) -> Option<String> {
Some( Some(
section(docs, "Example")? section(docs, "Example", level)?
.lines() .lines()
.skip_while(|line| !line.contains("```")) .skip_while(|line| !line.contains("```"))
.skip(1) .skip(1)
@ -119,7 +122,7 @@ pub fn example(docs: &mut String) -> Option<String> {
/// Parse the parameter section. /// Parse the parameter section.
fn params(docs: &mut String) -> Result<Vec<TokenStream>> { fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
let Some(section) = section(docs, "Parameters") else { return Ok(vec![]) }; let Some(section) = section(docs, "Parameters", 2) else { return Ok(vec![]) };
let mut s = Scanner::new(&section); let mut s = Scanner::new(&section);
let mut infos = vec![]; let mut infos = vec![];
@ -159,7 +162,7 @@ fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
s.expect(')'); s.expect(')');
let mut docs = dedent(s.eat_until("\n-").trim()); let mut docs = dedent(s.eat_until("\n-").trim());
let example = quote_option(example(&mut docs)); let example = quote_option(example(&mut docs, 3));
let docs = docs.trim(); let docs = docs.trim();
infos.push(quote! { infos.push(quote! {

View File

@ -337,7 +337,7 @@ fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod {
let shorthand = matches!(property.shorthand, Some(Shorthand::Positional)); let shorthand = matches!(property.shorthand, Some(Shorthand::Positional));
let mut docs = documentation(&property.attrs); let mut docs = documentation(&property.attrs);
let example = quote_option(super::func::example(&mut docs)); let example = quote_option(super::func::example(&mut docs, 1));
let docs = docs.trim(); let docs = docs.trim();
quote! { quote! {

View File

@ -42,6 +42,11 @@ impl Em {
(self.0).0 (self.0).0
} }
/// The absolute value of this em length.
pub fn abs(self) -> Self {
Self::new(self.get().abs())
}
/// Convert to an absolute length at the given font size. /// Convert to an absolute length at the given font size.
pub fn at(self, font_size: Abs) -> Abs { pub fn at(self, font_size: Abs) -> Abs {
let resolved = font_size * self.get(); let resolved = font_size * self.get();

View File

@ -18,6 +18,12 @@ impl Length {
Self { abs: Abs::zero(), em: Em::zero() } Self { abs: Abs::zero(), em: Em::zero() }
} }
/// Try to compute the absolute value of the length.
pub fn try_abs(self) -> Option<Self> {
(self.abs.is_zero() || self.em.is_zero())
.then(|| Self { abs: self.abs.abs(), em: self.em.abs() })
}
/// Try to divide two lengths. /// Try to divide two lengths.
pub fn try_div(self, other: Self) -> Option<f64> { pub fn try_div(self, other: Self) -> Option<f64> {
if self.abs.is_zero() && other.abs.is_zero() { if self.abs.is_zero() && other.abs.is_zero() {

View File

@ -774,7 +774,7 @@ impl<'a> CompletionContext<'a> {
matches!( matches!(
value, value,
Value::Func(func) if func.info().map_or(false, |info| { Value::Func(func) if func.info().map_or(false, |info| {
info.tags.contains(&"math") info.category == "math"
}), }),
) )
}); });
@ -805,7 +805,7 @@ impl<'a> CompletionContext<'a> {
!short_form || matches!( !short_form || matches!(
value, value,
Value::Func(func) if func.info().map_or(true, |info| { Value::Func(func) if func.info().map_or(true, |info| {
!info.tags.contains(&"math") info.category != "math"
}), }),
) )
}); });

View File

@ -476,10 +476,7 @@ impl Eval for ast::Frac {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.math_frac)( Ok((vm.items.math_frac)(self.num().eval(vm)?, self.denom().eval(vm)?))
self.num().eval(vm)?,
self.denom().eval(vm)?,
))
} }
} }
@ -781,11 +778,7 @@ impl Eval for ast::FieldAccess {
.field(&field) .field(&field)
.ok_or_else(|| format!("unknown field {field:?}")) .ok_or_else(|| format!("unknown field {field:?}"))
.at(span)?, .at(span)?,
v => bail!( v => bail!(self.target().span(), "cannot access field on {}", v.type_name()),
self.target().span(),
"cannot access field on {}",
v.type_name()
),
}) })
} }
} }

View File

@ -37,18 +37,16 @@ impl Func {
/// Create a new function from a native rust function. /// Create a new function from a native rust function.
pub fn from_fn( pub fn from_fn(
name: &'static str,
func: fn(&Vm, &mut Args) -> SourceResult<Value>, func: fn(&Vm, &mut Args) -> SourceResult<Value>,
info: FuncInfo, info: FuncInfo,
) -> Self { ) -> Self {
Self(Arc::new(Repr::Native(Native { name, func, set: None, node: None, info }))) Self(Arc::new(Repr::Native(Native { func, set: None, node: None, info })))
} }
/// Create a new function from a native rust node. /// Create a new function from a native rust node.
pub fn from_node<T: Node>(name: &'static str, mut info: FuncInfo) -> Self { pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
info.params.extend(T::properties()); info.params.extend(T::properties());
Self(Arc::new(Repr::Native(Native { Self(Arc::new(Repr::Native(Native {
name,
func: |ctx, args| { func: |ctx, args| {
let styles = T::set(args, true)?; let styles = T::set(args, true)?;
let content = T::construct(ctx, args)?; let content = T::construct(ctx, args)?;
@ -68,7 +66,7 @@ impl Func {
/// The name of the function. /// The name of the function.
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
match self.0.as_ref() { match self.0.as_ref() {
Repr::Native(native) => Some(native.name), Repr::Native(native) => Some(native.info.name),
Repr::Closure(closure) => closure.name.as_deref(), Repr::Closure(closure) => closure.name.as_deref(),
Repr::With(func, _) => func.name(), Repr::With(func, _) => func.name(),
} }
@ -184,8 +182,6 @@ pub trait FuncType {
/// A function defined by a native rust function or node. /// A function defined by a native rust function or node.
struct Native { struct Native {
/// The name of the function.
name: &'static str,
/// The function pointer. /// The function pointer.
func: fn(&Vm, &mut Args) -> SourceResult<Value>, func: fn(&Vm, &mut Args) -> SourceResult<Value>,
/// The set rule. /// The set rule.
@ -198,7 +194,6 @@ struct Native {
impl Hash for Native { impl Hash for Native {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
(self.func as usize).hash(state); (self.func as usize).hash(state);
self.set.map(|set| set as usize).hash(state); self.set.map(|set| set as usize).hash(state);
self.node.hash(state); self.node.hash(state);
@ -210,8 +205,10 @@ impl Hash for Native {
pub struct FuncInfo { pub struct FuncInfo {
/// The function's name. /// The function's name.
pub name: &'static str, pub name: &'static str,
/// Tags that categorize the function. /// The display name of the function.
pub tags: &'static [&'static str], pub display: &'static str,
/// Which category the function is part of.
pub category: &'static str,
/// Documentation for the function. /// Documentation for the function.
pub docs: &'static str, pub docs: &'static str,
/// The source code of an example, if any. /// The source code of an example, if any.

View File

@ -145,6 +145,9 @@ impl Args {
} }
fn library() -> Library { fn library() -> Library {
/// # Test
/// ## Category
/// test
#[func] #[func]
fn test(args: &mut typst::model::Args) -> SourceResult<Value> { fn test(args: &mut typst::model::Args) -> SourceResult<Value> {
let lhs = args.expect::<Value>("left-hand side")?; let lhs = args.expect::<Value>("left-hand side")?;
@ -155,6 +158,9 @@ fn library() -> Library {
Ok(Value::None) Ok(Value::None)
} }
/// # Print
/// ## Category
/// test
#[func] #[func]
fn print(args: &mut typst::model::Args) -> SourceResult<Value> { fn print(args: &mut typst::model::Args) -> SourceResult<Value> {
print!("> "); print!("> ");