340 lines
14 KiB
Python
340 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test script to verify the paragraph layout system with pagination and state management.
|
|
"""
|
|
|
|
from PIL import Image, ImageDraw
|
|
from pyWebLayout.abstract.block import Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font, FontStyle, FontWeight
|
|
from pyWebLayout.typesetting.paragraph_layout import ParagraphLayout, ParagraphRenderingState, ParagraphLayoutResult
|
|
from pyWebLayout.style.layout import Alignment
|
|
|
|
def create_test_paragraph(text: str) -> Paragraph:
|
|
"""Create a test paragraph with the given text."""
|
|
font_style = Font(
|
|
font_path=None,
|
|
font_size=12,
|
|
colour=(0, 0, 0, 255)
|
|
)
|
|
|
|
paragraph = Paragraph(style=font_style)
|
|
|
|
# Split text into words and add them to the paragraph
|
|
words = text.split()
|
|
for word_text in words:
|
|
word = Word(word_text, font_style)
|
|
paragraph.add_word(word)
|
|
|
|
return paragraph
|
|
|
|
def test_basic_paragraph_layout():
|
|
"""Test basic paragraph layout without height constraints."""
|
|
print("Testing basic paragraph layout...")
|
|
|
|
text = "This is a test paragraph that should be laid out across multiple lines based on the available width."
|
|
paragraph = create_test_paragraph(text)
|
|
|
|
# Create layout manager
|
|
layout = ParagraphLayout(
|
|
line_width=200,
|
|
line_height=20,
|
|
word_spacing=(3, 8),
|
|
line_spacing=2,
|
|
halign=Alignment.LEFT
|
|
)
|
|
|
|
# Layout the paragraph
|
|
lines = layout.layout_paragraph(paragraph)
|
|
|
|
print(f" Generated {len(lines)} lines")
|
|
for i, line in enumerate(lines):
|
|
words_in_line = [word.word.text for word in line.renderable_words]
|
|
print(f" Line {i+1}: {' '.join(words_in_line)}")
|
|
|
|
# Calculate total height
|
|
total_height = layout.calculate_paragraph_height(paragraph)
|
|
print(f" Total height: {total_height}px")
|
|
|
|
# Create visual representation
|
|
if lines:
|
|
# Create combined image
|
|
canvas = Image.new('RGB', (layout.line_width, total_height), (255, 255, 255))
|
|
|
|
for i, line in enumerate(lines):
|
|
line_img = line.render()
|
|
y_pos = i * (layout.line_height + layout.line_spacing)
|
|
canvas.paste(line_img, (0, y_pos), line_img)
|
|
|
|
canvas.save("test_basic_paragraph_layout.png")
|
|
print(f" Saved as: test_basic_paragraph_layout.png")
|
|
|
|
print()
|
|
|
|
def test_pagination_with_height_constraint():
|
|
"""Test paragraph layout with height constraints (pagination)."""
|
|
print("Testing pagination with height constraints...")
|
|
|
|
text = "This is a much longer paragraph that will definitely need to be split across multiple pages. It contains many words and should demonstrate how the pagination system works when we have height constraints. The system should be able to break the paragraph at appropriate points and provide information about remaining content that needs to be rendered on subsequent pages."
|
|
paragraph = create_test_paragraph(text)
|
|
|
|
layout = ParagraphLayout(
|
|
line_width=180,
|
|
line_height=18,
|
|
word_spacing=(2, 6),
|
|
line_spacing=3,
|
|
halign=Alignment.LEFT
|
|
)
|
|
|
|
# Test with different page heights
|
|
page_heights = [60, 100, 150] # Different page sizes
|
|
|
|
for page_height in page_heights:
|
|
print(f" Testing with page height: {page_height}px")
|
|
|
|
result = layout.layout_paragraph_with_pagination(paragraph, page_height)
|
|
|
|
print(f" Generated {len(result.lines)} lines")
|
|
print(f" Total height used: {result.total_height}px")
|
|
print(f" Is complete: {result.is_complete}")
|
|
|
|
if result.state:
|
|
print(f" Current word index: {result.state.current_word_index}")
|
|
print(f" Current char index: {result.state.current_char_index}")
|
|
print(f" Rendered lines: {result.state.rendered_lines}")
|
|
|
|
# Show lines
|
|
for i, line in enumerate(result.lines):
|
|
words_in_line = [word.word.text for word in line.renderable_words]
|
|
print(f" Line {i+1}: {' '.join(words_in_line)}")
|
|
|
|
# Create visual representation
|
|
if result.lines:
|
|
canvas = Image.new('RGB', (layout.line_width, page_height), (255, 255, 255))
|
|
|
|
# Add a border to show the page boundary
|
|
draw = ImageDraw.Draw(canvas)
|
|
draw.rectangle([(0, 0), (layout.line_width-1, page_height-1)], outline=(200, 200, 200), width=2)
|
|
|
|
for i, line in enumerate(result.lines):
|
|
line_img = line.render()
|
|
y_pos = i * (layout.line_height + layout.line_spacing)
|
|
if y_pos + layout.line_height <= page_height:
|
|
canvas.paste(line_img, (0, y_pos), line_img)
|
|
|
|
canvas.save(f"test_pagination_{page_height}px.png")
|
|
print(f" Saved as: test_pagination_{page_height}px.png")
|
|
|
|
print()
|
|
|
|
def test_state_management():
|
|
"""Test state saving and restoration for resumable rendering."""
|
|
print("Testing state management (save/restore)...")
|
|
|
|
text = "This is a test of the state management system. We will render part of this paragraph, save the state, and then continue rendering from where we left off. This demonstrates how the system can handle interruptions and resume rendering later."
|
|
paragraph = create_test_paragraph(text)
|
|
|
|
layout = ParagraphLayout(
|
|
line_width=150,
|
|
line_height=16,
|
|
word_spacing=(2, 5),
|
|
line_spacing=2,
|
|
halign=Alignment.LEFT
|
|
)
|
|
|
|
# First page - render with height constraint
|
|
page_height = 50
|
|
print(f" First page (height: {page_height}px):")
|
|
|
|
result1 = layout.layout_paragraph_with_pagination(paragraph, page_height)
|
|
|
|
print(f" Lines: {len(result1.lines)}")
|
|
print(f" Complete: {result1.is_complete}")
|
|
|
|
if result1.state:
|
|
# Save the state
|
|
state_json = result1.state.to_json()
|
|
print(f" Saved state: {state_json}")
|
|
|
|
# Create image for first page
|
|
if result1.lines:
|
|
canvas1 = Image.new('RGB', (layout.line_width, page_height), (255, 255, 255))
|
|
draw = ImageDraw.Draw(canvas1)
|
|
draw.rectangle([(0, 0), (layout.line_width-1, page_height-1)], outline=(200, 200, 200), width=2)
|
|
|
|
for i, line in enumerate(result1.lines):
|
|
line_img = line.render()
|
|
y_pos = i * (layout.line_height + layout.line_spacing)
|
|
canvas1.paste(line_img, (0, y_pos), line_img)
|
|
|
|
canvas1.save("test_state_page1.png")
|
|
print(f" First page saved as: test_state_page1.png")
|
|
|
|
# Continue from saved state on second page
|
|
if not result1.is_complete and result1.remaining_paragraph:
|
|
print(f" Second page (continuing from saved state):")
|
|
|
|
# Restore state
|
|
restored_state = ParagraphRenderingState.from_json(state_json)
|
|
print(f" Restored state: word_index={restored_state.current_word_index}, char_index={restored_state.current_char_index}")
|
|
|
|
# Continue rendering
|
|
result2 = layout.layout_paragraph_with_pagination(result1.remaining_paragraph, page_height)
|
|
|
|
print(f" Lines: {len(result2.lines)}")
|
|
print(f" Complete: {result2.is_complete}")
|
|
|
|
# Create image for second page
|
|
if result2.lines:
|
|
canvas2 = Image.new('RGB', (layout.line_width, page_height), (255, 255, 255))
|
|
draw = ImageDraw.Draw(canvas2)
|
|
draw.rectangle([(0, 0), (layout.line_width-1, page_height-1)], outline=(200, 200, 200), width=2)
|
|
|
|
for i, line in enumerate(result2.lines):
|
|
line_img = line.render()
|
|
y_pos = i * (layout.line_height + layout.line_spacing)
|
|
canvas2.paste(line_img, (0, y_pos), line_img)
|
|
|
|
canvas2.save("test_state_page2.png")
|
|
print(f" Second page saved as: test_state_page2.png")
|
|
|
|
print()
|
|
|
|
def test_long_word_handling():
|
|
"""Test handling of long words that require force-fitting."""
|
|
print("Testing long word handling...")
|
|
|
|
text = "This paragraph contains supercalifragilisticexpialidocious and other extraordinarily long words that should be handled gracefully."
|
|
paragraph = create_test_paragraph(text)
|
|
|
|
layout = ParagraphLayout(
|
|
line_width=120, # Narrow width to force long word issues
|
|
line_height=18,
|
|
word_spacing=(2, 5),
|
|
line_spacing=2,
|
|
halign=Alignment.LEFT
|
|
)
|
|
|
|
result = layout.layout_paragraph_with_pagination(paragraph, 200) # Generous height
|
|
|
|
print(f" Generated {len(result.lines)} lines")
|
|
print(f" Complete: {result.is_complete}")
|
|
|
|
# Show how long words were handled
|
|
for i, line in enumerate(result.lines):
|
|
words_in_line = [word.word.text for word in line.renderable_words]
|
|
line_text = ' '.join(words_in_line)
|
|
print(f" Line {i+1}: \"{line_text}\"")
|
|
|
|
# Create visual representation
|
|
if result.lines:
|
|
total_height = len(result.lines) * (layout.line_height + layout.line_spacing)
|
|
canvas = Image.new('RGB', (layout.line_width, total_height), (255, 255, 255))
|
|
|
|
for i, line in enumerate(result.lines):
|
|
line_img = line.render()
|
|
y_pos = i * (layout.line_height + layout.line_spacing)
|
|
canvas.paste(line_img, (0, y_pos), line_img)
|
|
|
|
canvas.save("test_long_word_handling.png")
|
|
print(f" Saved as: test_long_word_handling.png")
|
|
|
|
print()
|
|
|
|
def test_multiple_page_scenario():
|
|
"""Test a realistic multi-page scenario."""
|
|
print("Testing realistic multi-page scenario...")
|
|
|
|
text = """This is a comprehensive test of the paragraph layout system with pagination support.
|
|
The system needs to handle various scenarios including normal word wrapping, hyphenation of long words,
|
|
state management for resumable rendering, and proper text flow across multiple pages.
|
|
|
|
When a paragraph is too long to fit on a single page, the system should break it at appropriate
|
|
points and maintain state information so that rendering can be resumed on the next page.
|
|
This is essential for document processing applications where content needs to be paginated
|
|
across multiple pages or screens.
|
|
|
|
The system also needs to handle edge cases such as very long words that don't fit on a single line,
|
|
ensuring that no text is lost and that the rendering process can continue gracefully even
|
|
when encountering challenging content.""".replace('\n', ' ').replace(' ', ' ')
|
|
|
|
paragraph = create_test_paragraph(text)
|
|
|
|
layout = ParagraphLayout(
|
|
line_width=200,
|
|
line_height=20,
|
|
word_spacing=(3, 8),
|
|
line_spacing=3,
|
|
halign=Alignment.JUSTIFY
|
|
)
|
|
|
|
page_height = 80 # Small pages to force pagination
|
|
pages = []
|
|
current_paragraph = paragraph
|
|
page_num = 1
|
|
|
|
while current_paragraph:
|
|
print(f" Rendering page {page_num}...")
|
|
|
|
result = layout.layout_paragraph_with_pagination(current_paragraph, page_height)
|
|
|
|
print(f" Lines on page: {len(result.lines)}")
|
|
print(f" Page complete: {result.is_complete}")
|
|
|
|
if result.lines:
|
|
# Create page image
|
|
canvas = Image.new('RGB', (layout.line_width, page_height), (255, 255, 255))
|
|
draw = ImageDraw.Draw(canvas)
|
|
|
|
# Page border
|
|
draw.rectangle([(0, 0), (layout.line_width-1, page_height-1)], outline=(100, 100, 100), width=1)
|
|
|
|
# Page number
|
|
draw.text((5, page_height-15), f"Page {page_num}", fill=(150, 150, 150))
|
|
|
|
# Content
|
|
for i, line in enumerate(result.lines):
|
|
line_img = line.render()
|
|
y_pos = i * (layout.line_height + layout.line_spacing)
|
|
if y_pos + layout.line_height <= page_height - 20: # Leave space for page number
|
|
canvas.paste(line_img, (0, y_pos), line_img)
|
|
|
|
pages.append(canvas)
|
|
canvas.save(f"test_multipage_page_{page_num}.png")
|
|
print(f" Saved as: test_multipage_page_{page_num}.png")
|
|
|
|
# Continue with remaining content
|
|
current_paragraph = result.remaining_paragraph
|
|
page_num += 1
|
|
|
|
# Safety check to prevent infinite loop
|
|
if page_num > 10:
|
|
print(" Safety limit reached - stopping pagination")
|
|
break
|
|
|
|
print(f" Total pages generated: {len(pages)}")
|
|
print()
|
|
|
|
if __name__ == "__main__":
|
|
print("Testing paragraph layout system with pagination and state management...\n")
|
|
|
|
test_basic_paragraph_layout()
|
|
test_pagination_with_height_constraint()
|
|
test_state_management()
|
|
test_long_word_handling()
|
|
test_multiple_page_scenario()
|
|
|
|
print("All tests completed!")
|
|
print("\nGenerated files:")
|
|
print("- test_basic_paragraph_layout.png")
|
|
print("- test_pagination_*.png (multiple files)")
|
|
print("- test_state_page1.png, test_state_page2.png")
|
|
print("- test_long_word_handling.png")
|
|
print("- test_multipage_page_*.png (multiple files)")
|
|
print("\nThese images demonstrate:")
|
|
print("1. Basic paragraph layout with proper line wrapping")
|
|
print("2. Pagination with height constraints")
|
|
print("3. State management and resumable rendering")
|
|
print("4. Handling of long words with force-fitting")
|
|
print("5. Realistic multi-page document layout")
|