mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Support Greek Numbering (#4273)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
3eb74319cc
commit
23313b0af0
@ -59,9 +59,9 @@ pub fn numbering(
|
|||||||
context: Tracked<Context>,
|
context: Tracked<Context>,
|
||||||
/// Defines how the numbering works.
|
/// Defines how the numbering works.
|
||||||
///
|
///
|
||||||
/// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `一`, `壹`, `あ`, `い`,
|
/// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `α`, `Α`, `一`, `壹`,
|
||||||
/// `ア`, `イ`, `א`, `가`, `ㄱ`, `*`, `①`, and `⓵`. They are replaced by the
|
/// `あ`, `い`, `ア`, `イ`, `א`, `가`, `ㄱ`, `*`, `①`, and `⓵`. They are
|
||||||
/// number in the sequence, preserving the original case.
|
/// replaced by the number in the sequence, preserving the original case.
|
||||||
///
|
///
|
||||||
/// The `*` character means that symbols should be used to count, in the
|
/// The `*` character means that symbols should be used to count, in the
|
||||||
/// order of `*`, `†`, `‡`, `§`, `¶`, `‖`. If there are more than six
|
/// order of `*`, `†`, `‡`, `§`, `¶`, `‖`. If there are more than six
|
||||||
@ -141,9 +141,8 @@ cast! {
|
|||||||
|
|
||||||
/// How to turn a number into text.
|
/// How to turn a number into text.
|
||||||
///
|
///
|
||||||
/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, `I`,
|
/// A pattern consists of a prefix, followed by one of the counter symbols (see
|
||||||
/// `一`, `壹`, `あ`, `い`, `ア`, `イ`, `א`, `가`, `ㄱ`, `*`, `①`, or `⓵`, and then a
|
/// [`numbering()`] docs), and then a suffix.
|
||||||
/// suffix.
|
|
||||||
///
|
///
|
||||||
/// Examples of valid patterns:
|
/// Examples of valid patterns:
|
||||||
/// - `1)`
|
/// - `1)`
|
||||||
@ -263,7 +262,12 @@ pub enum NumberingKind {
|
|||||||
LowerRoman,
|
LowerRoman,
|
||||||
/// Uppercase Roman numerals (I, II, III, etc.).
|
/// Uppercase Roman numerals (I, II, III, etc.).
|
||||||
UpperRoman,
|
UpperRoman,
|
||||||
/// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use repeated symbols.
|
/// Lowercase Greek numerals (Α, Β, Γ, etc.).
|
||||||
|
LowerGreek,
|
||||||
|
/// Uppercase Greek numerals (α, β, γ, etc.).
|
||||||
|
UpperGreek,
|
||||||
|
/// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use
|
||||||
|
/// repeated symbols.
|
||||||
Symbol,
|
Symbol,
|
||||||
/// Hebrew numerals, including Geresh/Gershayim.
|
/// Hebrew numerals, including Geresh/Gershayim.
|
||||||
Hebrew,
|
Hebrew,
|
||||||
@ -322,6 +326,8 @@ impl NumberingKind {
|
|||||||
'A' => NumberingKind::UpperLatin,
|
'A' => NumberingKind::UpperLatin,
|
||||||
'i' => NumberingKind::LowerRoman,
|
'i' => NumberingKind::LowerRoman,
|
||||||
'I' => NumberingKind::UpperRoman,
|
'I' => NumberingKind::UpperRoman,
|
||||||
|
'α' => NumberingKind::LowerGreek,
|
||||||
|
'Α' => NumberingKind::UpperGreek,
|
||||||
'*' => NumberingKind::Symbol,
|
'*' => NumberingKind::Symbol,
|
||||||
'א' => NumberingKind::Hebrew,
|
'א' => NumberingKind::Hebrew,
|
||||||
'一' => NumberingKind::LowerSimplifiedChinese,
|
'一' => NumberingKind::LowerSimplifiedChinese,
|
||||||
@ -351,6 +357,8 @@ impl NumberingKind {
|
|||||||
Self::UpperLatin => 'A',
|
Self::UpperLatin => 'A',
|
||||||
Self::LowerRoman => 'i',
|
Self::LowerRoman => 'i',
|
||||||
Self::UpperRoman => 'I',
|
Self::UpperRoman => 'I',
|
||||||
|
Self::LowerGreek => 'α',
|
||||||
|
Self::UpperGreek => 'Α',
|
||||||
Self::Symbol => '*',
|
Self::Symbol => '*',
|
||||||
Self::Hebrew => 'א',
|
Self::Hebrew => 'א',
|
||||||
Self::LowerSimplifiedChinese | Self::LowerTraditionalChinese => '一',
|
Self::LowerSimplifiedChinese | Self::LowerTraditionalChinese => '一',
|
||||||
@ -377,6 +385,8 @@ impl NumberingKind {
|
|||||||
Self::Arabic => eco_format!("{n}"),
|
Self::Arabic => eco_format!("{n}"),
|
||||||
Self::LowerRoman => roman_numeral(n, Case::Lower),
|
Self::LowerRoman => roman_numeral(n, Case::Lower),
|
||||||
Self::UpperRoman => roman_numeral(n, Case::Upper),
|
Self::UpperRoman => roman_numeral(n, Case::Upper),
|
||||||
|
Self::LowerGreek => greek_numeral(n, Case::Lower),
|
||||||
|
Self::UpperGreek => greek_numeral(n, Case::Upper),
|
||||||
Self::Symbol => {
|
Self::Symbol => {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return '-'.into();
|
return '-'.into();
|
||||||
@ -502,6 +512,7 @@ impl NumberingKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringify an integer to a Hebrew number.
|
||||||
fn hebrew_numeral(mut n: usize) -> EcoString {
|
fn hebrew_numeral(mut n: usize) -> EcoString {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return '-'.into();
|
return '-'.into();
|
||||||
@ -555,6 +566,7 @@ fn hebrew_numeral(mut n: usize) -> EcoString {
|
|||||||
fmt
|
fmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringify an integer to a Roman numeral.
|
||||||
fn roman_numeral(mut n: usize, case: Case) -> EcoString {
|
fn roman_numeral(mut n: usize, case: Case) -> EcoString {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return match case {
|
return match case {
|
||||||
@ -602,6 +614,147 @@ fn roman_numeral(mut n: usize, case: Case) -> EcoString {
|
|||||||
fmt
|
fmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringify an integer to Greek numbers.
|
||||||
|
///
|
||||||
|
/// Greek numbers use the Greek Alphabet to represent numbers; it is based on 10
|
||||||
|
/// (decimal). Here we implement the single digit M power representation from
|
||||||
|
/// [The Greek Number Converter][convert] and also described in
|
||||||
|
/// [Greek Numbers][numbers].
|
||||||
|
///
|
||||||
|
/// [converter]: https://www.russellcottrell.com/greek/utilities/GreekNumberConverter.htm
|
||||||
|
/// [numbers]: https://mathshistory.st-andrews.ac.uk/HistTopics/Greek_numbers/
|
||||||
|
fn greek_numeral(n: usize, case: Case) -> EcoString {
|
||||||
|
let thousands = [
|
||||||
|
["͵α", "͵Α"],
|
||||||
|
["͵β", "͵Β"],
|
||||||
|
["͵γ", "͵Γ"],
|
||||||
|
["͵δ", "͵Δ"],
|
||||||
|
["͵ε", "͵Ε"],
|
||||||
|
["͵ϛ", "͵Ϛ"],
|
||||||
|
["͵ζ", "͵Ζ"],
|
||||||
|
["͵η", "͵Η"],
|
||||||
|
["͵θ", "͵Θ"],
|
||||||
|
];
|
||||||
|
let hundreds = [
|
||||||
|
["ρ", "Ρ"],
|
||||||
|
["σ", "Σ"],
|
||||||
|
["τ", "Τ"],
|
||||||
|
["υ", "Υ"],
|
||||||
|
["φ", "Φ"],
|
||||||
|
["χ", "Χ"],
|
||||||
|
["ψ", "Ψ"],
|
||||||
|
["ω", "Ω"],
|
||||||
|
["ϡ", "Ϡ"],
|
||||||
|
];
|
||||||
|
let tens = [
|
||||||
|
["ι", "Ι"],
|
||||||
|
["κ", "Κ"],
|
||||||
|
["λ", "Λ"],
|
||||||
|
["μ", "Μ"],
|
||||||
|
["ν", "Ν"],
|
||||||
|
["ξ", "Ξ"],
|
||||||
|
["ο", "Ο"],
|
||||||
|
["π", "Π"],
|
||||||
|
["ϙ", "Ϟ"],
|
||||||
|
];
|
||||||
|
let ones = [
|
||||||
|
["α", "Α"],
|
||||||
|
["β", "Β"],
|
||||||
|
["γ", "Γ"],
|
||||||
|
["δ", "Δ"],
|
||||||
|
["ε", "Ε"],
|
||||||
|
["ϛ", "Ϛ"],
|
||||||
|
["ζ", "Ζ"],
|
||||||
|
["η", "Η"],
|
||||||
|
["θ", "Θ"],
|
||||||
|
];
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
// Greek Zero Sign
|
||||||
|
return '𐆊'.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fmt = EcoString::new();
|
||||||
|
let case = match case {
|
||||||
|
Case::Lower => 0,
|
||||||
|
Case::Upper => 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract a list of decimal digits from the number
|
||||||
|
let mut decimal_digits: Vec<usize> = Vec::new();
|
||||||
|
let mut n = n;
|
||||||
|
while n > 0 {
|
||||||
|
decimal_digits.push(n % 10);
|
||||||
|
n /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad the digits with leading zeros to ensure we can form groups of 4
|
||||||
|
while decimal_digits.len() % 4 != 0 {
|
||||||
|
decimal_digits.push(0);
|
||||||
|
}
|
||||||
|
decimal_digits.reverse();
|
||||||
|
|
||||||
|
let mut m_power = decimal_digits.len() / 4;
|
||||||
|
|
||||||
|
// M are used to represent 10000, M_power = 2 means 10000^2 = 10000 0000
|
||||||
|
// The prefix of M is also made of Greek numerals but only be single digits, so it is 9 at max. This enables us
|
||||||
|
// to represent up to (10000)^(9 + 1) - 1 = 10^40 -1 (9,999,999,999,999,999,999,999,999,999,999,999,999,999)
|
||||||
|
let get_m_prefix = |m_power: usize| {
|
||||||
|
if m_power == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
assert!(m_power <= 9);
|
||||||
|
// the prefix of M is a single digit lowercase
|
||||||
|
Some(ones[m_power - 1][0])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut previous_has_number = false;
|
||||||
|
for chunk in decimal_digits.chunks_exact(4) {
|
||||||
|
// chunk must be exact 4 item
|
||||||
|
assert_eq!(chunk.len(), 4);
|
||||||
|
|
||||||
|
m_power = m_power.saturating_sub(1);
|
||||||
|
|
||||||
|
// `th`ousan, `h`undred, `t`en and `o`ne
|
||||||
|
let (th, h, t, o) = (chunk[0], chunk[1], chunk[2], chunk[3]);
|
||||||
|
if th + h + t + o == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if previous_has_number {
|
||||||
|
fmt.push_str(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(m_prefix) = get_m_prefix(m_power) {
|
||||||
|
fmt.push_str(m_prefix);
|
||||||
|
fmt.push_str("Μ");
|
||||||
|
}
|
||||||
|
if th != 0 {
|
||||||
|
let thousand_digit = thousands[th - 1][case];
|
||||||
|
fmt.push_str(thousand_digit);
|
||||||
|
}
|
||||||
|
if h != 0 {
|
||||||
|
let hundred_digit = hundreds[h - 1][case];
|
||||||
|
fmt.push_str(hundred_digit);
|
||||||
|
}
|
||||||
|
if t != 0 {
|
||||||
|
let ten_digit = tens[t - 1][case];
|
||||||
|
fmt.push_str(ten_digit);
|
||||||
|
}
|
||||||
|
if o != 0 {
|
||||||
|
let one_digit = ones[o - 1][case];
|
||||||
|
fmt.push_str(one_digit);
|
||||||
|
}
|
||||||
|
// if we do not have thousan, we need to append 'ʹ' at the end.
|
||||||
|
if th == 0 {
|
||||||
|
fmt.push_str("ʹ");
|
||||||
|
}
|
||||||
|
previous_has_number = true;
|
||||||
|
}
|
||||||
|
fmt
|
||||||
|
}
|
||||||
|
|
||||||
/// Stringify a number using a base-N counting system with no zero digit.
|
/// 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'.
|
/// This is best explained by example. Suppose our digits are 'A', 'B', and 'C'.
|
||||||
|
@ -16,6 +16,48 @@
|
|||||||
// Arabic.
|
// Arabic.
|
||||||
#t(pat: "1", "0", "1", "2", "3", "4", "5", "6", 107, "107", "108")
|
#t(pat: "1", "0", "1", "2", "3", "4", "5", "6", 107, "107", "108")
|
||||||
|
|
||||||
|
// Greek.
|
||||||
|
#t(
|
||||||
|
pat: "α",
|
||||||
|
"𐆊", "αʹ", "βʹ", "γʹ", "δʹ", "εʹ", "ϛʹ", "ζʹ", "ηʹ", "θʹ", "ιʹ",
|
||||||
|
"ιαʹ", "ιβʹ", "ιγʹ", "ιδʹ", "ιεʹ", "ιϛʹ", "ιζʹ", "ιηʹ", "ιθʹ", "κʹ",
|
||||||
|
241, "σμαʹ",
|
||||||
|
999, "ϡϙθʹ",
|
||||||
|
1005, "͵αε",
|
||||||
|
1999, "͵αϡϙθ",
|
||||||
|
2999, "͵βϡϙθ",
|
||||||
|
3000, "͵γ",
|
||||||
|
3398, "͵γτϙη",
|
||||||
|
4444, "͵δυμδ",
|
||||||
|
5683, "͵εχπγ",
|
||||||
|
9184, "͵θρπδ",
|
||||||
|
9999, "͵θϡϙθ",
|
||||||
|
20000, "αΜβʹ",
|
||||||
|
20001, "αΜβʹ, αʹ",
|
||||||
|
97554, "αΜθʹ, ͵ζφνδ",
|
||||||
|
99999, "αΜθʹ, ͵θϡϙθ",
|
||||||
|
1000000, "αΜρʹ",
|
||||||
|
1000001, "αΜρʹ, αʹ",
|
||||||
|
1999999, "αΜρϙθʹ, ͵θϡϙθ",
|
||||||
|
2345678, "αΜσλδʹ, ͵εχοη",
|
||||||
|
9999999, "αΜϡϙθʹ, ͵θϡϙθ",
|
||||||
|
10000000, "αΜ͵α",
|
||||||
|
90000001, "αΜ͵θ, αʹ",
|
||||||
|
100000000, "βΜαʹ",
|
||||||
|
1000000000, "βΜιʹ",
|
||||||
|
2000000000, "βΜκʹ",
|
||||||
|
2000000001, "βΜκʹ, αʹ",
|
||||||
|
2000010001, "βΜκʹ, αΜαʹ, αʹ",
|
||||||
|
2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ",
|
||||||
|
12312398676, "βΜρκγʹ, αΜ͵ασλθ, ͵ηχοϛ",
|
||||||
|
)
|
||||||
|
#t(
|
||||||
|
pat: sym.Alpha,
|
||||||
|
"𐆊", "Αʹ", "Βʹ", "Γʹ", "Δʹ", "Εʹ", "Ϛʹ", "Ζʹ", "Ηʹ", "Θʹ", "Ιʹ",
|
||||||
|
"ΙΑʹ", "ΙΒʹ", "ΙΓʹ", "ΙΔʹ", "ΙΕʹ", "ΙϚʹ", "ΙΖʹ", "ΙΗʹ", "ΙΘʹ", "Κʹ",
|
||||||
|
241, "ΣΜΑʹ",
|
||||||
|
)
|
||||||
|
|
||||||
// Symbols.
|
// Symbols.
|
||||||
#t(pat: "*", "-", "*", "†", "‡", "§", "¶", "‖", "**")
|
#t(pat: "*", "-", "*", "†", "‡", "§", "¶", "‖", "**")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user