creek

The AI Image Editor of 2030

android/app/src/main/python/stylesheet_generator.py (48011B)


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