ekp/.phrase/phases/phase-p1p2-20260726/NOTES.md
Kinneyzhang 64eb2f38d5 refactor!: per-gap break permissions — punctuation as first-class boxes
Kinsoku moves from merge-based box attachment to a general break-
permission layer, the foundation for no-break spans, NBSP, verbatim
atoms and punctuation protrusion:

- tokenizer: every CJK char (punctuation included) is its own box;
  the hold/attach machinery is gone.  Fixes two latent bugs: a second
  consecutive closer (字。」) could start a line, and an opener held
  across spaces reordered box content vs the original string
- ekp--str-type: cjk-punct splits into cjk-open (Ps/Pi) / cjk-close
- ekp-para gains breaks-allowed (bool-vector) + forbidden-positions
  (sparse, for C); glue at unbreakable punct gaps is nws, so
  punctuation hugs its content and justification stretch no longer
  opens gaps at 「x or x。
- halfwidth kinsoku: boxes consisting purely of .,;:!?)]}’”»›… may
  not start a line ((&[{‘“«‹ may not end one) — an ASCII comma after
  a CJK char no longer dangles at line start (old behavior violated
  CLREQ)
- DP (elisp 1D, elisp loose 2D, C): forbidden gaps are skipped as
  candidates while the line keeps extending; the emergency fallback
  generalizes from single boxes to atomic runs (no permitted break
  inside), recording real gap counts so multi-box emergency lines
  render correctly
- C module 1.2: ekp-c-break-with-arrays 11→12 args
  (forbidden-positions), batch vectors 12 elements, version gate bumped

Hyphenation improvement: 「Hello / Hello」 previously failed the
word regexp as merged boxes and were never hyphenated; as separate
boxes they hyphenate normally.

Tests: 48 ERT green (split tests migrated to the new box contract;
new unit test for break permissions and a rendered-output kinsoku
sweep across widths); 300-case fuzz 0 failures with C 1.2 parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 21:05:50 +08:00

72 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# P1+P2 功能阶段笔记(2026-07-26 起)
> 用户指令:高质量完成 P1(标点挤压、ragged 模式、no-break API)+ P2(悬挂、
> parshape/首行缩进、连续标点);代码块等特殊文本需正确处理;**通用机制优先,
> 万不得已才做场景特化**。
## 总体架构决策
1. **地基 = 逐间隙断行许可(breaks-allowed)+ 标点独立成盒**
禁则从"吞噬式附着"迁移为 DP 层的断点禁止;所有后续特性(NBSP、
no-break 区间、行内 verbatim 原子、标点类别)都是这套机制的实例。
2. **Emacs 显示引擎约束(已确证)**:无法缩减字形 advance(无负宽
display)→ CLREQ 行中标点挤压不可渲染;行首/行尾挤压视觉上等价于
"悬挂"(protrusion)→ P1-1 + P2-4 + P2-6 统一为**边缘突出机制**,
按字符类配比率(可扩展到拉丁连字符突出 = microtype)。
3. ragged-right/left/center:DP 侧 = 刚性 glue(stretch/shrink 数组置零)
+ 每行额外伸展量 R(badness 以 R 为 flexibility);渲染侧分派剩余量
(右/左/对半)。C 只需 +1 标量。
4. parshape/首行缩进:每行宽依赖行号 → 复用 looseness 的 2D DP,
elisp-only(C 自动旁路,同 looseness 先例)。
5. verbatim:段落级豁免(region 层谓词/属性)+ 行内原子
(ekp-no-break 属性 → 禁断点 + 刚性 glue + 禁断词)。
## 关键实现事实(读码结论)
- tokenizer 附着逻辑在 ekp-utils.el `ekp--handle-cjk-char/latin-char`
(开放标点 hold-and-prepend;闭合标点 append-to-prev)。
**已知老 bug**:连续闭合标点(字。」)第二个独立成盒且断点未禁止 →
」可出现行首;开放标点跨空格 hold 还会导致盒序与原文顺序不一致。
迁移后两者都根治。
- `ekp--str-type` 返回 space/latin/cjk/cjk-punct → 拆成 cjk-open
(opening-punct-p:general-category Ps/Pi)/ cjk-close(fw-punct-p
且非 open)。“” 特例保持 'cjk。
- `ekp--glue-type` 新矩阵(保持旧拓扑等价):space→nws;
before=open→nws(原盒内);after=close→nws(原盒内;close-close
从 cws 改为 nws,属有意修正);latin-latin→lws;cjk-cjk→cws;
cjk/latin 混→mws;其余含标点→cws。
- breaks-allowed 规则:`allowed[k] = !(tail(box[k-1])=open || head(box[k])=close)`,
k∈[1,n-1];k=n(段末)恒可。存 bool-vector(elisp DP 用)+
forbidden-positions int 向量(C 打包用,稀疏,仿 hyphen-positions)。
- DP 改动(elisp `ekp--dp-run-1d` + C `dp_process_position` 镜像):
候选 k 需 allowed;不 allowed 时**不 throw**继续延伸;
紧急兜底从 single-box 推广为 atomic-run(i 到 k 间无允许断点);
多盒紧急行需记录 gaps 计数(渲染 normal 路径 clamp ≥0 自然溢出)。
- C 桥:`ekp-c-break-with-arrays` 11→12 参(forbidden-positions),
batch 向量同步;`ekp-c-set-penalties` 后续 ragged 加 extra-stretch
标量;protrusion 再加两数组(head/tail protrude px)。每次 API 变
动 bump EKP_VERSION_MINOR + `ekp-c-module-required-version`
- 隐性收益:「Hello / Hello」 之前整盒无法匹配断词正则(左右标点类
不含 CJK 引号)→ 拆盒后可正常断词。
- 测试影响:tests/ekp-tests.el 里 split-* 结构测试要改为新盒契约;
新增行为级禁则测试(任意宽度:行首无 close、行尾无 open、
字。」不拆)。fuzz 断言与引擎无关,应保持 0 失败。
## 阶段与提交计划
- [ ] A 地基:标点成盒 + breaks-allowed + DP/C(1.2)+ 测试迁移
- [ ] B no-break API:ekp-no-break 属性、NBSP/WJ/2060/202F、刚性 glue
- [ ] C 对齐模式:ekp-alignment(justify|ragged-right|ragged-left|center)
+ C extra-stretch(1.3)
- [ ] D 突出/悬挂:ekp-protrusion(类→左右比率)+ C 两数组(1.4)
+ region 层宽度补偿
- [ ] E parshape:ekp-parshape + ekp-first-line-indent(2D,elisp-only)
- [ ] F verbatim:段落豁免(region 谓词/属性)+ 行内原子(含禁断词)
- [ ] G 文档(readme×2 DEVELOPER×2)+ GUI 目检 + 记忆更新
## 验证清单(每阶段)
byte-compile 零警告(error-on-warn)→ 47+ ERT → C 重建 + parity →
fuzz 300 → 提交。改 ekp_c/ 后必须 make clean && make。
Emacs: /Applications/Emacs.app/Contents/MacOS/Emacs