ozymandias/tools/subset-fonts.sh

146 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# subset-fonts.sh — Subset and convert TTF system fonts to WOFF2 for web use.
#
# Requires: fonttools (pyftsubset) — `pip install fonttools brotli`
# Output: static/fonts/
#
# ─── Font directories ────────────────────────────────────────────────────────
# Override any of these via the matching env var when your distro lays the
# fonts out differently. The script tries each candidate path in order and
# fails with a clear message if none resolve.
#
# SPECTRAL_DIR Spectral-{Regular,Italic,SemiBold,SemiBoldItalic,Bold,BoldItalic}.ttf
# FIRA_DIR FiraSans-{Regular,SemiBold}.ttf
# JBM_DIR JetBrainsMono-{Regular,Italic}.ttf
#
# Common locations:
# Arch: /usr/share/fonts/ttf-spectral, /usr/share/fonts/TTF
# Debian: /usr/share/fonts/truetype/spectral, /usr/share/fonts/truetype/firasans,
# /usr/share/fonts/truetype/jetbrains-mono
# macOS (brew): /opt/homebrew/share/fonts, ~/Library/Fonts
# Manual: wherever you unzipped the upstream releases
set -euo pipefail
OUTDIR="$(dirname "$0")/../static/fonts"
mkdir -p "$OUTDIR"
# ---------------------------------------------------------------------------
# Locate font directories (env var override > candidate list > error)
# ---------------------------------------------------------------------------
# Resolve a font dir by checking, in order:
# 1. The override env var (passed by name), if non-empty
# 2. Each candidate path
# Sets the global RESOLVED_DIR on success; exits with a descriptive message
# on failure. Sets the global rather than echoing because `exit 1` inside a
# `$(...)` substitution only exits the subshell, not the script.
resolve_font_dir() {
local label="$1" # human-readable name, e.g. "Spectral"
local env_var="$2" # env var name, e.g. "SPECTRAL_DIR"
shift 2
local candidates=("$@")
# Indirection: ${!env_var} reads the env var named in $env_var.
if [ -n "${!env_var:-}" ]; then
if [ ! -d "${!env_var}" ]; then
echo "Error: $env_var=${!env_var} is not a directory." >&2
exit 1
fi
RESOLVED_DIR="${!env_var}"
return
fi
for dir in "${candidates[@]}"; do
if [ -d "$dir" ]; then
RESOLVED_DIR="$dir"
return
fi
done
echo "Error: $label fonts not found in any of:" >&2
printf ' %s\n' "${candidates[@]}" >&2
echo "Set $env_var to point at the directory containing the .ttf files." >&2
exit 1
}
resolve_font_dir "Spectral" "SPECTRAL_DIR" \
/usr/share/fonts/ttf-spectral \
/usr/share/fonts/truetype/spectral \
/opt/homebrew/share/fonts \
"$HOME/Library/Fonts"
SPECTRAL="$RESOLVED_DIR"
resolve_font_dir "Fira Sans" "FIRA_DIR" \
/usr/share/fonts/TTF \
/usr/share/fonts/truetype/firasans \
/usr/share/fonts/truetype/fira-sans \
/opt/homebrew/share/fonts \
"$HOME/Library/Fonts"
FIRA="$RESOLVED_DIR"
resolve_font_dir "JetBrains Mono" "JBM_DIR" \
/usr/share/fonts/TTF \
/usr/share/fonts/truetype/jetbrains-mono \
/usr/share/fonts/truetype/jetbrainsmono \
/opt/homebrew/share/fonts \
"$HOME/Library/Fonts"
JBM="$RESOLVED_DIR"
# ---------------------------------------------------------------------------
# Subset configuration
# ---------------------------------------------------------------------------
# Unicode range: Latin, Latin Extended-A, General Punctuation, common symbols
UNICODES="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"
SPECTRAL_FEATURES="liga,dlig,smcp,c2sc,onum,lnum,pnum,tnum,frac,ordn,sups,subs,ss01,ss02,ss03,ss04,ss05,kern"
FIRA_FEATURES="smcp,liga,kern"
JBM_FEATURES="liga,kern,calt"
# ---------------------------------------------------------------------------
# Subset helper — fails fast if a source file is missing
# ---------------------------------------------------------------------------
subset() {
local src="$1" dest="$2" features="$3"
if [ ! -f "$src" ]; then
echo "Error: source font not found: $src" >&2
echo " Adjust the directory env var (SPECTRAL_DIR / FIRA_DIR / JBM_DIR)" >&2
echo " or install the corresponding font package." >&2
exit 1
fi
echo "$(basename "$dest")"
pyftsubset "$src" \
--output-file="$dest" \
--flavor=woff2 \
--layout-features="$features" \
--unicodes="$UNICODES" \
--no-hinting \
--desubroutinize
}
# ---------------------------------------------------------------------------
# Run
# ---------------------------------------------------------------------------
echo "Spectral... ($SPECTRAL)"
subset "$SPECTRAL/Spectral-Regular.ttf" "$OUTDIR/spectral-regular.woff2" "$SPECTRAL_FEATURES"
subset "$SPECTRAL/Spectral-Italic.ttf" "$OUTDIR/spectral-italic.woff2" "$SPECTRAL_FEATURES"
subset "$SPECTRAL/Spectral-SemiBold.ttf" "$OUTDIR/spectral-semibold.woff2" "$SPECTRAL_FEATURES"
subset "$SPECTRAL/Spectral-SemiBoldItalic.ttf" "$OUTDIR/spectral-semibold-italic.woff2" "$SPECTRAL_FEATURES"
subset "$SPECTRAL/Spectral-Bold.ttf" "$OUTDIR/spectral-bold.woff2" "$SPECTRAL_FEATURES"
subset "$SPECTRAL/Spectral-BoldItalic.ttf" "$OUTDIR/spectral-bold-italic.woff2" "$SPECTRAL_FEATURES"
echo "Fira Sans... ($FIRA)"
subset "$FIRA/FiraSans-Regular.ttf" "$OUTDIR/fira-sans-regular.woff2" "$FIRA_FEATURES"
subset "$FIRA/FiraSans-SemiBold.ttf" "$OUTDIR/fira-sans-semibold.woff2" "$FIRA_FEATURES"
echo "JetBrains Mono... ($JBM)"
subset "$JBM/JetBrainsMono-Regular.ttf" "$OUTDIR/jetbrains-mono-regular.woff2" "$JBM_FEATURES"
subset "$JBM/JetBrainsMono-Italic.ttf" "$OUTDIR/jetbrains-mono-italic.woff2" "$JBM_FEATURES"
echo ""
echo "Done. Output:"
ls -lh "$OUTDIR"/*.woff2