最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Python開(kāi)發(fā)一個(gè)桌面版PDF蓋章工具

 更新時(shí)間:2025年12月18日 09:56:01   作者:A0_張張  
在數(shù)字化辦公中,經(jīng)常需要在PDF文件上加蓋電子印章,今天我將分享一個(gè)使用Python開(kāi)發(fā)的桌面版PDF蓋章工具,支持可視化操作和精準(zhǔn)定位,這個(gè)工具基于PyQt5和PyMuPDF庫(kù),提供了友好的圖形界面,需要的朋友可以參考下

一、項(xiàng)目概述

這個(gè)工具的主要功能包括:

  • 打開(kāi)和預(yù)覽PDF文件
  • 加載印章圖片(支持PNG、JPG等格式)
  • 在PDF頁(yè)面上精確點(diǎn)擊添加印章
  • 調(diào)整印章大小
  • 撤銷(xiāo)操作、清空頁(yè)面印章
  • 保存蓋章后的PDF文件

二、環(huán)境準(zhǔn)備

首先需要安裝必要的Python庫(kù):

pip install PyQt5 PyMuPDF pillow

注意:PyMuPDF是fitz模塊的庫(kù),通過(guò)pip install PyMuPDF安裝。

三、核心代碼解析

1. PDF預(yù)覽編輯畫(huà)布類(lèi)

class PdfEditPreview(QLabel):
    # 信號(hào):點(diǎn)擊坐標(biāo) (x, y)
    clicked_pos = pyqtSignal(float, float)

    def __init__(self):
        super().__init__()
        self.setAlignment(Qt.AlignCenter)
        self.setStyleSheet('background: #e0e0e0; color: #888;')
        self.setText("請(qǐng)打開(kāi) PDF 文件")
        
        self.doc = None  # PDF文檔對(duì)象
        self.page_index = 0  # 當(dāng)前頁(yè)碼
        self.current_pixmap = None  # 當(dāng)前頁(yè)面圖像
        self.stamps_to_draw = []  # 當(dāng)前頁(yè)印章列表

這個(gè)類(lèi)繼承自QLabel,負(fù)責(zé)顯示PDF頁(yè)面并處理用戶(hù)點(diǎn)擊。關(guān)鍵點(diǎn):

  1. 坐標(biāo)轉(zhuǎn)換:將屏幕點(diǎn)擊坐標(biāo)轉(zhuǎn)換為PDF點(diǎn)坐標(biāo)(1點(diǎn)=1/72英寸)
  2. 自適應(yīng)高度:根據(jù)PDF頁(yè)面比例自動(dòng)調(diào)整顯示高度
  3. 印章疊加:在PDF頁(yè)面上繪制已添加的印章

2. 印章添加邏輯

def add_stamp(self, pdf_x, pdf_y):
    if not self.doc or self.seal_bytes is None:
        QMessageBox.warning(self, "提示", "請(qǐng)先加載 PDF 和 印章圖片!")
        return
        
    scale = self.spin_scale.value() / 100.0
    
    # 計(jì)算印章在PDF中的實(shí)際尺寸
    actual_w = self.seal_w_pt * scale
    actual_h = actual_w * self.seal_ratio
    
    stamp_record = {
        'page': self.current_page,
        'x': pdf_x,      # PDF坐標(biāo)X
        'y': pdf_y,      # PDF坐標(biāo)Y
        'w_pt': actual_w, # 印章寬度(點(diǎn))
        'h_pt': actual_h, # 印章高度(點(diǎn))
        'img_bytes': self.seal_bytes, # 印章圖片原始數(shù)據(jù)
        'pixmap': self.seal_pixmap    # 用于顯示的QPixmap
    }
    
    self.stamps_list.append(stamp_record)
    self.refresh_preview()

3. 滾動(dòng)預(yù)覽的修復(fù)

原版本存在預(yù)覽區(qū)域底部被裁剪的問(wèn)題,修復(fù)方案:

