typst/library/src/compute/utility.rs
2022-12-15 23:11:20 +01:00

187 lines
5.3 KiB
Rust

use std::str::FromStr;
use crate::prelude::*;
use crate::text::Case;
/// Create a blind text string.
///
/// Tags: utility.
#[func]
pub fn lorem(args: &mut Args) -> SourceResult<Value> {
let words: usize = args.expect("number of words")?;
Ok(Value::Str(lipsum::lipsum(words).into()))
}
/// Apply a numbering pattern to a number.
///
/// Tags: utility.
#[func]
pub fn numbering(args: &mut Args) -> SourceResult<Value> {
let pattern = args.expect::<NumberingPattern>("pattern")?;
let numbers = args.all::<NonZeroUsize>()?;
Ok(Value::Str(pattern.apply(&numbers).into()))
}
/// 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
}
}
impl FromStr for NumberingPattern {
type Err = &'static str;
fn from_str(pattern: &str) -> Result<Self, Self::Err> {
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 })
}
}
castable! {
NumberingPattern,
string: EcoString => string.parse()?,
}
/// 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 &[
("", 1000000),
("", 500000),
("", 100000),
("", 50000),
("", 10000),
("", 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()
}
}
}
}