From 5248ad683cd2e11adf889d90e5d3a13c958fcf36 Mon Sep 17 00:00:00 2001 From: con3code Date: Tue, 20 Oct 2026 19:33:13 +0900 Subject: [PATCH] Mojikumi: a middle dot beside a bracket keeps only its quarter em (JLREQ 3.1.4) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2 --- crates/doc/src/mojikumi.rs | 27 ++++++++++++++++++++------- docs/cjk.md | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/doc/src/mojikumi.rs b/crates/doc/src/mojikumi.rs index daabe34d7..54f3269c0 100644 --- a/crates/doc/src/mojikumi.rs +++ b/crates/doc/src/mojikumi.rs @@ -127,13 +127,21 @@ impl<'a> Rules<'a> { return Aki::fixed(if self.base == Base::LineEndHalfEm { 0.0 } else { trailing(left) }); } // Consecutive punctuation in the Japanese sets (JLREQ 3.1.4): opening brackets together, - // and closing brackets, commas and full stops together, touch. Other neighbours keep the - // larger blank, so a closing bracket and an opening one are half an em apart. The Chinese - // sets keep the larger blank throughout. + // and closing brackets, commas and full stops together, touch. A middle dot (・:;) after + // a closing bracket or before an opening one keeps only its own quarter em. Other + // neighbours keep the larger blank, so a closing bracket and an opening one are half an + // em apart. The Chinese sets keep the larger blank throughout. let opening = |c| matches!(c, 1 | 26 | 27); let closing = |c| matches!(c, 2 | 6 | 21 | 28..=31); - if self.base != Base::Chinese && ((opening(left) && opening(right)) || (closing(left) && closing(right))) { - return Aki::default(); + let closing_bracket = |c| matches!(c, 2 | 28 | 29); + let middle_dot = |c| matches!(c, 5 | 32); + if self.base != Base::Chinese { + if (opening(left) && opening(right)) || (closing(left) && closing(right)) { + return Aki::default(); + } + if (closing_bracket(left) && middle_dot(right)) || (middle_dot(left) && opening(right)) { + return Aki { min: 0.0, desired: 0.25, max: 0.25, priority: 1, discrete: false }; + } } let punctuation = trailing(left).max(leading(right)); if punctuation > 0.0 { @@ -260,12 +268,17 @@ mod tests { for (a, b) in [('」', '「'), ('。', '「'), ('、', '('), ('」', '漢'), ('漢', '「')] { assert_eq!(aki(a, b), 0.5, "{set}: {a}{b}"); } - assert_eq!(aki('」', '・'), 0.5, "{set}: the larger blank stays"); + // A middle dot keeps its quarter em beside a bracket (JLREQ 3.1.4, Figure 69 ⑦). + for (a, b) in [('」', '・'), (')', ':'), ('』', ';'), ('・', '「'), (';', '('), (':', '〈')] { + assert_eq!(aki(a, b), 0.25, "{set}: {a}{b}"); + } + // After a comma or full stop the larger blank stays. + assert_eq!(aki('、', '・'), 0.5, "{set}: 、・"); } // The Chinese sets are unchanged: the larger blank stays between any two marks. for set in ["SimpChineseDefault", "TradChineseDefault"] { let rules = Rules::resolve(&styles, set).unwrap().unwrap(); - for (a, b) in [('」', '。'), (',', ')'), ('「', '『'), ('」', '「')] { + for (a, b) in [('」', '。'), (',', ')'), ('「', '『'), ('」', '「'), ('」', '・'), ('・', '「')] { assert_eq!(rules.pair(class(a), class(b)).desired, 0.5, "{set}: {a}{b}"); } } diff --git a/docs/cjk.md b/docs/cjk.md index d37e9c29c..68839e0e8 100644 --- a/docs/cjk.md +++ b/docs/cjk.md @@ -37,7 +37,7 @@ presets or tables are copied. Our default mojikumi and kinsoku tables are built | Vertical metrics and shaping | done: upright runs shaped top to bottom (`vert`; `vkrn`/`vpal` when asked) with `vmtx`/`VORG` advances and origins, centred on the ideographic em box (`BASE`, else OS/2); turned runs shaped horizontally, without `vrt2`; Horizontal Kana (`hkna`) takes the vertical kana (`vkna`) in upright runs. Default location of variable fonts only (no `VVAR`) | | Kinsoku | named (hard, soft, Chinese, Korean) and custom kinsoku sets, bunri-kinshi, rensuuji, hanging punctuation (regular, force); without a set, fixed no-start / no-end lists and a break anywhere between CJK characters; kinsoku type (push in / push out priorities) kept but not applied | | Korean line breaking | at spaces (Hangul everywhere, hanja in Korean text); per-paragraph character-based breaking (`koreanCharBreaks`); DesignCraft-only, not in IDML | -| Mojikumi | applied in composition: the built-in base sets (line end half-width, line end one em, Simplified and Traditional Chinese) and the document's tables with their override rows, kept through IDML; Preflight notes tables it can't apply. In the Japanese sets consecutive punctuation closes up (JLREQ 3.1.4: 」。, 「『 touch; 」「 half an em apart) | +| Mojikumi | applied in composition: the built-in base sets (line end half-width, line end one em, Simplified and Traditional Chinese) and the document's tables with their override rows, kept through IDML; Preflight notes tables it can't apply. In the Japanese sets consecutive punctuation closes up (JLREQ 3.1.4: 」。, 「『 touch; 」・ a quarter em, 」「 half an em apart). Still open: 、・ and ・・ take the sum of both blanks in JLREQ (Table 1 notes) | | Aki, tsume, jidori | done ([cjk-typography.md](cjk-typography.md)) | | Character alignment, leading model | em box top / centre / bottom, ICF top / bottom (`BASE` `icft`/`icfb`, else the em box inset by 5%), roman baseline; aki above / below, centre (centre down as centre); under aki below a line's leading is the space to the line below it; with the Japanese composers (`Composer::Japanese`, `JapaneseSingleLine`, IDML `HL Composer J` / `HL Single J`), the others set lines baseline to baseline on the Roman baseline | | Tate-chu-yoko | manual, with offsets; auto tate-chu-yoko of short digit runs (with roman letters when asked) in vertical text, narrowed to fit the em (`type.autoTateChuYoko`, IDML `AutoTcy`, `AutoTcyIncludeRoman`); manual groups of 3+ digits overflow the em |