// 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.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(); } /** 該元素是否匹配 pill 選擇器(行內塊,如 code/kbd)。 */ pillMatches(el) { return !!(el.matches && this.pillSelector && el.matches(this.pillSelector)); } /** 邏輯文本應**透明穿越**之行內元素:樣式行內標籤,以及聚珍自身之 wrap 類 * jz-*(jz-hws 間隙、jz-jinze 禁則群組、jz-char/jz-inner 標點原子)。穿越這些 * 才能正確找到跨包裝之邏輯鄰字(如 jinze 已把 pill 納入 jz-jinze 後,pill 之左 * 鄰字仍須可達;I7)。 * ⚠ jz-char/jz-inner 必須透明:否則 adjacentLogicalChar 之 descend 遇標點原子既 * 不進入亦不終止、而是**跳過**它續找更前之兄弟(下游 Bug:`汉字。` 經 jinze * 綁定為 `汉`,pill 之左鄰字應 * 為「。」、不補間隙;舊邏輯卻越過 jz-char 取到「字」→ 誤包 jz-hws 於「字。」之間)。 * jz-char 僅含標點(jiya 只 charify biaodian),標點非 pillNeighbor,故透明後 descend * 回傳標點 → 不補隙,與 collectRuns 之既有透明遞迴一致。jz-inner 亦須列入,否則 * descend 進入 jz-char 後因 jz-inner 不透明而跳過其內標點。 */ isTransparentInline(name) { return this.styleSet.has(name) || name === "JZ-HWS" || name === "JZ-JINZE" || name === "JZ-CHAR" || name === "JZ-INNER"; } /** 節點是否處於 avoid 子樹(內建 skip 名單 / SVG / contentEditable / * data-jz-skip / 使用者 avoid 選擇器 / skipAttribute / isSkipped)。 */ isAvoided(node) { if (this.userIsSkipped && this.userIsSkipped(node)) { return true; } let p = node.parentNode; while (p && p.nodeType === 1) { const el = p; 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; } p = p.parentNode; } return false; } /** 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 / 非作用域)。 */ 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.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):樣式行內元素與 wrap 類 * jz-* 透明穿越;block/pill/avoid/間隙 marker(jz-hws)中斷 run。 * 回傳 text.length >= 2 之 run(短於 2 無間隙可言)。 */ collectRuns(block) { const runs = []; let curText = ""; let curMap = []; const flush = () => { if (curText.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; i += 1) { curText += data.charAt(i); curMap.push({ textNode: t, offset: i }); } return; } if (node.nodeType !== 1) { return; } const el = node; if (this.isAvoided(el)) { flush(); return; } if (this.skipSet.has(el.nodeName) || this.blockSet.has(el.nodeName)) { flush(); return; } if (this.pillMatches(el)) { flush(); return; } let c2 = el.firstChild; while (c2) { visit(c2); c2 = c2.nextSibling; } }; 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) { return { textNode: tn, offset: tn.data.length - 1, ch: tn.data.charAt(tn.data.length - 1) }; } return { textNode: tn, offset: 0, ch: tn.data.charAt(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 && this.isTransparentInline(n2.nodeName)) { 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.isAvoided(e)) { return null; } if (this.isTransparentInline(e.nodeName)) { const r = descend(e); if (r) { return r; } n = direction === -1 ? e.previousSibling : e.nextSibling; continue; } return null; } n = direction === -1 ? n.previousSibling : n.nextSibling; } if (!p || p.nodeType !== 1 || !this.isTransparentInline(p.nodeName)) { 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/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); } } function applyRequests(node, requests) { 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))); } frag.appendChild(req.build(data.slice(req.start, req.end))); cursor = req.end; } if (cursor === 0) { return; } if (cursor < data.length) { frag.appendChild(ownerDoc.createTextNode(data.slice(cursor))); } parent.replaceChild(frag, node); } // src/core/dom.ts var KIND_ATTR = "data-jz-kind"; var PASS_ATTR = "data-jz"; 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(tag, pass, kind, opts = {}) { const el = doc().createElement(tag); 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.attrs) { for (const k in opts.attrs) { const v = opts.attrs[k]; if (v !== void 0) { el.setAttribute(k, v); } } } if (opts.text !== void 0) { el.textContent = opts.text; } return el; } function revertPass(pass, root) { const nodes = Array.from( root.querySelectorAll("[" + PASS_ATTR + '="' + pass + '"]') ); 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 (kind === "splitoff") { continue; } touched.add(parent); if (kind === "marker") { parent.removeChild(el); } else { while (el.firstChild) { parent.insertBefore(el.firstChild, el); } parent.removeChild(el); } } const splitoffs = Array.from( root.querySelectorAll( "[" + PASS_ATTR + '="' + pass + '"][' + KIND_ATTR + '="splitoff"]' ) ); 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); } } 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; } // 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 || 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 resolveStyle(node, config) { const lang = effectiveLang(node, config.default); return styleFor(classify(lang), config); } // 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"; var ALNUM = "A-Za-z0-9"; var ALPHA = "A-Za-z"; var ANY_CJK = new RegExp("[" + CJK + "]"); 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_ALL = BD_STOP + BD_PAUSE + BD_OPEN + BD_CLOSE + BD_MIDDLE + BD_LIGA; 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) { for (const sub of BD_ORDER) { if (biaodian[sub].indexOf(ch) >= 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/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; i += 1) { const ch = data.charAt(i); const bdClass = classifyBiaodian(ch, biaodian); if (!bdClass) { 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(ch, langClass, biasOverrides); if (bias) { classes.push("jz-ink-" + bias); } out.push({ start: i, end: i + 1, pass: PASS, build: (text) => { const outer = createJz("jz-char", PASS, "wrap", { classes }); const inner = createJz("jz-inner", PASS, "wrap", { text }); outer.appendChild(inner); return outer; } }); } return out; }, revert(ctx) { revertPass(PASS, ctx.root); } }; var jiyaAdjacencyPass = { name: "jiyaAdjacency", kind: "standalone", order: 25, level: "text", enabled: (o) => o.features.jiya, render(ctx) { const { finder, options } = ctx; const blockSet = options.finder.blockTags; let prev = null; const squeeze = (L, R) => { if (!finder.featureEnabledFor(L, PASS)) { return; } const lSides = blankSides(bdClassOf(L)); const rSides = blankSides(bdClassOf(R)); if (lSides.right) { 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 (prev) { squeeze(prev, el); } prev = el; } else if (finder.pillMatches(el)) { prev = null; } else if (finder.isAvoided(el)) { prev = null; } else if (blockSet.has(nm)) { prev = null; visit(el); prev = null; } else { visit(el); } } }; 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", "MARK", "SMALL", "INS", "DEL", "SUB", "SUP" ]); function canSplit(el) { return SPLITTABLE.has(el.nodeName) && !el.hasAttribute("id"); } function firstText(el) { 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)) { return null; } const r = firstText(c); if (r) { return r; } } } return null; } function lastText(el) { 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)) { return null; } const r = lastText(c); if (r) { return r; } } } return null; } function isTokenChar(ch, anyCjk) { return !anyCjk.test(ch) && !/\s/.test(ch); } function leadingTokenEnd(d, anyCjk) { if (anyCjk.test(d.charAt(0))) { return 1; } let e = 1; while (e < d.length && isTokenChar(d.charAt(e), anyCjk)) { e += 1; } return e; } function trailingTokenStart(d, anyCjk) { const last = d.length - 1; if (anyCjk.test(d.charAt(last))) { return last; } let s = last; while (s > 0 && isTokenChar(d.charAt(s - 1), anyCjk)) { s -= 1; } return s; } function splitTreeAfter(top, textNode, offset, pass) { if (offset <= 0 && firstText(top) === 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"); 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) { if (!canSplit(el)) { return el; } const text = side === "tail" ? lastText(el) : firstText(el); 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); if (!clone) { return el; } return side === "tail" ? clone : el; } function splitElementAt(el, textNode, offset, pass) { if (!canSplit(el)) { return null; } return splitTreeAfter(el, textNode, offset, pass); } // 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 forbiddenBreak(a, b, finder) { if (isCharEl(b)) { const c = bdClassOf(b); if (c && AVOID_HEAD.has(c) && finder.featureEnabledFor(b, PASS2)) { return true; } } if (isCharEl(a)) { const c = bdClassOf(a); if (c && AVOID_TAIL.has(c) && finder.featureEnabledFor(a, PASS2)) { return true; } } return false; } function isTokenChar2(ch, anyCjk) { return !anyCjk.test(ch) && !/\s/.test(ch); } function leadingTokenEnd2(d, anyCjk) { if (anyCjk.test(d.charAt(0))) { return 1; } let e = 1; while (e < d.length && isTokenChar2(d.charAt(e), anyCjk)) { e += 1; } return e; } function trailingTokenStart2(d, anyCjk) { const last = d.length - 1; if (anyCjk.test(d.charAt(last))) { return last; } let s = last; while (s > 0 && isTokenChar2(d.charAt(s - 1), anyCjk)) { s -= 1; } 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) { 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); } if (needFirst) { isolateBoundaryToken(el, "head", anyCjk, PASS2); } } } 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("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 anyCjk = new RegExp("[" + ctx.options.ruleset.cjk + "]"); const parents = /* @__PURE__ */ new Set(); ctx.root.querySelectorAll("jz-char").forEach((c) => { if (!ctx.finder.levelAllows(c, "paragraph")) { return; } if (c.parentNode) { parents.add(c.parentNode); } }); for (const parent of parents) { processParent(parent, ctx.finder, anyCjk); } }, revert(ctx) { revertPass(PASS2, ctx.root); } }; // src/typeset/lineedge.ts var LE = "jz-half-le"; var HANG_L = "jz-hang-l"; var HANG_R = "jz-hang-r"; var ALL_EDGE = [LE, HANG_L, HANG_R]; var EPS = 2; var HEAD_CLASSES = /* @__PURE__ */ new Set(["bd-open"]); var TAIL_SQUEEZE = /* @__PURE__ */ new Set(["bd-close"]); var TAIL_HANG = /* @__PURE__ */ new Set(["bd-close", "bd-stop", "bd-pause"]); var observers = /* @__PURE__ */ new WeakMap(); function hasLayout() { return typeof ResizeObserver !== "undefined"; } function installLayout(root, key, run) { run(); let m = observers.get(root); if (!m) { m = /* @__PURE__ */ new Map(); observers.set(root, 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, key) { const m = observers.get(root); if (!m) { return; } const ro = m.get(key); if (ro) { ro.disconnect(); m.delete(key); } } 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" ? t.data.length - 1 : 0; range.setStart(t, at); range.setEnd(t, at + 1); 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; } var HANG_ENABLED = false; function applyEdge(el, edge, mode) { if (mode === "squeeze") { el.classList.add(LE); return; } if (mode === "hang" && HANG_ENABLED) { el.classList.add(edge === "head" ? HANG_L : HANG_R); } } function clearEdge(root) { for (const cls of ALL_EDGE) { root.querySelectorAll("jz-char." + cls).forEach((e) => e.classList.remove(cls)); } } function modeForChar(el, options) { const base = options.lineEnd; if (base !== "hang") { return base; } return "squeeze"; } function relayout(root, finder, options) { clearEdge(root); const wrapSet = buildWrapSet(options); const chars = Array.from(root.querySelectorAll("jz-char")).filter( (c) => finder.inScope(c) && !finder.isAvoided(c) ); const decisions = []; for (const el of chars) { if (!finder.levelAllows(el, "paragraph")) { continue; } if (!finder.featureEnabledFor(el, "jiya")) { continue; } const mode = modeForChar(el, options); if (mode === "none") { continue; } if (mode === "hang" && el.classList.contains("jz-half")) { continue; } const bd = bdClassOf(el); if (!bd) { continue; } const tailSet = mode === "hang" ? TAIL_HANG : TAIL_SQUEEZE; const isHead = HEAD_CLASSES.has(bd); const isTail = tailSet.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, edge: "head", mode }); } } else { const next = edgeRect(el.nextSibling, "next", el, wrapSet); if (!next || lowerLine(r, next)) { decisions.push({ el, edge: "tail", mode }); } } } for (const d of decisions) { applyEdge(d.el, d.edge, d.mode); } } var lineEdgePass = { name: "jiyaLineEdge", kind: "standalone", order: 90, level: "paragraph", enabled: (o) => o.features.jiya, render(ctx) { if (!hasLayout()) { return; } installLayout(ctx.root, "lineEdge", () => relayout(ctx.root, ctx.finder, ctx.options)); }, revert(ctx) { uninstallLayout(ctx.root, "lineEdge"); clearEdge(ctx.root); } }; var TRIM = "jz-hws-trim"; function trimGaps(root, finder, wrapSet) { const gaps = Array.from(root.querySelectorAll("jz-hws")).filter( (g) => finder.inScope(g) && !finder.isAvoided(g) && finder.levelAllows(g, "paragraph") ); if (!gaps.length) { return; } gaps.forEach((g) => g.classList.add(TRIM)); 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) { g.classList.remove(TRIM); } } } 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, "gapTrim", () => trimGaps(ctx.root, ctx.finder, wrapSet)); }, revert(ctx) { uninstallLayout(ctx.root, "gapTrim"); ctx.root.querySelectorAll("jz-hws." + TRIM).forEach((e) => e.classList.remove(TRIM)); } }; // 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; return span; } }); } return out; }, revert(ctx) { revertPass(PASS3, ctx.root); } }; // 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; i += 1) { const ch = d.charAt(i); if (anyCjk.test(ch) && !classifyBiaodian(ch, biaodian)) { out.push({ directChild, textNode: t, offset: i }); } } return; } if (node.nodeType !== 1) { return; } const el = node; if (finder.isAvoided(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) { 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 ); if (clone) { startChild = clone; } } const orphan = createJz("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 + "]"); const biaodian = options.ruleset.biaodian; const chars = options.orphan.chars; finder.eachBlock(ctx.root, (block) => { if (!finder.levelAllows(block, "paragraph")) { return; } if (!finder.featureEnabledFor(block, PASS4)) { return; } const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(","); if (sel && block.querySelector(sel)) { return; } processBlock(block, finder, blockSet, anyCjk, biaodian, chars); }); }, revert(ctx) { revertPass(PASS4, ctx.root); } }; // 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; return { anyCjk: new RegExp("[" + CJK2 + "]"), pillNeighbor: new RegExp("[" + ALNUM + CJK2 + "]"), STRIP_CJK_SPACE_ANS: new RegExp("([" + CJK2 + "]) ([" + ANS_AFTER + "])", "g"), STRIP_ANS_SPACE_CJK: new RegExp("([" + ANS_BEFORE + "]) ([" + CJK2 + "])", "g"), STRIP_CJK_SPACE_CJK: new RegExp("([" + CJK2 + "]) (?=[" + CJK2 + "])", "g"), DOTS_CJK: new RegExp("([\\.]{2,})([" + CJK2 + "])", "g"), CJK_PUNCTUATION: new RegExp("([" + CJK2 + "])([!;,\\?:]+)(?=[" + CJK2 + ALNUM + "])", "g"), AN_PUNCTUATION_CJK: new RegExp("([" + ALNUM + "])([!;,\\?]+)([" + CJK2 + "])", "g"), CJK_TILDE: new RegExp("([" + CJK2 + "])(~+)(?!=)(?=[" + CJK2 + ALNUM + "])", "g"), CJK_TILDE_EQUALS: new RegExp("([" + CJK2 + "])(~=)", "g"), CJK_PERIOD: new RegExp("([" + CJK2 + "])(\\.)(?![" + ALNUM + "\\./])(?=[" + CJK2 + ALNUM + "])", "g"), AN_PERIOD_CJK: new RegExp("([" + ALNUM + "])(\\.)([" + CJK2 + "])", "g"), AN_COLON_CJK: new RegExp("([" + ALNUM + "])(:)([" + CJK2 + "])", "g"), CJK_QUOTE: new RegExp("([" + CJK2 + "])([" + QUOTES_CLS + "])", "g"), QUOTE_CJK: new RegExp("([" + QUOTES_CLS + "])([" + CJK2 + "])", "g"), CJK_QUOTE_AN: new RegExp("([" + CJK2 + '])(")([' + ALNUM + "])", "g"), CJK_HASH: new RegExp("([" + CJK2 + "])(#([^ ]))", "g"), HASH_CJK: new RegExp("(([^ ])#)([" + CJK2 + "])", "g"), SINGLE_LETTER_GRADE: new RegExp("\\b([" + ALPHA + "])([" + OPS_GRADE + "])([" + CJK2 + "])", "g"), CJK_OPERATOR_ANS: new RegExp("([" + CJK2 + "])([" + OPS_HYPHEN + "])([" + ALNUM + "])", "g"), ANS_OPERATOR_CJK: new RegExp("([" + ALNUM + "])([" + OPS_HYPHEN + "])([" + CJK2 + "])", "g"), CJK_LESS_THAN: new RegExp("([" + CJK2 + "])(<)([" + ALNUM + "])", "g"), LESS_THAN_CJK: new RegExp("([" + ALNUM + "])(<)([" + CJK2 + "])", "g"), CJK_GREATER_THAN: new RegExp("([" + CJK2 + "])(>)([" + ALNUM + "])", "g"), GREATER_THAN_CJK: new RegExp("([" + ALNUM + "])(>)([" + CJK2 + "])", "g"), CJK_LEFT_BRACKET: new RegExp("([" + CJK2 + "])([" + LBR_EXT + "])", "g"), RIGHT_BRACKET_CJK: new RegExp("([" + RBR_EXT + "])([" + CJK2 + "])", "g"), AN_LEFT_BRACKET: new RegExp("([" + ALNUM + "])(? { 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" + MARK + "$2"); 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; } function wrapNodeInGap(node) { const parent = node.parentNode; if (!parent || parent.nodeName === "JZ-HWS") { return; } const gap = createJz("jz-hws", PASS5, "wrap"); parent.insertBefore(gap, node); gap.appendChild(node); } function wrapGapLeft(pos) { let tn = pos.textNode; if (!tn.parentNode || tn.parentNode.nodeName === "JZ-HWS") { return; } if (pos.offset > 0) { tn = tn.splitText(pos.offset); } if (tn.data.length > 1) { tn.splitText(1); } wrapNodeInGap(tn); } function processBlock2(block, finder, R) { const runs = finder.collectRuns(block); for (const run of runs) { const { text, map } = run; const spaced = panguSpace(text, R); if (spaced === text) { continue; } const actions = []; let origIdx = 0; let spacedIdx = 0; while (spacedIdx < spaced.length) { const sc = spaced.charAt(spacedIdx); if (sc === DEL) { actions.push({ before: void 0, space: map[origIdx] }); origIdx += 1; spacedIdx += 1; continue; } if (sc === MARK) { const oc = origIdx < text.length ? text.charAt(origIdx) : null; if (oc === " ") { actions.push({ before: map[origIdx - 1], space: map[origIdx] }); origIdx += 1; } else { actions.push({ before: map[origIdx - 1], space: null }); } spacedIdx += 1; continue; } origIdx += 1; spacedIdx += 1; } for (let i = actions.length - 1; i >= 0; i -= 1) { const act = actions[i]; if (act.space) { const tn = act.space.textNode; tn.data = tn.data.slice(0, act.space.offset) + tn.data.slice(act.space.offset + 1); } if (act.before) { wrapGapLeft(act.before); } } } } function processPills(root, finder, pillSelector, pillNeighbor) { root.querySelectorAll(pillSelector).forEach((pill) => { if (finder.isAvoided(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); } const next = finder.adjacentLogicalChar(pill, 1); if (next) { handlePillSide(next, pill, 1, pillNeighbor); } }); } function handlePillSide(adj, pill, dir, pillNeighbor) { if (dir === -1) { if (pillNeighbor.test(adj.ch)) { wrapGapLeft({ textNode: adj.textNode, offset: adj.offset }); return; } if (adj.ch === " " && adj.offset >= 1) { const tn = adj.textNode; const bc = tn.data.charAt(adj.offset - 1); if (bc !== " " && pillNeighbor.test(bc)) { tn.data = tn.data.slice(0, adj.offset) + tn.data.slice(adj.offset + 1); wrapGapLeft({ textNode: tn, offset: adj.offset - 1 }); } } } else { if (pillNeighbor.test(adj.ch)) { wrapNodeInGap(pill); return; } if (adj.ch === " " && adj.textNode.data.length >= 2) { const ac = adj.textNode.data.charAt(1); if (ac !== " " && pillNeighbor.test(ac)) { adj.textNode.data = adj.textNode.data.slice(1); wrapNodeInGap(pill); } } } } 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, finder, R); }); if (!options.finder.blockTags.has(ctx.root.nodeName) && finder.featureEnabledFor(ctx.root, PASS5) && finder.inScope(ctx.root) && !finder.isAvoided(ctx.root)) { processBlock2(ctx.root, finder, R); } processPills(ctx.root, finder, options.finder.pillSelector, R.pillNeighbor); }, revert(ctx) { revertPass(PASS5, ctx.root); } }; // src/compat/v1.ts function mapLongWord(v) { if (v === false) { return false; } if (v === void 0 || v === true || v === null) { return true; } const out = {}; if (typeof v.minLength === "number") { out.minLength = v.minLength; } if (typeof v.lang === "string") { out.lang = v.lang; } return out; } function createCjkAutospace(options = {}) { const o = options || {}; const mapped = { // finder 設定直通。 ...o.styleInlines ? { styleInlines: o.styleInlines } : {}, ...o.blockTags ? { blockTags: o.blockTags } : {}, ...o.skipTags ? { skipTags: o.skipTags } : {}, pillSelector: o.pillSelector || "code, kbd", autospaceClass: o.autospaceClass || "cjk-autospace", skipAttribute: o.skipAttribute || "data-md-key", ...o.isSkipped ? { isSkipped: o.isSkipped } : {}, spacing: true, longWord: mapLongWord(o.longWordWrap), // v1 Pass D 不分 lang 地擠句內點號(開明式);compat 強制 kaiming // 政策以避免功能倒退(預設 zh-Hant→quanjiao 不擠壓)。 jiya: o.punctuationSqueeze === false ? false : true, // v1 無連續標點綁定,保持 DOM 與 v1 一致:關閉 jinze。 jinze: false, lang: { policy: { "zh-Hant": "kaiming", "zh-Hans": "kaiming", "ja": "kaiming", "other": "kaiming" } } }; const jz = createJuzhen(mapped); return { apply(root) { jz.render(root); }, revert(root) { jz.revert(root); } }; } // 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 = [ "CODE", "KBD", "PRE", "SAMP", "TT", "VAR", "SCRIPT", "STYLE", "TEXTAREA", "INPUT" ]; var PASSES = [ jiyaPass, jiyaAdjacencyPass, longWordPass, spacingPass, jinzePass, orphanPass, lineEdgePass, gapTrimPass ]; 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 || {} }, ruleset: resolveRuleset(opts.charClass), autospaceClass: opts.autospaceClass || "", 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), longWord: opts.longWord === false ? false : feature(opts.longWord, true), jiya: feature(opts.jiya, true), jinze: feature(opts.jinze, true), orphan: opts.orphan === false ? false : feature(opts.orphan, true), hanging: feature(opts.hanging, false), biaodian: feature(opts.biaodian, false), emphasis: feature(opts.emphasis, false), ruby: feature(opts.ruby, false) }, longWord: resolveLongWord(opts.longWord), orphan: resolveOrphan(opts.orphan), // 行端模式恒為 squeeze(行端半形)。**標點懸掛 hang 已全面停用**(§6.5.2/§6.5.6): // 經 headless + 真實瀏覽器實測,純負 margin 機制不可行——句號墨色偏框左下角,安全 // 凸出量(<1em)只把空白半框推出版心、墨色仍在內(視覺不懸掛);凸出滿 1em 又會釋放 // 一字寬、把下一字拉上本行致重疊;且 ResizeObserver 重排時兩階段標記錯亂。原生 // hanging-punctuation 為正解但 Chrome 不支援、且與本庫 inline-block 原子衝突。 // 故此處**唯一關卡**鎖死為 squeeze——即使 opts.hanging 為 true 亦不啟用 hang(無逃逸); // hang 之 CSS/lineedge 程式碼保留(休眠),俟原生支援成熟再議。 lineEnd: "squeeze", 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", 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; } function createJuzhen(opts = {}) { const options = normalizeOptions(opts); function makeCtx(root) { return { root, options, finder: new Finder(options) }; } return { options, render(root) { const el = resolveRoot(root, options); if (!el) { return; } runPasses(PASSES, makeCtx(el)); if (options.features.jiya && options.jiyaConfig.halfWidth === "margin") { el.setAttribute("data-jz-halfwidth", "margin"); } if (!options.justifyAtoms) { el.setAttribute("data-jz-atoms", "inline"); } }, revert(root) { const el = resolveRoot(root, options); if (!el) { return; } revertPasses(PASSES, makeCtx(el)); el.removeAttribute("data-jz-halfwidth"); el.removeAttribute("data-jz-atoms"); } }; } export { createCjkAutospace, createJuzhen, normalizeOptions };