464 lines
15 KiB
Python
464 lines
15 KiB
Python
"""
|
|
Unit tests for PageRenderer coordinate transformations
|
|
"""
|
|
|
|
import pytest
|
|
from pyPhotoAlbum.page_renderer import PageRenderer
|
|
|
|
|
|
class TestPageRendererCoordinates:
|
|
"""Test coordinate transformation methods"""
|
|
|
|
def test_page_to_screen_no_zoom_no_pan(self):
|
|
"""Test page_to_screen conversion with zoom=1.0 and no pan"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Element at page origin should map to screen_x, screen_y
|
|
screen_x, screen_y = renderer.page_to_screen(0, 0)
|
|
assert screen_x == 100.0
|
|
assert screen_y == 200.0
|
|
|
|
# Element at (50, 75) should be offset by that amount
|
|
screen_x, screen_y = renderer.page_to_screen(50, 75)
|
|
assert screen_x == 150.0
|
|
assert screen_y == 275.0
|
|
|
|
def test_page_to_screen_with_zoom(self):
|
|
"""Test page_to_screen conversion with zoom applied"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=2.0
|
|
)
|
|
|
|
# With zoom=2.0, distances should be doubled
|
|
screen_x, screen_y = renderer.page_to_screen(50, 75)
|
|
assert screen_x == 200.0 # 100 + 50*2
|
|
assert screen_y == 350.0 # 200 + 75*2
|
|
|
|
def test_page_to_screen_with_fractional_zoom(self):
|
|
"""Test page_to_screen conversion with fractional zoom"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=0.5
|
|
)
|
|
|
|
# With zoom=0.5, distances should be halved
|
|
screen_x, screen_y = renderer.page_to_screen(100, 150)
|
|
assert screen_x == 150.0 # 100 + 100*0.5
|
|
assert screen_y == 275.0 # 200 + 150*0.5
|
|
|
|
def test_screen_to_page_no_zoom_no_pan(self):
|
|
"""Test screen_to_page conversion with zoom=1.0 and no pan"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Screen position at screen_x, screen_y should map to page origin
|
|
page_x, page_y = renderer.screen_to_page(100.0, 200.0)
|
|
assert page_x == 0.0
|
|
assert page_y == 0.0
|
|
|
|
# Screen position offset should map to same offset in page coords
|
|
page_x, page_y = renderer.screen_to_page(150.0, 275.0)
|
|
assert page_x == 50.0
|
|
assert page_y == 75.0
|
|
|
|
def test_screen_to_page_with_zoom(self):
|
|
"""Test screen_to_page conversion with zoom applied"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=2.0
|
|
)
|
|
|
|
# With zoom=2.0, screen distances should be divided by 2 to get page coords
|
|
page_x, page_y = renderer.screen_to_page(200.0, 350.0)
|
|
assert page_x == 50.0 # (200-100)/2
|
|
assert page_y == 75.0 # (350-200)/2
|
|
|
|
def test_roundtrip_conversion_no_zoom(self):
|
|
"""Test that page->screen->page conversion is accurate with no zoom"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Start with page coordinates
|
|
orig_page_x, orig_page_y = 123.45, 678.90
|
|
|
|
# Convert to screen and back
|
|
screen_x, screen_y = renderer.page_to_screen(orig_page_x, orig_page_y)
|
|
page_x, page_y = renderer.screen_to_page(screen_x, screen_y)
|
|
|
|
# Should get back the original values
|
|
assert abs(page_x - orig_page_x) < 0.001
|
|
assert abs(page_y - orig_page_y) < 0.001
|
|
|
|
def test_roundtrip_conversion_with_zoom(self):
|
|
"""Test that page->screen->page conversion is accurate with zoom"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.5
|
|
)
|
|
|
|
# Start with page coordinates
|
|
orig_page_x, orig_page_y = 123.45, 678.90
|
|
|
|
# Convert to screen and back
|
|
screen_x, screen_y = renderer.page_to_screen(orig_page_x, orig_page_y)
|
|
page_x, page_y = renderer.screen_to_page(screen_x, screen_y)
|
|
|
|
# Should get back the original values (with floating point tolerance)
|
|
assert abs(page_x - orig_page_x) < 0.001
|
|
assert abs(page_y - orig_page_y) < 0.001
|
|
|
|
def test_roundtrip_conversion_extreme_zoom(self):
|
|
"""Test coordinate conversion with extreme zoom levels"""
|
|
for zoom in [0.1, 0.5, 1.0, 2.0, 5.0]:
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=50.0,
|
|
screen_y=100.0,
|
|
dpi=96,
|
|
zoom=zoom
|
|
)
|
|
|
|
orig_page_x, orig_page_y = 250.0, 400.0
|
|
screen_x, screen_y = renderer.page_to_screen(orig_page_x, orig_page_y)
|
|
page_x, page_y = renderer.screen_to_page(screen_x, screen_y)
|
|
|
|
assert abs(page_x - orig_page_x) < 0.001
|
|
assert abs(page_y - orig_page_y) < 0.001
|
|
|
|
|
|
class TestPageRendererBounds:
|
|
"""Test page bounds and point detection"""
|
|
|
|
def test_is_point_in_page_inside(self):
|
|
"""Test is_point_in_page for points inside the page"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0, # A4 width
|
|
page_height_mm=297.0, # A4 height
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Calculate page dimensions in pixels
|
|
page_width_px = 210.0 * 96 / 25.4 # ~794 pixels
|
|
page_height_px = 297.0 * 96 / 25.4 # ~1123 pixels
|
|
|
|
# Point in center should be inside
|
|
center_x = 100.0 + page_width_px / 2
|
|
center_y = 200.0 + page_height_px / 2
|
|
assert renderer.is_point_in_page(center_x, center_y)
|
|
|
|
# Point at origin should be inside
|
|
assert renderer.is_point_in_page(100.0, 200.0)
|
|
|
|
# Point at bottom-right corner should be inside
|
|
assert renderer.is_point_in_page(
|
|
100.0 + page_width_px,
|
|
200.0 + page_height_px
|
|
)
|
|
|
|
def test_is_point_in_page_outside(self):
|
|
"""Test is_point_in_page for points outside the page"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Point before page start
|
|
assert not renderer.is_point_in_page(50.0, 150.0)
|
|
|
|
# Point way beyond page
|
|
assert not renderer.is_point_in_page(2000.0, 2000.0)
|
|
|
|
# Point to the left of page
|
|
assert not renderer.is_point_in_page(50.0, 500.0)
|
|
|
|
# Point above page
|
|
assert not renderer.is_point_in_page(500.0, 150.0)
|
|
|
|
def test_is_point_in_page_with_zoom(self):
|
|
"""Test is_point_in_page with different zoom levels"""
|
|
for zoom in [0.5, 1.0, 2.0]:
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=zoom
|
|
)
|
|
|
|
# Center of page should always be inside regardless of zoom
|
|
page_width_px = 210.0 * 96 / 25.4
|
|
page_height_px = 297.0 * 96 / 25.4
|
|
center_x = 100.0 + (page_width_px * zoom) / 2
|
|
center_y = 200.0 + (page_height_px * zoom) / 2
|
|
assert renderer.is_point_in_page(center_x, center_y)
|
|
|
|
def test_get_page_bounds_screen(self):
|
|
"""Test get_page_bounds_screen returns correct screen coordinates"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.5
|
|
)
|
|
|
|
x, y, w, h = renderer.get_page_bounds_screen()
|
|
|
|
assert x == 100.0
|
|
assert y == 200.0
|
|
|
|
# Width and height should be scaled by zoom
|
|
page_width_px = 210.0 * 96 / 25.4
|
|
page_height_px = 297.0 * 96 / 25.4
|
|
assert abs(w - page_width_px * 1.5) < 0.1
|
|
assert abs(h - page_height_px * 1.5) < 0.1
|
|
|
|
def test_get_page_bounds_page(self):
|
|
"""Test get_page_bounds_page returns correct page-local coordinates"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.5
|
|
)
|
|
|
|
x, y, w, h = renderer.get_page_bounds_page()
|
|
|
|
# Origin should be at 0,0 in page-local coordinates
|
|
assert x == 0.0
|
|
assert y == 0.0
|
|
|
|
# Width and height should NOT be affected by zoom (page-local coords)
|
|
page_width_px = 210.0 * 96 / 25.4
|
|
page_height_px = 297.0 * 96 / 25.4
|
|
assert abs(w - page_width_px) < 0.1
|
|
assert abs(h - page_height_px) < 0.1
|
|
|
|
|
|
class TestPageRendererSubPages:
|
|
"""Test sub-page detection for facing pages"""
|
|
|
|
def test_get_sub_page_at_single_page(self):
|
|
"""Test that get_sub_page_at returns None for single pages"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# For non-facing pages, should return None
|
|
result = renderer.get_sub_page_at(500.0, is_facing_page=False)
|
|
assert result is None
|
|
|
|
def test_get_sub_page_at_facing_page_left(self):
|
|
"""Test get_sub_page_at for left side of facing page"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=420.0, # Double width for facing page
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Calculate center line
|
|
page_width_px = 420.0 * 96 / 25.4
|
|
center_x = 100.0 + page_width_px / 2
|
|
|
|
# Point before center should be 'left'
|
|
result = renderer.get_sub_page_at(center_x - 10, is_facing_page=True)
|
|
assert result == 'left'
|
|
|
|
def test_get_sub_page_at_facing_page_right(self):
|
|
"""Test get_sub_page_at for right side of facing page"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=420.0, # Double width for facing page
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Calculate center line
|
|
page_width_px = 420.0 * 96 / 25.4
|
|
center_x = 100.0 + page_width_px / 2
|
|
|
|
# Point after center should be 'right'
|
|
result = renderer.get_sub_page_at(center_x + 10, is_facing_page=True)
|
|
assert result == 'right'
|
|
|
|
|
|
class TestPageRendererDimensions:
|
|
"""Test page dimension calculations"""
|
|
|
|
def test_page_dimensions_calculated_correctly(self):
|
|
"""Test that page dimensions are calculated correctly from mm to pixels"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0, # A4 width
|
|
page_height_mm=297.0, # A4 height
|
|
screen_x=0.0,
|
|
screen_y=0.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# A4 at 96 DPI
|
|
expected_width = 210.0 * 96 / 25.4 # ~794 pixels
|
|
expected_height = 297.0 * 96 / 25.4 # ~1123 pixels
|
|
|
|
assert abs(renderer.page_width_px - expected_width) < 0.1
|
|
assert abs(renderer.page_height_px - expected_height) < 0.1
|
|
|
|
def test_screen_dimensions_with_zoom(self):
|
|
"""Test that screen dimensions account for zoom"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=0.0,
|
|
screen_y=0.0,
|
|
dpi=96,
|
|
zoom=2.0
|
|
)
|
|
|
|
# Screen dimensions should be doubled due to zoom
|
|
expected_width = (210.0 * 96 / 25.4) * 2.0
|
|
expected_height = (297.0 * 96 / 25.4) * 2.0
|
|
|
|
assert abs(renderer.screen_width - expected_width) < 0.1
|
|
assert abs(renderer.screen_height - expected_height) < 0.1
|
|
|
|
def test_different_dpi_values(self):
|
|
"""Test page dimensions with different DPI values"""
|
|
dpi_values = [72, 96, 150, 300]
|
|
|
|
for dpi in dpi_values:
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=0.0,
|
|
screen_y=0.0,
|
|
dpi=dpi,
|
|
zoom=1.0
|
|
)
|
|
|
|
expected_width = 210.0 * dpi / 25.4
|
|
expected_height = 297.0 * dpi / 25.4
|
|
|
|
assert abs(renderer.page_width_px - expected_width) < 0.1
|
|
assert abs(renderer.page_height_px - expected_height) < 0.1
|
|
|
|
|
|
class TestPageRendererEdgeCases:
|
|
"""Test edge cases and boundary conditions"""
|
|
|
|
def test_zero_coordinates(self):
|
|
"""Test handling of zero coordinates"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
screen_x, screen_y = renderer.page_to_screen(0, 0)
|
|
assert screen_x == 100.0
|
|
assert screen_y == 200.0
|
|
|
|
page_x, page_y = renderer.screen_to_page(100.0, 200.0)
|
|
assert page_x == 0.0
|
|
assert page_y == 0.0
|
|
|
|
def test_negative_page_coordinates(self):
|
|
"""Test handling of negative page coordinates"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
# Negative page coordinates should still convert correctly
|
|
screen_x, screen_y = renderer.page_to_screen(-50, -75)
|
|
assert screen_x == 50.0
|
|
assert screen_y == 125.0
|
|
|
|
# And back again
|
|
page_x, page_y = renderer.screen_to_page(50.0, 125.0)
|
|
assert page_x == -50.0
|
|
assert page_y == -75.0
|
|
|
|
def test_very_large_coordinates(self):
|
|
"""Test handling of very large coordinates"""
|
|
renderer = PageRenderer(
|
|
page_width_mm=210.0,
|
|
page_height_mm=297.0,
|
|
screen_x=100.0,
|
|
screen_y=200.0,
|
|
dpi=96,
|
|
zoom=1.0
|
|
)
|
|
|
|
large_x, large_y = 10000.0, 20000.0
|
|
|
|
screen_x, screen_y = renderer.page_to_screen(large_x, large_y)
|
|
page_x, page_y = renderer.screen_to_page(screen_x, screen_y)
|
|
|
|
assert abs(page_x - large_x) < 0.001
|
|
assert abs(page_y - large_y) < 0.001
|