Commit Graph

24 Commits

Author SHA1 Message Date
Kinneyzhang
6a8c7e04f5 fix: harden post-audit contracts and test gates 2026-08-20 07:34:03 +08:00
Kinneyzhang
e823d89a4a fix: restore TeX final-pass reachability 2026-08-02 15:30:12 +08:00
Kinneyzhang
665164911f perf: make live structural commits incremental
Reuse prepared paragraph data, safe Elisp DP states, common layout lines, and dirty-island source reconstruction for exact append commits. Simplify C int32 validation and add the frozen task030 evaluator, regressions, and decision records.
2026-07-30 08:24:54 +08:00
Kinneyzhang
fd1210e557 fix: close repository audit findings
Complete task002 through task016 across cache correctness, editor lifecycle, C boundaries, build and release governance, dictionary provenance, performance, interactive workflows, tests, documentation, and final cleanup.
2026-07-28 22:43:10 +08:00
Kinneyzhang
74a780ce95 refactor!: remove the experimental C tokenization path; harden the module
The self-contained C path (ekp-c-break-lines, ekp-c-load-hyphenator,
ekp-c-hyphenate, ekp-c-set-spacing, ekp_paragraph.c, ekp_hyphen.c,
~1500 lines) was never used by ekp.el, diverged semantically from the
real pipeline (no kinsoku, no protrusion, no two-pass emergency), and
contained an exploitable heap overflow reachable from Lisp:
ekp_para_create sized its box array as box_count * 2, but a long word
hyphenates into arbitrarily many syllable boxes, overflowing the
calloc'd buffer.  Deleting the path deletes the bug class.

Hardening of the live path:

- thread pool: sized from the machine's core count instead of a
  hardcoded 8; created lazily on the first multi-paragraph batch
  (single-paragraph users never start worker threads); a full queue
  now blocks the submitter until a worker makes room — tasks were
  silently dropped before, degrading the batch to the Elisp fallback
  exactly when parallelism mattered most.
- unified failure gate: a partial allocation used to silently drop
  kinsoku, hyphenation or protrusion data and continue with a subtly
  different layout; any allocation failure or pending Lisp signal
  (non-local exit from a bad element type) now fails the whole call,
  and ekp.el falls back to the Elisp engine.  The Elisp bridge wraps
  both C entry points in condition-case, and a whole-batch nil no
  longer crashes the per-paragraph loop.
- integer safety: every extracted pixel value is clamped to int32
  instead of silently wrapping.
- EKP_INFINITY (the unreachable-state sentinel) is now a real
  infinity: extremely degenerate paragraphs could legitimately
  accumulate demerits past the old 1e10 constant, making C consider
  reachable states dead and diverge from the Elisp engine.

BREAKING: the four experimental module functions are gone; rebuild
with make -C ekp_c clean all (version gate unchanged at 1.5).

92 ERT green; fuzz 300/300 byte-identical across engines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 01:49:25 +08:00
Kinneyzhang
3fc05c1405 feat: typography wave — dictionary HYPHENMIN, JIS kinsoku, fast indent
Three quality gaps closed, one of them unlocking the C engine for
the most common Chinese configuration:

- first-line indent now runs on the 1D DP and the C engine (module
  1.5: ekp-c-break-with-arrays grows a 15th arg FIRST-LINE-WIDTH).
  A line starts at box 0 exactly when i = 0, so no (position x
  line-count) state is needed; only true parshape and looseness
  still take the 2D Elisp path.  Indented Chinese sample, w=400:
  C engine 82 ms (this configuration previously bypassed C entirely
  and ran the heaviest Elisp DP).  The C-result reconstruction
  mirrors the per-line width, and an equivalence test pins
  indent == the equivalent parshape across engines.
- LEFTHYPHENMIN / RIGHTHYPHENMIN are parsed from dictionaries and
  applied (en_US declares 2/3; the hardcoded 2/2 allowed breaks
  like "gen-cy" that the dictionary forbids).  Explicit overrides
  still win; partial overrides keep the dictionary's other side.
