344 lines
12 KiB
Python
344 lines
12 KiB
Python
"""
|
||
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)
|