creek

The AI Image Editor of 2030

models/stylesheet.py (45333B)


   1# -*- coding: utf-8 -*-
   2"""
   3Stylesheet & Prompt Generation
   4Original file: https://colab.research.google.com/drive/1eZLWqhB0KKFLhCA0E3qDHXpdvEIH44yE
   5"""
   6
   7## Stylesheet Generation
   8
   9import json
  10import math
  11import re
  12import numpy as np
  13from collections import defaultdict
  14from typing import List, Dict, Tuple, Any, Optional
  15from sklearn.cluster import KMeans
  16
  17RANK_DECAY_FACTOR = 0.85
  18MIN_CONSENSUS_THRESHOLD = 0.05
  19PALETTE_SIZE = 5
  20
  21
  22def hex_to_rgb(hex_str: str) -> List[int]:
  23    """Converts '#FF5733' to [255, 87, 51] for math operations."""
  24    try:
  25        hex_str = hex_str.strip().lstrip("#")
  26        if len(hex_str) != 6:
  27            return [0, 0, 0]
  28        return [int(hex_str[i : i + 2], 16) for i in (0, 2, 4)]
  29    except:
  30        return [0, 0, 0]
  31
  32
  33def rgb_to_hex(rgb: List[int]) -> str:
  34    """Converts [255, 87, 51] back to '#FF5733'."""
  35    return "#{:02x}{:02x}{:02x}".format(int(rgb[0]), int(rgb[1]), int(rgb[2]))
  36
  37
  38class FontClusterEngine:
  39    def __init__(self):
  40        self.full_font_map = {
  41            "abeezee": "Sans Serif (Generic)",
  42            "abel": "Condensed Sans",
  43            "abhayalibre": "Serif (Generic)",
  44            "abrilfatface": "Modern Serif",
  45            "abyssinicasil": "Serif (Generic)",
  46            "aclonica": "Display/Other",
  47            "acme": "Display/Other",
  48            "actor": "Sans Serif (Generic)",
  49            "adamina": "Serif (Generic)",
  50            "adventpro": "Display/Other",
  51            "aguafinascript": "Script/Handwritten",
  52            "akronim": "Display/Other",
  53            "aladin": "Display/Other",
  54            "aldrich": "Display/Other",
  55            "alef": "Sans Serif (Generic)",
  56            "alegreya": "Old Style Serif",
  57            "alegreyasans": "Humanist Sans",
  58            "alegreyasanssc": "Sans Serif (Generic)",
  59            "alegreyasc": "Old Style Serif",
  60            "alexbrush": "Script/Handwritten",
  61            "alfaslabone": "Slab Serif",
  62            "alice": "Serif (Generic)",
  63            "alike": "Serif (Generic)",
  64            "alikeangular": "Serif (Generic)",
  65            "allan": "Display/Other",
  66            "allerta": "Sans Serif (Generic)",
  67            "allertastencil": "Display/Other",
  68            "allura": "Script/Handwritten",
  69            "almendra": "Display/Other",
  70            "almendradisplay": "Display/Other",
  71            "almendrasc": "Display/Other",
  72            "amarante": "Display/Other",
  73            "amaranth": "Display/Other",
  74            "amaticasc": "Script/Handwritten",
  75            "amaticsc": "Script/Handwritten",
  76            "amethysta": "Serif (Generic)",
  77            "amiko": "Sans Serif (Generic)",
  78            "amiri": "Serif (Generic)",
  79            "amita": "Script/Handwritten",
  80            "anaheim": "Sans Serif (Generic)",
  81            "andada": "Serif (Generic)",
  82            "andadasc": "Serif (Generic)",
  83            "andika": "Sans Serif (Generic)",
  84            "annieuseyourtelescope": "Script/Handwritten",
  85            "anonymouspro": "Monospace",
  86            "antic": "Sans Serif (Generic)",
  87            "anticdidone": "Serif (Generic)",
  88            "anticslab": "Slab Serif",
  89            "anton": "Grotesque Sans",
  90            "antonio": "Sans Serif (Generic)",
  91            "arapey": "Serif (Generic)",
  92            "arbutus": "Display/Other",
  93            "arbutusslab": "Slab Serif",
  94            "architectsdaughter": "Script/Handwritten",
  95            "archivo": "Grotesque Sans",
  96            "archivoblack": "Grotesque Sans",
  97            "archivonarrow": "Condensed Sans",
  98            "archivovfbeta": "Grotesque Sans",
  99            "arefruqaa": "Serif (Generic)",
 100            "arimamadurai": "Display/Other",
 101            "arimo": "Neo-Grotesque Sans",
 102            "arizonia": "Script/Handwritten",
 103            "armata": "Sans Serif (Generic)",
 104            "arsenal": "Sans Serif (Generic)",
 105            "artifika": "Serif (Generic)",
 106            "arvo": "Slab Serif",
 107            "arya": "Sans Serif (Generic)",
 108            "asap": "Rounded Sans",
 109            "asapcondensed": "Condensed Sans",
 110            "asapvfbeta": "Rounded Sans",
 111            "asar": "Serif (Generic)",
 112            "asset": "Display/Other",
 113            "assistant": "Sans Serif (Generic)",
 114            "astloch": "Display/Other",
 115            "asul": "Sans Serif (Generic)",
 116            "athiti": "Sans Serif (Generic)",
 117            "atma": "Display/Other",
 118            "atomicage": "Display/Other",
 119            "aubrey": "Display/Other",
 120            "audiowide": "Display/Other",
 121            "autourone": "Display/Other",
 122            "average": "Serif (Generic)",
 123            "averagesans": "Sans Serif (Generic)",
 124            "averiagruesalibre": "Display/Other",
 125            "averialibre": "Display/Other",
 126            "averiasanslibre": "Sans Serif (Generic)",
 127            "averiaseriflibre": "Serif (Generic)",
 128            "badscript": "Script/Handwritten",
 129            "bahiana": "Display/Other",
 130            "baloo": "Display/Other",
 131            "baloobhai": "Display/Other",
 132            "baloobhaijaan": "Display/Other",
 133            "baloobhaina": "Display/Other",
 134            "baloochettan": "Display/Other",
 135            "balooda": "Display/Other",
 136            "baloopaaji": "Display/Other",
 137            "balootamma": "Display/Other",
 138            "balootammudu": "Display/Other",
 139            "baloothambi": "Display/Other",
 140            "balthazar": "Serif (Generic)",
 141            "bangers": "Display",
 142            "barlow": "Grotesque Sans",
 143            "barlowcondensed": "Condensed Sans",
 144            "barlowsemicondensed": "Condensed Sans",
 145            "barrio": "Display/Other",
 146            "basic": "Sans Serif (Generic)",
 147            "baumans": "Display/Other",
 148            "belgrano": "Serif (Generic)",
 149            "bellefair": "Serif (Generic)",
 150            "belleza": "Sans Serif (Generic)",
 151            "benchnine": "Sans Serif (Generic)",
 152            "bentham": "Serif (Generic)",
 153            "berkshireswash": "Script/Handwritten",
 154            "bevan": "Slab Serif",
 155            "bhavuka": "Display/Other",
 156            "bigelowrules": "Display/Other",
 157            "bigshotone": "Display/Other",
 158            "bilbo": "Script/Handwritten",
 159            "bilboswashcaps": "Script/Handwritten",
 160            "biorhyme": "Slab Serif",
 161            "biorhymeexpanded": "Slab Serif",
 162            "biryani": "Sans Serif (Generic)",
 163            "bitter": "Slab Serif",
 164            "blackandwhitepicture": "Display/Other",
 165            "blackhansans": "Sans Serif (Generic)",
 166            "blackopsone": "Display/Other",
 167            "bonbon": "Display/Other",
 168            "boogaloo": "Display/Other",
 169            "bowlbyone": "Display/Other",
 170            "bowlbyonesc": "Display/Other",
 171            "brawler": "Serif (Generic)",
 172            "breeserif": "Slab Serif",
 173            "brunoace": "Display/Other",
 174            "brunoacesc": "Display/Other",
 175            "bubblegumsans": "Display/Other",
 176            "bubblerone": "Display/Other",
 177            "buenard": "Serif (Generic)",
 178            "bungee": "Display/Other",
 179            "bungeehairline": "Display/Other",
 180            "bungeeinline": "Display/Other",
 181            "bungeeoutline": "Display/Other",
 182            "bungeeshade": "Display/Other",
 183            "butcherman": "Display/Other",
 184            "butchermancaps": "Display/Other",
 185            "butterflykids": "Script/Handwritten",
 186            "cabin": "Humanist Sans",
 187            "cabincondensed": "Condensed Sans",
 188            "cabinsketch": "Display/Other",
 189            "cabinvfbeta": "Humanist Sans",
 190            "caesardressing": "Display/Other",
 191            "cagliostro": "Sans Serif (Generic)",
 192            "cairo": "Grotesque Sans",
 193            "calligraffitti": "Script/Handwritten",
 194            "cambay": "Sans Serif (Generic)",
 195            "cambo": "Serif (Generic)",
 196            "cantarell": "Sans Serif (Generic)",
 197            "cantataone": "Serif (Generic)",
 198            "cantoraone": "Sans Serif (Generic)",
 199            "capriola": "Sans Serif (Generic)",
 200            "cardo": "Serif (Generic)",
 201            "carme": "Sans Serif (Generic)",
 202            "carroisgothic": "Sans Serif (Generic)",
 203            "carroisgothicsc": "Sans Serif (Generic)",
 204            "catamaran": "Sans Serif",
 205            "caudex": "Serif (Generic)",
 206            "caveat": "Script/Handwritten",
 207            "caveatbrush": "Script/Handwritten",
 208            "cevicheone": "Display/Other",
 209            "changa": "Sans Serif (Generic)",
 210            "changaone": "Display/Other",
 211            "chango": "Display/Other",
 212            "chathura": "Sans Serif (Generic)",
 213            "chauphilomeneone": "Display/Other",
 214            "chelaone": "Display/Other",
 215            "chelseamarket": "Display/Other",
 216            "cherrycreamsoda": "Display/Other",
 217            "cherryswash": "Display/Other",
 218            "chewy": "Display",
 219            "chicle": "Display/Other",
 220            "chivo": "Grotesque Sans",
 221            "chonburi": "Display/Other",
 222            "cinzel": "Serif",
 223            "cinzeldecorative": "Display/Other",
 224            "clickerscript": "Script/Handwritten",
 225            "coda": "Display/Other",
 226            "codystar": "Display/Other",
 227            "coiny": "Display/Other",
 228            "combo": "Display/Other",
 229            "comfortaa": "Rounded Sans",
 230            "comingsoon": "Script/Handwritten",
 231            "concertone": "Display/Other",
 232            "condiment": "Script/Handwritten",
 233            "contrailone": "Display/Other",
 234            "convergence": "Sans Serif (Generic)",
 235            "cookie": "Script",
 236            "copse": "Slab Serif",
 237            "cormorant": "Old Style Serif",
 238            "cormorantgaramond": "Old Style Serif",
 239            "cormorantinfant": "Old Style Serif",
 240            "cormorantsc": "Old Style Serif",
 241            "cormorantunicase": "Old Style Serif",
 242            "cormorantupright": "Old Style Serif",
 243            "courgette": "Script",
 244            "cousine": "Monospace",
 245            "coustard": "Slab Serif",
 246            "craftygirls": "Script/Handwritten",
 247            "creepster": "Display/Other",
 248            "creepstercaps": "Display/Other",
 249            "creteround": "Slab Serif",
 250            "crimsontext": "Old Style Serif",
 251            "croissantone": "Display/Other",
 252            "crushed": "Display/Other",
 253            "cuprum": "Sans Serif",
 254            "cutefont": "Display/Other",
 255            "cutive": "Serif (Generic)",
 256            "cutivemono": "Monospace",
 257            "damion": "Script/Handwritten",
 258            "dancingscript": "Script/Handwritten",
 259            "daysone": "Display/Other",
 260            "decovaralpha": "Display/Other",
 261            "dekko": "Script/Handwritten",
 262            "delius": "Script/Handwritten",
 263            "deliusswashcaps": "Script/Handwritten",
 264            "deliusunicase": "Script/Handwritten",
 265            "dellarespira": "Serif (Generic)",
 266            "denkone": "Display/Other",
 267            "devonshire": "Script/Handwritten",
 268            "dhurjati": "Display/Other",
 269            "dhyana": "Display/Other",
 270            "didactgothic": "Sans Serif (Generic)",
 271            "digitalnumbers": "Display/Other",
 272            "diplomata": "Display/Other",
 273            "diplomatasc": "Display/Other",
 274            "dohyeon": "Sans Serif (Generic)",
 275            "dokdo": "Script/Handwritten",
 276            "domine": "Serif",
 277            "donegalone": "Display/Other",
 278            "doppioone": "Sans Serif (Generic)",
 279            "dorsa": "Display/Other",
 280            "dosis": "Rounded Sans",
 281            "drsugiyama": "Script/Handwritten",
 282            "durusans": "Sans Serif (Generic)",
 283            "dynalight": "Script/Handwritten",
 284            "eaglelake": "Script/Handwritten",
 285            "eastseadokdo": "Script/Handwritten",
 286            "eater": "Display/Other",
 287            "eatercaps": "Display/Other",
 288            "ebgaramond": "Old Style Serif",
 289            "economica": "Sans Serif (Generic)",
 290            "eczar": "Display/Other",
 291            "ekmukta": "Sans Serif (Generic)",
 292            "electrolize": "Display/Other",
 293            "elmessiri": "Sans Serif (Generic)",
 294            "elsie": "Display/Other",
 295            "elsieswashcaps": "Display/Other",
 296            "emblemaone": "Display/Other",
 297            "emilyscandy": "Display/Other",
 298            "encodesans": "Sans Serif",
 299            "encodesanscondensed": "Condensed Sans",
 300            "encodesansexpanded": "Sans Serif (Generic)",
 301            "encodesanssemicondensed": "Condensed Sans",
 302            "encodesanssemiexpanded": "Sans Serif (Generic)",
 303            "engagement": "Script/Handwritten",
 304            "englebert": "Display/Other",
 305            "enriqueta": "Slab Serif",
 306            "ericaone": "Display/Other",
 307            "esteban": "Serif (Generic)",
 308            "euphoriascript": "Script/Handwritten",
 309            "ewert": "Display/Other",
 310            "exo": "Geometric Sans",
 311            "exo2": "Geometric Sans",
 312            "expletussans": "Display/Other",
 313            "fanwoodtext": "Serif (Generic)",
 314            "farsan": "Display/Other",
 315            "fascinate": "Display/Other",
 316            "fascinateinline": "Display/Other",
 317            "fasterone": "Display/Other",
 318            "fasthand": "Script/Handwritten",
 319            "faunaone": "Serif (Generic)",
 320            "faustina": "Serif (Generic)",
 321            "faustinavfbeta": "Serif (Generic)",
 322            "federant": "Display/Other",
 323            "federo": "Display/Other",
 324            "felipa": "Script/Handwritten",
 325            "fenix": "Serif (Generic)",
 326            "fingerpaint": "Display/Other",
 327            "firasans": "Humanist Sans",
 328            "firasanscondensed": "Condensed Sans",
 329            "firasansextracondensed": "Condensed Sans",
 330            "fjallaone": "Grotesque Sans",
 331            "fjordone": "Serif (Generic)",
 332            "flamenco": "Display/Other",
 333            "flavors": "Display/Other",
 334            "fondamento": "Script/Handwritten",
 335            "fontdinerswanky": "Display/Other",
 336            "forum": "Display/Other",
 337            "francoisone": "Sans Serif",
 338            "frankruhllibre": "Serif (Generic)",
 339            "freckleface": "Display/Other",
 340            "frederickathegreat": "Display/Other",
 341            "fredokaone": "Rounded Display",
 342            "fresca": "Sans Serif (Generic)",
 343            "frijole": "Display/Other",
 344            "fruktur": "Display/Other",
 345            "fugazone": "Display/Other",
 346            "gabriela": "Serif (Generic)",
 347            "gaegu": "Script/Handwritten",
 348            "gafata": "Sans Serif (Generic)",
 349            "galada": "Display/Other",
 350            "galdeano": "Sans Serif (Generic)",
 351            "galindo": "Display/Other",
 352            "gamjaflower": "Script/Handwritten",
 353            "gemunulibre": "Sans Serif (Generic)",
 354            "genbasb": "Serif (Generic)",
 355            "genbasbi": "Serif (Generic)",
 356            "genbkbasb": "Serif (Generic)",
 357            "genbkbasbi": "Serif (Generic)",
 358            "geo": "Display/Other",
 359            "geostar": "Display/Other",
 360            "geostarfill": "Display/Other",
 361            "germaniaone": "Display/Other",
 362            "gfsdidot": "Serif (Generic)",
 363            "gfsneohellenic": "Sans Serif (Generic)",
 364            "gfsneohellenicbold": "Sans Serif (Generic)",
 365            "gfsneohellenicbolditalic": "Sans Serif (Generic)",
 366            "gidugu": "Display/Other",
 367            "gildadisplay": "Serif (Generic)",
 368            "glassantiqua": "Display/Other",
 369            "glegoo": "Slab Serif",
 370            "gochihand": "Script/Handwritten",
 371            "gorditas": "Display/Other",
 372            "gothica1": "Sans Serif (Generic)",
 373            "graduate": "Display/Other",
 374            "grandhotel": "Script/Handwritten",
 375            "greatvibes": "Script",
 376            "griffy": "Display/Other",
 377            "gruppo": "Display/Other",
 378            "gudea": "Sans Serif (Generic)",
 379            "gugi": "Display/Other",
 380            "gurajada": "Display/Other",
 381            "habibi": "Serif (Generic)",
 382            "halant": "Serif (Generic)",
 383            "hammersmithone": "Sans Serif (Generic)",
 384            "hanalei": "Display/Other",
 385            "hanaleifill": "Display/Other",
 386            "handlee": "Script/Handwritten",
 387            "hannari": "Display/Other",
 388            "hanuman": "Serif (Generic)",
 389            "happymonkey": "Display/Other",
 390            "harmattan": "Sans Serif (Generic)",
 391            "headlandone": "Serif (Generic)",
 392            "heebo": "Sans Serif",
 393            "hennypenny": "Display/Other",
 394            "hermeneusone": "Sans Serif (Generic)",
 395            "herrvonmuellerhoff": "Script/Handwritten",
 396            "himelody": "Script/Handwritten",
 397            "hind": "Humanist Sans",
 398            "hindcolombo": "Sans Serif (Generic)",
 399            "hindguntur": "Sans Serif (Generic)",
 400            "hindjalandhar": "Sans Serif (Generic)",
 401            "hindkochi": "Sans Serif (Generic)",
 402            "hindmadurai": "Sans Serif (Generic)",
 403            "hindmysuru": "Sans Serif (Generic)",
 404            "hindsiliguri": "Sans Serif (Generic)",
 405            "hindvadodara": "Sans Serif (Generic)",
 406            "homemadeapple": "Script/Handwritten",
 407            "homenaje": "Sans Serif (Generic)",
 408            "ibmplexmono": "Monospace",
 409            "ibmplexsans": "Neo-Grotesque Sans",
 410            "ibmplexsanscondensed": "Condensed Sans",
 411            "ibmplexserif": "Transitional Serif",
 412            "iceberg": "Display/Other",
 413            "iceland": "Display/Other",
 414            "imprima": "Sans Serif (Generic)",
 415            "inconsolata": "Monospace",
 416            "inder": "Sans Serif (Generic)",
 417            "indieflower": "Handwritten",
 418            "inika": "Serif (Generic)",
 419            "inknutantiqua": "Serif (Generic)",
 420            "irishgrover": "Display/Other",
 421            "istokweb": "Sans Serif (Generic)",
 422            "italiana": "Serif (Generic)",
 423            "italianno": "Script/Handwritten",
 424            "itim": "Script/Handwritten",
 425            "jacquesfrancois": "Display/Other",
 426            "jacquesfrancoisshadow": "Display/Other",
 427            "jaldi": "Sans Serif (Generic)",
 428            "jejugothic": "Sans Serif (Generic)",
 429            "jejuhallasan": "Display/Other",
 430            "jejumyeongjo": "Serif (Generic)",
 431            "jimnightshade": "Display/Other",
 432            "jockeyone": "Display/Other",
 433            "jollylodger": "Display/Other",
 434            "jomhuria": "Display/Other",
 435            "josefinsans": "Geometric Sans",
 436            "josefinslab": "Slab Serif",
 437            "jotione": "Display/Other",
 438            "jua": "Sans Serif (Generic)",
 439            "judson": "Serif (Generic)",
 440            "julee": "Script/Handwritten",
 441            "juliussansone": "Sans Serif (Generic)",
 442            "junge": "Serif (Generic)",
 443            "jura": "Sans Serif (Generic)",
 444            "justanotherhand": "Script/Handwritten",
 445            "kadwa": "Serif (Generic)",
 446            "kalam": "Handwritten",
 447            "kameron": "Serif (Generic)",
 448            "kanit": "Sans Serif",
 449            "kantumruy": "Sans Serif (Generic)",
 450            "karla": "Sans Serif (Generic)",
 451            "karlatamilinclined": "Sans Serif (Generic)",
 452            "karlatamilupright": "Sans Serif (Generic)",
 453            "karma": "Serif (Generic)",
 454            "katibeh": "Display/Other",
 455            "kaushanscript": "Script",
 456            "kavivanar": "Script/Handwritten",
 457            "kavoon": "Display/Other",
 458            "kdamthmor": "Display/Other",
 459            "keaniaone": "Display/Other",
 460            "kellyslab": "Display/Other",
 461            "kenia": "Display/Other",
 462            "khand": "Sans Serif",
 463            "khula": "Sans Serif (Generic)",
 464            "khyay": "Display/Other",
 465            "kiranghaerang": "Display/Other",
 466            "kiteone": "Sans Serif (Generic)",
 467            "knewave": "Display/Other",
 468            "kokoro": "Display/Other",
 469            "kopubbatang": "Serif (Generic)",
 470            "kottaone": "Serif (Generic)",
 471            "kranky": "Display/Other",
 472            "kreon": "Slab Serif",
 473            "kristi": "Script/Handwritten",
 474            "kronaone": "Sans Serif (Generic)",
 475            "kurale": "Serif (Generic)",
 476            "laila": "Serif (Generic)",
 477            "lakkireddy": "Display/Other",
 478            "lalezar": "Display/Other",
 479            "lancelot": "Display/Other",
 480            "laomuangdon": "Display/Other",
 481            "laomuangkhong": "Display/Other",
 482            "laosanspro": "Sans Serif (Generic)",
 483            "lato": "Humanist Sans",
 484            "leckerlione": "Display/Other",
 485            "ledger": "Serif (Generic)",
 486            "lekton": "Monospace",
 487            "lemon": "Display/Other",
 488            "lemonada": "Display/Other",
 489            "lemonadavfbeta": "Display/Other",
 490            "librebaskerville": "Serif (Generic)",
 491            "librecaslontext": "Serif (Generic)",
 492            "librefranklin": "Neo-Grotesque Sans",
 493            "lifesavers": "Display/Other",
 494            "lilitaone": "Display/Other",
 495            "lilyscriptone": "Display/Other",
 496            "limelight": "Display/Other",
 497            "lindenhill": "Serif (Generic)",
 498            "lobster": "Script",
 499            "lobstertwo": "Script/Handwritten",
 500            "londrinaoutline": "Display/Other",
 501            "londrinashadow": "Display/Other",
 502            "londrinasketch": "Display/Other",
 503            "londrinasolid": "Display/Other",
 504            "lora": "Old Style Serif",
 505            "loversquarrel": "Script/Handwritten",
 506            "loveyalikeasister": "Script/Handwritten",
 507            "luckiestguy": "Display/Other",
 508            "lusitana": "Serif (Generic)",
 509            "lustria": "Serif (Generic)",
 510            "macondo": "Display/Other",
 511            "macondoswashcaps": "Display/Other",
 512            "mada": "Sans Serif (Generic)",
 513            "magra": "Sans Serif (Generic)",
 514            "maidenorange": "Display/Other",
 515            "maitree": "Serif (Generic)",
 516            "mako": "Sans Serif (Generic)",
 517            "mallanna": "Display/Other",
 518            "mandali": "Sans Serif (Generic)",
 519            "manuale": "Serif (Generic)",
 520            "marcellus": "Serif (Generic)",
 521            "marcellussc": "Serif (Generic)",
 522            "marckscript": "Script/Handwritten",
 523            "margarine": "Display/Other",
 524            "markazitext": "Serif (Generic)",
 525            "markoone": "Serif (Generic)",
 526            "marmelad": "Sans Serif (Generic)",
 527            "martel": "Serif (Generic)",
 528            "martelsans": "Sans Serif (Generic)",
 529            "marvel": "Sans Serif (Generic)",
 530            "mate": "Serif (Generic)",
 531            "matesc": "Serif (Generic)",
 532            "mavenpro": "Geometric Sans",
 533            "mavenprovfbeta": "Geometric Sans",
 534            "mclaren": "Display/Other",
 535            "medulaone": "Display/Other",
 536            "meerainimai": "Display/Other",
 537            "meiescript": "Script/Handwritten",
 538            "mergeone": "Sans Serif (Generic)",
 539            "merienda": "Display/Other",
 540            "meriendaone": "Display/Other",
 541            "merriweather": "Transitional Serif",
 542            "merriweathersans": "Humanist Sans",
 543            "mervalescript": "Script/Handwritten",
 544            "metalmania": "Display/Other",
 545            "metamorphous": "Display/Other",
 546            "metrophobic": "Sans Serif (Generic)",
 547            "miama": "Script/Handwritten",
 548            "milonga": "Display/Other",
 549            "miltonian": "Display/Other",
 550            "miltoniantattoo": "Display/Other",
 551            "mina": "Sans Serif (Generic)",
 552            "miniver": "Display/Other",
 553            "miriamlibre": "Sans Serif (Generic)",
 554            "mirza": "Display/Other",
 555            "missfajardose": "Script/Handwritten",
 556            "mitr": "Sans Serif (Generic)",
 557            "modak": "Display/Other",
 558            "modernantiqua": "Serif (Generic)",
 559            "mogra": "Display/Other",
 560            "molengo": "Sans Serif (Generic)",
 561            "molle": "Display/Other",
 562            "monda": "Sans Serif (Generic)",
 563            "monoton": "Display/Other",
 564            "monsieurladoulaise": "Script/Handwritten",
 565            "montaga": "Serif (Generic)",
 566            "montez": "Script/Handwritten",
 567            "montserrat": "Geometric Sans",
 568            "montserratalternates": "Geometric Sans",
 569            "montserratsubrayada": "Geometric Sans",
 570            "mountainsofchristmas": "Display/Other",
 571            "mousememoirs": "Display/Other",
 572            "mplus1p": "Rounded Sans",
 573            "mrbedfort": "Script/Handwritten",
 574            "mrdafoe": "Script/Handwritten",
 575            "mrdehaviland": "Script/Handwritten",
 576            "mrssaintdelafield": "Script/Handwritten",
 577            "mrssheppards": "Script/Handwritten",
 578            "mukta": "Sans Serif",
 579            "muktamahee": "Sans Serif (Generic)",
 580            "muktamalar": "Sans Serif (Generic)",
 581            "muktavaani": "Sans Serif (Generic)",
 582            "muli": "Humanist Sans",
 583            "myanmarsanspro": "Sans Serif (Generic)",
 584            "mysteryquest": "Display/Other",
 585            "nanumbrushscript": "Script/Handwritten",
 586            "nanumgothic": "Sans Serif (Generic)",
 587            "nanumgothiccoding": "Monospace",
 588            "nanummyeongjo": "Serif (Generic)",
 589            "nanumpenscript": "Script/Handwritten",
 590            "nats": "Display/Other",
 591            "neuton": "Serif (Generic)",
 592            "newrocker": "Display/Other",
 593            "newscycle": "Sans Serif (Generic)",
 594            "nicomoji": "Display/Other",
 595            "niconne": "Script/Handwritten",
 596            "nikukyu": "Display/Other",
 597            "nixieone": "Display/Other",
 598            "nobile": "Sans Serif (Generic)",
 599            "nokora": "Sans Serif (Generic)",
 600            "norican": "Script/Handwritten",
 601            "nosifer": "Display/Other",
 602            "nosifercaps": "Display/Other",
 603            "notable": "Display/Other",
 604            "noticiatext": "Serif (Generic)",
 605            "notosans": "Humanist Sans",
 606            "notosanstamil": "Humanist Sans",
 607            "notoserif": "Transitional Serif",
 608            "novascript": "Display/Other",
 609            "ntr": "Sans Serif (Generic)",
 610            "numans": "Sans Serif (Generic)",
 611            "nunito": "Rounded Sans",
 612            "nunitosans": "Rounded Sans",
 613            "offside": "Display/Other",
 614            "oldenburg": "Display/Other",
 615            "oldstandard": "Serif (Generic)",
 616            "oleoscript": "Script/Handwritten",
 617            "oleoscriptswashcaps": "Script/Handwritten",
 618            "opensans": "Humanist Sans",
 619            "opensanscondensed": "Condensed Sans",
 620            "opensanshebrew": "Humanist Sans",
 621            "opensanshebrewcondensed": "Condensed Sans",
 622            "oranienbaum": "Serif (Generic)",
 623            "orbitron": "Display/Other",
 624            "oregano": "Script/Handwritten",
 625            "orienta": "Sans Serif (Generic)",
 626            "originalsurfer": "Display/Other",
 627            "oswald": "Grotesque Sans",
 628            "overlock": "Display/Other",
 629            "overlocksc": "Display/Other",
 630            "overpass": "Sans Serif (Generic)",
 631            "overpassmono": "Monospace",
 632            "ovo": "Serif (Generic)",
 633            "oxygen": "Geometric Sans",
 634            "oxygenmono": "Monospace",
 635            "pacifico": "Script",
 636            "palanquin": "Sans Serif (Generic)",
 637            "palanquindark": "Sans Serif (Generic)",
 638            "pangolin": "Script/Handwritten",
 639            "paprika": "Display/Other",
 640            "parisienne": "Script/Handwritten",
 641            "passeroone": "Display/Other",
 642            "passionone": "Display",
 643            "pathwaygothicone": "Condensed Sans",
 644            "patrickhand": "Script/Handwritten",
 645            "patrickhandsc": "Script/Handwritten",
 646            "pattaya": "Display/Other",
 647            "patuaone": "Slab Serif",
 648            "pavanam": "Sans Serif (Generic)",
 649            "paytoneone": "Sans Serif (Generic)",
 650            "peddana": "Serif (Generic)",
 651            "peralta": "Display/Other",
 652            "permanentmarker": "Handwritten",
 653            "petitformalscript": "Script/Handwritten",
 654            "petrona": "Serif (Generic)",
 655            "philosopher": "Sans Serif (Generic)",
 656            "piedra": "Display/Other",
 657            "pinyonscript": "Script/Handwritten",
 658            "pirataone": "Display/Other",
 659            "plaster": "Display/Other",
 660            "play": "Sans Serif (Generic)",
 661            "playball": "Script/Handwritten",
 662            "playfairdisplay": "Modern Serif",
 663            "playfairdisplaysc": "Modern Serif",
 664            "podkova": "Slab Serif",
 665            "podkovavfbeta": "Slab Serif",
 666            "poetsenone": "Display/Other",
 667            "poiretone": "Display/Other",
 668            "poly": "Serif (Generic)",
 669            "pompiere": "Display/Other",
 670            "ponnala": "Display/Other",
 671            "pontanosans": "Sans Serif (Generic)",
 672            "poorstory": "Display/Other",
 673            "poppins": "Geometric Sans",
 674            "portersansblock": "Display/Other",
 675            "portlligatsans": "Display/Other",
 676            "portlligatslab": "Display/Other",
 677            "postnobillscolombo": "Display/Other",
 678            "postnobillsjaffna": "Display/Other",
 679            "pragatinarrow": "Condensed Sans",
 680            "prata": "Serif (Generic)",
 681            "pressstart2p": "Display/Other",
 682            "pridi": "Serif (Generic)",
 683            "princesssofia": "Script/Handwritten",
 684            "prociono": "Serif (Generic)",
 685            "prompt": "Sans Serif",
 686            "prostoone": "Display/Other",
 687            "prozalibre": "Sans Serif (Generic)",
 688            "pt_sans": "Humanist Sans",
 689            "pt_serif": "Transitional Serif",
 690            "puritan": "Sans Serif (Generic)",
 691            "purplepurse": "Display/Other",
 692            "questrial": "Geometric Sans",
 693            "qwigley": "Script/Handwritten",
 694        }
 695
 696    def get_cluster(self, raw_name: str) -> str:
 697        clean = raw_name.lower().split("-")[0].strip()
 698        if clean in self.full_font_map:
 699            return self.full_font_map[clean]
 700        return "Display/Other"
 701
 702
 703class UnifiedStyleEngine:
 704    def __init__(self):
 705        self.feature_registry = defaultdict(lambda: defaultdict(list))
 706        self.font_engine = FontClusterEngine()
 707        self.font_family_scores = defaultdict(float)
 708        self.font_individual_scores = defaultdict(float)
 709        self.font_members = defaultdict(list)
 710        self.color_pool = []
 711        self.doc_counter = 0
 712
 713        self.material_filter = {
 714            "glossy",
 715            "matte",
 716            "metallic",
 717            "organic",
 718            "wood",
 719            "laminated",
 720            "porcelain",
 721            "wet-look",
 722            "denim",
 723            "fabric",
 724            "leather",
 725        }
 726
 727        self.aliases = {
 728            "Colour Palette": "Color Palette",
 729            "Texture": "Background/Texture",
 730            "Era": "Era/Cultural Reference",
 731            "Font": "Typography",
 732            "Fonts": "Typography",
 733            "Text": "Typography",
 734            "Layout": "Composition",
 735        }
 736
 737    def ingest_data(self, data: Dict):
 738        """
 739        Parses the JSON structure from the app
 740        Handles: { "success": true, "data": { "results": { ... } } }
 741        """
 742        try:
 743            self.doc_counter += 1
 744
 745            source_data = {}
 746            if "data" in data and isinstance(data["data"], dict):
 747                if "results" in data["data"]:
 748                    source_data = data["data"]["results"]
 749                else:
 750                    source_data = data["data"]
 751            elif "results" in data:
 752                source_data = data["results"]
 753            else:
 754                source_data = data
 755
 756            for category, payload in source_data.items():
 757                if category in ["filename", "meta"]:
 758                    continue
 759
 760                std_category = self.aliases.get(category, category)
 761
 762                if std_category == "Color Palette":
 763                    if isinstance(payload, dict) and "palette" in payload:
 764                        for hex_code in payload["palette"]:
 765                            if isinstance(hex_code, str) and hex_code.startswith("#"):
 766                                self.color_pool.append(hex_to_rgb(hex_code))
 767                    elif isinstance(payload, list):
 768                        for item in payload:
 769                            if isinstance(item, str) and item.startswith("#"):
 770                                self.color_pool.append(hex_to_rgb(item))
 771
 772                elif std_category == "Typography":
 773                    if isinstance(payload, dict) and "scores" in payload:
 774                        payload = payload["scores"]
 775                    vectors = self._normalize(payload)
 776
 777                    for rank, (font_name, score) in enumerate(vectors):
 778                        if "no text" in font_name.lower():
 779                            continue
 780
 781                        weight = math.pow(RANK_DECAY_FACTOR, rank) * score
 782                        cluster = self.font_engine.get_cluster(font_name)
 783
 784                        self.font_family_scores[cluster] += weight
 785                        self.font_individual_scores[font_name] += weight
 786                        if font_name not in self.font_members[cluster]:
 787                            self.font_members[cluster].append(font_name)
 788
 789                else:
 790                    if isinstance(payload, dict) and "scores" in payload:
 791                        payload = payload["scores"]
 792                    vectors = self._normalize(payload)
 793
 794                    for rank, (label, score) in enumerate(vectors):
 795                        self.feature_registry[std_category][label].append(
 796                            {"raw_score": score, "rank": rank}
 797                        )
 798
 799        except Exception as e:
 800            print(f"Warning during ingestion: {e}")
 801
 802    def _normalize(self, payload) -> List[Tuple[str, float]]:
 803        """
 804        Robustly converts inputs to [(label, score), ...]
 805        Handles mixed types and weird nesting to prevent crashes.
 806        """
 807        if isinstance(payload, dict):
 808            clean_items = []
 809            for k, v in payload.items():
 810                if isinstance(v, (int, float)):
 811                    clean_items.append((k, float(v)))
 812            return sorted(clean_items, key=lambda x: x[1], reverse=True)
 813
 814        if isinstance(payload, list):
 815            res = []
 816            for item in payload:
 817                if isinstance(item, dict):
 818                    try:
 819                        score = float(item.get("score", 0.9))
 820                    except (ValueError, TypeError):
 821                        score = 0.0
 822
 823                    label = item.get("label", item.get("tag", "unknown"))
 824                    if not isinstance(label, str):
 825                        label = str(label)
 826
 827                    res.append((label, score))
 828                elif isinstance(item, str):
 829                    res.append((item, 1.0))
 830            return res
 831        return []
 832
 833    def compute_final_stylesheet(self) -> Dict:
 834        final_json = {"results": {}}
 835
 836        for category, tags_map in self.feature_registry.items():
 837            candidates = []
 838            for tag, occurrences in tags_map.items():
 839                w_sum = sum(
 840                    obs["raw_score"] * math.pow(RANK_DECAY_FACTOR, obs["rank"])
 841                    for obs in occurrences
 842                )
 843                score = w_sum / max(1, self.doc_counter)
 844
 845                if score > MIN_CONSENSUS_THRESHOLD:
 846                    candidates.append({"label": tag, "score": round(score, 2)})
 847
 848            candidates.sort(key=lambda x: x["score"], reverse=True)
 849            if candidates:
 850                final_json["results"][category] = candidates[:5]
 851
 852        if "Background/Texture" in final_json["results"]:
 853            textures = []
 854            materials = []
 855
 856            for item in final_json["results"]["Background/Texture"]:
 857                label = item["label"].lower().replace("_", " ").replace("-", " ")
 858                is_material = False
 859                if item["label"] in self.material_filter:
 860                    is_material = True
 861                else:
 862                    for mat in self.material_filter:
 863                        if mat in label:
 864                            is_material = True
 865                            break
 866
 867                if is_material:
 868                    materials.append(item)
 869                else:
 870                    textures.append(item)
 871
 872            if textures:
 873                final_json["results"]["Background/Texture"] = textures
 874            else:
 875                del final_json["results"]["Background/Texture"]
 876
 877            if materials:
 878                if "Material Look" in final_json["results"]:
 879                    final_json["results"]["Material Look"].extend(materials)
 880                    final_json["results"]["Material Look"].sort(
 881                        key=lambda x: x["score"], reverse=True
 882                    )
 883                else:
 884                    final_json["results"]["Material Look"] = materials
 885
 886        if self.font_family_scores:
 887            best_fam, _ = sorted(
 888                self.font_family_scores.items(), key=lambda x: x[1], reverse=True
 889            )[0]
 890            members = self.font_members[best_fam]
 891            members.sort(key=lambda x: self.font_individual_scores[x], reverse=True)
 892
 893            typo_output = []
 894            for font in members[:5]:
 895                score = self.font_individual_scores[font] / max(1, self.doc_counter)
 896                typo_output.append({"label": font, "score": round(score, 2)})
 897
 898            final_json["results"]["Typography"] = typo_output
 899            final_json["results"]["Typography_Family"] = best_fam
 900
 901        if len(self.color_pool) > 0:
 902            try:
 903                k_clusters = min(len(self.color_pool), PALETTE_SIZE)
 904
 905                kmeans = KMeans(n_clusters=k_clusters, n_init=1, random_state=42)
 906                kmeans.fit(self.color_pool)
 907                centers = sorted(kmeans.cluster_centers_.astype(int).tolist(), key=sum)
 908
 909                palette_output = []
 910                for i, rgb in enumerate(centers):
 911                    palette_output.append(
 912                        {"label": rgb_to_hex(rgb), "score": round(1.0 - (i * 0.1), 2)}
 913                    )
 914                final_json["results"]["Color Palette"] = palette_output
 915            except Exception as e:
 916                print(f"Color clustering failed: {e}")
 917
 918        final_json["results"]["meta"] = {"source_files": self.doc_counter}
 919        return final_json
 920
 921
 922def generate_stylesheet(json_strings_list: Any) -> str:
 923    """
 924    Entry point
 925    Accepts a Java ArrayList of JSON strings.
 926    """
 927    engine = UnifiedStyleEngine()
 928
 929    try:
 930        count = json_strings_list.size()
 931        for i in range(count):
 932            json_str = json_strings_list.get(i)
 933            if not json_str:
 934                continue
 935            try:
 936                data = json.loads(json_str)
 937                engine.ingest_data(data)
 938            except Exception as e:
 939                print(f"Error parsing JSON chunk at index {i}: {e}")
 940                continue
 941
 942    except AttributeError:
 943        for json_str in json_strings_list:
 944            if not json_str:
 945                continue
 946            try:
 947                data = json.loads(json_str)
 948                engine.ingest_data(data)
 949            except:
 950                continue
 951
 952    final_output = engine.compute_final_stylesheet()
 953    return json.dumps(final_output)
 954
 955
 956##  Prompt Generation
 957
 958DEFAULT_OPTIONS = {
 959    "width": 1024,
 960    "height": 1024,
 961    "steps": 30,
 962    "guidance_scale": 7.5,
 963    "sampler": "k_lms",
 964    "seed": "random",
 965}
 966
 967
 968def normalize_sheet(sheet: Dict[str, Any]) -> Dict[str, Dict[str, List[str]]]:
 969    """
 970    Organizes the flat stylesheet tags into Primary and Secondary categories.
 971    """
 972    results = sheet.get("results", sheet)
 973    norm: Dict[str, Dict[str, List[str]]] = {}
 974
 975    for cat, val in results.items():
 976        if cat in ("filename", "meta", "Color Palette"):
 977            continue
 978
 979        items = []
 980        if isinstance(val, list):
 981            items = val
 982        elif isinstance(val, dict) and "scores" in val:
 983            items = val["scores"]
 984
 985        try:
 986            items_sorted = sorted(
 987                items, key=lambda x: float(x.get("score", 0)), reverse=True
 988            )
 989        except Exception:
 990            items_sorted = items
 991
 992        labels = [
 993            str(it.get("label")).strip() for it in items_sorted if it.get("label")
 994        ]
 995        if labels:
 996            norm[cat] = {"Primary": labels[0], "Secondary": labels[1:]}
 997
 998    return norm
 999
