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

Python+PyQt5實現(xiàn)PDF批量水印工具

 更新時間:2026年03月05日 08:29:27   作者:小莊-Python辦公  
本文圍繞 PDF添加水印.py,完整講解如何將單文件水印腳本重構(gòu)為可交互的 PyQt5 桌面應(yīng)用,支持拖拽導(dǎo)入、實時預(yù)覽、子線程處理與輸出管理,并給出關(guān)鍵實現(xiàn)細(xì)節(jié)與結(jié)構(gòu)說明,需要的朋友可以參考下

PyQt5 的 PDF 批量水印工具:

本文圍繞 PDF添加水印.py,完整講解如何將單文件水印腳本重構(gòu)為可交互的 PyQt5 桌面應(yīng)用,支持拖拽導(dǎo)入、實時預(yù)覽、子線程處理與輸出管理,并給出關(guān)鍵實現(xiàn)細(xì)節(jié)與結(jié)構(gòu)說明。

一、功能概覽

這款工具面向“批量給 PDF 添加文字水印”的場景,核心功能如下:

  • 支持拖拽或選擇多個 PDF 文件
  • 參數(shù)可配置:字體、字號、行間距、旋轉(zhuǎn)角度、透明度
  • 實時預(yù)覽旋轉(zhuǎn)與透明度效果
  • 使用子線程處理,避免界面卡頓
  • 輸出目錄統(tǒng)一為 ./output/ 并自動重命名
  • 常見錯誤(空列表、字體缺失、權(quán)限不足)彈窗提示

二、整體架構(gòu)與模塊拆分

應(yīng)用主要分為三層:

UI 層(WatermarkApp)
負(fù)責(zé)界面布局、參數(shù)采集、按鈕交互、預(yù)覽觸發(fā)、狀態(tài)展示等。

處理層(WatermarkWorker / PreviewWorker)
使用 QThread 執(zhí)行批量水印與預(yù)覽生成,避免阻塞主線程。

工具層(字體解析 / 輸出重名 / 水印生成)
封裝公共邏輯,確保主流程清晰且復(fù)用性強。

三、界面布局設(shè)計

界面布局遵循“左列表、右參數(shù)、底部操作區(qū)”的結(jié)構(gòu)。

1. 左側(cè):PDF 文件列表 + 添加/刪除

  • 列表控件支持拖拽(繼承 QListWidget 重寫拖拽事件)
  • 添加按鈕打開文件對話框
  • 刪除按鈕移除選中項

2. 右側(cè):參數(shù)面板(按順序分組)

  • 水印文字:QLineEdit
  • 字體選擇:QComboBox(系統(tǒng)字體列表)
  • 行間距:QSpinBox(0–200%)
  • 字號:QSpinBox(8–200pt)
  • 角度:QSlider + QSpinBox(-90–90°)
  • 透明度:QSlider + QSpinBox(0–100%)
  • 預(yù)覽:QLabel 顯示動態(tài)渲染圖

3. 底部:操作按鈕 + 進(jìn)度/狀態(tài)

  • “開始處理”
  • “預(yù)覽效果”
  • “打開輸出目錄”
  • 進(jìn)度條 + 狀態(tài)提示文本

四、拖拽導(dǎo)入實現(xiàn)

自定義 PdfListWidget,重寫拖拽相關(guān)事件:

  • dragEnterEvent / dragMoveEvent:校驗 URL
  • dropEvent:過濾 .pdf 文件并添加到列表

這樣用戶可以直接把 PDF 拖進(jìn)列表,極大提升效率。

五、實時預(yù)覽機制

預(yù)覽的核心思想是:

  1. 使用當(dāng)前 UI 參數(shù)生成一個水印 PDF
  2. 將水印合成到原 PDF 的第一頁
  3. 保存為臨時預(yù)覽文件并自動打開

實現(xiàn)上通過 PreviewWorker(QThread) 執(zhí)行生成邏輯,以避免 UI 卡頓。
預(yù)覽按鈕會優(yōu)先使用“選中項”,若未選中則默認(rèn)使用第一個 PDF。

