@@ -3,7 +3,11 @@ from __future__ import annotations
|
||||
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.abstract import Paragraph, Word, Link
|
||||
from pyWebLayout.abstract.block import Image as AbstractImage
|
||||
from pyWebLayout.abstract.inline import LinkedWord
|
||||
from pyWebLayout.style.concrete_style import ConcreteStyleRegistry, RenderingContext, StyleResolver
|
||||
|
||||
def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pretext: Optional[Text] = None, alignment_override: Optional['Alignment'] = None) -> Tuple[bool, Optional[int], Optional[Text]]:
|
||||
@@ -130,6 +134,12 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
|
||||
|
||||
# Process words starting from start_word
|
||||
for i, word in enumerate(paragraph.words[start_word:], start=start_word):
|
||||
# Check if this is a LinkedWord and needs special handling in concrete layer
|
||||
# Note: The Line.add_word method will create Text objects internally,
|
||||
# but we may want to create LinkText for LinkedWord instances in future
|
||||
# For now, the abstract layer (LinkedWord) carries the link info,
|
||||
# and the concrete layer (LinkText) would be created during rendering
|
||||
|
||||
success, overflow_text = current_line.add_word(word, current_pretext)
|
||||
|
||||
if success:
|
||||
@@ -191,3 +201,144 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
|
||||
|
||||
# All words processed successfully
|
||||
return True, None, None
|
||||
|
||||
|
||||
def image_layouter(image: AbstractImage, page: Page, max_width: Optional[int] = None,
|
||||
max_height: Optional[int] = None) -> bool:
|
||||
"""
|
||||
Layout an image within a given page.
|
||||
|
||||
This function places an image on the page, respecting size constraints
|
||||
and available space. Images are centered horizontally by default.
|
||||
|
||||
Args:
|
||||
image: The abstract Image object to layout
|
||||
page: The page to layout the image on
|
||||
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
|
||||
"""
|
||||
from pyWebLayout.style import Alignment
|
||||
|
||||
# Use page available width if max_width not specified
|
||||
if max_width is None:
|
||||
max_width = page.available_width
|
||||
|
||||
# Calculate available height on page
|
||||
available_height = page.size[1] - page._current_y_offset - page.border_size
|
||||
if max_height is None:
|
||||
max_height = available_height
|
||||
else:
|
||||
max_height = min(max_height, available_height)
|
||||
|
||||
# Calculate scaled dimensions
|
||||
scaled_width, scaled_height = image.calculate_scaled_dimensions(max_width, max_height)
|
||||
|
||||
# Check if image fits on current page
|
||||
if scaled_height is None or scaled_height > available_height:
|
||||
return False
|
||||
|
||||
# Create renderable image
|
||||
x_offset = page.border_size
|
||||
y_offset = page._current_y_offset
|
||||
|
||||
renderable_image = RenderableImage(
|
||||
image=image,
|
||||
canvas=page.canvas,
|
||||
max_width=max_width,
|
||||
max_height=max_height,
|
||||
origin=(x_offset, y_offset),
|
||||
size=(scaled_width or max_width, scaled_height or max_height),
|
||||
halign=Alignment.CENTER,
|
||||
valign=Alignment.TOP
|
||||
)
|
||||
|
||||
# Add to page
|
||||
page.add_child(renderable_image)
|
||||
|
||||
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)
|
||||
|
||||
This class acts as a coordinator, managing the overall document flow
|
||||
and page context while delegating specific layout tasks to specialized
|
||||
layouter functions.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
def layout_paragraph(self, paragraph: Paragraph, start_word: int = 0,
|
||||
pretext: Optional[Text] = None) -> Tuple[bool, Optional[int], Optional[Text]]:
|
||||
"""
|
||||
Layout a paragraph using the paragraph_layouter.
|
||||
|
||||
Args:
|
||||
paragraph: The paragraph to layout
|
||||
start_word: Index of the first word to process (for continuation)
|
||||
pretext: Optional pretext from a previous hyphenated word
|
||||
|
||||
Returns:
|
||||
Tuple of (success, failed_word_index, remaining_pretext)
|
||||
"""
|
||||
return paragraph_layouter(paragraph, self.page, start_word, pretext)
|
||||
|
||||
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:
|
||||
"""
|
||||
Layout a list of abstract elements (paragraphs and images).
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
elements: List of abstract elements to layout
|
||||
|
||||
Returns:
|
||||
True if all elements were successfully laid out, False otherwise
|
||||
"""
|
||||
for element in elements:
|
||||
if isinstance(element, Paragraph):
|
||||
success, _, _ = self.layout_paragraph(element)
|
||||
if not success:
|
||||
return False
|
||||
elif isinstance(element, AbstractImage):
|
||||
success = self.layout_image(element)
|
||||
if not success:
|
||||
return False
|
||||
# Future: elif isinstance(element, Table): use table_layouter
|
||||
# Future: elif isinstance(element, CodeBlock): use code_layouter
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user