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()