# 創(chuàng)建滾動(dòng)區(qū)域
self.scroll_area = QScrollArea()
self.scroll_area.setWidget(self.preview)
self.scroll_area.setWidgetResizable(True)  # 關(guān)鍵:讓預(yù)覽組件寬度跟隨滾動(dòng)區(qū)
self.scroll_area.setStyleSheet("border: 2px dashed #aaa;")

# 在預(yù)覽類(lèi)中實(shí)現(xiàn)自適應(yīng)高度
def adjust_height(self):
    """根據(jù)當(dāng)前控件寬度和圖片比例,自動(dòng)調(diào)整控件高度"""
    if not self.current_pixmap or self.img_w_raw == 0:
        return
        
    # 計(jì)算寬高比
    ratio = self.img_h_raw / self.img_w_raw
    # 目標(biāo)高度 = 當(dāng)前寬度 * 比例
    target_height = int(self.width() * ratio)
    
    # 設(shè)置最小高度,這樣ScrollArea就會(huì)出現(xiàn)滾動(dòng)條
    if self.minimumHeight() != target_height:
        self.setMinimumHeight(target_height)

4. 保存PDF文件

def save_pdf(self):
    if not self.doc: return
    if not self.stamps_list:
        QMessageBox.information(self, "提示", "當(dāng)前沒(méi)有蓋任何章,不需要保存。")
        return
        
    save_path, _ = QFileDialog.getSaveFileName(
        self, "保存文件", 
        self.pdf_path.replace(".pdf", "_stamped.pdf"), 
        "PDF Files (*.pdf)"
    )
    
    for stamp in self.stamps_list:
        page = self.doc[stamp['page']]
        # 計(jì)算印章在PDF中的位置
        rect_x0 = stamp['x'] - stamp['w_pt'] / 2
        rect_y0 = stamp['y'] - stamp['h_pt'] / 2
        rect_x1 = stamp['x'] + stamp['w_pt'] / 2
        rect_y1 = stamp['y'] + stamp['h_pt'] / 2
        rect = fitz.Rect(rect_x0, rect_y0, rect_x1, rect_y1)
        
        # 將印章插入PDF
        page.insert_image(rect, stream=stamp['img_bytes'])

四、界面布局設(shè)計(jì)

工具界面分為三個(gè)主要區(qū)域:

+----------------+-------------------------------+
|  左側(cè)工具欄    |         PDF預(yù)覽區(qū)            |
|                |  (帶滾動(dòng)條,支持長(zhǎng)頁(yè)面)      |
+----------------+-------------------------------+
|                |        頁(yè)面導(dǎo)航條           |
+----------------+-------------------------------+

左側(cè)工具欄包含:

  1. 文件操作區(qū)域:打開(kāi)PDF文件
  2. 印章設(shè)置區(qū)域:選擇印章圖片、調(diào)整大小
  3. 編輯控制區(qū)域:撤銷(xiāo)、清空操作
  4. 保存按鈕

五、使用步驟

  1. 打開(kāi)PDF文件:點(diǎn)擊"打開(kāi)PDF"按鈕選擇文件
  2. 加載印章:點(diǎn)擊"選擇印章"按鈕,支持PNG、JPG等格式
  3. 調(diào)整印章大小:通過(guò)縮放比例旋鈕調(diào)整(5%-500%)
  4. 添加印章:在PDF預(yù)覽區(qū)域點(diǎn)擊鼠標(biāo)左鍵
  5. 編輯操作:可撤銷(xiāo)最后一個(gè)印章或清空當(dāng)前頁(yè)
  6. 保存文件:點(diǎn)擊"另存為PDF"保存蓋章后的文件

