【python】图片转PDF工具【附完整源码】

python 复制代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
import os
import sys
import subprocess
import platform


class PDFGeneratorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("PDF工具集 - 图片转PDF & PDF合并")
        self.root.geometry("1200x850")

        self.setup_styles()

        # 程序居中显示
        self.center_window()

        self.root.resizable(True, True)

        # 设置应用图标
        try:
            self.root.iconbitmap("pdf_icon.ico")
        except:
            pass

        # 检查依赖
        self.check_dependencies()

        # 初始化变量
        self.image1_path = tk.StringVar()
        self.image2_path = tk.StringVar()
        self.output_path = tk.StringVar(value=os.path.join(os.getcwd(), "output.pdf"))
        self.scale_factor = tk.DoubleVar(value=0.8)

        # PDF合并相关变量
        self.pdf_files = []
        self.merge_output_path = tk.StringVar(value=os.path.join(os.getcwd(), "merged_output.pdf"))

        # 多图片转PDF相关变量
        self.multi_images = []
        self.multi_output_path = tk.StringVar(value=os.path.join(os.getcwd(), "multi_images_output.pdf"))
        self.multi_scale_factor = tk.DoubleVar(value=0.8)

        # 创建UI
        self.create_ui()

        self.apply_styles()

    def center_window(self):
        """使窗口在屏幕居中显示"""
        self.root.update_idletasks()
        width = 1200
        height = 850
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry('{}x{}+{}+{}'.format(width, height, x, y))

    def check_dependencies(self):
        """检查必要的依赖库"""
        self.has_reportlab = False
        self.has_img2pdf = False
        self.has_pypdf2 = False

        try:
            from reportlab.pdfgen import canvas
            from reportlab.lib.pagesizes import A4
            self.has_reportlab = True
        except ImportError:
            print("reportlab 未安装")

        try:
            import img2pdf
            self.has_img2pdf = True
        except ImportError:
            print("img2pdf 未安装")

        try:
            import PyPDF2
            self.has_pypdf2 = True
        except ImportError:
            print("PyPDF2 未安装")

        missing_deps = []
        if not self.has_reportlab and not self.has_img2pdf:
            missing_deps.append("图片转PDF功能")
        if not self.has_pypdf2:
            missing_deps.append("PDF合并功能")

        if missing_deps:
            messagebox.showwarning(
                "缺少依赖",
                f"以下功能缺少依赖库:\n{', '.join(missing_deps)}\n\n"
                f"请安装所需库:\n"
                f"pip install reportlab img2pdf pypdf2"
            )

    def setup_styles(self):
        """为PDF工具集设置现代化样式"""
        self.style = ttk.Style()

        # 获取可用主题
        available_themes = self.style.theme_names()
        print("可用主题:", available_themes)

        # 选择主题
        if 'clam' in available_themes:
            self.style.theme_use('default')
            print("使用default主题")
        elif available_themes:
            self.style.theme_use(available_themes[0])
            print(f"使用主题: {available_themes[0]}")

        # 配置具体控件样式 - 使用正确的样式名称
        try:
            # 配置按钮样式 - 使用标准样式名称
            self.style.configure('TButton',
                                 padding=(8, 4),
                                 font=('Microsoft YaHei', 9))

            # 配置标签页样式
            self.style.configure('TNotebook', padding=5)
            self.style.configure('TNotebook.Tab',
                                 padding=(12, 6),
                                 font=('Microsoft YaHei', 9))

            # 配置进度条样式
            self.style.configure('Horizontal.TProgressbar',
                                 thickness=20,
                                 troughcolor='#f0f0f0',
                                 background='#4CAF50')

            # 配置滚动条样式
            self.style.configure('Vertical.TScrollbar',
                                 arrowsize=14)

            # 配置标签框架样式
            self.style.configure('TLabelframe',
                                 borderwidth=2)
            self.style.configure('TLabelframe.Label',
                                 font=('Microsoft YaHei', 9, 'bold'))

            # 配置标签样式
            self.style.configure('TLabel',
                                 font=('Microsoft YaHei', 9))

            # 配置输入框样式
            self.style.configure('TEntry',
                                 padding=3)

            print("PDF工具集样式配置完成")

        except Exception as e:
            print(f"样式配置错误: {e}")

    def apply_styles(self):
        """应用样式到已创建的控件"""
        try:
            # 遍历所有子控件并应用样式
            self.apply_styles_recursive(self.root)
            print("样式应用完成")
        except Exception as e:
            print(f"应用样式时出错: {e}")

    def apply_styles_recursive(self, widget):
        """递归应用样式到所有子控件"""
        try:
            # 获取控件的类名
            widget_class = widget.winfo_class()

            # 根据控件类型应用样式
            if widget_class in ['TButton', 'Button']:
                widget.configure(font=('Microsoft YaHei', 9))
            elif widget_class in ['TLabel', 'Label']:
                widget.configure(font=('Microsoft YaHei', 9))
            elif widget_class in ['TEntry', 'Entry']:
                widget.configure(font=('Consolas', 9))
            elif widget_class in ['TListbox', 'Listbox']:
                widget.configure(font=('Consolas', 9))

        except Exception as e:
            pass  # 忽略无法设置样式的控件

        # 递归处理子控件
        try:
            for child in widget.winfo_children():
                self.apply_styles_recursive(child)
        except:
            pass

    def create_ui(self):
        # 创建主框架
        main_frame = tk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 创建标签页
        self.notebook = ttk.Notebook(main_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)

        # 创建各标签页
        self.image_to_pdf_frame = tk.Frame(self.notebook)
        self.multi_image_to_pdf_frame = tk.Frame(self.notebook)
        self.pdf_merge_frame = tk.Frame(self.notebook)

        self.notebook.add(self.image_to_pdf_frame, text="双图转PDF")
        self.notebook.add(self.multi_image_to_pdf_frame, text="多图转PDF")
        self.notebook.add(self.pdf_merge_frame, text="PDF合并")

        # 创建各标签页内容
        self.create_image_to_pdf_ui()
        self.create_multi_image_to_pdf_ui()
        self.create_pdf_merge_ui()

    def create_image_to_pdf_ui(self):
        """创建双图片转PDF界面"""
        # 主容器
        main_frame = tk.Frame(self.image_to_pdf_frame, padx=10, pady=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 标题
        title_label = tk.Label(main_frame, text="双图片转PDF工具", font=("", 12, "bold"))
        title_label.pack(pady=(0, 10))

        # 创建各个功能区域
        self.create_image_selection_ui(main_frame)
        self.create_preview_ui(main_frame)
        self.create_settings_ui(main_frame)
        self.create_generation_ui(main_frame)

    def create_multi_image_to_pdf_ui(self):
        """创建多图片转PDF界面"""
        main_frame = tk.Frame(self.multi_image_to_pdf_frame, padx=10, pady=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        title_label = tk.Label(main_frame, text="多图片转PDF工具", font=("", 12, "bold"))
        title_label.pack(pady=(0, 10))

        self.create_multi_image_selection_ui(main_frame)
        self.create_multi_image_list_ui(main_frame)
        self.create_multi_settings_ui(main_frame)
        self.create_multi_generation_ui(main_frame)

    def create_pdf_merge_ui(self):
        """创建PDF合并界面"""
        main_frame = tk.Frame(self.pdf_merge_frame, padx=10, pady=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        title_label = tk.Label(main_frame, text="PDF文件合并工具", font=("", 12, "bold"))
        title_label.pack(pady=(0, 10))

        self.create_pdf_selection_ui(main_frame)
        self.create_pdf_list_ui(main_frame)
        self.create_merge_settings_ui(main_frame)
        self.create_merge_execution_ui(main_frame)

    def create_image_selection_ui(self, parent):
        """创建图片选择界面"""
        frame = tk.LabelFrame(parent, text="选择图片", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        # 第一张图片选择
        self.create_file_selection_row(frame, "第一张图片:", self.image1_path, self.browse_image1)

        # 第二张图片选择
        self.create_file_selection_row(frame, "第二张图片:", self.image2_path, self.browse_image2)

        # 输出路径选择
        self.create_file_selection_row(frame, "输出PDF:", self.output_path, self.browse_output, is_output=True)

    def create_multi_image_selection_ui(self, parent):
        """创建多图片选择界面"""
        frame = tk.LabelFrame(parent, text="选择图片", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        # 按钮容器
        button_frame = tk.Frame(frame)
        button_frame.pack(fill=tk.X, pady=5)

        # 操作按钮
        add_files_button = tk.Button(button_frame, text="添加图片文件", command=self.add_image_files)
        add_files_button.pack(side=tk.LEFT, padx=(0, 5))

        add_folder_button = tk.Button(button_frame, text="添加图片文件夹", command=self.add_image_folder)
        add_folder_button.pack(side=tk.LEFT, padx=5)

        clear_list_button = tk.Button(button_frame, text="清空列表", command=self.clear_image_list)
        clear_list_button.pack(side=tk.LEFT, padx=5)

        # 输出路径选择
        output_frame = tk.Frame(frame)
        output_frame.pack(fill=tk.X, pady=5)

        output_label = tk.Label(output_frame, text="输出PDF:", width=10, anchor='w')
        output_label.pack(side=tk.LEFT)

        output_entry = tk.Entry(output_frame, textvariable=self.multi_output_path, width=50)
        output_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        output_button = tk.Button(output_frame, text="浏览", command=self.browse_multi_output)
        output_button.pack(side=tk.RIGHT)

    def create_file_selection_row(self, parent, label_text, path_var, browse_command, is_output=False):
        """创建文件选择行"""
        frame = tk.Frame(parent)
        frame.pack(fill=tk.X, pady=5)

        label = tk.Label(frame, text=label_text, width=10, anchor='w')
        label.pack(side=tk.LEFT)

        entry = tk.Entry(frame, textvariable=path_var, width=50,
                         state='readonly' if not is_output else 'normal')
        entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        browse_button = tk.Button(frame, text="浏览", command=browse_command)
        browse_button.pack(side=tk.RIGHT)

    def create_multi_image_list_ui(self, parent):
        """创建多图片文件列表界面"""
        frame = tk.LabelFrame(parent, text="图片列表管理", padx=10, pady=10)
        frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # 创建列表框和滚动条
        list_container = tk.Frame(frame)
        list_container.pack(fill=tk.BOTH, expand=True)

        # 滚动条
        scrollbar = ttk.Scrollbar(list_container)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 文件列表框
        self.image_listbox = tk.Listbox(list_container,
                                        yscrollcommand=scrollbar.set,
                                        selectmode=tk.EXTENDED,
                                        height=8)
        self.image_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=self.image_listbox.yview)

        # 列表操作按钮
        list_buttons_frame = tk.Frame(frame)
        list_buttons_frame.pack(fill=tk.X, pady=5)

        move_up_button = tk.Button(list_buttons_frame, text="上移", command=self.move_image_up)
        move_up_button.pack(side=tk.LEFT, padx=(0, 5))

        move_down_button = tk.Button(list_buttons_frame, text="下移", command=self.move_image_down)
        move_down_button.pack(side=tk.LEFT, padx=5)

        remove_button = tk.Button(list_buttons_frame, text="移除选中", command=self.remove_selected_images)
        remove_button.pack(side=tk.LEFT, padx=5)

    def create_preview_ui(self, parent):
        """创建图片预览界面"""
        frame = tk.LabelFrame(parent, text="预览图片", padx=10, pady=10)
        frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # 初始提示文本
        self.preview_text = tk.Label(frame, text="请选择图片以预览")
        self.preview_text.pack(expand=True)

        self.preview_img1 = None
        self.preview_img2 = None

    def create_settings_ui(self, parent):
        """创建设置界面"""
        frame = tk.LabelFrame(parent, text="生成设置", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        self.create_scale_setting(frame, self.scale_factor, self.on_scale_change, "scale_label")

    def create_multi_settings_ui(self, parent):
        """创建多图片设置界面"""
        frame = tk.LabelFrame(parent, text="生成设置", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        self.create_scale_setting(frame, self.multi_scale_factor, self.on_multi_scale_change, "multi_scale_label")

    def create_scale_setting(self, parent, scale_var, change_callback, label_name):
        """创建缩放比例设置"""
        scale_frame = tk.Frame(parent)
        scale_frame.pack(fill=tk.X, pady=5)

        scale_label = tk.Label(scale_frame, text="图片缩放比例:", width=12, anchor='w')
        scale_label.pack(side=tk.LEFT)

        # 缩放比例滑块
        scale_slider = ttk.Scale(scale_frame, from_=0.3, to=1.0,
                                 variable=scale_var,
                                 orient=tk.HORIZONTAL)
        scale_slider.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        # 缩放比例显示
        scale_display_label = tk.Label(scale_frame, text="80%", width=5)
        scale_display_label.pack(side=tk.RIGHT)

        # 绑定滑块事件
        scale_slider.configure(command=change_callback)

        # 存储标签引用
        if label_name == "scale_label":
            self.scale_label = scale_display_label
        else:
            self.multi_scale_label = scale_display_label

    def create_generation_ui(self, parent):
        """创建生成界面"""
        frame = tk.LabelFrame(parent, text="生成PDF", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        self.create_action_ui(frame, self.generate_pdf, self.open_pdf_file,
                              self.open_output_folder, "open_file_button", "progress", "status_label")

    def create_multi_generation_ui(self, parent):
        """创建多图片生成界面"""
        frame = tk.LabelFrame(parent, text="生成PDF", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        self.create_action_ui(frame, self.generate_multi_pdf, self.open_multi_pdf_file,
                              self.open_multi_output_folder, "open_multi_file_button",
                              "multi_progress", "multi_status_label")

    def create_action_ui(self, parent, generate_command, open_file_command,
                         open_folder_command, open_button_name, progress_name, status_name):
        """创建操作界面"""
        # 按钮容器
        button_frame = tk.Frame(parent)
        button_frame.pack(fill=tk.X, pady=5)

        # 主要操作按钮
        generate_button = tk.Button(button_frame, text="生成PDF", command=generate_command)
        generate_button.pack(side=tk.LEFT, padx=(0, 5))

        # 打开文件按钮
        open_file_button = tk.Button(button_frame, text="打开PDF文件", command=open_file_command)
        open_file_button.pack(side=tk.LEFT, padx=5)
        setattr(self, open_button_name, open_file_button)

        # 打开文件夹按钮
        open_folder_button = tk.Button(button_frame, text="打开输出文件夹", command=open_folder_command)
        open_folder_button.pack(side=tk.LEFT, padx=5)

        # 进度条
        progress = ttk.Progressbar(parent, mode='indeterminate')
        progress.pack(fill=tk.X, pady=5)
        setattr(self, progress_name, progress)

        # 状态标签
        status_label = tk.Label(parent, text="准备就绪")
        status_label.pack()
        setattr(self, status_name, status_label)

    def create_pdf_selection_ui(self, parent):
        """创建PDF文件选择界面"""
        frame = tk.LabelFrame(parent, text="选择PDF文件", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        # 按钮容器
        button_frame = tk.Frame(frame)
        button_frame.pack(fill=tk.X, pady=5)

        # 操作按钮
        add_files_button = tk.Button(button_frame, text="添加PDF文件", command=self.add_pdf_files)
        add_files_button.pack(side=tk.LEFT, padx=(0, 5))

        add_folder_button = tk.Button(button_frame, text="添加文件夹", command=self.add_pdf_folder)
        add_folder_button.pack(side=tk.LEFT, padx=5)

        clear_list_button = tk.Button(button_frame, text="清空列表", command=self.clear_pdf_list)
        clear_list_button.pack(side=tk.LEFT, padx=5)

    def create_pdf_list_ui(self, parent):
        """创建PDF文件列表界面"""
        frame = tk.LabelFrame(parent, text="文件列表管理", padx=10, pady=10)
        frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # 创建列表框和滚动条
        list_container = tk.Frame(frame)
        list_container.pack(fill=tk.BOTH, expand=True)

        # 滚动条
        scrollbar = ttk.Scrollbar(list_container)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 文件列表框
        self.pdf_listbox = tk.Listbox(list_container,
                                      yscrollcommand=scrollbar.set,
                                      selectmode=tk.EXTENDED,
                                      height=8)
        self.pdf_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=self.pdf_listbox.yview)

        # 列表操作按钮
        list_buttons_frame = tk.Frame(frame)
        list_buttons_frame.pack(fill=tk.X, pady=5)

        move_up_button = tk.Button(list_buttons_frame, text="上移", command=self.move_pdf_up)
        move_up_button.pack(side=tk.LEFT, padx=(0, 5))

        move_down_button = tk.Button(list_buttons_frame, text="下移", command=self.move_pdf_down)
        move_down_button.pack(side=tk.LEFT, padx=5)

        remove_button = tk.Button(list_buttons_frame, text="移除选中", command=self.remove_selected_pdfs)
        remove_button.pack(side=tk.LEFT, padx=5)

    def create_merge_settings_ui(self, parent):
        """创建合并设置界面"""
        frame = tk.LabelFrame(parent, text="合并设置", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        # 输出路径设置
        output_frame = tk.Frame(frame)
        output_frame.pack(fill=tk.X, pady=5)

        output_label = tk.Label(output_frame, text="输出文件:", width=10, anchor='w')
        output_label.pack(side=tk.LEFT)

        output_entry = tk.Entry(output_frame, textvariable=self.merge_output_path, width=50)
        output_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        output_button = tk.Button(output_frame, text="浏览", command=self.browse_merge_output)
        output_button.pack(side=tk.RIGHT)

    def create_merge_execution_ui(self, parent):
        """创建合并执行界面"""
        frame = tk.LabelFrame(parent, text="执行合并", padx=10, pady=10)
        frame.pack(fill=tk.X, pady=5)

        # 按钮容器
        button_frame = tk.Frame(frame)
        button_frame.pack(fill=tk.X, pady=5)

        # 合并按钮
        merge_button = tk.Button(button_frame, text="开始合并", command=self.merge_pdfs)
        merge_button.pack(side=tk.LEFT, padx=(0, 5))

        # 打开合并文件按钮
        self.open_merge_file_button = tk.Button(button_frame, text="打开合并文件", command=self.open_merged_pdf)
        self.open_merge_file_button.pack(side=tk.LEFT, padx=5)

        # 打开合并文件夹按钮
        open_merge_folder_button = tk.Button(button_frame, text="打开输出文件夹", command=self.open_merge_output_folder)
        open_merge_folder_button.pack(side=tk.LEFT, padx=5)

        # 进度条
        self.merge_progress = ttk.Progressbar(frame, mode='indeterminate')
        self.merge_progress.pack(fill=tk.X, pady=5)

        # 状态标签
        self.merge_status_label = tk.Label(frame, text="准备就绪")
        self.merge_status_label.pack()

    # 事件处理方法
    def on_scale_change(self, value):
        """缩放比例滑块变化事件"""
        scale_percent = int(float(value) * 100)
        self.scale_label.config(text=f"{scale_percent}%")

    def on_multi_scale_change(self, value):
        """多图片缩放比例滑块变化事件"""
        scale_percent = int(float(value) * 100)
        self.multi_scale_label.config(text=f"{scale_percent}%")

    def browse_image1(self):
        filename = filedialog.askopenfilename(
            title="选择第一张图片",
            filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif *.bmp")]
        )
        if filename:
            self.image1_path.set(filename)
            self.update_preview()

    def browse_image2(self):
        filename = filedialog.askopenfilename(
            title="选择第二张图片",
            filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif *.bmp")]
        )
        if filename:
            self.image2_path.set(filename)
            self.update_preview()

    def browse_output(self):
        filename = filedialog.asksaveasfilename(
            title="保存PDF文件",
            defaultextension=".pdf",
            filetypes=[("PDF文件", "*.pdf")]
        )
        if filename:
            self.output_path.set(filename)

    def browse_multi_output(self):
        filename = filedialog.asksaveasfilename(
            title="保存PDF文件",
            defaultextension=".pdf",
            filetypes=[("PDF文件", "*.pdf")]
        )
        if filename:
            self.multi_output_path.set(filename)

    def browse_merge_output(self):
        filename = filedialog.asksaveasfilename(
            title="保存合并后的PDF文件",
            defaultextension=".pdf",
            filetypes=[("PDF文件", "*.pdf")]
        )
        if filename:
            self.merge_output_path.set(filename)

    def update_preview(self):
        """更新图片预览"""
        for widget in self.preview_text.master.winfo_children():
            widget.destroy()

        has_img1 = os.path.exists(self.image1_path.get()) if self.image1_path.get() else False
        has_img2 = os.path.exists(self.image2_path.get()) if self.image2_path.get() else False

        if not has_img1 and not has_img2:
            self.preview_text = tk.Label(self.preview_text.master, text="请选择图片以预览")
            self.preview_text.pack(expand=True)
            return

        preview_frame = tk.Frame(self.preview_text.master)
        preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        if has_img1:
            try:
                img1 = Image.open(self.image1_path.get())
                img1.thumbnail((150, 150))
                self.preview_img1 = ImageTk.PhotoImage(img1)

                img1_frame = tk.Frame(preview_frame, relief='solid', bd=1)
                img1_frame.pack(side=tk.LEFT, padx=10, pady=10)

                img1_label = tk.Label(img1_frame, image=self.preview_img1)
                img1_label.pack(padx=5, pady=5)

                original_img1 = Image.open(self.image1_path.get())
                img1_info = f"图片1: {original_img1.size[0]}x{original_img1.size[1]}"
                img1_text = tk.Label(img1_frame, text=img1_info)
                img1_text.pack(pady=(0, 5))
            except Exception as e:
                error_label = tk.Label(preview_frame, text=f"图片1加载失败: {str(e)}")
                error_label.pack(side=tk.LEFT, padx=10)

        if has_img2:
            try:
                img2 = Image.open(self.image2_path.get())
                img2.thumbnail((150, 150))
                self.preview_img2 = ImageTk.PhotoImage(img2)

                img2_frame = tk.Frame(preview_frame, relief='solid', bd=1)
                img2_frame.pack(side=tk.LEFT, padx=10, pady=10)

                img2_label = tk.Label(img2_frame, image=self.preview_img2)
                img2_label.pack(padx=5, pady=5)

                original_img2 = Image.open(self.image2_path.get())
                img2_info = f"图片2: {original_img2.size[0]}x{original_img2.size[1]}"
                img2_text = tk.Label(img2_frame, text=img2_info)
                img2_text.pack(pady=(0, 5))
            except Exception as e:
                error_label = tk.Label(preview_frame, text=f"图片2加载失败: {str(e)}")
                error_label.pack(side=tk.LEFT, padx=10)

    # 多图片转PDF相关方法
    def add_image_files(self):
        files = filedialog.askopenfilenames(
            title="选择图片文件",
            filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif *.bmp")]
        )
        if files:
            for file in files:
                if file not in self.multi_images:
                    self.multi_images.append(file)
                    self.image_listbox.insert(tk.END, os.path.basename(file))

    def add_image_folder(self):
        folder = filedialog.askdirectory(title="选择包含图片文件的文件夹")
        if folder:
            for file in os.listdir(folder):
                if file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
                    full_path = os.path.join(folder, file)
                    if full_path not in self.multi_images:
                        self.multi_images.append(full_path)
                        self.image_listbox.insert(tk.END, file)

    def clear_image_list(self):
        self.multi_images.clear()
        self.image_listbox.delete(0, tk.END)

    def remove_selected_images(self):
        selected_indices = self.image_listbox.curselection()
        if selected_indices:
            for index in selected_indices[::-1]:
                self.multi_images.pop(index)
                self.image_listbox.delete(index)

    def move_image_up(self):
        selected_indices = self.image_listbox.curselection()
        if selected_indices and selected_indices[0] > 0:
            index = selected_indices[0]
            self.multi_images[index], self.multi_images[index - 1] = self.multi_images[index - 1], self.multi_images[
                index]
            self.refresh_image_listbox()
            self.image_listbox.selection_set(index - 1)

    def move_image_down(self):
        selected_indices = self.image_listbox.curselection()
        if selected_indices and selected_indices[0] < len(self.multi_images) - 1:
            index = selected_indices[0]
            self.multi_images[index], self.multi_images[index + 1] = self.multi_images[index + 1], self.multi_images[
                index]
            self.refresh_image_listbox()
            self.image_listbox.selection_set(index + 1)

    def refresh_image_listbox(self):
        self.image_listbox.delete(0, tk.END)
        for file_path in self.multi_images:
            self.image_listbox.insert(tk.END, os.path.basename(file_path))

    # PDF合并相关方法
    def add_pdf_files(self):
        files = filedialog.askopenfilenames(
            title="选择PDF文件",
            filetypes=[("PDF文件", "*.pdf")]
        )
        if files:
            for file in files:
                if file not in self.pdf_files:
                    self.pdf_files.append(file)
                    self.pdf_listbox.insert(tk.END, os.path.basename(file))

    def add_pdf_folder(self):
        folder = filedialog.askdirectory(title="选择包含PDF文件的文件夹")
        if folder:
            for file in os.listdir(folder):
                if file.lower().endswith('.pdf'):
                    full_path = os.path.join(folder, file)
                    if full_path not in self.pdf_files:
                        self.pdf_files.append(full_path)
                        self.pdf_listbox.insert(tk.END, file)

    def clear_pdf_list(self):
        self.pdf_files.clear()
        self.pdf_listbox.delete(0, tk.END)

    def remove_selected_pdfs(self):
        selected_indices = self.pdf_listbox.curselection()
        if selected_indices:
            for index in selected_indices[::-1]:
                self.pdf_files.pop(index)
                self.pdf_listbox.delete(index)

    def move_pdf_up(self):
        selected_indices = self.pdf_listbox.curselection()
        if selected_indices and selected_indices[0] > 0:
            index = selected_indices[0]
            self.pdf_files[index], self.pdf_files[index - 1] = self.pdf_files[index - 1], self.pdf_files[index]
            self.refresh_pdf_listbox()
            self.pdf_listbox.selection_set(index - 1)

    def move_pdf_down(self):
        selected_indices = self.pdf_listbox.curselection()
        if selected_indices and selected_indices[0] < len(self.pdf_files) - 1:
            index = selected_indices[0]
            self.pdf_files[index], self.pdf_files[index + 1] = self.pdf_files[index + 1], self.pdf_files[index]
            self.refresh_pdf_listbox()
            self.pdf_listbox.selection_set(index + 1)

    def refresh_pdf_listbox(self):
        self.pdf_listbox.delete(0, tk.END)
        for file_path in self.pdf_files:
            self.pdf_listbox.insert(tk.END, os.path.basename(file_path))

    # 验证和生成方法
    def validate_inputs(self):
        if not self.image1_path.get() or not os.path.exists(self.image1_path.get()):
            messagebox.showerror("错误", "请选择有效的第一张图片")
            return False
        if not self.image2_path.get() or not os.path.exists(self.image2_path.get()):
            messagebox.showerror("错误", "请选择有效的第二张图片")
            return False
        if not self.output_path.get():
            messagebox.showerror("错误", "请指定输出PDF路径")
            return False
        if not self.has_reportlab and not self.has_img2pdf:
            messagebox.showerror("错误", "没有可用的PDF生成库")
            return False
        return True

    def validate_multi_inputs(self):
        if not self.multi_images:
            messagebox.showerror("错误", "请至少添加一张图片")
            return False
        if not self.multi_output_path.get():
            messagebox.showerror("错误", "请指定输出PDF路径")
            return False
        if not self.has_reportlab and not self.has_img2pdf:
            messagebox.showerror("错误", "没有可用的PDF生成库")
            return False
        return True

    def generate_pdf(self):
        if not self.validate_inputs():
            return

        self.progress.start()
        self.status_label.config(text="正在生成PDF...")
        self.root.update()

        success = False
        error_msg = ""
        used_method = ""

        if self.has_reportlab:
            success, error_msg = self.generate_with_reportlab()
            if success:
                used_method = "reportlab"

        if not success and self.has_img2pdf:
            success, error_msg = self.generate_with_img2pdf()
            if success:
                used_method = "img2pdf"

        self.progress.stop()

        if success:
            self.status_label.config(text=f"PDF已生成 ({used_method})")
            self.open_file_button.config(state='normal')
            messagebox.showinfo("成功", f"PDF已成功生成!\n方法: {used_method}")
        else:
            self.status_label.config(text="生成失败")
            messagebox.showerror("错误", f"所有PDF生成方法都失败了:\n{error_msg}")

    def generate_multi_pdf(self):
        if not self.validate_multi_inputs():
            return

        self.multi_progress.start()
        self.multi_status_label.config(text="正在生成PDF...")
        self.root.update()

        success = False
        error_msg = ""
        used_method = ""

        if self.has_reportlab:
            success, error_msg = self.generate_multi_with_reportlab()
            if success:
                used_method = "reportlab"

        if not success and self.has_img2pdf:
            success, error_msg = self.generate_multi_with_img2pdf()
            if success:
                used_method = "img2pdf"

        self.multi_progress.stop()

        if success:
            self.multi_status_label.config(text=f"PDF已生成 ({used_method})")
            self.open_multi_file_button.config(state='normal')
            messagebox.showinfo("成功", f"PDF已成功生成!\n图片数量: {len(self.multi_images)}")
        else:
            self.multi_status_label.config(text="生成失败")
            messagebox.showerror("错误", f"所有PDF生成方法都失败了:\n{error_msg}")

    def generate_with_reportlab(self):
        try:
            from reportlab.pdfgen import canvas
            from reportlab.lib.pagesizes import A4

            pdf_canvas = canvas.Canvas(self.output_path.get(), pagesize=A4)
            page_width, page_height = A4

            img1 = Image.open(self.image1_path.get())
            img2 = Image.open(self.image2_path.get())

            img1_size = img1.size
            img2_size = img2.size

            img1_width, img1_height, img2_width, img2_height = self.calculate_image_size(
                img1_size, img2_size, page_width, page_height
            )

            margin_x_img1 = (page_width - img1_width) / 2
            margin_x_img2 = (page_width - img2_width) / 2

            y1 = page_height - img1_height - 80
            y2 = y1 - img2_height - 40

            pdf_canvas.drawImage(
                self.image1_path.get(),
                margin_x_img1, y1,
                width=img1_width,
                height=img1_height,
                preserveAspectRatio=True
            )

            pdf_canvas.drawImage(
                self.image2_path.get(),
                margin_x_img2, y2,
                width=img2_width,
                height=img2_height,
                preserveAspectRatio=True
            )

            pdf_canvas.setFont("Helvetica", 12)
            pdf_canvas.drawString(50, page_height - 30,
                                  f"图片转PDF生成结果 - 缩放比例: {int(self.scale_factor.get() * 100)}%")

            pdf_canvas.save()
            return True, ""

        except Exception as e:
            error_msg = str(e)
            if "usedforsecurity" in error_msg:
                error_msg = "reportlab版本兼容性问题"
            return False, f"reportlab错误: {error_msg}"

    def generate_multi_with_reportlab(self):
        try:
            from reportlab.pdfgen import canvas
            from reportlab.lib.pagesizes import A4

            pdf_canvas = canvas.Canvas(self.multi_output_path.get(), pagesize=A4)
            page_width, page_height = A4

            for i, image_path in enumerate(self.multi_images):
                if i > 0:
                    pdf_canvas.showPage()

                try:
                    img = Image.open(image_path)
                    img_size = img.size

                    img_width, img_height = self.calculate_multi_image_size(
                        img_size, page_width, page_height
                    )

                    margin_x = (page_width - img_width) / 2
                    margin_y = (page_height - img_height) / 2

                    pdf_canvas.drawImage(
                        image_path,
                        margin_x, margin_y,
                        width=img_width,
                        height=img_height,
                        preserveAspectRatio=True
                    )

                    pdf_canvas.setFont("Helvetica", 10)
                    pdf_canvas.drawString(50, page_height - 30,
                                          f"第 {i + 1} 页 / 共 {len(self.multi_images)} 页")

                except Exception as img_error:
                    print(f"处理图片 {image_path} 时出错: {str(img_error)}")
                    continue

            pdf_canvas.save()
            return True, ""

        except Exception as e:
            error_msg = str(e)
            if "usedforsecurity" in error_msg:
                error_msg = "reportlab版本兼容性问题"
            return False, f"reportlab错误: {error_msg}"

    def generate_with_img2pdf(self):
        try:
            import img2pdf

            image_paths = []
            if self.image1_path.get() and os.path.exists(self.image1_path.get()):
                image_paths.append(self.image1_path.get())
            if self.image2_path.get() and os.path.exists(self.image2_path.get()):
                image_paths.append(self.image2_path.get())

            with open(self.output_path.get(), "wb") as f:
                f.write(img2pdf.convert(image_paths))

            return True, ""

        except Exception as e:
            return False, f"img2pdf错误: {str(e)}"

    def generate_multi_with_img2pdf(self):
        try:
            import img2pdf

            with open(self.multi_output_path.get(), "wb") as f:
                f.write(img2pdf.convert(self.multi_images))

            return True, ""

        except Exception as e:
            return False, f"img2pdf错误: {str(e)}"

    def calculate_image_size(self, img1_size, img2_size, page_width, page_height):
        scale = self.scale_factor.get()
        base_max_width = page_width * 0.9
        max_width = base_max_width * scale

        available_total_height = page_height - 150
        max_height_per_image = available_total_height / 2

        img1_ratio = img1_size[0] / img1_size[1]
        img1_width = min(max_width, img1_size[0] * scale)
        img1_height = img1_width / img1_ratio

        if img1_height > max_height_per_image:
            img1_height = max_height_per_image
            img1_width = img1_height * img1_ratio

        img2_ratio = img2_size[0] / img2_size[1]
        img2_width = min(max_width, img2_size[0] * scale)
        img2_height = img2_width / img2_ratio

        if img2_height > max_height_per_image:
            img2_height = max_height_per_image
            img2_width = img2_height * img2_ratio

        final_width = min(img1_width, img2_width, max_width)
        img1_height = final_width / img1_ratio
        img2_height = final_width / img2_ratio

        total_height = img1_height + img2_height + 50
        if total_height > available_total_height:
            height_scale_factor = available_total_height / total_height
            final_width *= height_scale_factor
            img1_height *= height_scale_factor
            img2_height *= height_scale_factor

        return final_width, img1_height, final_width, img2_height

    def calculate_multi_image_size(self, img_size, page_width, page_height):
        scale = self.multi_scale_factor.get()
        base_max_width = page_width * 0.9
        base_max_height = page_height * 0.8

        max_width = base_max_width * scale
        max_height = base_max_height * scale

        img_ratio = img_size[0] / img_size[1]
        img_width = min(max_width, img_size[0] * scale)
        img_height = img_width / img_ratio

        if img_height > max_height:
            img_height = max_height
            img_width = img_height * img_ratio

        if img_width > max_width:
            img_width = max_width
            img_height = img_width / img_ratio

        return img_width, img_height

    def merge_pdfs(self):
        if not self.pdf_files:
            messagebox.showerror("错误", "请先添加要合并的PDF文件")
            return

        if not self.merge_output_path.get():
            messagebox.showerror("错误", "请指定输出PDF路径")
            return

        if not self.has_pypdf2:
            messagebox.showerror("错误", "PDF合并功能需要PyPDF2库")
            return

        self.merge_progress.start()
        self.merge_status_label.config(text="正在合并PDF文件...")
        self.root.update()

        try:
            import PyPDF2

            pdf_merger = PyPDF2.PdfMerger()

            for pdf_file in self.pdf_files:
                pdf_merger.append(pdf_file)

            with open(self.merge_output_path.get(), 'wb') as output_file:
                pdf_merger.write(output_file)

            self.merge_progress.stop()
            self.merge_status_label.config(text="PDF合并完成")
            self.open_merge_file_button.config(state='normal')

            messagebox.showinfo("成功", f"PDF文件合并完成!\n合并了 {len(self.pdf_files)} 个PDF文件")

        except Exception as e:
            self.merge_progress.stop()
            self.merge_status_label.config(text="合并失败")
            messagebox.showerror("错误", f"合并PDF时出错:\n{str(e)}")

    # 文件打开方法
    def open_pdf_file(self):
        self.open_file(self.output_path.get())

    def open_multi_pdf_file(self):
        self.open_file(self.multi_output_path.get())

    def open_merged_pdf(self):
        self.open_file(self.merge_output_path.get())

    def open_output_folder(self):
        self.open_folder(os.path.dirname(self.output_path.get()))

    def open_multi_output_folder(self):
        self.open_folder(os.path.dirname(self.multi_output_path.get()))

    def open_merge_output_folder(self):
        self.open_folder(os.path.dirname(self.merge_output_path.get()))

    def open_file(self, file_path):
        if os.path.exists(file_path):
            try:
                if platform.system() == "Darwin":
                    subprocess.call(("open", file_path))
                elif platform.system() == "Windows":
                    os.startfile(file_path)
                else:
                    subprocess.call(("xdg-open", file_path))
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件:\n{str(e)}")
        else:
            messagebox.showwarning("警告", "文件不存在,请先生成文件")

    def open_folder(self, folder_path):
        if not folder_path:
            folder_path = "."
        if os.path.exists(folder_path):
            try:
                if platform.system() == "Darwin":
                    subprocess.call(("open", folder_path))
                elif platform.system() == "Windows":
                    os.startfile(folder_path)
                else:
                    subprocess.call(("xdg-open", folder_path))
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件夹:\n{str(e)}")
        else:
            messagebox.showwarning("警告", "文件夹不存在")


if __name__ == "__main__":
    root = tk.Tk()
    app = PDFGeneratorApp(root)
    root.mainloop()
相关推荐
数智化架构师-Aloong1 小时前
⚡️ PowerJob深度解析:Java生态下高并发分布式调度的终极选择
java·开发语言·分布式·系统架构
棒棒的皮皮1 小时前
【OpenCV】Python图像处理之读取与保存
图像处理·python·opencv
Godson_beginner1 小时前
Sa-Token (java权限认证框架)
java·开发语言
谢景行^顾1 小时前
numpy
开发语言·python·numpy
上天夭1 小时前
PyTorch的Dataloader模块解析
人工智能·pytorch·python
是一个Bug1 小时前
Spring Boot 的全局异常处理器
spring boot·后端·python
dTTb1 小时前
python元组和字典
python
2501_930707781 小时前
如何使用C#更改 PDF 文档的安全权限
安全·pdf