702 lines
45 KiB
Markdown
702 lines
45 KiB
Markdown
# 聚珍(Juzhen)
|
||
|
||
> 現代簡/繁中文網頁排版套件。Han.css 之現代化重寫,沿其功能版圖,並針對
|
||
> 數種情況作調整:**複製乾淨**(中西間隙不入剪貼板)、**依 `lang` 全角/開明
|
||
> 自動切換**、**`justify` 下句末標點只佔一份間距**、**跨瀏覽器一致**
|
||
> (Chrome/Edge/Safari/WebKit/Firefox)。
|
||
|
||
倉庫名仍為 `cjk-autospace`;公開 API 為 `createJuzhen` / 全域 `Juzhen`;注入之
|
||
自訂元素前綴 `jz-`。消費端契約見本文的[用法](#用法)與[標記說明](#行內元素三分類透明隔離整體),
|
||
升級影響見 [`UPDATES.md`](./UPDATES.md)。完整設計見 [`ARCHITECTURE.md`](./ARCHITECTURE.md)。
|
||
|
||
2026-10-02 的 Web/CSS 邊界與生命週期修復已推送至 `main`;
|
||
[修復提交 `b1a16c4`](https://git.commilitia.net/admin/cjk-autospace/commit/b1a16c49698dc418aa733e41a04bdbc9922927be)
|
||
包含配套構建產物。套件版本仍為 `2.0.0`、共享規則仍為 `1.0.0`,未進行 npm 發佈;
|
||
使用倉庫產物時須一併更新 JS 與 CSS,具體升級影響見上述消費端更新記錄。
|
||
|
||
> 本版(2.x)為從零重寫。v1 之 `createCjkAutospace` 相容層**已移除**(最後一批
|
||
> v1 消費端已遷移 v2 API);v1 原始碼保存於 `v1-autospace` 分支。
|
||
|
||
---
|
||
|
||
## 安裝與構建
|
||
|
||
```sh
|
||
npm install
|
||
npm run build # esbuild → dist/juzhen.{mjs,iife.js,css};tsc → dist/index.d.ts
|
||
npm test # jsdom DOM 正確性測試
|
||
node --test test/shared-rules.test.mjs # 共享規則 Schema/黃金向量/Web 漂移守門
|
||
npm run test:wkwebview # macOS 原生 WKWebView 版面/observer E2E(需 Xcode 與 GUI 會話)
|
||
```
|
||
|
||
產物(`dist/`,已入庫):
|
||
|
||
| 檔 | 用途 |
|
||
|---|---|
|
||
| `dist/juzhen.mjs` | ESM,供打包器/TypeScript |
|
||
| `dist/juzhen.iife.js` | IIFE,全域 `Juzhen`,原生 `<script>` 直跑 |
|
||
| `dist/juzhen.css` | 合併樣式(**必須引入**,視覺尺寸全在此) |
|
||
| `dist/index.d.ts` | TypeScript 型別宣告 |
|
||
| `rules/juzhen-rules.json` | 平台中立之共享排版規則(Web/XeLaTeX 共用) |
|
||
| `rules/golden-vectors.json` | 跨實作語義黃金向量 |
|
||
|
||
---
|
||
|
||
## 共享規則契約
|
||
|
||
`rules/` 是 Web/CSS 與並行 XeLaTeX 實作之唯一規則權威源;初版規則版本為 `1.0.0`。
|
||
規則以 Unicode code-point 區間、語言政策、標點寬度、間隙、禁斷與段末約束描述「應發生
|
||
什麼」,不規定 DOM/CSS、TeX node/glue/penalty 或其他 adapter 的實作機制。
|
||
|
||
套件提供四個唯讀子路徑:
|
||
|
||
```js
|
||
import rules from "cjk-autospace/rules" with { type: "json" };
|
||
import schema from "cjk-autospace/rules/schema" with { type: "json" };
|
||
import vectors from "cjk-autospace/rules/vectors" with { type: "json" };
|
||
import vectorsSchema from "cjk-autospace/rules/vectors-schema" with { type: "json" };
|
||
```
|
||
|
||
規則版本、Schema 版本、變更程序與 adapter 邊界詳見 [`rules/README.md`](./rules/README.md)。
|
||
任何規則變更必須同步更新 canonical JSON、Schema、黃金向量,並讓 Web 與 XeLaTeX adapter
|
||
同時通過相同語義向量;任一實作不得單邊分叉規則。
|
||
|
||
---
|
||
|
||
## 用法
|
||
|
||
呼叫端接入時須做兩個獨立選擇:
|
||
|
||
1. **元件如何參與排版**:依「行內元素三分類」選 pill(保護內部、處理交界)、
|
||
isolate(內部也排版、保持盒子完整)或 avoid(完全排除)。
|
||
2. **文字需要哪些功能**:依「功能分級」選 text(標題/UI 文字)或 paragraph(正文)。
|
||
|
||
完整操作範例與衝突規則見下方兩節;不要用其中一種分類代替另一種。
|
||
|
||
### 原生 `<script>`
|
||
|
||
```html
|
||
<link rel="stylesheet" href="dist/juzhen.css">
|
||
<main class="prose juzhen" lang="zh-Hant">需要排版的正文。</main>
|
||
<script src="dist/juzhen.iife.js"></script>
|
||
<script>
|
||
const jz = Juzhen.createJuzhen({ scope: { include: ".prose" } });
|
||
jz.render(); // 對 document.body 套用(僅命中 .prose 子樹)
|
||
// jz.revert(); // 還原
|
||
</script>
|
||
```
|
||
|
||
### ESM/TypeScript
|
||
|
||
```ts
|
||
import { createJuzhen } from "cjk-autospace";
|
||
import "cjk-autospace/css";
|
||
|
||
const jz = createJuzhen({ lang: { default: "zh-Hant" } });
|
||
jz.render(document.querySelector("main"));
|
||
```
|
||
|
||
**JS 與 CSS 缺一不可**:JS 只注入 `jz-*` 結構,所有視覺尺寸(間隙寬度、擠壓量、
|
||
`justify` 原子化)都在 `juzhen.css`;字型由呼叫端 CSS 設定。
|
||
|
||
### 動態頁面:增量恢復(局部變化免全樹重掃)
|
||
|
||
`render(root)`/`revert(root)` **皆可傳入局部 Element**——頁面局部變化時,只重處理受影響
|
||
之子樹即可,成本 O(該塊) 而非 O(全文)(實測編輯 400 段中之一段,局部恢復較全樹快數百倍)。
|
||
|
||
```ts
|
||
const jz = createJuzhen();
|
||
jz.render(document.body); // 初次全樹
|
||
|
||
// 局部變化後(手動):
|
||
jz.rerender(changedBlock); // = revert(changedBlock) + render(changedBlock)
|
||
jz.render(newlyAppendedBlock); // 新增之塊:直接 render(無須 revert)
|
||
|
||
// 自動(opt-in):觀察 DOM,內容/分類屬性變化時自動只重處理受影響之最近塊。
|
||
jz.observe(document.body); // 之後 DOM 變動即自動增量恢復
|
||
// jz.disconnect(); // 停止觀察
|
||
```
|
||
|
||
- **`rerender(root?)`**:`revert` 後 `render` 同一(子)樹(便利方法)。
|
||
- **`observe(root?)`/`disconnect()`**:以 `MutationObserver` 自動增量。juzhen 自身之注入
|
||
於處理期間暫停觀察、**絕不自觸發**;新增之塊直接 render(免全容器重掃)。需 `MutationObserver`
|
||
(瀏覽器/jsdom 有;無則 no-op)。
|
||
觀察根可尚未掛入 document;更新按目前是否仍在該根內判定。同批新增後移出根的節點,
|
||
或已離開根的舊變更目標,不會被此 observer 排版。
|
||
搬入的既有塊先撤回本實例於舊父環境的產物,再依新祖先的級別、功能及語言重排;
|
||
純新增塊仍直接 render。插入子樹的祖先快取也會失效。
|
||
手動 `render`/`revert`/`rerender` 暫停觀察前會先結算已排隊的變更(可能觸發其他區塊
|
||
的排版),再執行本次手動操作;因此局部回退仍最後生效,也不會丟失兄弟區塊更新。
|
||
結算中的 observer 錯誤仍由宿主非同步報告,手動操作自己的錯誤則照常同步拋出。
|
||
- **`invalidate(root?)`**:清該子樹之內部快取。**`revert` 已自動失效其子樹**,故僅在「改了既有
|
||
元素之分類相關屬性(`class`/`data-jz-*`/`lang` 等)**但不走 `revert`**」時才需顯式調用,以
|
||
免下次 render 沿用陳舊分類。內容(子節點/文本)變化不改分類、無需 `invalidate`(但須
|
||
`revert`+`render` 該塊方能重處理)。
|
||
|
||
> **元素變化之正確性**:實例內部對「元素分類(透明/pill/隔離/avoid)/功能級別/功能開關」
|
||
> 快取以加速跨 render 復用(快取隨實例持久、非每次 render 重建)。**改變既有元素之上列屬性後,
|
||
> 須經 `revert`/`rerender`/`invalidate`/`observe` 任一使該子樹快取失效**,否則沿用舊分類。
|
||
> 新增/移除元素與純內容編輯無此顧慮(新元素重新計算、移除者自動回收)。
|
||
|
||
> **增量之粒度為「塊」**:換塊內容(`textContent`/`innerHTML`)、增刪塊、改分類屬性——皆與
|
||
> 全量重繪一致。**不支持**對聚珍已切分之文本節點做外科式 `characterData` 直改(如持有 render
|
||
> **前**之 `p.firstChild` 引用、事後直改其 `.data`):render 後原文本節點已被 `jz-*` 切分/
|
||
> 替換,該引用已非原整段,直改單一碎片會令 `revert` 合併出之內容偏離作者意圖。此為後處理排版
|
||
> 器之固有限制(任何外科式文本 patch 之框架皆會與注入後之 DOM 失步)、非缺陷——聚珍對當前
|
||
> DOM 之處理始終自洽。欲改文字,以塊為單位替換內容即可。
|
||
|
||
> **`revert` 之範圍**:`revert` 乾淨移除聚珍注入之全部結構(`jz-*`/`data-jz-*`),DOM 回到**邏輯
|
||
> 等價**態;但**不還原** `spacing` 依契約規範化之贅餘作者空格(CJK↔西文吸收為 margin 間隙、
|
||
> CJK↔CJK 剝除;見上表「中西間隙」)。即 `render→revert` 對含 CJK↔西文作者空格之源,還原後語義
|
||
> 一致、無 `jz-*` 殘留,唯該空格已正規化(此差異穩定、不隨反覆 render/revert 累積)。若消費端需
|
||
> 保留原始作者空格以供編輯,應自存原文、勿以 `revert` 當「還原到未處理源碼」。
|
||
|
||
外部克隆/替換/刪除跨節點載體後,`revert` 保留作者當前內容,不補回已刪文字;
|
||
克隆不繼承本實例所有權。欲修改原文,先 `revert` 再修改,或直接替換整塊內容。
|
||
其他聚珍實例合法撤回包裹時,來源記錄會沿受控解包鏈更新,任意回退次序不丟 base/VS。
|
||
|
||
---
|
||
|
||
## 切片 1 已實作功能
|
||
|
||
| 功能 | 說明 |
|
||
|---|---|
|
||
| **中西間隙** `spacing` | CJK↔西文邊界以 copy-clean 之 `jz-hws` 與 `margin-right` 留隙;**不插入空白字元**,故剪貼板不多出空格。唯跨節點 IVS 產物以 `::after` 產生的 `U+200B` 斷行機會設 `user-select:none`;它不進入 DOM/`textContent`。「西文」涵蓋拉丁(含擴充 A/B,拼音聲調 `ā ǎ ē`)、希臘、西里爾、數字、貨幣(`€ ₩ ₿`)、數學運算符等;CJK 標點(`。,「」()……` 等)不誤判為西文、不生隙。含跨樣式行內邊界與 pill/隔離元素邊界(`code`/`kbd`/`.katex` 與 `mark`,兩端補隙口徑一致;pill 緊鄰標點時穿越 `jz-jinze` 仍補間隙;pill 跨行右隙、間隙落行末之自我應驗皆已修)。**pill 之邏輯鄰字為標點時(如 `漢字。<code>`)不誤加間隙**(標點非 pill 鄰字;`jz-char`/`jz-inner` 透明穿越,與 `collectRuns` 一致)。贅餘作者空格一律正規化:CJK↔西文吸收為受管間隙、CJK↔CJK(含跨 inline 標籤)剝除不留隙。 |
|
||
| **長英文詞** `longWord` | `[A-Za-z]{6,}` 包入 `<span lang="en">`,配合 CSS `hyphens:auto` 原生斷詞(DOM 無連字符 → 複製不污染)。 |
|
||
| **標點擠壓** `jiya` | 標點 charify 為 `jz-char`+`bd-*` class。三機制:①baseline 政策(全角式不擠/開明式擠句內點號+括號引號/半角式全擠);②行內連續擠壓(相鄰標點**雙側獨立**收——各標點若其有空白之側緊鄰另一標點即收半,故 `,「` 全角式與開明式皆 1em、`。」` 全角式 1.5em/開明式 1em;**唯句末點號接開類為句界**——`。「`/`?「` 保留句末點號右空白、僅收開類,全角式與開明式皆 1.5em;透明穿越 `strong`/`em` 等行內邊界);③行端擠壓(`lineedge` layout pass)。半形預設以字型 `halt` 渲染(按字形正確定位,居中字形如 `!?`/繁體 `。,` 不重疊;不依賴 `text-spacing-trim`)。**字型缺 `halt` 時可切 `margin` 後備模式**(見下節)。 |
|
||
| **禁則** `jinze` | 行首行末避頭尾:閉類/點號綁前一字(不置行首)、開類綁後一字(不置行末),只隔離邊界字故長引文中段仍可斷。亦為 `justify` 綁定載體。邊界字落於**透明**行內樣式元素(`strong`/`em` 等)內時**克隆切分**只綁邊界詞元(避免整個元素入 `nowrap` 致行末整體掉行、上行留巨大空隙),切出之克隆標 `data-jz-kind="splitoff"`、`revert` 時自動併回原元素;不可切元素(`a`/`abbr`/`ruby`/帶 `id`,以及 **pill/隔離**——見「行內元素三分類」)退化為**整元素綁定**——pill(如行內公式 `.katex`)即作為原子鄰字單位整體綁入 `jz-jinze`,絕不下潛肢解其內部。兩側須符合作用域、paragraph 級別與 `jinze` 功能閘;不可切時也不把 avoid/text/off 後代整綁帶入。 |
|
||
| **垂懸字避免** `orphan` | 段末可處理的連續區域 CJK 實義字 ≥ N(預設 2;標點/符號不計,完整碼點及 base+VS 算一字)。方案 C(斷行層、無測量):把段末「第 N 個實義字起至段末」包入 `jz-orphan`(`white-space:nowrap`),整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型成立。單行段無害。邊界字落於行內元素內時同樣**克隆切分**(只綁末 N 字、避免整個長元素入 `nowrap` 溢出容器;機制同禁則)。**預設開**(`orphan:false` 關,`{chars:n}` 調)。段落級功能。avoid/text/off/`isSkipped` 區域中斷計數,只綁其後的合法尾部;不足 N 字或不可切容器含排除內容時不綁。 |
|
||
| **功能分級** `level` | 功能分文本級(中西間隙、標點擠壓——任意文本片段適用)與段落級(禁則、垂懸字、長詞——需行/段布局)。塊級元素預設全功能;`data-jz-level="text"` 子樹或選項 `level:{text:"選擇器"}`(如標題/按鈕)僅跑文本級。 |
|
||
| **justify 單份間距** | 每個 `jz-char` 為 `inline-block` 原子項,句末標點只貢獻一份對齊伸縮量。 |
|
||
|
||
相容選項 `hanging`/`biaodian`/`emphasis`/`ruby` 仍被接受,但當前均為
|
||
**no-op**:沒有對應 pass、DOM 產物或 CSS。後三者僅屬明確標記的未來設計;
|
||
`hanging` 之舊負 margin 實作已移除,僅保留 API 相容。
|
||
|
||
---
|
||
|
||
## 選項
|
||
|
||
```ts
|
||
createJuzhen({
|
||
lang: {
|
||
default: "zh-Hant", // 無 lang 祖先時之預設
|
||
style: "kaiming", // 全域政策一鍵覆寫(quanjiao|kaiming|banjiao)
|
||
policy: { "zh-Hant": "quanjiao", "zh-Hans": "kaiming" }, // 逐 lang(最高優先)
|
||
},
|
||
scope: {
|
||
root: document.body, // 處理根;render(root) 可逐次覆寫
|
||
include: ".prose", // 啟用選擇器(opt-in):僅處理命中子樹
|
||
avoid: "blockquote.raw", // 追加排除選擇器
|
||
},
|
||
spacing: true,
|
||
longWord: { minLength: 6, lang: "en" },
|
||
jiya: true, // 或物件:{ halfWidth: "halt" | "margin", bias }
|
||
jinze: true,
|
||
orphan: true, // 垂懸字避免(預設開);或 { chars: 2 };false 關
|
||
level: { text: "h1,h2,h3,button,.ui" }, // 此選擇器子樹僅跑文本級功能(見下)
|
||
pillSelector: "code, kbd, .katex", // 整體(pill)元素(見「行內元素三分類」)
|
||
isolateSelector: "mark", // 內外隔離元素(預設 mark;"" 停用)
|
||
justifyAtoms: true, // jz-char/jz-jinze 以 inline-block 作對齊原子;
|
||
// 分頁器(Paged.js)下設 false 改 inline(見下)
|
||
blocks: [ // 分塊功能選擇
|
||
{ selector: ".code-comment", features: [ "spacing" ] },
|
||
],
|
||
});
|
||
```
|
||
|
||
### 全部選項速查(含少見但強力者)
|
||
|
||
完整選項一覽。**「標籤名」型別只吃大寫標籤名陣列**(精確比對 `nodeName`),與
|
||
`pillSelector`/`isolateSelector`/`scope.*`(**CSS 選擇器**,可用 class/屬性)**不同機制**,
|
||
勿混。斜體「詳見」指向本文檔展開該項之小節。
|
||
|
||
**語言與擠壓政策**(優先序:**逐元素** `data-jz-style`/`stylePolicy` > `policy` > `style` > 逐語言內建)
|
||
|
||
| 選項 | 型別 | 預設 | 作用 |
|
||
|---|---|---|---|
|
||
| `lang.default` | string | `"zh-Hant"` | 無 `lang` 祖先時之預設語言標籤 |
|
||
| `lang.style` | `quanjiao|kaiming|banjiao` | 逐語言內建(繁→全角、簡→開明) | **全域政策一鍵覆寫** |
|
||
| `lang.policy` | `Record<lang, style>` | `{}` | 逐 lang 政策 |
|
||
| `lang.stylePolicy` | `{selector,style}[]` | `[]` | **逐元素樣式覆寫**(選擇器命中之子樹用指定樣式;如 `[{selector:"h1,button,.ui",style:"banjiao"}]`);配合 `data-jz-style` 屬性,優先於 lang |
|
||
|
||
**作用域**
|
||
|
||
| 選項 | 型別 | 預設 | 作用 |
|
||
|---|---|---|---|
|
||
| `scope.root` | Element|選擇器 | `document.body` | 處理根(`render(root)` 可逐次覆寫) |
|
||
| `scope.include` | 選擇器 | `null` | opt-in:僅處理命中此選擇器之子樹 |
|
||
| `scope.avoid` | 選擇器 | `null` | 追加排除子樹 |
|
||
|
||
**功能開關 / 對齊 / 分級 / 分塊**(詳見「已實作功能」表及各節)
|
||
|
||
| 選項 | 型別 | 預設 | 作用 |
|
||
|---|---|---|---|
|
||
| `spacing` | bool | `true` | 中西間隙 |
|
||
| `longWord` | bool|`{minLength,lang}` | `true`(`{6,"en"}`) | 長英文詞斷字;`minLength` 須為 ≥2 的有限安全整數,無效值回退 6 |
|
||
| `jiya` | bool|`{halfWidth,bias}` | `true` | 標點擠壓(*詳見「標點半形」*) |
|
||
| `jinze` | bool | `true` | 禁則避頭尾 |
|
||
| `orphan` | bool|`{chars}` | `true`(`{chars:2}`) | 垂懸字避免;`chars` 須有限且 ≥1,向下取整,無效值回退 2 |
|
||
| `justifyAtoms` | bool | `true` | inline-block 對齊原子(分頁器設 `false`) |
|
||
| `level.text` | 選擇器 | `null` | 命中子樹僅跑文本級(*詳見「功能分級」*) |
|
||
| `blocks` | `{selector,features}[]` | `[]` | 分塊功能選擇 |
|
||
| `charClass` | `{cjk,western,biaodian}` | — | 追加字符分類(*詳見「自定義字符分類」*) |
|
||
|
||
**行內三分類與排除**(*詳見「行內元素三分類」*)
|
||
|
||
| 選項 | 型別 | 預設 | 作用 |
|
||
|---|---|---|---|
|
||
| `pillSelector` | CSS 選擇器 | `"code, kbd"` | 整體元素(內部不可見、兩端補隙) |
|
||
| `isolateSelector` | CSS 選擇器 | `"mark"`(`""` 停用) | 隔離元素(內部照常、兩端補隙) |
|
||
| `styleInlines` | 標籤名[] | 27 個標準行內標籤 | **透明穿越白名單**(未列之行內標籤=邊界) |
|
||
| `blockTags` | 標籤名[] | 36 個塊標籤 | 塊級邊界集 |
|
||
| `skipTags` | 標籤名[] | `PRE,SAMP,TT,VAR,SCRIPT,STYLE,TEXTAREA,INPUT` | avoid 標籤集(**不含 CODE/KBD**,由 pill 涵蓋) |
|
||
| `skipAttribute` | string | `null` | 自訂「跳過」屬性名(命中即 avoid) |
|
||
| `isSkipped` | `(node)=>bool` | `null` | 自訂跳過謂詞(函數級排除鉤子) |
|
||
|
||
**HTML 屬性**(各屬性之繼承/衝突規則見對應小節)
|
||
|
||
| 屬性 | 值 | 作用 |
|
||
|---|---|---|
|
||
| `data-jz-level` | `text|paragraph` | 逐子樹功能分級 |
|
||
| `data-jz-style` | `quanjiao|kaiming|banjiao` | 逐子樹標點樣式覆寫(近祖先勝、同層優先於選擇器,整體優先於 lang) |
|
||
| `data-juzhen` | 功能名(空格分隔) | 白名單:僅列出之功能生效 |
|
||
| `data-juzhen-off` | 功能名 | 黑名單:停用列出之功能 |
|
||
| `data-jz-skip` | (存在即可) | 標記元素為 avoid(內外皆不處理) |
|
||
| `data-jz-halfwidth` | `halt|margin` | 逐子樹半形渲染模式 |
|
||
|
||
**相容 no-op 選項**:`hanging`/`biaodian`/`emphasis`/`ruby` 均可傳入,但當前不註冊
|
||
pass,也不產生 DOM/CSS 效果。注意頂層 `biaodian` 與 `charClass.biaodian`(已實作的
|
||
標點子類擴充表)**同名異義**。
|
||
|
||
### 逐元素標點樣式(`data-jz-style`/`stylePolicy`)
|
||
|
||
標點樣式(`quanjiao` 全角式/`kaiming` 開明式/`banjiao` 半角式)預設由 `lang` 決定,但可
|
||
**逐元素/逐範圍覆寫**——典型:**非段落/UI 元素(標題、按鈕)用半角式,段落用全角/開明**。
|
||
|
||
```ts
|
||
// 選擇器批量:h1/h2/button 及 .ui 子樹用半角式,其餘(段落)走 lang
|
||
createJuzhen({
|
||
lang: {
|
||
default: "zh-Hant", // 段落 → 全角式(繁)
|
||
stylePolicy: [ { selector: "h1, h2, button, .ui", style: "banjiao" } ],
|
||
},
|
||
});
|
||
```
|
||
|
||
```html
|
||
<!-- 或以 HTML 屬性逐元素/逐範圍標記(標於元素或任一祖先,該子樹生效) -->
|
||
<h1 data-jz-style="banjiao">標題:半角標點更緊湊</h1>
|
||
<p>段落沿用 lang 之全角/開明式。</p>
|
||
```
|
||
|
||
優先序:**逐元素覆寫**(`data-jz-style` 屬性/`stylePolicy` 選擇器,**近祖先勝、同層屬性優先於
|
||
選擇器**)> `lang.policy` > `lang.style` > 內建逐語言預設。屬性值非法(拼錯)則忽略、回退
|
||
lang。半角式渲染沿用既有 `halt`/`margin` 機制(見下節),本功能只做「按元素選樣式」、無新增
|
||
渲染邏輯。
|
||
|
||
### 行內元素三分類:透明/隔離/整體
|
||
|
||
**先決定哪些內容交給聚珍處理,再決定它與前後文字如何銜接。** 呼叫端須主動選擇
|
||
pill/isolate/avoid;不要把「保護內部內容」一律寫成跳過,也不要只依元素外觀猜分類。
|
||
普通 `strong`/`em`/`span` 等只改文字樣式的元素通常保持透明,無須額外標記。
|
||
|
||
| 你的需求 | 選擇 | 元素內部 | 與外部文字的交界 | 如何啟用 |
|
||
|---|---|---|---|---|
|
||
| 內部由程式碼/公式等元件自己管理,但仍要接入正文排版 | **pill(整體)** | 不進入、不改寫內部文字、不拆分元素 | 視為整體;依相鄰字符規則補間隙,禁則需要時連同整個元素綁定 | `pillSelector` |
|
||
| 內部是普通文字,仍要補間隙、處理標點,但盒子不能被拆開 | **isolate(隔離)** | 照常排版,內部自成文字脈絡 | 與外部隔開;依相鄰字符規則補間隙,禁則需要時整體綁定 | `isolateSelector` |
|
||
| 這一區完全不交給聚珍,連交界間隙也自行管理 | **avoid(排除)** | 不處理整個子樹 | 停止跨界排版,不替它建立 pill/isolate 式交界間隙 | `data-jz-skip` 或 `scope.avoid` |
|
||
| 只是粗體、斜體等樣式,文字仍屬同一句 | **透明(一般文字)** | 照常排版 | 前後文字連續判斷;安全的樣式元素可能被拆分後於回退時合併 | 預設 `styleInlines` 中的標籤 |
|
||
|
||
**pill 與 avoid 都保護內部,差別在外部:pill 仍參與正文交界排版;avoid 完全退出。**
|
||
**pill 與 isolate 都保持元素完整,差別在內部:pill 不處理;isolate 照常處理。**
|
||
這裡的「完整」指聚珍不拆分元素,不代表替任意元件提供 CSS `white-space:nowrap`。
|
||
交界也不是無條件各加一格:須符合相鄰字符規則,例如句號緊接 pill 不會因此多補間隙。
|
||
|
||
#### 主動指定你的元件
|
||
|
||
以下 class 名稱由呼叫端自行命名,透過選擇器接入;它們不是聚珍內建標記。
|
||
`data-jz-skip` 則是內建屬性,存在即排除,寫成 `data-jz-skip="false"` 仍會排除。
|
||
|
||
```html
|
||
<article class="juzhen" lang="zh-Hant">
|
||
<p>執行<code>npm test</code>後查看結果。</p>
|
||
<p>公式<span class="formula">x+y</span>由公式元件管理。</p>
|
||
<p>狀態<mark class="badge">版本API,已更新</mark>仍要處理內部中西間隙與標點。</p>
|
||
<p>外部元件<span data-jz-skip>原樣保留 API ,</span>的交界由呼叫端自行處理。</p>
|
||
<div class="raw-widget">這個子樹也完全排除。</div>
|
||
</article>
|
||
```
|
||
|
||
```js
|
||
const jz = Juzhen.createJuzhen({
|
||
scope: { include: ".juzhen", avoid: ".raw-widget" },
|
||
pillSelector: "code, kbd, .formula, .cite-ref",
|
||
isolateSelector: "mark, .badge",
|
||
});
|
||
jz.render();
|
||
```
|
||
|
||
- **pill 預設只有 `code, kbd`**。公式 `.katex`、引用標 `.cite-ref` 等須自行加入。
|
||
自訂 `pillSelector` 是取代預設選擇器;要保留 code/kbd,須像上例一併列出。
|
||
- **isolate 預設為 `mark`**。自訂 `isolateSelector` 同樣取代預設;傳 `""` 停用此選擇器。
|
||
只有背景色的 `span` 不會因外觀自動變成 isolate,應加 class 並列入選擇器。
|
||
- **avoid** 可用 `data-jz-skip` 或 `scope.avoid` 主動指定;另外 `skipTags`、SVG、可編輯內容、
|
||
`skipAttribute` 和 `isSkipped` 也可能使元素被排除。子元素不能以 pill/isolate 或
|
||
`data-jz-level="paragraph"` 重新打開已被排除的祖先子樹。
|
||
- **同一元素多重命中時:avoid > pill > isolate**。例如同時命中 `.formula` 與 `.badge`
|
||
的元素採 pill;再加 `data-jz-skip` 則採 avoid。請避免互相矛盾的選擇器。
|
||
- 從 `pillSelector` 移除 `code`/`kbd` 不等於排除它們:預設 `skipTags` 不含二者。
|
||
若要完全跳過,須另列入 `scope.avoid`/`skipTags`。
|
||
- 未列在 `styleInlines` 的未知行內標籤會形成隔離式邊界,不會自動當透明樣式。
|
||
`styleInlines` 接受**大寫標籤名陣列**,與 pill/isolate 的 CSS 選擇器不同。
|
||
內部結構由第三方管理的元件宜明確指定 pill 或 avoid,不要為了穿越它而加入透明名單。
|
||
|
||
分類不等於功能級別。isolate 內部是否執行禁則、避孤字等,仍由下節的 text/paragraph
|
||
及功能開關決定;pill/avoid 內部則根本不進入文字處理。
|
||
|
||
### 分頁器(Paged.js 等)相容:`justifyAtoms`
|
||
|
||
預設 `jz-char`/`jz-jinze` 為 `inline-block`,作 `justify` 之單一對齊原子(句末標點
|
||
單份間距)。但**分頁器**(如 Paged.js)之對齊引擎會把每個 inline-block 邊界當伸縮點、
|
||
與 CJK inter-ideograph 疊加,於窄欄混排時字距失控、整段散架。此時設
|
||
`justifyAtoms: false`——`jz-char`/`jz-jinze` 改 `display:inline`(半形 `halt` 與禁則
|
||
`nowrap` 綁定均保留)。常規流式版面無需設定。
|
||
|
||
> 注意:`margin` 後備半形依賴 `inline-block`(`width:0.5em`),故
|
||
> `justifyAtoms:false` 與 `margin` 模式不可兼得——分頁器場景請用含 `halt` 之字型。
|
||
|
||
### 分塊功能選擇(HTML 屬性,就近繼承)
|
||
|
||
先按下一節選擇 text/paragraph,再用功能白名單或黑名單微調。
|
||
它們不改變 pill/isolate/avoid 分類,也不會繞過級別限制。
|
||
|
||
```html
|
||
<p data-juzhen="spacing jiya">只選中西間隙與標點擠壓。</p>
|
||
<p data-juzhen-off="orphan longWord">保留其他功能,停用避孤字與長詞斷字。</p>
|
||
```
|
||
|
||
功能名區分大小寫,以空白分隔。一般呼叫端使用 `spacing`、`jiya`、`jinze`、`orphan`、
|
||
`longWord`;前述相容 no-op 名稱不會產生效果。
|
||
|
||
對每個功能,從元素自身向上找:同一元素先取 `data-juzhen` 白名單,再檢查
|
||
`data-juzhen-off` 黑名單,接著取第一條命中的 `blocks` 規則。白名單未列入的功能停用;
|
||
黑名單未列入的功能繼續查找;皆無決定時沿祖先向上,最後回退全域開關。
|
||
因此同一元素同時寫白名單與黑名單時,**白名單優先**;`data-juzhen=""` 是空白名單,
|
||
不是恢復預設。全域停用的功能不會被局部白名單重新啟用。
|
||
|
||
`spacing` 與 `jiya` 的門控也作用於普通行內內容;禁用區域不刪作者空格、不參與
|
||
外部間隙/標點鄰接,空禁用元素同樣中斷邊界。子白名單可在自身子樹重新啟用。
|
||
`scope.include` 可選行內子樹;文本級間隙不要求其父塊也命中選擇器。
|
||
|
||
### 功能分級:文本級 vs 段落級(`level`/`data-jz-level`)
|
||
|
||
**text/paragraph 決定允許哪些排版功能,不是 HTML 標籤分類,也不設定 CSS display。**
|
||
呼叫端應按內容用途選擇,不能把「現在只有一行」當作 text 的唯一判準。
|
||
|
||
| 內容用途 | 選擇 | 允許的功能(仍受功能開關控制) | 不會替你做什麼 |
|
||
|---|---|---|---|
|
||
| 標題、按鈕、選單、短標籤等 UI 文字 | **text(文本級)** | `spacing` 中西間隙、`jiya` 標點擠壓 | 不做 `jinze` 禁則、`orphan` 避孤字、`longWord` 長詞斷字;不保證單行 |
|
||
| 正文、說明段落、完整句子的列表項等連續閱讀內容 | **paragraph(段落級)** | 上述文本級功能,加上禁則、避孤字、長詞斷字 | 不自動兩端對齊,不設定字型或容器寬度 |
|
||
|
||
**沒有任何級別訊號時預設 paragraph。** `h1`/`button` 不會因名稱自動變成 text;
|
||
`p` 也可以明確標成 text。即使一段正文在寬螢幕恰好只有一行,仍可選 paragraph;
|
||
即使標題在窄螢幕換行,也可保留 text。需不需段落約束由呼叫端決定。
|
||
|
||
#### HTML 標記與選擇器
|
||
|
||
```html
|
||
<article class="juzhen" lang="zh-Hant">
|
||
<h1 data-jz-level="text">聚珍API:使用說明</h1>
|
||
<p data-jz-level="paragraph">正文採段落級,允許禁則與段末避孤字。</p>
|
||
<div class="ui" data-jz-level="text">
|
||
<button>開啟API</button>
|
||
<p data-jz-level="paragraph">這段較長的說明重新採用段落級。</p>
|
||
</div>
|
||
</article>
|
||
```
|
||
|
||
也可在建立實例時用 `level: { text: "h1,h2,h3,button,.ui" }` 批量選定文本級子樹,
|
||
不必逐個寫屬性;已有 pill/isolate/avoid 設定時,將 `level` 加入**同一個**選項物件。
|
||
|
||
解析順序如下:
|
||
|
||
1. 從當前元素(文字則從父元素)開始往上找,包含自身。
|
||
2. 每一層先讀合法的 `data-jz-level="text|paragraph"`,再看是否命中 `level.text`。
|
||
3. 找到第一個訊號即停止;都沒有才用 paragraph。非法屬性值不構成訊號,繼續查找。
|
||
|
||
所以**距離優先、同層屬性優先**:近處選擇器可以勝過遠處祖先屬性;同一元素標
|
||
`paragraph` 則可以蓋過該元素命中的 `level.text`。在 text 子樹中寫
|
||
`data-juzhen="orphan"` 不能開啟避孤字,須先明確切回 paragraph,且該功能全域已啟用。
|
||
|
||
#### 與 pill/isolate/avoid 搭配
|
||
|
||
| 組合 | 實際意思 |
|
||
|---|---|
|
||
| paragraph 正文中的 pill 公式 | 正文有段落約束;公式內部由元件管理,公式整體仍參與正文交界排版 |
|
||
| text 標題中的 isolate 標籤 | 標籤內部仍補間隙、處理標點;不因 isolate 就開啟段落功能 |
|
||
| paragraph 容器中的 avoid 元件 | 元件子樹仍完全排除;paragraph 不會取消 avoid |
|
||
|
||
不要用 avoid 代替 text:若只是不想讓按鈕跑避孤字,應標 text,這樣仍保留間隙與標點處理。
|
||
也不要用 text 代替 pill:text 仍會改寫內部文字排版,不能用來保護公式或程式碼元件。
|
||
|
||
#### CSS 可查之 resolved 分類:`data-jz-block-level`(輸出屬性)
|
||
|
||
作者只寫 **`data-jz-level`**。`render` 後聚珍會對承載可處理非空白文字的塊級元素產生
|
||
**`data-jz-block-level`**,供呼叫端 CSS 使用;不要把輸出屬性當作設定值手寫。
|
||
|
||
```css
|
||
/* 依聚珍輸出分類選擇對齊;這是呼叫端的樣式決定。 */
|
||
.juzhen [data-jz-block-level="paragraph"] { text-align: justify; }
|
||
.juzhen .marginalia[data-jz-block-level="paragraph"] { text-align: start; }
|
||
```
|
||
|
||
純容器(例如只含子段落的 `div`/`ul`)不因包住正文就取得此輸出;域外文字、pill 內部與
|
||
avoid 子樹不計入。屬性描述**所屬塊的級別**,不是每個行內子元素的級別;同一段落內個別
|
||
`span` 標 text,不會把整個段落輸出改成 text。若需要段落 CSS 跟著切換,應標在該塊本身。
|
||
|
||
`revert` 會撤回本實例寫入的輸出,依所有權規則保留/恢復作者或其他實例的屬性。
|
||
輸入 `data-jz-level` 不由聚珍改寫;`data-jz-block-level` 不參與級別判定,也不觸發分類重算。
|
||
|
||
#### 標記改動後如何更新
|
||
|
||
在第一次 `render` **之前**準備好 class、排除與級別標記。已渲染後改動時,手動呼叫
|
||
`jz.rerender(changedBlock)`;若開啟 `jz.observe(root)`,受觀察範圍內的 `class`、
|
||
`data-jz-skip`、`data-jz-level`、`data-juzhen`/`data-juzhen-off` 變化會觸發增量更新。
|
||
自訂選擇器若依賴其他屬性(例如 `data-widget`),不要假設 observer 會追蹤該屬性,請手動
|
||
重排受影響區塊。更新時以塊為單位,詳見前面的「動態頁面」。
|
||
|
||
### 標點懸掛(`hanging`)— 已移除
|
||
|
||
原計劃之標點懸掛(行端標點凸出版心)經實測為**機制級死路、已移除**(負 margin 程式碼與
|
||
CSS 皆刪)。`hanging` 選項仍被接受(API 相容)但**無任何效果**——`hanging:true`/
|
||
`data-juzhen`/`blocks` 任一要求皆被靜默忽略,行端僅做半形擠壓。
|
||
|
||
**為何機制級不可行**(純 CSS 之寬度守恆矛盾):行端標點盒若保持非零寬,則「墨色出去多少、
|
||
版心內就空出多少」(行末實義字無法齊版心右緣);若收為零寬,則釋放一整字寬、斷行器把下一字
|
||
拉上本行與標點重疊(且 `justifyAtoms` 預設之 inline-block 下負 margin 被 justify 吸收)。
|
||
**墨色出去多少版心內就空多少;想不空就得讓斷行器重排這塊寬度,而 CSS hack 接觸不到斷行器**。
|
||
補償墊片、兩階段試錯都改變不了此恒等式。
|
||
|
||
唯一正解為原生 CSS `hanging-punctuation`,惟 **Chrome/Firefox 至今不支援**(僅 Safari),且
|
||
本庫之 `jz-char` 為 inline-block 原子、非文本,原生懸掛不作用——未來若做,須是繞開 `jz-char`
|
||
的「原生管線」新功能(`text-spacing-trim` + `hanging-punctuation`,按能力門控),與已移除之
|
||
程式碼無共享。詳見 `ARCHITECTURE.md §6.5.6`。
|
||
|
||
---
|
||
|
||
## 標點半形:`halt` 預設與 `margin` 後備
|
||
|
||
開明/半角式把標點收成「半形」,有兩種渲染機制:
|
||
|
||
| 機制 | 設定 | 原理 | 適用 |
|
||
|---|---|---|---|
|
||
| **`halt`**(預設) | `jiya: true` | 字型 OpenType `halt`,由字型按字形正確定位、不重疊 | 字型含 `halt`(Noto CJK/思源系列) |
|
||
| **`margin`**(後備) | `jiya: { halfWidth: "margin" }` | `width:0.5em`+字身依墨色偏側溢出定位(以 CSS 重現 halt) | 字型**缺** `halt`(多數系統字型、GBK 老字型如方正書宋 GBK、PingFang) |
|
||
|
||
> 為何需要後備:`halt` 缺席時開明式擠壓「完全無效」(標點仍佔全形)。實測
|
||
> **方正書宋 GBK 僅含 `vert`、無 `halt`** → halt 模式不擠;切 `margin` 後備後,
|
||
> (開明式 zh-Hans)同句行寬 `24.0em → 19.7em`、單一句內點號 `1.0em → 0.5em`,半形生效。
|
||
|
||
> **句末標點含全角句點 `.`**:句末點號為 `。.!?`——除表意句號 `。`(U+3002)外納入
|
||
> 全角句點 `.`(U+FF0E,實測 Noto CJK 有 `halt`)。政策行為與 `。!?` 同:全角式與開明式
|
||
> 句末**保持全角 1em**;唯 banjiao(或與鄰標點連續被收)時 `halt` 收為半形 advance 0.5em。
|
||
> **`halt` 即字型之「挤壓意願」**:字型不提供某標點之 `halt`(其字形占滿字框、不宜挤半,
|
||
> 如某些字型之全角 `?`、`〈〉`)時,聚珍**信任字型、保持全角不強挤**(優雅降級);
|
||
> `margin` 後備僅用於**整體無 `halt`** 之字型,逐字型以 `jiya.bias` 調教。
|
||
|
||
### 激活與粒度
|
||
|
||
模式由**最近的有效 `data-jz-halfwidth="halt|margin"` 標記**決定;CSS 繼承模式參數,
|
||
不依標記距離競爭選擇器。未標記時用 halt;非法值忽略並繼承祖先模式。
|
||
|
||
- **全域**:`jiya: { halfWidth: "margin" }` → `render()` 於處理 root 自動掛屬性。
|
||
- **逐區塊/逐語言**:在 HTML 任一祖先手寫 `data-jz-halfwidth="margin"`(或
|
||
`"halt"`),即令該子樹用對應機制——無需另建實例。可任意交替巢狀 halt/margin;
|
||
切回 halt 時同時撤銷 margin 盒寬、內盒與墨色偏移。未壓縮的全形標點不套偏側位移。
|
||
模式切換是純 CSS,變更此屬性即可,無須 rerender。
|
||
|
||
```html
|
||
<!-- 全頁 halt(預設),此塊改用無 halt 字型 → 局部 margin 後備 -->
|
||
<section data-jz-halfwidth="margin" style="font-family: '方正书宋_GBK'">…</section>
|
||
```
|
||
|
||
### 機制與字形偏側表(`margin` 模式專用,可覆寫)
|
||
|
||
`margin` 模式把標點 `jz-char` 之 inline-block 寬收為 **0.5em(真半形 advance)**,字身
|
||
(`jz-inner`)以 `overflow` 溢出該盒,再依**墨色偏側**左移、使墨色落於 0.5em 盒內、
|
||
空白溢向外側——即以 CSS 盒模型重現 `halt` 之「收字身寬+按字形定位」。相鄰兩標點各
|
||
0.5em、合佔 1em,無鄰接疊加或單側重疊問題。
|
||
|
||
偏側須知每字之墨色中心。聚珍內建**參考表**(依方正書宋 GBK 實測之 glyph ink 位):
|
||
簡體點號 `,、。;:!?` 偏左;繁體點號居中;彎引號 `“”‘’`、角括號 `「」`、尖括號
|
||
`〈〉` 墨色強偏一側(開→`right`、閉→`left`);圓/方/書名等括號 `()【】《》…` 墨色
|
||
近中→`center`。於 charify 標為 `jz-ink-{left,center,right}`。因字型字身分布各異,可逐
|
||
`lang` 逐字覆寫:
|
||
|
||
```ts
|
||
createJuzhen({
|
||
jiya: {
|
||
halfWidth: "margin",
|
||
bias: {
|
||
"zh-Hans": { ",": "center", "(": "right" }, // 覆寫參考預設
|
||
"zh-Hant": { "。": "center" },
|
||
},
|
||
},
|
||
});
|
||
// 偏側值(決定字身於 0.5em 盒內之左移):
|
||
// left 墨偏左 → 不移(字身左對齊)
|
||
// center 墨居中 → 左移 0.25em
|
||
// right 墨偏右 → 左移 0.5em(右半入盒)
|
||
// full 無可壓縮空白 → 不收、維持全形
|
||
```
|
||
|
||
> `halt` 模式不讀偏側表(由字型負責定位)。偏側表僅在 `margin` 後備模式生效。
|
||
> `demo/fallback.html` 用方正書宋 GBK 對照 halt 失效 vs margin 生效。配字工作流見下節。
|
||
|
||
### 字型適配工作流(margin 模式配字)
|
||
|
||
換用新字型時,依下列步驟確認半形渲染:
|
||
|
||
1. **驗 `halt`**:`python3 tools/measure-bias.py <font.ttf>`(需 `pip install fonttools`)。
|
||
首行報該字型是否含 OpenType `halt`。
|
||
- **有 `halt`** → 直接用預設(`jiya: true`,halt 模式),無需配字,到此為止。
|
||
- **無 `halt`**(多數系統字型、GBK 老字型)→ 用 `margin` 後備,續下。
|
||
2. **取偏側建議**:同一工具同時量各標點 glyph 之墨色中心、印出建議偏側表
|
||
(`left|center|right|full`)與可貼入 `jiya.bias` 之 JSON。
|
||
3. **套用**:`createJuzhen({ jiya: { halfWidth: "margin", bias: <貼入 JSON> } })`;
|
||
或全用內建參考表(多數宋體系 GBK 字型與參考表相符,未必需覆寫)。
|
||
4. **目視微調**:以 `demo/fallback.html`(或自有頁面)對照原文,逐字檢查——
|
||
- 標點與相鄰字**重疊** → 該字墨色比量測更偏該側,偏側往「不移」方向調
|
||
(`right`→`center`→`left`)。
|
||
- 標點與內容**仍有空隙、不夠半形** → 往「多移」方向調(`left`→`center`→`right`)。
|
||
- 某標點根本無可壓空白(字面已滿全形)→ 設 `full`,不收。
|
||
5. **記錄**:把該字型之 `bias` 覆寫存入專案設定,作為該字型之適配結果。
|
||
|
||
> 偏側是「字身於 0.5em 盒內左移多少」的三檔量化(0/0.25/0.5em),量測值近門檻
|
||
> (如 0.6 介於 center/right)時,以目視為準。`halt` 字型走 OpenType 由字型精確
|
||
> 定位,無此配字需求——`margin` 配字僅為缺 `halt` 字型之務實近似。
|
||
|
||
---
|
||
|
||
## CSS 基礎設定(消費端須知)
|
||
|
||
JS 只注入 `jz-*` 結構,**所有視覺尺寸在 `juzhen.css`**——故 CSS 不可或缺。消費端
|
||
須備妥下列幾項:
|
||
|
||
1. **引入樣式(必須)**:`<link rel="stylesheet" href="dist/juzhen.css">`(或 ESM
|
||
`import "cjk-autospace/css"`)。缺此則間隙/擠壓/justify 原子化全部失效。
|
||
2. **設定 `lang`(強烈建議)**:在內容容器標 `lang="zh-Hans"`/`"zh-Hant"`/`"ja"`,
|
||
驅動全角/開明政策與字形 `locl`。無 `lang` 時回退 `options.lang.default`。
|
||
3. **作用域類**:受管內容容器須加 `class="juzhen"`,以啟用內建原生間距禁用樣式。
|
||
`scope.include` 只控制 JS 處理範圍,不會改寫 CSS 選擇器;若使用 `.prose` 等自訂
|
||
選擇器,請同時加類別(如 `class="prose juzhen"`)。
|
||
4. **長英文詞斷字**:`longWord` 包出 `<span lang="en">` 後,須由消費端 CSS 提供
|
||
`hyphens: auto`(含 `-webkit-hyphens`)才會原生斷詞顯示連字符。
|
||
5. **`justify`(選用)**:欲兩端對齊則自行設 `text-align: justify`;句末標點單份間距
|
||
與行末去隙由聚珍處理,無需額外設定。
|
||
6. **字型由消費端負責(聚珍不管字型)**:聚珍**不**內建任何 `font-family`/字型堆疊
|
||
——逐語言字型完全由消費端 CSS 決定。聚珍只確保原子(`jz-*`)以 `font: inherit`
|
||
繼承消費端字型、絕不強加,並顯式保留 `locl`(見下「字型與 locl」一節)。半形擠壓
|
||
依字型 OpenType `halt`:含 `halt` 之字型(Noto CJK/思源)預設即可;**缺 `halt`
|
||
之字型**(如方正書宋 GBK)須改用 `jiya.halfWidth:"margin"` 後備,並按字型以
|
||
`jiya.bias` 逐字微調墨色偏側(見「字型適配工作流」)。
|
||
7. **自動處理、無需消費端介入**:`juzhen.css` 已在 `.juzhen` 範圍內以 `@supports`
|
||
關閉原生 `text-autospace`(避免與受管間隙重複作用);不設定 `text-spacing-trim`,
|
||
間隙不注入空白字元;僅跨節點 IVS gap 之 `U+200B ::after`
|
||
設 `user-select:none`,避免該斷行提示進入選取/複製。
|
||
8. **段落屬性照常設、原子透明**:聚珍把標點拆成 inline-block 原子,但已確保這些原子對
|
||
消費端段落樣式透明——可在段落正常設 `text-indent`(首行縮排)、`hanging-punctuation`、
|
||
`text-decoration`(下劃線)等;庫內已歸零會被原子重複套用之繼承屬性、並令原子承接段落
|
||
裝飾,故首行縮排正常、下劃線連續不斷,無需任何額外配置(機制見 ARCHITECTURE「原子透明
|
||
原則」)。
|
||
|
||
---
|
||
|
||
## 字型與 locl(消費端設定)
|
||
|
||
聚珍**不管字型**;逐語言字型由你以 CSS 設定。推薦範式——逐 `lang` 設 `font-family`,
|
||
掛在 `.juzhen` 或帶 `lang` 之容器(**勿**直接設 `jz-inner`/`jz-char`,它們靠繼承取字型):
|
||
|
||
```css
|
||
.juzhen:lang(zh-Hans), .juzhen [lang]:lang(zh-Hans) { font-family: "Noto Serif SC", "Source Han Serif SC", serif; }
|
||
.juzhen:lang(zh-Hant), .juzhen [lang]:lang(zh-Hant) { font-family: "Noto Serif TC", "Source Han Serif TC", serif; }
|
||
.juzhen:lang(ja), .juzhen [lang]:lang(ja) { font-family: "Noto Serif JP", serif; }
|
||
```
|
||
|
||
要點:
|
||
|
||
- **字型須含 OpenType `halt`** 才能半形擠壓;否則用 `jiya.halfWidth:"margin"` + 按字型
|
||
以 `jiya.bias` 逐字微調墨色偏側(見「字型適配工作流」)。
|
||
- **locl(地域字形)**:泛 CJK 字型(思源/Noto CJK)以 `locl` 依 `lang` 選地域字形
|
||
(如「直/海/骨」之簡繁日差異)。聚珍**不改寫 `lang`、不關閉 locl**,故只要你 ①
|
||
正確標 `lang`、② 用支援 locl 之字型,locl 即正常。body 文本之 locl 全由你的字型+
|
||
`lang` 決定;標點(`jz-inner`)之 `halt` 已顯式併入 `locl`,地域標點字形不致失。
|
||
- 若只想全語言同一字型,直接 `.juzhen { font-family: … }` 即可(無需逐 `lang`)。
|
||
|
||
---
|
||
|
||
## 自定義字符分類(`charClass`)
|
||
|
||
每個字符的分類(中文/西文、全寬標點各子類)抽象為**規則集**:內置默認規則集即聚珍
|
||
的分類表,消費端可經 `options.charClass` **擴充**,以覆寫 `spacing`/`jiya`/`jinze`
|
||
等規則對特定字符之判定。語義為**追加**——把當前未分類之字符、或新符號歸入對應類。
|
||
|
||
```js
|
||
createJuzhen({
|
||
charClass: {
|
||
cjk: "〇", // 追加為中文(CJK):與西文邊界補中西間隙、禁則/justify 視為單字
|
||
western: "★☆", // 追加為西文(半寬標點亦歸此):與 CJK 邊界補間隙
|
||
biaodian: { // 追加為全寬標點,按子類(jiya charify/擠壓、禁則避頭尾)
|
||
stop: "‼", // 追加為句末點號(。.!?已內置)
|
||
pause: "", // 句內(,、;:類)
|
||
open: "", close: "", // 開/閉括號引號
|
||
middle: "", liga: "", // 間隔號/省略破折
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
`.`(U+FF0E,全角句點)已是內置 `BD_STOP` 成員,無需再經
|
||
`charClass` 追加;其政策與 `。!?` 一致。`charClass.biaodian` 用於追加尚未內置的
|
||
符號(如上例 `‼`),不用來重複宣告內置字符。
|
||
|
||
要點:
|
||
|
||
- **向下兼容**:不傳 `charClass` 時,分類與行為與內置默認**完全一致**。
|
||
- **追加語義(v1)**:擴充內置類;**不**支援把內置字符「重分類/移除」(屬罕見需求,後續再議)。
|
||
勿把同一字符同時加入相衝之兩類。
|
||
- **結構性 ASCII**(字母數字 `A-Za-z0-9`、運算符 `+-*/` 等)為固定集、不開放覆寫——它們
|
||
服務於運算符/複合詞等內部規則,非「字符類型」。
|
||
- `margin` 後備模式下若新增開/閉括號,其墨色偏側未必命中內建參考表,可一併以
|
||
`jiya.bias` 指定(見「字型適配工作流」)。
|
||
|
||
---
|
||
|
||
## v1 向下相容 — 已移除
|
||
|
||
v1 相容層(`createCjkAutospace` 導出、IIFE 之 `globalThis.createCjkAutospace`
|
||
別名、`autospaceClass` 選項)**已隨最後一批 v1 消費端遷移完成而移除**。既有消費端
|
||
請改用 `createJuzhen(options).render(root)` v2 API;v1 原始碼保存於 `v1-autospace`
|
||
分支。
|
||
|
||
---
|
||
|
||
## 目視驗證
|
||
|
||
`demo/index.html` 載入 `dist/` 產物,簡繁並列、可切 `justify`、附 copy-clean 測試區。
|
||
`demo/e2e.html` 是可機讀的核心版面與 MutationObserver 驗收頁;macOS 上可以
|
||
`npm run test:wkwebview` 將同一頁面載入原生 `WKWebView`,自動執行全部斷言。
|
||
`demo/fallback.html` 以**方正書宋 GBK**(無 `halt`)對照 `halt` 模式失效 vs `margin`
|
||
後備生效。`demo/resize-check.html` 為**可拖曳容器**檢查頁:拖動方框寬度,驗證中西間隙在
|
||
各寬度下恆穩(含行末去隙之自我應驗、pill 跨行右隙、貨幣/拼音/西里爾外文補隙、全角省略號
|
||
與各式中文標點無誤隙),可切 `justify` 與「顯示間隙」描邊。版面相關結論(justify 單份間距、
|
||
擠壓視覺量、行端、`inline-block` 基線、間隙隨寬度之穩定性)須於 Chrome/Safari/Firefox
|
||
三引擎目視核對——jsdom 僅驗證 DOM 結構正確性。
|
||
|
||
---
|
||
|
||
## 授權
|
||
|
||
MIT。借鑑 Han.css/Heti 之思路與分類表(MIT),成品自含、不依賴其執行期程式碼。
|