138 lines
6.6 KiB
Markdown
138 lines
6.6 KiB
Markdown
# Emacs-KP 开发者文档
|
||
|
||
本文档详细介绍了 `emacs-kp` 的内部架构、API 和算法原理。旨在帮助贡献者和高级用户理解其工作机制。
|
||
|
||
## 1. 架构概览
|
||
|
||
`emacs-kp` 采用分层架构,将文本处理、布局计算和渲染分离。
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 用户 API 层 (ekp.el) │
|
||
│ ekp-pixel-justify ekp-pixel-range-justify ekp-clear-caches │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 缓存层 (ekp-utils.el) │
|
||
│ ekp--get-para (段落缓存) ekp-dp-cache (DP 结果缓存) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌───────────────┴───────────────┐
|
||
▼ ▼
|
||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||
│ 纯 Elisp 路径 │ │ C 模块路径 │
|
||
│ ekp--dp-cache-elisp │ │ ekp--dp-cache-via-c │
|
||
│ (Elisp 实现 O(n²) DP) │ │ (调用 C 进行 DP) │
|
||
└─────────────────────────┘ └─────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────┐
|
||
│ C 动态模块 │
|
||
│ ekp_break_with_prefixes│
|
||
│ (8 线程并行计算) │
|
||
└─────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 渲染层 │
|
||
│ ekp--render-justified (应用断点,插入 display 属性胶水) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 2. Elisp 核心 (ekp.el)
|
||
|
||
### 数据结构
|
||
|
||
#### `ekp-para` 结构体
|
||
|
||
核心数据结构是 `ekp-para`,代表预处理后的段落。它被缓存以避免重复分词和测量。
|
||
|
||
```elisp
|
||
(cl-defstruct ekp-para
|
||
string ; 带有属性的原始文本
|
||
latin-font ; 检测到的拉丁字体
|
||
cjk-font ; 检测到的 CJK 字体
|
||
boxes ; Box 字符串向量
|
||
boxes-widths ; Box 像素宽度向量
|
||
boxes-types ; 类型向量 (start-type . end-type)
|
||
glues-types ; 胶水类型符号向量 (lws, mws, cws, nws)
|
||
hyphen-pixel ; 连字符宽度
|
||
hyphen-positions ; 可断词 Box 索引向量
|
||
ideal-prefixs ; 前缀和:理想宽度 (用于 O(1) 宽度计算)
|
||
min-prefixs ; 前缀和:最小宽度
|
||
max-prefixs ; 前缀和:最大宽度
|
||
dp-cache) ; 哈希表:行宽像素 → DP 结果
|
||
```
|
||
|
||
#### 胶水类型 (Glue Types)
|
||
- `lws` (Latin Word Space): 拉丁词间距
|
||
- `mws` (Mixed Word Space): 中西文间距
|
||
- `cws` (CJK Word Space): CJK 字符间距
|
||
- `nws` (No Word Space): 固定间距
|
||
|
||
### 核心函数
|
||
|
||
#### `(ekp-pixel-justify STRING LINE-PIXEL)`
|
||
将 `STRING` 按 `LINE-PIXEL` 宽度对齐。
|
||
1. 检查缓存中是否有对应的 `ekp-para`。
|
||
2. 若未命中,创建 `ekp-para`(分词、测量、断词处理)。
|
||
3. 调用 DP 引擎(Elisp 或 C)计算断点。
|
||
4. 使用 display 属性(特别是 `space` 属性)渲染结果。
|
||
|
||
#### `(ekp-pixel-range-justify STRING MIN-PIXEL MAX-PIXEL)`
|
||
在范围内寻找“最佳”宽度。使用三分搜索 (O(log n)) 最小化 demerits。用于自动寻找最适合该段落的宽度。
|
||
|
||
#### `(ekp-param-set ...)`
|
||
设置 9 个间距参数(LWS/MWS/CWS 的 Ideal/Stretch/Shrink)。
|
||
|
||
## 3. C 动态模块 (ekp_c)
|
||
|
||
对于长文本,C 模块通过并行化 O(n²) 动态规划阶段提供约 20 倍的加速。
|
||
|
||
### 源码结构
|
||
- `ekp_c/ekp.c`: Emacs 模块入口点。
|
||
- `ekp_c/ekp_kp.c`: Knuth-Plass 算法实现。
|
||
- `ekp_c/ekp_thread_pool.c`: 工作线程池。
|
||
- `ekp_c/ekp_hyphen.c`: Liang 断词算法。
|
||
|
||
### C API (暴露给 Elisp)
|
||
|
||
#### `(ekp-c-init)`
|
||
初始化模块和线程池。
|
||
|
||
#### `(ekp-c-break-with-prefixes ...)`
|
||
底层 DP 函数。它接收来自 Elisp 的扁平数组(指针):
|
||
- 前缀和数组 (ideal, min, max)
|
||
- 每个 Box 的胶水参数
|
||
- 连字符位置
|
||
- 目标行宽
|
||
|
||
返回断点索引列表和总代价。
|
||
|
||
### 内存模型
|
||
- **零拷贝 (Zero Copy)**: Elisp 直接将向量数据的指针传递给 C。
|
||
- **扁平数组**: 数据结构为并行数组,提高缓存效率。
|
||
- **线程安全**: 模块使用固定线程池。DP 算法采用波前模式 (Wavefront) 并行化内部循环。
|
||
|
||
## 4. 算法细节
|
||
|
||
### Knuth-Plass 算法
|
||
基于 1981 年论文 "Breaking Paragraphs into Lines"。
|
||
|
||
**代价函数 (Demerits):**
|
||
`D = (LinePenalty + Badness)² + Penalty²`
|
||
|
||
**劣度 (Badness):**
|
||
`100 * |Adjustment / Flexibility|³`
|
||
|
||
### CJK 扩展
|
||
- **Boxes**: 每个 CJK 字符视为一个独立的 Box。
|
||
- **Glues**: 针对 CJK-CJK 和 CJK-Latin 的特定胶水类型允许精细调整间距(例如在汉字和英文之间增加微小的空隙)。
|
||
|
||
### 断词 (Hyphenation)
|
||
使用 Frank Liang 算法(TeX 标准)。
|
||
- 模式从 `dictionaries/*.dic` 加载。
|
||
- `ekp-hyphen.el` 在纯 Elisp 中处理。
|
||
- C 模块有自己的实现 (`ekp_hyphen.c`),目前主要由 Elisp 负责分词。
|