This commit is contained in:
Kinneyzhang 2026-01-25 18:23:33 +08:00
parent 68ca9b191a
commit a790aa87c3

116
ekp.el
View File

@ -256,14 +256,14 @@ Space boxes (preserved whitespace) need no additional glue."
(defun ekp--para-hash (string) (defun ekp--para-hash (string)
"Compute fast hash key for STRING. "Compute fast hash key for STRING.
Uses sxhash instead of MD5 for performance." Uses sxhash on serialized representation to correctly handle text properties."
(let ((latin-font (ekp-latin-font string)) (let ((latin-font (ekp-latin-font string))
(cjk-font (ekp-cjk-font string))) (cjk-font (ekp-cjk-font string)))
;; Combine: string identity + fonts + spacing params ;; Combine: string content + text properties + fonts + spacing params
;; sxhash is O(n) but much faster than MD5 ;; Use prin1-to-string on intervals to ensure property values are hashed
(sxhash (sxhash
(list (sxhash string) (list (sxhash string)
(object-intervals string) (sxhash (prin1-to-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
ekp-mws-ideal-pixel ekp-mws-stretch-pixel ekp-mws-shrink-pixel ekp-mws-ideal-pixel ekp-mws-stretch-pixel ekp-mws-shrink-pixel
@ -607,6 +607,101 @@ If `ekp-use-c-module' is non-nil and C module is available, uses it."
('cws 3) ('cws 3)
(_ 0))) ; nws or nil (_ 0))) ; nws or nil
(defun ekp--prepare-para-for-batch (para line-pixel)
"Prepare PARA data as vector for batch API at LINE-PIXEL.
Returns [ideal-prefix min-prefix max-prefix glue-ideals glue-shrinks
glue-stretches hyphen-positions hyphen-width line-width]."
(let* ((ideal-prefixs (ekp-para-ideal-prefixs para))
(min-prefixs (ekp-para-min-prefixs para))
(max-prefixs (ekp-para-max-prefixs para))
(glues-types (ekp-para-glues-types para))
(hyphen-positions (ekp-para-hyphen-positions para))
(hyphen-pixel (ekp-para-hyphen-pixel para))
(n (length (ekp-para-boxes para)))
(glue-ideals (make-vector n 0))
(glue-shrinks (make-vector n 0))
(glue-stretches (make-vector n 0)))
(dotimes (i n)
(let ((type (aref glues-types i)))
(aset glue-ideals i (ekp-glue-ideal-pixel type))
(aset glue-shrinks i (- (ekp-glue-ideal-pixel type)
(ekp-glue-min-pixel type)))
(aset glue-stretches i (- (ekp-glue-max-pixel type)
(ekp-glue-ideal-pixel type)))))
(vector ideal-prefixs min-prefixs max-prefixs
glue-ideals glue-shrinks glue-stretches
hyphen-positions hyphen-pixel line-pixel)))
(defun ekp--store-batch-result (para line-pixel breaks cost)
"Store batch result (BREAKS, COST) into PARA's dp-cache for LINE-PIXEL.
Computes rests and gaps from breaks."
(let* ((glues-types (ekp-para-glues-types para))
(ideal-prefixs (ekp-para-ideal-prefixs para))
(hyphen-positions (ekp-para-hyphen-positions para))
(hyphen-pixel (ekp-para-hyphen-pixel para))
(start 0)
lines-rests lines-gaps)
(dolist (end breaks)
(let* ((leading-glue-type (aref glues-types start))
(end-with-hyphenp (ekp--hyphenate-p hyphen-positions (1- end)))
(ideal-pixel (- (aref ideal-prefixs end)
(aref ideal-prefixs start)
(ekp-glue-ideal-pixel leading-glue-type))))
(when end-with-hyphenp
(cl-incf ideal-pixel hyphen-pixel))
(push (- line-pixel ideal-pixel) lines-rests)
(push (ekp--gaps-list
(seq-drop (cl-subseq glues-types start end) 1))
lines-gaps)
(setq start end)))
(let ((dp-result (list :rests (nreverse lines-rests)
:gaps (nreverse lines-gaps)
:breaks breaks
:cost cost
:line-count (length breaks))))
(puthash line-pixel dp-result (ekp-para-dp-cache para))
dp-result)))
(defun ekp--dp-cache-batch (strings line-pixel)
"Compute DP for multiple STRINGS in parallel using C batch API.
Returns list of dp-results in same order as STRINGS.
Only processes strings that aren't already cached."
(let* ((paras (mapcar #'ekp--get-para strings))
(needs-compute '()) ; list of (index . para)
(results (make-vector (length strings) nil)))
;; Check which paras need computation
(cl-loop for para in paras
for i from 0
for cached = (ekp--dp-get-cached para line-pixel)
do (if cached
(aset results i cached)
(push (cons i para) needs-compute)))
;; If all cached, return immediately
(if (null needs-compute)
(append results nil)
;; Prepare batch input for uncached paras
(let* ((needs-compute (nreverse needs-compute))
(batch-input (vconcat
(mapcar (lambda (ip)
(ekp--prepare-para-for-batch (cdr ip) line-pixel))
needs-compute)))
(batch-results (ekp-c-break-batch batch-input)))
;; Process results
(cl-loop for ip in needs-compute
for j from 0
for idx = (car ip)
for para = (cdr ip)
for res = (aref batch-results j)
for breaks = (car res)
for cost = (cdr res)
do (if breaks
(aset results idx
(ekp--store-batch-result para line-pixel breaks cost))
;; C failed, fallback to Elisp
(aset results idx
(ekp--dp-cache-elisp para (ekp-para-string para) line-pixel)))))
(append results nil))))
(defun ekp--dp-cache-via-c (para string line-pixel) (defun ekp--dp-cache-via-c (para string line-pixel)
"Compute breaks using C module with Elisp's pre-computed prefix arrays. "Compute breaks using C module with Elisp's pre-computed prefix arrays.
C module receives ALL font-dependent data from Elisp's para struct: C module receives ALL font-dependent data from Elisp's para struct:
@ -1063,8 +1158,17 @@ The removed space width is redistributed to remaining glues for proper justifica
(defun ekp-pixel-justify (string line-pixel) (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." When C module is available and enabled, uses parallel batch processing."
(let ((strs (split-string string "\n"))) (let* ((strs (split-string string "\n"))
(non-blank-strs (cl-remove-if #'string-blank-p strs))
(use-batch (and ekp-use-c-module
(boundp 'ekp-c-module-loaded) ekp-c-module-loaded
(fboundp 'ekp-c-break-batch)
(> (length non-blank-strs) 1))))
;; Pre-compute all DP results in parallel if using batch
(when use-batch
(ekp--dp-cache-batch non-blank-strs line-pixel))
;; Now process each string (DP results are cached)
(mapconcat (lambda (str) (mapconcat (lambda (str)
(if (string-blank-p str) (if (string-blank-p str)
"" ""