mirror of
https://github.com/hanzoai/font.git
synced 2026-11-02 23:42:52 +00:00
The estate carried Geist under our name in four repositories. Those files said family "Zen" and manufacturer "Hanzo AI, Inc." while their vendor tag was still VRCL and nameID 0 named one holder — a partial relabelling, which answers "Zen" to every question a stylesheet, a grep or a font menu asks. Family alone cannot tell you whose bytes these are; vendor and notice can. identity.py holds that answer once. name.py and notice.py write identity into a binary and each had its own copy of the notice text, free to drift; they now read it from one place. The same definition read backwards is the check, and copy-npm-fonts ends with it, so a face becomes something the package would publish and is refused in the same step. All 87 faces pass. Two things the check learned from being tested rather than assumed. A static cut outside the four styles a family can group puts its weight in nameID 1 and the shared family in nameID 16, so "Zen Thin" is correct and 16 is the field to read. And the notice lives in a Windows record and a Mac one: reading only the record fontTools hands you passes a face that rewrote just the other one, so every nameID 0 record must carry both holders.
92 lines
3.4 KiB
Python
92 lines
3.4 KiB
Python
"""Give the hand-exported binaries the same identity the built ones get.
|
|
|
|
`make build` compiles Zen and Zen Mono from `sources/`, so those come out of
|
|
fontmake already carrying family "Zen"/"Zen Mono", vendor HNZO and our copyright
|
|
— the Glyphs sources hold those fields and `scripts/set-fontinfo.py` keeps them
|
|
there. Zen Pixel is the exception: it is drawn on a virtual master, gftools
|
|
builds only its variable, and its five static cuts are exported by hand from
|
|
Glyphs. An export skips the source fields, so those files arrive naming the
|
|
upstream family, and a face is bound to whatever family the CSS declares — so
|
|
@font-face reads Zen while every font menu, inspector and PDF embed reads the
|
|
old name.
|
|
|
|
Run after a hand export, then commit the result:
|
|
|
|
python3 packages/zen/scripts/name.py
|
|
|
|
Idempotent: run it over the whole tree and the built files are already right.
|
|
|
|
nameID 0 carries the upstream copyright line above ours. OFL §2 binds each copy
|
|
to the notice, and family, uniqueID, version, manufacturer, designer and the
|
|
vendor URLs are not the notice — those are ours. §3 reserves nothing here: the
|
|
upstream block declares no Reserved Font Name, so the family name is free.
|
|
"""
|
|
import pathlib
|
|
import sys
|
|
from fontTools.ttLib import TTFont
|
|
|
|
from identity import NOTICE, TAG, VENDOR
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[3]
|
|
# The hand exports. Everything else under fonts/ comes out of fontmake already
|
|
# naming itself, including ZenPixel's variable, which gftools does build.
|
|
EXPORTS = [ROOT / 'fonts' / 'ZenPixel' / d for d in ('otf', 'ttf', 'webfonts')]
|
|
|
|
|
|
|
|
def retag(path: pathlib.Path) -> tuple[str, str]:
|
|
"""Name one cut. Each is its own family, so its subfamily is Regular.
|
|
|
|
Zen Pixel's five cuts are five drawings of one alphabet, not five weights of
|
|
one drawing — nothing orders them, so a font menu should list them side by
|
|
side rather than bury four in a style submenu. That makes nameID 1 the whole
|
|
name and nameID 2 plain Regular, and leaves the typographic pair (16/17) for
|
|
families that actually have styles to group.
|
|
"""
|
|
f = TTFont(str(path))
|
|
cut = path.stem.split('-')[-1]
|
|
family = f'Zen Pixel {cut}'
|
|
ver = (f['name'].getDebugName(5) or 'Version 1.000').replace('Version ', '')
|
|
was = f['name'].getDebugName(1)
|
|
names = {
|
|
0: NOTICE,
|
|
1: family,
|
|
2: 'Regular',
|
|
3: f'{ver};{TAG};ZenPixel-{cut}',
|
|
4: family,
|
|
6: f'ZenPixel-{cut}',
|
|
8: 'Hanzo AI, Inc.',
|
|
9: 'Hanzo AI, Inc.',
|
|
11: VENDOR,
|
|
12: VENDOR,
|
|
}
|
|
for nid, val in names.items():
|
|
f['name'].setName(val, nid, 3, 1, 0x409) # windows/unicode/en-US
|
|
f['name'].setName(val, nid, 1, 0, 0) # mac/roman, where present
|
|
for nid in (16, 17):
|
|
f['name'].removeNames(nid)
|
|
f['OS/2'].achVendID = TAG
|
|
f.save(str(path))
|
|
return was, family
|
|
|
|
|
|
def main() -> int:
|
|
moved = 0
|
|
for d in EXPORTS:
|
|
if not d.is_dir():
|
|
print(f'no {d} — run `make build` first', file=sys.stderr)
|
|
return 1
|
|
for path in sorted(d.iterdir()):
|
|
if path.suffix not in ('.otf', '.ttf', '.woff2'):
|
|
continue
|
|
was, now = retag(path)
|
|
if was != now:
|
|
moved += 1
|
|
print(f' {path.name:<32} {was} -> {now}')
|
|
print(f'{moved} renamed' if moved else 'every cut already names itself Zen Pixel')
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|