diff --git a/.gitignore b/.gitignore index 9c28c84..72db8e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ out -mutated \ No newline at end of file +src_processed \ No newline at end of file diff --git a/README.md b/README.md index 688b94b..3d68540 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,23 @@ To accomplish this, I wanted to start from the 9pt font, which I exported. Then, ## Project structure -- `./src`: folder containing all Readerly source files -- `./scripts`: some experimental scripts +- `./src`: source .sfd font files (Newsreader 9pt, renamed to Readerly) +- `./scripts`: FontForge Python scripts applied during the build + - `scale.py`: scales lowercase glyphs vertically to increase x-height + - `metrics.py`: sets vertical metrics (OS/2 Typo, Win, hhea) + - `rename.py`: updates font name metadata from Newsreader to Readerly +- `./src_processed`: intermediate .sfd files after processing (generated) +- `./out`: final TTF fonts (generated) -## Goal +## Building -- Increase the vertical sizing of the font by 5-10% (metrics.py) -- Update the xheight to be closer to what Bookerly looks like (xheight.py) -- This should apply to all fonts -- A separate "export" script should be added that generates TTF fonts (with old style kerning) +``` +python3 build.py +``` -In the end, I want to be able to run a script, `build.py`, which should: +This uses the Flatpak version of FontForge to: -- Use the flatpak version of FontForge -- Copy the ./src files to ./mutated -- Apply the edits mentioned above -- Export the fonts to TTF in ./out - -I will then manually review the fonts. +1. Copy `./src` to `./src_processed` +2. Scale lowercase glyphs (configurable in `scripts/scale.py`) +3. Set vertical metrics and update font names +4. Export to TTF with old-style kerning in `./out` diff --git a/build.py b/build.py index 960ec59..2a39c10 100755 --- a/build.py +++ b/build.py @@ -4,7 +4,7 @@ Readerly Build Script ───────────────────── Orchestrates the full font build pipeline: - 1. Copies ./src/*.sfd → ./mutated/ + 1. Copies ./src/*.sfd → ./src_processed/ 2. Applies vertical scale (scale.py) 3. Applies vertical metrics (metrics.py) 4. Exports to TTF with old-style kern table → ./out/ @@ -25,7 +25,7 @@ import textwrap ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) SRC_DIR = os.path.join(ROOT_DIR, "src") -MUTATED_DIR = os.path.join(ROOT_DIR, "mutated") +MUTATED_DIR = os.path.join(ROOT_DIR, "src_processed") OUT_DIR = os.path.join(ROOT_DIR, "out") SCRIPTS_DIR = os.path.join(ROOT_DIR, "scripts") @@ -123,8 +123,8 @@ def main(): print(" Readerly Build") print("=" * 60) - # Step 1: Copy src → mutated - print("\n── Step 1: Copy sources to ./mutated ──\n") + # Step 1: Copy src → src_processed + print("\n── Step 1: Copy sources to ./src_processed ──\n") if os.path.exists(MUTATED_DIR): shutil.rmtree(MUTATED_DIR) shutil.copytree(SRC_DIR, MUTATED_DIR) @@ -147,10 +147,11 @@ def main(): ]) run_fontforge_script(script) - # Step 3: Apply metrics.py to each font - print("\n── Step 3: Apply vertical metrics ──\n") + # Step 3: Apply metrics and rename + print("\n── Step 3: Apply metrics and rename ──\n") metrics_code = load_script_as_function(os.path.join(SCRIPTS_DIR, "metrics.py")) + rename_code = load_script_as_function(os.path.join(SCRIPTS_DIR, "rename.py")) for sfd_name in sfd_files: sfd_path = os.path.join(MUTATED_DIR, sfd_name) @@ -159,6 +160,7 @@ def main(): script = build_per_font_script(sfd_path, [ ("Setting vertical metrics", metrics_code), + ("Updating font names", rename_code), ]) run_fontforge_script(script) diff --git a/scripts/metrics.py b/scripts/metrics.py index cc43d57..99acb94 100644 --- a/scripts/metrics.py +++ b/scripts/metrics.py @@ -155,18 +155,19 @@ else: typo_extent = typo_ascender - typo_descender # ── OS/2 Win metrics ───────────────────────────────────────────────────────── -# Clipping boundaries on Windows. Must cover every glyph or Windows clips them. -# usWinDescent is a *positive* distance below the baseline (unlike Typo/hhea). +# Clipping boundaries on Windows. Based on the design ascender/descender +# (not the full font bbox, which can be inflated by stacked diacritics like +# Aringacute). A small margin prevents clipping of hinting artefacts. margin = int(math.ceil(upm * CLIP_MARGIN)) -win_ascent = int(math.ceil(max(font_ymax, design_top))) + margin -win_descent = int(math.ceil(max(abs(font_ymin), abs(design_bot)))) + margin +win_ascent = int(math.ceil(design_top)) + margin +win_descent = int(math.ceil(abs(design_bot))) + margin # ── hhea metrics ────────────────────────────────────────────────────────────── # macOS/iOS always uses hhea for *both* line spacing and clipping (it ignores # USE_TYPO_METRICS). To keep line height consistent across platforms, we fold # the Typo lineGap into hhea ascent/descent so hhea_lineGap can be 0. -# Then we take the max with the font bbox to also prevent Mac clipping. +# Based on design ascender/descender, not the full font bbox. half_gap = typo_linegap // 2 extra = typo_linegap - 2 * half_gap # +1 rounding remainder → ascent side @@ -174,8 +175,8 @@ extra = typo_linegap - 2 * half_gap # +1 rounding remainder → ascent sid spacing_asc = typo_ascender + half_gap + extra spacing_dsc = typo_descender - half_gap # more negative -hhea_ascent = max(spacing_asc, int(math.ceil(font_ymax)) + margin) -hhea_descent = min(spacing_dsc, int(math.floor(font_ymin)) - margin) # negative +hhea_ascent = max(spacing_asc, int(math.ceil(design_top)) + margin) +hhea_descent = min(spacing_dsc, int(math.floor(design_bot)) - margin) # negative hhea_linegap = 0 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/scripts/rename.py b/scripts/rename.py new file mode 100644 index 0000000..d056a67 --- /dev/null +++ b/scripts/rename.py @@ -0,0 +1,70 @@ +""" +FontForge: Update font name metadata +───────────────────────────────────── +Replaces Newsreader references with Readerly in all name table entries +and font-level properties. + +Run inside FontForge (or via build.py which sets `f` before running this). +""" + +import fontforge + +f = fontforge.activeFont() + +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# CONFIGURATION +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +FAMILY = "Readerly" + +# Map style suffixes to weight strings +STYLE_MAP = { + "Regular": "Regular", + "Bold": "Bold", + "Italic": "Italic", + "BoldItalic": "Bold Italic", +} + +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# DETECT STYLE +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +# Determine style from the current fontname (e.g. "Readerly-BoldItalic") +style_suffix = f.fontname.split("-")[-1] if "-" in f.fontname else "Regular" +style_display = STYLE_MAP.get(style_suffix, style_suffix) + +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# UPDATE FONT PROPERTIES +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +f.fontname = f"{FAMILY}-{style_suffix}" +f.familyname = FAMILY +f.fullname = f"{FAMILY} {style_display}" + +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# UPDATE SFNT NAME TABLE +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +lang = "English (US)" + +f.appendSFNTName(lang, "Family", FAMILY) +f.appendSFNTName(lang, "SubFamily", style_display) +f.appendSFNTName(lang, "Fullname", f"{FAMILY} {style_display}") +f.appendSFNTName(lang, "PostScriptName", f"{FAMILY}-{style_suffix}") +f.appendSFNTName(lang, "Preferred Family", FAMILY) +f.appendSFNTName(lang, "Preferred Styles", style_display) +f.appendSFNTName(lang, "Compatible Full", f"{FAMILY} {style_display}") +f.appendSFNTName(lang, "UniqueID", f"{FAMILY} {style_display}") + +# Clear Newsreader-specific entries +f.appendSFNTName(lang, "Trademark", "") +f.appendSFNTName(lang, "Manufacturer", "") +f.appendSFNTName(lang, "Designer", "") +f.appendSFNTName(lang, "Vendor URL", "") +f.appendSFNTName(lang, "Designer URL", "") + +count = 0 +for name in f.sfnt_names: + count += 1 +print(f" Updated {count} name entries for {FAMILY} {style_display}") +print("Done.") diff --git a/scripts/scale.py b/scripts/scale.py index 8cda262..c4e7cf1 100644 --- a/scripts/scale.py +++ b/scripts/scale.py @@ -24,7 +24,7 @@ f = fontforge.activeFont() # CONFIGURATION # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -SCALE_X = 1.0 +SCALE_X = 1.03 SCALE_Y = 1.10 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━