mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Extract numbering pattern from list node
This commit is contained in:
parent
bf5edbbbbb
commit
3cdd8bfa40
@ -1,6 +1,7 @@
|
|||||||
use typst::model::Regex;
|
use typst::model::Regex;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::shared::NumberingKind;
|
||||||
|
|
||||||
/// The string representation of a value.
|
/// The string representation of a value.
|
||||||
pub fn repr(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn repr(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
@ -32,110 +33,20 @@ pub fn regex(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
|||||||
|
|
||||||
/// Converts an integer into one or multiple letters.
|
/// Converts an integer into one or multiple letters.
|
||||||
pub fn letter(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn letter(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
numbered(Numbering::Letter, args)
|
numbered(NumberingKind::Letter, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an integer into a roman numeral.
|
/// Converts an integer into a roman numeral.
|
||||||
pub fn roman(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn roman(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
numbered(Numbering::Roman, args)
|
numbered(NumberingKind::Roman, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a number into a symbol.
|
/// Convert a number into a symbol.
|
||||||
pub fn symbol(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn symbol(_: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
numbered(Numbering::Symbol, args)
|
numbered(NumberingKind::Symbol, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn numbered(numbering: Numbering, args: &mut Args) -> SourceResult<Value> {
|
fn numbered(numbering: NumberingKind, args: &mut Args) -> SourceResult<Value> {
|
||||||
let n = args.expect::<usize>("non-negative integer")?;
|
let n = args.expect::<usize>("non-negative integer")?;
|
||||||
Ok(Value::Str(numbering.apply(n).into()))
|
Ok(Value::Str(numbering.apply(n).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows to convert a number into letters, roman numerals and symbols.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum Numbering {
|
|
||||||
Arabic,
|
|
||||||
Letter,
|
|
||||||
Roman,
|
|
||||||
Symbol,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Numbering {
|
|
||||||
/// Apply the numbering to the given number.
|
|
||||||
pub fn apply(self, mut n: usize) -> EcoString {
|
|
||||||
match self {
|
|
||||||
Self::Arabic => {
|
|
||||||
format_eco!("{}", n)
|
|
||||||
}
|
|
||||||
Self::Letter => {
|
|
||||||
if n == 0 {
|
|
||||||
return '-'.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
n -= 1;
|
|
||||||
|
|
||||||
let mut letters = vec![];
|
|
||||||
loop {
|
|
||||||
letters.push(b'a' + (n % 26) as u8);
|
|
||||||
n /= 26;
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
letters.reverse();
|
|
||||||
String::from_utf8(letters).unwrap().into()
|
|
||||||
}
|
|
||||||
Self::Roman => {
|
|
||||||
if n == 0 {
|
|
||||||
return 'N'.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ROMANS {
|
|
||||||
while n >= value {
|
|
||||||
n -= value;
|
|
||||||
fmt.push_str(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt
|
|
||||||
}
|
|
||||||
Self::Symbol => {
|
|
||||||
if n == 0 {
|
|
||||||
return '-'.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()];
|
|
||||||
let amount = ((n - 1) / SYMBOLS.len()) + 1;
|
|
||||||
std::iter::repeat(symbol).take(amount).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROMANS: &[(&str, usize)] = &[
|
|
||||||
("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),
|
|
||||||
];
|
|
||||||
|
|
||||||
const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶'];
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
//! Central definitions for the standard library.
|
|
||||||
|
|
||||||
mod behave;
|
|
||||||
mod ext;
|
|
||||||
|
|
||||||
pub use behave::*;
|
|
||||||
pub use ext::*;
|
|
@ -37,8 +37,8 @@ use typst::model::{
|
|||||||
};
|
};
|
||||||
use typst::World;
|
use typst::World;
|
||||||
|
|
||||||
use crate::core::BehavedBuilder;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::shared::BehavedBuilder;
|
||||||
use crate::structure::{
|
use crate::structure::{
|
||||||
DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
|
DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
//! Typst's standard library.
|
//! Typst's standard library.
|
||||||
|
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod core;
|
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
pub mod shared;
|
||||||
pub mod structure;
|
pub mod structure;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ pub use typst::util::{format_eco, EcoString};
|
|||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::World;
|
pub use typst::World;
|
||||||
|
|
||||||
#[doc(no_inline)]
|
|
||||||
pub use crate::core::{Behave, Behaviour, ContentExt, StyleMapExt};
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::layout::{LayoutBlock, LayoutInline, Regions};
|
pub use crate::layout::{LayoutBlock, LayoutInline, Regions};
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use crate::shared::{Behave, Behaviour, ContentExt, StyleMapExt};
|
||||||
|
9
library/src/shared/mod.rs
Normal file
9
library/src/shared/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//! Shared definitions for the standard library.
|
||||||
|
|
||||||
|
mod behave;
|
||||||
|
mod ext;
|
||||||
|
mod numbering;
|
||||||
|
|
||||||
|
pub use behave::*;
|
||||||
|
pub use ext::*;
|
||||||
|
pub use numbering::*;
|
139
library/src/shared/numbering.rs
Normal file
139
library/src/shared/numbering.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use typst::model::{castable, Value};
|
||||||
|
use typst::util::{format_eco, EcoString};
|
||||||
|
use unscanny::Scanner;
|
||||||
|
|
||||||
|
/// A numbering pattern for lists or headings.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct NumberingPattern {
|
||||||
|
prefix: EcoString,
|
||||||
|
numbering: NumberingKind,
|
||||||
|
upper: bool,
|
||||||
|
suffix: EcoString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NumberingPattern {
|
||||||
|
/// Apply the pattern to the given number.
|
||||||
|
pub fn apply(&self, n: usize) -> EcoString {
|
||||||
|
let fmt = self.numbering.apply(n);
|
||||||
|
let mid = if self.upper { fmt.to_uppercase() } else { fmt.to_lowercase() };
|
||||||
|
format_eco!("{}{}{}", self.prefix, mid, self.suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for NumberingPattern {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(pattern: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut s = Scanner::new(pattern);
|
||||||
|
let mut prefix;
|
||||||
|
let numbering = loop {
|
||||||
|
prefix = s.before();
|
||||||
|
match s.eat().map(|c| c.to_ascii_lowercase()) {
|
||||||
|
Some('1') => break NumberingKind::Arabic,
|
||||||
|
Some('a') => break NumberingKind::Letter,
|
||||||
|
Some('i') => break NumberingKind::Roman,
|
||||||
|
Some('*') => break NumberingKind::Symbol,
|
||||||
|
Some(_) => {}
|
||||||
|
None => Err("invalid numbering pattern")?,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let upper = s.scout(-1).map_or(false, char::is_uppercase);
|
||||||
|
let suffix = s.after().into();
|
||||||
|
Ok(Self { prefix: prefix.into(), numbering, upper, suffix })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
NumberingPattern,
|
||||||
|
Expected: "numbering pattern",
|
||||||
|
Value::Str(s) => s.parse()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Different kinds of numberings.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum NumberingKind {
|
||||||
|
Arabic,
|
||||||
|
Letter,
|
||||||
|
Roman,
|
||||||
|
Symbol,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NumberingKind {
|
||||||
|
/// Apply the numbering to the given number.
|
||||||
|
pub fn apply(self, mut n: usize) -> EcoString {
|
||||||
|
match self {
|
||||||
|
Self::Arabic => {
|
||||||
|
format_eco!("{n}")
|
||||||
|
}
|
||||||
|
Self::Letter => {
|
||||||
|
if n == 0 {
|
||||||
|
return '-'.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
n -= 1;
|
||||||
|
|
||||||
|
let mut letters = vec![];
|
||||||
|
loop {
|
||||||
|
letters.push(b'a' + (n % 26) as u8);
|
||||||
|
n /= 26;
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
letters.reverse();
|
||||||
|
String::from_utf8(letters).unwrap().into()
|
||||||
|
}
|
||||||
|
Self::Roman => {
|
||||||
|
if n == 0 {
|
||||||
|
return 'N'.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
fmt.push_str(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt
|
||||||
|
}
|
||||||
|
Self::Symbol => {
|
||||||
|
if n == 0 {
|
||||||
|
return '-'.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶'];
|
||||||
|
let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()];
|
||||||
|
let amount = ((n - 1) / SYMBOLS.len()) + 1;
|
||||||
|
std::iter::repeat(symbol).take(amount).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
use unscanny::Scanner;
|
|
||||||
|
|
||||||
use crate::base::Numbering;
|
|
||||||
use crate::layout::{BlockNode, GridNode, HNode, Spacing, TrackSizing};
|
use crate::layout::{BlockNode, GridNode, HNode, Spacing, TrackSizing};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::shared::NumberingPattern;
|
||||||
use crate::text::{ParNode, SpaceNode, TextNode};
|
use crate::text::{ParNode, SpaceNode, TextNode};
|
||||||
|
|
||||||
/// An unordered (bulleted) or ordered (numbered) list.
|
/// An unordered (bulleted) or ordered (numbered) list.
|
||||||
@ -223,7 +221,7 @@ pub enum Label {
|
|||||||
/// The default labelling.
|
/// The default labelling.
|
||||||
Default,
|
Default,
|
||||||
/// A pattern with prefix, numbering, lower / upper case and suffix.
|
/// A pattern with prefix, numbering, lower / upper case and suffix.
|
||||||
Pattern(EcoString, Numbering, bool, EcoString),
|
Pattern(NumberingPattern),
|
||||||
/// Bare content.
|
/// Bare content.
|
||||||
Content(Content),
|
Content(Content),
|
||||||
/// A closure mapping from an item number to a value.
|
/// A closure mapping from an item number to a value.
|
||||||
@ -231,7 +229,7 @@ pub enum Label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
/// Resolve the value based on the level.
|
/// Resolve the label based on the level.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
@ -244,11 +242,7 @@ impl Label {
|
|||||||
ENUM => TextNode::packed(format_eco!("{}.", number)),
|
ENUM => TextNode::packed(format_eco!("{}.", number)),
|
||||||
DESC | _ => panic!("description lists don't have a label"),
|
DESC | _ => panic!("description lists don't have a label"),
|
||||||
},
|
},
|
||||||
Self::Pattern(prefix, numbering, upper, suffix) => {
|
Self::Pattern(pattern) => TextNode::packed(pattern.apply(number)),
|
||||||
let fmt = numbering.apply(number);
|
|
||||||
let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() };
|
|
||||||
TextNode::packed(format_eco!("{}{}{}", prefix, mid, suffix))
|
|
||||||
}
|
|
||||||
Self::Content(content) => content.clone(),
|
Self::Content(content) => content.clone(),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
let args = Args::new(*span, [Value::Int(number as i64)]);
|
let args = Args::new(*span, [Value::Int(number as i64)]);
|
||||||
@ -266,24 +260,7 @@ impl Cast<Spanned<Value>> for Label {
|
|||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
match value.v {
|
match value.v {
|
||||||
Value::None => Ok(Self::Content(Content::empty())),
|
Value::None => Ok(Self::Content(Content::empty())),
|
||||||
Value::Str(pattern) => {
|
Value::Str(v) => Ok(Self::Pattern(v.parse()?)),
|
||||||
let mut s = Scanner::new(&pattern);
|
|
||||||
let mut prefix;
|
|
||||||
let numbering = loop {
|
|
||||||
prefix = s.before();
|
|
||||||
match s.eat().map(|c| c.to_ascii_lowercase()) {
|
|
||||||
Some('1') => break Numbering::Arabic,
|
|
||||||
Some('a') => break Numbering::Letter,
|
|
||||||
Some('i') => break Numbering::Roman,
|
|
||||||
Some('*') => break Numbering::Symbol,
|
|
||||||
Some(_) => {}
|
|
||||||
None => Err("invalid pattern")?,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let upper = s.scout(-1).map_or(false, char::is_uppercase);
|
|
||||||
let suffix = s.after().into();
|
|
||||||
Ok(Self::Pattern(prefix.into(), numbering, upper, suffix))
|
|
||||||
}
|
|
||||||
Value::Content(v) => Ok(Self::Content(v)),
|
Value::Content(v) => Ok(Self::Content(v)),
|
||||||
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
||||||
v => Err(format!(
|
v => Err(format!(
|
||||||
|
@ -59,7 +59,7 @@ pub fn eval(
|
|||||||
|
|
||||||
/// A virtual machine.
|
/// A virtual machine.
|
||||||
///
|
///
|
||||||
/// Holds the state needed to [evaluate](super::eval()) Typst sources. A new
|
/// Holds the state needed to [evaluate](eval) Typst sources. A new
|
||||||
/// virtual machine is created for each module evaluation and function call.
|
/// virtual machine is created for each module evaluation and function call.
|
||||||
pub struct Vm<'a> {
|
pub struct Vm<'a> {
|
||||||
/// The compilation environment.
|
/// The compilation environment.
|
||||||
@ -78,7 +78,7 @@ pub struct Vm<'a> {
|
|||||||
|
|
||||||
impl<'a> Vm<'a> {
|
impl<'a> Vm<'a> {
|
||||||
/// Create a new virtual machine.
|
/// Create a new virtual machine.
|
||||||
pub fn new(
|
pub(super) fn new(
|
||||||
world: Tracked<'a, dyn World>,
|
world: Tracked<'a, dyn World>,
|
||||||
route: Tracked<'a, Route>,
|
route: Tracked<'a, Route>,
|
||||||
location: SourceId,
|
location: SourceId,
|
||||||
|
@ -57,9 +57,9 @@
|
|||||||
No enum
|
No enum
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 18-20 invalid pattern
|
// Error: 18-20 invalid numbering pattern
|
||||||
#set enum(label: "")
|
#set enum(label: "")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 18-24 invalid pattern
|
// Error: 18-24 invalid numbering pattern
|
||||||
#set enum(label: "(())")
|
#set enum(label: "(())")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user