def calculate_available_text_area(width, height, overlays): """Calculate available area for text placement, avoiding overlay positions""" try: print(f"[Text Fitting] Calculating available area for {width}x{height} with {len(overlays)} overlays") # Create a boolean mask for available areas (True = available, False = blocked) available_mask = [[True for _ in range(width)] for _ in range(height)] # Mark overlay positions as unavailable for overlay in overlays: overlay_x = int(overlay.get('x', 0)) overlay_y = int(overlay.get('y', 0)) overlay_width = int(overlay.get('width', 0)) overlay_height = int(overlay.get('height', 0)) # Add some padding around overlays padding = 10 start_x = max(0, overlay_x - padding) end_x = min(width, overlay_x + overlay_width + padding) start_y = max(0, overlay_y - padding) end_y = min(height, overlay_y + overlay_height + padding) for y in range(start_y, end_y): for x in range(start_x, end_x): if 0 <= y < height and 0 <= x < width: available_mask[y][x] = False # Find largest available rectangular area max_area = 0 best_rect = (0, 0, 0, 0) # x, y, width, height # Use dynamic programming approach to find largest rectangle heights = [0] * width for row in range(height): # Update heights array for col in range(width): if available_mask[row][col]: heights[col] += 1 else: heights[col] = 0 # Find largest rectangle in histogram rect = largest_rectangle_in_histogram(heights) area = rect[2] * rect[3] # width * height if area > max_area: max_area = area best_rect = (rect[0], row - rect[3] + 1, rect[2], rect[3]) print(f"[Text Fitting] Found best rectangle: x={best_rect[0]}, y={best_rect[1]}, w={best_rect[2]}, h={best_rect[3]}, area={max_area}") return { 'x': best_rect[0], 'y': best_rect[1], 'width': best_rect[2], 'height': best_rect[3], 'area': max_area } except Exception as e: print(f"[Text Fitting] Error calculating available text area: {e}") import traceback traceback.print_exc() return {'x': 0, 'y': 0, 'width': width, 'height': height, 'area': width * height} def largest_rectangle_in_histogram(heights): """Find the largest rectangle in a histogram using stack-based algorithm""" stack = [] max_area = 0 best_rect = (0, 0, 0, 0) # x, y, width, height for i, h in enumerate(heights): start = i while stack and stack[-1][1] > h: idx, height = stack.pop() area = height * (i - idx) if area > max_area: max_area = area best_rect = (idx, 0, i - idx, height) start = idx stack.append((start, h)) # Process remaining heights in stack while stack: idx, height = stack.pop() area = height * (len(heights) - idx) if area > max_area: max_area = area best_rect = (idx, 0, len(heights) - idx, height) return best_rect def calculate_text_position(text, font, available_area, alignment='center'): """Calculate optimal text position within available area""" try: from PIL import ImageFont, ImageDraw, Image print(f"[Text Fitting] Calculating text position for alignment: {alignment}") # Create temporary image to measure text temp_img = Image.new('RGBA', (1, 1)) draw = ImageDraw.Draw(temp_img) # Get text bounding box bbox = draw.textbbox((0, 0), text, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] area_x = available_area['x'] area_y = available_area['y'] area_width = available_area['width'] area_height = available_area['height'] print(f"[Text Fitting] Text size: {text_width}x{text_height}, Available area: {area_width}x{area_height}") # Calculate position based on alignment if alignment == 'center': x = area_x + (area_width - text_width) // 2 y = area_y + (area_height - text_height) // 2 elif alignment == 'top-left': x = area_x y = area_y elif alignment == 'top-center': x = area_x + (area_width - text_width) // 2 y = area_y elif alignment == 'top-right': x = area_x + area_width - text_width y = area_y elif alignment == 'center-left': x = area_x y = area_y + (area_height - text_height) // 2 elif alignment == 'center-right': x = area_x + area_width - text_width y = area_y + (area_height - text_height) // 2 elif alignment == 'bottom-left': x = area_x y = area_y + area_height - text_height elif alignment == 'bottom-center': x = area_x + (area_width - text_width) // 2 y = area_y + area_height - text_height elif alignment == 'bottom-right': x = area_x + area_width - text_width y = area_y + area_height - text_height else: # Default to center x = area_x + (area_width - text_width) // 2 y = area_y + (area_height - text_height) // 2 # Ensure position is within bounds x = max(area_x, min(x, area_x + area_width - text_width)) y = max(area_y, min(y, area_y + area_height - text_height)) print(f"[Text Fitting] Calculated position: ({x}, {y})") return { 'x': x, 'y': y, 'text_width': text_width, 'text_height': text_height } except Exception as e: print(f"[Text Fitting] Error calculating text position: {e}") import traceback traceback.print_exc() return { 'x': available_area['x'], 'y': available_area['y'], 'text_width': 0, 'text_height': 0 } def find_optimal_font_size(text, font_path, max_width, max_height, min_size=8, max_size=200): """Find the largest font size that fits within the given constraints""" try: from PIL import ImageFont, ImageDraw, Image print(f"[Text Fitting] Finding optimal font size for constraints: {max_width}x{max_height}") # Binary search for optimal font size low, high = min_size, max_size best_size = min_size temp_img = Image.new('RGBA', (1, 1)) draw = ImageDraw.Draw(temp_img) while low <= high: mid = (low + high) // 2 try: font = ImageFont.truetype(font_path, mid) bbox = draw.textbbox((0, 0), text, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] if text_width <= max_width and text_height <= max_height: best_size = mid low = mid + 1 else: high = mid - 1 except Exception as font_error: print(f"[Text Fitting] Font size {mid} failed: {font_error}") high = mid - 1 print(f"[Text Fitting] Optimal font size: {best_size}") return best_size except Exception as e: print(f"[Text Fitting] Error finding optimal font size: {e}") import traceback traceback.print_exc() return min_size def calculate_text_scaling(text, font, target_width, target_height): """Calculate scaling factors to fit text within target dimensions""" try: from PIL import ImageDraw, Image print(f"[Text Fitting] Calculating scaling for target: {target_width}x{target_height}") # Measure current text size temp_img = Image.new('RGBA', (1, 1)) draw = ImageDraw.Draw(temp_img) bbox = draw.textbbox((0, 0), text, font=font) current_width = bbox[2] - bbox[0] current_height = bbox[3] - bbox[1] if current_width == 0 or current_height == 0: return 1.0, 1.0, 1.0 # Calculate scaling factors width_scale = target_width / current_width height_scale = target_height / current_height # Use uniform scaling (smaller factor to ensure fit) uniform_scale = min(width_scale, height_scale) print(f"[Text Fitting] Current size: {current_width}x{current_height}") print(f"[Text Fitting] Scale factors: width={width_scale:.2f}, height={height_scale:.2f}, uniform={uniform_scale:.2f}") return width_scale, height_scale, uniform_scale except Exception as e: print(f"[Text Fitting] Error calculating text scaling: {e}") import traceback traceback.print_exc() return 1.0, 1.0, 1.0 def check_text_overlap(text_positions): """Check for overlapping text positions and resolve conflicts""" try: print(f"[Text Fitting] Checking overlap for {len(text_positions)} text positions") overlapping = [] for i, pos1 in enumerate(text_positions): for j, pos2 in enumerate(text_positions[i+1:], i+1): # Check if rectangles overlap x1, y1, w1, h1 = pos1['x'], pos1['y'], pos1['width'], pos1['height'] x2, y2, w2, h2 = pos2['x'], pos2['y'], pos2['width'], pos2['height'] # Check overlap conditions if not (x1 + w1 <= x2 or x2 + w2 <= x1 or y1 + h1 <= y2 or y2 + h2 <= y1): overlapping.append((i, j)) print(f"[Text Fitting] Overlap detected between positions {i} and {j}") return overlapping except Exception as e: print(f"[Text Fitting] Error checking text overlap: {e}") import traceback traceback.print_exc() return [] def resolve_text_conflicts(text_positions, canvas_width, canvas_height): """Resolve overlapping text positions by repositioning""" try: print(f"[Text Fitting] Resolving text conflicts for {len(text_positions)} positions") resolved_positions = text_positions.copy() overlaps = check_text_overlap(resolved_positions) # Simple conflict resolution: move overlapping text for i, j in overlaps: pos1 = resolved_positions[i] pos2 = resolved_positions[j] # Move the second text down or to the right new_y = pos1['y'] + pos1['height'] + 10 if new_y + pos2['height'] <= canvas_height: resolved_positions[j]['y'] = new_y else: # Try moving to the right new_x = pos1['x'] + pos1['width'] + 10 if new_x + pos2['width'] <= canvas_width: resolved_positions[j]['x'] = new_x else: # If can't fit, reduce to smaller area print(f"[Text Fitting] Could not resolve conflict for position {j}") print(f"[Text Fitting] Resolved {len(overlaps)} conflicts") return resolved_positions except Exception as e: print(f"[Text Fitting] Error resolving text conflicts: {e}") import traceback traceback.print_exc() return text_positions