""" 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