# utils/generators/base.py import io import os from PIL import Image, ImageDraw, ImageFont class BaseGenerator: CANVAS_W, CANVAS_H = (660, 900) CARD_W, CARD_H = (580, 820) CORNER_RAD = 16 BANNER_H = 160 OFFSET_X, OFFSET_Y = (40, 40) ELEMENT_COLORS = { "Solstice": (239, 68, 68), "Equinox": (59, 130, 246), "Eclipse": (147, 197, 253), "Zenith": (168, 85, 247), "Aether": (16, 185, 129), "Nadir": (250, 204, 21), "Genesis": (34, 197, 94), "Light": (255, 255, 224), "Dark": (30, 27, 75), "None": (156, 163, 175), "Pyro": (239, 68, 68), "Fire": (239, 68, 68), "Hydro": (59, 130, 246), "Water": (59, 130, 246), "Cryo": (147, 197, 253), "Ice": (147, 197, 253), "Electro": (168, 85, 247), "Anemo": (16, 185, 129), "Wind": (16, 185, 129), "Geo": (250, 204, 21), "Earth": (250, 204, 21), "Dendro": (34, 197, 94) } @staticmethod def get_font(size: int): base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) font_path = os.path.join(base_dir, "assets", "font.ttf") try: if os.path.exists(font_path): return ImageFont.truetype(font_path, size) except: pass return ImageFont.load_default() @staticmethod def create_rounded_mask(size, radius): scale = 3 mask = Image.new('L', (size[0]*scale, size[1]*scale), 0) draw = ImageDraw.Draw(mask) draw.rounded_rectangle((0, 0, size[0]*scale, size[1]*scale), radius=radius*scale, fill=255) return mask.resize(size, Image.Resampling.LANCZOS) @staticmethod def create_bottom_rounded_mask(size, radius): scale = 3 mask = Image.new('L', (size[0]*scale, size[1]*scale), 0) draw = ImageDraw.Draw(mask) draw.rounded_rectangle((0, 0, size[0]*scale, size[1]*scale), radius=radius*scale, fill=255) draw.rectangle((0, 0, size[0]*scale, radius*scale), fill=255) return mask.resize(size, Image.Resampling.LANCZOS) @staticmethod def draw_text_with_glow(uid, position, text, font, core_color="white", glow_color=(0,0,0)): uid.text(position, text, font=font, fill=core_color, stroke_width=3, stroke_fill=glow_color) def apply_text(self, blank_template: Image.Image, char_name: str, print_num: int, element: str, rarity: str) -> io.BytesIO: canvas = blank_template.copy() uid = ImageDraw.Draw(canvas) r_color = self.ELEMENT_COLORS.get(element, (250, 204, 21) if rarity in ["Legendary", "Mythic", "SSR", "UR"] else (156, 163, 175)) light_r = tuple(min(255, c + 150) for c in r_color) glow_solid = r_color[:3] base_sz = int(self.CARD_W * 0.085) num_font = self.get_font(base_sz) num_text = str(print_num) # ========================================== # 1. Standard Number Box (100% UNTOUCHED LOGIC) # ========================================== try: num_w = num_font.getlength(num_text) except: num_w = 20 box_w = num_w + 24 box_h = 46 box_x2 = self.OFFSET_X + self.CARD_W - 30 box_x1 = box_x2 - box_w box_y1 = self.OFFSET_Y + self.CARD_H - self.BANNER_H + (self.BANNER_H - box_h) // 2 box_y2 = box_y1 + box_h uid.rounded_rectangle((box_x1, box_y1, box_x2, box_y2), radius=10, fill=(10, 10, 10, 180), outline=light_r, width=2) num_x = box_x1 + (box_w - num_w) / 2 num_y = box_y1 - 6 self.draw_text_with_glow(uid, (num_x, num_y), num_text, num_font, core_color="white", glow_color=glow_solid) # ========================================== # 2. Dynamic Centered Character Name # ========================================== final_char_name = char_name.title() available_name_w = (box_x1 - self.OFFSET_X) - 20 name_sz = base_sz min_sz = 20 name_font = self.get_font(name_sz) # ⚡ THE FIX: Dynamic Scaler Loop. Shrink font until it fits the available width. while True: try: n_w = name_font.getlength(final_char_name) except AttributeError: n_w = 20 if n_w <= available_name_w or name_sz <= min_sz: break name_sz -= 2 name_font = self.get_font(name_sz) try: n_bbox = uid.textbbox((0, 0), final_char_name, font=name_font, stroke_width=3) except: n_bbox = (0, 0, n_w, name_sz) n_w_final = n_bbox[2] - n_bbox[0] n_h_final = n_bbox[3] - n_bbox[1] # Center horizontally in the available space, and perfectly vertically in the banner name_x = self.OFFSET_X + 10 + (available_name_w - n_w_final) // 2 - n_bbox[0] name_y = self.OFFSET_Y + self.CARD_H - self.BANNER_H + (self.BANNER_H - n_h_final) // 2 - n_bbox[1] self.draw_text_with_glow(uid, (name_x, name_y), final_char_name, name_font, core_color="white", glow_color=glow_solid) buf = io.BytesIO() canvas.save(buf, format="WEBP", quality=85, method=0) buf.seek(0) return buf