3a4fa1c932
保留逐字 margin/jiya.bias 字体后备方案;补齐 demo 重排、量测边界说明及共享规则 1.0.1 反例向量。 验证:完整构建通过,Node 376 项、WKWebView 64 项及原生 demo 9 项通过;Firefox 仍暂缓。
96 lines
3.8 KiB
JavaScript
96 lines
3.8 KiB
JavaScript
import assert from "node:assert/strict";
|
||
import { mkdtempSync, rmSync } from "node:fs";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { spawnSync } from "node:child_process";
|
||
import { test } from "node:test";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const python = "python3";
|
||
const available = spawnSync(python, [ "-c", "import fontTools" ]).status === 0;
|
||
const tool = fileURLToPath(new URL("../tools/measure-bias.py", import.meta.url));
|
||
const fixture = `
|
||
from fontTools.fontBuilder import FontBuilder
|
||
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
|
||
import sys
|
||
fb = FontBuilder(1000, isTTF=True)
|
||
names = ['.notdef', 'comma', 'commaHant', 'fullstop']
|
||
fb.setupGlyphOrder(names)
|
||
fb.setupCharacterMap({0xff0c: 'comma', 0xff0e: 'fullstop'})
|
||
glyphs = {}
|
||
for name, left, right in [('.notdef', 0, 0), ('comma', 50, 250),
|
||
('commaHant', 450, 550), ('fullstop', 650, 850)]:
|
||
p = TTGlyphPen(None)
|
||
if right > left:
|
||
p.moveTo((left, 0)); p.lineTo((right, 0)); p.lineTo((right, 150))
|
||
p.lineTo((left, 150)); p.closePath()
|
||
glyphs[name] = p.glyph()
|
||
fb.setupGlyf(glyphs)
|
||
fb.setupHorizontalMetrics({'.notdef': (1000, 0), 'comma': (1000, 50),
|
||
'commaHant': (1000, 450), 'fullstop': (1000, 650)})
|
||
fb.setupHorizontalHeader(ascent=800, descent=-200)
|
||
fb.setupNameTable({'familyName': 'BiasFixture', 'styleName': 'Regular',
|
||
'uniqueFontIdentifier': 'BiasFixture', 'fullName': 'BiasFixture',
|
||
'psName': 'BiasFixture'})
|
||
fb.setupOS2(sTypoAscender=800, sTypoDescender=-200, usWinAscent=800, usWinDescent=200)
|
||
fb.setupPost(); fb.setupMaxp()
|
||
features = 'languagesystem hani ZHT; feature locl { script hani; language ZHT; sub comma by commaHant; } locl;'
|
||
if sys.argv[2] == 'halt':
|
||
features += ' feature halt { sub fullstop by comma; } halt;'
|
||
addOpenTypeFeaturesFromString(fb.font, features)
|
||
fb.save(sys.argv[1])
|
||
`;
|
||
|
||
function run(args)
|
||
{
|
||
const result = spawnSync(python, args, { encoding: "utf8" });
|
||
assert.equal(result.status, 0, result.stderr || String(result.error));
|
||
return result.stdout;
|
||
}
|
||
|
||
function withFont(feature, check)
|
||
{
|
||
const dir = mkdtempSync(join(tmpdir(), "juzhen-bias-test-"));
|
||
try
|
||
{
|
||
const font = join(dir, "fixture.ttf");
|
||
run([ "-c", fixture, font, feature ]);
|
||
check(font);
|
||
}
|
||
finally { rmSync(dir, { recursive: true, force: true }); }
|
||
}
|
||
|
||
function table(output)
|
||
{
|
||
return JSON.parse(output.slice(output.indexOf("\n{") + 1));
|
||
}
|
||
|
||
test("measure-bias:全角句點入表,lang 僅選鍵且輸出說明 cmap/locl 限制",
|
||
{ skip: available ? false : "需工具本身的 Python fonttools 依賴" }, () =>
|
||
{
|
||
withFont("none", (font) =>
|
||
{
|
||
for (const lang of [ "zh-Hans", "zh-Hant" ])
|
||
{
|
||
const output = run([ tool, font, lang ]);
|
||
assert.equal(table(output)[lang]["."], "right");
|
||
assert.equal(table(output)[lang][","], "left", "仍量預設 cmap,未套 ZHT locl 替代字形");
|
||
assert.match(output, /未做 shaping.*未應用 locl/);
|
||
assert.match(output, /語言參數僅選 JSON 分類鍵/);
|
||
assert.match(output, /瀏覽器實際字形逐字微調/);
|
||
}
|
||
});
|
||
});
|
||
|
||
test("measure-bias:halt feature 存在只報存在,不承諾逐字覆蓋",
|
||
{ skip: available ? false : "需工具本身的 Python fonttools 依賴" }, () =>
|
||
{
|
||
withFont("halt", (font) =>
|
||
{
|
||
const output = run([ tool, font ]);
|
||
assert.match(output, /halt:有.*不保證涵蓋所有標點/);
|
||
assert.equal(table(output)["zh-Hans"]["."], "right", "feature 偵測不改預設輪廓分類");
|
||
});
|
||
});
|