fix: 修复行缘、切分还原与语言解析并同步文档
保留逐字 margin/jiya.bias 字体后备方案;补齐 demo 重排、量测边界说明及共享规则 1.0.1 反例向量。 验证:完整构建通过,Node 376 项、WKWebView 64 项及原生 demo 9 项通过;Firefox 仍暂缓。
This commit is contained in:
Vendored
+84
-16
@@ -301,8 +301,8 @@ var Finder = class {
|
||||
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
|
||||
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
|
||||
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
|
||||
* 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1,
|
||||
* 另議)。
|
||||
* 隨實例持久化,跨 render/revert 復用;屬性或位置變化由 invalidateSubtree 清除受影響
|
||||
* 子樹,不能沿用舊祖先鏈結果。
|
||||
*/
|
||||
category(el) {
|
||||
const cached = this.categoryCache.get(el);
|
||||
@@ -810,6 +810,45 @@ var SPLITOFF_TAGS = /* @__PURE__ */ new Set([
|
||||
var ATTRIBUTE_WRITERS = /* @__PURE__ */ new WeakMap();
|
||||
var OWNER_ATTRIBUTES = /* @__PURE__ */ new WeakMap();
|
||||
var UNWRAPPED_PARENTS = /* @__PURE__ */ new WeakMap();
|
||||
var SPLITOFF_SOURCES = /* @__PURE__ */ new WeakMap();
|
||||
function registerSplitoffSource(clone, source, pass, ownedElements) {
|
||||
if (!ownedElements.has(clone) || !SPLITOFF_TAGS.has(clone.nodeName) || clone.nodeName !== source.nodeName) {
|
||||
return;
|
||||
}
|
||||
const earlier = SPLITOFF_SOURCES.get(source);
|
||||
const inherited = earlier && source.getAttribute(PASS_ATTR) === earlier.pass && source.getAttribute(KIND_ATTR) === "splitoff" ? earlier : null;
|
||||
SPLITOFF_SOURCES.set(clone, {
|
||||
source,
|
||||
pass,
|
||||
originalPass: inherited ? inherited.originalPass : clone.getAttribute(PASS_ATTR),
|
||||
originalKind: inherited ? inherited.originalKind : clone.getAttribute(KIND_ATTR)
|
||||
});
|
||||
}
|
||||
function sameSplitAttributes(a, b) {
|
||||
const attrs = (el) => el.getAttributeNames().filter((name) => name !== PASS_ATTR && name !== KIND_ATTR);
|
||||
const names = attrs(a);
|
||||
return names.length === attrs(b).length && names.every((name) => a.getAttribute(name) === b.getAttribute(name));
|
||||
}
|
||||
function splitOrigin(node) {
|
||||
let current = resolveUnwrappedParent(node);
|
||||
const visited = /* @__PURE__ */ new Set();
|
||||
while (current) {
|
||||
if (visited.has(current)) {
|
||||
return null;
|
||||
}
|
||||
visited.add(current);
|
||||
if (current.nodeType !== 1) {
|
||||
return current;
|
||||
}
|
||||
const el = current;
|
||||
const record = SPLITOFF_SOURCES.get(el);
|
||||
if (!record || el.getAttribute(PASS_ATTR) !== record.pass || el.getAttribute(KIND_ATTR) !== "splitoff") {
|
||||
return current;
|
||||
}
|
||||
current = resolveUnwrappedParent(record.source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function recordUnwrappedParent(el, parent, ownedElements) {
|
||||
const pass = el.getAttribute(PASS_ATTR);
|
||||
if (ownedElements.has(el) && pass && knownProduct(pass, el.getAttribute(KIND_ATTR), el) && !el.parentNode && !el.hasChildNodes()) {
|
||||
@@ -995,20 +1034,32 @@ function revertPass(pass, root, ownedElements) {
|
||||
continue;
|
||||
}
|
||||
touched.add(parent);
|
||||
const record = SPLITOFF_SOURCES.get(so);
|
||||
if (!record || record.pass !== pass) {
|
||||
continue;
|
||||
}
|
||||
const source = resolveUnwrappedParent(record.source);
|
||||
const prev = so.previousSibling;
|
||||
if (prev && prev.nodeType === 1 && prev.nodeName === so.nodeName) {
|
||||
const origin = source ? splitOrigin(source) : null;
|
||||
if (origin && prev?.nodeType === 1 && splitOrigin(prev) === origin && prev.nodeName === so.nodeName && sameSplitAttributes(so, prev)) {
|
||||
while (so.firstChild) {
|
||||
prev.appendChild(so.firstChild);
|
||||
}
|
||||
parent.removeChild(so);
|
||||
recordUnwrappedParent(so, prev, ownedElements);
|
||||
} else {
|
||||
while (so.firstChild) {
|
||||
parent.insertBefore(so.firstChild, so);
|
||||
for (const [name, value] of [
|
||||
[PASS_ATTR, record.originalPass],
|
||||
[KIND_ATTR, record.originalKind]
|
||||
]) {
|
||||
if (value === null) {
|
||||
so.removeAttribute(name);
|
||||
} else {
|
||||
so.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
parent.removeChild(so);
|
||||
recordUnwrappedParent(so, parent, ownedElements);
|
||||
}
|
||||
SPLITOFF_SOURCES.delete(so);
|
||||
ownedElements.delete(so);
|
||||
}
|
||||
for (const p of touched) {
|
||||
@@ -1328,11 +1379,11 @@ var DEFAULT_POLICY = {
|
||||
"other": "quanjiao"
|
||||
};
|
||||
function effectiveLang(node, fallback) {
|
||||
let p = node.parentNode;
|
||||
let p = node.nodeType === 1 ? node : node.parentNode;
|
||||
while (p && p.nodeType === 1) {
|
||||
const el = p;
|
||||
const v = el.getAttribute("lang");
|
||||
if (v) {
|
||||
if (v !== null) {
|
||||
return v.toLowerCase();
|
||||
}
|
||||
p = p.parentNode;
|
||||
@@ -1345,13 +1396,19 @@ function classify(lang) {
|
||||
return "ja";
|
||||
}
|
||||
if (l === "zh" || l.indexOf("zh-") === 0) {
|
||||
if (/(?:^|-)hant(?:-|$)/.test(l)) {
|
||||
const subtags = l.split("-");
|
||||
let at = 1;
|
||||
for (let count = 0; count < 3 && /^[a-z]{3}$/.test(subtags[at] || ""); count++) {
|
||||
at++;
|
||||
}
|
||||
const script = /^[a-z]{4}$/.test(subtags[at] || "") ? subtags[at++] : void 0;
|
||||
if (script === "hant") {
|
||||
return "zh-Hant";
|
||||
}
|
||||
if (/(?:^|-)hans(?:-|$)/.test(l)) {
|
||||
if (script === "hans") {
|
||||
return "zh-Hans";
|
||||
}
|
||||
if (/-(tw|hk|mo)\b/.test(l)) {
|
||||
if (["tw", "hk", "mo"].includes(subtags[at] || "")) {
|
||||
return "zh-Hant";
|
||||
}
|
||||
if (l === "zh") {
|
||||
@@ -1726,9 +1783,10 @@ function splitTreeAfter(top, textNode, offset, pass, finder, ownedElements) {
|
||||
if (rightAtLevel) {
|
||||
clone = anc.cloneNode(false);
|
||||
clone.removeAttribute("id");
|
||||
ownedElements.add(clone);
|
||||
registerSplitoffSource(clone, anc, pass, ownedElements);
|
||||
clone.setAttribute(PASS_ATTR2, pass);
|
||||
clone.setAttribute(KIND_ATTR2, "splitoff");
|
||||
ownedElements.add(clone);
|
||||
let n = rightAtLevel;
|
||||
while (n) {
|
||||
const next = n.nextSibling;
|
||||
@@ -2213,7 +2271,7 @@ function uninstallLayout(root, owner, key) {
|
||||
owners.delete(owner);
|
||||
}
|
||||
}
|
||||
var JZ_WRAP_TAGS = ["JZ-JINZE", "JZ-CHAR", "JZ-INNER", "JZ-HWS"];
|
||||
var JZ_WRAP_TAGS = ["JZ-JINZE", "JZ-CHAR", "JZ-INNER", "JZ-HWS", "JZ-ORPHAN"];
|
||||
function buildWrapSet(options) {
|
||||
const s = new Set(JZ_WRAP_TAGS);
|
||||
for (const name of options.finder.styleInlines) {
|
||||
@@ -2221,6 +2279,13 @@ function buildWrapSet(options) {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
function inlineFlow(el, wrapSet) {
|
||||
const display = el.ownerDocument.defaultView?.getComputedStyle(el).display;
|
||||
if (display) {
|
||||
return display === "contents" || display.startsWith("inline") || display.startsWith("ruby");
|
||||
}
|
||||
return wrapSet.has(el.nodeName);
|
||||
}
|
||||
function rectOf(n, side) {
|
||||
if (n.nodeType === 3 && n.data.length > 0) {
|
||||
const t = n;
|
||||
@@ -2250,6 +2315,9 @@ function edgeRect(start, side, from, wrapSet) {
|
||||
let parentRef = from.parentNode;
|
||||
for (; ; ) {
|
||||
while (n) {
|
||||
if (n.nodeType === 1 && n.nodeName === "BR" && n.ownerDocument.defaultView?.getComputedStyle(n).display !== "none") {
|
||||
return null;
|
||||
}
|
||||
const r = rectOf(n, side);
|
||||
if (r) {
|
||||
return r;
|
||||
@@ -2259,7 +2327,7 @@ function edgeRect(start, side, from, wrapSet) {
|
||||
if (!parentRef || parentRef.nodeType !== 1) {
|
||||
return null;
|
||||
}
|
||||
if (!wrapSet.has(parentRef.nodeName)) {
|
||||
if (!inlineFlow(parentRef, wrapSet)) {
|
||||
return null;
|
||||
}
|
||||
n = side === "prev" ? parentRef.previousSibling : parentRef.nextSibling;
|
||||
@@ -3375,7 +3443,7 @@ function normalizeOptions(opts = {}) {
|
||||
const scope = opts.scope || {};
|
||||
return {
|
||||
locale: {
|
||||
default: langOpt.default || "zh-Hant",
|
||||
default: langOpt.default === void 0 ? "zh-Hant" : langOpt.default,
|
||||
...langOpt.style ? { style: langOpt.style } : {},
|
||||
policy: langOpt.policy || {},
|
||||
stylePolicy: Array.isArray(langOpt.stylePolicy) ? langOpt.stylePolicy.slice() : []
|
||||
|
||||
Reference in New Issue
Block a user