diff --git a/.phrase/phases/phase-p1p2-20260726/NOTES.md b/.phrase/phases/phase-p1p2-20260726/NOTES.md index c2e494b..b005bf3 100644 --- a/.phrase/phases/phase-p1p2-20260726/NOTES.md +++ b/.phrase/phases/phase-p1p2-20260726/NOTES.md @@ -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)= 已知边界。 ## 验证清单(每阶段) diff --git a/DEVELOPER.md b/DEVELOPER.md index e85a606..fa4a1d4 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -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 4–7. + ## 6. Looseness `ekp-looseness` ≠ 0 switches to `ekp--dp-run-loose`, a full diff --git a/DEVELOPER_ZH.md b/DEVELOPER_ZH.md index fab3fa6..f9f3406 100644 --- a/DEVELOPER_ZH.md +++ b/DEVELOPER_ZH.md @@ -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` 4–7 参。 + ## 6. Looseness `ekp-looseness` ≠ 0 时切换到 `ekp--dp-run-loose`:完整的 diff --git a/readme.md b/readme.md index 8f19e22..17895e9 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/readme_zh.md b/readme_zh.md index ca7a421..a29fb8f 100644 --- a/readme_zh.md +++ b/readme_zh.md @@ -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——因此行边压缩以悬挂方式呈现;左缘 +悬挂同理不可渲染(文本无法起笔于行原点之前)。 + ## 配置 ### 断词语言