From 1fb8a422f9e37dc641bc2cd9324347b51511ca19 Mon Sep 17 00:00:00 2001 From: Aleksander Majda Date: Fri, 29 Aug 2025 10:44:11 +0200 Subject: [PATCH] add text align options, fix text padding --- text_to_image.py | 122 +++++++++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 42 deletions(-) diff --git a/text_to_image.py b/text_to_image.py index 9c63b1e..701b485 100644 --- a/text_to_image.py +++ b/text_to_image.py @@ -10,6 +10,7 @@ class BookToolsTextToImage: self.device = device _positions = ["top", "bottom"] _alignments = ["left", "center"] + _text_justifications = ["left", "center", "right"] _colors = [ (255, 50, 50), @@ -81,6 +82,8 @@ class BookToolsTextToImage: "background_style": (["none", "rectangle", "ribbon", "glow"], {"default": "none"}), "background_color": (["auto", "dark", "light"], {"default": "auto"}), "background_padding": ("INT", {"default": 20, "min": 0, "max": 100}), + "text_justification": (["left", "center", "right"], {"default": "left"}), + "vertical_position": (["top", "center", "bottom"], {"default": "center"}), } } @@ -100,7 +103,7 @@ class BookToolsTextToImage: # Fallback to simple width calculation return sum(font.getbbox(char)[2] - font.getbbox(char)[0] for char in text) - def calculate_text_size(self, text, font_size, font_path, max_width, max_height, line_height_factor, char_spacing_factor): + def calculate_text_size(self, text, font_size, font_path, max_width, max_height, line_height_factor, char_spacing_factor, text_justification="left"): try: font = ImageFont.truetype(font_path, font_size) except Exception as e: @@ -117,45 +120,51 @@ class BookToolsTextToImage: for paragraph in paragraphs: words = paragraph.split() current_line = [] - current_width = 0 for word in words: - # measure word width using char_spacing - word_width = 0 - for char in word: + # Test if adding this word would exceed the line width + test_line = current_line + [word] + test_text = ' '.join(test_line) + + # Calculate test line width with character spacing (same method as draw_text_with_border) + test_width = 0 + for i, char in enumerate(test_text): bbox = font.getbbox(char) - word_width += (bbox[2] - bbox[0]) + char_spacing + test_width += (bbox[2] - bbox[0]) + if i < len(test_text) - 1: # Add spacing between chars, but not after last + test_width += char_spacing - # Remove extra spacing after the last character - if word: - word_width -= char_spacing - - # Consider space width - space_width = font.getbbox(' ')[2] if current_line else 0 - - if current_width + word_width + space_width <= max_width: + if test_width <= max_width: + # Word fits, add it to current line current_line.append(word) - current_width += word_width + space_width else: + # Word doesn't fit, finish current line and start new one if current_line: line_text = ' '.join(current_line) lines.append(line_text) + # Calculate line width consistently line_width = 0 - for char in line_text: + for i, char in enumerate(line_text): bbox = font.getbbox(char) - line_width += (bbox[2] - bbox[0]) + char_spacing + line_width += (bbox[2] - bbox[0]) + if i < len(line_text) - 1: # Only add spacing between chars, not after last + line_width += char_spacing max_line_width = max(max_line_width, line_width) + # Start new line with current word current_line = [word] - current_width = word_width + # Add the last line if it exists if current_line: line_text = ' '.join(current_line) lines.append(line_text) + # Calculate line width consistently line_width = 0 - for char in line_text: + for i, char in enumerate(line_text): bbox = font.getbbox(char) - line_width += (bbox[2] - bbox[0]) + char_spacing + line_width += (bbox[2] - bbox[0]) + if i < len(line_text) - 1: # Only add spacing between chars, not after last + line_width += char_spacing max_line_width = max(max_line_width, line_width) line_height = int(font_size * line_height_factor) @@ -166,6 +175,35 @@ class BookToolsTextToImage: return lines, fits, max_line_width, total_height + def calculate_line_x_position(self, line, font, justification, container_width, padding, char_spacing_factor): + """Calculate x position for a line based on justification""" + # Calculate total line width including character spacing (same as draw_text_with_border) + total_width = 0 + + for i, char in enumerate(line): + bbox = font.getbbox(char) + char_width = bbox[2] - bbox[0] + total_width += char_width + if i < len(line) - 1: # Add spacing between chars, but not after last char + total_width += font.size * char_spacing_factor + + # Calculate effective width (considering only main padding) + effective_width = container_width - (2 * padding) + + if justification == "left": + return padding + elif justification == "center": + # Center within the effective area, but ensure it doesn't go outside boundaries + center_x = padding + (effective_width - total_width) // 2 + # Ensure text doesn't go outside left or right boundaries + min_x = padding + max_x = container_width - total_width - padding + return max(min_x, min(max_x, center_x)) + elif justification == "right": + return container_width - total_width - padding + else: + return padding # Default to left + def create_gradient_color(self, color, height, direction="vertical"): start_color = color end_color = tuple(max(0, c - 80) for c in color) # Darker version @@ -412,7 +450,8 @@ class BookToolsTextToImage: line_height_factor, curve_amount=0.0, char_spacing=0.2, random_position=False, position_offset=20, shadow_offset=4, outline_thickness=3, use_gradient=False, gradient_direction="vertical", - background_style="none", background_color="auto", background_padding=20): + background_style="none", background_color="auto", background_padding=20, + text_justification="left", vertical_position="center"): image_tensor = image image_np = image_tensor.cpu().numpy() image = Image.fromarray((image_np.squeeze(0) * 255).astype(np.uint8)) @@ -433,7 +472,7 @@ class BookToolsTextToImage: # First check if min_font_size fits lines, fits, actual_width, actual_height = self.calculate_text_size( - text, min_font_size, font, effective_width, effective_height, line_height_factor, char_spacing + text, min_font_size, font, effective_width, effective_height, line_height_factor, char_spacing, text_justification ) if fits: @@ -441,7 +480,7 @@ class BookToolsTextToImage: while low <= high: mid = (low + high) // 2 lines, fits, actual_width, actual_height = self.calculate_text_size( - text, mid, font, effective_width, effective_height, line_height_factor, char_spacing + text, mid, font, effective_width, effective_height, line_height_factor, char_spacing, text_justification ) if fits: @@ -478,8 +517,13 @@ class BookToolsTextToImage: base_y += random.randint(-10, 10) else: - base_y = (height - total_height) // 2 - horizontal_pos = "center" + # Use the vertical_position parameter (consistent with effective area calculation) + if vertical_position == "top": + base_y = padding + elif vertical_position == "bottom": + base_y = height - total_height - padding + else: # center + base_y = padding + (effective_height - total_height) // 2 y = base_y text_color = random.choice(self._colors) @@ -494,24 +538,18 @@ class BookToolsTextToImage: self.draw_curved_text(draw, line, loaded_font, width//2, y, width - 2*padding, curve_amount, char_spacing, text_color=text_color) else: - # Calculate total line width including character spacing - total_width = 0 - for i, char in enumerate(line): - bbox = loaded_font.getbbox(char) - char_width = bbox[2] - bbox[0] - total_width += char_width - if i < len(line) - 1: # Add spacing between chars, but not after last char - total_width += loaded_font.size * char_spacing - - if horizontal_pos == "left": - x = padding + position_offset - else: # center - # Center based on actual total width including spacing - x = (width - total_width) // 2 - + # Calculate x position based on justification if random_position: - x += random.randint(-10, 10) - + # For random positioning, use the existing logic but adjust for justification + if horizontal_pos == "left": + base_x = padding + position_offset + else: # center + base_x = self.calculate_line_x_position(line, loaded_font, "center", width, padding, char_spacing) + x = base_x + random.randint(-10, 10) + else: + # Use text justification parameter + x = self.calculate_line_x_position(line, loaded_font, text_justification, width, padding, char_spacing) + self.draw_text_with_border(draw, line, loaded_font, x, y, char_spacing, text_color, shadow_offset, outline_thickness, use_gradient, gradient_direction, background_style, background_color, background_padding) y += line_height