def process_multiline_text(text, font, max_width, max_height): """Process multiline text with automatic wrapping and fitting""" try: from PIL import ImageFont, ImageDraw, Image print(f"[Text Processor] Processing multiline text with constraints: {max_width}x{max_height}") # Split text into lines lines = text.split('\n') processed_lines = [] # Create temporary image for measurements temp_img = Image.new('RGBA', (1, 1)) draw = ImageDraw.Draw(temp_img) total_height = 0 for line in lines: if not line.strip(): # Empty line bbox = draw.textbbox((0, 0), "A", font=font) line_height = bbox[3] - bbox[1] processed_lines.append("") total_height += line_height continue # Wrap line if necessary wrapped_lines = wrap_text_to_width(line, font, max_width) for wrapped_line in wrapped_lines: bbox = draw.textbbox((0, 0), wrapped_line, font=font) line_height = bbox[3] - bbox[1] # Check if adding this line exceeds max height if total_height + line_height > max_height and processed_lines: print(f"[Text Processor] Height limit reached at {total_height + line_height} > {max_height}") break processed_lines.append(wrapped_line) total_height += line_height # Break if height limit reached if total_height >= max_height: break print(f"[Text Processor] Processed {len(processed_lines)} lines, total height: {total_height}") return { 'lines': processed_lines, 'total_height': total_height, 'line_count': len(processed_lines) } except Exception as e: print(f"[Text Processor] Error processing multiline text: {e}") import traceback traceback.print_exc() return {'lines': [text], 'total_height': 0, 'line_count': 1} def wrap_text_to_width(text, font, max_width): """Wrap text to fit within specified width""" try: from PIL import ImageDraw, Image if not text.strip(): return [""] # Create temporary image for measurements temp_img = Image.new('RGBA', (1, 1)) draw = ImageDraw.Draw(temp_img) # Check if entire text fits bbox = draw.textbbox((0, 0), text, font=font) text_width = bbox[2] - bbox[0] if text_width <= max_width: return [text] # Split into words and wrap words = text.split() lines = [] current_line = "" for word in words: test_line = current_line + (" " if current_line else "") + word bbox = draw.textbbox((0, 0), test_line, font=font) test_width = bbox[2] - bbox[0] if test_width <= max_width: current_line = test_line else: if current_line: lines.append(current_line) current_line = word else: # Single word is too long, force wrap lines.append(word) current_line = "" if current_line: lines.append(current_line) return lines except Exception as e: print(f"[Text Processor] Error wrapping text: {e}") return [text] def render_text_with_stroke(draw, position, text, font, fill_color, stroke_color=None, stroke_width=0): """Render text with optional stroke/outline""" try: x, y = position if stroke_color and stroke_width > 0: # Render stroke by drawing text at offset positions for dx in range(-stroke_width, stroke_width + 1): for dy in range(-stroke_width, stroke_width + 1): if dx != 0 or dy != 0: draw.text((x + dx, y + dy), text, font=font, fill=stroke_color) # Render main text draw.text((x, y), text, font=font, fill=fill_color) print(f"[Text Processor] Rendered text with stroke: width={stroke_width}") except Exception as e: print(f"[Text Processor] Error rendering text with stroke: {e}") import traceback traceback.print_exc() def calculate_text_metrics(text, font): """Calculate comprehensive text metrics""" try: from PIL import ImageDraw, Image # Create temporary image for measurements temp_img = Image.new('RGBA', (1, 1)) draw = ImageDraw.Draw(temp_img) # Get bounding box bbox = draw.textbbox((0, 0), text, font=font) metrics = { 'width': bbox[2] - bbox[0], 'height': bbox[3] - bbox[1], 'left_bearing': bbox[0], 'top_bearing': bbox[1], 'advance_width': bbox[2], 'advance_height': bbox[3] } # Get additional font metrics if available try: metrics['ascent'] = font.getmetrics()[0] metrics['descent'] = font.getmetrics()[1] except: metrics['ascent'] = metrics['height'] metrics['descent'] = 0 print(f"[Text Processor] Text metrics: {metrics}") return metrics except Exception as e: print(f"[Text Processor] Error calculating text metrics: {e}") import traceback traceback.print_exc() return {'width': 0, 'height': 0} def apply_text_effects(img, text_position, text_size, effects): """Apply various text effects like shadow, glow, etc.""" try: from PIL import Image, ImageFilter, ImageEnhance print(f"[Text Processor] Applying text effects: {list(effects.keys())}") result_img = img.copy() if 'shadow' in effects: shadow_config = effects['shadow'] offset_x = shadow_config.get('offset_x', 2) offset_y = shadow_config.get('offset_y', 2) blur_radius = shadow_config.get('blur', 0) shadow_color = shadow_config.get('color', (0, 0, 0, 128)) # Create shadow layer shadow_img = Image.new('RGBA', img.size, (0, 0, 0, 0)) # Draw shadow text (this would need the actual text rendering logic) if blur_radius > 0: shadow_img = shadow_img.filter(ImageFilter.GaussianBlur(radius=blur_radius)) # Composite shadow result_img = Image.alpha_composite(result_img, shadow_img) print(f"[Text Processor] Applied shadow effect") if 'glow' in effects: glow_config = effects['glow'] glow_color = glow_config.get('color', (255, 255, 255, 128)) glow_radius = glow_config.get('radius', 3) # Create glow effect glow_img = Image.new('RGBA', img.size, (0, 0, 0, 0)) # Apply glow rendering logic here result_img = Image.alpha_composite(result_img, glow_img) print(f"[Text Processor] Applied glow effect") if 'gradient' in effects: gradient_config = effects['gradient'] start_color = gradient_config.get('start', (255, 255, 255)) end_color = gradient_config.get('end', (0, 0, 0)) direction = gradient_config.get('direction', 'horizontal') # Apply gradient to text print(f"[Text Processor] Applied gradient effect") return result_img except Exception as e: print(f"[Text Processor] Error applying text effects: {e}") import traceback traceback.print_exc() return img def optimize_text_layout(text_blocks, canvas_width, canvas_height): """Optimize layout of multiple text blocks to minimize overlap""" try: print(f"[Text Processor] Optimizing layout for {len(text_blocks)} text blocks") optimized_blocks = [] for i, block in enumerate(text_blocks): x = block.get('x', 0) y = block.get('y', 0) width = block.get('width', 0) height = block.get('height', 0) # Check for overlaps with previous blocks overlap_found = False for prev_block in optimized_blocks: if rectangles_overlap( (x, y, width, height), (prev_block['x'], prev_block['y'], prev_block['width'], prev_block['height']) ): overlap_found = True break if overlap_found: # Find new position new_x, new_y = find_non_overlapping_position( width, height, optimized_blocks, canvas_width, canvas_height ) x, y = new_x, new_y print(f"[Text Processor] Moved block {i} to avoid overlap: ({x}, {y})") optimized_block = block.copy() optimized_block.update({'x': x, 'y': y}) optimized_blocks.append(optimized_block) print(f"[Text Processor] Layout optimization complete") return optimized_blocks except Exception as e: print(f"[Text Processor] Error optimizing text layout: {e}") import traceback traceback.print_exc() return text_blocks def rectangles_overlap(rect1, rect2): """Check if two rectangles overlap""" x1, y1, w1, h1 = rect1 x2, y2, w2, h2 = rect2 return not (x1 + w1 <= x2 or x2 + w2 <= x1 or y1 + h1 <= y2 or y2 + h2 <= y1) def find_non_overlapping_position(width, height, existing_blocks, canvas_width, canvas_height): """Find a position that doesn't overlap with existing blocks""" # Try positions from top-left, moving right then down for y in range(0, canvas_height - height, 20): for x in range(0, canvas_width - width, 20): rect = (x, y, width, height) overlap = False for block in existing_blocks: if rectangles_overlap(rect, (block['x'], block['y'], block['width'], block['height'])): overlap = True break if not overlap: return x, y # If no position found, return original or default return 0, 0 def validate_text_rendering(img, text_positions): """Validate that text rendering was successful""" try: print(f"[Text Processor] Validating text rendering for {len(text_positions)} positions") validation_results = [] for i, pos in enumerate(text_positions): result = { 'position_index': i, 'x': pos.get('x', 0), 'y': pos.get('y', 0), 'width': pos.get('width', 0), 'height': pos.get('height', 0), 'valid': True, 'issues': [] } # Check bounds if pos.get('x', 0) < 0 or pos.get('y', 0) < 0: result['valid'] = False result['issues'].append('Negative position') if pos.get('x', 0) + pos.get('width', 0) > img.width: result['valid'] = False result['issues'].append('Exceeds image width') if pos.get('y', 0) + pos.get('height', 0) > img.height: result['valid'] = False result['issues'].append('Exceeds image height') # Check for zero dimensions if pos.get('width', 0) == 0 or pos.get('height', 0) == 0: result['valid'] = False result['issues'].append('Zero dimensions') validation_results.append(result) valid_count = sum(1 for r in validation_results if r['valid']) print(f"[Text Processor] Validation complete: {valid_count}/{len(text_positions)} positions valid") return validation_results except Exception as e: print(f"[Text Processor] Error validating text rendering: {e}") import traceback traceback.print_exc() return []