fix: production-level improvements to typography_analyzer

Co-authored-by: blackboxprogramming <118287761+blackboxprogramming@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-03 11:42:15 +00:00
parent baa4e48c53
commit a80231c8c8
5 changed files with 79 additions and 24 deletions

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
*.egg-info/
dist/
build/
.coverage
htmlcov/

View File

@@ -15,6 +15,7 @@ import sys
import uuid
import argparse
import datetime
import urllib.parse
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Dict, List, Literal, Optional, Tuple
@@ -54,12 +55,18 @@ class Font:
tags: List[str] = field(default_factory=list)
created_at: str = ""
def __post_init__(self) -> None:
if self.category not in CATEGORIES:
raise ValueError(
f"Invalid category {self.category!r}; must be one of {CATEGORIES}"
)
def google_url(self, text: str = "") -> str:
family = self.google_font_id or self.name.replace(" ", "+")
wt = ":wght@" + ";".join(str(w) for w in sorted(self.weights))
url = GOOGLE_FONTS_BASE.format(family + wt)
if text:
url += f"&text={text[:50]}"
url += "&text=" + urllib.parse.quote(text[:50])
return url
def css_import(self) -> str:
@@ -292,7 +299,14 @@ def _linearize_channel(c: int) -> float:
def relative_luminance(hex_color: str) -> float:
h = hex_color.lstrip("#")
r = int(h[0:2], 16); g = int(h[2:4], 16); b = int(h[4:6], 16)
if len(h) == 3:
h = "".join(c * 2 for c in h) # expand #rgb → #rrggbb
if len(h) != 6:
raise ValueError(f"Invalid hex color: {hex_color!r}")
try:
r = int(h[0:2], 16); g = int(h[2:4], 16); b = int(h[4:6], 16)
except ValueError:
raise ValueError(f"Invalid hex color: {hex_color!r}")
return (0.2126 * _linearize_channel(r) +
0.7152 * _linearize_channel(g) +
0.0722 * _linearize_channel(b))
@@ -357,28 +371,29 @@ def suggest_pairing(font: Font, db_path: Path = DB_PATH) -> dict:
"""Suggest complementary fonts for a given font, using DB first then defaults."""
rules = _PAIRING_RULES.get(font.category, [])
suggestions = []
for rule in rules:
target_cat = rule["category"]
# Try DB first
conn = _db(db_path)
row = conn.execute(
"SELECT id,name,category FROM fonts WHERE category=? AND id!=? LIMIT 1",
(target_cat, font.id),
).fetchone()
conn.close()
conn = _db(db_path)
try:
for rule in rules:
target_cat = rule["category"]
row = conn.execute(
"SELECT id,name,category FROM fonts WHERE category=? AND id!=? LIMIT 1",
(target_cat, font.id),
).fetchone()
if row:
suggestions.append({
"font_id": row[0], "name": row[1], "category": row[2],
"reason": rule["reason"], "source": "database",
})
else:
defaults = _DEFAULTS.get(target_cat, [])
if defaults:
if row:
suggestions.append({
"font_id": None, "name": defaults[0], "category": target_cat,
"reason": rule["reason"], "source": "built-in",
"font_id": row[0], "name": row[1], "category": row[2],
"reason": rule["reason"], "source": "database",
})
else:
defaults = _DEFAULTS.get(target_cat, [])
if defaults:
suggestions.append({
"font_id": None, "name": defaults[0], "category": target_cat,
"reason": rule["reason"], "source": "built-in",
})
finally:
conn.close()
return {
"base_font": {"id": font.id, "name": font.name, "category": font.category},
@@ -441,7 +456,7 @@ def save_font(font: Font, db_path: Path = DB_PATH) -> None:
",".join(str(w) for w in font.weights),
font.google_font_id,
",".join(font.tags),
font.created_at or datetime.datetime.utcnow().isoformat()),
font.created_at or datetime.datetime.now(datetime.timezone.utc).isoformat()),
)
conn.commit(); conn.close()
@@ -578,7 +593,7 @@ examples:
weights=[int(w.strip()) for w in args.weights.split(",") if w.strip()],
google_font_id=args.google_font_id,
tags=[t.strip() for t in args.tags.split(",") if t.strip()],
created_at=datetime.datetime.utcnow().isoformat(),
created_at=datetime.datetime.now(datetime.timezone.utc).isoformat(),
)
save_font(font)
print(f"✅ added font '{font.name}'{font.id}")

View File

@@ -145,13 +145,43 @@ def test_relative_luminance_white():
def test_relative_luminance_black():
assert abs(relative_luminance("#000000")) < 0.001
def test_relative_luminance_shorthand():
# #fff should expand to #ffffff
assert abs(relative_luminance("#fff") - 1.0) < 0.001
def test_relative_luminance_invalid_raises():
with pytest.raises(ValueError):
relative_luminance("#xyz123")
def test_relative_luminance_nonhex_chars():
with pytest.raises(ValueError):
relative_luminance("#gggggg")
def test_relative_luminance_wrong_length():
with pytest.raises(ValueError):
relative_luminance("#12345")
# ── Font category validation ──────────────────────────────────────────────────
def test_font_invalid_category_raises():
with pytest.raises(ValueError):
make_font(category="invalid-category")
# ── URL encoding in google_url ────────────────────────────────────────────────
def test_google_url_text_encoded():
f = make_font()
url = f.google_url(text="hello world & more")
assert " " not in url
assert "&text=" in url
assert "hello%20world" in url
# ── Font dataclass ────────────────────────────────────────────────────────────
def make_font(**kw) -> Font:
defaults = dict(
id=str(uuid.uuid4()), name="Inter", category="sans-serif",
weights=[400, 700], google_font_id="Inter",
tags=["modern"], created_at=datetime.datetime.utcnow().isoformat(),
tags=["modern"], created_at=datetime.datetime.now(datetime.timezone.utc).isoformat(),
)
defaults.update(kw)
return Font(**defaults)