end2end table layouter
Python CI / test (push) Successful in 6m35s

This commit is contained in:
2025-11-07 21:13:01 +01:00
parent 5fe4db4cbe
commit 496f3bf334
5 changed files with 859 additions and 17 deletions
+92 -16
View File
@@ -5,8 +5,9 @@ from typing import List, Tuple, Optional, Union
from pyWebLayout.concrete import Page, Line, Text
from pyWebLayout.concrete.image import RenderableImage
from pyWebLayout.concrete.functional import LinkText
from pyWebLayout.concrete.table import TableRenderer, TableStyle
from pyWebLayout.abstract import Paragraph, Word, Link
from pyWebLayout.abstract.block import Image as AbstractImage, PageBreak
from pyWebLayout.abstract.block import Image as AbstractImage, PageBreak, Table
from pyWebLayout.abstract.inline import LinkedWord
from pyWebLayout.style.concrete_style import ConcreteStyleRegistry, RenderingContext, StyleResolver
from pyWebLayout.style import Font, Alignment
@@ -288,14 +289,65 @@ def image_layouter(image: AbstractImage, page: Page, max_width: Optional[int] =
return True
def table_layouter(table: Table, page: Page, style: Optional[TableStyle] = None) -> bool:
"""
Layout a table within a given page.
This function uses the TableRenderer to render the table at the current
page position, advancing the page's y-offset after successful rendering.
Args:
table: The abstract Table object to layout
page: The page to layout the table on
style: Optional table styling configuration
Returns:
bool: True if table was successfully laid out, False if page ran out of space
"""
# Calculate available space
available_width = page.available_width
x_offset = page.border_size
y_offset = page._current_y_offset
# Access page.draw to ensure canvas is initialized
draw = page.draw
canvas = page._canvas
# Create table renderer
origin = (x_offset, y_offset)
renderer = TableRenderer(
table=table,
origin=origin,
available_width=available_width,
draw=draw,
style=style,
canvas=canvas
)
# Check if table fits on current page
table_height = renderer.size[1]
available_height = page.size[1] - y_offset - page.border_size
if table_height > available_height:
return False
# Render the table
renderer.render()
# Update page y-offset
page._current_y_offset = y_offset + table_height
return True
class DocumentLayouter:
"""
Document layouter that orchestrates layout of various abstract elements.
Delegates to specialized layouters for different content types:
- paragraph_layouter for text paragraphs
- image_layouter for images (future)
- table_layouter for tables (future)
- image_layouter for images
- table_layouter for tables
This class acts as a coordinator, managing the overall document flow
and page context while delegating specific layout tasks to specialized
@@ -305,12 +357,20 @@ class DocumentLayouter:
def __init__(self, page: Page):
"""
Initialize the document layouter with a page.
Args:
page: The page to layout content on
"""
self.page = page
self.style_registry = ConcreteStyleRegistry(page.style_resolver)
# Create a style resolver if page doesn't have one
if hasattr(page, 'style_resolver'):
style_resolver = page.style_resolver
else:
# Create a default rendering context and style resolver
from pyWebLayout.style.concrete_style import RenderingContext
context = RenderingContext()
style_resolver = StyleResolver(context)
self.style_registry = ConcreteStyleRegistry(style_resolver)
def layout_paragraph(self, paragraph: Paragraph, start_word: int = 0,
pretext: Optional[Text] = None) -> Tuple[bool, Optional[int], Optional[Text]]:
@@ -327,33 +387,46 @@ class DocumentLayouter:
"""
return paragraph_layouter(paragraph, self.page, start_word, pretext)
def layout_image(self, image: AbstractImage, max_width: Optional[int] = None,
def layout_image(self, image: AbstractImage, max_width: Optional[int] = None,
max_height: Optional[int] = None) -> bool:
"""
Layout an image using the image_layouter.
Args:
image: The abstract Image object to layout
max_width: Maximum width constraint (defaults to page available width)
max_height: Maximum height constraint (defaults to remaining page height)
Returns:
bool: True if image was successfully laid out, False if page ran out of space
"""
return image_layouter(image, self.page, max_width, max_height)
def layout_document(self, elements: List[Union[Paragraph, AbstractImage]]) -> bool:
def layout_table(self, table: Table, style: Optional[TableStyle] = None) -> bool:
"""
Layout a list of abstract elements (paragraphs and images).
Layout a table using the table_layouter.
Args:
table: The abstract Table object to layout
style: Optional table styling configuration
Returns:
bool: True if table was successfully laid out, False if page ran out of space
"""
return table_layouter(table, self.page, style)
def layout_document(self, elements: List[Union[Paragraph, AbstractImage, Table]]) -> bool:
"""
Layout a list of abstract elements (paragraphs, images, and tables).
This method delegates to specialized layouters based on element type:
- Paragraphs are handled by layout_paragraph
- Images are handled by layout_image
- Tables and other elements can be added in the future
- Tables are handled by layout_table
Args:
elements: List of abstract elements to layout
Returns:
True if all elements were successfully laid out, False otherwise
"""
@@ -366,6 +439,9 @@ class DocumentLayouter:
success = self.layout_image(element)
if not success:
return False
# Future: elif isinstance(element, Table): use table_layouter
elif isinstance(element, Table):
success = self.layout_table(element)
if not success:
return False
# Future: elif isinstance(element, CodeBlock): use code_layouter
return True