ekp/DEVELOPER_ZH.md
2026-01-25 13:50:46 +08:00

6.6 KiB
Raw Blame History

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,代表预处理后的段落。它被缓存以避免重复分词和测量。

(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)

STRINGLINE-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 负责分词。