""" Mono-space font testing concepts and demo. This test demonstrates why mono-space fonts are valuable for testing rendering, line-breaking, and hyphenation, even when using regular fonts. """ import unittest import os from PIL import Image from pyWebLayout.concrete.text import Text, Line from pyWebLayout.concrete.page import Page, Container from pyWebLayout.style.fonts import Font from pyWebLayout.style.layout import Alignment class TestMonospaceConcepts(unittest.TestCase): """Demonstrate mono-space testing concepts.""" def setUp(self): """Set up test with available fonts.""" # Use the project's default font self.regular_font = Font(font_size=12) # Analyze character width variance test_chars = "iIlLmMwW0O" self.char_analysis = {} for char in test_chars: text = Text(char, self.regular_font) self.char_analysis[char] = text.width widths = list(self.char_analysis.values()) self.min_width = min(widths) self.max_width = max(widths) self.variance = self.max_width - self.min_width print(f"\nFont analysis:") print(f"Character width range: {self.min_width}-{self.max_width}px") print(f"Variance: {self.variance}px") # Find most uniform character (closest to average) avg_width = sum(widths) / len(widths) self.uniform_char = min(self.char_analysis.keys(), key=lambda c: abs(self.char_analysis[c] - avg_width)) print(f"Most uniform character: '{self.uniform_char}' ({self.char_analysis[self.uniform_char]}px)") def test_character_width_predictability(self): """Show why predictable character widths matter for testing.""" print("\n=== Character Width Predictability Demo ===") # Compare narrow vs wide characters narrow_word = "ill" # Narrow characters wide_word = "WWW" # Wide characters uniform_word = self.uniform_char * 3 # Uniform characters narrow_text = Text(narrow_word, self.regular_font) wide_text = Text(wide_word, self.regular_font) uniform_text = Text(uniform_word, self.regular_font) print(f"Same length (3 chars), different widths:") print(f" '{narrow_word}': {narrow_text.width}px") print(f" '{wide_word}': {wide_text.width}px") print(f" '{uniform_word}': {uniform_text.width}px") # Show the problem this creates for testing width_ratio = wide_text.width / narrow_text.width print(f" Width ratio: {width_ratio:.1f}x") if width_ratio > 1.5: print(" → This variance makes line capacity unpredictable!") # With mono-space, all would be ~36px (3 chars × 12px each) theoretical_mono = 3 * 12 print(f" With mono-space: ~{theoretical_mono}px each") def test_line_capacity_challenges(self): """Show how variable character widths affect line capacity.""" print("\n=== Line Capacity Prediction Challenges ===") line_width = 120 # Fixed width # Test with different character types test_cases = [ ("narrow", "i" * 20), # 20 narrow chars ("wide", "W" * 8), # 8 wide chars ("mixed", "Hello World"), # Mixed realistic text ("uniform", self.uniform_char * 15) # 15 uniform chars ] print(f"Line width: {line_width}px") for name, test_text in test_cases: text_obj = Text(test_text, self.regular_font) fits = "YES" if text_obj.width <= line_width else "NO" print(f" {name:8}: '{test_text[:15]}...' ({len(test_text)} chars)") print(f" Width: {text_obj.width}px, Fits: {fits}") print("\nWith mono-space fonts:") char_width = 12 # Theoretical mono-space width capacity = line_width // char_width print(f" Predictable capacity: ~{capacity} characters") print(f" Any {capacity}-character string would fit") def test_word_breaking_complexity(self): """Demonstrate word breaking complexity with variable widths.""" print("\n=== Word Breaking Complexity Demo ===") # Create a narrow line line_width = 80 line = Line( spacing=(2, 4), origin=(0, 0), size=(line_width, 20), font=self.regular_font, halign=Alignment.LEFT ) # Test different word types test_words = [ ("narrow", "illillill"), # 9 narrow chars ("wide", "WWWWW"), # 5 wide chars ("mixed", "Hello"), # 5 mixed chars ] print(f"Line width: {line_width}px") for word_type, word in test_words: # Create fresh line for each test test_line = Line( spacing=(2, 4), origin=(0, 0), size=(line_width, 20), font=self.regular_font, halign=Alignment.LEFT ) word_obj = Text(word, self.regular_font) result = test_line.add_word(word, self.regular_font) fits = "YES" if result is None else "NO" print(f" {word_type:6}: '{word}' ({len(word)} chars, {word_obj.width}px) → {fits}") if result is not None and test_line.text_objects: added = test_line.text_objects[0].text print(f" Added: '{added}', Remaining: '{result}'") print("\nWith mono-space fonts, word fitting would be predictable:") char_width = 12 capacity = line_width // char_width print(f" Any word ≤ {capacity} characters would fit") print(f" Any word > {capacity} characters would need breaking") def test_alignment_consistency(self): """Show how alignment behavior varies with character widths.""" print("\n=== Alignment Consistency Demo ===") line_width = 150 # Test different alignments with various text test_texts = [ "ill ill ill", # Narrow characters "WWW WWW WWW", # Wide characters "The cat sat", # Mixed characters ] alignments = [ (Alignment.LEFT, "LEFT"), (Alignment.CENTER, "CENTER"), (Alignment.RIGHT, "RIGHT"), (Alignment.JUSTIFY, "JUSTIFY") ] results = {} for align_enum, align_name in alignments: print(f"\n{align_name} alignment:") for text in test_texts: line = Line( spacing=(3, 8), origin=(0, 0), size=(line_width, 20), font=self.regular_font, halign=align_enum ) # Add words to line words = text.split() for word in words: result = line.add_word(word, self.regular_font) if result is not None: break # Render and save line_image = line.render() # Calculate text coverage text_obj = Text(text.replace(" ", ""), self.regular_font) coverage = text_obj.width / line_width print(f" '{text}': {coverage:.1%} line coverage") # Save example output_dir = "test_output" if not os.path.exists(output_dir): os.makedirs(output_dir) filename = f"align_{align_name.lower()}_{text.replace(' ', '_')}.png" output_path = os.path.join(output_dir, filename) line_image.save(output_path) print("\nWith mono-space fonts:") print(" - Alignment calculations would be simpler") print(" - Spacing distribution would be more predictable") print(" - Visual consistency would be higher") def test_hyphenation_decision_factors(self): """Show factors affecting hyphenation decisions.""" print("\n=== Hyphenation Decision Factors ===") # Test word that might benefit from hyphenation test_word = "development" # 11 characters word_obj = Text(test_word, self.regular_font) print(f"Test word: '{test_word}' ({len(test_word)} chars, {word_obj.width}px)") # Test different line widths test_widths = [60, 80, 100, 120, 140] for width in test_widths: line = Line( spacing=(2, 4), origin=(0, 0), size=(width, 20), font=self.regular_font, halign=Alignment.LEFT ) result = line.add_word(test_word, self.regular_font) if result is None: status = "FITS completely" elif line.text_objects: added = line.text_objects[0].text status = f"PARTIAL: '{added}' + '{result}'" else: status = "REJECTED completely" # Calculate utilization utilization = word_obj.width / width print(f" Width {width:3}px ({utilization:>5.1%} util): {status}") print("\nWith mono-space fonts:") char_width = 12 word_width_mono = len(test_word) * char_width # 132px print(f" Word would be exactly {word_width_mono}px") print(f" Hyphenation decisions would be based on character count") print(f" Line capacity would be width ÷ {char_width}px per char") def test_create_visual_comparison(self): """Create visual comparison showing the difference.""" print("\n=== Creating Visual Comparison ===") # Create a page showing the problems with variable width fonts page = Page(size=(600, 400)) # Create test content test_text = "The quick brown fox jumps over lazy dogs with varying character widths." # Split into words and create multiple lines with different alignments words = test_text.split() # Create container for demonstration demo_container = Container( origin=(0, 0), size=(580, 380), direction='vertical', spacing=5, padding=(10, 10, 10, 10) ) alignments = [ (Alignment.LEFT, "Left Aligned"), (Alignment.CENTER, "Center Aligned"), (Alignment.RIGHT, "Right Aligned"), (Alignment.JUSTIFY, "Justified") ] for align_enum, title in alignments: # Add title from pyWebLayout.style.fonts import FontWeight title_text = Text(title + ":", Font(font_size=14, weight=FontWeight.BOLD)) demo_container.add_child(title_text) # Create line with this alignment line = Line( spacing=(3, 8), origin=(0, 0), size=(560, 20), font=self.regular_font, halign=align_enum ) # Add as many words as fit for word in words[:6]: # Limit to first 6 words result = line.add_word(word, self.regular_font) if result is not None: break demo_container.add_child(line) # Add demo to page page.add_child(demo_container) # Render and save page_image = page.render() output_dir = "test_output" if not os.path.exists(output_dir): os.makedirs(output_dir) output_path = os.path.join(output_dir, "monospace_concepts_demo.png") page_image.save(output_path) print(f"Visual demonstration saved to: {output_path}") print("This shows why mono-space fonts make testing more predictable!") # Validation self.assertIsInstance(page_image, Image.Image) self.assertEqual(page_image.size, (600, 400)) if __name__ == '__main__': # Ensure output directory exists if not os.path.exists("test_output"): os.makedirs("test_output") unittest.main(verbosity=2)