summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst-library/src/model/numbering.rs637
-rw-r--r--tests/suite/model/numbering.typ42
2 files changed, 339 insertions, 340 deletions
diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs
index 320ed7d1..236ced36 100644
--- a/crates/typst-library/src/model/numbering.rs
+++ b/crates/typst-library/src/model/numbering.rs
@@ -9,7 +9,6 @@ use ecow::{eco_format, EcoString, EcoVec};
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{cast, func, Context, Func, Str, Value};
-use crate::text::Case;
/// Applies a numbering to a sequence of numbers.
///
@@ -381,40 +380,194 @@ impl NumberingKind {
/// Apply the numbering to the given number.
pub fn apply(self, n: u64) -> EcoString {
match self {
- Self::Arabic => eco_format!("{n}"),
- Self::LowerRoman => roman_numeral(n, Case::Lower),
- Self::UpperRoman => roman_numeral(n, Case::Upper),
- Self::LowerGreek => greek_numeral(n, Case::Lower),
- Self::UpperGreek => greek_numeral(n, Case::Upper),
- Self::Symbol => {
- if n == 0 {
- return '-'.into();
- }
-
- const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖'];
- let n_symbols = SYMBOLS.len() as u64;
- let symbol = SYMBOLS[((n - 1) % n_symbols) as usize];
- let amount = ((n - 1) / n_symbols) + 1;
- std::iter::repeat_n(symbol, amount.try_into().unwrap()).collect()
+ Self::Arabic => {
+ numeric(&['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n)
}
- Self::Hebrew => hebrew_numeral(n),
-
- Self::LowerLatin => zeroless(
- [
+ Self::LowerRoman => additive(
+ &[
+ ("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),
+ ("n", 0),
+ ],
+ n,
+ ),
+ Self::UpperRoman => additive(
+ &[
+ ("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),
+ ("N", 0),
+ ],
+ n,
+ ),
+ Self::LowerGreek => additive(
+ &[
+ ("͵θ", 9000),
+ ("͵η", 8000),
+ ("͵ζ", 7000),
+ ("͵ϛ", 6000),
+ ("͵ε", 5000),
+ ("͵δ", 4000),
+ ("͵γ", 3000),
+ ("͵β", 2000),
+ ("͵α", 1000),
+ ("ϡ", 900),
+ ("ω", 800),
+ ("ψ", 700),
+ ("χ", 600),
+ ("φ", 500),
+ ("υ", 400),
+ ("τ", 300),
+ ("σ", 200),
+ ("ρ", 100),
+ ("ϟ", 90),
+ ("π", 80),
+ ("ο", 70),
+ ("ξ", 60),
+ ("ν", 50),
+ ("μ", 40),
+ ("λ", 30),
+ ("κ", 20),
+ ("ι", 10),
+ ("θ", 9),
+ ("η", 8),
+ ("ζ", 7),
+ ("ϛ", 6),
+ ("ε", 5),
+ ("δ", 4),
+ ("γ", 3),
+ ("β", 2),
+ ("α", 1),
+ ("𐆊", 0),
+ ],
+ n,
+ ),
+ Self::UpperGreek => additive(
+ &[
+ ("͵Θ", 9000),
+ ("͵Η", 8000),
+ ("͵Ζ", 7000),
+ ("͵Ϛ", 6000),
+ ("͵Ε", 5000),
+ ("͵Δ", 4000),
+ ("͵Γ", 3000),
+ ("͵Β", 2000),
+ ("͵Α", 1000),
+ ("Ϡ", 900),
+ ("Ω", 800),
+ ("Ψ", 700),
+ ("Χ", 600),
+ ("Φ", 500),
+ ("Υ", 400),
+ ("Τ", 300),
+ ("Σ", 200),
+ ("Ρ", 100),
+ ("Ϟ", 90),
+ ("Π", 80),
+ ("Ο", 70),
+ ("Ξ", 60),
+ ("Ν", 50),
+ ("Μ", 40),
+ ("Λ", 30),
+ ("Κ", 20),
+ ("Ι", 10),
+ ("Θ", 9),
+ ("Η", 8),
+ ("Ζ", 7),
+ ("Ϛ", 6),
+ ("Ε", 5),
+ ("Δ", 4),
+ ("Γ", 3),
+ ("Β", 2),
+ ("Α", 1),
+ ("𐆊", 0),
+ ],
+ n,
+ ),
+ Self::Hebrew => additive(
+ &[
+ ("ת", 400),
+ ("ש", 300),
+ ("ר", 200),
+ ("ק", 100),
+ ("צ", 90),
+ ("פ", 80),
+ ("ע", 70),
+ ("ס", 60),
+ ("נ", 50),
+ ("מ", 40),
+ ("ל", 30),
+ ("כ", 20),
+ ("יט", 19),
+ ("יח", 18),
+ ("יז", 17),
+ ("טז", 16),
+ ("טו", 15),
+ ("י", 10),
+ ("ט", 9),
+ ("ח", 8),
+ ("ז", 7),
+ ("ו", 6),
+ ("ה", 5),
+ ("ד", 4),
+ ("ג", 3),
+ ("ב", 2),
+ ("א", 1),
+ ("-", 0),
+ ],
+ n,
+ ),
+ Self::LowerLatin => alphabetic(
+ &[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
],
n,
),
- Self::UpperLatin => zeroless(
- [
+ Self::UpperLatin => alphabetic(
+ &[
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
],
n,
),
- Self::HiraganaAiueo => zeroless(
- [
+ Self::HiraganaAiueo => alphabetic(
+ &[
'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ',
'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に',
'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む',
@@ -423,8 +576,8 @@ impl NumberingKind {
],
n,
),
- Self::HiraganaIroha => zeroless(
- [
+ Self::HiraganaIroha => alphabetic(
+ &[
'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る',
'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら',
'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ',
@@ -433,8 +586,8 @@ impl NumberingKind {
],
n,
),
- Self::KatakanaAiueo => zeroless(
- [
+ Self::KatakanaAiueo => alphabetic(
+ &[
'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ',
'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ',
'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム',
@@ -443,8 +596,8 @@ impl NumberingKind {
],
n,
),
- Self::KatakanaIroha => zeroless(
- [
+ Self::KatakanaIroha => alphabetic(
+ &[
'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル',
'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ',
'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ',
@@ -453,40 +606,40 @@ impl NumberingKind {
],
n,
),
- Self::KoreanJamo => zeroless(
- [
+ Self::KoreanJamo => alphabetic(
+ &[
'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ',
'ㅌ', 'ㅍ', 'ㅎ',
],
n,
),
- Self::KoreanSyllable => zeroless(
- [
+ Self::KoreanSyllable => alphabetic(
+ &[
'가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카',
'타', '파', '하',
],
n,
),
- Self::BengaliLetter => zeroless(
- [
+ Self::BengaliLetter => alphabetic(
+ &[
'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ',
'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল',
'শ', 'ষ', 'স', 'হ',
],
n,
),
- Self::CircledNumber => zeroless(
- [
- '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭',
- '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', '㉖',
- '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', '㊲',
- '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽',
- '㊾', '㊿',
+ Self::CircledNumber => fixed(
+ &[
+ '⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬',
+ '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕',
+ '㉖', '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱',
+ '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼',
+ '㊽', '㊾', '㊿',
],
n,
),
Self::DoubleCircledNumber => {
- zeroless(['⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n)
+ fixed(&['0', '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n)
}
Self::LowerSimplifiedChinese => {
@@ -502,306 +655,170 @@ impl NumberingKind {
u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n).into()
}
- Self::EasternArabic => decimal('\u{0660}', n),
- Self::EasternArabicPersian => decimal('\u{06F0}', n),
- Self::DevanagariNumber => decimal('\u{0966}', n),
- Self::BengaliNumber => decimal('\u{09E6}', n),
- }
- }
-}
-
-/// Stringify an integer to a Hebrew number.
-fn hebrew_numeral(mut n: u64) -> EcoString {
- if n == 0 {
- return '-'.into();
- }
- let mut fmt = EcoString::new();
- 'outer: for (name, value) in [
- ('ת', 400),
- ('ש', 300),
- ('ר', 200),
- ('ק', 100),
- ('צ', 90),
- ('פ', 80),
- ('ע', 70),
- ('ס', 60),
- ('נ', 50),
- ('מ', 40),
- ('ל', 30),
- ('כ', 20),
- ('י', 10),
- ('ט', 9),
- ('ח', 8),
- ('ז', 7),
- ('ו', 6),
- ('ה', 5),
- ('ד', 4),
- ('ג', 3),
- ('ב', 2),
- ('א', 1),
- ] {
- while n >= value {
- match n {
- 15 => fmt.push_str("ט״ו"),
- 16 => fmt.push_str("ט״ז"),
- _ => {
- let append_geresh = n == value && fmt.is_empty();
- if n == value && !fmt.is_empty() {
- fmt.push('״');
- }
- fmt.push(name);
- if append_geresh {
- fmt.push('׳');
- }
-
- n -= value;
- continue;
- }
+ Self::EasternArabic => {
+ numeric(&['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], n)
}
- break 'outer;
- }
- }
- fmt
-}
-
-/// Stringify an integer to a Roman numeral.
-fn roman_numeral(mut n: u64, case: Case) -> EcoString {
- if n == 0 {
- return match case {
- Case::Lower => 'n'.into(),
- Case::Upper => '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;
- for c in name.chars() {
- match case {
- Case::Lower => fmt.extend(c.to_lowercase()),
- Case::Upper => fmt.push(c),
- }
+ Self::EasternArabicPersian => {
+ numeric(&['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], n)
+ }
+ Self::DevanagariNumber => {
+ numeric(&['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], n)
+ }
+ Self::BengaliNumber => {
+ numeric(&['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], n)
}
+ Self::Symbol => symbolic(&['*', '†', '‡', '§', '¶', '‖'], n),
}
}
-
- fmt
}
-/// Stringify an integer to Greek numbers.
+/// Stringify a number using symbols representing values. The decimal
+/// representation of the number is recovered by summing over the values of the
+/// symbols present.
///
-/// Greek numbers use the Greek Alphabet to represent numbers; it is based on 10
-/// (decimal). Here we implement the single digit M power representation from
-/// [The Greek Number Converter][convert] and also described in
-/// [Greek Numbers][numbers].
+/// Consider the situation where ['I': 1, 'IV': 4, 'V': 5],
///
-/// [converter]: https://www.russellcottrell.com/greek/utilities/GreekNumberConverter.htm
-/// [numbers]: https://mathshistory.st-andrews.ac.uk/HistTopics/Greek_numbers/
-fn greek_numeral(n: u64, case: Case) -> EcoString {
- let thousands = [
- ["͵α", "͵Α"],
- ["͵β", "͵Β"],
- ["͵γ", "͵Γ"],
- ["͵δ", "͵Δ"],
- ["͵ε", "͵Ε"],
- ["͵ϛ", "͵Ϛ"],
- ["͵ζ", "͵Ζ"],
- ["͵η", "͵Η"],
- ["͵θ", "͵Θ"],
- ];
- let hundreds = [
- ["ρ", "Ρ"],
- ["σ", "Σ"],
- ["τ", "Τ"],
- ["υ", "Υ"],
- ["φ", "Φ"],
- ["χ", "Χ"],
- ["ψ", "Ψ"],
- ["ω", "Ω"],
- ["ϡ", "Ϡ"],
- ];
- let tens = [
- ["ι", "Ι"],
- ["κ", "Κ"],
- ["λ", "Λ"],
- ["μ", "Μ"],
- ["ν", "Ν"],
- ["ξ", "Ξ"],
- ["ο", "Ο"],
- ["π", "Π"],
- ["ϙ", "Ϟ"],
- ];
- let ones = [
- ["α", "Α"],
- ["β", "Β"],
- ["γ", "Γ"],
- ["δ", "Δ"],
- ["ε", "Ε"],
- ["ϛ", "Ϛ"],
- ["ζ", "Ζ"],
- ["η", "Η"],
- ["θ", "Θ"],
- ];
-
+/// ```text
+/// 1 => 'I'
+/// 2 => 'II'
+/// 3 => 'III'
+/// 4 => 'IV'
+/// 5 => 'V'
+/// 6 => 'VI'
+/// 7 => 'VII'
+/// 8 => 'VIII'
+/// ```
+///
+/// where this is the start of the familiar Roman numeral system.
+fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString {
if n == 0 {
- // Greek Zero Sign
- return '𐆊'.into();
- }
-
- let mut fmt = EcoString::new();
- let case = match case {
- Case::Lower => 0,
- Case::Upper => 1,
- };
-
- // Extract a list of decimal digits from the number
- let mut decimal_digits: Vec<usize> = Vec::new();
- let mut n = n;
- while n > 0 {
- decimal_digits.push((n % 10) as usize);
- n /= 10;
- }
-
- // Pad the digits with leading zeros to ensure we can form groups of 4
- while decimal_digits.len() % 4 != 0 {
- decimal_digits.push(0);
- }
- decimal_digits.reverse();
-
- let mut m_power = decimal_digits.len() / 4;
-
- // M are used to represent 10000, M_power = 2 means 10000^2 = 10000 0000
- // The prefix of M is also made of Greek numerals but only be single digits, so it is 9 at max. This enables us
- // to represent up to (10000)^(9 + 1) - 1 = 10^40 -1 (9,999,999,999,999,999,999,999,999,999,999,999,999,999)
- let get_m_prefix = |m_power: usize| {
- if m_power == 0 {
- None
- } else {
- assert!(m_power <= 9);
- // the prefix of M is a single digit lowercase
- Some(ones[m_power - 1][0])
+ if let Some(&(symbol, 0)) = symbols.last() {
+ return symbol.into();
}
- };
-
- let mut previous_has_number = false;
- for chunk in decimal_digits.chunks_exact(4) {
- // chunk must be exact 4 item
- assert_eq!(chunk.len(), 4);
-
- m_power = m_power.saturating_sub(1);
+ return '0'.into();
+ }
- // `th`ousan, `h`undred, `t`en and `o`ne
- let (th, h, t, o) = (chunk[0], chunk[1], chunk[2], chunk[3]);
- if th + h + t + o == 0 {
+ let mut s = EcoString::new();
+ for (symbol, weight) in symbols {
+ if *weight == 0 || *weight > n {
continue;
}
-
- if previous_has_number {
- fmt.push_str(", ");
+ let reps = n / weight;
+ for _ in 0..reps {
+ s.push_str(symbol);
}
- if let Some(m_prefix) = get_m_prefix(m_power) {
- fmt.push_str(m_prefix);
- fmt.push_str("Μ");
- }
- if th != 0 {
- let thousand_digit = thousands[th - 1][case];
- fmt.push_str(thousand_digit);
- }
- if h != 0 {
- let hundred_digit = hundreds[h - 1][case];
- fmt.push_str(hundred_digit);
- }
- if t != 0 {
- let ten_digit = tens[t - 1][case];
- fmt.push_str(ten_digit);
- }
- if o != 0 {
- let one_digit = ones[o - 1][case];
- fmt.push_str(one_digit);
+ n -= weight * reps;
+ if n == 0 {
+ return s;
}
- // if we do not have thousan, we need to append 'ʹ' at the end.
- if th == 0 {
- fmt.push_str("ʹ");
- }
- previous_has_number = true;
}
- fmt
+ s
}
-/// Stringify a number using a base-N counting system with no zero digit.
+/// Stringify a number using a base-n (where n is the number of provided
+/// symbols) system without a zero symbol.
///
-/// This is best explained by example. Suppose our digits are 'A', 'B', and 'C'.
-/// We would get the following:
+/// Consider the situation where ['A', 'B', 'C'] are the provided symbols,
///
/// ```text
-/// 1 => "A"
-/// 2 => "B"
-/// 3 => "C"
-/// 4 => "AA"
-/// 5 => "AB"
-/// 6 => "AC"
-/// 7 => "BA"
-/// 8 => "BB"
-/// 9 => "BC"
-/// 10 => "CA"
-/// 11 => "CB"
-/// 12 => "CC"
-/// 13 => "AAA"
-/// etc.
+/// 1 => 'A'
+/// 2 => 'B'
+/// 3 => 'C'
+/// 4 => 'AA
+/// 5 => 'AB'
+/// 6 => 'AC'
+/// 7 => 'BA'
+/// ...
/// ```
///
-/// You might be familiar with this scheme from the way spreadsheet software
-/// tends to label its columns.
-fn zeroless<const N_DIGITS: usize>(alphabet: [char; N_DIGITS], mut n: u64) -> EcoString {
+/// This system is commonly used in spreadsheet software.
+fn alphabetic(symbols: &[char], mut n: u64) -> EcoString {
+ let n_digits = symbols.len() as u64;
if n == 0 {
return '-'.into();
}
- let n_digits = N_DIGITS as u64;
- let mut cs = EcoString::new();
- while n > 0 {
+ let mut s = EcoString::new();
+ while n != 0 {
n -= 1;
- cs.push(alphabet[(n % n_digits) as usize]);
+ s.push(symbols[(n % n_digits) as usize]);
n /= n_digits;
}
- cs.chars().rev().collect()
+ s.chars().rev().collect()
+}
+
+/// Stringify a number using the symbols provided, defaulting to the arabic
+/// representation when the number is greater than the number of symbols.
+///
+/// Consider the situation where ['0', 'A', 'B', 'C'] are the provided symbols,
+///
+/// ```text
+/// 0 => '0'
+/// 1 => 'A'
+/// 2 => 'B'
+/// 3 => 'C'
+/// 4 => '4'
+/// ...
+/// n => 'n'
+/// ```
+fn fixed(symbols: &[char], n: u64) -> EcoString {
+ let n_digits = symbols.len() as u64;
+ if n < n_digits {
+ return symbols[(n) as usize].into();
+ }
+ eco_format!("{n}")
}
-/// Stringify a number using a base-10 counting system with a zero digit.
+/// Stringify a number using a base-n (where n is the number of provided
+/// symbols) system with a zero symbol.
+///
+/// Consider the situation where ['0', '1', '2'] are the provided symbols,
///
-/// This function assumes that the digits occupy contiguous codepoints.
-fn decimal(start: char, mut n: u64) -> EcoString {
+/// ```text
+/// 0 => '0'
+/// 1 => '1'
+/// 2 => '2'
+/// 3 => '10'
+/// 4 => '11'
+/// 5 => '12'
+/// 6 => '20'
+/// ...
+/// ```
+///
+/// which is the familiar trinary counting system.
+fn numeric(symbols: &[char], mut n: u64) -> EcoString {
+ let n_digits = symbols.len() as u64;
if n == 0 {
- return start.into();
+ return symbols[0].into();
+ }
+ let mut s = EcoString::new();
+ while n != 0 {
+ s.push(symbols[(n % n_digits) as usize]);
+ n /= n_digits;
}
- let mut cs = EcoString::new();
- while n > 0 {
- cs.push(char::from_u32((start as u32) + ((n % 10) as u32)).unwrap());
- n /= 10;
+ s.chars().rev().collect()
+}
+
+/// Stringify a number using repeating symbols.
+///
+/// Consider the situation where ['A', 'B', 'C'] are the provided symbols,
+///
+/// ```text
+/// 0 => '-'
+/// 1 => 'A'
+/// 2 => 'B'
+/// 3 => 'C'
+/// 4 => 'AA'
+/// 5 => 'BB'
+/// 6 => 'CC'
+/// 7 => 'AAA'
+/// ...
+/// ```
+fn symbolic(symbols: &[char], n: u64) -> EcoString {
+ let n_digits = symbols.len() as u64;
+ if n == 0 {
+ return '-'.into();
}
- cs.chars().rev().collect()
+ EcoString::from(symbols[((n - 1) % n_digits) as usize])
+ .repeat((n.div_ceil(n_digits)) as usize)
}
diff --git a/tests/suite/model/numbering.typ b/tests/suite/model/numbering.typ
index 6af989ff..2d6a3d6a 100644
--- a/tests/suite/model/numbering.typ
+++ b/tests/suite/model/numbering.typ
@@ -19,50 +19,32 @@
// Greek.
#t(
pat: "α",
- "𐆊", "αʹ", "βʹ", "γʹ", "δʹ", "εʹ", "ϛʹ", "ζʹ", "ηʹ", "θʹ", "ιʹ",
- "ιαʹ", "ιβʹ", "ιγʹ", "ιδʹ", "ιεʹ", "ιϛʹ", "ιζʹ", "ιηʹ", "ιθʹ", "κʹ",
- 241, "σμαʹ",
- 999, "ϡϙθʹ",
+ "𐆊", "α", "β", "γ", "δ", "ε", "ϛ", "ζ", "η", "θ", "ι",
+ "ια", "ιβ", "ιγ", "ιδ", "ιε", "ιϛ", "ιζ", "ιη", "ιθ", "κ",
+ 241, "σμα",
+ 999, "ϡϟθ",
1005, "͵αε",
- 1999, "͵αϡϙθ",
- 2999, "͵βϡϙθ",
+ 1999, "͵αϡϟθ",
+ 2999, "͵βϡϟθ",
3000, "͵γ",
- 3398, "͵γτϙη",
+ 3398, "͵γτϟη",
4444, "͵δυμδ",
5683, "͵εχπγ",
9184, "͵θρπδ",
- 9999, "͵θϡϙθ",
- 20000, "αΜβʹ",
- 20001, "αΜβʹ, αʹ",
- 97554, "αΜθʹ, ͵ζφνδ",
- 99999, "αΜθʹ, ͵θϡϙθ",
- 1000000, "αΜρʹ",
- 1000001, "αΜρʹ, αʹ",
- 1999999, "αΜρϙθʹ, ͵θϡϙθ",
- 2345678, "αΜσλδʹ, ͵εχοη",
- 9999999, "αΜϡϙθʹ, ͵θϡϙθ",
- 10000000, "αΜ͵α",
- 90000001, "αΜ͵θ, αʹ",
- 100000000, "βΜαʹ",
- 1000000000, "βΜιʹ",
- 2000000000, "βΜκʹ",
- 2000000001, "βΜκʹ, αʹ",
- 2000010001, "βΜκʹ, αΜαʹ, αʹ",
- 2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ",
- 12312398676, "βΜρκγʹ, αΜ͵ασλθ, ͵ηχοϛ",
+ 9999, "͵θϡϟθ",
)
#t(
pat: sym.Alpha,
- "𐆊", "Αʹ", "Βʹ", "Γʹ", "Δʹ", "Εʹ", "Ϛʹ", "Ζʹ", "Ηʹ", "Θʹ", "Ιʹ",
- "ΙΑʹ", "ΙΒʹ", "ΙΓʹ", "ΙΔʹ", "ΙΕʹ", "ΙϚʹ", "ΙΖʹ", "ΙΗʹ", "ΙΘʹ", "Κʹ",
- 241, "ΣΜΑʹ",
+ "𐆊", "Α", "Β", "Γ", "Δ", "Ε", "Ϛ", "Ζ", "Η", "Θ", "Ι",
+ "ΙΑ", "ΙΒ", "ΙΓ", "ΙΔ", "ΙΕ", "ΙϚ", "ΙΖ", "ΙΗ", "ΙΘ", "Κ",
+ 241, "ΣΜΑ",
)
// Symbols.
#t(pat: "*", "-", "*", "†", "‡", "§", "¶", "‖", "**")
// Hebrew.
-#t(pat: "א", step: 2, 9, "ט׳", "י״א", "י״ג")
+#t(pat: "א", step: 2, 9, "ט", "יא", "יג", 15, "טו", 16, "טז")
// Chinese.
#t(pat: "一", step: 2, 9, "九", "十一", "十三", "十五", "十七", "十九")