refactor: 元素分類單一入口 finder.category + ResizeObserver 派發序不變量確認
承階段二之兩件收尾:確認 lineedge 雙 ResizeObserver 之派發序(審計唯一未闭環項),並把散落 各遍歷原語之元素分類優先序收斂為單一 finder.category。 ResizeObserver 派發序——**確認為非缺陷**(Chrome headless 實測):resize 時回調按 observer **創建順序**派發(ResizeObserver 規範遍歷 observers 之插入序,非 observe 序;實測 seq=12—— 反向 observe 仍按創建序)。lineEdgePass(order 90)之 observer 先於 gapTrimPass(order 92)建、 故每次 resize 皆先派發,gapTrim 之行末去隙判定恆基於 lineEdge **已套用**之半形版面(非陳舊態)。 以註解鎖為不變量(勿調換兩 pass 之 order/創建序),不需改碼。 元素分類單一入口:新增 `Finder.category(el): InlineCategory`——一元素恰屬 avoid/pill/isolate/ block/transparent/opaque 之一,優先序 `avoid > pill > isolate > block > transparent > opaque` 集中一處(原散落 collectRuns/jiyaAdjacency/canSplit/isTransparent 各自短路、易漂移)。 ・isTransparent = `category === "transparent"`;collectRuns/jiyaAdjacency 改 `switch(category)`; canSplit = `SPLITTABLE ∩ transparent`(結構切分為獨立層、白名單更窄)。 ・行為等價(121 全綠);多命中優先序(data-jz-skip pill→avoid、pill+isolate→pill、未知行內 →opaque 邊界)由既有行為測試覆蓋。 ・README 三分類要點+ARCHITECTURE §4.1 補優先序與 category 說明。
This commit is contained in:
Vendored
+19
-14
@@ -15,6 +15,8 @@ export interface AdjacentChar {
|
||||
offset: number;
|
||||
ch: string;
|
||||
}
|
||||
/** 行內元素對排版之口徑分類(§4.1)——恰屬其一,優先序見 Finder.category。 */
|
||||
export type InlineCategory = "avoid" | "pill" | "isolate" | "block" | "transparent" | "opaque";
|
||||
export declare class Finder {
|
||||
private readonly opts;
|
||||
private readonly styleSet;
|
||||
@@ -39,20 +41,23 @@ export declare class Finder {
|
||||
* 一切 pass 不可見——不 charify、不收 run、不切分、不於其內插間隙(下游問題六:
|
||||
* .katex 內部曾被 jiya/spacing 直接變更)。 */
|
||||
insidePill(node: Node): boolean;
|
||||
/** 邏輯文本應**透明穿越**之行內元素:樣式行內標籤,以及聚珍自身之 wrap 類
|
||||
* jz-*(jz-hws 間隙、jz-jinze 禁則群組、jz-char/jz-inner 標點原子)。穿越這些
|
||||
* 才能正確找到跨包裝之邏輯鄰字(如 jinze 已把 pill 納入 jz-jinze 後,pill 之左
|
||||
* 鄰字仍須可達;I7)。
|
||||
* ⚠ 以**元素**而非標籤名判定:pill/isolate/avoid 縱使標籤名在樣式集亦為邊界、
|
||||
* 不可穿越(下游問題六:KaTeX 全樹皆 SPAN,名字判定令整棵公式被當透明下潛而
|
||||
* 肢解;scope.avoid/skipAttribute 命中自身者同理)。
|
||||
* ⚠ jz-char/jz-inner 必須透明:否則 adjacentLogicalChar 之 descend 遇標點原子既
|
||||
* 不進入亦不終止、而是**跳過**它續找更前之兄弟(下游 Bug:`汉字。<code>` 經 jinze
|
||||
* 綁定為 `汉<jz-jinze>字<jz-char>。</jz-char></jz-jinze><code>`,pill 之左鄰字應
|
||||
* 為「。」、不補間隙;舊邏輯卻越過 jz-char 取到「字」→ 誤包 jz-hws 於「字。」之間)。
|
||||
* jz-char 僅含標點(jiya 只 charify biaodian),標點非 pillNeighbor,故透明後 descend
|
||||
* 回傳標點 → 不補隙,與 collectRuns 之既有透明遞迴一致。jz-inner 亦須列入,否則
|
||||
* descend 進入 jz-char 後因 jz-inner 不透明而跳過其內標點。 */
|
||||
/**
|
||||
* 行內元素分類(§4.1)——一元素對排版之口徑,**恰屬其一**。**優先序**(依序短路,即多
|
||||
* 選擇器同命中時之定案規則):`avoid > pill > isolate > block > transparent > opaque`
|
||||
* (opaque=未在 styleInlines 之未知行內標籤,嚴格白名單下當隔離式邊界)。故:同時命中
|
||||
* pill 與 isolate → **pill**(pill 覆蓋 isolate);帶 data-jz-skip 之 pill/isolate → **avoid**
|
||||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||||
* transparent(結構切分為獨立層,故更窄)。
|
||||
*/
|
||||
category(el: Element): InlineCategory;
|
||||
/** category 之透明判定末步:styleInlines 標籤,或聚珍自身之 wrap 類 jz-*(jz-hws 間隙、
|
||||
* jz-jinze 禁則群組、**jz-char/jz-inner 標點原子**)。前四步已排除 avoid/pill/isolate/block。
|
||||
* jz-char/jz-inner 必須列入:否則 adjacentLogicalChar 之 descend 遇標點原子既不進入亦不
|
||||
* 終止、而**跳過**它續找更前之字(`汉字。<code>` 綁定後 pill 左鄰字應為「。」卻誤取「字」
|
||||
* 補 jz-hws);jz-char 僅含標點(非 pillNeighbor),透明後 descend 回傳標點、不補隙。 */
|
||||
private isTransparentTag;
|
||||
/** el 是否為邏輯文本流可透明穿越者(= category "transparent")。 */
|
||||
isTransparent(el: Element): boolean;
|
||||
/** 該元素**自身**是否為 avoid 邊界。avoid 之七種來源,依序短路判定、任一命中即是:
|
||||
* ① isSkipped 謂詞(消費端函數) ② skipTags 標籤名單(PRE/SAMP/SCRIPT…,可覆寫)
|
||||
|
||||
Vendored
+73
-42
@@ -65,27 +65,46 @@ var Juzhen = (() => {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** 邏輯文本應**透明穿越**之行內元素:樣式行內標籤,以及聚珍自身之 wrap 類
|
||||
* jz-*(jz-hws 間隙、jz-jinze 禁則群組、jz-char/jz-inner 標點原子)。穿越這些
|
||||
* 才能正確找到跨包裝之邏輯鄰字(如 jinze 已把 pill 納入 jz-jinze 後,pill 之左
|
||||
* 鄰字仍須可達;I7)。
|
||||
* ⚠ 以**元素**而非標籤名判定:pill/isolate/avoid 縱使標籤名在樣式集亦為邊界、
|
||||
* 不可穿越(下游問題六:KaTeX 全樹皆 SPAN,名字判定令整棵公式被當透明下潛而
|
||||
* 肢解;scope.avoid/skipAttribute 命中自身者同理)。
|
||||
* ⚠ jz-char/jz-inner 必須透明:否則 adjacentLogicalChar 之 descend 遇標點原子既
|
||||
* 不進入亦不終止、而是**跳過**它續找更前之兄弟(下游 Bug:`汉字。<code>` 經 jinze
|
||||
* 綁定為 `汉<jz-jinze>字<jz-char>。</jz-char></jz-jinze><code>`,pill 之左鄰字應
|
||||
* 為「。」、不補間隙;舊邏輯卻越過 jz-char 取到「字」→ 誤包 jz-hws 於「字。」之間)。
|
||||
* jz-char 僅含標點(jiya 只 charify biaodian),標點非 pillNeighbor,故透明後 descend
|
||||
* 回傳標點 → 不補隙,與 collectRuns 之既有透明遞迴一致。jz-inner 亦須列入,否則
|
||||
* descend 進入 jz-char 後因 jz-inner 不透明而跳過其內標點。 */
|
||||
isTransparent(el) {
|
||||
if (this.pillMatches(el) || this.isolateMatches(el) || this.avoidsSelf(el)) {
|
||||
return false;
|
||||
/**
|
||||
* 行內元素分類(§4.1)——一元素對排版之口徑,**恰屬其一**。**優先序**(依序短路,即多
|
||||
* 選擇器同命中時之定案規則):`avoid > pill > isolate > block > transparent > opaque`
|
||||
* (opaque=未在 styleInlines 之未知行內標籤,嚴格白名單下當隔離式邊界)。故:同時命中
|
||||
* pill 與 isolate → **pill**(pill 覆蓋 isolate);帶 data-jz-skip 之 pill/isolate → **avoid**
|
||||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||||
* transparent(結構切分為獨立層,故更窄)。
|
||||
*/
|
||||
category(el) {
|
||||
if (this.avoidsSelf(el)) {
|
||||
return "avoid";
|
||||
}
|
||||
if (this.pillMatches(el)) {
|
||||
return "pill";
|
||||
}
|
||||
if (this.isolateMatches(el)) {
|
||||
return "isolate";
|
||||
}
|
||||
if (this.blockSet.has(el.nodeName)) {
|
||||
return "block";
|
||||
}
|
||||
if (this.isTransparentTag(el)) {
|
||||
return "transparent";
|
||||
}
|
||||
return "opaque";
|
||||
}
|
||||
/** category 之透明判定末步:styleInlines 標籤,或聚珍自身之 wrap 類 jz-*(jz-hws 間隙、
|
||||
* jz-jinze 禁則群組、**jz-char/jz-inner 標點原子**)。前四步已排除 avoid/pill/isolate/block。
|
||||
* jz-char/jz-inner 必須列入:否則 adjacentLogicalChar 之 descend 遇標點原子既不進入亦不
|
||||
* 終止、而**跳過**它續找更前之字(`汉字。<code>` 綁定後 pill 左鄰字應為「。」卻誤取「字」
|
||||
* 補 jz-hws);jz-char 僅含標點(非 pillNeighbor),透明後 descend 回傳標點、不補隙。 */
|
||||
isTransparentTag(el) {
|
||||
const name = el.nodeName;
|
||||
return this.styleSet.has(name) || name === "JZ-HWS" || name === "JZ-JINZE" || name === "JZ-CHAR" || name === "JZ-INNER";
|
||||
}
|
||||
/** el 是否為邏輯文本流可透明穿越者(= category "transparent")。 */
|
||||
isTransparent(el) {
|
||||
return this.category(el) === "transparent";
|
||||
}
|
||||
/** 該元素**自身**是否為 avoid 邊界。avoid 之七種來源,依序短路判定、任一命中即是:
|
||||
* ① isSkipped 謂詞(消費端函數) ② skipTags 標籤名單(PRE/SAMP/SCRIPT…,可覆寫)
|
||||
* ③ SVG 命名空間 ④ contentEditable
|
||||
@@ -224,29 +243,31 @@ var Juzhen = (() => {
|
||||
return;
|
||||
}
|
||||
const el = node;
|
||||
if (this.avoidsSelf(el) || this.blockSet.has(el.nodeName)) {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
if (this.pillMatches(el)) {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
if (this.isTransparent(el)) {
|
||||
const recurse = () => {
|
||||
let c2 = el.firstChild;
|
||||
while (c2) {
|
||||
visit(c2);
|
||||
c2 = c2.nextSibling;
|
||||
}
|
||||
return;
|
||||
};
|
||||
switch (this.category(el)) {
|
||||
case "avoid":
|
||||
case "block":
|
||||
case "pill": {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
case "transparent": {
|
||||
recurse();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
flush();
|
||||
recurse();
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
flush();
|
||||
let ic = el.firstChild;
|
||||
while (ic) {
|
||||
visit(ic);
|
||||
ic = ic.nextSibling;
|
||||
}
|
||||
flush();
|
||||
};
|
||||
let c = block.firstChild;
|
||||
while (c) {
|
||||
@@ -897,14 +918,24 @@ var Juzhen = (() => {
|
||||
squeeze(prev, el);
|
||||
}
|
||||
prev = el;
|
||||
} else if (finder.pillMatches(el) || finder.avoidsSelf(el)) {
|
||||
prev = null;
|
||||
} else if (finder.isTransparent(el)) {
|
||||
visit(el);
|
||||
} else {
|
||||
prev = null;
|
||||
visit(el);
|
||||
prev = null;
|
||||
switch (finder.category(el)) {
|
||||
case "avoid":
|
||||
case "pill": {
|
||||
prev = null;
|
||||
break;
|
||||
}
|
||||
case "transparent": {
|
||||
visit(el);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
prev = null;
|
||||
visit(el);
|
||||
prev = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -932,7 +963,7 @@ var Juzhen = (() => {
|
||||
"SUP"
|
||||
]);
|
||||
function canSplit(el, finder) {
|
||||
return SPLITTABLE.has(el.nodeName) && !el.hasAttribute("id") && !finder.pillMatches(el) && !finder.isolateMatches(el) && !finder.avoidsSelf(el);
|
||||
return SPLITTABLE.has(el.nodeName) && !el.hasAttribute("id") && finder.category(el) === "transparent";
|
||||
}
|
||||
function firstText(el, finder) {
|
||||
for (let c = el.firstChild; c; c = c.nextSibling) {
|
||||
|
||||
Vendored
+73
-42
@@ -38,27 +38,46 @@ var Finder = class {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** 邏輯文本應**透明穿越**之行內元素:樣式行內標籤,以及聚珍自身之 wrap 類
|
||||
* jz-*(jz-hws 間隙、jz-jinze 禁則群組、jz-char/jz-inner 標點原子)。穿越這些
|
||||
* 才能正確找到跨包裝之邏輯鄰字(如 jinze 已把 pill 納入 jz-jinze 後,pill 之左
|
||||
* 鄰字仍須可達;I7)。
|
||||
* ⚠ 以**元素**而非標籤名判定:pill/isolate/avoid 縱使標籤名在樣式集亦為邊界、
|
||||
* 不可穿越(下游問題六:KaTeX 全樹皆 SPAN,名字判定令整棵公式被當透明下潛而
|
||||
* 肢解;scope.avoid/skipAttribute 命中自身者同理)。
|
||||
* ⚠ jz-char/jz-inner 必須透明:否則 adjacentLogicalChar 之 descend 遇標點原子既
|
||||
* 不進入亦不終止、而是**跳過**它續找更前之兄弟(下游 Bug:`汉字。<code>` 經 jinze
|
||||
* 綁定為 `汉<jz-jinze>字<jz-char>。</jz-char></jz-jinze><code>`,pill 之左鄰字應
|
||||
* 為「。」、不補間隙;舊邏輯卻越過 jz-char 取到「字」→ 誤包 jz-hws 於「字。」之間)。
|
||||
* jz-char 僅含標點(jiya 只 charify biaodian),標點非 pillNeighbor,故透明後 descend
|
||||
* 回傳標點 → 不補隙,與 collectRuns 之既有透明遞迴一致。jz-inner 亦須列入,否則
|
||||
* descend 進入 jz-char 後因 jz-inner 不透明而跳過其內標點。 */
|
||||
isTransparent(el) {
|
||||
if (this.pillMatches(el) || this.isolateMatches(el) || this.avoidsSelf(el)) {
|
||||
return false;
|
||||
/**
|
||||
* 行內元素分類(§4.1)——一元素對排版之口徑,**恰屬其一**。**優先序**(依序短路,即多
|
||||
* 選擇器同命中時之定案規則):`avoid > pill > isolate > block > transparent > opaque`
|
||||
* (opaque=未在 styleInlines 之未知行內標籤,嚴格白名單下當隔離式邊界)。故:同時命中
|
||||
* pill 與 isolate → **pill**(pill 覆蓋 isolate);帶 data-jz-skip 之 pill/isolate → **avoid**
|
||||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||||
* transparent(結構切分為獨立層,故更窄)。
|
||||
*/
|
||||
category(el) {
|
||||
if (this.avoidsSelf(el)) {
|
||||
return "avoid";
|
||||
}
|
||||
if (this.pillMatches(el)) {
|
||||
return "pill";
|
||||
}
|
||||
if (this.isolateMatches(el)) {
|
||||
return "isolate";
|
||||
}
|
||||
if (this.blockSet.has(el.nodeName)) {
|
||||
return "block";
|
||||
}
|
||||
if (this.isTransparentTag(el)) {
|
||||
return "transparent";
|
||||
}
|
||||
return "opaque";
|
||||
}
|
||||
/** category 之透明判定末步:styleInlines 標籤,或聚珍自身之 wrap 類 jz-*(jz-hws 間隙、
|
||||
* jz-jinze 禁則群組、**jz-char/jz-inner 標點原子**)。前四步已排除 avoid/pill/isolate/block。
|
||||
* jz-char/jz-inner 必須列入:否則 adjacentLogicalChar 之 descend 遇標點原子既不進入亦不
|
||||
* 終止、而**跳過**它續找更前之字(`汉字。<code>` 綁定後 pill 左鄰字應為「。」卻誤取「字」
|
||||
* 補 jz-hws);jz-char 僅含標點(非 pillNeighbor),透明後 descend 回傳標點、不補隙。 */
|
||||
isTransparentTag(el) {
|
||||
const name = el.nodeName;
|
||||
return this.styleSet.has(name) || name === "JZ-HWS" || name === "JZ-JINZE" || name === "JZ-CHAR" || name === "JZ-INNER";
|
||||
}
|
||||
/** el 是否為邏輯文本流可透明穿越者(= category "transparent")。 */
|
||||
isTransparent(el) {
|
||||
return this.category(el) === "transparent";
|
||||
}
|
||||
/** 該元素**自身**是否為 avoid 邊界。avoid 之七種來源,依序短路判定、任一命中即是:
|
||||
* ① isSkipped 謂詞(消費端函數) ② skipTags 標籤名單(PRE/SAMP/SCRIPT…,可覆寫)
|
||||
* ③ SVG 命名空間 ④ contentEditable
|
||||
@@ -197,29 +216,31 @@ var Finder = class {
|
||||
return;
|
||||
}
|
||||
const el = node;
|
||||
if (this.avoidsSelf(el) || this.blockSet.has(el.nodeName)) {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
if (this.pillMatches(el)) {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
if (this.isTransparent(el)) {
|
||||
const recurse = () => {
|
||||
let c2 = el.firstChild;
|
||||
while (c2) {
|
||||
visit(c2);
|
||||
c2 = c2.nextSibling;
|
||||
}
|
||||
return;
|
||||
};
|
||||
switch (this.category(el)) {
|
||||
case "avoid":
|
||||
case "block":
|
||||
case "pill": {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
case "transparent": {
|
||||
recurse();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
flush();
|
||||
recurse();
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
flush();
|
||||
let ic = el.firstChild;
|
||||
while (ic) {
|
||||
visit(ic);
|
||||
ic = ic.nextSibling;
|
||||
}
|
||||
flush();
|
||||
};
|
||||
let c = block.firstChild;
|
||||
while (c) {
|
||||
@@ -870,14 +891,24 @@ var jiyaAdjacencyPass = {
|
||||
squeeze(prev, el);
|
||||
}
|
||||
prev = el;
|
||||
} else if (finder.pillMatches(el) || finder.avoidsSelf(el)) {
|
||||
prev = null;
|
||||
} else if (finder.isTransparent(el)) {
|
||||
visit(el);
|
||||
} else {
|
||||
prev = null;
|
||||
visit(el);
|
||||
prev = null;
|
||||
switch (finder.category(el)) {
|
||||
case "avoid":
|
||||
case "pill": {
|
||||
prev = null;
|
||||
break;
|
||||
}
|
||||
case "transparent": {
|
||||
visit(el);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
prev = null;
|
||||
visit(el);
|
||||
prev = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -905,7 +936,7 @@ var SPLITTABLE = /* @__PURE__ */ new Set([
|
||||
"SUP"
|
||||
]);
|
||||
function canSplit(el, finder) {
|
||||
return SPLITTABLE.has(el.nodeName) && !el.hasAttribute("id") && !finder.pillMatches(el) && !finder.isolateMatches(el) && !finder.avoidsSelf(el);
|
||||
return SPLITTABLE.has(el.nodeName) && !el.hasAttribute("id") && finder.category(el) === "transparent";
|
||||
}
|
||||
function firstText(el, finder) {
|
||||
for (let c = el.firstChild; c; c = c.nextSibling) {
|
||||
|
||||
Reference in New Issue
Block a user