From 67b45403975ba89150d17356dfbcad3e2ed18391 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 16 Feb 2023 14:43:38 +0100 Subject: Dissolve utility category --- library/src/compute/mod.rs | 2 - library/src/compute/utility.rs | 313 ----------------------------------------- library/src/layout/enum.rs | 2 +- library/src/lib.rs | 4 +- library/src/meta/heading.rs | 2 +- library/src/meta/mod.rs | 2 + library/src/meta/numbering.rs | 282 +++++++++++++++++++++++++++++++++++++ library/src/text/misc.rs | 31 ++++ 8 files changed, 319 insertions(+), 319 deletions(-) delete mode 100644 library/src/compute/utility.rs create mode 100644 library/src/meta/numbering.rs (limited to 'library/src') diff --git a/library/src/compute/mod.rs b/library/src/compute/mod.rs index 5cfbe158..cf0486db 100644 --- a/library/src/compute/mod.rs +++ b/library/src/compute/mod.rs @@ -4,10 +4,8 @@ mod calc; mod construct; mod data; mod foundations; -mod utility; pub use self::calc::*; pub use self::construct::*; pub use self::data::*; pub use self::foundations::*; -pub use self::utility::*; diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs deleted file mode 100644 index 2220c890..00000000 --- a/library/src/compute/utility.rs +++ /dev/null @@ -1,313 +0,0 @@ -use std::str::FromStr; - -use crate::prelude::*; -use crate::text::Case; - -/// # Blind Text -/// Create blind text. -/// -/// This function yields a Latin-like _Lorem Ipsum_ blind text with the given -/// number of words. The sequence of words generated by the function is always -/// the same but randomly chosen. As usual for blind texts, it does not make any -/// sense. Use it as a placeholder to try layouts. -/// -/// ## Example -/// ```example -/// = Blind Text -/// #lorem(30) -/// -/// = More Blind Text -/// #lorem(15) -/// ``` -/// -/// ## Parameters -/// - words: `usize` (positional, required) -/// The length of the blind text in words. -/// -/// - returns: string -/// -/// ## Category -/// utility -#[func] -pub fn lorem(args: &mut Args) -> SourceResult { - let words: usize = args.expect("number of words")?; - Ok(Value::Str(lipsum::lipsum(words).into())) -} - -/// # Numbering -/// Apply a numbering to a sequence of numbers. -/// -/// 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, for which the actual -/// number is substituted, their prefixes, and one suffix. The prefixes and the -/// suffix are repeated as-is. -/// -/// ## Example -/// ```example -/// #numbering("1.1)", 1, 2, 3) \ -/// #numbering("1.a.i", 1, 2) \ -/// #numbering("I – 1", 12, 2) \ -/// #numbering( -/// (..nums) => nums -/// .pos() -/// .map(str) -/// .join(".") + ")", -/// 1, 2, 3, -/// ) -/// ``` -/// -/// ## Parameters -/// - 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. -/// -/// 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. -/// -/// 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 numbering to. Must be positive. -/// -/// If `numbering` is a pattern and more numbers than counting symbols are -/// given, the last counting symbol with its prefix is repeated. -/// -/// - returns: any -/// -/// ## Category -/// utility -#[func] -pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult { - let numbering = args.expect::("pattern or function")?; - let numbers = args.all::()?; - 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, - numbers: &[NonZeroUsize], - ) -> SourceResult { - 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. -/// -/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, `I` -/// or `*`, and then a suffix. -/// -/// Examples of valid patterns: -/// - `1)` -/// - `a.` -/// - `(I)` -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct NumberingPattern { - pieces: Vec<(EcoString, NumberingKind, Case)>, - suffix: EcoString, -} - -impl NumberingPattern { - /// Apply the pattern to the given number. - pub fn apply(&self, numbers: &[NonZeroUsize]) -> EcoString { - let mut fmt = EcoString::new(); - let mut numbers = numbers.into_iter(); - - for ((prefix, kind, case), &n) in self.pieces.iter().zip(&mut numbers) { - fmt.push_str(prefix); - fmt.push_str(&kind.apply(n, *case)); - } - - for ((prefix, kind, case), &n) in - self.pieces.last().into_iter().cycle().zip(numbers) - { - if prefix.is_empty() { - fmt.push_str(&self.suffix); - } else { - fmt.push_str(prefix); - } - fmt.push_str(&kind.apply(n, *case)); - } - - fmt.push_str(&self.suffix); - fmt - } - - /// Apply only the k-th segment of the pattern to a number. - pub fn apply_kth(&self, k: usize, number: NonZeroUsize) -> EcoString { - let mut fmt = EcoString::new(); - if let Some((prefix, _, _)) = self.pieces.first() { - fmt.push_str(prefix); - } - if let Some((_, kind, case)) = self - .pieces - .iter() - .chain(self.pieces.last().into_iter().cycle()) - .nth(k) - { - fmt.push_str(&kind.apply(number, *case)); - } - fmt.push_str(&self.suffix); - fmt - } -} - -impl FromStr for NumberingPattern { - type Err = &'static str; - - fn from_str(pattern: &str) -> Result { - let mut pieces = vec![]; - let mut handled = 0; - - for (i, c) in pattern.char_indices() { - let kind = match c.to_ascii_lowercase() { - '1' => NumberingKind::Arabic, - 'a' => NumberingKind::Letter, - 'i' => NumberingKind::Roman, - '*' => NumberingKind::Symbol, - _ => continue, - }; - - let prefix = pattern[handled..i].into(); - let case = if c.is_uppercase() { Case::Upper } else { Case::Lower }; - pieces.push((prefix, kind, case)); - handled = i + 1; - } - - let suffix = pattern[handled..].into(); - if pieces.is_empty() { - Err("invalid numbering pattern")?; - } - - Ok(Self { pieces, suffix }) - } -} - -/// Different kinds of numberings. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -enum NumberingKind { - Arabic, - Letter, - Roman, - Symbol, -} - -impl NumberingKind { - /// Apply the numbering to the given number. - pub fn apply(self, n: NonZeroUsize, case: Case) -> EcoString { - let mut n = n.get(); - match self { - Self::Arabic => { - format_eco!("{n}") - } - Self::Letter => { - n -= 1; - - let mut letters = vec![]; - loop { - let c = b'a' + (n % 26) as u8; - letters.push(match case { - Case::Lower => c, - Case::Upper => c.to_ascii_uppercase(), - }); - n /= 26; - if n == 0 { - break; - } - } - - letters.reverse(); - String::from_utf8(letters).unwrap().into() - } - Self::Roman => { - // Adapted from Yann Villessuzanne's roman.rs under the - // Unlicense, at https://github.com/linfir/roman.rs/ - let mut fmt = EcoString::new(); - for &(name, value) in &[ - ("M̅", 1000000), - ("D̅", 500000), - ("C̅", 100000), - ("L̅", 50000), - ("X̅", 10000), - ("V̅", 5000), - ("I̅V̅", 4000), - ("M", 1000), - ("CM", 900), - ("D", 500), - ("CD", 400), - ("C", 100), - ("XC", 90), - ("L", 50), - ("XL", 40), - ("X", 10), - ("IX", 9), - ("V", 5), - ("IV", 4), - ("I", 1), - ] { - while n >= value { - n -= value; - for c in name.chars() { - match case { - Case::Lower => fmt.extend(c.to_lowercase()), - Case::Upper => fmt.push(c), - } - } - } - } - - fmt - } - Self::Symbol => { - const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; - let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; - let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat(symbol).take(amount).collect() - } - } - } -} diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 866a6195..53fc3327 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -1,7 +1,7 @@ use std::str::FromStr; -use crate::compute::{Numbering, NumberingPattern}; use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing}; +use crate::meta::{Numbering, NumberingPattern}; use crate::prelude::*; use crate::text::TextNode; diff --git a/library/src/lib.rs b/library/src/lib.rs index 412ebd41..8a231531 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -42,6 +42,7 @@ fn global(math: Module, calc: Module) -> Module { global.def_func::("strike"); global.def_func::("overline"); global.def_func::("raw"); + global.def_func::("lorem"); // Math. global.define("math", math); @@ -86,6 +87,7 @@ fn global(math: Module, calc: Module) -> Module { global.def_func::("link"); global.def_func::("outline"); global.def_func::("heading"); + global.def_func::("numbering"); // Symbols. global.define("sym", symbols::sym()); @@ -110,8 +112,6 @@ fn global(math: Module, calc: Module) -> Module { global.def_func::("csv"); global.def_func::("json"); global.def_func::("xml"); - global.def_func::("lorem"); - global.def_func::("numbering"); // Calc. global.define("calc", calc); diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index b5b0fd9e..c9032e88 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -1,6 +1,6 @@ use typst::font::FontWeight; -use crate::compute::Numbering; +use super::Numbering; use crate::layout::{BlockNode, VNode}; use crate::prelude::*; use crate::text::{SpaceNode, TextNode, TextSize}; diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs index fd72a8cb..07f449a4 100644 --- a/library/src/meta/mod.rs +++ b/library/src/meta/mod.rs @@ -3,11 +3,13 @@ mod document; mod heading; mod link; +mod numbering; mod outline; mod reference; pub use self::document::*; pub use self::heading::*; pub use self::link::*; +pub use self::numbering::*; pub use self::outline::*; pub use self::reference::*; diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs new file mode 100644 index 00000000..c187c18d --- /dev/null +++ b/library/src/meta/numbering.rs @@ -0,0 +1,282 @@ +use std::str::FromStr; + +use crate::prelude::*; +use crate::text::Case; + +/// # Numbering +/// Apply a numbering to a sequence of numbers. +/// +/// 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, for which the actual +/// number is substituted, their prefixes, and one suffix. The prefixes and the +/// suffix are repeated as-is. +/// +/// ## Example +/// ```example +/// #numbering("1.1)", 1, 2, 3) \ +/// #numbering("1.a.i", 1, 2) \ +/// #numbering("I – 1", 12, 2) \ +/// #numbering( +/// (..nums) => nums +/// .pos() +/// .map(str) +/// .join(".") + ")", +/// 1, 2, 3, +/// ) +/// ``` +/// +/// ## Parameters +/// - 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. +/// +/// 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. +/// +/// 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 numbering to. Must be positive. +/// +/// If `numbering` is a pattern and more numbers than counting symbols are +/// given, the last counting symbol with its prefix is repeated. +/// +/// - returns: any +/// +/// ## Category +/// meta +#[func] +pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult { + let numbering = args.expect::("pattern or function")?; + let numbers = args.all::()?; + 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, + numbers: &[NonZeroUsize], + ) -> SourceResult { + 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. +/// +/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, `I` +/// or `*`, and then a suffix. +/// +/// Examples of valid patterns: +/// - `1)` +/// - `a.` +/// - `(I)` +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct NumberingPattern { + pieces: Vec<(EcoString, NumberingKind, Case)>, + suffix: EcoString, +} + +impl NumberingPattern { + /// Apply the pattern to the given number. + pub fn apply(&self, numbers: &[NonZeroUsize]) -> EcoString { + let mut fmt = EcoString::new(); + let mut numbers = numbers.into_iter(); + + for ((prefix, kind, case), &n) in self.pieces.iter().zip(&mut numbers) { + fmt.push_str(prefix); + fmt.push_str(&kind.apply(n, *case)); + } + + for ((prefix, kind, case), &n) in + self.pieces.last().into_iter().cycle().zip(numbers) + { + if prefix.is_empty() { + fmt.push_str(&self.suffix); + } else { + fmt.push_str(prefix); + } + fmt.push_str(&kind.apply(n, *case)); + } + + fmt.push_str(&self.suffix); + fmt + } + + /// Apply only the k-th segment of the pattern to a number. + pub fn apply_kth(&self, k: usize, number: NonZeroUsize) -> EcoString { + let mut fmt = EcoString::new(); + if let Some((prefix, _, _)) = self.pieces.first() { + fmt.push_str(prefix); + } + if let Some((_, kind, case)) = self + .pieces + .iter() + .chain(self.pieces.last().into_iter().cycle()) + .nth(k) + { + fmt.push_str(&kind.apply(number, *case)); + } + fmt.push_str(&self.suffix); + fmt + } +} + +impl FromStr for NumberingPattern { + type Err = &'static str; + + fn from_str(pattern: &str) -> Result { + let mut pieces = vec![]; + let mut handled = 0; + + for (i, c) in pattern.char_indices() { + let kind = match c.to_ascii_lowercase() { + '1' => NumberingKind::Arabic, + 'a' => NumberingKind::Letter, + 'i' => NumberingKind::Roman, + '*' => NumberingKind::Symbol, + _ => continue, + }; + + let prefix = pattern[handled..i].into(); + let case = if c.is_uppercase() { Case::Upper } else { Case::Lower }; + pieces.push((prefix, kind, case)); + handled = i + 1; + } + + let suffix = pattern[handled..].into(); + if pieces.is_empty() { + Err("invalid numbering pattern")?; + } + + Ok(Self { pieces, suffix }) + } +} + +/// Different kinds of numberings. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum NumberingKind { + Arabic, + Letter, + Roman, + Symbol, +} + +impl NumberingKind { + /// Apply the numbering to the given number. + pub fn apply(self, n: NonZeroUsize, case: Case) -> EcoString { + let mut n = n.get(); + match self { + Self::Arabic => { + format_eco!("{n}") + } + Self::Letter => { + n -= 1; + + let mut letters = vec![]; + loop { + let c = b'a' + (n % 26) as u8; + letters.push(match case { + Case::Lower => c, + Case::Upper => c.to_ascii_uppercase(), + }); + n /= 26; + if n == 0 { + break; + } + } + + letters.reverse(); + String::from_utf8(letters).unwrap().into() + } + Self::Roman => { + // Adapted from Yann Villessuzanne's roman.rs under the + // Unlicense, at https://github.com/linfir/roman.rs/ + let mut fmt = EcoString::new(); + for &(name, value) in &[ + ("M̅", 1000000), + ("D̅", 500000), + ("C̅", 100000), + ("L̅", 50000), + ("X̅", 10000), + ("V̅", 5000), + ("I̅V̅", 4000), + ("M", 1000), + ("CM", 900), + ("D", 500), + ("CD", 400), + ("C", 100), + ("XC", 90), + ("L", 50), + ("XL", 40), + ("X", 10), + ("IX", 9), + ("V", 5), + ("IV", 4), + ("I", 1), + ] { + while n >= value { + n -= value; + for c in name.chars() { + match case { + Case::Lower => fmt.extend(c.to_lowercase()), + Case::Upper => fmt.push(c), + } + } + } + } + + fmt + } + Self::Symbol => { + const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; + let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; + let amount = ((n - 1) / SYMBOLS.len()) + 1; + std::iter::repeat(symbol).take(amount).collect() + } + } + } +} diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 184294d4..43aea021 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -345,3 +345,34 @@ pub fn smallcaps(args: &mut Args) -> SourceResult { let body: Content = args.expect("content")?; Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } + +/// # Blind Text +/// Create blind text. +/// +/// This function yields a Latin-like _Lorem Ipsum_ blind text with the given +/// number of words. The sequence of words generated by the function is always +/// the same but randomly chosen. As usual for blind texts, it does not make any +/// sense. Use it as a placeholder to try layouts. +/// +/// ## Example +/// ```example +/// = Blind Text +/// #lorem(30) +/// +/// = More Blind Text +/// #lorem(15) +/// ``` +/// +/// ## Parameters +/// - words: `usize` (positional, required) +/// The length of the blind text in words. +/// +/// - returns: string +/// +/// ## Category +/// text +#[func] +pub fn lorem(args: &mut Args) -> SourceResult { + let words: usize = args.expect("number of words")?; + Ok(Value::Str(lipsum::lipsum(words).into())) +} -- cgit v1.2.3