fix(layout): honour horizontal padding and page origin (S2)

paragraph_layouter placed lines at page.border_size while sizing them to
available_width, which subtracts both paddings. Text therefore started flush
against the left border and the entire padding budget accumulated on the right,
so lines broke well short of the right border.

Page now describes its content box directly - content_origin, content_rect and
remaining_height - and the layouters use it instead of each recomputing the
geometry from border_size. The four block layouters had all been computing
remaining space as size[1] - y_offset - border_size, subtracting the border but
not the bottom padding, so every block type could be placed into the bottom
padding; remaining_height fixes that too.

Page also gains an origin, defaulting to (0, 0). That is inert for a top-level
page but lets a page be positioned inside another surface, which table cells
need in order to be laid out by the normal engine.

Golden images regenerated: content now sits inside the padding on all sides.
This commit is contained in:
2026-08-06 21:11:28 +02:00
parent a57da8011e
commit f18cec2da8
16 changed files with 198 additions and 17 deletions
+9 -9
View File
@@ -151,7 +151,7 @@ def paragraph_layouter(paragraph: Paragraph,
y_cursor = page._current_y_offset
else:
y_cursor = page._current_y_offset
x_cursor = page.border_size
x_cursor = page.content_origin[0]
# Create a temporary Text object to calculate word width
if word:
@@ -305,7 +305,7 @@ def image_layouter(image: AbstractImage, page: Page, max_width: Optional[int] =
max_width = page.available_width
# Calculate available height on page
available_height = page.size[1] - page._current_y_offset - page.border_size
available_height = page.remaining_height
# If no space available, image doesn't fit
if available_height <= 0:
@@ -325,7 +325,7 @@ def image_layouter(image: AbstractImage, page: Page, max_width: Optional[int] =
return False
# Create renderable image
x_offset = page.border_size
x_offset = page.content_origin[0]
y_offset = page._current_y_offset
# Access page.draw to ensure canvas is initialized
@@ -368,7 +368,7 @@ def table_layouter(
"""
# Calculate available space
available_width = page.available_width
x_offset = page.border_size
x_offset = page.content_origin[0]
y_offset = page._current_y_offset
# Access page.draw to ensure canvas is initialized
@@ -388,7 +388,7 @@ def table_layouter(
# Check if table fits on current page
table_height = renderer.size[1]
available_height = page.size[1] - y_offset - page.border_size
available_height = page.remaining_height
if table_height > available_height:
return False
@@ -436,7 +436,7 @@ def button_layouter(button: Button,
font = Font(font_size=14, colour=(255, 255, 255))
# Calculate available space
available_height = page.size[1] - page._current_y_offset - page.border_size
available_height = page.remaining_height
# Create ButtonText renderable
button_text = ButtonText(button, font, page.draw, padding=padding)
@@ -447,7 +447,7 @@ def button_layouter(button: Button,
return False, ""
# Position the button
x_offset = page.border_size
x_offset = page.content_origin[0]
y_offset = page._current_y_offset
button_text.set_origin(np.array([x_offset, y_offset]))
@@ -486,7 +486,7 @@ def form_field_layouter(field: FormField, page: Page, font: Optional[Font] = Non
font = Font(font_size=12, colour=(0, 0, 0))
# Calculate available space
available_height = page.size[1] - page._current_y_offset - page.border_size
available_height = page.remaining_height
# Create FormFieldText renderable
field_text = FormFieldText(field, font, page.draw, field_height=field_height)
@@ -497,7 +497,7 @@ def form_field_layouter(field: FormField, page: Page, font: Optional[Font] = Non
return False, ""
# Position the field
x_offset = page.border_size
x_offset = page.content_origin[0]
y_offset = page._current_y_offset
field_text.set_origin(np.array([x_offset, y_offset]))