pyWebLayout/tests/test_text_rendering_fix.py
2025-06-08 15:11:35 +02:00

286 lines
10 KiB
Python

#!/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()