`,以負
`letter-spacing`/`margin` 擠壓;連續、行端、簡繁差異以 CSS + `:lang()` 落實
(`bd-cop:lang(zh-Hant)` → 不擠=全角式)。
- **我們**:同構——charify → `jz-char.bd-*` + `jz-inner`;半形與否寫在
`juzhen.css`,由 JS 解析 `locale.style`(讀 lang)後加 `jz-half`/`jz-half-le`
class 觸發(I3;改用 class 而非 CSS `:lang()`,以避免 `:lang(zh-Hans)` 不匹配
`zh-CN` 等地區標籤之陷阱)。三機制並集落地:①**baseline 政策**(全角不擠/
開明擠句內點號+括號引號/半角全擠);②**行內連續擠壓**(`jiyaAdjacencyPass`,
lang 無關,見下「連續標點挤壓規則」);③**行端擠壓**(`lineedge.ts` layout pass)。
- **跨瀏覽器(I4)— 修訂**:半形改以字型 OpenType **`halt`**(`font-feature-
settings:"halt"`)渲染,**非**負 `margin`。原因:負 margin 假設可壓縮空白全在
單側,對**居中字形**(`!?`、繁體 `locl` 下之 `。,`)會把字面拉入鄰字而**重疊**;
`halt` 收的是字身寬度、由字型按字形正確定位(簡左/繁中/括號偏側皆對),故
不重疊。`font-feature-settings:"halt"` 各引擎一致(當初疑慮之不一致者為**自動**
的 `text-spacing-trim`/`chws`,非顯式 `halt`);缺 `halt` 之字型優雅降級為全形。
鄰接/行端之「收哪一字」之**決策**仍由 `blankSides` 邏輯定,渲染統一為 `halt`。
- **句末標點集含全角句點 `.`(下游回饋)**:`BD_STOP` 為 `。.!?`——除表意句號 `。`
(U+3002)外,納入**全角句點 `.`**(U+FF0E)。實測 Noto CJK SC/TC 之 `halt`(GPOS
SinglePos 調 advance)皆覆蓋 `.`、挤壓收 0.5em,與 `。` 一致;`inkBias` 同步補 `.`
(簡 left/繁 center),margin 後備亦可挤。政策行為與 `。!?` 同:quanjiao/kaiming 句末
保持全角、banjiao 挤半。
- **halt 即字型之「挤壓意願」(設計原則)**:字型對某標點提供 `halt` =該字形適合挤半;
**不提供**=該字形不宜挤(占滿字框,如 Noto TC 之全角 `?` 與 SC 不同、`〈〉` 無 halt)。
故聚珍**信任字型 halt 覆蓋**:有 halt 字型中個別無 halt 之字符,**保持全角、不強挤**(優雅
降級),不為其特設規則。`margin` 後備僅用於**整體無 halt** 之字型(GBK 老字型等),以
`inkBias` 逐字型調教近似 halt——非用來強挤「有 halt 字型刻意不挤」之字符。
- **連續標點挤壓規則(雙側獨立 + 透明穿越;下游回饋)**:相鄰兩標點,**對每一標點**獨立
判斷——若其「有空白之側」緊鄰另一標點,則挤掉該側空白(收半形 `jz-half`)。故 `,「`
(pause 右空白 + open 左空白)兩側皆收 → 全角式與開明式皆合占 1em;`。」`(stop 右空白;
」左為墨)只收 `。`→ 全角式 1.5em/開明式 1em(」baseline 已半);`·—`(皆無指向對方之
空白)皆不收 → 兩式皆 2em(不宜挤之例外)。多標點鏈逐對處理。`jiyaAdjacencyPass` 以**從 root 之透明遍歷**(穿越 strong/
em… 等樣式行內;block 邊界與 avoid 子樹斷相鄰;jz-char 為原子不下探)取代原「同 parent
直接兄弟」掃描,使**跨 inline 邊界**之連續標點亦正確配對(原漏致跨 strong/em 之標點對未挤、
達 2em)。
- **`margin` 後備模式(.feedback 增補)**:消費端字型未必含 `halt`(多數系統字型、
GBK 老字型,實測**方正書宋 GBK 僅含 `vert`、無 `halt`** → halt 模式不擠)。故提供
後備:`jiya: { halfWidth: "margin" }`。**機制**(演進後):把 `jz-char` 之 inline-block
寬收為 `width:0.5em`(真半形 advance——相鄰兩標點各 0.5em、合 1em,無負 margin 之
鄰接疊加/單側重疊問題),字身 `jz-inner` 以 `overflow` 溢出、再依墨色偏側左移
(right −0.5em/center −0.25em/left 0),使墨色落入 0.5em 盒、空白溢向外側——即以
CSS 盒模型重現 `halt`。偏側由 `core/unicode.ts` 之 `inkBias(ch, langClass, overrides)`
依**字身墨色中心**給出 `left|center|right|full`(簡體點號偏左、繁體居中;彎引號
`“”`/角括號 `「」`/尖括號 `〈〉` 墨強偏一側→開 right、閉 left;圓方書名括號
`()【】《》…` 近中→center),charify 時標為 `jz-ink-{left,center,right}`。消費端可
經 `jiya.bias` 逐 lang 逐字覆寫;配字工作流與量測工具 `tools/measure-bias.py` 見 README。
- **激活與粒度(I3/I10)**:模式由 `data-jz-halfwidth` 屬性**所在子樹**決定(CSS
後代選擇器)——`render()` 在全域 margin 時自動掛於 root;亦可手寫於任一祖先作逐區塊
/逐語言控制(含 `margin` 頁中嵌 `halt` 島之 halt-reset,憑來源順序勝出)。bias
class 恆於 charify 標記,halt 模式不讀,故模式切換為純 CSS、無需重 render。實測:
方正書宋 GBK(開明式 zh-Hans)下同句行寬 `24.0em(halt 無效)→ 19.7em(margin 生效)`、
單一句內點號 `1.0em → 0.5em`。
- **切片**:涵蓋並超越 v1 Pass D(開明式),加入全角式、lang 切換、行內連續與
行端擠壓、`halt`/`margin` 雙渲染。
### 6.4 禁則(Jinze,避頭尾)+ justify 綁定 — 切片 1(基礎)/後續(完整)
- **Han**:`h-jinze` 綁定行首行尾不可出現之標點與鄰字,避免標點落行首/行尾。
- **我們**:`jz-jinze`(CSS `display:inline-block; white-space:nowrap`)綁定不可斷
單元。**避頭尾已實作**(`jinze.ts`):因 jiya 把標點原子化為 inline-block,原子
邊界成可斷點、破壞瀏覽器原生 UAX#14 避頭尾,故補回——把「不可置行首」之標點
(閉類/點號/間隔/連接省略)綁到**前一字**、「不可置行末」之開類綁到**後一字**,
且**只隔離單一邊界字**(長引文中段仍正常斷行,不溢出)。實作為兩階段:依原始
相鄰算禁斷邊界→切邊界字;重掃併入 nowrap 群組。亦為 §8 justify 綁定載體。
- **切片**:切片 1 完成行首行末避頭尾(閉類/點號避頭、開類避尾);行首縮排聯動、
更細之禁則對象列後續,沿用同一 `jz-jinze` 結構。
- **已知交互(待瀏覽器精修)**:`lineedge` 之行端偵測需跨 `jz-jinze` 群組邊界向上
攀爬找流內鄰字(已於 `edgeRect` 處理 jz-* 包裹之攀出);屬 layout 層,jsdom 無法
驗,須瀏覽器目視。
### 6.5 標點懸掛 + 垂懸字避免 + 行端禁則 — 整體計劃(待實作)
三個相關機制之**整體實作計劃**(同步引入),皆處理「行端/段末之標點與字」;其分層職責須先
釐清,以免重複處理或衝突。**前置**須先落地「功能分級」(見 6.5.0a),因懸掛/垂懸字屬段落級、
不應作用於標題/按鈕/單行文本。
#### 6.5.0a 功能分級:文本級 vs 段落級 + 元素標記(前置基礎設施)
**動機**:懸掛/垂懸字/禁則等為**段落級**功能(依賴行/段布局),不應作用於標題、按鈕、單行
或單元素文本;而中西間隙、標點挤压為**文本級**,任意文本片段皆適用且想要。下游須能**方便標記**
元素屬哪級。此為貫穿性基礎設施,實作時應提升為正式架構節(§3.x);此處先記計劃。
**分類(含現有與待加功能)**:
| 級別 | 功能 | 判據/依賴 |
|---|---|---|
| **文本級** | `spacing`(中西間隙)、`jiya` baseline 政策半形、`jiya` 連續挤压(adjacency) | 僅字符鄰接,對單行/單元素文本即有意義 |
| **段落級** | `jinze` 避頭尾、行端(半形/懸掛)、`gapTrim`、`orphan` 垂懸字、`longWord` 斷詞 | 需行/段布局(換行、行端、段末、justify)才有意義 |
| 貫穿 | `charClass`、原子透明、normalize | 基礎設施,不分級 |
直覺:文本級=「字符級排版」(間隙、標點形態);段落級=「行/段級排版」(換行、行端、段末)。
**標記 API(下游)**:
- 預設推斷:塊級元素(`blockTags`:p/div/li/td…)→ **段落級(全功能)**。
- 顯式降級:`data-jz-level="text"` 於元素/祖先 → 該子樹**僅文本級**(段落級功能自動關);
`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` 選擇器 + 預設(塊→段落)。
- gate:段落級 pass 對 text-level 元素跳過;文本級 pass 不受限。與現有 `blocks`/
`featureEnabledFor` 疊加(level=粗粒度語義組先判,blocks=細粒度逐功能後調)。
- **文本級功能須支持非塊/單元素**:`spacing` 現用 `eachBlock`,須確認 scope root 為 inline/
單元素時亦能處理(或放寬走訪),使單行標題等之中西間隙、標點半形生效。
**與本批關係**:先落地分級(pass 標 level、`levelFor`、`data-jz-level`/選項)→ orphan/hanging
註冊為段落級,自動只作用段落級元素、跳過 text-level 者。實作順序插於 6.5.5 之首。
#### 6.5.0 四機制之分工與邊界(核心約定,實作前必讀)
| 機制 | 作用層 | 唯一職責 | 手段 | 狀態 |
|---|---|---|---|---|
| 禁則(§6.4 jinze) | **斷行層** | 把標點趕到正確的邊(點號/閉類→行末、開類→行首;連續標點不拆) | nowrap `jz-jinze` | 已實作 |
| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt) | 已實作(將併入行端模式) |
| 標點懸掛(本計劃) | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | 負 `margin` | 待實作 |
| 垂懸字避免(本計劃) | **斷行層** | 段末行 **CJK 實義字數 ≥ N** | nowrap `jz-orphan` | 待實作 |
- **正交性**:斷行層(禁則/垂懸字,管「哪些字符在哪行、不可斷」)與渲染層(半形/懸掛,管
「邊緣標點凸出/收縮」)作用於不同 CSS 維度(`white-space` vs `margin`),可疊加共存。
- **協同**:禁則決定標點落行首或行末 → 半形/懸掛對落在邊緣者施收縮/凸出。
- **行端「半形 vs 懸掛」互斥**(同屬渲染層):將 lineedge 行端處理升級為可配模式
`lineEnd: "squeeze"(現有半形)|"hang"(懸掛)|"none"`,二者共用行端標點偵測碼。
- **垂懸字 vs 懸掛邊界**(已研究):垂懸字限「末行實義字 ≥ N」、懸掛限「邊緣標點凸出」。唯一
重疊=**純標點末行**(實義字=0):懸掛可「吸回」(不動正文)、垂懸字綁定可「拉字下來」。
約定:方案 C(無測量)**附帶**消除純標點末行(副作用,現有益無害);未來若改由懸掛吸回
(更優),把 C 綁定**條件化**(僅末行實義字 ∈ [1,N−1] 才綁),借懸掛 layout pass 之測量協同
判定,C 不獨立測。末行實義字 ∈ [1,N−1] 僅垂懸字(換行)可解(懸掛不增實義字)。
#### 6.5.1 垂懸字避免(方案 C,斷行層,無測量)— 先做
- **目標**:段末行 CJK 實義字數 ≥ N(預設 2;標點/符號不計)。單行段無效。
- **機制**:standalone pass,遍歷作用域段落;自段末向前數 N 個 CJK 實義字(`ruleset.cjk` 判定、
跳過標點/空白/西文計數),把「第 N 個實義字起至段末」(含其間/尾隨標點)包入 ``
(CSS `white-space: nowrap`)。
- **保證(數學、免測 layout)**:該整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型/
瀏覽器成立。平時無害(末行本多字時不改變),僅末行將塌為 `;行首開類加
`jz-hang-l`→`margin-left: -<凸出量>`。
- **與現有行端半形整合**:抽出「行端標點偵測」共用,按 `lineEnd` 模式加 `jz-half-le`(squeeze)
或 `jz-hang-*`(hang)。重構後 squeeze 行為須不變(回歸測試保綠)。
- **凸出量**:取決於標點 advance(halt 半形 0.5em/全形 1em/margin 盒 0.5em)——**需 headless
實測校準**(風險)。目標令標點墨色邊緣齊版心。
- **lang 閘**:clreq——繁體橫排預設不懸掛(`zh-Hant` 預設 squeeze/none);簡體可開。經
`options.hanging` + 逐 lang。預設**待定**(建議預設關,顯式開)。
- **自我應驗**:懸掛凸出改行寬→改斷行→行末標點變→自我應驗(同 gapTrim/lineedge)。緩解:
仿 gapTrim「起始全施/穩定迭代」教訓。**重點風險**。
- **order**:layout pass,最後(整合進 lineedge 行端模式,或 order≈94)。
- **CSS**:`jz-char.jz-hang-r{margin-right:-…}` `.jz-hang-l{margin-left:-…}`。`revert` 清 class。
- **跨瀏覽器**:負 margin 一致;須 Chrome/Safari/FF 目視(R1/R2)。headless 量測行末標點是否
凸出右緣。
#### 6.5.3 行端禁則(§6.4)review
- 現狀避頭尾已與懸掛協同(禁則趕標點到邊、懸掛凸出邊緣)。review:①連續標點群組 `jz-jinze`
在行端懸掛時凸出最外側;②jz-orphan 與 jz-jinze 嵌套;③lineedge 偵測已跨 jz-jinze 攀爬。
預期 jinze 本身不需大改。
#### 6.5.4 統一 pass order(增補後)
`20 charify → 25 jiyaAdj → 40 jinze → 50 spacing → 60 longWord → 70 orphan(垂懸字)→
90 lineEdge(行端 squeeze/hang)→ 92 gapTrim`。
#### 6.5.5 實作順序(分步,風險遞增)
0. **功能分級(前置,6.5.0a)**:pass 標 `level`、`finder.levelFor`、`data-jz-level`/選項
`level.text`;段落級 pass gate 跳過 text-level 元素。orphan/hanging 依此確定適用範圍。
1. **垂懸字(方案 C)**:純結構、無 layout、最獨立、風險最低 → 先做。
2. **lineedge 重構為行端模式**:抽出偵測,現有半形=`squeeze`(行為不變、回歸保綠)。
3. **懸掛 hang 模式**:加 `jz-hang-*` + 負 margin + 凸出量校準 + 自我應驗處理 + lang 閘。
4. **邊界落地**:純標點末行本期 C 全包;記錄未來條件化路徑。
5. **文檔/docs/demo**(resize-check 增懸掛、垂懸字檢查)。
#### 6.5.6 未決(實作時定)
- 垂懸字預設開關(建議開 chars=2);懸掛 lang 預設(建議關)。
- 懸掛凸出量(headless 校準);懸掛自我應驗策略。
- jz-orphan 與 jz-jinze 嵌套之邊界字定位。
### 6.6 標點修正(Biaodian normalize)— 後續(opt-in)
- **Han**:`subst`/`biaodian` 修正標點變體、位置。
- **我們**:opt-in pass,預設**只分類+打 class**(不改文字,I1);顯式開啟才做
變體替換(簡↔繁標點、全半形修正),且可 `revert`。
- **預留**:`order=10` 最先;產物為 `jz-char` 之 class/屬性,與 charify 銜接。
### 6.7 著重號(Emphasis)— 後續
- **Han**:逐字包裝加圈點。
- **我們**:優先用原生 `text-emphasis`(Chrome/FF/Safari 支援良好);需精細
控制時退回 `jz-em` 逐字包裝 + CSS 圓點。lang 感知(位置簡繁略異)。
- **預留**:charify 能力直接複用。
### 6.8 注音/Ruby — 後續(最複雜)
- **我們**:基於原生 `` + `jz-ruby/jz-rt` 處理注音符號直排與聲調定位。
- **預留**:獨立 pass,`order=80`;不影響其餘。
### 6.9 樣式正常化/字型歸屬 — 貫穿(CSS)
- **normalize**:`juzhen.css` 內以 `@supports`/`:where()` 提供 CJK normalize(關閉
與託管擠壓重複作用之原生 `text-spacing-trim`/`text-autospace`、間隙 `user-select`
等),限 `.juzhen` 域、非破壞。
- **字型不歸聚珍管(設計決策,下游回饋)**:曾內建依 `:lang` 之 Noto 字型堆疊作「後備」,
但字型屬消費端領域、內建即越權(且曾蓋掉消費端字型釀 bug)。故**移除 `_lang.css`**,
逐語言 `font-family` 完全交消費端 CSS。聚珍僅守三項非管理性本分:① `_normalize.css`
之 `jz-* { font: inherit }` 使原子繼承消費端字型、不強加;② `_jiya.css` 半形之
`font-feature-settings` 顯式併入 `"locl" 1`(連同 `halt`),確保泛 CJK 字型之地域標點
字形不因設 halt 而失;③ render 只讀不改 `lang`。body 文本之 `locl` 本就不受聚珍影響
(未設任何 feature),由消費端字型+正確 `lang` 決定。消費端範式見 README「字型與 locl」。
- **B 案(字型參數)之否決**:以 JS 參數管字型須注入 CSS、違反「JS 注入結構/CSS 給視覺」
分工;而「缺 halt 字型須逐字型微調 margin」之指引由既有 `jiya.halfWidth`/`jiya.bias`
參數+文檔承擔,無需新參數。
- **跨瀏覽器**:`:where()`/`:lang()`/`font-feature-settings` 普遍支援。
### 6.10 計數器/緊排(counter/well-knit)— 低優先
- 後續以純 CSS 模組加入,不涉 JS 引擎。
---
## 7. 第一垂直切片(v1 對等)
**範圍**:核心基座(finder/unicode/locale/dom/pass 管線)+ 以下功能 pass:
| v1 | 切片 1 對應 | 機制 |
|---|---|---|
| Pass A 中西間隙 | `spacing` | `jz-hws`,copy-clean,justify 安全 |
| Pass B pill 邊界 | `spacing`(同引擎) | avoid + 鄰接判定 |
| Pass C 長英文詞 | `longWord` | `` + `hyphens` |
| Pass D 開明式擠壓 | `jiya` + `jinze` | 跨瀏覽器負 margin + `:lang` 全角/開明 + justify 綁定 |
**交付**:可 `build` 出 ESM/IIFE/CSS/d.ts;jsdom 測試覆蓋 DOM 變換正確性
(包裝、class、邊界、冪等、copy-clean、lang 政策);justify/簡繁 demo 頁供瀏覽器
目視驗證版面(layout 行為無法於 jsdom 驗證,見 §12)。
**擴充保證**:切片 1 所用之 finder/charify/pass 管線/`jz-*` 詞彙/CSS-JS 契約,
即後續所有 Han 功能所用者;新增功能 = 新增一個遵守 §2 不變量的 pass,不改切片 1。
---
## 8. justify 句末標點單份間距解法(要求 #3)
**問題**:`text-align: justify` 下,UA 在「對齊機會點」插入伸縮量。全角句末標點
(如 `。`)字面只占字框一部分、自帶半形留白;若 UA 在其**兩側**都開伸縮,標點
看似分到「兩份」間距(伸縮 gap + 自身留白)。
**解法**(CSS 主導,I2/I5):
1. charify 句末/句逗標點為 `。`。
2. `jz-char` 設 `display: inline-block`:使其成為 justify 的**單一原子項**,內部
不開伸縮 gap,標點與其字面留白同屬一項,對齊伸縮只落在**項與項之間** → 標點
只貢獻一份。
3. **開明式**下,`jz-inner` 以負 letter-spacing 擠去半形留白,該項本身即半形,
justify 一份 gap 即足。
4. **全角式**下,留白保留於原子項內,仍只算一份;以 `jz-jinze` 綁定確保不在留白
側額外開機會點。
> inline-block 影響基線對齊與行內換行,須以 CSS 校正(`vertical-align`、行高),
> 並**跨 Chrome/Safari/FF 目視驗證**(§12);此為切片 1 的重點驗證項。
**分頁器相容(下游回饋問題二)**:常規流式版面 inline-block 原子正常,但**分頁器**
(Paged.js)之對齊引擎會把每個 inline-block 邊界當伸縮點、與 CJK inter-ideograph
疊加,於窄欄混排散架。故提供 `options.justifyAtoms`(預設 true);設 false 時 render
於 root 掛 `data-jz-atoms="inline"`,CSS 令 `jz-char`/`jz-jinze` 改 `display:inline`
(halt 半形與 jinze nowrap 仍保留)。此即 R1 預案之一(解耦對齊原子化),切換僅 CSS、
不動 JS。注意:`margin` 後備半形依賴 inline-block 之 `width:0.5em`,與
`justifyAtoms:false` 不可兼得——分頁器場景須用含 `halt` 字型。
**原子透明原則(繼承屬性漏溢,下游回饋)**:`jz-char`/`jz-jinze`(及 `margin` 模式之
`jz-inner`)為 inline-block,而 inline-block 自成區塊容器。凡「作用於區塊容器首/末行」之
**繼承屬性**,會被每個單字標點原子各自重新套用一次,使「用聚珍」與「未用聚珍」表現不一致。
**作用範圍判斷**:此類間距/位移源於我們生成之原子,屬本庫責任,由庫內統一免疫(消費端無需
配置);且須以「同類機制」整體界定範圍——要麼全修、要麼全交下游,不做逐案補丁。修法
(`_normalize.css`)於原子上歸零此類屬性,只切斷其向原子內部之再套用,段落本身之同名屬性不
受影響(仍作用於段落自身行盒)。
經 headless Chrome 逐屬性實測,屬此類者僅兩項,皆已歸零:
- `text-indent: 0`——段落首行縮排傳入後,每原子內部首行各縮排一次,shrink-to-fit 寬膨脹為
`縮排量+字形`,標點附近冒出巨大間距。實測:歸零後段落首行縮排仍保持 2em,原子寬由 ~3em
回到 0.5em/1em。
- `hanging-punctuation: none`——同屬區塊容器首/末行機制,每原子各自懸掛自身標點。Chrome
未實作(測不出)、Safari 會觸發,且與規劃中之「標點懸掛」(§任務 6)相衝,故按機制一併歸零。
**不歸零者**(實測不洩漏或本應逐字穿透):`letter-spacing`/`word-spacing`(原子邊界處理
一致、無翻倍,實測 delta 與基線同);`text-align`/`text-align-last`/`text-justify`
(halt 為 shrink-to-fit、margin 為內容溢出,皆無對齊餘量,兩模式實測 `jz-inner` 偏移不變);
`text-transform`/`line-height`/`color`/`font`(逐字內容或縱向屬性,須穿透原子);
`word-break`/`overflow-wrap`/`hyphens`/`white-space`/`tab-size`/`text-emphasis`/
`text-shadow`/`font-variant`/`font-stretch`(逐屬性實測 delta 皆與基線同,不洩漏)。
**透明之另一面——欠套用(`text-decoration`)**:與上述「過套用」相反,`text-decoration`
**非繼承**屬性,由段落之「裝飾盒」統一繪製並跨後代延伸;但原子級 inline-block 會被裝飾盒
**跳過**,致段落下劃線在每個標點原子下斷缺(像素掃描實測:純文字 coverage 1.0、聚珍僅
~0.25)。故反向處理——令原子 `text-decoration: inherit` 承段落裝飾自繪,因共用基線與字型
度量而無縫接續(`_normalize.css` 設 `jz-char`/`jz-jinze`;`margin` 模式 `jz-inner` 亦為
inline-block,於 `_jiya.css` 補一條)。實測:halt/margin 兩模式 coverage 皆回 1.0,且
`margin` 下劃線隨半形收為 0.5em advance。**此為「同類問題」整體界定之一部分**:凡因原子化
而生之版面瑕疵,要麼庫內統一修復、要麼統一交下游;本庫選前者,使原子對消費端樣式透明。
---
## 9. 跨瀏覽器策略
- **一致性關鍵層**(間隙、擠壓、justify):只用 `margin`、`inline-block`、
`user-select`、`white-space`、`:where()`、`hyphens`,及字型 OpenType
`font-feature-settings:"halt"`(標點半形;各引擎一致,效果僅依字型是否含 halt,
Noto CJK 具備,缺則優雅降級為全形)——三大引擎(Blink/WebKit/Gecko)皆穩定
支援。**注意**:標點半形預設不用負 `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)。
- **驗證矩陣**:Chrome(Blink,含 VSCode/Electron)、Safari/WebKit、Firefox
(Gecko)。demo 頁含三引擎可開的測試案例。
### 9.1 顯式禁用不一致原語(I4)
當原生原語在各引擎支援不一,或會與我們託管之版面**重複作用**(雙重擠壓/間隙)
時,在 `.juzhen` 範圍內以 `@supports` 偵測並**主動關閉**,使所有引擎收斂於我們的
一致路徑:
```css
@supports (text-spacing-trim: space-all) {
.juzhen { text-spacing-trim: space-all; } /* 關閉 Blink 原生 CJK 標點 kerning */
}
@supports (text-autospace: no-autospace) {
.juzhen { text-autospace: no-autospace; } /* 關閉原生中西自動間距 */
}
```
政策:凡屬「一致性關鍵層」者(間隙、擠壓),一律由我們統一處理並禁用對應原生
原語;凡屬「漸進增強」且不致衝突者(如 Safari 之 `hanging-punctuation`,在我們未
託管懸掛時),方任其生效。此清單隨功能演進維護於 `css/_normalize.css`。
---
## 10. 模組/檔案佈局
```
src/
index.ts createJuzhen / Juzhen / pass 註冊 / render / revert
core/
pass.ts Pass 介面 + 管線執行器
finder.ts 遍歷/avoid/charify/邊界感知 replace
unicode.ts 字元類 + 標點子類 + 規則正則表
locale.ts 有效 lang / 分類 / 全角開明政策
dom.ts jz-* 工廠 / wrap / split / marker / revert
typeset/
spacing.ts 中西間隙 [切片 1]
longword.ts 長英文詞包裝 [切片 1]
jiya.ts 標點擠壓 + 全角開明 [切片 1]
jinze.ts 禁則 + justify 綁定 [切片 1 基礎]
hanging.ts 標點懸掛 [後續]
biaodian.ts 標點修正 [後續]
emphasis.ts 著重號 [後續]
ruby.ts 注音/ruby [後續]
css/
juzhen.css 入口(@import 各 partial)
_normalize.css _spacing.css _jiya.css _jinze.css
_hanging.css _emphasis.css _ruby.css (字型不內建,交消費端)
test/ jsdom DOM 正確性測試
demo/ 瀏覽器 layout/justify/簡繁 目視驗證頁
build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts
```
---
## 11. 構建與發佈
- **源碼**:TypeScript(strict),模組化。
- **構建**:`build.mjs` 以 esbuild 產出
- `dist/juzhen.mjs`(ESM,供打包器/TS)
- `dist/juzhen.iife.js`(IIFE,全域 `Juzhen`,原生 `