From 024ad5da93ea437ed20ab5292c7969e9a60d0df2 Mon Sep 17 00:00:00 2001 From: commilitia Date: Fri, 2 Oct 2026 19:29:33 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=90=8C=E6=AD=A5=E7=8F=BE=E8=A1=8C?= =?UTF-8?q?=E5=A5=91=E7=B4=84=E4=B8=A6=E4=BF=9D=E7=95=99=E9=80=90=E5=AD=97?= =?UTF-8?q?=E5=8D=8A=E5=BD=A2=E5=BE=8C=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 137 +++++++++++++++++++++++++++++------------------- README.md | 39 ++++++++------ UPDATES.md | 8 ++- 3 files changed, 113 insertions(+), 71 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c6bb1e6..d42b1a8 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -51,8 +51,9 @@ CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro 屬性);**所有視覺尺寸(margin/letter-spacing/寬度/懸掛位移)一律寫在 `juzhen.css`**。同一段 DOM 在 justify 與否、主題與否下都成立。此分離是擴充性 與跨瀏覽器調校的關鍵。 -- **I3 依 `lang` 行為**:每個 pass 解析節點的有效 `lang`(最近祖先),行為與 - CSS 皆以 `:lang()` 與 locale 政策驅動。全角式/開明式之別即由此決定。 +- **I3 依 `lang` 行為**:JS 解析節點的有效 `lang` 與逐元素樣式政策,決定標點是否半形, + 再以 `jz-half`/`jz-half-le` class 交由 CSS 渲染;CSS 不以 `:lang()` 另判寬度政策。 + 字型地域字形由呼叫端的 `lang` 與字型決定,`options.lang.default` 不會改寫 DOM 的 `lang`。 - **I4 跨瀏覽器一致**:間隙、擠壓等一致性關鍵層只用 `margin`/`letter-spacing`/ `inline-block`/`user-select` 等普遍原語。`text-spacing-trim`/ `hanging-punctuation`/`text-emphasis` 等僅作漸進增強,絕不作為唯一機制。**且當 @@ -61,12 +62,12 @@ CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro `text-spacing-trim` 不設定),使三引擎行為收斂於同一路徑(見 §9.1)。 - **I5 justify 單份間距**:間隙與擠壓的結構設計,須保證每個標點在 `text-align: justify` 下只貢獻**一份**對齊伸縮量(見 §8)。 -- **I6 冪等與可還原**:每個 pass 對其產物打標記,重跑跳過;提供 `revert` **乾淨移除聚珍注入之 - 全部結構**(`jz-*` 元素、class、`data-jz-*` 屬性;wrap→解包還原原內容、marker→移除、 - splitoff→併回原元素),DOM 回到 pass 前狀態之**邏輯等價**態。「邏輯等價」而非逐字節相同之**唯一 - 差異**:`spacing` 規範化之贅餘作者空格不還原(見 I1)——故 `render→revert` 對含 CJK↔西文作者 - 空格之源,還原後語義一致、結構全清,唯該空格已正規化(此差異穩定、不隨 `render`/`revert` 反覆 - 累積)。`revert` 後 DOM 內無任何 `[data-jz]` 殘留(實測驗收成立)。 +- **I6 冪等與可還原**:每個 pass 對產物打標記供定址,私有所有權憑證才決定還原權限。 + `revert` 撤回本實例仍有效的產物(wrap→解包、splitoff→併回)及受控屬性,保留作者與 + 其他實例內容。沒有外部修改時,DOM 回到處理前的**邏輯等價**態;`spacing` 正規化的 + 贅餘作者空格不還原(見 I1),此差異不隨反覆 render/revert 累積。 + 外部克隆、替換、刪除後,按產物及來源完整性檢查安全回退,不復活過時文字; + 不保證恢復歷史快照或清空所有 `[data-jz]`。DOM 標記本身不授予所有權。 - **I7 邊界感知**:跨樣式行內元素的鄰接(如 `是A`)須能識別並 正確處理——由核心 finder 統一提供,各 pass 不自行重複造輪。 - **I8 向下相容 v1(已退役)**:相容層曾保障 v1 消費端(build.sh 單檔串接、Nvim @@ -76,7 +77,8 @@ CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro (見 §3.4),以減少全樹走訪次數;聚合不得犧牲 I6(冪等可還原)與逐功能開關。 - **I10 作用域與分塊**:作用範圍可由 `scope.include` 選擇器限定(opt-in);功能集 可逐塊覆寫(屬性或 `blocks` 映射),由 pass 在 `collect` 內以 per-node 閘解析 - (見 §3.6)。CSS 視覺規則須與 JS 作用域同域收斂,不外溢。 + (見 §3.6)。`scope.include` 只限定 JS 處理;結構樣式直接作用於 `jz-*`,原生間距 + 禁用層固定於 `.juzhen`。呼叫端負責讓容器標記與 JS 作用域對應。 --- @@ -105,6 +107,10 @@ jz.disconnect(); // 停止觀察 同批移出根的節點與已離根的舊記錄則跳過,不以 `isConnected` 代替作用域判斷。 搬入既有塊時,以同批移除來源、私有產物或已知 render 根辨識;先 revert 再 render 撤回舊祖先的語言/級別/功能效果。純新增塊直接 render,但插入子樹的快取先失效。 +手動 `render`/`revert`/`rerender` 在最外層暫停 observer 前,先同步結算 `takeRecords()` +中的待交付變化,可能更新其他區塊;隨後才執行手動操作,局部回退仍最後生效。 +結算舊記錄的錯誤由宿主微任務非同步報告,不成為手動 API 的同步例外;手動操作自己的 +錯誤仍照常同步拋出。 **增量粒度契約(下游說明八)**:增量更新須以**塊**為粒度——換塊內容(`textContent`/ `innerHTML`)、增刪塊、改分類屬性,皆與全量重繪一致(差分複驗逐位元相同)。**不支持**對聚珍 @@ -165,6 +171,7 @@ interface BasePass { interface CharifyPass extends BasePass { kind: "charify"; + prepare?: (ctx: RenderContext) => void; collect(node: Text, ctx: RenderContext): WrapRequest[]; } @@ -176,7 +183,8 @@ interface StandalonePass extends BasePass { type Pass = CharifyPass | StandalonePass; ``` -`CharifyPass` 只收集文本包裝請求;`StandalonePass` 自行執行功能所需的掃描或 layout。 +`CharifyPass` 可先以 `prepare` 處理跨節點完整原子,再收集普通 Text 的包裝請求; +`StandalonePass` 自行執行功能所需的掃描或 layout。 兩類皆由同一註冊表管理,且均按 `level` 與功能閘判定是否生效。 `createJuzhen` 內建一個**有序、可開關**的 pass 註冊表。`render` 依 `order` 執行 @@ -247,8 +255,9 @@ Nvim peek/Claude 模板等 v1 消費端之過渡期。**最後一批 v1 消費 - `scope.include`:**啟用選擇器**(opt-in 模式)。設定後,**僅處理命中該選擇器之 子樹**(如 `".juzhen, [data-juzhen]"`),其餘一概不動——此即「限定作用範圍」。 - `scope.avoid`:排除選擇器,與內建 avoid 合併。 -- **CSS 同域**:`juzhen.css` 之視覺規則一律收斂於同一啟用選擇器(如 `.juzhen` 下), - 與 JS 作用域一致,杜絕樣式外溢(呼應 I2、§9.1 之禁用層亦限於此域)。 +- **CSS 作用域**:`scope.include` 不改寫 CSS。`jz-*` 結構樣式直接按元素/class 生效; + §9.1 的原生間距禁用層固定於 `.juzhen`。若 JS 用 `.prose` 啟用,呼叫端可用 + `class="prose juzhen"` 對齊兩者;作者自行建立的 `jz-*` 也會匹配結構樣式。 **分塊功能選擇**——不同塊啟用不同功能集: @@ -281,12 +290,11 @@ Nvim peek/Claude 模板等 v1 消費端之過渡期。**最後一批 v1 消費 - **scope.include**:若設啟用選擇器,僅走訪命中之子樹(opt-in,§3.6)。 - **featureEnabledFor(node, name)**:解析該節點之有效功能集(§3.6),供各 pass 之 `collect` per-node 閘用;結果以元素為鍵快取。 -- **charify**:把符合條件之字元/字串包進指定 `jz-*` 元素(兩階段:先收集目標、 - 後變更,避免遍歷中改 DOM 失效)。 -- **邊界感知 replace(I7)**:對「跨相鄰文本節點、可跨樣式行內元素」的邏輯字串 - 做正則 find,回呼決定如何包裝/插入標記。樣式行內元素(`strong/em/a/span/…`) - 透明穿越;block/pill/avoid 中斷邏輯串。此即 v1 Pass A「剝離樣式收集邏輯文本 - 再反映射」之一般化,提升為可重用引擎。 +- **文字走訪**:`eachTextNode` 提供受過濾的 Text,charify pass 收集 `WrapRequest`, + 由 `core/pass.ts` 批次包裝;Finder 本身不提供 charify mutation API。 +- **邊界與來源映射(I7)**:`collectRuns` 收集跨透明 Text 的邏輯字串及完整來源跨度, + `adjacentLogicalChar` 查詢完整鄰字。block/pill/avoid/isolate 等按分類中斷脈絡; + 正則匹配與間隙回放由功能模組實作,不是 Finder 的泛用 replace/回呼插入 API。 - **完整 Unicode 原子**:`collectRuns` 將跨透明 Text 的 surrogate pair 先合成完整碼點, 再將 base 與至多一個 variation selector 合為原子;`map.parts` 保留所有來源跨度。 間隙、禁則與避孤字共用此映射,不在原子中間建間隙/斷點、不把 selector 另算實義字。 @@ -385,8 +393,9 @@ avoid」用組合入口 `isAvoided`(=自身或祖先,階段二歸一,免 (`{ cjk?, western?, biaodian? }`)**擴充**,`resolveRuleset(override)` 以**追加**語義合併 為最終規則集,置於 `ResolvedOptions.ruleset`。各 pass 由此取分類: -- `spacing`:`makeRules(ruleset)` 由 `cjk`/`ansAfter`/`ansBefore` **per-instance 編譯** - 全部間隙正則(createJuzhen 各一次,非每 block);`ALNUM`/`ALPHA`/運算符/括號等結構性 +- `spacing`:每次 spacing render 開始時,`makeRules(ruleset)` 按該實例的 + `cjk`/`ansAfter`/`ansBefore` 編譯全部間隙正則,復用於本次各處理塊; + `ALNUM`/`ALPHA`/運算符/括號等結構性 ASCII 集為固定常數、不隨規則集變。 - `jiya`:`classifyBiaodian(ch, ruleset.biaodian)` 決定 charify 與 bd-* 子類。 - `jinze`:`anyCjk = RegExp(ruleset.cjk)` 決定詞元邊界之 CJK 判定。 @@ -409,21 +418,28 @@ avoid」用組合入口 `isAvoided`(=自身或祖先,階段二歸一,免 祖先鏈掃 `data-jz-style` 屬性/`stylePolicy` 選擇器命中,近祖先勝、同層屬性優先於選擇器) > 逐 lang `policy` > 全域 `style` > 內建逐語言預設(`zh-Hant`/`zh-HK` → 全角、`zh-Hans` → 開明)。逐元素覆寫供「非段落/UI 元素用半角、段落用全角/開明」等需求(`data-jz-style` 屬性 - 值非法則忽略、回退 lang)。此純為「按元素選樣式」之路由,`banjiao` 半形渲染沿用 `halt`/ + 值非法時只忽略該信號,仍查同層選擇器與祖先;逐元素信號皆缺失才回退語言政策)。 + 此純為「按元素選樣式」之路由,`banjiao` 半形渲染沿用 `halt`/ `margin`(§6.3),無新增壓縮邏輯。 -CSS 端對應以 `:lang()` 選擇器落實 lang 層政策(I2/I3);逐元素覆寫屬 JS 端 baseline 判定。 +語言與逐元素政策均由 JS 判定並轉為半形 class;CSS 按 class 使用 `halt`/`margin` 渲染。 +`lang.policy` 的鍵是 `zh-Hans`/`zh-Hant`/`ja`/`other` 四個分類,非任意完整語言標籤。 ### 4.4 `dom.ts`——`jz-*` 元素工廠與還原 -`createEl('jz-char', {class, attrs})`、`wrap(node, el)`、`splitAt`、`insertMarker`、 -冪等標記(`data-jz=""`)、`revertPass(name, root)`(移除該 pass 之產物、 -解包 `jz-*`、合併回文本節點並 `normalize()`)。 +`createJz(ownedElements, tag, pass, kind, opts)` 建立元素、登記私有所有權,並寫入 +`data-jz`/`data-jz-kind` 供定址。`setOwnedAttribute` 與 `revertOwnedAttribute`/ +`revertOwnedAttributes` 以共享寫者堆疊管理受控屬性;外部修改或其他實例寫入不被無條件覆蓋。 -`data-jz-kind` 區分產物還原方式:`marker`(純注入,移除)、`wrap`(包裝原內容,解包)、 -**`splitoff`**(行內元素克隆切分之邊界詞元,見 `core/split.ts`/§6.4)。`revertPass` 分 -**兩階段**處理:主迴圈逆序解包 marker/wrap,第二階段再把 splitoff 併回前兄弟(同標籤 -原元素)——順序必要,否則 splitoff 之前兄弟可能仍是 wrap 殼(如 `jz-jinze`)而非原元素。 +`revertPass(pass, root, ownedElements)` 先驗證本實例所有權與合法 pass/kind/tag 組合。 +目前接受 `wrap`(包裝原內容)與 **`splitoff`**(行內元素克隆切分之邊界詞元);不提供 +泛用 `marker` 還原。第一階段逆序解包 wrap,第二階段把 splitoff 併回同標籤前兄弟, +最後 normalize 受影響父節點。順序必要,否則 splitoff 前兄弟可能仍是 wrap 殼。 + +`recordUnwrappedParent`/`resolveUnwrappedParent` 記錄及解析合法產物的受控解包鏈, +供跨節點來源回退使用;重用空殼會使舊位置失效,重定向環不作為文字恢復依據。 +包裝請求由 `core/pass.ts` 執行,安全行內切分位於 `core/split.ts`;本模組沒有 +`createEl`/`wrap`/`splitAt`/`insertMarker` 等泛用變更函數。 --- @@ -584,7 +600,8 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd `halt` 收的是字身寬度、由字型按字形正確定位(簡左/繁中/括號偏側皆對),故 不重疊。`font-feature-settings:"halt"` 各引擎一致(當初疑慮之不一致者為**自動** 的 `text-spacing-trim`/`chws`,非顯式 `halt`);缺 `halt` 之字型優雅降級為全形。 - 鄰接/行端之「收哪一字」之**決策**仍由 `blankSides` 邏輯定,渲染統一為 `halt`。 + 鄰接/行端之「收哪一字」之**決策**仍由 `blankSides` 邏輯定;預設以 `halt` 渲染, + 缺半形特性的字型可採下述逐字 `margin` 後備。 - **句末標點集含全角句點 `.`(下游回饋)**:`BD_STOP` 為 `。.!?`——除表意句號 `。` (U+3002)外,納入**全角句點 `.`**(U+FF0E)。實測 Noto CJK SC/TC 之 `halt`(GPOS SinglePos 調 advance)皆覆蓋 `.`、挤壓收 0.5em,與 `。` 一致;`inkBias` 同步補 `.` @@ -617,7 +634,9 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd 依**字身墨色中心**給出 `left|center|right|full`(簡體點號偏左、繁體居中;彎引號 `“”`/角括號 `「」`/尖括號 `〈〉` 墨強偏一側→開 right、閉 left;圓方書名括號 `()【】《》…` 近中→center),charify 時標為 `jz-ink-{left,center,right}`。消費端可 - 經 `jiya.bias` 逐 lang 逐字覆寫;配字工作流與量測工具 `tools/measure-bias.py` 見 README。 + 經 `jiya.bias` 逐語言分類、逐字覆寫;`full` 不套 margin 的 0.5em 盒寬,保留全形。 + **此逐字字型相容方案仍是當前支援路徑**;配字工作流、JSON 範例與量測工具 + `tools/measure-bias.py` 見 README,不屬已移除的負 letter-spacing/標點懸掛機制。 - **激活與粒度(I3/I10)**:最近有效 `data-jz-halfwidth="halt|margin"` 屬性透過 可繼承 CSS 變數決定模式;`render()` 在全域 margin 時自動掛於 root。可在任意祖先 逐區塊/逐語言指定並交替巢狀,切回 halt 會撤銷 margin 盒寬、內盒及偏移。bias @@ -704,20 +723,24 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd 直覺:文本級=「字符級排版」(間隙、標點形態);段落級=「行/段級排版」(換行、行端、段末)。 **標記 API(下游)**: -- 預設推斷:塊級元素(`blockTags`:p/div/li/td…)→ **段落級(全功能)**。 + +- 級別由自身向上查最近有效信號,不依標籤名推斷;沒有任何信號時預設 **paragraph**。 - 顯式降級:`data-jz-level="text"` 於元素/祖先 → 該子樹**僅文本級**(段落級功能自動關); - `data-jz-level="paragraph"`(或不標)→ 全功能。 + 子元素可用 `data-jz-level="paragraph"` 重新允許段落級功能,但仍受全域功能開關、 + 逐元素功能閘與作用域限制。不標記的子元素繼承祖先信號,不會自動重新啟用全功能。 - 選項 `level?: { text?: string }`:選擇器一鍵標記(如 `level:{ text: "h1,h2,h3,button,.ui" }`)。 - 典型:標題/按鈕/UI 文字標 **text-level**(要間隙與標點半形、不要避孤字/避頭尾); 正文 `

` 預設全功能。 **實現**: + - 每個 pass 加元數據 `level: "text" | "paragraph"`。 -- `finder.levelFor(el)`:查 `data-jz-level` 祖先 + 選項 `level.text` 選擇器 + 預設(塊→段落)。 +- `finder.levelFor(el)`:從自身向上查;每層先取合法 `data-jz-level`,再查 `level.text` + 選擇器,第一個有效信號勝出;皆無才回退 paragraph。非法屬性值繼續查找。 - gate:段落級 pass 對 text-level 元素跳過;文本級 pass 不受限。與現有 `blocks`/ `featureEnabledFor` 疊加(level=粗粒度語義組先判,blocks=細粒度逐功能後調)。 -- **文本級功能須支持非塊/單元素**:`spacing` 現用 `eachBlock`,須確認 scope root 為 inline/ - 單元素時亦能處理(或放寬走訪),使單行標題等之中西間隙、標點半形生效。 +- **文本級功能支持非塊/單元素**:`spacing` 在逐塊處理後,補做未被塊覆蓋的行內 root + 及 `scope.include` 命中的行內子樹,避免重複處理;標點 charify 也可走訪行內 root。 **與本批關係**:分級已落地(pass 標 level、`levelFor`、`data-jz-level`/選項);`orphan` 等段落級 pass 只作用於段落級元素、跳過 text-level 者。`hanging` 未註冊為 pass。 @@ -739,7 +762,7 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd | 機制 | 作用層 | 唯一職責 | 手段 | 狀態 | |---|---|---|---|---| | 禁則(§6.4 jinze) | **斷行層** | 把標點趕到正確的邊(點號/閉類→行末、開類→行首;連續標點不拆) | nowrap `jz-jinze` | 已實作 | -| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt) | 已實作(將併入行端模式) | +| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt/margin) | 已實作 | | 標點懸掛 | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | ~~負 `margin`~~ | ❌ 已移除(機制級死路,見 6.5.6) | | 垂懸字避免 | **斷行層** | 段末行 **CJK 實義字數 ≥ N** | nowrap `jz-orphan` | 已實作 | @@ -761,7 +784,7 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd - **保證(成功建立尾部綁定時,免測 layout)**:該整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型/ 瀏覽器成立。平時無害(末行本多字時不改變),僅末行將塌為 ` 直跑——保留 build.sh/Nvim peek 之單檔串接消費) - `dist/juzhen.css`(合併之樣式) - - `tsc --emitDeclarationOnly` → `dist/index.d.ts` +- **完整構建**:`npm run build` 先執行 `node build.mjs`,再執行 + `tsc --emitDeclarationOnly` → `dist/index.d.ts` 及各模組型別宣告。 - **發佈物**:`dist/` 提交入庫(消費端可直接取單檔/CSS)。 --- @@ -1056,10 +1082,15 @@ build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts - **jsdom(自動)**:驗證 DOM 變換——包裝結構、`bd-*` class、邊界處理、冪等、 `revert` 還原、copy-clean(`textContent` 不被注入污染)、lang 政策分支。 -- **瀏覽器(目視,demo 頁)**:layout 相關**無法**於 jsdom 驗證——justify 單份 - 間距、擠壓視覺量、行端折疊、inline-block 基線。demo 頁載 Noto CJK,簡繁 - 並列、可切 justify、附選取/複製測試區,於 Chrome/Safari/FF 目視核對。 -- 文件明確標注哪些結論來自自動測試、哪些待瀏覽器目視。 +- **原生 WKWebView(自動)**:`npm run test:wkwebview` 需 macOS、Xcode 與 GUI 會話; + runner 編譯 Swift 宿主並載入 `demo/e2e.html`,驗證真實行盒、幾何、計算樣式、 + Selection 及 observer/layout 生命週期。此命令使用現有 `dist`,不自動構建; + 修改源碼後應先 `npm run build`。jsdom 本身不提供真實版面量測。 +- **跨引擎目視(demo 頁)**:檢查擠壓墨色、基線、字型適配、justify 和寬度變化等。 + demo 頁簡繁並列、可切 justify、附選取/複製測試區;Chrome/Safari/Firefox 是 + 待逐環境執行的驗證路線,WKWebView 通過不代表三引擎目視或真實下游頁面已通過。 +- 驗證記錄須綁定實際快照與環境,分別標示 DOM、原生幾何與目視證據;已知交付記錄 + 見 `UPDATES.md`,不能由測試入口存在推定當前驗收完成。 --- diff --git a/README.md b/README.md index 1108e62..21dd3d0 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ import { createJuzhen } from "cjk-autospace"; import "cjk-autospace/css"; const jz = createJuzhen({ lang: { default: "zh-Hant" } }); -jz.render(document.querySelector("main")); +const main = document.querySelector("main"); +if (main) { jz.render(main); } ``` **JS 與 CSS 缺一不可**:JS 只注入 `jz-*` 結構,所有視覺尺寸(間隙寬度、擠壓量、 @@ -145,11 +146,12 @@ jz.observe(document.body); // 之後 DOM 變動即自動增量恢復 > 器之固有限制(任何外科式文本 patch 之框架皆會與注入後之 DOM 失步)、非缺陷——聚珍對當前 > DOM 之處理始終自洽。欲改文字,以塊為單位替換內容即可。 -> **`revert` 之範圍**:`revert` 乾淨移除聚珍注入之全部結構(`jz-*`/`data-jz-*`),DOM 回到**邏輯 -> 等價**態;但**不還原** `spacing` 依契約規範化之贅餘作者空格(CJK↔西文吸收為 margin 間隙、 -> CJK↔CJK 剝除;見上表「中西間隙」)。即 `render→revert` 對含 CJK↔西文作者空格之源,還原後語義 -> 一致、無 `jz-*` 殘留,唯該空格已正規化(此差異穩定、不隨反覆 render/revert 累積)。若消費端需 -> 保留原始作者空格以供編輯,應自存原文、勿以 `revert` 當「還原到未處理源碼」。 +> **`revert` 之範圍**:撤回本實例仍持有所有權、且符合產物與來源完整性檢查的結構及屬性, +> 保留作者標記與其他實例的產物。沒有外部修改時,DOM 回到處理前的**邏輯等價**態;但 +> **不還原** `spacing` 依契約規範化之贅餘作者空格(CJK↔西文吸收為 margin 間隙、 +> CJK↔CJK 剝除;見後面的「中西間隙」功能表)。此差異穩定、不隨反覆 render/revert 累積。 +> 外部克隆、替換或刪除後,以作者當前內容為準,不保證恢復歷史快照或清空所有 `jz-*`。 +> 若消費端需保留原始作者空格以供編輯,應自存原文、勿以 `revert` 當「還原到未處理源碼」。 外部克隆/替換/刪除跨節點載體後,`revert` 保留作者當前內容,不補回已刪文字; 克隆不繼承本實例所有權。欲修改原文,先 `revert` 再修改,或直接替換整塊內容。 @@ -217,7 +219,7 @@ createJuzhen({ |---|---|---|---| | `lang.default` | string | `"zh-Hant"` | 無 `lang` 祖先時之預設語言標籤 | | `lang.style` | `quanjiao|kaiming|banjiao` | 逐語言內建(繁→全角、簡→開明) | **全域政策一鍵覆寫** | -| `lang.policy` | `Record` | `{}` | 逐 lang 政策 | +| `lang.policy` | `Partial>` | `{}` | 按語言分類覆寫:鍵僅為 `zh-Hans`、`zh-Hant`、`ja`、`other`;如 `zh-TW` 先歸入 `zh-Hant`,不直接作為鍵 | | `lang.stylePolicy` | `{selector,style}[]` | `[]` | **逐元素樣式覆寫**(選擇器命中之子樹用指定樣式;如 `[{selector:"h1,button,.ui",style:"banjiao"}]`);配合 `data-jz-style` 屬性,優先於 lang | **作用域** @@ -248,8 +250,8 @@ createJuzhen({ |---|---|---|---| | `pillSelector` | CSS 選擇器 | `"code, kbd"` | 整體元素(內部不可見、兩端補隙) | | `isolateSelector` | CSS 選擇器 | `"mark"`(`""` 停用) | 隔離元素(內部照常、兩端補隙) | -| `styleInlines` | 標籤名[] | 27 個標準行內標籤 | **透明穿越白名單**(未列之行內標籤=邊界) | -| `blockTags` | 標籤名[] | 36 個塊標籤 | 塊級邊界集 | +| `styleInlines` | 標籤名[] | 28 個標準行內標籤 | **透明穿越白名單**(未列之行內標籤=邊界) | +| `blockTags` | 標籤名[] | 43 個塊標籤 | 塊級邊界集 | | `skipTags` | 標籤名[] | `PRE,SAMP,TT,VAR,SCRIPT,STYLE,TEXTAREA,INPUT` | avoid 標籤集(**不含 CODE/KBD**,由 pill 涵蓋) | | `skipAttribute` | string | `null` | 自訂「跳過」屬性名(命中即 avoid) | | `isSkipped` | `(node)=>bool` | `null` | 自訂跳過謂詞(函數級排除鉤子) | @@ -291,8 +293,9 @@ createJuzhen({ ``` 優先序:**逐元素覆寫**(`data-jz-style` 屬性/`stylePolicy` 選擇器,**近祖先勝、同層屬性優先於 -選擇器**)> `lang.policy` > `lang.style` > 內建逐語言預設。屬性值非法(拼錯)則忽略、回退 -lang。半角式渲染沿用既有 `halt`/`margin` 機制(見下節),本功能只做「按元素選樣式」、無新增 +選擇器**)> `lang.policy` > `lang.style` > 內建逐語言預設。屬性值非法(拼錯)只忽略該信號, +仍查同層 `stylePolicy` 選擇器與祖先;沒有任何逐元素信號時才回退語言政策。 +半角式渲染沿用既有 `halt`/`margin` 機制(見下節),本功能只做「按元素選樣式」、無新增 渲染邏輯。 ### 行內元素三分類:透明/隔離/整體 @@ -487,6 +490,10 @@ CSS 皆刪)。`hanging` 選項仍被接受(API 相容)但**無任何效果 ## 標點半形:`halt` 預設與 `margin` 後備 +**缺半形特性字型的逐字相容方案仍保留**:`margin` 後備搭配 `jiya.bias`,可按語言分類、 +逐標點指定 `left`/`center`/`right`/`full`。它與字型自帶的 `halt` 是兩條渲染路徑; +偏側覆寫、量測工具與目視微調流程見本節,不因移除舊負 letter-spacing 方案而取消。 + 開明/半角式把標點收成「半形」,有兩種渲染機制: | 機制 | 設定 | 原理 | 適用 | @@ -605,11 +612,11 @@ JS 只注入 `jz-*` 結構,**所有視覺尺寸在 `juzhen.css`**——故 CSS 關閉原生 `text-autospace`(避免與受管間隙重複作用);不設定 `text-spacing-trim`, 間隙不注入空白字元;僅跨節點 IVS gap 之 `U+200B ::after` 設 `user-select:none`,避免該斷行提示進入選取/複製。 -8. **段落屬性照常設、原子透明**:聚珍把標點拆成 inline-block 原子,但已確保這些原子對 - 消費端段落樣式透明——可在段落正常設 `text-indent`(首行縮排)、`hanging-punctuation`、 - `text-decoration`(下劃線)等;庫內已歸零會被原子重複套用之繼承屬性、並令原子承接段落 - 裝飾,故首行縮排正常、下劃線連續不斷,無需任何額外配置(機制見 ARCHITECTURE「原子透明 - 原則」)。 +8. **段落縮排與裝飾**:聚珍把標點拆成 inline-block 原子,於原子上歸零 `text-indent`, + 避免段落首行縮排被每個原子重複套用;原子也承接 `text-decoration` 裝飾。 + 可在段落設定縮排與下劃線(機制見 ARCHITECTURE「原子透明原則」)。 + `hanging-punctuation` 在原子上設為 `none`;當前管線不提供標點懸掛,不能由此推定 + 段落設定該屬性便會使受管標點出版心。 --- diff --git a/UPDATES.md b/UPDATES.md index c92e5da..0c0a075 100644 --- a/UPDATES.md +++ b/UPDATES.md @@ -16,6 +16,9 @@ 切回 halt 也會殘留 inline-block 內盒。現在由最近有效標記決定模式,可交替巢狀, 切回 halt 同時復原內盒與偏移。margin 的偏側位移僅作用於已壓縮標點;全形標點不再偏移。 +既有缺半形特性字型的逐字相容方案仍保留:`margin` 後備搭配 `jiya.bias`,可按語言分類、 +逐標點選 `left`/`center`/`right`/`full`;字型量測與目視微調流程見 README。 + **受影響者**:混用含 halt 與缺 halt 字型、巢狀半形模式,或 margin 區域含全形標點的消費端。 標記與呼叫方式無需更換。若曾加 CSS workaround 抵銷舊版行為,更新後須確認其是否仍需要。 @@ -114,6 +117,7 @@ N 字,或不可切容器還含排除內容時不綁。pill 仍可作完整單 353 項 Node 測試、發佈構建及原生 WKWebView 53 項驗收;layout/數值回歸見 `test/layout-lifecycle.test.mjs` 與 `test/numeric-options.test.mjs`;觀察根及跨節點標點 回歸見 `test/observer-boundaries.test.mjs` 與 `test/punctuation-atoms.test.mjs`。 - 系統交互新增 42 項回歸見 `test/systematic-interactions.test.mjs`;另有 32 組功能 - 開關×10 類結構共 320 個 DOM 組合及 splitoff 多實例 36 個組合通過。 + 系統交互新增 42 項回歸見 `test/systematic-interactions.test.mjs`。修復過程中的中間快照 + 另通過 32 組功能開關×10 類結構共 320 個 DOM 組合及 splitoff 多實例 36 個組合; + 這兩組為調查階段證據,不列為最終提交的獨立復驗。最終快照驗收為上述 353/53 項。 真實下游頁面仍須自行驗證其字型與版面。