聚珍(Juzhen)/cjk-autospace 變更紀錄。← 回文檔
效能第二/三層:掃描/佈局提速 + 增量恢復(Tier3) 2026-07-12
承第一層(S2+S3)。本批含掃描/佈局提速與**增量恢復**(動態頁面局部變化免全樹重掃),全程獨立複驗+4 份真人撰寫真實文檔驗收。單元測試 130→140 全綠。
掃描 / 佈局提速
- 祖先游走記憶化(中間發現+S4):
insidePill/isAvoided之「逐文本節點沿祖先鏈」以迭代回填記憶化(每元素恰算一次、兄弟共用),O(文本×depth)→O(元素)。掃描層el.matches()累計約降五成;深嵌套 render 進程隔離墻鐘 pre-Tier1 707ms→254ms(2.8×)。 - S1 gapTrim 對齊門:尾隨 margin 僅在右緣 flush 之對齊可見,唯
left/start(ltr) 之 ragged 右緣可整批跳過,省其 O(gaps) 次同步重排。非兩端對齊內容 gapTrim 由數千 ms 降至數十 ms;justify/center/right/RTL 保留完整算法、行為不變(真瀏覽器差分:trim 集指紋逐位相同)。
增量恢復(第三層,向下兼容 API)
- C1 持久化 Finder:跨 render 復用元素鍵快取。正確性靠失效——新增
invalidate(el)清整棵子樹快取(祖先屬性變則後代快取變脏),revert自動失效其子樹。 - C3
rerender(root)=revert+render(局部恢復,實測編輯 400 段中一段 766×、matches 1441×)。 - C2
observe/disconnect:MutationObserver 自動增量——內容/分類屬性變化重處理最近塊、新增之塊直接 render;render/revert 期間暫停觀察,自身注入絕不自觸發。 - append 修復(獨立差分複驗 IV-3 發現、單測漏掉):向葉塊(帶
jz-orphan)追加子塊使其轉非葉,須撤容器陳舊 orphan。
獨立複驗 + 真實文檔驗收
- 5 種獨立方法(差分/往返/增量≡全量/進程隔離測速/真瀏覽器 S1),皆與既有單測/基準不同源——抓到並修復 append 真 bug。
- 4 份真人撰寫真實文檔(繁體技術/簡體評論/文學對話/混排):非空白字元多重集守恆、無字元改/增/漏/亂序,
revert淨移除結構、真瀏覽器整條管線零報錯。
文檔(I1/I6 收緊)
- 驗收浮現:
render→revert不還原spacing依契約規範化之贅餘作者空格(在 Neovim 中→在Neovim中)——固有、非本批引入。I1 明示此為刻意常態例外(語義不變、確刪空格且不還原);I6 明示「邏輯等價≠逐字節」之唯一差異即被規範化之空格。README 補「revert之範圍」注。
效能第一層:掃描提速(category 記憶化 + orphan 葉塊預計算) 2026-07-11
下游效能訴求(掃描疑 O(n²))之第一層——無狀態、保行為、無公開 API 變更之確定收益。單元測試 129→130 全綠、輸出不變。
優化
- S3 category 記憶化:
finder.category以WeakMap<Element, InlineCategory>記憶化,砍除collectRuns/jiyaAdjacency/jinze/split跨 pass 對同一元素之重複el.matches()(pill/isolate/avoid 選擇器)。實測 pill/isolate 密集頁面el.matches()8204→5842(約降三成,命中率 55%)。安全:單次 render 內一元素之分類恆定、且 Finder 隨makeCtx每次 render 重建 → 快取僅存活單次 render、無跨 render 失效(持久化屬另議)。 - S2 orphan 葉塊預計算:以一趟上攀預計算「非葉塊」Set 取代逐塊
block.querySelector(sel)掃整棵子樹之 O(Σ塊子樹)(深塊嵌套下 O(n²));某塊為另一塊之最近塊祖先 ⟺ 含塊級後代,與原 querySelector 同口徑、行為等價。
亲驗與待議
- 亲驗:暫禁 category 快取則
computeCategory由 1041 增至 2322、el.matches()由 5842 增至 8204——證快取真減負而非空操作(初次插桩誤打HTMLBodyElement.prototype而漏計,改補Element.prototype.matches後為準)。 - 新發現(下一機會):剩餘
el.matches()之最大來源為insidePill之「逐文本節點沿祖先鏈pillMatches」(eachTextNode每文本節點一趟、未記憶化)。
splitTreeAfter 攀升對稱守衛(opaque 中間祖先不再被克隆肢解) 2026-07-11
下游 .selection-bar-message 病例:垂懸字(orphan)之段末實義字落於 opaque <button>(巢於可切 <span>)時,splitElementAt 僅驗頂層 span 可切;splitTreeAfter 逐層克隆「文本節點→top」祖先鏈時,中間 opaque button 從未複查即被克隆肢解(複製出 splitoff button、破壞互動元素)。firstText/lastText 守下潛(遇非可切回 null),攀升卻無對稱守衛——此即真正病根,下游 per-instance 迴避僅治標。
修復
- 攀升守衛:
splitTreeAfter切分前先驗「textNode.parentNode…top(含 top)」整條祖先鏈皆canSplit,任一層 opaque/pill/isolate/帶 id 即放棄、回退整綁(退化但安全,與下潛守衛對稱)。純新增守衛、無新克隆邏輯;jinze 之isolateBoundaryToken因下潛已守,行為不變。附帶亦杜絕中間祖先帶id之克隆重複 id。 - 不變量:
canSplit須對「切點 textNode 至 top 之整條祖先鏈」成立、非僅 top——下潛(firstText/lastText)+攀升(splitTreeAfter守衛)雙向守。 - 單元測試 127→129 全綠(+2):末實義字在 opaque button(巢於可切 span)→ 綁整 span、無
splitoff、button 完整;退化路徑 render→revert 還原一致。亲驗——暫禁守衛則該測試於「無 splitoff」斷言失敗(證明非空通過、病根屬實)。
逐元素標點樣式覆寫(data-jz-style/stylePolicy) 2026-07-11
下游需求:非段落/特定範圍元素用半角式(banjiao),段落用全角/開明。原標點樣式僅由 lang 決定(lang.policy/lang.style),無逐元素覆寫。新增與 data-jz-level/level.text 同構之逐元素樣式機制(banjiao 渲染早已就緒、此為純路由,無新壓縮邏輯/CSS)。單元測試 121→127 全綠。
設置格式
- HTML 屬性:
data-jz-style="banjiao|kaiming|quanjiao"——標於元素或任一祖先,該子樹用此樣式。 - 選項:
lang: { stylePolicy: [{ selector, style }] }——如[{ selector: "h1, h2, button, .ui", style: "banjiao" }]。 - 優先序:逐元素覆寫(近祖先勝、同層屬性優先於選擇器)>
lang.policy>lang.style> 內建逐語言預設。屬性值非法(拼錯)則忽略、回退 lang。
專案整理:架構一致性 + 確認缺陷修復 + 文檔重整 2026-07-07
四路並行審計(core/typeset+CSS/設置 API/文檔)後之兩階段整理。核心:消除「透明穿越」判定之兩套口徑分裂,並修四個審計缺陷中之確認項(另一證偽、一延後)。單元測試 116→121 全綠。
階段一 · 確認缺陷修復 + 清理
- jinze 冪等:二次 render(未 revert)曾產生巢狀
jz-jinze(違反 I6,實測 3→6)——守衛改判isJz(parent, PASS)。 - pill 自身 avoid 漏檢:
processPills補avoidsSelf——自身命中 avoid(如<code data-jz-skip>)之 pill 曾仍被補間隙。連帶:CODE/KBD自skipTags預設移除(已由pillSelector以 pill 涵蓋;再列 skipTags 違反三分類互斥、且會抹掉普通code之 pill 兩端間隙)。若把code移出pillSelector,須自行加入skipTags/scope.avoid。 - 刪零引用死代碼(
createMarker/ANY_CJK/BD_ALL/BD_CLASS_NAMES/CreateOptions.attrs);死旗標biaodian/emphasis/ruby補@reserved註記。
階段二 · 架構一致性
- 透明判定單一真相源:
transparentInline提升為公開isTransparent,邏輯文本流之遍歷原語(collectRuns/adjacentLogicalChar/jiyaAdjacency)共用之。未在styleInlines之未知行內標籤(如<button>)採嚴格白名單、一律當邊界(用戶抉擇;透明須顯式登記)。分層澄清:結構切分安全canSplit、行端物理版面lineedge各有正當之不同邊界、刻意不併入。 - 跨透明元素邊界避頭尾(修 #3):避頭類標點落於
<strong>邊緣(你好<strong>。世界</strong>之句末點號位於 strong 首位)時,forbiddenBreak經edgeCharEl穿越透明元素下探定位、render 沿透明邊緣上攀處理外層 parent,不再漏綁、誤置行首。 - avoid 排除歸一:
isAvoided改單一組合入口(自身或祖先),免除「avoidsSelf ‖ isAvoided」易漏一半之誤用(階段一 pill 漏檢即此類)。 - 小項:
scope.include型別去多餘| null;gapTrim補逐節點功能閘;PASSES陣列補註執行序。 - 元素分類單一入口
finder.category(收尾):散落各遍歷原語各自短路之分類優先序,收斂為單一分類器——一元素恰屬 avoid/pill/isolate/block/transparent/opaque 之一,優先序avoid > pill > isolate > block > transparent > opaque(帶data-jz-skip之 pill → avoid;同命中 pill 與 isolate → pill)。isTransparent=category==="transparent";collectRuns/jiyaAdjacency改switch(category);canSplit=SPLITTABLE ∩ transparent。行為等價(121 全綠)。
復驗後重新定性(不照單全收)
- lineedge 攀穿 isolate——非缺陷:
edgeRect為物理版面測量(getClientRects),isolate 斷的是邏輯 run/相鄰,物理行首行尾判定須跨<mark>找真實相鄰內容;初評誤判、已還原。 - lineEdge/gapTrim 雙 ResizeObserver 派發序——確認非缺陷:Chrome headless 實測回調按 observer 創建順序派發(ResizeObserver 規範遍歷 observers 插入序,非 observe 序;實測 seq=12);
lineEdge(order 90)之 observer 先於gapTrim(order 92)建、故 resize 時恆先派發,gapTrim之行末去隙恆基於已套用之半形版面(非陳舊態)。以註解鎖為不變量、不需改碼。 - 五處幂等收斂——評估後不做:各檢查不同位置關係(祖先/父/子),無單一
isJz形狀可全覆蓋,強行歸一徒增風險。
文檔(本次)
- README 新增「全部選項速查」表——突出被埋沒之
lang.style/styleInlines/blockTags/skipTags(標籤名陣列)/skipAttribute/isSkipped;三分類改分層表述、avoid 改七源+組合入口。 - ARCHITECTURE §3.1
scope.avoid過時示例、§4.1 透明分層與 avoid 七源、§4.2 標點六子類(舊列bd-cop/bd-sep/quote與實作不符)修正。
標點擠壓句界例外(句末點號接開類保留右空白) 2026-06-13
行內連續擠壓(機制 A)新增句界例外:句末點號(。.!?)後接開類引號/括號(。「/?「)時保留句末點號右側空白、僅收開類左側,全角式與開明式皆合占 1.5em(對稱於 。」)——下游回饋之語感修正。單元測試 113→116 全綠。
變更
- 句界例外:原機制 A 對相鄰標點對
(L, R)兩側獨立收——L有右側空白即收、不看 R 為何類,致句末點號接開類兩側皆收、合占 1em,丟失句界視覺(句末點號收尾後緊接開類起新引文,本應保留句界間距)。修為:L為句末點號(bd-stop)且R為開類(bd-open)時不收L、僅收R左側 → 1.5em。 - 例外僅限句末點號+開類:句內點號
,「(屬同句)與雙括號」「/》《(貼合相連)不屬句界,仍兩側皆收 1em;。」(句末接閉類,閉類左為墨無左空白)本就只收句末點號、合占 1.5em,未變。 - 半角式不回歸:半角式句末點號由 baseline(機制 C)已收半形,句界守衛僅跳過 adjacency 之重複追加、不移除 baseline 之
jz-half;32 個可壓縮標點於半角式仍全半形(實測 0 回歸)。非可壓縮·・‧/…—各式皆全寬,屬設計。
範圍
- 唯一改動點
jiyaAdjacencyPass.squeeze()(src/typeset/jiya.ts)加stopBeforeOpen守衛;行端擠壓lineedge、baseline 政策、jinze/orphan/spacing皆不波及(純視覺寬度、無結構變更)。
行內元素三分類(KaTeX 肢解修復)+ v1 相容層移除 2026-06-12
下游問題六(KaTeX 公式被禁則綁定肢解)修復:確立行內元素三分類口徑(透明/隔離/整體),所有遍歷原語共用同一判定。v1 相容層隨最後一批消費端遷移完成而移除。單元測試 101→112 全綠。
修復(下游問題六)
- Bug:jinze 透明下潛肢解 pill。行內公式緊鄰禁則標點(
…$x$,)時,jinze 為綁鄰字下潛.katex——KaTeX 全樹皆<span>,舊版僅憑標籤名判透明,把公式內側末字連同完整殼層逐層克隆切出(splitoff),上標漂移錯位;pillSelector/skipAttribute/scope.avoid三層防護全被刺穿。復現另揭兩向量:相鄰兩 pill 時descend刺穿致jz-hws插入公式內部;pill 內部標點被 jiya charify。 - 根因一:判透明以標籤名而非元素。
transparentInline(el)改以元素判定——先排除 pill/isolate/avoid 再查標籤集;core/split之canSplit諮詢 finder(pill/isolate/avoid 一律不可切 → 退化整元素綁定——pill 即「作為原子鄰字單位整體綁入jz-jinze」之期望行為)。 - 根因二:avoid 僅查祖先。新增
avoidsSelf(el)自身判定——元素自身命中scope.avoid/skipAttribute/data-jz-skip時即為邊界(舊版被當透明穿越,防護形同虛設)。 - 根因三:descend 越界跳找。
adjacentLogicalChar之descend改三態(邊界字/無內容續找兄弟/blocked 終止)——遇 pill/isolate/avoid 不可跳過續找更遠之字。 - pill 內部全面不可見:
eachTextNode過濾insidePill——pill 內部不 charify、不插間隙、不做長詞包裝(舊版消費端自加之 pill 如.katex內部曾被直接變更)。
新增:行內元素三分類(§4.1)
- 透明(
styleInlines+jz-*):穿越、run 延續、禁則可克隆切分——strong/em等純樣式。 - 隔離(
isolateSelector,新增選項,預設"mark",""停用):邊界斷 run/斷相鄰/不可切分、兩端補隙(同 pill),內部照常處理(自成脈絡)——帶背景色之<mark>等盒樣式行內(切分背景盒有可見接縫,MARK並自可切白名單移除)。 - 整體(
pillSelector):內部對一切 pass 不可見,禁則時整體綁入——code/kbd、行內公式.katex、引用標.cite-ref等宜納入。 - spacing Pass B 邊界選擇器擴為 pill ∪ isolate;jiyaAdjacency 對 isolate 斷相鄰而內部自成脈絡;orphan 補 avoid 塊不綁(舊漏判)。
移除:v1 相容層
- 最後一批 v1 消費端遷移 v2 API 完成,
src/compat/v1.ts(createCjkAutospace)、IIFE 全域別名、死選項autospaceClass(經查從未被任何 pass 讀取)一併移除;不變量 I8 退役。v1 原始碼保存於v1-autospace分支。
行內排版透明性修復 + 標點懸掛移除 2026-06-11
修復兩個下游 Bug(同源於「行內元素對排版透明」原則在各遍歷原語口徑不一)+三項同根隱患審計;並把上一輪「停用」之標點懸掛正式移除(負 margin 程式碼/CSS/型別一併刪)。單元測試 90→101 全綠。
修復
- Bug:pill 前標點誤加間隙(如
漢字。<code>)。根因:finder.adjacentLogicalChar之descend遇不透明之jz-char既不進入亦不終止、而跳過它越過標點取到更前之漢字,誤補jz-hws。修復=isTransparentInline補JZ-CHAR/JZ-INNER,descend遂回傳標點(非 pill 鄰字、不補隙),與collectRuns之既有透明遞迴一致。 - Bug:行內樣式元素整體掉行(
<strong>很長一串</strong>,把整串粗體塞進nowrapinline-block,行末放不下即整體掉行、上行留巨大空隙)。新增core/split.ts之克隆切分:沿祖先鏈把邊界詞元剝入淺克隆(保留標籤/class/style/lang、去 id;標data-jz-kind="splitoff"),禁則/垂懸字只綁該克隆(最小綁定),原元素餘部留原位、渲染無縫。revertPass增延後階段把 splitoff 併回原元素(先解包 wrap、後併 splitoff),render→revert還原一致。純樣式標籤白名單可切,a/abbr/ruby/帶 id 退化整綁。 - 審計(同根隱患):T4
lineedge之行端攀爬集改由options.finder.styleInlines派生(棄硬編碼子集,免遺漏ins/del/sub等致行端誤判);T5jiyaAdjacency遇 pill 斷相鄰、不穿越;T6spacingprocessPills 補 per-node 功能閘(data-juzhen-off="spacing"區域內不補隙)。
標點懸掛:實作後停用 → 正式移除
純 CSS 手段(負 margin/相對位移/零寬盒)經實測為機制級死路,已移除(負 margin 之 lineedge 程式碼/jz-hang-* CSS/LineEndMode/lineEnd/features.hanging 一併刪)。行端僅餘半形擠壓(jz-half-le)。
- 寬度守恆論證:行端標點盒若保持非零寬,則墨色出去多少、版心內就空出多少(行末實義字無法齊版心右緣);若收為零寬,則釋放一整字寬、斷行器把下一字拉上本行與標點重疊(且
justifyAtoms預設之 inline-block 下負 margin 被 justify 吸收)。CSS hack 接觸不到斷行器,此恒等式無解。 - 唯一正解=原生
hanging-punctuation,惟 Chrome/Firefox 至今不支援(僅 Safari),且jz-char為 inline-block 原子非文本、原生懸掛不作用之。未來若做須是繞開 jz-char 的「原生管線」新功能(text-spacing-trim+hanging-punctuation,按能力門控),與已移除碼無共享,故不保留休眠碼。 hanging公開選項保留為 @deprecated no-op(API 相容;測試覆蓋 option/data-juzhen/blocks各路徑皆不產生jz-hang-*且輸出與不傳一致)。詳見ARCHITECTURE §6.5.6。
功能分級 + 垂懸字(標點懸掛停用) 2026-06-09
ARCHITECTURE §6.5:功能分級、垂懸字(方案 C)落地工作良好;原計劃之標點懸掛經實測確認不可行、全面停用。共 90 單元測試全綠(含懸掛防逃逸覆蓋)。
新增
- 功能分級(文本級 vs 段落級):把已實作功能切為兩級——文本級(中西間隙
spacing、標點擠壓jiya,任意文本片段適用)與段落級(禁則jinze、垂懸字orphan、長詞longWord,需行/段布局)。塊級元素預設全功能;data-jz-level="text"子樹(最近祖先勝)或選項level:{text:"選擇器"}標記之元素(標題/按鈕/UI)僅跑文本級。實作為Finder.levelFor/levelAllows+ 各 passlevel元數據;段落級 pass(含 charify 之 longWord)逐元素受閘。spacing補單元素 root 支援(行內/非塊 root 亦處理)。 - 垂懸字避免
orphan(方案 C,斷行層無測量,order 70):段末行 CJK 實義字 ≥ N(預設 2;標點/符號不計)。把段末「第 N 個實義字起至段末」(含尾隨標點)包入jz-orphan(white-space:nowrap),整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型成立、無需量測。邊界字落於行內元素內時綁定整個塊級直接子(跨jz-jinze攀爬、不切割)。預設開 chars=2(orphan:false關);段落級,data-jz-level="text"跳過。 - 行端模式重構 + 行歸屬判定 bug 修正:lineedge 行端處理抽象為可配模式(
squeeze/hang/none),現有行端半形=squeeze、保留並改善。修「行歸屬判定」bug:jz-char為 inline-block,其行盒top與相鄰純文本字形top因基線差約 5px、遠小於行高卻超舊容差(EPS=4px),致同行誤判為下一行;改判「垂直區間」(下一行=b.top ≥ a.bottom、上一行=b.bottom ≤ a.top,容 2px)。
標點懸掛:實作後全面停用
原計劃之標點懸掛(行端標點凸出版心)經 headless + 真實瀏覽器實測確認純負 margin 機制不可行,已全面停用(hanging 選項仍被接受但無任何效果)。
- 不可行原因:① 句號墨色偏字框左下角,安全凸出量(< 1 個 CJK 字寬 1em)只把空白半框推出版心、墨色仍在內(視覺不懸掛);② 凸出滿 1em 釋放一整字寬、把下一字拉上本行致重疊;③ 與
inline-block對齊原子互斥(justifyAtoms:true下負 margin 被 justify 吸收);④ResizeObserver重排時兩階段標記錯亂。 - 三重防逃逸:①
index鎖lineEnd:"squeeze"(忽略hanging選項);②modeForChar無條件降 squeeze;③applyEdge之HANG_ENABLED=false總開關。縱使下游以hanging/data-juzhen/blocks任何方式要求亦不啟用(單元測試覆蓋各路徑皆不產生jz-hang-*)。 - 未來路徑:原生 CSS
hanging-punctuation為正解,待 Chrome 支援(現僅 Safari)並解決與 inline-block 原子衝突。hang 之程式碼、CSS、型別保留休眠。詳見ARCHITECTURE §6.5.6。
工具
demo/showcase.html(新增):定寬文本框並排對照,逐場景演示功能分級與垂懸字(關閉/開啟孤字、chars=3、標點不計入 N)。demo/resize-check.html:增垂懸字(jz-orphan描框 + 關閉對照)拖曳檢查區。兩頁均不含懸掛(已停用)。
標點挤压一致性 2026-06-04
新增/修復
- 全角句點
.納入句末標點:BD_STOP由。!?擴為。.!?——納入全角句點.(U+FF0E,異於表意句號。U+3002)。實測 Noto CJK SC/TC 之halt皆覆蓋、挤压收半形 advance 0.5em,與。一致;inkBias同步補(簡 left/繁 center)。政策行為同。!?:全角式與開明式句末保持全角 1em,banjiao(或鄰接被收)挤半。設計原則:halt即字型之「挤压意願」,字型不提供某標點halt(字形占滿、不宜挤)則信任字型、保持全角不強挤。 - 連續標點挤压規則:雙側獨立 + 透明穿越(修正既有不一致)。規則:相鄰兩標點,對每一標點獨立判斷——若其「有空白之側」緊鄰另一標點即收半。原為「二選一只收一側」,致
,「全角式誤為 1.5em;修後,「全角式與開明式皆 1em、。」全角式 1.5em/開明式 1em、·—兩式皆 2em(不宜挤)。並改「同 parent 直接兄弟」掃描為從 root 之透明遍歷(穿越strong/em等行內邊界、block/avoid 斷相鄰),使跨 inline 邊界之連續標點亦正確配對(原漏致達 2em)。
字符分類規則集 2026-06-04
新增
- 可配置字符分類(
charClass):把散落各 pass 之硬編碼分類(中文 CJK/西文 ANS/全寬標點 BD_* 子類)統一抽象為規則集,內置默認即現狀值,消費端經options.charClass({ cjk?, western?, biaodian?:{stop,pause,open,close,middle,liga} })追加擴充,覆寫spacing/jiya/jinze之分類判定。動機:未分類符號(如二點省略‥U+2025)——不 charify/不挤;以charClass:{biaodian:{stop:"‥"}}即可歸入對應類。向下兼容:不傳則行為與內置完全一致、各實例隔離(覆寫不污染默認)。v1 為追加語義(不重分類/移除內置);結構性 ASCII 不開放。spacing之間隙正則改由規則集 per-instance 編譯。測試 66 通過(含 9 條 charClass)。
下游回饋修復批次 2026-06-03
聚焦 Peek.nvim 等下游回饋之原子透明與中西間隙正確性,皆以真實 Chromium 量測驗證(jsdom 之 TreeWalker/offset 失真,不可信)。
修復
- 原子透明(段落繼承屬性漏溢):
jz-*inline-block 原子對消費端段落樣式透明——歸零會被原子重複套用之text-indent/hanging-punctuation(過套用),並令原子text-decoration: inherit以接續段落下劃線(欠套用)。段落首行縮排正常、下劃線連續不斷,消費端無需配置。 - 中西間隙 ·
gapTrim自我應驗:行末去隙原依「後字是否在更低行」判定,而後字落點受間隙自身(及下游間隙)之margin推擠,致行中間隙被誤 trim——表現為容器寬相關之「最後一個間隙消失」、行內codepill 跨行時右側間隙消失。改為「起始全部 trim、由左至右逐一定案」,量測不受任何margin污染。 - 中西間隙 · 西文字集完整化:補入貨幣符號(
€等 U+20A0–20BF)、拉丁擴充 A/B(拼音聲調 ā ǎ ē)、西里爾、數學運算符,並把希臘補全至含 α-ω。鄰接 CJK 之手工空格遂正規化、邊界補隙。列舉法以\uXXXX表示、無需 regex flag,相容 2016/Windows 10 時代瀏覽器。 - 標點誤隙:全角省略號 …… 後接 CJK 不再誤補隙(
DOTS_CJK移除|…);收尾彎引號 ” 後接拉丁/數字不再補隙(移除 panguQUOTE_AN偏例,與 」』)》】〕 等所有收尾標點一致)。全部常用中文標點 × 簡繁共 104 例稽核,CJK 間皆 0 誤隙。
變更
- 字型移出聚珍管理:字型屬消費端領域,聚珍不再內建任何
font-family/字型堆疊(移除_lang.css,dist/juzhen.css零字型規則)。逐語言字型完全由消費端 CSS 設定(範式見文檔「字型與 locl」)。聚珍僅守三項非管理性本分:jz-* { font: inherit }使原子繼承消費端字型、不強加;半形font-feature-settings顯式併入"locl" 1(連同halt)確保泛 CJK 字型之地域標點字形不因設 halt 而失;render 只讀不改lang。halt/margin 兩模式皆已驗證字型繼承與 locl 正常。
工具
demo/resize-check.html:可拖曳容器檢查頁——拖動方框寬度,驗證中西間隙在各寬度下恆穩、標點無誤隙;可切justify與「顯示間隙」描邊。
v2 切片 1 · 增補 2026-06
切片 1 之上的修補與強化,聚焦標點半形對缺 halt 字型之支援,及下游回饋之邊界/分頁器修復。
新增
- 標點半形
margin後備模式(缺 OpenTypehalt之字型):jiya.halfWidth: "halt"(預設)|"margin"。margin以width:0.5em+字身依墨色偏側溢出定位,於 CSS 盒模型重現halt之半形 advance(真 0.5em;相鄰標點各 0.5em、合佔 1em,無負 margin 之鄰接疊加/單側重疊)。模式由data-jz-halfwidth子樹屬性決定,可逐區塊/逐lang(含 halt-island 反置)。 - 字形偏側表
inkBias:依字身墨色中心給left/center/right/full;彎引號/角括號/尖括號偏一側、圓方括號近中。參考表據方正書宋 GBK 實測,可經jiya.bias逐lang逐字覆寫。 - 字型適配工具與工作流:
tools/measure-bias.py(量 glyph ink 中心、產偏側建議表)+文檔之「字型適配工作流」。demo/fallback.html:方正書宋 GBK 對照 halt 失效 vs margin 生效。 justifyAtoms選項:預設true;設false時jz-char/jz-jinze改display:inline,相容分頁器(Paged.js)之對齊引擎(halt半形與禁則nowrap保留)。
修復
- 字型不覆寫消費端:
_lang.css之 Noto 字型堆疊改為純後備——全包:where()零特異度、只命中帶lang屬性之元素、不直接覆寫標點字面容器jz-inner。消費端font-family恆優先,標點隨之以消費端字型渲染(修正先前標點被強制換成 Noto 之缺陷)。 - pill 緊鄰標點之左緣間隙:
adjacentLogicalChar透明穿越jz-jinze(同jz-hws/樣式行內)→ pill 後接 CJK 標點時,左緣jz-hws與作者空格剝除復原。 - CJK↔CJK 贅餘作者空格:含跨 inline 標籤者一律剝除、不留隙,與 CJK↔西文之邊界正規化一致(全形空格 U+3000 不動)。
已知限制
margin後備半形依賴inline-block(width:0.5em),與justifyAtoms:false不可兼得——分頁器場景請用含halt之字型。margin偏側為三檔量化(0/0.25/0.5em)之近似;字形極端者須以jiya.bias逐字微調。
v2.0.0 切片 1 · 從零重寫
依 ARCHITECTURE.md 契約,從零重寫為「現代簡/繁中文網頁排版套件」。倉庫名 cjk-autospace 不變;公開 API 為 createJuzhen /全域 Juzhen;自訂元素前綴 jz-。v1 完整保留於 v1-autospace 分支。
新增
- 核心引擎:
finder(遍歷/avoid/scope.include/邏輯 run/pill 鄰字/分塊閘featureEnabledFor)、unicode(字元表+標點子類+字形側位)、locale(語言分類+全角/開明政策)、dom(jz-*工廠+可還原標記)、pass聚合管線。 - 中西間隙(margin 模型):包裹邊界左側字、以
margin-right留隙,不注入空白字元 →textContent完全不變、複製緊湊、justify不暴增、不撐框、行首乾淨。gapTrimlayout pass 行末去隙以齊右。 - 標點擠壓:全角式/開明式/半角式,三機制(baseline 政策+行內連續擠壓+行端擠壓)。半形以字型 OpenType
halt渲染。全域lang.style+逐langpolicy。 - 禁則(避頭尾):閉類/點號綁前一字、開類綁後一字,以詞元為邊界、長引文中段可斷;兼作
justify綁定。 - 長英文詞:
<span lang>+hyphens原生斷詞。 - justify 句末單份間距:
jz-charinline-block 原子化。 - v1 相容層:
createCjkAutospace().apply()映射;IIFE 全域別名。 - 構建/測試:esbuild(ESM/IIFE/CSS)+ tsc(d.ts);jsdom 測試;
demo/簡繁並列+justify+標點寬度量測。
相對 v1 之改進
- 中西間隙複製乾淨(
textContent不變;v1 之<span>真實空格會被選取)。 - 擠壓由「
halt套整段=半角式」進化為依lang之全角/開明/半角,且加入行內連續與行端擠壓。 - 新增禁則避頭尾、justify 句末單份間距、跨瀏覽器一致策略與顯式禁用層。
v1(cjk-autospace) · v1-autospace 分支
Pangu.js v7.2.1 風格之 CJK↔Latin 邊界自動間距 shim,工廠 createCjkAutospace(options).apply(root)。
- Pass A:跨樣式邊界中西間隙(marker span 含真實空格 +
user-select:none)。 - Pass B:pill(
code/kbd)邊界間隙。 - Pass C:長英文詞
<span lang>+hyphens。 - Pass D:開明式標點擠壓(句內點號
font-feature-settings:"halt"半形)。