1000
1001STYLE_WORDS = [
1002    "hand-drawn",
1003    "hand drawn",
1004    "sketch",
1005    "thin lines",
1006    "minimalistic",
1007    "minimal",
1008    "flat",
1009    "cartoon",
1010    "comic",
1011    "pop-art",
1012    "surreal",
1013    "photorealistic",
1014    "realistic",
1015    "painting",
1016    "oil painting",
1017    "pastel",
1018    "artistic",
1019    "illustration",
1020    "line drawing",
1021    "line art",
1022    "3d effect",
1023    "texture",
1024    "pattern",
1025    "soft light",
1026    "studio light",
1027    "dramatic",
1028    "cinematic",
1029    "moody",
1030    "vintage",
1031    "retro",
1032    "poster",
1033    "render",
1034    "octane render",
1035    "unreal engine",
1036    "digital illustration",
1037]
1038
1039style_words_regex = re.compile(
1040    r"\b(" + "|".join(re.escape(w) for w in STYLE_WORDS) + r")\b",
1041    flags=re.IGNORECASE,
1042)
1043
1044
1045def remove_style_words(caption: str) -> str:
1046    """
1047    Removes specific artistic style descriptions from the caption so they
1048    don't conflict with the Stylesheet.
1049    """
1050    if not caption:
1051        return caption
1052    clean = style_words_regex.sub(" ", caption)
1053    clean = re.sub(r"\s{2,}", " ", clean).strip()
1054    return clean
1055
1056
1057def extract_subject(caption_text: str) -> str:
1058    """
1059    Heuristic to guess the main subject (first few words of the first sentence).
1060    """
1061    txt = caption_text.strip()
1062    if not txt:
1063        return "subject"
1064    first_clause = re.split(r"[.?!]\s*", txt)[0]
1065    return " ".join(first_clause.split()[:12])
1066
1067
1068def pick_style_phrases(
1069    norm: Dict[str, Dict[str, List[str]]], max_secondaries=3
1070) -> List[str]:
1071    """
1072    Extracts non-color features from the Stylesheet.
1073    """
1074    cats = [
1075        "Style",
1076        "Background/Texture",
1077        "Lighting",
1078        "Composition",
1079        "Era/Cultural Reference",
1080        "Material Look",
1081        "Typography",
1082    ]
1083    key_map = {
1084        "Style": "art style",
1085        "Background/Texture": "background texture",
1086        "Lighting": "lighting",
1087        "Composition": "composition",
1088        "Era/Cultural Reference": "era",
1089        "Material Look": "material",
1090        "Typography": "typography",
1091    }
1092
1093    out: List[str] = []
1094    for cat in cats:
1095        entry = norm.get(cat)
1096        if not entry:
1097            continue
1098
1099        prim = entry.get("Primary")
1100        secs = entry.get("Secondary", [])
1101        chosen: List[str] = []
1102        if prim:
1103            chosen.append(prim)
1104        for s in secs:
1105            if s and len(chosen) < (1 + max_secondaries):
1106                chosen.append(s)
1107        if chosen:
1108            out.append(f"{key_map.get(cat, cat)}: {', '.join(chosen)}")
1109    return out
1110
1111
1112def style_to_tone(primary: str) -> str:
1113    """
1114    Infers a general tone based on the primary style.
1115    """
1116    s = (primary or "").lower()
1117    if any(k in s for k in ["retro", "collage", "poster", "pop-art", "surreal"]):
1118        return "illustrative, textured, poster-like"
1119    if any(k in s for k in ["photoreal", "realistic", "film"]):
1120        return "photorealistic, high-detail"
1121    return "high detail, sharp focus"
1122
1123
1124def generate_magic_prompt(
1125    stylesheet_json_str: str, caption: str, user_prompt: str
1126) -> str:
1127    try:
1128        sheet = json.loads(stylesheet_json_str)
1129    except Exception as e:
1130        return f"{user_prompt}. The image features: {caption}"
1131
1132    cleaned_caption = remove_style_words(caption)
1133
1134    norm = normalize_sheet(sheet)
1135    subject = extract_subject(cleaned_caption)
1136
1137    style_phrases = pick_style_phrases(norm)
1138
1139    style_primary = (norm.get("Style") or {}).get("Primary", "")
1140    tone_hint = style_to_tone(style_primary)
1141
1142    style_instruction = ""
1143    if style_phrases:
1144        style_instruction = "Style elements: " + "; ".join(style_phrases)
1145
1146    prompt_lines: List[str] = [
1147        f"Main subject: {subject}.",
1148        f"Description: {cleaned_caption}.",
1149    ]
1150
1151    if user_prompt:
1152        prompt_lines.append(f"User instruction: {user_prompt}.")
1153
1154    if style_instruction:
1155        prompt_lines.append(f"{style_instruction}.")
1156
1157    prompt_lines += [
1158        f"Tone: {tone_hint}.",
1159        "Note: Colors should be derived strictly from the Description.",
1160        "Avoid: blurry, deformed, text, watermark, extra limbs, artifacts.",
1161    ]
1162
1163    prompt = "\n".join(prompt_lines)
1164
1165    payload = {
1166        "prompt": prompt,
1167        "negative_prompt": "blurry, low resolution, deformed, text, watermark, extra limbs, artifacts",
1168        "options": DEFAULT_OPTIONS,
1169        "provenance": {"generated_from": "final_output"},
1170        "weights": {"caption": 0.7, "style_sheet": 0.2, "user": 0.1},
1171    }
1172
1173    return prompt + " " + json.dumps(payload)