v2.0.0: Modularize project into single-responsibility packages
Replaced monolithic modules/ package with a clean architecture:
- core/ config.py, utils.py
- ai/ models.py (ResilientModel), setup.py (init_models)
- story/ planner.py, writer.py, editor.py, style_persona.py, bible_tracker.py
- marketing/ cover.py, blurb.py, fonts.py, assets.py
- export/ exporter.py
- web/ app.py (Flask factory), db.py, helpers.py, tasks.py, routes/{auth,project,run,persona,admin}.py
- cli/ engine.py (run_generation), wizard.py (BookWizard)
Flask routes split into 5 Blueprints; all templates updated with blueprint-
prefixed url_for() calls. Dockerfile and docker-compose updated to use
web.app entry point and new package paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
55
marketing/fonts.py
Normal file
55
marketing/fonts.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
import requests
|
||||
from core import config, utils
|
||||
|
||||
|
||||
def download_font(font_name):
|
||||
if not font_name: font_name = "Roboto"
|
||||
if not os.path.exists(config.FONTS_DIR): os.makedirs(config.FONTS_DIR)
|
||||
|
||||
if "," in font_name: font_name = font_name.split(",")[0].strip()
|
||||
|
||||
if font_name.lower().endswith(('.ttf', '.otf')):
|
||||
font_name = os.path.splitext(font_name)[0]
|
||||
|
||||
font_name = font_name.strip().strip("'").strip('"')
|
||||
for suffix in ["-Regular", " Regular", " regular", "Regular", " Bold", " Italic"]:
|
||||
if font_name.endswith(suffix):
|
||||
font_name = font_name[:-len(suffix)]
|
||||
font_name = font_name.strip()
|
||||
|
||||
clean_name = font_name.replace(" ", "").lower()
|
||||
font_filename = f"{clean_name}.ttf"
|
||||
font_path = os.path.join(config.FONTS_DIR, font_filename)
|
||||
|
||||
if os.path.exists(font_path) and os.path.getsize(font_path) > 1000:
|
||||
utils.log("ASSETS", f"Using cached font: {font_path}")
|
||||
return font_path
|
||||
|
||||
utils.log("ASSETS", f"Downloading font: {font_name}...")
|
||||
compact_name = font_name.replace(" ", "")
|
||||
title_compact = "".join(x.title() for x in font_name.split())
|
||||
|
||||
patterns = [
|
||||
f"static/{title_compact}-Regular.ttf", f"{title_compact}-Regular.ttf",
|
||||
f"{title_compact}[wght].ttf", f"{title_compact}[wdth,wght].ttf",
|
||||
f"static/{compact_name}-Regular.ttf", f"{compact_name}-Regular.ttf",
|
||||
f"{title_compact}-Regular.otf",
|
||||
]
|
||||
|
||||
headers = {"User-Agent": "Mozilla/5.0 (BookApp/1.0)"}
|
||||
for license_type in ["ofl", "apache", "ufl"]:
|
||||
base_url = f"https://github.com/google/fonts/raw/main/{license_type}/{clean_name}"
|
||||
for pattern in patterns:
|
||||
try:
|
||||
r = requests.get(f"{base_url}/{pattern}", headers=headers, timeout=5)
|
||||
if r.status_code == 200 and len(r.content) > 1000:
|
||||
with open(font_path, 'wb') as f: f.write(r.content)
|
||||
utils.log("ASSETS", f"✅ Downloaded {font_name} to {font_path}")
|
||||
return font_path
|
||||
except Exception: continue
|
||||
|
||||
if clean_name != "roboto":
|
||||
utils.log("ASSETS", f"⚠️ Font '{font_name}' not found. Falling back to Roboto.")
|
||||
return download_font("Roboto")
|
||||
return None
|
||||
Reference in New Issue
Block a user