refactor: 移除標點懸掛(機制級死路)——負 margin 程式碼/CSS/型別一併刪
標點懸掛經實測為**機制級死路**(純 CSS 之寬度守恆矛盾):行端標點盒保持非零寬則墨色
出去多少版心內就空多少(行末實義字無法齊邊);收為零寬則釋放一整字寬、斷行器把下一字
拉上本行與標點重疊(且 justifyAtoms 預設之 inline-block 下負 margin 被 justify 吸收)。
CSS hack 接觸不到斷行器,此恒等式無解。唯一正解為原生 hanging-punctuation(僅 Safari
支援、且 jz-char 為 inline-block 原子非文本,原生懸掛不作用),屬遠期「原生管線」新功能、
與此處無共享,故不保留休眠碼。
移除:
lineedge.ts — jz-hang-* class、HANG_* 常量、applyEdge hang 分支、modeForChar、
HANG_ENABLED 總開關;行端僅餘 squeeze(半形)。
_jiya.css — jz-hang-* 三條規則(margin 半形 fallback 完整保留)。
types.ts — LineEndMode 型別、ResolvedOptions.lineEnd、features.hanging。
index.ts — lineEnd 鎖。
hanging 公開選項保留為 @deprecated no-op(API 相容)。
文檔:README「標點懸掛」節、ARCHITECTURE §6.5(開頭/§6.5.2/§6.5.4/§6.5.5/§6.5.6)、
demo(showcase §三/resize-check)全部由「停用/休眠」改為「已移除」+寬度守恆論證。
測試:防逃逸測試改寫為「hanging 為 no-op、輸出與不傳一致、各路徑不產生 jz-hang-*」。
全綠 101。dist 已無 jz-hang/lineEnd/HANG 殘留。
This commit is contained in:
Vendored
-10
@@ -85,16 +85,6 @@ jz-char.jz-half-le jz-inner {
|
||||
[data-jz-halfwidth=halt] jz-char.jz-half-le {
|
||||
width: auto;
|
||||
}
|
||||
jz-char.bd-stop.jz-hang-r,
|
||||
jz-char.bd-pause.jz-hang-r {
|
||||
margin-right: calc(-1 * var(--jz-hang-stop, 0.5em));
|
||||
}
|
||||
jz-char.bd-close.jz-hang-r {
|
||||
margin-right: calc(-1 * var(--jz-hang-bracket, 0.5em));
|
||||
}
|
||||
jz-char.bd-open.jz-hang-l {
|
||||
margin-left: calc(-1 * var(--jz-hang-bracket, 0.5em));
|
||||
}
|
||||
|
||||
/* src/css/_jinze.css */
|
||||
jz-jinze {
|
||||
|
||||
Vendored
+9
-43
@@ -1160,13 +1160,10 @@ var Juzhen = (() => {
|
||||
|
||||
// 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 ALL_EDGE = [LE];
|
||||
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 TAIL_CLASSES = /* @__PURE__ */ new Set(["bd-close"]);
|
||||
var observers = /* @__PURE__ */ new WeakMap();
|
||||
function hasLayout() {
|
||||
return typeof ResizeObserver !== "undefined";
|
||||
@@ -1267,28 +1264,11 @@ var Juzhen = (() => {
|
||||
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);
|
||||
@@ -1303,20 +1283,12 @@ var Juzhen = (() => {
|
||||
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);
|
||||
const isTail = TAIL_CLASSES.has(bd);
|
||||
if (!isHead && !isTail) {
|
||||
continue;
|
||||
}
|
||||
@@ -1327,17 +1299,17 @@ var Juzhen = (() => {
|
||||
if (isHead) {
|
||||
const prev = edgeRect(el.previousSibling, "prev", el, wrapSet);
|
||||
if (!prev || higherLine(r, prev)) {
|
||||
decisions.push({ el, edge: "head", mode });
|
||||
decisions.push({ el });
|
||||
}
|
||||
} else {
|
||||
const next = edgeRect(el.nextSibling, "next", el, wrapSet);
|
||||
if (!next || lowerLine(r, next)) {
|
||||
decisions.push({ el, edge: "tail", mode });
|
||||
decisions.push({ el });
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const d of decisions) {
|
||||
applyEdge(d.el, d.edge, d.mode);
|
||||
d.el.classList.add(LE);
|
||||
}
|
||||
}
|
||||
var lineEdgePass = {
|
||||
@@ -2002,21 +1974,15 @@ var Juzhen = (() => {
|
||||
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",
|
||||
// 行端僅做半形擠壓(lineedge:行首開類收左、行尾閉類收右)。**標點懸掛已移除**
|
||||
// (§6.5.6):純負 margin 機制為機制級死路(寬度守恆矛盾,CSS 接觸不到斷行器);
|
||||
// opts.hanging 仍被接受以保 API 相容,但無任何效果。
|
||||
jiyaConfig: resolveJiya(opts.jiya),
|
||||
blocks: Array.isArray(opts.blocks) ? opts.blocks.slice() : [],
|
||||
finder: {
|
||||
|
||||
Vendored
+9
-43
@@ -1132,13 +1132,10 @@ var jinzePass = {
|
||||
|
||||
// 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 ALL_EDGE = [LE];
|
||||
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 TAIL_CLASSES = /* @__PURE__ */ new Set(["bd-close"]);
|
||||
var observers = /* @__PURE__ */ new WeakMap();
|
||||
function hasLayout() {
|
||||
return typeof ResizeObserver !== "undefined";
|
||||
@@ -1239,28 +1236,11 @@ 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);
|
||||
@@ -1275,20 +1255,12 @@ function relayout(root, finder, options) {
|
||||
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);
|
||||
const isTail = TAIL_CLASSES.has(bd);
|
||||
if (!isHead && !isTail) {
|
||||
continue;
|
||||
}
|
||||
@@ -1299,17 +1271,17 @@ function relayout(root, finder, options) {
|
||||
if (isHead) {
|
||||
const prev = edgeRect(el.previousSibling, "prev", el, wrapSet);
|
||||
if (!prev || higherLine(r, prev)) {
|
||||
decisions.push({ el, edge: "head", mode });
|
||||
decisions.push({ el });
|
||||
}
|
||||
} else {
|
||||
const next = edgeRect(el.nextSibling, "next", el, wrapSet);
|
||||
if (!next || lowerLine(r, next)) {
|
||||
decisions.push({ el, edge: "tail", mode });
|
||||
decisions.push({ el });
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const d of decisions) {
|
||||
applyEdge(d.el, d.edge, d.mode);
|
||||
d.el.classList.add(LE);
|
||||
}
|
||||
}
|
||||
var lineEdgePass = {
|
||||
@@ -1974,21 +1946,15 @@ function normalizeOptions(opts = {}) {
|
||||
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",
|
||||
// 行端僅做半形擠壓(lineedge:行首開類收左、行尾閉類收右)。**標點懸掛已移除**
|
||||
// (§6.5.6):純負 margin 機制為機制級死路(寬度守恆矛盾,CSS 接觸不到斷行器);
|
||||
// opts.hanging 仍被接受以保 API 相容,但無任何效果。
|
||||
jiyaConfig: resolveJiya(opts.jiya),
|
||||
blocks: Array.isArray(opts.blocks) ? opts.blocks.slice() : [],
|
||||
finder: {
|
||||
|
||||
Reference in New Issue
Block a user