跟尴 发表于 2025-5-31 23:30:23

一个PDF合并器

效果


直接上代码

from PySide6.QtWidgets import (    QGridLayout,    QGroupBox,    QHBoxLayout,    QVBoxLayout,    QApplication,    QWidget,    QLabel,    QScrollArea,)from PySide6.QtCore import Signalfrom PySide6.QtCore import Qt, QSize, Signal,QMimeData, QPoint, QRect, QUrlfrom PySide6.QtGui import QPixmap, QPainter, QImage, QDragimport subprocessimport pathlibimport fitz #pip install PyMuPdfimport weakrefimport qdarkthemeclass PDFInfo:    def __init__(self, path: pathlib.Path):      self.path: pathlib.Path = path      self.pixmap: QPixmap = None# type: ignore      self.page_count: int = 0    def load_pixmap(self):      if not self.pixmap:            doc = fitz.open(self.path)            self.page_count = len(doc)            page = doc.load_page(0)            pm = page.get_pixmap()# type: ignore            imgfmt = (                QImage.Format.Format_RGBA8888                if pm.alpha                else QImage.Format.Format_RGB888            )            self.pixmap = QPixmap.fromImage(                QImage(pm.samples, pm.width, pm.height, pm.stride, imgfmt)            )      return self.pixmap    def word(self, ind: int):      return f"{ind}: 共{self.page_count}页"class card(QGroupBox):    doubleClicked = Signal(int)    cardDropped = Signal(int, int)# 参数1:源card索引, 参数2:目标card索引    def __init__(self, parent):      super().__init__(parent)      self.pp = weakref.ref(parent)      self.pdf_info: PDFInfo = None# type: ignore      self.setAcceptDrops(True)    def supportedDropActions(self):      return Qt.DropAction.CopyAction | Qt.DropAction.MoveAction    def setui(self):      self.lb = QLabel()      self.lb.setScaledContents(True)      hbox = QHBoxLayout()      self.setLayout(hbox)      hbox.addWidget(self.lb)      self.setFixedSize(QSize(200, 160))      return self    def setdata(self, pdf_info: PDFInfo):      self.pdf_info = pdf_info      if self.pdf_info.page_count > 1:            self.setProperty("muti", True)      else:            self.setProperty("muti", False)      # self.setTitle(f"{pdf_info.path.stem}: 共{pdf_info.page_count}页")    def mouseDoubleClickEvent(self, event):      if self.pdf_info:            self.doubleClicked.emit(self.pdf_info.path)      super().mouseDoubleClickEvent(event)    def mousePressEvent(self, event):      self.dragStartPosition = event.position().toPoint()      super().mousePressEvent(event)    def mime_encode(self):      mime = QMimeData()      # 设置自定义格式的拖动数据(传入当前card在box中的索引)      main_window = self.pp()      if isinstance(main_window, box):            card_index = main_window.cards.index(self)            mime.setData("application/x-item", str(card_index).encode("utf-8"))      return mime    def mime_decode(self, mime: QMimeData):      if mime.hasFormat("application/x-item"):            return int(mime.data("application/x-item").data())      else:            return None    def mouseMoveEvent(self, event):      if not (event.buttons() & Qt.MouseButton.LeftButton):            return      offset = (event.position().toPoint() - self.dragStartPosition).manhattanLength()      if offset < QApplication.startDragDistance():            # 记录当前card的索引            mime = self.mime_encode()            drag = QDrag(self)            drag.setMimeData(mime)            drag.exec(Qt.DropAction.MoveAction)            # 设置拖动预览图像            pixmap = QPixmap(self.size())            self.render(pixmap)            drag.setPixmap(pixmap)            drag.setHotSpot(event.position().toPoint())            drag.exec()    def dragEnterEvent(self, event):      mime = event.mimeData()      if mime.hasFormat("application/x-item"):            event.acceptProposedAction()    def dropEvent(self, event):      s = self.mime_decode(event.mimeData())      if s is not None:            parent = self.pp()            if parent and isinstance(parent, box):                target_index = parent.cards.index(self)                if s != target_index and s != target_index - 1:                  self.cardDropped.emit(s, target_index)      event.accept()    def paintEvent(self, event):      super().paintEvent(event)      if not self.pdf_info:            return      painter = QPainter(self)      painter.setRenderHint(QPainter.RenderHint.Antialiasing)      # 设置字体      font = painter.font()      font.setPointSize(8)      painter.setFont(font)      # 设置文本颜色      cl = painter.background().color()      painter.setPen(cl)      painter.setBrush(cl)      # 获取文本内容      text = self.pdf_info.path.name      # 计算文本绘制位置      text_rect = painter.fontMetrics().boundingRect(text)      if text_rect.width() > self.width() - 20:            text = text[:18] + "..."            text_rect = painter.fontMetrics().boundingRect(text)      x = (self.width() - text_rect.width()) // 2      y = self.height() - 5      d = 3      r2 = QRect(            x - d, y - 10 - d, text_rect.width() + 2 * d, text_rect.height() + 2 * d      )      painter.drawRect(r2)      painter.setPen(Qt.GlobalColor.black)      painter.setBrush(Qt.BrushStyle.NoBrush)      # 绘制文本      painter.drawText(x, y, text)class EndLabel(QLabel):    cardDropped = Signal(int)    def __init__(self, text, parent=None):      super().__init__(text, parent)      self.setAlignment(Qt.AlignmentFlag.AlignCenter)      self.setStyleSheet("background-color: lightgray; border: 1px solid gray;")      self.setFixedSize(200, 150)      self.setAcceptDrops(True)    def dragEnterEvent(self, event):      mime = event.mimeData()      if mime.hasFormat("application/x-item"):            event.acceptProposedAction()    def dropEvent(self, event):      mime = event.mimeData()      if mime.hasFormat("application/x-item"):            source_index = int(mime.data("application/x-item").data())            self.cardDropped.emit(source_index)      event.accept()class c1(QLabel):    sg_dropped = Signal(list)    def __init__(self, text, parent=None):      super().__init__(text, parent)      self.setFixedSize(200, 100)      self.setProperty("level", "funcCard1")      self.setAcceptDrops(True)      self.setAlignment(Qt.AlignmentFlag.AlignCenter)    def dragEnterEvent(self, event):      if event.mimeData().hasUrls():            event.acceptProposedAction()    def dropEvent(self, event):      urls = event.mimeData().urls()      if urls:            ret = []            for file in urls:                file_path = file.toLocalFile()                if file_path.lower().endswith(".pdf"):                  ret.append(pathlib.Path(file_path).absolute())            if ret:                self.sg_dropped.emit(ret)      event.accept()class c2(QLabel):    sg_dropped = Signal(int)    def __init__(self, text, parent=None):      super().__init__(text, parent)      self.setFixedSize(200, 100)      self.setProperty("level", "funcCard2")      self.setAcceptDrops(True)      self.setAlignment(Qt.AlignmentFlag.AlignCenter)    def dragEnterEvent(self, event):      mime = event.mimeData()      if mime.hasFormat("application/x-item"):            event.acceptProposedAction()    def dropEvent(self, event):      mime = event.mimeData()      if mime.hasFormat("application/x-item"):            source_index = int(mime.data("application/x-item").data())            self.sg_dropped.emit(source_index)      event.accept()class c3(QLabel):    sg_dropped = Signal(int)    def __init__(self, text, parent=None):      super().__init__(text, parent)      self.setFixedSize(200, 100)      self.setProperty("level", "funcCard3")      self.setAcceptDrops(True)      self.setAlignment(Qt.AlignmentFlag.AlignCenter)      self.temp_pdf = None      self.dragStartPosition = QPoint()    def mousePressEvent(self, event):      if event.button() == Qt.MouseButton.LeftButton:            self.dragStartPosition = event.position().toPoint()      super().mousePressEvent(event)    def mouseMoveEvent(self, event):      if not (event.buttons() & Qt.MouseButton.LeftButton):            return      # 检查拖动距离是否足够      if (            event.position().toPoint() - self.dragStartPosition      ).manhattanLength() < QApplication.startDragDistance():            return      try:            # 合并所有PDF文件            output_path = pathlib.Path("temp.pdf").absolute()            doc = fitz.open()            parent = self.parent()            if isinstance(parent, mainw) and parent.w1.cards:                for card in parent.w1.cards:                  if hasattr(card, "pdf_info"):                        try:                            src_doc = fitz.open(card.pdf_info.path)                            doc.insert_pdf(src_doc)                            src_doc.close()                        except Exception as e:                            print(f"Error processing {card.pdf_info.path}: {e}")                if len(doc) > 0:                  doc.save(output_path)                  print(f"PDF合并完成,保存到: {output_path}")                  doc.close()                  self.temp_pdf = output_path                  # 创建拖拽操作                  drag = QDrag(self)                  mime = QMimeData()                  urls =                   print(urls)                  mime.setUrls(urls)                  drag.setMimeData(mime)                  # 设置拖拽预览图像                  pixmap = QPixmap(self.size())                  self.render(pixmap)                  drag.setPixmap(pixmap)                  drag.setHotSpot(event.position().toPoint())                  # 执行拖拽操作                  result = drag.exec(                        Qt.DropAction.CopyAction | Qt.DropAction.MoveAction                  )                  print(f"拖拽操作结果: {result}")                else:                  print("没有可合并的PDF文件")                  doc.close()      except Exception as e:            print(f"拖拽操作出错: {e}")class box(QScrollArea):    def __init__(self, parent=None):      super().__init__(parent)      # self.setAcceptDrops(True)      self.cards: list = []      # 创建容器widget和布局      self.container = QWidget()      self.grid = QGridLayout(self.container)      self.container.setLayout(self.grid)      # 设置滚动区域属性      self.setWidgetResizable(True)      self.setWidget(self.container)      self.setMinimumSize(450, 350)# 设置最小窗口尺寸      # 添加始终显示在末尾的特殊EndLabel      self.end_label = EndLabel("拖到此处移动到最后", self.container)      self.end_label.cardDropped.connect(self.move_card_to_end)    def setui(self):      # self.load_pdfs(r"C:\Users\Administrator\Desktop\ZJ\5.6")      return self    def load_pdfs(self, folder_path):      pp = pathlib.Path(folder_path).absolute()      for i, pdf_file in enumerate(pp.glob("*.pdf")):            self.add_pdf_item(pdf_file, i)    def add_pdf_item(self, pdf_path, pos):      pdf_info = PDFInfo(pdf_path)      pdf_info.load_pixmap()      cd = card(self).setui()      cd.setdata(pdf_info)      cd.lb.setPixmap(pdf_info.load_pixmap())      cd.doubleClicked.connect(self.on_item_double_clicked)      cd.cardDropped.connect(self.move_card)      row = pos // (self.width() // 200)      col = pos % (self.width() // 200)      self.grid.addWidget(cd, row, col)      self.cards.append(cd)    def resizeEvent(self, event):      self._resize()      super().resizeEvent(event)    def _resize(self):      self.rearrange_items()      # 计算并设置高度: card高度*(行数+2)      cols = max(1, self.width() // 200)      rows = (len(self.cards) + cols - 1) // cols + 2# 行数+2      self.container.setFixedHeight(150 * rows)    def rearrange_items(self):      cols = max(1, self.width() // 200)      # 清除布局      while self.grid.count():            item = self.grid.takeAt(0)            if item.widget():                item.widget().setParent(None)      # 添加所有普通card      cd: card      for i, cd in enumerate(self.cards):            row = i // cols            col = i % cols            self.grid.addWidget(cd, row, col)            cd.setTitle(cd.pdf_info.word(i + 1))      # 添加end_label到最后一行第一列      total_items = len(self.cards)      rows = (total_items + cols - 1) // cols      self.grid.addWidget(self.end_label, rows, 0)    def makepixmap(self, p: pathlib.Path):      pone = fitz.open(p).load_page(0)      pm = pone.get_pixmap()# type: ignore      imgfmt = (            QImage.Format.Format_RGBA8888 if pm.alpha else QImage.Format.Format_RGB888      )      pimg = QImage(pm.samples, pm.width, pm.height, pm.stride, imgfmt)      return QPixmap.fromImage(pimg)    def move_card_to_end(self, source_index):      if source_index != -1:            source_card = self.cards.pop(source_index)            self.cards.append(source_card)            self.rearrange_items()    def move_card(self, source_index, target_index):      if source_index == target_index:            return      # 从原位置移除源card      source_card = self.cards.pop(source_index)      # 在目标位置插入源card      if source_index < target_index:            target_index -= 1      self.cards.insert(target_index, source_card)      # 重新排列布局      self.rearrange_items()    def on_item_double_clicked(self, index):      print(f"Item {index} double clicked")      pinfo:PDFInfo=self.cards.pdf_info      cmd=['explorer',str(pinfo.path.absolute())]      subprocess.Popen(cmd)class mainw(QWidget):    def __init__(self):      super().__init__()      self.setWindowTitle("ZLPDF-PDF合并器")      ly = QVBoxLayout()      self.w1 = box().setui()      ly.addWidget(self.w1)      hb = QHBoxLayout()      self.c1 = c1("把要合并的PDF拖到我这里", self)      self.c2 = c2("把要去掉的PDF拖动到我这里", self)      self.c3 = c3("把合并后的文件从我这里拖走", self)      for i in :            hb.addWidget(i)      ly.addLayout(hb)      self.setLayout(ly)      self.c1.sg_dropped.connect(self.addpdfs)      self.c2.sg_dropped.connect(self.remove_pdf)    def addpdfs(self, ll: list):      for pdf in ll:            self.w1.add_pdf_item(pdf, len(self.w1.cards))      self.w1._resize()    def remove_pdf(self, index: int):      """移除指定索引的PDF卡片"""      if 0
页: [1]
查看完整版本: 一个PDF合并器