Prepare for initial release
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
out
|
out
|
||||||
mutated
|
src_processed
|
||||||
30
README.md
30
README.md
@@ -10,21 +10,23 @@ To accomplish this, I wanted to start from the 9pt font, which I exported. Then,
|
|||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
- `./src`: folder containing all Readerly source files
|
- `./src`: source .sfd font files (Newsreader 9pt, renamed to Readerly)
|
||||||
- `./scripts`: some experimental scripts
|
- `./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)
|
python3 build.py
|
||||||
- This should apply to all fonts
|
```
|
||||||
- A separate "export" script should be added that generates TTF fonts (with old style kerning)
|
|
||||||
|
|
||||||
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
|
1. Copy `./src` to `./src_processed`
|
||||||
- Copy the ./src files to ./mutated
|
2. Scale lowercase glyphs (configurable in `scripts/scale.py`)
|
||||||
- Apply the edits mentioned above
|
3. Set vertical metrics and update font names
|
||||||
- Export the fonts to TTF in ./out
|
4. Export to TTF with old-style kerning in `./out`
|
||||||
|
|
||||||
I will then manually review the fonts.
|
|
||||||
|
|||||||
14
build.py
14
build.py
@@ -4,7 +4,7 @@ Readerly Build Script
|
|||||||
─────────────────────
|
─────────────────────
|
||||||
Orchestrates the full font build pipeline:
|
Orchestrates the full font build pipeline:
|
||||||
|
|
||||||
1. Copies ./src/*.sfd → ./mutated/
|
1. Copies ./src/*.sfd → ./src_processed/
|
||||||
2. Applies vertical scale (scale.py)
|
2. Applies vertical scale (scale.py)
|
||||||
3. Applies vertical metrics (metrics.py)
|
3. Applies vertical metrics (metrics.py)
|
||||||
4. Exports to TTF with old-style kern table → ./out/
|
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__))
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
SRC_DIR = os.path.join(ROOT_DIR, "src")
|
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")
|
OUT_DIR = os.path.join(ROOT_DIR, "out")
|
||||||
SCRIPTS_DIR = os.path.join(ROOT_DIR, "scripts")
|
SCRIPTS_DIR = os.path.join(ROOT_DIR, "scripts")
|
||||||
|
|
||||||
@@ -123,8 +123,8 @@ def main():
|
|||||||
print(" Readerly Build")
|
print(" Readerly Build")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
# Step 1: Copy src → mutated
|
# Step 1: Copy src → src_processed
|
||||||
print("\n── Step 1: Copy sources to ./mutated ──\n")
|
print("\n── Step 1: Copy sources to ./src_processed ──\n")
|
||||||
if os.path.exists(MUTATED_DIR):
|
if os.path.exists(MUTATED_DIR):
|
||||||
shutil.rmtree(MUTATED_DIR)
|
shutil.rmtree(MUTATED_DIR)
|
||||||
shutil.copytree(SRC_DIR, MUTATED_DIR)
|
shutil.copytree(SRC_DIR, MUTATED_DIR)
|
||||||
@@ -147,10 +147,11 @@ def main():
|
|||||||
])
|
])
|
||||||
run_fontforge_script(script)
|
run_fontforge_script(script)
|
||||||
|
|
||||||
# Step 3: Apply metrics.py to each font
|
# Step 3: Apply metrics and rename
|
||||||
print("\n── Step 3: Apply vertical metrics ──\n")
|
print("\n── Step 3: Apply metrics and rename ──\n")
|
||||||
|
|
||||||
metrics_code = load_script_as_function(os.path.join(SCRIPTS_DIR, "metrics.py"))
|
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:
|
for sfd_name in sfd_files:
|
||||||
sfd_path = os.path.join(MUTATED_DIR, sfd_name)
|
sfd_path = os.path.join(MUTATED_DIR, sfd_name)
|
||||||
@@ -159,6 +160,7 @@ def main():
|
|||||||
|
|
||||||
script = build_per_font_script(sfd_path, [
|
script = build_per_font_script(sfd_path, [
|
||||||
("Setting vertical metrics", metrics_code),
|
("Setting vertical metrics", metrics_code),
|
||||||
|
("Updating font names", rename_code),
|
||||||
])
|
])
|
||||||
run_fontforge_script(script)
|
run_fontforge_script(script)
|
||||||
|
|
||||||
|
|||||||
@@ -155,18 +155,19 @@ else:
|
|||||||
typo_extent = typo_ascender - typo_descender
|
typo_extent = typo_ascender - typo_descender
|
||||||
|
|
||||||
# ── OS/2 Win metrics ─────────────────────────────────────────────────────────
|
# ── OS/2 Win metrics ─────────────────────────────────────────────────────────
|
||||||
# Clipping boundaries on Windows. Must cover every glyph or Windows clips them.
|
# Clipping boundaries on Windows. Based on the design ascender/descender
|
||||||
# usWinDescent is a *positive* distance below the baseline (unlike Typo/hhea).
|
# (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))
|
margin = int(math.ceil(upm * CLIP_MARGIN))
|
||||||
win_ascent = int(math.ceil(max(font_ymax, design_top))) + margin
|
win_ascent = int(math.ceil(design_top)) + margin
|
||||||
win_descent = int(math.ceil(max(abs(font_ymin), abs(design_bot)))) + margin
|
win_descent = int(math.ceil(abs(design_bot))) + margin
|
||||||
|
|
||||||
# ── hhea metrics ──────────────────────────────────────────────────────────────
|
# ── hhea metrics ──────────────────────────────────────────────────────────────
|
||||||
# macOS/iOS always uses hhea for *both* line spacing and clipping (it ignores
|
# 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
|
# 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.
|
# 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
|
half_gap = typo_linegap // 2
|
||||||
extra = typo_linegap - 2 * half_gap # +1 rounding remainder → ascent side
|
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_asc = typo_ascender + half_gap + extra
|
||||||
spacing_dsc = typo_descender - half_gap # more negative
|
spacing_dsc = typo_descender - half_gap # more negative
|
||||||
|
|
||||||
hhea_ascent = max(spacing_asc, int(math.ceil(font_ymax)) + margin)
|
hhea_ascent = max(spacing_asc, int(math.ceil(design_top)) + margin)
|
||||||
hhea_descent = min(spacing_dsc, int(math.floor(font_ymin)) - margin) # negative
|
hhea_descent = min(spacing_dsc, int(math.floor(design_bot)) - margin) # negative
|
||||||
hhea_linegap = 0
|
hhea_linegap = 0
|
||||||
|
|
||||||
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|||||||
70
scripts/rename.py
Normal file
70
scripts/rename.py
Normal file
@@ -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.")
|
||||||
@@ -24,7 +24,7 @@ f = fontforge.activeFont()
|
|||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
SCALE_X = 1.0
|
SCALE_X = 1.03
|
||||||
SCALE_Y = 1.10
|
SCALE_Y = 1.10
|
||||||
|
|
||||||
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|||||||
Reference in New Issue
Block a user