""" Report Printer Handles printing of reports to physical printers using QPrinter. """ from typing import Dict, List, Tuple from PyQt5.QtPrintSupport import QPrinter from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QPixmap, QPainter from PyQt5.QtWidgets import QMessageBox from reportlab.lib.units import inch class ReportPrinter: """ Handles report printing to physical printers. """ def print_report( self, parent_widget, report_content: Dict, visualizations: List[Tuple[str, QPixmap]], include_visualizations: bool = True ) -> bool: """ Print report to default printer. Args: parent_widget: Parent widget for error dialogs report_content: Dictionary with report text content visualizations: List of (title, QPixmap) tuples include_visualizations: Whether to include visualizations Returns: bool: True if print succeeded, False otherwise """ try: # Create QPrinter with default printer printer = QPrinter(QPrinter.HighResolution) # Create painter and render report painter = QPainter() painter.begin(printer) # Get page rectangle page_rect = printer.pageRect(QPrinter.DevicePixel) # Render report content self._render_report_to_painter(painter, page_rect, report_content, include_visualizations) # Add visualizations if selected if include_visualizations and visualizations: printer.newPage() margin = 0.5 * 72 # 0.5 inch in pixels x_pos = int(margin) y_pos = int(margin) max_height = int(page_rect.height() - 2 * margin) max_width = int(page_rect.width() - 2 * margin) title_font = QFont("Arial", 12, QFont.Bold) painter.setFont(title_font) painter.drawText(x_pos, y_pos, max_width, 30, Qt.AlignLeft, "Visualizations") y_pos += 40 for viz_title, viz_pixmap in visualizations: # Check if we need a new page if y_pos + 250 > page_rect.height(): printer.newPage() y_pos = int(margin) # Draw title painter.drawText(x_pos, y_pos, max_width, 25, Qt.AlignLeft, viz_title) y_pos += 30 # Scale and draw image pixmap = viz_pixmap if pixmap.width() > max_width: aspect = pixmap.height() / pixmap.width() pixmap = pixmap.scaledToWidth(max_width, Qt.SmoothTransformation) pixmap_height = int(pixmap.width() * aspect) if pixmap_height > max_height / 2: pixmap = pixmap.scaledToHeight(int(max_height / 2), Qt.SmoothTransformation) painter.drawPixmap(x_pos, y_pos, pixmap) y_pos += pixmap.height() + 20 painter.end() QMessageBox.information( parent_widget, "Print Sent", "Report sent to default printer successfully." ) return True except Exception as e: QMessageBox.critical( parent_widget, "Print Error", f"Error printing report:\n{str(e)}" ) return False def _render_report_to_painter( self, painter: QPainter, page_rect, report_content: Dict, include_visualizations: bool = True ) -> int: """ Render report content to a QPainter (for printing). Args: painter: QPainter to render to page_rect: Rectangle defining the printable area report_content: Dictionary with report text content include_visualizations: Whether to include images Returns: Number of pages needed """ margin = 0.5 * inch content_width = page_rect.width() - 2 * margin y_pos = margin line_height = 20 # Font setup title_font = QFont("Arial", 16, QFont.Bold) heading_font = QFont("Arial", 12, QFont.Bold) normal_font = QFont("Arial", 10) # Title painter.setFont(title_font) painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, "Durian Analysis Report") y_pos += line_height + 10 # Report Info painter.setFont(heading_font) painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, "Report Information") y_pos += line_height painter.setFont(normal_font) painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Report ID: {report_content['report_id']}") y_pos += line_height painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Generated: {report_content['generated']}") y_pos += line_height + 10 # Analysis Results painter.setFont(heading_font) painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, "Analysis Results") y_pos += line_height painter.setFont(normal_font) results = report_content['results'] if results.get('locule_count') is not None: painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Locule Count: {results['locule_count']} locules") y_pos += line_height if results.get('defect_status'): painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Defect Status: {results['defect_status']} ({results.get('total_detections', 0)} detections)") y_pos += line_height if results.get('shape_class'): painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Shape: {results['shape_class']} ({results.get('shape_confidence', 0)*100:.1f}%)") y_pos += line_height if results.get('maturity_class'): painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Maturity: {results['maturity_class']} ({results.get('maturity_confidence', 0)*100:.1f}%)") y_pos += line_height if results.get('ripeness_class'): painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Ripeness: {results['ripeness_class']} ({results.get('ripeness_confidence', 0)*100:.1f}%)") y_pos += line_height # Grade painter.setFont(heading_font) y_pos += 10 painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, f"Overall Grade: Class {report_content['grade']}") y_pos += line_height painter.setFont(normal_font) painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height * 2), Qt.AlignLeft | Qt.TextWordWrap, report_content['grade_description']) y_pos += line_height * 2 + 10 # Visualizations if include_visualizations: painter.setFont(heading_font) painter.drawText(int(margin), int(y_pos), int(content_width), int(line_height), Qt.AlignLeft, "Visualizations") y_pos += line_height + 10 return 1