Files
cjk-autospace/demo/index.html
T
admin 5b9c6b5fa1 refactor: 字型移出聚珍管理(移除 _lang.css),交消費端;半形顯式保留 locl
下游回饋之字型樹需求。決策:字型屬消費端領域,聚珍不應內建字型堆疊(原 _lang.css
之 Noto 後備係越權,且曾蓋掉消費端字型釀 bug)。採「完全移除、交消費端 CSS」(A 案),
否決「以參數管字型」(B 案——JS 管字型須注入 CSS、違反 JS 注入結構/CSS 給視覺之分工;
而缺 halt 字型須逐字型微調 margin 之指引,由既有 jiya.halfWidth/jiya.bias +文檔承擔)。

移除:
- 刪 src/css/_lang.css,juzhen.css 移除其 @import。dist/juzhen.css 現零 font-family
  規則(3.1→2.5kb)。

聚珍保留之三項非管理性本分(確保不礙消費端字型與 locl):
- _normalize.css `jz-* { font: inherit }`:原子繼承消費端字型、不強加。
- _jiya.css 半形 font-feature-settings 顯式改 "locl" 1, "halt" 1(預設+halt-island 兩
  處):確保泛 CJK 字型(思源/Noto CJK)之地域標點字形不因設 halt 而失。
- render 只讀不改 lang。body 文本之 locl 全由消費端字型+正確 lang 決定。

驗證(真實 Chromium):
- halt 模式:消費端逐 lang 字型生效並繼承至 jz-inner,無強制 Noto。
- margin 後備:半形盒 0.5em、字身依墨色偏側位移正確、jz-inner 繼承消費端字型、
  feature normal(locl 既定開),皆不受字型移除影響。

- demo/index.html、resize-check.html 改示範「消費端逐 lang 設 font-family」之推薦範式;
  fallback.html 更新註解。
- README 新增「字型與 locl」節+改寫 CSS 基礎設定字型項;ARCHITECTURE §6.9 改寫
  (A 案決策、否決 B 案、三項本分)+檔案樹。
- test:新增 CSS 回歸(不內建 font-family、半形保留 locl)。57 測試通過。
- dist/ 重建(僅 juzhen.css 變動,JS 不變)。
2026-06-03 02:04:34 +08:00

