Files
cjk-autospace/README.md
T
admin 2890549f44 fix: 行內排版透明性——pill 前標點誤隙+行內元素整體掉行+審計(Bug1/Bug2/T4-T6)
統一「行內元素對排版透明」原則在各遍歷原語之口徑,修復兩個下游 Bug 及三項同根隱患。

Bug1(pill 前標點誤加間隙):
  isTransparentInline 補 JZ-CHAR/JZ-INNER。`漢字。<code>` 經禁則綁定後,
  adjacentLogicalChar 之 descend 遇不透明 jz-char 既不進入亦不終止、而跳過它越過
  標點取到「字」誤補 jz-hws;透明後回傳標點「。」(非 pill 鄰字)→ 不補隙,與
  collectRuns 既有透明遞迴對齊。

Bug2(行內樣式元素整體掉行):
  新增 core/split.ts 之 isolateBoundaryToken/splitElementAt——沿祖先鏈逐層把邊界
  詞元剝入淺克隆(保留標籤/class/style/lang、去 id;標 data-jz-kind="splitoff"),
  jinze/orphan 改最小綁定:`<strong>長串</strong>,` 只綁末字+逗號(非整串入 nowrap
  inline-block 致行末整體掉行);段末長 strong 不再整綁溢出容器。revertPass 增延後
  階段把 splitoff 併回原元素(先解包 wrap、後併 splitoff),render→revert 還原一致。
  純樣式標籤白名單可切,a/abbr/ruby/帶 id 退化整綁。

審計(同根隱患):
  T4 lineedge edgeRect 攀爬集改由 options.finder.styleInlines 派生(棄硬編碼子集,
     免遺漏 ins/del/sub 等致行端誤判)。
  T5 jiyaAdjacency 遇 pill(pillMatches)斷相鄰、不穿越(內容對排版不可見)。
  T6 spacing processPills 補 per-node 功能閘(data-juzhen-off=spacing 區域內不補隙)。

文檔:README 切片表(spacing/jinze/orphan 三行)+ARCHITECTURE §4.1 透明性原則、
  §4.4 splitoff 還原、§6.4 行內元素最小綁定、§5 jz-orphan 詞彙。

測試 90→102(Bug1×4、Bug2×6、T5/T6×2)。headless Chrome 實證 Bug2:段末 strong
最小切分、orphan 寬 60px 不溢出版心。
2026-06-11 16:06:17 +08:00

23 KiB
Raw Blame History

聚珍(Juzhen

現代簡/繁中文網頁排版套件。Han.css 之現代化重寫,沿其功能版圖,並針對 數種情況作調整:複製乾淨(中西間隙不入剪貼板)、lang 全角/開明 自動切換justify 下句末標點只佔一份間距跨瀏覽器一致 ChromeEdgeSafariWebKitFirefox)。

倉庫名仍為 cjk-autospace;公開 API 為 createJuzhen / 全域 Juzhen;注入之 自訂元素前綴 jz-。完整設計見 ARCHITECTURE.md

本版(2.x)為從零重寫。v1 之 createCjkAutospace API 由相容層保留(見下), 既有消費端不需改碼即可運行;v1 原始碼保存於 v1-autospace 分支。


安裝與構建

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,原生 <script> 直跑
dist/juzhen.css 合併樣式(必須引入,視覺尺寸全在此)
dist/index.d.ts TypeScript 型別宣告

用法

原生 <script>

<link rel="stylesheet" href="dist/juzhen.css">
<script src="dist/juzhen.iife.js"></script>
<script>
    const jz = Juzhen.createJuzhen({ scope: { include: ".prose" } });
    jz.render();          // 對 document.body 套用(僅命中 .prose 子樹)
    // jz.revert();        // 還原
</script>

