diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 404db16..c6443bf 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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;符號不計;單行段/實義字 **停用(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) diff --git a/README.md b/README.md index 26f20fa..5bfff1d 100644 --- a/README.md +++ b/README.md @@ -146,23 +146,22 @@ createJuzhen({ createJuzhen({ level: { text: "h1,h2,h3,button,.ui" } }); // 選擇器一鍵標記 ``` -### 標點懸掛(`hanging`)— 已評估停用 +### 標點懸掛(`hanging`)— 已移除 -原計劃之標點懸掛(行端標點凸出版心)經 headless + 真實瀏覽器實測**確認不可行、已全面停用**。 -`hanging` 選項仍被接受(API 相容)但**無任何效果**——縱使下游以 `hanging:true`/`data-juzhen`/ -`blocks` 等任何方式要求,亦不會啟用(行端恒 squeeze;三重防逃逸:`lineEnd` 鎖死、`modeForChar` -降級、`applyEdge` 總開關)。 +原計劃之標點懸掛(行端標點凸出版心)經實測為**機制級死路、已移除**(負 margin 程式碼與 +CSS 皆刪)。`hanging` 選項仍被接受(API 相容)但**無任何效果**——`hanging:true`/ +`data-juzhen`/`blocks` 任一要求皆被靜默忽略,行端僅做半形擠壓。 -**為何不可行**(純負 `margin` 機制): +**為何機制級不可行**(純 CSS 之寬度守恆矛盾):行端標點盒若保持非零寬,則「墨色出去多少、 +版心內就空出多少」(行末實義字無法齊版心右緣);若收為零寬,則釋放一整字寬、斷行器把下一字 +拉上本行與標點重疊(且 `justifyAtoms` 預設之 inline-block 下負 margin 被 justify 吸收)。 +**墨色出去多少版心內就空多少;想不空就得讓斷行器重排這塊寬度,而 CSS hack 接觸不到斷行器**。 +補償墊片、兩階段試錯都改變不了此恒等式。 -1. 句號等標點墨色偏字框左下角;安全凸出量(< 1 個 CJK 字寬 1em)只把空白半框推出版心、 - **墨色仍在版心內**(視覺不懸掛)。 -2. 凸出滿 1em 又**釋放一整字寬**、把下一字拉上本行、與懸掛標點**重疊**。 -3. `ResizeObserver` 重排時,「自然佈局標記→施加 margin→佈局再變」致標記**錯亂**。 - -真正的解是原生 CSS `hanging-punctuation`,惟 **Chrome 至今不支援**(僅 Safari 較全),且與本庫 -`inline-block` 對齊原子結構衝突。故 hang 之程式碼與 CSS **保留休眠**,俟原生支援成熟再議。 -詳見 `ARCHITECTURE.md §6.5.6`。 +唯一正解為原生 CSS `hanging-punctuation`,惟 **Chrome/Firefox 至今不支援**(僅 Safari),且 +本庫之 `jz-char` 為 inline-block 原子、非文本,原生懸掛不作用——未來若做,須是繞開 `jz-char` +的「原生管線」新功能(`text-spacing-trim` + `hanging-punctuation`,按能力門控),與已移除之 +程式碼無共享。詳見 `ARCHITECTURE.md §6.5.6`。 --- diff --git a/demo/resize-check.html b/demo/resize-check.html index 2f5fc93..af2b266 100644 --- a/demo/resize-check.html +++ b/demo/resize-check.html @@ -77,7 +77,7 @@