82c2d22535
句內點號(,、;:)逐字包進 <span class="cjk-squeeze">,配合消費端 CSS `font-feature-settings: "halt"` 擠成半形;句末點號(。?!)保持全形—— 此即開明式。span 內含真標點字元,複製不污染原文。 clreq 之「連續標點擠壓」與「行端壓縮」屬 layout-aware 規則,交由原生 `text-spacing-trim` 處理(Chrome 123+ 預設生效,要求字型有 halt/chws), JS 不涉入以免重複擠壓。真·行尾懸掛(hanging-punctuation)僅 Safari 支援 且繁體橫排不宜,故不納入配方。 預設啟用,支援 `langWhitelist` 依 lang 前綴限定擠壓範圍、`marks` 與 `squeezeClass` 自訂。含 16 項 jsdom DOM 測試。
110 lines
4.4 KiB
TypeScript
110 lines
4.4 KiB
TypeScript
/**
|
||
* cjk-autospace — CJK ↔ Latin 邊界自動間距 shim(Pangu.js v7.2.1 風格)。
|
||
*
|
||
* 用法:
|
||
* ```ts
|
||
* import { createCjkAutospace } from "./cjk-autospace.js";
|
||
*
|
||
* const autospace = createCjkAutospace({
|
||
* pillSelector: "code, kbd, .cjk-pill",
|
||
* });
|
||
* autospace.apply(document.body);
|
||
* ```
|
||
*/
|
||
|
||
export interface CjkAutospaceOptions {
|
||
/** 行內塊(PILL)選擇器。預設 `"code, kbd"`。 */
|
||
pillSelector?: string;
|
||
|
||
/** 樣式標籤集合(shim 透明走過)。tagName 用大寫。 */
|
||
styleInlines?: readonly string[];
|
||
|
||
/** 段落級標籤集合(Pass A run 邊界)。tagName 用大寫。 */
|
||
blockTags?: readonly string[];
|
||
|
||
/** Skip-ancestor 標籤集合(subtree 整個跳過)。tagName 用大寫。 */
|
||
skipTags?: readonly string[];
|
||
|
||
/** Marker span 之 class 名。預設 `"cjk-autospace"`。 */
|
||
autospaceClass?: string;
|
||
|
||
/** Skip-ancestor 額外屬性檢查(任一祖先帶此屬性則 skip)。預設 `"data-md-key"`。 */
|
||
skipAttribute?: string;
|
||
|
||
/** 自訂 skip 判定(在內建判定前先檢查)。回傳 true 表示應跳過。 */
|
||
isSkipped?: (node: Node) => boolean;
|
||
|
||
/**
|
||
* Pass C(**預設啟用**):把 `[A-Za-z]{minLength,}` 之長英文段包進
|
||
* `<span lang="…">`,讓瀏覽器原生 `hyphens: auto` 可斷詞並渲染連字
|
||
* 符(連字符純由 CSS 渲染,DOM 內不存在 → 複製不污染原文)。需消費
|
||
* 端 CSS 配:
|
||
*
|
||
* ```css
|
||
* [lang|="en"] {
|
||
* hyphens: auto;
|
||
* -webkit-hyphens: auto;
|
||
* hyphenate-limit-chars: 6 3 3; // 詞長 / 斷點前 / 斷點後最少字符
|
||
* }
|
||
* ```
|
||
*
|
||
* 傳 `false` 顯式關閉;`true` 或省略等同 `{ minLength: 6, lang: "en" }`。
|
||
*/
|
||
longWordWrap?: boolean | {
|
||
/** 觸發包裝之最短字母串長度。預設 6。 */
|
||
minLength?: number;
|
||
/** 包裝 span 之 `lang` 屬性值。預設 `"en"`。 */
|
||
lang?: string;
|
||
};
|
||
|
||
/**
|
||
* Pass D(**預設啟用**):開明式標點擠壓。把句內點號(`,、;:`)包進
|
||
* `<span class="…">`,配合消費端 CSS `font-feature-settings: "halt"` 擠
|
||
* 成半形;句末點號(`。?!`)保持全形——此即「開明式」。span 內含真
|
||
* 標點字元本身,故 Copy / Paste 返回原文不變。
|
||
*
|
||
* `font-feature-settings: "halt"` 套整個元素只會把所有標點變半形(半角
|
||
* 式),故必須逐字選擇性包裝——這是 CSS 表達不了、JS 不可替代的一層。
|
||
* clreq 之「連續標點擠壓」與「行端壓縮」交由原生 CSS(layout-aware):
|
||
*
|
||
* ```css
|
||
* .cjk-squeeze { font-feature-settings: "halt" 1; } // 句內點號半形
|
||
* .article { text-spacing-trim: normal; } // 連續標點 + 行端壓縮
|
||
* ```
|
||
*
|
||
* `text-spacing-trim` 於 Chrome 123+ 預設生效(要求字型有 `halt`/`chws`,
|
||
* Noto CJK 滿足);`hanging-punctuation`(真·懸掛)僅 Safari 支援且繁體
|
||
* 橫排不宜,故不納入配方。
|
||
*
|
||
* 傳 `false` 顯式關閉;`true` 或省略等同預設(marks `[",","、",";",":"]`、
|
||
* class `"cjk-squeeze"`、不限語言)。
|
||
*/
|
||
punctuationSqueeze?: boolean | {
|
||
/** 包裝 span 之 class 名。預設 `"cjk-squeeze"`。 */
|
||
squeezeClass?: string;
|
||
/** 要擠壓之標點字元(單字元)陣列。預設句內點號 `[",", "、", ";", ":"]`。 */
|
||
marks?: readonly string[];
|
||
/**
|
||
* BCP-47 語言前綴白名單。非空時僅擠壓最近祖先 `lang` 前綴命中之文本
|
||
* (`"zh"` 命中 `"zh-Hant"`;無 `lang` 祖先視為不命中)。空陣列或省略
|
||
* 則不限語言。例:`["zh-Hans"]` 限定簡體(開明式於簡體字型下字面位
|
||
* 於左下角,擠壓最自然;繁體點號居中,視覺略異)。
|
||
*/
|
||
langWhitelist?: readonly string[];
|
||
};
|
||
}
|
||
|
||
export interface CjkAutospaceInstance {
|
||
/**
|
||
* 對指定 root(預設 `document.body`)套用 Pass A + Pass B。
|
||
* 在已注入 marker 之 DOM 上重跑為冪等(會跳過既有 `.cjk-autospace`)。
|
||
*/
|
||
apply(root?: ParentNode | Document): void;
|
||
}
|
||
|
||
export function createCjkAutospace(options?: CjkAutospaceOptions): CjkAutospaceInstance;
|
||
|
||
declare global {
|
||
var createCjkAutospace: typeof createCjkAutospace;
|
||
}
|