diff --git a/Cargo.lock b/Cargo.lock index 8d21d48ce..d8061d8bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1221,6 +1221,7 @@ dependencies = [ "proc-macro2", "quote", "syn", + "unscanny", ] [[package]] diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index 60415697c..2781034eb 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -7,7 +7,14 @@ use crate::text::{SpaceNode, TextNode, TextSize}; /// A section heading. /// -/// Tags: basics. +/// # Parameters +/// - body: Content (positional, required) +/// The heading's contents. +/// - level: NonZeroUsize (named) +/// The logical nesting depth of the heading, starting from one. +/// +/// # Tags +/// - basics #[func] #[capable(Prepare, Show, Finalize)] #[derive(Debug, Hash)] diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs index 4c016128b..c9215a507 100644 --- a/library/src/basics/list.rs +++ b/library/src/basics/list.rs @@ -5,7 +5,17 @@ use crate::text::{SpaceNode, TextNode}; /// An unordered (bulleted) or ordered (numbered) list. /// -/// Tags: basics. +/// # Parameters +/// - items: Content (positional, variadic) +/// The contents of the list items. +/// - start: NonZeroUsize (named) +/// Which number to start the enumeration with. +/// - tight: bool (named) +/// Makes the list more compact, if enabled. This looks better if the items +/// fit into a single line each. +/// +/// # Tags +/// - basics #[func] #[capable(Layout)] #[derive(Debug, Hash)] diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index 10a9143f8..d7e2e08fb 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -3,7 +3,22 @@ use crate::prelude::*; /// A table of items. /// -/// Tags: basics. +/// # Parameters +/// - cells: Content (positional, variadic) +/// The contents of the table cells. +/// - rows: TrackSizings (named) +/// Defines the row sizes. +/// - columns: TrackSizings (named) +/// Defines the column sizes. +/// - gutter: TrackSizings (named) +/// Defines the gaps between rows & columns. +/// - column-gutter: TrackSizings (named) +/// Defines the gaps between columns. Takes precedence over `gutter`. +/// - row-gutter: TrackSizings (named) +/// Defines the gaps between rows. Takes precedence over `gutter`. +/// +/// # Tags +/// - basics #[func] #[capable(Layout)] #[derive(Debug, Hash)] diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index 62d0a4191..eccc45315 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -4,26 +4,37 @@ use crate::prelude::*; /// The absolute value of a numeric value. /// -/// Tags: calculate. +/// # Parameters +/// - value: ToAbs (positional, required) +/// The value whose absolute value to calculate. +/// +/// # Tags +/// - calculate #[func] pub fn abs(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("numeric value")?; - Ok(match v { - Value::Int(v) => Value::Int(v.abs()), - Value::Float(v) => Value::Float(v.abs()), - Value::Angle(v) => Value::Angle(v.abs()), - Value::Ratio(v) => Value::Ratio(v.abs()), - Value::Fraction(v) => Value::Fraction(v.abs()), - Value::Length(_) | Value::Relative(_) => { - bail!(span, "cannot take absolute value of a length") - } - v => bail!(span, "expected numeric value, found {}", v.type_name()), - }) + Ok(args.expect::("value")?.0) +} + +/// A value of which the absolute value can be taken. +struct ToAbs(Value); + +castable! { + ToAbs, + v: i64 => Self(Value::Int(v.abs())), + v: f64 => Self(Value::Float(v.abs())), + v: Angle => Self(Value::Angle(v.abs())), + v: Ratio => Self(Value::Ratio(v.abs())), + v: Fr => Self(Value::Fraction(v.abs())), } /// The minimum of a sequence of values. /// -/// Tags: calculate. +/// # Parameters +/// - values: Value (positional, variadic) +/// The sequence of values. +/// +/// # Tags +/// - calculate #[func] pub fn min(args: &mut Args) -> SourceResult { minmax(args, Ordering::Less) @@ -31,7 +42,12 @@ pub fn min(args: &mut Args) -> SourceResult { /// The maximum of a sequence of values. /// -/// Tags: calculate. +/// # Parameters +/// - values: Value (positional, variadic) +/// The sequence of values. +/// +/// # Tags +/// - calculate #[func] pub fn max(args: &mut Args) -> SourceResult { minmax(args, Ordering::Greater) @@ -60,27 +76,44 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { /// Whether an integer is even. /// -/// Tags: calculate. +/// # Parameters +/// - value: i64 (positional, required) +/// The number to check for evenness. +/// +/// # Tags +/// - calculate #[func] pub fn even(args: &mut Args) -> SourceResult { - Ok(Value::Bool(args.expect::("integer")? % 2 == 0)) + Ok(Value::Bool(args.expect::("value")? % 2 == 0)) } /// Whether an integer is odd. /// -/// Tags: calculate. +/// # Parameters +/// - value: i64 (positional, required) +/// The number to check for oddness. +/// +/// # Tags +/// - calculate #[func] pub fn odd(args: &mut Args) -> SourceResult { - Ok(Value::Bool(args.expect::("integer")? % 2 != 0)) + Ok(Value::Bool(args.expect::("value")? % 2 != 0)) } -/// The modulo of two numbers. +/// The modulus of two numbers. /// -/// Tags: calculate. +/// # Parameters +/// - dividend: ToMod (positional, required) +/// The dividend of the modulus. +/// - divisor: ToMod (positional, required) +/// The divisor of the modulus. +/// +/// # Tags +/// - calculate #[func] pub fn mod_(args: &mut Args) -> SourceResult { - let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; - let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; + let Spanned { v: v1, span: span1 } = args.expect("dividend")?; + let Spanned { v: v2, span: span2 } = args.expect("divisor")?; let (a, b) = match (v1, v2) { (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) { @@ -104,3 +137,12 @@ pub fn mod_(args: &mut Args) -> SourceResult { Ok(Value::Float(a % b)) } + +/// A value which can be passed to the `mod` function. +struct ToMod; + +castable! { + ToMod, + _: i64 => Self, + _: f64 => Self, +} diff --git a/library/src/compute/create.rs b/library/src/compute/create.rs index a0eecfb8e..e3733d60a 100644 --- a/library/src/compute/create.rs +++ b/library/src/compute/create.rs @@ -6,42 +6,60 @@ use crate::prelude::*; /// Convert a value to an integer. /// -/// Tags: create. +/// # Parameters +/// - value: ToInt (positional, required) +/// The value that should be converted to an integer. +/// +/// # Tags +/// - create #[func] pub fn int(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Int(match v { - Value::Bool(v) => v as i64, - Value::Int(v) => v, - Value::Float(v) => v as i64, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid integer"), - }, - v => bail!(span, "cannot convert {} to integer", v.type_name()), - })) + Ok(Value::Int(args.expect::("value")?.0)) +} + +/// A value that can be cast to an integer. +struct ToInt(i64); + +castable! { + ToInt, + v: bool => Self(v as i64), + v: i64 => Self(v), + v: f64 => Self(v as i64), + v: EcoString => Self(v.parse().map_err(|_| "not a valid integer")?), } /// Convert a value to a float. /// -/// Tags: create. +/// # Parameters +/// - value: ToFloat (positional, required) +/// The value that should be converted to a float. +/// +/// # Tags +/// - create #[func] pub fn float(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Float(match v { - Value::Int(v) => v as f64, - Value::Float(v) => v, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid float"), - }, - v => bail!(span, "cannot convert {} to float", v.type_name()), - })) + Ok(Value::Float(args.expect::("value")?.0)) +} + +/// A value that can be cast to a float. +struct ToFloat(f64); + +castable! { + ToFloat, + v: bool => Self(v as i64 as f64), + v: i64 => Self(v as f64), + v: f64 => Self(v), + v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?), } /// Create a grayscale color. /// -/// Tags: create. +/// # Parameters +/// - gray: Component (positional, required) +/// The gray component. +/// +/// # Tags +/// - create #[func] pub fn luma(args: &mut Args) -> SourceResult { let Component(luma) = args.expect("gray component")?; @@ -50,7 +68,26 @@ pub fn luma(args: &mut Args) -> SourceResult { /// Create an RGB(A) color. /// -/// Tags: create. +/// # Parameters +/// - hex: EcoString (positional) +/// The color in hexademical 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. +/// +/// - red: Component (positional) +/// The red component. +/// - green: Component (positional) +/// The green component. +/// - blue: Component (positional) +/// The blue component. +/// - alpha: Component (positional) +/// The alpha component. +/// +/// # Tags +/// - create #[func] pub fn rgb(args: &mut Args) -> SourceResult { Ok(Value::Color(if let Some(string) = args.find::>()? { @@ -67,18 +104,6 @@ pub fn rgb(args: &mut Args) -> SourceResult { })) } -/// Create a CMYK color. -/// -/// Tags: create. -#[func] -pub fn cmyk(args: &mut Args) -> SourceResult { - let RatioComponent(c) = args.expect("cyan component")?; - let RatioComponent(m) = args.expect("magenta component")?; - let RatioComponent(y) = args.expect("yellow component")?; - let RatioComponent(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) -} - /// An integer or ratio component. struct Component(u8); @@ -95,6 +120,29 @@ castable! { }, } +/// Create a CMYK color. +/// +/// # Parameters +/// - cyan: RatioComponent (positional, required) +/// The cyan component. +/// - magenta: RatioComponent (positional, required) +/// The magenta component. +/// - yellow: RatioComponent (positional, required) +/// The yellow component. +/// - key: RatioComponent (positional, required) +/// The key component. +/// +/// # Tags +/// - create +#[func] +pub fn cmyk(args: &mut Args) -> SourceResult { + let RatioComponent(c) = args.expect("cyan component")?; + let RatioComponent(m) = args.expect("magenta component")?; + let RatioComponent(y) = args.expect("yellow component")?; + let RatioComponent(k) = args.expect("key component")?; + Ok(Value::Color(CmykColor::new(c, m, y, k).into())) +} + /// A component that must be a ratio. struct RatioComponent(u8); @@ -109,22 +157,36 @@ castable! { /// Convert a value to a string. /// -/// Tags: create. +/// # Parameters +/// - value: ToStr (positional, required) +/// The value that should be converted to a string. +/// +/// # Tags +/// - create #[func] pub fn str(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Str(match v { - Value::Int(v) => format_str!("{}", v), - Value::Float(v) => format_str!("{}", v), - Value::Label(label) => label.0.into(), - Value::Str(v) => v, - v => bail!(span, "cannot convert {} to string", v.type_name()), - })) + Ok(Value::Str(args.expect::("value")?.0)) +} + +/// A value that can be cast to a string. +struct ToStr(Str); + +castable! { + ToStr, + v: i64 => Self(format_str!("{}", v)), + v: f64 => Self(format_str!("{}", v)), + v: Label => Self(v.0.into()), + v: Str => Self(v), } /// Create a label from a string. /// -/// Tags: create. +/// # Parameters +/// - name: EcoString (positional, required) +/// The name of the label. +/// +/// # Tags +/// - create #[func] pub fn label(args: &mut Args) -> SourceResult { Ok(Value::Label(Label(args.expect("string")?))) @@ -132,7 +194,12 @@ pub fn label(args: &mut Args) -> SourceResult { /// Create a regular expression from a string. /// -/// Tags: create. +/// # Parameters +/// - regex: EcoString (positional, required) +/// The regular expression. +/// +/// # Tags +/// - create #[func] pub fn regex(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("regular expression")?; @@ -141,7 +208,18 @@ pub fn regex(args: &mut Args) -> SourceResult { /// Create an array consisting of a sequence of numbers. /// -/// Tags: create. +/// # Parameters +/// - start: i64 (positional) +/// The start of the range (inclusive). +/// +/// - end: i64 (positional, required) +/// The end of the range (exclusive). +/// +/// - step: i64 (named) +/// The distance between the generated numbers. +/// +/// # Tags +/// - create #[func] pub fn range(args: &mut Args) -> SourceResult { let first = args.expect::("end")?; diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index af545304a..5de3eb617 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -6,7 +6,12 @@ use crate::prelude::*; /// Read structured data from a CSV file. /// -/// Tags: data-loading. +/// # Parameters +/// - path: EcoString (positional, required) +/// Path to a CSV file. +/// +/// # Tags +/// - data-loading #[func] pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = @@ -49,7 +54,12 @@ fn format_csv_error(error: csv::Error) -> String { /// Read structured data from a JSON file. /// -/// Tags: data-loading. +/// # Parameters +/// - path: EcoString (positional, required) +/// Path to a JSON file. +/// +/// # Tags +/// - data-loading #[func] pub fn json(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = @@ -92,7 +102,12 @@ fn format_json_error(error: serde_json::Error) -> String { /// Read structured data from an XML file. /// -/// Tags: data-loading. +/// # Parameters +/// - path: EcoString (positional, required) +/// Path to an XML file. +/// +/// # Tags +/// - data-loading #[func] pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 22d26553e..cb952d812 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -6,7 +6,12 @@ use typst::syntax::Source; /// The name of a value's type. /// -/// Tags: foundations. +/// # Parameters +/// - value: Value (positional, required) +/// The value whose type's to determine. +/// +/// # Tags +/// - foundations #[func] pub fn type_(args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.type_name().into()) @@ -14,7 +19,12 @@ pub fn type_(args: &mut Args) -> SourceResult { /// The string representation of a value. /// -/// Tags: foundations. +/// # Parameters +/// - value: Value (positional, required) +/// The value whose string representation to produce. +/// +/// # Tags +/// - foundations #[func] pub fn repr(args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.repr().into()) @@ -22,7 +32,12 @@ pub fn repr(args: &mut Args) -> SourceResult { /// Ensure that a condition is fulfilled. /// -/// Tags: foundations. +/// # Parameters +/// - condition: bool (positional, required) +/// The condition that must be true for the assertion to pass. +/// +/// # Tags +/// - foundations #[func] pub fn assert(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("condition")?; @@ -34,7 +49,12 @@ pub fn assert(args: &mut Args) -> SourceResult { /// Evaluate a string as Typst markup. /// -/// Tags: foundations. +/// # Parameters +/// - source: String (positional, required) +/// A string of Typst markup to evaluate. +/// +/// # Tags +/// - foundations #[func] pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: text, span } = args.expect::>("source")?; diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs index 5a6534f47..bdad96185 100644 --- a/library/src/compute/utility.rs +++ b/library/src/compute/utility.rs @@ -3,18 +3,30 @@ use std::str::FromStr; use crate::prelude::*; use crate::text::Case; -/// Create a blind text string. +/// Create blind text. /// -/// Tags: utility. +/// # Parameters +/// - words: usize (positional, required) +/// The length of the blind text in words. +/// +/// # Tags +/// - utility #[func] pub fn lorem(args: &mut Args) -> SourceResult { let words: usize = args.expect("number of words")?; Ok(Value::Str(lipsum::lipsum(words).into())) } -/// Apply a numbering pattern to a number. +/// Apply a numbering pattern to a sequence of numbers. /// -/// Tags: utility. +/// # Parameters +/// - pattern: NumberingPattern (positional, required) +/// A string that defines how the numbering works. +/// - numbers: NonZeroUsize (positional, variadic) +/// The numbers to apply the pattern to. +/// +/// # Tags +/// - utility #[func] pub fn numbering(args: &mut Args) -> SourceResult { let pattern = args.expect::("pattern")?; diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index f00aeaf27..7853d84df 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -2,7 +2,14 @@ use crate::prelude::*; /// Align content horizontally and vertically. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The content to align. +/// - alignment: Axes> (positional, settable) +/// The alignment along both axes. +/// +/// # Tags +/// - layout #[func] #[capable] #[derive(Debug, Hash)] diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 3bbf56e4e..b31b0a6d5 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -3,13 +3,20 @@ use crate::text::TextNode; /// Separate a region into multiple equally sized columns. /// -/// Tags: layout. +/// # Parameters +/// - count: usize (positional, required) +/// The number of columns. +/// - body: Content (positional, required) +/// The content that should be layouted into the columns. +/// +/// # Tags +/// - layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] pub struct ColumnsNode { /// How many columns there should be. - pub columns: NonZeroUsize, + pub count: NonZeroUsize, /// The child to be layouted into the columns. Most likely, this should be a /// flow or stack node. pub body: Content, @@ -23,7 +30,7 @@ impl ColumnsNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self { - columns: args.expect("column count")?, + count: args.expect("column count")?, body: args.expect("body")?, } .pack()) @@ -44,7 +51,7 @@ impl Layout for ColumnsNode { } // Determine the width of the gutter and each column. - let columns = self.columns.get(); + let columns = self.count.get(); let gutter = styles.get(Self::GUTTER).relative_to(regions.base.x); let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64; @@ -106,7 +113,13 @@ impl Layout for ColumnsNode { /// A column break. /// -/// Tags: layout. +/// # Parameters +/// - weak: bool (named) +/// If true, the column break is skipped if the current column is already +/// empty. +/// +/// # Tags +/// - layout #[func] #[capable(Behave)] #[derive(Debug, Hash)] diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index d451bccfd..4b58434b8 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -1,9 +1,19 @@ use super::VNode; +use crate::layout::Spacing; use crate::prelude::*; /// An inline-level container that sizes content. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional) +/// The contents of the box. +/// - width: Rel (named) +/// The width of the box. +/// - height: Rel (named) +/// The height of the box. +/// +/// # Tags +/// - layout #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] @@ -65,7 +75,20 @@ impl Inline for BoxNode {} /// A block-level container that places content into a separate flow. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional) +/// The contents of the block. +/// - spacing: Spacing (named, settable) +/// The spacing around this block. +/// - above: Spacing (named, settable) +/// The spacing between the previous and this block. Takes precedence over +/// `spacing`. +/// - below: Spacing (named, settable) +/// The spacing between this block and the following one. Takes precedence +/// over `spacing`. +/// +/// # Tags +/// - layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] @@ -74,10 +97,10 @@ pub struct BlockNode(pub Content); #[node] impl BlockNode { /// The spacing between the previous and this block. - #[property(reflect, skip)] + #[property(skip)] pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into()); /// The spacing between this and the following block. - #[property(reflect, skip)] + #[property(skip)] pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into()); /// Whether this block must stick to the following one. #[property(skip)] diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index e70210c0d..8aaffe084 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -4,7 +4,22 @@ use super::Spacing; /// Arrange content in a grid. /// -/// Tags: layout. +/// # Parameters +/// - cells: Content (positional, variadic) +/// The contents of the table cells. +/// - rows: TrackSizings (named) +/// Defines the row sizes. +/// - columns: TrackSizings (named) +/// Defines the column sizes. +/// - gutter: TrackSizings (named) +/// Defines the gaps between rows & columns. +/// - column-gutter: TrackSizings (named) +/// Defines the gaps between columns. Takes precedence over `gutter`. +/// - row-gutter: TrackSizings (named) +/// Defines the gaps between rows. Takes precedence over `gutter`. +/// +/// # Tags +/// - layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs index 4e70dca91..fc9150c15 100644 --- a/library/src/layout/hide.rs +++ b/library/src/layout/hide.rs @@ -2,7 +2,12 @@ use crate::prelude::*; /// Hide content without affecting layout. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The content to hide. +/// +/// # Tags +/// - layout #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index 9d882ba49..94492f834 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -2,7 +2,26 @@ use crate::prelude::*; /// Pad content at the sides. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The content to pad at the sides. +/// - left: Rel (named) +/// The padding at the left side. +/// - right: Rel (named) +/// The padding at the right side. +/// - top: Rel (named) +/// The padding at the top side. +/// - bottom: Rel (named) +/// The padding at the bottom side. +/// - x: Rel (named) +/// The horizontal padding. Both `left` and `right` take precedence over this. +/// - y: Rel (named) +/// The vertical padding. Both `top` and `bottom` take precedence over this. +/// - rest: Rel (named) +/// The padding for all sides. All other parameters take precedence over this. +/// +/// # Tags +/// - layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index fe83137e8..8782ed08d 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -6,7 +6,14 @@ use crate::text::TextNode; /// Layouts its child onto one or multiple pages. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The contents of the page(s). +/// - paper: Paper (positional, settable) +/// The paper size. +/// +/// # Tags +/// - layout #[func] #[capable] #[derive(Clone, Hash)] @@ -14,9 +21,6 @@ pub struct PageNode(pub Content); #[node] impl PageNode { - /// The paper size. - #[property(reflect, skip, shorthand)] - pub const PAPER: Paper = Paper::A4; /// The unflipped width of the page. #[property(resolve)] pub const WIDTH: Smart = Smart::Custom(Paper::A4.width().into()); @@ -91,7 +95,7 @@ impl PageNode { // Realize columns. let columns = styles.get(Self::COLUMNS); if columns.get() > 1 { - child = ColumnsNode { columns, body: self.0.clone() }.pack(); + child = ColumnsNode { count: columns, body: self.0.clone() }.pack(); } // Realize margins. @@ -151,7 +155,12 @@ impl Debug for PageNode { /// A page break. /// -/// Tags: layout. +/// # Parameters +/// - weak: bool (named) +/// If true, the page break is skipped if the current page is already empty. +/// +/// # Tags +/// - layout #[func] #[capable] #[derive(Debug, Copy, Clone, Hash)] @@ -266,7 +275,10 @@ macro_rules! papers { castable! { Paper, - $($pat => Self::$var,)* + $( + /// Produces a paper of the respective size. + $pat => Self::$var, + )* } }; } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index eed0dbb19..f9731ff2a 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -13,7 +13,12 @@ use crate::text::{ /// Arrange text, spacing and inline-level nodes into a paragraph. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The contents of the paragraph. +/// +/// # Tags +/// - layout #[func] #[capable] #[derive(Hash)] @@ -144,7 +149,8 @@ castable! { /// A paragraph break. /// -/// Tags: layout. +/// # Tags +/// - layout #[func] #[capable(Unlabellable)] #[derive(Debug, Hash)] diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index c3fcd0d5a..890480a71 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -2,7 +2,18 @@ use crate::prelude::*; /// Place content at an absolute position. /// -/// Tags: layout. +/// # Parameters +/// - alignment: Axes> (positional) +/// Relative to which position in the parent container to place the content. +/// - body: Content (positional, required) +/// The content to place. +/// - dx: Rel (named) +/// The horizontal displacement of the placed content. +/// - dy: Rel (named) +/// The vertical displacement of the placed content. +/// +/// # Tags +/// - layout #[func] #[capable(Layout, Behave)] #[derive(Debug, Hash)] diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index a47dbb3e4..fa87d922d 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -2,7 +2,12 @@ use crate::prelude::*; /// Repeats content to fill a line. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The content to repeat. +/// +/// # Tags +/// - layout #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index e961c0cf9..ef69c0709 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -2,9 +2,18 @@ use std::cmp::Ordering; use crate::prelude::*; -/// Horizontal spacing. +/// Horizontal spacing in a paragraph. /// -/// Tags: layout. +/// # Parameters +/// - amount: Spacing (positional, required) +/// How much spacing to insert. +/// - weak: bool (named) +/// If true, the spacing collapses at the start or end of a paragraph. +/// Moreover, from multiple adjacent weak spacings all but the largest one +/// collapse. +/// +/// # Tags +/// - layout #[func] #[capable(Behave)] #[derive(Debug, Copy, Clone, Hash)] @@ -55,7 +64,16 @@ impl Behave for HNode { /// Vertical spacing. /// -/// Tags: layout. +/// # Parameters +/// - amount: Spacing (positional, required) +/// How much spacing to insert. +/// - weak: bool (named) +/// If true, the spacing collapses at the start or end of a flow. +/// Moreover, from multiple adjacent weak spacings all but the largest one +/// collapse. +/// +/// # Tags +/// - layout #[func] #[capable(Behave)] #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)] @@ -123,11 +141,6 @@ impl Behave for VNode { } } -castable! { - VNode, - spacing: Spacing => VNode::block_around(spacing), -} - /// Kinds of spacing. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Spacing { diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 111e34331..deb565b61 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -5,7 +5,16 @@ use crate::prelude::*; /// Arrange content and spacing along an axis. /// -/// Tags: layout. +/// # Parameters +/// - items: StackChild (positional, variadic) +/// The items to stack along an axis. +/// - dir: Dir (named) +/// The direction along which the items are stacked. +/// - spacing: Spacing (named) +/// Spacing to insert between items where no explicit spacing was provided. +/// +/// # Tags +/// - layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index f1a89d4c0..3af44ca06 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -4,7 +4,16 @@ use crate::prelude::*; /// Move content without affecting layout. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The content to move. +/// - dx: Rel (named) +/// The horizontal displacement of the content. +/// - dy: Rel (named) +/// The vertical displacement of the content. +/// +/// # Tags +/// - layout #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] @@ -49,7 +58,18 @@ impl Inline for MoveNode {} /// Transform content without affecting layout. /// -/// Tags: layout. +/// # Parameters +/// - body: Content (positional, required) +/// The content to transform. +/// - angle: Angle (named) +/// The amount of rotation. +/// - x: Ratio (named) +/// The horizontal scaling factor. +/// - y: Ratio (named) +/// The vertical scaling factor. +/// +/// # Tags +/// - layout #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] diff --git a/library/src/lib.rs b/library/src/lib.rs index 2d03ef7c8..7e65ff791 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -50,7 +50,6 @@ fn scope() -> Scope { // Math. std.def_func::("math"); - std.def_func::("atom"); std.def_func::("acc"); std.def_func::("frac"); std.def_func::("binom"); @@ -203,7 +202,7 @@ fn items() -> LangItems { desc_item: |term, body| { basics::ListItem::Desc(Box::new(basics::DescItem { term, body })).pack() }, - math: |children, display| math::MathNode { children, display }.pack(), + math: |children, block| math::MathNode { children, block }.pack(), math_atom: |atom| math::AtomNode(atom).pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(), diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index 21294b71d..a32750ff1 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -2,7 +2,12 @@ use super::*; /// A column vector. /// -/// Tags: math. +/// # Parameters +/// - elements: Content (positional, variadic) +/// The elements of the vector. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -66,7 +71,12 @@ castable! { /// A case distinction. /// -/// Tags: math. +/// # Parameters +/// - branches: Content (positional, variadic) +/// The branches of the case distinction. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 41cdb9f55..3bd7b2e34 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -16,22 +16,35 @@ use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode}; /// A piece of a mathematical formula. /// -/// Tags: math. +/// # Parameters +/// - items: Content (positional, variadic) +/// The individual parts of the formula. +/// - block: bool (named) +/// Whether the formula is displayed as a separate block. +/// +/// # Tags +/// - math #[func] #[capable(Show, Layout, Inline, Texify)] #[derive(Debug, Clone, Hash)] pub struct MathNode { - /// Whether the formula is display-level. - pub display: bool, + /// Whether the formula is displayed as a separate block. + pub block: bool, /// The pieces of the formula. pub children: Vec, } #[node] impl MathNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let block = args.named("block")?.unwrap_or(false); + let children = args.all()?; + Ok(Self { block, children }.pack()) + } + fn field(&self, name: &str) -> Option { match name { - "display" => Some(Value::Bool(self.display)), + "block" => Some(Value::Bool(self.block)), _ => None, } } @@ -48,7 +61,7 @@ impl Show for MathNode { .guarded(Guard::Base(NodeId::of::())) .styled_with_map(map); - if self.display { + if self.block { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } @@ -65,7 +78,7 @@ impl Layout for MathNode { ) -> SourceResult { let mut t = Texifier::new(styles); self.texify(&mut t)?; - Ok(layout_tex(vt, &t.finish(), self.display, styles) + Ok(layout_tex(vt, &t.finish(), self.block, styles) .unwrap_or(Fragment::frame(Frame::new(Size::zero())))) } } @@ -247,7 +260,12 @@ impl Texify for Content { /// An atom in a math formula: `x`, `+`, `12`. /// -/// Tags: math. +/// # Parameters +/// - text: EcoString (positional, required) +/// The atom's text. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -288,7 +306,14 @@ impl Texify for AtomNode { /// An accented node. /// -/// Tags: math. +/// # Parameters +/// - base: Content (positional, required) +/// The base to which the accent is applied. +/// - accent: Content (positional, required) +/// The accent to apply to the base. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -365,7 +390,14 @@ impl Texify for AccNode { /// A fraction. /// -/// Tags: math. +/// # Parameters +/// - num: Content (positional, required) +/// The fraction's numerator. +/// - denom: Content (positional, required) +/// The fraction's denominator. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -398,7 +430,14 @@ impl Texify for FracNode { /// A binomial. /// -/// Tags: math. +/// # Parameters +/// - upper: Content (positional, required) +/// The binomial's upper index. +/// - lower: Content (positional, required) +/// The binomial's lower index. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -431,7 +470,16 @@ impl Texify for BinomNode { /// A sub- and/or superscript. /// -/// Tags: math. +/// # Parameters +/// - base: Content (positional, required) +/// The base to which the applies the sub- and/or superscript. +/// - sub: Content (named) +/// The subscript. +/// - sup: Content (named) +/// The superscript. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -445,7 +493,14 @@ pub struct ScriptNode { } #[node] -impl ScriptNode {} +impl ScriptNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let base = args.expect("base")?; + let sub = args.named("sub")?; + let sup = args.named("sup")?; + Ok(Self { base, sub, sup }.pack()) + } +} impl Texify for ScriptNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { @@ -469,14 +524,23 @@ impl Texify for ScriptNode { /// A math alignment point: `&`, `&&`. /// -/// Tags: math. +/// # Parameters +/// - index: usize (positional, required) +/// The alignment point's index. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] -pub struct AlignPointNode(pub usize); +pub struct AlignPointNode(pub NonZeroUsize); #[node] -impl AlignPointNode {} +impl AlignPointNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("index")?).pack()) + } +} impl Texify for AlignPointNode { fn texify(&self, _: &mut Texifier) -> SourceResult<()> { @@ -486,7 +550,12 @@ impl Texify for AlignPointNode { /// A square root. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The expression to take the square root of. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -510,7 +579,12 @@ impl Texify for SqrtNode { /// A floored expression. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The expression to floor. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -534,7 +608,12 @@ impl Texify for FloorNode { /// A ceiled expression. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The expression to ceil. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] diff --git a/library/src/math/style.rs b/library/src/math/style.rs index 444b1fb44..344aa0718 100644 --- a/library/src/math/style.rs +++ b/library/src/math/style.rs @@ -2,7 +2,12 @@ use super::*; /// Serif (roman) font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -26,7 +31,12 @@ impl Texify for SerifNode { /// Sans-serif font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -50,7 +60,12 @@ impl Texify for SansNode { /// Bold font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -74,7 +89,12 @@ impl Texify for BoldNode { /// Italic font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -98,7 +118,12 @@ impl Texify for ItalNode { /// Calligraphic font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -122,7 +147,12 @@ impl Texify for CalNode { /// Fraktur font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -146,7 +176,12 @@ impl Texify for FrakNode { /// Monospace font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] @@ -170,7 +205,12 @@ impl Texify for MonoNode { /// Blackboard bold (double-struck) font style. /// -/// Tags: math. +/// # Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// # Tags +/// - math #[func] #[capable(Texify)] #[derive(Debug, Hash)] diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 8c664df37..e20821c56 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -3,7 +3,8 @@ use crate::prelude::*; /// The root node that represents a full document. /// -/// Tags: meta. +/// # Tags +/// - meta #[func] #[capable(LayoutRoot)] #[derive(Hash)] diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 6f5d8af1f..27294a8d1 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -3,7 +3,14 @@ use crate::text::TextNode; /// Link text and other elements to a destination. /// -/// Tags: meta. +/// # Parameters +/// - dest: Destination (positional, required) +/// The destination the link points to. +/// - body: Content (positional) +/// How the link is represented. Defaults to the destination if it is a link. +/// +/// # Tags +/// - meta #[func] #[capable(Show, Finalize)] #[derive(Debug, Hash)] diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 27ca59440..6ea653914 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -5,7 +5,8 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode}; /// A section outline (table of contents). /// -/// Tags: meta. +/// # Tags +/// - meta #[func] #[capable(Prepare, Show)] #[derive(Debug, Hash)] diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 378d19d2d..c49ff9705 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -3,7 +3,12 @@ use crate::text::TextNode; /// A reference to a label. /// -/// Tags: meta. +/// # Parameters +/// - target: Label (positional, required) +/// The label that should be referenced. +/// +/// # Tags +/// - meta #[func] #[capable(Show)] #[derive(Debug, Hash)] diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 868667157..d725697e8 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -6,7 +6,12 @@ use crate::prelude::*; /// Typeset underline, stricken-through or overlined text. /// -/// Tags: text. +/// # Parameters +/// - body: Content (positional, required) +/// The content to decorate. +/// +/// # Tags +/// - text #[func] #[capable(Show)] #[derive(Debug, Hash)] diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index fc4f7d73d..20be156af 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -3,7 +3,8 @@ use crate::prelude::*; /// A text space. /// -/// Tags: text. +/// # Tags +/// - text #[func] #[capable(Unlabellable, Behave)] #[derive(Debug, Hash)] @@ -26,7 +27,12 @@ impl Behave for SpaceNode { /// A line break. /// -/// Tags: text. +/// # Parameters +/// - justify: bool (named) +/// Whether to justify the line before the break. +/// +/// # Tags +/// - text #[func] #[capable(Behave)] #[derive(Debug, Hash)] @@ -50,7 +56,12 @@ impl Behave for LinebreakNode { /// Strongly emphasizes content by increasing the font weight. /// -/// Tags: text. +/// # Parameters +/// - body: Content (positional, required) +/// The content to strongly emphasize. +/// +/// # Tags +/// - text #[func] #[capable(Show)] #[derive(Debug, Hash)] @@ -98,7 +109,12 @@ impl Fold for Delta { /// Emphasizes content by flipping the italicness. /// -/// Tags: text. +/// # Parameters +/// - body: Content (positional, required) +/// The content to emphasize. +/// +/// # Tags +/// - text #[func] #[capable(Show)] #[derive(Debug, Hash)] @@ -136,17 +152,27 @@ impl Fold for Toggle { } } -/// Convert a string or content to lowercase. +/// Convert text or content to lowercase. /// -/// Tags: text. +/// # Parameters +/// - text: ToCase (positional, required) +/// The text to convert to lowercase. +/// +/// # Tags +/// - text #[func] pub fn lower(args: &mut Args) -> SourceResult { case(Case::Lower, args) } -/// Convert a string or content to uppercase. +/// Convert text or content to uppercase. /// -/// Tags: text. +/// # Parameters +/// - text: ToCase (positional, required) +/// The text to convert to uppercase. +/// +/// # Tags +/// - text #[func] pub fn upper(args: &mut Args) -> SourceResult { case(Case::Upper, args) @@ -162,6 +188,15 @@ fn case(case: Case, args: &mut Args) -> SourceResult { }) } +/// A value whose case can be changed. +struct ToCase; + +castable! { + ToCase, + _: Str => Self, + _: Content => Self, +} + /// A case transformation on text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Case { @@ -183,7 +218,12 @@ impl Case { /// Display text in small capitals. /// -/// Tags: text. +/// # Parameters +/// - text: Content (positional, required) +/// The text to display to small capitals. +/// +/// # Tags +/// - text #[func] pub fn smallcaps(args: &mut Args) -> SourceResult { let body: Content = args.expect("content")?; diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 7340e5def..21dea8af0 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -25,9 +25,16 @@ use typst::util::EcoString; use crate::layout::ParNode; use crate::prelude::*; -/// A single run of text with the same style. +/// Stylable text. /// -/// Tags: text. +/// # Parameters +/// - family: EcoString (positional, variadic, settable) +/// A prioritized sequence of font families. +/// - body: Content (positional, required) +/// Content in which all text is styled according to the other arguments. +/// +/// # Tags +/// - text #[func] #[capable] #[derive(Clone, Hash)] diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index ab6f166c8..4f65c7fd0 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -4,7 +4,12 @@ use crate::prelude::*; /// A smart quote. /// -/// Tags: text. +/// # Parameters +/// - double: bool (named) +/// Whether to produce a smart double quote. +/// +/// # Tags +/// - text #[func] #[capable] #[derive(Debug, Hash)] diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 125a5da15..746265885 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -8,14 +8,21 @@ use crate::prelude::*; /// Raw text with optional syntax highlighting. /// -/// Tags: text. +/// # Parameters +/// - text: EcoString (positional, required) +/// The raw text. +/// - block: bool (named) +/// Whether the raw text is displayed as a separate block. +/// +/// # Tags +/// - text #[func] #[capable(Show)] #[derive(Debug, Hash)] pub struct RawNode { /// The raw text. pub text: EcoString, - /// Whether the node is block-level. + /// Whether the raw text is displayed as a separate block. pub block: bool, } diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index ad4a6cd96..969dce682 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -4,14 +4,19 @@ use typst::util::EcoString; use super::{variant, SpaceNode, TextNode, TextSize}; use crate::prelude::*; -/// Sub or superscript text. +/// Sub- or superscript text. /// -/// The text is rendered smaller and its baseline is raised. To provide the best -/// typography possible, we first try to transform the text to superscript -/// codepoints. If that fails, we fall back to rendering shrunk normal letters -/// in a raised way. +/// The text is rendered smaller and its baseline is raised/lowered. To provide +/// the best typography possible, we first try to transform the text to +/// superscript codepoints. If that fails, we fall back to rendering shrunk +/// normal letters in a raised way. /// -/// Tags: text. +/// # Parameters +/// - body: Content (positional, required) +/// The text to display in sub- or superscript. +/// +/// # Tags +/// - text #[func] #[capable(Show)] #[derive(Debug, Hash)] diff --git a/library/src/text/symbol.rs b/library/src/text/symbol.rs index eece81abf..ccf1a55e5 100644 --- a/library/src/text/symbol.rs +++ b/library/src/text/symbol.rs @@ -3,7 +3,12 @@ use crate::text::TextNode; /// A symbol identified by symmie notation. /// -/// Tags: text. +/// # Parameters +/// - notation: EcoString (positional, required) +/// The symbols symmie notation. +/// +/// # Tags +/// - text #[func] #[capable(Show)] #[derive(Debug, Hash)] diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 936ec3bfd..0ce3f20bd 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -6,7 +6,14 @@ use crate::prelude::*; /// Show a raster or vector graphic. /// -/// Tags: visualize. +/// Supported formats are PNG, JPEG, GIF and SVG. +/// +/// # Parameters +/// - path: EcoString (positional, required) +/// Path to an image file. +/// +/// # Tags +/// - visualize #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index 9c9b8b008..5c8c96444 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -2,7 +2,20 @@ use crate::prelude::*; /// Display a line without affecting the layout. /// -/// Tags: visualize. +/// You should only provide either an endpoint or an angle and a length. +/// +/// # Parameters +/// - origin: Axes> (named) +/// The start point of the line. +/// - to: Axes> (named) +/// The end point of the line. +/// - length: Rel (named) +/// The line's length. +/// - angle: Angle (named) +/// The angle at which the line points away from the origin. +/// +/// # Tags +/// - visualize #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index a34431899..be80b4fbc 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -4,7 +4,22 @@ use crate::prelude::*; /// A sizable and fillable shape with optional content. /// -/// Tags: visualize. +/// # Parameters +/// - body: Content (positional) +/// The content to place into the shape. +/// - width: Rel (named) +/// The shape's width. +/// - height: Rel (named) +/// The shape's height. +/// - size: Length (named) +/// The square's side length. +/// - radius: Length (named) +/// The circle's radius. +/// - stroke: Smart>> (named) +/// How to stroke the shape. +/// +/// # Tags +/// - visualize #[func] #[capable(Layout, Inline)] #[derive(Debug, Hash)] @@ -27,7 +42,7 @@ impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; /// How to stroke the shape. - #[property(reflect, skip, resolve, fold)] + #[property(skip, resolve, fold)] pub const STROKE: Smart>> = Smart::Auto; /// How much to pad the shape's content. @@ -38,7 +53,7 @@ impl ShapeNode { pub const OUTSET: Sides>> = Sides::splat(Rel::zero()); /// How much to round the shape's corners. - #[property(reflect, skip, resolve, fold)] + #[property(skip, resolve, fold)] pub const RADIUS: Corners>> = Corners::splat(Rel::zero()); fn construct(_: &Vm, args: &mut Args) -> SourceResult { diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 20ca02629..679a5bec8 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,3 +14,4 @@ bench = false proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full"] } +unscanny = "0.1" diff --git a/macros/src/castable.rs b/macros/src/castable.rs index 48cdf9e13..6738ee1d8 100644 --- a/macros/src/castable.rs +++ b/macros/src/castable.rs @@ -150,7 +150,7 @@ fn create_describe_func(castable: &Castable) -> TokenStream { let mut infos = vec![]; for cast in &castable.casts { - let docs = doc_comment(&cast.attrs); + let docs = documentation(&cast.attrs); infos.push(match &cast.pattern { Pattern::Str(lit) => { quote! { ::typst::model::CastInfo::Value(#lit.into(), #docs) } diff --git a/macros/src/func.rs b/macros/src/func.rs index 4523d48a2..98fca6a43 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -1,36 +1,26 @@ +use proc_macro2::Span; +use unscanny::Scanner; + use super::*; /// Expand the `#[func]` macro. pub fn func(item: syn::Item) -> Result { - let doc_comment = match &item { - syn::Item::Struct(item) => doc_comment(&item.attrs), - syn::Item::Enum(item) => doc_comment(&item.attrs), - syn::Item::Fn(item) => doc_comment(&item.attrs), + let mut docs = match &item { + syn::Item::Struct(item) => documentation(&item.attrs), + syn::Item::Enum(item) => documentation(&item.attrs), + syn::Item::Fn(item) => documentation(&item.attrs), _ => String::new(), }; - let mut tags = vec![]; - let mut kept = vec![]; - for line in doc_comment.lines() { - let line = line.trim(); - if let Some(suffix) = line.trim_end_matches(".").strip_prefix("Tags: ") { - tags.extend(suffix.split(", ")); - } else { - kept.push(line); - } - } - - while kept.last().map_or(false, |line| line.is_empty()) { - kept.pop(); - } - - let docs = kept.join("\n"); + let tags = tags(&mut docs); + let params = params(&mut docs)?; + let docs = docs.trim(); let info = quote! { ::typst::model::FuncInfo { name, docs: #docs, tags: &[#(#tags),*], - params: ::std::vec![], + params: ::std::vec![#(#params),*], } }; @@ -83,3 +73,93 @@ pub fn func(item: syn::Item) -> Result { }) } } + +/// Extract a section. +pub fn section(docs: &mut String, title: &str) -> Option { + let needle = format!("# {title}\n"); + let start = docs.find(&needle)?; + let rest = &docs[start..]; + let len = rest[1..].find('#').map(|x| 1 + x).unwrap_or(rest.len()); + let end = start + len; + let section = docs[start + needle.len()..].to_owned(); + docs.replace_range(start..end, ""); + Some(section) +} + +/// Parse the tag section. +pub fn tags(docs: &mut String) -> Vec { + section(docs, "Tags") + .unwrap_or_default() + .lines() + .filter_map(|line| line.strip_prefix('-')) + .map(|s| s.trim().into()) + .collect() +} + +/// Parse the parameter section. +pub fn params(docs: &mut String) -> Result> { + let Some(section) = section(docs, "Parameters") else { return Ok(vec![]) }; + let mut s = Scanner::new(§ion); + let mut infos = vec![]; + + while s.eat_if('-') { + s.eat_whitespace(); + let name = s.eat_until(':'); + s.expect(": "); + let ty: syn::Type = syn::parse_str(s.eat_until(char::is_whitespace))?; + s.eat_whitespace(); + let mut named = false; + let mut positional = false; + let mut required = false; + let mut variadic = false; + let mut settable = false; + s.expect('('); + for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) { + match part { + "named" => named = true, + "positional" => positional = true, + "required" => required = true, + "variadic" => variadic = true, + "settable" => settable = true, + _ => { + return Err(syn::Error::new( + Span::call_site(), + format!("unknown parameter flag {:?}", part), + )) + } + } + } + + if (!named && !positional) + || (variadic && !positional) + || (named && variadic) + || (required && variadic) + { + return Err(syn::Error::new( + Span::call_site(), + "invalid combination of parameter flags", + )); + } + + s.expect(')'); + let docs = dedent(s.eat_until("\n-").trim()); + infos.push(quote! { + ::typst::model::ParamInfo { + name: #name, + docs: #docs, + cast: <#ty as ::typst::model::Cast< + ::typst::syntax::Spanned<::typst::model::Value> + >>::describe(), + named: #named, + positional: #positional, + required: #required, + variadic: #variadic, + settable: #settable, + } + }); + + s.eat_whitespace(); + } + + Ok(infos) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 15dc3ee7f..a03dc30e4 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -64,7 +64,7 @@ pub fn castable(stream: BoundaryStream) -> BoundaryStream { } /// Extract documentation comments from an attribute list. -fn doc_comment(attrs: &[syn::Attribute]) -> String { +fn documentation(attrs: &[syn::Attribute]) -> String { let mut doc = String::new(); // Parse doc comments. @@ -72,7 +72,9 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String { if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { if meta.path.is_ident("doc") { if let syn::Lit::Str(string) = &meta.lit { - doc.push_str(&string.value()); + let full = string.value(); + let line = full.strip_prefix(' ').unwrap_or(&full); + doc.push_str(line); doc.push('\n'); } } @@ -81,3 +83,8 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String { doc.trim().into() } + +/// Dedent documentation text. +fn dedent(text: &str) -> String { + text.lines().map(str::trim).collect::>().join("\n") +} diff --git a/macros/src/node.rs b/macros/src/node.rs index ad079c0e5..1e03c4414 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -36,7 +36,6 @@ struct Property { shorthand: Option, resolve: bool, fold: bool, - reflect: bool, } /// The shorthand form of a style property. @@ -118,7 +117,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result { let mut referenced = false; let mut resolve = false; let mut fold = false; - let mut reflect = false; // Parse the `#[property(..)]` attribute. let mut stream = tokens.into_iter().peekable(); @@ -152,7 +150,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result { "referenced" => referenced = true, "resolve" => resolve = true, "fold" => fold = true, - "reflect" => reflect = true, _ => bail!(ident, "invalid attribute"), } } @@ -192,7 +189,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result { referenced, resolve, fold, - reflect, }) } @@ -275,9 +271,9 @@ fn create_node_construct_func(node: &Node) -> syn::ImplItemMethod { parse_quote! { fn construct( _: &::typst::model::Vm, - _: &mut ::typst::model::Args, + args: &mut ::typst::model::Args, ) -> ::typst::diag::SourceResult<::typst::model::Content> { - unimplemented!() + ::typst::diag::bail!(args.span, "cannot be constructed manually"); } } }) @@ -335,27 +331,26 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod { /// Create the node's `properties` function. fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod { - let infos = node - .properties - .iter() - .filter(|p| !p.skip || p.reflect) - .map(|property| { - let name = property.name.to_string().replace('_', "-").to_lowercase(); - let docs = doc_comment(&property.attrs); - let value_ty = &property.value_ty; - let shorthand = matches!(property.shorthand, Some(Shorthand::Positional)); - quote! { - ::typst::model::ParamInfo { - name: #name, - docs: #docs, - settable: true, - shorthand: #shorthand, - cast: <#value_ty as ::typst::model::Cast< - ::typst::syntax::Spanned<::typst::model::Value> - >>::describe(), - } + let infos = node.properties.iter().filter(|p| !p.skip).map(|property| { + let name = property.name.to_string().replace('_', "-").to_lowercase(); + let docs = documentation(&property.attrs); + let value_ty = &property.value_ty; + let shorthand = matches!(property.shorthand, Some(Shorthand::Positional)); + quote! { + ::typst::model::ParamInfo { + name: #name, + docs: #docs, + cast: <#value_ty as ::typst::model::Cast< + ::typst::syntax::Spanned<::typst::model::Value> + >>::describe(), + named: true, + positional: #shorthand, + required: false, + variadic: false, + settable: true, } - }); + } + }); parse_quote! { fn properties() -> ::std::vec::Vec<::typst::model::ParamInfo> diff --git a/src/ide/complete.rs b/src/ide/complete.rs index d4e72b3d1..153a9ccb1 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use if_chain::if_chain; use super::summarize_font_family; @@ -141,7 +143,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool { SyntaxKind::Colon | SyntaxKind::Space { .. } => ctx.cursor, _ => ctx.leaf.offset(), }; - ctx.param_value_completions(&callee, ¶m); + ctx.named_param_value_completions(&callee, ¶m); return true; } } @@ -360,6 +362,7 @@ struct CompletionContext<'a> { explicit: bool, from: usize, completions: Vec, + seen_casts: HashSet, } impl<'a> CompletionContext<'a> { @@ -382,6 +385,7 @@ impl<'a> CompletionContext<'a> { explicit, from: cursor, completions: vec![], + seen_casts: HashSet::new(), }) } @@ -440,14 +444,16 @@ impl<'a> CompletionContext<'a> { continue; } - self.completions.push(Completion { - kind: CompletionKind::Param, - label: param.name.into(), - apply: Some(format_eco!("{}: ${{}}", param.name)), - detail: Some(param.docs.into()), - }); + if param.named { + self.completions.push(Completion { + kind: CompletionKind::Param, + label: param.name.into(), + apply: Some(format_eco!("{}: ${{}}", param.name)), + detail: Some(param.docs.into()), + }); + } - if param.shorthand { + if param.positional { self.cast_completions(¶m.cast); } } @@ -458,11 +464,12 @@ impl<'a> CompletionContext<'a> { } /// Add completions for the values of a function parameter. - fn param_value_completions(&mut self, callee: &ast::Ident, name: &str) { + fn named_param_value_completions(&mut self, callee: &ast::Ident, name: &str) { let param = if_chain! { if let Some(Value::Func(func)) = self.scope.get(callee); if let Some(info) = func.info(); if let Some(param) = info.param(name); + if param.named; then { param } else { return; } }; @@ -475,7 +482,12 @@ impl<'a> CompletionContext<'a> { } /// Add completions for a castable. - fn cast_completions(&mut self, cast: &CastInfo) { + fn cast_completions(&mut self, cast: &'a CastInfo) { + // Prevent duplicate completions from appearing. + if !self.seen_casts.insert(crate::util::hash128(cast)) { + return; + } + match cast { CastInfo::Any => {} CastInfo::Value(value, docs) => { @@ -485,7 +497,7 @@ impl<'a> CompletionContext<'a> { self.snippet_completion("none", "none", "Nonexistent.") } CastInfo::Type("auto") => { - self.snippet_completion("auto", "auto", "A smart default"); + self.snippet_completion("auto", "auto", "A smart default."); } CastInfo::Type("boolean") => { self.snippet_completion("false", "false", "Yes / Enabled."); diff --git a/src/model/cast.rs b/src/model/cast.rs index 833b9e9ea..5c95fe4b3 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -33,7 +33,7 @@ pub trait Cast: Sized { } /// Describes a possible value for a cast. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub enum CastInfo { /// Any value is okay. Any, diff --git a/src/model/eval.rs b/src/model/eval.rs index d54acf0ad..a9fa2e145 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -411,7 +411,7 @@ impl Eval for ast::Math { self.children() .map(|node| node.eval(vm)) .collect::>()?, - self.display(), + self.block(), )) } } diff --git a/src/model/func.rs b/src/model/func.rs index 5b38b7008..46befd777 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -232,12 +232,21 @@ pub struct ParamInfo { pub name: &'static str, /// Documentation for the parameter. pub docs: &'static str, - /// Is the parameter settable with a set rule? - pub settable: bool, - /// Can the name be omitted? - pub shorthand: bool, /// Valid values for the parameter. pub cast: CastInfo, + /// Is the parameter positional? + pub positional: bool, + /// Is the parameter named? + /// + /// Can be true even if `positional` is true if the parameter can be given + /// in both variants. + pub named: bool, + /// Is the parameter required? + pub required: bool, + /// Can the parameter be given any number of times? + pub variadic: bool, + /// Is the parameter settable with a set rule? + pub settable: bool, } /// A user-defined closure. diff --git a/src/model/library.rs b/src/model/library.rs index 02eb9179c..83310610d 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -66,7 +66,7 @@ pub struct LangItems { /// An item in a description list: `/ Term: Details`. pub desc_item: fn(term: Content, body: Content) -> Content, /// A mathematical formula: `$x$`, `$ x^2 $`. - pub math: fn(children: Vec, display: bool) -> Content, + pub math: fn(children: Vec, block: bool) -> Content, /// An atom in a formula: `x`, `+`, `12`. pub math_atom: fn(atom: EcoString) -> Content, /// A base with optional sub- and superscripts in a formula: `a_1^2`. @@ -75,7 +75,7 @@ pub struct LangItems { /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, /// An alignment point in a formula: `&`, `&&`. - pub math_align_point: fn(count: usize) -> Content, + pub math_align_point: fn(count: NonZeroUsize) -> Content, } impl Debug for LangItems { diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 56d4415e0..abbca5ec1 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -295,7 +295,7 @@ impl Raw { self.get().lang.as_ref() } - /// Whether the raw block is block-level. + /// Whether the raw text should be displayed in a separate block. pub fn block(&self) -> bool { self.get().block } @@ -425,8 +425,8 @@ impl Math { self.0.children().filter_map(SyntaxNode::cast) } - /// Whether this is a display-level math formula. - pub fn display(&self) -> bool { + /// Whether the formula should be displayed as a separate block. + pub fn block(&self) -> bool { matches!(self.children().next(), Some(MathNode::Space(_))) && matches!(self.children().last(), Some(MathNode::Space(_))) } @@ -564,8 +564,13 @@ node! { impl AlignPoint { /// The number of ampersands. - pub fn count(&self) -> usize { - self.0.children().filter(|n| n.kind() == &SyntaxKind::Amp).count() + pub fn count(&self) -> NonZeroUsize { + self.0 + .children() + .filter(|n| n.kind() == &SyntaxKind::Amp) + .count() + .try_into() + .expect("alignment point is missing ampersand sign") } } diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ index 4ccefa229..bc6ef7f64 100644 --- a/tests/typ/compute/calc.typ +++ b/tests/typ/compute/calc.typ @@ -13,19 +13,19 @@ #test(type(float(10)), "float") --- -// Error: 6-10 cannot convert length to integer +// Error: 6-10 expected boolean, integer, float, or string, found length #int(10pt) --- -// Error: 8-13 cannot convert function to float +// Error: 8-13 expected boolean, integer, float, or string, found function #float(float) --- -// Error: 6-12 invalid integer +// Error: 6-12 not a valid integer #int("nope") --- -// Error: 8-15 invalid float +// Error: 8-15 not a valid float #float("1.2.3") --- @@ -39,17 +39,9 @@ #test(abs(-25%), 25%) --- -// Error: 6-17 expected numeric value, found string +// Error: 6-17 expected integer, float, angle, ratio, or fraction, found string #abs("no number") ---- -// Error: 6-11 cannot take absolute value of a length -#abs(-12pt) - ---- -// Error: 6-16 cannot take absolute value of a length -#abs(50% - 12pt) - --- // Test the `even` and `odd` functions. #test(even(2), true) diff --git a/tests/typ/compute/create.typ b/tests/typ/compute/create.typ index 462f06e94..ccb7bd2e7 100644 --- a/tests/typ/compute/create.typ +++ b/tests/typ/compute/create.typ @@ -48,7 +48,7 @@ #test(str(10 / 3).len() > 10, true) --- -// Error: 6-8 cannot convert content to string +// Error: 6-8 expected integer, float, label, or string, found content #str([]) ---