added tests to ensure border is respected.
All checks were successful
Python CI / test (push) Successful in 4m50s
All checks were successful
Python CI / test (push) Successful in 4m50s
This commit is contained in:
parent
27d029350a
commit
210358913a
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user