complete the table rendering
Python CI / test (push) Successful in 6m37s

This commit is contained in:
2025-11-07 19:45:47 +01:00
parent b1553f1628
commit 03918fc716
8 changed files with 1116 additions and 6 deletions
+369
View File
@@ -0,0 +1,369 @@
#!/usr/bin/env python3
"""
Table Rendering Example
This example demonstrates rendering HTML tables:
- Simple tables with headers
- Tables with multiple rows and columns
- Tables with colspan and borders
- Tables with formatted content
- Tables parsed from HTML
Shows the TableRenderer system in action.
"""
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.concrete.table import TableRenderer, TableStyle
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.io.readers.html_extraction import parse_html_string
from pyWebLayout.style import Font
from pyWebLayout.abstract.block import Table
def create_simple_table_example():
"""Create a simple table from HTML."""
print(" - Simple data table")
html = """
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>City</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>28</td>
<td>Paris</td>
</tr>
<tr>
<td>Bob</td>
<td>34</td>
<td>London</td>
</tr>
<tr>
<td>Charlie</td>
<td>25</td>
<td>Tokyo</td>
</tr>
</tbody>
</table>
"""
return html, "Simple Table"
def create_styled_table_example():
"""Create a table with custom styling."""
print(" - Styled table")
html = """
<table>
<caption>Monthly Sales Report</caption>
<thead>
<tr>
<th>Month</th>
<th>Revenue</th>
<th>Expenses</th>
<th>Profit</th>
</tr>
</thead>
<tbody>
<tr>
<td>January</td>
<td>$50,000</td>
<td>$30,000</td>
<td>$20,000</td>
</tr>
<tr>
<td>February</td>
<td>$55,000</td>
<td>$32,000</td>
<td>$23,000</td>
</tr>
<tr>
<td>March</td>
<td>$60,000</td>
<td>$35,000</td>
<td>$25,000</td>
</tr>
</tbody>
</table>
"""
return html, "Styled Table"
def create_complex_table_example():
"""Create a table with colspan."""
print(" - Complex table with colspan")
html = """
<table>
<caption>Product Specifications</caption>
<thead>
<tr>
<th>Product</th>
<th>Features</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Laptop</td>
<td>16GB RAM, 512GB SSD</td>
<td>$1,299</td>
</tr>
<tr>
<td>Monitor</td>
<td>27 inch, 4K Resolution</td>
<td>$599</td>
</tr>
<tr>
<td>Keyboard</td>
<td>Mechanical, RGB</td>
<td>$129</td>
</tr>
</tbody>
</table>
"""
return html, "Complex Table"
def create_data_table_example():
"""Create a table with numerical data."""
print(" - Data table")
html = """
<table>
<caption>Test Results</caption>
<thead>
<tr>
<th>Test</th>
<th>Score</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Unit Tests</td>
<td>98%</td>
<td>Pass</td>
</tr>
<tr>
<td>Integration</td>
<td>95%</td>
<td>Pass</td>
</tr>
<tr>
<td>Performance</td>
<td>87%</td>
<td>Pass</td>
</tr>
</tbody>
</table>
"""
return html, "Data Table"
def render_table_example(html: str, title: str, style_variant: int = 0, page_size=(500, 400)):
"""Render a table from HTML to an image."""
# Create page with varying backgrounds
bg_colors = [
(255, 255, 255), # White
(250, 255, 250), # Light green tint
(255, 250, 245), # Light orange tint
(245, 250, 255), # Light blue tint
]
page_style = PageStyle(
border_width=2,
border_color=(200, 200, 200),
padding=(20, 20, 20, 20),
background_color=bg_colors[style_variant % len(bg_colors)]
)
page = Page(size=page_size, style=page_style)
image = page.render()
draw = ImageDraw.Draw(image)
# Add title
from PIL import ImageFont
try:
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
except:
title_font = ImageFont.load_default()
draw.text((page.border_size + 10, page.border_size + 10), title, fill=(50, 50, 150), font=title_font)
# Parse HTML to get table
base_font = Font(font_size=12)
blocks = parse_html_string(html, base_font=base_font)
# Find the table block
table = None
for block in blocks:
if isinstance(block, Table):
table = block
break
if table:
# Create table renderer with different styles
table_styles = [
# Style 0: Classic blue header
TableStyle(
border_width=1,
border_color=(80, 80, 80),
cell_padding=(8, 10, 8, 10),
header_bg_color=(70, 130, 180), # Steel blue
cell_bg_color=(255, 255, 255),
alternate_row_color=(240, 248, 255) # Alice blue
),
# Style 1: Green theme
TableStyle(
border_width=2,
border_color=(34, 139, 34), # Forest green
cell_padding=(10, 12, 10, 12),
header_bg_color=(144, 238, 144), # Light green
cell_bg_color=(255, 255, 255),
alternate_row_color=(240, 255, 240) # Honeydew
),
# Style 2: Minimal style
TableStyle(
border_width=0,
border_color=(200, 200, 200),
cell_padding=(6, 8, 6, 8),
header_bg_color=(245, 245, 245),
cell_bg_color=(255, 255, 255),
alternate_row_color=None # No alternating
),
# Style 3: Bold borders
TableStyle(
border_width=3,
border_color=(0, 0, 0),
cell_padding=(10, 10, 10, 10),
header_bg_color=(255, 215, 0), # Gold
cell_bg_color=(255, 255, 255),
alternate_row_color=(255, 250, 205) # Lemon chiffon
),
]
table_style = table_styles[style_variant % len(table_styles)]
# Position table below title
table_origin = (page.border_size + 10, page.border_size + 40)
table_width = page.content_size[0] - 20
renderer = TableRenderer(
table,
table_origin,
table_width,
draw,
table_style
)
renderer.render()
else:
# Draw "No table found" message
draw.text((page.border_size + 10, page.border_size + 50),
"No table found in HTML",
fill=(200, 0, 0), font=title_font)
return image
def combine_examples(examples):
"""Combine multiple table examples into a grid."""
print("\n Rendering table examples...")
images = []
for i, (html, title) in enumerate(examples):
img = render_table_example(html, title, style_variant=i)
images.append(img)
# Create grid (2x2)
padding = 15
cols = 2
rows = 2
img_width = images[0].size[0]
img_height = images[0].size[1]
total_width = cols * img_width + (cols + 1) * padding
total_height = rows * img_height + (rows + 1) * padding + 50 # Extra for main title
combined = Image.new('RGB', (total_width, total_height), (240, 240, 240))
draw = ImageDraw.Draw(combined)
# Add main title
from PIL import ImageFont
try:
main_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
except:
main_font = ImageFont.load_default()
title_text = "Table Rendering Examples"
bbox = draw.textbbox((0, 0), title_text, font=main_font)
text_width = bbox[2] - bbox[0]
title_x = (total_width - text_width) // 2
draw.text((title_x, 15), title_text, fill=(50, 50, 50), font=main_font)
# Place images
y_offset = 50 + padding
for row in range(rows):
x_offset = padding
for col in range(cols):
idx = row * cols + col
if idx < len(images):
combined.paste(images[idx], (x_offset, y_offset))
x_offset += img_width + padding
y_offset += img_height + padding
return combined
def main():
"""Demonstrate table rendering."""
print("Table Rendering Example")
print("=" * 50)
# Create table examples
print("\n Creating table examples...")
examples = [
create_simple_table_example(),
create_styled_table_example(),
create_complex_table_example(),
create_data_table_example()
]
# Render and combine
combined_image = combine_examples(examples)
# Save output
output_dir = Path("docs/images")
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "example_04_table_rendering.png"
combined_image.save(output_path)
print(f"\n✓ Example completed!")
print(f" Output saved to: {output_path}")
print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels")
print(f" Created {len(examples)} table examples")
return combined_image
if __name__ == "__main__":
main()
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""
Table with Images Example
This example demonstrates rendering tables with images:
- Creating tables programmatically
- Adding images to table cells
- Book catalog / product showcase tables
- Mixed content (images and text) in cells
Uses the cover images from tests/data directory.
"""
import sys
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
# 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.concrete.table import TableRenderer, TableStyle
from pyWebLayout.abstract.block import Table, Image as AbstractImage
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style import Font
def create_book_catalog_table(cover_images: dict):
"""Create a book catalog table with cover images and details."""
print(" - Creating book catalog table...")
# Get base path for images
data_path = Path(__file__).parent.parent / "tests" / "data"
# Create table
table = Table(caption="Book Catalog", style=Font(font_size=14))
# Header row
header = table.create_row("header")
header.create_cell(is_header=True).create_paragraph().add_word(Word("Cover", Font(font_size=12)))
header.create_cell(is_header=True).create_paragraph().add_word(Word("Title", Font(font_size=12)))
header.create_cell(is_header=True).create_paragraph().add_word(Word("Author", Font(font_size=12)))
header.create_cell(is_header=True).create_paragraph().add_word(Word("Price", Font(font_size=12)))
# Book entries
books = [
("cover 1.png", "The Great Adventure", "John Smith", "$19.99"),
("cover 2.png", "Mystery of the Ages", "Jane Doe", "$24.99"),
("cover 3.png", "Science Today", "Dr. Brown", "$29.99"),
("cover 4.png", "Art & Design", "M. Artist", "$34.99"),
]
for cover_file, title, author, price in books:
row = table.create_row("body")
# Cover cell with actual Image block
cover_cell = row.create_cell()
if cover_file in cover_images:
cover_path = str(data_path / cover_file)
AbstractImage.create_and_add_to(cover_cell, source=cover_path, alt_text=title)
# Title cell
title_cell = row.create_cell()
title_para = title_cell.create_paragraph()
for word in title.split():
title_para.add_word(Word(word, Font(font_size=11)))
# Author cell
author_cell = row.create_cell()
author_para = author_cell.create_paragraph()
for word in author.split():
author_para.add_word(Word(word, Font(font_size=11)))
# Price cell
price_cell = row.create_cell()
price_para = price_cell.create_paragraph()
price_para.add_word(Word(price, Font(font_size=11, weight="bold")))
return table
def create_product_showcase_table(cover_images: dict):
"""Create a product showcase table."""
print(" - Creating product showcase table...")
# Get base path for images
data_path = Path(__file__).parent.parent / "tests" / "data"
table = Table(caption="Product Showcase", style=Font(font_size=14))
# Header row
header = table.create_row("header")
header.create_cell(is_header=True).create_paragraph().add_word(Word("Product", Font(font_size=12)))
header.create_cell(is_header=True).create_paragraph().add_word(Word("Description", Font(font_size=12)))
# Products with covers
products = [
("cover 1.png", "Premium Edition - Hardcover with gold embossing"),
("cover 2.png", "Collector's Item - Limited print run"),
]
for cover_file, description in products:
row = table.create_row("body")
# Product cell with actual Image block
product_cell = row.create_cell()
if cover_file in cover_images:
cover_path = str(data_path / cover_file)
AbstractImage.create_and_add_to(product_cell, source=cover_path, alt_text="Product cover")
# Description cell
desc_cell = row.create_cell()
desc_para = desc_cell.create_paragraph()
for word in description.split():
desc_para.add_word(Word(word, Font(font_size=10)))
return table
def render_table_with_images(table: Table, title: str, style_variant: int = 0,
page_size=(600, 500)):
"""Render a table with images using the library API."""
# Create page
page_style = PageStyle(
border_width=2,
border_color=(180, 180, 180),
padding=(20, 20, 20, 20),
background_color=(255, 255, 255)
)
page = Page(size=page_size, style=page_style)
canvas = page.render()
draw = ImageDraw.Draw(canvas)
# Add title
try:
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
except:
title_font = ImageFont.load_default()
draw.text((page.border_size + 10, page.border_size + 10), title, fill=(50, 50, 150), font=title_font)
# Table styles
table_styles = [
TableStyle(
border_width=1,
border_color=(100, 100, 100),
cell_padding=(8, 10, 8, 10),
header_bg_color=(70, 130, 180),
cell_bg_color=(255, 255, 255),
alternate_row_color=(245, 248, 250)
),
TableStyle(
border_width=2,
border_color=(60, 120, 60),
cell_padding=(10, 12, 10, 12),
header_bg_color=(144, 238, 144),
cell_bg_color=(255, 255, 255),
alternate_row_color=(240, 255, 240)
),
]
table_style = table_styles[style_variant % len(table_styles)]
# Position table
table_origin = (page.border_size + 10, page.border_size + 45)
table_width = page.content_size[0] - 20
# Render table with canvas support for images
renderer = TableRenderer(
table,
table_origin,
table_width,
draw,
table_style,
canvas # Pass canvas to enable image rendering
)
renderer.render()
return canvas
def main():
"""Demonstrate tables with images."""
print("Table with Images Example")
print("=" * 50)
# Load cover images
print("\n Loading cover images...")
cover_images = {}
data_path = Path(__file__).parent.parent / "tests" / "data"
for i in range(1, 5):
cover_path = data_path / f"cover {i}.png"
if cover_path.exists():
cover_images[f"cover {i}.png"] = Image.open(cover_path)
print(f" ✓ Loaded cover {i}.png")
if not cover_images:
print(" ✗ No cover images found!")
return
# Create tables
print("\n Creating tables...")
book_table = create_book_catalog_table(cover_images)
product_table = create_product_showcase_table(cover_images)
# Render tables
print("\n Rendering tables with images...")
print(" - Rendering book catalog...")
book_image = render_table_with_images(
book_table,
"Book Catalog with Covers",
style_variant=0,
page_size=(700, 600)
)
print(" - Rendering product showcase...")
product_image = render_table_with_images(
product_table,
"Product Showcase",
style_variant=1,
page_size=(600, 350)
)
# Combine images side by side
print("\n Combining images...")
padding = 20
total_width = book_image.size[0] + product_image.size[0] + padding * 3
total_height = max(book_image.size[1], product_image.size[1]) + padding * 2
combined = Image.new('RGB', (total_width, total_height), (240, 240, 240))
combined.paste(book_image, (padding, padding))
combined.paste(product_image, (book_image.size[0] + padding * 2, padding))
# Save output
output_dir = Path("docs/images")
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "example_05_table_with_images.png"
combined.save(output_path)
print(f"\n✓ Example completed!")
print(f" Output saved to: {output_path}")
print(f" Image size: {combined.size[0]}x{combined.size[1]} pixels")
print(f" Used {len(cover_images)} cover images")
return combined
if __name__ == "__main__":
main()
+34
View File
@@ -51,6 +51,38 @@ Demonstrates:
![Page Layouts Example](../docs/images/example_03_page_layouts.png)
### 04. Table Rendering
**`04_table_rendering.py`** - HTML table rendering with styling
```bash
python 04_table_rendering.py
```
Demonstrates:
- Rendering HTML tables
- Table headers and body rows
- Cell borders and padding
- Caption support
- Custom table styling
![Table Rendering Example](../docs/images/example_04_table_rendering.png)
### 05. Tables with Images
**`05_table_with_images.py`** - Tables containing images and mixed content
```bash
python 05_table_with_images.py
```
Demonstrates:
- Creating tables programmatically
- Adding images to table cells
- Book catalog and product showcase tables
- Mixed content (images and text) in cells
- Using cover images from test data
![Table with Images Example](../docs/images/example_05_table_with_images.png)
## Advanced Examples
### HTML Rendering
@@ -72,6 +104,8 @@ cd examples
python 01_simple_page_rendering.py
python 02_text_and_layout.py
python 03_page_layouts.py
python 04_table_rendering.py
python 05_table_with_images.py
```
Output images are saved to the `docs/images/` directory.