Skip to content

Commit ef39ce4

Browse files
Tweak hinting, generate KF variants with build.py
1 parent 5cfc4ea commit ef39ce4

2 files changed

Lines changed: 48 additions & 73 deletions

File tree

.github/workflows/build.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,6 @@ jobs:
1717
- name: Build fonts
1818
run: python3 build.py
1919

20-
- name: Download kobofix.py
21-
run: curl -sL https://raw.githubusercontent.com/nicoverbruggen/kobo-font-fix/main/kobofix.py -o kobofix.py
22-
23-
- name: Generate Kobo (KF) fonts
24-
run: |
25-
python3 kobofix.py --preset kf out/ttf/*.ttf
26-
mkdir -p out/kf
27-
mv out/ttf/KF_*.ttf out/kf/
28-
2920
- name: Upload artifact
3021
uses: actions/upload-artifact@v6
3122
with:

build.py

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
2. Applies vertical scale (scale.py) via FontForge
99
3. Applies vertical metrics, line height, rename (metrics.py, lineheight.py, rename.py)
1010
4. Exports to TTF → ./out/ttf/
11-
5. Post-processes TTFs: x-height overshoot clamping, style flags, kern pairs, autohinting
11+
5. Post-processes TTFs: style flags, kern pairs, autohinting
1212
1313
Uses FontForge (detected automatically).
1414
Run with: python3 build.py
@@ -43,6 +43,7 @@
4343
SRC_DIR = os.path.join(ROOT_DIR, "src")
4444
OUT_DIR = os.path.join(ROOT_DIR, "out")
4545
OUT_TTF_DIR = os.path.join(OUT_DIR, "ttf") # generated TTFs
46+
OUT_KF_DIR = os.path.join(OUT_DIR, "kf") # Kobo (KF) variants
4647

4748
REGULAR_VF = os.path.join(SRC_DIR, "Newsreader-VariableFont_opsz,wght.ttf")
4849
ITALIC_VF = os.path.join(SRC_DIR, "Newsreader-Italic-VariableFont_opsz,wght.ttf")
@@ -95,18 +96,13 @@
9596
# - Other options are left at ttfautohint defaults; uncomment to override.
9697
AUTOHINT_OPTS = [
9798
"--no-info",
98-
"--stem-width-mode=nss",
99+
"--stem-width-mode=qss",
99100
# "--hinting-range-min=8",
100101
# "--hinting-range-max=50",
101102
# "--hinting-limit=200",
102-
"--increase-x-height=0",
103+
"--increase-x-height=14",
103104
]
104105

105-
# Glyphs whose x-height overshoot is an outlier (+12 vs the standard +22).
106-
# The inconsistent overshoot lands between the hinter's snap zones, causing
107-
# these glyphs to render taller than their neighbors on low-res e-ink.
108-
CLAMP_XHEIGHT_GLYPHS = ["u", "uogonek"]
109-
110106
# Explicit kern pairs: (left_glyph, right_glyph, kern_value_in_units).
111107
# Negative values tighten spacing. These are added on top of any existing
112108
# kerning from the source variable font.
@@ -610,61 +606,6 @@ def clean_ttf_degenerate_contours(ttf_path):
610606
font.close()
611607

612608

613-
def clamp_xheight_overshoot(ttf_path):
614-
"""Clamp outlier x-height overshoots in a TTF in-place.
615-
616-
Some glyphs (e.g. 'u') have a smaller overshoot than the standard
617-
round overshoot, landing between the hinter's snap zones. This
618-
flattens them to the true x-height measured from flat-topped glyphs.
619-
"""
620-
try:
621-
from fontTools.ttLib import TTFont
622-
except Exception:
623-
print(" [warn] Skipping x-height clamp: fontTools not available", file=sys.stderr)
624-
return
625-
626-
font = TTFont(ttf_path)
627-
glyf = font["glyf"]
628-
629-
# Measure x-height from flat-topped reference glyphs.
630-
xheight = 0
631-
for ref in ("x", "v"):
632-
if ref not in glyf:
633-
continue
634-
coords = glyf[ref].coordinates
635-
if coords:
636-
ymax = max(c[1] for c in coords)
637-
if ymax > xheight:
638-
xheight = ymax
639-
640-
if xheight == 0:
641-
font.close()
642-
return
643-
644-
clamped = []
645-
for name in CLAMP_XHEIGHT_GLYPHS:
646-
if name not in glyf:
647-
continue
648-
glyph = glyf[name]
649-
coords = glyph.coordinates
650-
if not coords:
651-
continue
652-
ymax = max(c[1] for c in coords)
653-
if ymax <= xheight:
654-
continue
655-
glyph.coordinates = type(coords)(
656-
[(x, min(y, xheight)) for x, y in coords]
657-
)
658-
glyph_set = font.getGlyphSet()
659-
if hasattr(glyph, "recalcBounds"):
660-
glyph.recalcBounds(glyph_set)
661-
clamped.append(name)
662-
663-
if clamped:
664-
font.save(ttf_path)
665-
print(f" Clamped x-height overshoot for: {', '.join(clamped)} (xh={xheight})")
666-
font.close()
667-
668609

669610
def fix_ttf_style_flags(ttf_path, style_suffix):
670611
"""Normalize OS/2 fsSelection and head.macStyle for style linking."""
@@ -867,6 +808,42 @@ def check_ttfautohint():
867808
sys.exit(1)
868809

869810

811+
KOBOFIX_URL = (
812+
"https://raw.githubusercontent.com/nicoverbruggen/kobo-font-fix/main/kobofix.py"
813+
)
814+
815+
816+
def _download_kobofix(dest):
817+
"""Download kobofix.py if not already cached."""
818+
import urllib.request
819+
print(f" Downloading kobofix.py ...")
820+
urllib.request.urlretrieve(KOBOFIX_URL, dest)
821+
print(f" Saved to {dest}")
822+
823+
824+
def _run_kobofix(kobofix_path, variant_names):
825+
"""Run kobofix.py --preset kf on built TTFs, move KF_ files to out/kf/."""
826+
ttf_files = [os.path.join(OUT_TTF_DIR, f"{n}.ttf") for n in variant_names]
827+
cmd = [sys.executable, kobofix_path, "--preset", "kf"] + ttf_files
828+
result = subprocess.run(cmd, capture_output=True, text=True)
829+
if result.stdout:
830+
print(result.stdout, end="")
831+
if result.returncode != 0:
832+
print("\nERROR: kobofix.py failed", file=sys.stderr)
833+
if result.stderr:
834+
print(result.stderr, file=sys.stderr)
835+
sys.exit(1)
836+
837+
os.makedirs(OUT_KF_DIR, exist_ok=True)
838+
import glob
839+
moved = 0
840+
for kf_file in glob.glob(os.path.join(OUT_TTF_DIR, "KF_*.ttf")):
841+
dest = os.path.join(OUT_KF_DIR, os.path.basename(kf_file))
842+
shutil.move(kf_file, dest)
843+
moved += 1
844+
print(f" Moved {moved} KF font(s) to {OUT_KF_DIR}/")
845+
846+
870847
def main():
871848
print("=" * 60)
872849
print(" Readerly Build")
@@ -998,15 +975,22 @@ def _build(tmp_dir, family=DEFAULT_FAMILY, outline_fix=True):
998975
run_fontforge_script(script)
999976
if outline_fix:
1000977
clean_ttf_degenerate_contours(ttf_path)
1001-
clamp_xheight_overshoot(ttf_path)
1002978
fix_ttf_style_flags(ttf_path, style_suffix)
1003979
add_kern_pairs(ttf_path)
1004980
autohint_ttf(ttf_path)
1005981

1006982

983+
# Step 5: Generate Kobo (KF) variants via kobofix.py
984+
print("\n── Step 5: Generate Kobo (KF) variants ──\n")
985+
986+
kobofix_path = os.path.join(tmp_dir, "kobofix.py")
987+
_download_kobofix(kobofix_path)
988+
_run_kobofix(kobofix_path, variant_names)
989+
1007990
print("\n" + "=" * 60)
1008991
print(" Build complete!")
1009992
print(f" TTF fonts are in: {OUT_TTF_DIR}/")
993+
print(f" KF fonts are in: {OUT_KF_DIR}/")
1010994
print("=" * 60)
1011995

1012996

0 commit comments

Comments
 (0)