"use strict"; var Juzhen = (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createJuzhen: () => createJuzhen, normalizeOptions: () => normalizeOptions }); // 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); for (const sub of BD_ORDER) { if (biaodian[sub].indexOf(base) >= 0) { return "bd-" + sub; } } 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"; 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 選擇器(三分類之「隔離」:帶盒樣式之行內,如背景色 * )。邊界口徑同 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 * 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1, * 另議)。 */ 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 遇標點原子既不進入亦不 * 終止、而**跳過**它續找更前之字(`汉字。` 綁定後 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)。 */ 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.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。回傳 text.length >= 2 之 run。 */ collectRuns(block) { const runs = []; let curText = ""; let curMap = []; const flush = () => { if (curMap.length >= 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)) { return; } const data = t.data; for (let i = 0; i < data.length; ) { const ch = variationSequenceAtUtf16(data, i); curText += ch; const part = { textNode: t, start: i, end: i + ch.length }; const previous = curMap[curMap.length - 1]; if (isVariationSelector(ch) && previous && !isVariationSelector(codePointAtUtf16(previous.text, 0)) && !isVariationSelector(codePointAtUtf16( previous.text, previousCodePointStart(previous.text, previous.text.length) ))) { previous.text += ch; previous.parts.push(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": { recurse(); return; } default: { flush(); recurse(); flush(); return; } } }; let c = block.firstChild; while (c) { visit(c); c = c.nextSibling; } flush(); return runs; } /** * pill 之邏輯鄰字(架構 §4.1):經樣式鏈遞迴,遇 pill/block/avoid/間隙 * marker 即止。direction = -1 取前鄰字,+1 取後鄰字。 */ adjacentLogicalChar(el, direction) { const pickEnd = (tn) => { if (direction === -1) { const offset = previousVariationSequenceStart(tn.data, tn.data.length); return { textNode: tn, offset, ch: variationSequenceAtUtf16(tn.data, offset) }; } return { textNode: tn, offset: 0, ch: variationSequenceAtUtf16(tn.data, 0) }; }; const descend = (into) => { let n2 = direction === -1 ? into.lastChild : into.firstChild; while (n2) { if (n2.nodeType === 3 && n2.data.length > 0) { return pickEnd(n2); } if (n2.nodeType === 1) { if (!this.isTransparent(n2)) { return "blocked"; } const r = descend(n2); if (r) { return r; } } n2 = direction === -1 ? n2.previousSibling : n2.nextSibling; } return null; }; let n = direction === -1 ? el.previousSibling : el.nextSibling; let p = el.parentNode; for (; ; ) { while (n) { if (n.nodeType === 3) { if (n.data.length > 0) { return pickEnd(n); } n = direction === -1 ? n.previousSibling : n.nextSibling; continue; } if (n.nodeType === 1) { const e = n; if (!this.isTransparent(e)) { return null; } const r = descend(e); if (r === "blocked") { return null; } if (r) { return r; } n = direction === -1 ? e.previousSibling : e.nextSibling; continue; } n = direction === -1 ? n.previousSibling : n.nextSibling; } if (!p || p.nodeType !== 1 || !this.isTransparent(p)) { return null; } const pe = p; n = direction === -1 ? pe.previousSibling : pe.nextSibling; p = pe.parentNode; } } /** * 節點之功能級別(§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(); 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); 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 prev = so.previousSibling; if (prev && prev.nodeType === 1 && prev.nodeName === so.nodeName) { while (so.firstChild) { prev.appendChild(so.firstChild); } parent.removeChild(so); } else { while (so.firstChild) { parent.insertBefore(so.firstChild, so); } parent.removeChild(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) { 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/locale.ts var DEFAULT_POLICY = { "zh-Hant": "quanjiao", "zh-Hans": "kaiming", "ja": "quanjiao", "other": "quanjiao" }; function effectiveLang(node, fallback) { let p = node.parentNode; while (p && p.nodeType === 1) { const el = p; const v = el.getAttribute("lang"); if (v) { 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) { if (l.indexOf("hant") >= 0) { return "zh-Hant"; } if (l.indexOf("hans") >= 0) { return "zh-Hans"; } if (/-(tw|hk|mo)\b/.test(l)) { 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, 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) { revertPass(PASS, ctx.root, ctx.ownedElements); } }; 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)) { 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 (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)) { prev = null; continue; } if (prev) { squeeze(prev, el); } prev = el; } else { switch (finder.category(el)) { case "avoid": case "pill": { prev = null; break; } case "transparent": { visit(el); 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"); clone.setAttribute(PASS_ATTR2, pass); clone.setAttribute(KIND_ATTR2, "splitoff"); ownedElements.add(clone); 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; } 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; } 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 forbiddenBreak(a, b, finder) { 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 = []; for (let i2 = 0; i2 < n - 1; i2 += 1) { forbidden.push(forbiddenBreak(units[i2], units[i2 + 1], finder)); } 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) { kids.push(c); } let i = 0; while (i < kids.length) { const run = [kids[i]]; while (i + 1 < kids.length && forbiddenBreak(kids[i], kids[i + 1], finder)) { run.push(kids[i + 1]); i += 1; } if (run.length >= 2) { const group = createJz(ownedElements, "jz-jinze", PASS2, "wrap"); parent.insertBefore(group, run[0]); for (const node of run) { group.appendChild(node); } } 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 (!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 ro = new ResizeObserver(() => run()); ro.observe(root); m.set(key, ro); const fonts = root.ownerDocument.fonts; if (fonts && fonts.ready && typeof fonts.ready.then === "function") { fonts.ready.then(run, () => 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"]; function buildWrapSet(options) { const s = new Set(JZ_WRAP_TAGS); for (const name of options.finder.styleInlines) { s.add(name); } return s; } 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) { const r = rectOf(n, side); if (r) { return r; } n = side === "prev" ? n.previousSibling : n.nextSibling; } if (!parentRef || parentRef.nodeType !== 1) { return null; } if (!wrapSet.has(parentRef.nodeName)) { 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 || next.top > r.top + EPS; 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 visit = (node, directChild) => { if (node.nodeType === 3) { const t = node; const d = t.data; for (let i = 0; i < d.length; ) { const ch = variationSequenceAtUtf16(d, i); if (anyCjk.test(ch) && !classifyBiaodian(ch, biaodian)) { out.push({ directChild, textNode: t, offset: i }); } i += ch.length; } return; } if (node.nodeType !== 1) { return; } const el = node; if (finder.avoidsSelf(el)) { return; } if (blockSet.has(el.nodeName) || finder.pillMatches(el)) { return; } 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 out; } function processBlock(block, finder, blockSet, anyCjk, biaodian, chars, ownedElements) { if (hasOrphan(block)) { return; } const subst = 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; } } 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 MARK = "\uE000"; var PH_OPEN = "\uE001"; var PH_CLOSE = "\uE002"; var OPS_HYPHEN = "\\+\\*=&\\-"; var OPS_GRADE = "\\+\\-\\*"; var QUOTES_CLS = '`"\u05F4'; var LBR_BASIC = "\\(\\[\\{"; var RBR_BASIC = "\\)\\]\\}"; var LBR_EXT = "\\(\\[\\{<>"; var RBR_EXT = "\\)\\]\\}<>"; var DEL = String.fromCharCode(57347); 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"), 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 PH_PATTERN = new RegExp(PH_OPEN + "(\\d+)" + PH_CLOSE, "g"); var ASTRAL_CJK_SHADOW = "\u6F22"; 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) { if (splitVariationSequences(text).length <= 1 || !R.anyCjk.test(text)) { return text; } 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); 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 restored = 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); for (const run of runs) { const { map } = run; const originalChars = map.map((span) => span.text); const ruleText = shadowAstralCjkAtoms(originalChars, R); const spaced = panguSpace(ruleText, R); if (spaced === ruleText) { continue; } const actions = []; let origIdx = 0; for (const sc of splitVariationSequences(spaced)) { if (sc === DEL) { actions.push({ before: void 0, space: map[origIdx] }); origIdx += 1; continue; } if (sc === MARK) { 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); if (prev) { handlePillSide(prev, pill, -1, pillNeighbor, ownedElements, ownerToken, root); } const next = finder.adjacentLogicalChar(pill, 1); if (next) { handlePillSide(next, pill, 1, pillNeighbor, ownedElements, ownerToken, root); } }); } function handlePillSide(adj, pill, dir, pillNeighbor, ownedElements, ownerToken, scopeRoot) { if (dir === -1) { if (pillNeighbor.test(adj.ch)) { wrapGapLeft({ text: adj.ch, parts: [{ textNode: adj.textNode, start: adj.offset, end: adj.offset + adj.ch.length }] }, ownedElements, ownerToken, pill.parentElement ?? scopeRoot, scopeRoot); return; } if (adj.ch === " " && adj.offset >= 1) { const tn = adj.textNode; const beforeOffset = previousVariationSequenceStart(tn.data, adj.offset); const bc = variationSequenceAtUtf16(tn.data, beforeOffset); if (bc !== " " && pillNeighbor.test(bc)) { tn.data = tn.data.slice(0, adj.offset) + tn.data.slice(adj.offset + 1); wrapGapLeft({ text: bc, parts: [{ textNode: tn, start: beforeOffset, end: beforeOffset + bc.length }] }, ownedElements, ownerToken, pill.parentElement ?? scopeRoot, scopeRoot); } } } else { if (pillNeighbor.test(adj.ch)) { wrapNodeInGap(pill, ownedElements); return; } if (adj.ch === " " && adj.textNode.data.length > 1) { const ac = variationSequenceAtUtf16(adj.textNode.data, 1); if (ac !== " " && pillNeighbor.test(ac)) { adj.textNode.data = adj.textNode.data.slice(1); 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); ctx.finder.eachBlock(ctx.root, (block) => { if (!finder.featureEnabledFor(block, PASS5)) { return; } processBlock2(block, ctx.root, finder, R, ctx.ownedElements, ctx.ownerToken); }); if (!options.finder.blockTags.has(ctx.root.nodeName) && finder.featureEnabledFor(ctx.root, PASS5) && finder.inScope(ctx.root) && !finder.isAvoided(ctx.root)) { processBlock2(ctx.root, ctx.root, finder, R, ctx.ownedElements, ctx.ownerToken); } 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" && 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" && 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 || "zh-Hant", ...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 三分類):預設 (瀏覽器預設帶背景,切分有可見 // 接縫、邊界需留隙)。顯式判 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); if (roots.length === 0) { revertPasses(PASSES, makeCtx(el)); } else { unwindRoots(roots); } 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() { if (observerPauseDepth === 0) { observerActive = false; if (observer) { observer.disconnect(); } clearExpectedClassMutations(); } observerPauseDepth += 1; } 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 nearestBlock(node) { 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 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 (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((b) => b.isConnected); 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 (!el.isConnected) { continue; } if (blocks.some((b) => b.contains(el))) { continue; } 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(); } }; } return __toCommonJS(index_exports); })(); if(typeof globalThis!=='undefined'){globalThis.Juzhen=Juzhen;}