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

1""" 

2Page renderer helper for pyPhotoAlbum 

3 

4This module provides a unified coordinate system for rendering pages and their elements. 

5All coordinate transformations are centralized here to ensure consistency. 

6 

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""" 

12 

13from typing import Tuple, Optional 

14from pyPhotoAlbum.gl_imports import glPushMatrix, glPopMatrix, glScalef, glTranslatef 

15 

16 

17class PageRenderer: 

18 """ 

19 Handles rendering and coordinate transformations for a single page. 

20 

21 This class encapsulates all coordinate transformations needed to render 

22 a page and its elements consistently. 

23 """ 

24 

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. 

30 

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 

45 

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 

49 

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 

53 

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. 

57 

58 Args: 

59 page_x: X coordinate in page-local space (pixels) 

60 page_y: Y coordinate in page-local space (pixels) 

61 

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) 

68 

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). 

72 

73 Args: 

74 screen_x: X coordinate in screen space 

75 screen_y: Y coordinate in screen space 

76 

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) 

83 

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. 

87 

88 Args: 

89 screen_x: X coordinate in screen space 

90 screen_y: Y coordinate in screen space 

91 

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 ) 

99 

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. 

103 

104 Args: 

105 screen_x: X coordinate in screen space 

106 is_facing_page: Whether this is a facing page spread 

107 

108 Returns: 

109 'left' or 'right' for facing pages, None for single pages 

110 """ 

111 if not is_facing_page: 

112 return None 

113 

114 # Calculate the center line of the spread 

115 center_x = self.screen_x + self.screen_width / 2 

116 

117 if screen_x < center_x: 

118 return "left" 

119 else: 

120 return "right" 

121 

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) 

132 

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() 

139 

140 def get_page_bounds_screen(self) -> Tuple[float, float, float, float]: 

141 """ 

142 Get the page bounds in screen coordinates. 

143 

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) 

148 

149 def get_page_bounds_page(self) -> Tuple[float, float, float, float]: 

150 """ 

151 Get the page bounds in page-local coordinates. 

152 

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)