Files
admin c00fb3e482 feat: 逐元素標點樣式覆寫 data-jz-style + lang.stylePolicy(非段落用半角式等)
下游需求:非段落/特定範圍元素用半角式(banjiao),段落用全角/開明。原標點樣式僅由 lang
決定(lang.policy/lang.style),無逐元素覆寫。新增與 data-jz-level/level.text 同構之逐元素樣式
機制(banjiao 渲染早已就緒,此為純路由、無新壓縮邏輯、無新 CSS)。

設置格式(供下游):
・HTML 屬性:data-jz-style="banjiao|kaiming|quanjiao"——標於元素或其祖先,該子樹用此樣式。
・選項:lang: { stylePolicy: [{ selector, style }] }——如
  [{ selector: "h1,h2,button,.ui", style: "banjiao" }]。
・優先序:逐元素覆寫(近祖先勝、同層屬性優先於選擇器)> lang.policy > lang.style > 內建。

實作:resolveStyle 先查 resolveStyleOverride(自身及祖先鏈掃 data-jz-style 屬性/stylePolicy
選擇器命中),無則回退 lang。屬性值非法(拼錯)則忽略、續回退。types.ts 補 lang.stylePolicy
+導出 StyleRule;index.ts 正規化(stylePolicy 預設 [])。

冒烟三例通過(屬性 banjiao/選擇器 banjiao/屬性覆蓋選擇器);既有 121 測試無回歸(stylePolicy
預設空、行為不變)。回歸測試+文檔隨後補(e2e 階段)。
2026-07-11 01:42:31 +08:00

