pyWebLayout/examples/08_pagination_demo.py
2025-11-09 21:40:50 +01:00

368 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Pagination Example with PageBreak
This example demonstrates:
- Using PageBreak to force content onto new pages
- Multi-page document layout with automatic page creation
- Different content types across multiple pages
- Page numbering and document flow
- Combining text, images, and tables across pages
This shows how to create multi-page documents with explicit page breaks.
"""
import sys
from pathlib import Path
from PIL import Image, ImageDraw
# Add pyWebLayout to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.style.fonts import Font
from pyWebLayout.abstract.inline import Word
from pyWebLayout.abstract.block import Paragraph, PageBreak, Image as AbstractImage
from pyWebLayout.layout.document_layouter import DocumentLayouter
def create_sample_paragraph(text: str, font_size: int = 14) -> Paragraph:
"""Create a paragraph from plain text."""
font = Font(font_size=font_size, colour=(50, 50, 50))
paragraph = Paragraph(style=font)
for word in text.split():
paragraph.add_word(Word(word, font))
return paragraph
def create_title_paragraph(text: str) -> Paragraph:
"""Create a title paragraph with larger font."""
font = Font(font_size=24, colour=(0, 0, 100), weight='bold')
paragraph = Paragraph(style=font)
for word in text.split():
paragraph.add_word(Word(word, font))
return paragraph
def create_heading_paragraph(text: str) -> Paragraph:
"""Create a heading paragraph."""
font = Font(font_size=18, colour=(50, 50, 100), weight='bold')
paragraph = Paragraph(style=font)
for word in text.split():
paragraph.add_word(Word(word, font))
return paragraph
def create_placeholder_image(width: int, height: int, label: str) -> AbstractImage:
"""Create a placeholder image for demonstration."""
img = Image.new('RGB', (width, height), (200, 220, 240))
draw = ImageDraw.Draw(img)
# Draw border
draw.rectangle([0, 0, width-1, height-1], outline=(100, 120, 140), width=2)
# Add label
text_bbox = draw.textbbox((0, 0), label)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
text_x = (width - text_width) // 2
text_y = (height - text_height) // 2
draw.text((text_x, text_y), label, fill=(80, 80, 120))
return AbstractImage(source=img)
def create_example_document_with_pagebreaks():
"""
Example: Multi-page document with explicit page breaks.
This demonstrates how PageBreak forces content onto new pages.
"""
print("\n Creating multi-page document with PageBreaks...")
# Define common page style
page_style = PageStyle(
border_width=2,
border_color=(100, 100, 150),
padding=(30, 40, 30, 40),
background_color=(255, 255, 255),
line_spacing=6
)
# Create document content with page breaks
content = [
# Page 1: Title and Introduction
create_title_paragraph("Multi-Page Document Example"),
create_sample_paragraph(
"This document demonstrates how to use PageBreak elements to control "
"document pagination. Each PageBreak forces subsequent content to start "
"on a new page, allowing you to structure multi-page documents precisely."
),
create_sample_paragraph(
"Page breaks are particularly useful for creating chapters, sections, or "
"ensuring that important content starts at the top of a fresh page rather "
"than being split across page boundaries."
),
# Force page break - next content will be on page 2
PageBreak(),
# Page 2: First Section
create_heading_paragraph("Section 1: Text Content"),
create_sample_paragraph(
"This is the second page of our document. It starts with a clean break "
"from the previous page, ensuring the section heading appears at the top."
),
create_sample_paragraph(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat."
),
create_sample_paragraph(
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum "
"dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non "
"proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
),
# Another page break
PageBreak(),
# Page 3: Images
create_heading_paragraph("Section 2: Visual Content"),
create_sample_paragraph(
"This page contains image content, demonstrating that page breaks work "
"correctly with different content types."
),
create_placeholder_image(300, 200, "Figure 1: Sample Image"),
create_sample_paragraph("The image above is placed on this dedicated page."),
# Final page break
PageBreak(),
# Page 4: Conclusion
create_heading_paragraph("Conclusion"),
create_sample_paragraph(
"This final page demonstrates that you can create complex multi-page "
"documents by strategically placing PageBreak elements in your content."
),
create_sample_paragraph(
"Key benefits of using PageBreak: 1) Control where pages start, "
"2) Prevent awkward content splits, 3) Create professional-looking "
"documents with proper sectioning, 4) Ensure important content gets "
"visual prominence at page tops."
),
create_sample_paragraph(
"Thank you for reviewing this pagination example. Try experimenting "
"with PageBreak placement to create your own multi-page documents!"
),
]
# Layout the document across multiple pages
pages = []
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
for element in content:
if isinstance(element, PageBreak):
# Save current page and create a new one
pages.append(current_page)
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
elif isinstance(element, Paragraph):
success, _, _ = layouter.layout_paragraph(element)
if not success:
# Page is full, create new page and retry
pages.append(current_page)
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
success, _, _ = layouter.layout_paragraph(element)
if not success:
print(" WARNING: Content too large for page")
elif isinstance(element, AbstractImage):
success = layouter.layout_image(element)
if not success:
# Image doesn't fit, try on new page
pages.append(current_page)
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
success = layouter.layout_image(element)
if not success:
print(" WARNING: Image too large for page")
# Add the final page
pages.append(current_page)
print(f" Created {len(pages)} pages")
return pages
def create_auto_pagination_example():
"""
Example: Document that automatically flows to multiple pages.
This shows the difference between automatic pagination (when content
doesn't fit) vs explicit PageBreak usage.
"""
print("\n Creating auto-paginated document (no explicit breaks)...")
page_style = PageStyle(
border_width=1,
border_color=(150, 150, 150),
padding=(20, 30, 20, 30),
background_color=(250, 250, 250),
line_spacing=5
)
# Create lots of content that will naturally overflow
content = [
create_heading_paragraph("Auto-Pagination Example"),
create_sample_paragraph(
"This document does NOT use PageBreak. Instead, it demonstrates how "
"content automatically flows to new pages when the current page is full."
),
]
# Add many paragraphs to force automatic page breaks
for i in range(1, 11):
content.append(
create_sample_paragraph(
f"Paragraph {i}: This is automatically laid out content. "
f"When this paragraph doesn't fit on the current page, the layouter "
f"will create a new page automatically. This is different from using "
f"PageBreak which forces a new page regardless of available space. "
f"Auto-pagination is useful for flowing content naturally."
)
)
# Layout across pages
pages = []
current_page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(current_page)
for element in content:
if isinstance(element, Paragraph):
success, _, _ = layouter.layout_paragraph(element)
if not success:
# Auto page break - content didn't fit
pages.append(current_page)
current_page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(current_page)
layouter.layout_paragraph(element)
pages.append(current_page)
print(f" Auto-created {len(pages)} pages")
return pages
def add_page_numbers(pages, start_number: int = 1):
"""Add page numbers to rendered pages."""
numbered_pages = []
font = Font(font_size=10, colour=(100, 100, 100))
for i, page in enumerate(pages, start=start_number):
# Render the page
img = page.render()
draw = ImageDraw.Draw(img)
# Add page number at bottom center
page_text = f"Page {i}"
bbox = draw.textbbox((0, 0), page_text)
text_width = bbox[2] - bbox[0]
x = (img.size[0] - text_width) // 2
y = img.size[1] - 20
draw.text((x, y), page_text, fill=(100, 100, 100))
numbered_pages.append(img)
return numbered_pages
def combine_pages_vertically(pages, title: str = ""):
"""Combine multiple pages into a vertical strip."""
if not pages:
return None
padding = 20
title_height = 40 if title else 0
# Calculate dimensions
page_width = pages[0].size[0]
page_height = pages[0].size[1]
total_width = page_width + 2 * padding
total_height = len(pages) * (page_height + padding) + padding + title_height
# Create combined image
combined = Image.new('RGB', (total_width, total_height), (240, 240, 240))
draw = ImageDraw.Draw(combined)
# Draw title if provided
if title:
from PIL import ImageFont
try:
title_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16
)
except:
title_font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), title, font=title_font)
text_width = bbox[2] - bbox[0]
title_x = (total_width - text_width) // 2
draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font)
# Place pages vertically
y_offset = title_height + padding
for page_img in pages:
combined.paste(page_img, (padding, y_offset))
y_offset += page_height + padding
return combined
def main():
"""Demonstrate pagination with PageBreak."""
print("Pagination Example with PageBreak")
print("=" * 50)
# Example 1: Explicit page breaks
pages1 = create_example_document_with_pagebreaks()
rendered_pages1 = add_page_numbers(pages1)
combined1 = combine_pages_vertically(
rendered_pages1,
"Example 1: Explicit PageBreak Usage"
)
# Example 2: Auto pagination
pages2 = create_auto_pagination_example()
rendered_pages2 = add_page_numbers(pages2)
combined2 = combine_pages_vertically(
rendered_pages2,
"Example 2: Automatic Pagination"
)
# Save outputs
output_dir = Path("docs/images")
output_dir.mkdir(parents=True, exist_ok=True)
output_path1 = output_dir / "example_08_pagination_explicit.png"
output_path2 = output_dir / "example_08_pagination_auto.png"
combined1.save(output_path1)
combined2.save(output_path2)
print("\n✓ Example completed!")
print(f" Output 1 saved to: {output_path1}")
print(f" - {len(pages1)} pages with explicit PageBreaks")
print(f" Output 2 saved to: {output_path2}")
print(f" - {len(pages2)} pages with auto-pagination")
return combined1, combined2
if __name__ == "__main__":
main()