302 lines
12 KiB
Python
302 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit tests for multi-line text rendering and line wrapping functionality.
|
|
"""
|
|
|
|
import unittest
|
|
import os
|
|
from PIL import Image, ImageDraw
|
|
|
|
from pyWebLayout.concrete.text import Text, Line
|
|
from pyWebLayout.style import Font, FontStyle, FontWeight
|
|
from pyWebLayout.style.layout import Alignment
|
|
|
|
|
|
class TestMultilineRendering(unittest.TestCase):
|
|
"""Test cases for multi-line text rendering"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.font_style = Font(
|
|
font_path=None,
|
|
font_size=12,
|
|
colour=(0, 0, 0, 255)
|
|
)
|
|
|
|
# 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 _create_multiline_test(self, sentence, line_width, line_height, font_size=14):
|
|
"""
|
|
Helper method to test rendering a sentence across multiple lines
|
|
|
|
Args:
|
|
sentence: The sentence to render
|
|
line_width: Width of each line in pixels
|
|
line_height: Height of each line in pixels
|
|
font_size: Font size to use
|
|
|
|
Returns:
|
|
tuple: (actual_lines_used, lines_list, combined_image)
|
|
"""
|
|
font_style = Font(
|
|
font_path=None,
|
|
font_size=font_size,
|
|
colour=(0, 0, 0, 255)
|
|
)
|
|
|
|
# Split sentence into words
|
|
words = sentence.split()
|
|
|
|
# Create lines and distribute words
|
|
lines = []
|
|
words_remaining = words.copy()
|
|
|
|
while words_remaining:
|
|
# Create a new line
|
|
current_line = Line(
|
|
spacing=(3, 8), # min, max spacing
|
|
origin=(0, len(lines) * line_height),
|
|
size=(line_width, line_height),
|
|
font=font_style,
|
|
halign=Alignment.LEFT
|
|
)
|
|
|
|
lines.append(current_line)
|
|
|
|
# Add words to current line until it's full
|
|
words_added_to_line = []
|
|
while words_remaining:
|
|
word = words_remaining[0]
|
|
result = current_line.add_word(word)
|
|
|
|
if result is None:
|
|
# Word fit in the line
|
|
words_added_to_line.append(word)
|
|
words_remaining.pop(0)
|
|
else:
|
|
# Word didn't fit, try next line
|
|
break
|
|
|
|
# If no words were added to this line, break to avoid infinite loop
|
|
if not words_added_to_line:
|
|
# Force add the word to avoid infinite loop
|
|
current_line.add_word(words_remaining[0])
|
|
words_remaining.pop(0)
|
|
|
|
# Create combined image showing all lines
|
|
total_height = len(lines) * line_height
|
|
combined_image = Image.new('RGBA', (line_width, total_height), (255, 255, 255, 255))
|
|
|
|
for i, line in enumerate(lines):
|
|
line_img = line.render()
|
|
y_pos = i * line_height
|
|
combined_image.paste(line_img, (0, y_pos), line_img)
|
|
|
|
# Add a subtle line border for visualization
|
|
draw = ImageDraw.Draw(combined_image)
|
|
draw.rectangle([(0, y_pos), (line_width-1, y_pos + line_height-1)], outline=(200, 200, 200), width=1)
|
|
|
|
return len(lines), lines, combined_image
|
|
|
|
def test_two_line_sentence(self):
|
|
"""Test sentence that should wrap to two lines"""
|
|
sentence = "This is a simple test sentence that should wrap to exactly two lines."
|
|
line_width = 200
|
|
expected_lines = 2
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Save test image
|
|
filename = "test_multiline_1_two_line_sentence.png"
|
|
combined_image.save(filename)
|
|
self.test_images.append(filename)
|
|
|
|
# Assertions
|
|
self.assertGreaterEqual(actual_lines, 1, "Should have at least one line")
|
|
self.assertLessEqual(actual_lines, 3, "Should not exceed 3 lines for this sentence")
|
|
self.assertTrue(os.path.exists(filename), "Test image should be created")
|
|
|
|
# Check that all lines have content
|
|
for i, line in enumerate(lines):
|
|
self.assertGreater(len(line.text_objects), 0, f"Line {i+1} should have content")
|
|
|
|
def test_three_line_sentence(self):
|
|
"""Test sentence that should wrap to three lines"""
|
|
sentence = "This is a much longer sentence that contains many more words and should definitely wrap across three lines when rendered with the specified width constraints."
|
|
line_width = 180
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Save test image
|
|
filename = "test_multiline_2_three_line_sentence.png"
|
|
combined_image.save(filename)
|
|
self.test_images.append(filename)
|
|
|
|
# Assertions - Allow for font variations across environments
|
|
# Different fonts can cause different text widths and line wrapping behavior
|
|
self.assertGreaterEqual(actual_lines, 2, "Should have at least two lines")
|
|
self.assertLessEqual(actual_lines, 8, "Should not exceed 8 lines for this sentence (allowing for font variations)")
|
|
self.assertTrue(os.path.exists(filename), "Test image should be created")
|
|
|
|
def test_four_line_sentence(self):
|
|
"""Test sentence that should wrap to four lines"""
|
|
sentence = "Here we have an even longer sentence with significantly more content that will require four lines to properly display all the text when using the constrained width setting."
|
|
line_width = 160
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Save test image
|
|
filename = "test_multiline_3_four_line_sentence.png"
|
|
combined_image.save(filename)
|
|
self.test_images.append(filename)
|
|
|
|
# Assertions - Allow for font variations across environments
|
|
# Different fonts can cause different text widths and line wrapping behavior
|
|
self.assertGreaterEqual(actual_lines, 3, "Should have at least three lines")
|
|
self.assertLessEqual(actual_lines, 9, "Should not exceed 9 lines for this sentence (allowing for font variations)")
|
|
self.assertTrue(os.path.exists(filename), "Test image should be created")
|
|
|
|
def test_single_line_sentence(self):
|
|
"""Test short sentence that should fit on one line"""
|
|
sentence = "Short sentence."
|
|
line_width = 300
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Save test image
|
|
filename = "test_multiline_4_single_line_sentence.png"
|
|
combined_image.save(filename)
|
|
self.test_images.append(filename)
|
|
|
|
# Assertions
|
|
self.assertEqual(actual_lines, 1, "Short sentence should fit on one line")
|
|
self.assertGreater(len(lines[0].text_objects), 0, "Line should have content")
|
|
self.assertTrue(os.path.exists(filename), "Test image should be created")
|
|
|
|
def test_long_words_sentence(self):
|
|
"""Test sentence with long words that might need special handling"""
|
|
sentence = "This sentence has some really long words like supercalifragilisticexpialidocious that might need hyphenation."
|
|
line_width = 150
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Save test image
|
|
filename = "test_multiline_5_sentence_with_long_words.png"
|
|
combined_image.save(filename)
|
|
self.test_images.append(filename)
|
|
|
|
# Assertions
|
|
self.assertGreaterEqual(actual_lines, 2, "Should have at least two lines")
|
|
self.assertTrue(os.path.exists(filename), "Test image should be created")
|
|
|
|
def test_fixed_width_scenarios(self):
|
|
"""Test specific width scenarios to verify line utilization"""
|
|
sentence = "The quick brown fox jumps over the lazy dog near the riverbank."
|
|
widths = [300, 200, 150, 100, 80]
|
|
|
|
for width in widths:
|
|
with self.subTest(width=width):
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, width, 20, font_size=12
|
|
)
|
|
|
|
# Assertions
|
|
self.assertGreater(actual_lines, 0, f"Should have lines for width {width}")
|
|
self.assertIsInstance(combined_image, Image.Image)
|
|
|
|
# Save test image
|
|
filename = f"test_width_{width}px.png"
|
|
combined_image.save(filename)
|
|
self.test_images.append(filename)
|
|
self.assertTrue(os.path.exists(filename), f"Test image should be created for width {width}")
|
|
|
|
# Check line utilization
|
|
for j, line in enumerate(lines):
|
|
self.assertGreater(len(line.text_objects), 0, f"Line {j+1} should have content")
|
|
self.assertGreaterEqual(line._current_width, 0, f"Line {j+1} should have positive width")
|
|
|
|
def test_line_word_distribution(self):
|
|
"""Test that words are properly distributed across lines"""
|
|
sentence = "This is a test sentence with several words to distribute."
|
|
line_width = 200
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Check that each line has words
|
|
total_words = 0
|
|
for i, line in enumerate(lines):
|
|
word_count = len(line.text_objects)
|
|
self.assertGreater(word_count, 0, f"Line {i+1} should have at least one word")
|
|
total_words += word_count
|
|
|
|
# Total words should match original sentence
|
|
original_words = len(sentence.split())
|
|
self.assertEqual(total_words, original_words, "All words should be distributed across lines")
|
|
|
|
def test_line_width_constraints(self):
|
|
"""Test that lines respect width constraints"""
|
|
sentence = "Testing width constraints with this sentence."
|
|
line_width = 150
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Check that no line exceeds the specified width (with some tolerance for edge cases)
|
|
for i, line in enumerate(lines):
|
|
# Current width should not significantly exceed line width
|
|
# Allow some tolerance for edge cases where words are force-fitted
|
|
self.assertLessEqual(line._current_width, line_width + 50,
|
|
f"Line {i+1} width should not significantly exceed limit")
|
|
|
|
def test_empty_sentence(self):
|
|
"""Test handling of empty sentence"""
|
|
sentence = ""
|
|
line_width = 200
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Should handle empty sentence gracefully
|
|
self.assertIsInstance(actual_lines, int)
|
|
self.assertIsInstance(lines, list)
|
|
self.assertIsInstance(combined_image, Image.Image)
|
|
|
|
def test_single_word_sentence(self):
|
|
"""Test handling of single word sentence"""
|
|
sentence = "Hello"
|
|
line_width = 200
|
|
|
|
actual_lines, lines, combined_image = self._create_multiline_test(
|
|
sentence, line_width, 25, font_size=12
|
|
)
|
|
|
|
# Single word should fit on one line
|
|
self.assertEqual(actual_lines, 1, "Single word should fit on one line")
|
|
self.assertEqual(len(lines[0].text_objects), 1, "Line should have exactly one word")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|