This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Tuple, Optional, Union
|
||||
import numpy as np
|
||||
|
||||
from pyWebLayout.concrete import Page, Line, Text
|
||||
from pyWebLayout.concrete.image import RenderableImage
|
||||
from pyWebLayout.concrete.functional import LinkText
|
||||
from pyWebLayout.concrete.functional import LinkText, ButtonText, FormFieldText
|
||||
from pyWebLayout.concrete.table import TableRenderer, TableStyle
|
||||
from pyWebLayout.abstract import Paragraph, Word, Link
|
||||
from pyWebLayout.abstract.block import Image as AbstractImage, PageBreak, Table
|
||||
from pyWebLayout.abstract.functional import Button, Form, FormField
|
||||
from pyWebLayout.abstract.inline import LinkedWord
|
||||
from pyWebLayout.style.concrete_style import ConcreteStyleRegistry, RenderingContext, StyleResolver
|
||||
from pyWebLayout.style import Font, Alignment
|
||||
@@ -340,6 +342,157 @@ def table_layouter(table: Table, page: Page, style: Optional[TableStyle] = None)
|
||||
return True
|
||||
|
||||
|
||||
def button_layouter(button: Button, page: Page, font: Optional[Font] = None,
|
||||
padding: Tuple[int, int, int, int] = (4, 8, 4, 8)) -> Tuple[bool, str]:
|
||||
"""
|
||||
Layout a button within a given page and register it for callback binding.
|
||||
|
||||
This function creates a ButtonText renderable, positions it on the page,
|
||||
and registers it in the page's callback registry using the button's html_id
|
||||
(if available) or an auto-generated id.
|
||||
|
||||
Args:
|
||||
button: The abstract Button object to layout
|
||||
page: The page to layout the button on
|
||||
font: Optional font for button text (defaults to page default)
|
||||
padding: Padding around button text (top, right, bottom, left)
|
||||
|
||||
Returns:
|
||||
Tuple of:
|
||||
- bool: True if button was successfully laid out, False if page ran out of space
|
||||
- str: The id used to register the button in the callback registry
|
||||
"""
|
||||
# Use provided font or create a default one
|
||||
if font is None:
|
||||
font = Font(font_size=14, colour=(255, 255, 255))
|
||||
|
||||
# Calculate available space
|
||||
available_height = page.size[1] - page._current_y_offset - page.border_size
|
||||
|
||||
# Create ButtonText renderable
|
||||
button_text = ButtonText(button, font, page.draw, padding=padding)
|
||||
|
||||
# Check if button fits on current page
|
||||
button_height = button_text.size[1]
|
||||
if button_height > available_height:
|
||||
return False, ""
|
||||
|
||||
# Position the button
|
||||
x_offset = page.border_size
|
||||
y_offset = page._current_y_offset
|
||||
|
||||
button_text.set_origin(np.array([x_offset, y_offset]))
|
||||
|
||||
# Register in callback registry
|
||||
html_id = button.html_id
|
||||
registered_id = page.callbacks.register(button_text, html_id=html_id)
|
||||
|
||||
# Add to page
|
||||
page.add_child(button_text)
|
||||
|
||||
return True, registered_id
|
||||
|
||||
|
||||
def form_field_layouter(field: FormField, page: Page, font: Optional[Font] = None,
|
||||
field_height: int = 24) -> Tuple[bool, str]:
|
||||
"""
|
||||
Layout a form field within a given page and register it for callback binding.
|
||||
|
||||
This function creates a FormFieldText renderable, positions it on the page,
|
||||
and registers it in the page's callback registry.
|
||||
|
||||
Args:
|
||||
field: The abstract FormField object to layout
|
||||
page: The page to layout the field on
|
||||
font: Optional font for field label (defaults to page default)
|
||||
field_height: Height of the input field area
|
||||
|
||||
Returns:
|
||||
Tuple of:
|
||||
- bool: True if field was successfully laid out, False if page ran out of space
|
||||
- str: The id used to register the field in the callback registry
|
||||
"""
|
||||
# Use provided font or create a default one
|
||||
if font is None:
|
||||
font = Font(font_size=12, colour=(0, 0, 0))
|
||||
|
||||
# Calculate available space
|
||||
available_height = page.size[1] - page._current_y_offset - page.border_size
|
||||
|
||||
# Create FormFieldText renderable
|
||||
field_text = FormFieldText(field, font, page.draw, field_height=field_height)
|
||||
|
||||
# Check if field fits on current page
|
||||
total_field_height = field_text.size[1]
|
||||
if total_field_height > available_height:
|
||||
return False, ""
|
||||
|
||||
# Position the field
|
||||
x_offset = page.border_size
|
||||
y_offset = page._current_y_offset
|
||||
|
||||
field_text.set_origin(np.array([x_offset, y_offset]))
|
||||
|
||||
# Register in callback registry (use field name as html_id fallback)
|
||||
html_id = getattr(field, '_html_id', None) or field.name
|
||||
registered_id = page.callbacks.register(field_text, html_id=html_id)
|
||||
|
||||
# Add to page
|
||||
page.add_child(field_text)
|
||||
|
||||
return True, registered_id
|
||||
|
||||
|
||||
def form_layouter(form: Form, page: Page, font: Optional[Font] = None,
|
||||
field_spacing: int = 10) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Layout a complete form with all its fields within a given page.
|
||||
|
||||
This function creates FormFieldText renderables for all fields in the form,
|
||||
positions them vertically, and registers both the form and its fields in
|
||||
the page's callback registry.
|
||||
|
||||
Args:
|
||||
form: The abstract Form object to layout
|
||||
page: The page to layout the form on
|
||||
font: Optional font for field labels (defaults to page default)
|
||||
field_spacing: Vertical spacing between fields in pixels
|
||||
|
||||
Returns:
|
||||
Tuple of:
|
||||
- bool: True if form was successfully laid out, False if page ran out of space
|
||||
- List[str]: List of registered ids for all fields (empty if layout failed)
|
||||
"""
|
||||
# Use provided font or create a default one
|
||||
if font is None:
|
||||
font = Font(font_size=12, colour=(0, 0, 0))
|
||||
|
||||
# Track registered field ids
|
||||
field_ids = []
|
||||
|
||||
# Layout each field in the form
|
||||
for field_name, field in form._fields.items():
|
||||
# Add spacing before each field (except the first)
|
||||
if field_ids:
|
||||
page._current_y_offset += field_spacing
|
||||
|
||||
# Layout the field
|
||||
success, field_id = form_field_layouter(field, page, font)
|
||||
|
||||
if not success:
|
||||
# Couldn't fit this field, return failure
|
||||
return False, []
|
||||
|
||||
field_ids.append(field_id)
|
||||
|
||||
# Register the form itself (optional, for form submission)
|
||||
# Note: The form doesn't have a visual representation, but we can track it
|
||||
# for submission callbacks
|
||||
# form_id = page.callbacks.register(form, html_id=form.html_id)
|
||||
|
||||
return True, field_ids
|
||||
|
||||
|
||||
class DocumentLayouter:
|
||||
"""
|
||||
Document layouter that orchestrates layout of various abstract elements.
|
||||
@@ -415,14 +568,46 @@ class DocumentLayouter:
|
||||
"""
|
||||
return table_layouter(table, self.page, style)
|
||||
|
||||
def layout_document(self, elements: List[Union[Paragraph, AbstractImage, Table]]) -> bool:
|
||||
def layout_button(self, button: Button, font: Optional[Font] = None,
|
||||
padding: Tuple[int, int, int, int] = (4, 8, 4, 8)) -> Tuple[bool, str]:
|
||||
"""
|
||||
Layout a list of abstract elements (paragraphs, images, and tables).
|
||||
Layout a button using the button_layouter.
|
||||
|
||||
Args:
|
||||
button: The abstract Button object to layout
|
||||
font: Optional font for button text
|
||||
padding: Padding around button text
|
||||
|
||||
Returns:
|
||||
Tuple of (success, registered_id)
|
||||
"""
|
||||
return button_layouter(button, self.page, font, padding)
|
||||
|
||||
def layout_form(self, form: Form, font: Optional[Font] = None,
|
||||
field_spacing: int = 10) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Layout a form using the form_layouter.
|
||||
|
||||
Args:
|
||||
form: The abstract Form object to layout
|
||||
font: Optional font for field labels
|
||||
field_spacing: Vertical spacing between fields
|
||||
|
||||
Returns:
|
||||
Tuple of (success, list_of_field_ids)
|
||||
"""
|
||||
return form_layouter(form, self.page, font, field_spacing)
|
||||
|
||||
def layout_document(self, elements: List[Union[Paragraph, AbstractImage, Table, Button, Form]]) -> bool:
|
||||
"""
|
||||
Layout a list of abstract elements (paragraphs, images, tables, buttons, and forms).
|
||||
|
||||
This method delegates to specialized layouters based on element type:
|
||||
- Paragraphs are handled by layout_paragraph
|
||||
- Images are handled by layout_image
|
||||
- Tables are handled by layout_table
|
||||
- Buttons are handled by layout_button
|
||||
- Forms are handled by layout_form
|
||||
|
||||
Args:
|
||||
elements: List of abstract elements to layout
|
||||
@@ -443,5 +628,13 @@ class DocumentLayouter:
|
||||
success = self.layout_table(element)
|
||||
if not success:
|
||||
return False
|
||||
elif isinstance(element, Button):
|
||||
success, _ = self.layout_button(element)
|
||||
if not success:
|
||||
return False
|
||||
elif isinstance(element, Form):
|
||||
success, _ = self.layout_form(element)
|
||||
if not success:
|
||||
return False
|
||||
# Future: elif isinstance(element, CodeBlock): use code_layouter
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user