ekp/ekp_c/README.md
Kinneyzhang 9dcaeb0ba3 chore: licensing, customize support, and packaging hygiene
- add COPYING (GPL-3.0-or-later) and license headers to all elisp
  and C sources; real author/maintainer info replaces placeholders
- convert user-facing options to defcustom under new group `ekp'
  (spacing internals managed by ekp-param-set stay defvars)
- autoload user commands: ekp-param-reset, ekp-clear-caches,
  ekp-c-module-load, ekp-c-module-build
- ekp--load-dicts: a missing dictionaries/ directory now only
  disables hyphenation instead of breaking (require 'ekp);
  ekp--split-with-hyphen degrades gracefully (and resolves the
  hyphenator once per call instead of once per word)
- package summary no longer redundantly says "for Emacs"
- Makefile: drop personal Windows EMACS_ROOT default; add guidance
- ekp_c/README.md: fix stale 1.1 version references (module is 1.4)
- DEVELOPER*.md: remove archive/ from file map (not in the repo)
- remove unreferenced 10 MB demo GIF

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 00:52:37 +08:00

107 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# EKP C Dynamic Module
C implementation of the Knuth-Plass DP for emacs-kp (module version 1.4).
The division of labor: **Elisp owns all font-dependent data**
(tokenization, pixel measurement, glue values, prefix sums); the C
module runs only the O(n²) dynamic program. This keeps the two engines
byte-identical in output while making the hot loop native.
## Architecture
```
ekp_c/
├── ekp_module.h # Core data structures and API declarations
├── ekp.c # Emacs module entry point (emacs_module_init)
├── ekp_kp.c # Knuth-Plass DP + two-pass emergency strategy
├── ekp_thread_pool.c # Thread pool (parallelism across paragraphs)
├── ekp_hyphen.c # Liang hyphenation (experimental path only)
├── ekp_paragraph.c # C-side tokenization (experimental path only)
└── Makefile
```
Parallelism model: the DP for one paragraph is sequential (each
position depends on all earlier ones), so the thread pool parallelizes
across **paragraphs** via `ekp-c-break-batch` — the correct granularity,
with zero synchronization in the inner loop.
## Building
```bash
cd ekp_c
make # → ekp.dylib (macOS) / ekp.so (Linux) / ekp.dll (Windows)
```
Requirements: C11 compiler, Emacs 27.1+ headers, pthreads.
```bash
make DEBUG=1 # Debug build with sanitizers
make clean
make info
```
## API (as used by ekp.el)
```elisp
(ekp-c-init) ; init global state + thread pool
(ekp-c-version) ; => "1.4" — checked by ekp-c-module-load
(ekp-c-thread-count) ; => 8
(ekp-c-cleanup)
;; Synced automatically by ekp.el before every call:
(ekp-c-set-penalties LINE HYPHEN FITNESS LAST-RATIO
&optional CONSEC-HYPHEN LAST-SHORT)
;; Single paragraph (11 args):
(ekp-c-break-with-arrays IDEAL-PREFIX MIN-PREFIX MAX-PREFIX
GLUE-IDEALS GLUE-SHRINKS GLUE-STRETCHES
HYPHEN-POS HYPHEN-WIDTH LINE-WIDTH
LEAD-SPACES TRAIL-SPACES)
;; => (BREAKS . TOTAL-COST)
;; Many paragraphs in parallel: vector of 11-element vectors
(ekp-c-break-batch PARAGRAPHS) ; => vector of (BREAKS . COST)
```
`LEAD-SPACES` / `TRAIL-SPACES` are the space-box run widths that the
Elisp renderer strips from line edges; the DP excludes them from line
metrics so both layers agree exactly (since 1.1).
The DP uses the same two-pass strategy as the Elisp engine: a strict
Knuth-Plass pass, then — only when the paragraph end is unreachable —
a second pass permitting emergency single-box breaks, so overlong
unbreakable tokens can never make the result empty. Badness saturates
at 10000 exactly like the Elisp side.
### Experimental: self-contained C path
`ekp-c-break-lines` tokenizes and hyphenates in C
(`ekp_paragraph.c`, `ekp_hyphen.c`) with a measurement callback into
Emacs. ekp.el does **not** use this path; its tokenizer is a
simplified approximation of `ekp-split-to-boxes`. Kept for
experimentation.
```elisp
(ekp-c-load-hyphenator "/path/to/hyph_en_US.dic") ; => index
(ekp-c-hyphenate 0 "hyphenation") ; => (2 5)
(ekp-c-break-lines "text..." 0 600 #'string-pixel-width)
```
## Performance
Measured with `tests/ekp-bench.el` (batch Emacs 30.2, Apple Silicon,
byte-compiled Elisp around the C calls, min of 3 cold-cache runs):
| Case | Elisp engine (compiled) | C engine |
|:----------------------------|------------------------:|---------:|
| justify text-zh.txt w=200 | 96 ms | 57 ms |
| justify mixed text w=300 | 53 ms | 23 ms |
| range-justify zh 340380 | 294 ms | 75 ms |
| range-justify mix 280320 | 480 ms | 34 ms |
| DP only, text-zh w=400 | 15 ms | 1.3 ms |
The pure-DP speedup is ~12× (1.3 ms vs 15 ms); end-to-end gains are
smaller because tokenization, measurement and rendering stay in Elisp.
The C engine matters most for `range-justify` (many widths per text)
and multi-paragraph batches.