#!/usr/bin/env python # -*- coding: utf-8 -*- """数字字体度量核对(票 17 维护者走查项:「大屏 KPI 数字不跳格」)。 规格 11 Q3:数字字体不可授权时,回退备选需与设计字体**度量兼容**,避免大屏数字列宽跳动。 本脚本把「跳格」拆成两个可测的事实,并明确哪一个由本脚本断言、哪一个必须人工走查: ① 等宽性(本脚本断言):数字 0–9 在**同一字体内部**是否等宽。宽差越大,数值刷新时 整串数字的总宽变化越大——这才是「跳格」的成因。若新字体的内部宽差**不比旧字体更差**, 换字就没有引入新的抖动来源。 ② 逐字差(本脚本**只报告、不断言**):新旧字体在同一字符上的 advance 差。这些差值 是否造成可见观感变化,属人工语义判断(走查),规格 12 明令不转自动断言—— 这里只把数字摆出来,写进票 06 缺口清单供走查核对,不假装代码能判定观感。 用法: set FONTTOOLS_LIBS= python tools/check-num-metrics.py 退出码非 0 仅表示「换字后数字抖动比旧字体更差」。 """ import os import sys FT_LIBS = os.environ.get('FONTTOOLS_LIBS') if FT_LIBS: sys.path.insert(0, FT_LIBS) from fontTools.ttLib import TTFont # noqa: E402 HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(HERE, '..', '..', '..')) DIGITS = '0123456789' EXTRA = '.%:+-/()' INCUMBENT = ('Bahnschrift(旧 / 系统数字字体)', r'C:\Windows\Fonts\bahnschrift.ttf') CANDIDATES = [ ('Rajdhani Subset 400(新,--num 首位)', os.path.join(ROOT, 'public', 'cockpit', 'assets', 'fonts', 'rajdhani-400.woff2')), ('Rajdhani Subset 700(新)', os.path.join(ROOT, 'public', 'cockpit', 'assets', 'fonts', 'rajdhani-700.woff2')), ('Share Tech Mono Subset(新,--num 次位)', os.path.join(ROOT, 'public', 'cockpit', 'assets', 'fonts', 'share-tech-mono-400.woff2')), ] def advances(path, chars): if not os.path.exists(path): return None font = TTFont(path, lazy=True) try: upm = font['head'].unitsPerEm cmap = font.getBestCmap() hmtx = font['hmtx'] return {ch: hmtx[cmap[ord(ch)]][0] / upm for ch in chars if ord(ch) in cmap} finally: font.close() def main(): base = advances(INCUMBENT[1], DIGITS + EXTRA) if base is None: print('FAIL 基准字体缺失:', INCUMBENT[1]) return 1 base_spread = max(base[c] for c in DIGITS) - min(base[c] for c in DIGITS) deviations = [] regressions = [] for label, path in CANDIDATES: adv = advances(path, DIGITS + EXTRA) if adv is None: print(f'SKIP {label}: 文件不存在') continue spread = max(adv[c] for c in DIGITS) - min(adv[c] for c in DIGITS) worse = spread > base_spread print(f'{label}') print(f' 数字内部宽差 {spread:.3f} em (旧 {base_spread:.3f} em) ' + ('更差 -> 回归' if worse else '不更差 -> 未引入新抖动')) if worse: regressions.append(f'{label}: 数字内部宽差 {spread:.3f} em > 旧 {base_spread:.3f} em') for ch in DIGITS + EXTRA: if ch in base and ch in adv: d = adv[ch] - base[ch] if abs(d) > 0.05: deviations.append((label, ch, d)) print() print(f'逐字差 > 0.05 em 的字符({len(deviations)} 项,属人工走查判定的观感偏差,须登记票 06 缺口清单):') for label, ch, d in deviations: print(f' {label:38s} {ch!r} {d:+.3f} em') if not deviations: print(' (无)') print() if regressions: print('FAIL(新字体数字抖动比旧字体更差):') for r in regressions: print(' -', r) return 1 print(f'PASS 未引入新的数字抖动(各新字体数字内部宽差均不劣于 {INCUMBENT[0]});' f'逐字差 {len(deviations)} 项已列出,交维护者走查判定') return 0 if __name__ == '__main__': sys.exit(main())