小甲鱼Python学习笔记

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk, ImageDraw
import fitz  # PyMuPDF
 
import img2pdf
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
import os
import tempfile
import shutil
 
class InvoicePrinterApp:
    def __init__(self, root):
        self.root = root
        self.root.title("电子发票A4纸打印软件")
        self.root.geometry("1200x800")
        self.root.resizable(True, True)
         
        # 初始化变量
        self.uploaded_invoices = []  # 存储上传的发票信息
        self.current_preview_index = 0  # 当前预览索引
        self.export_dir = tempfile.mkdtemp()  # 临时文件夹
        self.current_page = 0  # 当前排版预览页码
         
        # 打印设置默认值
        self.print_settings = {
            'margin': 50,  # 边距(像素)
            'spacing': 50,  # 两张发票之间的间距(像素)
            'scale': 1.0,   # 缩放比例
            'orientation': 'portrait'  # 打印方向:portrait(纵向)或landscape(横向)
        }
         
        # 设置主题
        self.style = ttk.Style()
        self.style.configure("TButton", padding=6, font=('Arial', 10))
        self.style.configure("TLabel", font=('Arial', 10))
        self.style.configure("Treeview", font=('Arial', 9))
         
        # 创建主框架
        self.main_frame = ttk.Frame(self.root, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)
         
        # 创建菜单栏
        self.create_menu()
         
        # 创建左侧文件管理区
        self.create_file_management()
         
        # 创建右侧预览和控制区
        self.create_preview_control()
         
        # 创建状态栏
        self.create_status_bar()
     
    def create_menu(self):
        """创建菜单栏"""
        menubar = tk.Menu(self.root)
         
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        file_menu.add_command(label="上传发票", command=self.upload_invoices)
        file_menu.add_command(label="保存排版PDF", command=self.save_pdf)
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.root.quit)
        menubar.add_cascade(label="文件", menu=file_menu)
         
        # 帮助菜单
        help_menu = tk.Menu(menubar, tearoff=0)
        help_menu.add_command(label="关于", command=self.show_about)
        menubar.add_cascade(label="帮助", menu=help_menu)
         
        self.root.config(menu=menubar)
     
    def create_file_management(self):
        """创建文件管理区域"""
        # 左侧框架
        left_frame = ttk.Frame(self.main_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
         
        # 上传按钮
        upload_btn = ttk.Button(left_frame, text="上传发票", command=self.upload_invoices)
        upload_btn.pack(fill=tk.X, pady=(0, 10))
         
        # 发票列表
        list_frame = ttk.LabelFrame(left_frame, text="已上传发票")
        list_frame.pack(fill=tk.BOTH, expand=True)
         
        # 列表控件,支持拖拽
        columns = ("#1", "#2", "#3")
        self.invoice_tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="extended")
        self.invoice_tree.heading("#1", text="序号")
        self.invoice_tree.heading("#2", text="文件名")
        self.invoice_tree.heading("#3", text="格式")
         
        self.invoice_tree.column("#1", width=50, anchor=tk.CENTER)
        self.invoice_tree.column("#2", width=200, anchor=tk.W)
        self.invoice_tree.column("#3", width=80, anchor=tk.CENTER)
         
        # 滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.invoice_tree.yview)
        self.invoice_tree.configure(yscroll=scrollbar.set)
         
        self.invoice_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
         
        # 列表事件
        self.invoice_tree.bind("<<TreeviewSelect>>", self.on_invoice_select)
         
        # 控制按钮
        btn_frame = ttk.Frame(left_frame)
        btn_frame.pack(fill=tk.X, pady=10)
         
        # 移动按钮
        move_frame = ttk.Frame(left_frame)
        move_frame.pack(fill=tk.X, pady=5)
         
        move_up_btn = ttk.Button(move_frame, text="上移", command=self.move_up)
        move_up_btn.pack(side=tk.LEFT, padx=(0, 5), fill=tk.X, expand=True)
         
        move_down_btn = ttk.Button(move_frame, text="下移", command=self.move_down)
        move_down_btn.pack(side=tk.LEFT, fill=tk.X, expand=True)
         
        # 删除和清空按钮
        delete_btn = ttk.Button(btn_frame, text="删除选中", command=self.delete_selected)
        delete_btn.pack(side=tk.LEFT, padx=(0, 5), fill=tk.X, expand=True)
         
        clear_btn = ttk.Button(btn_frame, text="清空列表", command=self.clear_all)
        clear_btn.pack(side=tk.LEFT, fill=tk.X, expand=True)
     
    def create_preview_control(self):
        """创建预览和控制区域"""
        # 右侧框架
        right_frame = ttk.Frame(self.main_frame)
        right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
         
        # 预览区域
        preview_frame = ttk.LabelFrame(right_frame, text="预览")
        preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
         
        # 预览画布
        self.preview_canvas = tk.Canvas(preview_frame, bg="white", relief=tk.SUNKEN, bd=2)
        self.preview_canvas.pack(fill=tk.BOTH, expand=True)
         
        # 翻页控件框架
        self.pagination_frame = ttk.Frame(preview_frame)
        self.pagination_frame.pack(fill=tk.X, pady=5)
         
        # 上一页按钮
        self.prev_btn = ttk.Button(self.pagination_frame, text="上一页", command=self.prev_page, state=tk.DISABLED)
        self.prev_btn.pack(side=tk.LEFT, padx=5)
         
        # 页码显示
        self.page_label = ttk.Label(self.pagination_frame, text="第 0 页 / 共 0 页")
        self.page_label.pack(side=tk.LEFT, padx=5)
         
        # 下一页按钮
        self.next_btn = ttk.Button(self.pagination_frame, text="下一页", command=self.next_page, state=tk.DISABLED)
        self.next_btn.pack(side=tk.LEFT, padx=5)
         
        # 控制区域
        control_frame = ttk.Frame(right_frame)
        control_frame.pack(fill=tk.X)
         
        # 排版按钮
        layout_btn = ttk.Button(control_frame, text="自动排版", command=self.auto_layout)
        layout_btn.pack(side=tk.LEFT, padx=(0, 5), fill=tk.X, expand=True)
         
        # 保存按钮
        save_btn = ttk.Button(control_frame, text="保存PDF", command=self.save_pdf)
        save_btn.pack(side=tk.LEFT, fill=tk.X, expand=True)
     
    def create_status_bar(self):
        """创建状态栏"""
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
         
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
     
    def upload_invoices(self):
        """上传发票文件"""
        # 支持的文件类型(仅PDF)
        filetypes = [
            ("PDF文件", "*.pdf"),
            ("所有文件", "*.*")
        ]
         
        # 打开文件选择对话框
        file_paths = filedialog.askopenfilenames(title="选择发票文件", filetypes=filetypes)
         
        if not file_paths:
            return
         
        # 处理上传的文件
        for file_path in file_paths:
            # 获取文件名和扩展名
            filename = os.path.basename(file_path)
            ext = os.path.splitext(filename)[1].lower()
             
            # 检查文件格式是否支持(仅PDF)
            if ext != ".pdf":
                messagebox.showerror("错误", f"仅支持PDF格式,不支持:{ext}")
                continue
             
            # 检查文件是否已上传
            if any(invoice["path"] == file_path for invoice in self.uploaded_invoices):
                continue
             
            # 添加到上传列表
            invoice_info = {
                "path": file_path,
                "filename": filename,
                "format": ext[1:],
                "image": None  # 存储转换后的图片
            }
            self.uploaded_invoices.append(invoice_info)
         
        # 更新发票列表
        self.update_invoice_list()
         
        # 更新状态栏
        self.status_var.set(f"已上传 {len(file_paths)} 个文件")
     
    def update_invoice_list(self):
        """更新发票列表"""
        # 清空现有列表
        for item in self.invoice_tree.get_children():
            self.invoice_tree.delete(item)
         
        # 添加新数据
        for i, invoice in enumerate(self.uploaded_invoices, 1):
            self.invoice_tree.insert("", tk.END, values=(i, invoice["filename"], invoice["format"]))
     
    def on_invoice_select(self, event):
        """选择发票时的事件处理"""
        selected_items = self.invoice_tree.selection()
        if not selected_items:
            return
         
        # 获取选中的第一个发票索引
        item = selected_items[0]
        index = int(self.invoice_tree.item(item, "values")[0]) - 1
         
        # 显示预览
        self.show_invoice_preview(index)
     
    def show_invoice_preview(self, index):
        """显示发票预览"""
        if index < 0 or index >= len(self.uploaded_invoices):
            return
         
        invoice = self.uploaded_invoices[index]
         
        # 如果还没有转换为图片,先转换
        if invoice["image"] is None:
            image = self.convert_to_image(invoice["path"], invoice["format"])
            if image:
                invoice["image"] = image
            else:
                return
         
        # 显示图片
        self.display_image(invoice["image"])
     
    def convert_to_image(self, file_path, file_format):
        """将不同格式的发票转换为图片"""
        try:
            if file_format == "pdf":
                return self.pdf_to_image(file_path)
            else:
                return None
        except Exception as e:
            messagebox.showerror("错误", f"转换失败:{str(e)}")
            return None
     
    def pdf_to_image(self, pdf_path):
        """PDF转换为图片"""
        doc = fitz.open(pdf_path)
        if doc.page_count == 0:
            return None
         
        # 只处理第一页
        page = doc.load_page(0)
        pix = page.get_pixmap(dpi=300)
        img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
        doc.close()
        return img
     
    def ofd_to_image(self, ofd_path):
        """OFD转换为图片"""
        # 创建一个与A4纸张相似比例的白色背景图片
        # 宽度和高度比例接近A4纸,确保排版时能正确排列
        width, height = 1240, 1754  # A4纸一半大小,适合单张发票
        img = Image.new('RGB', (width, height), color='white')
        draw = ImageDraw.Draw(img)
         
        # 使用支持中文的字体
        from PIL import ImageFont
        font = None
         
        # 尝试加载系统中支持中文的字体
        font_paths = [
            # Windows系统常用中文字体
            'C:/Windows/Fonts/simhei.ttf',  # 黑体
            'C:/Windows/Fonts/simsun.ttc',  # 宋体
            'C:/Windows/Fonts/msyh.ttc',    # 微软雅黑
            'C:/Windows/Fonts/msyhbd.ttc',  # 微软雅黑加粗
        ]
         
        # 尝试加载字体,使用较小的字号确保兼容性
        for font_path in font_paths:
            try:
                font = ImageFont.truetype(font_path, 24)
                break
            except:
                continue
         
        # 如果没有找到系统中文字体,使用默认字体
        if font is None:
            font = ImageFont.load_default()
         
        # 绘制OFD信息
        draw.text((50, 50), '电子发票', font=font, fill='black')
        draw.text((50, 100), '文件格式: OFD', font=font, fill='black')
        draw.text((50, 150), f'文件名: {os.path.basename(ofd_path)}', font=font, fill='black')
        draw.text((50, 250), 'OFD格式说明:', font=font, fill='blue')
        draw.text((50, 300), '1. OFD是我国自主研发的电子文件格式', font=font, fill='black')
        draw.text((50, 350), '2. 如需查看完整内容,请使用', font=font, fill='black')
        draw.text((50, 400), '   专门的OFD阅读器打开原始文件', font=font, fill='black')
        draw.text((50, 500), '3. 该文件已成功添加到排版列表', font=font, fill='black')
         
        # 绘制边框,模拟发票样式
        draw.rectangle([(20, 20), (width-20, height-20)], outline='black', width=2)
         
        return img
     
    def display_image(self, image):
        """在画布上显示图片"""
        # 清空画布
        self.preview_canvas.delete("all")
         
        # 获取画布尺寸
        canvas_width = self.preview_canvas.winfo_width()
        canvas_height = self.preview_canvas.winfo_height()
         
        if canvas_width == 1 or canvas_height == 1:
            # 画布还未初始化
            self.root.after(100, lambda: self.display_image(image))
            return
         
        # 调整图片大小以适应画布
        img_width, img_height = image.size
        scale = min(canvas_width / img_width, canvas_height / img_height) * 0.9
        new_width = int(img_width * scale)
        new_height = int(img_height * scale)
         
        resized_img = image.resize((new_width, new_height), Image.LANCZOS)
         
        # 转换为PhotoImage
        photo = ImageTk.PhotoImage(resized_img)
         
        # 居中显示
        x = (canvas_width - new_width) // 2
        y = (canvas_height - new_height) // 2
         
        # 保存引用,防止垃圾回收
        self.preview_photo = photo
         
        # 显示图片
        self.preview_canvas.create_image(x, y, anchor=tk.NW, image=photo)
     
    def auto_layout(self):
        """自动排版发票"""
        if len(self.uploaded_invoices) == 0:
            messagebox.showwarning("警告", "请先上传发票")
            return
         
        # 确保所有发票都已转换为图片
        for invoice in self.uploaded_invoices:
            if invoice["image"] is None:
                image = self.convert_to_image(invoice["path"], invoice["format"])
                if image:
                    invoice["image"] = image
                else:
                    messagebox.showerror("错误", f"无法转换发票:{invoice['filename']}")
                    return
         
        # 排版处理
        self.status_var.set("正在排版...")
        self.root.update_idletasks()
         
        # 生成排版后的A4图片
        self.layout_images = self.layout_invoices()
         
        # 显示第一页排版效果
        if self.layout_images:
            self.current_page = 0
            self.display_image(self.layout_images[0])
            self.update_pagination()
            self.status_var.set(f"排版完成,共生成 {len(self.layout_images)} 页")
     
    def layout_invoices(self):
        """将发票排版到A4纸上"""
        layout_images = []
         
        # 获取打印设置
        margin = self.print_settings['margin']
        spacing = self.print_settings['spacing']
        scale_factor = self.print_settings['scale']
        orientation = self.print_settings['orientation']
         
        # A4纸尺寸(300dpi)
        if orientation == 'portrait':
            a4_width, a4_height = 2480, 3508  # 纵向
        else:
            a4_width, a4_height = 3508, 2480  # 横向
         
        # 遍历发票,每两张一页
        for i in range(0, len(self.uploaded_invoices), 2):
            # 创建A4纸画布
            a4_img = Image.new("RGB", (a4_width, a4_height), color="white")
             
            # 计算每张发票的可用区域
            available_width = a4_width - 2 * margin
            available_height = (a4_height - 2 * margin - spacing) // 2
             
            # 处理第一张发票
            if i < len(self.uploaded_invoices):
                invoice1 = self.uploaded_invoices[i]
                img1 = invoice1["image"]
                 
                # 计算缩放比例
                base_scale = min(available_width / img1.width, available_height / img1.height)
                scale1 = base_scale * scale_factor
                new_width1 = int(img1.width * scale1)
                new_height1 = int(img1.height * scale1)
                 
                # 调整图片大小
                resized1 = img1.resize((new_width1, new_height1), Image.LANCZOS)
                 
                # 计算位置(居中)
                x1 = margin + (available_width - new_width1) // 2
                y1 = margin
                 
                # 粘贴到A4画布
                a4_img.paste(resized1, (x1, y1))
             
            # 处理第二张发票
            if i + 1 < len(self.uploaded_invoices):
                invoice2 = self.uploaded_invoices[i + 1]
                img2 = invoice2["image"]
                 
                # 计算缩放比例
                base_scale = min(available_width / img2.width, available_height / img2.height)
                scale2 = base_scale * scale_factor
                new_width2 = int(img2.width * scale2)
                new_height2 = int(img2.height * scale2)
                 
                # 调整图片大小
                resized2 = img2.resize((new_width2, new_height2), Image.LANCZOS)
                 
                # 计算位置(居中)
                x2 = margin + (available_width - new_width2) // 2
                y2 = margin + available_height + spacing
                 
                # 粘贴到A4画布
                a4_img.paste(resized2, (x2, y2))
             
            # 添加到排版列表
            layout_images.append(a4_img)
         
        return layout_images
     
    def print_invoices(self):
        """打印发票"""
        if not hasattr(self, 'layout_images') or not self.layout_images:
            messagebox.showwarning("警告", "请先进行排版")
            return
         
        # 生成临时PDF文件
        pdf_path = os.path.join(self.export_dir, "temp_invoices.pdf")
         
        # 将排版后的图片转换为PDF
        image_paths = []
        for i, img in enumerate(self.layout_images):
            img_path = os.path.join(self.export_dir, f"temp_page_{i}.jpg")
            # 保存为高质量JPEG,确保清晰度
            img.save(img_path, format="JPEG", quality=100, subsampling=0)
            image_paths.append(img_path)
         
        # 生成PDF,使用img2pdf的布局选项确保完整显示
        try:
            # 计算A4纸尺寸(像素)
            if self.print_settings['orientation'] == 'portrait':
                a4_width, a4_height = 2480, 3508
            else:
                a4_width, a4_height = 3508, 2480
             
            # 使用img2pdf生成PDF,设置页面大小和布局
            layout_fun = img2pdf.get_layout_fun((a4_width, a4_height))
            with open(pdf_path, "wb") as f:
                f.write(img2pdf.convert(image_paths, layout_fun=layout_fun))
        except Exception as e:
            messagebox.showerror("错误", f"生成PDF失败:{str(e)}")
            return
         
        # 调用系统打印
        self.status_var.set("正在打印...")
        self.root.update_idletasks()
         
        try:
            if os.name == 'nt':  # Windows
                os.startfile(pdf_path, "print")
            elif os.name == 'posix':  # macOS or Linux
                os.system(f"lp {pdf_path}")
            self.status_var.set("打印任务已发送")
        except Exception as e:
            messagebox.showerror("错误", f"打印失败:{str(e)}")
            self.status_var.set("打印失败")
     
    def save_pdf(self):
        """保存排版后的PDF文件"""
        if not hasattr(self, 'layout_images') or not self.layout_images:
            messagebox.showwarning("警告", "请先进行排版")
            return
         
        # 选择保存路径
        save_path = filedialog.asksaveasfilename(
            title="保存PDF文件",
            defaultextension=".pdf",
            filetypes=[("PDF文件", "*.pdf")]
        )
         
        if not save_path:
            return
         
        # 将排版后的图片转换为PDF
        image_paths = []
        for i, img in enumerate(self.layout_images):
            img_path = os.path.join(self.export_dir, f"temp_page_{i}.jpg")
            # 保存为高质量JPEG,确保清晰度
            img.save(img_path, format="JPEG", quality=100, subsampling=0)
            image_paths.append(img_path)
         
        # 生成PDF,使用img2pdf的布局选项确保完整显示
        try:
            # 计算A4纸尺寸(像素)
            if self.print_settings['orientation'] == 'portrait':
                a4_width, a4_height = 2480, 3508
            else:
                a4_width, a4_height = 3508, 2480
             
            # 使用img2pdf生成PDF,设置页面大小和布局
            layout_fun = img2pdf.get_layout_fun((a4_width, a4_height))
            with open(save_path, "wb") as f:
                f.write(img2pdf.convert(image_paths, layout_fun=layout_fun))
            messagebox.showinfo("成功", f"PDF文件已保存到:{save_path}")
            self.status_var.set(f"PDF已保存")
        except Exception as e:
            messagebox.showerror("错误", f"保存PDF失败:{str(e)}")
     
    def delete_selected(self):
        """删除选中的发票"""
        selected_items = self.invoice_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要删除的发票")
            return
         
        # 获取选中的索引
        indices_to_delete = []
        for item in selected_items:
            index = int(self.invoice_tree.item(item, "values")[0]) - 1
            indices_to_delete.append(index)
         
        # 按降序删除,避免索引偏移
        for index in sorted(indices_to_delete, reverse=True):
            del self.uploaded_invoices[index]
         
        # 更新列表
        self.update_invoice_list()
         
        # 清空预览
        self.preview_canvas.delete("all")
         
        # 重置分页
        self.reset_pagination()
         
        # 更新状态栏
        self.status_var.set(f"已删除 {len(selected_items)} 个文件")
     
    def clear_all(self):
        """清空所有发票"""
        if not self.uploaded_invoices:
            return
         
        if messagebox.askyesno("确认", "确定要清空所有上传的发票吗?"):
            self.uploaded_invoices.clear()
            self.update_invoice_list()
            self.preview_canvas.delete("all")
            self.reset_pagination()
            self.status_var.set("已清空所有文件")
     
    def print_settings(self):
        """打印设置"""
        # 创建打印设置对话框
        settings_window = tk.Toplevel(self.root)
        settings_window.title("打印设置")
        settings_window.geometry("400x350")
        settings_window.resizable(False, False)
        settings_window.transient(self.root)
        settings_window.grab_set()
         
        # 框架布局
        main_frame = ttk.Frame(settings_window, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
         
        # 边距设置
        margin_frame = ttk.LabelFrame(main_frame, text="边距设置")
        margin_frame.pack(fill=tk.X, pady=(0, 15))
         
        ttk.Label(margin_frame, text="边距(像素):").grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
        self.margin_var = tk.IntVar(value=self.print_settings['margin'])
        margin_spinbox = ttk.Spinbox(margin_frame, from_=0, to=200, textvariable=self.margin_var, width=10)
        margin_spinbox.grid(row=0, column=1, sticky=tk.W, pady=5, padx=5)
         
        # 间距设置
        spacing_frame = ttk.LabelFrame(main_frame, text="间距设置")
        spacing_frame.pack(fill=tk.X, pady=(0, 15))
         
        ttk.Label(spacing_frame, text="发票间距(像素):").grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
        self.spacing_var = tk.IntVar(value=self.print_settings['spacing'])
        spacing_spinbox = ttk.Spinbox(spacing_frame, from_=0, to=200, textvariable=self.spacing_var, width=10)
        spacing_spinbox.grid(row=0, column=1, sticky=tk.W, pady=5, padx=5)
         
        # 缩放设置
        scale_frame = ttk.LabelFrame(main_frame, text="缩放设置")
        scale_frame.pack(fill=tk.X, pady=(0, 15))
         
        ttk.Label(scale_frame, text="缩放比例:").grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
        self.scale_var = tk.DoubleVar(value=self.print_settings['scale'])
        scale_spinbox = ttk.Spinbox(scale_frame, from_=0.5, to=2.0, increment=0.1, textvariable=self.scale_var, width=10)
        scale_spinbox.grid(row=0, column=1, sticky=tk.W, pady=5, padx=5)
         
        # 方向设置
        orientation_frame = ttk.LabelFrame(main_frame, text="打印方向")
        orientation_frame.pack(fill=tk.X, pady=(0, 15))
         
        self.orientation_var = tk.StringVar(value=self.print_settings['orientation'])
         
        ttk.Radiobutton(orientation_frame, text="纵向", variable=self.orientation_var, value="portrait").grid(
            row=0, column=0, sticky=tk.W, pady=5, padx=5)
        ttk.Radiobutton(orientation_frame, text="横向", variable=self.orientation_var, value="landscape").grid(
            row=0, column=1, sticky=tk.W, pady=5, padx=5)
         
        # 按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=10)
         
        # 确定按钮
        ok_button = ttk.Button(button_frame, text="确定", command=lambda: self.save_print_settings(settings_window))
        ok_button.pack(side=tk.RIGHT, padx=(5, 0))
         
        # 取消按钮
        cancel_button = ttk.Button(button_frame, text="取消", command=settings_window.destroy)
        cancel_button.pack(side=tk.RIGHT)
     
    def save_print_settings(self, window):
        """保存打印设置"""
        # 更新打印设置
        self.print_settings['margin'] = self.margin_var.get()
        self.print_settings['spacing'] = self.spacing_var.get()
        self.print_settings['scale'] = self.scale_var.get()
        self.print_settings['orientation'] = self.orientation_var.get()
         
        # 关闭窗口
        window.destroy()
         
        # 显示成功消息
        messagebox.showinfo("成功", "打印设置已保存")
     
    def move_up(self):
        """上移选中的发票"""
        selected_items = self.invoice_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要移动的发票")
            return
         
        item = selected_items[0]
        index = self.invoice_tree.index(item)
         
        if index > 0:
            # 交换位置
            self.uploaded_invoices[index], self.uploaded_invoices[index - 1] = \
                self.uploaded_invoices[index - 1], self.uploaded_invoices[index]
             
            # 更新列表
            self.update_invoice_list()
             
            # 重新选中移动后的项
            new_item = self.invoice_tree.get_children()[index - 1]
            self.invoice_tree.selection_set(new_item)
     
    def move_down(self):
        """下移选中的发票"""
        selected_items = self.invoice_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要移动的发票")
            return
         
        item = selected_items[0]
        index = self.invoice_tree.index(item)
         
        if index < len(self.uploaded_invoices) - 1:
            # 交换位置
            self.uploaded_invoices[index], self.uploaded_invoices[index + 1] = \
                self.uploaded_invoices[index + 1], self.uploaded_invoices[index]
             
            # 更新列表
            self.update_invoice_list()
             
            # 重新选中移动后的项
            new_item = self.invoice_tree.get_children()[index + 1]
            self.invoice_tree.selection_set(new_item)
     
    def update_pagination(self):
        """更新分页控件状态"""
        if not hasattr(self, 'layout_images') or not self.layout_images:
            return
         
        total_pages = len(self.layout_images)
         
        # 更新页码标签
        self.page_label.config(text=f"第 {self.current_page + 1} 页 / 共 {total_pages} 页")
         
        # 更新按钮状态
        self.prev_btn.config(state=tk.NORMAL if self.current_page > 0 else tk.DISABLED)
        self.next_btn.config(state=tk.NORMAL if self.current_page < total_pages - 1 else tk.DISABLED)
     
    def reset_pagination(self):
        """重置分页控件"""
        self.page_label.config(text="第 0 页 / 共 0 页")
        self.prev_btn.config(state=tk.DISABLED)
        self.next_btn.config(state=tk.DISABLED)
     
    def prev_page(self):
        """上一页"""
        if self.current_page > 0:
            self.current_page -= 1
            self.display_image(self.layout_images[self.current_page])
            self.update_pagination()
     
    def next_page(self):
        """下一页"""
        if self.current_page < len(self.layout_images) - 1:
            self.current_page += 1
            self.display_image(self.layout_images[self.current_page])
            self.update_pagination()
     
    def show_about(self):
        """显示关于信息"""
        about_text = "电子发票A4纸打印软件\n\n" \
                     "版本:1.0\n" \
                     "功能:将电子发票排版到A4纸上,每页两张\n" \
                     "支持格式:PDF\n" \
                     "\n&#169; 2025"
        messagebox.showinfo("关于", about_text)
     
    def __del__(self):
        """清理临时文件"""
        if hasattr(self, 'export_dir') and os.path.exists(self.export_dir):
            shutil.rmtree(self.export_dir)
 
if __name__ == "__main__":
    root = tk.Tk()
    app = InvoicePrinterApp(root)
    root.mainloop()