feat: 全角句點 . 納入句末挤压 + 連續標點挤压規則(雙側獨立+透明穿越)
本會話標點挤压一致性兩項(改內置默認;皆以 Noto CJK + 真實 Chromium 量測驗證)。 一、全角句點 .(U+FF0E)納入句末標點 - BD_STOP 由 。!? 擴為 。.!?——納入全角句點 .(異於表意句號 。U+3002)。實測 Noto CJK SC/TC 之 halt(GPOS SinglePos)皆覆蓋 .、挤压收半形 advance 0.5em,與 。 一致。inkBias 同步補 .(簡 left/繁 center),margin 後備亦可挤。 - 政策行為與 。!? 同:全角式與開明式句末保持全角 1em,banjiao(或鄰接被收)挤半。 - 設計原則:halt 即字型之「挤压意願」——字型不提供某標點 halt(字形占滿、不宜挤,如某些 字型之全角 ?、〈〉)則信任字型、保持全角不強挤;margin 後備僅用於整體無 halt 之字型。 二、連續標點挤压規則:雙側獨立 + 透明穿越(修正既有不一致) - 規則(用戶確定):相鄰兩標點,**對每一標點**獨立判斷——若其「有空白之側」緊鄰另一標點, 即挤掉該側空白(收半形)。原 jiyaAdjacencyPass 為「二選一只收一側」,致: · ,「(pause 右空白 + open 左空白)原只收 ,→ 全角式 1.5em;應雙側皆收 → 1em。 改 else-if 為雙獨立 if。修後:,「 全角式與開明式皆 1em;。」 全角式 1.5em/開明式 1em (」左為墨不收);·— 兩式皆 2em(不宜挤之例外)。 - 透明穿越:原以「同 parent 直接兄弟」掃描,漏掉跨 strong/em 等行內邊界之連續標點(致 跨邊界標點對未挤、達 2em)。改為從 root 之透明遍歷(穿越樣式行內;block 邊界與 avoid 子樹斷相鄰;jz-char 為原子不下探),跨 inline 邊界之連續標點亦正確配對。 - src/core/unicode.ts:BD_STOP +.;inkBias 表 +.。 - src/typeset/jiya.ts:jiyaAdjacencyPass 重寫(雙側收 + root 透明遍歷)。 - test:. 納入+連續挤压(雙側/句末單側/跨 inline/多標點鏈)回歸,70 測試通過。 - README/ARCHITECTURE:. 與連續挤压規則、halt 挤压意願原則;**涉長度處標明全角式/ 開明式**(。」 全角 1.5em/開明 1em 等)。dist 重建(僅 JS,juzhen.css 不變)。
This commit is contained in:
Vendored
+45
-24
@@ -515,7 +515,7 @@ var Juzhen = (() => {
|
||||
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\uFF01\uFF1F";
|
||||
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";
|
||||
@@ -603,6 +603,7 @@ var Juzhen = (() => {
|
||||
"\uFF0C": "left",
|
||||
"\u3001": "left",
|
||||
"\u3002": "left",
|
||||
"\uFF0E": "left",
|
||||
"\uFF1B": "left",
|
||||
"\uFF1A": "left",
|
||||
"\uFF01": "left",
|
||||
@@ -612,6 +613,7 @@ var Juzhen = (() => {
|
||||
"\uFF0C": "center",
|
||||
"\u3001": "center",
|
||||
"\u3002": "center",
|
||||
"\uFF0E": "center",
|
||||
"\uFF1B": "center",
|
||||
"\uFF1A": "center",
|
||||
"\uFF01": "center",
|
||||
@@ -718,33 +720,52 @@ var Juzhen = (() => {
|
||||
order: 25,
|
||||
enabled: (o) => o.features.jiya,
|
||||
render(ctx) {
|
||||
const { finder } = ctx;
|
||||
const parents = /* @__PURE__ */ new Set();
|
||||
ctx.root.querySelectorAll("jz-char").forEach((c) => {
|
||||
if (c.parentNode) {
|
||||
parents.add(c.parentNode);
|
||||
const { finder, options } = ctx;
|
||||
const blockSet = options.finder.blockTags;
|
||||
let prev = null;
|
||||
const squeeze = (L, R) => {
|
||||
if (!finder.featureEnabledFor(L, PASS)) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
for (const parent of parents) {
|
||||
let child = parent.firstChild;
|
||||
while (child) {
|
||||
const next = child.nextSibling;
|
||||
if (child.nodeType === 1 && child.nodeName === "JZ-CHAR" && next && next.nodeType === 1 && next.nodeName === "JZ-CHAR") {
|
||||
const L = child;
|
||||
const R = next;
|
||||
if (finder.featureEnabledFor(L, PASS)) {
|
||||
const lSides = blankSides(bdClassOf(L));
|
||||
const rSides = blankSides(bdClassOf(R));
|
||||
if (lSides.right) {
|
||||
L.classList.add("jz-half");
|
||||
} else if (rSides.left) {
|
||||
R.classList.add("jz-half");
|
||||
}
|
||||
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.isAvoided(el)) {
|
||||
prev = null;
|
||||
} else if (blockSet.has(nm)) {
|
||||
prev = null;
|
||||
visit(el);
|
||||
prev = null;
|
||||
} else {
|
||||
visit(el);
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(ctx.root);
|
||||
},
|
||||
revert() {
|
||||
}
|
||||
|
||||
Vendored
+45
-24
@@ -487,7 +487,7 @@ 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\uFF01\uFF1F";
|
||||
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";
|
||||
@@ -575,6 +575,7 @@ var BIAS_HANS = {
|
||||
"\uFF0C": "left",
|
||||
"\u3001": "left",
|
||||
"\u3002": "left",
|
||||
"\uFF0E": "left",
|
||||
"\uFF1B": "left",
|
||||
"\uFF1A": "left",
|
||||
"\uFF01": "left",
|
||||
@@ -584,6 +585,7 @@ var BIAS_HANT = {
|
||||
"\uFF0C": "center",
|
||||
"\u3001": "center",
|
||||
"\u3002": "center",
|
||||
"\uFF0E": "center",
|
||||
"\uFF1B": "center",
|
||||
"\uFF1A": "center",
|
||||
"\uFF01": "center",
|
||||
@@ -690,33 +692,52 @@ var jiyaAdjacencyPass = {
|
||||
order: 25,
|
||||
enabled: (o) => o.features.jiya,
|
||||
render(ctx) {
|
||||
const { finder } = ctx;
|
||||
const parents = /* @__PURE__ */ new Set();
|
||||
ctx.root.querySelectorAll("jz-char").forEach((c) => {
|
||||
if (c.parentNode) {
|
||||
parents.add(c.parentNode);
|
||||
const { finder, options } = ctx;
|
||||
const blockSet = options.finder.blockTags;
|
||||
let prev = null;
|
||||
const squeeze = (L, R) => {
|
||||
if (!finder.featureEnabledFor(L, PASS)) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
for (const parent of parents) {
|
||||
let child = parent.firstChild;
|
||||
while (child) {
|
||||
const next = child.nextSibling;
|
||||
if (child.nodeType === 1 && child.nodeName === "JZ-CHAR" && next && next.nodeType === 1 && next.nodeName === "JZ-CHAR") {
|
||||
const L = child;
|
||||
const R = next;
|
||||
if (finder.featureEnabledFor(L, PASS)) {
|
||||
const lSides = blankSides(bdClassOf(L));
|
||||
const rSides = blankSides(bdClassOf(R));
|
||||
if (lSides.right) {
|
||||
L.classList.add("jz-half");
|
||||
} else if (rSides.left) {
|
||||
R.classList.add("jz-half");
|
||||
}
|
||||
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.isAvoided(el)) {
|
||||
prev = null;
|
||||
} else if (blockSet.has(nm)) {
|
||||
prev = null;
|
||||
visit(el);
|
||||
prev = null;
|
||||
} else {
|
||||
visit(el);
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(ctx.root);
|
||||
},
|
||||
revert() {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user