| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- """数字字体度量核对(票 17 维护者走查项:「大屏 KPI 数字不跳格」)。
- 规格 11 Q3:数字字体不可授权时,回退备选需与设计字体**度量兼容**,避免大屏数字列宽跳动。
- 本脚本把「跳格」拆成两个可测的事实,并明确哪一个由本脚本断言、哪一个必须人工走查:
- ① 等宽性(本脚本断言):数字 0–9 在**同一字体内部**是否等宽。宽差越大,数值刷新时
- 整串数字的总宽变化越大——这才是「跳格」的成因。若新字体的内部宽差**不比旧字体更差**,
- 换字就没有引入新的抖动来源。
- ② 逐字差(本脚本**只报告、不断言**):新旧字体在同一字符上的 advance 差。这些差值
- 是否造成可见观感变化,属人工语义判断(走查),规格 12 明令不转自动断言——
- 这里只把数字摆出来,写进票 06 缺口清单供走查核对,不假装代码能判定观感。
- 用法:
- set FONTTOOLS_LIBS=<fontTools 安装目录>
- 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())
|