六、子線程批量處理

處理邏輯放在 WatermarkWorker(QThread) 中完成:

  • 讀取每個 PDF
  • 對每一頁合成水印
  • 保存到 ./output 目錄
  • 通過信號更新進(jìn)度條與狀態(tài)文本

七、水印生成邏輯

水印生成使用 ReportLab,在內(nèi)存中創(chuàng)建一頁 PDF:

  1. 讀取當(dāng)前頁面大?。ù_保適配不同尺寸 PDF)
  2. 在中心點平移 + 旋轉(zhuǎn)
  3. 按固定網(wǎng)格繪制水印文本
  4. 輸出為 BytesIO 內(nèi)存對象

關(guān)鍵點:

  • 行間距與透明度全部來自 UI 參數(shù)
  • 字體通過系統(tǒng)字體注冊,缺失時拋錯

八、字體選擇與注冊

字體下拉框基于系統(tǒng)字體列表:

  • QFontDatabase().families() 獲取可用字體
  • 默認(rèn)選“宋體”

字體注冊則通過 Windows 注冊表查詢真實字體文件路徑,然后用 ReportLab 注冊:

  1. 在注冊表中查找字體名稱
  2. 拼接字體文件路徑
  3. pdfmetrics.registerFont(TTFont(...))

這樣既能保證預(yù)覽字體一致,也能保證 PDF 輸出字體可用。

九、輸出策略與重名機制

輸出統(tǒng)一保存到:

./output/原文件名_watermarked.pdf

如果文件已存在,會自動追加序號:

xxx_watermarked_1.pdf
xxx_watermarked_2.pdf

這一邏輯由 unique_output_path 負(fù)責(zé),避免覆蓋歷史結(jié)果。

十、錯誤處理與彈窗提示

以下情況會觸發(fā)彈窗警告:

  • PDF 列表為空
  • 字體文件缺失
  • 沒有讀寫權(quán)限
  • 缺少 PyPDF2 依賴

這樣可以避免用戶在無感知情況下操作失敗。

十一、運行方式

直接運行腳本即可啟動 GUI:

python d:\測試\公眾號水文\11-PDF添加水印\PDF添加水印.py

如需打包為 EXE,可使用 PyInstaller(另行說明)。

十二、總結(jié)與擴(kuò)展建議

當(dāng)前版本已經(jīng)具備完整的批量水印能力,并具備以下優(yōu)勢:

  • 參數(shù)全可配置
  • UI 友好
  • 處理不卡頓
  • 輸出安全可靠

后續(xù)可考慮的擴(kuò)展方向:

  • 增加顏色選擇
  • 自定義橫向/縱向密度
  • 多行預(yù)覽與縮略圖預(yù)覽
  • 輸出目錄可自定義

如需進(jìn)一步增強功能或增加更多參數(shù),我可以繼續(xù)擴(kuò)展優(yōu)化。

完整代碼

import io
import os
import sys
import winreg

from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont, QFontDatabase, QPainter, QPixmap
from PyQt5.QtWidgets import (
    QApplication,
    QFileDialog,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QListWidget,
    QListWidgetItem,
    QMessageBox,
    QPushButton,
    QProgressBar,
    QSlider,
    QSpinBox,
    QDoubleSpinBox,
    QVBoxLayout,
    QWidget,
    QComboBox,
    QAbstractItemView,
    QSizePolicy,
)
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

try:
    from PyPDF2 import PdfReader, PdfWriter
except Exception:
    PdfReader = None
    PdfWriter = None


def find_font_file(font_family):
    fonts_dir = os.path.join(os.environ.get("WINDIR", "C:\\Windows"), "Fonts")
    try:
        with winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE,
            r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts",
        ) as key:
            index = 0
            while True:
                try:
                    name, data, _ = winreg.EnumValue(key, index)
                    index += 1
                except OSError:
                    break
                if font_family.lower() in name.lower():
                    if os.path.isabs(data):
                        return data
                    return os.path.join(fonts_dir, data)
    except OSError:
        pass
    return None