六、技術(shù)要點(diǎn)

  1. 坐標(biāo)系統(tǒng)轉(zhuǎn)換:需要在屏幕像素坐標(biāo)、PDF點(diǎn)坐標(biāo)和圖像顯示坐標(biāo)之間進(jìn)行精確轉(zhuǎn)換
  2. 圖像處理:使用Pillow處理印章圖片的透明通道
  3. 內(nèi)存管理:及時(shí)關(guān)閉PDF文檔,避免內(nèi)存泄漏
  4. 用戶(hù)體驗(yàn):添加撤銷(xiāo)功能、實(shí)時(shí)預(yù)覽和錯(cuò)誤提示

七、完整代碼

以下是完整的Python代碼:

import sys
import os
import fitz  # PyMuPDF
from io import BytesIO 
from PIL import Image

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QGridLayout, QPushButton, QLabel, QFileDialog, QMessageBox,
    QSpinBox, QGroupBox, QLineEdit, QSplitter, QScrollArea
)
from PyQt5.QtCore import Qt, pyqtSignal, QRect
from PyQt5.QtGui import QPixmap, QImage, QPainter, QPen, QColor, QIcon

# ===========================
# PDF 預(yù)覽/編輯畫(huà)布
# ===========================
class PdfEditPreview(QLabel):
    # 信號(hào):點(diǎn)擊坐標(biāo) (x, y)
    clicked_pos = pyqtSignal(float, float)

    def __init__(self):
        super().__init__()
        self.setAlignment(Qt.AlignCenter)
        # 去掉固定的 border 樣式,改由 ScrollArea 管理外觀,這里只保留背景
        self.setStyleSheet('background: #e0e0e0; color: #888;')
        self.setText("請(qǐng)打開(kāi) PDF 文件")
        
        self.doc = None
        self.page_index = 0
        self.current_pixmap = None
        
        # 存儲(chǔ)當(dāng)前頁(yè)面的已蓋章列表 (由主窗口傳入)
        self.stamps_to_draw = []
        
        # 坐標(biāo)轉(zhuǎn)換參數(shù)
        self.img_w_raw = 0.0
        self.img_h_raw = 0.0

    def load_page(self, doc, index):
        self.doc = doc
        self.page_index = index
        self.render()

    def render(self):
        if not self.doc: return
        
        page = self.doc[self.page_index]
        
        # 獲取 120 DPI 的圖像用于顯示
        pix = page.get_pixmap(dpi=120) 
        self.img_w_raw = pix.width
        self.img_h_raw = pix.height

        img = QImage(pix.samples, self.img_w_raw, self.img_h_raw, pix.stride, QImage.Format_RGB888)
        self.current_pixmap = QPixmap.fromImage(img)
        
        # 加載新圖片后,立即調(diào)整控件高度以適應(yīng)比例
        self.adjust_height()
        self.update() 

    def adjust_height(self):
        """根據(jù)當(dāng)前控件寬度和圖片比例,自動(dòng)調(diào)整控件高度,確保底部不被裁剪"""
        if not self.current_pixmap or self.img_w_raw == 0:
            return
            
        # 計(jì)算寬高比
        ratio = self.img_h_raw / self.img_w_raw
        # 目標(biāo)高度 = 當(dāng)前寬度 * 比例
        target_height = int(self.width() * ratio)
        
        # 設(shè)置最小高度,這樣 ScrollArea 就會(huì)出現(xiàn)滾動(dòng)條
        if self.minimumHeight() != target_height:
            self.setMinimumHeight(target_height)

    def resizeEvent(self, event):
        """當(dāng)窗口大小改變(寬度改變)時(shí),重新計(jì)算高度"""
        super().resizeEvent(event)
        self.adjust_height()

    def set_stamps(self, stamps_list):
        """接收主窗口傳來(lái)的當(dāng)前頁(yè)蓋章數(shù)據(jù),用于重繪"""
        self.stamps_to_draw = stamps_list
        self.update()

    def mousePressEvent(self, e):
        if not self.doc or not self.current_pixmap: return
        
        # 計(jì)算圖片在 Label 中的顯示區(qū)域(居中)
        scaled_pix = self.current_pixmap.scaledToWidth(self.width(), Qt.SmoothTransformation)
        img_x_start = (self.width() - scaled_pix.width()) / 2
        img_y_start = (self.height() - scaled_pix.height()) / 2
        
        # 獲取相對(duì)于圖片的點(diǎn)擊坐標(biāo)
        click_x = e.x() - img_x_start
        click_y = e.y() - img_y_start
        
        # 檢查是否點(diǎn)擊在圖片范圍內(nèi)
        if click_x < 0 or click_x > scaled_pix.width() or \
           click_y < 0 or click_y > scaled_pix.height():
            return

        # 轉(zhuǎn)換為 PDF 點(diǎn)坐標(biāo) (PDF Points)
        pdf_page = self.doc[self.page_index]
        scale = pdf_page.rect.width / scaled_pix.width()
        
        pdf_x = click_x * scale
        pdf_y = click_y * scale
        
        self.clicked_pos.emit(pdf_x, pdf_y)

    def paintEvent(self, e):
        super().paintEvent(e) # 繪制背景
        
        if not self.doc or not self.current_pixmap: return

        painter = QPainter(self)
        painter.setRenderHint(QPainter.SmoothPixmapTransform)
        
        # 1. 繪制 PDF 底圖
        # 使用 scaledToWidth 確保寬度填滿(mǎn),高度隨 resizeEvent 自動(dòng)調(diào)整
        scaled_pix = self.current_pixmap.scaledToWidth(self.width(), Qt.SmoothTransformation)
        img_x_start = (self.width() - scaled_pix.width()) / 2
        img_y_start = (self.height() - scaled_pix.height()) / 2
        
        painter.drawPixmap(int(img_x_start), int(img_y_start), scaled_pix)
        
        # 2. 繪制已確認(rèn)的印章 (Overlay)
        pdf_page = self.doc[self.page_index]
        pt_to_px = scaled_pix.width() / pdf_page.rect.width
        
        for stamp in self.stamps_to_draw:
            # 尺寸轉(zhuǎn)屏幕像素
            w_screen = stamp['w_pt'] * pt_to_px
            h_screen = stamp['h_pt'] * pt_to_px
            
            # 中心坐標(biāo)轉(zhuǎn)屏幕像素
            cx_screen = stamp['x'] * pt_to_px
            cy_screen = stamp['y'] * pt_to_px
            
            # 計(jì)算繪制左上角
            draw_x = img_x_start + cx_screen - (w_screen / 2)
            draw_y = img_y_start + cy_screen - (h_screen / 2)
            
            target_rect = QRect(int(draw_x), int(draw_y), int(w_screen), int(h_screen))
            painter.drawPixmap(target_rect, stamp['pixmap'])

            # 繪制一個(gè)小紅框表示這是后加的章
            painter.setPen(QPen(QColor(255, 0, 0, 100), 1, Qt.DashLine))
            painter.drawRect(target_rect)

        painter.end()


