pyPhotoAlbum/pyPhotoAlbum/page_renderer.py
Duncan Tourolle f6ed11b0bc
All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
black formatting
2025-11-27 23:07:16 +01:00

157 lines
5.2 KiB
Python

"""
Page renderer helper for pyPhotoAlbum
This module provides a unified coordinate system for rendering pages and their elements.
All coordinate transformations are centralized here to ensure consistency.
Coordinate Systems:
- Page-local: Coordinates in millimeters relative to the page's top-left corner
- Pixel: Coordinates in pixels at working DPI
- Screen: Coordinates on screen after applying zoom and pan
"""
from typing import Tuple, Optional
from pyPhotoAlbum.gl_imports import glPushMatrix, glPopMatrix, glScalef, glTranslatef
class PageRenderer:
"""
Handles rendering and coordinate transformations for a single page.
This class encapsulates all coordinate transformations needed to render
a page and its elements consistently.
"""
def __init__(
self, page_width_mm: float, page_height_mm: float, screen_x: float, screen_y: float, dpi: int, zoom: float
):
"""
Initialize a page renderer.
Args:
page_width_mm: Page width in millimeters
page_height_mm: Page height in millimeters
screen_x: X position on screen where page should be rendered
screen_y: Y position on screen where page should be rendered
dpi: Working DPI for converting mm to pixels
zoom: Current zoom level
"""
self.page_width_mm = page_width_mm
self.page_height_mm = page_height_mm
self.screen_x = screen_x
self.screen_y = screen_y
self.dpi = dpi
self.zoom = zoom
# Calculate page dimensions in pixels
self.page_width_px = page_width_mm * dpi / 25.4
self.page_height_px = page_height_mm * dpi / 25.4
# Calculate screen dimensions (with zoom applied)
self.screen_width = self.page_width_px * zoom
self.screen_height = self.page_height_px * zoom
def page_to_screen(self, page_x: float, page_y: float) -> Tuple[float, float]:
"""
Convert page-local coordinates (in pixels) to screen coordinates.
Args:
page_x: X coordinate in page-local space (pixels)
page_y: Y coordinate in page-local space (pixels)
Returns:
Tuple of (screen_x, screen_y)
"""
screen_x = self.screen_x + page_x * self.zoom
screen_y = self.screen_y + page_y * self.zoom
return (screen_x, screen_y)
def screen_to_page(self, screen_x: float, screen_y: float) -> Tuple[float, float]:
"""
Convert screen coordinates to page-local coordinates (in pixels).
Args:
screen_x: X coordinate in screen space
screen_y: Y coordinate in screen space
Returns:
Tuple of (page_x, page_y) in pixels, or None if outside page bounds
"""
page_x = (screen_x - self.screen_x) / self.zoom
page_y = (screen_y - self.screen_y) / self.zoom
return (page_x, page_y)
def is_point_in_page(self, screen_x: float, screen_y: float) -> bool:
"""
Check if a screen coordinate is within the page bounds.
Args:
screen_x: X coordinate in screen space
screen_y: Y coordinate in screen space
Returns:
True if the point is within the page bounds
"""
return (
self.screen_x <= screen_x <= self.screen_x + self.screen_width
and self.screen_y <= screen_y <= self.screen_y + self.screen_height
)
def get_sub_page_at(self, screen_x: float, is_facing_page: bool) -> Optional[str]:
"""
For facing page spreads, determine if mouse is on left or right page.
Args:
screen_x: X coordinate in screen space
is_facing_page: Whether this is a facing page spread
Returns:
'left' or 'right' for facing pages, None for single pages
"""
if not is_facing_page:
return None
# Calculate the center line of the spread
center_x = self.screen_x + self.screen_width / 2
if screen_x < center_x:
return "left"
else:
return "right"
def begin_render(self):
"""
Set up OpenGL transformations for rendering this page.
Call this before rendering page content.
"""
glPushMatrix()
# Apply zoom
glScalef(self.zoom, self.zoom, 1.0)
# Translate to page position (in zoomed coordinates)
glTranslatef(self.screen_x / self.zoom, self.screen_y / self.zoom, 0.0)
def end_render(self):
"""
Clean up OpenGL transformations after rendering this page.
Call this after rendering page content.
"""
glPopMatrix()
def get_page_bounds_screen(self) -> Tuple[float, float, float, float]:
"""
Get the page bounds in screen coordinates.
Returns:
Tuple of (x, y, width, height) in screen space
"""
return (self.screen_x, self.screen_y, self.screen_width, self.screen_height)
def get_page_bounds_page(self) -> Tuple[float, float, float, float]:
"""
Get the page bounds in page-local coordinates.
Returns:
Tuple of (x, y, width, height) in page-local space (pixels)
"""
return (0, 0, self.page_width_px, self.page_height_px)