#!/usr/bin/env python3 """ Unit tests for text rendering fixes. Tests the fixes for text cropping and line length issues. """ import unittest import os from PIL import Image, ImageFont from pyWebLayout.concrete.text import Text, Line from pyWebLayout.style import Font, FontStyle, FontWeight from pyWebLayout.style.layout import Alignment class TestTextRenderingFix(unittest.TestCase): """Test cases for text rendering fixes""" def setUp(self): """Set up test fixtures""" self.font_style = Font( font_path=None, # Use default font font_size=16, colour=(0, 0, 0, 255), weight=FontWeight.NORMAL, style=FontStyle.NORMAL ) # Clean up any existing test images self.test_images = [] def tearDown(self): """Clean up after tests""" # Clean up test images for img in self.test_images: if os.path.exists(img): os.remove(img) def test_text_cropping_fix(self): """Test that text is no longer cropped at the beginning and end""" # Test with text that might have overhang (like italic or characters with descenders) test_texts = [ "Hello World!", "Typography", "gjpqy", # Characters with descenders "AWVT", # Characters that might have overhang "Italic Text" ] for i, text_content in enumerate(test_texts): with self.subTest(text=text_content): text = Text(text_content, self.font_style) # Verify dimensions are reasonable self.assertGreater(text.width, 0, f"Text '{text_content}' should have positive width") self.assertGreater(text.height, 0, f"Text '{text_content}' should have positive height") # Render the text rendered = text.render() # Verify rendered image self.assertIsInstance(rendered, Image.Image) self.assertGreater(rendered.size[0], 0, "Rendered image should have positive width") self.assertGreater(rendered.size[1], 0, "Rendered image should have positive height") # Save for visual inspection output_path = f"test_text_{i}_{text_content.replace(' ', '_').replace('!', '')}.png" rendered.save(output_path) self.test_images.append(output_path) self.assertTrue(os.path.exists(output_path), f"Test image should be created for '{text_content}'") def test_line_length_fix(self): """Test that lines are using the full available width properly""" font_style = Font( font_path=None, font_size=14, colour=(0, 0, 0, 255) ) # Create a line with specific width line_width = 300 line_height = 20 spacing = (5, 10) # min, max spacing line = Line( spacing=spacing, origin=(0, 0), size=(line_width, line_height), font=font_style, halign=Alignment.LEFT ) # Add words to the line words = ["This", "is", "a", "test", "of", "line", "length", "calculation"] words_added = 0 for word in words: result = line.add_word(word) if result: # Word didn't fit break else: words_added += 1 # Assertions self.assertGreater(words_added, 0, "Should have added at least one word") self.assertGreaterEqual(line._current_width, 0, "Line width should be non-negative") self.assertLessEqual(line._current_width, line_width, "Line width should not exceed maximum") # Render the line rendered_line = line.render() self.assertIsInstance(rendered_line, Image.Image) self.assertEqual(rendered_line.size, (line_width, line_height)) # Save for inspection output_path = "test_line_length.png" rendered_line.save(output_path) self.test_images.append(output_path) self.assertTrue(os.path.exists(output_path), "Line test image should be created") def test_justification(self): """Test text justification to ensure proper spacing""" font_style = Font( font_path=None, font_size=12, colour=(0, 0, 0, 255) ) alignments = [ (Alignment.LEFT, "left"), (Alignment.CENTER, "center"), (Alignment.RIGHT, "right"), (Alignment.JUSTIFY, "justify") ] for alignment, name in alignments: with self.subTest(alignment=name): line = Line( spacing=(3, 8), origin=(0, 0), size=(250, 18), font=font_style, halign=alignment ) # Add some words words = ["Testing", "text", "alignment", "and", "spacing"] for word in words: line.add_word(word) # Verify line has content self.assertGreater(len(line.text_objects), 0, f"{name} alignment should have text objects") # Render and verify rendered = line.render() self.assertIsInstance(rendered, Image.Image) self.assertEqual(rendered.size, (250, 18)) # Save for inspection output_path = f"test_alignment_{name}.png" rendered.save(output_path) self.test_images.append(output_path) self.assertTrue(os.path.exists(output_path), f"Alignment test image should be created for {name}") def test_text_dimensions_consistency(self): """Test that text dimensions are consistent between calculation and rendering""" test_texts = ["Short", "Medium length text", "Very long text that might cause issues"] for text_content in test_texts: with self.subTest(text=text_content): text = Text(text_content, self.font_style) # Get calculated dimensions calc_width = text.width calc_height = text.height calc_size = text.size # Verify consistency self.assertEqual(calc_size, (calc_width, calc_height)) self.assertGreater(calc_width, 0) self.assertGreater(calc_height, 0) # Render and check dimensions match expectation rendered = text.render() self.assertIsInstance(rendered, Image.Image) # Note: rendered size might differ slightly due to margins/padding self.assertGreaterEqual(rendered.size[0], calc_width - 10) # Allow small tolerance self.assertGreaterEqual(rendered.size[1], calc_height - 10) def test_different_font_sizes(self): """Test text rendering with different font sizes""" font_sizes = [8, 12, 16, 20, 24] test_text = "Sample Text" for font_size in font_sizes: with self.subTest(font_size=font_size): font = Font( font_path=None, font_size=font_size, colour=(0, 0, 0, 255) ) text = Text(test_text, font) # Larger fonts should generally produce larger text self.assertGreater(text.width, 0) self.assertGreater(text.height, 0) # Render should work rendered = text.render() self.assertIsInstance(rendered, Image.Image) def test_empty_text_handling(self): """Test handling of empty text""" text = Text("", self.font_style) # Should handle empty text gracefully self.assertGreaterEqual(text.width, 0) self.assertGreaterEqual(text.height, 0) # Should be able to render rendered = text.render() self.assertIsInstance(rendered, Image.Image) def test_line_multiple_words(self): """Test adding multiple words to a line""" font_style = Font(font_path=None, font_size=12, colour=(0, 0, 0, 255)) line = Line( spacing=(3, 8), origin=(0, 0), size=(200, 20), font=font_style, halign=Alignment.LEFT ) words = ["One", "Two", "Three", "Four", "Five"] added_words = [] for word in words: result = line.add_word(word) if result is None: added_words.append(word) else: break # Should have added at least some words self.assertGreater(len(added_words), 0) self.assertEqual(len(line.text_objects), len(added_words)) # Verify text objects contain correct text for i, text_obj in enumerate(line.text_objects): self.assertEqual(text_obj.text, added_words[i]) def test_line_spacing_constraints(self): """Test that line spacing respects min/max constraints""" font_style = Font(font_path=None, font_size=12, colour=(0, 0, 0, 255)) min_spacing = 3 max_spacing = 10 line = Line( spacing=(min_spacing, max_spacing), origin=(0, 0), size=(300, 20), font=font_style, halign=Alignment.JUSTIFY # Justify will test spacing limits ) # Add multiple words words = ["Test", "spacing", "constraints", "here"] for word in words: line.add_word(word) # Render the line rendered = line.render() self.assertIsInstance(rendered, Image.Image) # Line should respect spacing constraints (this is more of a system test) self.assertGreater(len(line.text_objects), 1, "Should have multiple words for spacing test") if __name__ == '__main__': unittest.main()