"""Widget de visualisation PDF avec pypdfium2.""" from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSpinBox, QScrollArea, QSizePolicy) from PyQt6.QtGui import QImage, QPixmap from PyQt6.QtCore import Qt, pyqtSignal import pypdfium2 as pdfium class PdfViewer(QWidget): """Widget d'apercu PDF avec navigation et zoom.""" page_changed = pyqtSignal(int, int) # page_num, total_pages def __init__(self, parent=None): super().__init__(parent) self._pdf = None self._current_page = 0 self._total_pages = 0 self._zoom = 1.0 self._setup_ui() def _setup_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) # Zone d'affichage avec scroll self._scroll = QScrollArea() self._scroll.setWidgetResizable(True) self._scroll.setAlignment(Qt.AlignmentFlag.AlignCenter) self._image_label = QLabel() self._image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._image_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored) self._scroll.setWidget(self._image_label) layout.addWidget(self._scroll, 1) # Barre de navigation nav = QHBoxLayout() self._btn_prev = QPushButton("<") self._btn_prev.setFixedWidth(40) self._btn_prev.clicked.connect(self._prev_page) nav.addWidget(self._btn_prev) self._page_spin = QSpinBox() self._page_spin.setMinimum(1) self._page_spin.setMaximum(1) self._page_spin.valueChanged.connect(self._go_to_page) nav.addWidget(self._page_spin) self._label_total = QLabel("/ 0") nav.addWidget(self._label_total) self._btn_next = QPushButton(">") self._btn_next.setFixedWidth(40) self._btn_next.clicked.connect(self._next_page) nav.addWidget(self._btn_next) nav.addSpacing(20) self._btn_zoom_out = QPushButton("-") self._btn_zoom_out.setFixedWidth(30) self._btn_zoom_out.clicked.connect(self._zoom_out) nav.addWidget(self._btn_zoom_out) self._label_zoom = QLabel("100%") self._label_zoom.setFixedWidth(45) self._label_zoom.setAlignment(Qt.AlignmentFlag.AlignCenter) nav.addWidget(self._label_zoom) self._btn_zoom_in = QPushButton("+") self._btn_zoom_in.setFixedWidth(30) self._btn_zoom_in.clicked.connect(self._zoom_in) nav.addWidget(self._btn_zoom_in) self._btn_fit = QPushButton("Ajuster") self._btn_fit.clicked.connect(self._zoom_fit) nav.addWidget(self._btn_fit) nav.addStretch() layout.addLayout(nav) def load_pdf(self, path): """Charger un fichier PDF.""" try: self._pdf = pdfium.PdfDocument(path) self._total_pages = len(self._pdf) self._current_page = 0 self._page_spin.setMaximum(self._total_pages) self._page_spin.setValue(1) self._label_total.setText(f"/ {self._total_pages}") self._render_page() except Exception as e: self._image_label.setText(f"Erreur: {e}") def load_pdf_bytes(self, data): """Charger un PDF depuis des bytes.""" try: self._pdf = pdfium.PdfDocument(data) self._total_pages = len(self._pdf) self._current_page = 0 self._page_spin.setMaximum(self._total_pages) self._page_spin.setValue(1) self._label_total.setText(f"/ {self._total_pages}") self._render_page() except Exception as e: self._image_label.setText(f"Erreur: {e}") def _render_page(self): """Afficher la page courante.""" if not self._pdf or self._current_page >= self._total_pages: return page = self._pdf[self._current_page] # Rendu a 150 DPI * zoom scale = 150 / 72 * self._zoom bitmap = page.render(scale=scale) pil_image = bitmap.to_pil() # Convertir PIL -> QImage -> QPixmap data = pil_image.tobytes("raw", "RGBA") qimage = QImage(data, pil_image.width, pil_image.height, QImage.Format.Format_RGBA8888) pixmap = QPixmap.fromImage(qimage) self._image_label.setPixmap(pixmap) self._image_label.adjustSize() self._label_zoom.setText(f"{int(self._zoom * 100)}%") self.page_changed.emit(self._current_page + 1, self._total_pages) def _prev_page(self): if self._current_page > 0: self._current_page -= 1 self._page_spin.setValue(self._current_page + 1) def _next_page(self): if self._current_page < self._total_pages - 1: self._current_page += 1 self._page_spin.setValue(self._current_page + 1) def _go_to_page(self, value): page = value - 1 if 0 <= page < self._total_pages and page != self._current_page: self._current_page = page self._render_page() def _zoom_in(self): self._zoom = min(self._zoom * 1.25, 5.0) self._render_page() def _zoom_out(self): self._zoom = max(self._zoom / 1.25, 0.25) self._render_page() def _zoom_fit(self): """Ajuster le zoom pour que la page tienne dans la zone visible.""" if not self._pdf or self._current_page >= self._total_pages: return page = self._pdf[self._current_page] pw, ph = page.get_size() vw = self._scroll.viewport().width() - 20 vh = self._scroll.viewport().height() - 20 scale_x = vw / pw scale_y = vh / ph self._zoom = min(scale_x, scale_y) * (72 / 150) self._render_page() def refresh(self): """Rafraichir l'affichage.""" self._render_page() @property def current_page(self): return self._current_page @property def total_pages(self): return self._total_pages