summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoremilylime <emilyyyylime+git@gmail.com>2024-09-30 18:22:46 +0300
committerGitHub <noreply@github.com>2024-09-30 15:22:46 +0000
commit1a24b29d86dfe72e0d3c7f79475c187ed4df5e5a (patch)
tree280ecacae0fb11ed7eccc848c913b6f0238287f3
parent7ff83db757330899576b50b87968ca86f2539f7b (diff)
Remove `Case`s from NumberingPatterns (#5059)
-rw-r--r--crates/typst-pdf/src/page.rs28
-rw-r--r--crates/typst/src/model/numbering.rs524
2 files changed, 274 insertions, 278 deletions
diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs
index 7eac69fc..f112c81c 100644
--- a/crates/typst-pdf/src/page.rs
+++ b/crates/typst-pdf/src/page.rs
@@ -9,7 +9,6 @@ use typst::foundations::Label;
use typst::introspection::Location;
use typst::layout::{Abs, Page};
use typst::model::{Destination, Numbering};
-use typst::text::Case;
use crate::content;
use crate::{
@@ -246,26 +245,23 @@ impl PdfPageLabel {
return None;
};
- let (prefix, kind, case) = pat.pieces.first()?;
+ let (prefix, kind) = pat.pieces.first()?;
// If there is a suffix, we cannot use the common style optimisation,
// since PDF does not provide a suffix field.
- let mut style = None;
- if pat.suffix.is_empty() {
+ let style = if pat.suffix.is_empty() {
use {typst::model::NumberingKind as Kind, PdfPageLabelStyle as Style};
- match (kind, case) {
- (Kind::Arabic, _) => style = Some(Style::Arabic),
- (Kind::Roman, Case::Lower) => style = Some(Style::LowerRoman),
- (Kind::Roman, Case::Upper) => style = Some(Style::UpperRoman),
- (Kind::Letter, Case::Lower) if number <= 26 => {
- style = Some(Style::LowerAlpha)
- }
- (Kind::Letter, Case::Upper) if number <= 26 => {
- style = Some(Style::UpperAlpha)
- }
- _ => {}
+ match kind {
+ Kind::Arabic => Some(Style::Arabic),
+ Kind::LowerRoman => Some(Style::LowerRoman),
+ Kind::UpperRoman => Some(Style::UpperRoman),
+ Kind::LowerLatin if number <= 26 => Some(Style::LowerAlpha),
+ Kind::LowerLatin if number <= 26 => Some(Style::UpperAlpha),
+ _ => None,
}
- }
+ } else {
+ None
+ };
// Prefix and offset depend on the style: If it is supported by the PDF
// spec, we use the given prefix and an offset. Otherwise, everything
diff --git a/crates/typst/src/model/numbering.rs b/crates/typst/src/model/numbering.rs
index bc135a7b..f0aa06e5 100644
--- a/crates/typst/src/model/numbering.rs
+++ b/crates/typst/src/model/numbering.rs
@@ -1,9 +1,10 @@
use std::str::FromStr;
-use chinese_number::{ChineseCase, ChineseCountMethod, ChineseVariant, NumberToChinese};
+use chinese_number::{
+ from_usize_to_chinese_ten_thousand as usize_to_chinese, ChineseCase, ChineseVariant,
+};
use comemo::Tracked;
use ecow::{eco_format, EcoString, EcoVec};
-use smallvec::{smallvec, SmallVec};
use crate::diag::SourceResult;
use crate::engine::Engine;
@@ -150,7 +151,7 @@ cast! {
/// - `(I)`
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct NumberingPattern {
- pub pieces: EcoVec<(EcoString, NumberingKind, Case)>,
+ pub pieces: EcoVec<(EcoString, NumberingKind)>,
pub suffix: EcoString,
trimmed: bool,
}
@@ -161,24 +162,21 @@ impl NumberingPattern {
let mut fmt = EcoString::new();
let mut numbers = numbers.iter();
- for (i, ((prefix, kind, case), &n)) in
- self.pieces.iter().zip(&mut numbers).enumerate()
+ for (i, ((prefix, kind), &n)) in self.pieces.iter().zip(&mut numbers).enumerate()
{
if i > 0 || !self.trimmed {
fmt.push_str(prefix);
}
- fmt.push_str(&kind.apply(n, *case));
+ fmt.push_str(&kind.apply(n));
}
- for ((prefix, kind, case), &n) in
- self.pieces.last().into_iter().cycle().zip(numbers)
- {
+ for ((prefix, kind), &n) in self.pieces.last().into_iter().cycle().zip(numbers) {
if prefix.is_empty() {
fmt.push_str(&self.suffix);
} else {
fmt.push_str(prefix);
}
- fmt.push_str(&kind.apply(n, *case));
+ fmt.push_str(&kind.apply(n));
}
if !self.trimmed {
@@ -191,16 +189,16 @@ impl NumberingPattern {
/// Apply only the k-th segment of the pattern to a number.
pub fn apply_kth(&self, k: usize, number: usize) -> EcoString {
let mut fmt = EcoString::new();
- if let Some((prefix, _, _)) = self.pieces.first() {
+ if let Some((prefix, _)) = self.pieces.first() {
fmt.push_str(prefix);
}
- if let Some((_, kind, case)) = self
+ if let Some((_, kind)) = self
.pieces
.iter()
.chain(self.pieces.last().into_iter().cycle())
.nth(k)
{
- fmt.push_str(&kind.apply(number, *case));
+ fmt.push_str(&kind.apply(number));
}
fmt.push_str(&self.suffix);
fmt
@@ -220,14 +218,12 @@ impl FromStr for NumberingPattern {
let mut handled = 0;
for (i, c) in pattern.char_indices() {
- let Some(kind) = NumberingKind::from_char(c.to_ascii_lowercase()) else {
+ let Some(kind) = NumberingKind::from_char(c) else {
continue;
};
let prefix = pattern[handled..i].into();
- let case =
- if c.is_uppercase() || c == '壹' { Case::Upper } else { Case::Lower };
- pieces.push((prefix, kind, case));
+ pieces.push((prefix, kind));
handled = c.len_utf8() + i;
}
@@ -244,13 +240,9 @@ cast! {
NumberingPattern,
self => {
let mut pat = EcoString::new();
- for (prefix, kind, case) in &self.pieces {
+ for (prefix, kind) in &self.pieces {
pat.push_str(prefix);
- let mut c = kind.to_char();
- if *case == Case::Upper {
- c = c.to_ascii_uppercase();
- }
- pat.push(c);
+ pat.push(kind.to_char());
}
pat.push_str(&self.suffix);
pat.into_value()
@@ -263,26 +255,36 @@ cast! {
pub enum NumberingKind {
/// Arabic numerals (1, 2, 3, etc.).
Arabic,
- /// Latin letters (A, B, C, etc.). Items beyond Z use multiple symbols.
- /// Uses both cases.
- Letter,
- /// Roman numerals (I, II, III, etc.). Uses both cases.
- Roman,
- /// The symbols *, †, ‡, §, ¶, and ‖. Further items use multiple symbols.
+ /// Lowercase Latin letters (a, b, c, etc.). Items beyond z use base-26.
+ LowerLatin,
+ /// Uppercase Latin letters (A, B, C, etc.). Items beyond Z use base-26.
+ UpperLatin,
+ /// Lowercase Roman numerals (i, ii, iii, etc.).
+ LowerRoman,
+ /// Uppercase Roman numerals (I, II, III, etc.).
+ UpperRoman,
+ /// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use repeated symbols.
Symbol,
- /// Hebrew numerals.
+ /// Hebrew numerals, including Geresh/Gershayim.
Hebrew,
- /// Simplified Chinese numerals. Uses standard numerals for lowercase and
- /// "banknote" numerals for uppercase.
- SimplifiedChinese,
+ /// Simplified Chinese standard numerals. This corresponds to the
+ /// `ChineseCase::Lower` variant.
+ LowerSimplifiedChinese,
+ /// Simplified Chinese "banknote" numerals. This corresponds to the
+ /// `ChineseCase::Upper` variant.
+ UpperSimplifiedChinese,
// TODO: Pick the numbering pattern based on languages choice.
// As the first character of Simplified and Traditional Chinese numbering
// are the same, we are unable to determine if the context requires
// Simplified or Traditional by only looking at this character.
#[allow(unused)]
- /// Traditional Chinese numerals. Uses standard numerals for lowercase and
- /// "banknote" numerals for uppercase.
- TraditionalChinese,
+ /// Traditional Chinese standard numerals. This corresponds to the
+ /// `ChineseCase::Lower` variant.
+ LowerTraditionalChinese,
+ #[allow(unused)]
+ /// Traditional Chinese "banknote" numerals. This corresponds to the
+ /// `ChineseCase::Upper` variant.
+ UpperTraditionalChinese,
/// Hiragana in the gojūon order. Includes n but excludes wi and we.
HiraganaAiueo,
/// Hiragana in the iroha order. Includes wi and we but excludes n.
@@ -312,15 +314,18 @@ pub enum NumberingKind {
}
impl NumberingKind {
- /// Create a numbering kind from a lowercase character.
+ /// Create a numbering kind from a representative character.
pub fn from_char(c: char) -> Option<Self> {
Some(match c {
'1' => NumberingKind::Arabic,
- 'a' => NumberingKind::Letter,
- 'i' => NumberingKind::Roman,
+ 'a' => NumberingKind::LowerLatin,
+ 'A' => NumberingKind::UpperLatin,
+ 'i' => NumberingKind::LowerRoman,
+ 'I' => NumberingKind::UpperRoman,
'*' => NumberingKind::Symbol,
'א' => NumberingKind::Hebrew,
- '一' | '壹' => NumberingKind::SimplifiedChinese,
+ '一' => NumberingKind::LowerSimplifiedChinese,
+ '壹' => NumberingKind::UpperSimplifiedChinese,
'あ' => NumberingKind::HiraganaAiueo,
'い' => NumberingKind::HiraganaIroha,
'ア' => NumberingKind::KatakanaAiueo,
@@ -338,16 +343,18 @@ impl NumberingKind {
})
}
- /// The lowercase character for this numbering kind.
+ /// The representative character for this numbering kind.
pub fn to_char(self) -> char {
match self {
Self::Arabic => '1',
- Self::Letter => 'a',
- Self::Roman => 'i',
+ Self::LowerLatin => 'a',
+ Self::UpperLatin => 'A',
+ Self::LowerRoman => 'i',
+ Self::UpperRoman => 'I',
Self::Symbol => '*',
Self::Hebrew => 'א',
- Self::SimplifiedChinese => '一',
- Self::TraditionalChinese => '一',
+ Self::LowerSimplifiedChinese | Self::LowerTraditionalChinese => '一',
+ Self::UpperSimplifiedChinese | Self::UpperTraditionalChinese => '壹',
Self::HiraganaAiueo => 'あ',
Self::HiraganaIroha => 'い',
Self::KatakanaAiueo => 'ア',
@@ -365,109 +372,11 @@ impl NumberingKind {
}
/// Apply the numbering to the given number.
- pub fn apply(self, mut n: usize, case: Case) -> EcoString {
+ pub fn apply(self, n: usize) -> EcoString {
match self {
- Self::Arabic => {
- eco_format!("{n}")
- }
- Self::Letter => zeroless::<26>(
- |x| match case {
- Case::Lower => char::from(b'a' + x as u8),
- Case::Upper => char::from(b'A' + x as u8),
- },
- n,
- ),
- Self::HiraganaAiueo => zeroless::<46>(
- |x| {
- [
- 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ',
- 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に',
- 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む',
- 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ',
- 'を', 'ん',
- ][x]
- },
- n,
- ),
- Self::HiraganaIroha => zeroless::<47>(
- |x| {
- [
- 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る',
- 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら',
- 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ',
- 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ',
- 'も', 'せ', 'す',
- ][x]
- },
- n,
- ),
- Self::KatakanaAiueo => zeroless::<46>(
- |x| {
- [
- 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ',
- 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ',
- 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム',
- 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ',
- 'ヲ', 'ン',
- ][x]
- },
- n,
- ),
- Self::KatakanaIroha => zeroless::<47>(
- |x| {
- [
- 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル',
- 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ',
- 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ',
- 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ',
- 'モ', 'セ', 'ス',
- ][x]
- },
- n,
- ),
- Self::Roman => {
- if n == 0 {
- return 'N'.into();
- }
-
- // Adapted from Yann Villessuzanne's roman.rs under the
- // Unlicense, at https://github.com/linfir/roman.rs/
- let mut fmt = EcoString::new();
- for &(name, value) in &[
- ("M̅", 1000000),
- ("D̅", 500000),
- ("C̅", 100000),
- ("L̅", 50000),
- ("X̅", 10000),
- ("V̅", 5000),
- ("I̅V̅", 4000),
- ("M", 1000),
- ("CM", 900),
- ("D", 500),
- ("CD", 400),
- ("C", 100),
- ("XC", 90),
- ("L", 50),
- ("XL", 40),
- ("X", 10),
- ("IX", 9),
- ("V", 5),
- ("IV", 4),
- ("I", 1),
- ] {
- while n >= value {
- n -= value;
- for c in name.chars() {
- match case {
- Case::Lower => fmt.extend(c.to_lowercase()),
- Case::Upper => fmt.push(c),
- }
- }
- }
- }
-
- fmt
- }
+ Self::Arabic => eco_format!("{n}"),
+ Self::LowerRoman => roman_numeral(n, Case::Lower),
+ Self::UpperRoman => roman_numeral(n, Case::Upper),
Self::Symbol => {
if n == 0 {
return '-'.into();
@@ -478,128 +387,219 @@ impl NumberingKind {
let amount = ((n - 1) / SYMBOLS.len()) + 1;
std::iter::repeat(symbol).take(amount).collect()
}
- Self::Hebrew => {
- if n == 0 {
- return '-'.into();
- }
+ Self::Hebrew => hebrew_numeral(n),
- 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;
- }
- }
- break 'outer;
- }
- }
- fmt
- }
- l @ (Self::SimplifiedChinese | Self::TraditionalChinese) => {
- let chinese_case = match case {
- Case::Lower => ChineseCase::Lower,
- Case::Upper => ChineseCase::Upper,
- };
-
- match (n as u64).to_chinese(
- match l {
- Self::SimplifiedChinese => ChineseVariant::Simple,
- Self::TraditionalChinese => ChineseVariant::Traditional,
- _ => unreachable!(),
- },
- chinese_case,
- ChineseCountMethod::TenThousand,
- ) {
- Ok(num_str) => EcoString::from(num_str),
- Err(_) => '-'.into(),
- }
- }
- Self::KoreanJamo => zeroless::<14>(
- |x| {
- [
- 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ',
- 'ㅌ', 'ㅍ', 'ㅎ',
- ][x]
- },
+ Self::LowerLatin => zeroless(
+ [
+ '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::KoreanSyllable => zeroless::<14>(
- |x| {
- [
- '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카',
- '타', '파', '하',
- ][x]
- },
+ Self::UpperLatin => zeroless(
+ [
+ '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::EasternArabic => decimal('\u{0660}', n),
- Self::EasternArabicPersian => decimal('\u{06F0}', n),
- Self::DevanagariNumber => decimal('\u{0966}', n),
- Self::BengaliNumber => decimal('\u{09E6}', n),
- Self::BengaliLetter => zeroless::<32>(
- |x| {
- [
- 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড',
- 'ঢ', 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য',
- 'র', 'ল', 'শ', 'ষ', 'স', 'হ',
- ][x]
- },
+ Self::HiraganaAiueo => zeroless(
+ [
+ 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ',
+ 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に',
+ 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む',
+ 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ',
+ 'を', 'ん',
+ ],
+ n,
+ ),
+ Self::HiraganaIroha => zeroless(
+ [
+ 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る',
+ 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら',
+ 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ',
+ 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ',
+ 'も', 'せ', 'す',
+ ],
+ n,
+ ),
+ Self::KatakanaAiueo => zeroless(
+ [
+ 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ',
+ 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ',
+ 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム',
+ 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ',
+ 'ヲ', 'ン',
+ ],
+ n,
+ ),
+ Self::KatakanaIroha => zeroless(
+ [
+ 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル',
+ 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ',
+ 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ',
+ 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ',
+ 'モ', 'セ', 'ス',
+ ],
+ n,
+ ),
+ Self::KoreanJamo => zeroless(
+ [
+ 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ',
+ 'ㅌ', 'ㅍ', 'ㅎ',
+ ],
+ n,
+ ),
+ Self::KoreanSyllable => zeroless(
+ [
+ '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카',
+ '타', '파', '하',
+ ],
n,
),
- Self::CircledNumber => zeroless::<50>(
- |x| {
- [
- '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬',
- '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕',
- '㉖', '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱',
- '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼',
- '㊽', '㊾', '㊿',
- ][x]
- },
+ Self::BengaliLetter => zeroless(
+ [
+ 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ',
+ 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল',
+ 'শ', 'ষ', 'স', 'হ',
+ ],
n,
),
- Self::DoubleCircledNumber => zeroless::<10>(
- |x| ['⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'][x],
+ Self::CircledNumber => zeroless(
+ [
+ '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭',
+ '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', '㉖',
+ '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', '㊲',
+ '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽',
+ '㊾', '㊿',
+ ],
n,
),
+ Self::DoubleCircledNumber => {
+ zeroless(['⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n)
+ }
+
+ Self::LowerSimplifiedChinese => {
+ usize_to_chinese(ChineseVariant::Simple, ChineseCase::Lower, n).into()
+ }
+ Self::UpperSimplifiedChinese => {
+ usize_to_chinese(ChineseVariant::Simple, ChineseCase::Upper, n).into()
+ }
+ Self::LowerTraditionalChinese => {
+ usize_to_chinese(ChineseVariant::Traditional, ChineseCase::Lower, n)
+ .into()
+ }
+ Self::UpperTraditionalChinese => {
+ usize_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),
+ }
+ }
+}
+
+fn hebrew_numeral(mut n: usize) -> 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;
+ }
+ }
+ break 'outer;
}
}
+ fmt
+}
+
+fn roman_numeral(mut n: usize, 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),
+ }
+ }
+ }
+ }
+
+ fmt
}
/// Stringify a number using a base-N counting system with no zero digit.
@@ -627,19 +627,19 @@ impl NumberingKind {
/// You might be familiar with this scheme from the way spreadsheet software
/// tends to label its columns.
fn zeroless<const N_DIGITS: usize>(
- mk_digit: impl Fn(usize) -> char,
+ alphabet: [char; N_DIGITS],
mut n: usize,
) -> EcoString {
if n == 0 {
return '-'.into();
}
- let mut cs: SmallVec<[char; 8]> = smallvec![];
+ let mut cs = EcoString::new();
while n > 0 {
n -= 1;
- cs.push(mk_digit(n % N_DIGITS));
+ cs.push(alphabet[n % N_DIGITS]);
n /= N_DIGITS;
}
- cs.into_iter().rev().collect()
+ cs.chars().rev().collect()
}
/// Stringify a number using a base-10 counting system with a zero digit.
@@ -649,10 +649,10 @@ fn decimal(start: char, mut n: usize) -> EcoString {
if n == 0 {
return start.into();
}
- let mut cs: SmallVec<[char; 8]> = smallvec![];
+ let mut cs = EcoString::new();
while n > 0 {
cs.push(char::from_u32((start as u32) + ((n % 10) as u32)).unwrap());
n /= 10;
}
- cs.into_iter().rev().collect()
+ cs.chars().rev().collect()
}