ESMTypeScript

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-hwsuser-select:none,剪貼板跳過)。「西文」涵蓋拉丁(含擴充 A/B,拼音聲調 ā ǎ ē)、希臘、西里爾、數字、貨幣(€ ₩ ₿)、數學運算符等;CJK 標點(。,「」()…… 等)不誤判為西文、不生隙。含跨樣式行內邊界與 codekbd pill 邊界(pill 緊鄰標點時穿越 jz-jinze 仍補間隙;pill 跨行右隙、間隙落行末之自我應驗皆已修)。pill 之邏輯鄰字為標點時(如 漢字。<code>)不誤加間隙(標點非 pill 鄰字;jz-charjz-inner 透明穿越,與 collectRuns 一致)。贅餘作者空格一律正規化:CJK↔西文吸收為受管間隙、CJK↔CJK(含跨 inline 標籤)剝除不留隙。
長英文詞 longWord [A-Za-z]{6,} 包入 <span lang="en">,配合 CSS hyphens:auto 原生斷詞(DOM 無連字符 → 複製不污染)。
標點擠壓 jiya 標點 charify 為 jz-charbd-* class。三機制:①baseline 政策(全角式不擠/開明式擠句內點號+括號引號/半角式全擠);②行內連續擠壓(相鄰標點雙側獨立收——各標點若其有空白之側緊鄰另一標點即收半,故 ,「 全角式與開明式皆 1em、。」 全角式 1.5em/開明式 1em;透明穿越 strongem 等行內邊界);③行端擠壓(lineedge layout pass)。半形預設以字型 halt 渲染(按字形正確定位,居中字形如 !?/繁體 。, 不重疊;不依賴 text-spacing-trim)。字型缺 halt 時可切 margin 後備模式(見下節)。
禁則 jinze 行首行末避頭尾:閉類/點號綁前一字(不置行首)、開類綁後一字(不置行末),只隔離邊界字故長引文中段仍可斷。亦為 justify 綁定載體。邊界字落於行內樣式元素(strongemspan 等)內時克隆切分只綁邊界詞元(避免整個元素入 nowrap 致行末整體掉行、上行留巨大空隙),切出之克隆標 data-jz-kind="splitoff"revert 時自動併回原元素;不可切元素(aabbrruby/帶 id)退化為整元素綁定。
垂懸字避免 orphan 段末行 CJK 實義字 ≥ N(預設 2;標點/符號不計)。方案 C(斷行層、無測量):把段末「第 N 個實義字起至段末」包入 jz-orphanwhite-space:nowrap),整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型成立。單行段無害。邊界字落於行內元素內時同樣克隆切分(只綁末 N 字、避免整個長元素入 nowrap 溢出容器;機制同禁則)。預設開orphan:false 關,{chars:n} 調)。段落級功能。
標點懸掛 hanging 已評估停用(§6.5.6):純負 margin 機制不可行(墨色不出版心/滿凸拉升後字重疊/resize 重排錯亂),原生 hanging-punctuation 為正解但 Chrome 不支援。hanging 選項仍被接受但無任何效果;程式碼休眠待原生支援成熟。
功能分級 level 功能分文本級(中西間隙、標點擠壓——任意文本片段適用)與段落級(禁則、垂懸字、懸掛、長詞——需行/段布局)。塊級元素預設全功能;data-jz-level="text" 子樹或選項 level:{text:"選擇器"}(如標題/按鈕)僅跑文本級。
justify 單份間距 每個 jz-charinline-block 原子項,句末標點只貢獻一份對齊伸縮量。

後續(架構已預留,不返工):標點修正(簡↔繁)、著重號、注音/Ruby。


選項

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,
    orphan: true,                   // 垂懸字避免(預設開);或 { chars: 2 }false 關
    // hanging:已停用、無效果(見「標點懸掛」一節)
    level: { text: "h1,h2,h3,button,.ui" }, // 此選擇器子樹僅跑文本級功能(見下)
    justifyAtoms: true,             // jz-char/jz-jinze 以 inline-block 作對齊原子;
                                    // 分頁器(Paged.js)下設 false 改 inline(見下)
    blocks: [                       // 分塊功能選擇
        { selector: ".code-comment", features: [ "spacing" ] },
    ],
});

分頁器(Paged.js 等)相容:justifyAtoms

預設 jz-charjz-jinzeinline-block,作 justify 之單一對齊原子(句末標點 單份間距)。但分頁器(如 Paged.js)之對齊引擎會把每個 inline-block 邊界當伸縮點、 與 CJK inter-ideograph 疊加,於窄欄混排時字距失控、整段散架。此時設 justifyAtoms: false——jz-charjz-jinzedisplay:inline(半形 halt 與禁則 nowrap 綁定均保留)。常規流式版面無需設定。

注意:margin 後備半形依賴 inline-blockwidth:0.5em),故 justifyAtoms:falsemargin 模式不可兼得——分頁器場景請用含 halt 之字型。

分塊功能選擇(HTML 屬性,就近繼承)

<p data-juzhen="spacing jiya"></p>     <!-- 白名單:僅此二功能 -->
<p data-juzhen-off="jiya"></p>          <!-- 黑名單:停用 jiya,其餘繼承 -->
<div lang="zh-Hans"></div>              <!-- lang 驅動全角/開明 -->

