Files
playground/download_google_font.py
T

133 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["httpx"]
# ///
import argparse
import logging
import re
from pathlib import Path
import httpx
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
logger = logging.getLogger(__name__)
HEADERS = {
"sec-ch-ua": '"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"',
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("url", help="Google Fonts CSS URL")
parser.add_argument("-o", "--output", type=Path, default=Path.cwd(), help="Output directory")
parser.add_argument("--subsets", default="latin,latin-ext", help="Comma-separated unicode subsets to include")
return parser.parse_args()
def filter_css_by_subsets(css_content: str, subsets: list[str]) -> str:
"""Filter @font-face rules to only include specified unicode subsets."""
font_faces = re.findall(r"@font-face\s*{[^}]+}", css_content, re.DOTALL)
filtered_faces = []
for face in font_faces:
if any(subset in face for subset in subsets):
filtered_faces.append(face)
return "\n".join(filtered_faces)
def parse_googlefont_stylesheet(css_content: str) -> list[dict]:
"""Parse Google Fonts CSS and return list of font entries with metadata."""
comments = re.findall(r"/\*\s*([^*]+)\s*\*/", css_content)
families = re.findall(r"font-family:\s*['\"]([^'\"]+)['\"]", css_content)
styles = re.findall(r"font-style:\s*(\w+)", css_content)
weights = re.findall(r"font-weight:\s*([^;]+)", css_content)
displays = re.findall(r"font-display:\s*(\w+)", css_content)
urls = re.findall(r"url\(([^)]+)\)", css_content)
ranges = re.findall(r"unicode-range:\s*([^;]+)", css_content)
fonts = []
for comment, family, style, weight, display, url, unicode_range in zip(comments, families, styles, weights, displays, urls, ranges):
entry = {
"subset": comment.strip().lower(),
"family": family,
"style": style,
"weight": weight.strip(),
"display": display,
"url": url.strip("'\""),
"unicode_range": unicode_range.strip(),
}
fonts.append(entry)
return fonts
def download_google_fonts(url: str, output_dir: Path, subsets: list[str]) -> None:
client = httpx.Client(headers=HEADERS)
output_dir = output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
fonts_dir = output_dir / "fonts"
fonts_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"Downloading from {url}")
response = client.get(url)
response.raise_for_status()
css_content = response.text
fonts = parse_googlefont_stylesheet(css_content=css_content)
filtered_fonts = []
for font in fonts:
if font.get("subset") in subsets:
filtered_fonts.append(font)
logger.info(f"Found {len(filtered_fonts)} font entries for subsets: {', '.join(subsets)}")
for font in filtered_fonts:
font_url = font["url"]
logger.info(f"Downloading {font_url}")
font_response = client.get(url=font_url)
font_response.raise_for_status()
font_filename = Path(font_url).name
font_path = fonts_dir / font_filename
font_path.write_bytes(font_response.content)
logger.info(f"Saved to {font_path}")
css_lines = []
for font in filtered_fonts:
css_lines.append(f"/* {font['subset']} */")
css_lines.append("@font-face {")
css_lines.append(f" font-family: '{font['family']}';")
css_lines.append(f" font-style: {font['style']};")
css_lines.append(f" font-weight: {font['weight']};")
css_lines.append(f" font-display: {font['display']};")
font_filename = Path(font["url"]).name
css_lines.append(f" src: url(fonts/{font_filename}) format('woff2');")
css_lines.append(f" unicode-range: {font['unicode_range']};")
css_lines.append("}")
local_css_content = "\n".join(css_lines)
css_file = output_dir / "fonts.css"
css_file.write_text(local_css_content)
logger.info(f"CSS file saved to {css_file}")
def main() -> None:
args = parse_args()
subset_list = [s.strip() for s in args.subsets.split(",")]
download_google_fonts(
url=args.url,
output_dir=args.output,
subsets=subset_list,
)
if __name__ == "__main__":
main()