Coverage for pyPhotoAlbum/page_renderer.py: 90%
41 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
1"""
2Page renderer helper for pyPhotoAlbum
4This module provides a unified coordinate system for rendering pages and their elements.
5All coordinate transformations are centralized here to ensure consistency.
7Coordinate Systems:
8- Page-local: Coordinates in millimeters relative to the page's top-left corner
9- Pixel: Coordinates in pixels at working DPI
10- Screen: Coordinates on screen after applying zoom and pan
11"""
13from typing import Tuple, Optional
14from pyPhotoAlbum.gl_imports import glPushMatrix, glPopMatrix, glScalef, glTranslatef
17class PageRenderer:
18 """
19 Handles rendering and coordinate transformations for a single page.
21 This class encapsulates all coordinate transformations needed to render
22 a page and its elements consistently.
23 """
25 def __init__(
26 self, page_width_mm: float, page_height_mm: float, screen_x: float, screen_y: float, dpi: int, zoom: float
27 ):
28 """
29 Initialize a page renderer.
31 Args:
32 page_width_mm: Page width in millimeters
33 page_height_mm: Page height in millimeters
34 screen_x: X position on screen where page should be rendered
35 screen_y: Y position on screen where page should be rendered
36 dpi: Working DPI for converting mm to pixels
37 zoom: Current zoom level
38 """
39 self.page_width_mm = page_width_mm
40 self.page_height_mm = page_height_mm
41 self.screen_x = screen_x
42 self.screen_y = screen_y
43 self.dpi = dpi
44 self.zoom = zoom
46 # Calculate page dimensions in pixels
47 self.page_width_px = page_width_mm * dpi / 25.4
48 self.page_height_px = page_height_mm * dpi / 25.4
50 # Calculate screen dimensions (with zoom applied)
51 self.screen_width = self.page_width_px * zoom
52 self.screen_height = self.page_height_px * zoom
54 def page_to_screen(self, page_x: float, page_y: float) -> Tuple[float, float]:
55 """
56 Convert page-local coordinates (in pixels) to screen coordinates.
58 Args:
59 page_x: X coordinate in page-local space (pixels)
60 page_y: Y coordinate in page-local space (pixels)
62 Returns:
63 Tuple of (screen_x, screen_y)
64 """
65 screen_x = self.screen_x + page_x * self.zoom
66 screen_y = self.screen_y + page_y * self.zoom
67 return (screen_x, screen_y)
69 def screen_to_page(self, screen_x: float, screen_y: float) -> Tuple[float, float]:
70 """
71 Convert screen coordinates to page-local coordinates (in pixels).
73 Args:
74 screen_x: X coordinate in screen space
75 screen_y: Y coordinate in screen space
77 Returns:
78 Tuple of (page_x, page_y) in pixels, or None if outside page bounds
79 """
80 page_x = (screen_x - self.screen_x) / self.zoom
81 page_y = (screen_y - self.screen_y) / self.zoom
82 return (page_x, page_y)
84 def is_point_in_page(self, screen_x: float, screen_y: float) -> bool:
85 """
86 Check if a screen coordinate is within the page bounds.
88 Args:
89 screen_x: X coordinate in screen space
90 screen_y: Y coordinate in screen space
92 Returns:
93 True if the point is within the page bounds
94 """
95 return (
96 self.screen_x <= screen_x <= self.screen_x + self.screen_width
97 and self.screen_y <= screen_y <= self.screen_y + self.screen_height
98 )
100 def get_sub_page_at(self, screen_x: float, is_facing_page: bool) -> Optional[str]:
101 """
102 For facing page spreads, determine if mouse is on left or right page.
104 Args:
105 screen_x: X coordinate in screen space
106 is_facing_page: Whether this is a facing page spread
108 Returns:
109 'left' or 'right' for facing pages, None for single pages
110 """
111 if not is_facing_page:
112 return None
114 # Calculate the center line of the spread
115 center_x = self.screen_x + self.screen_width / 2
117 if screen_x < center_x:
118 return "left"
119 else:
120 return "right"
122 def begin_render(self):
123 """
124 Set up OpenGL transformations for rendering this page.
125 Call this before rendering page content.
126 """
127 glPushMatrix()
128 # Apply zoom
129 glScalef(self.zoom, self.zoom, 1.0)
130 # Translate to page position (in zoomed coordinates)
131 glTranslatef(self.screen_x / self.zoom, self.screen_y / self.zoom, 0.0)
133 def end_render(self):
134 """
135 Clean up OpenGL transformations after rendering this page.
136 Call this after rendering page content.
137 """
138 glPopMatrix()
140 def get_page_bounds_screen(self) -> Tuple[float, float, float, float]:
141 """
142 Get the page bounds in screen coordinates.
144 Returns:
145 Tuple of (x, y, width, height) in screen space
146 """
147 return (self.screen_x, self.screen_y, self.screen_width, self.screen_height)
149 def get_page_bounds_page(self) -> Tuple[float, float, float, float]:
150 """
151 Get the page bounds in page-local coordinates.
153 Returns:
154 Tuple of (x, y, width, height) in page-local space (pixels)
155 """
156 return (0, 0, self.page_width_px, self.page_height_px)