""" Test for line overflow behavior in pyWebLayout/concrete/text.py This test demonstrates how line overflow is triggered when words cannot fit in progressively shorter lines. Uses a simple sentence with non-hyphenatable words. """ import unittest import numpy as np from PIL import ImageFont from pyWebLayout.concrete.text import Line, Text from pyWebLayout.style import Font, FontStyle, FontWeight from pyWebLayout.style.layout import Alignment class TestLineOverflow(unittest.TestCase): """Test line overflow behavior with progressively shorter lines.""" def setUp(self): """Set up test fixtures with a basic font and simple sentence.""" # Create a simple font for testing self.font = Font() # Simple sentence with short, non-hyphenatable words self.simple_words = ["cat", "dog", "pig", "rat", "ox", "bee", "fly", "ant"] def test_line_overflow_progression(self): """Test that line overflow is triggered as line width decreases.""" # Start with a reasonably wide line that can fit all words initial_width = 800 line_height = 50 spacing = (5, 20) # min_spacing, max_spacing # Test results storage results = [] # Test with progressively shorter lines for width in range(initial_width, 50, -50): # Decrease by 50px each time # Create a new line with current width origin = np.array([0, 0]) size = (width, line_height) line = Line(spacing, origin, size, self.font, halign=Alignment.LEFT) # Try to add words until overflow occurs words_added = [] overflow_word = None for word in self.simple_words: result = line.add_word(word, self.font) if result is None: # Word fit in line words_added.append(word) else: # Overflow occurred overflow_word = word break # Record the test result test_result = { 'width': width, 'words_added': words_added.copy(), 'overflow_word': overflow_word, 'total_words_fit': len(words_added) } results.append(test_result) # Print progress for visibility print(f"Width {width}px: Fit {len(words_added)} words: {' '.join(words_added)}") if overflow_word: print(f" -> Overflow on word: '{overflow_word}'") # Assertions to verify expected behavior self.assertTrue(len(results) > 0, "Should have test results") # First (widest) line should fit more words than later lines first_result = results[0] last_result = results[-1] self.assertGreaterEqual( first_result['total_words_fit'], last_result['total_words_fit'], "Wider lines should fit at least as many words as narrower lines" ) # At some point, overflow should occur (not all words fit) overflow_occurred = any(r['overflow_word'] is not None for r in results) self.assertTrue(overflow_occurred, "Line overflow should occur with narrow lines") # Very narrow lines should fit fewer words narrow_results = [r for r in results if r['width'] < 200] if narrow_results: # At least one narrow line should have triggered overflow narrow_overflow = any(r['overflow_word'] is not None for r in narrow_results) self.assertTrue(narrow_overflow, "Narrow lines should trigger overflow") def test_single_word_overflow(self): """Test overflow behavior when even a single word doesn't fit.""" # Create a very narrow line narrow_width = 30 line_height = 50 spacing = (5, 20) origin = np.array([0, 0]) size = (narrow_width, line_height) line = Line(spacing, origin, size, self.font, halign=Alignment.LEFT) # Try to add a word that likely won't fit test_word = "elephant" # Longer word that should overflow result = line.add_word(test_word, self.font) # The implementation may partially fit the word and return the remaining part # Some overflow occurred - either full word or remaining part self.assertIsInstance(result, str, "Should return remaining text as string") self.assertGreater(len(result), 0, "Remaining text should not be empty") # Check that some part was fitted self.assertGreater(len(line.text_objects), 0, "Should have fitted at least some characters") # The fitted part + remaining part should equal the original word fitted_text = line.text_objects[0].text if line.text_objects else "" self.assertEqual(fitted_text + result, test_word, f"Fitted part '{fitted_text}' + remaining '{result}' should equal '{test_word}'") print(f"Word '{test_word}' partially fit: '{fitted_text}' fitted, '{result}' remaining") def test_empty_line_behavior(self): """Test behavior when adding words to an empty line.""" width = 300 line_height = 50 spacing = (5, 20) origin = np.array([0, 0]) size = (width, line_height) line = Line(spacing, origin, size, self.font, halign=Alignment.LEFT) # Initially empty self.assertEqual(len(line.text_objects), 0, "Line should start empty") # Add first word result = line.add_word("cat", self.font) self.assertIsNone(result, "First word should fit in reasonable width") self.assertEqual(len(line.text_objects), 1, "Should have one text object") self.assertEqual(line.text_objects[0].text, "cat", "Text should match added word") def test_progressive_overflow_demonstration(self): """Demonstrate the exact point where overflow begins.""" # Use a specific set of short words words = ["a", "bb", "ccc", "dd", "e"] # Start wide and narrow down until we find the overflow point for width in range(200, 10, -10): origin = np.array([0, 0]) size = (width, 50) line = Line((3, 15), origin, size, self.font, halign=Alignment.LEFT) words_that_fit = [] for word in words: result = line.add_word(word, self.font) if result is None: words_that_fit.append(word) else: # This is where overflow started print(f"Overflow at width {width}px: '{' '.join(words_that_fit)}' + '{word}' (overflow)") self.assertGreater(len(words_that_fit), 0, "Should fit at least some words before overflow") return # Test successful # If we get here, no overflow occurred even at minimum width print("No overflow occurred - all words fit even in narrowest line") if __name__ == '__main__': unittest.main(verbosity=2)