diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-12-30 19:40:29 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-12-30 20:00:50 +0100 |
| commit | a6d90c1bf1e9fefa0af04206909a40e112d6bb14 (patch) | |
| tree | fc16276142f74b9a50102a2e855942f7e2593c25 /library | |
| parent | f70cea508cd30fa40770ea989fe2a19e715a357b (diff) | |
Numbering functions
Diffstat (limited to 'library')
| -rw-r--r-- | library/src/basics/enum.rs | 51 | ||||
| -rw-r--r-- | library/src/basics/heading.rs | 32 | ||||
| -rw-r--r-- | library/src/basics/table.rs | 23 | ||||
| -rw-r--r-- | library/src/compute/data.rs | 15 | ||||
| -rw-r--r-- | library/src/compute/utility.rs | 101 | ||||
| -rw-r--r-- | library/src/layout/mod.rs | 3 | ||||
| -rw-r--r-- | library/src/layout/page.rs | 70 | ||||
| -rw-r--r-- | library/src/meta/outline.rs | 38 | ||||
| -rw-r--r-- | library/src/text/raw.rs | 9 |
9 files changed, 168 insertions, 174 deletions
diff --git a/library/src/basics/enum.rs b/library/src/basics/enum.rs index f1784a7e..c5d4d8ae 100644 --- a/library/src/basics/enum.rs +++ b/library/src/basics/enum.rs @@ -1,9 +1,8 @@ use std::str::FromStr; -use crate::compute::NumberingPattern; +use crate::compute::{Numbering, NumberingPattern}; use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; use crate::prelude::*; -use crate::text::TextNode; /// # Numbered List /// A numbered list. @@ -98,7 +97,7 @@ pub struct EnumNode { #[node] impl EnumNode { /// How to number the enumeration. Accepts a - /// [numbering pattern](@numbering). + /// [numbering pattern or function](@numbering). /// /// # Example /// ``` @@ -109,8 +108,8 @@ impl EnumNode { /// + Style /// ``` #[property(referenced)] - pub const NUMBERING: EnumNumbering = - EnumNumbering::Pattern(NumberingPattern::from_str("1.").unwrap()); + pub const NUMBERING: Numbering = + Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()); /// The indentation of each item's label. #[property(resolve)] @@ -188,7 +187,7 @@ impl Layout for EnumNode { let mut number = NonZeroUsize::new(1).unwrap(); for ((n, item), map) in self.items.iter() { number = n.unwrap_or(number); - let resolved = numbering.resolve(vt, number)?; + let resolved = numbering.apply(vt.world(), &[number])?.display(); cells.push(Content::empty()); cells.push(resolved.styled_with_map(map.clone())); cells.push(Content::empty()); @@ -209,43 +208,3 @@ impl Layout for EnumNode { .layout(vt, styles, regions) } } - -/// How to number an enumeration. -#[derive(Debug, Clone, Hash)] -pub enum EnumNumbering { - /// A pattern with prefix, numbering, lower / upper case and suffix. - Pattern(NumberingPattern), - /// A closure mapping from an item's number to content. - Func(Func, Span), -} - -impl EnumNumbering { - /// Resolve the marker based on the number. - pub fn resolve(&self, vt: &Vt, number: NonZeroUsize) -> SourceResult<Content> { - Ok(match self { - Self::Pattern(pattern) => TextNode::packed(pattern.apply(&[number])), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(number.get() as i64)]); - func.call_detached(vt.world(), args)?.display() - } - }) - } -} - -impl Cast<Spanned<Value>> for EnumNumbering { - fn is(value: &Spanned<Value>) -> bool { - matches!(&value.v, Value::Content(_) | Value::Func(_)) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::Str(v) => Ok(Self::Pattern(v.parse()?)), - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => Self::error(v), - } - } - - fn describe() -> CastInfo { - CastInfo::Union(vec![CastInfo::Type("string"), CastInfo::Type("function")]) - } -} diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index 925d23a2..c2696dd6 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -1,6 +1,6 @@ use typst::font::FontWeight; -use crate::compute::NumberingPattern; +use crate::compute::Numbering; use crate::layout::{BlockNode, VNode}; use crate::prelude::*; use crate::text::{SpaceNode, TextNode, TextSize}; @@ -15,8 +15,8 @@ use crate::text::{SpaceNode, TextNode, TextSize}; /// (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). +/// specify how you want your headings to be numbered with a +/// [numbering pattern or function](@numbering). /// /// Independently from the numbering, Typst can also automatically generate an /// [outline](@outline) of all headings for you. To exclude one or more headings @@ -60,7 +60,8 @@ pub struct HeadingNode { #[node] impl HeadingNode { - /// How to number the heading. Accepts a [numbering pattern](@numbering). + /// How to number the heading. Accepts a + /// [numbering pattern or function](@numbering). /// /// # Example /// ``` @@ -71,7 +72,7 @@ impl HeadingNode { /// === A sub-subsection /// ``` #[property(referenced)] - pub const NUMBERING: Option<NumberingPattern> = None; + pub const NUMBERING: Option<Numbering> = None; /// Whether the heading should appear in the outline. /// @@ -106,7 +107,12 @@ impl HeadingNode { } impl Prepare for HeadingNode { - fn prepare(&self, vt: &mut Vt, mut this: Content, styles: StyleChain) -> Content { + fn prepare( + &self, + vt: &mut Vt, + mut this: Content, + styles: StyleChain, + ) -> SourceResult<Content> { let my_id = vt.identify(&this); let mut counter = HeadingCounter::new(); @@ -115,30 +121,32 @@ impl Prepare for HeadingNode { break; } - if matches!(node.field("numbers"), Some(Value::Str(_))) { + let numbers = node.field("numbers").unwrap(); + if numbers != Value::None { let heading = node.to::<Self>().unwrap(); counter.advance(heading); } } let mut numbers = Value::None; - if let Some(pattern) = styles.get(Self::NUMBERING) { - numbers = Value::Str(pattern.apply(counter.advance(self)).into()); + if let Some(numbering) = styles.get(Self::NUMBERING) { + numbers = numbering.apply(vt.world(), counter.advance(self))?; } this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); this.push_field("numbers", numbers); let meta = Meta::Node(my_id, this.clone()); - this.styled(Meta::DATA, vec![meta]) + Ok(this.styled(Meta::DATA, vec![meta])) } } impl Show for HeadingNode { fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> { let mut realized = self.title.clone(); - if let Some(Value::Str(numbering)) = this.field("numbers") { - realized = TextNode::packed(numbering) + SpaceNode.pack() + realized; + let numbers = this.field("numbers").unwrap(); + if numbers != Value::None { + realized = numbers.display() + SpaceNode.pack() + realized; } Ok(BlockNode(realized).pack()) } diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index 01b16898..20881830 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -199,7 +199,7 @@ pub enum Celled<T> { /// A bare value, the same for all cells. Value(T), /// A closure mapping from cell coordinates to a value. - Func(Func, Span), + Func(Func), } impl<T: Cast + Clone> Celled<T> { @@ -207,24 +207,25 @@ impl<T: Cast + Clone> Celled<T> { pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult<T> { Ok(match self { Self::Value(value) => value.clone(), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(vt.world(), args)?.cast().at(*span)? + Self::Func(func) => { + let args = + Args::new(func.span(), [Value::Int(x as i64), Value::Int(y as i64)]); + func.call_detached(vt.world(), args)?.cast().at(func.span())? } }) } } -impl<T: Cast> Cast<Spanned<Value>> for Celled<T> { - fn is(value: &Spanned<Value>) -> bool { - matches!(&value.v, Value::Func(_)) || T::is(&value.v) +impl<T: Cast> Cast for Celled<T> { + fn is(value: &Value) -> bool { + matches!(value, Value::Func(_)) || T::is(value) } - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::Func(v) => Ok(Self::Func(v, value.span)), + fn cast(value: Value) -> StrResult<Self> { + match value { + Value::Func(v) => Ok(Self::Func(v)), v if T::is(&v) => Ok(Self::Value(T::cast(v)?)), - v => Self::error(v), + v => <Self as Cast>::error(v), } } diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index bcf64360..e3013389 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -218,10 +218,7 @@ fn convert_json(value: serde_json::Value) -> Value { /// Format the user-facing JSON error message. fn format_json_error(error: serde_json::Error) -> String { assert!(error.is_syntax() || error.is_eof()); - format!( - "failed to parse json file: syntax error in line {}", - error.line() - ) + format!("failed to parse json file: syntax error in line {}", error.line()) } /// # XML @@ -252,22 +249,22 @@ fn format_json_error(error: serde_json::Error) -> String { /// let author = findChild(elem, "author") /// let pars = findChild(elem, "content") /// -/// heading((title.children)(0)) +/// heading(title.children.first()) /// text(10pt, weight: "medium")[ /// Published by -/// {(author.children)(0)} +/// {author.children.first()} /// ] /// /// for p in pars.children { /// if (type(p) == "dictionary") { /// parbreak() -/// (p.children)(0) +/// p.children.first() /// } /// } /// } /// -/// #let file = xml("example.xml") -/// #for child in file(0).children { +/// #let data = xml("example.xml") +/// #for child in data.first().children { /// if (type(child) == "dictionary") { /// article(child) /// } diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs index 78cf3953..414a62f5 100644 --- a/library/src/compute/utility.rs +++ b/library/src/compute/utility.rs @@ -35,53 +35,105 @@ pub fn lorem(args: &mut Args) -> SourceResult<Value> { } /// # Numbering -/// Apply a numbering pattern to a sequence of numbers. +/// Apply a numbering to a sequence of numbers. /// -/// Numbering patterns are strings that define how a sequence of numbers should -/// be rendered as text. The patterns consist of [counting -/// symbols](#parameters--pattern) for which the actual number is substituted, -/// their prefixes, and one suffix. The prefixes and the suffix are repeated as-is. +/// A numbering defines how a sequence of numbers should be displayed as +/// content. It is defined either through a pattern string or an arbitrary +/// function. +/// +/// A numbering pattern consists of [counting symbols](#parameters--numbering) +/// for which the actual number is substituted, their prefixes, and one suffix. +/// The prefixes and the suffix are repeated as-is. /// /// ## Example /// ``` /// #numbering("1.1)", 1, 2, 3) \ /// #numbering("1.a.i", 1, 2) \ -/// #numbering("I – 1", 12, 2) +/// #numbering("I – 1", 12, 2) \ +/// #numbering( +/// (..nums) => nums +/// .pos() +/// .map(str) +/// .join(".") + ")", +/// 1, 2, 3, +/// ) /// ``` /// /// ## Parameters -/// - pattern: NumberingPattern (positional, required) -/// A string that defines how the numbering works. +/// - numbering: Numbering (positional, required) +/// Defines how the numbering works. /// -/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are replaced -/// by the number in the sequence, in the given case. +/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are +/// replaced by the number in the sequence, in the given case. /// -/// The `*` character means that symbols should be used to count, in the order -/// of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six items, the -/// number is represented using multiple symbols. +/// The `*` character means that symbols should be used to count, in the +/// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six +/// items, the number is represented using multiple symbols. /// /// **Suffixes** are all characters after the last counting symbol. They are /// repeated as-is at the end of any rendered number. /// /// **Prefixes** are all characters that are neither counting symbols nor -/// suffixes. They are repeated as-is at in front of their rendered equivalent -/// of their counting symbol. +/// suffixes. They are repeated as-is at in front of their rendered +/// equivalent of their counting symbol. +/// +/// This parameter can also be an arbitrary function that gets each number as +/// an individual argument. When given a function, the `numbering` function +/// just forwards the arguments to that function. While this is not +/// particularly useful in itself, it means that you can just give arbitrary +/// numberings to the `numbering` function without caring whether they are +/// defined as a pattern or function. /// /// - numbers: NonZeroUsize (positional, variadic) -/// The numbers to apply the pattern to. Must be positive. +/// The numbers to apply the numbering to. Must be positive. /// -/// If more numbers than counting symbols are given, the last counting symbol -/// with its prefix is repeated. +/// If `numbering` is a pattern and more numbers than counting symbols are +/// given, the last counting symbol with its prefix is repeated. /// -/// - returns: string +/// - returns: any /// /// ## Category /// utility #[func] -pub fn numbering(args: &mut Args) -> SourceResult<Value> { - let pattern = args.expect::<NumberingPattern>("pattern")?; +pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> { + let numbering = args.expect::<Numbering>("pattern or function")?; let numbers = args.all::<NonZeroUsize>()?; - Ok(Value::Str(pattern.apply(&numbers).into())) + numbering.apply(vm.world(), &numbers) +} + +/// How to number an enumeration. +#[derive(Debug, Clone, Hash)] +pub enum Numbering { + /// A pattern with prefix, numbering, lower / upper case and suffix. + Pattern(NumberingPattern), + /// A closure mapping from an item's number to content. + Func(Func), +} + +impl Numbering { + /// Apply the pattern to the given numbers. + pub fn apply( + &self, + world: Tracked<dyn World>, + numbers: &[NonZeroUsize], + ) -> SourceResult<Value> { + Ok(match self { + Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), + Self::Func(func) => { + let args = Args::new( + func.span(), + numbers.iter().map(|n| Value::Int(n.get() as i64)), + ); + func.call_detached(world, args)? + } + }) + } +} + +castable! { + Numbering, + v: Str => Self::Pattern(v.parse()?), + v: Func => Self::Func(v), } /// How to turn a number into text. @@ -157,11 +209,6 @@ impl FromStr for NumberingPattern { } } -castable! { - NumberingPattern, - string: EcoString => string.parse()?, -} - /// Different kinds of numberings. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] enum NumberingKind { diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 91822be8..330db716 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -317,7 +317,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { // Prepare only if this is the first application for this node. if let Some(node) = content.with::<dyn Prepare>() { if !content.is_prepared() { - let prepared = node.prepare(self.vt, content.clone().prepared(), styles); + let prepared = + node.prepare(self.vt, content.clone().prepared(), styles)?; let stored = self.scratch.content.alloc(prepared); return self.accept(stored, styles); } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index f4f9620c..4f5231de 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use super::ColumnsNode; use crate::prelude::*; -use crate::text::TextNode; /// # Page /// Layouts its child onto one or multiple pages. @@ -174,7 +173,7 @@ impl PageNode { /// #lorem(18) /// ``` #[property(referenced)] - pub const HEADER: Marginal = Marginal::None; + pub const HEADER: Option<Marginal> = None; /// The page's footer. /// @@ -199,7 +198,7 @@ impl PageNode { /// #lorem(18) /// ``` #[property(referenced)] - pub const FOOTER: Marginal = Marginal::None; + pub const FOOTER: Option<Marginal> = None; /// Content in the page's background. /// @@ -221,7 +220,7 @@ impl PageNode { /// (of typesetting). /// ``` #[property(referenced)] - pub const BACKGROUND: Marginal = Marginal::None; + pub const BACKGROUND: Option<Marginal> = None; /// Content in the page's foreground. /// @@ -239,7 +238,7 @@ impl PageNode { /// not understand our approach... /// ``` #[property(referenced)] - pub const FOREGROUND: Marginal = Marginal::None; + pub const FOREGROUND: Option<Marginal> = None; fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self(args.expect("body")?).pack()) @@ -323,14 +322,15 @@ impl PageNode { (foreground, Point::zero(), size), (background, Point::zero(), size), ] { - if let Some(content) = marginal.resolve(vt, page)? { - let pod = Regions::one(area, area, Axes::splat(true)); - let sub = content.layout(vt, styles, pod)?.into_frame(); - if std::ptr::eq(marginal, background) { - frame.prepend_frame(pos, sub); - } else { - frame.push_frame(pos, sub); - } + let in_background = std::ptr::eq(marginal, background); + let Some(marginal) = marginal else { continue }; + let content = marginal.resolve(vt, page)?; + let pod = Regions::one(area, area, Axes::splat(true)); + let sub = content.layout(vt, styles, pod)?.into_frame(); + if in_background { + frame.prepend_frame(pos, sub); + } else { + frame.push_frame(pos, sub); } } @@ -396,53 +396,29 @@ impl PagebreakNode { /// A header, footer, foreground or background definition. #[derive(Debug, Clone, Hash)] pub enum Marginal { - /// Nothing, - None, /// Bare content. Content(Content), /// A closure mapping from a page number to content. - Func(Func, Span), + Func(Func), } impl Marginal { /// Resolve the marginal based on the page number. - pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Option<Content>> { + pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Content> { Ok(match self { - Self::None => None, - Self::Content(content) => Some(content.clone()), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(page as i64)]); - Some(func.call_detached(vt.world(), args)?.display()) + Self::Content(content) => content.clone(), + Self::Func(func) => { + let args = Args::new(func.span(), [Value::Int(page as i64)]); + func.call_detached(vt.world(), args)?.display() } }) } } -impl Cast<Spanned<Value>> for Marginal { - fn is(value: &Spanned<Value>) -> bool { - matches!( - &value.v, - Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_) - ) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::None => Ok(Self::None), - Value::Str(v) => Ok(Self::Content(TextNode::packed(v))), - Value::Content(v) => Ok(Self::Content(v)), - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => Self::error(v), - } - } - - fn describe() -> CastInfo { - CastInfo::Union(vec![ - CastInfo::Type("none"), - CastInfo::Type("content"), - CastInfo::Type("function"), - ]) - } +castable! { + Marginal, + v: Content => Self::Content(v), + v: Func => Self::Func(v), } /// Specification of a paper. diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index c3d17196..4b70666b 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -84,7 +84,12 @@ impl OutlineNode { } impl Prepare for OutlineNode { - fn prepare(&self, vt: &mut Vt, mut this: Content, _: StyleChain) -> Content { + fn prepare( + &self, + vt: &mut Vt, + mut this: Content, + _: StyleChain, + ) -> SourceResult<Content> { let headings = vt .locate(Selector::node::<HeadingNode>()) .into_iter() @@ -94,7 +99,7 @@ impl Prepare for OutlineNode { .collect(); this.push_field("headings", Value::Array(Array::from_vec(headings))); - this + Ok(this) } } @@ -151,32 +156,27 @@ impl Show for OutlineNode { // Add hidden ancestors numberings to realize the indent. if indent { - let text = ancestors + let hidden: Vec<_> = ancestors .iter() - .filter_map(|node| match node.field("numbers").unwrap() { - Value::Str(numbering) => { - Some(EcoString::from(numbering) + ' '.into()) - } - _ => None, - }) - .collect::<EcoString>(); - - if !text.is_empty() { - seq.push(HideNode(TextNode::packed(text)).pack()); + .map(|node| node.field("numbers").unwrap()) + .filter(|numbers| *numbers != Value::None) + .map(|numbers| numbers.display() + SpaceNode.pack()) + .collect(); + + if !hidden.is_empty() { + seq.push(HideNode(Content::sequence(hidden)).pack()); seq.push(SpaceNode.pack()); } } // Format the numbering. - let numbering = match node.field("numbers").unwrap() { - Value::Str(numbering) => { - TextNode::packed(EcoString::from(numbering) + ' '.into()) - } - _ => Content::empty(), + let mut start = heading.title.clone(); + let numbers = node.field("numbers").unwrap(); + if numbers != Value::None { + start = numbers.display() + SpaceNode.pack() + start; }; // Add the numbering and section name. - let start = numbering + heading.title.clone(); seq.push(start.linked(Destination::Internal(loc))); // Add filler symbols between the section name and page number. diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index c6026f89..2f0ec79e 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -132,7 +132,12 @@ impl RawNode { } impl Prepare for RawNode { - fn prepare(&self, _: &mut Vt, mut this: Content, styles: StyleChain) -> Content { + fn prepare( + &self, + _: &mut Vt, + mut this: Content, + styles: StyleChain, + ) -> SourceResult<Content> { this.push_field( "lang", match styles.get(Self::LANG) { @@ -140,7 +145,7 @@ impl Prepare for RawNode { None => Value::None, }, ); - this + Ok(this) } } |
