5b9c6b5fa1
下游回饋之字型樹需求。決策:字型屬消費端領域,聚珍不應內建字型堆疊(原 _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 不變)。
182 lines
11 KiB
HTML
182 lines
11 KiB
HTML
<!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 halt(Noto 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-HK/zh-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 = "已 revert(DOM 还原)";
|
||
}
|
||
document.getElementById("force-kaiming").addEventListener("change", function (e)
|
||
{
|
||
forceKaiming = e.target.checked;
|
||
render();
|
||
});
|
||
// 量測每個 jz-char 之實際渲染寬度(em)。輸出純 ASCII(碼位+class+數值),
|
||
// 並按 lang/codepoint/class 去重,便於回報診斷 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>
|