- JIS X 4051 line-start prohibition for small kana, the prolonged
  sound mark and iteration marks (new defcustom
  ekp-cjk-no-line-start-extra, char-table backed): っょー々 can no
  longer start a line.  Pure break-permission change; spacing and
  the DP are untouched.

Also fixed in passing:
- ekp--split-with-hyphen extracted regexp groups AFTER resolving
  the hyphenator; dictionary compilation clobbers the match data
  (latent since the resolver was hoisted; exposed by the HYPHENMIN
  parser) — groups are captured first now.
- ekp--first-indent-pixel goes through the width cache; it runs
  once per rendered line and was re-measuring the reference glyph
  every time (C path was 397 ms before, 82 ms after).

4 new ERT tests (92 total); fuzz 300/300 against C 1.5.
C module must be rebuilt: make -C ekp_c clean all

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 01:36:52 +08:00
Kinneyzhang
9dcaeb0ba3 chore: licensing, customize support, and packaging hygiene
- add COPYING (GPL-3.0-or-later) and license headers to all elisp
  and C sources; real author/maintainer info replaces placeholders
- convert user-facing options to defcustom under new group `ekp'
  (spacing internals managed by ekp-param-set stay defvars)
- autoload user commands: ekp-param-reset, ekp-clear-caches,
  ekp-c-module-load, ekp-c-module-build
- ekp--load-dicts: a missing dictionaries/ directory now only
  disables hyphenation instead of breaking (require 'ekp);
  ekp--split-with-hyphen degrades gracefully (and resolves the
  hyphenator once per call instead of once per word)
- package summary no longer redundantly says "for Emacs"
- Makefile: drop personal Windows EMACS_ROOT default; add guidance
- ekp_c/README.md: fix stale 1.1 version references (module is 1.4)
- DEVELOPER*.md: remove archive/ from file map (not in the repo)
- remove unreferenced 10 MB demo GIF

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 00:52:37 +08:00
Kinneyzhang
f6aa64b3b1 feat: right-edge protrusion — hanging punctuation (C 1.4)
ekp-protrusion enables microtype-style character protrusion at the
right edge: a line ending in fullwidth punctuation (。、」…) hangs
the whitespace half of the glyph past the flush edge — visually
identical to CLREQ line-end punctuation compression — and latin
periods/commas/quotes and soft hyphens protrude by their configured
ratios (ekp-protrusion-ratios).

Mechanics: per-box tail protrusion, folded through trailing space
boxes into a per-gap array; each DP candidate widens its effective
target (lw = width + release), mirrored exactly in the renderer's
per-line effective width and in the C-result reconstruction, so DP,
rendering and both engines stay coherent.  Left-edge protrusion is
documented as out of scope (Emacs cannot render before the line
origin).  ekp-region reserves the protrusion width in auto layouts
(ekp-region-protrusion-reserve).

Off by default; all-zero arrays make it a no-op on the shared path.
C module 1.4: break-with-arrays 12→14 args (tail-protrudes array,
hyphen-protrude scalar), batch vectors 14 elements.

Tests: 59 ERT green (deterministic hang check, flush-when-off,
protrusion C parity); fuzz 300/300.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 21:32:54 +08:00
Kinneyzhang
57a3abe853 feat: alignment modes — ragged-right, ragged-left, center (C 1.3)
ekp-alignment selects the paragraph alignment. Non-justify modes keep
inter-word glue rigid (arrays and class params zeroed at para build)
while the DP gains a per-line flexibility ekp-ragged-stretch-pixel
(default ≈2 em): badness = 100·(shortfall/R)³, so Knuth-Plass still
globally minimizes raggedness — this is \raggedright with a finite
\rightskip stretch, not first-fit.

Rendering distributes each line's leftover by mode: trailing
(ragged-right), split evenly (center), leading (ragged-left); the
spacers are ekp-glue-marked, so buffer roundtrips stay exact.

C module 1.3: set-penalties gains an extra-stretch scalar (reset when
omitted); max_w widens by it, so the prefix-based flexibility picks it
up automatically. Cache keys include alignment and R.

Tests: 56 ERT green incl. per-mode invariants (fit, rigid interior
glue, flush edges, centered leftover ±1px) and four-mode C parity;
fuzz 300/300.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 21:22:19 +08:00
Kinneyzhang
64eb2f38d5 refactor!: per-gap break permissions — punctuation as first-class boxes
Kinsoku moves from merge-based box attachment to a general break-
permission layer, the foundation for no-break spans, NBSP, verbatim
atoms and punctuation protrusion:

- tokenizer: every CJK char (punctuation included) is its own box;
  the hold/attach machinery is gone.  Fixes two latent bugs: a second
  consecutive closer (字。」) could start a line, and an opener held
  across spaces reordered box content vs the original string
- ekp--str-type: cjk-punct splits into cjk-open (Ps/Pi) / cjk-close
- ekp-para gains breaks-allowed (bool-vector) + forbidden-positions
  (sparse, for C); glue at unbreakable punct gaps is nws, so
  punctuation hugs its content and justification stretch no longer
  opens gaps at 「x or x。
- halfwidth kinsoku: boxes consisting purely of .,;:!?)]}’”»›… may
  not start a line ((&[{‘“«‹ may not end one) — an ASCII comma after
  a CJK char no longer dangles at line start (old behavior violated
  CLREQ)
- DP (elisp 1D, elisp loose 2D, C): forbidden gaps are skipped as
  candidates while the line keeps extending; the emergency fallback
  generalizes from single boxes to atomic runs (no permitted break
  inside), recording real gap counts so multi-box emergency lines
  render correctly
- C module 1.2: ekp-c-break-with-arrays 11→12 args
  (forbidden-positions), batch vectors 12 elements, version gate bumped

Hyphenation improvement: 「Hello / Hello」 previously failed the
word regexp as merged boxes and were never hyphenated; as separate
boxes they hyphenate normally.

Tests: 48 ERT green (split tests migrated to the new box contract;
new unit test for break permissions and a rendered-output kinsoku
sweep across widths); 300-case fuzz 0 failures with C 1.2 parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 21:05:50 +08:00
Kinneyzhang
9afe491e1a chore: silence build warnings on non-macOS toolchains
- ekp_hyphen.c: check fgets return when skipping the dictionary
  encoding line (gcc -Wunused-result via glibc fortify); behavior
  unchanged — an empty file already fell through to EOF in the loops
- ekp-utils.el: declare-function for font-info, which does not exist
  in non-window-system builds (emacs-nox); call site is runtime-guarded
  by display-multi-font-p. Keeps byte-compile-error-on-warn green.

Verified: clean gcc build + 36/36 ERT + 300-case fuzz on Ubuntu 24.04
(Emacs 29.3, arm64), clean clang build + 36/36 ERT on macOS (30.2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 19:53:39 +08:00
Kinneyzhang
112b3a0e52 refactor!: overhaul KP core — correctness, C parity, performance, tests, docs
- fix: ekp-param-set silently reset after first justify (now persists;
  ekp-param-reset added)
- fix: narrow-width CJK returned empty string (data loss); two-pass
  emergency-break strategy guarantees output for any input
- fix: K-P penalties never synced to C module; space-box metrics
  divergence between C and Elisp engines
- fix: para cache ignored ekp-latin-lang (stale hyphenation after
  language switch) and used collision-prone sxhash keys
- fix: fullwidth letters/digits misclassified as CJK punctuation
- fix: combining chars split from their base char in the tokenizer
- fix: punctuation-wrapped words (word!/(word)/word;) never hyphenated
- fix: renderer double-counted stripped space widths; negative glue
  clamped; batch/tty font detection no longer crashes
- feat: real looseness support via (position × line-count) DP
- perf: O(1) line metrics and gap counts via prefix arrays (inner loop
  previously allocated O(n) subsequences → O(n³) total); box measurement
  dedupe; eq fast-path para lookup; prebuilt per-para glue arrays
  → zh justify 7547ms → 96ms (compiled elisp) / 57ms (C);
    range-justify 68.5s → 0.48s / 34ms; C module itself 3–19× faster
- test: 36 batch-safe ERT tests + 300-case property fuzz (C/elisp
  byte-identical output, zero content loss) replacing ad-hoc suite
- docs: readme/readme_zh/DEVELOPER/DEVELOPER_ZH/ekp_c-README rewritten
  to match the implementation; phase handoff in .phrase/phases/

BREAKING: requires Emacs 29.1+; C module must be rebuilt (v1.1, new
arities); ekp-threshold-factor / ekp-flagged-penalty /
ekp-forced-break-penalty removed; Rust module stubs removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 18:44:02 +08:00
copilot-swe-agent[bot]
af855e3394 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>
2026-02-10 12:18:00 +00:00
Kinneyzhang
262b5eefdc Makefile supports multiple plantform 2026-01-26 11:51:19 +08:00
Kinneyzhang
136e53fe8e improve C module 2026-01-26 01:24:47 +08:00
Kinneyzhang
f42c2bda00 delete unnecessary code 2026-01-25 21:32:34 +08:00
copilot-swe-agent[bot]
a421926e91 Fix C module single-box line handling order to match Elisp
Bug: C module and Elisp produced different results for same text.

Root cause: Different condition checking order for single-box vs last-line:
- Elisp: single-box check comes BEFORE is-last check
- C: is_last check came BEFORE is_single_box check (now fixed)

When the last line has only one box, Elisp uses single-box calculation
while C was using last-line calculation, resulting in different demerits
and different final breakpoints.

Fix: Moved is_single_box check before is_last check in dp_process_position()
to match Elisp's ekp--dp-compute-line-demerits behavior.

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
2026-01-25 12:02:09 +00:00
copilot-swe-agent[bot]
4a84723181 Refactor C module to eliminate duplicate DP logic
Extracted shared DP core algorithm into dp_process_position() function:

1. Added dp_input_t struct - unified input interface for DP algorithm
   - Contains prefix arrays, glue arrays, hyphen info
   - Contains K-P parameters (penalties, ratios)

2. Added dp_is_hyphen() function - unified hyphen position detection
   - Uses O(log n) binary search
   - Replaces is_hyphen_pos() function

3. Added dp_process_position() function - core DP algorithm
   - Processes single start position i, tries all end positions k
   - Contains complete demerits calculation, fitness classification
   - Both entry functions now call this shared core

4. Refactored process_dp_range()
   - Builds temporary glue arrays from ekp_paragraph_t
   - Creates dp_input_t and calls dp_process_position()

5. Refactored ekp_break_with_prefixes()
   - Directly uses input arrays to create dp_input_t
   - Calls dp_process_position() for each position

Benefits:
- Single point of maintenance: DP bugs only need fixing once
- Consistency guaranteed: both paths use identical algorithm
- ~100 lines of duplicate code removed
- Better testability: core algorithm can be tested independently

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
2026-01-25 11:55:27 +00:00
copilot-swe-agent[bot]
b70affb9ef Optimize hyphen position lookup O(n)->O(log n) and add named constants
Performance:
- ekp--hyphenate-p: O(n) cl-find -> O(log n) binary search
- is_hyphen_break: O(n) linear -> O(log n) binary search
- is_hyphen_pos: O(n) linear -> O(log n) binary search

In O(n²) DP loop, this reduces worst-case complexity from O(n³) to O(n² log n)

Code quality:
- Add ekp-consecutive-hyphen-penalty (was hardcoded 100)
- Add ekp-forced-break-penalty (was hardcoded 10000)
- Add ekp-last-line-short-penalty (was hardcoded 50)
- Update ekp-looseness docstring noting partial implementation

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
2026-01-25 11:15:42 +00:00
copilot-swe-agent[bot]
fee05f769d Fix C module to match Elisp behavior for last-line overflow handling
When is_last && ideal > line_width, the C module now correctly
triggers force break logic, matching ekp--dp-cache-elisp behavior.
This ensures consistent results between C module and pure Elisp paths.

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
2026-01-25 10:32:06 +00:00
Kinneyzhang
68ca9b191a improve C module parallel 2026-01-25 17:28:04 +08:00
Kinneyzhang
6cf6f75f71 fix bugs of C moudule 2026-01-25 16:53:23 +08:00
Kinneyzhang
aaeea099c9 fix bug 2026-01-25 15:11:47 +08:00
Kinneyzhang
c88bbdd395 add dynamic moudle to improve prefermance 2026-01-25 13:50:46 +08:00