V2.3.1 — Recadrage avec selection visuelle

- Bouton "Selectionner sur l'apercu" dans le dialogue Recadrer
- Tracer un rectangle a la souris directement sur le PDF
- Zone sombre en dehors de la selection, cadre bleu en pointilles
- Les marges se mettent a jour automatiquement depuis la selection
- Les marges saisies manuellement affichent le cadre sur l'apercu
- Synchronisation bidirectionnelle selection <-> valeurs
- Nettoyage auto du mode selection a la fermeture du dialogue

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-21 14:19:44 +01:00
parent e7a27018e5
commit 1f8f6bb89d
5 changed files with 263 additions and 35 deletions

View File

@@ -1,14 +1,15 @@
"""Dialogue Recadrer les pages.""" """Dialogue Recadrer les pages — avec selection visuelle."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QDoubleSpinBox, QComboBox, QRadioButton, QGroupBox, QDoubleSpinBox, QComboBox, QRadioButton,
QDialogButtonBox, QLineEdit) QDialogButtonBox, QLineEdit, QPushButton)
from PyQt6.QtCore import Qt
from app.engine.pdf_utils import mm_to_pt, pt_to_mm from app.engine.pdf_utils import mm_to_pt, pt_to_mm
class CropDialog(QDialog): class CropDialog(QDialog):
def __init__(self, page_width_pt=595, page_height_pt=842, total_pages=1, def __init__(self, page_width_pt=595, page_height_pt=842, total_pages=1,
current_page=0, parent=None): current_page=0, viewer=None, parent=None):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("Recadrer") self.setWindowTitle("Recadrer")
self.setMinimumWidth(450) self.setMinimumWidth(450)
@@ -16,19 +17,40 @@ class CropDialog(QDialog):
self._page_h = page_height_pt self._page_h = page_height_pt
self._total_pages = total_pages self._total_pages = total_pages
self._current_page = current_page self._current_page = current_page
self._viewer = viewer
self._setup_ui() self._setup_ui()
self._update_preview() self._update_preview()
def _setup_ui(self): def _setup_ui(self):
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
# Info page actuelle # Info page
pw_mm = pt_to_mm(self._page_w) pw_mm = pt_to_mm(self._page_w)
ph_mm = pt_to_mm(self._page_h) ph_mm = pt_to_mm(self._page_h)
info = QLabel(f"Page actuelle : {pw_mm:.1f} x {ph_mm:.1f} mm") info = QLabel(f"Page actuelle : {pw_mm:.1f} x {ph_mm:.1f} mm")
info.setStyleSheet("color: #888; font-size: 11px;") info.setStyleSheet("color: #888; font-size: 11px;")
layout.addWidget(info) layout.addWidget(info)
# Bouton selection visuelle
if self._viewer:
sel_layout = QHBoxLayout()
self.btn_select = QPushButton("Selectionner sur l'apercu")
self.btn_select.setCheckable(True)
self.btn_select.setStyleSheet("""
QPushButton { background: #0066cc; color: white; padding: 8px 16px;
border-radius: 4px; font-weight: bold; }
QPushButton:checked { background: #cc3300; }
QPushButton:hover { background: #0055aa; }
QPushButton:checked:hover { background: #aa2200; }
""")
self.btn_select.toggled.connect(self._toggle_select_mode)
sel_layout.addWidget(self.btn_select)
sel_layout.addStretch()
layout.addLayout(sel_layout)
# Connecter les signaux du viewer
self._viewer.selection_changed.connect(self._on_selection_changed)
# Mode # Mode
grp_mode = QGroupBox("Mode de recadrage") grp_mode = QGroupBox("Mode de recadrage")
ml = QVBoxLayout(grp_mode) ml = QVBoxLayout(grp_mode)
@@ -38,14 +60,14 @@ class CropDialog(QDialog):
ml.addWidget(self.rb_margins) ml.addWidget(self.rb_margins)
ml.addWidget(self.rb_size) ml.addWidget(self.rb_size)
layout.addWidget(grp_mode) layout.addWidget(grp_mode)
self.rb_margins.toggled.connect(self._on_mode_changed) self.rb_margins.toggled.connect(self._on_mode_changed)
self.rb_size.toggled.connect(self._on_mode_changed) self.rb_size.toggled.connect(self._on_mode_changed)
# Marges de recadrage # Marges
self.grp_margins_input = QGroupBox("Marges a retirer (mm)") self.grp_margins_input = QGroupBox("Marges a retirer (mm)")
mgl = QVBoxLayout(self.grp_margins_input) mgl = QVBoxLayout(self.grp_margins_input)
self.margin_spins = {} self.margin_spins = {}
row1 = QHBoxLayout() row1 = QHBoxLayout()
row1.addStretch() row1.addStretch()
row1.addWidget(QLabel("Haut :")) row1.addWidget(QLabel("Haut :"))
@@ -53,6 +75,7 @@ class CropDialog(QDialog):
self.margin_spins["haut"].setRange(0, 500) self.margin_spins["haut"].setRange(0, 500)
self.margin_spins["haut"].setSuffix(" mm") self.margin_spins["haut"].setSuffix(" mm")
self.margin_spins["haut"].valueChanged.connect(self._update_preview) self.margin_spins["haut"].valueChanged.connect(self._update_preview)
self.margin_spins["haut"].valueChanged.connect(self._show_selection_from_margins)
row1.addWidget(self.margin_spins["haut"]) row1.addWidget(self.margin_spins["haut"])
row1.addStretch() row1.addStretch()
mgl.addLayout(row1) mgl.addLayout(row1)
@@ -63,6 +86,7 @@ class CropDialog(QDialog):
self.margin_spins["gauche"].setRange(0, 500) self.margin_spins["gauche"].setRange(0, 500)
self.margin_spins["gauche"].setSuffix(" mm") self.margin_spins["gauche"].setSuffix(" mm")
self.margin_spins["gauche"].valueChanged.connect(self._update_preview) self.margin_spins["gauche"].valueChanged.connect(self._update_preview)
self.margin_spins["gauche"].valueChanged.connect(self._show_selection_from_margins)
row2.addWidget(self.margin_spins["gauche"]) row2.addWidget(self.margin_spins["gauche"])
row2.addStretch() row2.addStretch()
row2.addWidget(QLabel("Droite :")) row2.addWidget(QLabel("Droite :"))
@@ -70,6 +94,7 @@ class CropDialog(QDialog):
self.margin_spins["droite"].setRange(0, 500) self.margin_spins["droite"].setRange(0, 500)
self.margin_spins["droite"].setSuffix(" mm") self.margin_spins["droite"].setSuffix(" mm")
self.margin_spins["droite"].valueChanged.connect(self._update_preview) self.margin_spins["droite"].valueChanged.connect(self._update_preview)
self.margin_spins["droite"].valueChanged.connect(self._show_selection_from_margins)
row2.addWidget(self.margin_spins["droite"]) row2.addWidget(self.margin_spins["droite"])
mgl.addLayout(row2) mgl.addLayout(row2)
@@ -80,6 +105,7 @@ class CropDialog(QDialog):
self.margin_spins["bas"].setRange(0, 500) self.margin_spins["bas"].setRange(0, 500)
self.margin_spins["bas"].setSuffix(" mm") self.margin_spins["bas"].setSuffix(" mm")
self.margin_spins["bas"].valueChanged.connect(self._update_preview) self.margin_spins["bas"].valueChanged.connect(self._update_preview)
self.margin_spins["bas"].valueChanged.connect(self._show_selection_from_margins)
row3.addWidget(self.margin_spins["bas"]) row3.addWidget(self.margin_spins["bas"])
row3.addStretch() row3.addStretch()
mgl.addLayout(row3) mgl.addLayout(row3)
@@ -105,7 +131,7 @@ class CropDialog(QDialog):
layout.addWidget(self.grp_size_input) layout.addWidget(self.grp_size_input)
self.grp_size_input.setVisible(False) self.grp_size_input.setVisible(False)
# Apercu dimension finale # Apercu
self.lbl_preview = QLabel() self.lbl_preview = QLabel()
self.lbl_preview.setStyleSheet("font-weight: bold; font-size: 13px; color: #0066cc; padding: 8px;") self.lbl_preview.setStyleSheet("font-weight: bold; font-size: 13px; color: #0066cc; padding: 8px;")
layout.addWidget(self.lbl_preview) layout.addWidget(self.lbl_preview)
@@ -123,7 +149,6 @@ class CropDialog(QDialog):
al.addWidget(self.rb_all) al.addWidget(self.rb_all)
al.addWidget(self.rb_even) al.addWidget(self.rb_even)
al.addWidget(self.rb_odd) al.addWidget(self.rb_odd)
range_layout = QHBoxLayout() range_layout = QHBoxLayout()
range_layout.addWidget(self.rb_range) range_layout.addWidget(self.rb_range)
self.edit_range = QLineEdit() self.edit_range = QLineEdit()
@@ -132,7 +157,6 @@ class CropDialog(QDialog):
range_layout.addWidget(self.edit_range) range_layout.addWidget(self.edit_range)
al.addLayout(range_layout) al.addLayout(range_layout)
layout.addWidget(grp_apply) layout.addWidget(grp_apply)
self.rb_range.toggled.connect(self.edit_range.setEnabled) self.rb_range.toggled.connect(self.edit_range.setEnabled)
# Boutons # Boutons
@@ -143,6 +167,54 @@ class CropDialog(QDialog):
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
layout.addWidget(buttons) layout.addWidget(buttons)
def _toggle_select_mode(self, checked):
if self._viewer:
self._viewer.set_select_mode(checked)
if checked:
self.btn_select.setText("Cliquez et tracez sur l'apercu...")
else:
self.btn_select.setText("Selectionner sur l'apercu")
def _on_selection_changed(self, left, top, right, bottom):
"""Recoit les ratios de selection du viewer et met a jour les marges."""
if not self.rb_margins.isChecked():
return
pw_mm = pt_to_mm(self._page_w)
ph_mm = pt_to_mm(self._page_h)
# Les ratios representent la zone SELECTIONNEE (a garder)
# Les marges sont ce qu'on ENLEVE
margin_left = left * pw_mm
margin_right = (1 - right) * pw_mm
margin_top = top * ph_mm
margin_bottom = (1 - bottom) * ph_mm
# Mettre a jour sans declencher les signaux en boucle
for spin in self.margin_spins.values():
spin.blockSignals(True)
self.margin_spins["gauche"].setValue(margin_left)
self.margin_spins["droite"].setValue(margin_right)
self.margin_spins["haut"].setValue(margin_top)
self.margin_spins["bas"].setValue(margin_bottom)
for spin in self.margin_spins.values():
spin.blockSignals(False)
self._update_preview()
def _show_selection_from_margins(self):
"""Afficher le rectangle de selection sur le viewer a partir des marges."""
if not self._viewer or not self.rb_margins.isChecked():
return
pw_mm = pt_to_mm(self._page_w)
ph_mm = pt_to_mm(self._page_h)
left = self.margin_spins["gauche"].value() / pw_mm if pw_mm > 0 else 0
top = self.margin_spins["haut"].value() / ph_mm if ph_mm > 0 else 0
right = 1 - (self.margin_spins["droite"].value() / pw_mm) if pw_mm > 0 else 1
bottom = 1 - (self.margin_spins["bas"].value() / ph_mm) if ph_mm > 0 else 1
self._viewer.show_selection(left, top, right, bottom)
def _on_mode_changed(self): def _on_mode_changed(self):
self.grp_margins_input.setVisible(self.rb_margins.isChecked()) self.grp_margins_input.setVisible(self.rb_margins.isChecked())
self.grp_size_input.setVisible(self.rb_size.isChecked()) self.grp_size_input.setVisible(self.rb_size.isChecked())
@@ -168,7 +240,6 @@ class CropDialog(QDialog):
self.lbl_preview.setStyleSheet("font-weight: bold; font-size: 13px; color: #0066cc; padding: 8px;") self.lbl_preview.setStyleSheet("font-weight: bold; font-size: 13px; color: #0066cc; padding: 8px;")
def _parse_range(self): def _parse_range(self):
"""Parser la plage de pages. Retourne une liste d'indices (0-based)."""
text = self.edit_range.text().strip() text = self.edit_range.text().strip()
if not text: if not text:
return [] return []
@@ -192,16 +263,30 @@ class CropDialog(QDialog):
pass pass
return sorted(set(indices)) return sorted(set(indices))
def closeEvent(self, event):
"""Nettoyer le mode selection a la fermeture."""
if self._viewer:
self._viewer.set_select_mode(False)
self._viewer.clear_selection()
super().closeEvent(event)
def reject(self):
if self._viewer:
self._viewer.set_select_mode(False)
self._viewer.clear_selection()
super().reject()
def get_settings(self): def get_settings(self):
# Mode if self._viewer:
self._viewer.set_select_mode(False)
self._viewer.clear_selection()
if self.rb_margins.isChecked(): if self.rb_margins.isChecked():
mode = "margins"
crop_top = mm_to_pt(self.margin_spins["haut"].value()) crop_top = mm_to_pt(self.margin_spins["haut"].value())
crop_bottom = mm_to_pt(self.margin_spins["bas"].value()) crop_bottom = mm_to_pt(self.margin_spins["bas"].value())
crop_left = mm_to_pt(self.margin_spins["gauche"].value()) crop_left = mm_to_pt(self.margin_spins["gauche"].value())
crop_right = mm_to_pt(self.margin_spins["droite"].value()) crop_right = mm_to_pt(self.margin_spins["droite"].value())
else: else:
mode = "size"
final_w = mm_to_pt(self.spin_final_w.value()) final_w = mm_to_pt(self.spin_final_w.value())
final_h = mm_to_pt(self.spin_final_h.value()) final_h = mm_to_pt(self.spin_final_h.value())
crop_left = (self._page_w - final_w) / 2 crop_left = (self._page_w - final_w) / 2
@@ -209,7 +294,6 @@ class CropDialog(QDialog):
crop_top = (self._page_h - final_h) / 2 crop_top = (self._page_h - final_h) / 2
crop_bottom = crop_top crop_bottom = crop_top
# Pages cibles
if self.rb_current.isChecked(): if self.rb_current.isChecked():
pages = [self._current_page] pages = [self._current_page]
elif self.rb_all.isChecked(): elif self.rb_all.isChecked():

View File

@@ -610,6 +610,7 @@ class MainWindow(QMainWindow):
page_width_pt=w, page_height_pt=h, page_width_pt=w, page_height_pt=h,
total_pages=len(reader.pages), total_pages=len(reader.pages),
current_page=self._viewer.current_page, current_page=self._viewer.current_page,
viewer=self._viewer,
parent=self parent=self
) )
if dlg.exec(): if dlg.exec():

View File

@@ -1,16 +1,148 @@
"""Widget de visualisation PDF avec pypdfium2 — V2.""" """Widget de visualisation PDF avec pypdfium2 — V2.3.1 avec selection."""
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QLabel,
QScrollArea, QSizePolicy, QApplication) QScrollArea, QSizePolicy, QApplication)
from PyQt6.QtGui import QImage, QPixmap, QWheelEvent from PyQt6.QtGui import (QImage, QPixmap, QWheelEvent, QPainter, QPen, QColor,
from PyQt6.QtCore import Qt, pyqtSignal, QTimer QMouseEvent, QCursor)
from PyQt6.QtCore import Qt, pyqtSignal, QRect, QPoint
import pypdfium2 as pdfium import pypdfium2 as pdfium
class PdfViewer(QWidget): class SelectableImageLabel(QLabel):
"""Widget d'apercu PDF avec zoom molette et navigation fluide.""" """QLabel avec possibilite de dessiner un rectangle de selection."""
page_changed = pyqtSignal(int, int) # page_num (0-based), total_pages selection_changed = pyqtSignal(float, float, float, float) # left, top, right, bottom en ratio (0-1)
selection_done = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._selecting = False
self._select_mode = False
self._start = QPoint()
self._end = QPoint()
self._rect = QRect()
self._has_selection = False
self._img_rect = QRect() # zone de l'image dans le label
def set_select_mode(self, enabled):
self._select_mode = enabled
self.setCursor(QCursor(Qt.CursorShape.CrossCursor if enabled else Qt.CursorShape.ArrowCursor))
if not enabled:
self._has_selection = False
self.update()
def clear_selection(self):
self._has_selection = False
self.update()
def set_selection_from_margins(self, left_ratio, top_ratio, right_ratio, bottom_ratio):
"""Afficher un rectangle de selection a partir de ratios (0-1)."""
if not self.pixmap():
return
pm = self.pixmap()
img_w, img_h = pm.width(), pm.height()
# Calculer la position de l'image dans le label (centree)
lbl_w, lbl_h = self.width(), self.height()
off_x = max(0, (lbl_w - img_w) // 2)
off_y = max(0, (lbl_h - img_h) // 2)
x1 = off_x + int(left_ratio * img_w)
y1 = off_y + int(top_ratio * img_h)
x2 = off_x + int(right_ratio * img_w)
y2 = off_y + int(bottom_ratio * img_h)
self._rect = QRect(QPoint(x1, y1), QPoint(x2, y2))
self._has_selection = True
self.update()
def mousePressEvent(self, event: QMouseEvent):
if self._select_mode and event.button() == Qt.MouseButton.LeftButton:
self._selecting = True
self._start = event.pos()
self._end = event.pos()
self._rect = QRect(self._start, self._end).normalized()
self._has_selection = True
self.update()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QMouseEvent):
if self._selecting:
self._end = event.pos()
self._rect = QRect(self._start, self._end).normalized()
self.update()
self._emit_ratios()
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent):
if self._selecting:
self._selecting = False
self._end = event.pos()
self._rect = QRect(self._start, self._end).normalized()
self.update()
self._emit_ratios()
self.selection_done.emit()
else:
super().mouseReleaseEvent(event)
def _emit_ratios(self):
if not self.pixmap():
return
pm = self.pixmap()
img_w, img_h = pm.width(), pm.height()
lbl_w, lbl_h = self.width(), self.height()
off_x = max(0, (lbl_w - img_w) // 2)
off_y = max(0, (lbl_h - img_h) // 2)
left = max(0, (self._rect.left() - off_x)) / img_w
top = max(0, (self._rect.top() - off_y)) / img_h
right = min(1, (self._rect.right() - off_x)) / img_w
bottom = min(1, (self._rect.bottom() - off_y)) / img_h
left = max(0, min(1, left))
top = max(0, min(1, top))
right = max(0, min(1, right))
bottom = max(0, min(1, bottom))
self.selection_changed.emit(left, top, right, bottom)
def paintEvent(self, event):
super().paintEvent(event)
if self._has_selection and not self._rect.isNull():
painter = QPainter(self)
# Zone sombre en dehors de la selection
painter.setBrush(QColor(0, 0, 0, 100))
painter.setPen(Qt.PenStyle.NoPen)
full = self.rect()
r = self._rect.normalized()
# Haut
painter.drawRect(full.left(), full.top(), full.width(), r.top() - full.top())
# Bas
painter.drawRect(full.left(), r.bottom(), full.width(), full.bottom() - r.bottom())
# Gauche
painter.drawRect(full.left(), r.top(), r.left() - full.left(), r.height())
# Droite
painter.drawRect(r.right(), r.top(), full.right() - r.right(), r.height())
# Cadre de selection
pen = QPen(QColor(0, 120, 255), 2, Qt.PenStyle.DashLine)
painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.drawRect(r)
# Dimensions en mm (affichees dans le coin)
painter.setPen(QColor(0, 120, 255))
painter.drawText(r.left() + 4, r.top() - 4,
f"{r.width()}x{r.height()} px")
painter.end()
class PdfViewer(QWidget):
"""Widget d'apercu PDF avec zoom molette, navigation et selection."""
page_changed = pyqtSignal(int, int)
selection_changed = pyqtSignal(float, float, float, float) # ratios
selection_done = pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@@ -19,6 +151,8 @@ class PdfViewer(QWidget):
self._current_page = 0 self._current_page = 0
self._total_pages = 0 self._total_pages = 0
self._zoom = 1.0 self._zoom = 1.0
self._page_width_pt = 0
self._page_height_pt = 0
self._setup_ui() self._setup_ui()
def _setup_ui(self): def _setup_ui(self):
@@ -31,19 +165,27 @@ class PdfViewer(QWidget):
self._scroll.setAlignment(Qt.AlignmentFlag.AlignCenter) self._scroll.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._scroll.setStyleSheet("QScrollArea { background: #1e1e1e; border: none; }") self._scroll.setStyleSheet("QScrollArea { background: #1e1e1e; border: none; }")
self._image_label = QLabel() self._image_label = SelectableImageLabel()
self._image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._image_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored) self._image_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
self._image_label.setStyleSheet("background: #1e1e1e;") self._image_label.setStyleSheet("background: #1e1e1e;")
self._image_label.selection_changed.connect(self.selection_changed.emit)
self._image_label.selection_done.connect(self.selection_done.emit)
self._scroll.setWidget(self._image_label) self._scroll.setWidget(self._image_label)
# Intercepter la molette sur le scroll area
self._scroll.wheelEvent = self._on_wheel self._scroll.wheelEvent = self._on_wheel
layout.addWidget(self._scroll, 1) layout.addWidget(self._scroll, 1)
def set_select_mode(self, enabled):
self._image_label.set_select_mode(enabled)
def clear_selection(self):
self._image_label.clear_selection()
def show_selection(self, left_ratio, top_ratio, right_ratio, bottom_ratio):
self._image_label.set_selection_from_margins(left_ratio, top_ratio, right_ratio, bottom_ratio)
def _on_wheel(self, event: QWheelEvent): def _on_wheel(self, event: QWheelEvent):
"""Molette = pages, Ctrl+Molette = zoom."""
if event.modifiers() & Qt.KeyboardModifier.ControlModifier: if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
delta = event.angleDelta().y() delta = event.angleDelta().y()
if delta > 0: if delta > 0:
@@ -59,7 +201,6 @@ class PdfViewer(QWidget):
self.prev_page() self.prev_page()
def load_pdf(self, path): def load_pdf(self, path):
"""Charger un fichier PDF."""
try: try:
self._pdf_path = path self._pdf_path = path
self._pdf = pdfium.PdfDocument(path) self._pdf = pdfium.PdfDocument(path)
@@ -71,7 +212,6 @@ class PdfViewer(QWidget):
self._image_label.setText(f"Erreur: {e}") self._image_label.setText(f"Erreur: {e}")
def load_pdf_bytes(self, data): def load_pdf_bytes(self, data):
"""Charger un PDF depuis des bytes."""
try: try:
self._pdf = pdfium.PdfDocument(data) self._pdf = pdfium.PdfDocument(data)
self._total_pages = len(self._pdf) self._total_pages = len(self._pdf)
@@ -82,25 +222,21 @@ class PdfViewer(QWidget):
self._image_label.setText(f"Erreur: {e}") self._image_label.setText(f"Erreur: {e}")
def _render_page(self): def _render_page(self):
"""Afficher la page courante."""
if not self._pdf or self._current_page >= self._total_pages: if not self._pdf or self._current_page >= self._total_pages:
return return
page = self._pdf[self._current_page] page = self._pdf[self._current_page]
self._page_width_pt, self._page_height_pt = page.get_size()
scale = 150 / 72 * self._zoom scale = 150 / 72 * self._zoom
bitmap = page.render(scale=scale) bitmap = page.render(scale=scale)
pil_image = bitmap.to_pil() pil_image = bitmap.to_pil()
data = pil_image.tobytes("raw", "RGBA") data = pil_image.tobytes("raw", "RGBA")
qimage = QImage(data, pil_image.width, pil_image.height, QImage.Format.Format_RGBA8888) qimage = QImage(data, pil_image.width, pil_image.height, QImage.Format.Format_RGBA8888)
pixmap = QPixmap.fromImage(qimage) pixmap = QPixmap.fromImage(qimage)
self._image_label.setPixmap(pixmap) self._image_label.setPixmap(pixmap)
self._image_label.adjustSize() self._image_label.adjustSize()
self.page_changed.emit(self._current_page, self._total_pages) self.page_changed.emit(self._current_page, self._total_pages)
def _zoom_fit(self): 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: if not self._pdf or self._current_page >= self._total_pages:
return return
page = self._pdf[self._current_page] page = self._pdf[self._current_page]
@@ -145,7 +281,6 @@ class PdfViewer(QWidget):
self._render_page() self._render_page()
def get_page_thumbnail(self, page_index, width=120): def get_page_thumbnail(self, page_index, width=120):
"""Generer une miniature pour le panneau de vignettes."""
if not self._pdf or page_index >= self._total_pages: if not self._pdf or page_index >= self._total_pages:
return None return None
page = self._pdf[page_index] page = self._pdf[page_index]
@@ -172,3 +307,11 @@ class PdfViewer(QWidget):
@property @property
def pdf_path(self): def pdf_path(self):
return self._pdf_path return self._pdf_path
@property
def page_width_pt(self):
return self._page_width_pt
@property
def page_height_pt(self):
return self._page_height_pt

View File

@@ -14,7 +14,7 @@ REPO = "jules/copydev-imposing-tool"
BRANCH = "master" BRANCH = "master"
# Version actuelle (incrementee a chaque release) # Version actuelle (incrementee a chaque release)
VERSION = "2.3.0" VERSION = "2.3.1"
def get_remote_version(): def get_remote_version():

View File

@@ -1 +1 @@
2.3.0 2.3.1