refactor: Linus-style code quality improvements across Elisp and C modules

- Extract shared binary search from duplicate ekp--hyphenate-p/ekp--flagged-p
- Remove duplicate ekp-root-dir definition (shadowed ekp-utils.el version)
- Remove dead ekp-clear-caches from ekp-utils.el (cleared non-existent var)
- Replace hardcoded 'zsh' with shell-file-name for portability
- Deduplicate module reload: shared ekp--module-reload for Rust/C
- Add NULL guard for ekp_global in ekp_paragraph.c (crash prevention)
- Track actual thread count in pool to fix destroy joining wrong count
- Normalize line endings (CRLF→LF) in ekp-utils.el

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-02-10 12:18:00 +00:00
parent ddfa26fafc
commit af855e3394
5 changed files with 398 additions and 410 deletions

View File

@ -1,379 +1,375 @@
;;; ekp-utils.el --- Utility functions for EKP -*- lexical-binding: t; -*- ;;; ekp-utils.el --- Utility functions for EKP -*- lexical-binding: t; -*-
;; Copyright (C) 2024 ;; Copyright (C) 2024
;; Author: emacs-kp contributors ;; Author: emacs-kp contributors
;;; Commentary: ;;; Commentary:
;; Utilities for the Emacs Knuth-Plass (EKP) typesetting package. ;; Utilities for the Emacs Knuth-Plass (EKP) typesetting package.
;;; Code: ;;; Code:
(defconst ekp-utils--load-file (or load-file-name (buffer-file-name)) (defconst ekp-utils--load-file (or load-file-name (buffer-file-name))
"Path to this file, for locating module directories.") "Path to this file, for locating module directories.")
(defun ekp-root-dir () (defun ekp-root-dir ()
"Return directory containing ekp files." "Return directory containing ekp files."
(when ekp-utils--load-file (when ekp-utils--load-file
(file-name-directory ekp-utils--load-file))) (file-name-directory ekp-utils--load-file)))
;;;; Font Detection ;;;; Font Detection
(defsubst ekp-cjk-char-p (char) (defsubst ekp-cjk-char-p (char)
"Return non-nil if CHAR is a CJK character." "Return non-nil if CHAR is a CJK character."
(let ((entry (aref (category-table) char))) (let ((entry (aref (category-table) char)))
;; Use describe-categories for a full list of categories. ;; Use describe-categories for a full list of categories.
;; Another way is to use char-script-table (see ;; Another way is to use char-script-table (see
;; script-representative-chars for possible scripts), which is ;; script-representative-chars for possible scripts), which is
;; not as convenient. ;; not as convenient.
(or (aref entry ?c) ; Chinese (or (aref entry ?c) ; Chinese
(aref entry ?h) ; Korean (aref entry ?h) ; Korean
(aref entry ?j) ; Japanese (aref entry ?j) ; Japanese
))) )))
(defun ekp-font-family (string &optional position) (defun ekp-font-family (string &optional position)
(format "%s" (font-get (font-at (or position 0) nil string) :family))) (format "%s" (font-get (font-at (or position 0) nil string) :family)))
(defun ekp-font-monospace-p (font-family) (defun ekp-font-monospace-p (font-family)
(let* ((font (find-font (font-spec :family font-family))) (let* ((font (find-font (font-spec :family font-family)))
(font-name (font-xlfd-name font)) (font-name (font-xlfd-name font))
(type (nth 10 (split-string font-name "-" t)))) (type (nth 10 (split-string font-name "-" t))))
;; 'c' used in terminal ;; 'c' used in terminal
(or (or (string= "m" type) (string= "c" type)) (or (or (string= "m" type) (string= "c" type))
(let ((info (font-info font-name))) (let ((info (font-info font-name)))
(and info (> (length info) 4) (and info (> (length info) 4)
;; 等宽字体的核心标志: 最大宽度等于平均宽度 ;; 等宽字体的核心标志: 最大宽度等于平均宽度
(= (aref info 7) (aref info 11))))))) (= (aref info 7) (aref info 11)))))))
(defun ekp-get-latin-letter (string) (defun ekp-get-latin-letter (string)
(with-temp-buffer (with-temp-buffer
(insert string) (insert string)
(goto-char (point-min)) (goto-char (point-min))
(while (and (< (point) (point-max)) (while (and (< (point) (point-max))
(let ((char (char-after))) (let ((char (char-after)))
(not (or (and (>= char ?a) (<= char ?z)) (not (or (and (>= char ?a) (<= char ?z))
(and (>= char ?A) (<= char ?Z)))))) (and (>= char ?A) (<= char ?Z))))))
(forward-char 1)) (forward-char 1))
(unless (eobp) (unless (eobp)
(buffer-substring (point) (1+ (point)))))) (buffer-substring (point) (1+ (point))))))
(defun ekp-get-cjk-letter (string) (defun ekp-get-cjk-letter (string)
(with-temp-buffer (with-temp-buffer
(insert string) (insert string)
(goto-char (point-min)) (goto-char (point-min))
(while (and (< (point) (point-max)) (while (and (< (point) (point-max))
(let* ((char (char-after)) (let* ((char (char-after))
(width (char-width char))) (width (char-width char)))
(or (or (= 1 width) (= 0 width)) (or (or (= 1 width) (= 0 width))
(not (ekp-cjk-char-p char))))) (not (ekp-cjk-char-p char)))))
(forward-char 1)) (forward-char 1))
(unless (eobp) (unless (eobp)
(buffer-substring (point) (1+ (point)))))) (buffer-substring (point) (1+ (point))))))
(defun ekp-monospace-p (string) (defun ekp-monospace-p (string)
"判断字符串中的拉丁字母的字体是否等宽,返回字体名称" "判断字符串中的拉丁字母的字体是否等宽,返回字体名称"
(if-let* ((letter (ekp-get-latin-letter string)) (if-let* ((letter (ekp-get-latin-letter string))
(font-family (ekp-font-family letter))) (font-family (ekp-font-family letter)))
;; return monospace font family ;; return monospace font family
(when (ekp-font-monospace-p font-family) (when (ekp-font-monospace-p font-family)
font-family) font-family)
;; no latin letter in string, use default ;; no latin letter in string, use default
(face-attribute 'default :family))) (face-attribute 'default :family)))
(defun ekp-word-spacing-pixel (string) (defun ekp-word-spacing-pixel (string)
;; font is monospace, use the pixel of blank ;; font is monospace, use the pixel of blank
;; as word spacing pixel ;; as word spacing pixel
(if-let ((font-family (ekp-monospace-p string))) (if-let ((font-family (ekp-monospace-p string)))
(string-pixel-width (string-pixel-width
(propertize " " 'face `(:family ,font-family))) (propertize " " 'face `(:family ,font-family)))
(let* ((letter (ekp-get-latin-letter string)) (let* ((letter (ekp-get-latin-letter string))
(font-family (ekp-font-family letter))) (font-family (ekp-font-family letter)))
(string-pixel-width (string-pixel-width
(propertize (propertize
" " 'face `(:family ,font-family)))))) " " 'face `(:family ,font-family))))))
(defun ekp-latin-font (string) (defun ekp-latin-font (string)
(if-let ((letter (ekp-get-latin-letter string))) (if-let ((letter (ekp-get-latin-letter string)))
(ekp-font-family letter) (ekp-font-family letter)
(face-attribute 'default :family))) (face-attribute 'default :family)))
(defun ekp-cjk-font (string) (defun ekp-cjk-font (string)
(if-let ((letter (ekp-get-cjk-letter string))) (if-let ((letter (ekp-get-cjk-letter string)))
(ekp-font-family letter) (ekp-font-family letter)
(ekp-font-family ""))) (ekp-font-family "")))
(defun ekp-pixel-spacing (pixel) (defun ekp-pixel-spacing (pixel)
"Return a pixel spacing with a PIXEL pixel width." "Return a pixel spacing with a PIXEL pixel width."
(if (= pixel 0) (if (= pixel 0)
"" ""
(propertize " " 'display `(space :width (,pixel))))) (propertize " " 'display `(space :width (,pixel)))))
(defun ekp-cjk-fw-punct-p (str) (defun ekp-cjk-fw-punct-p (str)
"Return if CHAR is CJK full-width punctuation." "Return if CHAR is CJK full-width punctuation."
(let ((char (seq-first str))) (let ((char (seq-first str)))
(or (equal (char-syntax char) ?.) (or (equal (char-syntax char) ?.)
(and (>= char #x3000) (<= char #x303F)) (and (>= char #x3000) (<= char #x303F))
(and (>= char #xFF00) (<= char #xFF60))))) (and (>= char #xFF00) (<= char #xFF60)))))
(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))
(defun ekp--flush-cjk-char (char boxes) (defun ekp--flush-cjk-char (char boxes)
"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) (defun ekp--flush-spaces (spaces boxes prev-state next-width)
"Push SPACES to BOXES based on context. "Push SPACES to BOXES based on context.
PREV-STATE: 1=latin, 2=CJK (previous content type). PREV-STATE: 1=latin, 2=CJK (previous content type).
NEXT-WIDTH: width of next character (1=latin, 2=CJK). NEXT-WIDTH: width of next character (1=latin, 2=CJK).
Rules: Rules:
- Leading spaces (boxes is nil): preserve all spaces - Leading spaces (boxes is nil): preserve all spaces
- CJK involved (prev or next is CJK): preserve all spaces - CJK involved (prev or next is CJK): preserve all spaces
- Latin-Latin with single space: let glue handle it - Latin-Latin with single space: let glue handle it
- Latin-Latin with multiple spaces: preserve all but last" - Latin-Latin with multiple spaces: preserve all but last"
(when (and spaces (not (string-empty-p spaces))) (when (and spaces (not (string-empty-p spaces)))
(let ((cjk-involved (or (= prev-state 2) (= next-width 2)))) (let ((cjk-involved (or (= prev-state 2) (= next-width 2))))
(cond (cond
;; Leading spaces (no previous boxes): preserve all ;; Leading spaces (no previous boxes): preserve all
((null boxes) ((null boxes)
(setq boxes (cons spaces boxes))) (setq boxes (cons spaces boxes)))
;; CJK involved: preserve all spaces ;; CJK involved: preserve all spaces
(cjk-involved (cjk-involved
(setq boxes (cons spaces boxes))) (setq boxes (cons spaces boxes)))
;; Latin-Latin with multiple spaces: preserve all but last ;; Latin-Latin with multiple spaces: preserve all but last
((> (length spaces) 1) ((> (length spaces) 1)
(setq boxes (cons (substring spaces 0 -1) boxes))) (setq boxes (cons (substring spaces 0 -1) boxes)))
;; Latin-Latin with single space: let glue handle it ;; Latin-Latin with single space: let glue handle it
(t nil)))) (t nil))))
boxes) boxes)
(defun ekp--flush-trailing-spaces (spaces boxes) (defun ekp--flush-trailing-spaces (spaces boxes)
"Push all trailing SPACES to BOXES (for end of string)." "Push all trailing SPACES to BOXES (for end of string)."
(if (and spaces (not (string-empty-p spaces))) (if (and spaces (not (string-empty-p spaces)))
(cons spaces boxes) (cons spaces boxes)
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)."
(if (= state 1) (if (= state 1)
;; 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)))) (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))) (list 2 nil nil (cons str (ekp--flush-latin-word latin-word boxes)))
;; Already in CJK mode ;; Already in CJK mode
(if (ekp-cjk-fw-punct-p str) (if (ekp-cjk-fw-punct-p str)
;; Punctuation attaches to previous CJK char ;; Punctuation attaches to previous CJK char
(list 2 nil nil (cons (concat cjk-char str) boxes)) (list 2 nil nil (cons (concat cjk-char str) boxes))
;; Regular CJK char: flush previous, hold current ;; Regular CJK char: flush previous, hold current
(list 2 nil str (ekp--flush-cjk-char cjk-char boxes))))) (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.
Latin words become single boxes; CJK chars are individual boxes. Latin words become single boxes; CJK chars are individual boxes.
Whitespace runs are preserved as separate 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 (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 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 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)) (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)
(setq spaces (concat spaces str))) (setq spaces (concat spaces str)))
;; Non-whitespace: flush spaces first, then handle char ;; Non-whitespace: flush spaces first, then handle char
(t (t
(setq boxes (ekp--flush-spaces spaces boxes prev-state width)) (setq boxes (ekp--flush-spaces spaces boxes prev-state width))
(setq spaces nil) (setq spaces nil)
(cond (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)
(ekp--handle-latin-char (ekp--handle-latin-char
str state latin-word cjk-char boxes))) 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)))
;; CJK character (width = 2) ;; CJK character (width = 2)
((= 2 width) ((= 2 width)
(pcase-let ((`(,s ,lw ,cc ,bx) (pcase-let ((`(,s ,lw ,cc ,bx)
(ekp--handle-cjk-char (ekp--handle-cjk-char
str state latin-word cjk-char boxes))) 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)) (setq boxes (ekp--flush-trailing-spaces spaces boxes))
(vconcat (nreverse boxes)))))) (vconcat (nreverse boxes))))))
(defun ekp-clear-caches () ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(interactive)
(setq ekp-caches (defun ekp-start-process-with-callback
(make-hash-table (process-name command-args callback
:test 'equal :size 100 :rehash-size 1.5 :weakness nil))) &optional output-buffer)
"执行命令(带参数)并在完成后调用回调"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let* ((buffer-name (generate-new-buffer-name
(or output-buffer "*EKP Process Output*")))
(defun ekp-start-process-with-callback (process (apply #'start-process process-name
(process-name command-args callback buffer-name command-args)))
&optional output-buffer) (set-process-sentinel
"执行命令(带参数)并在完成后调用回调" process
(let* ((buffer-name (generate-new-buffer-name `(lambda (proc event)
(or output-buffer "*EKP Process Output*"))) (if (string-match-p "finished" event)
(process (apply #'start-process process-name (when (memq (process-status proc) '(exit signal))
buffer-name command-args))) (unwind-protect
(set-process-sentinel (funcall ',callback proc (process-buffer proc))
process (when (buffer-live-p (process-buffer proc))
`(lambda (proc event) (kill-buffer (process-buffer proc)))))
(if (string-match-p "finished" event) (message "%s, please check %s" (string-trim event)
(when (memq (process-status proc) '(exit signal)) ,buffer-name))))
(unwind-protect process))
(funcall ',callback proc (process-buffer proc))
(when (buffer-live-p (process-buffer proc)) (defun ekp--module-reload (module)
(kill-buffer (process-buffer proc))))) "Load MODULE from a temp copy to allow rebuilding."
(message "%s, please check %s" (string-trim event) (let ((tmpfile (make-temp-file
,buffer-name)))) (file-name-nondirectory module))))
process)) (copy-file module tmpfile t)
(module-load tmpfile)))
(defun ekp-rust-module-reload (module)
(let ((tmpfile (make-temp-file ;;; Rust Module Support (currently unused — ekp_rust/ directory does not exist)
(file-name-nondirectory module))))
(copy-file module tmpfile t) (defun ekp-rust-module-reload (module)
(module-load tmpfile))) (ekp--module-reload module))
(defun ekp-module-dir () (defun ekp-module-dir ()
(when-let ((root-dir (ekp-root-dir))) (when-let ((root-dir (ekp-root-dir)))
(expand-file-name "ekp_rust" root-dir))) (expand-file-name "ekp_rust" root-dir)))
(defun ekp-module-file () (defun ekp-module-file ()
(when-let* ((module-dir (ekp-module-dir)) (when-let* ((module-dir (ekp-module-dir))
(filename (cond ((eq system-type 'darwin) "libekp.dylib") (filename (cond ((eq system-type 'darwin) "libekp.dylib")
((eq system-type 'windows-nt) "ekp.dll") ((eq system-type 'windows-nt) "ekp.dll")
(t "libekp.so")))) (t "libekp.so"))))
(expand-file-name (concat "target/release/" filename) module-dir))) (expand-file-name (concat "target/release/" filename) module-dir)))
(defun ekp-module-load () (defun ekp-module-load ()
"Load rust module of ekp." "Load rust module of ekp."
(if (executable-find "cargo") (if (executable-find "cargo")
(let ((file (ekp-module-file))) (let ((file (ekp-module-file)))
(if file (if file
(ekp-rust-module-reload file) (ekp--module-reload file)
(ekp-module-build))) (ekp-module-build)))
(error "Please install cargo and add it to executable path!"))) (error "Please install cargo and add it to executable path!")))
(defun ekp-module-build () (defun ekp-module-build ()
"Reload ekp rust module." "Reload ekp rust module."
(interactive) (interactive)
(if (executable-find "cargo") (if (executable-find "cargo")
(ekp-start-process-with-callback (ekp-start-process-with-callback
"ekp-build" "ekp-build"
(cond (cond
((eq system-type 'windows-nt) ((eq system-type 'windows-nt)
`("cmd.exe" "/c" ,(format "cd %s && cargo build -r" `("cmd.exe" "/c" ,(format "cd %s && cargo build -r"
(ekp-module-dir)))) (ekp-module-dir))))
(t `("zsh" "-c" ,(format "cd %s && cargo build -r" (t `(,shell-file-name "-c" ,(format "cd %s && cargo build -r"
(ekp-module-dir))))) (ekp-module-dir)))))
(lambda (proc buffer) (lambda (proc buffer)
(ekp-rust-module-reload (ekp-module-file)) (ekp--module-reload (ekp-module-file))
(message "ekp rust module reload success!"))) (message "ekp rust module reload success!")))
(error "Please install cargo and add it to executable path!"))) (error "Please install cargo and add it to executable path!")))
;;; C Module Support ;;; C Module Support
;; Parallel C implementation using pthreads ;; Parallel C implementation using pthreads
(defvar ekp-c-module-loaded nil (defvar ekp-c-module-loaded nil
"Non-nil if C module is loaded.") "Non-nil if C module is loaded.")
(defvar ekp-c-hyphenator-index nil (defvar ekp-c-hyphenator-index nil
"Index of the loaded hyphenator in C module.") "Index of the loaded hyphenator in C module.")
(defun ekp-c-module-dir () (defun ekp-c-module-dir ()
"Return the C module directory." "Return the C module directory."
(when-let ((root-dir (ekp-root-dir))) (when-let ((root-dir (ekp-root-dir)))
(expand-file-name "ekp_c" root-dir))) (expand-file-name "ekp_c" root-dir)))
(defun ekp-c-module-file () (defun ekp-c-module-file ()
"Return path to compiled C module." "Return path to compiled C module."
(when-let* ((module-dir (ekp-c-module-dir)) (when-let* ((module-dir (ekp-c-module-dir))
(filename (cond ((eq system-type 'darwin) "ekp.dylib") (filename (cond ((eq system-type 'darwin) "ekp.dylib")
((eq system-type 'windows-nt) "ekp.dll") ((eq system-type 'windows-nt) "ekp.dll")
(t "ekp.so")))) (t "ekp.so"))))
(expand-file-name filename module-dir))) (expand-file-name filename module-dir)))
(defun ekp-c-module-reload (module) (defalias 'ekp-c-module-reload #'ekp--module-reload
"Load MODULE from a temp copy to allow rebuilding." "Load MODULE from a temp copy to allow rebuilding.")
(let ((tmpfile (make-temp-file
(file-name-nondirectory module)))) (defun ekp-c-module-load ()
(copy-file module tmpfile t) "Load EKP C module if available."
(module-load tmpfile))) (interactive)
(let ((file (ekp-c-module-file)))
(defun ekp-c-module-load () (if (and file (file-exists-p file))
"Load EKP C module if available." (progn
(interactive) (ekp-c-module-reload file)
(let ((file (ekp-c-module-file))) (when (fboundp 'ekp-c-init)
(if (and file (file-exists-p file)) (ekp-c-init)
(progn (setq ekp-c-module-loaded t)
(ekp-c-module-reload file) (message "ekp-c module loaded (version %s, %d threads)"
(when (fboundp 'ekp-c-init) (ekp-c-version) (ekp-c-thread-count))))
(ekp-c-init) (message "C module not found. Run 'make' in ekp_c/ directory."))))
(setq ekp-c-module-loaded t)
(message "ekp-c module loaded (version %s, %d threads)" (defun ekp-c-load-dictionary (lang)
(ekp-c-version) (ekp-c-thread-count)))) "Load hyphenation dictionary for LANG into C module."
(message "C module not found. Run 'make' in ekp_c/ directory.")))) (when ekp-c-module-loaded
(let* ((root-dir (ekp-root-dir))
(defun ekp-c-load-dictionary (lang) (dict-file (expand-file-name
"Load hyphenation dictionary for LANG into C module." (format "dictionaries/hyph_%s.dic" lang)
(when ekp-c-module-loaded root-dir)))
(let* ((root-dir (ekp-root-dir)) (when (file-exists-p dict-file)
(dict-file (expand-file-name (setq ekp-c-hyphenator-index
(format "dictionaries/hyph_%s.dic" lang) (ekp-c-load-hyphenator dict-file))
root-dir))) (when ekp-c-hyphenator-index
(when (file-exists-p dict-file) (message "Loaded hyphenator for %s (index %d)"
(setq ekp-c-hyphenator-index lang ekp-c-hyphenator-index))))))
(ekp-c-load-hyphenator dict-file))
(when ekp-c-hyphenator-index (defun ekp-c-module-build ()
(message "Loaded hyphenator for %s (index %d)" "Build the C module using make."
lang ekp-c-hyphenator-index)))))) (interactive)
(let ((module-dir (ekp-c-module-dir)))
(defun ekp-c-module-build () (if (and module-dir (file-exists-p
"Build the C module using make." (expand-file-name "Makefile" module-dir)))
(interactive) (ekp-start-process-with-callback
(let ((module-dir (ekp-c-module-dir))) "ekp-c-build"
(if (and module-dir (file-exists-p (cond
(expand-file-name "Makefile" module-dir))) ((eq system-type 'windows-nt)
(ekp-start-process-with-callback `("cmd.exe" "/c" ,(format "cd %s && make" module-dir)))
"ekp-c-build" (t `(,shell-file-name "-c" ,(format "cd %s && make" module-dir))))
(cond (lambda (proc buffer)
((eq system-type 'windows-nt) (ekp-c-module-load)
`("cmd.exe" "/c" ,(format "cd %s && make" module-dir))) (message "ekp C module build success!")))
(t `("zsh" "-c" ,(format "cd %s && make" module-dir)))) (error "Makefile not found in ekp_c/ directory"))))
(lambda (proc buffer)
(ekp-c-module-load) (provide 'ekp-utils)
(message "ekp C module build success!")))
(error "Makefile not found in ekp_c/ directory")))) ;;; ekp-utils.el ends here
(provide 'ekp-utils)
;;; ekp-utils.el ends here

42
ekp.el
View File

@ -124,11 +124,7 @@ Used for explicit line breaks in poetry, code blocks, etc.")
"Internal flag for parameter initialization.") "Internal flag for parameter initialization.")
;;;; Initialization ;;;; Initialization
;; ekp-root-dir is provided by ekp-utils.el
(defun ekp-root-dir ()
"Return directory containing ekp.el."
(when ekp--load-file
(file-name-directory ekp--load-file)))
(defun ekp--load-dicts () (defun ekp--load-dicts ()
"Load hyphenation dictionaries." "Load hyphenation dictionaries."
@ -530,35 +526,25 @@ Returns (:badness NUM :fitness NUM :gaps LIST :adjustment NUM :flexibility NUM).
:adjustment adjustment :adjustment adjustment
:flexibility flexibility))) :flexibility flexibility)))
(defun ekp--hyphenate-p (hyphen-positions n) (defun ekp--sorted-vector-member-p (vec n)
"Return non-nil if position N ends with hyphenation. "Return non-nil if N exists in sorted vector VEC.
HYPHEN-POSITIONS is a sorted vector of indices where hyphenation can occur.
Uses binary search for O(log n) lookup instead of O(n) linear search."
(and hyphen-positions
(> (length hyphen-positions) 0)
(let ((lo 0)
(hi (1- (length hyphen-positions))))
(while (< lo hi)
(let ((mid (/ (+ lo hi) 2)))
(if (< (aref hyphen-positions mid) n)
(setq lo (1+ mid))
(setq hi mid))))
(= (aref hyphen-positions lo) n))))
(defun ekp--flagged-p (flagged-positions n)
"Return non-nil if position N is a flagged (forced) break.
FLAGGED-POSITIONS is a sorted vector of indices where forced breaks occur.
Uses binary search for O(log n) lookup." Uses binary search for O(log n) lookup."
(and flagged-positions (and vec
(> (length flagged-positions) 0) (> (length vec) 0)
(let ((lo 0) (let ((lo 0)
(hi (1- (length flagged-positions)))) (hi (1- (length vec))))
(while (< lo hi) (while (< lo hi)
(let ((mid (/ (+ lo hi) 2))) (let ((mid (/ (+ lo hi) 2)))
(if (< (aref flagged-positions mid) n) (if (< (aref vec mid) n)
(setq lo (1+ mid)) (setq lo (1+ mid))
(setq hi mid)))) (setq hi mid))))
(= (aref flagged-positions lo) n)))) (= (aref vec lo) n))))
(defalias 'ekp--hyphenate-p #'ekp--sorted-vector-member-p
"Return non-nil if position N ends with hyphenation.")
(defalias 'ekp--flagged-p #'ekp--sorted-vector-member-p
"Return non-nil if position N is a flagged (forced) break.")
;;;; Dynamic Programming Line Breaking ;;;; Dynamic Programming Line Breaking

View File

@ -155,6 +155,7 @@ typedef struct {
*/ */
typedef struct { typedef struct {
pthread_t threads[EKP_THREAD_POOL_SIZE]; pthread_t threads[EKP_THREAD_POOL_SIZE];
size_t thread_count;
pthread_mutex_t queue_lock; pthread_mutex_t queue_lock;
pthread_cond_t queue_cond; pthread_cond_t queue_cond;
pthread_cond_t done_cond; pthread_cond_t done_cond;

View File

@ -311,6 +311,11 @@ ekp_paragraph_t *ekp_para_create(const char *text, size_t len,
return NULL; return NULL;
} }
if (!ekp_global) {
ekp_para_destroy(p);
return NULL;
}
ekp_spacing_t *sp = &ekp_global->spacing; ekp_spacing_t *sp = &ekp_global->spacing;
for (size_t i = 0; i < final_count; i++) { for (size_t i = 0; i < final_count; i++) {

View File

@ -92,6 +92,7 @@ ekp_thread_pool_t *ekp_pool_create(size_t num_threads)
} }
} }
pool->thread_count = num_threads;
return pool; return pool;
} }
@ -105,9 +106,8 @@ void ekp_pool_destroy(ekp_thread_pool_t *pool)
pthread_cond_broadcast(&pool->queue_cond); pthread_cond_broadcast(&pool->queue_cond);
pthread_mutex_unlock(&pool->queue_lock); pthread_mutex_unlock(&pool->queue_lock);
for (size_t i = 0; i < EKP_THREAD_POOL_SIZE; i++) { for (size_t i = 0; i < pool->thread_count; i++) {
if (pool->threads[i]) pthread_join(pool->threads[i], NULL);
pthread_join(pool->threads[i], NULL);
} }
pthread_mutex_destroy(&pool->queue_lock); pthread_mutex_destroy(&pool->queue_lock);