182 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>聚珍(Juzhen)切片 1 — 目视验证</title>
<!-- 明确载入 Noto 系列 webfont 作参考:擠壓依赖字型 OpenType halt
系统 fallback 字型(如 PingFang)可能无 halt,Safari 下尤其表现为开明式无效。
Noto Serif/Sans SC/TC 含 halt,确保跨浏览器一致参考。
注意:字型由**消费端**自行设定——聚珍不再内建 _lang.css 字体堆叠。下方 <style>
之 `.juzhen:lang(...)` 即推荐范式(逐 lang 设 font-family,须含 halt)。 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC&family=Noto+Sans+TC&family=Noto+Serif+SC&family=Noto+Serif+TC&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../dist/juzhen.css">
<style>
/* demo 自身样式(非套件之一)。 */
/* 消费端字型设定(推荐范式):逐 lang 设 font-family,置于 .juzhen 或带 lang 之
元素;字型须含 OpenType haltNoto CJK 具备),否则用 jiya.halfWidth:"margin"。 */
.juzhen:lang(zh-Hans), .juzhen [lang]:lang(zh-Hans) { font-family: "Noto Serif SC", "Noto Serif CJK SC", serif; }
.juzhen:lang(zh-Hant), .juzhen [lang]:lang(zh-Hant) { font-family: "Noto Serif TC", "Noto Serif CJK TC", serif; }
body { margin: 0; padding: 2rem; font-size: 18px; line-height: 2; background: #fafafa; color: #1a1a1a; }
h1 { font-size: 1.4rem; }
.toolbar { position: sticky; top: 0; background: #fff; border: 1px solid #ddd; padding: .75rem 1rem; margin-bottom: 1.5rem; display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; z-index: 10; }
.toolbar button, .toolbar label { font-size: .85rem; }
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.panel { border: 1px solid #ddd; padding: 1rem 1.25rem; background: #fff; }
.panel h2 { font-size: .95rem; color: #666; margin: 0 0 .5rem; font-weight: 600; }
.ruler { border-left: 1px dashed #c00; border-right: 1px dashed #c00; }
.narrow { max-width: 20em; }
.primary { border-color: #06c; border-width: 2px; }
code { background: #eee; padding: 0 .25em; border-radius: 3px; font-family: ui-monospace, monospace; }
.note { font-size: .8rem; color: #888; margin-top: 1.5rem; }
.selbox { background: #fffbe6; }
body.justify .juzhen p { text-align: justify; }
.cases p { margin: .4rem 0; }
.label { font-size: .72rem; color: #999; }
</style>
</head>
<body>
<h1>聚珍(Juzhen)切片 1 — 目视验证页</h1>
<div class="toolbar">
<button id="render">render</button>
<button id="revert">revert</button>
<label><input type="checkbox" id="justify"> text-align: justify</label>
<label><input type="checkbox" id="wide"> 放宽栏(看行内擠壓)</label>
<label><input type="checkbox" id="force-kaiming"> 强制繁体开明式(兼容性检查)</label>
<button id="measure">测量标点宽度</button>
<span id="status" style="font-size:.8rem;color:#888;"></span>
</div>
<div class="cols">
<!-- 主:简体开明式。窄栏强制换行,以观察行端擠壓(机制 B)。 -->
<div class="panel juzhen narrow ruler primary cases" lang="zh-Hans">
<h2>主 · 简体(开明式)— 句内点号+括号引号半形</h2>
<p><span class="label">中西混排:</span>我用 Neovim 写 TypeScript,配合 ripgrep 与 fzf 做模糊搜索,效率提升了 200%,达到约 3.5 倍。</p>
<p><span class="label">句末+引号:</span>他问:“真的吗?”我说:“当然!”这是 GPT-4 与 state-of-the-art 工具的时代。</p>
<p><span class="label">连续标点:</span>完了……怎么办?!真是的——他竟然走了。</p>
<p><span class="label">括号嵌套:</span>(含括号)与【方括号】以及《标题》,还有“她讲过‘小心’二字”。</p>
<p><span class="label">间隔号:</span>卡尔·马克思与弗里德里希·恩格斯合著。</p>
<p><span class="label">列举:</span>第一、第二、第三;以及其他(杂项)的内容。</p>
</div>
<!-- 对比:繁体全角式。同样内容,用以发现政策分支差异。 -->
<div class="panel juzhen narrow ruler cases" lang="zh-Hant">
<h2>对比 · 繁體(全角式)— 句內標點不擠,僅鄰接/行端收縮</h2>
<p><span class="label">中西混排:</span>我用 Neovim 寫 TypeScript,配合 ripgrep 與 fzf 做模糊搜尋,效率提升了 200%,達到約 3.5 倍。</p>
<p><span class="label">句末+引號:</span>他問:「真的嗎?」我說:「當然!」這是 GPT-4 與 state-of-the-art 工具的時代。</p>
<p><span class="label">連續標點:</span>完了……怎麼辦?!真是的——他竟然走了。</p>
<p><span class="label">括號嵌套:</span>(含括號)與【方括號】以及《標題》,還有「她講過『小心』二字」。</p>
<p><span class="label">間隔號:</span>卡爾·馬克思與弗里德里希·恩格斯合著。</p>
<p><span class="label">列舉:</span>第一、第二、第三;以及其他(雜項)的內容。</p>
</div>
</div>
<div class="panel juzhen selbox" lang="zh-Hans" style="margin-top:1.5rem;">
<h2>copy-clean 测试 — 框选整段并复制到纯文本编辑器,中西间隙的空格不应被带入</h2>
<p>请选取此句:我用 Neovim 编辑配置文件,搭配 fzf 与 ripgrep 做模糊搜索。复制后粘到纯文本,应为“我用Neovim编辑配置文件,搭配fzf与ripgrep做模糊搜索。”般紧凑(标点原样保留,中西间隙不入剪贴板)。</p>
</div>
<p class="note">
验证重点(jsdom 无法覆盖之版面层):①justify 下句末标点只占一份间距(不被拉成两份);
②开明式句内点号与括号引号视觉半形、繁体全角式不擠(仅相邻/行端收缩);
③句末标点后接闭引号(如 。 接 ”)行内应约 1.5em、行尾应约 1em;
④长英文词在窄栏可原生连字断行(需字体支持);⑤复制粘贴中西间隙不污染原文。
请于 Chrome / Safari / Firefox 三引擎各核对一次。
<br><b>字型注意</b>:擠壓(开明式半形)依赖字型的 OpenType <code>halt</code> 特性。
本页已用 webfont 载入 Noto Serif SC/TC(含 halt)作参考;若改用无 halt 的字型
(含部分系统字型如 PingFang),Safari 等浏览器下开明式擠壓会不生效——这是字型限制,非逻辑错误。
</p>
<details open style="margin-top:1rem;">
<summary style="font-size:.85rem;color:#666;cursor:pointer;">标点实测宽度(诊断用——点「测量标点宽度」后把下表复制给我)</summary>
<pre id="measure-out" style="font-size:.75rem;background:#f4f4f4;padding:1rem;overflow:auto;white-space:pre-wrap;">(点上方「测量标点宽度」)</pre>
</details>
<script src="../dist/juzhen.iife.js"></script>
<script>
var forceKaiming = false;
function makeJz()
{
// 強制繁體開明式:policy 將 zh-Hant(含 zh-HKzh-TW,皆歸 zh-Hant)覆寫為
// 開明式,用以檢查開明政策套到繁體之相容性。
return Juzhen.createJuzhen({
scope: { include: ".juzhen" },
lang: forceKaiming ? { policy: { "zh-Hant": "kaiming" } } : {}
});
}
var jz = makeJz();
var statusEl = document.getElementById("status");
function render()
{
jz.revert(document.body); // 先還原(重套政策時清除上次產物;首次為 no-op)
jz = makeJz();
jz.render(document.body);
statusEl.textContent = "已 render" + document.querySelectorAll("jz-hws").length + " 间隙、" + document.querySelectorAll("jz-char").length + " 标点原子;繁体政策:"
+ (forceKaiming ? "开明式" : "全角式") + "";
}
function revert()
{
jz.revert(document.body);
statusEl.textContent = "已 revertDOM 还原)";
}
document.getElementById("force-kaiming").addEventListener("change", function (e)
{
forceKaiming = e.target.checked;
render();
});
// 量測每個 jz-char 之實際渲染寬度(em)。輸出純 ASCII(碼位+class+數值),
// 並按 langcodepointclass 去重,便於回報診斷 halt 是否生效、收哪側。
function measure()
{
var out = [];
var byPanel = {};
document.querySelectorAll(".juzhen").forEach(function (panel)
{
var lang = panel.getAttribute("lang") || "?";
var key = lang + " | " + (panel.querySelector("h2") ? panel.querySelector("h2").textContent.slice(0, 10) : "");
panel.querySelectorAll("jz-char").forEach(function (c)
{
var fs = parseFloat(getComputedStyle(c).fontSize) || 16;
var w = c.getBoundingClientRect().width / fs;
var cp = "U+" + c.textContent.codePointAt(0).toString(16).toUpperCase();
while (cp.length < 6) { cp = cp.slice(0, 2) + "0" + cp.slice(2); }
var cls = Array.prototype.slice.call(c.classList).join(",") || "-";
var line = key + " " + cp + " " + w.toFixed(3) + "em [" + cls + "]";
if (!byPanel[line]) { byPanel[line] = true; out.push(line); }
});
});
// 參考:一個漢字寬度(應 ≈ 1.000em)。
var ref = document.querySelector(".juzhen p");
if (ref)
{
var span = document.createElement("span");
span.textContent = "字";
ref.appendChild(span);
var fs2 = parseFloat(getComputedStyle(span).fontSize) || 16;
out.unshift("参考 漢字「字」 " + (span.getBoundingClientRect().width / fs2).toFixed(3) + "em(应 ≈ 1.000");
ref.removeChild(span);
}
document.getElementById("measure-out").textContent = out.join("\n");
}
document.getElementById("measure").addEventListener("click", measure);
document.getElementById("render").addEventListener("click", render);
document.getElementById("revert").addEventListener("click", revert);
document.getElementById("justify").addEventListener("change", function (e)
{
document.body.classList.toggle("justify", e.target.checked);
});
document.getElementById("wide").addEventListener("change", function (e)
{
document.querySelectorAll(".narrow").forEach(function (el)
{
el.style.maxWidth = e.target.checked ? "none" : "";
});
});
render();
</script>
</body>
</html>