# ===========================
# 主窗口
# ===========================
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        # --- 數(shù)據(jù)狀態(tài) ---
        self.doc = None
        self.pdf_path = ""
        self.current_page = 0
        self.stamps_list = [] 
        self.seal_pixmap = None
        self.seal_bytes = None
        self.seal_w_pt = 0.0
        self.seal_ratio = 1.0
        
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('PDF 手動(dòng)蓋章編輯器 (滾動(dòng)預(yù)覽修復(fù)版)')
        self.setGeometry(100, 100, 1300, 850)
        
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QHBoxLayout(main_widget)

        # ===== 左側(cè):工具欄 (保持不變) =====
        tools_panel = QVBoxLayout()
        tools_panel.setContentsMargins(0, 0, 10, 0)
        
        gb_file = QGroupBox("1. 文件操作")
        v_file = QVBoxLayout(gb_file)
        btn_open = QPushButton("?? 打開(kāi) PDF")
        btn_open.setStyleSheet("padding: 8px; font-weight: bold;")
        btn_open.clicked.connect(self.open_pdf)
        self.lbl_info = QLabel("未加載文件")
        self.lbl_info.setStyleSheet("color: #666; font-size: 11px;")
        v_file.addWidget(btn_open)
        v_file.addWidget(self.lbl_info)
        
        gb_seal = QGroupBox("2. 印章設(shè)置")
        v_seal = QVBoxLayout(gb_seal)
        self.txt_seal_path = QLineEdit()
        self.txt_seal_path.setPlaceholderText("選擇印章圖片...")
        self.txt_seal_path.setReadOnly(True)
        btn_sel_seal = QPushButton("??? 選擇印章")
        btn_sel_seal.clicked.connect(self.select_seal)
        self.spin_scale = QSpinBox()
        self.spin_scale.setRange(5, 500)
        self.spin_scale.setValue(35)
        self.spin_scale.setSuffix(" %")
        self.spin_scale.valueChanged.connect(self.update_seal_preview_info)
        self.lbl_seal_preview = QLabel("印章預(yù)覽")
        self.lbl_seal_preview.setAlignment(Qt.AlignCenter)
        self.lbl_seal_preview.setFixedSize(150, 150)
        self.lbl_seal_preview.setStyleSheet("border: 1px solid #ddd; background: #fff;")
        v_seal.addWidget(btn_sel_seal)
        v_seal.addWidget(self.txt_seal_path)
        v_seal.addWidget(QLabel("縮放比例:"))
        v_seal.addWidget(self.spin_scale)
        v_seal.addWidget(self.lbl_seal_preview, 0, Qt.AlignCenter)
        
        gb_action = QGroupBox("3. 編輯控制")
        v_action = QVBoxLayout(gb_action)
        btn_undo = QPushButton("?? 撤銷(xiāo)上一個(gè)印章")
        btn_undo.clicked.connect(self.undo_last_stamp)
        btn_clear_page = QPushButton("??? 清空當(dāng)前頁(yè)印章")
        btn_clear_page.clicked.connect(self.clear_page_stamps)
        v_action.addWidget(btn_undo)
        v_action.addWidget(btn_clear_page)

        btn_save = QPushButton("?? 另存為 PDF")
        btn_save.setFixedHeight(50)
        btn_save.setStyleSheet("background-color: #28a745; color: white; font-size: 14px; font-weight: bold;")
        btn_save.clicked.connect(self.save_pdf)
        
        tools_panel.addWidget(gb_file)
        tools_panel.addWidget(gb_seal)
        tools_panel.addWidget(gb_action)
        tools_panel.addStretch()
        tools_panel.addWidget(btn_save)

        # ===== 中間:預(yù)覽區(qū) (修復(fù)重點(diǎn)) =====
        preview_layout = QVBoxLayout()
        
        self.preview = PdfEditPreview()
        self.preview.clicked_pos.connect(self.add_stamp)
        
        # --- 創(chuàng)建滾動(dòng)區(qū)域 ---
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.preview)
        self.scroll_area.setWidgetResizable(True) # 關(guān)鍵:讓預(yù)覽組件寬度跟隨滾動(dòng)區(qū),但高度由組件自己決定
        self.scroll_area.setStyleSheet("border: 2px dashed #aaa;")
        
        # 翻頁(yè)條
        nav_layout = QHBoxLayout()
        self.btn_prev = QPushButton("? 上一頁(yè)")
        self.btn_next = QPushButton("下一頁(yè) ?")
        self.lbl_page = QLabel("0 / 0")
        
        self.btn_prev.clicked.connect(self.prev_page)
        self.btn_next.clicked.connect(self.next_page)
        
        nav_layout.addWidget(self.btn_prev)
        nav_layout.addWidget(self.lbl_page)
        nav_layout.addWidget(self.btn_next)
        
        preview_layout.addWidget(QLabel("<b>??? 操作說(shuō)明:</b>加載 PDF 和印章后,直接在右側(cè)頁(yè)面上<b>點(diǎn)擊鼠標(biāo)左鍵</b>即可蓋章。"))
        preview_layout.addWidget(self.scroll_area, 1) # 添加滾動(dòng)區(qū)域而不是直接添加 preview
        preview_layout.addLayout(nav_layout)

        # 組合布局
        tools_widget = QWidget()
        tools_widget.setLayout(tools_panel)
        tools_widget.setFixedWidth(280)
        
        main_layout.addWidget(tools_widget)
        main_layout.addLayout(preview_layout)
        
        self.update_ui_state()

    # ---------- 邏輯功能 (保持不變) ----------

    def open_pdf(self):
        path, _ = QFileDialog.getOpenFileName(self, "打開(kāi) PDF", "", "PDF Files (*.pdf)")
        if not path: return
        
        try:
            if self.doc: self.doc.close()
            self.doc = fitz.open(path)
            self.pdf_path = path
            self.current_page = 0
            self.stamps_list = []
            
            self.lbl_info.setText(os.path.basename(path))
            self.refresh_preview()
            self.update_ui_state()
            
        except Exception as e:
            QMessageBox.critical(self, "錯(cuò)誤", f"無(wú)法打開(kāi)文件:\n{str(e)}")

    def select_seal(self):
        path, _ = QFileDialog.getOpenFileName(self, "選擇印章圖片", "", "Images (*.png *.jpg *.jpeg *.bmp)")
        if not path: return
        
        self.txt_seal_path.setText(path)
        
        try:
            pil_img = Image.open(path)
            if pil_img.mode != 'RGBA':
                pil_img = pil_img.convert('RGBA')
            
            byte_io = BytesIO()
            pil_img.save(byte_io, format='PNG')
            self.seal_bytes = byte_io.getvalue()
            
            img_doc = fitz.open("png", self.seal_bytes)
            page = img_doc.load_page(0)
            self.seal_w_pt = page.rect.width
            self.seal_ratio = page.rect.height / page.rect.width
            img_doc.close()
            
            self.seal_pixmap = QPixmap(path)
            self.lbl_seal_preview.setPixmap(self.seal_pixmap.scaled(
                self.lbl_seal_preview.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
            ))
            self.update_seal_preview_info()
            
        except Exception as e:
            QMessageBox.critical(self, "圖片錯(cuò)誤", f"處理印章圖片失敗:\n{e}")

    def update_seal_preview_info(self):
        pass

    def add_stamp(self, pdf_x, pdf_y):
        if not self.doc or self.seal_bytes is None:
            QMessageBox.warning(self, "提示", "請(qǐng)先加載 PDF 和 印章圖片!")
            return
            
        scale = self.spin_scale.value() / 100.0
        
        actual_w = self.seal_w_pt * scale
        actual_h = actual_w * self.seal_ratio
        
        stamp_record = {
            'page': self.current_page,
            'x': pdf_x,
            'y': pdf_y,
            'w_pt': actual_w,
            'h_pt': actual_h,
            'img_bytes': self.seal_bytes,
            'pixmap': self.seal_pixmap
        }
        
        self.stamps_list.append(stamp_record)
        self.refresh_preview()

    def undo_last_stamp(self):
        if not self.stamps_list: return
        removed = self.stamps_list.pop()
        if removed['page'] == self.current_page:
            self.refresh_preview()
        else:
            QMessageBox.information(self, "撤銷(xiāo)", f"已撤銷(xiāo)第 {removed['page']+1} 頁(yè)上的印章")

    def clear_page_stamps(self):
        old_len = len(self.stamps_list)
        self.stamps_list = [s for s in self.stamps_list if s['page'] != self.current_page]
        if len(self.stamps_list) < old_len:
            self.refresh_preview()

    def refresh_preview(self):
        if not self.doc: return
        current_page_stamps = [s for s in self.stamps_list if s['page'] == self.current_page]
        self.preview.set_stamps(current_page_stamps)
        self.preview.load_page(self.doc, self.current_page)
        self.lbl_page.setText(f"{self.current_page + 1} / {len(self.doc)}")
        self.update_ui_state()

    def prev_page(self):
        if self.current_page > 0:
            self.current_page -= 1
            self.refresh_preview()
            # 翻頁(yè)時(shí)重置滾動(dòng)條到頂部
            self.scroll_area.verticalScrollBar().setValue(0)

    def next_page(self):
        if self.doc and self.current_page < len(self.doc) - 1:
            self.current_page += 1
            self.refresh_preview()
            # 翻頁(yè)時(shí)重置滾動(dòng)條到頂部
            self.scroll_area.verticalScrollBar().setValue(0)
            
    def update_ui_state(self):
        has_doc = self.doc is not None
        self.btn_prev.setEnabled(has_doc and self.current_page > 0)
        self.btn_next.setEnabled(has_doc and self.current_page < len(self.doc) - 1)

    def save_pdf(self):
        if not self.doc: return
        if not self.stamps_list:
            QMessageBox.information(self, "提示", "當(dāng)前沒(méi)有蓋任何章,不需要保存。")
            return
            
        save_path, _ = QFileDialog.getSaveFileName(self, "保存文件", self.pdf_path.replace(".pdf", "_stamped.pdf"), "PDF Files (*.pdf)")
        if not save_path: return
        
        try:
            for stamp in self.stamps_list:
                page = self.doc[stamp['page']]
                rect_x0 = stamp['x'] - stamp['w_pt'] / 2
                rect_y0 = stamp['y'] - stamp['h_pt'] / 2
                rect_x1 = stamp['x'] + stamp['w_pt'] / 2
                rect_y1 = stamp['y'] + stamp['h_pt'] / 2
                rect = fitz.Rect(rect_x0, rect_y0, rect_x1, rect_y1)
                page.insert_image(rect, stream=stamp['img_bytes'])
            
            self.doc.save(save_path)
            QMessageBox.information(self, "成功", f"文件已保存至:\n{save_path}")
            
            self.doc.close()
            self.doc = fitz.open(save_path)
            self.pdf_path = save_path
            self.stamps_list = []
            self.refresh_preview()
            
        except Exception as e:
            QMessageBox.critical(self, "保存失敗", str(e))