def unique_output_path(output_dir, base_name):
    output_path = os.path.join(output_dir, base_name)
    if not os.path.exists(output_path):
        return output_path
    name, ext = os.path.splitext(base_name)
    counter = 1
    while True:
        candidate = os.path.join(output_dir, f"{name}_{counter}{ext}")
        if not os.path.exists(candidate):
            return candidate
        counter += 1


def get_reportlab_font(font_family, font_cache):
    if font_family in font_cache:
        return font_cache[font_family]
    font_path = find_font_file(font_family)
    if not font_path or not os.path.exists(font_path):
        raise FileNotFoundError(f"字體文件缺失:{font_family}")
    font_key = f"font_{len(font_cache)}"
    pdfmetrics.registerFont(TTFont(font_key, font_path))
    font_cache[font_family] = font_key
    return font_key


def create_watermark_pdf_buffer(width, height, options, font_cache):
    buffer = io.BytesIO()
    watermark_canvas = canvas.Canvas(buffer, pagesize=(width, height))
    font_name = get_reportlab_font(options["font_family"], font_cache)
    watermark_canvas.setFont(font_name, options["font_size"])
    watermark_canvas.setFillAlpha(options["opacity"] / 100)
    watermark_canvas.translate(width / 2, height / 2)
    watermark_canvas.rotate(options["angle"])
    x_step = width / 5
    y_step = height / 10 * (options["line_spacing"] / 100)
    for i in range(5):
        for j in range(10):
            a = (i - 2) * x_step
            b = (j - 4) * y_step
            watermark_canvas.drawString(a, b, options["text"])
    watermark_canvas.save()
    buffer.seek(0)
    return buffer


class PdfListWidget(QListWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event):
        if not event.mimeData().hasUrls():
            return
        for url in event.mimeData().urls():
            path = url.toLocalFile()
            if path.lower().endswith(".pdf"):
                self.add_pdf_item(path)

    def add_pdf_item(self, path):
        for i in range(self.count()):
            if self.item(i).text() == path:
                return
        item = QListWidgetItem(path)
        self.addItem(item)

    def selected_paths(self):
        return [item.text() for item in self.selectedItems()]

    def all_paths(self):
        return [self.item(i).text() for i in range(self.count())]


class WatermarkWorker(QThread):
    progress_changed = pyqtSignal(int)
    status_changed = pyqtSignal(str)
    error_occurred = pyqtSignal(str)
    finished_success = pyqtSignal(str)

    def __init__(self, files, options, parent=None):
        super().__init__(parent)
        self.files = files
        self.options = options
        self._font_cache = {}

    def run(self):
        if PdfReader is None or PdfWriter is None:
            self.error_occurred.emit("缺少 PyPDF2 庫,請先安裝 PyPDF2。")
            return
        try:
            output_dir = os.path.join(os.getcwd(), "output")
            os.makedirs(output_dir, exist_ok=True)
        except Exception as exc:
            self.error_occurred.emit(f"無法創(chuàng)建輸出目錄:{exc}")
            return

        total = len(self.files)
        for index, file_path in enumerate(self.files, start=1):
            try:
                self.status_changed.emit(f"處理中:{os.path.basename(file_path)}")
                output_name = f"{os.path.splitext(os.path.basename(file_path))[0]}_watermarked.pdf"
                output_path = unique_output_path(output_dir, output_name)
                reader = PdfReader(file_path)
                writer = PdfWriter()
                for page in reader.pages:
                    width = float(page.mediabox.width)
                    height = float(page.mediabox.height)
                    watermark_pdf = create_watermark_pdf_buffer(
                        width,
                        height,
                        self.options,
                        self._font_cache,
                    )
                    watermark_reader = PdfReader(watermark_pdf)
                    watermark_page = watermark_reader.pages[0]
                    page.merge_page(watermark_page)
                    writer.add_page(page)
                with open(output_path, "wb") as output_file:
                    writer.write(output_file)
                progress = int(index / total * 100)
                self.progress_changed.emit(progress)
            except PermissionError:
                self.error_occurred.emit("文件權(quán)限不足,無法讀寫 PDF。")
                return
            except Exception as exc:
                self.error_occurred.emit(f"處理失敗:{exc}")
                return

        self.progress_changed.emit(100)
        self.status_changed.emit("處理完成")
        self.finished_success.emit(output_dir)


