feat: download and self-host Google Fonts woff2 files

- Created scripts/download-fonts.py: downloads Google Fonts woff2 files (latin subset only) using urllib.request with Chrome User-Agent header
- Parses CSS response for latin @font-face blocks, extracts family/weight/style/URL metadata
- Downloads to src/fonts/ with naming convention {family}-{weight}{-italic}.woff2
- Created src/styles/fonts.css: 8 @font-face declarations for:
  * Source Serif 4 (400, 600, 400-italic) — prose voice
  * Inter (400, 500, 600, 700) — structure voice
  * JetBrains Mono (400) — evidence voice
- All fonts use format('woff2'), font-display: swap, and relative URLs
- Fonts placed in src/fonts/ for Vite processing at build time

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This commit is contained in:
Ken
2026-05-26 22:39:10 +00:00
parent b302f8d910
commit e100119e3b
11 changed files with 403 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Download Google Fonts woff2 files (latin subset only) for self-hosting.
Fonts downloaded:
- Source Serif 4: 400, 600, 400-italic
- Inter: 400, 500, 600, 700
- JetBrains Mono: 400
Files are saved to src/fonts/ with naming: {family}-{weight}{-italic}.woff2
"""
import os
import re
import urllib.request
# Chrome User-Agent to get woff2 format from Google Fonts CSS API
USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
)
# Google Fonts CSS2 API URLs
FONT_REQUESTS = [
# Source Serif 4: 400 normal, 600 normal, 400 italic
(
"https://fonts.googleapis.com/css2?"
"family=Source+Serif+4:ital,wght@0,400;0,600;1,400"
"&display=swap"
),
# Inter: 400, 500, 600, 700
(
"https://fonts.googleapis.com/css2?"
"family=Inter:wght@400;500;600;700"
"&display=swap"
),
# JetBrains Mono: 400
(
"https://fonts.googleapis.com/css2?"
"family=JetBrains+Mono:wght@400"
"&display=swap"
),
]
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
FONTS_DIR = os.path.join(PROJECT_ROOT, "src", "fonts")
def fetch_css(url):
"""Fetch CSS from Google Fonts with Chrome User-Agent to get woff2."""
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req) as resp:
return resp.read().decode("utf-8")
def parse_latin_font_faces(css_text):
"""Parse CSS for latin @font-face blocks and extract metadata + URL.
Returns list of dicts with keys: family, weight, style, url
"""
results = []
# Split into blocks by looking for /* latin */ comment followed by @font-face
# Google Fonts CSS2 uses comments like /* latin */ before each @font-face block
parts = re.split(r'/\*\s*([^*]+?)\s*\*/', css_text)
# parts alternates: [text_before, comment1, text_after1, comment2, text_after2, ...]
i = 1
while i < len(parts):
subset_label = parts[i].strip()
block_text = parts[i + 1] if i + 1 < len(parts) else ""
i += 2
if subset_label != "latin":
continue
# Extract the @font-face block
face_match = re.search(r'@font-face\s*\{([^}]+)\}', block_text)
if not face_match:
continue
face_body = face_match.group(1)
# Extract font-family
family_match = re.search(r"font-family:\s*'([^']+)'", face_body)
if not family_match:
continue
family = family_match.group(1)
# Extract font-weight
weight_match = re.search(r'font-weight:\s*(\d+)', face_body)
weight = weight_match.group(1) if weight_match else "400"
# Extract font-style
style_match = re.search(r'font-style:\s*(\w+)', face_body)
style = style_match.group(1) if style_match else "normal"
# Extract woff2 URL
url_match = re.search(r"url\(([^)]+\.woff2[^)]*)\)", face_body)
if not url_match:
continue
font_url = url_match.group(1)
results.append({
"family": family,
"weight": weight,
"style": style,
"url": font_url,
})
return results
def font_filename(family, weight, style):
"""Generate filename: {family}-{weight}{-italic}.woff2"""
# Normalize family name: "Source Serif 4" -> "source-serif-4"
slug = family.lower().replace(" ", "-")
suffix = "-italic" if style == "italic" else ""
return f"{slug}-{weight}{suffix}.woff2"
def download_font(url, dest_path):
"""Download a font file."""
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req) as resp:
data = resp.read()
with open(dest_path, "wb") as f:
f.write(data)
return len(data)
def main():
os.makedirs(FONTS_DIR, exist_ok=True)
downloaded = []
for css_url in FONT_REQUESTS:
print(f"\nFetching CSS: {css_url[:80]}...")
css_text = fetch_css(css_url)
faces = parse_latin_font_faces(css_text)
print(f" Found {len(faces)} latin @font-face block(s)")
for face in faces:
filename = font_filename(face["family"], face["weight"], face["style"])
dest = os.path.join(FONTS_DIR, filename)
print(f" Downloading {filename}...", end=" ")
size = download_font(face["url"], dest)
print(f"({size:,} bytes)")
downloaded.append(filename)
print(f"\nDone! Downloaded {len(downloaded)} font files to src/fonts/:")
for f in sorted(downloaded):
print(f" {f}")
if __name__ == "__main__":
main()