Text: Japanese line breaking keeps small kana and "ー" off line starts; only commas and stops hang
Observed on PowerPoint's export of a wrapped Japanese paragraph (24 pt, 11 characters a line): it breaks "けません(禁則処" / "理)。ー長音もで" / "す。っゃゅょ小書き文字", i.e. small kana (っゃゅょ…), the prolonged sound mark and iteration marks never start a line (strict kinsoku), where UAX #14's default allows it. And "し、句読点「、」や" / "「。」は…": with hanging punctuation on, a closing bracket that overflows does not hang; only the ideographic comma and full stop (and their full-width and half-width forms) do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d30e4edb45
commit
257e79de27
|
|
@ -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<bool>, Vec<bool>)
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -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:?}");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue