diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs index f155abf8..aed2a9e3 100644 --- a/library/src/meta/numbering.rs +++ b/library/src/meta/numbering.rs @@ -36,8 +36,9 @@ use crate::text::Case; pub fn numbering( /// Defines how the numbering works. /// - /// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are - /// replaced by the number in the sequence, in the given case. + /// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `い`, `イ`, + /// `א`, and `*`. They are replaced by the number in the sequence, + /// in the given case. /// /// The `*` character means that symbols should be used to count, in the /// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six @@ -129,8 +130,8 @@ cast_to_value! { /// How to turn a number into text. /// -/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, `I` -/// or `*`, and then a suffix. +/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, +/// `I`, `い`, `イ`, `א`, or `*`, and then a suffix. /// /// Examples of valid patterns: /// - `1)` @@ -256,6 +257,8 @@ enum NumberingKind { Roman, Symbol, Hebrew, + HiraganaIroha, + KatakanaIroha, } impl NumberingKind { @@ -267,6 +270,8 @@ impl NumberingKind { 'i' => NumberingKind::Roman, '*' => NumberingKind::Symbol, 'א' => NumberingKind::Hebrew, + 'い' => NumberingKind::HiraganaIroha, + 'イ' => NumberingKind::KatakanaIroha, _ => return None, }) } @@ -279,6 +284,8 @@ impl NumberingKind { Self::Roman => 'i', Self::Symbol => '*', Self::Hebrew => 'א', + Self::HiraganaIroha => 'い', + Self::KatakanaIroha => 'イ', } } @@ -288,29 +295,37 @@ impl NumberingKind { Self::Arabic => { eco_format!("{n}") } - Self::Letter => { - if n == 0 { - return '-'.into(); - } - - n -= 1; - - let mut letters = vec![]; - loop { - let c = b'a' + (n % 26) as u8; - letters.push(match case { - Case::Lower => c, - Case::Upper => c.to_ascii_uppercase(), - }); - n /= 26; - if n == 0 { - break; - } - } - - letters.reverse(); - String::from_utf8(letters).unwrap().into() - } + 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::HiraganaIroha => zeroless::<47>( + |x| { + [ + 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', + 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', + 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', + 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ', + 'も', 'せ', 'す', + ][x] + }, + n, + ), + Self::KatakanaIroha => zeroless::<47>( + |x| { + [ + 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', + 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', + 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', + 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', + 'モ', 'セ', 'ス', + ][x] + }, + n, + ), Self::Roman => { if n == 0 { return 'N'.into(); @@ -420,3 +435,43 @@ impl NumberingKind { } } } + +/// 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( + mk_digit: impl Fn(usize) -> char, + mut n: usize, +) -> EcoString { + if n == 0 { + return '-'.into(); + } + let mut cs = vec![]; + while n > 0 { + n -= 1; + cs.push(mk_digit(n % N_DIGITS)); + n /= N_DIGITS; + } + cs.into_iter().rev().collect() +} diff --git a/tests/ref/meta/numbering.png b/tests/ref/meta/numbering.png index 998efe57..2c5db2d5 100644 Binary files a/tests/ref/meta/numbering.png and b/tests/ref/meta/numbering.png differ diff --git a/tests/typ/meta/numbering.typ b/tests/typ/meta/numbering.typ index cf8104ed..100fa480 100644 --- a/tests/typ/meta/numbering.typ +++ b/tests/typ/meta/numbering.typ @@ -9,6 +9,25 @@ parbreak() } +--- +#for i in range(0, 4) { + numbering("A", i) + [ for #i] + linebreak() +} +#par[...] +#for i in range(26, 30) { + numbering("A", i) + [ for #i] + linebreak() +} +#par[...] +#for i in range(702, 706) { + numbering("A", i) + [ for #i] + linebreak() +} + --- // Error: 17-19 number must be at least zero #numbering("1", -1) @@ -21,3 +40,26 @@ [ עבור #i] parbreak() } + +--- +#for i in range(0, 4) { + numbering("イ", i) + [ (or ] + numbering("い", i) + [) for #i] + linebreak() +} +#par[...] +#for i in range(47, 51) { + numbering("イ", i) + [ (or ] + numbering("い", i) + [) for #i] + linebreak() +} +#par[...] +#for i in range(2256, 2260) { + numbering("イ", i) + [ for #i] + linebreak() +}