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>
72 lines
4.1 KiB
Markdown
72 lines
4.1 KiB
Markdown
# 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
|