@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Typesetting module for the pyWebLayout library.
|
||||
|
||||
This package handles the organization and arrangement of elements for rendering, including:
|
||||
- Flow layout algorithms
|
||||
- Container management
|
||||
- Element positioning and sizing
|
||||
- Content wrapping and overflow
|
||||
- Coordinate systems and transformations
|
||||
- Pagination for book-like content
|
||||
"""
|
||||
@@ -0,0 +1,162 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from pyWebLayout.concrete import Page, Line, Text
|
||||
from pyWebLayout.abstract import Paragraph, Word, Link
|
||||
from pyWebLayout.style.concrete_style import ConcreteStyleRegistry
|
||||
|
||||
|
||||
def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pretext: Optional[Text] = None) -> Tuple[bool, Optional[int], Optional[Text]]:
|
||||
"""
|
||||
Layout a paragraph of text within a given page.
|
||||
|
||||
This function extracts word spacing constraints from the style system
|
||||
and uses them to create properly spaced lines of text.
|
||||
|
||||
Args:
|
||||
paragraph: The paragraph to layout
|
||||
page: The page to layout the paragraph on
|
||||
start_word: Index of the first word to process (for continuation)
|
||||
pretext: Optional pretext from a previous hyphenated word
|
||||
|
||||
Returns:
|
||||
Tuple of:
|
||||
- bool: True if paragraph was completely laid out, False if page ran out of space
|
||||
- Optional[int]: Index of first word that didn't fit (if any)
|
||||
- Optional[Text]: Remaining pretext if word was hyphenated (if any)
|
||||
"""
|
||||
if not paragraph.words:
|
||||
return True, None, None
|
||||
|
||||
# Validate inputs
|
||||
if start_word >= len(paragraph.words):
|
||||
return True, None, None
|
||||
|
||||
# Get the concrete style with resolved word spacing constraints
|
||||
style_registry = ConcreteStyleRegistry(page.style_resolver)
|
||||
concrete_style = style_registry.get_concrete_style(paragraph.style)
|
||||
|
||||
# Extract word spacing constraints (min, max) for Line constructor
|
||||
word_spacing_constraints = (
|
||||
int(concrete_style.word_spacing_min),
|
||||
int(concrete_style.word_spacing_max)
|
||||
)
|
||||
|
||||
def create_new_line() -> Optional[Line]:
|
||||
"""Helper function to create a new line, returns None if page is full."""
|
||||
if not page.can_fit_line(paragraph.line_height):
|
||||
return None
|
||||
|
||||
y_cursor = page._current_y_offset
|
||||
x_cursor = page.border_size
|
||||
|
||||
return Line(
|
||||
spacing=word_spacing_constraints,
|
||||
origin=(x_cursor, y_cursor),
|
||||
size=(page.available_width, paragraph.line_height),
|
||||
draw=page.draw,
|
||||
font=concrete_style.create_font(),
|
||||
halign=concrete_style.text_align
|
||||
)
|
||||
|
||||
# Create initial line
|
||||
current_line = create_new_line()
|
||||
if not current_line:
|
||||
return False, start_word, pretext
|
||||
|
||||
page.add_child(current_line)
|
||||
page._current_y_offset += paragraph.line_height
|
||||
|
||||
# Track current position in paragraph
|
||||
current_pretext = pretext
|
||||
|
||||
# Process words starting from start_word
|
||||
for i, word in enumerate(paragraph.words[start_word:], start=start_word):
|
||||
success, overflow_text = current_line.add_word(word, current_pretext)
|
||||
|
||||
if success:
|
||||
# Word fit successfully
|
||||
current_pretext = None # Clear pretext after successful placement
|
||||
else:
|
||||
# Word didn't fit, need a new line
|
||||
current_line = create_new_line()
|
||||
if not current_line:
|
||||
# Page is full, return current position
|
||||
return False, i, overflow_text
|
||||
|
||||
page.add_child(current_line)
|
||||
page._current_y_offset += paragraph.line_height
|
||||
|
||||
# Try to add the word to the new line
|
||||
success, overflow_text = current_line.add_word(word, current_pretext)
|
||||
|
||||
if not success:
|
||||
# Word still doesn't fit even on a new line
|
||||
# This might happen with very long words or narrow pages
|
||||
if overflow_text:
|
||||
# Word was hyphenated, continue with the overflow
|
||||
current_pretext = overflow_text
|
||||
continue
|
||||
else:
|
||||
# Word cannot be broken, skip it or handle as error
|
||||
# For now, we'll return indicating we couldn't process this word
|
||||
return False, i, None
|
||||
else:
|
||||
current_pretext = overflow_text # May be None or hyphenated remainder
|
||||
|
||||
# All words processed successfully
|
||||
return True, None, None
|
||||
|
||||
|
||||
class DocumentLayouter:
|
||||
"""
|
||||
Class-based document layouter for more complex layout operations.
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page):
|
||||
"""Initialize the layouter with a page."""
|
||||
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 class-based approach.
|
||||
|
||||
This method provides the same functionality as the standalone function
|
||||
but with better state management and reusability.
|
||||
"""
|
||||
return paragraph_layouter(paragraph, self.page, start_word, pretext)
|
||||
|
||||
def layout_document(self, paragraphs: List[Paragraph]) -> bool:
|
||||
"""
|
||||
Layout multiple paragraphs in sequence.
|
||||
|
||||
Args:
|
||||
paragraphs: List of paragraphs to layout
|
||||
|
||||
Returns:
|
||||
True if all paragraphs were laid out successfully, False otherwise
|
||||
"""
|
||||
for paragraph in paragraphs:
|
||||
start_word = 0
|
||||
pretext = None
|
||||
|
||||
while True:
|
||||
complete, next_word, remaining_pretext = self.layout_paragraph(
|
||||
paragraph, start_word, pretext
|
||||
)
|
||||
|
||||
if complete:
|
||||
# Paragraph finished
|
||||
break
|
||||
|
||||
if next_word is None:
|
||||
# Error condition
|
||||
return False
|
||||
|
||||
# Continue on next page or handle page break
|
||||
# For now, we'll just return False indicating we need more space
|
||||
return False
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user