Files
NumberSwipe_New/wechat-campaign-200-r2/build-fonts.py
T

69 lines
3.9 KiB
Python

"""Offline font subset builder. Sources and fonttools are build-only, not shipped.
Run with fonttools on PYTHONPATH (e.g. .font-tools), from this directory.
Source downloads: https://github.com/google/fonts/tree/main/ofl/{nunito,notosanssc}
"""
from pathlib import Path
from hashlib import sha256
import json
from fontTools.ttLib import TTFont
from fontTools import subset
from fontTools.varLib.instancer import instantiateVariableFont
root = Path(__file__).resolve().parent
corpus = ''.join(p.read_text(encoding='utf-8') for p in (root / 'src').rglob('*')
if p.suffix in ('.mjs', '.json'))
previous_chars = json.loads((root / 'fonts' / 'manifest.json').read_text(encoding='utf-8')).get('characters', '')
chars = ''.join(sorted(set(corpus + previous_chars + ''.join(chr(i) for i in range(32, 127)) + '✓→…!:·')))
manifest = {'generator': 'fonttools', 'sources': [], 'outputs': [], 'characters': chars}
for family, weights in [('notosanssc', [500, 600, 700]), ('nunito', [800])]:
source = root / '.font-sources' / (family + '.ttf')
manifest['sources'].append({'family': family, 'sha256': sha256(source.read_bytes()).hexdigest(),
'url': 'https://github.com/google/fonts/tree/main/ofl/' + family})
for weight in weights:
font = TTFont(source)
options = subset.Options()
options.name_IDs = ['*']
sub = subset.Subsetter(options=options)
text = chars if family == 'notosanssc' else '0123456789'
sub.populate(text=text)
sub.subset(font)
font = instantiateVariableFont(font, {'wght': weight}, inplace=True)
name = ('NumberSwipeSans' if family == 'notosanssc' else 'NumberSwipeDigits') + str(weight)
for record in font['name'].names:
if record.nameID in (1, 2, 3, 4, 6, 16, 17):
value = 'Regular' if record.nameID in (2, 17) else name
record.string = value.encode(record.getEncoding(), errors='replace')
target = root / 'fonts' / (name + '.ttf')
font.save(target)
cmap = font.getBestCmap()
missing = sorted({c for c in text if not c.isspace() and ord(c) not in cmap})
# Source fonts don't contain code symbols like private-use/non-printing chars;
# all player-visible Han characters and digits must be present.
critical = [c for c in missing if '\u4e00' <= c <= '\u9fff' or c.isdigit()]
if critical:
raise ValueError('Missing glyphs: ' + ''.join(critical))
manifest['outputs'].append({'file': target.name, 'weight': weight,
'sha256': sha256(target.read_bytes()).hexdigest(),
'bytes': target.stat().st_size, 'glyphs': len(cmap)})
print(target.name, target.stat().st_size)
source = root / '.font-sources' / 'zcoolkuaile.ttf'
font = TTFont(source)
options = subset.Options()
options.name_IDs = ['*']
sub = subset.Subsetter(options=options)
playful_chars = '重开撤销指路已走步还剩块第关0123456789 ·'
sub.populate(text=playful_chars)
sub.subset(font)
for record in font['name'].names:
if record.nameID in (1, 2, 3, 4, 6, 16, 17):
record.string = ('Regular' if record.nameID in (2, 17) else 'NumberSwipePlayful').encode(record.getEncoding(), errors='replace')
target = root / 'fonts' / 'NumberSwipePlayful.ttf'
font.save(target)
manifest['sources'].append({'family': 'zcoolkuaile', 'sha256': sha256(source.read_bytes()).hexdigest(),
'url': 'https://github.com/google/fonts/tree/main/ofl/zcoolkuaile'})
manifest['outputs'].append({'file': target.name, 'weight': 400, 'sha256': sha256(target.read_bytes()).hexdigest(),
'bytes': target.stat().st_size, 'glyphs': len(font.getBestCmap()), 'characters': playful_chars})
print(target.name, target.stat().st_size)
(root / 'fonts' / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8')