功能分級:文本級 vs 段落級(leveldata-jz-level

功能分兩級:

  • 文本級(中西間隙 spacing、標點擠壓 jiya):僅依字符鄰接,任意文本片段(單行 標題、按鈕、單一行內元素)皆適用且想要。
  • 段落級(禁則 jinze、垂懸字 orphan、標點懸掛 hanging、長詞 longWord):依賴 行/段布局(換行、行端、段末),對單行/非段落文本無意義甚至有害。

塊級元素(pdivlih1…)預設段落級(全功能)。把標題/按鈕/UI 文字標為 文本級(要間隙與標點半形、不要懸掛/避孤字/避頭尾):

<h1 data-jz-level="text">標題:只跑中西間隙與標點擠壓</h1>
<p data-jz-level="paragraph"></p>   <!-- 顯式全功能(巢狀於 text 子樹內可覆寫,最近祖先勝) -->
createJuzhen({ level: { text: "h1,h2,h3,button,.ui" } }); // 選擇器一鍵標記

標點懸掛(hanging)— 已評估停用

原計劃之標點懸掛(行端標點凸出版心)經 headless + 真實瀏覽器實測確認不可行、已全面停用hanging 選項仍被接受(API 相容)但無任何效果——縱使下游以 hanging:truedata-juzhen blocks 等任何方式要求,亦不會啟用(行端恒 squeeze;三重防逃逸:lineEnd 鎖死、modeForChar 降級、applyEdge 總開關)。

為何不可行(純負 margin 機制):

  1. 句號等標點墨色偏字框左下角;安全凸出量(< 1 個 CJK 字寬 1em)只把空白半框推出版心、 墨色仍在版心內(視覺不懸掛)。
  2. 凸出滿 1em 又釋放一整字寬、把下一字拉上本行、與懸掛標點重疊
  3. ResizeObserver 重排時,「自然佈局標記→施加 margin→佈局再變」致標記錯亂

真正的解是原生 CSS hanging-punctuation,惟 Chrome 至今不支援(僅 Safari 較全),且與本庫 inline-block 對齊原子結構衝突。故 hang 之程式碼與 CSS 保留休眠,俟原生支援成熟再議。 詳見 ARCHITECTURE.md §6.5.6


標點半形:halt 預設與 margin 後備

開明/半角式把標點收成「半形」,有兩種渲染機制:

機制 設定 原理 適用
halt(預設) jiya: true 字型 OpenType halt,由字型按字形正確定位、不重疊 字型含 haltNoto 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 屬性所在子樹決定(CSS 後代選擇器),故粒度極靈活:

  • 全域jiya: { halfWidth: "margin" }render() 於處理 root 自動掛屬性。
  • 逐區塊/逐語言:在 HTML 任一祖先手寫 data-jz-halfwidth="margin"(或 "halt"),即令該子樹用對應機制——無需另建實例。常見如「全頁 halt,某用 無 halt 字型之區塊掛 margin」,或反之(margin 頁中嵌一塊 halt 島)。
<!-- 全頁 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 逐字覆寫:

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. haltpython3 tools/measure-bias.py <font.ttf>(需 pip install fonttools)。 首行報該字型是否含 OpenType halt
    • halt → 直接用預設(jiya: true,halt 模式),無需配字,到此為止。
    • halt(多數系統字型、GBK 老字型)→ 用 margin 後備,續下。
  2. 取偏側建議:同一工具同時量各標點 glyph 之墨色中心、印出建議偏側表 (leftcenterrightfull)與可貼入 jiya.bias 之 JSON。
  3. 套用createJuzhen({ jiya: { halfWidth: "margin", bias: <貼入 JSON> } }); 或全用內建參考表(多數宋體系 GBK 字型與參考表相符,未必需覆寫)。
  4. 目視微調:以 demo/fallback.html(或自有頁面)對照原文,逐字檢查——
    • 標點與相鄰字重疊 → 該字墨色比量測更偏該側,偏側往「不移」方向調 (rightcenterleft)。
    • 標點與內容仍有空隙、不夠半形 → 往「多移」方向調(leftcenterright)。
    • 某標點根本無可壓空白(字面已滿全形)→ 設 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. 作用域類(用 scope.include 時):若設 scope.include: ".juzhen",須給內容 容器加對應 class(如 class="juzhen");juzhen.css 之 normalize 亦收斂於此域, 不外溢。
  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 關閉 Blink 原生 text-spacing-trimtext-autospace(避免與託管擠壓重複作用), 並令注入間隙 user-select: none(複製乾淨)。
  8. 段落屬性照常設、原子透明:聚珍把標點拆成 inline-block 原子,但已確保這些原子對 消費端段落樣式透明——可在段落正常設 text-indent(首行縮排)、hanging-punctuationtext-decoration(下劃線)等;庫內已歸零會被原子重複套用之繼承屬性、並令原子承接段落 裝飾,故首行縮排正常、下劃線連續不斷,無需任何額外配置(機制見 ARCHITECTURE「原子透明 原則」)。

字型與 locl(消費端設定)

聚珍不管字型;逐語言字型由你以 CSS 設定。推薦範式——逐 langfont-family 掛在 .juzhen 或帶 lang 之容器(直接設 jz-innerjz-char,它們靠繼承取字型):

.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)以 locllang 選地域字形 (如「直/海/骨」之簡繁日差異)。聚珍不改寫 lang、不關閉 locl,故只要你 ① 正確標 lang、② 用支援 locl 之字型,locl 即正常。body 文本之 locl 全由你的字型+ lang 決定;標點(jz-inner)之 halt 已顯式併入 locl,地域標點字形不致失。
  • 若只想全語言同一字型,直接 .juzhen { font-family: … } 即可(無需逐 lang)。

