typst_library/model/
numbering.rs

1use std::str::FromStr;
2
3use chinese_number::{
4    ChineseCase, ChineseVariant, from_u64_to_chinese_ten_thousand as u64_to_chinese,
5};
6use comemo::Tracked;
7use ecow::{EcoString, EcoVec, eco_format};
8
9use crate::diag::SourceResult;
10use crate::engine::Engine;
11use crate::foundations::{Context, Func, Str, Value, cast, func};
12
13/// Applies a numbering to a sequence of numbers.
14///
15/// A numbering defines how a sequence of numbers should be displayed as
16/// content. It is defined either through a pattern string or an arbitrary
17/// function.
18///
19/// A numbering pattern consists of counting symbols, for which the actual
20/// number is substituted, their prefixes, and one suffix. The prefixes and the
21/// suffix are displayed as-is.
22///
23/// # Example
24/// ```example
25/// #numbering("1.1)", 1, 2, 3) \
26/// #numbering("1.a.i", 1, 2) \
27/// #numbering("I – 1", 12, 2) \
28/// #numbering(
29///   (..nums) => nums
30///     .pos()
31///     .map(str)
32///     .join(".") + ")",
33///   1, 2, 3,
34/// )
35/// ```
36///
37/// # Numbering patterns and numbering functions
38/// There are multiple instances where you can provide a numbering pattern or
39/// function in Typst. For example, when defining how to number
40/// [headings]($heading) or [figures]($figure). Every time, the expected format
41/// is the same as the one described below for the
42/// [`numbering`]($numbering.numbering) parameter.
43///
44/// The following example illustrates that a numbering function is just a
45/// regular [function] that accepts numbers and returns [`content`].
46/// ```example
47/// #let unary(.., last) = "|" * last
48/// #set heading(numbering: unary)
49/// = First heading
50/// = Second heading
51/// = Third heading
52/// ```
53#[func]
54pub fn numbering(
55    engine: &mut Engine,
56    context: Tracked<Context>,
57    /// Defines how the numbering works.
58    ///
59    /// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `α`, `Α`, `一`, `壹`,
60    /// `あ`, `い`, `ア`, `イ`, `א`, `가`, `ㄱ`, `*`, `١`, `۱`, `१`, `১`, `ক`,
61    /// `①`, and `⓵`. They are replaced by the number in the sequence,
62    /// preserving the original case.
63    ///
64    /// The `*` character means that symbols should be used to count, in the
65    /// order of `*`, `†`, `‡`, `§`, `¶`, `‖`. If there are more than six
66    /// items, the number is represented using repeated symbols.
67    ///
68    /// **Suffixes** are all characters after the last counting symbol. They are
69    /// displayed as-is at the end of any rendered number.
70    ///
71    /// **Prefixes** are all characters that are neither counting symbols nor
72    /// suffixes. They are displayed as-is at in front of their rendered
73    /// equivalent of their counting symbol.
74    ///
75    /// This parameter can also be an arbitrary function that gets each number
76    /// as an individual argument. When given a function, the `numbering`
77    /// function just forwards the arguments to that function. While this is not
78    /// particularly useful in itself, it means that you can just give arbitrary
79    /// numberings to the `numbering` function without caring whether they are
80    /// defined as a pattern or function.
81    numbering: Numbering,
82    /// The numbers to apply the numbering to. Must be non-negative.
83    ///
84    /// In general, numbers are counted from one. A number of zero indicates
85    /// that the first element has not yet appeared.
86    ///
87    /// If `numbering` is a pattern and more numbers than counting symbols are
88    /// given, the last counting symbol with its prefix is repeated.
89    #[variadic]
90    numbers: Vec<u64>,
91) -> SourceResult<Value> {
92    numbering.apply(engine, context, &numbers)
93}
94
95/// How to number a sequence of things.
96#[derive(Debug, Clone, PartialEq, Hash)]
97pub enum Numbering {
98    /// A pattern with prefix, numbering, lower / upper case and suffix.
99    Pattern(NumberingPattern),
100    /// A closure mapping from an item's number to content.
101    Func(Func),
102}
103
104impl Numbering {
105    /// Apply the pattern to the given numbers.
106    pub fn apply(
107        &self,
108        engine: &mut Engine,
109        context: Tracked<Context>,
110        numbers: &[u64],
111    ) -> SourceResult<Value> {
112        Ok(match self {
113            Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()),
114            Self::Func(func) => func.call(engine, context, numbers.iter().copied())?,
115        })
116    }
117
118    /// Trim the prefix suffix if this is a pattern.
119    pub fn trimmed(mut self) -> Self {
120        if let Self::Pattern(pattern) = &mut self {
121            pattern.trimmed = true;
122        }
123        self
124    }
125}
126
127impl From<NumberingPattern> for Numbering {
128    fn from(pattern: NumberingPattern) -> Self {
129        Self::Pattern(pattern)
130    }
131}
132
133cast! {
134    Numbering,
135    self => match self {
136        Self::Pattern(pattern) => pattern.into_value(),
137        Self::Func(func) => func.into_value(),
138    },
139    v: NumberingPattern => Self::Pattern(v),
140    v: Func => Self::Func(v),
141}
142
143/// How to turn a number into text.
144///
145/// A pattern consists of a prefix, followed by one of the counter symbols (see
146/// [`numbering()`] docs), and then a suffix.
147///
148/// Examples of valid patterns:
149/// - `1)`
150/// - `a.`
151/// - `(I)`
152#[derive(Debug, Clone, Eq, PartialEq, Hash)]
153pub struct NumberingPattern {
154    pub pieces: EcoVec<(EcoString, NumberingKind)>,
155    pub suffix: EcoString,
156    trimmed: bool,
157}
158
159impl NumberingPattern {
160    /// Apply the pattern to the given number.
161    pub fn apply(&self, numbers: &[u64]) -> EcoString {
162        let mut fmt = EcoString::new();
163        let mut numbers = numbers.iter();
164
165        for (i, ((prefix, kind), &n)) in self.pieces.iter().zip(&mut numbers).enumerate()
166        {
167            if i > 0 || !self.trimmed {
168                fmt.push_str(prefix);
169            }
170            fmt.push_str(&kind.apply(n));
171        }
172
173        for ((prefix, kind), &n) in self.pieces.last().into_iter().cycle().zip(numbers) {
174            if prefix.is_empty() {
175                fmt.push_str(&self.suffix);
176            } else {
177                fmt.push_str(prefix);
178            }
179            fmt.push_str(&kind.apply(n));
180        }
181
182        if !self.trimmed {
183            fmt.push_str(&self.suffix);
184        }
185
186        fmt
187    }
188
189    /// Apply only the k-th segment of the pattern to a number.
190    pub fn apply_kth(&self, k: usize, number: u64) -> EcoString {
191        let mut fmt = EcoString::new();
192        if let Some((prefix, _)) = self.pieces.first() {
193            fmt.push_str(prefix);
194        }
195        if let Some((_, kind)) = self
196            .pieces
197            .iter()
198            .chain(self.pieces.last().into_iter().cycle())
199            .nth(k)
200        {
201            fmt.push_str(&kind.apply(number));
202        }
203        fmt.push_str(&self.suffix);
204        fmt
205    }
206
207    /// How many counting symbols this pattern has.
208    pub fn pieces(&self) -> usize {
209        self.pieces.len()
210    }
211}
212
213impl FromStr for NumberingPattern {
214    type Err = &'static str;
215
216    fn from_str(pattern: &str) -> Result<Self, Self::Err> {
217        let mut pieces = EcoVec::new();
218        let mut handled = 0;
219
220        for (i, c) in pattern.char_indices() {
221            let Some(kind) = NumberingKind::from_char(c) else {
222                continue;
223            };
224
225            let prefix = pattern[handled..i].into();
226            pieces.push((prefix, kind));
227            handled = c.len_utf8() + i;
228        }
229
230        let suffix = pattern[handled..].into();
231        if pieces.is_empty() {
232            return Err("invalid numbering pattern");
233        }
234
235        Ok(Self { pieces, suffix, trimmed: false })
236    }
237}
238
239cast! {
240    NumberingPattern,
241    self => {
242        let mut pat = EcoString::new();
243        for (prefix, kind) in &self.pieces {
244            pat.push_str(prefix);
245            pat.push(kind.to_char());
246        }
247        pat.push_str(&self.suffix);
248        pat.into_value()
249    },
250    v: Str => v.parse()?,
251}
252
253/// Different kinds of numberings.
254#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
255pub enum NumberingKind {
256    /// Arabic numerals (1, 2, 3, etc.).
257    Arabic,
258    /// Lowercase Latin letters (a, b, c, etc.). Items beyond z use base-26.
259    LowerLatin,
260    /// Uppercase Latin letters (A, B, C, etc.). Items beyond Z use base-26.
261    UpperLatin,
262    /// Lowercase Roman numerals (i, ii, iii, etc.).
263    LowerRoman,
264    /// Uppercase Roman numerals (I, II, III, etc.).
265    UpperRoman,
266    /// Lowercase Greek letters (α, β, γ, etc.).
267    LowerGreek,
268    /// Uppercase Greek letters (Α, Β, Γ, etc.).
269    UpperGreek,
270    /// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use
271    /// repeated symbols.
272    Symbol,
273    /// Hebrew numerals, including Geresh/Gershayim.
274    Hebrew,
275    /// Simplified Chinese standard numerals. This corresponds to the
276    /// `ChineseCase::Lower` variant.
277    LowerSimplifiedChinese,
278    /// Simplified Chinese "banknote" numerals. This corresponds to the
279    /// `ChineseCase::Upper` variant.
280    UpperSimplifiedChinese,
281    // TODO: Pick the numbering pattern based on languages choice.
282    // As the first character of Simplified and Traditional Chinese numbering
283    // are the same, we are unable to determine if the context requires
284    // Simplified or Traditional by only looking at this character.
285    #[allow(unused)]
286    /// Traditional Chinese standard numerals. This corresponds to the
287    /// `ChineseCase::Lower` variant.
288    LowerTraditionalChinese,
289    #[allow(unused)]
290    /// Traditional Chinese "banknote" numerals. This corresponds to the
291    /// `ChineseCase::Upper` variant.
292    UpperTraditionalChinese,
293    /// Hiragana in the gojūon order. Includes n but excludes wi and we.
294    HiraganaAiueo,
295    /// Hiragana in the iroha order. Includes wi and we but excludes n.
296    HiraganaIroha,
297    /// Katakana in the gojūon order. Includes n but excludes wi and we.
298    KatakanaAiueo,
299    /// Katakana in the iroha order. Includes wi and we but excludes n.
300    KatakanaIroha,
301    /// Korean jamo (ㄱ, ㄴ, ㄷ, etc.).
302    KoreanJamo,
303    /// Korean syllables (가, 나, 다, etc.).
304    KoreanSyllable,
305    /// Eastern Arabic numerals, used in some Arabic-speaking countries.
306    EasternArabic,
307    /// The variant of Eastern Arabic numerals used in Persian and Urdu.
308    EasternArabicPersian,
309    /// Devanagari numerals.
310    DevanagariNumber,
311    /// Bengali numerals.
312    BengaliNumber,
313    /// Bengali letters (ক, খ, গ, ...কক, কখ etc.).
314    BengaliLetter,
315    /// Circled numbers (①, ②, ③, etc.), up to 50.
316    CircledNumber,
317    /// Double-circled numbers (⓵, ⓶, ⓷, etc.), up to 10.
318    DoubleCircledNumber,
319}
320
321impl NumberingKind {
322    /// Create a numbering kind from a representative character.
323    pub fn from_char(c: char) -> Option<Self> {
324        Some(match c {
325            '1' => NumberingKind::Arabic,
326            'a' => NumberingKind::LowerLatin,
327            'A' => NumberingKind::UpperLatin,
328            'i' => NumberingKind::LowerRoman,
329            'I' => NumberingKind::UpperRoman,
330            'α' => NumberingKind::LowerGreek,
331            'Α' => NumberingKind::UpperGreek,
332            '*' => NumberingKind::Symbol,
333            'א' => NumberingKind::Hebrew,
334            '一' => NumberingKind::LowerSimplifiedChinese,
335            '壹' => NumberingKind::UpperSimplifiedChinese,
336            'あ' => NumberingKind::HiraganaAiueo,
337            'い' => NumberingKind::HiraganaIroha,
338            'ア' => NumberingKind::KatakanaAiueo,
339            'イ' => NumberingKind::KatakanaIroha,
340            'ㄱ' => NumberingKind::KoreanJamo,
341            '가' => NumberingKind::KoreanSyllable,
342            '\u{0661}' => NumberingKind::EasternArabic,
343            '\u{06F1}' => NumberingKind::EasternArabicPersian,
344            '\u{0967}' => NumberingKind::DevanagariNumber,
345            '\u{09E7}' => NumberingKind::BengaliNumber,
346            '\u{0995}' => NumberingKind::BengaliLetter,
347            '①' => NumberingKind::CircledNumber,
348            '⓵' => NumberingKind::DoubleCircledNumber,
349            _ => return None,
350        })
351    }
352
353    /// The representative character for this numbering kind.
354    pub fn to_char(self) -> char {
355        match self {
356            Self::Arabic => '1',
357            Self::LowerLatin => 'a',
358            Self::UpperLatin => 'A',
359            Self::LowerRoman => 'i',
360            Self::UpperRoman => 'I',
361            Self::LowerGreek => 'α',
362            Self::UpperGreek => 'Α',
363            Self::Symbol => '*',
364            Self::Hebrew => 'א',
365            Self::LowerSimplifiedChinese | Self::LowerTraditionalChinese => '一',
366            Self::UpperSimplifiedChinese | Self::UpperTraditionalChinese => '壹',
367            Self::HiraganaAiueo => 'あ',
368            Self::HiraganaIroha => 'い',
369            Self::KatakanaAiueo => 'ア',
370            Self::KatakanaIroha => 'イ',
371            Self::KoreanJamo => 'ㄱ',
372            Self::KoreanSyllable => '가',
373            Self::EasternArabic => '\u{0661}',
374            Self::EasternArabicPersian => '\u{06F1}',
375            Self::DevanagariNumber => '\u{0967}',
376            Self::BengaliNumber => '\u{09E7}',
377            Self::BengaliLetter => '\u{0995}',
378            Self::CircledNumber => '①',
379            Self::DoubleCircledNumber => '⓵',
380        }
381    }
382
383    /// Apply the numbering to the given number.
384    pub fn apply(self, n: u64) -> EcoString {
385        match self {
386            Self::Arabic => {
387                numeric(&['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n)
388            }
389            Self::LowerRoman => additive(
390                &[
391                    ("m̅", 1000000),
392                    ("d̅", 500000),
393                    ("c̅", 100000),
394                    ("l̅", 50000),
395                    ("x̅", 10000),
396                    ("v̅", 5000),
397                    ("i̅v̅", 4000),
398                    ("m", 1000),
399                    ("cm", 900),
400                    ("d", 500),
401                    ("cd", 400),
402                    ("c", 100),
403                    ("xc", 90),
404                    ("l", 50),
405                    ("xl", 40),
406                    ("x", 10),
407                    ("ix", 9),
408                    ("v", 5),
409                    ("iv", 4),
410                    ("i", 1),
411                    ("n", 0),
412                ],
413                n,
414            ),
415            Self::UpperRoman => additive(
416                &[
417                    ("M̅", 1000000),
418                    ("D̅", 500000),
419                    ("C̅", 100000),
420                    ("L̅", 50000),
421                    ("X̅", 10000),
422                    ("V̅", 5000),
423                    ("I̅V̅", 4000),
424                    ("M", 1000),
425                    ("CM", 900),
426                    ("D", 500),
427                    ("CD", 400),
428                    ("C", 100),
429                    ("XC", 90),
430                    ("L", 50),
431                    ("XL", 40),
432                    ("X", 10),
433                    ("IX", 9),
434                    ("V", 5),
435                    ("IV", 4),
436                    ("I", 1),
437                    ("N", 0),
438                ],
439                n,
440            ),
441            Self::LowerGreek => additive(
442                &[
443                    ("͵θ", 9000),
444                    ("͵η", 8000),
445                    ("͵ζ", 7000),
446                    ("͵ϛ", 6000),
447                    ("͵ε", 5000),
448                    ("͵δ", 4000),
449                    ("͵γ", 3000),
450                    ("͵β", 2000),
451                    ("͵α", 1000),
452                    ("ϡ", 900),
453                    ("ω", 800),
454                    ("ψ", 700),
455                    ("χ", 600),
456                    ("φ", 500),
457                    ("υ", 400),
458                    ("τ", 300),
459                    ("σ", 200),
460                    ("ρ", 100),
461                    ("ϟ", 90),
462                    ("π", 80),
463                    ("ο", 70),
464                    ("ξ", 60),
465                    ("ν", 50),
466                    ("μ", 40),
467                    ("λ", 30),
468                    ("κ", 20),
469                    ("ι", 10),
470                    ("θ", 9),
471                    ("η", 8),
472                    ("ζ", 7),
473                    ("ϛ", 6),
474                    ("ε", 5),
475                    ("δ", 4),
476                    ("γ", 3),
477                    ("β", 2),
478                    ("α", 1),
479                    ("𐆊", 0),
480                ],
481                n,
482            ),
483            Self::UpperGreek => additive(
484                &[
485                    ("͵Θ", 9000),
486                    ("͵Η", 8000),
487                    ("͵Ζ", 7000),
488                    ("͵Ϛ", 6000),
489                    ("͵Ε", 5000),
490                    ("͵Δ", 4000),
491                    ("͵Γ", 3000),
492                    ("͵Β", 2000),
493                    ("͵Α", 1000),
494                    ("Ϡ", 900),
495                    ("Ω", 800),
496                    ("Ψ", 700),
497                    ("Χ", 600),
498                    ("Φ", 500),
499                    ("Υ", 400),
500                    ("Τ", 300),
501                    ("Σ", 200),
502                    ("Ρ", 100),
503                    ("Ϟ", 90),
504                    ("Π", 80),
505                    ("Ο", 70),
506                    ("Ξ", 60),
507                    ("Ν", 50),
508                    ("Μ", 40),
509                    ("Λ", 30),
510                    ("Κ", 20),
511                    ("Ι", 10),
512                    ("Θ", 9),
513                    ("Η", 8),
514                    ("Ζ", 7),
515                    ("Ϛ", 6),
516                    ("Ε", 5),
517                    ("Δ", 4),
518                    ("Γ", 3),
519                    ("Β", 2),
520                    ("Α", 1),
521                    ("𐆊", 0),
522                ],
523                n,
524            ),
525            Self::Hebrew => additive(
526                &[
527                    ("ת", 400),
528                    ("ש", 300),
529                    ("ר", 200),
530                    ("ק", 100),
531                    ("צ", 90),
532                    ("פ", 80),
533                    ("ע", 70),
534                    ("ס", 60),
535                    ("נ", 50),
536                    ("מ", 40),
537                    ("ל", 30),
538                    ("כ", 20),
539                    ("יט", 19),
540                    ("יח", 18),
541                    ("יז", 17),
542                    ("טז", 16),
543                    ("טו", 15),
544                    ("י", 10),
545                    ("ט", 9),
546                    ("ח", 8),
547                    ("ז", 7),
548                    ("ו", 6),
549                    ("ה", 5),
550                    ("ד", 4),
551                    ("ג", 3),
552                    ("ב", 2),
553                    ("א", 1),
554                    ("-", 0),
555                ],
556                n,
557            ),
558            Self::LowerLatin => alphabetic(
559                &[
560                    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
561                    'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
562                ],
563                n,
564            ),
565            Self::UpperLatin => alphabetic(
566                &[
567                    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
568                    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
569                ],
570                n,
571            ),
572            Self::HiraganaAiueo => alphabetic(
573                &[
574                    'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ',
575                    'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に',
576                    'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む',
577                    'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ',
578                    'を', 'ん',
579                ],
580                n,
581            ),
582            Self::HiraganaIroha => alphabetic(
583                &[
584                    'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る',
585                    'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら',
586                    'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ',
587                    'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ',
588                    'も', 'せ', 'す',
589                ],
590                n,
591            ),
592            Self::KatakanaAiueo => alphabetic(
593                &[
594                    'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ',
595                    'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ',
596                    'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム',
597                    'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ',
598                    'ヲ', 'ン',
599                ],
600                n,
601            ),
602            Self::KatakanaIroha => alphabetic(
603                &[
604                    'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル',
605                    'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ',
606                    'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ',
607                    'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ',
608                    'モ', 'セ', 'ス',
609                ],
610                n,
611            ),
612            Self::KoreanJamo => alphabetic(
613                &[
614                    'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ',
615                    'ㅌ', 'ㅍ', 'ㅎ',
616                ],
617                n,
618            ),
619            Self::KoreanSyllable => alphabetic(
620                &[
621                    '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카',
622                    '타', '파', '하',
623                ],
624                n,
625            ),
626            Self::BengaliLetter => alphabetic(
627                &[
628                    'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ',
629                    'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল',
630                    'শ', 'ষ', 'স', 'হ',
631                ],
632                n,
633            ),
634            Self::CircledNumber => fixed(
635                &[
636                    '⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬',
637                    '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕',
638                    '㉖', '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱',
639                    '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼',
640                    '㊽', '㊾', '㊿',
641                ],
642                n,
643            ),
644            Self::DoubleCircledNumber => {
645                fixed(&['0', '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n)
646            }
647
648            Self::LowerSimplifiedChinese => {
649                u64_to_chinese(ChineseVariant::Simple, ChineseCase::Lower, n).into()
650            }
651            Self::UpperSimplifiedChinese => {
652                u64_to_chinese(ChineseVariant::Simple, ChineseCase::Upper, n).into()
653            }
654            Self::LowerTraditionalChinese => {
655                u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Lower, n).into()
656            }
657            Self::UpperTraditionalChinese => {
658                u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n).into()
659            }
660
661            Self::EasternArabic => {
662                numeric(&['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], n)
663            }
664            Self::EasternArabicPersian => {
665                numeric(&['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], n)
666            }
667            Self::DevanagariNumber => {
668                numeric(&['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], n)
669            }
670            Self::BengaliNumber => {
671                numeric(&['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], n)
672            }
673            Self::Symbol => symbolic(&['*', '†', '‡', '§', '¶', '‖'], n),
674        }
675    }
676}
677
678/// Stringify a number using symbols representing values. The decimal
679/// representation of the number is recovered by summing over the values of the
680/// symbols present.
681///
682/// Consider the situation where ['I': 1, 'IV': 4, 'V': 5],
683///
684/// ```text
685/// 1 => 'I'
686/// 2 => 'II'
687/// 3 => 'III'
688/// 4 => 'IV'
689/// 5 => 'V'
690/// 6 => 'VI'
691/// 7 => 'VII'
692/// 8 => 'VIII'
693/// ```
694///
695/// where this is the start of the familiar Roman numeral system.
696fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString {
697    if n == 0 {
698        if let Some(&(symbol, 0)) = symbols.last() {
699            return symbol.into();
700        }
701        return '0'.into();
702    }
703
704    let mut s = EcoString::new();
705    for (symbol, weight) in symbols {
706        if *weight == 0 || *weight > n {
707            continue;
708        }
709        let reps = n / weight;
710        for _ in 0..reps {
711            s.push_str(symbol);
712        }
713
714        n -= weight * reps;
715        if n == 0 {
716            return s;
717        }
718    }
719    s
720}
721
722/// Stringify a number using a base-n (where n is the number of provided
723/// symbols) system without a zero symbol.
724///
725/// Consider the situation where ['A', 'B', 'C'] are the provided symbols,
726///
727/// ```text
728/// 1 => 'A'
729/// 2 => 'B'
730/// 3 => 'C'
731/// 4 => 'AA
732/// 5 => 'AB'
733/// 6 => 'AC'
734/// 7 => 'BA'
735/// ...
736/// ```
737///
738/// This system is commonly used in spreadsheet software.
739fn alphabetic(symbols: &[char], mut n: u64) -> EcoString {
740    let n_digits = symbols.len() as u64;
741    if n == 0 {
742        return '-'.into();
743    }
744    let mut s = EcoString::new();
745    while n != 0 {
746        n -= 1;
747        s.push(symbols[(n % n_digits) as usize]);
748        n /= n_digits;
749    }
750    s.chars().rev().collect()
751}
752
753/// Stringify a number using the symbols provided, defaulting to the arabic
754/// representation when the number is greater than the number of symbols.
755///
756/// Consider the situation where ['0', 'A', 'B', 'C'] are the provided symbols,
757///
758/// ```text
759/// 0 => '0'
760/// 1 => 'A'
761/// 2 => 'B'
762/// 3 => 'C'
763/// 4 => '4'
764/// ...
765/// n => 'n'
766/// ```
767fn fixed(symbols: &[char], n: u64) -> EcoString {
768    let n_digits = symbols.len() as u64;
769    if n < n_digits {
770        return symbols[(n) as usize].into();
771    }
772    eco_format!("{n}")
773}
774
775/// Stringify a number using a base-n (where n is the number of provided
776/// symbols) system with a zero symbol.
777///
778/// Consider the situation where ['0', '1', '2'] are the provided symbols,
779///
780/// ```text
781/// 0 => '0'
782/// 1 => '1'
783/// 2 => '2'
784/// 3 => '10'
785/// 4 => '11'
786/// 5 => '12'
787/// 6 => '20'
788/// ...
789/// ```
790///
791/// which is the familiar trinary counting system.
792fn numeric(symbols: &[char], mut n: u64) -> EcoString {
793    let n_digits = symbols.len() as u64;
794    if n == 0 {
795        return symbols[0].into();
796    }
797    let mut s = EcoString::new();
798    while n != 0 {
799        s.push(symbols[(n % n_digits) as usize]);
800        n /= n_digits;
801    }
802    s.chars().rev().collect()
803}
804
805/// Stringify a number using repeating symbols.
806///
807/// Consider the situation where ['A', 'B', 'C'] are the provided symbols,
808///
809/// ```text
810/// 0 => '-'
811/// 1 => 'A'
812/// 2 => 'B'
813/// 3 => 'C'
814/// 4 => 'AA'
815/// 5 => 'BB'
816/// 6 => 'CC'
817/// 7 => 'AAA'
818/// ...
819/// ```
820fn symbolic(symbols: &[char], n: u64) -> EcoString {
821    let n_digits = symbols.len() as u64;
822    if n == 0 {
823        return '-'.into();
824    }
825    EcoString::from(symbols[((n - 1) % n_digits) as usize])
826        .repeat((n.div_ceil(n_digits)) as usize)
827}