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