@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
HTML Table with Images Example - End-to-End Rendering
|
||||
|
||||
This example demonstrates the complete pipeline:
|
||||
1. HTML table source with <img> tags in cells
|
||||
2. parse_html_string() converts HTML → Abstract document structure
|
||||
3. DocumentLayouter handles all layout and rendering
|
||||
|
||||
No custom rendering code needed - DocumentLayouter handles everything!
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
# Add pyWebLayout to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from pyWebLayout.io.readers.html_extraction import parse_html_string
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.style.page_style import PageStyle
|
||||
from pyWebLayout.layout.document_layouter import DocumentLayouter
|
||||
from pyWebLayout.concrete.table import TableStyle
|
||||
from pyWebLayout.style import Font
|
||||
|
||||
|
||||
def create_book_catalog_html():
|
||||
"""Create HTML for a book catalog table with actual <img> tags."""
|
||||
# Get base path for images - use absolute paths for the img src
|
||||
data_path = Path(__file__).parent.parent / "tests" / "data"
|
||||
|
||||
html = f"""
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cover</th>
|
||||
<th>Title</th>
|
||||
<th>Author</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img src="{data_path / 'cover 1.png'}" alt="The Great Adventure" /></td>
|
||||
<td>The Great Adventure</td>
|
||||
<td>John Smith</td>
|
||||
<td><b>$19.99</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="{data_path / 'cover 2.png'}" alt="Mystery of the Ages" /></td>
|
||||
<td>Mystery of the Ages</td>
|
||||
<td>Jane Doe</td>
|
||||
<td><b>$24.99</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="{data_path / 'cover 3.png'}" alt="Science Today" /></td>
|
||||
<td>Science Today</td>
|
||||
<td>Dr. Brown</td>
|
||||
<td><b>$29.99</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="{data_path / 'cover 4.png'}" alt="Art & Design" /></td>
|
||||
<td>Art & Design</td>
|
||||
<td>M. Artist</td>
|
||||
<td><b>$34.99</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return html
|
||||
|
||||
|
||||
def create_product_showcase_html():
|
||||
"""Create HTML for a product showcase table with images."""
|
||||
data_path = Path(__file__).parent.parent / "tests" / "data"
|
||||
|
||||
html = f"""
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img src="{data_path / 'cover 1.png'}" alt="Premium Edition" /></td>
|
||||
<td>Premium Edition - Hardcover with gold embossing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="{data_path / 'cover 2.png'}" alt="Collector's Item" /></td>
|
||||
<td>Collector's Item - Limited print run</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return html
|
||||
|
||||
|
||||
def render_html_with_layouter(html_string: str, title: str,
|
||||
table_style: TableStyle,
|
||||
page_size=(600, 500)):
|
||||
"""
|
||||
Render HTML using DocumentLayouter - the proper way!
|
||||
|
||||
This function demonstrates the correct usage:
|
||||
1. Parse HTML → Abstract blocks
|
||||
2. Create Page
|
||||
3. Create DocumentLayouter
|
||||
4. Layout all blocks using layouter
|
||||
|
||||
Args:
|
||||
html_string: HTML source containing table with <img> tags
|
||||
title: Title for the output (for logging)
|
||||
table_style: Table styling configuration
|
||||
page_size: Page dimensions
|
||||
|
||||
Returns:
|
||||
PIL Image with rendered content
|
||||
"""
|
||||
print(f"\n Processing '{title}'...")
|
||||
|
||||
# Step 1: Parse HTML to abstract blocks
|
||||
print(" 1. Parsing HTML → Abstract blocks...")
|
||||
base_font = Font(font_size=11)
|
||||
blocks = parse_html_string(html_string, base_font=base_font)
|
||||
print(f" → Parsed {len(blocks)} blocks")
|
||||
|
||||
# Step 2: Create page
|
||||
print(" 2. Creating 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)
|
||||
|
||||
# Step 3: Create DocumentLayouter
|
||||
print(" 3. Creating DocumentLayouter...")
|
||||
layouter = DocumentLayouter(page)
|
||||
|
||||
# Step 4: Layout all blocks using the layouter
|
||||
print(" 4. Laying out all blocks...")
|
||||
for block in blocks:
|
||||
# For tables, we can pass a custom style
|
||||
from pyWebLayout.abstract.block import Table
|
||||
if isinstance(block, Table):
|
||||
success = layouter.layout_table(block, style=table_style)
|
||||
else:
|
||||
# For other blocks (paragraphs, headings, images), use layout_document
|
||||
success = layouter.layout_document([block])
|
||||
|
||||
if not success:
|
||||
print(f" ⚠ Warning: Block {type(block).__name__} didn't fit on page")
|
||||
|
||||
print(f" ✓ Layout complete!")
|
||||
|
||||
# Step 5: Get the rendered canvas
|
||||
# Note: Tables render directly onto page._canvas
|
||||
# We access page.draw to ensure canvas is initialized
|
||||
print(" 5. Getting rendered canvas...")
|
||||
_ = page.draw # Ensure canvas exists
|
||||
return page._canvas
|
||||
|
||||
|
||||
def main():
|
||||
"""Demonstrate end-to-end HTML table with images rendering using DocumentLayouter."""
|
||||
print("HTML Table with Images Example - DocumentLayouter")
|
||||
print("=" * 60)
|
||||
print("\nThis example demonstrates:")
|
||||
print(" 1. HTML with <img> tags inside <td> cells")
|
||||
print(" 2. parse_html_string() automatically handles images")
|
||||
print(" 3. DocumentLayouter handles all layout and rendering")
|
||||
print(" 4. NO manual TableRenderer or custom rendering code!")
|
||||
|
||||
# Verify images exist
|
||||
print("\n Checking for cover images...")
|
||||
data_path = Path(__file__).parent.parent / "tests" / "data"
|
||||
cover_count = 0
|
||||
for i in range(1, 5):
|
||||
cover_path = data_path / f"cover {i}.png"
|
||||
if cover_path.exists():
|
||||
cover_count += 1
|
||||
print(f" ✓ Found cover {i}.png")
|
||||
|
||||
if cover_count == 0:
|
||||
print(" ✗ No cover images found! This example requires cover images.")
|
||||
return
|
||||
|
||||
# Create HTML sources with <img> tags
|
||||
print("\n Creating HTML sources with <img> tags...")
|
||||
print(" - Book catalog HTML")
|
||||
book_html = create_book_catalog_html()
|
||||
|
||||
print(" - Product showcase HTML")
|
||||
product_html = create_product_showcase_html()
|
||||
|
||||
# Define table styles
|
||||
book_style = 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)
|
||||
)
|
||||
|
||||
product_style = 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)
|
||||
)
|
||||
|
||||
# Render using DocumentLayouter - the proper way!
|
||||
print("\n Rendering with DocumentLayouter (HTML → Abstract → Layout → PNG)...")
|
||||
|
||||
book_image = render_html_with_layouter(
|
||||
book_html,
|
||||
"Book Catalog",
|
||||
book_style,
|
||||
page_size=(700, 600)
|
||||
)
|
||||
|
||||
product_image = render_html_with_layouter(
|
||||
product_html,
|
||||
"Product Showcase",
|
||||
product_style,
|
||||
page_size=(600, 350)
|
||||
)
|
||||
|
||||
# Combine images side by side
|
||||
print("\n Combining output 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_html_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"\nThe complete pipeline:")
|
||||
print(f" 1. HTML with <img> tags → parse_html_string() → Abstract blocks")
|
||||
print(f" 2. Abstract blocks → DocumentLayouter → Concrete objects")
|
||||
print(f" 3. Page.render() → PNG output")
|
||||
print(f"\n ✓ Using DocumentLayouter - NO custom rendering code!")
|
||||
|
||||
return combined
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user