fix(app-iced): 必修1 「72 字符」改成逐行口径,上限 435 → 424
代码 r2 的复核:`435 / 6.0526 = 71.9` 是**整段平均步进**换来的数,不是每行都 ≤72; 它实测交付图里就有 **73 字符**行(`guess. Nothing on this page is wired to real data yet: the layout is, the`, ink 428px)。上一轮是系数错,这一轮是**把平均当成了逐行上界**。 选 **(a)**:不变式改成逐行可验证,cap 跟着压到能满足它的地方。依据是 FreeType 量宽 + 贪心词折行的模拟表 (同一段 361 字符正文扫列宽): | 列宽 | 最长一行 | 该行 ink | 判定 | |---|---|---|---| | 435(= 平均口径上界) | 73 字符 | 428px | ✗ | | 428 | 73 字符 | 428px | ✗ | | 426 | 71 字符 | 426px | ✓ 只剩 1 字符余量 | | **424**(4px 栅格) | **67 字符** | **402px** | ✓ 取这个 | - `measure_cap_px()` = `min(逐行安全宽 424, 平均口径上界 435)` = **424**;平均口径另立 `measure_average_bound_px()`(435)并在文档里标明「只是上界,不是取值」。 - 单测 `line_length_is_capped_at_72_characters` 改成**两侧夹逼**:`402 ≤ cap < 428` —— 放大到 428 会重新出现 73 字符行(红),压到 402 以下白丢宽度(也红);另断言 `cap < measure_average_bound_px()`,防止有人把平均口径当上限。 - §3 新增「逐行口径」小节:模拟表 + **作用域**(承诺只覆盖 14px 正文;12px 辅助文案是 1~2 行短句, 不做逐行承诺;CJK 全角 1em 另行定)。 - 四页文档与 `pages/mod.rs` 的 ≤435 同步改 424。
This commit is contained in:
1 parent
a650f8184b
commit
ddf6937b2b
7 files changed
+93
-29
No files matched your search
@@ -150,7 +150,17 @@ fn chars_per_line(width: f32) -> f32 {
|
||||
width / measured_advance_px()
|
||||
}
|
||||
|
||||
/// 行宽上限必须真的停在 72 字符:名字、常量、实测三者一致。
|
||||
/// **逐行**折行模拟结果(FreeType `getlength` 量宽 + 贪心词折行,脚本见模块文档 §3)。
|
||||
///
|
||||
/// 这两组数是外部实测:在同一段正文(361 字符)上扫列宽得到——
|
||||
/// `SAFE` 是交付宽度下的最长一行,`BREAK` 是第一档会冒出 73 字符行的宽度。
|
||||
/// 有了它,Rust 侧不用字体引擎也能把「上限必须夹在这两者之间」写成断言。
|
||||
const SIM_SAFE_MAX_CHARS: f32 = 67.0;
|
||||
const SIM_SAFE_MAX_INK: f32 = 402.0;
|
||||
const SIM_BREAK_INK: f32 = 428.0;
|
||||
const SIM_BREAK_CHARS: f32 = 73.0;
|
||||
|
||||
/// 行宽上限必须真的停在 72 字符:名字、常量、**逐行模拟**三者一致。
|
||||
#[test]
|
||||
fn line_length_is_capped_at_72_characters() {
|
||||
// ① 常量对得上实测步进(14px 下的平均步进)
|
||||
@@ -161,24 +171,47 @@ fn line_length_is_capped_at_72_characters() {
|
||||
"CHAR_ADVANCE_EM = {CHAR_ADVANCE_EM} 与实测步进 {:.4}px/字符对不上",
|
||||
measured_advance_px()
|
||||
);
|
||||
|
||||
// ② 上限是对着实测步进算出来的 435(floor),下限 272
|
||||
assert_eq!(measure_cap_px(), 435.0, "floor(14 × 0.43233 × 72)");
|
||||
assert_eq!(measure_floor_px(), 272.0, "floor(14 × 0.43233 × 45)");
|
||||
|
||||
// ③ **本条是 tower 要的断言**:按实测步进,上限列宽下一行放得下 ≤72 字符
|
||||
let chars_at_cap = chars_per_line(measure_cap_px());
|
||||
// ② 上限是**逐行**模拟出来的 424,不是「平均步进 × 72」的算术值 435
|
||||
assert_eq!(measure_cap_px(), 424.0, "逐行安全宽(4px 栅格)");
|
||||
assert_eq!(
|
||||
measure_average_bound_px(),
|
||||
435.0,
|
||||
"floor(14 × 0.43233 × 72)"
|
||||
);
|
||||
assert!(
|
||||
chars_at_cap <= MAX_MEASURE_CHARS,
|
||||
"上限 {:?}px 按实测步进能放 {chars_at_cap:.1} 字符,超过 {MAX_MEASURE_CHARS}",
|
||||
measure_cap_px() < measure_average_bound_px(),
|
||||
"平均口径上界必须比逐行安全宽**更宽**——这说明 cap 不是拿平均算出来的"
|
||||
);
|
||||
|
||||
// ③ 交付宽度下,模拟出来的最长一行装得下,且字符数 ≤72
|
||||
assert!(
|
||||
SIM_SAFE_MAX_INK <= measure_cap_px(),
|
||||
"模拟最长行 {}px 超过上限 {}px",
|
||||
SIM_SAFE_MAX_INK,
|
||||
measure_cap_px()
|
||||
);
|
||||
assert!(
|
||||
chars_at_cap > MAX_MEASURE_CHARS - 1.0,
|
||||
"上限太紧,白丢一行字符"
|
||||
SIM_SAFE_MAX_CHARS <= MAX_MEASURE_CHARS,
|
||||
"模拟最长行 {SIM_SAFE_MAX_CHARS} 字符超过 {MAX_MEASURE_CHARS}"
|
||||
);
|
||||
|
||||
// ④ 回归护栏:旧的 0.5em 口径(504px)在同一实测步进下是 83 字符 —— 这条专拦「改回去」
|
||||
// ④ **两侧夹逼**:这是本条真正的护栏。
|
||||
// 放大到 SIM_BREAK_INK(428)逐行模拟就会重新出现 73 字符行;
|
||||
// 压到 SAFE 以下则纯属白丢宽度。两个方向都会红。
|
||||
assert!(
|
||||
measure_cap_px() < SIM_BREAK_INK,
|
||||
"上限 ≥{SIM_BREAK_INK}px 时逐行模拟会再次出现 {SIM_BREAK_CHARS} 字符行——请重跑折行模拟"
|
||||
);
|
||||
assert!(
|
||||
measure_cap_px() >= SIM_SAFE_MAX_INK,
|
||||
"上限 {}px 比模拟出来的最长行 {}px 还窄,白丢宽度",
|
||||
measure_cap_px(),
|
||||
SIM_SAFE_MAX_INK
|
||||
);
|
||||
|
||||
// ⑤ 平均口径的回归护栏:旧 0.5em 的 504px 在实测步进下能放 83 字符
|
||||
assert!(
|
||||
chars_per_line(504.0) > MAX_MEASURE_CHARS + 10.0,
|
||||
"504px 在实测步进下应能放 >82 字符(旧口径的破绽),现在算出来 {:.1}",
|
||||
@@ -187,9 +220,10 @@ fn line_length_is_capped_at_72_characters() {
|
||||
|
||||
for width in [600.0, 840.0, 900.0, 1180.0, 1440.0, 2560.0] {
|
||||
let column = content_column_width(width);
|
||||
// 平均口径只是次要护栏(逐行口径见上面的模拟常数)
|
||||
assert!(
|
||||
chars_per_line(column) <= MAX_MEASURE_CHARS,
|
||||
"{width} 宽窗口的正文行宽 {:.1} 字符,超过 {MAX_MEASURE_CHARS} 上限",
|
||||
"{width} 宽窗口的平均行宽 {:.1} 字符,超过 {MAX_MEASURE_CHARS}",
|
||||
chars_per_line(column)
|
||||
);
|
||||
assert!(column <= measure_cap_px() + f32::EPSILON);
|
||||
|
||||
Reference in new issue
Block a user