added tests to ensure border is respected.
All checks were successful
Python CI / test (push) Successful in 4m50s

This commit is contained in:
Duncan Tourolle 2025-06-08 17:29:42 +02:00
parent 27d029350a
commit 210358913a

View File

@ -455,5 +455,302 @@ class TestPage(unittest.TestCase):
self.assertEqual(large_result.size, (1920, 1080))
class TestPageBorderMarginRendering(unittest.TestCase):
"""Test cases specifically for border/margin consistency in Page rendering"""
def setUp(self):
"""Set up test fixtures for border/margin tests"""
self.page_size = (400, 300)
self.padding = (20, 15, 25, 10) # top, right, bottom, left
self.background_color = (240, 240, 240)
def test_border_consistency_with_text_content(self):
"""Test that borders/margins are consistent when rendering text content"""
from pyWebLayout.concrete.text import Text
from pyWebLayout.style.fonts import Font
# Create page with specific padding
page = Page(size=self.page_size, background_color=self.background_color)
page._padding = self.padding
# Add text content - let Text objects calculate their own dimensions
font = Font(font_size=14)
text1 = Text("First line of text", font)
text2 = Text("Second line of text", font)
page.add_child(text1)
page.add_child(text2)
# Render the page
result = page.render()
# Extract border areas and verify consistency
border_measurements = self._extract_border_measurements(result, self.padding)
self._verify_border_consistency(border_measurements, self.padding)
# Verify content area is correctly positioned
content_area = self._extract_content_area(result, self.padding)
self.assertIsNotNone(content_area)
# Ensure content doesn't bleed into border areas
self._verify_no_content_in_borders(result, self.padding, self.background_color)
def test_border_consistency_with_paragraph_content(self):
"""Test borders/margins with paragraph content that may wrap"""
from pyWebLayout.abstract.block import Paragraph
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style.fonts import Font
# Create a mock paragraph with multiple words
paragraph = Paragraph()
font = Font(font_size=12)
# Add words to create a longer paragraph
words_text = ["This", "is", "a", "longer", "paragraph", "that", "should", "wrap", "across", "multiple", "lines", "to", "test", "margin", "consistency"]
for word_text in words_text:
word = Word(word_text, font)
paragraph.add_word(word)
# Create page with specific padding
page = Page(size=self.page_size, background_color=self.background_color)
page._padding = self.padding
# Render paragraph on page
page.render_blocks([paragraph])
result = page.render()
# Extract and verify border measurements
border_measurements = self._extract_border_measurements(result, self.padding)
self._verify_border_consistency(border_measurements, self.padding)
# Verify content positioning
self._verify_content_within_bounds(result, self.padding)
def test_border_consistency_with_mixed_content(self):
"""Test borders/margins with mixed content types"""
from pyWebLayout.concrete.text import Text
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style.fonts import Font, FontWeight
# Create page with asymmetric padding to test edge cases
asymmetric_padding = (30, 20, 15, 25)
page = Page(size=(500, 400), background_color=self.background_color)
page._padding = asymmetric_padding
# Create mixed content
heading = Heading(HeadingLevel.H2)
heading_font = Font(font_size=18, weight=FontWeight.BOLD)
heading.add_word(Word("Test Heading", heading_font))
paragraph = Paragraph()
para_font = Font(font_size=12)
para_words = ["This", "paragraph", "follows", "the", "heading", "and", "tests", "mixed", "content", "rendering"]
for word_text in para_words:
paragraph.add_word(Word(word_text, para_font))
# Render mixed content
page.render_blocks([heading, paragraph])
result = page.render()
# Verify border consistency with asymmetric padding
border_measurements = self._extract_border_measurements(result, asymmetric_padding)
self._verify_border_consistency(border_measurements, asymmetric_padding)
# Verify no content bleeds into margins
self._verify_no_content_in_borders(result, asymmetric_padding, self.background_color)
def test_border_consistency_with_different_padding_values(self):
"""Test that different padding values maintain consistent borders"""
from pyWebLayout.concrete.text import Text
from pyWebLayout.style.fonts import Font
padding_configs = [
(10, 10, 10, 10), # uniform
(5, 15, 5, 15), # symmetric horizontal/vertical
(20, 30, 10, 5), # asymmetric
(0, 5, 0, 5), # minimal top/bottom
]
font = Font(font_size=14)
for padding in padding_configs:
with self.subTest(padding=padding):
# Create a fresh text object for each test to avoid state issues
test_text = Text("Border consistency test", font)
page = Page(size=self.page_size, background_color=self.background_color)
page._padding = padding
page.add_child(test_text)
result = page.render()
# Verify border measurements match expected padding
border_measurements = self._extract_border_measurements(result, padding)
self._verify_border_consistency(border_measurements, padding)
# Verify content area calculation
expected_content_width = self.page_size[0] - padding[1] - padding[3] # width - right - left
expected_content_height = self.page_size[1] - padding[0] - padding[2] # height - top - bottom
content_area = self._extract_content_area(result, padding)
self.assertEqual(content_area['width'], expected_content_width)
self.assertEqual(content_area['height'], expected_content_height)
def test_border_uniformity_across_renders(self):
"""Test that border areas remain uniform across multiple renders"""
from pyWebLayout.concrete.text import Text
from pyWebLayout.style.fonts import Font
page = Page(size=self.page_size, background_color=self.background_color)
page._padding = self.padding
font = Font(font_size=12)
text = Text("Consistency test content", font)
page.add_child(text)
# Render multiple times
results = []
for i in range(3):
result = page.render()
results.append(result)
border_measurements = self._extract_border_measurements(result, self.padding)
# Store first measurement as baseline
if i == 0:
baseline_measurements = border_measurements
else:
# Compare with baseline
self._compare_border_measurements(baseline_measurements, border_measurements)
def _extract_border_measurements(self, image, padding):
"""Extract measurements of border/margin areas from rendered image"""
width, height = image.size
top_pad, right_pad, bottom_pad, left_pad = padding
measurements = {
'top_border': {
'area': (0, 0, width, top_pad),
'pixels': self._get_area_pixels(image, (0, 0, width, top_pad))
},
'right_border': {
'area': (width - right_pad, 0, width, height),
'pixels': self._get_area_pixels(image, (width - right_pad, 0, width, height))
},
'bottom_border': {
'area': (0, height - bottom_pad, width, height),
'pixels': self._get_area_pixels(image, (0, height - bottom_pad, width, height))
},
'left_border': {
'area': (0, 0, left_pad, height),
'pixels': self._get_area_pixels(image, (0, 0, left_pad, height))
}
}
return measurements
def _get_area_pixels(self, image, area):
"""Extract pixel data from a specific area of the image"""
if area[2] <= area[0] or area[3] <= area[1]:
return [] # Invalid area
cropped = image.crop(area)
return list(cropped.getdata())
def _verify_border_consistency(self, measurements, expected_padding):
"""Verify that border measurements match expected padding values"""
# Get actual dimensions from the measurements instead of using self.page_size
# This allows the test to work with different page sizes
top_area = measurements['top_border']['area']
width = top_area[2] # right coordinate of top border gives us the width
height = measurements['left_border']['area'][3] # bottom coordinate of left border gives us the height
top_pad, right_pad, bottom_pad, left_pad = expected_padding
# Check area dimensions
self.assertEqual(top_area, (0, 0, width, top_pad))
right_area = measurements['right_border']['area']
self.assertEqual(right_area, (width - right_pad, 0, width, height))
bottom_area = measurements['bottom_border']['area']
self.assertEqual(bottom_area, (0, height - bottom_pad, width, height))
left_area = measurements['left_border']['area']
self.assertEqual(left_area, (0, 0, left_pad, height))
def _extract_content_area(self, image, padding):
"""Extract the content area (area inside borders/margins)"""
width, height = image.size
top_pad, right_pad, bottom_pad, left_pad = padding
content_area = {
'left': left_pad,
'top': top_pad,
'right': width - right_pad,
'bottom': height - bottom_pad,
'width': width - left_pad - right_pad,
'height': height - top_pad - bottom_pad
}
return content_area
def _verify_no_content_in_borders(self, image, padding, background_color):
"""Verify that no content bleeds into the border/margin areas"""
measurements = self._extract_border_measurements(image, padding)
# Check that border areas contain only background color
for border_name, border_data in measurements.items():
pixels = border_data['pixels']
if pixels: # Only check if area is not empty
# Most pixels should be background color (allowing for some anti-aliasing)
bg_count = sum(1 for pixel in pixels if self._is_background_color(pixel, background_color))
total_pixels = len(pixels)
# Allow up to 10% deviation for anti-aliasing effects
bg_ratio = bg_count / total_pixels if total_pixels > 0 else 1.0
self.assertGreaterEqual(bg_ratio, 0.9,
f"Border area '{border_name}' contains too much non-background content. "
f"Background ratio: {bg_ratio:.2f}")
def _is_background_color(self, pixel, background_color, tolerance=10):
"""Check if a pixel is close to the background color within tolerance"""
if len(pixel) >= 3:
r_diff = abs(pixel[0] - background_color[0])
g_diff = abs(pixel[1] - background_color[1])
b_diff = abs(pixel[2] - background_color[2])
return r_diff <= tolerance and g_diff <= tolerance and b_diff <= tolerance
return False
def _verify_content_within_bounds(self, image, padding):
"""Verify that content is positioned within the expected bounds"""
content_area = self._extract_content_area(image, padding)
# Sample the content area to ensure it's not all background
if content_area['width'] > 0 and content_area['height'] > 0:
content_crop = image.crop((
content_area['left'],
content_area['top'],
content_area['right'],
content_area['bottom']
))
# Content area should have some non-background pixels
content_pixels = list(content_crop.getdata())
non_bg_pixels = sum(1 for pixel in content_pixels
if not self._is_background_color(pixel, self.background_color))
# Expect at least some content in the content area
self.assertGreater(non_bg_pixels, 0, "Content area appears to be empty")
def _compare_border_measurements(self, baseline, current):
"""Compare two sets of border measurements for consistency"""
for border_name in baseline.keys():
baseline_area = baseline[border_name]['area']
current_area = current[border_name]['area']
self.assertEqual(baseline_area, current_area,
f"Border area '{border_name}' is inconsistent between renders")
if __name__ == '__main__':
unittest.main()