292 lines
8.4 KiB
Python
292 lines
8.4 KiB
Python
#!/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()
|