if __name__ == '__main__':
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

八、擴(kuò)展功能建議

  1. 批量蓋章:支持在多個(gè)位置批量添加相同印章
  2. 印章庫(kù)管理:保存常用的多個(gè)印章,方便快速選擇
  3. 模板功能:保存常用的蓋章位置模板
  4. 文字水印:除了圖片印章,支持添加文字水印
  5. 多頁(yè)操作:支持跨頁(yè)復(fù)制印章位置

九、總結(jié)

通過(guò)這個(gè)項(xiàng)目,我們實(shí)現(xiàn)了:

  • 一個(gè)完整的桌面GUI應(yīng)用
  • PDF文件的解析和渲染
  • 精確的坐標(biāo)定位系統(tǒng)
  • 圖像與PDF的合成功能
  • 友好的用戶(hù)交互界面

這個(gè)工具不僅實(shí)用,也是學(xué)習(xí)PyQt5圖形界面編程和PDF處理的好例子。

到此這篇關(guān)于使用Python開(kāi)發(fā)一個(gè)桌面版PDF蓋章工具的文章就介紹到這了,更多相關(guān)Python PDF蓋章工具內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python自然語(yǔ)言處理停用詞過(guò)濾實(shí)例詳解

    Python自然語(yǔ)言處理停用詞過(guò)濾實(shí)例詳解

    這篇文章主要為大家介紹了Python自然語(yǔ)言處理停用詞過(guò)濾實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Python Pandas學(xué)習(xí)之基本數(shù)據(jù)操作詳解

    Python Pandas學(xué)習(xí)之基本數(shù)據(jù)操作詳解

    本文將通過(guò)讀取一個(gè)股票數(shù)據(jù),來(lái)進(jìn)行Pandas的一些基本數(shù)據(jù)操作的語(yǔ)法介紹。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-02-02
  • python可視化實(shí)現(xiàn)代碼

    python可視化實(shí)現(xiàn)代碼

    今天小編就為大家分享一篇關(guān)于python可視化實(shí)現(xiàn)代碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • 詳解pandas映射與數(shù)據(jù)轉(zhuǎn)換

    詳解pandas映射與數(shù)據(jù)轉(zhuǎn)換

    這篇文章主要介紹了pandas映射與數(shù)據(jù)轉(zhuǎn)換的相關(guān)資料,幫助大家更好的利用python進(jìn)行數(shù)據(jù)分析,感興趣的朋友可以了解下
    2021-01-01
  • Python中json文件和jsonl文件的區(qū)別小結(jié)

    Python中json文件和jsonl文件的區(qū)別小結(jié)

    本文主要介紹了JSON和JSONL兩種文件格式的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-01-01
  • Python編程tkinter庫(kù)Canvas實(shí)現(xiàn)涂鴉顏色表及圍棋盤(pán)示例

    Python編程tkinter庫(kù)Canvas實(shí)現(xiàn)涂鴉顏色表及圍棋盤(pán)示例

    這篇文章主要為大家介紹了Python編程中如何使用tkinter庫(kù)Canvas來(lái)實(shí)現(xiàn)涂鴉,顏色表及圍棋盤(pán)的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-10-10
  • SymPy庫(kù)關(guān)于矩陣的基本操作和運(yùn)算

    SymPy庫(kù)關(guān)于矩陣的基本操作和運(yùn)算

    本文主要介紹了SymPy庫(kù)關(guān)于矩陣的基本操作和運(yùn)算,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • python udp如何實(shí)現(xiàn)同時(shí)收發(fā)信息

    python udp如何實(shí)現(xiàn)同時(shí)收發(fā)信息

    這篇文章主要介紹了python udp如何實(shí)現(xiàn)同時(shí)收發(fā)信息,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 一文詳解如何從根本上優(yōu)雅地解決VSCode中的Python模塊導(dǎo)入問(wèn)題

    一文詳解如何從根本上優(yōu)雅地解決VSCode中的Python模塊導(dǎo)入問(wèn)題

    有時(shí)你可能會(huì)遇到這種問(wèn)題,明明用pip安裝好了一個(gè)python模塊,但在VScode中總是顯示錯(cuò)誤,這篇文章主要給大家介紹了關(guān)于如何從根本上優(yōu)雅地解決VSCode中的Python模塊導(dǎo)入問(wèn)題的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • python報(bào)錯(cuò)TypeError: Input z must be 2D, not 3D的解決方法

    python報(bào)錯(cuò)TypeError: Input z must be 

    大家好,本篇文章主要講的是python報(bào)錯(cuò)TypeError: Input z must be 2D, not 3D的解決方法,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下
    2021-12-12

最新評(píng)論

海原县| 周宁县| 崇州市| 沭阳县| 濉溪县| 肥西县| 温州市| 伊春市| 曲周县| 安塞县| 彭泽县| 武邑县| 宜川县| 廉江市| 惠水县| 安西县| 祁阳县| 泸州市| 商河县| 紫云| 安国市| 海伦市| 镇巴县| 古田县| 遵化市| 绵阳市| 阜新市| 嘉禾县| 寻乌县| 万山特区| 应城市| 永康市| 天津市| 星子县| 水富县| 河北省| 伊春市| 万安县| 万盛区| 平乡县| 韶山市|