Files
pyWebLayout/tests/concrete/test_page_geometry.py

134 lines
5.2 KiB
Python

"""
Regression tests for page content geometry (spec S2).
Content must be laid out inside the content box - the page box less its border
and padding - on all four sides. Horizontal padding was previously ignored on the
left, shifting every line left by padding_left and leaving a gutter of
padding_left + padding_right on the right, so lines appeared to break early.
"""
import pytest
from pyWebLayout.abstract.block import Paragraph
from pyWebLayout.abstract.inline import Word
from pyWebLayout.concrete.page import Page
from pyWebLayout.layout.document_layouter import DocumentLayouter
from pyWebLayout.style import Font
from pyWebLayout.style.page_style import PageStyle
PADDING = (40, 30, 40, 20) # top, right, bottom, left - deliberately asymmetric
@pytest.fixture
def font():
return Font(font_size=12)
def filled_page(size, style, font, word_count=120):
page = Page(size=size, style=style)
paragraph = Paragraph(font)
for i in range(word_count):
paragraph.add_word(Word(f"word{i}", font))
DocumentLayouter(page).layout_paragraph(paragraph)
return page
class TestContentBox:
"""content_origin / content_rect describe the box content lives in."""
def test_content_origin_includes_border_and_padding(self):
page = Page(size=(400, 300), style=PageStyle(border_width=2, padding=PADDING))
assert page.content_origin == (2 + 20, 2 + 40)
def test_content_rect_subtracts_both_paddings(self):
page = Page(size=(400, 300), style=PageStyle(border_width=2, padding=PADDING))
x, y, w, h = page.content_rect
assert (x, y) == (22, 42)
assert w == 400 - 2 * 2 - 20 - 30
assert h == 300 - 2 * 2 - 40 - 40
def test_page_origin_offsets_the_content_box(self):
"""A page placed inside another surface reports absolute coordinates."""
page = Page(size=(100, 50), style=PageStyle(border_width=1, padding=(5, 5, 5, 5)),
origin=(200, 300))
assert page.content_origin == (206, 306)
def test_remaining_height_respects_bottom_padding(self, font):
style = PageStyle(border_width=2, padding=PADDING)
page = Page(size=(400, 300), style=style)
# Nothing laid out yet: the whole content box is available.
assert page.remaining_height == page.content_rect[3]
class TestLinePlacement:
"""Lines must start after the left padding and end before the right padding."""
def test_first_line_starts_at_content_origin(self, font):
style = PageStyle(border_width=2, padding=PADDING)
page = filled_page((400, 300), style, font)
line = page.children[0]
assert int(line.origin[0]) == page.content_origin[0]
assert int(line.origin[1]) == page.content_origin[1]
def test_line_width_matches_content_width(self, font):
style = PageStyle(border_width=2, padding=PADDING)
page = filled_page((400, 300), style, font)
line = page.children[0]
assert int(line.size[0]) == page.content_rect[2]
def test_no_line_extends_past_the_right_content_edge(self, font):
style = PageStyle(border_width=2, padding=PADDING)
page = filled_page((400, 300), style, font)
right_edge = page.content_rect[0] + page.content_rect[2]
for line in page.children:
assert int(line.origin[0]) + int(line.size[0]) <= right_edge
def test_ink_stays_inside_the_content_box(self, font):
"""The rendered pixels, not just the boxes, respect the padding."""
style = PageStyle(border_width=0, padding=PADDING,
background_color=(255, 255, 255))
page = filled_page((400, 300), style, font)
image = page.render().convert("L")
pixels = image.load()
inked_x = [x for x in range(400) for y in range(300) if pixels[x, y] < 128]
assert inked_x, "the page should have text on it"
x0, _, w, _ = page.content_rect
assert min(inked_x) >= x0
assert max(inked_x) <= x0 + w
def test_right_gutter_is_not_double_width(self, font):
"""
The regression: text was shifted left by padding_left, so the right gutter
was padding_left + padding_right wide while the left gutter was zero.
"""
style = PageStyle(border_width=0, padding=(10, 30, 10, 30))
page = filled_page((400, 300), style, font, word_count=200)
image = page.render().convert("L")
pixels = image.load()
inked_x = [x for x in range(400) for y in range(300) if pixels[x, y] < 128]
left_gutter = min(inked_x)
right_gutter = 400 - max(inked_x)
# Justification means the right edge is not always exactly flush, so allow
# slack - but the two gutters must be comparable, not 0 vs 60.
assert abs(left_gutter - right_gutter) < 25, \
f"asymmetric gutters: left={left_gutter} right={right_gutter}"
class TestBlockBottomBoundary:
"""Blocks must not be placed into the bottom padding."""
def test_lines_stop_before_bottom_padding(self, font):
style = PageStyle(border_width=2, padding=PADDING)
page = filled_page((400, 300), style, font, word_count=500)
bottom_edge = page.content_rect[1] + page.content_rect[3]
for line in page.children:
assert int(line.origin[1]) <= bottom_edge