173 lines
6.3 KiB
Markdown
173 lines
6.3 KiB
Markdown
[中文文档](./readme_zh.md)
|
||
|
||
# Emacs-KP: Knuth-Plass Line Breaking for Emacs
|
||
|
||
Emacs-kp implements the Knuth-Plass optimal line breaking algorithm with full support for CJK (Chinese, Japanese, Korean) and Latin mixed text typesetting.
|
||
|
||
## Demo
|
||
|
||

|
||
|
||
## Algorithm Overview
|
||
|
||
### The Knuth-Plass Algorithm
|
||
|
||
The algorithm is based on the seminal 1981 paper ["Breaking Paragraphs into Lines"](https://gwern.net/doc/design/typography/tex/1981-knuth.pdf) by Donald Knuth and Michael Plass. Unlike greedy line-breaking (used by most text editors), K-P considers **all possible breakpoints** simultaneously to find the globally optimal solution.
|
||
|
||
#### Core Concepts
|
||
|
||
**1. Boxes, Glue, and Penalties**
|
||
|
||
Text is modeled as a sequence of three elements:
|
||
- **Box**: Indivisible content (characters, words) with fixed width
|
||
- **Glue**: Flexible space with ideal width, stretchability, and shrinkability
|
||
- **Penalty**: Cost for breaking at specific points (e.g., hyphenation)
|
||
|
||
```
|
||
┌─────┐ ┌─────┐ ┌─────┐
|
||
│ Box │─Glue─│ Box │─Glue─│ Box │
|
||
└─────┘ └─────┘ └─────┘
|
||
word (flexible) word
|
||
```
|
||
|
||
**2. Badness: Measuring Line Quality**
|
||
|
||
Each line's quality is measured by how much glue must stretch/shrink:
|
||
|
||
```
|
||
⎧ 0 if adjustment = 0
|
||
badness = ⎨ ∞ if impossible to fit
|
||
⎩ 100 × |adjustment/flexibility|³
|
||
```
|
||
|
||
- `adjustment` = target_width - natural_width
|
||
- `flexibility` = total stretchability (if stretching) or shrinkability (if shrinking)
|
||
|
||
**3. Demerits: Ranking Break Sequences**
|
||
|
||
Demerits combine badness with penalties to rank entire paragraph layouts:
|
||
|
||
```
|
||
demerits = (line_penalty + badness)² + penalty² + fitness_penalty
|
||
```
|
||
|
||
Where:
|
||
- `line_penalty`: Base cost per line (default: 10)
|
||
- `penalty`: Break-specific cost (hyphenation: 50)
|
||
- `fitness_penalty`: Extra cost when adjacent lines differ significantly in tightness
|
||
|
||
**4. Fitness Classes**
|
||
|
||
Lines are classified by tightness to ensure visual consistency:
|
||
- Class 0: Tight (significantly shrunk)
|
||
- Class 1: Decent (close to ideal)
|
||
- Class 2: Loose (stretched)
|
||
- Class 3: Very loose (significantly stretched)
|
||
|
||
Adjacent lines differing by more than one class incur additional penalty.
|
||
|
||
**5. Dynamic Programming**
|
||
|
||
The algorithm uses DP to find the minimum-demerits path through all valid breakpoints:
|
||
|
||
```
|
||
dp[k] = min over all valid i < k {
|
||
dp[i] + demerits(line from i to k)
|
||
}
|
||
```
|
||
|
||
Time complexity: O(n²) where n = number of potential breakpoints.
|
||
|
||
### CJK Extensions
|
||
|
||
Emacs-kp extends the original algorithm for CJK text:
|
||
|
||
1. **Character-level breaking**: CJK text can break between any characters
|
||
2. **Mixed spacing**: Three glue types for Latin-Latin, Latin-CJK, and CJK-CJK gaps
|
||
3. **Punctuation handling**: CJK punctuation attaches to adjacent characters
|
||
|
||
### Hyphenation
|
||
|
||
Latin word hyphenation uses Frank Liang's algorithm (TeX's hyphenation):
|
||
- Pattern-based approach with priority values
|
||
- Language-specific dictionaries (en_US, de_DE, fr, etc.)
|
||
- Configurable minimum characters before/after breaks
|
||
|
||
## Limitations
|
||
|
||
Currently supports CJK mixed with **one** Latin language only. Multi-Latin-language mixing is not supported because the system cannot reliably determine which language a word belongs to for hyphenation.
|
||
|
||
## Usage
|
||
|
||
### Configuration
|
||
|
||
**`ekp-latin-lang`**: Primary Latin language for hyphenation (default: `"en_US"`).
|
||
See `dictionaries/` for supported languages.
|
||
|
||
**`ekp-param-set`**: Configure spacing parameters (in pixels):
|
||
|
||
| Parameter | Description |
|
||
|:----------------------|:-----------------------------------------------|
|
||
| `ekp-lws-ideal-pixel` | Ideal space between Latin words |
|
||
| `ekp-lws-stretch-pixel` | Maximum stretch between Latin words |
|
||
| `ekp-lws-shrink-pixel` | Maximum shrink between Latin words |
|
||
| `ekp-mws-ideal-pixel` | Ideal space between Latin and CJK |
|
||
| `ekp-mws-stretch-pixel` | Maximum stretch between Latin and CJK |
|
||
| `ekp-mws-shrink-pixel` | Maximum shrink between Latin and CJK |
|
||
| `ekp-cws-ideal-pixel` | Ideal space between CJK characters |
|
||
| `ekp-cws-stretch-pixel` | Maximum stretch between CJK characters |
|
||
| `ekp-cws-shrink-pixel` | Maximum shrink between CJK characters |
|
||
|
||
Example: `(ekp-param-set 7 3 2 5 2 1 0 2 0)`
|
||
|
||
**Do not set these variables directly—always use `ekp-param-set`.**
|
||
|
||
Default values follow K-P recommendations:
|
||
- Ideal = space character width
|
||
- Stretch = ideal × 0.5
|
||
- Shrink = ideal × 0.33
|
||
|
||
### K-P Algorithm Parameters
|
||
|
||
| Parameter | Default | Description |
|
||
|:------------------------------|:--------|:-----------------------------------------|
|
||
| `ekp-line-penalty` | 10 | Base penalty per line break |
|
||
| `ekp-hyphen-penalty` | 50 | Penalty for hyphenated breaks |
|
||
| `ekp-adjacent-fitness-penalty`| 100 | Penalty for inconsistent line tightness |
|
||
| `ekp-last-line-min-ratio` | 0.5 | Minimum fill ratio for last line |
|
||
| `ekp-looseness` | 0 | Target line count offset (±n lines) |
|
||
|
||
### Core Functions
|
||
|
||
```elisp
|
||
(ekp-pixel-justify string line-pixel)
|
||
```
|
||
Justify STRING to LINE-PIXEL width per line. Returns formatted text.
|
||
|
||
```elisp
|
||
(ekp-pixel-range-justify string min-pixel max-pixel)
|
||
```
|
||
Find optimal width in [MIN-PIXEL, MAX-PIXEL] range using ternary search.
|
||
Returns `(formatted-text . optimal-pixel)`.
|
||
|
||
Note: Uses O(log n) ternary search with aggressive caching.
|
||
|
||
```elisp
|
||
(ekp-clear-caches)
|
||
```
|
||
Clear all paragraph caches.
|
||
|
||
## Roadmap
|
||
|
||
- [x] Preserve original text properties after formatting
|
||
- [x] Full Knuth-Plass demerits model with fitness classes
|
||
- [x] Hyphenation with consecutive-hyphen penalty
|
||
- [ ] Rust dynamic module for parallel computation
|
||
- [ ] Auto-correction for mixed punctuation
|
||
|
||
## Credits
|
||
|
||
- Core algorithm: ["Breaking Paragraphs into Lines"](https://gwern.net/doc/design/typography/tex/1981-knuth.pdf) by Donald E. Knuth and Michael F. Plass (1981)
|
||
- Hyphenation: Adapted from [Pyphen](https://github.com/Kozea/Pyphen), using Liang's algorithm
|
||
- Dictionaries: [Hunspell hyphenation patterns](https://github.com/Kozea/Pyphen)
|