diff --git a/crates/text/src/lib.rs b/crates/text/src/lib.rs index 3df5a74..dae8a6d 100644 --- a/crates/text/src/lib.rs +++ b/crates/text/src/lib.rs @@ -1057,31 +1057,50 @@ fn is_hangul(c: char) -> bool { matches!(c, '\u{1100}'..='\u{11FF}' | '\u{3130}'..='\u{318F}' | '\u{A960}'..='\u{A97F}' | '\u{AC00}'..='\u{D7FF}') } -/// Closing East Asian punctuation that may hang past the end of a line (`hangingPunct`). +/// East Asian commas and full stops, the punctuation PowerPoint lets hang past the end of a line +/// (`hangingPunct`). Closing brackets don't hang: "「。」" overflowing by its "」" moves to the next +/// line whole. fn is_hanging_punct(c: char) -> bool { + matches!(c, '、' | '。' | ',' | '.' | '、' | '。') +} + +/// Small kana, the prolonged sound mark and iteration marks: PowerPoint keeps them off the start +/// of a line (strict Japanese line breaking), where UAX #14's default allows a break before them. +fn is_cj_nonstarter(c: char) -> bool { matches!( c, - '、' | '。' - | ',' - | '.' - | ':' - | ';' - | '!' - | '?' - | ')' - | '」' - | '』' - | '】' - | '〕' - | '〉' - | '》' - | '〙' - | '〛' - | '}' - | ']' - | '」' - | '、' - | '。' + 'ぁ' | 'ぃ' + | 'ぅ' + | 'ぇ' + | 'ぉ' + | 'っ' + | 'ゃ' + | 'ゅ' + | 'ょ' + | 'ゎ' + | 'ゕ' + | 'ゖ' + | 'ァ' + | 'ィ' + | 'ゥ' + | 'ェ' + | 'ォ' + | 'ッ' + | 'ャ' + | 'ュ' + | 'ョ' + | 'ヮ' + | 'ヵ' + | 'ヶ' + | 'ー' + | '々' + | '〻' + | 'ゝ' + | 'ゞ' + | 'ヽ' + | 'ヾ' + | '\u{31F0}'..='\u{31FF}' + | 'ァ'..='ー' ) } @@ -1113,7 +1132,7 @@ fn break_opportunities(text: &str, latin_break: bool) -> (Vec, Vec) for (i, w) in chars.windows(2).enumerate() { let (prev, next) = (w[0], w[1]); // PowerPoint never breaks after a zero-width space, nor before an em dash (only after it). - let keep = if (is_hangul(prev) && is_hangul(next)) || prev == '\u{200B}' || next == '\u{2014}' { + let keep = if (is_hangul(prev) && is_hangul(next)) || prev == '\u{200B}' || next == '\u{2014}' || is_cj_nonstarter(next) { false } else { latin_break || !matches!(prev, '/' | '\\') || next.is_whitespace() diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 8613e02..112e100 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -1013,3 +1013,18 @@ fn a_line_broken_at_a_soft_hyphen_shows_a_hyphen() { assert_eq!(wide.lines.len(), 1); assert!(wide.runs.iter().all(|r| r.cells.iter().all(|c| c.1 != '\u{AD}' || r.glyphs.iter().all(|g| g.0 != r.face.glyph_for('-'))))); } + +#[test] +fn small_kana_and_closing_brackets_follow_powerpoint_kinsoku() { + // "っ" and "ー" never start a line: the character before them moves down too. + let w = one_line("あいうえお") + 0.5; + for (text, first) in [("あいうえおっかき", "あいうえ"), ("あいうえおーかき", "あいうえ"), ("あいうえおかき", "あいうえお")] + { + let lines = wrapped(text, w, |p| p.hanging_punct = Some(true)); + assert_eq!(lines[0], first, "{text}: {lines:?}"); + } + // A closing bracket doesn't hang like "。" does: "「。」" overflowing by "」" moves down whole. + let w = one_line("あいうえ「。") + 0.5; + let lines = wrapped("あいうえ「。」かき", w, |p| p.hanging_punct = Some(true)); + assert_eq!(lines[0], "あいうえ", "{lines:?}"); +}