自定義字符分類(charClass

每個字符的分類(中文/西文、全寬標點各子類)抽象為規則集:內置默認規則集即聚珍 的分類表,消費端可經 options.charClass 擴充,以覆寫 spacingjiyajinze 等規則對特定字符之判定。語義為追加——把當前未分類之字符、或新符號歸入對應類。

createJuzhen({
    charClass: {
        cjk: "",                    // 追加為中文(CJK):與西文邊界補中西間隙、禁則/justify 視為單字
        western: "★☆",               // 追加為西文(半寬標點亦歸此):與 CJK 邊界補間隙
        biaodian: {                   // 追加為全寬標點,按子類(jiya charify/擠壓、禁則避頭尾)
            stop: "",               //   句末(。!?類)
            pause: "",                //   句內(,、;:類)
            open: "", close: "",      //   開/閉括號引號
            middle: "", liga: "",     //   間隔號/省略破折
        },
    },
});

典型用例:英文全角句號 U+FF0E,有別於中文句號 U+3002)預設未分類 ——不被 charify、不挤壓、與 CJK 間不補隙。若內容用 作句末標點,以 charClass: { biaodian: { stop: "" } } 歸為全寬句末標點,即比照 處理(開明式挤半形、 全角式佔全形、行首避頭尾)。

要點:

  • 向下兼容:不傳 charClass 時,分類與行為與內置默認完全一致
  • 追加語義(v1:擴充內置類;支援把內置字符「重分類/移除」(屬罕見需求,後續再議)。 勿把同一字符同時加入相衝之兩類。
  • 結構性 ASCII(字母數字 A-Za-z0-9、運算符 +-*/ 等)為固定集、不開放覆寫——它們 服務於運算符/複合詞等內部規則,非「字符類型」。
  • margin 後備模式下若新增開/閉括號,其墨色偏側未必命中內建參考表,可一併以 jiya.bias 指定(見「字型適配工作流」)。

v1 向下相容

// 既有消費端零改動:
const cjk = createCjkAutospace({ pillSelector: "code, kbd" });
cjk.apply(document.body);

IIFE 構建另掛 globalThis.createCjkAutospace 別名。與 v1 之差異(行為等價、 不要求逐位一致):

  • 間隙標記由 <span class="cjk-autospace"> 變為 <jz-hws class="cjk-autospace"> (仍帶原 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 後備生效。demo/resize-check.html可拖曳容器檢查頁:拖動方框寬度,驗證中西間隙在 各寬度下恆穩(含行末去隙之自我應驗、pill 跨行右隙、貨幣/拼音/西里爾外文補隙、全角省略號 與各式中文標點無誤隙),可切 justify 與「顯示間隙」描邊。版面相關結論(justify 單份間距、 擠壓視覺量、行端、inline-block 基線、間隙隨寬度之穩定性)須於 ChromeSafariFirefox 三引擎目視核對——jsdom 僅驗證 DOM 結構正確性。


授權

MIT。借鑑 Han.cssHeti 之思路與分類表(MIT),成品自含、不依賴其執行期程式碼。