#!/usr/bin/env python3 """ Demo: Table Pagination This example demonstrates table pagination when content exceeds page height: - Large table that spans multiple pages - Automatic row-level pagination (entire rows move to next page) - Continuation markers ("continued on next page", "continued from previous page") - Headers repeated on each page The pagination system: 1. Renders rows sequentially until page height limit reached 2. Moves entire row to next page if it doesn't fit 3. Repeats header row on continuation pages 4. Adds visual markers to show table continues """ from pyWebLayout.concrete.page import Page from pyWebLayout.concrete.table import TableRenderer, TableStyle from pyWebLayout.style.page_style import PageStyle from pyWebLayout.style import Font from pyWebLayout.abstract.block import Table, TableRow, TableCell, Paragraph from pyWebLayout.abstract.inline import Word from PIL import Image, ImageDraw def create_large_table(): """Create a table with many rows that will require pagination.""" table = Table() table.caption = "Employee Directory (Paginated)" font = Font(font_size=11) # Header header_row = TableRow() for text in ["ID", "Name", "Department", "Email", "Phone"]: cell = TableCell(is_header=True) para = Paragraph(font) para.add_word(Word(text, font)) cell.add_block(para) header_row.add_cell(cell) table.add_row(header_row, section="header") # Many body rows (will span multiple pages) departments = ["Engineering", "Sales", "Marketing", "HR", "Finance", "Operations", "Support"] for i in range(60): row = TableRow() # ID cell = TableCell() para = Paragraph(font) para.add_word(Word(f"EMP{i+1001}", font)) cell.add_block(para) row.add_cell(cell) # Name cell = TableCell() para = Paragraph(font) names = ["Alice Johnson", "Bob Smith", "Charlie Brown", "Diana Lee", "Eve Wilson", "Frank Miller", "Grace Davis", "Henry Taylor"] para.add_word(Word(names[i % len(names)], font)) cell.add_block(para) row.add_cell(cell) # Department cell = TableCell() para = Paragraph(font) para.add_word(Word(departments[i % len(departments)], font)) cell.add_block(para) row.add_cell(cell) # Email cell = TableCell() para = Paragraph(font) email = f"{names[i % len(names)].lower().replace(' ', '.')}@company.com" para.add_word(Word(email, font)) cell.add_block(para) row.add_cell(cell) # Phone cell = TableCell() para = Paragraph(font) para.add_word(Word(f"+1-555-{(i*17)%1000:04d}", font)) cell.add_block(para) row.add_cell(cell) table.add_row(row, section="body") return table def render_table_with_pagination(table, page_size, max_pages=3): """ Render a table across multiple pages. Args: table: The table to render page_size: Tuple of (width, height) for each page max_pages: Maximum number of pages to render Returns: List of PIL Images (one per page) """ pages = [] page_style = PageStyle( border_width=1, padding=(20, 20, 20, 20), background_color=(255, 255, 255) ) table_style = TableStyle( border_width=1, border_color=(100, 100, 100), cell_padding=(6, 8, 6, 8), header_bg_color=(220, 230, 240), cell_bg_color=(255, 255, 255), alternate_row_color=(248, 248, 248) ) # Get all rows all_rows = list(table.all_rows()) header_rows = [row for section, row in all_rows if section == "header"] body_rows = [row for section, row in all_rows if section == "body"] # Calculate header height once temp_page = Page(size=page_size, style=page_style) temp_canvas = temp_page._create_canvas() temp_draw = ImageDraw.Draw(temp_canvas) # Create temporary table with just header to measure header_table = Table() header_table.caption = table.caption for header_row in header_rows: header_table.add_row(header_row, section="header") header_renderer = TableRenderer( header_table, origin=(20, 20), available_width=page_size[0] - 40, draw=temp_draw, style=table_style, canvas=temp_canvas ) header_height = header_renderer.height # Available height for body rows available_body_height = page_size[1] - 60 - header_height # margins + header # Paginate body rows current_page_rows = [] current_height = 0 page_num = 0 for i, body_row in enumerate(body_rows): if page_num >= max_pages: break # Estimate row height (simplified - actual would measure each row) # For this demo, assume ~30px per row row_height = 35 if current_height + row_height > available_body_height and current_page_rows: # Render current page page_canvas = render_page( table, header_rows, current_page_rows, page_size, page_style, table_style, page_num, is_last=False ) pages.append(page_canvas) # Start new page page_num += 1 current_page_rows = [] current_height = 0 current_page_rows.append(body_row) current_height += row_height # Render final page if current_page_rows and page_num < max_pages: page_canvas = render_page( table, header_rows, current_page_rows, page_size, page_style, table_style, page_num, is_last=(i == len(body_rows) - 1) ) pages.append(page_canvas) return pages def render_page(table, header_rows, body_rows, page_size, page_style, table_style, page_num, is_last): """Render a single page with header and body rows.""" page = Page(size=page_size, style=page_style) canvas = page._create_canvas() page._canvas = canvas page._draw = ImageDraw.Draw(canvas) # Create table for this page page_table = Table() if page_num == 0: page_table.caption = table.caption else: page_table.caption = f"{table.caption} (continued)" # Add header rows for header_row in header_rows: page_table.add_row(header_row, section="header") # Add body rows for this page for body_row in body_rows: page_table.add_row(body_row, section="body") # Render table renderer = TableRenderer( page_table, origin=(20, 20), available_width=page_size[0] - 40, draw=page._draw, style=table_style, canvas=canvas ) renderer.render() # Add continuation marker at bottom if not is_last: font = Font(font_size=10) y_pos = page_size[1] - 30 page._draw.text( (page_size[0] // 2 - 100, y_pos), "(continued on next page)", fill=(100, 100, 100), font=font.font ) # Add page number page._draw.text( (page_size[0] // 2 - 20, page_size[1] - 15), f"Page {page_num + 1}", fill=(150, 150, 150), font=Font(font_size=9).font ) return canvas def main(): # Create large table table = create_large_table() # Render with pagination (3 pages max for demo) page_size = (900, 700) pages = render_table_with_pagination(table, page_size, max_pages=3) # Combine pages side-by-side for visualization total_width = page_size[0] * len(pages) + (len(pages) - 1) * 20 # 20px spacing combined = Image.new('RGB', (total_width, page_size[1]), (240, 240, 240)) x_offset = 0 for i, page_canvas in enumerate(pages): combined.paste(page_canvas, (x_offset, 0)) x_offset += page_size[0] + 20 # Save output_path = "docs/images/example_13_table_pagination.png" combined.save(output_path) print(f"✓ Table pagination demo created!") print(f" Output: {output_path}") print(f" Pages rendered: {len(pages)}") print(f" Image size: {combined.size}") print(f"\nDemonstrates:") print(f" - Large table (60 rows) paginated across {len(pages)} pages") print(f" - Header repeated on each page") print(f" - Continuation markers") print(f" - Page numbers") if __name__ == "__main__": main()