import gradio as gr import numpy as np import os from huggingface_hub import login from sentence_transformers import SentenceTransformer, util # --- CONFIGURATION --- # Centralized place for all settings and constants. class Config: """Configuration settings for the application.""" EMBEDDING_MODEL_ID = "google/embeddinggemma-300M" PROMPT_NAME = "STS" TOP_K = 5 HF_TOKEN = os.getenv('HF_TOKEN') # --- FONT DATA --- # Comprehensive font dataset with descriptions for mood matching. FONT_DATA = [ { "name": "Playfair Display", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&display=swap", "description": "Elegant, sophisticated, editorial, high-contrast serif with dramatic flair, perfect for luxury brands and fashion magazines" }, { "name": "Inter", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap", "description": "Modern, clean, professional, highly legible sans-serif designed for digital interfaces and contemporary design" }, { "name": "Amatic SC", "family": "handwriting", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Amatic+SC:wght@400;700&display=swap", "description": "Playful, casual, handwritten, fun, child-like, informal font perfect for creative and whimsical projects" }, { "name": "Crimson Text", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600&display=swap", "description": "Classical, scholarly, academic, readable serif inspired by old-style typefaces, ideal for books and literature" }, { "name": "Roboto", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap", "description": "Friendly, approachable, geometric sans-serif with a mechanical skeleton, widely used in digital applications" }, { "name": "Dancing Script", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&display=swap", "description": "Romantic, flowing, elegant script font perfect for wedding invitations, greeting cards, and feminine designs" }, { "name": "Oswald", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Oswald:wght@300;400;600&display=swap", "description": "Bold, condensed, impactful sans-serif with strong presence, ideal for headlines and masculine designs" }, { "name": "Lora", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Lora:wght@400;600&display=swap", "description": "Warm, friendly, contemporary serif with calligraphic roots, perfect for body text and storytelling" }, { "name": "Montserrat", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600&display=swap", "description": "Urban, modern, versatile sans-serif inspired by Buenos Aires signage, great for branding and corporate use" }, { "name": "Pacifico", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Pacifico&display=swap", "description": "Surfing, California, retro, casual script font with beach vibes and laid-back summer feeling" }, { "name": "Source Code Pro", "family": "monospace", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400;600&display=swap", "description": "Technical, programming, coding, monospaced font designed for developers and technical documentation" }, { "name": "Merriweather", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap", "description": "Traditional, readable, pleasant serif designed for comfortable reading on screens and in print" }, { "name": "Abril Fatface", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap", "description": "Bold, dramatic, high-contrast display serif inspired by French and Italian typography, perfect for headlines" }, { "name": "Great Vibes", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap", "description": "Elegant, formal, calligraphic script with sophisticated curves, ideal for luxury and premium branding" }, { "name": "Raleway", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;600&display=swap", "description": "Sophisticated, thin, elegant sans-serif with distinctive 'W', perfect for fashion and high-end design" }, { "name": "Fredoka One", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap", "description": "Friendly, rounded, playful display font perfect for children's content, toys, and fun applications" }, { "name": "Libre Baskerville", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&display=swap", "description": "Classic, traditional, scholarly serif based on American Type Founder's Baskerville, perfect for academic texts" }, { "name": "Poppins", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap", "description": "Geometric, modern, friendly sans-serif with circular forms, popular for contemporary web design" }, { "name": "Lobster", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Lobster&display=swap", "description": "Bold, retro, vintage script font with a 1950s diner feel, perfect for nostalgic and Americana designs" }, { "name": "Open Sans", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600&display=swap", "description": "Neutral, friendly, optimistic sans-serif designed for legibility across interfaces, print, and web" }, { "name": "Shadows Into Light", "family": "handwriting", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Shadows+Into+Light&display=swap", "description": "Casual, handwritten, personal font that feels like natural handwriting with a marker or pen" }, { "name": "Creepster", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Creepster&display=swap", "description": "Horror, scary, Halloween, gothic font with dripping effect, perfect for spooky and thriller themes" }, { "name": "Righteous", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Righteous&display=swap", "description": "Futuristic, sci-fi, technology, bold display font with unique character shapes for modern designs" }, { "name": "Satisfy", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Satisfy&display=swap", "description": "Casual, relaxed, handwritten script with natural flow, perfect for personal and informal communications" }, { "name": "Anton", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Anton&display=swap", "description": "Bold, condensed, impactful sans-serif perfect for headlines, posters, and attention-grabbing text" }, { "name": "Courgette", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Courgette&display=swap", "description": "French, bistro, café, elegant script font with continental European charm and sophistication" }, { "name": "Indie Flower", "family": "handwriting", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap", "description": "Indie, hipster, handwritten font with quirky personality, perfect for creative and artistic projects" }, { "name": "PT Serif", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=PT+Serif:wght@400;700&display=swap", "description": "Russian, Cyrillic, transitional serif with excellent readability for both Latin and Cyrillic scripts" }, { "name": "Questrial", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Questrial&display=swap", "description": "Simple, clean, minimal sans-serif with subtle quirks, perfect for modern and understated designs" }, { "name": "Bangers", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Bangers&display=swap", "description": "Comic book, superhero, pop art font inspired by mid-20th century comic books and advertisements" }, { "name": "Sacramento", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Sacramento&display=swap", "description": "Monoline, cursive script with vintage charm, perfect for elegant and sophisticated branding" }, { "name": "Bitter", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Bitter:wght@400;700&display=swap", "description": "Contemporary, slab serif with slight contrast, designed for comfortable reading in long texts" } ] # --- CORE LOGIC --- # Encapsulated in a class to manage state (model, embeddings) cleanly. class FontMoodGenerator: """Handles model loading, embedding generation, and font palette creation.""" def __init__(self, config: Config, font_data: list[dict[str, any]]): """Initializes the generator, logs in, and loads necessary assets.""" self.config = config self.font_data = font_data self._login_to_hf() self.embedding_model = self._load_model() self.font_embeddings = self._precompute_font_embeddings() def _login_to_hf(self): """Logs into Hugging Face Hub if a token is provided.""" if self.config.HF_TOKEN: print("Logging into Hugging Face Hub...") login(token=self.config.HF_TOKEN) else: print("HF_TOKEN not found. Proceeding without login.") print("Note: This may fail if the model is gated.") def _load_model(self) -> SentenceTransformer: """Loads the Sentence Transformer model.""" print(f"Initializing embedding model: {self.config.EMBEDDING_MODEL_ID}...") try: return SentenceTransformer(self.config.EMBEDDING_MODEL_ID) except Exception as e: print(f"Error loading model: {e}") raise def _precompute_font_embeddings(self) -> np.ndarray: """Generates and stores embeddings for the font descriptions.""" print("Pre-computing embeddings for font palette...") font_texts = [ f"{font['name']}, {font['description']}" for font in self.font_data ] embeddings = self.embedding_model.encode( font_texts, prompt_name=self.config.PROMPT_NAME ) print("Embeddings computed successfully.") return embeddings def _format_palette_as_html(self, top_hits: list[dict[str, any]]) -> str: """Formats the top font hits into a displayable HTML string.""" if not top_hits: return "
Could not generate a font palette. Please try another mood.
" # Sample texts for different font types sample_texts = [ "The Quick Brown Fox Jumps Over The Lazy Dog", "Sphinx of black quartz, judge my vow", "How vexingly quick daft zebras jump!", "Pack my box with five dozen liquor jugs", "Waltz, bad nymph, for quick jigs vex" ] cards_html = "" for i, hit in enumerate(top_hits): font_info = self.font_data[hit['corpus_id']] font_name = font_info['name'] font_family = font_info['family'] score = hit['score'] sample_text = sample_texts[i % len(sample_texts)] cards_html += f"""Please enter a mood or a description.
", "" mood_embedding = self.embedding_model.encode( mood_text, prompt_name=self.config.PROMPT_NAME ) top_hits = util.semantic_search( mood_embedding, self.font_embeddings, top_k=self.config.TOP_K )[0] palette_html = self._format_palette_as_html(top_hits) theme_css = self._create_dynamic_theme_css(top_hits) return palette_html, theme_css def clear_theme(self) -> tuple[str, str]: return "", "" # --- GRADIO UI --- # Defines and launches the web interface. def create_ui(generator: FontMoodGenerator): """Creates the Gradio web interface.""" with gr.Blocks(theme="ocean") as demo: # This invisible component will hold our dynamic CSS dynamic_css_output = gr.HTML() gr.Markdown(""" # 📝 Font Mood Generator Describe a mood, a scene, or a feeling, and get a matching font palette.