Fix: DP algorithm now excludes leading/trailing space box widths to match rendering behavior

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-25 12:41:04 +00:00
parent 836ff75c89
commit c60ddb3903

90
ekp.el
View File

@ -175,31 +175,6 @@ Each takes ideal, stretch (+), and shrink (-) values."
"[A-Za-z'\\-\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u024F\u1E00-\u1EFF]" "[A-Za-z'\\-\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u024F\u1E00-\u1EFF]"
"Regexp matching Latin characters including accented forms.") "Regexp matching Latin characters including accented forms.")
(defun ekp--split-hyphenated-word (word)
"Split WORD at existing hyphens into parts with hyphens attached.
E.g., \"help-echo\" -> (\"help-\" \"echo\").
Handles edge cases: trailing hyphens, leading hyphens, consecutive hyphens.
Does NOT apply dictionary hyphenation - just uses existing hyphens."
(let ((result nil)
(current nil) ; list of chars (built in reverse)
(i 0)
(len (length word)))
(while (< i len)
(let ((char (aref word i)))
(if (= char ?-)
;; Hyphen: attach to current segment and push
(progn
(push char current)
(push (apply #'string (nreverse current)) result)
(setq current nil))
;; Non-hyphen: accumulate
(push char current)))
(cl-incf i))
;; Push remaining content (last segment after final hyphen, or entire word if no hyphen)
(unless (null current)
(push (apply #'string (nreverse current)) result))
(nreverse result)))
(defun ekp--split-with-hyphen (string) (defun ekp--split-with-hyphen (string)
"Split STRING into boxes with hyphenation points marked. "Split STRING into boxes with hyphenation points marked.
Returns (boxes-vector . hyphen-positions-vector)." Returns (boxes-vector . hyphen-positions-vector)."
@ -210,16 +185,12 @@ Returns (boxes-vector . hyphen-positions-vector)."
(format "^\\([[{<„‚¿¡*@\"']*\\)\\(%s+\\)\\([]}>.,*?\"']*\\)$" (format "^\\([[{<„‚¿¡*@\"']*\\)\\(%s+\\)\\([]}>.,*?\"']*\\)$"
ekp--latin-regexp) ekp--latin-regexp)
box) box)
;; Latin word (possibly with embedded hyphens) ;; 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))
;; If word contains hyphens, split at those points only (no further hyphenation) (parts (ekp-hyphen-boxes
;; Otherwise, apply dictionary hyphenation (ekp-hyphen-create ekp-latin-lang) word))
(parts (if (string-match-p "-" word)
(ekp--split-hyphenated-word word)
(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) (when right (setcar (last parts)
@ -613,16 +584,51 @@ When looseness != 0, alt-paths tracks alternative paths by (position . line-coun
(list backptrs demerits rests gaps (list backptrs demerits rests gaps
hyphen-counts fitness-classes line-counts alt-paths))) hyphen-counts fitness-classes line-counts alt-paths)))
(defun ekp--leading-space-width (i boxes-types boxes-widths)
"Compute total width of leading space boxes starting at position I.
Returns 0 if box at I is not a space box."
(let ((n (length boxes-types))
(width 0)
(pos i))
(while (and (< pos n)
(let ((box-type (aref boxes-types pos)))
(and box-type (eq (car box-type) 'space))))
(cl-incf width (aref boxes-widths pos))
(cl-incf pos))
width))
(defun ekp--trailing-space-width (k boxes-types boxes-widths)
"Compute total width of trailing space boxes ending before position K.
K is the exclusive end position (break point).
Returns 0 if box at K-1 is not a space box."
(let ((width 0)
(pos (1- k)))
(while (and (>= pos 0)
(let ((box-type (aref boxes-types pos)))
(and box-type (eq (car box-type) 'space))))
(cl-incf width (aref boxes-widths pos))
(cl-decf pos))
width))
(defun ekp--dp-line-metrics (para i k glues-types ideal-prefixs min-prefixs max-prefixs) (defun ekp--dp-line-metrics (para i k glues-types ideal-prefixs min-prefixs max-prefixs)
"Compute line metrics for boxes I to K using PARA's stored glue params. "Compute line metrics for boxes I to K using PARA's stored glue params.
Returns (ideal-pixel min-pixel max-pixel) excluding leading glue." Returns (ideal-pixel min-pixel max-pixel) excluding leading glue and leading/trailing space boxes."
(let ((leading-glue-type (aref glues-types i))) (let* ((leading-glue-type (aref glues-types i))
(boxes-types (ekp-para-boxes-types para))
(boxes-widths (ekp-para-boxes-widths para))
;; Compute width of leading/trailing space boxes (these will be stripped during rendering)
(leading-space-width (ekp--leading-space-width i boxes-types boxes-widths))
(trailing-space-width (ekp--trailing-space-width k boxes-types boxes-widths))
(space-width (+ leading-space-width trailing-space-width)))
(list (- (aref ideal-prefixs k) (aref ideal-prefixs i) (list (- (aref ideal-prefixs k) (aref ideal-prefixs i)
(ekp--para-glue-ideal para leading-glue-type)) (ekp--para-glue-ideal para leading-glue-type)
space-width)
(- (aref min-prefixs k) (aref min-prefixs i) (- (aref min-prefixs k) (aref min-prefixs i)
(ekp--para-glue-min para leading-glue-type)) (ekp--para-glue-min para leading-glue-type)
space-width)
(- (aref max-prefixs k) (aref max-prefixs i) (- (aref max-prefixs k) (aref max-prefixs i)
(ekp--para-glue-max para leading-glue-type))))) (ekp--para-glue-max para leading-glue-type)
space-width))))
(defun ekp--dp-force-break (para i k arrays glues-types hyphen-positions ideal-prefixs hyphen-pixel line-pixel) (defun ekp--dp-force-break (para 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.
@ -635,9 +641,17 @@ Uses PARA's stored glue params for consistency."
(line-counts (nth 6 arrays)) (line-counts (nth 6 arrays))
(break-pos (1- k)) (break-pos (1- k))
(hyphenate-p (ekp--hyphenate-p hyphen-positions break-pos)) (hyphenate-p (ekp--hyphenate-p hyphen-positions break-pos))
(boxes-types (ekp-para-boxes-types para))
(boxes-widths (ekp-para-boxes-widths para))
;; Exclude leading/trailing space boxes (will be stripped during rendering)
;; Use k as the exclusive end for trailing space calculation
(leading-space-width (ekp--leading-space-width i boxes-types boxes-widths))
(trailing-space-width (ekp--trailing-space-width k boxes-types boxes-widths))
(space-width (+ leading-space-width trailing-space-width))
(ideal-pixel (- (aref ideal-prefixs break-pos) (ideal-pixel (- (aref ideal-prefixs break-pos)
(aref ideal-prefixs i) (aref ideal-prefixs i)
(ekp--para-glue-ideal para (aref glues-types i)))) (ekp--para-glue-ideal para (aref glues-types i))
space-width))
(rest-pixel (- line-pixel ideal-pixel))) (rest-pixel (- line-pixel ideal-pixel)))
(when hyphenate-p (cl-incf ideal-pixel hyphen-pixel)) (when hyphenate-p (cl-incf ideal-pixel hyphen-pixel))
;; Force break with high demerits ;; Force break with high demerits