Merge pull request #10 from Kinneyzhang/copilot/fix-issue-9

fix: enforce kinsoku rules for CJK opening punctuation (Fixes #9)
This commit is contained in:
Geekinney 2026-05-25 12:18:47 +08:00 committed by GitHub
commit 11437cb029
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 10 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ archive
*.dylib *.dylib
*.dll *.dll
*.o *.o
*.elc

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))
(setq boxes (ekp--flush-cjk-char cjk-char boxes)) ;; Don't flush opening punct - keep it held for attachment to next char
(when cjk-char (setq prev-state 2)) (if (and cjk-char (ekp-cjk-opening-punct-p cjk-char))
(setq cjk-char nil) nil ; keep cjk-char as-is
(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))
(when latin-word (setq prev-state 1)) (when latin-word (setq prev-state 1))
(setq latin-word nil) (setq latin-word nil)