Files
cjk-autospace/dist/juzhen.mjs
T
admin 3a4fa1c932 fix: 修复行缘、切分还原与语言解析并同步文档
保留逐字 margin/jiya.bias 字体后备方案;补齐 demo 重排、量测边界说明及共享规则 1.0.1 反例向量。

验证:完整构建通过,Node 376 项、WKWebView 64 项及原生 demo 9 项通过;Firefox 仍暂缓。
2026-10-03 21:15:54 +08:00

3865 lines
118 KiB
JavaScript
Raw 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.
// src/core/unicode.ts
var CJK = "\u2E80-\u2EFF\u2F00-\u2FDF\u3040-\u309F\u30A0-\u30FA\u30FC-\u30FF\u3100-\u312F\u3200-\u32FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B73F}\\u{2B740}-\\u{2B81F}\\u{2B820}-\\u{2CEAF}\\u{2CEB0}-\\u{2EBEF}\\u{2EBF0}-\\u{2EE5F}\\u{2F800}-\\u{2FA1F}\\u{30000}-\\u{3134F}\\u{31350}-\\u{323AF}\\u{323B0}-\\u{3347F}";
var VARIATION_SELECTORS = "\\uFE00-\\uFE0F\\u{E0100}-\\u{E01EF}";
function codePointAtUtf16(text, offset) {
const value = text.codePointAt(offset);
return value === void 0 ? "" : String.fromCodePoint(value);
}
function previousCodePointStart(text, offset) {
if (offset <= 0) {
return 0;
}
const last = text.charCodeAt(offset - 1);
if (last >= 56320 && last <= 57343 && offset >= 2) {
const first = text.charCodeAt(offset - 2);
if (first >= 55296 && first <= 56319) {
return offset - 2;
}
}
return offset - 1;
}
function isVariationSelector(ch) {
const value = ch.codePointAt(0);
return value !== void 0 && (value >= 65024 && value <= 65039 || value >= 917760 && value <= 917999);
}
function variationSequenceAtUtf16(text, offset) {
const base = codePointAtUtf16(text, offset);
if (!base || isVariationSelector(base)) {
return base;
}
const selector = codePointAtUtf16(text, offset + base.length);
return isVariationSelector(selector) ? base + selector : base;
}
function previousVariationSequenceStart(text, offset) {
const last = previousCodePointStart(text, offset);
const lastChar = codePointAtUtf16(text, last);
if (!isVariationSelector(lastChar) || last <= 0) {
return last;
}
const base = previousCodePointStart(text, last);
return isVariationSelector(codePointAtUtf16(text, base)) ? last : base;
}
function splitVariationSequences(text) {
const out = [];
for (let offset = 0; offset < text.length; ) {
const sequence = variationSequenceAtUtf16(text, offset);
out.push(sequence);
offset += sequence.length;
}
return out;
}
var ALNUM = "A-Za-z0-9";
var ALPHA = "A-Za-z";
var WEST_SHARED = "0-9\\$%\\^&\\*\\-\\+\\\\=\\u0370-\\u03ff\\u0100-\\u024f\\u0400-\\u04ff\\u00a1-\\u00b6\\u00b8-\\u00ff\\u20a0-\\u20bf\\u2150-\\u21ff\\u2200-\\u22ff\\u2700-\\u27bf";
var ANS_AFTER_CJK = ALPHA + "@" + WEST_SHARED;
var ANS_BEFORE_CJK = ALPHA + WEST_SHARED;
var BD_STOP = "\u3002\uFF0E\uFF01\uFF1F";
var BD_PAUSE = "\uFF0C\u3001\uFF1B\uFF1A";
var BD_OPEN = "\u300C\u300E\uFF08\u300A\u3008\u3010\u3016\u3014\uFF3B\uFF5B\u201C\u2018";
var BD_CLOSE = "\u300D\u300F\uFF09\u300B\u3009\u3011\u3017\u3015\uFF3D\uFF5D\u201D\u2019";
var BD_MIDDLE = "\xB7\u30FB\u2027";
var BD_LIGA = "\u2026\u2014";
var BD_ORDER = [
"stop",
"pause",
"open",
"close",
"middle",
"liga"
];
var DEFAULT_RULESET = {
cjk: CJK,
ansAfter: ANS_AFTER_CJK,
ansBefore: ANS_BEFORE_CJK,
biaodian: {
stop: BD_STOP,
pause: BD_PAUSE,
open: BD_OPEN,
close: BD_CLOSE,
middle: BD_MIDDLE,
liga: BD_LIGA
}
};
function resolveRuleset(override) {
if (!override) {
return DEFAULT_RULESET;
}
const west = override.western || "";
const bd = { ...DEFAULT_RULESET.biaodian };
if (override.biaodian) {
for (const sub of BD_ORDER) {
const extra = override.biaodian[sub];
if (extra) {
bd[sub] = bd[sub] + extra;
}
}
}
return {
cjk: CJK + (override.cjk || ""),
ansAfter: ANS_AFTER_CJK + west,
ansBefore: ANS_BEFORE_CJK + west,
biaodian: bd
};
}
function classifyBiaodian(ch, biaodian = DEFAULT_RULESET.biaodian) {
const base = codePointAtUtf16(ch, 0);
if (!base) {
return null;
}
for (const sub of BD_ORDER) {
const chars = biaodian[sub];
let at = chars.indexOf(base);
while (at >= 0) {
const previous = chars.charCodeAt(at - 1);
const first = base.charCodeAt(0);
const startsInsidePair = previous >= 55296 && previous <= 56319 && first >= 56320 && first <= 57343;
if (!startsInsidePair && codePointAtUtf16(chars, at) === base) {
return "bd-" + sub;
}
at = chars.indexOf(base, at + 1);
}
}
return null;
}
function blankSides(bdClass) {
switch (bdClass) {
case "bd-open": {
return { left: true, right: false };
}
case "bd-close":
case "bd-pause":
case "bd-stop": {
return { left: false, right: true };
}
default: {
return { left: false, right: false };
}
}
}
var BRACKET_OPEN_RIGHT = "\u300C\u3008\u201C\u2018";
var BRACKET_CLOSE_LEFT = "\u300D\u3009\u201D\u2019";
var BIAS_BRACKETS = (() => {
const t = {};
for (const ch of BD_OPEN) {
t[ch] = BRACKET_OPEN_RIGHT.indexOf(ch) >= 0 ? "right" : "center";
}
for (const ch of BD_CLOSE) {
t[ch] = BRACKET_CLOSE_LEFT.indexOf(ch) >= 0 ? "left" : "center";
}
return t;
})();
var BIAS_HANS = {
"\uFF0C": "left",
"\u3001": "left",
"\u3002": "left",
"\uFF0E": "left",
"\uFF1B": "left",
"\uFF1A": "left",
"\uFF01": "left",
"\uFF1F": "left"
};
var BIAS_HANT = {
"\uFF0C": "center",
"\u3001": "center",
"\u3002": "center",
"\uFF0E": "center",
"\uFF1B": "center",
"\uFF1A": "center",
"\uFF01": "center",
"\uFF1F": "center"
};
var BIAS_DEFAULT = {
"zh-Hans": BIAS_HANS,
"zh-Hant": BIAS_HANT,
"ja": BIAS_HANT,
// 日文點號慣例居中,沿用繁體表
"other": BIAS_HANT
};
function inkBias(ch, langClass, overrides) {
const ov = overrides ? overrides[langClass] : void 0;
if (ov && ov[ch]) {
return ov[ch];
}
const bracket = BIAS_BRACKETS[ch];
if (bracket) {
return bracket;
}
const t = BIAS_DEFAULT[langClass] || BIAS_DEFAULT.other;
return t[ch] || null;
}
// src/core/finder.ts
var SVG_NS = "http://www.w3.org/2000/svg";
function isHighSurrogate(ch) {
return ch.length === 1 && ch.charCodeAt(0) >= 55296 && ch.charCodeAt(0) <= 56319;
}
function isLowSurrogate(ch) {
return ch.length === 1 && ch.charCodeAt(0) >= 56320 && ch.charCodeAt(0) <= 57343;
}
function appendParts(parts, extra) {
for (const part of extra) {
const previous = parts[parts.length - 1];
if (previous?.textNode === part.textNode && previous.end === part.start) {
previous.end = part.end;
} else {
parts.push({ ...part });
}
}
}
function acceptsVariationSelector(atom) {
return !isVariationSelector(codePointAtUtf16(atom.text, 0)) && !isVariationSelector(codePointAtUtf16(atom.text, previousCodePointStart(atom.text, atom.text.length)));
}
var Finder = class {
constructor(opts) {
this.opts = opts;
this.styleSet = opts.finder.styleInlines;
this.blockSet = opts.finder.blockTags;
this.skipSet = opts.finder.skipTags;
this.pillSelector = opts.finder.pillSelector;
this.isolateSelector = opts.finder.isolateSelector;
this.skipAttr = opts.finder.skipAttribute;
this.userIsSkipped = opts.finder.isSkipped;
this.levelTextSelector = opts.levelText;
this.avoidSelector = opts.scope.avoid;
this.includeSelector = opts.scope.include;
this.featureCache = /* @__PURE__ */ new WeakMap();
this.levelCache = /* @__PURE__ */ new WeakMap();
this.categoryCache = /* @__PURE__ */ new WeakMap();
this.pillAncestorCache = /* @__PURE__ */ new WeakMap();
this.avoidAncestorCache = /* @__PURE__ */ new WeakMap();
}
/**
* 清除 el 及其**整棵子樹**之元素鍵快取(C1 失效原語)。持久化 Finder(跨 render 復用快取)
* 下,元素之屬性或內容變化會令其分類(category)/級別(level)/功能(feature)/祖先游走
* (pill/avoid ancestor)快取失效。**須清整棵子樹、非僅該元素**:level/feature/祖先游走
* 之結果依賴祖先鏈,某祖先之 data-jz-level/data-juzhen/class 變化會令其**後代**之快取值變脏,
* 即便後代自身未動。querySelectorAll('*') 走子樹(O(子樹),native、無遞迴棧風險)。移除之元素
* 由 WeakMap 自動回收,無需顯式清。 */
invalidate(el) {
this.forget(el);
el.querySelectorAll("*").forEach((c) => this.forget(c));
}
forget(e) {
this.categoryCache.delete(e);
this.pillAncestorCache.delete(e);
this.avoidAncestorCache.delete(e);
this.levelCache.delete(e);
this.featureCache.delete(e);
}
/** 該元素是否匹配 pill 選擇器(三分類之「整體」:code/kbd/行內公式等)。 */
pillMatches(el) {
return !!(el.matches && this.pillSelector && el.matches(this.pillSelector));
}
/** 該元素是否匹配 isolate 選擇器(三分類之「隔離」:帶盒樣式之行內,如背景色
* <mark>)。邊界口徑同 pill(斷 run/斷相鄰/不可切分/兩端補隙),內部照常處理。 */
isolateMatches(el) {
return !!(el.matches && this.isolateSelector && el.matches(this.isolateSelector));
}
/** 節點是否位於 pill **內部**(僅查祖先,不含自身)。pill 為整體元素:內部對
* 一切 pass 不可見——不 charify、不收 run、不切分、不於其內插間隙(下游問題六:
* .katex 內部曾被 jiya/spacing 直接變更)。 */
insidePill(node) {
const p = node.parentElement;
return p ? this.ancestorOrSelfPill(p) : false;
}
/** el 或其祖先鏈任一為 pill(記憶化)。沿祖先鏈迭代(非遞迴,任意深度棧安全)至最近之
* 已快取/pill 命中/鏈頂,途中非 pill 元素入棧、以斷點之值一次回填——每元素恰算一次,
* 兄弟共用祖先鏈快取。insidePill 為 pillMatches 之最大來源(S3 延伸)。 */
ancestorOrSelfPill(el) {
const chain = [];
let cur = el;
let base = false;
while (cur) {
const c = this.pillAncestorCache.get(cur);
if (c !== void 0) {
base = c;
break;
}
if (this.pillMatches(cur)) {
this.pillAncestorCache.set(cur, true);
base = true;
break;
}
chain.push(cur);
cur = cur.parentElement;
}
for (const e of chain) {
this.pillAncestorCache.set(e, base);
}
return base;
}
/**
* 行內元素分類(§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(結構切分為獨立層,故更窄)。
*
* **記憶化**(S3):分類含至多三次 el.matches()(pill/isolate/avoid 選擇器),且被
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
* 隨實例持久化,跨 render/revert 復用;屬性或位置變化由 invalidateSubtree 清除受影響
* 子樹,不能沿用舊祖先鏈結果。
*/
category(el) {
const cached = this.categoryCache.get(el);
if (cached) {
return cached;
}
const result = this.computeCategory(el);
this.categoryCache.set(el, result);
return result;
}
computeCategory(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
* ⑤ data-jz-skip 屬性(內建) ⑥ skipAttribute(消費端指定屬性名)
* ⑦ scope.avoid 選擇器
* **此為自身判定**,供遍歷原語(collectRuns/jiyaAdjacency/canSplit/isTransparent)於
* 下探時逐元素自判(祖先由遞迴涵蓋)。**一次性**問「某節點是否被 avoid(自身或祖先)」
* 請改用 isAvoided(單一組合入口),勿只用本方法而漏判祖先、或只用 isAvoided 而漏判自身
* (下游問題六/階段一 #2:漏一半致 avoid 對自身形同虛設)。 */
avoidsSelf(el) {
if (this.userIsSkipped && this.userIsSkipped(el)) {
return true;
}
if (this.skipSet.has(el.nodeName)) {
return true;
}
if (el.namespaceURI === SVG_NS) {
return true;
}
if (el.isContentEditable) {
return true;
}
if (el.hasAttribute("data-jz-skip")) {
return true;
}
if (this.skipAttr && el.hasAttribute(this.skipAttr)) {
return true;
}
if (this.avoidSelector && el.matches && el.matches(this.avoidSelector)) {
return true;
}
return false;
}
/** 某節點是否被 avoid——**自身即邊界**(元素經 avoidsSelf 之七源;非元素節點僅判
* isSkipped)**或**祖先鏈上任一為 avoid 邊界。此為「是否 avoid」之**單一組合入口(安全
* 預設)**:一次性檢查用此,免除「avoidsSelf ‖ isAvoided」易漏一半之誤用。遍歷原語逐
* 元素下探時則用 avoidsSelf(僅自身,祖先由遞迴涵蓋)。 */
isAvoided(node) {
if (node.nodeType === 1) {
return this.ancestorOrSelfAvoided(node);
}
if (this.userIsSkipped && this.userIsSkipped(node)) {
return true;
}
const p = node.parentElement;
return p ? this.ancestorOrSelfAvoided(p) : false;
}
/** el 或其祖先鏈任一為 avoid 邊界(記憶化,迭代回填,同 ancestorOrSelfPill)。 */
ancestorOrSelfAvoided(el) {
const chain = [];
let cur = el;
let base = false;
while (cur) {
const c = this.avoidAncestorCache.get(cur);
if (c !== void 0) {
base = c;
break;
}
if (this.avoidsSelf(cur)) {
this.avoidAncestorCache.set(cur, true);
base = true;
break;
}
chain.push(cur);
cur = cur.parentElement;
}
for (const e of chain) {
this.avoidAncestorCache.set(e, base);
}
return base;
}
/** scope.include 啟用判定:未設則恆 true;設則須有命中祖先(opt-in)。 */
inScope(node) {
if (!this.includeSelector) {
return true;
}
const el = node.nodeType === 1 ? node : node.parentElement;
if (!el) {
return false;
}
return !!(el.closest && el.closest(this.includeSelector));
}
/** 走訪 root 下所有「可處理」文本節點(已過濾 avoid / pill 內部 / 非作用域)。 */
eachTextNode(root, cb) {
const ownerDoc = root.ownerDocument || (typeof document !== "undefined" ? document : null);
if (!ownerDoc) {
return;
}
const walker = ownerDoc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const nodes = [];
let n = walker.nextNode();
while (n) {
const t = n;
if (!this.isAvoided(t) && !this.insidePill(t) && this.inScope(t)) {
nodes.push(t);
}
n = walker.nextNode();
}
for (const node of nodes) {
cb(node);
}
}
/** 走訪 root 下可處理之 block(含 root 本身若為 block);pill/avoid 內部不可見。 */
eachBlock(root, cb) {
const sel = Array.from(this.blockSet).map((t) => t.toLowerCase()).join(",");
const blocks = [];
if (sel) {
root.querySelectorAll(sel).forEach((el) => blocks.push(el));
}
if (root.nodeType === 1 && this.blockSet.has(root.nodeName)) {
blocks.push(root);
}
for (const b of blocks) {
if (this.isAvoided(b) || this.insidePill(b) || this.pillMatches(b)) {
continue;
}
if (this.includeSelector && !(b.closest && b.closest(this.includeSelector))) {
continue;
}
cb(b);
}
}
/**
* 收集 block 之邏輯文本 run(架構 §4.1,I7):透明行內(isTransparent:styleInlines
* + wrap 類 jz-*)穿越、run 延續;block/pill/avoid 中斷 run;isolate 與**未知行內
* 標籤**(嚴格白名單)中斷 run 且內部自成獨立 run。預設至少兩原子;includeSingletons
* 供段落邊界查詢保留單原子 run,不改間隙處理之預設。
*/
collectRuns(block, includeSingletons = false, feature2) {
const runs = [];
let curText = "";
let curMap = [];
const flush = () => {
if (curMap.length >= (includeSingletons ? 1 : 2)) {
runs.push({ text: curText, map: curMap });
}
curText = "";
curMap = [];
};
const visit = (node) => {
if (!node) {
return;
}
if (node.nodeType === 3) {
const t = node;
if (this.isAvoided(t) || !this.inScope(t) || feature2 && !this.featureEnabledFor(t, feature2)) {
flush();
return;
}
const data = t.data;
for (let i = 0; i < data.length; ) {
const ch = codePointAtUtf16(data, i);
curText += ch;
const part = { textNode: t, start: i, end: i + ch.length };
const previous = curMap[curMap.length - 1];
if (previous && isHighSurrogate(previous.text) && isLowSurrogate(ch)) {
previous.text += ch;
appendParts(previous.parts, [part]);
const base = curMap[curMap.length - 2];
if (isVariationSelector(previous.text) && base && acceptsVariationSelector(base)) {
base.text += previous.text;
appendParts(base.parts, previous.parts);
curMap.pop();
}
} else if (isVariationSelector(ch) && previous && acceptsVariationSelector(previous)) {
previous.text += ch;
appendParts(previous.parts, [part]);
} else {
curMap.push({ text: ch, parts: [part] });
}
i += ch.length;
}
return;
}
if (node.nodeType !== 1) {
return;
}
const el = node;
const recurse = () => {
let c2 = el.firstChild;
while (c2) {
visit(c2);
c2 = c2.nextSibling;
}
};
switch (this.category(el)) {
case "avoid":
case "block":
case "pill": {
flush();
return;
}
case "transparent": {
const boundary = feature2 && !this.featureEnabledFor(el, feature2);
if (boundary) {
flush();
}
recurse();
if (boundary) {
flush();
}
return;
}
default: {
flush();
recurse();
flush();
return;
}
}
};
let c = block.firstChild;
while (c) {
visit(c);
c = c.nextSibling;
}
flush();
return runs;
}
/** 從節點邊界或 Text offset 取相鄰碼點;只穿越透明、域內內容,scopeRoot 為硬邊界。 */
adjacentTextCodePoint(from, direction, scopeRoot, offset, feature2) {
if (scopeRoot && (from === scopeRoot || !scopeRoot.contains(from))) {
return null;
}
const pick = (tn, at) => {
if (this.isAvoided(tn) || !this.inScope(tn) || feature2 && !this.featureEnabledFor(tn, feature2)) {
return null;
}
if (direction === -1) {
if (at <= 0) {
return null;
}
return { textNode: tn, start: previousCodePointStart(tn.data, at), end: at };
}
if (at >= tn.data.length) {
return null;
}
return { textNode: tn, start: at, end: at + codePointAtUtf16(tn.data, at).length };
};
if (from.nodeType === 3 && offset !== void 0) {
const tn = from;
if (this.isAvoided(tn) || !this.inScope(tn) || feature2 && !this.featureEnabledFor(tn, feature2)) {
return null;
}
if (direction === -1 ? offset > 0 : offset < tn.data.length) {
return pick(tn, offset);
}
}
let cursor = from;
for (; ; ) {
if (cursor === scopeRoot) {
return null;
}
let next = direction === -1 ? cursor.previousSibling : cursor.nextSibling;
while (!next) {
const parent = cursor.parentElement;
if (!parent || parent === scopeRoot || !this.isTransparent(parent) || !this.inScope(parent) || feature2 && !this.featureEnabledFor(parent, feature2)) {
return null;
}
cursor = parent;
next = direction === -1 ? cursor.previousSibling : cursor.nextSibling;
}
for (; ; ) {
if (scopeRoot && !scopeRoot.contains(next)) {
return null;
}
if (next.nodeType === 3) {
const tn = next;
if (this.isAvoided(tn) || !this.inScope(tn) || feature2 && !this.featureEnabledFor(tn, feature2)) {
return null;
}
if (tn.data.length) {
return pick(tn, direction === -1 ? tn.data.length : 0);
}
} else if (next.nodeType === 1) {
const el = next;
if (!this.isTransparent(el) || !this.inScope(el) || feature2 && !this.featureEnabledFor(el, feature2)) {
return null;
}
const child = direction === -1 ? el.lastChild : el.firstChild;
if (child) {
next = child;
continue;
}
}
cursor = next;
break;
}
}
}
/** 補全透明 Text 邊界拆開的 surrogate pair;孤立碼元保留自身,不跨任何邊界拼接。 */
adjacentCodePoint(from, direction, scopeRoot, offset, feature2) {
const first = this.adjacentTextCodePoint(from, direction, scopeRoot, offset, feature2);
if (!first) {
return null;
}
const char = (part) => part.textNode.data.slice(part.start, part.end);
const firstChar = char(first);
let parts = [first];
if (direction === -1 ? isLowSurrogate(firstChar) : isHighSurrogate(firstChar)) {
const second = this.adjacentTextCodePoint(
first.textNode,
direction,
scopeRoot,
direction === -1 ? first.start : first.end,
feature2
);
if (second && (direction === -1 ? isHighSurrogate(char(second)) : isLowSurrogate(char(second)))) {
parts = direction === -1 ? [second, first] : [first, second];
}
}
return { text: parts.map(char).join(""), parts };
}
/** pill 之完整鄰字(base+至多一個 VS);after 供查單個作者空格另一側,不越 scopeRoot。 */
adjacentLogicalChar(el, direction, scopeRoot, after, feature2) {
const edge = after?.parts[direction === -1 ? 0 : after.parts.length - 1];
const first = this.adjacentCodePoint(
edge?.textNode || el,
direction,
scopeRoot,
edge ? direction === -1 ? edge.start : edge.end : void 0,
feature2
);
if (!first) {
return null;
}
let atoms = [first];
const firstChar = first.text;
if (direction === -1 && isVariationSelector(firstChar) || direction === 1 && !isVariationSelector(firstChar)) {
const edge2 = first.parts[direction === -1 ? 0 : first.parts.length - 1];
const second = this.adjacentCodePoint(
edge2.textNode,
direction,
scopeRoot,
direction === -1 ? edge2.start : edge2.end,
feature2
);
if (second && (direction === -1 ? !isVariationSelector(second.text) : isVariationSelector(second.text))) {
atoms = direction === -1 ? [second, first] : [first, second];
}
}
const ch = atoms.map((atom) => atom.text).join("");
const parts = [];
for (const atom of atoms) {
appendParts(parts, atom.parts);
}
return { textNode: parts[0].textNode, offset: parts[0].start, ch, parts };
}
/**
* 節點之功能級別(§6.5.0a)。自 node 向上找**最近**之顯式信號:元素屬性
* `data-jz-level="text|paragraph"`(最優先),或命中 `level.text` 選擇器(→ text);
* 皆無則預設 paragraph(全功能)。結果以解析後之元素為鍵快取。
*/
levelFor(node) {
const start = node.nodeType === 1 ? node : node.parentElement;
if (!start) {
return "paragraph";
}
const cached = this.levelCache.get(start);
if (cached) {
return cached;
}
let el = start;
let result = "paragraph";
while (el) {
const attr = el.getAttribute ? el.getAttribute("data-jz-level") : null;
if (attr === "text" || attr === "paragraph") {
result = attr;
break;
}
if (this.levelTextSelector && el.matches && el.matches(this.levelTextSelector)) {
result = "text";
break;
}
el = el.parentElement;
}
this.levelCache.set(start, result);
return result;
}
/**
* 某級別之 pass 是否可作用於該節點(§6.5.0a gate)。文本級 pass 不受限(恆 true);
* 段落級 pass 僅作用於段落級元素(text-level 子樹跳過)。與 featureEnabledFor 疊加:
* level 為粗粒度語義組(先判),blocks/屬性為細粒度逐功能(後調)。
*/
levelAllows(node, passLevel) {
if (passLevel === "text") {
return true;
}
return this.levelFor(node) === "paragraph";
}
/**
* 解析節點對某功能之有效啟用狀態(§3.6,per-node 閘)。向上找最近帶
* 功能指令之祖先(data-juzhen 白名單 / data-juzhen-off 黑名單 / 命中
* blocks 選擇器),無則回退全域 features。結果以元素為鍵快取。
*/
featureEnabledFor(node, name) {
const el = node.nodeType === 1 ? node : node.parentElement;
const globalOn = this.globalFeature(name);
if (!el) {
return globalOn;
}
const cached = this.featureCache.get(el);
if (cached && name in cached) {
return cached[name];
}
const result = this.resolveFeature(el, name, globalOn);
const bucket = cached || {};
bucket[name] = result;
this.featureCache.set(el, bucket);
return result;
}
globalFeature(name) {
const f = this.opts.features;
return !!f[name];
}
resolveFeature(start, name, globalOn) {
let el = start;
while (el) {
if (el.hasAttribute("data-juzhen")) {
const list = (el.getAttribute("data-juzhen") || "").split(/\s+/).filter(Boolean);
return list.indexOf(name) >= 0;
}
if (el.hasAttribute("data-juzhen-off")) {
const off = (el.getAttribute("data-juzhen-off") || "").split(/\s+/).filter(Boolean);
if (off.indexOf(name) >= 0) {
return false;
}
}
for (const rule of this.opts.blocks) {
if (rule.selector && el.matches && el.matches(rule.selector)) {
return rule.features.indexOf(name) >= 0;
}
}
el = el.parentElement;
}
return globalOn;
}
};
// src/core/dom.ts
var KIND_ATTR = "data-jz-kind";
var PASS_ATTR = "data-jz";
var WRAP_TAGS = /* @__PURE__ */ new Map([
["spacing", /* @__PURE__ */ new Set(["JZ-HWS"])],
["jiya", /* @__PURE__ */ new Set(["JZ-CHAR", "JZ-INNER"])],
["jinze", /* @__PURE__ */ new Set(["JZ-JINZE"])],
["longWord", /* @__PURE__ */ new Set(["SPAN"])],
["orphan", /* @__PURE__ */ new Set(["JZ-ORPHAN"])]
]);
var SPLITOFF_TAGS = /* @__PURE__ */ new Set([
"STRONG",
"EM",
"B",
"I",
"U",
"S",
"SPAN",
"SMALL",
"INS",
"DEL",
"SUB",
"SUP"
]);
var ATTRIBUTE_WRITERS = /* @__PURE__ */ new WeakMap();
var OWNER_ATTRIBUTES = /* @__PURE__ */ new WeakMap();
var UNWRAPPED_PARENTS = /* @__PURE__ */ new WeakMap();
var SPLITOFF_SOURCES = /* @__PURE__ */ new WeakMap();
function registerSplitoffSource(clone, source, pass, ownedElements) {
if (!ownedElements.has(clone) || !SPLITOFF_TAGS.has(clone.nodeName) || clone.nodeName !== source.nodeName) {
return;
}
const earlier = SPLITOFF_SOURCES.get(source);
const inherited = earlier && source.getAttribute(PASS_ATTR) === earlier.pass && source.getAttribute(KIND_ATTR) === "splitoff" ? earlier : null;
SPLITOFF_SOURCES.set(clone, {
source,
pass,
originalPass: inherited ? inherited.originalPass : clone.getAttribute(PASS_ATTR),
originalKind: inherited ? inherited.originalKind : clone.getAttribute(KIND_ATTR)
});
}
function sameSplitAttributes(a, b) {
const attrs = (el) => el.getAttributeNames().filter((name) => name !== PASS_ATTR && name !== KIND_ATTR);
const names = attrs(a);
return names.length === attrs(b).length && names.every((name) => a.getAttribute(name) === b.getAttribute(name));
}
function splitOrigin(node) {
let current = resolveUnwrappedParent(node);
const visited = /* @__PURE__ */ new Set();
while (current) {
if (visited.has(current)) {
return null;
}
visited.add(current);
if (current.nodeType !== 1) {
return current;
}
const el = current;
const record = SPLITOFF_SOURCES.get(el);
if (!record || el.getAttribute(PASS_ATTR) !== record.pass || el.getAttribute(KIND_ATTR) !== "splitoff") {
return current;
}
current = resolveUnwrappedParent(record.source);
}
return null;
}
function recordUnwrappedParent(el, parent, ownedElements) {
const pass = el.getAttribute(PASS_ATTR);
if (ownedElements.has(el) && pass && knownProduct(pass, el.getAttribute(KIND_ATTR), el) && !el.parentNode && !el.hasChildNodes()) {
UNWRAPPED_PARENTS.set(el, parent);
}
}
function resolveUnwrappedParent(parent) {
let current = parent;
const visited = /* @__PURE__ */ new Set();
for (; ; ) {
if (visited.has(current)) {
return null;
}
visited.add(current);
const next = UNWRAPPED_PARENTS.get(current);
if (!next) {
return current;
}
if (current.parentNode || current.hasChildNodes()) {
UNWRAPPED_PARENTS.delete(current);
return current;
}
current = next;
}
}
function trackOwnedAttribute(owner, name, el) {
let attrs = OWNER_ATTRIBUTES.get(owner);
if (!attrs) {
attrs = /* @__PURE__ */ new Map();
OWNER_ATTRIBUTES.set(owner, attrs);
}
let elements = attrs.get(name);
if (!elements) {
elements = /* @__PURE__ */ new Set();
attrs.set(name, elements);
}
elements.add(el);
}
function untrackOwnedAttribute(owner, name, el) {
const attrs = OWNER_ATTRIBUTES.get(owner);
const elements = attrs?.get(name);
if (!elements) {
return;
}
elements.delete(el);
if (elements.size === 0) {
attrs.delete(name);
}
}
function doc() {
if (typeof document === "undefined") {
throw new Error("juzhen: \u7121 document\uFF08\u9700\u65BC\u700F\u89BD\u5668\u6216 jsdom \u74B0\u5883\u57F7\u884C\uFF09\u3002");
}
return document;
}
function createJz(ownedElements, tag, pass, kind, opts = {}) {
const el = doc().createElement(tag);
ownedElements.add(el);
el.setAttribute(PASS_ATTR, pass);
el.setAttribute(KIND_ATTR, kind);
if (opts.classes) {
const list = Array.isArray(opts.classes) ? opts.classes : [opts.classes];
for (const c of list) {
if (c) {
el.classList.add(c);
}
}
}
if (opts.text !== void 0) {
el.textContent = opts.text;
}
return el;
}
function setOwnedAttribute(el, name, value, owner) {
let attrs = ATTRIBUTE_WRITERS.get(el);
if (!attrs) {
attrs = /* @__PURE__ */ new Map();
ATTRIBUTE_WRITERS.set(el, attrs);
}
let state = attrs.get(name);
if (!state) {
state = { original: el.getAttribute(name), writers: [] };
attrs.set(name, state);
} else {
const top = state.writers[state.writers.length - 1];
if (top && el.getAttribute(name) !== top.value) {
for (const writer of state.writers) {
untrackOwnedAttribute(writer.owner, name, el);
}
state = { original: el.getAttribute(name), writers: [] };
attrs.set(name, state);
}
}
state.writers = state.writers.filter((writer) => writer.owner !== owner);
state.writers.push({ owner, value });
trackOwnedAttribute(owner, name, el);
el.setAttribute(name, value);
}
function revertOwnedAttribute(el, name, owner) {
const attrs = ATTRIBUTE_WRITERS.get(el);
const state = attrs?.get(name);
if (!state) {
untrackOwnedAttribute(owner, name, el);
return;
}
const index = state.writers.findIndex((writer) => writer.owner === owner);
if (index < 0) {
untrackOwnedAttribute(owner, name, el);
return;
}
const wasTop = index === state.writers.length - 1;
const top = state.writers[state.writers.length - 1];
const externallyChanged = el.getAttribute(name) !== top.value;
state.writers.splice(index, 1);
untrackOwnedAttribute(owner, name, el);
if (!externallyChanged && wasTop) {
const next = state.writers[state.writers.length - 1];
const restored = next ? next.value : state.original;
if (restored === null) {
el.removeAttribute(name);
} else {
el.setAttribute(name, restored);
}
}
if (state.writers.length === 0) {
attrs.delete(name);
}
}
function revertOwnedAttributes(root, name, owner) {
const elements = OWNER_ATTRIBUTES.get(owner)?.get(name);
if (!elements) {
return;
}
for (const el of Array.from(elements)) {
if (el === root || root.contains(el)) {
revertOwnedAttribute(el, name, owner);
}
}
}
function knownProduct(pass, kind, el) {
if (kind === "wrap") {
return !!WRAP_TAGS.get(pass)?.has(el.nodeName);
}
if (kind === "splitoff") {
return (pass === "jinze" || pass === "orphan") && SPLITOFF_TAGS.has(el.nodeName);
}
return false;
}
function revertPass(pass, root, ownedElements) {
const nodes = Array.from(
root.querySelectorAll("[" + PASS_ATTR + '="' + pass + '"]')
).filter((el) => ownedElements.has(el));
const touched = /* @__PURE__ */ new Set();
for (let i = nodes.length - 1; i >= 0; i -= 1) {
const el = nodes[i];
const parent = el.parentNode;
if (!parent) {
continue;
}
const kind = el.getAttribute(KIND_ATTR);
if (!knownProduct(pass, kind, el)) {
continue;
}
if (kind === "splitoff") {
continue;
}
touched.add(parent);
while (el.firstChild) {
parent.insertBefore(el.firstChild, el);
}
parent.removeChild(el);
recordUnwrappedParent(el, parent, ownedElements);
ownedElements.delete(el);
}
const splitoffs = Array.from(
root.querySelectorAll(
"[" + PASS_ATTR + '="' + pass + '"][' + KIND_ATTR + '="splitoff"]'
)
).filter((el) => ownedElements.has(el) && knownProduct(pass, "splitoff", el));
for (const so of splitoffs) {
const parent = so.parentNode;
if (!parent) {
continue;
}
touched.add(parent);
const record = SPLITOFF_SOURCES.get(so);
if (!record || record.pass !== pass) {
continue;
}
const source = resolveUnwrappedParent(record.source);
const prev = so.previousSibling;
const origin = source ? splitOrigin(source) : null;
if (origin && prev?.nodeType === 1 && splitOrigin(prev) === origin && prev.nodeName === so.nodeName && sameSplitAttributes(so, prev)) {
while (so.firstChild) {
prev.appendChild(so.firstChild);
}
parent.removeChild(so);
recordUnwrappedParent(so, prev, ownedElements);
} else {
for (const [name, value] of [
[PASS_ATTR, record.originalPass],
[KIND_ATTR, record.originalKind]
]) {
if (value === null) {
so.removeAttribute(name);
} else {
so.setAttribute(name, value);
}
}
}
SPLITOFF_SOURCES.delete(so);
ownedElements.delete(so);
}
for (const p of touched) {
if (p.normalize) {
p.normalize();
}
}
}
var BD_CLASSES = ["bd-open", "bd-close", "bd-pause", "bd-stop", "bd-middle", "bd-liga"];
function bdClassOf(el) {
for (const c of BD_CLASSES) {
if (el.classList.contains(c)) {
return c;
}
}
return null;
}
function isJz(node, pass) {
if (!node || node.nodeType !== 1) {
return false;
}
const el = node;
if (!el.hasAttribute(PASS_ATTR)) {
return false;
}
return pass === void 0 || el.getAttribute(PASS_ATTR) === pass;
}
// src/core/pass.ts
function runPasses(passes, ctx) {
const enabled = passes.filter((p) => p.enabled(ctx.options));
const charify = enabled.filter((p) => p.kind === "charify").sort((a, b) => a.order - b.order);
const standalone = enabled.filter((p) => p.kind === "standalone").sort((a, b) => a.order - b.order);
if (charify.length > 0) {
runCharify(charify, ctx);
}
for (const p of standalone) {
p.render(ctx);
}
}
function revertPasses(passes, ctx) {
const ordered = passes.slice().sort((a, b) => b.order - a.order);
for (const p of ordered) {
p.revert(ctx);
}
}
function runCharify(passes, ctx) {
for (const pass of passes) {
pass.prepare?.(ctx);
}
const work = [];
ctx.finder.eachTextNode(ctx.root, (node) => {
let requests = [];
for (const p of passes) {
if (!ctx.finder.levelAllows(node, p.level)) {
continue;
}
const got = p.collect(node, ctx);
if (got.length > 0) {
requests = requests.concat(got);
}
}
if (requests.length > 0) {
work.push({ node, requests });
}
});
for (const item of work) {
applyRequests(item.node, item.requests, ctx.ownedElements);
}
}
function applyRequests(node, requests, ownedElements) {
const parent = node.parentNode;
if (!parent) {
return;
}
const sorted = requests.slice().sort((a, b) => a.start - b.start || a.end - b.end);
const data = node.data;
const ownerDoc = node.ownerDocument;
if (!ownerDoc) {
return;
}
const frag = ownerDoc.createDocumentFragment();
let cursor = 0;
for (const req of sorted) {
if (req.start < cursor) {
continue;
}
if (req.start < 0 || req.end > data.length || req.end <= req.start) {
continue;
}
if (req.start > cursor) {
frag.appendChild(ownerDoc.createTextNode(data.slice(cursor, req.start)));
}
const built = req.build(data.slice(req.start, req.end));
ownedElements.add(built);
frag.appendChild(built);
cursor = req.end;
}
if (cursor === 0) {
return;
}
if (cursor < data.length) {
frag.appendChild(ownerDoc.createTextNode(data.slice(cursor)));
}
parent.replaceChild(frag, node);
}
// src/core/cross-atom.ts
var MOVES = /* @__PURE__ */ new WeakMap();
function isolate(part) {
let text = part.textNode;
if (part.start > 0) {
text = text.splitText(part.start);
}
if (text.length > part.end - part.start) {
text.splitText(part.end - part.start);
}
return text;
}
function insertionPoint(part) {
if (part.anchor.parentNode === part.parent) {
return part.anchor;
}
if (part.next?.parentNode === part.parent) {
return part.next;
}
if (part.next !== null) {
return void 0;
}
if (part.previous?.parentNode === part.parent) {
return part.previous === part.parent.lastChild ? null : void 0;
}
if (part.previous === null && part.next === null && part.anchor.parentNode !== null && part.anchor.parentNode !== part.parent && !part.parent.hasChildNodes()) {
return null;
}
return void 0;
}
function restorePart(part, inner) {
if (part.text.parentNode === part.parent) {
return true;
}
if (part.text.parentNode !== inner) {
return false;
}
const before = insertionPoint(part);
if (before === void 0) {
return false;
}
part.parent.insertBefore(part.text, before);
return true;
}
function wrapCrossAtom(span, outer, inner, pass, ctx) {
const first = isolate(span.parts[0]);
const parent = first.parentNode;
if (!parent) {
return;
}
const move = {
pass,
outer,
inner,
parent,
previous: first.previousSibling,
next: first.nextSibling,
root: ctx.root,
ancestors: [],
parts: []
};
for (let p = ctx.root.parentElement; p; p = p.parentElement) {
move.ancestors.push(p);
}
outer.appendChild(inner);
parent.insertBefore(outer, first);
inner.appendChild(first);
try {
for (const source of span.parts.slice(1)) {
const text = isolate(source);
const sourceParent = text.parentNode;
if (!sourceParent) {
throw new Error("juzhen: \u8DE8\u7BC0\u9EDE\u6A19\u9EDE\u4F86\u6E90\u5DF2\u812B\u96E2\u3002");
}
const anchor = text.ownerDocument.createComment("jz-" + pass + "-atom-anchor");
const innerAnchor = text.ownerDocument.createComment("jz-" + pass + "-atom-part");
const part = { text, anchor, innerAnchor, parent: sourceParent, previous: text.previousSibling, next: text.nextSibling };
move.parts.push(part);
sourceParent.insertBefore(anchor, text);
inner.append(innerAnchor, text);
}
let moves = MOVES.get(ctx.ownerToken);
if (!moves) {
moves = /* @__PURE__ */ new Set();
MOVES.set(ctx.ownerToken, moves);
}
moves.add(move);
} catch (error) {
for (const part of move.parts) {
restorePart(part, inner);
part.anchor.remove();
part.innerAnchor.remove();
}
while (inner.firstChild) {
parent.insertBefore(inner.firstChild, outer);
}
outer.remove();
ctx.ownedElements.delete(inner);
ctx.ownedElements.delete(outer);
throw error;
}
}
function ownedWrap(el, tag, move, ctx) {
return ctx.ownedElements.has(el) && el.nodeName === tag && el.getAttribute("data-jz") === move.pass && el.getAttribute("data-jz-kind") === "wrap";
}
function placeOuter(move) {
if (move.outer.parentNode === move.parent) {
return true;
}
let before = null;
if (move.next?.parentNode === move.parent) {
before = move.next;
} else if (move.next !== null) {
return false;
} else if (move.previous?.parentNode === move.parent) {
if (move.previous !== move.parent.lastChild) {
return false;
}
} else if (move.parent.hasChildNodes()) {
return false;
}
move.parent.insertBefore(move.outer, before);
return true;
}
function revertCrossAtoms(pass, ctx) {
const touched = /* @__PURE__ */ new Set();
const moves = MOVES.get(ctx.ownerToken);
if (!moves) {
return touched;
}
const within = (node) => node === ctx.root || ctx.root.contains(node);
for (const move of Array.from(moves)) {
if (move.pass !== pass) {
continue;
}
if (!(within(move.root) || within(move.outer) || within(move.parent) || move.ancestors.includes(ctx.root) || move.parts.some((part) => within(part.parent) || within(part.anchor)))) {
continue;
}
const parent = resolveUnwrappedParent(move.parent);
const sources = move.parts.map((part) => resolveUnwrappedParent(part.parent));
if (!parent || sources.some((source) => source === null)) {
for (const part of move.parts) {
part.anchor.remove();
part.innerAnchor.remove();
}
moves.delete(move);
continue;
}
move.parent = parent;
move.parts.forEach((part, index) => {
part.parent = sources[index];
});
const live = (node) => move.root.contains(node) || node.isConnected;
const validCarrier = ownedWrap(move.outer, "JZ-CHAR", move, ctx) && ownedWrap(move.inner, "JZ-INNER", move, ctx) && move.inner.parentNode === move.outer && live(move.outer);
if (!validCarrier) {
for (const part of move.parts) {
part.anchor.remove();
part.innerAnchor.remove();
}
ctx.ownedElements.delete(move.inner);
if (!live(move.outer)) {
ctx.ownedElements.delete(move.outer);
}
moves.delete(move);
continue;
}
if (validCarrier) {
for (const part of move.parts) {
if (live(part.parent) && restorePart(part, move.inner)) {
touched.add(part.parent);
} else if (part.text.parentNode === move.inner) {
part.text.remove();
}
}
}
for (const part of move.parts) {
part.anchor.remove();
part.innerAnchor.remove();
}
if (ownedWrap(move.outer, "JZ-CHAR", move, ctx) && placeOuter(move)) {
if (move.inner.parentNode === move.outer && ownedWrap(move.inner, "JZ-INNER", move, ctx)) {
while (move.inner.firstChild) {
move.outer.insertBefore(move.inner.firstChild, move.inner);
}
move.inner.remove();
recordUnwrappedParent(move.inner, move.outer, ctx.ownedElements);
ctx.ownedElements.delete(move.inner);
}
while (move.outer.firstChild) {
move.parent.insertBefore(move.outer.firstChild, move.outer);
}
move.outer.remove();
recordUnwrappedParent(move.outer, move.parent, ctx.ownedElements);
ctx.ownedElements.delete(move.outer);
touched.add(move.parent);
}
moves.delete(move);
}
if (!moves.size) {
MOVES.delete(ctx.ownerToken);
}
return touched;
}
// src/core/locale.ts
var DEFAULT_POLICY = {
"zh-Hant": "quanjiao",
"zh-Hans": "kaiming",
"ja": "quanjiao",
"other": "quanjiao"
};
function effectiveLang(node, fallback) {
let p = node.nodeType === 1 ? node : node.parentNode;
while (p && p.nodeType === 1) {
const el = p;
const v = el.getAttribute("lang");
if (v !== null) {
return v.toLowerCase();
}
p = p.parentNode;
}
return (fallback || "").toLowerCase();
}
function classify(lang) {
const l = (lang || "").toLowerCase();
if (l === "ja" || l.indexOf("ja-") === 0) {
return "ja";
}
if (l === "zh" || l.indexOf("zh-") === 0) {
const subtags = l.split("-");
let at = 1;
for (let count = 0; count < 3 && /^[a-z]{3}$/.test(subtags[at] || ""); count++) {
at++;
}
const script = /^[a-z]{4}$/.test(subtags[at] || "") ? subtags[at++] : void 0;
if (script === "hant") {
return "zh-Hant";
}
if (script === "hans") {
return "zh-Hans";
}
if (["tw", "hk", "mo"].includes(subtags[at] || "")) {
return "zh-Hant";
}
if (l === "zh") {
return "zh-Hant";
}
return "zh-Hans";
}
return "other";
}
function styleFor(langClass, config) {
const perLang = config.policy ? config.policy[langClass] : void 0;
if (perLang) {
return perLang;
}
if (config.style) {
return config.style;
}
return DEFAULT_POLICY[langClass];
}
function isPunctStyle(v) {
return v === "quanjiao" || v === "kaiming" || v === "banjiao";
}
function resolveStyleOverride(node, stylePolicy) {
let el = node.nodeType === 1 ? node : node.parentElement;
while (el) {
const attr = el.getAttribute ? el.getAttribute("data-jz-style") : null;
if (isPunctStyle(attr)) {
return attr;
}
if (stylePolicy) {
for (const rule of stylePolicy) {
if (rule.selector && el.matches && el.matches(rule.selector)) {
return rule.style;
}
}
}
el = el.parentElement;
}
return null;
}
function resolveStyle(node, config) {
const override = resolveStyleOverride(node, config.stylePolicy);
if (override) {
return override;
}
const lang = effectiveLang(node, config.default);
return styleFor(classify(lang), config);
}
// src/typeset/jiya.ts
var PASS = "jiya";
function baselineHalf(style, bdClass) {
if (style === "quanjiao") {
return false;
}
if (style === "banjiao") {
return bdClass === "bd-pause" || bdClass === "bd-stop" || bdClass === "bd-open" || bdClass === "bd-close";
}
return bdClass === "bd-pause" || bdClass === "bd-open" || bdClass === "bd-close";
}
function inJzChar(node) {
let p = node.parentNode;
while (p && p.nodeType === 1) {
const name = p.nodeName;
if (name === "JZ-CHAR" || name === "JZ-INNER") {
return true;
}
p = p.parentNode;
}
return false;
}
var jiyaPass = {
name: PASS,
kind: "charify",
order: 20,
level: "text",
enabled: (o) => o.features.jiya,
prepare(ctx) {
const { finder, options } = ctx;
const roots = [ctx.root];
const selector = Array.from(options.finder.blockTags).map((tag) => tag.toLowerCase()).join(",");
if (selector) {
roots.push(...Array.from(ctx.root.querySelectorAll(selector)));
}
for (const root of roots) {
if (finder.isAvoided(root) || finder.insidePill(root) || finder.pillMatches(root)) {
continue;
}
for (const run of finder.collectRuns(root, true, PASS)) {
for (let i = run.map.length - 1; i >= 0; i -= 1) {
const atom = run.map[i];
if (atom.parts.length < 2 || atom.parts.some((part) => inJzChar(part.textNode) || !finder.featureEnabledFor(part.textNode, PASS))) {
continue;
}
const first = atom.parts[0].textNode;
const bdClass = classifyBiaodian(atom.text, options.ruleset.biaodian);
if (!bdClass) {
continue;
}
const style = resolveStyle(first, options.locale);
const lang = classify(effectiveLang(first, options.locale.default));
const classes = baselineHalf(style, bdClass) ? [bdClass, "jz-half"] : [bdClass];
const bias = inkBias(codePointAtUtf16(atom.text, 0), lang, options.jiyaConfig.bias);
if (bias) {
classes.push("jz-ink-" + bias);
}
const outer = createJz(ctx.ownedElements, "jz-char", PASS, "wrap", { classes });
const inner = createJz(ctx.ownedElements, "jz-inner", PASS, "wrap");
wrapCrossAtom(atom, outer, inner, PASS, ctx);
}
}
}
},
collect(node, ctx) {
if (inJzChar(node)) {
return [];
}
if (!ctx.finder.featureEnabledFor(node, PASS)) {
return [];
}
const data = node.data;
const out = [];
let style = null;
let langClass = null;
const biasOverrides = ctx.options.jiyaConfig.bias;
const biaodian = ctx.options.ruleset.biaodian;
for (let i = 0; i < data.length; ) {
const ch = variationSequenceAtUtf16(data, i);
const bdClass = classifyBiaodian(ch, biaodian);
if (!bdClass) {
i += ch.length;
continue;
}
if (style === null) {
style = resolveStyle(node, ctx.options.locale);
}
if (langClass === null) {
langClass = classify(effectiveLang(node, ctx.options.locale.default));
}
const classes = baselineHalf(style, bdClass) ? [bdClass, "jz-half"] : [bdClass];
const bias = inkBias(codePointAtUtf16(ch, 0), langClass, biasOverrides);
if (bias) {
classes.push("jz-ink-" + bias);
}
out.push({
start: i,
end: i + ch.length,
pass: PASS,
build: (text) => {
const outer = createJz(
ctx.ownedElements,
"jz-char",
PASS,
"wrap",
{ classes }
);
const inner = createJz(
ctx.ownedElements,
"jz-inner",
PASS,
"wrap",
{ text }
);
outer.appendChild(inner);
return outer;
}
});
i += ch.length;
}
return out;
},
revert(ctx) {
const touched = revertCrossAtoms(PASS, ctx);
revertPass(PASS, ctx.root, ctx.ownedElements);
for (const parent of touched) {
parent.normalize?.();
}
}
};
var jiyaAdjacencyPass = {
name: "jiyaAdjacency",
kind: "standalone",
order: 25,
level: "text",
enabled: (o) => o.features.jiya,
render(ctx) {
const { finder } = ctx;
let prev = null;
const squeeze = (L, R) => {
if (!finder.featureEnabledFor(L, PASS) || !finder.featureEnabledFor(R, PASS)) {
return;
}
const lClass = bdClassOf(L);
const rClass = bdClassOf(R);
const lSides = blankSides(lClass);
const rSides = blankSides(rClass);
const stopBeforeOpen = lClass === "bd-stop" && rClass === "bd-open";
if (lSides.right && !stopBeforeOpen) {
L.classList.add("jz-half");
}
if (rSides.left) {
R.classList.add("jz-half");
}
};
const visit = (node) => {
for (let c = node.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3) {
if (finder.isAvoided(c) || !finder.inScope(c) || !finder.featureEnabledFor(c, PASS) || c.data.length > 0) {
prev = null;
}
continue;
}
if (c.nodeType !== 1) {
continue;
}
const el = c;
const nm = el.nodeName;
if (nm === "JZ-CHAR") {
if (!ctx.ownedElements.has(el) || !finder.featureEnabledFor(el, PASS)) {
prev = null;
continue;
}
if (prev) {
squeeze(prev, el);
}
prev = el;
} else {
switch (finder.category(el)) {
case "avoid":
case "pill": {
prev = null;
break;
}
case "transparent": {
const boundary = !finder.featureEnabledFor(el, PASS);
if (boundary) {
prev = null;
}
visit(el);
if (boundary) {
prev = null;
}
break;
}
default: {
prev = null;
visit(el);
prev = null;
break;
}
}
}
}
};
visit(ctx.root);
},
revert() {
}
};
// src/core/split.ts
var PASS_ATTR2 = "data-jz";
var KIND_ATTR2 = "data-jz-kind";
var SPLITTABLE = /* @__PURE__ */ new Set([
"STRONG",
"EM",
"B",
"I",
"U",
"S",
"SPAN",
"SMALL",
"INS",
"DEL",
"SUB",
"SUP"
]);
function canSplit(el, finder) {
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) {
if (c.nodeType === 3 && c.data.length > 0) {
return c;
}
if (c.nodeType === 1) {
if (!canSplit(c, finder)) {
return null;
}
const r = firstText(c, finder);
if (r) {
return r;
}
}
}
return null;
}
function lastText(el, finder) {
for (let c = el.lastChild; c; c = c.previousSibling) {
if (c.nodeType === 3 && c.data.length > 0) {
return c;
}
if (c.nodeType === 1) {
if (!canSplit(c, finder)) {
return null;
}
const r = lastText(c, finder);
if (r) {
return r;
}
}
}
return null;
}
function isTokenChar(ch, anyCjk) {
return !anyCjk.test(ch) && !/\s/.test(ch);
}
function leadingTokenEnd(d, anyCjk) {
const first = variationSequenceAtUtf16(d, 0);
if (anyCjk.test(first)) {
return first.length;
}
let e = first.length;
while (e < d.length) {
const ch = variationSequenceAtUtf16(d, e);
if (!isTokenChar(ch, anyCjk)) {
break;
}
e += ch.length;
}
return e;
}
function trailingTokenStart(d, anyCjk) {
let s = previousVariationSequenceStart(d, d.length);
if (anyCjk.test(variationSequenceAtUtf16(d, s))) {
return s;
}
while (s > 0) {
const previous = previousVariationSequenceStart(d, s);
if (!isTokenChar(variationSequenceAtUtf16(d, previous), anyCjk)) {
break;
}
s = previous;
}
return s;
}
function splitTreeAfter(top, textNode, offset, pass, finder, ownedElements) {
for (let a = textNode.parentNode; a && a.nodeType === 1; a = a.parentNode) {
if (!canSplit(a, finder)) {
return null;
}
if (a === top) {
break;
}
}
if (offset <= 0 && firstText(top, finder) === textNode) {
return null;
}
let rightAtLevel;
if (offset >= textNode.data.length) {
rightAtLevel = textNode.nextSibling;
} else if (offset <= 0) {
rightAtLevel = textNode;
} else {
rightAtLevel = textNode.splitText(offset);
}
let ancestor = textNode.parentNode;
let topClone = null;
while (ancestor && ancestor.nodeType === 1) {
const anc = ancestor;
let clone = null;
if (rightAtLevel) {
clone = anc.cloneNode(false);
clone.removeAttribute("id");
ownedElements.add(clone);
registerSplitoffSource(clone, anc, pass, ownedElements);
clone.setAttribute(PASS_ATTR2, pass);
clone.setAttribute(KIND_ATTR2, "splitoff");
let n = rightAtLevel;
while (n) {
const next = n.nextSibling;
clone.appendChild(n);
n = next;
}
}
if (anc === top) {
if (!clone) {
return null;
}
if (top.parentNode) {
top.parentNode.insertBefore(clone, top.nextSibling);
}
topClone = clone;
break;
}
if (clone) {
if (anc.parentNode) {
anc.parentNode.insertBefore(clone, anc.nextSibling);
}
rightAtLevel = clone;
} else {
rightAtLevel = anc.nextSibling;
}
ancestor = anc.parentNode;
}
return topClone;
}
function isolateBoundaryToken(el, side, anyCjk, pass, finder, ownedElements) {
if (!canSplit(el, finder)) {
return el;
}
const text = side === "tail" ? lastText(el, finder) : firstText(el, finder);
if (!text) {
return el;
}
const point = side === "tail" ? { textNode: text, offset: trailingTokenStart(text.data, anyCjk) } : { textNode: text, offset: leadingTokenEnd(text.data, anyCjk) };
const clone = splitTreeAfter(el, point.textNode, point.offset, pass, finder, ownedElements);
if (!clone) {
return el;
}
return side === "tail" ? clone : el;
}
function splitElementAt(el, textNode, offset, pass, finder, ownedElements) {
if (!canSplit(el, finder)) {
return null;
}
return splitTreeAfter(el, textNode, offset, pass, finder, ownedElements);
}
// src/typeset/jinze.ts
var PASS2 = "jinze";
var AVOID_HEAD = /* @__PURE__ */ new Set(["bd-close", "bd-pause", "bd-stop", "bd-middle", "bd-liga"]);
var AVOID_TAIL = /* @__PURE__ */ new Set(["bd-open"]);
function isCharEl(n) {
return !!n && n.nodeType === 1 && n.nodeName === "JZ-CHAR";
}
function firstMeaningfulChild(el) {
for (let c = el.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3 && c.data.length === 0) {
continue;
}
if (c.nodeType !== 1 && c.nodeType !== 3) {
continue;
}
return c;
}
return null;
}
function lastMeaningfulChild(el) {
for (let c = el.lastChild; c; c = c.previousSibling) {
if (c.nodeType === 3 && c.data.length === 0) {
continue;
}
if (c.nodeType !== 1 && c.nodeType !== 3) {
continue;
}
return c;
}
return null;
}
function atEdge(node, el, side) {
return node === (side === "head" ? firstMeaningfulChild(el) : lastMeaningfulChild(el));
}
function edgeCharEl(node, side, finder) {
let n = node;
while (n && n.nodeType === 1) {
if (isCharEl(n)) {
return n;
}
if (!finder.isTransparent(n)) {
return null;
}
n = side === "head" ? firstMeaningfulChild(n) : lastMeaningfulChild(n);
}
return null;
}
function bindingEnabled(node, finder, side) {
if (node.nodeType === 1 || node.nodeType === 3) {
if (!finder.inScope(node)) {
return false;
}
}
if (finder.isAvoided(node)) {
return false;
}
if (node.nodeType === 3) {
return finder.levelAllows(node, "paragraph") && finder.featureEnabledFor(node, PASS2);
}
if (node.nodeType !== 1) {
return true;
}
const el = node;
const category = finder.category(el);
if (category === "block") {
return false;
}
if (category === "pill" || side && category !== "transparent") {
return finder.levelAllows(el, "paragraph") && finder.featureEnabledFor(el, PASS2);
}
if (side) {
const child = side === "head" ? firstMeaningfulChild(el) : lastMeaningfulChild(el);
return child ? bindingEnabled(child, finder, side) : true;
}
for (let c = el.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3 && c.data.length === 0) {
continue;
}
if (!bindingEnabled(c, finder)) {
return false;
}
}
return true;
}
function atomicBoundaries(parent, finder) {
const boundaries = /* @__PURE__ */ new Map();
const directChildren = /* @__PURE__ */ new Map();
const directChild = (node) => {
const chain = [];
let current = node;
let result = null;
while (current && current !== parent) {
if (directChildren.has(current)) {
result = directChildren.get(current);
break;
}
chain.push(current);
if (current.parentNode === parent) {
result = current;
break;
}
current = current.parentNode;
}
for (const part of chain) {
directChildren.set(part, result);
}
return result;
};
for (const run of finder.collectRuns(parent, true)) {
for (const atom of run.map) {
let previous = null;
for (const part of atom.parts) {
const current = directChild(part.textNode);
if (previous && current && previous !== current) {
let next = boundaries.get(previous);
if (!next) {
next = /* @__PURE__ */ new Set();
boundaries.set(previous, next);
}
next.add(current);
}
previous = current;
}
}
}
return boundaries;
}
function forbiddenBreak(a, b, finder, atoms, wholeUnits = false) {
if (!bindingEnabled(a, finder, wholeUnits ? void 0 : "tail") || !bindingEnabled(b, finder, wholeUnits ? void 0 : "head")) {
return false;
}
if (atoms.get(a)?.has(b)) {
return true;
}
const head = edgeCharEl(b, "head", finder);
if (head) {
const c = bdClassOf(head);
if (c && AVOID_HEAD.has(c) && finder.featureEnabledFor(head, PASS2)) {
return true;
}
}
const tail = edgeCharEl(a, "tail", finder);
if (tail) {
const c = bdClassOf(tail);
if (c && AVOID_TAIL.has(c) && finder.featureEnabledFor(tail, PASS2)) {
return true;
}
}
return false;
}
function depthOf(node) {
let d = 0;
let p = node.parentNode;
while (p) {
d += 1;
p = p.parentNode;
}
return d;
}
function isTokenChar2(ch, anyCjk) {
return !anyCjk.test(ch) && !/\s/.test(ch);
}
function leadingTokenEnd2(d, anyCjk) {
const first = variationSequenceAtUtf16(d, 0);
if (anyCjk.test(first)) {
return first.length;
}
let e = first.length;
while (e < d.length) {
const ch = variationSequenceAtUtf16(d, e);
if (!isTokenChar2(ch, anyCjk)) {
break;
}
e += ch.length;
}
return e;
}
function trailingTokenStart2(d, anyCjk) {
let s = previousVariationSequenceStart(d, d.length);
if (anyCjk.test(variationSequenceAtUtf16(d, s))) {
return s;
}
while (s > 0) {
const previous = previousVariationSequenceStart(d, s);
if (!isTokenChar2(variationSequenceAtUtf16(d, previous), anyCjk)) {
break;
}
s = previous;
}
return s;
}
function splitBoundaries(t, needFirst, needLast, anyCjk) {
let node = t;
if (needFirst) {
const e = leadingTokenEnd2(node.data, anyCjk);
if (e < node.data.length) {
node = node.splitText(e);
}
}
if (needLast) {
const s = trailingTokenStart2(node.data, anyCjk);
if (s > 0) {
node.splitText(s);
}
}
}
function processParent(parent, finder, anyCjk, ownedElements) {
if (isJz(parent, PASS2)) {
return;
}
for (let c = parent.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 1 && c.nodeName === "JZ-JINZE") {
return;
}
}
const units = [];
for (let c = parent.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3 && c.data.length > 0) {
units.push(c);
} else if (c.nodeType === 1) {
units.push(c);
}
}
const n = units.length;
if (n < 2) {
return;
}
const forbidden = [];
const atoms = atomicBoundaries(parent, finder);
for (let i2 = 0; i2 < n - 1; i2 += 1) {
forbidden.push(forbiddenBreak(units[i2], units[i2 + 1], finder, atoms));
}
for (let i2 = 0; i2 < n; i2 += 1) {
const u = units[i2];
const needFirst = i2 > 0 && forbidden[i2 - 1];
const needLast = i2 < n - 1 && forbidden[i2];
if (!needFirst && !needLast) {
continue;
}
if (u.nodeType === 3) {
splitBoundaries(u, needFirst, needLast, anyCjk);
} else if (u.nodeType === 1) {
const el = u;
if (needLast) {
isolateBoundaryToken(el, "tail", anyCjk, PASS2, finder, ownedElements);
}
if (needFirst) {
isolateBoundaryToken(el, "head", anyCjk, PASS2, finder, ownedElements);
}
}
}
const kids = [];
for (let c = parent.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 1 || c.nodeType === 3 && c.data.length > 0) {
kids.push(c);
}
}
const splitAtoms = atomicBoundaries(parent, finder);
let i = 0;
while (i < kids.length) {
const run = [kids[i]];
while (i + 1 < kids.length && forbiddenBreak(kids[i], kids[i + 1], finder, splitAtoms, true)) {
run.push(kids[i + 1]);
i += 1;
}
if (run.length >= 2) {
const group = createJz(ownedElements, "jz-jinze", PASS2, "wrap");
parent.insertBefore(group, run[0]);
const last = run[run.length - 1];
let node = run[0];
while (node) {
const next = node.nextSibling;
group.appendChild(node);
if (node === last) {
break;
}
node = next;
}
}
i += 1;
}
}
var jinzePass = {
name: PASS2,
kind: "standalone",
order: 40,
level: "paragraph",
enabled: (o) => o.features.jinze,
render(ctx) {
const { finder } = ctx;
const anyCjk = new RegExp("[" + ctx.options.ruleset.cjk + "]", "u");
const parents = /* @__PURE__ */ new Set();
ctx.root.querySelectorAll("jz-char").forEach((c) => {
if (!ctx.ownedElements.has(c)) {
return;
}
if (!finder.levelAllows(c, "paragraph")) {
return;
}
let child = c;
let parent = c.parentNode;
while (parent && parent.nodeType === 1) {
parents.add(parent);
const pe = parent;
if (pe === ctx.root) {
break;
}
if (!finder.isTransparent(pe)) {
break;
}
if (!atEdge(child, pe, "head") && !atEdge(child, pe, "tail")) {
break;
}
child = pe;
parent = pe.parentNode;
}
});
const ordered = Array.from(parents).sort((a, b) => depthOf(b) - depthOf(a));
for (const parent of ordered) {
processParent(parent, finder, anyCjk, ctx.ownedElements);
}
},
revert(ctx) {
revertPass(PASS2, ctx.root, ctx.ownedElements);
}
};
// src/typeset/levelmark.ts
var ATTR = "data-jz-block-level";
var levelMarkPass = {
name: "levelMark",
kind: "standalone",
order: 10,
level: "text",
// 不受段落級閘限制:text 級之塊亦須標記(標記本身即分級輸出)
enabled: () => true,
// 分類鉤子非排版功能,恆開(純屬性輸出、零版面影響)
render(ctx) {
const { finder, options } = ctx;
const blockSet = options.finder.blockTags;
const nearest = /* @__PURE__ */ new Map();
const nearestBlock = ((p) => {
const cached = nearest.get(p);
if (cached !== void 0) {
return cached;
}
let e = p;
let block = null;
while (e) {
if (blockSet.has(e.nodeName)) {
block = e;
break;
}
if (e === ctx.root) {
break;
}
e = e.parentElement;
}
nearest.set(p, block);
return block;
});
finder.eachTextNode(ctx.root, (t) => {
if (!/\S/.test(t.data)) {
return;
}
const p = t.parentElement;
const block = p ? nearestBlock(p) : null;
if (!block) {
return;
}
const level = finder.levelFor(block);
setOwnedAttribute(block, ATTR, level, ctx.ownerToken);
});
},
revert(ctx) {
revertOwnedAttributes(ctx.root, ATTR, ctx.ownerToken);
}
};
// src/typeset/lineedge.ts
var LE = "jz-half-le";
var ALL_EDGE = [LE];
var EPS = 2;
var HEAD_CLASSES = /* @__PURE__ */ new Set(["bd-open"]);
var TAIL_CLASSES = /* @__PURE__ */ new Set(["bd-close"]);
var observers = /* @__PURE__ */ new WeakMap();
function hasLayout() {
return typeof ResizeObserver !== "undefined";
}
function installLayout(root, owner, key, run) {
run();
let owners = observers.get(root);
if (!owners) {
owners = /* @__PURE__ */ new Map();
observers.set(root, owners);
}
let m = owners.get(owner);
if (!m) {
m = /* @__PURE__ */ new Map();
owners.set(owner, m);
}
const existing = m.get(key);
if (existing) {
existing.disconnect();
}
const runCurrent = () => {
if (m.get(key) === ro) {
run();
}
};
const ro = new ResizeObserver(runCurrent);
m.set(key, ro);
ro.observe(root);
const fonts = root.ownerDocument.fonts;
if (fonts && fonts.ready && typeof fonts.ready.then === "function") {
fonts.ready.then(runCurrent, () => void 0);
}
}
function uninstallLayout(root, owner, key) {
const owners = observers.get(root);
const m = owners?.get(owner);
if (!m) {
return;
}
const ro = m.get(key);
if (ro) {
ro.disconnect();
m.delete(key);
}
if (m.size === 0) {
owners.delete(owner);
}
}
var JZ_WRAP_TAGS = ["JZ-JINZE", "JZ-CHAR", "JZ-INNER", "JZ-HWS", "JZ-ORPHAN"];
function buildWrapSet(options) {
const s = new Set(JZ_WRAP_TAGS);
for (const name of options.finder.styleInlines) {
s.add(name);
}
return s;
}
function inlineFlow(el, wrapSet) {
const display = el.ownerDocument.defaultView?.getComputedStyle(el).display;
if (display) {
return display === "contents" || display.startsWith("inline") || display.startsWith("ruby");
}
return wrapSet.has(el.nodeName);
}
function rectOf(n, side) {
if (n.nodeType === 3 && n.data.length > 0) {
const t = n;
const doc2 = t.ownerDocument;
if (!doc2) {
return null;
}
const range = doc2.createRange();
const at = side === "prev" ? previousVariationSequenceStart(t.data, t.data.length) : 0;
const length = variationSequenceAtUtf16(t.data, at).length;
range.setStart(t, at);
range.setEnd(t, at + length);
const rects = range.getClientRects();
if (rects.length) {
return rects[side === "prev" ? rects.length - 1 : 0];
}
} else if (n.nodeType === 1) {
const rects = n.getClientRects();
if (rects.length) {
return rects[side === "prev" ? rects.length - 1 : 0];
}
}
return null;
}
function edgeRect(start, side, from, wrapSet) {
let n = start;
let parentRef = from.parentNode;
for (; ; ) {
while (n) {
if (n.nodeType === 1 && n.nodeName === "BR" && n.ownerDocument.defaultView?.getComputedStyle(n).display !== "none") {
return null;
}
const r = rectOf(n, side);
if (r) {
return r;
}
n = side === "prev" ? n.previousSibling : n.nextSibling;
}
if (!parentRef || parentRef.nodeType !== 1) {
return null;
}
if (!inlineFlow(parentRef, wrapSet)) {
return null;
}
n = side === "prev" ? parentRef.previousSibling : parentRef.nextSibling;
parentRef = parentRef.parentNode;
}
}
function firstRect(el) {
const rects = el.getClientRects();
return rects.length ? rects[0] : null;
}
function lowerLine(a, b) {
return b.top >= a.bottom - EPS;
}
function higherLine(a, b) {
return b.bottom <= a.top + EPS;
}
function lastRect(el) {
const rects = el.getClientRects();
return rects.length ? rects[rects.length - 1] : null;
}
function clearEdge(ctx) {
for (const cls of ALL_EDGE) {
ctx.root.querySelectorAll("jz-char." + cls).forEach((e) => {
if (ctx.ownedElements.has(e)) {
ctx.setOwnedClass(e, cls, false);
}
});
}
}
function relayout(ctx) {
clearEdge(ctx);
const wrapSet = buildWrapSet(ctx.options);
const chars = Array.from(ctx.root.querySelectorAll("jz-char")).filter(
(c) => ctx.ownedElements.has(c) && ctx.finder.inScope(c) && !ctx.finder.isAvoided(c)
);
const decisions = [];
for (const el of chars) {
if (!ctx.finder.levelAllows(el, "paragraph")) {
continue;
}
if (!ctx.finder.featureEnabledFor(el, "jiya")) {
continue;
}
const bd = bdClassOf(el);
if (!bd) {
continue;
}
const isHead = HEAD_CLASSES.has(bd);
const isTail = TAIL_CLASSES.has(bd);
if (!isHead && !isTail) {
continue;
}
const r = firstRect(el);
if (!r) {
continue;
}
if (isHead) {
const prev = edgeRect(el.previousSibling, "prev", el, wrapSet);
if (!prev || higherLine(r, prev)) {
decisions.push({ el });
}
} else {
const next = edgeRect(el.nextSibling, "next", el, wrapSet);
if (!next || lowerLine(r, next)) {
decisions.push({ el });
}
}
}
for (const d of decisions) {
ctx.setOwnedClass(d.el, LE, true);
}
}
var lineEdgePass = {
name: "jiyaLineEdge",
kind: "standalone",
order: 90,
level: "paragraph",
enabled: (o) => o.features.jiya,
render(ctx) {
if (!hasLayout()) {
return;
}
installLayout(ctx.root, ctx.ownerToken, "lineEdge", () => {
relayout(ctx);
});
},
revert(ctx) {
uninstallLayout(ctx.root, ctx.ownerToken, "lineEdge");
clearEdge(ctx);
}
};
var TRIM = "jz-hws-trim";
function skippableAlign(g, cache) {
const key = g.parentElement;
if (!key) {
return false;
}
const cached = cache.get(key);
if (cached !== void 0) {
return cached;
}
let result = false;
const view = g.ownerDocument ? g.ownerDocument.defaultView : null;
if (view) {
const cs = view.getComputedStyle(g);
const ltr = cs.direction !== "rtl";
result = cs.textAlign === "left" || cs.textAlign === "start" && ltr;
}
cache.set(key, result);
return result;
}
function trimGaps(ctx, wrapSet) {
const alignCache = /* @__PURE__ */ new Map();
const gaps = Array.from(ctx.root.querySelectorAll("jz-hws")).filter(
(g) => ctx.ownedElements.has(g) && ctx.finder.inScope(g) && !ctx.finder.isAvoided(g) && ctx.finder.levelAllows(g, "paragraph") && ctx.finder.featureEnabledFor(g, "spacing") && !skippableAlign(g, alignCache)
);
if (!gaps.length) {
return;
}
gaps.forEach((g) => ctx.setOwnedClass(g, TRIM, true));
for (const g of gaps) {
const r = lastRect(g);
const next = r ? edgeRect(g.nextSibling, "next", g, wrapSet) : null;
const atLineEnd = !r || !next || lowerLine(r, next);
if (!atLineEnd) {
ctx.setOwnedClass(g, TRIM, false);
}
}
}
var gapTrimPass = {
name: "spacingGapTrim",
kind: "standalone",
order: 92,
level: "paragraph",
enabled: (o) => o.features.spacing,
render(ctx) {
if (!hasLayout()) {
return;
}
const wrapSet = buildWrapSet(ctx.options);
installLayout(ctx.root, ctx.ownerToken, "gapTrim", () => {
trimGaps(ctx, wrapSet);
});
},
revert(ctx) {
uninstallLayout(ctx.root, ctx.ownerToken, "gapTrim");
ctx.root.querySelectorAll("jz-hws." + TRIM).forEach((e) => {
if (ctx.ownedElements.has(e)) {
ctx.setOwnedClass(e, TRIM, false);
}
});
}
};
// src/typeset/longword.ts
var PASS3 = "longWord";
function inLangSpan(node) {
const p = node.parentNode;
if (!p || p.nodeType !== 1) {
return false;
}
const el = p;
if (el.nodeName !== "SPAN") {
return false;
}
return el.hasAttribute("lang");
}
var longWordPass = {
name: PASS3,
kind: "charify",
order: 60,
level: "paragraph",
enabled: (o) => o.features.longWord,
collect(node, ctx) {
if (inLangSpan(node)) {
return [];
}
if (!ctx.finder.featureEnabledFor(node, PASS3)) {
return [];
}
const cfg = ctx.options.longWord;
const re = new RegExp("[A-Za-z]{" + cfg.minLength + ",}", "g");
const data = node.data;
const out = [];
let m;
while ((m = re.exec(data)) !== null) {
const start = m.index;
const end = m.index + m[0].length;
out.push({
start,
end,
pass: PASS3,
build: (text) => {
const span = node.ownerDocument.createElement("span");
span.setAttribute("lang", cfg.lang);
span.setAttribute("data-jz", PASS3);
span.setAttribute("data-jz-kind", "wrap");
span.textContent = text;
ctx.ownedElements.add(span);
return span;
}
});
}
return out;
},
revert(ctx) {
revertPass(PASS3, ctx.root, ctx.ownedElements);
}
};
// src/typeset/orphan.ts
var PASS4 = "orphan";
function hasOrphan(block) {
for (let c = block.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 1 && c.nodeName === "JZ-ORPHAN") {
return true;
}
}
return false;
}
function collectSubst(block, finder, blockSet, anyCjk, biaodian) {
const out = [];
const atoms = /* @__PURE__ */ new Map();
for (const run of finder.collectRuns(block, true)) {
for (const atom of run.map) {
const first = atom.parts[0];
let starts = atoms.get(first.textNode);
if (!starts) {
starts = /* @__PURE__ */ new Map();
atoms.set(first.textNode, starts);
}
starts.set(first.start, atom.text);
}
}
const blockedChildren = /* @__PURE__ */ new Set();
const boundary = (directChild) => {
out.length = 0;
blockedChildren.add(directChild);
};
const visit = (node, directChild) => {
if (node.nodeType === 3) {
const t = node;
if (finder.isAvoided(t) || !finder.levelAllows(t, "paragraph") || !finder.featureEnabledFor(t, PASS4)) {
boundary(directChild);
return;
}
for (const [offset, ch] of atoms.get(t) || []) {
if (anyCjk.test(ch) && !classifyBiaodian(ch, biaodian)) {
out.push({ directChild, textNode: t, offset });
}
}
return;
}
if (node.nodeType !== 1) {
return;
}
const el = node;
if (finder.avoidsSelf(el) || blockSet.has(el.nodeName)) {
boundary(directChild);
return;
}
if (finder.pillMatches(el)) {
return;
}
if (!finder.levelAllows(el, "paragraph") || !finder.featureEnabledFor(el, PASS4)) {
boundary(directChild);
}
for (let c = el.firstChild; c; c = c.nextSibling) {
visit(c, directChild);
}
};
for (let dc = block.firstChild; dc; dc = dc.nextSibling) {
visit(dc, dc);
}
return { subst: out, blockedChildren };
}
function processBlock(block, finder, blockSet, anyCjk, biaodian, chars, ownedElements) {
if (hasOrphan(block)) {
return;
}
const { subst, blockedChildren } = collectSubst(block, finder, blockSet, anyCjk, biaodian);
if (subst.length < chars) {
return;
}
const boundary = subst[subst.length - chars];
let startChild = boundary.directChild;
if (boundary.textNode && boundary.textNode === startChild) {
if (boundary.offset > 0) {
startChild = boundary.textNode.splitText(boundary.offset);
}
} else if (boundary.textNode && startChild.nodeType === 1) {
const clone = splitElementAt(
startChild,
boundary.textNode,
boundary.offset,
PASS4,
finder,
ownedElements
);
if (clone) {
startChild = clone;
} else if (blockedChildren.has(startChild)) {
return;
}
}
const orphan = createJz(ownedElements, "jz-orphan", PASS4, "wrap");
block.insertBefore(orphan, startChild);
let n = startChild;
while (n) {
const next = n.nextSibling;
orphan.appendChild(n);
n = next;
}
}
var orphanPass = {
name: PASS4,
kind: "standalone",
order: 70,
level: "paragraph",
enabled: (o) => o.features.orphan,
render(ctx) {
const { finder, options } = ctx;
const blockSet = options.finder.blockTags;
const anyCjk = new RegExp("[" + options.ruleset.cjk + "]", "u");
const biaodian = options.ruleset.biaodian;
const chars = options.orphan.chars;
const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(",");
const nonLeaf = /* @__PURE__ */ new Set();
if (sel) {
ctx.root.querySelectorAll(sel).forEach((b) => {
let p = b.parentElement;
while (p) {
if (blockSet.has(p.nodeName)) {
nonLeaf.add(p);
break;
}
p = p.parentElement;
}
});
}
finder.eachBlock(ctx.root, (block) => {
if (!finder.levelAllows(block, "paragraph")) {
return;
}
if (!finder.featureEnabledFor(block, PASS4)) {
return;
}
if (finder.isAvoided(block)) {
return;
}
if (nonLeaf.has(block)) {
return;
}
processBlock(
block,
finder,
blockSet,
anyCjk,
biaodian,
chars,
ctx.ownedElements
);
});
},
revert(ctx) {
revertPass(PASS4, ctx.root, ctx.ownedElements);
}
};
// src/typeset/spacing.ts
var PASS5 = "spacing";
var OPS_HYPHEN = "\\+\\*=&\\-";
var OPS_GRADE = "\\+\\-\\*";
var QUOTES_CLS = '`"\u05F4';
var LBR_BASIC = "\\(\\[\\{";
var RBR_BASIC = "\\)\\]\\}";
var LBR_EXT = "\\(\\[\\{<>";
var RBR_EXT = "\\)\\]\\}<>";
function makeRules(rs) {
const CJK2 = rs.cjk;
const ANS_AFTER = rs.ansAfter;
const ANS_BEFORE = rs.ansBefore;
const CJK_ATOM = "(?:[" + CJK2 + "](?:[" + VARIATION_SELECTORS + "])?)";
const CJK_OR_ALNUM = "(?:" + CJK_ATOM + "|[" + ALNUM + "])";
return {
anyCjk: new RegExp(CJK_ATOM, "u"),
pillNeighbor: new RegExp("^(?:[" + ALNUM + "]|" + CJK_ATOM + ")$", "u"),
markerForbidden: new RegExp("[" + CJK2 + ANS_AFTER + ANS_BEFORE + "]", "u"),
STRIP_CJK_SPACE_ANS: new RegExp("(" + CJK_ATOM + ") ([" + ANS_AFTER + "])", "gu"),
STRIP_ANS_SPACE_CJK: new RegExp("([" + ANS_BEFORE + "]) (" + CJK_ATOM + ")", "gu"),
STRIP_CJK_SPACE_CJK: new RegExp("(" + CJK_ATOM + ") (?=" + CJK_ATOM + ")", "gu"),
DOTS_CJK: new RegExp("([\\.]{2,})(" + CJK_ATOM + ")", "gu"),
CJK_PUNCTUATION: new RegExp("(" + CJK_ATOM + ")([!;,\\?:]+)(?=" + CJK_OR_ALNUM + ")", "gu"),
AN_PUNCTUATION_CJK: new RegExp("([" + ALNUM + "])([!;,\\?]+)(" + CJK_ATOM + ")", "gu"),
CJK_TILDE: new RegExp("(" + CJK_ATOM + ")(~+)(?!=)(?=" + CJK_OR_ALNUM + ")", "gu"),
CJK_TILDE_EQUALS: new RegExp("(" + CJK_ATOM + ")(~=)", "gu"),
CJK_PERIOD: new RegExp("(" + CJK_ATOM + ")(\\.)(?![" + ALNUM + "\\./])(?=" + CJK_OR_ALNUM + ")", "gu"),
AN_PERIOD_CJK: new RegExp("([" + ALNUM + "])(\\.)(" + CJK_ATOM + ")", "gu"),
AN_COLON_CJK: new RegExp("([" + ALNUM + "])(:)(" + CJK_ATOM + ")", "gu"),
CJK_QUOTE: new RegExp("(" + CJK_ATOM + ")([" + QUOTES_CLS + "])", "gu"),
QUOTE_CJK: new RegExp("([" + QUOTES_CLS + "])(" + CJK_ATOM + ")", "gu"),
CJK_QUOTE_AN: new RegExp("(" + CJK_ATOM + ')(")([' + ALNUM + "])", "gu"),
CJK_HASH: new RegExp("(" + CJK_ATOM + ")(#([^ ]))", "gu"),
HASH_CJK: new RegExp("(([^ ])#)(" + CJK_ATOM + ")", "gu"),
SINGLE_LETTER_GRADE: new RegExp("\\b([" + ALPHA + "])([" + OPS_GRADE + "])(" + CJK_ATOM + ")", "gu"),
CJK_OPERATOR_ANS: new RegExp("(" + CJK_ATOM + ")([" + OPS_HYPHEN + "])([" + ALNUM + "])", "gu"),
ANS_OPERATOR_CJK: new RegExp("([" + ALNUM + "])([" + OPS_HYPHEN + "])(" + CJK_ATOM + ")", "gu"),
CJK_LESS_THAN: new RegExp("(" + CJK_ATOM + ")(<)([" + ALNUM + "])", "gu"),
LESS_THAN_CJK: new RegExp("([" + ALNUM + "])(<)(" + CJK_ATOM + ")", "gu"),
CJK_GREATER_THAN: new RegExp("(" + CJK_ATOM + ")(>)([" + ALNUM + "])", "gu"),
GREATER_THAN_CJK: new RegExp("([" + ALNUM + "])(>)(" + CJK_ATOM + ")", "gu"),
CJK_LEFT_BRACKET: new RegExp("(" + CJK_ATOM + ")([" + LBR_EXT + "])", "gu"),
RIGHT_BRACKET_CJK: new RegExp("([" + RBR_EXT + "])(" + CJK_ATOM + ")", "gu"),
// 英數字串段前之字元一併匹配,才能在不用 lookbehind 下排除 `.foo(`。
// 括號只作 lookahead,避免 `a(b(` 之前一次匹配吞掉 `(` 而漏掉第二處。
AN_LEFT_BRACKET: new RegExp(
"(^|[^." + ALNUM + "])([" + ALNUM + "]+)(?=[" + LBR_BASIC + "])",
"g"
),
RIGHT_BRACKET_AN: new RegExp("([" + RBR_BASIC + "])([" + ALNUM + "])", "g"),
CJK_ANS: new RegExp("(" + CJK_ATOM + ")([" + ANS_AFTER + "])", "gu"),
ANS_CJK: new RegExp("([" + ANS_BEFORE + "])(" + CJK_ATOM + ")", "gu"),
S_A: new RegExp("(%)([" + ALPHA + "])", "g")
};
}
var COMPOUND_WORD = /\b(?:[A-Za-z0-9]*[a-z][A-Za-z0-9]*-[A-Za-z0-9]+|[A-Za-z0-9]+-[A-Za-z0-9]*[a-z][A-Za-z0-9]*|[A-Za-z]+-[0-9]+|[A-Za-z]+[0-9]+-[A-Za-z0-9]+)(?:-[A-Za-z0-9]+)*\b/g;
var ASTRAL_CJK_SHADOW = "\u6F22";
function markersFor(text, R) {
const occupied = new Set(text);
const markers = [];
const tryRange = (start, end) => {
for (let cp = start; cp <= end; cp += 1) {
const ch = String.fromCodePoint(cp);
if (occupied.has(ch) || R.markerForbidden.test(ch)) {
continue;
}
markers.push(ch);
if (markers.length === 4) {
return true;
}
}
return false;
};
let found = tryRange(57344, 63743) || tryRange(983040, 1048573) || tryRange(1048576, 1114109) || tryRange(64976, 65007);
for (let plane = 0; !found && plane <= 16; plane += 1) {
found = tryRange(plane * 65536 + 65534, plane * 65536 + 65535);
}
if (!found) {
throw new Error("juzhen: \u6E90\u6587\u672C\u8207\u5B57\u7B26\u898F\u5247\u5DF2\u4F54\u7528\u5168\u90E8\u9593\u9699\u6A19\u8A18\u5019\u9078\u3002");
}
return {
gap: markers[0],
compoundOpen: markers[1],
compoundClose: markers[2],
deletion: markers[3]
};
}
function shadowAstralCjkAtoms(atoms, R) {
const shadowed = new Array(atoms.length);
for (let i = 0; i < atoms.length; i += 1) {
const atom = atoms[i];
const base = atom.codePointAt(0);
const match = base !== void 0 && base > 65535 ? R.anyCjk.exec(atom) : null;
shadowed[i] = match?.index === 0 ? ASTRAL_CJK_SHADOW : atom;
}
return shadowed.join("");
}
function panguSpace(text, R, markers) {
const MARK = markers.gap;
const PH_OPEN = markers.compoundOpen;
const PH_CLOSE = markers.compoundClose;
const DEL = markers.deletion;
const PH_PATTERN = new RegExp(PH_OPEN + "(\\d+)" + PH_CLOSE, "g");
let s = text;
s = s.replace(R.STRIP_CJK_SPACE_ANS, "$1$2");
s = s.replace(R.STRIP_ANS_SPACE_CJK, "$1$2");
s = s.replace(R.STRIP_CJK_SPACE_CJK, "$1" + DEL);
s = s.replace(R.DOTS_CJK, "$1" + MARK + "$2");
s = s.replace(R.CJK_PUNCTUATION, "$1$2" + MARK);
s = s.replace(R.AN_PUNCTUATION_CJK, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_TILDE, "$1$2" + MARK);
s = s.replace(R.CJK_TILDE_EQUALS, "$1" + MARK + "$2" + MARK);
s = s.replace(R.CJK_PERIOD, "$1$2" + MARK);
s = s.replace(R.AN_PERIOD_CJK, "$1$2" + MARK + "$3");
s = s.replace(R.AN_COLON_CJK, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_QUOTE, "$1" + MARK + "$2");
s = s.replace(R.QUOTE_CJK, "$1" + MARK + "$2");
s = s.replace(R.CJK_QUOTE_AN, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_HASH, "$1" + MARK + "$2");
s = s.replace(R.HASH_CJK, "$1" + MARK + "$3");
const compounds = [];
s = s.replace(COMPOUND_WORD, (m) => {
compounds.push(m);
return PH_OPEN + (compounds.length - 1) + PH_CLOSE;
});
s = s.replace(R.SINGLE_LETTER_GRADE, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_OPERATOR_ANS, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.ANS_OPERATOR_CJK, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.CJK_LESS_THAN, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.LESS_THAN_CJK, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.CJK_GREATER_THAN, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.GREATER_THAN_CJK, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(PH_PATTERN, (_m, idx) => compounds[Number(idx)] || "");
s = s.replace(R.CJK_LEFT_BRACKET, "$1" + MARK + "$2");
s = s.replace(R.RIGHT_BRACKET_CJK, "$1" + MARK + "$2");
s = s.replace(R.AN_LEFT_BRACKET, "$1$2" + MARK);
s = s.replace(R.RIGHT_BRACKET_AN, "$1" + MARK + "$2");
s = s.replace(R.CJK_ANS, "$1" + MARK + "$2");
s = s.replace(R.ANS_CJK, "$1" + MARK + "$2");
s = s.replace(R.S_A, "$1" + MARK + "$2");
return s;
}
var MOVED_PARTS = /* @__PURE__ */ new WeakMap();
var MOVED_ANCHOR = "jz-spacing-anchor";
var MOVED_GAP_ANCHOR = "jz-spacing-gap-anchor";
var CROSS_NODE_ATTR = "data-jz-cross-node-atom";
function wrapNodeInGap(node, ownedElements) {
const parent = node.parentNode;
if (!parent || parent.nodeName === "JZ-HWS") {
return;
}
const gap = createJz(ownedElements, "jz-hws", PASS5, "wrap");
parent.insertBefore(gap, node);
gap.appendChild(node);
}
function isolateTextSpan(part) {
let tn = part.textNode;
if (!tn.parentNode) {
return null;
}
if (part.start > 0) {
tn = tn.splitText(part.start);
}
const length = part.end - part.start;
if (tn.data.length > length) {
tn.splitText(length);
}
return tn;
}
function wrapSingleSpanInGap(part, ownedElements) {
const containingGap = part.textNode.parentElement?.closest("jz-hws");
if (containingGap) {
return ownedElements.has(containingGap) && containingGap.getAttribute("data-jz") === PASS5 && containingGap.getAttribute("data-jz-kind") === "wrap" ? containingGap : null;
}
const tn = isolateTextSpan(part);
if (!tn) {
return null;
}
const parent = tn.parentNode;
if (!parent) {
return null;
}
const gap = createJz(ownedElements, "jz-hws", PASS5, "wrap");
try {
parent.insertBefore(gap, tn);
gap.appendChild(tn);
return gap;
} catch (error) {
if (tn.parentNode === gap) {
parent.insertBefore(tn, gap);
}
if (gap.parentNode) {
gap.parentNode.removeChild(gap);
}
ownedElements.delete(gap);
parent.normalize?.();
throw error;
}
}
function isOwnedCrossGap(gap, ownedElements) {
return ownedElements.has(gap) && gap.getAttribute("data-jz") === PASS5 && gap.getAttribute("data-jz-kind") === "wrap" && gap.hasAttribute(CROSS_NODE_ATTR);
}
function trustedInsertionPoint(record) {
if (record.anchor.parentNode === record.originalParent) {
return record.anchor;
}
if (record.originalNextSibling?.parentNode === record.originalParent) {
return record.originalNextSibling;
}
if (record.originalNextSibling !== null) {
return void 0;
}
if (record.originalPreviousSibling?.parentNode === record.originalParent) {
return record.originalPreviousSibling === record.originalParent.lastChild ? null : void 0;
}
if (record.originalPreviousSibling === null && record.originalNextSibling === null && record.anchor.parentNode !== null && record.anchor.parentNode !== record.originalParent && !record.originalParent.hasChildNodes()) {
return null;
}
return void 0;
}
function restoreMovedText(record) {
if (record.textNode.parentNode === record.originalParent) {
return true;
}
if (record.textNode.parentNode !== record.gap) {
return false;
}
const before = trustedInsertionPoint(record);
if (before === void 0) {
return false;
}
record.originalParent.insertBefore(record.textNode, before);
return true;
}
function movePartIntoGap(part, gap, block, scopeRoot) {
const textNode = isolateTextSpan(part);
const parent = textNode?.parentNode;
const gapParent = gap.parentNode;
if (!textNode || !parent || !gapParent) {
return null;
}
const anchor = textNode.ownerDocument.createComment(MOVED_ANCHOR);
const gapAnchor = textNode.ownerDocument.createComment(MOVED_GAP_ANCHOR);
const record = {
anchor,
gapAnchor,
gap,
textNode,
originalParent: parent,
originalPreviousSibling: textNode.previousSibling,
originalNextSibling: textNode.nextSibling,
gapOriginalParent: gapParent,
gapOriginalPreviousSibling: gap.previousSibling,
gapOriginalNextSibling: gap.nextSibling,
block,
scopeRoot,
scopeAncestors: (() => {
const ancestors = [];
for (let parent2 = scopeRoot.parentElement; parent2; parent2 = parent2.parentElement) {
ancestors.push(parent2);
}
return ancestors;
})()
};
try {
parent.insertBefore(anchor, textNode);
gap.appendChild(gapAnchor);
gap.appendChild(textNode);
return record;
} catch (error) {
restoreMovedText(record);
if (anchor.parentNode) {
anchor.parentNode.removeChild(anchor);
}
if (gapAnchor.parentNode) {
gapAnchor.parentNode.removeChild(gapAnchor);
}
parent.normalize?.();
throw error;
}
}
function wrapGapLeft(span, ownedElements, ownerToken, block, scopeRoot) {
const containingGaps = /* @__PURE__ */ new Set();
for (const part of span.parts) {
const containing = part.textNode.parentElement?.closest("jz-hws");
if (containing) {
containingGaps.add(containing);
}
}
if (containingGaps.size > 0) {
if (containingGaps.size === 1) {
const existing = Array.from(containingGaps)[0];
if (isOwnedCrossGap(existing, ownedElements) && span.parts.every((part) => existing.contains(part.textNode))) {
return;
}
}
return;
}
const gap = wrapSingleSpanInGap(span.parts[0], ownedElements);
if (!gap) {
return;
}
if (span.parts.length === 1) {
return;
}
const moved = [];
try {
for (let i = 1; i < span.parts.length; i += 1) {
const record = movePartIntoGap(span.parts[i], gap, block, scopeRoot);
if (!record) {
throw new Error("juzhen: \u8DE8\u7BC0\u9EDE\u5B57\u5143\u642C\u79FB\u5931\u6557\u3002");
}
moved.push(record);
}
gap.setAttribute(CROSS_NODE_ATTR, "");
let records = MOVED_PARTS.get(ownerToken);
if (!records) {
records = /* @__PURE__ */ new Set();
MOVED_PARTS.set(ownerToken, records);
}
for (const record of moved) {
records.add(record);
}
} catch (error) {
for (let i = moved.length - 1; i >= 0; i -= 1) {
const record = moved[i];
restoreMovedText(record);
if (record.anchor.parentNode) {
record.anchor.parentNode.removeChild(record.anchor);
}
if (record.gapAnchor.parentNode) {
record.gapAnchor.parentNode.removeChild(record.gapAnchor);
}
record.originalParent.normalize?.();
}
gap.removeAttribute(CROSS_NODE_ATTR);
const gapParent = gap.parentNode;
if (gapParent) {
while (gap.firstChild) {
gapParent.insertBefore(gap.firstChild, gap);
}
gapParent.removeChild(gap);
gapParent.normalize?.();
}
ownedElements.delete(gap);
throw error;
}
}
function recordWithinRoot(record, root) {
const within = (node) => node === root || root.contains(node);
const detached = !record.anchor.isConnected && !record.gap.isConnected && !record.gapAnchor.isConnected && !record.originalParent.isConnected;
return root === record.scopeRoot || root.contains(record.scopeRoot) || record.scopeAncestors.some((ancestor) => root === ancestor || root.contains(ancestor)) || within(record.gap) || within(record.anchor) || within(record.originalParent) || within(record.block) || detached && root === root.ownerDocument.body;
}
function placeGapAtOriginalParent(record) {
if (record.gap.parentNode === record.gapOriginalParent) {
return true;
}
let before = null;
if (record.gapOriginalNextSibling?.parentNode === record.gapOriginalParent) {
before = record.gapOriginalNextSibling;
} else if (record.gapOriginalNextSibling !== null) {
return false;
} else if (record.gapOriginalPreviousSibling?.parentNode === record.gapOriginalParent) {
if (record.gapOriginalPreviousSibling !== record.gapOriginalParent.lastChild) {
return false;
}
before = null;
} else if (record.gapOriginalParent.lastChild !== record.gapOriginalPreviousSibling || record.gapOriginalPreviousSibling === null && record.gapOriginalNextSibling === null && record.gapOriginalParent.hasChildNodes()) {
return false;
}
record.gapOriginalParent.insertBefore(record.gap, before);
return true;
}
function unwrapCrossGap(record, ownedElements, touched) {
const gap = record.gap;
if (!isOwnedCrossGap(gap, ownedElements)) {
return;
}
if (!placeGapAtOriginalParent(record)) {
if (!gap.isConnected) {
ownedElements.delete(gap);
}
return;
}
const parent = gap.parentNode;
if (!parent) {
ownedElements.delete(gap);
return;
}
while (gap.firstChild) {
parent.insertBefore(gap.firstChild, gap);
}
parent.removeChild(gap);
recordUnwrappedParent(gap, parent, ownedElements);
ownedElements.delete(gap);
touched.add(parent);
}
function restoreMovedParts(ctx) {
const touched = /* @__PURE__ */ new Set();
const records = MOVED_PARTS.get(ctx.ownerToken);
if (!records) {
return touched;
}
const gaps = /* @__PURE__ */ new Map();
for (const record of Array.from(records)) {
if (!recordWithinRoot(record, ctx.root)) {
continue;
}
const sourceParent = resolveUnwrappedParent(record.originalParent);
const gapParent = resolveUnwrappedParent(record.gapOriginalParent);
if (!sourceParent || !gapParent) {
record.anchor.remove();
record.gapAnchor.remove();
records.delete(record);
continue;
}
record.originalParent = sourceParent;
record.gapOriginalParent = gapParent;
const live = (node) => record.scopeRoot.contains(node) || node.isConnected;
if (!isOwnedCrossGap(record.gap, ctx.ownedElements) || !live(record.gap)) {
record.anchor.remove();
record.gapAnchor.remove();
if (!live(record.gap)) {
ctx.ownedElements.delete(record.gap);
}
records.delete(record);
continue;
}
const restored = live(record.originalParent) && restoreMovedText(record);
if (!restored && record.textNode.parentNode === record.gap) {
record.gap.removeChild(record.textNode);
}
if (restored) {
touched.add(record.originalParent);
}
if (record.anchor.parentNode) {
record.anchor.parentNode.removeChild(record.anchor);
}
if (record.gapAnchor.parentNode) {
record.gapAnchor.parentNode.removeChild(record.gapAnchor);
}
gaps.set(record.gap, record);
records.delete(record);
}
for (const record of gaps.values()) {
unwrapCrossGap(record, ctx.ownedElements, touched);
}
if (records.size === 0) {
MOVED_PARTS.delete(ctx.ownerToken);
}
return touched;
}
function deleteSpan(span) {
for (let i = span.parts.length - 1; i >= 0; i -= 1) {
const part = span.parts[i];
part.textNode.data = part.textNode.data.slice(0, part.start) + part.textNode.data.slice(part.end);
}
}
function processBlock2(block, scopeRoot, finder, R, ownedElements, ownerToken) {
const runs = finder.collectRuns(block, false, PASS5);
for (const run of runs) {
const { map } = run;
const originalChars = map.map((span) => span.text);
const ruleText = shadowAstralCjkAtoms(originalChars, R);
if (originalChars.length < 2 || !R.anyCjk.test(ruleText)) {
continue;
}
const markers = markersFor(ruleText, R);
const spaced = panguSpace(ruleText, R, markers);
if (spaced === ruleText) {
continue;
}
const actions = [];
let origIdx = 0;
for (const sc of splitVariationSequences(spaced)) {
if (sc === markers.deletion) {
actions.push({ before: void 0, space: map[origIdx] });
origIdx += 1;
continue;
}
if (sc === markers.gap) {
const oc = origIdx < originalChars.length ? originalChars[origIdx] : null;
if (oc === " ") {
actions.push({ before: map[origIdx - 1], space: map[origIdx] });
origIdx += 1;
} else {
actions.push({ before: map[origIdx - 1], space: null });
}
continue;
}
origIdx += 1;
}
for (let i = actions.length - 1; i >= 0; i -= 1) {
const act = actions[i];
if (act.space) {
deleteSpan(act.space);
}
if (act.before) {
wrapGapLeft(act.before, ownedElements, ownerToken, block, scopeRoot);
}
}
}
}
function processPills(root, finder, boundarySelector, pillNeighbor, ownedElements, ownerToken) {
root.querySelectorAll(boundarySelector).forEach((pill) => {
if (finder.isAvoided(pill) || finder.insidePill(pill) || !finder.inScope(pill)) {
return;
}
if (!finder.featureEnabledFor(pill, PASS5)) {
return;
}
if (!pill.parentNode) {
return;
}
const prev = finder.adjacentLogicalChar(pill, -1, root, void 0, PASS5);
if (prev) {
handlePillSide(prev, pill, -1, finder, pillNeighbor, ownedElements, ownerToken, root);
}
const next = finder.adjacentLogicalChar(pill, 1, root, void 0, PASS5);
if (next) {
handlePillSide(next, pill, 1, finder, pillNeighbor, ownedElements, ownerToken, root);
}
});
}
function handlePillSide(adj, pill, dir, finder, pillNeighbor, ownedElements, ownerToken, scopeRoot) {
if (!adj.parts.every((part) => finder.featureEnabledFor(part.textNode, PASS5))) {
return;
}
let neighbor = adj;
if (adj.ch === " ") {
const beyond = finder.adjacentLogicalChar(pill, dir, scopeRoot, adj, PASS5);
if (!beyond || !pillNeighbor.test(beyond.ch) || !beyond.parts.every((part) => finder.featureEnabledFor(part.textNode, PASS5))) {
return;
}
const space = adj.parts[0];
deleteSpan({ text: " ", parts: adj.parts });
for (const part of beyond.parts) {
if (part.textNode === space.textNode && part.start >= space.end) {
part.start -= 1;
part.end -= 1;
}
}
beyond.offset = beyond.parts[0].start;
neighbor = beyond;
}
if (!pillNeighbor.test(neighbor.ch)) {
return;
}
if (dir === -1) {
wrapGapLeft(
{ text: neighbor.ch, parts: neighbor.parts },
ownedElements,
ownerToken,
pill.parentElement ?? scopeRoot,
scopeRoot
);
} else {
wrapNodeInGap(pill, ownedElements);
}
}
var spacingPass = {
name: PASS5,
kind: "standalone",
order: 50,
level: "text",
enabled: (o) => o.features.spacing,
render(ctx) {
const { finder, options } = ctx;
const R = makeRules(options.ruleset);
const processedRoots = /* @__PURE__ */ new Set();
ctx.finder.eachBlock(ctx.root, (block) => {
processBlock2(block, ctx.root, finder, R, ctx.ownedElements, ctx.ownerToken);
processedRoots.add(block);
});
const inlineRoots = [ctx.root];
if (options.scope.include) {
inlineRoots.push(...Array.from(ctx.root.querySelectorAll(options.scope.include)));
}
for (const root of inlineRoots) {
let covered = processedRoots.has(root);
for (let parent = root.parentElement; !covered && parent; parent = parent.parentElement) {
covered = processedRoots.has(parent);
if (parent === ctx.root) {
break;
}
}
if (options.finder.blockTags.has(root.nodeName) || covered || !finder.inScope(root) || finder.isAvoided(root) || finder.category(root) === "pill" || finder.insidePill(root)) {
continue;
}
processBlock2(root, ctx.root, finder, R, ctx.ownedElements, ctx.ownerToken);
processedRoots.add(root);
}
const boundarySelector = [options.finder.pillSelector, options.finder.isolateSelector].filter(Boolean).join(", ");
if (boundarySelector) {
processPills(
ctx.root,
finder,
boundarySelector,
R.pillNeighbor,
ctx.ownedElements,
ctx.ownerToken
);
}
},
revert(ctx) {
const touched = restoreMovedParts(ctx);
revertPass(PASS5, ctx.root, ctx.ownedElements);
for (const parent of touched) {
parent.normalize?.();
}
}
};
// src/index.ts
var DEFAULT_STYLE_INLINES = [
"STRONG",
"EM",
"A",
"B",
"I",
"U",
"S",
"MARK",
"INS",
"DEL",
"SUB",
"SUP",
"SMALL",
"ABBR",
"CITE",
"DFN",
"Q",
"SPAN",
"VAR",
"SAMP",
"TIME",
"BDO",
"BDI",
"RUBY",
"RB",
"RT",
"RP",
"WBR"
];
var DEFAULT_BLOCK_TAGS = [
"BODY",
"P",
"DIV",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"LI",
"DT",
"DD",
"BLOCKQUOTE",
"FIGURE",
"FIGCAPTION",
"TD",
"TH",
"CAPTION",
"SUMMARY",
"DETAILS",
"HEADER",
"FOOTER",
"SECTION",
"ARTICLE",
"ASIDE",
"NAV",
"MAIN",
"ADDRESS",
"PRE",
"UL",
"OL",
"DL",
"TABLE",
"TR",
"THEAD",
"TBODY",
"TFOOT",
"FORM",
"FIELDSET",
"LEGEND",
"LABEL",
"BR",
"HR"
];
var DEFAULT_SKIP_TAGS = [
"PRE",
"SAMP",
"TT",
"VAR",
"SCRIPT",
"STYLE",
"TEXTAREA",
"INPUT"
];
var PASSES = [
jiyaPass,
jiyaAdjacencyPass,
longWordPass,
spacingPass,
jinzePass,
orphanPass,
lineEdgePass,
gapTrimPass,
levelMarkPass
];
function feature(v, dflt) {
return v === void 0 ? dflt : !!v;
}
function resolveLongWord(v) {
if (v === false) {
return { minLength: 6, lang: "en" };
}
if (v === void 0 || v === true || v === null) {
return { minLength: 6, lang: "en" };
}
const minLength = typeof v.minLength === "number" && Number.isSafeInteger(v.minLength) && v.minLength >= 2 ? v.minLength : 6;
const lang = typeof v.lang === "string" && v.lang ? v.lang : "en";
return { minLength, lang };
}
function resolveOrphan(v) {
if (v && typeof v === "object") {
const chars = typeof v.chars === "number" && Number.isFinite(v.chars) && v.chars >= 1 ? Math.floor(v.chars) : 2;
return { chars };
}
return { chars: 2 };
}
function resolveJiya(v) {
if (v && typeof v === "object") {
return {
halfWidth: v.halfWidth === "margin" ? "margin" : "halt",
bias: v.bias || {}
};
}
return { halfWidth: "halt", bias: {} };
}
function normalizeOptions(opts = {}) {
const langOpt = opts.lang || {};
const scope = opts.scope || {};
return {
locale: {
default: langOpt.default === void 0 ? "zh-Hant" : langOpt.default,
...langOpt.style ? { style: langOpt.style } : {},
policy: langOpt.policy || {},
stylePolicy: Array.isArray(langOpt.stylePolicy) ? langOpt.stylePolicy.slice() : []
},
ruleset: resolveRuleset(opts.charClass),
justifyAtoms: opts.justifyAtoms === false ? false : true,
levelText: opts.level && opts.level.text || null,
scope: {
root: scope.root || null,
include: scope.include || null,
avoid: scope.avoid || null
},
features: {
spacing: feature(opts.spacing, true),
// feature(v,dflt) 對 v===false 即回 false,故無需 ===false 特判(與 jiya/jinze 同構)。
longWord: feature(opts.longWord, true),
jiya: feature(opts.jiya, true),
jinze: feature(opts.jinze, true),
orphan: feature(opts.orphan, true),
biaodian: feature(opts.biaodian, false),
emphasis: feature(opts.emphasis, false),
ruby: feature(opts.ruby, false)
},
longWord: resolveLongWord(opts.longWord),
orphan: resolveOrphan(opts.orphan),
// 行端僅做半形擠壓(lineedge:行首開類收左、行尾閉類收右)。**標點懸掛已移除**
// (§6.5.6):純負 margin 機制為機制級死路(寬度守恆矛盾,CSS 接觸不到斷行器);
// opts.hanging 仍被接受以保 API 相容,但無任何效果。
jiyaConfig: resolveJiya(opts.jiya),
blocks: Array.isArray(opts.blocks) ? opts.blocks.slice() : [],
finder: {
styleInlines: new Set(opts.styleInlines || DEFAULT_STYLE_INLINES),
blockTags: new Set(opts.blockTags || DEFAULT_BLOCK_TAGS),
skipTags: new Set(opts.skipTags || DEFAULT_SKIP_TAGS),
pillSelector: opts.pillSelector || "code, kbd",
// 內外隔離(§4.1 三分類):預設 <mark>(瀏覽器預設帶背景,切分有可見
// 接縫、邊界需留隙)。顯式判 undefined,使 "" 可停用。
isolateSelector: opts.isolateSelector !== void 0 ? opts.isolateSelector : "mark",
skipAttribute: opts.skipAttribute || null,
isSkipped: typeof opts.isSkipped === "function" ? opts.isSkipped : null
}
};
}
function resolveRoot(arg, options) {
const pick = arg !== void 0 ? arg : options.scope.root;
if (pick && typeof pick !== "string") {
return pick;
}
if (typeof pick === "string" && typeof document !== "undefined") {
return document.querySelector(pick);
}
return typeof document !== "undefined" ? document.body : null;
}
var OBSERVE_ATTRS = [
"class",
"lang",
"id",
"contenteditable",
"data-jz-skip",
"data-jz-level",
"data-jz-style",
"data-juzhen",
"data-juzhen-off"
];
function createJuzhen(opts = {}) {
const options = normalizeOptions(opts);
const finder = new Finder(options);
const ownedElements = /* @__PURE__ */ new WeakSet();
const ownerToken = {};
const renderedRoots = /* @__PURE__ */ new Set();
function makeCtx(root) {
return { root, options, finder, ownedElements, ownerToken, setOwnedClass };
}
function renderImpl(el) {
renderedRoots.add(el);
runPasses(PASSES, makeCtx(el));
if (options.features.jiya && options.jiyaConfig.halfWidth === "margin") {
setOwnedAttribute(el, "data-jz-halfwidth", "margin", ownerToken);
}
if (!options.justifyAtoms) {
setOwnedAttribute(el, "data-jz-atoms", "inline", ownerToken);
}
}
function rootsWithin(el) {
return Array.from(renderedRoots).filter((root) => root === el || el.contains(root)).sort((a, b) => a.contains(b) ? 1 : b.contains(a) ? -1 : 0);
}
function unwindRoots(roots) {
for (const root of roots) {
revertPasses(PASSES, makeCtx(root));
revertOwnedAttribute(root, "data-jz-halfwidth", ownerToken);
revertOwnedAttribute(root, "data-jz-atoms", ownerToken);
renderedRoots.delete(root);
finder.invalidate(root);
}
}
function revertImpl(el) {
const roots = rootsWithin(el);
unwindRoots(roots);
if (!roots.includes(el)) {
revertPasses(PASSES, makeCtx(el));
}
finder.invalidate(el);
}
let observer = null;
let observeRoot = null;
let observerPauseDepth = 0;
let observerActive = false;
const expectedClassMutations = [];
let expectedClassMutationHead = 0;
const observeConfig = {
subtree: true,
childList: true,
characterData: true,
attributes: true,
attributeOldValue: true,
attributeFilter: OBSERVE_ATTRS.slice()
};
function clearExpectedClassMutations() {
expectedClassMutations.length = 0;
expectedClassMutationHead = 0;
}
function setOwnedClass(element, className, present) {
if (!ownedElements.has(element) || element.classList.contains(className) === present) {
return;
}
const oldValue = element.getAttribute("class");
const tokens = Array.from(element.classList).filter((token) => token !== className);
if (present) {
tokens.push(className);
}
const newValue = tokens.join(" ");
const shouldExpect = observerActive && observerPauseDepth === 0 && !!observeRoot && observeRoot.contains(element);
const expectation = shouldExpect ? { target: element, oldValue, newValue } : null;
if (expectation) {
expectedClassMutations.push(expectation);
}
try {
if (present) {
element.classList.add(className);
} else {
element.classList.remove(className);
}
} catch (error) {
if (expectation) {
expectedClassMutations.pop();
}
throw error;
}
if (expectation && element.getAttribute("class") !== newValue) {
expectedClassMutations.pop();
}
}
function classMutationNewValues(records) {
const values = new Array(records.length);
const nextValues = /* @__PURE__ */ new Map();
for (let i = records.length - 1; i >= 0; i -= 1) {
const record = records[i];
if (record.type !== "attributes" || record.attributeName !== "class") {
continue;
}
const target = record.target;
values[i] = nextValues.has(target) ? nextValues.get(target) : target.getAttribute("class");
nextValues.set(target, record.oldValue);
}
return values;
}
function consumeExpectedClassMutation(record, newValue) {
if (record.type !== "attributes" || record.attributeName !== "class") {
return false;
}
const expected = expectedClassMutations[expectedClassMutationHead];
if (!expected || expected.target !== record.target || expected.oldValue !== record.oldValue || expected.newValue !== newValue) {
return false;
}
expectedClassMutationHead += 1;
return true;
}
function compactExpectedClassMutations() {
if (expectedClassMutationHead === 0) {
return;
}
if (expectedClassMutationHead === expectedClassMutations.length) {
clearExpectedClassMutations();
return;
}
if (expectedClassMutationHead >= 1024 && expectedClassMutationHead * 2 >= expectedClassMutations.length) {
expectedClassMutations.splice(0, expectedClassMutationHead);
expectedClassMutationHead = 0;
}
}
function pauseObserver() {
observerPauseDepth += 1;
if (observerPauseDepth === 1) {
const records = observer ? observer.takeRecords() : [];
const errorWindow = observeRoot?.ownerDocument.defaultView;
observerActive = false;
if (observer) {
observer.disconnect();
}
try {
if (records.length) {
onMutations(records);
}
} catch (error) {
const report = () => {
throw error;
};
if (errorWindow) {
errorWindow.queueMicrotask(report);
} else {
queueMicrotask(report);
}
} finally {
clearExpectedClassMutations();
}
}
}
function resumeObserver() {
if (observerPauseDepth <= 0) {
return;
}
observerPauseDepth -= 1;
if (observerPauseDepth === 0 && observer && observeRoot) {
observer.takeRecords();
clearExpectedClassMutations();
observer.observe(observeRoot, observeConfig);
observerActive = true;
}
}
function withObserverPaused(run) {
pauseObserver();
try {
return run();
} finally {
resumeObserver();
}
}
function withinObservedRoot(node) {
return !!observeRoot && (node === observeRoot || observeRoot.contains(node));
}
function nearestBlock(node) {
if (!withinObservedRoot(node)) {
return null;
}
let e = node.nodeType === 1 ? node : node.parentElement;
while (e) {
if (options.finder.blockTags.has(e.nodeName)) {
return e;
}
if (e === observeRoot) {
break;
}
e = e.parentElement;
}
return observeRoot;
}
function onMutations(records) {
const rerenderBlocks = /* @__PURE__ */ new Set();
const renderNew = [];
const removedRoots = /* @__PURE__ */ new Set();
const removedSubtrees = records.flatMap((record) => record.type === "childList" ? Array.from(record.removedNodes) : []);
const classNewValues = classMutationNewValues(records);
try {
for (let recordIndex = 0; recordIndex < records.length; recordIndex += 1) {
const m = records[recordIndex];
if (consumeExpectedClassMutation(m, classNewValues[recordIndex])) {
continue;
}
if (m.type === "childList") {
m.addedNodes.forEach((n) => {
if (!withinObservedRoot(n)) {
return;
}
if (n.nodeType === 1 && options.finder.blockTags.has(n.nodeName)) {
const np = n;
const P = np.parentElement;
const pIsBlock = !!P && options.finder.blockTags.has(P.nodeName);
const pHadLeafOrphan = !!P && Array.prototype.some.call(
P.children,
(c) => c.nodeName === "JZ-ORPHAN"
);
if (pIsBlock && pHadLeafOrphan) {
rerenderBlocks.add(P);
} else if (!pIsBlock && P) {
const b = nearestBlock(P);
if (b) {
rerenderBlocks.add(b);
}
} else {
renderNew.push(np);
}
} else if (n.nodeType === 1 || n.nodeType === 3) {
const b = nearestBlock(m.target);
if (b) {
rerenderBlocks.add(b);
}
}
});
if (m.removedNodes.length) {
m.removedNodes.forEach((node) => {
if (node.nodeType !== 1) {
return;
}
for (const root of rootsWithin(node)) {
removedRoots.add(root);
}
});
const b = nearestBlock(m.target);
if (b) {
rerenderBlocks.add(b);
}
}
} else {
const b = nearestBlock(m.target);
if (b) {
rerenderBlocks.add(b);
}
}
}
} finally {
compactExpectedClassMutations();
}
if (!rerenderBlocks.size && !renderNew.length && !removedRoots.size) {
return;
}
withObserverPaused(() => {
unwindRoots(Array.from(removedRoots).sort((a, b) => a.contains(b) ? 1 : b.contains(a) ? -1 : 0));
const all = Array.from(rerenderBlocks).filter(withinObservedRoot);
const blocks = all.filter((b) => !all.some((o) => o !== b && o.contains(b)));
for (const b of blocks) {
revertImpl(b);
renderImpl(b);
}
for (const el of renderNew) {
if (!withinObservedRoot(el)) {
continue;
}
if (blocks.some((b) => b.contains(el))) {
continue;
}
const relocated = removedSubtrees.some((node) => node === el || node.contains(el));
const hasOwnedContent = ownedElements.has(el) || Array.from(el.querySelectorAll("[data-jz]")).some((node) => ownedElements.has(node));
if (relocated || hasOwnedContent || rootsWithin(el).length) {
revertImpl(el);
} else {
finder.invalidate(el);
}
renderImpl(el);
}
});
}
return {
options,
render(root) {
const el = resolveRoot(root, options);
if (!el) {
return;
}
withObserverPaused(() => renderImpl(el));
},
revert(root) {
const el = resolveRoot(root, options);
if (!el) {
return;
}
withObserverPaused(() => revertImpl(el));
},
rerender(root) {
const el = resolveRoot(root, options);
if (!el) {
return;
}
withObserverPaused(() => {
revertImpl(el);
renderImpl(el);
});
},
invalidate(root) {
const el = resolveRoot(root, options);
if (el) {
finder.invalidate(el);
}
},
observe(root) {
if (typeof MutationObserver === "undefined") {
return;
}
const el = resolveRoot(root, options);
if (!el) {
return;
}
observerActive = false;
if (observer) {
observer.disconnect();
}
clearExpectedClassMutations();
observeRoot = el;
observer = new MutationObserver(onMutations);
if (observerPauseDepth === 0) {
observer.observe(el, observeConfig);
observerActive = true;
}
},
disconnect() {
observerActive = false;
if (observer) {
observer.disconnect();
observer = null;
observeRoot = null;
}
clearExpectedClassMutations();
}
};
}
export {
createJuzhen,
normalizeOptions
};