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:
+61
-12
@@ -199,6 +199,49 @@ test("jiya 機制 A:連續開類,後者半形(「「→ 第二個 jz-half
|
||||
assert.ok(opens[1].classList.contains("jz-half"), "第二個開類內側 → 半形");
|
||||
});
|
||||
|
||||
test("jiya 機制 A:,「→ 兩側皆收半形(雙側獨立判斷,合占 1em)", () =>
|
||||
{
|
||||
// ,右側有空白且右鄰標點「→ 收,;「左側有空白且左鄰標點,→ 收「。兩者皆半 = 1em。
|
||||
const { document } = setupDom('<p lang="zh-Hant">甲,「乙</p>');
|
||||
createJuzhen({ spacing: false, longWord: false, jinze: false }).render(document.body);
|
||||
const comma = document.querySelector("jz-char.bd-pause"); // ,
|
||||
const open = document.querySelector("jz-char.bd-open"); // 「
|
||||
assert.ok(comma.classList.contains("jz-half"), ",右空白鄰標點 → 半形");
|
||||
assert.ok(open.classList.contains("jz-half"), "「左空白鄰標點 → 半形(雙側收,非二選一)");
|
||||
});
|
||||
|
||||
test("jiya 機制 A:。」→ 僅 。半形(」左為墨、無空白可挤,全角式合占 1.5em)", () =>
|
||||
{
|
||||
const { document } = setupDom('<p lang="zh-Hant">甲。」乙</p>');
|
||||
createJuzhen({ spacing: false, longWord: false, jinze: false }).render(document.body);
|
||||
const stop = document.querySelector("jz-char.bd-stop"); // 。
|
||||
const close = document.querySelector("jz-char.bd-close"); // 」
|
||||
assert.ok(stop.classList.contains("jz-half"), "。右空白鄰標點 → 半形");
|
||||
assert.ok(!close.classList.contains("jz-half"), "」左為墨、無空白 → 不收(全角式 1.5em)");
|
||||
});
|
||||
|
||||
test("jiya 機制 A:跨透明 inline 邊界之連續標點亦正確擠(,「 分置 strong/em,雙側皆收)", () =>
|
||||
{
|
||||
// 下游回饋:原「同 parent 直接兄弟」掃描漏掉跨 strong/em 邊界之連續標點。
|
||||
const { document } = setupDom('<p lang="zh-Hant">甲<strong>,</strong><em>「</em>乙</p>');
|
||||
createJuzhen({ spacing: false, longWord: false, jinze: false }).render(document.body);
|
||||
assert.ok(document.querySelector("jz-char.bd-pause").classList.contains("jz-half"), "跨邊界 ,半形");
|
||||
assert.ok(document.querySelector("jz-char.bd-open").classList.contains("jz-half"), "跨邊界 「半形(透明穿越)");
|
||||
});
|
||||
|
||||
test("jiya 機制 A:多標點鏈 ,」。逐對判斷(,半/」半/。全)", () =>
|
||||
{
|
||||
const { document } = setupDom('<p lang="zh-Hant">甲,」。乙</p>');
|
||||
createJuzhen({ spacing: false, longWord: false, jinze: false }).render(document.body);
|
||||
const p = document.querySelector("p");
|
||||
const comma = p.querySelector("jz-char.bd-pause"); // ,右空白鄰」→ 半
|
||||
const close = p.querySelector("jz-char.bd-close"); // 」右空白鄰。→ 半
|
||||
const stop = p.querySelector("jz-char.bd-stop"); // 。左為墨、右鄰漢字 → 全
|
||||
assert.ok(comma.classList.contains("jz-half"), ",→ 半");
|
||||
assert.ok(close.classList.contains("jz-half"), "」右空白鄰標點。→ 半");
|
||||
assert.ok(!stop.classList.contains("jz-half"), "。左為墨、右側無標點鄰 → 全");
|
||||
});
|
||||
|
||||
test("jiya:revert 還原標點(無 jz-char,textContent 不變)", () =>
|
||||
{
|
||||
const { document } = setupDom('<p lang="zh-Hans">好,走。</p>');
|
||||
@@ -511,21 +554,27 @@ test("CSS:聚珍不內建 font-family(字型交消費端),半形顯式
|
||||
|
||||
// ----- charClass:自定義字符分類規則集(向下兼容+擴充)-----
|
||||
|
||||
test("charClass:預設不傳時,英文全角句號 . 未分類(不 charify、不挤、不補隙)", () =>
|
||||
test("jiya:英文全角句號 . 已內建為句末標點 bd-stop(本次納入;Noto CJK 有 halt)", () =>
|
||||
{
|
||||
const { document } = setupDom('<p lang="zh-Hans">中.文</p>');
|
||||
// . U+FF0E(異於表意句號 。U+3002)已納入內置 BD_STOP,預設即 charify 為 bd-stop。
|
||||
const { document } = setupDom('<p lang="zh-Hans">中.文。</p>');
|
||||
createJuzhen({ lang: { style: "kaiming" } }).render(document.body);
|
||||
const p = document.querySelector("p");
|
||||
assert.equal(countTag(p, "jz-char"), 0, ". 預設未分類為標點,不 charify");
|
||||
assert.equal(countTag(p, "jz-hws"), 0, ". 預設非西文,無中西間隙");
|
||||
const dot = Array.from(p.querySelectorAll("jz-char")).find((c) => c.textContent === ".");
|
||||
assert.ok(dot && dot.classList.contains("bd-stop"), ". 預設 charify 為 bd-stop");
|
||||
// 開明式句末不挤(與 。一致);banjiao 才挤——此處僅確認分類與政策一致。
|
||||
assert.equal(dot.classList.contains("jz-half"), false, "開明式句末標點不挤半(保持全角,同 。)");
|
||||
});
|
||||
|
||||
test("charClass.biaodian.stop:把 . 歸為全寬句末標點 → 被 jiya charify 為 bd-stop", () =>
|
||||
test("charClass.biaodian.stop:把仍未分類之 ‥ 歸為全寬句末標點 → charify 為 bd-stop", () =>
|
||||
{
|
||||
const { document } = setupDom('<p lang="zh-Hans">中.文.</p>');
|
||||
createJuzhen({ lang: { style: "kaiming" }, charClass: { biaodian: { stop: "." } } }).render(document.body);
|
||||
const p = document.querySelector("p");
|
||||
assert.ok(countTag(p, "jz-char.bd-stop") >= 1, ". 應 charify 為 bd-stop");
|
||||
// ‥ U+2025 預設未分類;以 charClass 歸句末標點以示「追加」機制。
|
||||
const base = setupDom('<p lang="zh-Hans">中‥文</p>');
|
||||
createJuzhen({ lang: { style: "kaiming" } }).render(base.document.body);
|
||||
assert.equal(countTag(base.document.querySelector("p"), "jz-char"), 0, "‥ 預設未分類、不 charify");
|
||||
const { document } = setupDom('<p lang="zh-Hans">中‥文</p>');
|
||||
createJuzhen({ lang: { style: "kaiming" }, charClass: { biaodian: { stop: "‥" } } }).render(document.body);
|
||||
assert.ok(countTag(document.querySelector("p"), "jz-char.bd-stop") >= 1, "‥ 經 charClass 歸 bd-stop");
|
||||
});
|
||||
|
||||
test("charClass.western:把未分類符號 ★ 歸為西文 → 與 CJK 邊界補 jz-hws", () =>
|
||||
@@ -586,10 +635,10 @@ test("charClass.western:追加不破壞內置——€(內置)與 ★(
|
||||
|
||||
test("charClass.biaodian:多子類同傳,各自生效(stop/pause)", () =>
|
||||
{
|
||||
const { document } = setupDom('<p lang="zh-Hans">甲.乙•丙</p>');
|
||||
createJuzhen({ lang: { style: "kaiming" }, charClass: { biaodian: { stop: ".", pause: "•" } } }).render(document.body);
|
||||
const { document } = setupDom('<p lang="zh-Hans">甲‥乙•丙</p>');
|
||||
createJuzhen({ lang: { style: "kaiming" }, charClass: { biaodian: { stop: "‥", pause: "•" } } }).render(document.body);
|
||||
const p = document.querySelector("p");
|
||||
assert.ok(countTag(p, "jz-char.bd-stop") >= 1, ". → bd-stop");
|
||||
assert.ok(countTag(p, "jz-char.bd-stop") >= 1, "‥ → bd-stop");
|
||||
const pause = p.querySelector("jz-char.bd-pause");
|
||||
assert.ok(pause && pause.classList.contains("jz-half"), "• → bd-pause 且開明式挤半形");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user