refactor: 移除標點懸掛(機制級死路)——負 margin 程式碼/CSS/型別一併刪
標點懸掛經實測為**機制級死路**(純 CSS 之寬度守恆矛盾):行端標點盒保持非零寬則墨色
出去多少版心內就空多少(行末實義字無法齊邊);收為零寬則釋放一整字寬、斷行器把下一字
拉上本行與標點重疊(且 justifyAtoms 預設之 inline-block 下負 margin 被 justify 吸收)。
CSS hack 接觸不到斷行器,此恒等式無解。唯一正解為原生 hanging-punctuation(僅 Safari
支援、且 jz-char 為 inline-block 原子非文本,原生懸掛不作用),屬遠期「原生管線」新功能、
與此處無共享,故不保留休眠碼。
移除:
lineedge.ts — jz-hang-* class、HANG_* 常量、applyEdge hang 分支、modeForChar、
HANG_ENABLED 總開關;行端僅餘 squeeze(半形)。
_jiya.css — jz-hang-* 三條規則(margin 半形 fallback 完整保留)。
types.ts — LineEndMode 型別、ResolvedOptions.lineEnd、features.hanging。
index.ts — lineEnd 鎖。
hanging 公開選項保留為 @deprecated no-op(API 相容)。
文檔:README「標點懸掛」節、ARCHITECTURE §6.5(開頭/§6.5.2/§6.5.4/§6.5.5/§6.5.6)、
demo(showcase §三/resize-check)全部由「停用/休眠」改為「已移除」+寬度守恆論證。
測試:防逃逸測試改寫為「hanging 為 no-op、輸出與不傳一致、各路徑不產生 jz-hang-*」。
全綠 101。dist 已無 jz-hang/lineEnd/HANG 殘留。
This commit is contained in:
+55
-78
@@ -511,25 +511,21 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
不應作用於標題/按鈕/單行文本。
|
||||
|
||||
> **實作狀態(2026-06)**:6.5.0a 功能分級、6.5.1 垂懸字(方案 C,預設開)已落地且工作良好。
|
||||
> 6.5.2 行端模式重構為 squeeze|hang|none(squeeze 為現有行端半形、保留並改善);**惟標點懸掛
|
||||
> hang 經實測確認不可行、已全面停用**(仍保留休眠程式碼)。
|
||||
> 6.5.2 行端僅做半形擠壓(squeeze,行首開類收左/行尾閉類收右);**標點懸掛 hang 經實測為機制級
|
||||
> 死路、已移除**(負 margin 之 lineedge 程式碼、CSS、`lineEnd`/`LineEndMode`/`HANG_ENABLED`
|
||||
> 等一併刪除;`hanging` 選項保留為 no-op)。
|
||||
>
|
||||
> **行歸屬判定 bug 修正**(連帶改善 squeeze,保留):原以「行盒 top 差 > EPS=4px」判同/異行,
|
||||
> **行歸屬判定 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。
|
||||
> **標點懸掛移除之根因**(純 CSS 之寬度守恆矛盾,headless + 真實瀏覽器實測):行端標點盒若
|
||||
> 保持非零寬,墨色出去多少、版心內就空出多少(行末實義字無法齊版心右緣);若收為零寬,釋放
|
||||
> 一整字寬、斷行器把下一字拉上本行與標點重疊(且 justifyAtoms 預設之 inline-block 下負 margin
|
||||
> 被 justify 吸收)。**CSS hack 接觸不到斷行器,此恒等式無解**。唯一正解為原生
|
||||
> `hanging-punctuation`(僅 Safari 支援、且 jz-char 為 inline-block 原子非文本,原生懸掛不作用),
|
||||
> 屬遠期「原生管線」新功能、與已移除程式碼無共享。詳見 6.5.6。
|
||||
|
||||
#### 6.5.0a 功能分級:文本級 vs 段落級 + 元素標記(前置基礎設施)
|
||||
|
||||
@@ -573,19 +569,17 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
|---|---|---|---|---|
|
||||
| 禁則(§6.4 jinze) | **斷行層** | 把標點趕到正確的邊(點號/閉類→行末、開類→行首;連續標點不拆) | nowrap `jz-jinze` | 已實作 |
|
||||
| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt) | 已實作(將併入行端模式) |
|
||||
| 標點懸掛 | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | 負 `margin` | ⛔ 實作後停用(見 6.5.6) |
|
||||
| 垂懸字避免(本計劃) | **斷行層** | 段末行 **CJK 實義字數 ≥ N** | nowrap `jz-orphan` | 待實作 |
|
||||
| 標點懸掛 | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | ~~負 `margin`~~ | ❌ 已移除(機制級死路,見 6.5.6) |
|
||||
| 垂懸字避免 | **斷行層** | 段末行 **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] 僅垂懸字(換行)可解(懸掛不增實義字)。
|
||||
- **行端僅半形擠壓**:lineedge 行端處理只做 squeeze(行首開類收左、行尾閉類收右半形)。
|
||||
原計劃之「懸掛」模式已移除(機制級死路,§6.5.6),故無「半形 vs 懸掛」之模式切換。
|
||||
- **垂懸字 vs 懸掛邊界**(歷史記錄):曾研究兩者於「純標點末行」之重疊(懸掛吸回 vs 垂懸字
|
||||
拉字)。懸掛移除後此協同不再適用;方案 C(無測量)**附帶**消除純標點末行即可(副作用,
|
||||
現有益無害)。
|
||||
|
||||
#### 6.5.1 垂懸字避免(方案 C,斷行層,無測量)— 已實作
|
||||
|
||||
@@ -605,44 +599,16 @@ 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)— ❌ 已移除(機制級死路)
|
||||
|
||||
> **停用(2026-06)**:本節原設計之懸掛經實測**不可行、已全面停用**(原因見 6.5 開頭與 6.5.6;
|
||||
> 三重防逃逸見 6.5.6)。lineedge 之 squeeze 模式(行端半形)保留並改善。下文為原設計記錄,
|
||||
> 供日後改用原生 `hanging-punctuation` 重啟時參考。
|
||||
> **已移除(2026-06)**:原計劃之懸掛(行端標點負 margin 凸出版心)經 headless + 真實瀏覽器
|
||||
> 實測確認為**機制級死路**(寬度守恆矛盾,純 CSS 接觸不到斷行器),程式碼與 CSS 已刪。完整
|
||||
> 論證、已移除清單、未來原生路徑見 **§6.5.6**。`hanging` 選項保留為 no-op(API 相容)。
|
||||
|
||||
- **目標**:行末 bd-stop/pause/close **右凸**出右緣;行首 bd-open **左凸**出左緣。文字邊緣齊。
|
||||
- **不用 CSS `hanging-punctuation`**:瀏覽器支援差(僅 Safari 較全)+聚珍「原子透明」已對 jz-*
|
||||
`hanging-punctuation: none`。故**自管負 margin**。
|
||||
- **機制**:layout pass(複用 lineedge `installLayout`:立即跑+`ResizeObserver(root)`+
|
||||
`fonts.ready` 重算)。量測各標點 jz-char 是否在行首/行末(同 lineedge `edgeRect`/`firstRect`,
|
||||
跨 jz-* 包裹攀爬)。行末點號/閉類加 `jz-hang-r`→`margin-right: -<凸出量>`;行首開類加
|
||||
`jz-hang-l`→`margin-left: -<凸出量>`。
|
||||
- **與現有行端半形整合**(已實作):抽出「行端標點偵測」共用,按 `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)已驗
|
||||
行末標點凸出右緣、行首凸出左緣、繁體不掛、無中段誤觸。
|
||||
lineedge 行端處理只餘 **squeeze**(行端半形):量測各 jz-char 是否在行首(開類)/行末(閉類),
|
||||
加 `jz-half-le`(CSS 同 halt)。兩階段免自我應驗(先 `clearEdge`、自然佈局量測全部、再一次套用);
|
||||
行歸屬以「垂直區間」`lowerLine`/`higherLine` 判定(吸收 inline-block 行盒與純文本基線差 ~5px)。
|
||||
order `90 lineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過、須瀏覽器目視。
|
||||
|
||||
#### 6.5.3 行端禁則(§6.4)review
|
||||
|
||||
@@ -653,34 +619,45 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
#### 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 半形)→ 92 gapTrim`。
|
||||
|
||||
#### 6.5.5 實作順序與結果
|
||||
|
||||
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)。程式碼休眠 + 三重防逃逸。
|
||||
2. ✅ **lineedge 行端半形(squeeze)**:量測行首開類/行末閉類加 `jz-half-le`;連帶修「行歸屬
|
||||
判定」bug(垂直區間取代 top 差)。保留。
|
||||
3. ❌ **懸掛 hang**:實作後經 headless + 真實瀏覽器實測確認為**機制級死路、已移除**(負 margin
|
||||
程式碼/CSS/`LineEndMode`/`lineEnd`/`features.hanging` 一併刪;`hanging` 選項保留 no-op)。
|
||||
論證見 6.5.6。
|
||||
4. ✅ **邊界落地**:純標點末行由 orphan 之尾段綁定(含尾隨標點)全包消除。
|
||||
5. ✅ **文檔/docs/demo**:ARCHITECTURE/README/docs 分支標記懸掛停用;demo(showcase/
|
||||
resize-check)移除懸掛、保留功能分級與垂懸字。
|
||||
5. ✅ **行內透明性修復(下游 Bug)**:pill 前標點誤隙(finder 透明)、行內元素整體掉行
|
||||
(core/split 克隆切分)、行端攀爬集派生(T4)等,見 §4.1/§6.4。
|
||||
6. ✅ **文檔/docs/demo**:ARCHITECTURE/README/docs 分支記懸掛移除;demo 同步。
|
||||
|
||||
#### 6.5.6 標點懸掛停用記錄與未來路徑
|
||||
#### 6.5.6 標點懸掛移除記錄與未來路徑
|
||||
|
||||
- **停用結論**:純負 `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` 型別皆保留休眠,重啟成本低。
|
||||
- **移除結論(寬度守恆論證)**:純 CSS 手段(負 margin/相對位移/零寬盒)存在守恆矛盾,
|
||||
機制級不可行。設行末標點墨色須落版心外、且行末實義字須齊版心右緣:
|
||||
- 行末盒**保持非零寬**(負 margin 收一部分、或 `position:relative` 純視覺外移)→ 墨色可移出,
|
||||
但盒仍佔行末寬 → 版心內留等寬空缺,「行末齊邊」不成立(0.5em 實測之「看不出懸掛」即此相)。
|
||||
- 行末盒**收為零寬**(margin-right:-1em 等)→ 釋放一整字寬 → 斷行器把下一字拉上本行與標點
|
||||
重疊(headless 實測每標點疊一字);且 `justifyAtoms:true` 之 inline-block 下負 margin 被
|
||||
justify 吸收。
|
||||
|
||||
**墨色出去多少、版心內就空出多少;想不空就得讓斷行器重排這塊寬度,而 CSS hack 接觸不到
|
||||
斷行器。** 補償墊片、兩階段試錯都改變不了此恒等式,只會重蹈 ResizeObserver 標記錯亂之覆轍。
|
||||
- **已移除之物**:`lineedge.ts` 之 `jz-hang-*` class/`HANG_*` 常量/`applyEdge` hang 分支/
|
||||
`modeForChar`/`HANG_ENABLED`;`_jiya.css` 之 `jz-hang-*` 規則;`types.ts` 之 `LineEndMode`/
|
||||
`ResolvedOptions.lineEnd`/`features.hanging`;`index.ts` 之 `lineEnd` 鎖。行端僅餘半形擠壓
|
||||
(`jz-half-le`)。`hanging` 公開選項保留為 **@deprecated no-op**(API 相容;測試覆蓋
|
||||
option/data-juzhen/blocks 各路徑皆不產生 jz-hang-* 且輸出與不傳一致)。
|
||||
- **未來路徑(與已移除碼無共享)**:原生 CSS `hanging-punctuation` 為唯一正解,惟 Chrome/
|
||||
Firefox 至今不支援(僅 Safari);且本庫 `jz-char` 為 inline-block 原子、非文本,原生懸掛
|
||||
不作用之。未來若做,須是**繞開 jz-char 的「原生管線」新功能**——`text-spacing-trim` 接管
|
||||
擠壓 + `hanging-punctuation` 接管懸掛,按 `CSS.supports` 能力門控,整體退出 jz-char 機制。
|
||||
與已移除之負 margin 程式碼**沒有一行可複用**,故不保留休眠碼。
|
||||
|
||||
### 6.6 標點修正(Biaodian normalize)— 後續(opt-in)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user