fix(web): 修復排版邊界與增量生命週期

This commit is contained in:
2026-10-02 19:08:02 +08:00
parent 1c2fb0ed59
commit b1a16c4969
39 changed files with 4763 additions and 673 deletions
+6
View File
@@ -0,0 +1,6 @@
import type { CharSpan } from "./finder.js";
import type { RenderContext } from "../types.js";
/** span 按逆文件序處理,來源 offset 不受後方切分影響。outer/inner 須為本 pass 新產物。 */
export declare function wrapCrossAtom(span: CharSpan, outer: HTMLElement, inner: HTMLElement, pass: string, ctx: RenderContext): void;
/** 局部回退也涵蓋原子之另一端;只還原私有記錄,不採信可偽造的作者屬性。 */
export declare function revertCrossAtoms(pass: string, ctx: RenderContext): Set<Node>;
+4
View File
@@ -7,6 +7,10 @@ export interface CreateOptions {
/** 文本內容。 */
text?: string;
}
/** 完成受控解包後登記;只接受真實、已移除且空的受管產物。 */
export declare function recordUnwrappedParent(el: Element, parent: Node, ownedElements: WeakSet<Element>): void;
/** 其他 pass/實例合法撤回來源包裹後,搬移記錄沿受控解包鏈查詢現父。 */
export declare function resolveUnwrappedParent(parent: Node): Node | null;
/** 造一個帶 pass 標記之 jz-* 元素。 */
export declare function createJz(ownedElements: WeakSet<Element>, tag: JzTag, pass: string, kind: JzKind, opts?: CreateOptions): HTMLElement;
/** 記錄後寫屬性;同實例重寫時仍保留最初原值。 */
+11 -8
View File
@@ -25,6 +25,7 @@ export interface AdjacentChar {
textNode: Text;
offset: number;
ch: string;
parts: TextSpan[];
}
/** 行內元素對排版之口徑分類(§4.1)——恰屬其一,優先序見 Finder.category。 */
export type InlineCategory = "avoid" | "pill" | "isolate" | "block" | "transparent" | "opaque";
@@ -115,19 +116,21 @@ export declare class Finder {
inScope(node: Node): boolean;
/** 走訪 root 下所有「可處理」文本節點(已過濾 avoid / pill 內部 / 非作用域)。 */
eachTextNode(root: Element, cb: (node: Text) => void): void;
/** 走訪 root 下所有 block 元素(含 root 本身若為 block)。 */
/** 走訪 root 下可處理之 block(含 root 本身若為 block);pill/avoid 內部不可見。 */
eachBlock(root: Element, cb: (block: Element) => void): void;
/**
* 收集 block 之邏輯文本 run(架構 §4.1,I7):透明行內(isTransparent:styleInlines
* + wrap 類 jz-*)穿越、run 延續;block/pill/avoid 中斷 run;isolate 與**未知行內
* 標籤**(嚴格白名單)中斷 run 且內部自成獨立 run。回傳 text.length >= 2 之 run。
* 標籤**(嚴格白名單)中斷 run 且內部自成獨立 run。預設至少兩原子;includeSingletons
* 供段落邊界查詢保留單原子 run,不改間隙處理之預設。
*/
collectRuns(block: Element): LogicalRun[];
/**
* pill 之邏輯鄰字(架構 §4.1):經樣式鏈遞迴,遇 pill/block/avoid/間隙
* marker 即止。direction = -1 取前鄰字,+1 取後鄰字。
*/
adjacentLogicalChar(el: Element, direction: -1 | 1): AdjacentChar | null;
collectRuns(block: Element, includeSingletons?: boolean, feature?: string): LogicalRun[];
/** 從節點邊界或 Text offset 取相鄰碼點;只穿越透明、域內內容,scopeRoot 為硬邊界。 */
private adjacentTextCodePoint;
/** 補全透明 Text 邊界拆開的 surrogate pair;孤立碼元保留自身,不跨任何邊界拼接。 */
private adjacentCodePoint;
/** pill 之完整鄰字(base+至多一個 VS);after 供查單個作者空格另一側,不越 scopeRoot。 */
adjacentLogicalChar(el: Element, direction: -1 | 1, scopeRoot?: Element, after?: AdjacentChar, feature?: string): AdjacentChar | null;
/**
* 節點之功能級別(§6.5.0a)。自 node 向上找**最近**之顯式信號:元素屬性
* `data-jz-level="text|paragraph"`(最優先),或命中 `level.text` 選擇器(→ text);
+35 -36
View File
@@ -47,49 +47,48 @@ jz-inner {
}
jz-char.jz-half jz-inner,
jz-char.jz-half-le jz-inner {
font-feature-settings: "locl" 1, "halt" 1;
-webkit-font-feature-settings: "locl" 1, "halt" 1;
font-feature-settings: var(--jz-half-features, "locl" 1, "halt" 1);
-webkit-font-feature-settings: var(--jz-half-features, "locl" 1, "halt" 1);
}
[data-jz-halfwidth=margin] jz-char.jz-half jz-inner,
[data-jz-halfwidth=margin] jz-char.jz-half-le jz-inner {
font-feature-settings: normal;
-webkit-font-feature-settings: normal;
[data-jz-halfwidth=margin] {
--jz-half-features: normal;
--jz-half-width: 0.5em;
--jz-half-inner-display: inline-block;
--jz-half-center-offset: -0.25em;
--jz-half-right-offset: -0.5em;
}
[data-jz-halfwidth=margin] jz-char.jz-half.jz-ink-left,
[data-jz-halfwidth=margin] jz-char.jz-half.jz-ink-center,
[data-jz-halfwidth=margin] jz-char.jz-half.jz-ink-right,
[data-jz-halfwidth=margin] jz-char.jz-half-le.jz-ink-left,
[data-jz-halfwidth=margin] jz-char.jz-half-le.jz-ink-center,
[data-jz-halfwidth=margin] jz-char.jz-half-le.jz-ink-right {
width: 0.5em;
[data-jz-halfwidth=halt] {
--jz-half-features: "locl" 1, "halt" 1;
--jz-half-width: auto;
--jz-half-inner-display: inline;
--jz-half-center-offset: 0;
--jz-half-right-offset: 0;
}
jz-char.jz-half.jz-ink-left,
jz-char.jz-half.jz-ink-center,
jz-char.jz-half.jz-ink-right,
jz-char.jz-half-le.jz-ink-left,
jz-char.jz-half-le.jz-ink-center,
jz-char.jz-half-le.jz-ink-right {
width: var(--jz-half-width, auto);
overflow: visible;
}
[data-jz-halfwidth=margin] jz-char.jz-half.jz-ink-left jz-inner,
[data-jz-halfwidth=margin] jz-char.jz-half.jz-ink-center jz-inner,
[data-jz-halfwidth=margin] jz-char.jz-half.jz-ink-right jz-inner,
[data-jz-halfwidth=margin] jz-char.jz-half-le.jz-ink-left jz-inner,
[data-jz-halfwidth=margin] jz-char.jz-half-le.jz-ink-center jz-inner,
[data-jz-halfwidth=margin] jz-char.jz-half-le.jz-ink-right jz-inner {
display: inline-block;
}
[data-jz-halfwidth=margin] jz-char.jz-ink-center jz-inner {
margin-left: -0.25em;
}
[data-jz-halfwidth=margin] jz-char.jz-ink-right jz-inner {
margin-left: -0.5em;
}
[data-jz-halfwidth=margin] jz-char jz-inner {
jz-char.jz-half.jz-ink-left jz-inner,
jz-char.jz-half.jz-ink-center jz-inner,
jz-char.jz-half.jz-ink-right jz-inner,
jz-char.jz-half-le.jz-ink-left jz-inner,
jz-char.jz-half-le.jz-ink-center jz-inner,
jz-char.jz-half-le.jz-ink-right jz-inner {
display: var(--jz-half-inner-display, inline);
text-decoration: inherit;
}
[data-jz-halfwidth=halt] jz-char.jz-half jz-inner,
[data-jz-halfwidth=halt] jz-char.jz-half-le jz-inner {
font-feature-settings: "locl" 1, "halt" 1;
-webkit-font-feature-settings: "locl" 1, "halt" 1;
margin-left: 0;
jz-char.jz-half.jz-ink-center jz-inner,
jz-char.jz-half-le.jz-ink-center jz-inner {
margin-left: var(--jz-half-center-offset, 0);
}
[data-jz-halfwidth=halt] jz-char.jz-half,
[data-jz-halfwidth=halt] jz-char.jz-half-le {
width: auto;
jz-char.jz-half.jz-ink-right jz-inner,
jz-char.jz-half-le.jz-ink-right jz-inner {
margin-left: var(--jz-half-right-offset, 0);
}
/* src/css/_jinze.css */
+791 -170
View File
File diff suppressed because it is too large Load Diff
+791 -170
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -4,19 +4,21 @@ import type { Bias, BiasTable, BdSubclass, CharClassOverride, Ruleset } from "./
export type { LangClass, PunctStyle, StyleRule, Bias, BiasTable, BdSubclass, CharClassOverride, Ruleset };
/** 長英文詞包裝設定。 */
export interface LongWordOptions {
/** 有限安全整數且 ≥2;無效值回退 6。 */
minLength?: number;
lang?: string;
}
/** 垂懸字避免設定(§6.5.1)。布林 true 等同 { chars: 2 }。 */
export interface OrphanOptions {
/** 段末行至少保留之 CJK 實義字數(標點/符號不計;預設 2)。 */
/** 段末行至少保留之 CJK 實義字數(標點/符號不計;預設 2)。
* 有限值且 ≥1,向下取整;無效值回退 2。 */
chars?: number;
}
/** 標點擠壓設定(架構 §6.3)。布林 true 等同 { halfWidth: "halt" }。 */
export interface JiyaOptions {
/** 半形渲染機制:
* - "halt"(預設)— 用字型 OpenType `halt`,由字型按字形正確定位,需字型支援;
* - "margin" — 負 margin 後備模式,供缺 halt 之字型,依字形偏側表收半形。 */
* - "margin" — 0.5em 盒寬後備模式,供缺 halt 之字型,依字形偏側表定位。 */
halfWidth?: "halt" | "margin";
/** margin 後備模式之字形偏側覆寫表(逐 lang 逐字),覆寫內建參考預設。
* halt 模式不讀此欄。 */
@@ -179,6 +181,8 @@ interface BasePass {
/** 共享 P1 走訪之字元包裝 pass。 */
export interface CharifyPass extends BasePass {
kind: "charify";
/** 在逐 Text 收集前處理不可被 DOM 分割的跨節點原子。 */
prepare?: (ctx: RenderContext) => void;
collect: (node: Text, ctx: RenderContext) => WrapRequest[];
}
/** 自跑走訪/版面後處理之 pass。 */