mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-30 13:16:56 -06:00
Merge kanji-page-numbers: Kanji page numbers
Conflicts: engine layout.rs and notes.rs params docs (next's lists plus kanji).
This commit is contained in:
@@ -793,6 +793,8 @@ pub enum NumberStyle {
|
||||
ArabicFourDigits,
|
||||
/// *, †, ‡, §, ¶, ‖, then doubled (**, ††, …): footnotes.
|
||||
Symbols,
|
||||
/// 一, 二, 三, …: Chinese numerals written digit by digit, so 10 is 一〇 and 38 is 三八.
|
||||
Kanji,
|
||||
}
|
||||
|
||||
/// Numbers past this are written in Arabic digits by the styles that repeat characters (roman
|
||||
@@ -823,6 +825,7 @@ impl NumberStyle {
|
||||
let n = n.max(1) as usize - 1;
|
||||
std::iter::repeat_n(SYM[n % SYM.len()], n / SYM.len() + 1).collect()
|
||||
}
|
||||
NumberStyle::Kanji => kanji_digits(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -892,6 +895,11 @@ pub fn expand_list_levels(s: &str, number: &str, levels: &[String]) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
fn kanji_digits(n: u32) -> String {
|
||||
const DIGITS: [char; 10] = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
|
||||
n.to_string().chars().map(|c| c.to_digit(10).and_then(|d| DIGITS.get(d as usize)).copied().unwrap_or(c)).collect()
|
||||
}
|
||||
|
||||
fn roman(mut n: u32) -> String {
|
||||
if n == 0 {
|
||||
return "0".into();
|
||||
@@ -1631,6 +1639,27 @@ mod tests {
|
||||
assert_eq!(NumberStyle::Symbols.format(u32::MAX), u32::MAX.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kanji_numbers_are_written_digit_by_digit() {
|
||||
for (n, s) in [
|
||||
(0, "〇"),
|
||||
(1, "一"),
|
||||
(5, "五"),
|
||||
(10, "一〇"),
|
||||
(11, "一一"),
|
||||
(20, "二〇"),
|
||||
(38, "三八"),
|
||||
(99, "九九"),
|
||||
(100, "一〇〇"),
|
||||
(105, "一〇五"),
|
||||
(1000, "一〇〇〇"),
|
||||
(2026, "二〇二六"),
|
||||
(u32::MAX, "四二九四九六七二九五"),
|
||||
] {
|
||||
assert_eq!(NumberStyle::Kanji.format(n), s, "{n}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_labels_expand_metacharacters() {
|
||||
let p = ParaProps { number_style: NumberStyle::UpperLetters, ..Default::default() };
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Document Endnote Options…",
|
||||
["Type"],
|
||||
None,
|
||||
"{style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters, startAt?, prefix?, suffix?, heading?, headingStyle?, paraStyle?, separator?} → the options",
|
||||
"{style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters|kanji, startAt?, prefix?, suffix?, heading?, headingStyle?, paraStyle?, separator?} → the options",
|
||||
has_doc,
|
||||
|s, p| {
|
||||
let cur = serde_json::to_value(&s.doc()?.doc.endnote_options).map_err(|e| bad("endnote.options", e.to_string()))?;
|
||||
|
||||
@@ -400,7 +400,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Numbering & Section Options…",
|
||||
["Layout"],
|
||||
None,
|
||||
"{page (1-based; starts a section there), startNumber?: n|null (continue), style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters|arabicLeadingZero|arabicThreeDigits|arabicFourDigits, prefix? (up to 8 characters; no + or comma), includePrefix?, marker?, remove?: bool}",
|
||||
"{page (1-based; starts a section there), startNumber?: n|null (continue), style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters|arabicLeadingZero|arabicThreeDigits|arabicFourDigits|kanji, prefix? (up to 8 characters; no + or comma), includePrefix?, marker?, remove?: bool}",
|
||||
has_doc,
|
||||
|s, p| {
|
||||
let page = p.get("page").and_then(Value::as_u64).ok_or_else(|| bad("layout.section", "missing page"))? as usize;
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Document Footnote Options…",
|
||||
["Type"],
|
||||
None,
|
||||
"{style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters|symbols, startAt?, restart?: never|page|spread|section, prefix?, suffix?, affixIn?: none|reference|text|both, refPosition?: superscript|subscript|normal|otSuperscript, refCharStyle?, paraStyle?, separator?, spaceBefore?, spaceBetween?, firstBaseline?: ascent|capHeight|leading|xHeight|emboxHeight|fixed, firstBaselineMin?, spanColumns?, rule?: {on, weight, color, tint, width, offset, leftIndent}} → the options (no params: just read them)",
|
||||
"{style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters|symbols|kanji, startAt?, restart?: never|page|spread|section, prefix?, suffix?, affixIn?: none|reference|text|both, refPosition?: superscript|subscript|normal|otSuperscript, refCharStyle?, paraStyle?, separator?, spaceBefore?, spaceBetween?, firstBaseline?: ascent|capHeight|leading|xHeight|emboxHeight|fixed, firstBaselineMin?, spanColumns?, rule?: {on, weight, color, tint, width, offset, leftIndent}} → the options (no params: just read them)",
|
||||
has_doc,
|
||||
options
|
||||
),
|
||||
|
||||
@@ -2020,7 +2020,7 @@ impl<'a> Ex<'a> {
|
||||
let restart = names::NOTE_RESTART.iter().find(|r| r.0 == o.restart).map_or("DontRestart", |r| r.1);
|
||||
let affix = names::NOTE_AFFIX.iter().find(|r| r.0 == o.affix_in).map_or("NoPrefixSuffix", |r| r.1);
|
||||
let mut props = El::new("Properties");
|
||||
props.push(enumv("FootnoteNumberingStyle", names::note_style_out(o.style)));
|
||||
props.push(enumv("FootnoteNumberingStyle", names::number_style_out(o.style)));
|
||||
props.push(enumv("RestartNumbering", restart));
|
||||
props.push(enumv("ShowPrefixSuffix", affix));
|
||||
props.push(enumv("MarkerPositioning", names::note_marker_out(o.ref_position)));
|
||||
|
||||
+14
-13
@@ -548,23 +548,32 @@ pub const REF_POINTS: [&str; 9] = [
|
||||
"BottomRightPoint",
|
||||
];
|
||||
|
||||
/// IDML `PageNumberStyle` of a section, also written for `FootnoteNumberingStyle`.
|
||||
pub fn number_style_out(n: NumberStyle) -> &'static str {
|
||||
match n {
|
||||
NumberStyle::Arabic | NumberStyle::ArabicLeadingZero | NumberStyle::ArabicThreeDigits | NumberStyle::ArabicFourDigits => "Arabic",
|
||||
NumberStyle::Arabic => "Arabic",
|
||||
NumberStyle::ArabicLeadingZero => "SingleLeadingZeros",
|
||||
NumberStyle::ArabicThreeDigits => "DoubleLeadingZeros",
|
||||
NumberStyle::ArabicFourDigits => "TripleLeadingZeros",
|
||||
NumberStyle::UpperRoman => "UpperRoman",
|
||||
NumberStyle::LowerRoman => "LowerRoman",
|
||||
NumberStyle::UpperLetters => "UpperLetters",
|
||||
NumberStyle::LowerLetters => "LowerLetters",
|
||||
NumberStyle::Symbols => "Symbols",
|
||||
NumberStyle::Kanji => "Kanji",
|
||||
}
|
||||
}
|
||||
pub fn number_style_in(s: &str) -> NumberStyle {
|
||||
match s {
|
||||
"SingleLeadingZeros" => NumberStyle::ArabicLeadingZero,
|
||||
"DoubleLeadingZeros" => NumberStyle::ArabicThreeDigits,
|
||||
"TripleLeadingZeros" => NumberStyle::ArabicFourDigits,
|
||||
"UpperRoman" => NumberStyle::UpperRoman,
|
||||
"LowerRoman" => NumberStyle::LowerRoman,
|
||||
"UpperLetters" => NumberStyle::UpperLetters,
|
||||
"LowerLetters" => NumberStyle::LowerLetters,
|
||||
"Symbols" => NumberStyle::Symbols,
|
||||
"Kanji" => NumberStyle::Kanji,
|
||||
_ => NumberStyle::Arabic,
|
||||
}
|
||||
}
|
||||
@@ -632,7 +641,7 @@ pub fn pt(x: f64, y: f64) -> String {
|
||||
// ---------- list numbering ----------
|
||||
|
||||
/// A paragraph's `NumberingFormat` ("A, B, C, D...", "001, 002, 003...", …), told apart by its
|
||||
/// first item. None for formats with no counterpart (CJK, Arabic, Hebrew, …).
|
||||
/// first item. None for formats with no counterpart (other CJK forms, Arabic, Hebrew, …).
|
||||
pub fn numbering_format_in(s: &str) -> Option<NumberStyle> {
|
||||
Some(match s.split(',').next().unwrap_or("").trim() {
|
||||
"1" => NumberStyle::Arabic,
|
||||
@@ -643,6 +652,7 @@ pub fn numbering_format_in(s: &str) -> Option<NumberStyle> {
|
||||
"i" => NumberStyle::LowerRoman,
|
||||
"A" => NumberStyle::UpperLetters,
|
||||
"a" => NumberStyle::LowerLetters,
|
||||
"一" => NumberStyle::Kanji,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -656,6 +666,7 @@ pub fn numbering_format_out(n: NumberStyle) -> &'static str {
|
||||
NumberStyle::LowerRoman => "i, ii, iii, iv...",
|
||||
NumberStyle::UpperLetters => "A, B, C, D...",
|
||||
NumberStyle::LowerLetters => "a, b, c, d...",
|
||||
NumberStyle::Kanji => "一, 二, 三, 四...",
|
||||
}
|
||||
}
|
||||
/// Label text in IDML metacharacters (a tab is `^t`).
|
||||
@@ -665,19 +676,9 @@ pub fn list_text_out(s: &str) -> String {
|
||||
|
||||
// ---------- footnote options ----------
|
||||
|
||||
pub fn note_style_out(n: NumberStyle) -> &'static str {
|
||||
match n {
|
||||
NumberStyle::ArabicLeadingZero => "SingleLeadingZeros",
|
||||
NumberStyle::ArabicThreeDigits => "DoubleLeadingZeros",
|
||||
NumberStyle::ArabicFourDigits => "TripleLeadingZeros",
|
||||
n => number_style_out(n),
|
||||
}
|
||||
}
|
||||
/// IDML `FootnoteNumberingStyle`: the page number styles and `Asterisks`.
|
||||
pub fn note_style_in(s: &str) -> NumberStyle {
|
||||
match s {
|
||||
"SingleLeadingZeros" => NumberStyle::ArabicLeadingZero,
|
||||
"DoubleLeadingZeros" => NumberStyle::ArabicThreeDigits,
|
||||
"TripleLeadingZeros" => NumberStyle::ArabicFourDigits,
|
||||
"Asterisks" => NumberStyle::Symbols,
|
||||
s => number_style_in(s),
|
||||
}
|
||||
|
||||
@@ -628,6 +628,48 @@ fn odd_page_break_ends_its_paragraph_and_keeps_its_parity() {
|
||||
assert_eq!(cs.frames[1].lines.iter().map(|l| l.para).collect::<Vec<_>>(), vec![1]);
|
||||
}
|
||||
|
||||
/// A section numbered in Kanji: page names, the folio a page-number marker draws, and a round trip.
|
||||
#[test]
|
||||
fn kanji_page_numbers() {
|
||||
let designmap = DESIGNMAP.replace(">LowerRoman<", ">Kanji<");
|
||||
let bytes = zip_files(&[
|
||||
("designmap.xml", &designmap),
|
||||
("Resources/Graphic.xml", GRAPHIC),
|
||||
("Resources/Styles.xml", STYLES),
|
||||
("Resources/Preferences.xml", PREFS),
|
||||
("MasterSpreads/MasterSpread_m1.xml", MASTER),
|
||||
("Spreads/Spread_sp1.xml", SPREAD),
|
||||
("Stories/Story_s1.xml", STORY),
|
||||
]);
|
||||
let mut d = import_idml(&bytes).unwrap();
|
||||
assert_eq!(d.sections[0].style, designcraft_doc::NumberStyle::Kanji);
|
||||
assert_eq!((d.page_name(0), d.page_name(1)), ("五".to_string(), "六".to_string()));
|
||||
let back = import_idml(&export_idml(&d)).unwrap();
|
||||
assert_eq!(back.sections[0].style, designcraft_doc::NumberStyle::Kanji);
|
||||
assert_eq!(back.page_name(1), "六");
|
||||
|
||||
// The marker draws the same glyphs as the page name typed in its place.
|
||||
let sid = *d.stories.keys().next().unwrap();
|
||||
let at = d.story(sid).unwrap().text.find(st::PAGE_NUMBER).unwrap();
|
||||
let marker = |d: &Document| {
|
||||
let cs = designcraft_compose::compose_story(d, sid, &Default::default());
|
||||
cs.frames
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(fi, f)| {
|
||||
let gids: Vec<u32> = f.lines.iter().flat_map(|l| &l.glyphs).filter(|g| g.byte == at).map(|g| g.gid).collect();
|
||||
(!gids.is_empty()).then_some((fi, gids))
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
let (frame, folio) = marker(&d);
|
||||
let name = d.page_name(frame);
|
||||
assert_eq!(name.len(), st::PAGE_NUMBER.len_utf8(), "same byte length keeps the story's ranges");
|
||||
let story = d.story_mut(sid).unwrap();
|
||||
story.text.replace_range(at..at + name.len(), &name);
|
||||
assert_eq!(marker(&d), (frame, folio));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_hand_written_fixture() {
|
||||
let d = import_idml_with(&fixture(), &|_| None).unwrap();
|
||||
@@ -2332,7 +2374,7 @@ fn list_numbering_format_expression_and_bullet_import_and_round_trip() {
|
||||
let (table, _) = d.styles.resolve_para_style("Text/Table");
|
||||
assert_eq!(table.number_label(7), "Tabel 007\t");
|
||||
let (kanji, _) = d.styles.resolve_para_style("Text/Kanji");
|
||||
assert_eq!(kanji.number_style, designcraft_doc::NumberStyle::Arabic);
|
||||
assert_eq!(kanji.number_style, designcraft_doc::NumberStyle::Kanji);
|
||||
let (point, _) = d.styles.resolve_para_style("Text/Point");
|
||||
assert_eq!(point.bullet_label(), "\u{25A0}\u{2002}");
|
||||
};
|
||||
|
||||
@@ -4457,6 +4457,7 @@ fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
("lowerLetters", "a, b, c, d..."),
|
||||
("arabicLeadingZero", "01, 02, 03..."),
|
||||
("symbols", "*, †, ‡, §..."),
|
||||
("kanji", "一, 二, 三, 四..."),
|
||||
],
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
@@ -2704,6 +2704,7 @@ const TABLE: &[(&str, [&str; 5])] = &[
|
||||
("Linear", ["Linear", "Linear", "Linear", "Linear", "线性"]),
|
||||
("Radial", ["Radial", "Radial", "Radial", "Radial", "径向"]),
|
||||
("Arabic", ["Arabic", "Arabic", "Arabic", "Arabic", "阿拉伯语"]),
|
||||
("Kanji", ["Kanji", "Kanji", "Kanji", "漢数字", "汉字数字"]),
|
||||
("Beveled Rectangle", ["Beveled Rectangle", "Beveled Rectangle", "Beveled Rectangle", "Beveled Rectangle", "斜角矩形"]),
|
||||
("Chinese", ["Chinese", "Chinese", "Chinese", "Chinese", "中文"]),
|
||||
(
|
||||
|
||||
@@ -650,6 +650,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Linear", "خطي"),
|
||||
("Radial", "شعاعي"),
|
||||
("Arabic", "العربية"),
|
||||
("Kanji", "أرقام كانجي"),
|
||||
("Beveled Rectangle", "مستطيل مشطوف"),
|
||||
("Chinese", "الصينية"),
|
||||
("Constrain proportions for width and height", "تقييد نسب العرض والارتفاع"),
|
||||
|
||||
@@ -1214,6 +1214,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Linear", "Lineare"),
|
||||
("Radial", "Radiale"),
|
||||
("Arabic", "Arabo"),
|
||||
("Kanji", "Kanji"),
|
||||
("Beveled Rectangle", "Rettangolo smussato"),
|
||||
("Chinese", "Cinese"),
|
||||
("Constrain proportions for width and height", "Vincola proporzioni di larghezza e altezza"),
|
||||
|
||||
@@ -1330,6 +1330,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Linear", "線形"),
|
||||
("Radial", "放射状"),
|
||||
("Arabic", "アラビア語"),
|
||||
("Kanji", "漢数字"),
|
||||
("Beveled Rectangle", "角を面取りした長方形"),
|
||||
("Chinese", "中国語"),
|
||||
("Constrain proportions for width and height", "幅と高さの比率を固定"),
|
||||
|
||||
@@ -641,6 +641,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Linear", "Linear"),
|
||||
("Radial", "Radial"),
|
||||
("Arabic", "Árabe"),
|
||||
("Kanji", "Kanji"),
|
||||
("Beveled Rectangle", "Retângulo chanfrado"),
|
||||
("Chinese", "Chinês"),
|
||||
("Constrain proportions for width and height", "Restringir proporções entre largura e altura"),
|
||||
|
||||
@@ -1210,6 +1210,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Linear", "Лінійний"),
|
||||
("Radial", "Радіальний"),
|
||||
("Arabic", "Арабська"),
|
||||
("Kanji", "Кандзі"),
|
||||
("Beveled Rectangle", "Прямокутник зі скошеними кутами"),
|
||||
("Chinese", "Китайська"),
|
||||
("Constrain proportions for width and height", "Зберігати співвідношення ширини та висоти"),
|
||||
|
||||
Reference in New Issue
Block a user