2d42c7ecf2
從零重寫之第一垂直切片(依 ARCHITECTURE.md 契約),涵蓋並超越 v1 全部功能。 核心基座(src/core):unicode 字元表+標點子類+字形側位;locale 語言與 全角/開明政策(全域 lang.style +逐 lang policy);dom(jz-* 工廠+可還原 標記);finder 遍歷引擎(avoid/scope.include/邏輯 run/pill 鄰字/分塊閘); pass 聚合管線。 功能 pass(src/typeset): - spacing:中西間隙採 margin 模型——包裹邊界左側字、以 margin-right 留隙, 不注入空白字元(textContent 完全不變、複製緊湊、justify 不暴增、不撐框、 行首乾淨)。含跨樣式邊界、pill、原檔空格移除。 - gapTrim(lineedge):layout pass,行末去隙,修正 justify 右緣內縮。 - longword:長英文詞 <span lang> + hyphens。 - jiya:標點 charify + 全角/開明/半角(全域 + 逐 lang);半形以字型 OpenType halt 渲染(按字形正確定位、不重疊,居中字形 !? 與繁體 locl 句逗點號皆正確); 行內連續擠壓。 - jinze:行首行末避頭尾(閉類/點號綁前字、開類綁後字,以詞元為邊界、長引文 中段可斷);亦為 justify 綁定載體。 - lineedge:行端擠壓 layout pass(括號引號;ResizeObserver 重算,瀏覽器專用)。 §8 justify:jz-char inline-block 原子,句末標點單份間距。 v1 相容層(compat/v1):createCjkAutospace().apply 映射,IIFE 全域別名。 構建:esbuild → ESM/IIFE/CSS;tsc → d.ts。測試:39 個 jsdom 案例。 demo:簡繁並列+justify+強制繁體開明+標點寬度量測,載 Noto webfont 作參考 (擠壓依賴字型 halt)。間隔號 · 不觸發中西間隙;200% 等詞元不被禁則切斷。
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import type { Finder } from "./core/finder.js";
|
||
import type { LangClass, LocaleConfig, PunctStyle } from "./core/locale.js";
|
||
export type { LangClass, PunctStyle };
|
||
/** 長英文詞包裝設定。 */
|
||
export interface LongWordOptions {
|
||
minLength?: number;
|
||
lang?: string;
|
||
}
|
||
/** 分塊功能選擇:選擇器 → 該塊內生效之功能集(覆寫全域)。 */
|
||
export interface BlockRule {
|
||
selector: string;
|
||
features: string[];
|
||
}
|
||
/** createJuzhen 之公開選項。所有欄位皆可選;預設見 normalizeOptions。 */
|
||
export interface JuzhenOptions {
|
||
/** 語言與全角/開明政策。 */
|
||
lang?: {
|
||
/** 無 lang 祖先時之預設語言標籤(預設 "zh-Hant")。 */
|
||
default?: string;
|
||
/** 全域標點擠壓預設:quanjiao(全角式)|kaiming(開明式)|
|
||
* banjiao(半角式)。覆寫內建逐語言預設,可被 policy 逐 lang 覆寫。 */
|
||
style?: PunctStyle;
|
||
/** 逐 lang 覆寫(最高優先)。 */
|
||
policy?: Partial<Record<LangClass, PunctStyle>>;
|
||
};
|
||
/** 間隙標記沿用之 class(v1 相容;預設無額外 class)。 */
|
||
autospaceClass?: string;
|
||
/** 作用域限定(§3.6)。 */
|
||
scope?: {
|
||
root?: Element | string;
|
||
include?: string | null;
|
||
avoid?: string;
|
||
};
|
||
spacing?: boolean;
|
||
longWord?: boolean | LongWordOptions;
|
||
jiya?: boolean;
|
||
jinze?: boolean;
|
||
hanging?: boolean;
|
||
biaodian?: boolean;
|
||
emphasis?: boolean;
|
||
ruby?: boolean;
|
||
/** 分塊功能選擇(§3.6)。 */
|
||
blocks?: BlockRule[];
|
||
styleInlines?: string[];
|
||
blockTags?: string[];
|
||
skipTags?: string[];
|
||
pillSelector?: string;
|
||
skipAttribute?: string;
|
||
isSkipped?: (node: Node) => boolean;
|
||
}
|
||
/** 正規化後之內部選項。 */
|
||
export interface ResolvedOptions {
|
||
locale: LocaleConfig;
|
||
autospaceClass: string;
|
||
scope: {
|
||
root: Element | string | null;
|
||
include: string | null;
|
||
avoid: string | null;
|
||
};
|
||
features: {
|
||
spacing: boolean;
|
||
longWord: boolean;
|
||
jiya: boolean;
|
||
jinze: boolean;
|
||
hanging: boolean;
|
||
biaodian: boolean;
|
||
emphasis: boolean;
|
||
ruby: boolean;
|
||
};
|
||
longWord: {
|
||
minLength: number;
|
||
lang: string;
|
||
};
|
||
blocks: BlockRule[];
|
||
finder: {
|
||
styleInlines: Set<string>;
|
||
blockTags: Set<string>;
|
||
skipTags: Set<string>;
|
||
pillSelector: string;
|
||
skipAttribute: string | null;
|
||
isSkipped: ((node: Node) => boolean) | null;
|
||
};
|
||
}
|
||
/** render/revert 之統一上下文(§3.3)。 */
|
||
export interface RenderContext {
|
||
root: Element;
|
||
options: ResolvedOptions;
|
||
finder: Finder;
|
||
}
|
||
/**
|
||
* charify 階段之包裝請求:把 node.data[start, end) 包進 build() 造出之元素。
|
||
* 同階段各 pass 之請求須互不重疊(標點/字母段天然不交集)。
|
||
*/
|
||
export interface WrapRequest {
|
||
start: number;
|
||
end: number;
|
||
build: (text: string) => HTMLElement;
|
||
pass: string;
|
||
}
|
||
/** pass 種類。 */
|
||
export type PassKind = "charify" | "standalone";
|
||
interface BasePass {
|
||
name: string;
|
||
order: number;
|
||
kind: PassKind;
|
||
enabled: (options: ResolvedOptions) => boolean;
|
||
revert: (ctx: RenderContext) => void;
|
||
}
|
||
/** 共享 P1 走訪之字元包裝 pass。 */
|
||
export interface CharifyPass extends BasePass {
|
||
kind: "charify";
|
||
collect: (node: Text, ctx: RenderContext) => WrapRequest[];
|
||
}
|
||
/** 自跑走訪/版面後處理之 pass。 */
|
||
export interface StandalonePass extends BasePass {
|
||
kind: "standalone";
|
||
render: (ctx: RenderContext) => void;
|
||
}
|
||
export type Pass = CharifyPass | StandalonePass;
|