process spaces between words

This commit is contained in:
Kinneyzhang 2026-01-25 01:17:18 +08:00
parent 6cf17e0066
commit 91d9bbc1e9
2 changed files with 179 additions and 99 deletions

View File

@ -115,6 +115,32 @@
"Push CJK CHAR to BOXES if non-nil. Return updated boxes." "Push CJK CHAR to BOXES if non-nil. Return updated boxes."
(if char (cons char boxes) boxes)) (if char (cons char boxes) boxes))
(defun ekp--flush-spaces (spaces boxes prev-state next-width)
"Push SPACES to BOXES based on context.
PREV-STATE: 1=latin, 2=CJK (previous content type).
NEXT-WIDTH: width of next character (1=latin, 2=CJK).
Rules:
- Latin-Latin: single space handled by glue, multiple preserves all but last
- CJK involved (prev or next is CJK): preserve ALL spaces"
(when (and spaces (not (string-empty-p spaces)))
(let ((cjk-involved (or (= prev-state 2) (= next-width 2))))
(cond
;; CJK involved: preserve all spaces
(cjk-involved
(setq boxes (cons spaces boxes)))
;; Latin-Latin with multiple spaces: preserve all but last
((> (length spaces) 1)
(setq boxes (cons (substring spaces 0 -1) boxes)))
;; Latin-Latin with single space: let glue handle it
(t nil))))
boxes)
(defun ekp--flush-trailing-spaces (spaces boxes)
"Push all trailing SPACES to BOXES (for end of string)."
(if (and spaces (not (string-empty-p spaces)))
(cons spaces boxes)
boxes))
(defun ekp--handle-latin-char (str state latin-word cjk-char boxes) (defun ekp--handle-latin-char (str state latin-word cjk-char boxes)
"Handle a latin (width=1) character. "Handle a latin (width=1) character.
Return (new-state new-latin-word new-cjk-char new-boxes)." Return (new-state new-latin-word new-cjk-char new-boxes)."
@ -140,24 +166,36 @@ Return (new-state new-latin-word new-cjk-char new-boxes)."
(defun ekp-split-to-boxes (string) (defun ekp-split-to-boxes (string)
"Split STRING into typographic boxes. "Split STRING into typographic boxes.
Latin words become single boxes; CJK chars are individual boxes. Latin words become single boxes; CJK chars are individual boxes.
Whitespace separates boxes; CJK punctuation attaches to preceding char." Whitespace runs are preserved as separate boxes; CJK punctuation attaches to preceding char."
(if (string-blank-p string) (if (string-blank-p string)
(vector string) (vector string)
(with-temp-buffer (with-temp-buffer
(insert string) (insert string)
(goto-char (point-min)) (goto-char (point-min))
(let ((state (char-width (seq-first string))) ; 1=latin, 2=CJK (let ((state (char-width (seq-first string))) ; 1=latin, 2=CJK
(prev-state 1) ; track previous content state for space handling
latin-word ; accumulator for latin characters latin-word ; accumulator for latin characters
cjk-char ; holds previous CJK char (for punct attachment) cjk-char ; holds previous CJK char (for punct attachment)
spaces ; accumulator for whitespace runs
boxes) ; result list (built in reverse) boxes) ; result list (built in reverse)
(while (not (eobp)) (while (not (eobp))
(let* ((str (buffer-substring (point) (1+ (point)))) (let* ((str (buffer-substring (point) (1+ (point))))
(width (string-width str))) (width (string-width str)))
(cond (cond
;; Whitespace or zero-width: flush latin word, start new box ;; Whitespace or zero-width: flush content, accumulate spaces
((or (string-blank-p str) (= 0 width)) ((or (string-blank-p str) (= 0 width))
(setq boxes (ekp--flush-cjk-char cjk-char boxes))
(when cjk-char (setq prev-state 2))
(setq cjk-char nil)
(setq boxes (ekp--flush-latin-word latin-word boxes)) (setq boxes (ekp--flush-latin-word latin-word boxes))
(setq latin-word nil)) (when latin-word (setq prev-state 1))
(setq latin-word nil)
(setq spaces (concat spaces str)))
;; Non-whitespace: flush spaces first, then handle char
(t
(setq boxes (ekp--flush-spaces spaces boxes prev-state width))
(setq spaces nil)
(cond
;; Latin character (width = 1) ;; Latin character (width = 1)
((= 1 width) ((= 1 width)
(pcase-let ((`(,s ,lw ,cc ,bx) (pcase-let ((`(,s ,lw ,cc ,bx)
@ -167,11 +205,12 @@ Whitespace separates boxes; CJK punctuation attaches to preceding char."
((= 2 width) ((= 2 width)
(pcase-let ((`(,s ,lw ,cc ,bx) (pcase-let ((`(,s ,lw ,cc ,bx)
(ekp--handle-cjk-char str state latin-word cjk-char boxes))) (ekp--handle-cjk-char str state latin-word cjk-char boxes)))
(setq state s latin-word lw cjk-char cc boxes bx))))) (setq state s latin-word lw cjk-char cc boxes bx)))))))
(forward-char 1)) (forward-char 1))
;; Flush remaining content ;; Flush remaining content
(setq boxes (ekp--flush-cjk-char cjk-char boxes)) (setq boxes (ekp--flush-cjk-char cjk-char boxes))
(setq boxes (ekp--flush-latin-word latin-word boxes)) (setq boxes (ekp--flush-latin-word latin-word boxes))
(setq boxes (ekp--flush-trailing-spaces spaces boxes))
(vconcat (nreverse boxes)))))) (vconcat (nreverse boxes))))))
(defun ekp-clear-caches () (defun ekp-clear-caches ()

107
ekp.el
View File

@ -76,7 +76,7 @@
"Preprocessed paragraph data." "Preprocessed paragraph data."
string latin-font cjk-font string latin-font cjk-font
boxes boxes-widths boxes-types glues-types boxes boxes-widths boxes-types glues-types
hyphen-pixel hyphen-pixel hyphen-positions
ideal-prefixs min-prefixs max-prefixs ideal-prefixs min-prefixs max-prefixs
(dp-cache nil :type hash-table)) (dp-cache nil :type hash-table))
@ -120,9 +120,11 @@
"Set all spacing parameters. "Set all spacing parameters.
LWS = Latin word space, MWS = mixed, CWS = CJK. LWS = Latin word space, MWS = mixed, CWS = CJK.
Each takes ideal, stretch (+), and shrink (-) values." Each takes ideal, stretch (+), and shrink (-) values."
(setq ekp-lws-ideal-pixel lws-i ekp-lws-stretch-pixel lws-+ ekp-lws-shrink-pixel lws-- (setq ekp-lws-ideal-pixel lws-i ekp-lws-stretch-pixel lws-+
ekp-mws-ideal-pixel mws-i ekp-mws-stretch-pixel mws-+ ekp-mws-shrink-pixel mws-- ekp-lws-shrink-pixel lws-- ekp-mws-ideal-pixel mws-i
ekp-cws-ideal-pixel cws-i ekp-cws-stretch-pixel cws-+ ekp-cws-shrink-pixel cws--) ekp-mws-stretch-pixel mws-+ ekp-mws-shrink-pixel mws--
ekp-cws-ideal-pixel cws-i ekp-cws-stretch-pixel cws-+
ekp-cws-shrink-pixel cws--)
(unless (ekp--params-set-p) (unless (ekp--params-set-p)
(error "All spacing parameters must be non-nil")) (error "All spacing parameters must be non-nil"))
(setq ekp-lws-max-pixel (+ lws-i lws-+) ekp-lws-min-pixel (- lws-i lws--) (setq ekp-lws-max-pixel (+ lws-i lws-+) ekp-lws-min-pixel (- lws-i lws--)
@ -142,17 +144,20 @@ Returns (boxes-vector . hyphen-positions-vector)."
(let* ((boxes (ekp-split-to-boxes string)) (let* ((boxes (ekp-split-to-boxes string))
(idx 0) new-boxes hyphen-idxs) (idx 0) new-boxes hyphen-idxs)
(dolist (box (append boxes nil)) (dolist (box (append boxes nil))
(if (string-match (format "^\\([[{<„‚¿¡*@\"']*\\)\\(%s+\\)\\([]}>.,*?\"']*\\)$" (if (string-match
(format "^\\([[{<„‚¿¡*@\"']*\\)\\(%s+\\)\\([]}>.,*?\"']*\\)$"
ekp--latin-regexp) ekp--latin-regexp)
box) box)
;; Latin word: apply hyphenation ;; Latin word: apply hyphenation
(let* ((left (match-string 1 box)) (let* ((left (match-string 1 box))
(word (match-string 2 box)) (word (match-string 2 box))
(right (match-string 3 box)) (right (match-string 3 box))
(parts (ekp-hyphen-boxes (ekp-hyphen-create ekp-latin-lang) word)) (parts (ekp-hyphen-boxes
(ekp-hyphen-create ekp-latin-lang) word))
(n (length parts))) (n (length parts)))
(when left (setcar parts (concat left (car parts)))) (when left (setcar parts (concat left (car parts))))
(when right (setcar (last parts) (concat (car (last parts)) right))) (when right (setcar (last parts)
(concat (car (last parts)) right)))
(push parts new-boxes) (push parts new-boxes)
(dotimes (i n) (dotimes (i n)
(when (< i (1- n)) (push idx hyphen-idxs)) (when (< i (1- n)) (push idx hyphen-idxs))
@ -166,6 +171,8 @@ Returns (boxes-vector . hyphen-positions-vector)."
(defun ekp--str-type (str) (defun ekp--str-type (str)
"STR should be single letter string." "STR should be single letter string."
(cond (cond
;; Whitespace (space, tab, etc.) or zero-width characters
((or (string-blank-p str) (= (string-width str) 0)) 'space)
;; a half-width cjk punct ;; a half-width cjk punct
((or (string= "" str) (string= "" str)) 'cjk) ((or (string= "" str) (string= "" str)) 'cjk)
((= (string-width str) 1) 'latin) ((= (string-width str) 1) 'latin)
@ -178,17 +185,23 @@ Returns (boxes-vector . hyphen-positions-vector)."
(defun ekp--box-type (box) (defun ekp--box-type (box)
(unless (or (null box) (string-empty-p box)) (unless (or (null box) (string-empty-p box))
;; Space/zero-width boxes: type is (space . space)
(if (or (string-blank-p box) (= (string-width box) 0))
'(space . space)
(cons (ekp--str-type (substring box 0 1)) (cons (ekp--str-type (substring box 0 1))
(ekp--str-type (substring box -1))))) (ekp--str-type (substring box -1))))))
(defun ekp--glue-type (prev-box-type curr-box-type) (defun ekp--glue-type (prev-box-type curr-box-type)
"Lws means whitespace between latin words; cws means "Lws means whitespace between latin words; cws means
whitespace between cjk words; mws means whitespace between whitespace between cjk words; mws means whitespace between
cjk and latin words; nws means no whitespace." cjk and latin words; nws means no whitespace.
Space boxes (preserved whitespace) need no additional glue."
(let ((before (cdr prev-box-type)) (let ((before (cdr prev-box-type))
(after (car curr-box-type))) (after (car curr-box-type)))
(if before (if before
(cond (cond
;; Space boxes: no additional glue needed
((or (eq before 'space) (eq after 'space)) 'nws)
((and (eq before 'latin) (eq after 'latin)) 'lws) ((and (eq before 'latin) (eq after 'latin)) 'lws)
((and (eq before 'cjk) (eq after 'cjk)) 'cws) ((and (eq before 'cjk) (eq after 'cjk)) 'cws)
((or (and (eq before 'cjk) (eq after 'latin)) ((or (and (eq before 'cjk) (eq after 'latin))
@ -240,7 +253,8 @@ Uses sxhash instead of MD5 for performance."
(cjk-font (ekp-cjk-font string))) (cjk-font (ekp-cjk-font string)))
;; Combine: string identity + fonts + spacing params ;; Combine: string identity + fonts + spacing params
;; sxhash is O(n) but much faster than MD5 ;; sxhash is O(n) but much faster than MD5
(sxhash (list (sxhash string) (sxhash
(list (sxhash string)
(object-intervals string) (object-intervals string)
latin-font cjk-font latin-font cjk-font
ekp-lws-ideal-pixel ekp-lws-stretch-pixel ekp-lws-shrink-pixel ekp-lws-ideal-pixel ekp-lws-stretch-pixel ekp-lws-shrink-pixel
@ -265,7 +279,8 @@ Computes ALL data in one pass: text, params, and prefix arrays."
;; Compute box properties ;; Compute box properties
(boxes-widths (vconcat (mapcar #'string-pixel-width boxes))) (boxes-widths (vconcat (mapcar #'string-pixel-width boxes)))
(boxes-types (vconcat (mapcar #'ekp--box-type boxes))) (boxes-types (vconcat (mapcar #'ekp--box-type boxes)))
(glues-types (ekp--compute-glue-types boxes boxes-types hyphen-positions)) (glues-types (ekp--compute-glue-types
boxes boxes-types hyphen-positions))
(hyphen-pixel (string-pixel-width "-")) (hyphen-pixel (string-pixel-width "-"))
;; Compute prefix arrays in one pass ;; Compute prefix arrays in one pass
(ideal-prefixs (make-vector (1+ n) 0)) (ideal-prefixs (make-vector (1+ n) 0))
@ -294,6 +309,7 @@ Computes ALL data in one pass: text, params, and prefix arrays."
:boxes-types boxes-types :boxes-types boxes-types
:glues-types glues-types :glues-types glues-types
:hyphen-pixel hyphen-pixel :hyphen-pixel hyphen-pixel
:hyphen-positions hyphen-positions
:ideal-prefixs ideal-prefixs :ideal-prefixs ideal-prefixs
:min-prefixs min-prefixs :min-prefixs min-prefixs
:max-prefixs max-prefixs :max-prefixs max-prefixs
@ -338,6 +354,9 @@ This is the main entry point for cached paragraph data."
(defun ekp--hyphen-pixel (string) (defun ekp--hyphen-pixel (string)
(ekp-para-hyphen-pixel (ekp--get-para string))) (ekp-para-hyphen-pixel (ekp--get-para string)))
(defun ekp--hyphen-positions (string)
(ekp-para-hyphen-positions (ekp--get-para string)))
(defun ekp--hyphen-str (_string) (defun ekp--hyphen-str (_string)
"Return hyphen character." "Return hyphen character."
"-") "-")
@ -426,10 +445,11 @@ Returns (:badness NUM :fitness NUM :gaps LIST :adjustment NUM :flexibility NUM).
:adjustment adjustment :adjustment adjustment
:flexibility flexibility))) :flexibility flexibility)))
(defun ekp--hyphenate-p (glues-types n) (defun ekp--hyphenate-p (hyphen-positions n)
"Return non-nil if position N ends with hyphenation." "Return non-nil if position N ends with hyphenation.
(and (< n (length glues-types)) HYPHEN-POSITIONS is a vector of indices where hyphenation can occur."
(eq 'nws (aref glues-types n)))) (and hyphen-positions
(cl-find n hyphen-positions)))
;;;; Dynamic Programming Line Breaking ;;;; Dynamic Programming Line Breaking
@ -444,7 +464,8 @@ Returns (backptrs demerits rests gaps hyphen-counts fitness-classes line-counts)
(fitness-classes (make-vector (1+ n) 1)) ; default: decent (fitness-classes (make-vector (1+ n) 1)) ; default: decent
(line-counts (make-vector (1+ n) 0))) ; for looseness (line-counts (make-vector (1+ n) 0))) ; for looseness
(aset demerits 0 0.0) (aset demerits 0 0.0)
(list backptrs demerits rests gaps hyphen-counts fitness-classes line-counts))) (list backptrs demerits rests gaps
hyphen-counts fitness-classes line-counts)))
(defun ekp--dp-line-metrics (i k glues-types ideal-prefixs min-prefixs max-prefixs) (defun ekp--dp-line-metrics (i k glues-types ideal-prefixs min-prefixs max-prefixs)
"Compute line metrics for boxes I to K. "Compute line metrics for boxes I to K.
@ -457,7 +478,7 @@ Returns (ideal-pixel min-pixel max-pixel) excluding leading glue."
(- (aref max-prefixs k) (aref max-prefixs i) (- (aref max-prefixs k) (aref max-prefixs i)
(ekp-glue-max-pixel leading-glue-type))))) (ekp-glue-max-pixel leading-glue-type)))))
(defun ekp--dp-force-break (i k arrays glues-types ideal-prefixs hyphen-pixel line-pixel) (defun ekp--dp-force-break (i k arrays glues-types hyphen-positions ideal-prefixs hyphen-pixel line-pixel)
"Force a break at K-1 when no valid break found. Update ARRAYS." "Force a break at K-1 when no valid break found. Update ARRAYS."
(let* ((backptrs (nth 0 arrays)) (let* ((backptrs (nth 0 arrays))
(demerits (nth 1 arrays)) (demerits (nth 1 arrays))
@ -466,7 +487,7 @@ Returns (ideal-pixel min-pixel max-pixel) excluding leading glue."
(fitness-classes (nth 5 arrays)) (fitness-classes (nth 5 arrays))
(line-counts (nth 6 arrays)) (line-counts (nth 6 arrays))
(break-pos (1- k)) (break-pos (1- k))
(hyphenate-p (ekp--hyphenate-p glues-types break-pos)) (hyphenate-p (ekp--hyphenate-p hyphen-positions break-pos))
(ideal-pixel (- (aref ideal-prefixs break-pos) (ideal-pixel (- (aref ideal-prefixs break-pos)
(aref ideal-prefixs i) (aref ideal-prefixs i)
(ekp-glue-ideal-pixel (aref glues-types i)))) (ekp-glue-ideal-pixel (aref glues-types i))))
@ -508,7 +529,8 @@ Returns (demerits gaps fitness new-hyphen-count)."
(list dem nil 1 0))) (list dem nil 1 0)))
;; Normal line ;; Normal line
(t (t
(let* ((result (ekp--line-badness-and-fitness ideal-pixel line-pixel (let* ((result (ekp--line-badness-and-fitness
ideal-pixel line-pixel
(seq-subseq glues-types i k))) (seq-subseq glues-types i k)))
(badness (plist-get result :badness)) (badness (plist-get result :badness))
(fitness (plist-get result :fitness)) (fitness (plist-get result :fitness))
@ -563,6 +585,7 @@ Uses Knuth-Plass dynamic programming with demerits."
(let* ((glues-types (ekp-para-glues-types para)) (let* ((glues-types (ekp-para-glues-types para))
(boxes (ekp-para-boxes para)) (boxes (ekp-para-boxes para))
(hyphen-pixel (ekp-para-hyphen-pixel para)) (hyphen-pixel (ekp-para-hyphen-pixel para))
(hyphen-positions (ekp-para-hyphen-positions para))
(n (length boxes)) (n (length boxes))
(ideal-prefixs (ekp-para-ideal-prefixs para)) (ideal-prefixs (ekp-para-ideal-prefixs para))
(min-prefixs (ekp-para-min-prefixs para)) (min-prefixs (ekp-para-min-prefixs para))
@ -586,9 +609,12 @@ Uses Knuth-Plass dynamic programming with demerits."
(dotimes (j (- n i)) (dotimes (j (- n i))
(let* ((k (+ i j 1)) (let* ((k (+ i j 1))
(is-last (= k n)) (is-last (= k n))
(end-with-hyphenp (ekp--hyphenate-p glues-types k)) ;; k is the break position (exclusive), k-1 is the last box index
(end-with-hyphenp
(ekp--hyphenate-p hyphen-positions (1- k)))
(metrics (ekp--dp-line-metrics (metrics (ekp--dp-line-metrics
i k glues-types ideal-prefixs min-prefixs max-prefixs)) i k glues-types
ideal-prefixs min-prefixs max-prefixs))
(ideal-pixel (nth 0 metrics)) (ideal-pixel (nth 0 metrics))
(min-pixel (nth 1 metrics)) (min-pixel (nth 1 metrics))
(max-pixel (nth 2 metrics))) (max-pixel (nth 2 metrics)))
@ -601,7 +627,8 @@ Uses Knuth-Plass dynamic programming with demerits."
(when (or (> min-pixel line-pixel) (when (or (> min-pixel line-pixel)
(and is-last (> ideal-pixel line-pixel))) (and is-last (> ideal-pixel line-pixel)))
(when (null (aref demerits (1- k))) (when (null (aref demerits (1- k)))
(ekp--dp-force-break i k arrays glues-types (ekp--dp-force-break
i k arrays glues-types hyphen-positions
ideal-prefixs hyphen-pixel line-pixel)) ideal-prefixs hyphen-pixel line-pixel))
(throw 'break nil)) (throw 'break nil))
;; Valid break point: compute demerits ;; Valid break point: compute demerits
@ -664,8 +691,12 @@ Returns ((latin-adj . latin-extra) (mix-adj . mix-extra) (cjk-adj . cjk-extra)).
(cjk-gaps (nth 2 gaps-list)) (cjk-gaps (nth 2 gaps-list))
(remaining rest-pixel) (remaining rest-pixel)
;; Per-gap adjustment values ;; Per-gap adjustment values
(latin-change (if stretch-p ekp-lws-stretch-pixel ekp-lws-shrink-pixel)) (latin-change (if stretch-p
(mix-change (if stretch-p ekp-mws-stretch-pixel ekp-mws-shrink-pixel)) ekp-lws-stretch-pixel
ekp-lws-shrink-pixel))
(mix-change (if stretch-p
ekp-mws-stretch-pixel
ekp-mws-shrink-pixel))
(cjk-change (if stretch-p ekp-cws-stretch-pixel 0)) (cjk-change (if stretch-p ekp-cws-stretch-pixel 0))
;; Results ;; Results
(latin-adj 0) (latin-extra 0) (latin-adj 0) (latin-extra 0)
@ -751,6 +782,7 @@ Each line's glues: [0 glue1 glue2 ... trailing-space]."
(let* ((boxes-widths (ekp--boxes-widths string)) (let* ((boxes-widths (ekp--boxes-widths string))
(boxes-num (length (ekp--boxes string))) (boxes-num (length (ekp--boxes string)))
(glues-types (ekp--glues-types string)) (glues-types (ekp--glues-types string))
(hyphen-positions (ekp--hyphen-positions string))
(ideal-prefixs (ekp--ideal-prefixs string)) (ideal-prefixs (ekp--ideal-prefixs string))
(max-prefixs (ekp--max-prefixs string)) (max-prefixs (ekp--max-prefixs string))
(breaks (ekp-line-breaks string line-pixel)) (breaks (ekp-line-breaks string line-pixel))
@ -764,7 +796,8 @@ Each line's glues: [0 glue1 glue2 ... trailing-space]."
(line-boxes-widths (cl-subseq boxes-widths start end)) (line-boxes-widths (cl-subseq boxes-widths start end))
(line-glues-types (seq-drop (cl-subseq glues-types start end) 1)) (line-glues-types (seq-drop (cl-subseq glues-types start end) 1))
(is-last (>= end boxes-num)) (is-last (>= end boxes-num))
(hyphen-p (ekp--hyphenate-p glues-types end)) ;; end is exclusive, end-1 is the last box index
(hyphen-p (ekp--hyphenate-p hyphen-positions (1- end)))
(ideal-pixel (- (aref ideal-prefixs end) (ideal-pixel (- (aref ideal-prefixs end)
(aref ideal-prefixs start) (aref ideal-prefixs start)
(ekp-glue-ideal-pixel (aref glues-types start)))) (ekp-glue-ideal-pixel (aref glues-types start))))
@ -782,7 +815,8 @@ Each line's glues: [0 glue1 glue2 ... trailing-space]."
hyphen-p hyphen-pixel)) hyphen-p hyphen-pixel))
;; Last line: ragged right ;; Last line: ragged right
(is-last (is-last
(ekp--line-glue-last-line line-glues-types ideal-pixel line-pixel)) (ekp--line-glue-last-line
line-glues-types ideal-pixel line-pixel))
;; Forced break (line too short even at max stretch) ;; Forced break (line too short even at max stretch)
((< max-pixel line-pixel) ((< max-pixel line-pixel)
(append '(0) (append '(0)
@ -824,15 +858,21 @@ Each line's glues: [0 glue1 glue2 ... trailing-space]."
(breaks (ekp-line-breaks string line-pixel)) (breaks (ekp-line-breaks string line-pixel))
(num (length breaks)) (num (length breaks))
(lines-glues (ekp-line-glues string line-pixel)) (lines-glues (ekp-line-glues string line-pixel))
(glues-types (ekp--glues-types string)) (hyphen-positions (ekp--hyphen-positions string))
(start 0) strings) (start 0) strings)
(dotimes (i num) (dotimes (i num)
(let* ((end (nth i breaks)) (let* ((end (nth i breaks))
(line-boxes (cl-subseq boxes start end)) (line-boxes (cl-subseq boxes start end))
(line-glues (mapcar #'ekp-pixel-spacing (line-glues (mapcar #'ekp-pixel-spacing
(aref lines-glues i)))) (aref lines-glues i)))
;; not last line and glue is 'nws, should add hyphen ;; Check if last box of this line needs hyphen
(when (ekp--hyphenate-p glues-types end) ;; hyphen-positions stores box indices, end-1
;; is the last box index
(last-box-idx (1- end))
(need-hyphen
(and (< i (1- num)) ; not last line
(ekp--hyphenate-p hyphen-positions last-box-idx))))
(when need-hyphen
(setf (aref line-boxes (- end start 1)) (setf (aref line-boxes (- end start 1))
(concat (aref line-boxes (- end start 1)) hyphen))) (concat (aref line-boxes (- end start 1)) hyphen)))
(push (ekp--combine-glues-and-boxes line-glues line-boxes) (push (ekp--combine-glues-and-boxes line-glues line-boxes)
@ -840,7 +880,7 @@ Each line's glues: [0 glue1 glue2 ... trailing-space]."
(setq start end))) (setq start end)))
(mapconcat 'identity (nreverse strings) "\n"))) (mapconcat 'identity (nreverse strings) "\n")))
(defun ekp-pixel-justify (string line-pixel &optional _use-cache) (defun ekp-pixel-justify (string line-pixel)
"Justify multiline STRING to LINE-PIXEL. "Justify multiline STRING to LINE-PIXEL.
USE-CACHE is ignored; caching is always enabled via ekp--para-cache." USE-CACHE is ignored; caching is always enabled via ekp--para-cache."
(let ((strs (split-string string "\n"))) (let ((strs (split-string string "\n")))
@ -892,7 +932,7 @@ Returns the pixel width with minimum average cost."
best-pixel p))))) best-pixel p)))))
best-pixel))) best-pixel)))
(defun ekp-pixel-range-justify (string min-pixel max-pixel &optional _use-cache) (defun ekp-pixel-range-justify (string min-pixel max-pixel)
"Find optimal width for STRING between MIN-PIXEL and MAX-PIXEL. "Find optimal width for STRING between MIN-PIXEL and MAX-PIXEL.
Returns (justified-text . optimal-pixel). Returns (justified-text . optimal-pixel).
Uses ternary search for O(log n) width evaluations. Uses ternary search for O(log n) width evaluations.
@ -902,7 +942,8 @@ All preprocessing is cached via ekp--para-cache."
(_ (dolist (s strings) (_ (dolist (s strings)
(unless (string-blank-p s) (unless (string-blank-p s)
(ekp--get-para s)))) (ekp--get-para s))))
(best-pixel (ekp--ternary-search-optimal-width strings min-pixel max-pixel))) (best-pixel (ekp--ternary-search-optimal-width
strings min-pixel max-pixel)))
(cons (ekp-pixel-justify string best-pixel) best-pixel))) (cons (ekp-pixel-justify string best-pixel) best-pixel)))
(provide 'ekp) (provide 'ekp)