mirror of
https://github.com/hanzoai/font.git
synced 2026-11-02 23:42:52 +00:00
LICENSE.txt at the root lists both holders — Vercel/basement.studio for Geist and Hanzo AI for Zen — and packages/zen/scripts/name.py writes both onto the hand-exported Pixel cuts, its docstring stating the reason: OFL §2 binds each copy of the Font Software to the notice, and a .woff2 served to a browser is a copy travelling alone. The fontmake-built families took the other path. Their fields come from the Glyphs sources through scripts/set-fontinfo.py, and that wrote our line only — so Zen and Zen Mono, the two faces every Hanzo surface actually loads, shipped without the notice the licence beside them carries. Same repo, two answers, and the wrong one was on the faces in production. set-fontinfo.py now writes both, so a rebuild produces it directly. packages/zen/scripts/notice.py repairs the binaries already built and committed; it is idempotent and reports what it changed. 199 of 219 faces gained the line — the 20 that did not are the Pixel cuts name.py had already handled. Family, uniqueID, version, manufacturer, designer and the vendor URLs are untouched: those are ours, and §3 reserves nothing here — the upstream block declares no Reserved Font Name.
71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Set the attribution fields in every Glyphs source.
|
|
|
|
These fields land in the built font's `name` table and OS/2 `achVendID`, so
|
|
they travel with a bare .woff2 wherever it is served. Run after editing a
|
|
source in Glyphs, which rewrites the file from its own UI fields.
|
|
|
|
nameID 0 carries the upstream copyright line above ours, for the reason
|
|
`packages/zen/scripts/name.py` already states about the hand exports: OFL §2
|
|
binds each copy of the Font Software to the notice, and a .woff2 served to a
|
|
browser IS a copy travelling alone. Family, uniqueID, version, manufacturer,
|
|
designer and the vendor URLs are not the notice — those are ours. LICENSE.txt
|
|
at the root already lists both holders; this keeps the binaries agreeing with it.
|
|
"""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
FIELDS = {
|
|
"copyrights": (
|
|
"Copyright 2024 The Geist Project Authors "
|
|
"(https://github.com/vercel/geist-font)\n"
|
|
"Copyright 2026 Hanzo AI, Inc. (https://git.hanzo.ai/hanzoai/font)"
|
|
),
|
|
"designers": "Hanzo AI, Inc.",
|
|
"designerURL": "https://hanzo.ai",
|
|
"manufacturers": "Hanzo AI, Inc.",
|
|
"manufacturerURL": "https://hanzo.ai",
|
|
"vendorID": "HNZO",
|
|
}
|
|
|
|
SOURCES = Path(__file__).resolve().parent.parent / "sources"
|
|
|
|
|
|
def patch(text, key, value):
|
|
"""Replace the first `value = ...;` after `key = <key>;`.
|
|
|
|
Glyphs writes two shapes: a bare `value` for scalars, and a `values = (…)`
|
|
list of per-language entries. Both begin with the same `value =` token, so
|
|
one pattern covers each.
|
|
"""
|
|
pattern = re.compile(
|
|
r"(key = " + re.escape(key) + r";(?:.*?\n){0,6}?\s*value = )"
|
|
r'("(?:[^"\\]|\\.)*"|[^;\n]+)(;)',
|
|
re.DOTALL,
|
|
)
|
|
new, n = pattern.subn(lambda m: m.group(1) + f'"{value}"' + m.group(3),
|
|
text, count=1)
|
|
if n != 1:
|
|
raise SystemExit(f"{key}: expected 1 match, got {n}")
|
|
return new
|
|
|
|
|
|
def main():
|
|
files = sorted(SOURCES.glob("*.glyphspackage/fontinfo.plist"))
|
|
if not files:
|
|
raise SystemExit(f"no sources under {SOURCES}")
|
|
for path in files:
|
|
text = original = path.read_text(encoding="utf-8")
|
|
for key, value in FIELDS.items():
|
|
text = patch(text, key, value)
|
|
if text != original:
|
|
path.write_text(text, encoding="utf-8")
|
|
print(f"patched {path.relative_to(SOURCES.parent)}")
|
|
else:
|
|
print(f"unchanged {path.relative_to(SOURCES.parent)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|