From 29e1d7fd5db6f9d0f4ec6b6dd2907b8d8c61a1ac Mon Sep 17 00:00:00 2001 From: Samuel Ireson Date: Mon, 31 Mar 2025 15:23:00 +0100 Subject: [PATCH 1/5] initial refactor to use standard numbering fns --- crates/typst-library/src/model/numbering.rs | 538 +++++++++----------- 1 file changed, 233 insertions(+), 305 deletions(-) diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index d82c3e4cd..5417e15ee 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. /// @@ -382,38 +381,180 @@ impl NumberingKind { 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::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, + ), + 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, + ), + Self::LowerGreek => additive( + [ + ("ϡ", 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( + [ + ("Ϡ", 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::Symbol => symbolic(['*', '†', '‡', '§', '¶', '‖'], n), + Self::Hebrew => additive( + [ + ("י׳", 10000), + ("ט׳", 9000), + ("ח׳", 8000), + ("ז׳", 7000), + ("ו׳", 6000), + ("ה׳", 5000), + ("ד׳", 4000), + ("ג׳", 3000), + ("ב׳", 2000), + ("א׳", 1000), + ("ת", 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), + ], + 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,7 +564,7 @@ impl NumberingKind { ], n, ), - Self::HiraganaIroha => zeroless( + Self::HiraganaIroha => alphabetic( [ 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', @@ -433,7 +574,7 @@ impl NumberingKind { ], n, ), - Self::KatakanaAiueo => zeroless( + Self::KatakanaAiueo => alphabetic( [ 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', @@ -443,7 +584,7 @@ impl NumberingKind { ], n, ), - Self::KatakanaIroha => zeroless( + Self::KatakanaIroha => alphabetic( [ 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', @@ -453,21 +594,21 @@ impl NumberingKind { ], n, ), - Self::KoreanJamo => zeroless( + Self::KoreanJamo => alphabetic( [ 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ', ], n, ), - Self::KoreanSyllable => zeroless( + Self::KoreanSyllable => alphabetic( [ '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', '타', '파', '하', ], n, ), - Self::BengaliLetter => zeroless( + Self::BengaliLetter => alphabetic( [ 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ', 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল', @@ -475,7 +616,7 @@ impl NumberingKind { ], n, ), - Self::CircledNumber => zeroless( + Self::CircledNumber => fixed( [ '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', '㉖', @@ -486,7 +627,7 @@ impl NumberingKind { n, ), Self::DoubleCircledNumber => { - zeroless(['⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n) + fixed(['⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n) } Self::LowerSimplifiedChinese => { @@ -502,306 +643,93 @@ 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) } } } - - fmt } -/// Stringify an integer to Greek numbers. -/// -/// 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]. -/// -/// [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 = [ - ["α", "Α"], - ["β", "Β"], - ["γ", "Γ"], - ["δ", "Δ"], - ["ε", "Ε"], - ["ϛ", "Ϛ"], - ["ζ", "Ζ"], - ["η", "Η"], - ["θ", "Θ"], - ]; - +fn additive( + symbols: [(&str, u64); N_DIGITS], + 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 = 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]) + for (symbol, weight) in symbols { + if weight == 0 { + return (*symbol).into(); + } } - }; + return '0'.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); - - // `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("Μ"); + n -= weight * reps; + if n == 0 { + return s; } - 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); - } - // 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. -/// -/// This is best explained by example. Suppose our digits are 'A', 'B', and 'C'. -/// We would get the following: -/// -/// ```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. -/// ``` -/// -/// You might be familiar with this scheme from the way spreadsheet software -/// tends to label its columns. -fn zeroless(alphabet: [char; N_DIGITS], mut n: u64) -> EcoString { +fn alphabetic(symbols: [char; N_DIGITS], mut n: u64) -> EcoString { + let n_digits = N_DIGITS 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 a base-10 counting system with a zero digit. -/// -/// This function assumes that the digits occupy contiguous codepoints. -fn decimal(start: char, mut n: u64) -> EcoString { - if n == 0 { - return start.into(); +fn fixed(symbols: [char; N_DIGITS], n: u64) -> EcoString { + let n_digits = N_DIGITS as u64; + if n - 1 < n_digits { + return symbols[(n - 1) as usize].into(); } - let mut cs = EcoString::new(); - while n > 0 { - cs.push(char::from_u32((start as u32) + ((n % 10) as u32)).unwrap()); - n /= 10; - } - cs.chars().rev().collect() + eco_format!("{n}") +} + +fn numeric(symbols: [char; N_DIGITS], mut n: u64) -> EcoString { + let n_digits = N_DIGITS as u64; + if n == 0 { + return symbols[0].into(); + } + let mut s = EcoString::new(); + while n != 0 { + s.push(symbols[(n % n_digits) as usize]); + n /= n_digits; + } + s.chars().rev().collect() +} + +fn symbolic(symbols: [char; N_DIGITS], n: u64) -> EcoString { + let n_digits = N_DIGITS as u64; + if n == 0 { + return '-'.into(); + } + EcoString::from(symbols[((n - 1) % n_digits) as usize]) + .repeat((n.div_ceil(n_digits)) as usize) } From 01d45e981b50bdddee49ee9287e5ecc155ff8734 Mon Sep 17 00:00:00 2001 From: Samuel Ireson Date: Mon, 31 Mar 2025 15:45:34 +0100 Subject: [PATCH 2/5] tests passing --- crates/typst-library/src/model/numbering.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index 5417e15ee..add7ba3f7 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -497,7 +497,6 @@ impl NumberingKind { ], n, ), - Self::Symbol => symbolic(['*', '†', '‡', '§', '¶', '‖'], n), Self::Hebrew => additive( [ ("י׳", 10000), @@ -655,6 +654,7 @@ impl NumberingKind { Self::BengaliNumber => { numeric(['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], n) } + Self::Symbol => symbolic(['*', '†', '‡', '§', '¶', '‖'], n), } } } From c3873b199dd161ab65451b83bcde1f0a6473c82d Mon Sep 17 00:00:00 2001 From: Samuel Ireson Date: Wed, 2 Apr 2025 09:17:24 +0100 Subject: [PATCH 3/5] use 'n' and 'N' for romain 0 --- crates/typst-library/src/model/numbering.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index add7ba3f7..d8cce1664 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -403,6 +403,7 @@ impl NumberingKind { ("v", 5), ("iv", 4), ("i", 1), + ("n", 0), ], n, ), @@ -428,6 +429,7 @@ impl NumberingKind { ("V", 5), ("IV", 4), ("I", 1), + ("N", 0), ], n, ), From fb9f000adaecd7bf12cf4b43fa7101857ece467e Mon Sep 17 00:00:00 2001 From: Samuel Ireson Date: Thu, 3 Apr 2025 09:56:40 +0100 Subject: [PATCH 4/5] add comments for each numbering style include higher greek terms to support more numbers. --- crates/typst-library/src/model/numbering.rs | 100 +++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index d8cce1664..fb388fd18 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -380,7 +380,9 @@ impl NumberingKind { /// Apply the numbering to the given number. pub fn apply(self, n: u64) -> EcoString { match self { - Self::Arabic => eco_format!("{n}"), + Self::Arabic => { + numeric(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n) + } Self::LowerRoman => additive( [ ("m̅", 1000000), @@ -435,6 +437,15 @@ impl NumberingKind { ), Self::LowerGreek => additive( [ + ("͵θ", 9000), + ("͵η", 8000), + ("͵ζ", 7000), + ("͵ϛ", 6000), + ("͵ε", 5000), + ("͵δ", 4000), + ("͵γ", 3000), + ("͵β", 2000), + ("͵α", 1000), ("ϡ", 900), ("ω", 800), ("ψ", 700), @@ -468,6 +479,15 @@ impl NumberingKind { ), Self::UpperGreek => additive( [ + ("͵Θ", 9000), + ("͵Η", 8000), + ("͵Ζ", 7000), + ("͵Ϛ", 6000), + ("͵Ε", 5000), + ("͵Δ", 4000), + ("͵Γ", 3000), + ("͵Β", 2000), + ("͵Α", 1000), ("Ϡ", 900), ("Ω", 800), ("Ψ", 700), @@ -538,6 +558,7 @@ impl NumberingKind { ("ג", 3), ("ב", 2), ("א", 1), + ("-", 0), ], n, ), @@ -661,6 +682,23 @@ impl NumberingKind { } } +/// Stringify a number using symbols representing values. The decimal representation of the number +/// is recovered by summing over the values of the symbols present. +/// +/// Consider the situation where ['I': 1, 'IV': 4, 'V': 5], +/// +/// ```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); N_DIGITS], mut n: u64, @@ -692,6 +730,23 @@ fn additive( s } +/// Stringify a number using a base-n (where n is the number of provided symbols) system without a +/// zero symbol. +/// +/// 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' +/// ... +/// ``` +/// +/// This system is commonly used in spreadsheet software. fn alphabetic(symbols: [char; N_DIGITS], mut n: u64) -> EcoString { let n_digits = N_DIGITS as u64; if n == 0 { @@ -706,6 +761,19 @@ fn alphabetic(symbols: [char; N_DIGITS], mut n: u64) -> E 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 ['A', 'B', 'C'] are the provided symbols, +/// +/// ```text +/// 1 => 'A' +/// 2 => 'B' +/// 3 => 'C' +/// 4 => '4' +/// ... +/// n => 'n' +/// ``` fn fixed(symbols: [char; N_DIGITS], n: u64) -> EcoString { let n_digits = N_DIGITS as u64; if n - 1 < n_digits { @@ -714,6 +782,22 @@ fn fixed(symbols: [char; N_DIGITS], n: u64) -> EcoString eco_format!("{n}") } +/// 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, +/// +/// ```text +/// 1 => '1' +/// 2 => '2' +/// 3 => '10' +/// 4 => '11' +/// 5 => '12' +/// 6 => '20' +/// ... +/// ``` +/// +/// which is the familiar trinary counting system. fn numeric(symbols: [char; N_DIGITS], mut n: u64) -> EcoString { let n_digits = N_DIGITS as u64; if n == 0 { @@ -727,6 +811,20 @@ fn numeric(symbols: [char; N_DIGITS], mut n: u64) -> EcoS s.chars().rev().collect() } +/// Stringify a number using repeating symbols. +/// +/// Consider the situation where ['A', 'B', 'C'] are the provided symbols, +/// +/// ```text +/// 1 => 'A' +/// 2 => 'B' +/// 3 => 'C' +/// 4 => 'AA' +/// 5 => 'BB' +/// 6 => 'CC' +/// 7 => 'AAA' +/// ... +/// ``` fn symbolic(symbols: [char; N_DIGITS], n: u64) -> EcoString { let n_digits = N_DIGITS as u64; if n == 0 { From 7336066a9c4e16bb8b4f08ae436542d41cb3d6fc Mon Sep 17 00:00:00 2001 From: Samuel Ireson Date: Fri, 11 Apr 2025 13:53:17 +0100 Subject: [PATCH 5/5] tests passing --- tests/suite/model/numbering.typ | 42 ++++++++++----------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/tests/suite/model/numbering.typ b/tests/suite/model/numbering.typ index 6af989ff1..9b2373650 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, "ט", "יא", "יג") // Chinese. #t(pat: "一", step: 2, 9, "九", "十一", "十三", "十五", "十七", "十九")