atmos/os_lib/layout/line_break.rs
1//! 行の折り返し機会(soft wrap opportunity)の判定。
2//!
3//! # なぜ必要か
4//!
5//! 従来の折り返しは**1 文字単位**だった。次の文字が入らなければ
6//! そこで改行するので、英単語が語中で分割されていた。
7//! CSS の既定(`overflow-wrap: normal` / `word-break: normal`)では
8//! **単語は分割しない**。収まらなければはみ出すのが正しい。
9//!
10//! 実害は見た目だけではない。min-content(最小内容幅)は
11//! 「available=1 で組んだときの幅」で測っているため、語中で切れる実装だと
12//! min-content が 1 文字ぶんになる。flex の縮小はこの値を下限に使うので、
13//! **項目を 1 文字幅まで潰してしまう**(実サイトのナビ項目 "Papers" が
14//! 縦一列に潰れた)。
15//!
16//! # 方針
17//!
18//! UAX #14 の完全実装はしない。実用上効く規則だけを入れる。
19//! - 空白の**後ろ**では改行できる
20//! - 漢字・かな等(表意文字)の前後では改行できる
21//! - それ以外(英数字が連なる部分)では改行できない
22//!
23//! 禁則処理(行頭・行末禁則)は既存の `vector_font` 側が持っており、
24//! ここでは扱わない。
25//!
26//! # 既知の差
27//!
28//! 厳密には、行の途中で始まった単語が収まらない場合は**その単語ごと
29//! 次の行へ送る**のが正しい。現状は送らずにはみ出させる。
30//! 計測(`vector_font`)と描画(`web_engine::layout`)で**同じ規則**を
31//! 使うことを優先した——両者がずれると、箱の大きさと実際の文字の位置が
32//! 食い違い、見た目の崩れとして現れる。
33
34/// その文字が表意文字(漢字・かな・全角記号など)か。
35///
36/// 日本語は単語の区切りが無いので、文字ごとに改行できる。
37/// 範囲は日本語の本文で実際に現れるものに絞る。
38pub fn is_ideographic(c: char) -> bool {
39 matches!(c as u32,
40 0x3040..=0x309F // ひらがな
41 | 0x30A0..=0x30FF // カタカナ
42 | 0x3400..=0x4DBF // CJK 拡張 A
43 | 0x4E00..=0x9FFF // CJK 統合漢字
44 | 0xF900..=0xFAFF // CJK 互換漢字
45 | 0xFF00..=0xFF60 // 全角英数・記号
46 | 0xFFE0..=0xFFE6
47 | 0x3000..=0x303F // 句読点・括弧など
48 )
49}
50
51/// `prev` と `next` の**間**で改行してよいか。
52///
53/// `break_all` が真(`word-break: break-all` 等)なら常に改行できる。
54pub fn can_break_between(prev: char, next: char, break_all: bool) -> bool {
55 if break_all {
56 return true;
57 }
58 // 空白の後ろでは改行できる。空白そのものは行末に置ける。
59 if prev.is_whitespace() {
60 return true;
61 }
62 // 次が空白なら、そこで行を切る意味は無い(空白は行末で潰れる)。
63 if next.is_whitespace() {
64 return false;
65 }
66 // 表意文字が絡む境目では改行できる。
67 if is_ideographic(prev) || is_ideographic(next) {
68 return true;
69 }
70 // 英数字が連なる部分(=単語の内部)では改行しない。
71 false
72}