Text: East Asian, Thai, Hindi, Arabic and Hebrew auto-number schemes

Every scheme other than the Latin, Roman and circled ones drew Arabic digits ("4." for
ea1ChsPeriod, "- 10 -" for arabic2Minus). Each is now written as PowerPoint's export draws it
(probe deck: one box per a:buAutoNum type and start value): Chinese numerals for ea1Chs/ea1Cht
(十, 十一, 二十一), digit-by-digit kanji with 〇 for ea1Jpn* (一〇), full-width digits for
arabicDb*, a full-width full stop after the East Asian ones, Thai letters (without ฃ ฅ ฆ) and
digits, Devanagari vowels, consonants and digits, the Arabic alphabet (arabic1Minus) and abjad
(arabic2Minus) and the Hebrew alphabet with a hyphen, and the Wingdings-style circles that go to
ten (circleNumWdWhite 13 → ③, circleNumWdBlack → ❶…). w04-lists s2-s4 drew wrong labels in 24 of
36 boxes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-13 06:46:02 -04:00
co-authored by Claude Opus 5.5
parent 5a64fdbc3e
commit bbaa1aa7b1
2 changed files with 138 additions and 12 deletions
+103 -12
View File
@@ -550,31 +550,122 @@ fn alpha_num(n: u32, upper: bool) -> String {
if upper { s.to_uppercase() } else { s }
}
/// Text of an auto-number bullet: `arabicPeriod` 3 → "3.".
/// Letters of a numbering alphabet: 1 → the first, past the end the letter doubles ("aa"), as
/// the Latin alphabetic schemes do.
fn letters(list: &[char], n: u32) -> String {
let len = list.len().max(1) as u32;
let Some(i) = n.checked_sub(1) else { return String::new() };
let c = list.get((i % len) as usize).copied().unwrap_or('?');
std::iter::repeat_n(c, (i / len + 1).min(50) as usize).collect()
}
/// `n` written with the digits `zero..zero+9` (Thai, Devanagari, full-width digits).
fn digits_in(n: u32, zero: char) -> String {
n.to_string().chars().map(|d| d.to_digit(10).and_then(|v| char::from_u32(zero as u32 + v)).unwrap_or(d)).collect()
}
const CJK_DIGITS: [char; 10] = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
/// Chinese numerals: 10 → 十, 11 → 十一, 25 → 二十五, 105 → 一百零五.
fn chinese_num(n: u32) -> String {
if n == 0 || n > 99_999 {
return n.to_string();
}
let units = ['\0', '十', '百', '千', '万'];
let ds: Vec<u32> = n.to_string().chars().filter_map(|c| c.to_digit(10)).collect();
let mut out = String::new();
let mut zero = false;
for (i, d) in ds.iter().enumerate() {
let place = ds.len() - 1 - i;
if *d == 0 {
zero = true;
continue;
}
if zero {
out.push('零');
zero = false;
}
// "十" alone for 10–19, not "一十".
if !(*d == 1 && place == 1 && i == 0) {
out.push(CJK_DIGITS.get(*d as usize).copied().unwrap_or('?'));
}
if let Some(u) = units.get(place).filter(|u| **u != '\0') {
out.push(*u);
}
}
out
}
/// Text of an auto-number bullet: `arabicPeriod` 3 → "3.". The East Asian, Thai, Hindi, Arabic
/// and Hebrew schemes are written as PowerPoint draws them: `ea1ChsPeriod` 10 → "十.",
/// `ea1JpnKorPeriod` 10 → "一〇." (digit by digit), `arabicDbPeriod` 13 → "13.",
/// `thaiAlphaParenR` 4 → "ง)", `hindiAlpha1Period` 1 → "क.", `arabic2Minus` 10 → "ي-"
/// (abjad order), `circleNumWdWhitePlain` 13 → "③" (those circles go to ten).
pub fn autonum(scheme: &str, n: u32) -> String {
let (core, upper) = if scheme.starts_with("romanUc") {
(roman(n, true), true)
const THAI: [char; 41] = [
'ก', 'ข', 'ค', 'ง', 'จ', 'ฉ', 'ช', 'ซ', 'ฌ', 'ญ', 'ฎ', 'ฏ', 'ฐ', 'ฑ', 'ฒ', 'ณ', 'ด', 'ต', 'ถ', 'ท', 'ธ', 'น', 'บ', 'ป', 'ผ', 'ฝ', 'พ', 'ฟ',
'ภ', 'ม', 'ย', 'ร', 'ล', 'ว', 'ศ', 'ษ', 'ส', 'ห', 'ฬ', 'อ', 'ฮ',
];
const HINDI_VOWELS: [char; 11] = ['अ', 'आ', 'इ', 'ई', 'उ', 'ऊ', 'ऋ', 'ए', 'ऐ', 'ओ', 'औ'];
const HINDI_CONSONANTS: [char; 33] = [
'क', 'ख', 'ग', 'घ', 'ङ', 'च', 'छ', 'ज', 'झ', 'ञ', 'ट', 'ठ', 'ड', 'ढ', 'ण', 'त', 'थ', 'द', 'ध', 'न', 'प', 'फ', 'ब', 'भ', 'म', 'य', 'र', 'ल',
'व', 'श', 'ष', 'स', 'ह',
];
const ARABIC_ALPHABET: [char; 28] =
['ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ك', 'ل', 'م', 'ن', 'ه', 'و', 'ي'];
const ARABIC_ABJAD: [char; 28] =
['ا', 'ب', 'ج', 'د', 'ه', 'و', 'ز', 'ح', 'ط', 'ي', 'ك', 'ل', 'م', 'ن', 'س', 'ع', 'ف', 'ص', 'ق', 'ر', 'ش', 'ت', 'ث', 'خ', 'ذ', 'ض', 'ظ', 'غ'];
const HEBREW: [char; 22] = ['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק', 'ר', 'ש', 'ת'];
let circled = |first: u32, count: u32| char::from_u32(first + n.saturating_sub(1) % count).map(String::from).unwrap_or_else(|| n.to_string());
let core = if scheme.starts_with("romanUc") {
roman(n, true)
} else if scheme.starts_with("romanLc") {
(roman(n, false), false)
roman(n, false)
} else if scheme.starts_with("alphaUc") {
(alpha_num(n, true), true)
alpha_num(n, true)
} else if scheme.starts_with("alphaLc") {
(alpha_num(n, false), false)
alpha_num(n, false)
} else if scheme.starts_with("circleNumWdWhite") {
return circled(0x2460, 10);
} else if scheme.starts_with("circleNumWdBlack") {
return circled(0x2776, 10);
} else if scheme.starts_with("circleNum") {
let c = char::from_u32(0x2460 + n.saturating_sub(1).min(19)).unwrap_or('•');
return c.to_string();
return char::from_u32(0x2460 + n.saturating_sub(1).min(19)).map(String::from).unwrap_or_else(|| n.to_string());
} else if scheme.starts_with("ea1Chs") || scheme.starts_with("ea1Cht") {
chinese_num(n)
} else if scheme.starts_with("ea1Jpn") {
n.to_string().chars().map(|d| d.to_digit(10).and_then(|v| CJK_DIGITS.get(v as usize).copied()).unwrap_or(d)).collect()
} else if scheme.starts_with("arabicDb") {
digits_in(n, '0')
} else if scheme.starts_with("thaiAlpha") {
letters(&THAI, n)
} else if scheme.starts_with("thaiNum") {
digits_in(n, '๐')
} else if scheme.starts_with("hindiAlpha1") {
letters(&HINDI_CONSONANTS, n)
} else if scheme.starts_with("hindiAlpha") {
letters(&HINDI_VOWELS, n)
} else if scheme.starts_with("hindiNum") {
digits_in(n, '०')
} else if scheme.starts_with("arabic1") {
letters(&ARABIC_ALPHABET, n)
} else if scheme.starts_with("arabic2") {
letters(&ARABIC_ABJAD, n)
} else if scheme.starts_with("hebrew2") {
letters(&HEBREW, n)
} else {
(n.to_string(), false)
n.to_string()
};
let _ = upper;
// East Asian schemes end with a full-width full stop.
let wide = scheme.starts_with("ea1") || scheme.starts_with("arabicDb");
if scheme.ends_with("ParenBoth") {
format!("({core})")
} else if scheme.ends_with("ParenR") {
format!("{core})")
} else if scheme.ends_with("Period") {
format!("{core}.")
format!("{core}{}", if wide { '.' } else { '.' })
} else if scheme.ends_with("Minus") {
format!("- {core} -")
format!("{core}-")
} else {
core
}
+35
View File
@@ -1028,3 +1028,38 @@ fn small_kana_and_closing_brackets_follow_powerpoint_kinsoku() {
let lines = wrapped("あいうえ「。」かき", w, |p| p.hanging_punct = Some(true));
assert_eq!(lines[0], "あいうえ", "{lines:?}");
}
#[test]
fn autonumber_schemes_follow_powerpoint() {
// As PowerPoint's export draws them (start values as in the probe deck).
for (scheme, n, want) in [
("ea1ChsPlain", 4, "四"),
("ea1ChsPeriod", 10, "十."),
("ea1ChtPeriod", 21, "二十一."),
("ea1JpnKorPeriod", 10, "一〇."),
("ea1JpnChsDbPeriod", 13, "一三."),
("arabicDbPlain", 10, "10"),
("arabicDbPeriod", 13, "13."),
("circleNumDbPlain", 13, "⑬"),
("circleNumWdWhitePlain", 13, "③"),
("circleNumWdBlackPlain", 2, "❷"),
("thaiAlphaPeriod", 1, "ก."),
("thaiAlphaParenR", 4, "ง)"),
("thaiAlphaParenBoth", 7, "(ช)"),
("thaiNumPeriod", 10, "๑๐."),
("hindiAlphaPeriod", 4, "ई."),
("hindiAlpha1Period", 1, "क."),
("hindiNumParenR", 13, "१३)"),
("hebrew2Minus", 1, "א-"),
("arabic1Minus", 13, "ش-"),
("arabic2Minus", 10, "ي-"),
("arabicPlain", 10, "10"),
] {
assert_eq!(autonum(scheme, n), want, "{scheme} {n}");
}
assert_eq!(chinese_num(105), "一百零五");
assert_eq!(chinese_num(20), "二十");
// Past the end of an alphabet the letter doubles, like "aa".
assert_eq!(autonum("hebrew2Minus", 23), "אא-");
assert_eq!(autonum("thaiAlphaPeriod", 0), ".");
}