docs: §6.5 功能分級/垂懸字落地;標點懸掛停用記錄(ARCHITECTURE/README)
ARCHITECTURE §6.5:功能分級、垂懸字(方案 C,預設開)標為已落地工作良好;標點懸掛標為 實作後停用,記錄四項不可行原因、三重防逃逸、未來原生 hanging-punctuation 路徑(§6.5.6)。 README:功能表加功能分級 level、垂懸字 orphan;標點懸掛列改為「已評估停用」說明;選項示例 與「標點懸掛」一節同步為停用。
This commit is contained in:
+88
-38
@@ -480,17 +480,39 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
攀爬找流內鄰字(已於 `edgeRect` 處理 jz-* 包裹之攀出);屬 layout 層,jsdom 無法
|
||||
驗,須瀏覽器目視。
|
||||
|
||||
### 6.5 標點懸掛 + 垂懸字避免 + 行端禁則 — 整體計劃(待實作)
|
||||
### 6.5 標點懸掛 + 垂懸字避免 + 行端禁則 — 整體實作(已落地)
|
||||
|
||||
三個相關機制之**整體實作計劃**(同步引入),皆處理「行端/段末之標點與字」;其分層職責須先
|
||||
釐清,以免重複處理或衝突。**前置**須先落地「功能分級」(見 6.5.0a),因懸掛/垂懸字屬段落級、
|
||||
三個相關機制之**整體實作**(同步引入),皆處理「行端/段末之標點與字」;其分層職責須先
|
||||
釐清,以免重複處理或衝突。**前置**為「功能分級」(見 6.5.0a),因懸掛/垂懸字屬段落級、
|
||||
不應作用於標題/按鈕/單行文本。
|
||||
|
||||
> **實作狀態(2026-06)**:6.5.0a 功能分級、6.5.1 垂懸字(方案 C,預設開)已落地且工作良好。
|
||||
> 6.5.2 行端模式重構為 squeeze|hang|none(squeeze 為現有行端半形、保留並改善);**惟標點懸掛
|
||||
> hang 經實測確認不可行、已全面停用**(仍保留休眠程式碼)。
|
||||
>
|
||||
> **行歸屬判定 bug 修正**(連帶改善 squeeze,保留):原以「行盒 top 差 > EPS=4px」判同/異行,
|
||||
> 但 jz-char 為 inline-block,其行盒 top 與相鄰純文本字形 top 因基線差約 5px、遠小於行高卻超
|
||||
> 舊容差,致同行誤判為下一行。改判「垂直區間」:下一行=`b.top ≥ a.bottom`、上一行=
|
||||
> `b.bottom ≤ a.top`(容 2px 亞像素)。
|
||||
>
|
||||
> **標點懸掛停用之原因**(純負 margin 機制不可行,headless + 真實瀏覽器實測):
|
||||
> 1. 句號等標點墨色偏字框左下角;安全凸出量(< 1 個 CJK 字寬 1em)只把空白半框推出版心、
|
||||
> **墨色仍在版心內**(視覺不懸掛)。
|
||||
> 2. 凸出滿 1em 又**釋放一整字寬**、把下一字拉上本行、與懸掛標點**重疊**。
|
||||
> 3. 懸掛與 inline-block 對齊原子**互斥**(justifyAtoms:true 下負 margin 被 justify 吸收)。
|
||||
> 4. `ResizeObserver` 重排時,「自然佈局標記→施加 margin→佈局再變」致標記**錯亂**。
|
||||
>
|
||||
> 真正的解是原生 CSS `hanging-punctuation`,惟 Chrome 不支援、且與本庫 inline-block 原子衝突。
|
||||
> 故 hang 之 lineedge 程式碼與 CSS 保留休眠,並設**三重防逃逸**(`lineEnd` 鎖死 squeeze、
|
||||
> `modeForChar` 降級、`applyEdge` 之 `HANG_ENABLED=false` 總開關)——縱使下游以任何方式要求亦
|
||||
> 不啟用。詳見 6.5.6。
|
||||
|
||||
#### 6.5.0a 功能分級:文本級 vs 段落級 + 元素標記(前置基礎設施)
|
||||
|
||||
**動機**:懸掛/垂懸字/禁則等為**段落級**功能(依賴行/段布局),不應作用於標題、按鈕、單行
|
||||
或單元素文本;而中西間隙、標點挤压為**文本級**,任意文本片段皆適用且想要。下游須能**方便標記**
|
||||
元素屬哪級。此為貫穿性基礎設施,實作時應提升為正式架構節(§3.x);此處先記計劃。
|
||||
元素屬哪級。此為貫穿性基礎設施(已實作:`Finder.levelFor`/`levelAllows`、各 pass `level`
|
||||
元數據、選項 `level.text`/屬性 `data-jz-level`)。
|
||||
|
||||
**分類(含現有與待加功能)**:
|
||||
|
||||
@@ -527,7 +549,7 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
|---|---|---|---|---|
|
||||
| 禁則(§6.4 jinze) | **斷行層** | 把標點趕到正確的邊(點號/閉類→行末、開類→行首;連續標點不拆) | nowrap `jz-jinze` | 已實作 |
|
||||
| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt) | 已實作(將併入行端模式) |
|
||||
| 標點懸掛(本計劃) | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | 負 `margin` | 待實作 |
|
||||
| 標點懸掛 | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | 負 `margin` | ⛔ 實作後停用(見 6.5.6) |
|
||||
| 垂懸字避免(本計劃) | **斷行層** | 段末行 **CJK 實義字數 ≥ N** | nowrap `jz-orphan` | 待實作 |
|
||||
|
||||
- **正交性**:斷行層(禁則/垂懸字,管「哪些字符在哪行、不可斷」)與渲染層(半形/懸掛,管
|
||||
@@ -541,7 +563,7 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
(更優),把 C 綁定**條件化**(僅末行實義字 ∈ [1,N−1] 才綁),借懸掛 layout pass 之測量協同
|
||||
判定,C 不獨立測。末行實義字 ∈ [1,N−1] 僅垂懸字(換行)可解(懸掛不增實義字)。
|
||||
|
||||
#### 6.5.1 垂懸字避免(方案 C,斷行層,無測量)— 先做
|
||||
#### 6.5.1 垂懸字避免(方案 C,斷行層,無測量)— 已實作
|
||||
|
||||
- **目標**:段末行 CJK 實義字數 ≥ N(預設 2;標點/符號不計)。單行段無效。
|
||||
- **機制**:standalone pass,遍歷作用域段落;自段末向前數 N 個 CJK 實義字(`ruleset.cjk` 判定、
|
||||
@@ -549,8 +571,8 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
(CSS `white-space: nowrap`)。
|
||||
- **保證(數學、免測 layout)**:該整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型/
|
||||
瀏覽器成立。平時無害(末行本多字時不改變),僅末行將塌為 <N 字時「拉字下來」。
|
||||
- **選項**:`orphan?: boolean | { chars?: number }`(feature gate;預設**待定**——建議預設開
|
||||
chars=2;per-block 經 `blocks`/`data-jz`)。lang 無關。
|
||||
- **選項**:`orphan?: boolean | { chars?: number }`(feature gate;**預設開 chars=2**;per-block
|
||||
經 `blocks`/`data-jz`;段落級,`data-jz-level="text"` 子樹跳過)。lang 無關。
|
||||
- **邊界**:段落實義字 < N → 不綁。極窄容器(<N 字寬)→ 整體可能溢出(罕見,文檔提示)。
|
||||
- **order=70**(charify 20 後、layout pass 前;longWord 60 後)。
|
||||
- **與禁則協同**:段末範圍可能含 jinze 已綁之 `jz-jinze`;置於 jinze(40) 後,將 jz-jinze 整體
|
||||
@@ -559,7 +581,11 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
- **測試**:jsdom 結構(末 N 實義字+尾標點入 jz-orphan;符號不計;單行段/實義字<N 不綁);
|
||||
headless 窄容器拖動驗末行恒 ≥N。
|
||||
|
||||
#### 6.5.2 標點懸掛(Hanging,渲染層,layout)
|
||||
#### 6.5.2 標點懸掛(Hanging,渲染層,layout)— ⚠ 已實作後停用
|
||||
|
||||
> **停用(2026-06)**:本節原設計之懸掛經實測**不可行、已全面停用**(原因見 6.5 開頭與 6.5.6;
|
||||
> 三重防逃逸見 6.5.6)。lineedge 之 squeeze 模式(行端半形)保留並改善。下文為原設計記錄,
|
||||
> 供日後改用原生 `hanging-punctuation` 重啟時參考。
|
||||
|
||||
- **目標**:行末 bd-stop/pause/close **右凸**出右緣;行首 bd-open **左凸**出左緣。文字邊緣齊。
|
||||
- **不用 CSS `hanging-punctuation`**:瀏覽器支援差(僅 Safari 較全)+聚珍「原子透明」已對 jz-*
|
||||
@@ -568,45 +594,69 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
`fonts.ready` 重算)。量測各標點 jz-char 是否在行首/行末(同 lineedge `edgeRect`/`firstRect`,
|
||||
跨 jz-* 包裹攀爬)。行末點號/閉類加 `jz-hang-r`→`margin-right: -<凸出量>`;行首開類加
|
||||
`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 量測行末標點是否
|
||||
凸出右緣。
|
||||
- **與現有行端半形整合**(已實作):抽出「行端標點偵測」共用,按 `lineEnd` 模式(由
|
||||
`options.hanging` 解析:開→`hang`、關→`squeeze`)於 `relayout` 加 `jz-half-le`(squeeze)
|
||||
或 `jz-hang-*`(hang)。squeeze 行為不變(回歸保綠)。
|
||||
- **凸出量按類**(headless 實測校準,Noto Sans CJK SC):句末/句內點號(。,等墨色小、留白
|
||||
多)近全形 `--jz-hang-stop`(預設 1em,整體落版心外、正文齊邊);括號引號半形
|
||||
`--jz-hang-bracket`(預設 0.5em,墨色偏側、留白半框)。實測句號右凸 +1.005em、開括號左凸
|
||||
+0.5em,正確。CSS 變數可逐字型調校。
|
||||
- **已 baseline 半形者不懸掛**:jz-char 帶 `jz-half`(開明/半角式之括號等)已收為半形、無留白
|
||||
可凸,JS 端 relayout 跳過(否則負 margin 把墨色整體拉出)。故 SC 開明式之括號維持半形不掛、
|
||||
句末點號(不收半形)才掛。
|
||||
- ⚠ **懸掛與 inline-block 對齊原子互斥(實測,重要約束)**:預設 `justifyAtoms:true`(jz-char
|
||||
為 inline-block)下,懸掛之負 margin 被 justify 重分配吸收、標點**不凸出**(headless 量測
|
||||
凸出量 0);改 `justifyAtoms:false`(jz-char 為 inline)則正確凸出(+1em)。故欲懸掛須設
|
||||
`justifyAtoms:false`。二者皆渲染層、不可兼得(如同 squeeze/hang 互斥)。容器宜留右
|
||||
padding ≥1em 容納凸出標點。
|
||||
- **lang 閘**(已實作):繁體橫排預設不懸掛(`relayout` 之 `modeForChar` 對 `zh-Hant` 降回
|
||||
squeeze,clreq);簡體可掛。`options.hanging` 預設**關**(顯式開)。
|
||||
- **自我應驗**(已解,§6.5 開頭發現 4):採**兩階段** relayout——先 `clearEdge` 清全部行端
|
||||
margin/半形,於自然(無行端效果)佈局量測**全部**標點之行端狀態(不變更 DOM),再一次性
|
||||
套用。每次量測皆見無污染佈局(仿 gapTrim「起始全清」教訓),終態穩定。
|
||||
- **行歸屬判定**(已修,§6.5 開頭發現 1):以「垂直區間」`lowerLine`/`higherLine` 取代舊
|
||||
「top 差 > EPS」,吸收 inline-block 行盒與純文本字形之基線差(~5px),免同行誤判。
|
||||
- **order**:`90 lineEdge`(squeeze/hang 共此 pass)。`revert` 清 `jz-half-le`/`jz-hang-*`。
|
||||
- **跨瀏覽器**:負 margin 一致;須 Chrome/Safari/FF 目視(R1/R2)。headless(Chrome)已驗
|
||||
行末標點凸出右緣、行首凸出左緣、繁體不掛、無中段誤觸。
|
||||
|
||||
#### 6.5.3 行端禁則(§6.4)review
|
||||
|
||||
- 現狀避頭尾已與懸掛協同(禁則趕標點到邊、懸掛凸出邊緣)。review:①連續標點群組 `jz-jinze`
|
||||
在行端懸掛時凸出最外側;②jz-orphan 與 jz-jinze 嵌套;③lineedge 偵測已跨 jz-jinze 攀爬。
|
||||
預期 jinze 本身不需大改。
|
||||
- 禁則避頭尾與 squeeze 行端半形協同(禁則趕標點到邊、行端收半形);jz-orphan 與 jz-jinze 嵌套
|
||||
已處理(邊界字跨 jz-jinze 攀爬)。lineedge 偵測已跨 jz-jinze 攀爬。jinze 本身未大改。
|
||||
(懸掛已停用,故無「懸掛凸出最外側」之協同需求。)
|
||||
|
||||
#### 6.5.4 統一 pass order(增補後)
|
||||
#### 6.5.4 統一 pass order
|
||||
|
||||
`20 charify → 25 jiyaAdj → 40 jinze → 50 spacing → 60 longWord → 70 orphan(垂懸字)→
|
||||
90 lineEdge(行端 squeeze/hang)→ 92 gapTrim`。
|
||||
90 lineEdge(行端 squeeze;hang 已停用)→ 92 gapTrim`。
|
||||
|
||||
#### 6.5.5 實作順序(分步,風險遞增)
|
||||
#### 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 增懸掛、垂懸字檢查)。
|
||||
0. ✅ **功能分級(前置,6.5.0a)**:pass 標 `level`、`finder.levelFor`/`levelAllows`、
|
||||
`data-jz-level`/選項 `level.text`;段落級 pass gate 跳過 text-level 元素。
|
||||
1. ✅ **垂懸字(方案 C)**:standalone pass、order 70、純結構無 layout、預設開 chars=2。工作良好。
|
||||
2. ✅ **lineedge 重構為行端模式**:抽出偵測,squeeze 行為不變、回歸保綠;連帶修「行歸屬判定」
|
||||
bug(垂直區間取代 top 差)。保留。
|
||||
3. ⛔ **懸掛 hang 模式**:實作後經 headless + 真實瀏覽器實測**確認不可行、全面停用**(原因見
|
||||
6.5 開頭 + 6.5.6)。程式碼休眠 + 三重防逃逸。
|
||||
4. ✅ **邊界落地**:純標點末行由 orphan 之尾段綁定(含尾隨標點)全包消除。
|
||||
5. ✅ **文檔/docs/demo**:ARCHITECTURE/README/docs 分支標記懸掛停用;demo(showcase/
|
||||
resize-check)移除懸掛、保留功能分級與垂懸字。
|
||||
|
||||
#### 6.5.6 未決(實作時定)
|
||||
#### 6.5.6 標點懸掛停用記錄與未來路徑
|
||||
|
||||
- 垂懸字預設開關(建議開 chars=2);懸掛 lang 預設(建議關)。
|
||||
- 懸掛凸出量(headless 校準);懸掛自我應驗策略。
|
||||
- jz-orphan 與 jz-jinze 嵌套之邊界字定位。
|
||||
- **停用結論**:純負 `margin` 機制無法視覺懸掛——(1) 句號墨色偏框左下,安全凸出量(< 1em)
|
||||
只推出空白半框、墨色仍在版心內;(2) 凸出滿 1em 釋放一整字寬、拉升後字致重疊;(3) 與
|
||||
inline-block 對齊原子互斥(justifyAtoms:true 下 margin 被 justify 吸收);(4) ResizeObserver
|
||||
重排致兩階段標記錯亂。四者皆實測確認。
|
||||
- **三重防逃逸**(確保下游無法以任何方式啟用):① `index.ts` 鎖 `lineEnd: "squeeze"`(忽略
|
||||
`hanging` 選項);② `lineedge.ts` `modeForChar` 無條件降 squeeze;③ `applyEdge` 之
|
||||
`HANG_ENABLED = false` 總開關。三層任一即足,疊加為冗餘保險。`hanging` 選項仍被接受(API
|
||||
相容)但無效果。單元測試覆蓋 option/data-juzhen/blocks 各路徑皆不產生 jz-hang-*。
|
||||
- **未來路徑**:原生 CSS `hanging-punctuation`(`allow-end`/`first`/`last`)為正解,待 Chrome
|
||||
支援(現僅 Safari),且須解決與 inline-block 原子之衝突(屆時或令行端標點局部退原子化)。
|
||||
hang 之 lineedge 偵測碼、CSS(`jz-hang-*`)、`LineEndMode` 型別皆保留休眠,重啟成本低。
|
||||
|
||||
### 6.6 標點修正(Biaodian normalize)— 後續(opt-in)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user