class PreviewWorker(QThread):
    status_changed = pyqtSignal(str)
    error_occurred = pyqtSignal(str)
    preview_ready = pyqtSignal(str)

    def __init__(self, file_path, options, parent=None):
        super().__init__(parent)
        self.file_path = file_path
        self.options = options
        self._font_cache = {}

    def run(self):
        if PdfReader is None or PdfWriter is None:
            self.error_occurred.emit("缺少 PyPDF2 庫,請先安裝 PyPDF2。")
            return
        try:
            output_dir = os.path.join(os.getcwd(), "output")
            os.makedirs(output_dir, exist_ok=True)
        except Exception as exc:
            self.error_occurred.emit(f"無法創(chuàng)建輸出目錄:{exc}")
            return
        try:
            self.status_changed.emit("生成預(yù)覽中")
            reader = PdfReader(self.file_path)
            if not reader.pages:
                self.error_occurred.emit("PDF 內(nèi)容為空,無法預(yù)覽。")
                return
            writer = PdfWriter()
            page = reader.pages[0]
            width = float(page.mediabox.width)
            height = float(page.mediabox.height)
            watermark_pdf = create_watermark_pdf_buffer(
                width,
                height,
                self.options,
                self._font_cache,
            )
            watermark_reader = PdfReader(watermark_pdf)
            watermark_page = watermark_reader.pages[0]
            page.merge_page(watermark_page)
            writer.add_page(page)
            base_name = os.path.splitext(os.path.basename(self.file_path))[0]
            output_name = f"{base_name}_preview.pdf"
            output_path = unique_output_path(output_dir, output_name)
            with open(output_path, "wb") as output_file:
                writer.write(output_file)
            self.preview_ready.emit(output_path)
        except PermissionError:
            self.error_occurred.emit("文件權(quán)限不足,無法讀寫 PDF。")
        except Exception as exc:
            self.error_occurred.emit(f"預(yù)覽失?。簕exc}")


