Fonts: every Google font the editor offers (13 families, latin and latin-ext, 1.2 MB) now lives in public/fonts; the layout inlines @font-face rules only for the fonts in use, built from src/lib/font-faces.json. scripts/fonts-holen.py re-fetches them. The admin font preview loads the same files via /admin/fonts and /admin/api/font-faces. Checked: no request leaves larpaso.de on the start page, Impressum or Datenschutz, nor in the admin editor. nginx: access_log off, so visitor IPs are not stored. Caddy has no access log configured either. Privacy policy replaces the placeholder and describes only what the site actually does: controller (from the Impressum), hosting at netcup in Nuremberg (from the server IP), no access logs, HTTPS, mailto contact form and e-mail, self-hosted fonts and media, no cookies, data subject rights and the Lower Saxony supervisory authority. The closing note is no longer styled as a warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30 lines
2.1 KiB
Python
30 lines
2.1 KiB
Python
import re, json, urllib.request, urllib.error, pathlib
|
|
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36"
|
|
FAMS = ['Playfair Display','Lora','Merriweather','Libre Baskerville','DM Serif Display','Inter','Roboto','Open Sans','Poppins','Montserrat','Nunito','Work Sans','Source Sans 3']
|
|
out_dir = pathlib.Path('public/fonts'); out_dir.mkdir(parents=True, exist_ok=True)
|
|
manifest = {}
|
|
def get(url):
|
|
return urllib.request.urlopen(urllib.request.Request(url, headers={'User-Agent': UA})).read()
|
|
for fam in FAMS:
|
|
css = None
|
|
for w in ['400;500;600;700', '400;700', '400']:
|
|
try:
|
|
css = get(f"https://fonts.googleapis.com/css2?family={fam.replace(' ','+')}:wght@{w}&display=swap").decode(); break
|
|
except urllib.error.HTTPError: pass
|
|
slug = fam.lower().replace(' ', '-'); d = out_dir / slug; d.mkdir(exist_ok=True)
|
|
faces = []; seen = {}
|
|
for subset, body in re.findall(r'/\* ([\w-]+) \*/\s*@font-face \{(.*?)\}', css, re.S):
|
|
if subset not in ('latin', 'latin-ext'): continue
|
|
weight = re.search(r'font-weight: (\d+)', body).group(1)
|
|
url = re.search(r'url\((.*?)\)', body).group(1)
|
|
rng = re.search(r'unicode-range: (.*?);', body).group(1)
|
|
if url not in seen:
|
|
fn = f"{slug}-{subset}-{len(seen)}.woff2"; (d / fn).write_bytes(get(url)); seen[url] = fn
|
|
faces.append({'weight': int(weight), 'subset': subset, 'file': f"/fonts/{slug}/{seen[url]}", 'unicodeRange': rng})
|
|
# gleiche Datei fuer mehrere Gewichte (variable Schrift) -> eine Regel mit Gewichtsbereich
|
|
merged = {}
|
|
for f in faces:
|
|
k = (f['file'], f['subset']); merged.setdefault(k, dict(f, weights=[]))['weights'].append(f['weight'])
|
|
manifest[fam] = [{'file': v['file'], 'weight': f"{min(v['weights'])} {max(v['weights'])}" if len(v['weights']) > 1 else str(v['weights'][0]), 'unicodeRange': v['unicodeRange']} for v in merged.values()]
|
|
print(fam, len(manifest[fam]), 'Regeln', len(seen), 'Dateien')
|
|
json.dump(manifest, open('src/lib/font-faces.json', 'w', encoding='utf8'), indent=2)
|