# 聚珍(Juzhen) > 現代簡/繁中文網頁排版套件。Han.css 之現代化重寫,沿其功能版圖,並針對 > 數種情況作調整:**複製乾淨**(中西間隙不入剪貼板)、**依 `lang` 全角/開明 > 自動切換**、**`justify` 下句末標點只佔一份間距**、**跨瀏覽器一致** > (Chrome/Edge/Safari/WebKit/Firefox)。 倉庫名仍為 `cjk-autospace`;公開 API 為 `createJuzhen` / 全域 `Juzhen`;注入之 自訂元素前綴 `jz-`。完整設計見 [`ARCHITECTURE.md`](./ARCHITECTURE.md)。 > 本版(2.x)為從零重寫。v1 之 `createCjkAutospace` 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 正確性測試 ``` 產物(`dist/`,已入庫): | 檔 | 用途 | |---|---| | `dist/juzhen.mjs` | ESM,供打包器/TypeScript | | `dist/juzhen.iife.js` | IIFE,全域 `Juzhen`,原生 ` ``` ### 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`。 --- ## 切片 1 已實作功能 | 功能 | 說明 | |---|---| | **中西間隙** `spacing` | CJK↔西文邊界插入 copy-clean 之 `jz-hws`(`user-select:none`,剪貼板跳過)。含跨樣式行內邊界與 `code`/`kbd` pill 邊界(pill 緊鄰標點時穿越 `jz-jinze` 仍補間隙)。贅餘作者空格一律正規化:CJK↔西文吸收為受管間隙、CJK↔CJK(含跨 inline 標籤)剝除不留隙。 | | **長英文詞** `longWord` | `[A-Za-z]{6,}` 包入 ``,配合 CSS `hyphens:auto` 原生斷詞(DOM 無連字符 → 複製不污染)。 | | **標點擠壓** `jiya` | 標點 charify 為 `jz-char`+`bd-*` class。三機制:①baseline 政策(全角式不擠/開明式擠句內點號+括號引號/半角式全擠);②行內連續擠壓(相鄰標點對向側收);③行端擠壓(`lineedge` layout pass)。半形預設以字型 `halt` 渲染(按字形正確定位,居中字形如 `!?`/繁體 `。,` 不重疊;不依賴 `text-spacing-trim`)。**字型缺 `halt` 時可切 `margin` 後備模式**(見下節)。 | | **禁則** `jinze` | 行首行末避頭尾:閉類/點號綁前一字(不置行首)、開類綁後一字(不置行末),只隔離邊界字故長引文中段仍可斷。亦為 `justify` 綁定載體。 | | **justify 單份間距** | 每個 `jz-char` 為 `inline-block` 原子項,句末標點只貢獻一份對齊伸縮量。 | 後續(架構已預留,不返工):標點懸掛、標點修正(簡↔繁)、著重號、注音/Ruby。 --- ## 選項 ```ts createJuzhen({ lang: { default: "zh-Hant", // 無 lang 祖先時之預設 policy: { "zh-Hant": "quanjiao", "zh-Hans": "kaiming" }, // 全角/開明/半角 }, 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, justifyAtoms: true, // jz-char/jz-jinze 以 inline-block 作對齊原子; // 分頁器(Paged.js)下設 false 改 inline(見下) blocks: [ // 分塊功能選擇 { selector: ".code-comment", features: [ "spacing" ] }, ], }); ``` ### 分頁器(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 屬性,就近繼承) ```html

``` --- ## 標點半形:`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` 後備後, > 同句行寬 `24.0em → 19.7em`、單一標點 `1.0em → 0.5em`,半形生效。 ### 激活與粒度 模式由 **`data-jz-halfwidth` 屬性所在子樹**決定(CSS 後代選擇器),故粒度極靈活: - **全域**:`jiya: { halfWidth: "margin" }` → `render()` 於處理 root 自動掛屬性。 - **逐區塊/逐語言**:在 HTML 任一祖先手寫 `data-jz-halfwidth="margin"`(或 `"halt"`),即令該子樹用對應機制——無需另建實例。常見如「全頁 `halt`,某用 無 `halt` 字型之區塊掛 `margin`」,或反之(`margin` 頁中嵌一塊 `halt` 島)。 ```html
``` ### 機制與字形偏側表(`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 `(需 `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. **引入樣式(必須)**:``(或 ESM `import "cjk-autospace/css"`)。缺此則間隙/擠壓/justify 原子化全部失效。 2. **設定 `lang`(強烈建議)**:在內容容器標 `lang="zh-Hans"`/`"zh-Hant"`/`"ja"`, 驅動全角/開明政策與字形 `locl`。無 `lang` 時回退 `options.lang.default`。 3. **作用域類(用 `scope.include` 時)**:若設 `scope.include: ".juzhen"`,須給內容 容器加對應 class(如 `class="juzhen"`);`juzhen.css` 之 normalize/字型堆疊亦 收斂於此域,不外溢。 4. **長英文詞斷字**:`longWord` 包出 `` 後,須由消費端 CSS 提供 `hyphens: auto`(含 `-webkit-hyphens`)才會原生斷詞顯示連字符。 5. **`justify`(選用)**:欲兩端對齊則自行設 `text-align: justify`;句末標點單份間距 與行末去隙由聚珍處理,無需額外設定。 6. **字型(影響擠壓)**:開明/半角式半形預設依字型 `halt`。若用含 `halt` 之字型 (Noto CJK/思源),預設即可;若用缺 `halt` 之字型,改用 `margin` 後備模式(見 上節)。`juzhen.css` 內建依 `:lang` 之 Noto Sans/Serif CJK 字型堆疊,但**僅作 後備**(全包於 `:where()`、零特異度,且只命中帶 `lang` 屬性之元素、不直接覆寫標點 字面容器 `jz-inner`)——**消費端在 `.juzhen` 或任一祖先設 `font-family` 即恆優先**, 標點亦隨之以消費端字型渲染(故缺 `halt` 字型務必配 `margin` 後備,否則標點不擠)。 7. **自動處理、無需消費端介入**:`juzhen.css` 已在 `.juzhen` 範圍內以 `@supports` 關閉 Blink 原生 `text-spacing-trim`/`text-autospace`(避免與託管擠壓重複作用), 並令注入間隙 `user-select: none`(複製乾淨)。 --- ## v1 向下相容 ```js // 既有消費端零改動: const cjk = createCjkAutospace({ pillSelector: "code, kbd" }); cjk.apply(document.body); ``` IIFE 構建另掛 `globalThis.createCjkAutospace` 別名。**與 v1 之差異**(行為等價、 不要求逐位一致): - 間隙標記由 `` 變為 `` (仍帶原 class,既有 `.cjk-autospace{user-select:none}` CSS 仍命中)。 - 標點擠壓改由 `juzhen.css` 託管(須引入),預設仍以字型 `halt` 渲染半形——與 v1 消費端 `font-feature-settings:"halt"` 同機制;缺 `halt` 之字型可切 `margin` 後備。 --- ## 目視驗證 `demo/index.html` 載入 `dist/` 產物,簡繁並列、可切 `justify`、附 copy-clean 測試區。 `demo/fallback.html` 以**方正書宋 GBK**(無 `halt`)對照 `halt` 模式失效 vs `margin` 後備生效。版面相關結論(justify 單份間距、擠壓視覺量、行端、`inline-block` 基線)須於 Chrome/Safari/Firefox 三引擎目視核對——jsdom 僅驗證 DOM 結構正確性。 --- ## 授權 MIT。借鑑 Han.css/Heti 之思路與分類表(MIT),成品自含、不依賴其執行期程式碼。