docs: typography features, verbatim protection, engine notes

readme/readme_zh gain Typography and verbatim-protection sections
(alignment, hanging punctuation, parshape/first-line indent,
no-break characters, halfwidth kinsoku) plus the honest limitation
notes (mid-line CLREQ compression and left protrusion are not
renderable in Emacs).  DEVELOPER/DEVELOPER_ZH document the break-
permission layer, alignment extra-stretch, protrusion lockstep and
per-line specs (§5.1) and the C 1.4 API surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kinneyzhang 2026-07-26 21:44:56 +08:00
parent 4d9a018fbf
commit c703ce0226
5 changed files with 142 additions and 10 deletions

View File

@ -52,17 +52,28 @@
新增行为级禁则测试(任意宽度:行首无 close、行尾无 open、
字。」不拆)。fuzz 断言与引擎无关,应保持 0 失败。
## 阶段与提交计划
## 阶段与提交计划(全部完成 2026-07-26)
- [ ] 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 目检 + 记忆更新
- [x] A 地基 64eb2f3:标点成盒 + breaks-allowed + DP/C 1.2;顺带修复
连续闭合标点行首漏洞、open-punct 跨空格盒序错乱、「Hello 断词失效;
半角标点禁则(纯标点盒判定)
- [x] B ea96a6d:ekp-no-break 属性(刚性原子/禁断词)、NBSP/NNBSP/
FIGURE SPACE/WJ/ZWNBSP、命令 ×2;零 C 改动
- [x] C 57a3abe:ekp-alignment 四模式 + ekp-ragged-stretch-pixel;
C 1.3(set-penalties 第 7 参 extra-stretch,缺省归零)
- [x] D f6aa64b:ekp-protrusion 右缘悬挂(cjk-close/latin-close/hyphen
比率);DP/渲染/C 重建三处 lw=width+release 同步;C 1.4
(break-with-arrays 14 参);region 预留 protrusion-reserve;
仅右缘(左缘无法渲染,文档已注明)
- [x] E 720b1cd:ekp-parshape + ekp-first-line-indent(t=2em 按段落
CJK 字体);loose 2D 每行宽;C 旁路
- [x] F 4d9a018:ekp-verbatim 段落豁免 + ekp-region-skip-faces +
buffer-local skip-predicate;行内原子沿用 ekp-no-break;核心零改动
- [x] G:readme×2 排版特性/verbatim 章节、DEVELOPER×2 §5.1;GUI 目检
(悬挂+缩进+verbatim+auto-mode 齐行/ragged 两态截图确认)
最终状态:66 ERT 全绿,fuzz 300/300(每阶段跑),C 模块 1.4 两引擎
逐字节一致。行中挤压不可渲染(Emacs 无负宽 display)= 已知边界。
## 验证清单(每阶段)

View File

