368 lines
13 KiB
Python
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()
|