184 lines
8.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Finder } from "./core/finder.js";
import type { LangClass, LocaleConfig, PunctStyle, StyleRule } from "./core/locale.js";
import type { Bias, BiasTable, BdSubclass, CharClassOverride, Ruleset } from "./core/unicode.js";
export type { LangClass, PunctStyle, StyleRule, Bias, BiasTable, BdSubclass, CharClassOverride, Ruleset };
/** 長英文詞包裝設定。 */
export interface LongWordOptions {
minLength?: number;
lang?: string;
}
/** 垂懸字避免設定(§6.5.1)。布林 true 等同 { chars: 2 }。 */
export interface OrphanOptions {
/** 段末行至少保留之 CJK 實義字數(標點/符號不計;預設 2)。 */
chars?: number;
}
/** 標點擠壓設定(架構 §6.3)。布林 true 等同 { halfWidth: "halt" }。 */
export interface JiyaOptions {
/** 半形渲染機制:
* - "halt"(預設)— 用字型 OpenType `halt`,由字型按字形正確定位,需字型支援;
* - "margin" — 負 margin 後備模式,供缺 halt 之字型,依字形偏側表收半形。 */
halfWidth?: "halt" | "margin";
/** margin 後備模式之字形偏側覆寫表(逐 lang 逐字),覆寫內建參考預設。
* halt 模式不讀此欄。 */
bias?: BiasTable;
}
/** 分塊功能選擇:選擇器 → 該塊內生效之功能集(覆寫全域)。 */
export interface BlockRule {
selector: string;
features: string[];
}
/** createJuzhen 之公開選項。所有欄位皆可選;預設見 normalizeOptions。 */
export interface JuzhenOptions {
/** 語言與全角/開明政策。 */
lang?: {
/** 無 lang 祖先時之預設語言標籤(預設 "zh-Hant")。 */
default?: string;
/** 全域標點擠壓預設:quanjiao(全角式)|kaiming(開明式)|
* banjiao(半角式)。覆寫內建逐語言預設,可被 policy 逐 langstylePolicy 逐元素覆寫。 */
style?: PunctStyle;
/** 逐 lang 覆寫。 */
policy?: Partial<Record<LangClass, PunctStyle>>;
/** **逐元素**覆寫標點樣式(優先於 lang/policy/style):選擇器命中之元素及其子樹
* 用指定樣式,如非段落/UI 元素用半角式 `[{ selector: "h1,h2,button,.ui", style: "banjiao" }]`。
* 與 HTML 屬性 `data-jz-style="banjiao|kaiming|quanjiao"` 同一機制;近祖先勝、屬性優先於選擇器。 */
stylePolicy?: StyleRule[];
};
/** 自定義字符分類(擴充內置規則集)。把未分類字符或新符號歸入中文/西文/
* 全寬標點各子類,以覆寫 spacing/jiya 等規則之分類判定(追加語義)。 */
charClass?: CharClassOverride;
/** 功能分級標記(§6.5.0a)。段落級功能(禁則/行端/懸掛/垂懸字/長詞斷字)
* 僅作用於段落級元素;文本級功能(中西間隙/標點擠壓)任意文本皆適用。
* - `text`:選擇器,命中之子樹**僅文本級**(標題/按鈕/UI 文字典型);亦可
* 經元素屬性 `data-jz-level="text"``"paragraph"` 標記(屬性優先、最近祖先勝)。
* 預設:塊級元素(blockTags)→ 段落級(全功能)。 */
level?: {
text?: string;
};
/** justify 原子化:jz-charjz-jinze 是否以 inline-block 作對齊單一原子
* (預設 true)。設 false 改 display:inline——用於分頁器(如 Paged.js)之
* 對齊引擎會把每個 inline-block 邊界當伸縮點、與 CJK inter-ideograph 疊加
* 致窄欄散架之情形(halt 半形與 jinze nowrap 仍保留)。 */
justifyAtoms?: boolean;
/** 作用域限定(§3.6)。 */
scope?: {
root?: Element | string;
include?: string;
avoid?: string;
};
spacing?: boolean;
longWord?: boolean | LongWordOptions;
jiya?: boolean | JiyaOptions;
jinze?: boolean;
orphan?: boolean | OrphanOptions;
/** @deprecated 標點懸掛**已移除**(§6.5.6):純負 margin 機制經實測為機制級死路(寬度
* 守恆矛盾,CSS 接觸不到斷行器),原生 hanging-punctuation 為唯一正解但僅 Safari 支援、
* 且與本庫 inline-block 原子互斥。此選項仍被接受以保 API 相容,但**無任何效果**。 */
hanging?: boolean;
/** @reserved 標點修正(簡/繁正規化)——架構已預留(切片 #7),**當前無任何效果**。
* 注意:此為功能開關,與 `charClass.biaodian`(標點子類擴充表,語義完全不同)同名,勿混。 */
biaodian?: boolean;
/** @reserved 著重號/裝飾——架構已預留(切片 #8),**當前無任何效果**。 */
emphasis?: boolean;
/** @reserved Ruby/注音——架構已預留(切片 #8),**當前無任何效果**。 */
ruby?: boolean;
/** 分塊功能選擇(§3.6)。 */
blocks?: BlockRule[];
styleInlines?: string[];
blockTags?: string[];
skipTags?: string[];
/** 整體(pill)元素選擇器(預設 "code, kbd")。pill 之邊界口徑與 isolate 完全
* 相同(行為覆蓋),差異僅在內部不可見;同元素雙選擇器命中時 pill 勝出。 */
pillSelector?: string;
/** 內外隔離元素選擇器(預設 "mark")。傳 "" 停用(mark 回歸透明)。 */
isolateSelector?: string;
skipAttribute?: string;
isSkipped?: (node: Node) => boolean;
}
/** 正規化後之內部選項。 */
export interface ResolvedOptions {
locale: LocaleConfig;
/** 最終字符分類規則集(內置默認 + charClass 覆寫)。 */
ruleset: Ruleset;
justifyAtoms: boolean;
/** 功能分級之 text-level 選擇器(§6.5.0a);null 表示未設。 */
levelText: string | null;
scope: {
root: Element | string | null;
include: string | null;
avoid: string | null;
};
features: {
spacing: boolean;
longWord: boolean;
jiya: boolean;
jinze: boolean;
orphan: boolean;
biaodian: boolean;
emphasis: boolean;
ruby: boolean;
};
longWord: {
minLength: number;
lang: string;
};
/** 垂懸字避免設定(解析後)。 */
orphan: {
chars: number;
};
/** 擠壓半形渲染設定(halt 預設/margin 後備)。 */
jiyaConfig: {
halfWidth: "halt" | "margin";
bias: BiasTable;
};
blocks: BlockRule[];
finder: {
styleInlines: Set<string>;
blockTags: Set<string>;
skipTags: Set<string>;
pillSelector: string;
isolateSelector: string;
skipAttribute: string | null;
isSkipped: ((node: Node) => boolean) | null;
};
}
/** renderrevert 之統一上下文(§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";
/** 功能分級(§6.5.0a):text=任意文本片段皆適用;paragraph=需行/段布局。 */
export type FeatureLevel = "text" | "paragraph";
interface BasePass {
name: string;
order: number;
kind: PassKind;
/** 功能分級(§6.5.0a)。paragraph 級 pass 對 text-level 元素自動跳過。 */
level: FeatureLevel;
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;