@ -165,6 +165,34 @@ inverts these four structurally (`ekp-unjustify-region`) — exact even
after the justified text was edited — and builds
`ekp-justify-region` / `ekp-auto-justify-mode` on top.
### 5.1 Break permissions, alignment, protrusion, shapes
- **Break permissions**: every CJK char (punctuation included) is its
own box; `ekp-para-breaks-allowed` forbids gaps per kinsoku (full-
and halfwidth), `ekp-no-break' spans and NBSP-family joiners.
Forbidden gaps carry no glue. The DP skips them as candidates while
the line keeps extending; the emergency fallback treats a run with
no permitted inner break as atomic. C receives the sparse
`forbidden-positions` vector.
- **Alignment** (`ekp-alignment`): non-justify modes zero the glue
stretch/shrink arrays and class params; the DP widens `max_w` by an
extra per-line stretch R (`ekp-c-set-penalties` arg 7), so badness =
100·(shortfall/R)³. The renderer places each line's leftover per
mode (trailing / split / leading).
- **Protrusion** (`ekp-protrusion`): per-gap `tail-protrudes[k]` (the
last non-space box's allowance, looked through trailing spaces) and
a `hyphen-protrude` scalar widen each candidate's effective target
(`lw = width + release`) in the DP, in `ekp-line-glues', and in the
C-result reconstruction — all three must stay in lockstep.
- **Per-line widths** (`ekp-parshape' / `ekp-first-line-indent'):
resolved by `ekp--line-spec' (line-index → INDENT . WIDTH); they
require the (position × line-count) DP and bypass C, like
looseness. Indents render as leading `ekp-glue' spacers.
C module 1.4: `ekp-c-break-with-arrays` takes 14 args
(…, forbidden-positions, tail-protrudes, hyphen-protrude); batch
vectors have 14 elements; `ekp-c-set-penalties` takes 47.
## 6. Looseness
`ekp-looseness` ≠ 0 switches to `ekp--dp-run-loose`, a full

View File

@ -147,6 +147,29 @@ batch/tty 下按字符列精确。
(`ekp-unjustify-region`)——即使排版后又被编辑过也能精确还原——并在
其上实现 `ekp-justify-region` / `ekp-auto-justify-mode`
### 5.1 断行许可、对齐、悬挂、段形
- **断行许可**:每个 CJK 字符(含标点)独立成盒;
`ekp-para-breaks-allowed` 按禁则(全角与半角)、`ekp-no-break`
区间及 NBSP 族连接符禁止相应间隙,被禁间隙不携带 glue。DP 跳过
被禁候选但继续延伸行;紧急兜底把"内部无许可断点的连跑段"视为
原子。C 侧接收稀疏 `forbidden-positions` 向量。
- **对齐**(`ekp-alignment`):非两端对齐把 glue 伸缩数组与类参数
置零,DP 给 `max_w` 加每行额外伸展 R(`ekp-c-set-penalties` 第 7
参),badness = 100·(欠宽/R)³;渲染层按模式分派剩余(尾部/对半/
头部)。
- **悬挂**(`ekp-protrusion`):逐间隙 `tail-protrudes[k]`(穿透尾
随空格盒取最后内容盒)加 `hyphen-protrude` 标量,在 DP、
`ekp-line-glues`、C 结果重建三处同步放宽每个候选的有效目标宽
(`lw = width + release`)——三处必须保持一致。
- **每行宽度**(`ekp-parshape` / `ekp-first-line-indent`):由
`ekp--line-spec`(行号 → 缩进 . 宽度)解析;需要(位置×行数)DP,
与 looseness 一样旁路 C。缩进渲染为行首 `ekp-glue` 垫片。
C 模块 1.4:`ekp-c-break-with-arrays` 14 参(…、
forbidden-positions、tail-protrudes、hyphen-protrude);batch 向量
14 元;`ekp-c-set-penalties` 47 参。
## 6. Looseness
`ekp-looseness` ≠ 0 时切换到 `ekp--dp-run-loose`:完整的

View File

@ -89,6 +89,45 @@ loading refuses with a message asking you to rebuild.
`ekp-region-margin-pixel` (default 2) is subtracted from the window
width as a rounding safety margin.
### Protecting code and other verbatim text
- Block level: paragraphs carrying the `ekp-verbatim` text property
(`M-x ekp-verbatim-region`), wearing a face listed in
`ekp-region-skip-faces` (e.g. `org-block`, `markdown-code-face`), or
matched by the buffer-local function `ekp-region-skip-predicate`
pass through completely untouched.
- Inline level: spans carrying `ekp-no-break`
(`M-x ekp-no-break-region`) become rigid atoms — never broken,
never hyphenated, spacing kept literal — ideal for inline code,
product names, or numbers with units.
## Typography
- **Alignment**`ekp-alignment`: `justify` (default),
`ragged-right`, `ragged-left`, or `center`. Non-justify modes keep
word spacing natural while Knuth-Plass still minimizes raggedness
within `ekp-ragged-stretch-pixel` (≈2 em) per line.
- **Hanging punctuation** — set `ekp-protrusion` to `t` and line-final
punctuation (。、」 as well as periods, commas and break hyphens)
hangs past the flush edge by `ekp-protrusion-ratios`. The 0.5
default for fullwidth closers is visually equivalent to CLREQ
line-end punctuation compression. `ekp-auto-justify-mode` reserves
the protrusion width automatically.
- **Paragraph shapes**`ekp-first-line-indent` (`t` = 2 em) for the
CJK paragraph convention, or full TeX-style `ekp-parshape` with
per-line `(INDENT . WIDTH)`. Both are Elisp-only paths (the C
module is bypassed, as with `ekp-looseness`).
- **Unbreakables** — NO-BREAK SPACE, NARROW NBSP, FIGURE SPACE and
WORD JOINER characters keep their neighbors together out of the box.
- Kinsoku covers full- *and* halfwidth punctuation: a line never
starts with `。、」!?` or a lone `.,;:!?`, never ends with `「(` etc.
Limitations worth knowing: mid-line CLREQ punctuation *compression*
(e.g. 「字。下」 squeezed inside a line) cannot be rendered — Emacs
cannot shrink a glyph's advance — which is why line-edge compression
is delivered via protrusion instead; left-edge protrusion is likewise
not renderable (text cannot start before the line origin).
## Configuration
### Hyphenation language

View File

@ -77,6 +77,37 @@ Elisp 与 C 两个引擎的输出**完全一致**;Elisp 是永远可用的后备
`ekp-region-margin-pixel`(默认 2)是从窗口宽度中扣除的取整安全边距。
### 保护代码块与 verbatim 文本
- 段落级:携带 `ekp-verbatim` 文本属性(`M-x ekp-verbatim-region`)、
face 在 `ekp-region-skip-faces` 列表中(如 `org-block`
`markdown-code-face`)、或被 buffer-local 的
`ekp-region-skip-predicate` 判定的段落**原样跳过**,一个字节都不动。
- 行内级:带 `ekp-no-break` 属性的区间(`M-x ekp-no-break-region`)
成为刚性原子——不断行、不断词、空格保持字面宽度——适合行内代码、
产品名、数字加单位。
## 排版特性
- **对齐模式**`ekp-alignment`:`justify`(默认)/`ragged-right`/
`ragged-left`/`center`。非两端对齐模式下词间距保持自然,K-P 仍在
每行 `ekp-ragged-stretch-pixel`(≈2 em)的余量内全局最小化参差。
- **标点悬挂** — 置 `ekp-protrusion``t`,行尾标点(。、」以及
西文句读、断词连字符)按 `ekp-protrusion-ratios` 悬出齐边。全角
闭合标点默认 0.5,视觉上等价于 CLREQ 的行尾标点半角化。
`ekp-auto-justify-mode` 自动预留悬挂宽度。
- **段落形状**`ekp-first-line-indent`(`t` = 2 em)实现中文段首
缩进惯例;或用 TeX 式 `ekp-parshape` 逐行指定 `(缩进 . 宽度)`
两者走 Elisp 2D 路径(C 模块自动旁路,同 `ekp-looseness`)。
- **不可断字符** — NBSP、窄 NBSP、数字空格、WORD JOINER 天然把两侧
锁在同一行。
- 禁则覆盖全角**与半角**标点:行首不会出现 `。、」!?` 或独立的
`.,;:!?`,行尾不会出现 `「(` 等。
已知限制:行中的 CLREQ 标点**压缩**(如「字。下」行内挤压)无法渲
染——Emacs 不能缩减字形 advance——因此行边压缩以悬挂方式呈现;左缘
悬挂同理不可渲染(文本无法起笔于行原点之前)。
## 配置
### 断词语言