diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 16f57cd..a61fd33 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,8 +5,8 @@ > 單份間距、跨瀏覽器一致)作調整。 > > 倉庫名 `cjk-autospace` 不變;公開 API 為 `createJuzhen` / 全域 `Juzhen`; -> 自訂元素前綴 `jz-`。本文件先於任何程式碼,作為實作契約——後續每個功能點 -> 皆按此落地,以避免不一致與重大返工。 +> 自訂元素前綴 `jz-`。平台中立之排版語義以 `rules/juzhen-rules.json`、其 Schema 與 +> 黃金向量為唯一權威源;本文件描述 Web adapter 架構及設計理由,不得覆蓋或分叉共享規則。 --- @@ -14,28 +14,38 @@ 1. **第一垂直切片 = v1(cjk-autospace)全部功能**,在新引擎上重寫:中西間隙 (Pass A/B)、長英文詞斷詞包裝(Pass C)、開明式標點擠壓(Pass D)。 -2. **架構須預留完整復刻 Han.css 的能力**:標點修正、擠壓、懸掛、禁則、著重號、 - 注音/ruby、樣式正常化、字體偵測、計數器等,皆能在不改動切片 1 既有程式碼 - 的前提下,以「新增一個 pass」的方式加入。 +2. **架構保留擴充 Han.css 功能版圖的能力**:標點修正、著重號與注音/ruby 等目前 + **尚未實作**,若未來加入,須沿既有 pass、finder 與 CSS 契約擴充。標點懸掛之舊 + 負 margin 實作則已移除;四個同名公開選項現階段僅為相容 no-op(見 §3.1)。 3. **跨瀏覽器一致**(Chrome/Edge/Safari/WebKit/Firefox):一致性關鍵的版面 行為,只用普遍支援的 CSS 原語;原生新特性僅作非必要增強。 非目標:不依賴 Han.css/Heti 等既有庫之執行期程式碼(MIT 條款下可借鑑其思路與 分類表,但成品自含)。 +### 1.1 共享規則與 adapter 所有權 + +`rules/` 固定字符分類、語言政策、標點目標寬度、四分之一字間隙、禁斷、段末約束與條件 +行端結果;Web/CSS 與並行 XeLaTeX 實作均消費同一 `rulesetVersion` 及黃金向量。DOM wrapper、 +CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro 都只是各 adapter 可選之 +非規範機制。共享規則由本項目維護;任何變更須先更新 Schema/向量,再由兩個 adapter 分別 +證明相容,不能在某一實作內靜默改義。 + --- ## 2. 設計不變量(所有 pass 必須遵守) 這些不變量是「避免返工」的核心。任何新功能點都不得違反。 -- **I1 複製乾淨(copy-clean)**:注入之間隙與標記一律 `user-select: none`,不改變邏輯 - `textContent` 的**語義**內容。**一項刻意之常態正規化例外**:`spacing` pass 剝除**贅餘之作者 +- **I1 複製乾淨(copy-clean)**:`spacing` 以 `jz-hws` 的 margin 留隙,**不插入空白字元**; + 唯跨節點 IVS 間隙以 `::after` 生成 `U+200B` 斷行機會,且只對此生成內容設 + `user-select:none`。該偽元素不進入 DOM/`textContent`。**一項刻意之常態正規化例外**: + `spacing` pass 剝除**贅餘之作者 空格**——CJK↔西文之作者空格吸收為受管 `margin` 間隙、CJK↔CJK(含跨 inline 標籤)之作者 空格剝除不留隙。此為 CJK 排版正解(該空格屬裝飾性、間隙改由 margin 承載,語義內容不變), 但**確實刪除空格字元、且此刪除不經 `revert` 還原**(見 I6)。除此常態正規化外,預設「只包裝、 - 不改寫文字」;其餘真正會改寫文字之 pass(如標點替換 `biaodian`)須為顯式 opt-in 且可 `revert` - 還原。(實測 4 份真人撰寫之真實文檔:非空白字元多重集守恆、無任何字元改/增/漏/亂序,唯 + 不改寫文字」。未來若新增會改寫文字之 pass,須為顯式 opt-in 且可 `revert` 還原;目前不存在 + `biaodian` 改寫 pass。(實測 4 份真人撰寫之真實文檔:非空白字元多重集守恆、無任何字元改/增/漏/亂序,唯 贅餘作者空格依此契約規範化。) - **I2 JS/CSS 職責分離**:JS 只注入**結構**(`jz-*` 元素、class、`data-jz-*` 屬性);**所有視覺尺寸(margin/letter-spacing/寬度/懸掛位移)一律寫在 @@ -107,8 +117,6 @@ jz.disconnect(); // 停止觀察 // 全域。style = 全域擠壓預設(quanjiao|kaiming|banjiao);policy = 逐 lang // 覆寫。優先序:policy[lang] > style > 內建預設(繁→全角、簡→開明)。 lang: { default: "zh-Hant", style: "kaiming", policy: { "zh-Hant": "quanjiao" } }, - classPrefix: "jz", - // 作用域限定(§3.6) scope: { root: document.body, // 預設處理根;render(root) 可逐次覆寫 @@ -122,86 +130,97 @@ jz.disconnect(); // 停止觀察 longWord: { minLength: 6, lang: "en" }, jiya: true, // 標點擠壓 jinze: true, // 禁則(含 justify 綁定) - hanging: false, // 標點懸掛(預設關,繁體橫排不宜) - biaodian: false, // 標點修正(opt-in,可能改寫文字) - emphasis: false, - ruby: false, - + orphan: { chars: 2 }, // 段末垂懸字避免 + level: { text: "h1,h2,h3,button,.ui" }, // 分塊功能選擇(§3.6):選擇器 → 該塊內生效之功能集(覆寫全域) blocks: [ - { selector: ".prose", features: ["spacing", "jiya", "jinze", "emphasis"] }, + { selector: ".prose", features: ["spacing", "jiya", "jinze"] }, { selector: ".code-comment", features: ["spacing"] }, // 此塊只間隙、不擠壓 ], } ``` +`hanging`/`biaodian`/`emphasis`/`ruby` 仍可作為頂層選項傳入,但目前全部為 +**相容 no-op**:沒有註冊 pass,也不產生 DOM 或 CSS 效果。`hanging` 的舊負 margin +實作已移除;其餘三項僅保留為明確標記的未來設計。`charClass.biaodian` 則是已實作的 +標點字符分類擴充,與同名頂層 no-op 選項無關。 + ### 3.2 Pass 管線 -每個功能點是一個 `Pass`: +每個功能點是一個 `Pass`;目前型別是 `CharifyPass | StandalonePass`: ``` -interface Pass { +interface BasePass { name: string; - phase: "P0" | "P1" | "P2"; // 遍歷階段,供聚合(§3.4) - order: number; // phase 內 apply 先後(charify→擠壓→間隙→…) - enabled(options): boolean; - // 聚合模式:collect 純判定(同階段共享單次走訪),apply 批次變更 - collect(node: Text, ctx: RenderContext): Target[]; - apply(targets: Target[], ctx: RenderContext): void; + order: number; + kind: "charify" | "standalone"; + level: "text" | "paragraph"; + enabled(options: ResolvedOptions): boolean; revert(ctx: RenderContext): void; } + +interface CharifyPass extends BasePass { + kind: "charify"; + collect(node: Text, ctx: RenderContext): WrapRequest[]; +} + +interface StandalonePass extends BasePass { + kind: "standalone"; + render(ctx: RenderContext): void; +} + +type Pass = CharifyPass | StandalonePass; ``` -需獨立走訪/layout 之 pass(如 hanging)可改採 `render(ctx)` 整段式而不參與 -collect 聚合;執行器兩種皆容納。 +`CharifyPass` 只收集文本包裝請求;`StandalonePass` 自行執行功能所需的掃描或 layout。 +兩類皆由同一註冊表管理,且均按 `level` 與功能閘判定是否生效。 `createJuzhen` 內建一個**有序、可開關**的 pass 註冊表。`render` 依 `order` 執行 已啟用者。順序契約(避免互相破壞): ``` -10 biaodian 標點修正(若開,最先;可能改寫文字/包裝) -20 charify 標點字元包裝(jz-char/jz-inner)——擠壓、禁則、著重號之共同前置 -30 jiya 標點擠壓(依 class + :lang 之 CSS) -40 jinze 禁則 + justify 綁定 -50 spacing 中西間隙(jz-hws) +Charify group(共享一次文本遍歷): +20 jiya 標點字元包裝(jz-char/jz-inner) 60 longWord 長英文詞 lang 包裝 -70 emphasis 著重號 -80 ruby 注音/ruby -90 hanging 標點懸掛(需 layout,最後;掛 ResizeObserver) + +Standalone group(各自 render): +10 levelMark 寫回 resolved block level +25 jiyaAdjacency 連續標點擠壓標記 +40 jinze 禁則 + justify 綁定 +50 spacing 中西間隙(jz-hws) +70 orphan 垂懸字避免 +90 jiyaLineEdge 行端半形擠壓 +92 spacingGapTrim 行末中西間隙裁切 ``` -> 順序理由:charify 先把標點變成可定址的 `jz-char`,擠壓/禁則/著重號才有對象; -> 間隙在標點結構穩定後插入;懸掛依賴最終行盒,必須最後且 layout 後再算。 +執行器先跑完整個 charify group,再跑 standalone group;因此跨組不以數字互相比較。 +例如 `levelMark` 的 `order=10` 仍在 charify 完成後執行。`revert` 則把全部 pass 合併後 +按 `order` 逆序呼叫,產物的 ownership/kind 標記負責精確還原。 ### 3.3 RenderContext -`render`/`revert` 收到統一上下文:`{ root, options, finder, locale }`。各 pass -共用同一 `finder` 實例與 locale 解析,確保 avoid 名單、邊界規則、語言判定一致。 +`render`/`revert` 收到統一上下文:`{ root, options, finder, ownedElements, +ownerToken, setOwnedClass }`。各 pass 共用同一 `finder` 與 resolved options;產物透過 +本實例的 ownership 集合/token 辨識,避免把外來或偽造的 `jz-*` 當成本實例產物。 ### 3.4 遍歷聚合(減少 pass 遍歷次數,I9) -樸素做法是每個 pass 各跑一次 `TreeWalker`,9 個 pass 即 9 次全樹走訪。為此引入 -**遍歷階段(phase)**:相容之 pass 共享單次走訪。Pass 介面拆為 -`collect(node, ctx) → targets[]`(純判定、不變更 DOM)與 `apply(targets, ctx)` -(批次變更),執行器: +執行器先篩出已啟用的 pass,再按 `kind` 分組。所有 `CharifyPass` 共用一次 +`finder.eachTextNode(root)`:對每個文本節點依 `order` 呼叫各 pass 的 `collect`, +先把節點與 `WrapRequest` 累積為 work,遍歷結束後才由管線執行器統一套用,避免走訪途中改 DOM。 +之後 `StandalonePass` 依 `order` 各自呼叫 `render(ctx)`: ``` -for phase in phases: - walk(root) 一次:對每個文本節點,依序呼叫該 phase 內各 pass 的 collect() - 依 order 對收集到的 targets 做 apply()(兩階段:先收集後變更,避免遍歷中改 DOM) +charify = enabled.filter(kind == "charify").sort(order) +standalone = enabled.filter(kind == "standalone").sort(order) + +walk(root) 一次:對每個 Text,依序把 charify 的 WrapRequest 收入 work +for item in work: applyRequests(item.node, item.requests, ownedElements) +for pass in standalone: pass.render(ctx) ``` -階段劃分(把 ~9 次走訪壓到 3 次): - -| phase | 內含 pass | 走訪 | 備註 | -|---|---|---|---| -| P0 文字修正 | biaodian(opt-in) | 1 次 | 唯一可能改寫文字者,須最先 | -| P1 行內結構 | charify/jiya 標記/jinze/spacing/longWord/emphasis | 1 次 | 共享走訪,`apply` 依 order | -| P2 版面後處理 | hanging | 0 次走訪+layout | 需最終行盒,掛 ResizeObserver | - -> 聚合僅是**執行優化**:邏輯上仍「一功能一 pass」、可獨立開關與 `revert`(I6); -> 只是 P1 諸 pass 的 `collect` 在同一走訪內完成。若某 pass 不相容(需獨立走訪), -> 退化為自跑一次,不影響正確性。 +聚合僅是**執行優化**:邏輯上仍「一功能一 pass」、可獨立開關與 `revert`(I6)。 +需要自訂掃描、跨節點 run 或 layout 的功能採 standalone,不假裝參與 charify 聚合。 ### 3.5 v1 向下相容層 — 已移除(I8 退役) @@ -227,14 +246,14 @@ Nvim peek/Claude 模板等 v1 消費端之過渡期。**最後一批 v1 消費 - **宣告式(屬性,就近繼承)**: - `data-juzhen="spacing jiya"`——白名單:此塊內僅啟用列出之功能。 - - `data-juzhen-off="jiya hanging"`——黑名單:此塊停用列出者,其餘繼承。 + - `data-juzhen-off="jiya orphan"`——黑名單:此塊停用列出者,其餘繼承。 - `lang`——驅動全角/開明(正交於功能開關)。 - **程式式**:`options.blocks: [{ selector, features }]`,選擇器 → 功能集。 - **解析(per-node 閘)**:對每個節點,向上找最近帶功能指令之祖先(屬性或命中 `blocks` 選擇器者)決定其**有效功能集**,無則用全域 `options`;render 期間以 - 元素為鍵快取。每個 pass 之 `collect(node)` 先問 `featureEnabledFor(node, name)`, - 否則跳過——**故分塊閘只是單次走訪內的 per-node 判定,不增遍歷、不破壞聚合 - (§3.4)**。 + 元素為鍵快取。charify pass 在 `collect(node)`、standalone pass 在自身掃描時查詢 + `featureEnabledFor(node, name)`,未啟用即跳過。分塊閘不改變 §3.4 的兩類 pass + 執行模型。 - **與 revert 之關係**:`revert` 依標記移除某功能產物,**不受當前閘狀態影響**,故 「關閉某功能 + revert」恆能乾淨還原(I6)。 @@ -358,9 +377,9 @@ avoid」用組合入口 `isAvoided`(=自身或祖先,階段二歸一,免 - `jinze`:`anyCjk = RegExp(ruleset.cjk)` 決定詞元邊界之 CJK 判定。 **設計取捨**:① **向下兼容**——不傳 `charClass` 則 `ruleset === DEFAULT_RULESET`,行為與 -重構前完全一致(現有測試全綠)。② **追加語義(v1)**——擴充內置類(把未分類字符如英文全角 -句號 `.`U+FF0E 歸入、或納新符號);不支援重分類/移除內置字符(範圍式集合難以挖洞、且需 -求罕見,列為後續)。③ 結構性 ASCII 不開放(非「字符類型」)。 +重構前完全一致(現有測試全綠)。② **追加語義(v1)**——擴充尚未內置的字符;全角句點 +`.`(U+FF0E)已是內置 `BD_STOP`,無須追加。規則不支援重分類/移除內置字符(範圍式集合 +難以挖洞、且需求罕見,列為後續)。③ 結構性 ASCII 不開放(非「字符類型」)。 > 借鑑 Han 之 `regex/typeset.js`、`regex/unicode.js` 與 Heti 之分類字集,重寫為 > 現代 TS 常數表+可配置規則集。 @@ -400,17 +419,17 @@ CSS 端對應以 `:lang()` 選擇器落實 lang 層政策(I2/I3);逐元 | 元素 | 用途 | |---|---| -| `jz-hws` | 中西間隙。`user-select:none`,copy-clean,justify 安全(§8) | +| `jz-hws` | 中西間隙,以 margin 留隙、不插入空白字元;跨節點 IVS 例外見 §6.1 | | `jz-char` | 包裝單一標點/漢字,承載 `bd-*` class 與 `data-jz` 屬性 | | `jz-inner` | `jz-char` 內層字面容器,供 margin/letter-spacing 擠壓而不動外盒 | -| `jz-cs` | 字間補償空白(連續/行端擠壓用,於行邊界折疊) | | `jz-jinze` | 禁則群組:綁定不可斷單元(亦為 justify 綁定載體) | | `jz-orphan` | 垂懸字避免群組:段末末 N 實義字 `nowrap`(§6.5.1) | -| `jz-em` | 著重號載體 | -| `jz-ruby`/`jz-rb`/`jz-rt` | ruby/注音 | -class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`jiya`、 -`jiya-consecutive`、`hangable`、`jinze-{tou,wei,touwei}` 等。 +`jz-cs`、`jz-em`、`jz-ruby`/`jz-rb`/`jz-rt` 只屬未實作之未來設計名稱,當前程式 +不會產生。 + +class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd-pause`/ +`bd-stop`/`bd-middle`/`bd-liga`;擠壓另用 `jz-half`/`jz-half-le` 與 `jz-ink-*`。 --- @@ -426,6 +445,9 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j 包進 `jz-hws`,間隙由 CSS `margin-right`(0.25em)提供——**不注入任何空白字元**。 WHERE 之規則沿用 v1 pangu 規則表(已驗證);作者原寫之 CJK↔西文空格一律移除 (改由 margin 留隙)。 +- **跨節點 IVS**:當 base 與 variation selector 分居文本節點時,`jz-hws` 承載完整 + base+VS 序列;僅此類 gap 以 `::after` 生成 `U+200B` 斷行機會,並只對該偽元素設 + `user-select:none`。它不是 DOM 文本,不改變 `textContent` 或複製結果。 - **為何不用空白字元/inline-block(justify 三難)**:純 CSS 下,CJK↔西文間隙在 `justify` 必有一個邊緣瑕疵——①真實空白→被當詞間空白**暴增**;②inline-block 固定 寬→罕見落**行首縮排**;③`margin-right`→**行末右緣內縮 0.25em**。選 ③(margin), @@ -628,7 +650,7 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j #### 6.5.0a 功能分級:文本級 vs 段落級 + 元素標記(前置基礎設施) -**動機**:懸掛/垂懸字/禁則等為**段落級**功能(依賴行/段布局),不應作用於標題、按鈕、單行 +**動機**:垂懸字/禁則等為**段落級**功能(依賴行/段布局),不應作用於標題、按鈕、單行 或單元素文本;而中西間隙、標點挤压為**文本級**,任意文本片段皆適用且想要。下游須能**方便標記** 元素屬哪級。此為貫穿性基礎設施(已實作:`Finder.levelFor`/`levelAllows`、各 pass `level` 元數據、選項 `level.text`/屬性 `data-jz-level`)。 @@ -638,7 +660,7 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j | 級別 | 功能 | 判據/依賴 | |---|---|---| | **文本級** | `spacing`(中西間隙)、`jiya` baseline 政策半形、`jiya` 連續挤压(adjacency) | 僅字符鄰接,對單行/單元素文本即有意義 | -| **段落級** | `jinze` 避頭尾、行端(半形/懸掛)、`gapTrim`、`orphan` 垂懸字、`longWord` 斷詞 | 需行/段布局(換行、行端、段末、justify)才有意義 | +| **段落級** | `jinze` 避頭尾、行端半形、`gapTrim`、`orphan` 垂懸字、`longWord` 斷詞 | 需行/段布局(換行、行端、段末、justify)才有意義 | | 貫穿 | `charClass`、原子透明、normalize | 基礎設施,不分級 | 直覺:文本級=「字符級排版」(間隙、標點形態);段落級=「行/段級排版」(換行、行端、段末)。 @@ -648,7 +670,7 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j - 顯式降級:`data-jz-level="text"` 於元素/祖先 → 該子樹**僅文本級**(段落級功能自動關); `data-jz-level="paragraph"`(或不標)→ 全功能。 - 選項 `level?: { text?: string }`:選擇器一鍵標記(如 `level:{ text: "h1,h2,h3,button,.ui" }`)。 -- 典型:標題/按鈕/UI 文字標 **text-level**(要間隙與標點半形、不要懸掛/避孤字/避頭尾); +- 典型:標題/按鈕/UI 文字標 **text-level**(要間隙與標點半形、不要避孤字/避頭尾); 正文 `
` 預設全功能。 **實現**: @@ -659,8 +681,8 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j - **文本級功能須支持非塊/單元素**:`spacing` 現用 `eachBlock`,須確認 scope root 為 inline/ 單元素時亦能處理(或放寬走訪),使單行標題等之中西間隙、標點半形生效。 -**與本批關係**:先落地分級(pass 標 level、`levelFor`、`data-jz-level`/選項)→ orphan/hanging -註冊為段落級,自動只作用段落級元素、跳過 text-level 者。實作順序插於 6.5.5 之首。 +**與本批關係**:分級已落地(pass 標 level、`levelFor`、`data-jz-level`/選項);`orphan` +等段落級 pass 只作用於段落級元素、跳過 text-level 者。`hanging` 未註冊為 pass。 **Resolved 分類之 CSS 鉤子(下游建議七,`typeset/levelmark.ts`)**:消費端之段落級 CSS(首要 `text-align:justify`)語義上與段落級 pass 應作用於**同一集合**,但 level 判定原僅存內部快取, @@ -707,7 +729,8 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j 逐塊 `block.querySelector(sel)` 掃整棵子樹判葉塊,累加 O(Σ塊子樹)、深塊嵌套下 O(n²);改以一趟 上攀預計算「非葉塊」Set(某塊為另一塊之最近塊祖先 ⟺ 含塊級後代),scope 無涉、與原 querySelector 同口徑,行為等價。 -- **order=70**(charify 20 後、layout pass 前;longWord 60 後)。 +- **order=70**(standalone 群中位於 jinze 40/spacing 50 後、layout pass 前;整個 + standalone 群又在含 longWord 60 的 charify 群之後)。 - **與禁則協同**:段末範圍可能含 jinze 已綁之 `jz-jinze`;置於 jinze(40) 後,將 jz-jinze 整體 納入 jz-orphan,並處理邊界字定位**跨 jz-jinze 攀爬**(實作細節)。免重複包裹。 - **CSS**:`jz-orphan { white-space: nowrap; }`。`revert`:wrap-class,隨 `revertPass` 還原。 @@ -723,7 +746,7 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j lineedge 行端處理只餘 **squeeze**(行端半形):量測各 jz-char 是否在行首(開類)/行末(閉類), 加 `jz-half-le`(CSS 同 halt)。兩階段免自我應驗(先 `clearEdge`、自然佈局量測全部、再一次套用); 行歸屬以「垂直區間」`lowerLine`/`higherLine` 判定(吸收 inline-block 行盒與純文本基線差 ~5px)。 -order `90 lineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過、須瀏覽器目視。 +order `90 jiyaLineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過、須瀏覽器目視。 #### 6.5.3 行端禁則(§6.4)review @@ -733,8 +756,9 @@ order `90 lineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過、須 #### 6.5.4 統一 pass order -`20 charify → 25 jiyaAdj → 40 jinze → 50 spacing → 60 longWord → 70 orphan(垂懸字)→ -90 lineEdge(行端 squeeze 半形)→ 92 gapTrim`。 +實際分兩群且不跨群比較 order:charify 群為 `20 jiya → 60 longWord`,共用一次文本 +遍歷;其後 standalone 群為 `10 levelMark → 25 jiyaAdjacency → 40 jinze → 50 spacing → +70 orphan → 90 jiyaLineEdge → 92 spacingGapTrim`。 #### 6.5.5 實作順序與結果 @@ -774,30 +798,33 @@ order `90 lineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過、須 擠壓 + `hanging-punctuation` 接管懸掛,按 `CSS.supports` 能力門控,整體退出 jz-char 機制。 與已移除之負 margin 程式碼**沒有一行可複用**,故不保留休眠碼。 -### 6.6 標點修正(Biaodian normalize)— 後續(opt-in) +### 6.6 標點修正(Biaodian normalize)— 未實作之未來設計 - **Han**:`subst`/`biaodian` 修正標點變體、位置。 -- **我們**:opt-in pass,預設**只分類+打 class**(不改文字,I1);顯式開啟才做 - 變體替換(簡↔繁標點、全半形修正),且可 `revert`。 -- **預留**:`order=10` 最先;產物為 `jz-char` 之 class/屬性,與 charify 銜接。 +- **擬議方向**:未來可設計 opt-in pass;顯式開啟才做變體替換(簡↔繁標點、全半形 + 修正),且須可 `revert`。目前沒有 `biaodian` 模組、pass 或文字改寫。 +- **相容狀態**:頂層 `biaodian` 選項僅為 no-op;`charClass.biaodian` 是另一套已實作的 + 字符分類擴充 API。 -### 6.7 著重號(Emphasis)— 後續 +### 6.7 著重號(Emphasis)— 未實作之未來設計 - **Han**:逐字包裝加圈點。 -- **我們**:優先用原生 `text-emphasis`(Chrome/FF/Safari 支援良好);需精細 - 控制時退回 `jz-em` 逐字包裝 + CSS 圓點。lang 感知(位置簡繁略異)。 -- **預留**:charify 能力直接複用。 +- **擬議方向**:可優先使用原生 `text-emphasis`;若未來需要精細控制,再評估逐字 + 包裝與 lang 感知。`jz-em` 只是設計草案名稱,當前沒有對應模組、pass、DOM 或 CSS。 +- **相容狀態**:頂層 `emphasis` 選項僅為 no-op。 -### 6.8 注音/Ruby — 後續(最複雜) +### 6.8 注音/Ruby — 未實作之未來設計 -- **我們**:基於原生 `` + `jz-ruby/jz-rt` 處理注音符號直排與聲調定位。 -- **預留**:獨立 pass,`order=80`;不影響其餘。 +- **擬議方向**:未來可基於原生 `` 評估注音符號直排與聲調定位;`jz-ruby`/ + `jz-rb`/`jz-rt` 只是設計草案名稱,當前沒有對應模組、pass、DOM 或 CSS。 +- **相容狀態**:頂層 `ruby` 選項僅為 no-op。 ### 6.9 樣式正常化/字型歸屬 — 貫穿(CSS) - **normalize**:`juzhen.css` 內以 `@supports`/`:where()` 提供 CJK normalize(關閉 - 與託管擠壓重複作用之原生 `text-spacing-trim`/`text-autospace`、間隙 `user-select` - 等),限 `.juzhen` 域、非破壞。 + 與託管擠壓重複作用之原生 `text-spacing-trim`/`text-autospace` 等),限 `.juzhen` + 域、非破壞。間隙本身不插入空白;只有跨節點 IVS 的 `U+200B` 偽元素設 + `user-select:none`。 - **字型不歸聚珍管(設計決策,下游回饋)**:曾內建依 `:lang` 之 Noto 字型堆疊作「後備」, 但字型屬消費端領域、內建即越權(且曾蓋掉消費端字型釀 bug)。故**移除 `_lang.css`**, 逐語言 `font-family` 完全交消費端 CSS。聚珍僅守三項非管理性本分:① `_normalize.css` @@ -825,7 +852,7 @@ order `90 lineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過、須 | Pass A 中西間隙 | `spacing` | `jz-hws`,copy-clean,justify 安全 | | Pass B pill 邊界 | `spacing`(同引擎) | avoid + 鄰接判定 | | Pass C 長英文詞 | `longWord` | `` + `hyphens` | -| Pass D 開明式擠壓 | `jiya` + `jinze` | 跨瀏覽器負 margin + `:lang` 全角/開明 + justify 綁定 | +| Pass D 開明式擠壓 | `jiya` + `jinze` | OpenType `halt`;缺 `halt` 時用 0.5em 盒寬+ink bias 後備;lang/style 政策+justify 綁定 | **交付**:可 `build` 出 ESM/IIFE/CSS/d.ts;jsdom 測試覆蓋 DOM 變換正確性 (包裝、class、邊界、冪等、copy-clean、lang 政策);justify/簡繁 demo 頁供瀏覽器 @@ -907,9 +934,9 @@ inline-block,於 `_jiya.css` 補一條)。實測:halt/margin 兩模式 c 支援。**注意**:標點半形預設不用負 `margin`(對居中字形會重疊),改用 `halt`(見 §6.3 修訂)。**缺 `halt` 字型之後備**:`jiya.halfWidth:"margin"` 以 `width:0.5em`+ 字身依偏側(`jz-ink-*`)溢出定位,於 CSS 重現 halt、真半形 advance(見 §6.3)。 -- **漸進增強(非必要)**:`text-spacing-trim`(Blink)、`hanging-punctuation` - (WebKit)、`text-emphasis`、`text-autospace`——僅在支援時錦上添花,缺席不影響 - 基本排版(I4)。 +- **原生原語政策**:`text-spacing-trim`/`text-autospace` 在現行託管功能中須避免 + 重複作用;`hanging-punctuation`/`text-emphasis` 只屬尚未實作之未來設計評估, + 不是當前 pass 或驗收項目。 - **驗證矩陣**:Chrome(Blink,含 VSCode/Electron)、Safari/WebKit、Firefox (Gecko)。demo 頁含三引擎可開的測試案例。 @@ -929,8 +956,8 @@ inline-block,於 `_jiya.css` 補一條)。實測:halt/margin 兩模式 c ``` 政策:凡屬「一致性關鍵層」者(間隙、擠壓),一律由我們統一處理並禁用對應原生 -原語;凡屬「漸進增強」且不致衝突者(如 Safari 之 `hanging-punctuation`,在我們未 -託管懸掛時),方任其生效。此清單隨功能演進維護於 `css/_normalize.css`。 +原語。`hanging-punctuation` 等未來設計若要加入,須另立能力門控與三引擎驗收,不能 +從目前保留的 no-op 選項推定已實作。此清單隨功能演進維護於 `css/_normalize.css`。 --- @@ -938,31 +965,34 @@ inline-block,於 `_jiya.css` 補一條)。實測:halt/margin 兩模式 c ``` src/ - index.ts createJuzhen / Juzhen / pass 註冊 / render / revert + index.ts createJuzhen / Juzhen / pass 註冊 / observer / render / revert + types.ts 公開選項、RenderContext、CharifyPass / StandalonePass 型別 core/ - pass.ts Pass 介面 + 管線執行器 - finder.ts 遍歷/avoid/charify/邊界感知 replace + pass.ts 兩類 pass 之分組、排序與管線執行器 + finder.ts 遍歷/avoid/charify/邊界感知與跨節點 run unicode.ts 字元類 + 標點子類 + 規則正則表 locale.ts 有效 lang / 分類 / 全角開明政策 - dom.ts jz-* 工廠 / wrap / split / marker / revert + dom.ts jz-* 工廠 / ownership / wrap / revert + split.ts 禁則/垂懸字之行內元素邊界切分 typeset/ - spacing.ts 中西間隙 [切片 1] - longword.ts 長英文詞包裝 [切片 1] - jiya.ts 標點擠壓 + 全角開明 [切片 1] - jinze.ts 禁則 + justify 綁定 [切片 1 基礎] - hanging.ts 標點懸掛 [後續] - biaodian.ts 標點修正 [後續] - emphasis.ts 著重號 [後續] - ruby.ts 注音/ruby [後續] + spacing.ts 中西間隙 + longword.ts 長英文詞包裝 + jiya.ts 標點擠壓 + 全角開明 + jinze.ts 禁則 + justify 綁定 + orphan.ts 段末垂懸字避免 + levelmark.ts resolved block level 寫回 + lineedge.ts 行端半形擠壓 css/ juzhen.css 入口(@import 各 partial) - _normalize.css _spacing.css _jiya.css _jinze.css - _hanging.css _emphasis.css _ruby.css (字型不內建,交消費端) + _normalize.css _spacing.css _jiya.css _jinze.css _orphan.css test/ jsdom DOM 正確性測試 demo/ 瀏覽器 layout/justify/簡繁 目視驗證頁 build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts ``` +`biaodian.ts`/`emphasis.ts`/`ruby.ts`/`hanging.ts` 及同名 CSS partial 均不存在; +前述章節是明確標記的未來設計或已移除機制,不是當前模組清單。 + --- ## 11. 構建與發佈 @@ -973,7 +1003,7 @@ build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts - `dist/juzhen.iife.js`(IIFE,全域 `Juzhen`,原生 ` + +