from PIL import Image, ImageDraw, ImageFont # Add this function to get available fonts with fallbacks def get_font_path(font_name, font_size): """Get font path for the selected font with fallbacks""" font_mappings = { "Impact": [ "impact.ttf", "Impact.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" ], "Arial Bold": [ "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", "arial-bold.ttf" ], "Times Bold": [ "/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf", "times-bold.ttf" ], "Comic Sans": [ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "comic.ttf" ], "Bebas Neue": [ "/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", "bebas-neue.ttf" ] } # Get font paths for the selected font font_paths = font_mappings.get(font_name, []) # Try each path for font_path in font_paths: try: font = ImageFont.truetype(font_path, font_size) print(f"SUCCESS: Loaded {font_name} from {font_path}") return font except (OSError, IOError): continue # If no specific font found, try default system fonts default_paths = [ "DejaVuSans-Bold.ttf", "arial.ttf", "/System/Library/Fonts/Arial.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", ] for font_path in default_paths: try: font = ImageFont.truetype(font_path, font_size) print(f"FALLBACK: Loaded default font from {font_path} for {font_name}") return font except (OSError, IOError): continue # Final fallback try: font = ImageFont.load_default() print(f"FINAL FALLBACK: Using system default for {font_name}") return font except Exception: print(f"ERROR: Could not load any font for {font_name}") return None # Function to create template images def create_pattern_template(pattern, width=200, height=120): """Create a visual template showing text placement for each pattern""" # Create black background img = Image.new('RGB', (width, height), color='black') draw = ImageDraw.Draw(img) # Define positions based on pattern positions = [] if pattern == "2-lines-top": positions = [(width // 2, int(height * 0.15)), (width // 2, int(height * 0.35))] elif pattern == "2-lines-center": positions = [(width // 2, int(height * 0.35)), (width // 2, int(height * 0.65))] elif pattern == "2-lines-bottom": positions = [(width // 2, int(height * 0.65)), (width // 2, int(height * 0.85))] elif pattern == "3-lines-top": positions = [(width // 2, int(height * 0.15)), (width // 2, int(height * 0.35)), (width // 2, int(height * 0.55))] elif pattern == "3-lines-center": positions = [(width // 2, int(height * 0.25)), (width // 2, int(height * 0.50)), (width // 2, int(height * 0.75))] elif pattern == "3-lines-bottom": positions = [(width // 2, int(height * 0.45)), (width // 2, int(height * 0.65)), (width // 2, int(height * 0.85))] # Draw white rectangles to represent text boxes for i, (x, y) in enumerate(positions): # Draw white rectangle as text placeholder box_width = 60 box_height = 12 left = x - box_width // 2 top = y - box_height // 2 right = x + box_width // 2 bottom = y + box_height // 2 draw.rectangle([left, top, right, bottom], fill='white', outline='white') return img def get_pattern_template(pattern): """Get template for pattern - create on demand to avoid initialization issues""" return create_pattern_template(pattern) # Text Overlay Functions def add_text_to_image(img, pattern, line1, line2, line3, font_size, color, add_outline, font_name): """Overlay 2- or 3-line text on the image in a preset layout.""" print("=== DEBUG: add_text_to_image called ===") print(f"Input image: {type(img)}") print(f"Pattern: {pattern}") print(f"Lines: [{line1!r}, {line2!r}, {line3!r}]") print(f"Font size: {font_size}") print(f"Font name: {font_name}") print(f"Color: {color!r}") print(f"Add outline: {add_outline}") if img is None: print("ERROR: No image provided") return None, "Please supply an image first." try: # Create a working copy print(f"Original image mode: {img.mode}, size: {img.size}") image = img.convert("RGB") print(f"Converted image mode: {image.mode}") draw = ImageDraw.Draw(image) print("ImageDraw created successfully") # UPDATED: Use selected font instead of hardcoded paths font = get_font_path(font_name, font_size) if font is None: return None, f"Could not load font: {font_name}" w, h = image.size print(f"Image dimensions: {w}x{h}") # Pattern matching (same as before) positions = [] detected_pattern = "unknown" pattern_lower = pattern.lower() if ("2-lines-top" in pattern_lower) or ("2 lines - top" in pattern_lower): positions = [(w // 2, int(h * 0.10)), (w // 2, int(h * 0.20))] detected_pattern = "2-lines-top" elif ("2-lines-bottom" in pattern_lower) or ("2 lines - bottom" in pattern_lower): positions = [(w // 2, int(h * 0.80)), (w // 2, int(h * 0.90))] detected_pattern = "2-lines-bottom" elif ("2-lines-center" in pattern_lower) or ("2 lines - center" in pattern_lower): positions = [(w // 2, int(h * 0.45)), (w // 2, int(h * 0.55))] detected_pattern = "2-lines-center" elif ("3-lines-top" in pattern_lower) or ("3 lines - top" in pattern_lower): positions = [(w // 2, int(h * 0.10)), (w // 2, int(h * 0.20)), (w // 2, int(h * 0.30))] detected_pattern = "3-lines-top" elif ("3-lines-center" in pattern_lower) or ("3 lines - center" in pattern_lower): positions = [(w // 2, int(h * 0.40)), (w // 2, int(h * 0.50)), (w // 2, int(h * 0.60))] detected_pattern = "3-lines-center" elif ("3-lines-bottom" in pattern_lower) or ("3 lines - bottom" in pattern_lower): positions = [(w // 2, int(h * 0.70)), (w // 2, int(h * 0.80)), (w // 2, int(h * 0.90))] detected_pattern = "3-lines-bottom" else: print(f"WARNING: Unknown pattern '{pattern}', using 2-lines-center as default") positions = [(w // 2, int(h * 0.45)), (w // 2, int(h * 0.55))] detected_pattern = "2-lines-center (default)" print(f"Detected pattern: {detected_pattern}") print(f"Text positions calculated: {positions}") # Color parsing (same as before) original_color = color final_color = (255, 255, 255) if isinstance(color, str): if color.startswith('rgba(') and color.endswith(')'): print(f"Parsing RGBA string: {color}") try: rgba_part = color[5:-1] values = [float(x.strip()) for x in rgba_part.split(',')] if len(values) >= 3: final_color = (int(round(values[0])), int(round(values[1])), int(round(values[2]))) print(f"Successfully parsed RGBA {color} to RGB: {final_color}") except (ValueError, IndexError) as e: print(f"Error parsing RGBA {color}: {e}, using white") final_color = (255, 255, 255) elif color.startswith('#'): print(f"Converting hex color {color}") hex_color = color.lstrip('#') if len(hex_color) == 6: try: final_color = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) print(f"Successfully converted hex {color} to RGB: {final_color}") except ValueError as e: print(f"Invalid hex color {color}: {e}, using white") final_color = (255, 255, 255) print(f"Final color for drawing: {final_color}") # Draw text (same logic as before) lines = [line1, line2, line3] print(f"All input lines: {lines}") active_lines = [] for i in range(len(positions)): if i < len(lines) and lines[i] and lines[i].strip(): active_lines.append((positions[i], lines[i].strip())) print(f"Active lines to draw: {len(active_lines)} out of {len(positions)} positions") text_drawn = False for i, ((x, y), txt) in enumerate(active_lines): print(f"Drawing line {i+1}: '{txt}' at position ({x}, {y}) with font {font_name}") # Optional outline/stroke if add_outline: stroke_width = max(1, font_size // 20) print(f"Adding outline with stroke width: {stroke_width}") # Draw text outline (black) for dx in [-stroke_width, 0, stroke_width]: for dy in [-stroke_width, 0, stroke_width]: if dx != 0 or dy != 0: try: draw.text((x + dx, y + dy), txt, fill=(0, 0, 0), font=font, anchor="mm") except TypeError: try: bbox = draw.textbbox((0, 0), txt, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] draw.text((x - text_w//2 + dx, y - text_h//2 + dy), txt, fill=(0, 0, 0), font=font) except Exception: draw.text((x + dx, y + dy), txt, fill=(0, 0, 0), font=font) # Draw main text try: draw.text((x, y), txt, fill=final_color, font=font, anchor="mm") print(f"SUCCESS: Main text drawn with {font_name} at ({x}, {y})") except TypeError: try: bbox = draw.textbbox((0, 0), txt, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] fallback_x = x - text_w//2 fallback_y = y - text_h//2 draw.text((fallback_x, fallback_y), txt, fill=final_color, font=font) print(f"SUCCESS: Fallback text drawn with {font_name} at ({fallback_x}, {fallback_y})") except Exception: draw.text((x, y), txt, fill=final_color, font=font) print(f"SUCCESS: Basic text drawn with {font_name} at ({x}, {y})") text_drawn = True print(f"Completed drawing line {i+1}") if not text_drawn: print("No text was drawn - all lines were empty") return img, "No text to add (all lines were empty)" print("=== Text drawing completed successfully ===") return image, f"✅ {len(active_lines)} lines added! Font: {font_name}, Pattern: {detected_pattern}" except Exception as e: print(f"CRITICAL ERROR in add_text_to_image: {e}") import traceback traceback.print_exc() return None, f"Error adding text: {str(e)}"