#!/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()