133 lines
4.6 KiB
Python
Executable File
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()
|