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}