6.3 KiB
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" 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_widthflexibility= 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:
- Character-level breaking: CJK text can break between any characters
- Mixed spacing: Three glue types for Latin-Latin, Latin-CJK, and CJK-CJK gaps
- 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
(ekp-pixel-justify string line-pixel)
Justify STRING to LINE-PIXEL width per line. Returns formatted text.
(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.
(ekp-clear-caches)
Clear all paragraph caches.
Roadmap
- Preserve original text properties after formatting
- Full Knuth-Plass demerits model with fitness classes
- Hyphenation with consecutive-hyphen penalty
- Rust dynamic module for parallel computation
- Auto-correction for mixed punctuation
Credits
- Core algorithm: "Breaking Paragraphs into Lines" by Donald E. Knuth and Michael F. Plass (1981)
- Hyphenation: Adapted from Pyphen, using Liang's algorithm
- Dictionaries: Hunspell hyphenation patterns
