report_printer.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. """
  2. Report Printer
  3. Handles printing of reports to physical printers using QPrinter.
  4. """
  5. from typing import Dict, List, Tuple
  6. from PyQt5.QtPrintSupport import QPrinter
  7. from PyQt5.QtCore import Qt
  8. from PyQt5.QtGui import QFont, QPixmap, QPainter
  9. from PyQt5.QtWidgets import QMessageBox
  10. from reportlab.lib.units import inch
  11. class ReportPrinter:
  12. """
  13. Handles report printing to physical printers.
  14. """
  15. def print_report(
  16. self,
  17. parent_widget,
  18. report_content: Dict,
  19. visualizations: List[Tuple[str, QPixmap]],
  20. include_visualizations: bool = True
  21. ) -> bool:
  22. """
  23. Print report to default printer.
  24. Args:
  25. parent_widget: Parent widget for error dialogs
  26. report_content: Dictionary with report text content
  27. visualizations: List of (title, QPixmap) tuples
  28. include_visualizations: Whether to include visualizations
  29. Returns:
  30. bool: True if print succeeded, False otherwise
  31. """
  32. try:
  33. # Create QPrinter with default printer
  34. printer = QPrinter(QPrinter.HighResolution)
  35. # Create painter and render report
  36. painter = QPainter()
  37. painter.begin(printer)
  38. # Get page rectangle
  39. page_rect = printer.pageRect(QPrinter.DevicePixel)
  40. # Render report content
  41. self._render_report_to_painter(painter, page_rect, report_content, include_visualizations)
  42. # Add visualizations if selected
  43. if include_visualizations and visualizations:
  44. printer.newPage()
  45. margin = 0.5 * 72 # 0.5 inch in pixels
  46. x_pos = int(margin)
  47. y_pos = int(margin)
  48. max_height = int(page_rect.height() - 2 * margin)
  49. max_width = int(page_rect.width() - 2 * margin)
  50. title_font = QFont("Arial", 12, QFont.Bold)
  51. painter.setFont(title_font)
  52. painter.drawText(x_pos, y_pos, max_width, 30,
  53. Qt.AlignLeft, "Visualizations")
  54. y_pos += 40
  55. for viz_title, viz_pixmap in visualizations:
  56. # Check if we need a new page
  57. if y_pos + 250 > page_rect.height():
  58. printer.newPage()
  59. y_pos = int(margin)
  60. # Draw title
  61. painter.drawText(x_pos, y_pos, max_width, 25,
  62. Qt.AlignLeft, viz_title)
  63. y_pos += 30
  64. # Scale and draw image
  65. pixmap = viz_pixmap
  66. if pixmap.width() > max_width:
  67. aspect = pixmap.height() / pixmap.width()
  68. pixmap = pixmap.scaledToWidth(max_width, Qt.SmoothTransformation)
  69. pixmap_height = int(pixmap.width() * aspect)
  70. if pixmap_height > max_height / 2:
  71. pixmap = pixmap.scaledToHeight(int(max_height / 2), Qt.SmoothTransformation)
  72. painter.drawPixmap(x_pos, y_pos, pixmap)
  73. y_pos += pixmap.height() + 20
  74. painter.end()
  75. QMessageBox.information(
  76. parent_widget,
  77. "Print Sent",
  78. "Report sent to default printer successfully."
  79. )
  80. return True
  81. except Exception as e:
  82. QMessageBox.critical(
  83. parent_widget,
  84. "Print Error",
  85. f"Error printing report:\n{str(e)}"
  86. )
  87. return False
  88. def _render_report_to_painter(
  89. self,
  90. painter: QPainter,
  91. page_rect,
  92. report_content: Dict,
  93. include_visualizations: bool = True
  94. ) -> int:
  95. """
  96. Render report content to a QPainter (for printing).
  97. Args:
  98. painter: QPainter to render to
  99. page_rect: Rectangle defining the printable area
  100. report_content: Dictionary with report text content
  101. include_visualizations: Whether to include images
  102. Returns:
  103. Number of pages needed
  104. """
  105. margin = 0.5 * inch
  106. content_width = page_rect.width() - 2 * margin
  107. y_pos = margin
  108. line_height = 20
  109. # Font setup
  110. title_font = QFont("Arial", 16, QFont.Bold)
  111. heading_font = QFont("Arial", 12, QFont.Bold)
  112. normal_font = QFont("Arial", 10)
  113. # Title
  114. painter.setFont(title_font)
  115. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  116. Qt.AlignLeft, "Durian Analysis Report")
  117. y_pos += line_height + 10
  118. # Report Info
  119. painter.setFont(heading_font)
  120. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  121. Qt.AlignLeft, "Report Information")
  122. y_pos += line_height
  123. painter.setFont(normal_font)
  124. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  125. Qt.AlignLeft, f"Report ID: {report_content['report_id']}")
  126. y_pos += line_height
  127. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  128. Qt.AlignLeft, f"Generated: {report_content['generated']}")
  129. y_pos += line_height + 10
  130. # Analysis Results
  131. painter.setFont(heading_font)
  132. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  133. Qt.AlignLeft, "Analysis Results")
  134. y_pos += line_height
  135. painter.setFont(normal_font)
  136. results = report_content['results']
  137. if results.get('locule_count') is not None:
  138. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  139. Qt.AlignLeft, f"Locule Count: {results['locule_count']} locules")
  140. y_pos += line_height
  141. if results.get('defect_status'):
  142. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  143. Qt.AlignLeft, f"Defect Status: {results['defect_status']} ({results.get('total_detections', 0)} detections)")
  144. y_pos += line_height
  145. if results.get('shape_class'):
  146. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  147. Qt.AlignLeft, f"Shape: {results['shape_class']} ({results.get('shape_confidence', 0)*100:.1f}%)")
  148. y_pos += line_height
  149. if results.get('maturity_class'):
  150. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  151. Qt.AlignLeft, f"Maturity: {results['maturity_class']} ({results.get('maturity_confidence', 0)*100:.1f}%)")
  152. y_pos += line_height
  153. if results.get('ripeness_class'):
  154. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  155. Qt.AlignLeft, f"Ripeness: {results['ripeness_class']} ({results.get('ripeness_confidence', 0)*100:.1f}%)")
  156. y_pos += line_height
  157. # Grade
  158. painter.setFont(heading_font)
  159. y_pos += 10
  160. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  161. Qt.AlignLeft, f"Overall Grade: Class {report_content['grade']}")
  162. y_pos += line_height
  163. painter.setFont(normal_font)
  164. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height * 2),
  165. Qt.AlignLeft | Qt.TextWordWrap, report_content['grade_description'])
  166. y_pos += line_height * 2 + 10
  167. # Visualizations
  168. if include_visualizations:
  169. painter.setFont(heading_font)
  170. painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height),
  171. Qt.AlignLeft, "Visualizations")
  172. y_pos += line_height + 10
  173. return 1