fix: enforce kinsoku rules for CJK opening punctuation in box splitting

Opening brackets ((, 「, 《, etc.) are now attached to the following
character instead of the preceding one, preventing them from appearing
at the end of a line which violates kinsoku typographic rules.

Fixes #9

Agent-Logs-Url: https://github.com/Kinneyzhang/emacs-kp/sessions/d05069f4-db64-4601-9816-f582edad4a1b

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-24 15:22:52 +00:00 committed by GitHub
parent caf6576260
commit 50d41ed30f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 10 deletions

View File

@ -115,6 +115,14 @@
(and (>= char #x3000) (<= char #x303F)) (and (>= char #x3000) (<= char #x303F))
(and (>= char #xFF00) (<= char #xFF60))))) (and (>= char #xFF00) (<= char #xFF60)))))
(defun ekp-cjk-opening-punct-p (str)
"Return non-nil if STR ends with a CJK opening punctuation.
These characters must not appear at the end of a line (kinsoku rule).
When STR is held as cjk-char, this checks if it still needs attachment."
(let ((char (aref str (1- (length str)))))
(memq (get-char-code-property char 'general-category)
'(Ps Pi))))
(defun ekp--flush-latin-word (word boxes) (defun ekp--flush-latin-word (word boxes)
"Push latin WORD to BOXES if non-nil. Return updated boxes." "Push latin WORD to BOXES if non-nil. Return updated boxes."
(if word (cons word boxes) boxes)) (if word (cons word boxes) boxes))
@ -161,20 +169,41 @@ Return (new-state new-latin-word new-cjk-char new-boxes)."
;; Already in latin mode: accumulate ;; Already in latin mode: accumulate
(list 1 (concat latin-word str) nil boxes) (list 1 (concat latin-word str) nil boxes)
;; Was in CJK mode: flush CJK char, switch to latin ;; Was in CJK mode: flush CJK char, switch to latin
(list 1 str nil (ekp--flush-cjk-char cjk-char boxes)))) ;; If held cjk-char is opening punct, prepend it to the latin word
(if (and cjk-char (ekp-cjk-opening-punct-p cjk-char))
(list 1 (concat cjk-char str) nil boxes)
(list 1 str nil (ekp--flush-cjk-char cjk-char boxes)))))
(defun ekp--handle-cjk-char (str state latin-word cjk-char boxes) (defun ekp--handle-cjk-char (str state latin-word cjk-char boxes)
"Handle a CJK (width=2) character. "Handle a CJK (width=2) character.
Return (new-state new-latin-word new-cjk-char new-boxes)." Return (new-state new-latin-word new-cjk-char new-boxes)."
(if (= state 1) (if (= state 1)
;; Was in latin mode: flush latin word, push CJK directly ;; Was in latin mode: flush latin word, push CJK directly
(list 2 nil nil (cons str (ekp--flush-latin-word latin-word boxes))) (let ((new-boxes (ekp--flush-latin-word latin-word boxes)))
(if (ekp-cjk-opening-punct-p str)
;; Opening punct: hold as cjk-char (will attach to next char)
(list 2 nil str new-boxes)
(list 2 nil nil (cons str new-boxes))))
;; Already in CJK mode ;; Already in CJK mode
(if (ekp-cjk-fw-punct-p str) (cond
;; Punctuation attaches to previous CJK char ((ekp-cjk-opening-punct-p str)
(list 2 nil nil (cons (concat cjk-char str) boxes)) ;; Opening punct: cannot end a line (kinsoku rule).
;; Regular CJK char: flush previous, hold current ;; If previous held char is also opening punct, concatenate them.
(list 2 nil str (ekp--flush-cjk-char cjk-char boxes))))) ;; Otherwise flush previous and hold this opening punct.
(if (and cjk-char (ekp-cjk-opening-punct-p cjk-char))
(list 2 nil (concat cjk-char str) boxes)
(list 2 nil str (ekp--flush-cjk-char cjk-char boxes))))
((ekp-cjk-fw-punct-p str)
;; Closing/other punct: attaches to previous CJK char
(list 2 nil nil (cons (concat cjk-char str) boxes)))
(t
;; Regular CJK char: prepend any held opening punct
(if (and cjk-char (ekp-cjk-opening-punct-p cjk-char))
;; Previous was opening punct: combine with current char and hold.
;; Now last char is regular, so this won't be detected as opening.
(list 2 nil (concat cjk-char str) boxes)
;; Normal case: flush previous, hold current
(list 2 nil str (ekp--flush-cjk-char cjk-char boxes)))))))
(defun ekp-split-to-boxes (string) (defun ekp-split-to-boxes (string)
"Split STRING into typographic boxes. "Split STRING into typographic boxes.
@ -197,9 +226,12 @@ Whitespace runs are preserved as separate boxes; CJK punctuation attaches to pre
(cond (cond
;; Whitespace or zero-width: flush content, accumulate spaces ;; Whitespace or zero-width: flush content, accumulate spaces
((or (string-blank-p str) (= 0 width)) ((or (string-blank-p str) (= 0 width))
;; Don't flush opening punct - keep it held for attachment to next char
(if (and cjk-char (ekp-cjk-opening-punct-p cjk-char))
nil ; keep cjk-char as-is
(setq boxes (ekp--flush-cjk-char cjk-char boxes)) (setq boxes (ekp--flush-cjk-char cjk-char boxes))
(when cjk-char (setq prev-state 2)) (when cjk-char (setq prev-state 2))
(setq cjk-char nil) (setq cjk-char nil))
(setq boxes (ekp--flush-latin-word latin-word boxes)) (setq boxes (ekp--flush-latin-word latin-word boxes))
(when latin-word (setq prev-state 1)) (when latin-word (setq prev-state 1))
(setq latin-word nil) (setq latin-word nil)

BIN
ekp-utils.elc Normal file

Binary file not shown.