class WatermarkApp(QWidget):
    def __init__(self):
        super().__init__()
        self.worker = None
        self.preview_worker = None
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("PDF 水印工具")
        self.resize(1100, 650)

        main_layout = QVBoxLayout(self)
        content_layout = QHBoxLayout()
        main_layout.addLayout(content_layout)

        left_layout = QVBoxLayout()
        content_layout.addLayout(left_layout, 3)

        self.pdf_list = PdfListWidget()
        self.pdf_list.setToolTip("拖拽或通過按鈕添加 PDF 文件")
        left_layout.addWidget(self.pdf_list)

        list_button_layout = QHBoxLayout()
        left_layout.addLayout(list_button_layout)

        self.add_button = QPushButton("添加 PDF")
        self.add_button.setToolTip("選擇并添加 PDF 文件")
        self.add_button.clicked.connect(self.add_files)
        list_button_layout.addWidget(self.add_button)

        self.remove_button = QPushButton("刪除選中")
        self.remove_button.setToolTip("刪除列表中選中的 PDF 文件")
        self.remove_button.clicked.connect(self.remove_selected)
        list_button_layout.addWidget(self.remove_button)

        right_layout = QVBoxLayout()
        content_layout.addLayout(right_layout, 4)

        self.text_group = QGroupBox("水印文字")
        self.text_group.setToolTip("設(shè)置水印顯示的文字內(nèi)容")
        text_layout = QVBoxLayout()
        self.text_input = QLineEdit("python-小莊辦公")
        self.text_input.setToolTip("輸入水印文字內(nèi)容")
        self.text_input.textChanged.connect(self.update_preview)
        text_layout.addWidget(self.text_input)
        self.text_group.setLayout(text_layout)
        right_layout.addWidget(self.text_group)

        self.font_group = QGroupBox("字體選擇器")
        self.font_group.setToolTip("選擇系統(tǒng)已安裝字體")
        font_layout = QVBoxLayout()
        self.font_combo = QComboBox()
        self.font_combo.setToolTip("從系統(tǒng)字體列表中選擇字體")
        font_layout.addWidget(self.font_combo)
        self.font_group.setLayout(font_layout)
        right_layout.addWidget(self.font_group)

        self.spacing_group = QGroupBox("水印行間距")
        self.spacing_group.setToolTip("調(diào)整水印縱向間距")
        spacing_layout = QHBoxLayout()
        self.spacing_spin = QSpinBox()
        self.spacing_spin.setToolTip("設(shè)置水印行間距百分比")
        self.spacing_spin.setRange(0, 200)
        self.spacing_spin.setSingleStep(1)
        self.spacing_spin.setSuffix(" %")
        self.spacing_spin.setValue(100)
        self.spacing_spin.valueChanged.connect(self.update_preview)
        spacing_layout.addWidget(self.spacing_spin)
        self.spacing_group.setLayout(spacing_layout)
        right_layout.addWidget(self.spacing_group)

        self.size_group = QGroupBox("水印字體大小")
        self.size_group.setToolTip("設(shè)置水印字體大小")
        size_layout = QHBoxLayout()
        self.size_spin = QSpinBox()
        self.size_spin.setToolTip("設(shè)置水印字體大?。╬t)")
        self.size_spin.setRange(8, 200)
        self.size_spin.setSingleStep(1)
        self.size_spin.setSuffix(" pt")
        self.size_spin.setValue(36)
        self.size_spin.valueChanged.connect(self.update_preview)
        size_layout.addWidget(self.size_spin)
        self.size_group.setLayout(size_layout)
        right_layout.addWidget(self.size_group)

        self.angle_group = QGroupBox("旋轉(zhuǎn)角度")
        self.angle_group.setToolTip("調(diào)整水印旋轉(zhuǎn)角度")
        angle_layout = QHBoxLayout()
        self.angle_slider = QSlider(Qt.Horizontal)
        self.angle_slider.setToolTip("拖動調(diào)整旋轉(zhuǎn)角度")
        self.angle_slider.setRange(-90, 90)
        self.angle_slider.setSingleStep(1)
        self.angle_slider.setValue(0)
        self.angle_spin = QSpinBox()
        self.angle_spin.setToolTip("輸入旋轉(zhuǎn)角度")
        self.angle_spin.setRange(-90, 90)
        self.angle_spin.setSingleStep(1)
        self.angle_spin.setValue(0)
        angle_layout.addWidget(self.angle_slider)
        angle_layout.addWidget(self.angle_spin)
        self.angle_group.setLayout(angle_layout)
        right_layout.addWidget(self.angle_group)

        self.opacity_group = QGroupBox("透明度")
        self.opacity_group.setToolTip("調(diào)整水印透明度")
        opacity_layout = QHBoxLayout()
        self.opacity_slider = QSlider(Qt.Horizontal)
        self.opacity_slider.setToolTip("拖動調(diào)整透明度")
        self.opacity_slider.setRange(0, 100)
        self.opacity_slider.setSingleStep(1)
        self.opacity_slider.setValue(30)
        self.opacity_spin = QSpinBox()
        self.opacity_spin.setToolTip("輸入透明度百分比")
        self.opacity_spin.setRange(0, 100)
        self.opacity_spin.setSingleStep(1)
        self.opacity_spin.setValue(30)
        opacity_layout.addWidget(self.opacity_slider)
        opacity_layout.addWidget(self.opacity_spin)
        self.opacity_group.setLayout(opacity_layout)
        right_layout.addWidget(self.opacity_group)

        self.preview_group = QGroupBox("預(yù)覽")
        self.preview_group.setToolTip("實時預(yù)覽水印效果")
        preview_layout = QVBoxLayout()
        self.preview_label = QLabel()
        self.preview_label.setToolTip("顯示水印旋轉(zhuǎn)與透明度預(yù)覽")
        self.preview_label.setMinimumHeight(140)
        self.preview_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.preview_label.setAlignment(Qt.AlignCenter)
        preview_layout.addWidget(self.preview_label)
        self.preview_group.setLayout(preview_layout)
        right_layout.addWidget(self.preview_group)

        right_layout.addStretch()

        bottom_layout = QHBoxLayout()
        main_layout.addLayout(bottom_layout)

        self.start_button = QPushButton("開始處理")
        self.start_button.setToolTip("開始給列表中的 PDF 添加水印")
        self.start_button.clicked.connect(self.start_processing)
        bottom_layout.addWidget(self.start_button)

        self.preview_button = QPushButton("預(yù)覽效果")
        self.preview_button.setToolTip("生成當(dāng)前參數(shù)下的水印預(yù)覽文件")
        self.preview_button.clicked.connect(self.start_preview)
        bottom_layout.addWidget(self.preview_button)

        self.open_output_button = QPushButton("打開輸出目錄")
        self.open_output_button.setToolTip("打開輸出文件夾")
        self.open_output_button.clicked.connect(self.open_output_dir)
        bottom_layout.addWidget(self.open_output_button)

        self.progress_bar = QProgressBar()
        self.progress_bar.setToolTip("顯示處理進(jìn)度")
        self.progress_bar.setValue(0)
        bottom_layout.addWidget(self.progress_bar, 3)

        self.status_label = QLabel("就緒")
        self.status_label.setToolTip("顯示當(dāng)前處理狀態(tài)")
        bottom_layout.addWidget(self.status_label, 2)

        self.load_fonts()
        self.bind_signals()
        self.update_preview()

    def load_fonts(self):
        db = QFontDatabase()
        families = db.families()
        self.font_combo.addItems(families)
        default_font = "宋體"
        if default_font in families:
            self.font_combo.setCurrentText(default_font)
        elif families:
            self.font_combo.setCurrentIndex(0)
        self.font_combo.currentTextChanged.connect(self.update_preview)

    def bind_signals(self):
        self.angle_slider.valueChanged.connect(self.angle_spin.setValue)
        self.angle_spin.valueChanged.connect(self.angle_slider.setValue)
        self.angle_slider.valueChanged.connect(self.update_preview)
        self.angle_spin.valueChanged.connect(self.update_preview)

        self.opacity_slider.valueChanged.connect(self.opacity_spin.setValue)
        self.opacity_spin.valueChanged.connect(self.opacity_slider.setValue)
        self.opacity_slider.valueChanged.connect(self.update_preview)
        self.opacity_spin.valueChanged.connect(self.update_preview)

    def update_preview(self):
        width = max(self.preview_label.width(), 300)
        height = max(self.preview_label.height(), 140)
        pixmap = QPixmap(width, height)
        pixmap.fill(Qt.white)
        painter = QPainter(pixmap)
        painter.setRenderHint(QPainter.Antialiasing)
        font = QFont(self.font_combo.currentText(), self.size_spin.value())
        painter.setFont(font)
        painter.setOpacity(self.opacity_spin.value() / 100)
        painter.translate(width / 2, height / 2)
        painter.rotate(self.angle_spin.value())
        painter.drawText(-width // 4, 0, self.text_input.text())
        painter.end()
        self.preview_label.setPixmap(pixmap)

    def add_files(self):
        files, _ = QFileDialog.getOpenFileNames(
            self,
            "選擇 PDF 文件",
            "",
            "PDF 文件 (*.pdf)",
        )
        for file_path in files:
            self.pdf_list.add_pdf_item(file_path)

    def remove_selected(self):
        for item in self.pdf_list.selectedItems():
            row = self.pdf_list.row(item)
            self.pdf_list.takeItem(row)

    def start_processing(self):
        files = self.pdf_list.all_paths()
        if not files:
            QMessageBox.warning(self, "提示", "請先添加需要處理的 PDF 文件。")
            return
        if PdfReader is None or PdfWriter is None:
            QMessageBox.warning(self, "提示", "未安裝 PyPDF2,無法處理 PDF。")
            return
        font_path = find_font_file(self.font_combo.currentText())
        if not font_path or not os.path.exists(font_path):
            QMessageBox.warning(self, "提示", "所選字體文件缺失,請更換字體。")
            return

        options = {
            "text": self.text_input.text().strip() or " ",
            "font_family": self.font_combo.currentText(),
            "line_spacing": self.spacing_spin.value(),
            "font_size": self.size_spin.value(),
            "angle": self.angle_spin.value(),
            "opacity": self.opacity_spin.value(),
        }

        self.progress_bar.setValue(0)
        self.status_label.setText("準(zhǔn)備處理")
        self.start_button.setEnabled(False)
        self.preview_button.setEnabled(False)
        self.worker = WatermarkWorker(files, options)
        self.worker.progress_changed.connect(self.progress_bar.setValue)
        self.worker.status_changed.connect(self.status_label.setText)
        self.worker.error_occurred.connect(self.handle_error)
        self.worker.finished_success.connect(self.handle_success)
        self.worker.start()

    def handle_error(self, message):
        self.start_button.setEnabled(True)
        self.preview_button.setEnabled(True)
        self.status_label.setText("處理失敗")
        QMessageBox.warning(self, "錯誤", message)

    def handle_success(self, output_dir):
        self.start_button.setEnabled(True)
        self.preview_button.setEnabled(True)
        self.status_label.setText("處理完成")
        QMessageBox.information(self, "完成", f"水印處理完成,輸出目錄:{output_dir}")

    def start_preview(self):
        files = self.pdf_list.selected_paths() or self.pdf_list.all_paths()
        if not files:
            QMessageBox.warning(self, "提示", "請先添加需要預(yù)覽的 PDF 文件。")
            return
        if PdfReader is None or PdfWriter is None:
            QMessageBox.warning(self, "提示", "未安裝 PyPDF2,無法預(yù)覽。")
            return
        font_path = find_font_file(self.font_combo.currentText())
        if not font_path or not os.path.exists(font_path):
            QMessageBox.warning(self, "提示", "所選字體文件缺失,請更換字體。")
            return

        options = {
            "text": self.text_input.text().strip() or " ",
            "font_family": self.font_combo.currentText(),
            "line_spacing": self.spacing_spin.value(),
            "font_size": self.size_spin.value(),
            "angle": self.angle_spin.value(),
            "opacity": self.opacity_spin.value(),
        }

        self.status_label.setText("生成預(yù)覽")
        self.preview_button.setEnabled(False)
        self.start_button.setEnabled(False)
        self.preview_worker = PreviewWorker(files[0], options)
        self.preview_worker.status_changed.connect(self.status_label.setText)
        self.preview_worker.error_occurred.connect(self.handle_error)
        self.preview_worker.preview_ready.connect(self.handle_preview_ready)
        self.preview_worker.start()

    def handle_preview_ready(self, preview_path):
        self.start_button.setEnabled(True)
        self.preview_button.setEnabled(True)
        self.status_label.setText("預(yù)覽完成")
        try:
            os.startfile(preview_path)
        except Exception as exc:
            QMessageBox.warning(self, "提示", f"無法打開預(yù)覽文件:{exc}")

    def open_output_dir(self):
        output_dir = os.path.join(os.getcwd(), "output")
        if not os.path.exists(output_dir):
            QMessageBox.warning(self, "提示", "輸出目錄不存在,請先處理文件。")
            return
        try:
            os.startfile(output_dir)
        except Exception as exc:
            QMessageBox.warning(self, "提示", f"無法打開輸出目錄:{exc}")


def main():
    app = QApplication(sys.argv)
    window = WatermarkApp()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()


以上就是Python+PyQt5實現(xiàn)PDF批量水印工具的詳細(xì)內(nèi)容,更多關(guān)于Python PDF批量水印的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Python3自動安裝第三方庫,跟pip說再見

    Python3自動安裝第三方庫,跟pip說再見

    很多朋友私信小編Python安裝第三方庫安裝技巧,在這就不一一回復(fù)大家了,今天小編給大家分享一篇教程關(guān)于Python自動安裝第三方庫的小技巧,本文以安裝plotly為例給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧
    2021-10-10
  • 如何使用Python獲取昨天的日期

    如何使用Python獲取昨天的日期

    Python 是一種高級語言,可用于數(shù)據(jù)科學(xué)和機器學(xué)習(xí),以使用 Python 的數(shù)據(jù)結(jié)構(gòu)訓(xùn)練 AI,它用于編寫各種應(yīng)用程序,從 Web 和桌面到操作系統(tǒng)級程序,這篇文章主要介紹了在 Python 中獲取昨天的日期,需要的朋友可以參考下
    2023-05-05
  • 利用python 讀寫csv文件

    利用python 讀寫csv文件

    python中有一個讀寫csv文件的包,直接import csv即可。利用這個python包可以很方便對csv文件進(jìn)行操作。
    2020-09-09
  • 如何基于pythonnet調(diào)用halcon腳本

    如何基于pythonnet調(diào)用halcon腳本

    這篇文章主要介紹了如何基于pythonnet調(diào)用halcon腳本,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • python在多玩圖片上下載妹子圖的實現(xiàn)代碼

    python在多玩圖片上下載妹子圖的實現(xiàn)代碼

    學(xué)python的第二天,想寫個東西出來玩玩,于是就寫了這個,供那些才學(xué)一天的參考參考也行
    2013-08-08
  • pyqt5使用按鈕進(jìn)行界面的跳轉(zhuǎn)方法

    pyqt5使用按鈕進(jìn)行界面的跳轉(zhuǎn)方法

    今天小編就為大家分享一篇pyqt5使用按鈕進(jìn)行界面的跳轉(zhuǎn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-06-06
  • python生成xml時規(guī)定dtd實例方法

    python生成xml時規(guī)定dtd實例方法

    在本篇文章里小編給大家整理的是關(guān)于python生成xml時規(guī)定dtd實例方法,需要的朋友們學(xué)習(xí)參考下。
    2020-09-09
  • Python實現(xiàn)IP端口掃描程序

    Python實現(xiàn)IP端口掃描程序

    這篇文章主要為大家詳細(xì)介紹了如何使用Python實現(xiàn)IP端口掃描程序,可以掃描該IP哪些端口對外是開放的并輸出端口列表,有需要的小伙伴可以了解下
    2025-05-05
  • 淺談keras中的目標(biāo)函數(shù)和優(yōu)化函數(shù)MSE用法

    淺談keras中的目標(biāo)函數(shù)和優(yōu)化函數(shù)MSE用法

    這篇文章主要介紹了淺談keras中的目標(biāo)函數(shù)和優(yōu)化函數(shù)MSE用法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-06-06
  • Python極值整數(shù)的邊界探討分析

    Python極值整數(shù)的邊界探討分析

    這篇文章主要介紹了Python極值整數(shù)的邊界探討分析,閱讀本文來一起領(lǐng)略Python中的極值,看一下Python整數(shù)是否有邊界,有需要的朋友可以借鑒參考下
    2021-09-09

最新評論

广河县| 本溪市| 红桥区| 新乐市| 白玉县| 元阳县| 沁水县| 青阳县| 南郑县| 云安县| 青岛市| 米脂县| 抚州市| 卓资县| 天等县| 盘锦市| 成都市| 盘山县| 阿巴嘎旗| 桃源县| 岱山县| 汾西县| 伽师县| 娱乐| 天水市| 丰镇市| 宁强县| 荃湾区| 云林县| 新宁县| 桐城市| 沈阳市| 黄陵县| 炎陵县| 项城市| 杭州市| 方正县| 曲靖市| 东乌珠穆沁旗| 大邑县| 云龙县|