update readme

This commit is contained in:
Kinneyzhang 2025-07-27 21:35:44 +08:00
parent f7f1f7104c
commit a265357e41
12 changed files with 316 additions and 119 deletions

View File

@ -250,7 +250,7 @@ LEFT and RIGHT are minimum first/last syllable chars. CACHE t/nil."
(concat "" (mapconcat #'char-to-string letters ""))))
(defun ekp-hyphen-boxes (ekp-hyphen word)
(split-string (ekp-hyphen-inserted ekp-hyphen word) "-"))
(split-string (ekp-hyphen-inserted ekp-hyphen word " ") " "))
(provide 'ekp-hyphen)

View File

@ -1,51 +0,0 @@
;; -*- lexical-binding: t; -*-
;; (ekp-clear-caches)
;; (ekp-pixel-init 6 2 1 4 2 1 0 1 0)
;; (ekp-param-fmtstr)
;; (ekp-text-cache ekp-test-str1)
;; (ekp-param-cache ekp-test-str1)
;; (ekp-dp-cache ekp-test-str1 888)
;; (ekp-dp-cache ekp-test-str1 888)
;; (ekp-total-cost ekp-test-str1 888)
;; (ekp-line-breaks ekp-test-str1 444)
;; (ekp-line-glues ekp-test-str1 888)
(progn
(defvar ekp-string-1
(propertize (file-content "./text1.txt")
'face 'ekp-latin-face))
;; (ekp-clear-caches)
;; (ekp-pixel-init 6 2 1 4 2 1 0 1 0)
(pop-buffer-do 25
(let ((cons (ekp-pixel-range-justify ekp-string-1 100 1500)))
(insert (car cons)))))
;; "Cascadia Next SC"
(defvar ekp-string-1
(propertize (file-content "./text1.txt")
'face '(:family "Cascadia Next SC")
;; 'face 'ekp-latin-face
))
(ekp-param-fmtstr)
(progn
(setq elog-level nil)
;; (elog-log-clear)
;; (ekp-clear-caches)
;; (ekp-param-set 6 2 6 4 3 1 0 1 0)
;; (ekp-param-set-default ekp-string-1)
(pop-buffer-do 25
(benchmark-progn
(let ((str (ekp-pixel-justify ekp-string-1 277)))
(insert str)))))
(defun ekp-test-justify ()
(interactive)
(let ((buf (get-buffer-create "ekp-test")))
(switch-to-buffer buf)
(with-current-buffer buf
(dotimes (i 1200)
(erase-buffer)
(elog-log-clear)
(insert (ekp-pixel-justify ekp-string-1 (+ i 200)))
(sit-for 0.000001)))))

View File

@ -2,6 +2,25 @@
;;; related to font
(defun ekp-cjk-char-p (char)
"Return if char CHAR is cjk."
(or
;; CJK统一表意文字基本区
(<= #x4E00 char #x9FFF)
;; CJK扩展A区
(<= #x3400 char #x4DBF)
;; CJK扩展B区注意超出16位范围
(and (<= #x20000 char) (<= char #x2A6DF))
;; CJK兼容/部首扩展等
;; CJK符号和标点
(<= #x3000 char #x303F)
;; 日文假名
(<= #x3040 char #x30FF)
;; 韩文谚文
(<= #xAC00 char #xD7AF)
;; CJK兼容表意文字
(<= #xF900 char #xFAFF)))
(defun ekp-font-family (string &optional position)
(format "%s" (font-get (font-at (or position 0) nil string) :family)))
@ -21,7 +40,9 @@
(insert string)
(goto-char (point-min))
(while (and (< (point) (point-max))
(= 2 (char-width (char-after))))
(let ((char (char-after)))
(not (or (and (>= char ?a) (<= char ?z))
(and (>= char ?A) (<= char ?Z))))))
(forward-char 1))
(unless (eobp)
(buffer-substring (point) (1+ (point))))))
@ -31,7 +52,10 @@
(insert string)
(goto-char (point-min))
(while (and (< (point) (point-max))
(= 1 (char-width (char-after))))
(let* ((char (char-after))
(width (char-width char)))
(or (or (= 1 width) (= 0 width))
(not (ekp-cjk-char-p char)))))
(forward-char 1))
(unless (eobp)
(buffer-substring (point) (1+ (point))))))
@ -148,8 +172,10 @@ the value of [SYM]-default."
""
(propertize " " 'display `(space :width (,pixel)))))
(defun ekp-cjk-punct-p (char)
(or (and (>= char #x3000) (<= char #x303F))
(defun ekp-cjk-fw-punct-p (char)
"Return if CHAR is CJK full-width punctuation."
(or (equal (char-syntax char) ?.)
(and (>= char #x3000) (<= char #x303F))
(and (>= char #xFF00) (<= char #xFF60))))
(defun ekp-split-to-boxes (string)
@ -161,7 +187,9 @@ the value of [SYM]-default."
(while (not (eobp))
(let* ((char (char-after))
(str (char-to-string char)))
(if (string-blank-p str)
(if (or (string-blank-p str)
;; 零宽 unicode
(= 0 (string-width str)))
(when curr-str
(push curr-str boxes)
(setq curr-str nil))
@ -178,13 +206,17 @@ the value of [SYM]-default."
(setq state 2)))
(cond
((= 2 (string-width str))
(if (ekp-cjk-punct-p char)
(if (ekp-cjk-fw-punct-p char)
;; cjk punct 连在前一个字符后面
(progn
(push (concat prev-str str) boxes)
(setq prev-str nil))
(when prev-str (push prev-str boxes))
(setq prev-str str)))
((= 1 (string-width str))
(when prev-str
(push prev-str boxes)
(setq prev-str nil))
;; switch to state 1
(setq curr-str (concat curr-str str))
(setq state 1))))))
@ -199,6 +231,40 @@ the value of [SYM]-default."
(make-hash-table
:test 'equal :size 100 :rehash-size 1.5 :weakness nil)))
;; (defun ekp-split-to-boxes (string)
;; "Split STRING into a vector: English by word, Chinese
;; by character, punctuation attached to previous unit."
;; (with-temp-buffer
;; (insert string)
;; (goto-char (point-min))
;; (let (words (index 0))
;; (while (not (eobp))
;; ;; skip whitespace
;; (skip-syntax-forward "-")
;; (unless (eobp)
;; (let ((start (point)))
;; (if (ekp-cjk-char-p (char-after))
;; (forward-char 1)
;; (forward-word 1)
;; ;; process float number
;; (when-let* ((char1 (char-after (point)))
;; (char2 (char-after (1+ (point))))
;; (_ (and (eq char1 ?.) (<= ?0 char2 ?9))))
;; (forward-word 1))
;; ;; process hyphen and contraction
;; (while (or (eq (char-after (point)) ?-)
;; (eq (char-after (point)) ?')
;; (eq (char-after (point)) ?/))
;; (forward-word 1)))
;; ;; attach punctuation
;; (while (and (not (eobp))
;; (with-syntax-table (standard-syntax-table)
;; (eq (char-syntax (char-after)) ?.)))
;; ;; (message "(char-after):%s" (char-after))
;; (forward-char 1))
;; (push (buffer-substring start (point)) words))))
;; (vconcat (nreverse words)))))
;; (defvar ekp-par-num 8)
;; (defun ekp-strings (string n)
;; "Split STRING to average N parts but don't split in a word."

174
ekp.el
View File

@ -108,6 +108,20 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 正确版本:包含完整字符集和组合标记
(defvar ekp-latin-regexp
(concat
"[" ; 开始字符集
"A-Za-z'-" ; 基础拉丁字母
"\300-\326\330-\366\370-\417" ; ISO-8859-1补充
"\u00C0-\u00D6\u00D8-\u00F6" ; Unicode基本补充
"\u00F8-\u00FF\u0100-\u024F" ; 扩展A/B
"\u1E00-\u1EFF" ; 扩展附加
"\uA780-\uA7F9" ; 拉丁扩展-D
"]" ; 闭合字符集
)
"正则表达式匹配所有拉丁字符及其变体,包括组合变音符")
(defun ekp-split-string (string)
;; return (boxes-vector . hyphen-positions-vector)
(let* ((boxes (ekp-split-to-boxes string))
@ -115,7 +129,11 @@
new-boxes idxs)
(dolist (box (append boxes nil))
(save-match-data
(if (string-match "^\\([\"']*\\)\\([a-zA-z']+\\)\\([.,\"']*\\)$" box)
(if (string-match
(format
"^\\([[{<„‚¿¡*@\"']*\\)\\(%s+\\)\\([]}>.,*?\"']*\\)$"
ekp-latin-regexp)
box)
(let* ((pure-word (match-string 2 box))
(left-punct (match-string 1 box))
(right-punct (match-string 3 box))
@ -144,50 +162,56 @@
(cons (vconcat boxes-lst)
(vconcat (nreverse idxs))))))
(defun ekp-str-type (str)
"STR should be single letter string."
(cond
;; a half-width cjk punct
((or (string= "" str) (string= "" str)) 'cjk)
((= (string-width str) 1) 'latin)
((= (string-width str) 2)
(if (ekp-cjk-fw-punct-p (string-to-char str))
'cjk-punct
'cjk))
(t (error "Abnormal string width %s for %s"
(string-width str) str))))
(defun ekp-box-type (box)
(unless (or (null box) (string-empty-p box))
(let* ((str (substring box -1))
(width (string-width str)))
(cond ((= width 1) 'latin)
((= width 2)
(let ((char (string-to-char str)))
(if (ekp-cjk-punct-p char)
'cjk-punct
'cjk)))
(_ (error "Abnormal string width %s for %s" width str))))))
(cons (ekp-str-type (substring box 0 1))
(ekp-str-type (substring box -1)))))
(defun ekp-glue-type (curr prev prev-prev)
(defun ekp-glue-type (prev-box-type curr-box-type)
"Lws means whitespace between latin words; cws means
whitespace between cjk words; mws means whitespace between
cjk and latin words; nws means no whitespace."
(if prev
(cond
((and (eq prev 'latin) (eq curr 'latin)) 'lws)
((and (eq prev 'cjk) (eq curr 'cjk)) 'cws)
((or (and (eq prev 'cjk) (eq curr 'latin))
(and (eq prev 'latin) (eq curr 'cjk)))
'mws)
((null curr) 'nws)
((or (eq prev 'cjk-punct) (eq curr 'cjk-punct)) 'cws))
;; prev is nil, determined by prev-prev
(when prev-prev
(ekp-glue-type prev-prev curr nil))))
(let ((before (cdr prev-box-type))
(after (car curr-box-type)))
(if before
(cond
((and (eq before 'latin) (eq after 'latin)) 'lws)
((and (eq before 'cjk) (eq after 'cjk)) 'cws)
((or (and (eq before 'cjk) (eq after 'latin))
(and (eq before 'latin) (eq after 'cjk)))
'mws)
((or (eq before 'cjk-punct) (eq after 'cjk-punct)) 'cws))
'nws)))
(defun ekp--glues-types (boxes boxes-types hyphen_positions)
"Set type of all glue in boxes using `ekp-glue-type',
set type to 'nws for each glue after position in hyphen_positions."
;; IMPORTANT!
(let* ((num (length boxes))
(glues-types (make-vector num nil))
prev-type prev-prev-type)
prev-box-type curr-box-type)
;; set hyphen position to 'nws
(dolist (i (append hyphen_positions nil))
(aset glues-types (1+ i) 'nws))
(dotimes (i num)
(unless (aref glues-types i)
(let ((curr-type (aref boxes-types i)))
(aset glues-types i (ekp-glue-type
curr-type prev-type prev-prev-type))
(setq prev-prev-type prev-type)
(setq prev-type curr-type))))
(let ((curr-box-type (aref boxes-types i)))
(aset glues-types
i (ekp-glue-type prev-box-type curr-box-type))
(setq prev-box-type curr-box-type))))
glues-types))
(defun ekp-glue-ideal-pixel (type)
@ -431,8 +455,9 @@ return the value of KEY in plist."
(aset gaps (1- k)
(ekp--gaps-list
(seq-drop (cl-subseq glues-types i (1- k)) 1)))
(elog-debug "1-k:%s; rests:%S" (1- k)
(aref rests (1- k)))))
;; (elog-debug "1-k:%s; rests:%S" (1- k)
;; (aref rests (1- k)))
))
(throw 'break nil))
(when (or (<= min-pixel line-pixel max-pixel)
@ -456,7 +481,7 @@ return the value of KEY in plist."
(progn
;; add extra cost of hypen
(cl-incf hyphen-line-count)
(+ cost (* 100 hyphen-line-count)))
(+ cost (* 1000 hyphen-line-count)))
(setq hyphen-line-count 0)
cost)))))
(total-cost (+ (aref costs i) line-cost)))
@ -547,8 +572,8 @@ So the length of line glues is: line-boxes-num + 1"
(ekp-glue-ideal-pixel
(aref glues-types start)))))))
(t
(elog-debug "-----------------")
(elog-debug "glues-types:%s" line-glues-types)
;; (elog-debug "-----------------")
;; (elog-debug "glues-types:%s" line-glues-types)
(let ((max-pixel (- (aref max-prefixs end)
(aref max-prefixs start)
(ekp-glue-max-pixel
@ -556,12 +581,12 @@ So the length of line glues is: line-boxes-num + 1"
;; ends with hyphen
(when (ekp-hyphenate-p glues-types end)
(cl-incf max-pixel (ekp-hyphen-pixel string)))
(elog-debug "start:%s; end:%s; max:%s" start end max-pixel)
;; (elog-debug "start:%s; end:%s; max:%s" start end max-pixel)
(if (< max-pixel line-pixel)
(progn
;; 行尾直接断行的情况
(elog-debug "暴力断行 i:%s pixel:%s"
i (- line-pixel max-pixel))
;; (elog-debug "暴力断行 i:%s pixel:%s"
;; i (- line-pixel max-pixel))
(append '(0)
(mapcar #'ekp-glue-max-pixel line-glues-types)
(list (- line-pixel max-pixel))))
@ -572,12 +597,13 @@ So the length of line glues is: line-boxes-num + 1"
(aref glues-types start)))))
;; ideal-pixel 包含第一个box之前的glue
;; (elog-debug "boxes:%S" (cl-subseq boxes start end))
(elog-debug "line:%s; ideal:%s; rest:%s; stored-rest:%s"
line-pixel ideal-pixel (- line-pixel ideal-pixel)
(nth i (ekp-dp-data string line-pixel :rests))))
;; (elog-debug "line:%s; ideal:%s; rest:%s; stored-rest:%s"
;; line-pixel ideal-pixel (- line-pixel ideal-pixel)
;; (nth i (ekp-dp-data string line-pixel :rests)))
)
(let* ((lines-rests (ekp-dp-data string line-pixel :rests))
(curr-rest-pixel (nth i lines-rests)))
(elog-debug "curr-rest-pixel:%s" curr-rest-pixel)
;; (elog-debug "curr-rest-pixel:%s" curr-rest-pixel)
(cond
((= curr-rest-pixel 0)
(append '(0) (mapcar #'ekp-glue-ideal-pixel
@ -586,7 +612,8 @@ So the length of line glues is: line-boxes-num + 1"
(t
(let* ((lines-gaps (ekp-dp-data string line-pixel :gaps))
(line-gaps (nth i lines-gaps))
(_ (elog-debug "line-gaps:%S" line-gaps))
(_ ;; (elog-debug "line-gaps:%S" line-gaps)
)
(latin-gaps (nth 0 line-gaps))
(mix-gaps (nth 1 line-gaps))
(cjk-gaps (nth 2 line-gaps))
@ -602,10 +629,10 @@ So the length of line glues is: line-boxes-num + 1"
(if (< (- line-rest-pixel latin-max-pixel) 0)
(when (> latin-gaps 0)
;; only stretch latin glues
(elog-debug "latin-gap-pixel:%s"
(/ line-rest-pixel latin-gaps))
(elog-debug "latin-extra-gaps:%s"
(% line-rest-pixel latin-gaps))
;; (elog-debug "latin-gap-pixel:%s"
;; (/ line-rest-pixel latin-gaps))
;; (elog-debug "latin-extra-gaps:%s"
;; (% line-rest-pixel latin-gaps))
(setq latin-gap-pixel (/ line-rest-pixel latin-gaps))
(setq latin-extra-gaps (% line-rest-pixel latin-gaps)))
;; stretch latin glues max and continue to stretch mix glues
@ -655,8 +682,8 @@ So the length of line glues is: line-boxes-num + 1"
;; shrink
(- (ekp-glue-ideal-pixel type) pixel))))
line-glues-types)))
(elog-debug "glue-pixel-lst:%S" glue-pixel-lst)
(elog-debug "--------------")
;; (elog-debug "glue-pixel-lst:%S" glue-pixel-lst)
;; (elog-debug "--------------")
(append '(0) glue-pixel-lst '(0))))))))))))
(aset line-glues i (vconcat line-glue nil))
(setq start end)))
@ -672,7 +699,8 @@ So the length of line glues is: line-boxes-num + 1"
(list last-glue)))
(error "(length glues) + 1 != (length boxes)"))))
(defun ekp-pixel-justify (string line-pixel)
(defun ekp--pixel-justify (string line-pixel)
"Justify single STRING to LINE-PIXEL."
(let* ((boxes (ekp-boxes string))
(hyphen (ekp-hyphen-str string))
(breaks (ekp-line-breaks string line-pixel))
@ -694,19 +722,45 @@ So the length of line glues is: line-boxes-num + 1"
(setq start end)))
(mapconcat 'identity (nreverse strings) "\n")))
(defun ekp-pixel-justify (string line-pixel)
"Justify multiline STRING to LINE-PIXEL."
(let ((strs (split-string string "\n")))
(mapconcat (lambda (str)
(if (string-blank-p str)
""
(ekp--pixel-justify str line-pixel)))
strs "\n")))
(defun ekp-pixel-range-justify (string min-pixel max-pixel)
"Return text with optimal typing when STRING's line width
is between MIN-PIXEL and MAX-PIXEL."
(let ((best-pixel max-pixel)
(best-cost (abs (ekp-total-cost string max-pixel)))
(pixel max-pixel))
(while (>= pixel min-pixel)
(let ((cost (abs (ekp-total-cost string pixel))))
(when (< cost best-cost)
"Find the optimal breakpoint for STRING typesetting between
a MIN-PIXEL and MAX-PIXEL width and return a cons-cell. The car
of it is the typeset tex and cdr is the best pixel."
(let* ((strings (split-string string "\n"))
(best-pixel max-pixel)
(best-cost-lst
(mapcar (lambda (string)
(if (string-blank-p string)
0
(abs (ekp-total-cost string max-pixel))))
strings))
;; get average cost of all lines' costs
(best-cost (/ (float (apply #'+ best-cost-lst))
(length best-cost-lst)))
(curr-pixel max-pixel))
(while (>= curr-pixel min-pixel)
(let* ((cost-lst
(mapcar (lambda (string)
(if (string-blank-p string)
0
(abs (ekp-total-cost string curr-pixel))))
strings))
(curr-cost (/ (float (apply #'+ cost-lst))
(length cost-lst))))
(when (< curr-cost best-cost)
(progn
(setq best-cost cost)
(setq best-pixel pixel)))
(cl-decf pixel 1)))
(setq best-cost curr-cost)
(setq best-pixel curr-pixel)))
(cl-decf curr-pixel 1)))
(cons (ekp-pixel-justify string best-pixel) best-pixel)))
(provide 'ekp)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 MiB

View File

@ -0,0 +1,23 @@
# 介绍
emcas-kp 实现了 knuth-plass 的排版算法单其功能不局限于算法本身。原本的KP算法实现了对英文的排版emacs-kp对其进行了进一步的拓展实现了 CJK 语言与 latin 系语言的混合混合排版。
# 演示
先来看一下排版效果的 demo
[[images/ekp-demo-with-cache.gif]]
# 局限
目前只支持 CJK 与任意一种拉丁系语言的混合排版,不支持多种拉丁系语言混合排版的场景。原因是无法精确判断单词属于哪种语言,从而对其进行 hyphen 断词。
# 用法
**配置项:**
`ekp-latin-lang` 用来设置文本中主要的拉丁系的语言dictionary 下可以看到所有支持的语言。
**核心函数:**
- ```(ekp-pixel-justify string line-pixel)```
将文本 STRING 按照每行像素宽度为 LINE-PIXEL 排版,返回排版后的文本。
- ```(ekp-pixel-range-justify string min-pixel max-pixel)```
在 MIN-PIXEL 到 MAX-PIXEL 的返回内寻找最优排版,返回一个 cons-cellcar 是排版后的文本cdr 是最优排版效果的像素值。

79
tests/ekp-tests.el Normal file
View File

@ -0,0 +1,79 @@
;; -*- lexical-binding: t; -*-
;;; utils
(defun my-pop-to-buffer (buffer-or-name &optional action norecord)
(declare (indent defun))
(let ((buffer (pop-to-buffer buffer-or-name action norecord)))
(with-current-buffer buffer (local-set-key "q" 'quit-window))
buffer))
(defun pop-buffer-insert (height &rest strings)
(declare (indent defun))
(let* ((height (or height 10))
(buffer (my-pop-to-buffer "*pop-buffer-insert*"
`(display-buffer-at-bottom
(window-height . ,height)))))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(apply #'insert strings)))))
;;; tests
(defun ekp-test-hyphen-word (lang word)
(setq ekp-latin-lang lang)
(ekp-hyphen-boxes
(ekp-hyphen-create ekp-latin-lang) word))
;; (ekp-test-hyphen-word "de_DE" "ästhetisch")
(defun ekp-test-justify (cjk latin font pixel)
;; (ekp-clear-caches)
(setq ekp-latin-lang latin)
(pop-buffer-insert 35
(ekp-pixel-justify
(ekp-test-str cjk latin font) pixel)))
(defun ekp-test-range-justify (cjk latin font min max)
;; (ekp-clear-caches)
(setq ekp-latin-lang latin)
(pop-buffer-insert 35
(car (ekp-pixel-range-justify
(ekp-test-str cjk latin font) min max))))
;; (ekp-test-justify "zh" "en_US" "Cascadia Next SC" 666)
;; (ekp-test-justify nil "en_US" "Cascadia Next SC" 699)
;; (ekp-test-justify nil "en_US" "Times New Roman" 699)
;; (ekp-test-justify nil "en_US" "Georgia" 699)
;; (ekp-test-justify nil "en_US" "Garamond" 699)
;; (ekp-test-range-justify "zh" "en_US" "Cascadia Next SC" 666 690)
;; (ekp-test-justify nil "fr" "Cascadia Next SC" 666)
;; (ekp-test-justify nil "de_DE" "Cascadia Next SC" 666)
;; (ekp-test-justify "zh" "en_US" "Noto Serif" 689)
;;; FIXME: font size also affect!
(defun ekp-test-str (cjk latin font)
(let ((file (concat "./text"
(and cjk (concat "-" cjk))
(and latin (concat "-" latin))
".txt" )))
(propertize (file-content file)
'face `(:family ,font))))
(defun ekp-test-demo (min-pixel max-pixel &optional inc)
(let ((str (ekp-test-str "zh" "en_US" "Cascadia Next SC"))
(pixel-lst (number-sequence min-pixel max-pixel inc))
(buf (get-buffer-create "*ekp-test-demo*")))
;; (ekp-clear-caches)
(save-window-excursion
(delete-other-windows)
(switch-to-buffer buf)
(with-current-buffer buf
(dolist (pixel pixel-lst)
(erase-buffer)
(insert (ekp-pixel-justify str pixel))
(goto-char (point-min))
(sit-for 0.00001))))))
(ekp-test-demo 400 800 1)

9
tests/text-de_DE.txt Normal file
View File

@ -0,0 +1,9 @@
Der Knuth-Plass-Algorithmus, auch bekannt als der Algorithmus für den Zeilenumbruch in TeX, ist ein bedeutendes Verfahren im Bereich des computergestützten Satzes. Entwickelt von Donald E. Knuth und Michael F. Plass, löst dieser Algorithmus das Problem des optimalen Zeilenumbruchs in einem Absatz, indem er die Summe der Quadrate der notwendigen Dehnungen und Stauchungen der Zwischenräume in jeder Zeile minimiert. Emacs, als ein erweiterbarer Texteditor, bietet verschiedene Wege, um mit Satz und insbesondere mit dem Testen dieses Algorithmus umzugehen. In diesem Artikel werden wir untersuchen, wie man den Knuth-Plass-Algorithmus in Emacs testen kann.
Abschnitt 1: Die Bedeutung des Knuth-Plass-Algorithmus
Der Algorithmus von Knuth und Plass hat die Satzqualität in TeX-basierten Systemen revolutioniert. Im Vergleich zu einfacheren Algorithmen (wie dem Greedy-Algorithmus) produziert er ein ästhetisch ansprechenderes Layout, da er eine Gesamtbetrachtung des Absatzes vornimmt. Der Algorithmus berücksichtigt mögliche Umbruchpunkte über mehrere Zeilen hinweg und wählt diejenige Abfolge von Umbrüchen, die die Gesamtunregelmäßigkeit minimiert. Dies ist besonders wichtig in wissenschaftlichen Dokumenten und Büchern, wo ein hochwertiger Satz erwartet wird.
Abschnitt 2: Emacs und seine Satzfunktionalitäten
Emacs selbst ist kein Satzsystem, sondern ein Texteditor. Dennoch können durch Erweiterungen wie AUCTeX oder Org-Mode Satzfunktionen eingebunden werden. AUCTeX bietet eine Integration mit LaTeX, welches wiederum den TeX-Satzmotor verwendet. In diesem Kontext ist der Knuth-Plass-Algorithmus bereits in TeX implementiert. Allerdings geht es hier um das Testen des Algorithmus wie man seine Funktionsweise und Ausgabe in Emacs überprüfen kann.
Einige Emacs-Pakete ermöglichen die Vorschau von gesetzten Dokumenten, aber das reine Testen des Algorithmus erfordert eine tiefere Integration. Es gibt auch das Paket `adaptive-wrap', das den Zeilenumbruch innerhalb von Emacs verbessert, aber es verwendet nicht den vollen Knuth-Plass-Algorithmus.

7
tests/text-en_US.txt Normal file
View File

@ -0,0 +1,7 @@
* Emacs: The Extensible, Self-Documenting Computing Environment
Developed initially by Richard Stallman in the 1970s as part of the GNU Project and continuously refined for over four decades, GNU Emascs transcends conventional text editor categorization. At its architectural core, it operates as a dynamically extensible Lisp virtual machine optimized for symbolic computation and text transformation, enabling unparalleled workflow customization across programming, technical writing, scientific research, and system administration. Unlike transient application-centric tools, Emacs implements a paradigm where all user interactions—file editing, version control, email, shell access, calendar management, and even web browsing—occur within a unified, buffer-centric workspace that preserves context and minimizes cognitive friction. This environment persists across sessions through powerful state serialization, allowing users to resume complex tasks with full editing history intact.
* Foundational Technical Principles
The resilience of Emacs stems from its Emacs Lisp (Elisp) interpreter, which executes over 2.7 million lines of self-hosting code written predominantly in this Turing-complete dialect. Every editor function—from basic character insertion to syntax-aware refactoring—is implemented as Lisp callables, permitting real-time modification via interactive eval-expression (M-:) commands even during active usage. This runtime mutability distinguishes Emacs from static editors, enabling users to redefine core functions like find-file or alter keyboard mappings adaptively. The system further leverages non-blocking I/O through modern libuv integration, allowing asynchronous processes (compilers, network requests) to operate concurrently without freezing the UI while maintaining the legendary stability of its single-process model. Display rendering employs optimized redisplay algorithms that dynamically minimize screen updates, efficiently handling large files and latency-sensitive remote editing scenarios.

6
tests/text-fr.txt Normal file
View File

@ -0,0 +1,6 @@
Les paléocosmologistes contemporains affrontent un désarroi paradigmatique depuis la découverte fortuite des microfossiles astrobiologiques interstellaires dans les météorites carbonées d'Orgueil. Ces structures cryptocristallines microtubulaires, interprétées par certains comme des nanoorganismes exoplanétaires fossiles, nécessitent une réévaluation radicale des mécanismes d'abiogenèse transgalactique postulés par les théories néodarwiniennes panspermiques.
Le spectromètre magnétohydrodynamique interférentiel installé sur le radiotélescope ALMA (Atacama Large Millimeter Array) a détecté dans le nuage moléculaire Sagittarius B2 des prébiosignatures chimiques absolument inattendues : des chaînes polycycloaromatiques hétéronucléaires présentant une chiralité excessivement disproportionnée. Cette découverte impliquerait une énantiocatalyse asymétrique extraterrestre, phénomène que le modèle anthropocentrique traditionnel considérait strictement confiné aux écosystèmes terrestres.
Lors du 35ème Congrès International d'Astropaléobiologie, le professeur belgo-canadien Jérémie Van der Waals proposa l'hypothèse révolutionnaire des "macro-organismes photoniques solitoniques" (MOPS). Ces entités hypothétiques, dont la complexité autoréplicative quasi-organique défierait les lois classiques de la thermodynamique, se manifesteraient par des anomalies dans la polarisation du fond diffus cosmologique. Van der Waals s'appuie sur des calculs d'automates cellulaires quantiques non-locaux indiquant que ces structures proto-conscientes photocentrées pourraient constituer une biosphère cryptique circumuniverselle.

5
tests/text-zh-en_US.txt Normal file
View File

@ -0,0 +1,5 @@
作为神之编辑器Editor of the GodsEmacs 早已超越了普通文本编辑器的范畴。它是由Richard Stallman于1976年创建的GNU项目核心组件其名字源自 Editor MACroS。在过去的半个世纪里Emacs演化成了一个self-documenting, customizable, extensible的生态系统用户可通过Emacs Lisp (elisp) 重新定义编辑行为。M-x 是每个Emacer的魔法咒语——按下Alt或Meta键加x即可召唤任意命令比如M-x butterfly这样的复活节彩蛋。中国开发者常戏称其为“永远的操作系统因为你可以通过org-mode管理TODO list、用magit操作Git仓库、甚至用EMMS播放MP3音乐。在Unix哲学中Emacs坚持“一个编辑器统治所有One Editor to Rule Them All的理念这与VS Code等现代编辑器形成鲜明对比。C-x C-f打开文件C-x C-s保存文档看似复杂的组合键一旦形成肌肉记忆效率就会呈指数级飙升。著名Python库Black的开发者曾公开表示"My .emacs is my second brain."
当然学习曲线Learning Curve也是陡峭的。新用户需要理解buffer、window、frame的区别当你C-x 2分割视窗时两个窗口共享同一个frame而C-x 5 2会创建新frame——这种设计对Retina显示屏尤其友好。中文用户还必须配置ivy或helm这类模糊搜索插件来优化中文输入著名的.emacs.d配置仓库如 purcell/emacs.d 在GitHub上有超过11k stars。最令人震撼的是其实时文档系统按下C-h k后点击任何按键组合都会弹出对应函数的elisp文档。这种introspective capability让定制化没有边界。有程序员开玩笑说“当你终于调通.emacs配置时第一件事应该是用M-x doctor找心理医生。” 毕竟,谁没经历过因为(setq indent-tabs-mode nil)配置错误导致Python代码崩溃的深夜呢
Emacs哲学的精髓在于不是工具适应人而是人塑造工具Not the tool adapts to human, but human forges the tool。正如Stallman所言"Free as in freedom." 当你用org-capture记录灵感时用gnus收发邮件时甚至用M-x tetris摸鱼时都是在践行这种数字世界的自由意志。This text intentionally contains mixed spacing/tab alignment for KP algorithm testing purposes. 本文故意包含混合空格/制表符对齐以测试KP算法。

View File

@ -1 +0,0 @@
在使用 Emacs 进行开发时候,很多用户会依赖 Org-mode 来管理他们的任务和笔记。For instance, with Org-mode, you can easily organize your projects and even export your notes to various formats like HTML or PDF. 此外Emacs 的可定制性是它的一个巨大优势。You can write your own Emacs Lisp functions to automate repetitive tasks or extend the editor's functionality. 比如通过编写简单的函数你可以实现自动格式化代码、批量重命名文件或者集成Git等版本控制工具。Emacs 的强大之处在于它的灵活性和扩展性让用户可以根据自己的需求定制工作环境。Ni-ka Ford has always known that she wanted to be an artist. But she wasn't sure how to channel that passion until her final year as a studio art major in college. She remembers one day in an art studio when she was looking out the window at a tree. "And I was like, 'Wow, the branches really look like veins in the body,'" she says. This inspiration led her to notice "a lot of similarities between our bodies and nature" and drew her to the field of medical illustration. Today her work distills medical complexity into illustrations and graphics that appear in journal articles, teaching materials and popular publications.