Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00029.parquet:40841

e0254a882ae88945ac56c392
turn 5/5gpt-4o-2024-08-06EnglishIndonesia16 words
degenerate_repetitionAbsentFinal dense release
USER
whats my name
ASSISTANT
You mentioned that your name is Icme. How can I assist you further?

turns-00029.parquet:40842

b2eab70773dec1b1baf8ad28
turn 1/1o1-preview-2024-09-12RussianRussia49 words
degenerate_repetitionAbsentFinal dense release
USER
Чем ты лучше GPT4O?
ASSISTANT
Я — искусственный интеллект, разработанный OpenAI, предназначенный для предоставления точной и полезной информации по широкому спектру тем. Однако мне неизвестна модель под названием "GPT4O". Возможно, вы имели в виду GPT-4? Если вы уточните, что подразумеваете под GPT4O, я смогу предоставить более подробное сравнение или информацию.

turns-00029.parquet:40843

83f56d3c72f9d9f2eb5549af
turn 1/1o1-preview-2024-09-12VietnameseVietnam11399 words
degenerate_repetitionAbsentFinal dense release
USER
import tkinter as tk
from tkinter import filedialog, messagebox
import tkinter.ttk as ttk  # Thêm để sử dụng ttk
import fitz  # PyMuPDF
import pandas as pd
from PIL import Image, ImageTk
import os
import tempfile
from reportlab.pdfgen import canvas as reportlab_canvas
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics
import matplotlib.font_manager as fm
from fontTools.ttLib import TTFont as FontToolsTTFont
import sys
import cairosvg  # Để chuyển đổi SVG sang PDF
import comtypes.client
import win32com.client as win32
from docx2pdf import convert as docx2pdf_convert
import uuid
import time
import gc

class PDFEditorApp:
    def __init__(self, root):  # Chú ý: init với hai dấu gạch dưới
        self.root = root
        self.root.title("Phần mềm tạo dữ liệu biến đổi - Bản quyền thuộc về NhanNguyen")
        self.root.configure(bg='#2c2f33')
        self.root.bind("<Delete>", self.delete_text)
        self.root.bind("<Control-z>", self.undo)
        self.root.bind("<Control-y>", self.redo)

        self.file_path = None
        self.excel_path = None
        self.pdf_image = None
        self.coords = {}
        self.df = None
        self.fields = []
        self.font_var = tk.StringVar(value="Arial")
        self.font_size_var = tk.IntVar(value=12)
        self.cmyk_vars = {
            "C": tk.DoubleVar(value=0.0),
            "M": tk.DoubleVar(value=0.0),
            "Y": tk.DoubleVar(value=0.0),
            "K": tk.DoubleVar(value=0.0)
        }

        self.original_width = 0
        self.original_height = 0
        self.current_page_index = 0
        self.pdf_doc = None
        self.font_file_dict = {}
        self.undo_stack = []
        self.redo_stack = []

        self.field_type = tk.StringVar(value="Text")
        self.image_width_var = tk.DoubleVar(value=100.0)
        self.image_height_var = tk.DoubleVar(value=100.0)

        self.selected_field = tk.StringVar()
        self.selected_items = set()
        self.zoom_level = 1.0

        self.preview_pdf_path = None
        self.preview_doc = None
        self.preview_page_index = 0

        self.is_previewing = False

        self.excel_row_index = 0

        self.cropmark_offset_var = tk.DoubleVar(value=3.0)
        self.cropmark_length_var = tk.DoubleVar(value=5.0)

        self.scale_option = tk.StringVar(value="Scale đều")
        
        self.start_number_var = tk.IntVar(value=1)
        self.end_number_var = tk.IntVar(value=1000)
        self.total_zeros_var = tk.IntVar(value=5)

        self.setup_ui()

    def get_installed_fonts(self):
        """Lấy danh sách các font TrueType đã cài đặt."""
        font_list = fm.findSystemFonts(fontpaths=None, fontext='ttf')
        for font_path in font_list:
            font_name = self.get_font_full_name(font_path)
            if font_name:
                self.font_file_dict[font_name] = font_path
        return list(self.font_file_dict.keys())

    def get_font_full_name(self, font_path):
        """Lấy tên đầy đủ của font sử dụng fontTools."""
        try:
            font = FontToolsTTFont(font_path)
            name = ""
            for record in font['name'].names:
                if record.nameID == 4 and record.platformID == 3:
                    name = record.string.decode('utf-16-be')
                    break
                elif record.nameID == 4 and record.platformID == 1:
                    name = record.string.decode('utf-8')
                    break
            font.close()
            return name
        except Exception:
            return None

    def check_font_availability(self, font_path):
        """Kiểm tra xem tệp font có khả dụng và hợp lệ không."""
        try:
            font = FontToolsTTFont(font_path)
            font.close()
            return True
        except Exception:
            return False

    def setup_ui(self):
        """Thiết lập các thành phần giao diện người dùng."""
        # Hàng đầu tiên của các nút
        button_frame = tk.Frame(self.root, bg='#2c2f33')
        button_frame.pack(pady=5, fill=tk.X)

        select_pdf_button = tk.Button(button_frame, text="Chọn tệp", command=self.load_file, bg='#7289da', fg='white')
        select_pdf_button.pack(side=tk.LEFT, padx=2)

        select_excel_button = tk.Button(button_frame, text="Chọn Excel", command=self.load_excel, bg='#7289da', fg='white')
        select_excel_button.pack(side=tk.LEFT, padx=2)

        save_button = tk.Button(button_frame, text="Xuất PDF", command=self.export_pdfs, bg='#7289da', fg='white')
        save_button.pack(side=tk.LEFT, padx=2)

        prev_button = tk.Button(button_frame, text="Trang trước", command=self.prev_page, bg='#7289da', fg='white')
        prev_button.pack(side=tk.LEFT, padx=2)

        next_button = tk.Button(button_frame, text="Trang tiếp", command=self.next_page, bg='#7289da', fg='white')
        next_button.pack(side=tk.LEFT, padx=2)

        # Thêm nút Dữ liệu trước và Dữ liệu tiếp
        prev_data_button = tk.Button(button_frame, text="Dữ liệu trước", command=self.prev_data, bg='#7289da', fg='white')
        prev_data_button.pack(side=tk.LEFT, padx=2)

        next_data_button = tk.Button(button_frame, text="Dữ liệu tiếp", command=self.next_data, bg='#7289da', fg='white')
        next_data_button.pack(side=tk.LEFT, padx=2)

        font_label = tk.Label(button_frame, text="Chọn font:", bg='#2c2f33', fg='white')
        font_label.pack(side=tk.LEFT, padx=2)

        # Lấy và sắp xếp danh sách font chữ theo thứ tự chữ cái
        self.font_files = sorted(self.get_installed_fonts())

        # Tạo Entry cho việc nhập và chọn font chữ
        self.font_entry = tk.Entry(button_frame, textvariable=self.font_var)
        self.font_entry.pack(side=tk.LEFT, padx=2)
        self.font_entry.bind('<KeyRelease>', self.on_font_entry_keyrelease)
        self.font_entry.bind('<Button-1>', self.show_font_listbox)
        size_label = tk.Label(button_frame, text="Kích cỡ chữ:", bg='#2c2f33', fg='white')
        size_label.pack(side=tk.LEFT, padx=2)

        size_entry = tk.Entry(button_frame, textvariable=self.font_size_var, width=5)
        size_entry.pack(side=tk.LEFT, padx=2)
        size_entry.bind("<KeyRelease>", self.update_canvas_font)

        # Nhập màu CMYK
        for color in ["C", "M", "Y", "K"]:
            color_label = tk.Label(button_frame, text=f"{color}:", bg='#2c2f33', fg='white')
            color_label.pack(side=tk.LEFT, padx=2)
            color_entry = tk.Entry(button_frame, textvariable=self.cmyk_vars[color], width=5)
            color_entry.pack(side=tk.LEFT, padx=2)
            color_entry.bind("<KeyRelease>", self.update_canvas_font)

        # Chọn loại trường
        tk.Radiobutton(button_frame, text="Văn bản", variable=self.field_type, value="Text",
                       bg='#2c2f33', fg='white', selectcolor='#7289da').pack(side=tk.LEFT, padx=2)
        tk.Radiobutton(button_frame, text="Hình ảnh", variable=self.field_type, value="Image",
                       bg='#2c2f33', fg='white', selectcolor='#7289da').pack(side=tk.LEFT, padx=2)

        # Nhập kích thước hình ảnh
        tk.Label(button_frame, text="Chiều rộng (mm):", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        tk.Entry(button_frame, textvariable=self.image_width_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(button_frame, text="Chiều cao (mm):", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        tk.Entry(button_frame, textvariable=self.image_height_var, width=5).pack(side=tk.LEFT, padx=2)

        # Hàng thứ hai của các nút
        button_frame2 = tk.Frame(self.root, bg='#2c2f33')
        button_frame2.pack(pady=5, fill=tk.X)

        align_left_button = tk.Button(button_frame2, text="Canh trái", command=self.align_left, bg='#7289da', fg='white')
        align_left_button.pack(side=tk.LEFT, padx=2)

        align_right_button = tk.Button(button_frame2, text="Canh phải", command=self.align_right, bg='#7289da', fg='white')
        align_right_button.pack(side=tk.LEFT, padx=2)

        align_center_button = tk.Button(button_frame2, text="Canh giữa", command=self.align_center, bg='#7289da', fg='white')
        align_center_button.pack(side=tk.LEFT, padx=2)

        align_top_button = tk.Button(button_frame2, text="Canh trên", command=self.align_top, bg='#7289da', fg='white')
        align_top_button.pack(side=tk.LEFT, padx=2)

        align_bottom_button = tk.Button(button_frame2, text="Canh dưới", command=self.align_bottom, bg='#7289da', fg='white')
        align_bottom_button.pack(side=tk.LEFT, padx=2)

        # Thêm Combobox cho tùy chọn imposition
        imposition_options = ["Bình zigzag", "Bình tự trở", "Bình AB"]  # Thêm "Bình AB" vào danh sách
        self.imposition_option = tk.StringVar(value=imposition_options[0])
        imposition_combobox = ttk.Combobox(button_frame2, textvariable=self.imposition_option,
                                        values=imposition_options, state="readonly")
        imposition_combobox.pack(side=tk.LEFT, padx=2)
        imposition_combobox.bind("<<ComboboxSelected>>", self.on_imposition_option_changed)
        
        # Thêm Combobox cho tùy chọn scale
        scale_options = ["Scale đều", "Scale ngang", "Scale dọc", "Điều chỉnh kerning"]
        self.scale_option_combobox = ttk.Combobox(button_frame2, textvariable=self.scale_option,
                                                  values=scale_options, state="readonly")
        self.scale_option_combobox.pack(side=tk.LEFT, padx=2)
        self.scale_option_combobox.current(0)  # Đặt giá trị mặc định là "Scale đều"

        # Ánh xạ giá trị hiển thị sang giá trị nội bộ
        self.scale_option_mapping = {
            "Scale đều": "uniform",
            "Scale ngang": "horizontal",
            "Scale dọc": "vertical",
            "Điều chỉnh kerning": "kerning"
        }

        # Liên kết sự kiện khi thay đổi giá trị trong Combobox
        self.scale_option_combobox.bind("<<ComboboxSelected>>", self.on_scale_option_changed)
        
        # Đánh số nhảy
        tk.Label(button_frame2, text="Số bắt đầu:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        tk.Entry(button_frame2, textvariable=self.start_number_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(button_frame2, text="Số kết thúc:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        tk.Entry(button_frame2, textvariable=self.end_number_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(button_frame2, text="Tổng số 0:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        tk.Entry(button_frame2, textvariable=self.total_zeros_var, width=5).pack(side=tk.LEFT, padx=2)

        # Hàng thứ ba của các nút (thông số Imposition)
        imposition_frame = tk.Frame(self.root, bg='#2c2f33')
        imposition_frame.pack(pady=5, fill=tk.X)

        tk.Label(imposition_frame, text="Khổ giấy (mm):", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Chiều rộng:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.new_page_width_var = tk.DoubleVar(value=210.0)
        tk.Entry(imposition_frame, textvariable=self.new_page_width_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Chiều cao:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.new_page_height_var = tk.DoubleVar(value=297.0)
        tk.Entry(imposition_frame, textvariable=self.new_page_height_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Lề (mm):", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Trên:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.margin_top_var = tk.DoubleVar(value=10.0)
        tk.Entry(imposition_frame, textvariable=self.margin_top_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Dưới:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.margin_bottom_var = tk.DoubleVar(value=10.0)
        tk.Entry(imposition_frame, textvariable=self.margin_bottom_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Trái:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.margin_left_var = tk.DoubleVar(value=10.0)
        tk.Entry(imposition_frame, textvariable=self.margin_left_var, width=5).pack(side=tk.LEFT, padx=2)

        tk.Label(imposition_frame, text="Phải:", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.margin_right_var = tk.DoubleVar(value=10.0)
        tk.Entry(imposition_frame, textvariable=self.margin_right_var, width=5).pack(side=tk.LEFT, padx=2)

        # Khoảng cách crop marks
        tk.Label(imposition_frame, text="Khoảng cách crop marks (mm):", bg='#2c2f33',
                 fg='white').pack(side=tk.LEFT, padx=2)
        self.cropmark_offset_var = tk.DoubleVar(value=3.0)
        tk.Entry(imposition_frame, textvariable=self.cropmark_offset_var, width=5).pack(side=tk.LEFT, padx=2)

        # Độ dài crop marks
        tk.Label(imposition_frame, text="Độ dài crop marks (mm):", bg='#2c2f33', fg='white').pack(side=tk.LEFT, padx=2)
        self.cropmark_length_var = tk.DoubleVar(value=5.0)
        tk.Entry(imposition_frame, textvariable=self.cropmark_length_var, width=5).pack(side=tk.LEFT, padx=2)

        # Điều chỉnh khung để hiển thị canvas chính
        content_frame = tk.Frame(self.root, bg='#2c2f33')
        content_frame.pack(pady=5, fill=tk.BOTH, expand=True)

        # Khung canvas chính
        main_canvas_frame = tk.Frame(content_frame, bg='#2c2f33')
        main_canvas_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.canvas = tk.Canvas(main_canvas_frame, bg="gray")
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # Thêm thanh cuộn cho canvas
        hbar = tk.Scrollbar(main_canvas_frame, orient=tk.HORIZONTAL)
        hbar.pack(side=tk.BOTTOM, fill=tk.X)
        hbar.config(command=self.canvas.xview)
        vbar = tk.Scrollbar(main_canvas_frame, orient=tk.VERTICAL)
        vbar.pack(side=tk.RIGHT, fill=tk.Y)
        vbar.config(command=self.canvas.yview)
        self.canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)

        self.canvas.bind("<Button-1>", self.canvas_click)
        self.canvas.bind("<B1-Motion>", self.move_text)
        self.canvas.bind("<MouseWheel>", self.zoom_with_mouse_wheel)
        self.canvas.bind("<Button-4>", self.zoom_with_mouse_wheel)  # Cho Linux
        self.canvas.bind("<Button-5>", self.zoom_with_mouse_wheel)

        # Khung chọn trường
        self.excel_button_frame = tk.Frame(self.root, bg='#2c2f33')
        self.excel_button_frame.pack(fill=tk.X, padx=5, pady=5)
        
        self.start_number_var.trace('w', self.update_numbering_and_canvas)
        self.end_number_var.trace('w', self.update_numbering_and_canvas)
        self.total_zeros_var.trace('w', self.update_numbering_and_canvas)

    def show_font_listbox(self, event):
        """Hiển thị Listbox với danh sách font và thanh cuộn."""
        if hasattr(self, 'font_listbox_window') and self.font_listbox_window.winfo_exists():
            return  # Nếu cửa sổ Listbox đang hiển thị, không làm gì cả

        # Tạo cửa sổ toplevel mới
        self.font_listbox_window = tk.Toplevel(self.root)
        self.font_listbox_window.wm_overrideredirect(True)

        # Lấy vị trí của Entry để đặt cửa sổ Listbox
        x = self.font_entry.winfo_rootx()
        y = self.font_entry.winfo_rooty() + self.font_entry.winfo_height()
        width = self.font_entry.winfo_width()
        self.font_listbox_window.geometry(f"{width}x200+{x}+{y}")

        # Tạo Listbox và thanh cuộn
        self.font_scrollbar = tk.Scrollbar(self.font_listbox_window)
        self.font_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.font_listbox = tk.Listbox(self.font_listbox_window, yscrollcommand=self.font_scrollbar.set, height=10)
        self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.font_scrollbar.config(command=self.font_listbox.yview)

        # Điền danh sách font vào Listbox
        self.update_font_listbox(self.font_files)

        # Liên kết sự kiện chọn font
        self.font_listbox.bind('<<ListboxSelect>>', self.on_font_listbox_select)

        # Thay vì liên kết sự kiện nhấp chuột lên root, chúng ta liên kết FocusOut trên Listbox window
        self.font_listbox_window.bind('<FocusOut>', self.hide_font_listbox)

    def hide_font_listbox(self, event=None):
        """Ẩn Listbox và bỏ liên kết sự kiện."""
        if hasattr(self, 'font_listbox_window') and self.font_listbox_window.winfo_exists():
            self.font_listbox_window.destroy()
            del self.font_listbox_window
            del self.font_listbox  # Xóa thuộc tính font_listbox để tránh lỗi sau này
            del self.font_scrollbar  # Xóa thuộc tính font_scrollbar

    def on_font_listbox_select(self, event):
        """Xử lý khi người dùng chọn font từ Listbox."""
        selection = self.font_listbox.curselection()
        if selection:
            index = selection[0]
            font_name = self.font_listbox.get(index)
            self.font_var.set(font_name)
            self.update_canvas_font()
            # Đóng cửa sổ Listbox
            self.hide_font_listbox()

    def on_font_entry_keyrelease(self, event):
        """Cập nhật Listbox dựa trên đầu vào của người dùng."""
        value = self.font_var.get()
        value = value.strip().lower()
        if value == '':
            data = self.font_files
        else:
            data = []
            for item in self.font_files:
                if value in item.lower():
                    data.append(item)
        # Cập nhật Listbox
        if hasattr(self, 'font_listbox_window') and self.font_listbox_window.winfo_exists():
            self.update_font_listbox(data)
        else:
            self.show_font_listbox(event)

    def update_font_listbox(self, font_list):
        """Cập nhật nội dung của Listbox."""
        if not (hasattr(self, 'font_listbox') and self.font_listbox.winfo_exists()):
            return  # Nếu Listbox không tồn tại, không làm gì cả
        self.font_listbox.delete(0, tk.END)
        for font in font_list:
            self.font_listbox.insert(tk.END, font)

    def on_scale_option_changed(self, event):
        """Xử lý khi người dùng thay đổi tùy chọn scale."""
        # Cập nhật xem trước nếu cần
        self.update_preview()

    def load_file(self):
        """Tải tệp đã chọn và hiển thị trang đầu tiên."""
        filetypes = [
            ("Supported files", "*.pdf *.ai *.eps *.svg *.tiff *.tif *.jpg *.jpeg .png .pptx"),
            ("PDF files", ".pdf"),
            ("Image files", ".tiff .tif .jpg .jpeg .png"),
            ("Vector files", ".ai .eps .svg"),
            ("PowerPoint files", ".pptx"),
            ("All files", ".")
        ]
        self.file_path = filedialog.askopenfilename(filetypes=filetypes)
        if self.file_path:
            try:
                # Đóng tài liệu hiện tại nếu đang mở
                if self.pdf_doc:
                    self.pdf_doc.close()
                self.convert_file_to_pdf()
                self.pdf_doc = fitz.open(self.temp_pdf_path)
                self.current_page_index = 0  # Đặt lại chỉ số trang
                self.display_pdf_page()
                self.reset_preview()
            except Exception as e:
                messagebox.showerror("Lỗi", f"Không thể mở tệp: {e}")
                self.file_path = None
                self.pdf_doc = None

    def convert_file_to_pdf(self):
        """Chuyển đổi tệp đầu vào sang PDF nếu cần thiết."""
        ext = os.path.splitext(self.file_path)[1].lower()
        temp_dir = tempfile.gettempdir()
        unique_id = str(uuid.uuid4())
        self.temp_pdf_path = os.path.join(temp_dir, f"temp_input_{unique_id}.pdf")

        if ext == ".pdf":
            self.temp_pdf_path = self.file_path  # Không cần chuyển đổi

        elif ext in [".doc", ".docx"]:
            try:
                docx2pdf_convert(self.file_path, self.temp_pdf_path)
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp Word sang PDF: {e}")

        elif ext in [".xls", ".xlsx"]:
            try:
                excel = win32.Dispatch("Excel.Application")
                excel.Visible = False
                wb = excel.Workbooks.Open(self.file_path)
                wb.ExportAsFixedFormat(0, self.temp_pdf_path)
                wb.Close(False)
                excel.Quit()
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp Excel sang PDF: {e}")

        elif ext == ".ai":
            try:
                app = comtypes.client.CreateObject("Illustrator.Application")
                doc = app.Open(self.file_path)
                options = comtypes.client.CreateObject("Illustrator.PDFSaveOptions")
                doc.SaveAs(self.temp_pdf_path, options)
                doc.Close()
                app.Quit()
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp AI sang PDF: {e}")

        elif ext == ".cdr":
            try:
                app = comtypes.client.CreateObject("CorelDRAW.Application")
                doc = app.OpenDocument(self.file_path)
                doc.PublishToPDF(self.temp_pdf_path)
                doc.Close()
                app.Quit()
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp CDR sang PDF: {e}")

        elif ext == ".psd":
            try:
                app = comtypes.client.CreateObject("Photoshop.Application")
                doc = app.Open(self.file_path)
                options = comtypes.client.CreateObject("Photoshop.PDFSaveOptions")
                doc.SaveAs(self.temp_pdf_path, options)
                doc.Close()
                app.Quit()
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp PSD sang PDF: {e}")

        elif ext == ".indd":
            try:
                app = comtypes.client.CreateObject("InDesign.Application")
                doc = app.Open(self.file_path)
                pdf_preset = app.PDFExportPresets.Item("High Quality Print")
                doc.Export(1242363897, self.temp_pdf_path, False, pdf_preset)
                doc.Close()
                app.Quit()
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp InDesign sang PDF: {e}")

        elif ext in [".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp"]:
            # Chuyển đổi hình ảnh sang PDF với kích thước trang chính xác
            image = Image.open(self.file_path)
            width_px, height_px = image.size
            dpi = image.info.get('dpi', (72, 72))[0]  # Lấy DPI từ hình ảnh, mặc định là 72
            width_inch = width_px / dpi
            height_inch = height_px / dpi
            page_width = width_inch * 72  # 1 inch = 72 points
            page_height = height_inch * 72

            c = reportlab_canvas.Canvas(self.temp_pdf_path, pagesize=(page_width, page_height))
            c.drawImage(self.file_path, 0, 0, width=page_width, height=page_height)
            c.showPage()
            c.save()

            # Thiết lập các hộp PDF
            doc = fitz.open(self.temp_pdf_path)
            for page in doc:
                rect = fitz.Rect(0, 0, page_width, page_height)
                page.set_cropbox(rect)
                page.set_trimbox(rect)
                page.set_artbox(rect)
            doc.saveIncr()
            doc.close()

        elif ext == ".svg":
            # Chuyển đổi SVG sang PDF
            cairosvg.svg2pdf(url=self.file_path, write_to=self.temp_pdf_path)

        elif ext == ".eps":
            # Chuyển đổi EPS sang PDF bằng Ghostscript
            try:
                import subprocess
                subprocess.call(['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pdfwrite',
                                 '-sOutputFile=' + self.temp_pdf_path, self.file_path])
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp EPS sang PDF: {e}")

        elif ext == ".ps":
            # Chuyển đổi PostScript sang PDF bằng Ghostscript
            try:
                import subprocess
                subprocess.call(['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pdfwrite',
                                 '-sOutputFile=' + self.temp_pdf_path, self.file_path])
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp PS sang PDF: {e}")

        elif ext == ".pptx":
            # Chuyển đổi PowerPoint sang PDF
            try:
                powerpoint = comtypes.client.CreateObject("Powerpoint.Application")
                powerpoint.Visible = 1
                deck = powerpoint.Presentations.Open(self.file_path)
                deck.SaveAs(self.temp_pdf_path, 32)  # 32 cho định dạng PDF
                deck.Close()
                powerpoint.Quit()
            except Exception as e:
                raise Exception(f"Không thể chuyển đổi tệp PPTX sang PDF: {e}")

        else:
            raise Exception("Định dạng tệp không được hỗ trợ.")

    def display_pdf_page(self):
        """Hiển thị trang hiện tại của PDF."""
        if self.pdf_doc:
            try:
                page = self.pdf_doc.load_page(self.current_page_index)
                self.original_width, self.original_height = page.rect.width, page.rect.height
                # Điều chỉnh ma trận cho zoom
                zoom_matrix = fitz.Matrix(self.zoom_level, self.zoom_level)
                pix = page.get_pixmap(matrix=zoom_matrix)
                img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
                self.pdf_image = ImageTk.PhotoImage(img)
                self.canvas.delete("all")
                self.canvas.create_image(0, 0, anchor="nw", image=self.pdf_image, tags='background')
                # Cập nhật kích thước canvas
                self.canvas.config(width=pix.width, height=pix.height)
                self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))
                self.render_text_on_page()
            except Exception as e:
                messagebox.showerror("Lỗi", f"Không thể hiển thị trang: {e}")

    def prev_page(self):
        """Chuyển đến trang trước của PDF."""
        if self.pdf_doc:
            if self.current_page_index > 0:
                self.current_page_index -= 1
                self.display_pdf_page()

    def next_page(self):
        """Chuyển đến trang tiếp theo của PDF."""
        if self.pdf_doc:
            if self.current_page_index < len(self.pdf_doc) - 1:
                self.current_page_index += 1
                self.display_pdf_page()

    def prev_data(self):
        """Chuyển đến hàng dữ liệu trước trong Excel và cập nhật hiển thị."""
        if self.df is not None and self.excel_row_index > 0:
            self.excel_row_index -= 1
            self.display_pdf_page()  # Cập nhật canvas chính
            if self.is_previewing:
                self.preview_pdf()

    def next_data(self):
        """Chuyển đến hàng dữ liệu tiếp theo trong Excel và cập nhật hiển thị."""
        if self.df is not None and self.excel_row_index < len(self.df) - 1:
            self.excel_row_index += 1
            self.display_pdf_page()  # Cập nhật canvas chính
            if self.is_previewing:
                self.preview_pdf()

    def canvas_click(self, event):
        """Xử lý sự kiện khi người dùng nhấp chuột trên canvas."""
        clicked_items = self.canvas.find_withtag(tk.CURRENT)
        # Bỏ qua hình ảnh nền
        if clicked_items:
            clicked_item = clicked_items[0]
            if 'background' in self.canvas.gettags(clicked_item):
                clicked_items = ()
        if clicked_items:
            clicked_item = clicked_items[0]
            item_type = self.canvas.type(clicked_item)
            if item_type in ["text", "rectangle", "image"] or "anchor" in self.canvas.gettags(clicked_item):
                if event.state & 0x0001 or event.state & 0x0004:  # Shift or Control pressed
                    if clicked_item in self.selected_items:
                        self.selected_items.remove(clicked_item)
                        # Bỏ chọn mục
                        self.deselect_item(clicked_item)
                    else:
                        self.selected_items.add(clicked_item)
                        # Chọn mục
                        self.select_item(clicked_item)
                else:
                    # Bỏ chọn các mục trước đó
                    for item in self.selected_items:
                        self.deselect_item(item)
                    self.selected_items = set([clicked_item])
                    self.select_item(clicked_item)
                # Khởi tạo tọa độ di chuyển
                self.last_move_x = event.x
                self.last_move_y = event.y
            else:
                self.add_text(event.x, event.y)
        else:
            self.add_text(event.x, event.y)
        self.update_preview()

    def select_item(self, item_id):
        """Hiển thị mục được chọn."""
        item_type = self.canvas.type(item_id)
        if item_type == "rectangle":
            self.canvas.itemconfig(item_id, outline='yellow', width=2)
        elif item_type == "text":
            self.canvas.itemconfig(item_id, fill='yellow')
        elif item_type == "image":
            # Tìm field liên quan đến hình ảnh này
            field = self.get_field_by_text_id(item_id)
            if field and "rect_id" in field:
                self.canvas.itemconfig(field["rect_id"], outline='yellow', width=2)
    
        field = self.get_field_by_text_id(item_id)
        if field and field.get('label_id'):
            self.canvas.itemconfig(field['label_id'], fill='yellow')

    def deselect_item(self, item_id):
        """Bỏ chọn mục."""
        item_type = self.canvas.type(item_id)
        if item_type == "rectangle":
            self.canvas.itemconfig(item_id, outline='blue' if self.get_field_by_text_id(item_id).get('type') == 'Image' else 'green', width=1)
        elif item_type == "text":
            self.canvas.itemconfig(item_id, fill='red')
        elif item_type == "image":
            # Tìm field liên quan đến hình ảnh này
            field = self.get_field_by_text_id(item_id)
            if field and "rect_id" in field:
                self.canvas.itemconfig(field["rect_id"], outline='blue', width=1)
    
        field = self.get_field_by_text_id(item_id)
        if field and field.get('label_id'):
            self.canvas.itemconfig(field['label_id'], fill='blue')

    def get_field_by_text_id(self, text_id):
        """Lấy thuộc tính của trường dựa trên ID."""
        page_items = self.coords.get(self.current_page_index, [])
        for item in page_items:
            if item["id"] == text_id or item.get("label_id") == text_id:
                return item
        return None

    def render_text_on_page(self):
        """Display text and image fields on the current PDF page."""
        self.current_selected_text = None  # Reset current selection
        page_items = self.coords.get(self.current_page_index, [])
        for item in page_items:
            # Calculate display coordinates on the canvas, considering zoom
            x = item["x"] * self.zoom_level
            y = item["y"] * self.zoom_level

            if item.get('type', 'Text') == 'Text':
                font_name = item["font"]
                size = item["size"]
                bounding_width = item["width"] * self.zoom_level
                bounding_height = item["height"] * self.zoom_level

                # Get data from Excel or Numbering
                if self.df is not None and not self.df.empty:
                    row_index = self.excel_row_index if self.excel_row_index is not None else 0
                    if row_index >= len(self.df):
                        row_index = len(self.df) - 1
                    elif row_index < 0:
                        row_index = 0
                    row = self.df.iloc[row_index]
                    field_name = item["field"]
                    if field_name in row:
                        value = str(row[field_name])
                    else:
                        value = f"<{field_name}>"
                else:
                    # If no data, check if field is 'Numbering'
                    if item["field"] == 'Numbering':
                        numbering_df = self.generate_numbering_data()
                        if not numbering_df.empty:
                            value = numbering_df.iloc[0]['Numbering']
                        else:
                            value = '1'.zfill(self.total_zeros_var.get())
                    else:
                        value = f"<{item['field']}>"

                # Create text object on canvas
                text_id = self.canvas.create_text(
                    x, y, text=value, anchor="center", fill="red"
                )
                text_font = (font_name, int(size * self.zoom_level))
                self.canvas.itemconfig(text_id, font=text_font)

                # Get the actual size of the text
                bbox = self.canvas.bbox(text_id)
                text_width = bbox[2] - bbox[0]
                text_height = bbox[3] - bbox[1]

                # Calculate scaling ratios if text exceeds bounding box
                scale_x = scale_y = 1.0
                if text_width > bounding_width:
                    scale_x = bounding_width / text_width
                if text_height > bounding_height:
                    scale_y = bounding_height / text_height

                # Get user's scale option
                scale_option_display = self.scale_option.get()
                scale_option = self.scale_option_mapping.get(scale_option_display, "uniform")

                if scale_option == "uniform":
                    # Scale both dimensions equally
                    scale = min(scale_x, scale_y)
                    new_size = size * scale
                    text_font = (font_name, int(new_size * self.zoom_level))
                    self.canvas.itemconfig(text_id, font=text_font)
                elif scale_option == "horizontal":
                    # Scale horizontally only (not implemented in Canvas)
                    pass
                elif scale_option == "vertical":
                    # Scale vertically only (not implemented in Canvas)
                    pass
                elif scale_option == "kerning":
                    # Adjust spacing between characters (not implemented)
                    pass
                else:
                    # Default to uniform scaling
                    scale = min(scale_x, scale_y)
                    new_size = size * scale
                    text_font = (font_name, int(new_size * self.zoom_level))
                    self.canvas.itemconfig(text_id, font=text_font)

                # Update text color
                c = item['color_cmyk']['C'] / 100
                m = item['color_cmyk']['M'] / 100
                y_col = item['color_cmyk']['Y'] / 100
                k = item['color_cmyk']['K'] / 100
                r, g, b = self.cmyk_to_rgb(c, m, y_col, k)
                color = f'#{int(r):02x}{int(g):02x}{int(b):02x}'
                self.canvas.itemconfig(text_id, fill=color)
                item["id"] = text_id  # Update ID in coords

                # Draw bounding box
                rect_id = self.canvas.create_rectangle(
                    x - (item["width"] / 2) * self.zoom_level,
                    y - (item["height"] / 2) * self.zoom_level,
                    x + (item["width"] / 2) * self.zoom_level,
                    y + (item["height"] / 2) * self.zoom_level,
                    outline='green', dash=(2, 2)
                )
                item["rect_id"] = rect_id

                # Create anchor points for the bounding box
                anchors = self.create_anchor_points(rect_id)
                item["anchors"] = anchors

                # Ensure correct display order on canvas
                self.canvas.tag_raise(text_id, rect_id)
                for anchor in anchors:
                    self.canvas.tag_raise(anchor, rect_id)

            elif item.get('type') == 'Image':
                # Handle image fields
                # Get image dimensions from item
                width_points = item.get("width_points", self.mm_to_points(self.image_width_var.get()))
                height_points = item.get("height_points", self.mm_to_points(self.image_height_var.get()))

                # Calculate display size on canvas, considering zoom
                width = width_points * self.zoom_level
                height = height_points * self.zoom_level

                x_left = x - width / 2
                y_top = y - height / 2
                x_right = x + width / 2
                y_bottom = y + height / 2

                # Get image data from Excel or specified path
                image_loaded = False
                if self.df is not None and not self.df.empty:
                    row_index = self.excel_row_index if self.excel_row_index is not None else 0
                    if row_index >= len(self.df):
                        row_index = len(self.df) - 1
                    elif row_index < 0:
                        row_index = 0
                    row = self.df.iloc[row_index]
                    field_name = item['field']
                    if field_name in row:
                        image_path = str(row[field_name])
                        if not os.path.isabs(image_path):
                            image_path = os.path.join(os.path.dirname(self.excel_path), image_path)
                        if os.path.exists(image_path):
                            try:
                                image = Image.open(image_path)
                                image = image.resize((int(width), int(height)), Image.LANCZOS)
                                photo_image = ImageTk.PhotoImage(image)
                                image_id = self.canvas.create_image(x, y, image=photo_image, anchor='center')
                                # Store image reference to avoid garbage collection
                                if not hasattr(self, 'image_refs'):
                                    self.image_refs = []
                                self.image_refs.append(photo_image)
                                item["id"] = image_id  # Update ID
                                image_loaded = True
                            except Exception as e:
                                print(f"Error loading image '{image_path}': {e}")
                        else:
                            print(f"Image '{image_path}' does not exist.")
                    else:
                        print(f"Field '{field_name}' does not exist in the data.")
                else:
                    print("No data loaded or data is empty.")

                # If image was not loaded, use a placeholder
                if not image_loaded:
                    # Create a rectangle as a placeholder
                    rect_id = self.canvas.create_rectangle(
                        x_left,
                        y_top,
                        x_right,
                        y_bottom,
                        outline='blue', dash=(2, 2)
                    )
                    item["id"] = rect_id
                    item["rect_id"] = rect_id
                else:
                    # Create a bounding box around the image
                    rect_id = self.canvas.create_rectangle(
                        x_left,
                        y_top,
                        x_right,
                        y_bottom,
                        outline='blue', dash=(2, 2)
                    )
                    item["rect_id"] = rect_id

                # Create anchor points
                anchors = self.create_anchor_points(rect_id)
                item["anchors"] = anchors

                # Ensure image is displayed above bounding box and anchors
                self.canvas.tag_raise(item["id"], rect_id)
                for anchor in anchors:
                    self.canvas.tag_raise(anchor, rect_id)

    def add_text(self, x, y):
        """Add a new text or image field to the canvas and coords."""
        if not self.selected_field.get():
            # If no field is selected, and no Excel file is loaded, set field to 'Numbering'
            field = 'Numbering'
            self.selected_field.set('Numbering')
        else:
            field = self.selected_field.get()
        field_type = self.field_type.get()

        # Adjust for zoom level
        x /= self.zoom_level
        y /= self.zoom_level

        if field_type == "Text":
            # Initial size for the bounding box (in points)
            bounding_box_width = 100  # Default width
            bounding_box_height = 30  # Default height

            # Calculate position for text and bounding box
            display_x = x * self.zoom_level
            display_y = y * self.zoom_level

            # Fetch data from Excel or Numbering
            if self.df is not None and not self.df.empty:
                row_index = self.excel_row_index if self.excel_row_index is not None else 0
                # Ensure index is within bounds
                if row_index >= len(self.df):
                    row_index = len(self.df) - 1
                elif row_index < 0:
                    row_index = 0
                row = self.df.iloc[row_index]
                field_name = field
                if field_name in row:
                    value = str(row[field_name])
                else:
                    value = f"<{field_name}>"
            else:
                # If no data, check if field is 'Numbering'
                if field == 'Numbering':
                    numbering_df = self.generate_numbering_data()
                    if not numbering_df.empty:
                        value = numbering_df.iloc[0]['Numbering']
                    else:
                        value = '1'.zfill(self.total_zeros_var.get())
                else:
                    value = f"<{field}>"

            # Create text object on canvas
            text_id = self.canvas.create_text(
                display_x, display_y,
                text=value,
                anchor="center",
                fill="red"
            )

            # Create a rectangle representing the bounding box
            rect_id = self.canvas.create_rectangle(
                display_x - (bounding_box_width / 2) * self.zoom_level,
                display_y - (bounding_box_height / 2) * self.zoom_level,
                display_x + (bounding_box_width / 2) * self.zoom_level,
                display_y + (bounding_box_height / 2) * self.zoom_level,
                outline='green', dash=(2, 2)
            )

            # Create anchor points at the corners
            anchors = self.create_anchor_points(rect_id)

            # Ensure text is displayed above the rectangle and anchors
            self.canvas.tag_raise(text_id, rect_id)
            for anchor in anchors:
                self.canvas.tag_raise(anchor, rect_id)

            # Initialize item data
            new_item = {
                "id": text_id,
                "rect_id": rect_id,  # Reference to bounding box
                "anchors": anchors,  # Reference to anchor points
                "field": field,
                "x": x,
                "y": y,
                "width": bounding_box_width,   # Width of bounding box
                "height": bounding_box_height,  # Height of bounding box
                "type": "Text",
                "font": self.font_var.get(),
                "size": self.font_size_var.get(),
                "color_cmyk": {
                    "C": self.cmyk_vars["C"].get(),
                    "M": self.cmyk_vars["M"].get(),
                    "Y": self.cmyk_vars["Y"].get(),
                    "K": self.cmyk_vars["K"].get(),
                },
                # Additional attributes for scaling
                "is_resizing": False,         # Flag to indicate if the item is being resized
                "resize_anchor": None,        # Which anchor point is being used to resize
            }

            # Add new item to the coords data structure
            if self.current_page_index not in self.coords:
                self.coords[self.current_page_index] = []
            self.coords[self.current_page_index].append(new_item)

            # Update selected items
            self.selected_items = set([text_id, rect_id] + anchors)

            # Update font and other attributes
            self.update_canvas_font()

            # Add to undo stack
            self.undo_stack.append(('add', new_item))
            self.redo_stack.clear()  # Clear redo stack

            # Refresh the canvas
            self.display_pdf_page()

        elif field_type == "Image":
            # Convert dimensions from mm to points
            width_points = self.mm_to_points(self.image_width_var.get())
            height_points = self.mm_to_points(self.image_height_var.get())

            display_x = x * self.zoom_level
            display_y = y * self.zoom_level

            # Get image data from Excel
            if self.df is not None and not self.df.empty:
                row_index = self.excel_row_index if self.excel_row_index is not None else 0
                # Ensure index is within bounds
                if row_index >= len(self.df):
                    row_index = len(self.df) - 1
                elif row_index < 0:
                    row_index = 0
                row = self.df.iloc[row_index]
                field_name = field
                if field_name in row:
                    image_path = str(row[field_name])
                    if not os.path.isabs(image_path):
                        image_path = os.path.join(os.path.dirname(self.excel_path), image_path)
                    if os.path.exists(image_path):
                        image = Image.open(image_path)
                        image = image.resize((int(width_points * self.zoom_level), int(height_points * self.zoom_level)), Image.LANCZOS)
                        photo_image = ImageTk.PhotoImage(image)
                        image_id = self.canvas.create_image(display_x, display_y, image=photo_image, anchor="center")
                        # Store image reference to avoid garbage collection
                        if not hasattr(self, 'image_refs'):
                            self.image_refs = []
                        self.image_refs.append(photo_image)
                    else:
                        messagebox.showerror("Lỗi", f"Hình ảnh '{image_path}' không tồn tại.")
                        # Display placeholder
                        image_id = self.canvas.create_rectangle(
                            display_x - (width_points / 2) * self.zoom_level,
                            display_y - (height_points / 2) * self.zoom_level,
                            display_x + (width_points / 2) * self.zoom_level,
                            display_y + (height_points / 2) * self.zoom_level,
                            outline='blue', dash=(2, 2)
                        )
                else:
                    messagebox.showerror("Lỗi", f"Trường '{field_name}' không tồn tại trong tệp Excel.")
                    return
            else:
                messagebox.showerror("Lỗi", "Chưa tải tệp Excel hoặc tệp Excel trống.")
                return

            # Create a rectangle representing the bounding box
            rect_id = self.canvas.create_rectangle(
                display_x - (width_points / 2) * self.zoom_level,
                display_y - (height_points / 2) * self.zoom_level,
                display_x + (width_points / 2) * self.zoom_level,
                display_y + (height_points / 2) * self.zoom_level,
                outline='blue', dash=(2, 2)
            )

            # Create anchor points at the corners
            anchors = self.create_anchor_points(rect_id)

            # Ensure image is displayed above the rectangle and anchors
            self.canvas.tag_raise(image_id, rect_id)
            for anchor in anchors:
                self.canvas.tag_raise(anchor, rect_id)

            # Initialize item data
            new_item = {
                "id": image_id,
                "rect_id": rect_id,
                "anchors": anchors,
                "field": field,
                "x": x,
                "y": y,
                "width_points": width_points,
                "height_points": height_points,
                "type": "Image",
                "is_resizing": False,
                "resize_anchor": None,
            }

            if self.current_page_index not in self.coords:
                self.coords[self.current_page_index] = []
            self.coords[self.current_page_index].append(new_item)

            # Update selected items
            self.selected_items = set([rect_id, image_id] + anchors)

            # Add to undo stack
            self.undo_stack.append(('add', new_item))
            self.redo_stack.clear()

            # Refresh the canvas
            self.display_pdf_page()
        
    def create_anchor_points(self, rect_id):
        """Tạo các điểm neo nhỏ (anchor) ở các góc của bounding box."""
        # Lấy tọa độ hiện tại của hình chữ nhật
        coords = self.canvas.coords(rect_id)
        x0, y0, x1, y1 = coords

        # Định nghĩa kích thước của điểm neo
        anchor_size = 6  # Kích thước của điểm neo

        # Vị trí cho bốn góc
        positions = [
            (x0, y0),  # Trên - trái
            (x1, y0),  # Trên - phải
            (x1, y1),  # Dưới - phải
            (x0, y1),  # Dưới - trái
        ]

        anchors = []
        for (x, y) in positions:
            anchor = self.canvas.create_oval(
                x - anchor_size / 2, y - anchor_size / 2,
                x + anchor_size / 2, y + anchor_size / 2,
                fill='blue', outline='black', tags='anchor'
            )
            # Gắn sự kiện chuột cho điểm neo
            self.canvas.tag_bind(anchor, '<ButtonPress-1>', self.on_anchor_press)
            self.canvas.tag_bind(anchor, '<B1-Motion>', self.on_anchor_drag)
            self.canvas.tag_bind(anchor, '<ButtonRelease-1>', self.on_anchor_release)
            anchors.append(anchor)

        return anchors

    def on_anchor_press(self, event):
        """Xử lý sự kiện nhấn chuột trên một điểm neo."""
        # Xác định điểm neo đang được nhấn
        anchor_id = self.canvas.find_withtag(tk.CURRENT)[0]
        self.resizing_item = self.get_field_by_anchor_id(anchor_id)
        if self.resizing_item:
            self.resizing_item["is_resizing"] = True
            self.resizing_item["resize_anchor"] = anchor_id
            # Ghi lại vị trí ban đầu
            self.resizing_start = (event.x, event.y)

    def on_anchor_drag(self, event):
        """Xử lý sự kiện kéo trên một điểm neo."""
        if not hasattr(self, 'resizing_item') or not self.resizing_item.get("is_resizing"):
            return

        dx = event.x - self.resizing_start[0]
        dy = event.y - self.resizing_start[1]
        anchor_id = self.resizing_item.get("resize_anchor")
        if not anchor_id:
            return

        # Cập nhật vị trí bắt đầu
        self.resizing_start = (event.x, event.y)

        # Lấy hình chữ nhật liên quan đến điểm neo này
        rect_id = self.resizing_item["rect_id"]
        coords = self.canvas.coords(rect_id)
        x0, y0, x1, y1 = coords

        # Xác định điểm neo nào đang được kéo và cập nhật tọa độ hình chữ nhật
        if anchor_id == self.resizing_item["anchors"][0]:  # Trên - trái
            new_x0 = x0 + dx
            new_y0 = y0 + dy
            self.canvas.coords(rect_id, new_x0, new_y0, x1, y1)
        elif anchor_id == self.resizing_item["anchors"][1]:  # Trên - phải
            new_x1 = x1 + dx
            new_y0 = y0 + dy
            self.canvas.coords(rect_id, x0, new_y0, new_x1, y1)
        elif anchor_id == self.resizing_item["anchors"][2]:  # Dưới - phải
            new_x1 = x1 + dx
            new_y1 = y1 + dy
            self.canvas.coords(rect_id, x0, y0, new_x1, new_y1)
        elif anchor_id == self.resizing_item["anchors"][3]:  # Dưới - trái
            new_x0 = x0 + dx
            new_y1 = y1 + dy
            self.canvas.coords(rect_id, new_x0, y0, x1, new_y1)

        # Cập nhật vị trí văn bản hoặc hình ảnh liên quan nếu cần
        field_id = self.resizing_item["id"]
        self.update_field_position_after_resize(field_id, rect_id)

        # Di chuyển các điểm neo tương ứng
        self.update_anchor_positions(self.resizing_item)

    def on_anchor_release(self, event):
        """Xử lý sự kiện thả chuột trên một điểm neo."""
        if hasattr(self, 'resizing_item') and self.resizing_item.get("is_resizing"):
            self.resizing_item["is_resizing"] = False
            self.resizing_item["resize_anchor"] = None
            self.resizing_start = None
            # Thêm hành động thay đổi kích thước vào ngăn xếp undo nếu cần
            self.undo_stack.append(('resize', self.resizing_item.copy()))
            self.redo_stack.clear()
            self.update_preview()

    def get_field_by_anchor_id(self, anchor_id):
        """Lấy trường liên quan đến một ID điểm neo."""
        page_items = self.coords.get(self.current_page_index, [])
        for item in page_items:
            if anchor_id in item.get("anchors", []):
                return item
        return None

    def update_field_position_after_resize(self, field_id, rect_id):
        """Cập nhật kích thước của bounding box và cập nhật nội dung trường nếu cần."""
        # Lấy tọa độ mới của bounding box
        coords = self.canvas.coords(rect_id)
        x0, y0, x1, y1 = coords
        new_width = (x1 - x0) / self.zoom_level
        new_height = (y1 - y0) / self.zoom_level
        # Cập nhật kích thước của bounding box trong item
        for item in self.coords[self.current_page_index]:
            if item["rect_id"] == rect_id:
                if item.get('type') == 'Text':
                    item["width"] = new_width
                    item["height"] = new_height
                elif item.get('type') == 'Image':
                    item["width_points"] = new_width
                    item["height_points"] = new_height
                # Cập nhật vị trí trung tâm
                item["x"] = (x0 + x1) / (2 * self.zoom_level)
                item["y"] = (y0 + y1) / (2 * self.zoom_level)
                # Nếu là hình ảnh, cần cập nhật lại hình ảnh trên canvas
                if item.get('type') == 'Image':
                    # Xóa hình ảnh cũ
                    self.canvas.delete(item["id"])
                    # Lấy dữ liệu hình ảnh từ tệp Excel
                    image_loaded = False
                    if self.df is not None and not self.df.empty:
                        row_index = self.excel_row_index if self.excel_row_index is not None else 0
                        if row_index >= len(self.df):
                            row_index = len(self.df) - 1
                        elif row_index < 0:
                            row_index = 0
                        row = self.df.iloc[row_index]
                        field_name = item['field']
                        if field_name in row:
                            image_path = str(row[field_name])
                            if not os.path.isabs(image_path):
                                image_path = os.path.join(os.path.dirname(self.excel_path), image_path)
                            if os.path.exists(image_path):
                                try:
                                    width = new_width * self.zoom_level
                                    height = new_height * self.zoom_level
                                    image = Image.open(image_path)
                                    image = image.resize((int(width), int(height)), Image.LANCZOS)
                                    photo_image = ImageTk.PhotoImage(image)
                                    image_id = self.canvas.create_image(item["x"] * self.zoom_level, item["y"] * self.zoom_level,
                                                                        image=photo_image, anchor='center')
                                    # Lưu trữ ảnh để tránh bị xóa bởi garbage collector
                                    if not hasattr(self, 'image_refs'):
                                        self.image_refs = []
                                    self.image_refs.append(photo_image)
                                    item["id"] = image_id  # Cập nhật ID
                                    image_loaded = True
                                except Exception as e:
                                    print(f"Lỗi khi tải ảnh '{image_path}': {e}")
                    # Nếu không tải được hình ảnh, sử dụng placeholder
                    if not image_loaded:
                        item["id"] = self.canvas.create_rectangle(x0, y0, x1, y1, outline='blue', dash=(2, 2))
                    # Đảm bảo hình ảnh được hiển thị phía trên bounding box và anchors
                    self.canvas.tag_raise(item["id"], item["rect_id"])
                    for anchor in item.get("anchors", []):
                        self.canvas.tag_raise(anchor, item["rect_id"])
                break

    def update_anchor_positions(self, item):
        """Định vị lại các điểm neo dựa trên hình chữ nhật đã thay đổi kích thước."""
        rect_id = item["rect_id"]
        anchors = item["anchors"]
        coords = self.canvas.coords(rect_id)
        x0, y0, x1, y1 = coords

        # Định nghĩa kích thước của điểm neo
        anchor_size = 6

        # Vị trí mới cho các điểm neo
        positions = [
            (x0, y0),  # Trên - trái
            (x1, y0),  # Trên - phải
            (x1, y1),  # Dưới - phải
            (x0, y1),  # Dưới - trái
        ]

        for anchor, (x, y) in zip(anchors, positions):
            self.canvas.coords(
                anchor,
                x - anchor_size / 2, y - anchor_size / 2,
                x + anchor_size / 2, y + anchor_size / 2
            )

    def move_text(self, event):
        """Xử lý việc di chuyển các trường văn bản hoặc hình ảnh trên canvas."""
        if self.selected_items:
            dx = event.x - self.last_move_x
            dy = event.y - self.last_move_y
            for item_id in self.selected_items.copy():
                field = self.get_field_by_text_id(item_id)
                if field:
                    # Di chuyển hình chữ nhật bao quanh
                    self.canvas.move(field["rect_id"], dx, dy)
                    # Di chuyển các điểm neo
                    for anchor in field.get("anchors", []):
                        self.canvas.move(anchor, dx, dy)
                    # Di chuyển văn bản hoặc hình ảnh
                    self.canvas.move(field["id"], dx, dy)
                    # Cập nhật tọa độ
                    field["x"] += dx / self.zoom_level
                    field["y"] += dy / self.zoom_level
            self.last_move_x = event.x
            self.last_move_y = event.y
            self.undo_stack.append(('move', list(self.selected_items), dx / self.zoom_level, dy / self.zoom_level))
            self.redo_stack.clear()
            self.update_preview()

    def update_canvas_font(self, event=None):
        """Cập nhật thuộc tính font của văn bản được chọn."""
        if self.selected_items:
            for item_id in self.selected_items:
                field = self.get_field_by_text_id(item_id)
                if field and field.get('type', 'Text') == 'Text':
                    # Kiểm tra kích thước font
                    try:
                        size = int(self.font_size_var.get())
                        if size <= 0:
                            raise ValueError
                    except ValueError:
                        messagebox.showerror("Lỗi nhập liệu", "Kích thước font phải là số nguyên dương.")
                        self.font_size_var.set(12)
                        return

                    self.canvas.itemconfig(item_id, font=(self.font_var.get(), int(self.font_size_var.get() * self.zoom_level)))
                    c = self.cmyk_vars['C'].get() / 100
                    m = self.cmyk_vars['M'].get() / 100
                    y = self.cmyk_vars['Y'].get() / 100
                    k = self.cmyk_vars['K'].get() / 100
                    r, g, b = self.cmyk_to_rgb(c, m, y, k)
                    color = f'#{int(r):02x}{int(g):02x}{int(b):02x}'
                    self.canvas.itemconfig(item_id, fill=color)
                    field["font"] = self.font_var.get()
                    field["size"] = self.font_size_var.get()
                    field["color_cmyk"] = {
                        "C": self.cmyk_vars["C"].get(),
                        "M": self.cmyk_vars["M"].get(),
                        "Y": self.cmyk_vars["Y"].get(),
                        "K": self.cmyk_vars["K"].get(),
                    }
            self.update_preview()

    def delete_text(self, event=None):
        """Xóa các trường văn bản hoặc hình ảnh được chọn khỏi canvas."""
        if self.selected_items:
            for item_id in list(self.selected_items):
                field = self.get_field_by_text_id(item_id)
                if field:
                    # Xóa các mục liên quan
                    items_to_delete = [field["id"], field["rect_id"]] + field.get("anchors", [])
                    if field.get("label_id"):
                        items_to_delete.append(field["label_id"])
                    for del_id in items_to_delete:
                        self.canvas.delete(del_id)
                    self.coords[self.current_page_index].remove(field)
                    self.undo_stack.append(('delete', field))
                    self.redo_stack.clear()  # Xóa ngăn xếp redo
            self.selected_items.clear()
            self.update_preview()

    def undo(self, event=None):
        """Hoàn tác hành động cuối cùng."""
        if self.undo_stack:
            action = self.undo_stack.pop()
            if action[0] == 'add':
                # Xóa mục đã thêm
                item = action[1]
                self.canvas.delete(item["id"])
                if item.get("label_id"):
                    self.canvas.delete(item["label_id"])
                for anchor in item.get("anchors", []):
                    self.canvas.delete(anchor)
                self.canvas.delete(item.get("rect_id", ""))
                self.coords[self.current_page_index].remove(item)
                self.redo_stack.append(('add', item))
            elif action[0] == 'move':
                # Di chuyển trở lại vị trí trước đó
                item_ids, dx, dy = action[1:]
                for item_id in item_ids:
                    self.canvas.move(item_id, -dx * self.zoom_level, -dy * self.zoom_level)
                    field = self.get_field_by_text_id(item_id)
                    if field:
                        self.canvas.move(field.get("label_id", ""), -dx * self.zoom_level, -dy * self.zoom_level)
                        for anchor in field.get("anchors", []):
                            self.canvas.move(anchor, -dx * self.zoom_level, -dy * self.zoom_level)
                        field["x"] -= dx
                        field["y"] -= dy
                self.redo_stack.append(('move', item_ids, dx, dy))
            elif action[0] == 'delete':
                # Thêm lại mục đã xóa
                item = action[1]
                if item.get('type', 'Text') == 'Text':
                    self.add_text(item["x"] * self.zoom_level, item["y"] * self.zoom_level)
                elif item.get('type') == 'Image':
                    self.add_text(item["x"] * self.zoom_level, item["y"] * self.zoom_level)
                self.coords[self.current_page_index].append(item)
                self.redo_stack.append(('delete', item))
            self.update_preview()

    def redo(self, event=None):
        """Làm lại hành động đã hoàn tác."""
        if self.redo_stack:
            action = self.redo_stack.pop()
            if action[0] == 'add':
                # Thêm lại mục
                item = action[1]
                self.add_text(item["x"] * self.zoom_level, item["y"] * self.zoom_level)
                self.coords[self.current_page_index].append(item)
                self.undo_stack.append(('add', item))
            elif action[0] == 'move':
                # Di chuyển đến vị trí mới
                item_ids, dx, dy = action[1:]
                for item_id in item_ids:
                    self.canvas.move(item_id, dx * self.zoom_level, dy * self.zoom_level)
                    field = self.get_field_by_text_id(item_id)
                    if field:
                        self.canvas.move(field.get("label_id", ""), dx * self.zoom_level, dy * self.zoom_level)
                        for anchor in field.get("anchors", []):
                            self.canvas.move(anchor, dx * self.zoom_level, dy * self.zoom_level)
                        field["x"] += dx
                        field["y"] += dy
                self.undo_stack.append(('move', item_ids, dx, dy))
            elif action[0] == 'delete':
                # Xóa mục
                item = action[1]
                self.canvas.delete(item["id"])
                if item.get("label_id"):
                    self.canvas.delete(item.get("label_id"))
                for anchor in item.get("anchors", []):
                    self.canvas.delete(anchor)
                self.canvas.delete(item.get("rect_id", ""))
                self.coords[self.current_page_index].remove(item)
                self.undo_stack.append(('delete', item))
            self.update_preview()

    def load_excel(self):
        """Load Excel file and display data."""
        self.excel_path = filedialog.askopenfilename(filetypes=[("Excel files", ".xlsx"), ("Excel files", ".xls")])

        if self.excel_path:
            try:
                self.df = pd.read_excel(self.excel_path)
                # Generate numbering data matching the length of Excel data
                numbering_df = self.generate_numbering_data(num_rows=len(self.df))
                self.df['Numbering'] = numbering_df['Numbering'].values
                # Add 'Numbering' to the list of fields if not already present
                if 'Numbering' not in self.df.columns:
                    self.df['Numbering'] = numbering_df['Numbering'].values
                self.fields = self.df.columns.tolist()
                self.selected_field.set(self.fields[0])  # Default selected field
                self.create_field_radiobuttons()
                self.reset_preview()
                self.excel_row_index = 0  # Reset index to 0
            except Exception as e:
                messagebox.showerror("Error", f"Cannot read Excel file: {e}")
                self.excel_path = None
                self.df = None
        else:
            # If no Excel file is selected, use Numbering data only
            self.df = self.generate_numbering_data()
            if self.df.empty:
                messagebox.showerror("Error", "Numbering data is empty. Please check the parameters.")
                return
            self.fields = ['Numbering']
            self.selected_field.set('Numbering')  # Set default field
            self.create_field_radiobuttons()
            self.reset_preview()
            self.excel_row_index = 0

    def create_field_radiobuttons(self):
        """Tạo các nút radio cho các trường dữ liệu Excel."""
        # Xóa khung nút hiện có và tạo mới
        for widget in self.excel_button_frame.winfo_children():
            widget.destroy()

        # Tạo canvas để chứa các nút với thanh cuộn
        canvas = tk.Canvas(self.excel_button_frame, bg='#2c2f33', highlightthickness=0)
        scrollbar = tk.Scrollbar(self.excel_button_frame, orient=tk.HORIZONTAL, command=canvas.xview)
        canvas.configure(xscrollcommand=scrollbar.set)

        inner_frame = tk.Frame(canvas, bg='#2c2f33')

        # Thêm inner_frame vào canvas
        canvas.create_window((0, 0), window=inner_frame, anchor='nw')

        canvas.pack(side=tk.TOP, fill=tk.X, expand=True)
        scrollbar.pack(side=tk.BOTTOM, fill=tk.X)

        # Cập nhật scrollregion sau khi thêm widget
        def configure_canvas(event):
            canvas.configure(scrollregion=canvas.bbox("all"))
        inner_frame.bind("<Configure>", configure_canvas)

        # Đặt tất cả các nút trong inner_frame
        for field in self.fields:
            rb = tk.Radiobutton(inner_frame, text=field, variable=self.selected_field, value=field,
                                bg='#2c2f33', fg='white', selectcolor='#7289da')
            rb.pack(side=tk.LEFT, padx=5, pady=5)
        canvas.config(scrollregion=canvas.bbox("all"))

    def cmyk_to_rgb(self, c, m, y, k):
        """Chuyển đổi giá trị CMYK sang RGB."""
        r = min(max(0, 255 * (1 - c) * (1 - k)), 255)
        g = min(max(0, 255 * (1 - m) * (1 - k)), 255)
        b = min(max(0, 255 * (1 - y) * (1 - k)), 255)
        return int(r), int(g), int(b)

    def mm_to_points(self, mm):
        """Chuyển đổi millimeters sang points."""
        return mm * 72 / 25.4

    def points_to_mm(self, points):
        """Chuyển đổi points sang millimeters."""
        return points * 25.4 / 72

    def create_text_pdf(self, output_pdf_path, row, selected_font_path):
        """Tạo tệp PDF tùy chỉnh với các trường văn bản và hình ảnh."""
        c = reportlab_canvas.Canvas(output_pdf_path, pagesize=(self.original_width, self.original_height))
        total_pages = len(self.pdf_doc)

        for page_index in range(total_pages):
            c.setPageSize((self.original_width, self.original_height))
            if page_index in self.coords:
                fields = self.coords[page_index]
                for item in fields:
                    field_name = item["field"]
                    if field_name not in row:
                        print(f"Trường '{field_name}' không có trong dữ liệu. Bỏ qua...")
                        continue
                    x = item["x"]
                    y = item["y"]
                    if item.get('type', 'Text') == 'Text':
                        font_name = item["font"]
                        size = item["size"]
                        color_cmyk = item["color_cmyk"]
                        bounding_width = item["width"]
                        bounding_height = item["height"]

                        # Lấy đường dẫn font
                        font_path = self.font_file_dict.get(font_name, selected_font_path)
                        if not self.check_font_availability(font_path):
                            font_path = selected_font_path

                        # Đăng ký và đặt font
                        if font_name not in pdfmetrics.getRegisteredFontNames():
                            pdfmetrics.registerFont(TTFont(font_name, font_path))

                        value = str(row[field_name])

                        # Cài đặt màu
                        c.setFillColorCMYK(
                            color_cmyk["C"] / 100,
                            color_cmyk["M"] / 100,
                            color_cmyk["Y"] / 100,
                            color_cmyk["K"] / 100
                        )

                        # Tính kích thước nội dung
                        text_width = pdfmetrics.stringWidth(value, font_name, size)
                        text_height = size  # Xấp xỉ chiều cao bằng kích thước font

                        # Tính toán scale
                        scale_x = scale_y = 1.0
                        if text_width > bounding_width:
                            scale_x = bounding_width / text_width
                        if text_height > bounding_height:
                            scale_y = bounding_height / text_height

                        scale_option_display = self.scale_option.get()
                        scale_option = self.scale_option_mapping.get(scale_option_display, "uniform")

                        if scale_option == "uniform":
                            scale = min(scale_x, scale_y)
                            new_size = size * scale
                            c.setFont(font_name, new_size)
                            text_width_scaled = pdfmetrics.stringWidth(value, font_name, new_size)
                            adjusted_y = self.original_height - y - new_size / 2
                            adjusted_x = x - text_width_scaled / 2
                            c.drawString(adjusted_x, adjusted_y, value)
                        elif scale_option == "horizontal":
                            scale = scale_x
                            new_size = size  # Giữ nguyên kích thước font
                            c.setFont(font_name, new_size)
                            c.saveState()
                            c.translate(x, self.original_height - y)
                            c.scale(scale, 1)
                            adjusted_x = -text_width / 2
                            adjusted_y = -new_size / 2
                            c.drawString(adjusted_x, adjusted_y, value)
                            c.restoreState()
                        elif scale_option == "vertical":
                            scale = scale_y
                            new_size = size  # Giữ nguyên kích thước font
                            c.setFont(font_name, new_size)
                            c.saveState()
                            c.translate(x, self.original_height - y)
                            c.scale(1, scale)
                            adjusted_x = -text_width / 2
                            adjusted_y = -new_size / 2
                            c.drawString(adjusted_x, adjusted_y, value)
                            c.restoreState()
                        elif scale_option == "kerning":
                            # Điều chỉnh khoảng cách giữa các ký tự
                            total_characters = len(value)
                            if total_characters > 1:
                                total_space_needed = text_width - bounding_width
                                if total_space_needed > 0:
                                    extra_space_per_char = total_space_needed / (total_characters - 1)
                                    c.setFont(font_name, size)
                                    adjusted_x_positions = []
                                    current_x = x - text_width / 2
                                    for char in value:
                                        adjusted_x_positions.append(current_x)
                                        char_width = pdfmetrics.stringWidth(char, font_name, size)
                                        current_x += char_width - extra_space_per_char
                                    # Vẽ từng ký tự tại vị trí mới
                                    adjusted_y = self.original_height - y - size / 2
                                    for i, char in enumerate(value):
                                        c.drawString(adjusted_x_positions[i], adjusted_y, char)

                    elif item.get('type') == 'Image':
                        image_path = str(row[field_name])
                        if not os.path.isabs(image_path):
                            image_path = os.path.join(os.path.dirname(self.excel_path), image_path)
                        if not os.path.exists(image_path):
                            print(f"Hình ảnh '{image_path}' không tồn tại. Bỏ qua...")
                            continue
                        # Sử dụng chiều rộng và chiều cao đã lưu
                        width_in_points = item["width_points"]
                        height_in_points = item["height_points"]
                        adjusted_y = self.original_height - y - height_in_points / 2
                        adjusted_x = x - width_in_points / 2
                        try:
                            c.drawImage(image_path, adjusted_x, adjusted_y,
                                        width=width_in_points,
                                        height=height_in_points)
                        except Exception as e:
                            print(f"Lỗi khi chèn hình ảnh: {e}")

            c.showPage()

        c.save()

    def export_pdfs(self):
        """Export PDF files with data from Excel or numbering, and merge them."""
        if not self.file_path:
            messagebox.showerror("Lỗi", "Hãy chọn tệp trước khi xuất.")
            return
        if not self.coords:
            messagebox.showerror("Lỗi", "Chưa đặt trường nào trên tệp.")
            return

        # If self.df is None or empty, generate numbering data
        if self.df is None or self.df.empty:
            self.df = self.generate_numbering_data()
            if self.df.empty:
                messagebox.showerror("Lỗi", "Dữ liệu đánh số trống. Vui lòng kiểm tra các tham số.")
                return
            self.fields = ['Numbering']
            self.selected_field.set('Numbering')
            self.create_field_radiobuttons()
            self.excel_row_index = 0  # Reset index to 0

        # Ask user to select the output folder
        output_folder = filedialog.askdirectory()
        if not output_folder:
            return
        
        # Create a 'data' subfolder inside the selected output folder
        data_folder = os.path.join(output_folder, 'data')
        os.makedirs(data_folder, exist_ok=True)

        selected_font_name = self.font_var.get()
        selected_font_path = self.font_file_dict.get(selected_font_name, None)
        if not selected_font_path or not self.check_font_availability(selected_font_path):
            messagebox.showerror("Lỗi", f"Không thể mở tệp font {selected_font_name}, sử dụng font mặc định.")
            if sys.platform == "win32":
                selected_font_path = "C:/Windows/Fonts/Arial.ttf"
            else:
                selected_font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"

        # List to store paths of final PDFs
        individual_pdfs = []

        temp_dir = tempfile.gettempdir()

        # Điều chỉnh phần này để tạo hai trang PDF cho mỗi hàng dữ liệu
        for index, row in self.df.iterrows():
            # Open the original PDF template
            doc = fitz.open(self.temp_pdf_path)

            # Create a unique name for the final output file
            fields_in_name = '_'.join([str(row[field]) for field in self.fields[:3]])  # Customize as needed
            if not fields_in_name.strip():
                fields_in_name = f'output_{index}'

            # File path for the final output PDF in the 'data' folder
            final_output_path = os.path.join(data_folder, f"{fields_in_name}_final.pdf")

            # Generate the custom data PDF with two pages in the temp directory
            temp_output_pdf_path = os.path.join(temp_dir, f"temp_custom_{uuid.uuid4()}.pdf")
            
            # Create custom PDF with two pages
            self.create_text_pdf_with_two_pages(temp_output_pdf_path, row, selected_font_path)

            # Open the custom data PDF
            new_doc = fitz.open(temp_output_pdf_path)

            # Ensure the custom PDF has the same number of pages as the original
            if new_doc.page_count != len(doc):
                messagebox.showerror("Lỗi", "Số trang trong tệp gốc và tệp mới không khớp.")
                new_doc.close()
                doc.close()
                continue

            # Overlay the custom data onto the original PDF
            for page_num in range(len(doc)):
                page = doc.load_page(page_num)
                rect = fitz.Rect(0, 0, self.original_width, self.original_height)
                page.show_pdf_page(rect, new_doc, page_num)

            new_doc.close()

            # Save the final PDF into the 'data' folder
            doc.save(final_output_path)
            individual_pdfs.append(final_output_path)

            doc.close()

            # Remove the temporary custom PDF
            try:
                os.remove(temp_output_pdf_path)
            except Exception as e:
                print(f"Lỗi khi xóa tệp tạm: {e}")

        # Merge the individual final PDFs into one, saved in the selected output folder
        if individual_pdfs:
            merged_pdf_path = os.path.join(output_folder, "merged_final_output.pdf")
            self.merge_pdfs(individual_pdfs, merged_pdf_path)

            # Apply imposition automatically
            # Get parameters from the UI
            new_page_width = self.new_page_width_var.get()
            new_page_height = self.new_page_height_var.get()
            margin_top = self.margin_top_var.get()
            margin_bottom = self.margin_bottom_var.get()
            margin_left = self.margin_left_var.get()
            margin_right = self.margin_right_var.get()
            cropmark_offset = self.cropmark_offset_var.get()

            # Convert units from mm to points
            new_page_width_pt = self.mm_to_points(new_page_width)
            new_page_height_pt = self.mm_to_points(new_page_height)
            margin_top_pt = self.mm_to_points(margin_top)
            margin_bottom_pt = self.mm_to_points(margin_bottom)
            margin_left_pt = self.mm_to_points(margin_left)
            margin_right_pt = self.mm_to_points(margin_right)
            cropmark_offset_pt = self.mm_to_points(cropmark_offset)

            # Apply imposition, save the imposed PDF in the selected output folder
            imposed_pdf_path = os.path.join(output_folder, "imposed_output.pdf")
            self.apply_imposition(merged_pdf_path, imposed_pdf_path)

            # Inform the user that all three results have been successfully exported
            messagebox.showinfo("Thành công", f"File PDF đã được xuất thành công.\n\n- Các file PDF cá nhân được lưu trong thư mục 'data'.\n- File PDF đã merge: {merged_pdf_path}\n- File PDF đã áp dụng imposition: {imposed_pdf_path}")

        else:
            messagebox.showerror("Lỗi", "Không có file PDF nào được tạo.")

    def create_text_pdf_with_two_pages(self, output_pdf_path, row, selected_font_path):
        """Create a two-page PDF with the same data for front and back."""
        c = reportlab_canvas.Canvas(output_pdf_path, pagesize=(self.original_width, self.original_height))

        for page_index in range(2):  # Tạo hai trang
            c.setPageSize((self.original_width, self.original_height))
            if page_index in self.coords:
                fields = self.coords[page_index]
            else:
                fields = self.coords.get(0, [])  # Nếu không có coords cho trang 1, sử dụng trang 0

            for item in fields:
                field_name = item["field"]
                if field_name not in row:
                    print(f"Trường '{field_name}' không có trong dữ liệu. Bỏ qua...")
                    continue
                x = item["x"]
                y = item["y"]
                if item.get('type', 'Text') == 'Text':
                    font_name = item["font"]
                    size = item["size"]
                    color_cmyk = item["color_cmyk"]
                    bounding_width = item["width"]
                    bounding_height = item["height"]

                    # Lấy đường dẫn font
                    font_path = self.font_file_dict.get(font_name, selected_font_path)
                    if not self.check_font_availability(font_path):
                        font_path = selected_font_path

                    # Đăng ký và đặt font
                    if font_name not in pdfmetrics.getRegisteredFontNames():
                        pdfmetrics.registerFont(TTFont(font_name, font_path))

                    value = str(row[field_name])

                    # Cài đặt màu
                    c.setFillColorCMYK(
                        color_cmyk["C"] / 100,
                        color_cmyk["M"] / 100,
                        color_cmyk["Y"] / 100,
                        color_cmyk["K"] / 100
                    )

                    # Tính kích thước nội dung
                    text_width = pdfmetrics.stringWidth(value, font_name, size)
                    text_height = size  # Xấp xỉ chiều cao bằng kích thước font

                    # Tính toán scale
                    scale_x = scale_y = 1.0
                    if text_width > bounding_width:
                        scale_x = bounding_width / text_width
                    if text_height > bounding_height:
                        scale_y = bounding_height / text_height

                    scale_option_display = self.scale_option.get()
                    scale_option = self.scale_option_mapping.get(scale_option_display, "uniform")

                    if scale_option == "uniform":
                        scale = min(scale_x, scale_y)
                        new_size = size * scale
                        c.setFont(font_name, new_size)
                        text_width_scaled = pdfmetrics.stringWidth(value, font_name, new_size)
                        adjusted_y = self.original_height - y - new_size / 2
                        adjusted_x = x - text_width_scaled / 2
                        c.drawString(adjusted_x, adjusted_y, value)
                    elif scale_option == "horizontal":
                        scale = scale_x
                        new_size = size  # Giữ nguyên kích thước font
                        c.setFont(font_name, new_size)
                        c.saveState()
                        c.translate(x, self.original_height - y)
                        c.scale(scale, 1)
                        adjusted_x = -text_width / 2
                        adjusted_y = -new_size / 2
                        c.drawString(adjusted_x, adjusted_y, value)
                        c.restoreState()
                    elif scale_option == "vertical":
                        scale = scale_y
                        new_size = size  # Giữ nguyên kích thước font
                        c.setFont(font_name, new_size)
                        c.saveState()
                        c.translate(x, self.original_height - y)
                        c.scale(1, scale)
                        adjusted_x = -text_width / 2
                        adjusted_y = -new_size / 2
                        c.drawString(adjusted_x, adjusted_y, value)
                        c.restoreState()
                    elif scale_option == "kerning":
                        # Điều chỉnh khoảng cách giữa các ký tự
                        total_characters = len(value)
                        if total_characters > 1:
                            total_space_needed = text_width - bounding_width
                            if total_space_needed > 0:
                                extra_space_per_char = total_space_needed / (total_characters - 1)
                                c.setFont(font_name, size)
                                adjusted_x_positions = []
                                current_x = x - text_width / 2
                                for char in value:
                                    adjusted_x_positions.append(current_x)
                                    char_width = pdfmetrics.stringWidth(char, font_name, size)
                                    current_x += char_width - extra_space_per_char
                                # Vẽ từng ký tự tại vị trí mới
                                adjusted_y = self.original_height - y - size / 2
                                for i, char in enumerate(value):
                                    c.drawString(adjusted_x_positions[i], adjusted_y, char)

                elif item.get('type') == 'Image':
                    image_path = str(row[field_name])
                    if not os.path.isabs(image_path):
                        image_path = os.path.join(os.path.dirname(self.excel_path), image_path)
                    if not os.path.exists(image_path):
                        print(f"Hình ảnh '{image_path}' không tồn tại. Bỏ qua...")
                        continue
                    # Sử dụng chiều rộng và chiều cao đã lưu
                    width_in_points = item["width_points"]
                    height_in_points = item["height_points"]
                    adjusted_y = self.original_height - y - height_in_points / 2
                    adjusted_x = x - width_in_points / 2
                    try:
                        c.drawImage(image_path, adjusted_x, adjusted_y,
                                    width=width_in_points,
                                    height=height_in_points)
                    except Exception as e:
                        print(f"Lỗi khi chèn hình ảnh: {e}")

            c.showPage()

        c.save()

    def merge_pdfs(self, pdf_paths, output_path):
        """Merge nhiều tệp PDF thành một."""
        merged_doc = fitz.open()
        for pdf_path in pdf_paths:
            individual_doc = fitz.open(pdf_path)
            merged_doc.insert_pdf(individual_doc)
            individual_doc.close()
        merged_doc.save(output_path)
        merged_doc.close()

    def align_left(self):
        """Căn trái các mục đã chọn."""
        if not self.selected_items:
            messagebox.showinfo("Thông báo", "Chưa chọn mục nào để căn chỉnh.")
            return
        min_x = None
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                x = field['x']
                if min_x is None or x < min_x:
                    min_x = x
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                dx = (min_x - field['x']) * self.zoom_level
                self.canvas.move(item_id, dx, 0)
                field['x'] = min_x
                if field.get('label_id'):
                    self.canvas.move(field['label_id'], dx, 0)
        self.update_preview()

    def align_right(self):
        """Căn phải các mục đã chọn."""
        if not self.selected_items:
            messagebox.showinfo("Thông báo", "Chưa chọn mục nào để căn chỉnh.")
            return
        max_x = None
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                x = field['x']
                if max_x is None or x > max_x:
                    max_x = x
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                dx = (max_x - field['x']) * self.zoom_level
                self.canvas.move(item_id, dx, 0)
                field['x'] = max_x
                if field.get('label_id'):
                    self.canvas.move(field['label_id'], dx, 0)
        self.update_preview()

    def align_top(self):
        """Căn trên các mục đã chọn."""
        if not self.selected_items:
            messagebox.showinfo("Thông báo", "Chưa chọn mục nào để căn chỉnh.")
            return
        min_y = None
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                y = field['y']
                if min_y is None or y < min_y:
                    min_y = y
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                dy = (min_y - field['y']) * self.zoom_level
                self.canvas.move(item_id, 0, dy)
                field['y'] = min_y
                if field.get('label_id'):
                    self.canvas.move(field['label_id'], 0, dy)
        self.update_preview()

    def align_bottom(self):
        """Căn dưới các mục đã chọn."""
        if not self.selected_items:
            messagebox.showinfo("Thông báo", "Chưa chọn mục nào để căn chỉnh.")
            return
        max_y = None
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                y = field['y']
                if max_y is None or y > max_y:
                    max_y = y
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                dy = (max_y - field['y']) * self.zoom_level
                self.canvas.move(item_id, 0, dy)
                field['y'] = max_y
                if field.get('label_id'):
                    self.canvas.move(field['label_id'], 0, dy)
        self.update_preview()

    def align_center(self):
        """Căn giữa các mục đã chọn."""
        if not self.selected_items:
            messagebox.showinfo("Thông báo", "Chưa chọn mục nào để căn chỉnh.")
            return
        total_x = 0
        count = 0
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                total_x += field['x']
                count += 1
        if count == 0:
            return
        avg_x = total_x / count
        for item_id in self.selected_items:
            field = self.get_field_by_text_id(item_id)
            if field:
                dx = (avg_x - field['x']) * self.zoom_level
                self.canvas.move(item_id, dx, 0)
                field['x'] = avg_x
                if field.get('label_id'):
                    self.canvas.move(field['label_id'], dx, 0)
        self.update_preview()

    def zoom_in(self):
        """Phóng to canvas."""
        self.zoom_level *= 1.1
        self.display_pdf_page()
        # self.display_preview_page()

    def zoom_out(self):
        """Thu nhỏ canvas."""
        self.zoom_level /= 1.1
        self.display_pdf_page()
        # self.display_preview_page()

    def zoom_with_mouse_wheel(self, event):
        """Phóng to hoặc thu nhỏ bằng con lăn chuột."""
        if event.delta > 0 or event.num == 4:
            self.zoom_in()
        elif event.delta < 0 or event.num == 5:
            self.zoom_out()

    def preview_pdf(self):
        """Tạo và hiển thị PDF xem trước trong một cửa sổ mới."""
        if not self.file_path or not self.excel_path:
            messagebox.showerror("Lỗi", "Hãy chọn tệp và Excel trước khi xem trước.")
            return
        if not self.coords:
            messagebox.showerror("Lỗi", "Chưa đặt trường nào trên tệp.")
            return

        # Tạo PDF xem trước trong thư mục tạm
        temp_dir = tempfile.gettempdir()
        self.preview_pdf_path = os.path.join(temp_dir, "preview.pdf")

        selected_font_name = self.font_var.get()
        selected_font_path = self.font_file_dict.get(selected_font_name, None)
        if not selected_font_path or not self.check_font_availability(selected_font_path):
            if sys.platform == "win32":
                selected_font_path = "C:/Windows/Fonts/Arial.ttf"
            else:
                selected_font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"

        # Tạo một file PDF duy nhất chứa tất cả các trang với dữ liệu được điền
        doc = fitz.open()

        # Danh sách lưu các tệp PDF cá nhân
        individual_pdfs = []

        for index, row in self.df.iterrows():
            temp_pdf_path = os.path.join(temp_dir, f"temp_data_{uuid.uuid4()}.pdf")

            # Tạo PDF tùy chỉnh
            self.create_text_pdf(temp_pdf_path, row, selected_font_path)

            # Mở PDF tùy chỉnh và thêm vào doc
            temp_doc = fitz.open(temp_pdf_path)
            doc.insert_pdf(temp_doc)
            temp_doc.close()

            # Thêm vào danh sách để xóa sau này
            individual_pdfs.append(temp_pdf_path)

        # Lưu tài liệu PDF với dữ liệu
        output_pdf_path = os.path.join(temp_dir, "output_with_data_preview.pdf")
        doc.save(output_pdf_path)
        doc.close()

        # Áp dụng Imposition và lưu vào tệp mới
        imposed_preview_pdf_path = os.path.join(temp_dir, "imposed_preview.pdf")
        self.apply_imposition(output_pdf_path, imposed_preview_pdf_path)

        # Xóa các tệp PDF tạm thời
        try:
            for pdf_path in individual_pdfs:
                os.remove(pdf_path)
            os.remove(output_pdf_path)
        except Exception as e:
            print(f"Lỗi khi xóa tệp tạm: {e}")

        # Mở tệp xem trước đã áp dụng imposition
        self.preview_pdf_path = imposed_preview_pdf_path
        if self.preview_doc is not None:
            self.preview_doc.close()
        self.preview_doc = fitz.open(self.preview_pdf_path)
        self.preview_page_index = 0

        # Tạo cửa sổ xem trước mới nếu chưa có
        if hasattr(self, 'preview_window') and self.preview_window.winfo_exists():
            # Nếu cửa sổ đã tồn tại, chỉ cần cập nhật canvas
            self.display_preview_page()
        else:
            # Tạo cửa sổ xem trước mới
            self.preview_window = tk.Toplevel(self.root)
            self.preview_window.title("Xem trước")
            self.preview_window.geometry("800x600")

            # Thêm sự kiện khi đóng cửa sổ
            self.preview_window.protocol("WM_DELETE_WINDOW", self.close_preview_window)

            # Tạo canvas trong cửa sổ xem trước
            self.preview_canvas = tk.Canvas(self.preview_window, bg="gray")
            self.preview_canvas.pack(fill=tk.BOTH, expand=True)

            # Thêm thanh cuộn cho canvas
            hbar = tk.Scrollbar(self.preview_window, orient=tk.HORIZONTAL)
            hbar.pack(side=tk.BOTTOM, fill=tk.X)
            hbar.config(command=self.preview_canvas.xview)
            vbar = tk.Scrollbar(self.preview_window, orient=tk.VERTICAL)
            vbar.pack(side=tk.RIGHT, fill=tk.Y)
            vbar.config(command=self.preview_canvas.yview)
            self.preview_canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)

            # Hiển thị trang xem trước
            self.display_preview_page()

        self.is_previewing = True

    def display_preview_page(self):
        """Hiển thị trang xem trước trong cửa sổ xem trước."""
        if self.preview_doc:
            try:
                page = self.preview_doc.load_page(self.preview_page_index)
                zoom_matrix = fitz.Matrix(self.zoom_level, self.zoom_level)
                pix = page.get_pixmap(matrix=zoom_matrix)
                img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
                self.preview_image = ImageTk.PhotoImage(img)
                self.preview_canvas.delete("all")
                self.preview_canvas.create_image(0, 0, anchor="nw", image=self.preview_image)
                # Cập nhật kích thước canvas
                self.preview_canvas.config(width=pix.width, height=pix.height)
                self.preview_canvas.config(scrollregion=self.preview_canvas.bbox(tk.ALL))
            except Exception as e:
                messagebox.showerror("Lỗi", f"Không thể hiển thị trang: {e}")

    def close_preview_window(self):
        """Xử lý khi đóng cửa sổ xem trước."""
        if self.preview_doc is not None:
            self.preview_doc.close()
            self.preview_doc = None
        if hasattr(self, 'preview_window') and self.preview_window.winfo_exists():
            self.preview_window.destroy()
        self.is_previewing = False

    def update_preview(self):
        """Cập nhật xem trước khi có thay đổi."""
        if self.is_previewing and hasattr(self, 'preview_window') and self.preview_window.winfo_exists():
            self.preview_pdf()

    def reset_preview(self):
        """Đặt lại xem trước khi tải tệp hoặc Excel mới."""
        if hasattr(self, 'preview_window') and self.preview_window.winfo_exists():
            self.preview_window.destroy()
        self.preview_doc = None
        self.preview_pdf_path = None
        self.preview_page_index = 0
        self.is_previewing = False
        self.excel_row_index = 0  # Đặt lại chỉ số hàng dữ liệu..

    def on_closing(self):
        """Xử lý khi đóng ứng dụng."""
        if hasattr(self, 'temp_pdf_path') and os.path.exists(self.temp_pdf_path):
            try:
                os.remove(self.temp_pdf_path)
            except Exception as e:
                print(f"Lỗi khi xóa tệp tạm: {e}")
        if self.preview_doc is not None:
            self.preview_doc.close()
        if hasattr(self, 'preview_window') and self.preview_window.winfo_exists():
            self.preview_window.destroy()
        self.root.destroy()

    def on_imposition_option_changed(self, event):
        """Xử lý khi người dùng thay đổi tùy chọn imposition."""
        # Nếu cần cập nhật hoặc thực hiện hành động gì khi tùy chọn thay đổi, bạn thêm mã xử lý tại đây
        pass

    def apply_imposition(self, input_pdf_path, output_pdf_path):
        """Áp dụng Imposition để sắp xếp nhiều dữ liệu trên một trang."""
        # Lấy loại imposition người dùng đã chọn
        imposition_type = self.imposition_option.get()

        if imposition_type == "Bình zigzag":
            self.apply_zigzag_imposition(input_pdf_path, output_pdf_path)
        elif imposition_type == "Bình tự trở":
            self.apply_self_turn_imposition(input_pdf_path, output_pdf_path)
        elif imposition_type == "Bình AB":
            self.apply_ab_imposition(input_pdf_path, output_pdf_path)
        else:
            messagebox.showerror("Lỗi", "Loại imposition không được hỗ trợ.")

    def apply_zigzag_imposition(self, input_pdf_path, output_pdf_path):
        """Applies Imposition to arrange multiple data on a page in Zigzag style."""
        # Get parameters from the UI
        new_page_width = self.new_page_width_var.get()
        new_page_height = self.new_page_height_var.get()
        margin_top = self.margin_top_var.get()
        margin_bottom = self.margin_bottom_var.get()
        margin_left = self.margin_left_var.get()
        margin_right = self.margin_right_var.get()
        cropmark_offset = self.cropmark_offset_var.get()
        cropmark_length = self.cropmark_length_var.get()

        # Convert units from mm to points
        new_page_width_pt = self.mm_to_points(new_page_width)
        new_page_height_pt = self.mm_to_points(new_page_height)
        margin_top_pt = self.mm_to_points(margin_top)
        margin_bottom_pt = self.mm_to_points(margin_bottom)
        margin_left_pt = self.mm_to_points(margin_left)
        margin_right_pt = self.mm_to_points(margin_right)
        cropmark_offset_pt = self.mm_to_points(cropmark_offset)
        crop_length_pt = self.mm_to_points(cropmark_length)

        # Độ dày của crop marks
        crop_thickness = 0.5  # Bạn có thể thay đổi giá trị này

        # Calculate available area
        available_width = new_page_width_pt - margin_left_pt - margin_right_pt
        available_height = new_page_height_pt - margin_top_pt - margin_bottom_pt

        # Read the input PDF
        doc = fitz.open(input_pdf_path)
        new_doc = fitz.open()
        total_pages = len(doc)

        # Get the size of the input page
        input_page = doc.load_page(0)
        bbox = input_page.rect
        entry_width_orig = bbox.width
        entry_height_orig = bbox.height

        # Options for rotation and non-rotation
        options = [
            {'rotate': False, 'entry_width': entry_width_orig, 'entry_height': entry_height_orig},
            {'rotate': True, 'entry_width': entry_height_orig, 'entry_height': entry_width_orig},
        ]

        # Determine the best arrangement
        best_arrangement = None
        max_total_entries = 0
        for option in options:
            entries_per_row = int(available_width // option['entry_width'])
            entries_per_column = int(available_height // option['entry_height'])
            total_entries = entries_per_row * entries_per_column
            if total_entries > max_total_entries:
                max_total_entries = total_entries
                best_arrangement = option
                best_arrangement['entries_per_row'] = entries_per_row
                best_arrangement['entries_per_column'] = entries_per_column

        if not best_arrangement or best_arrangement['entries_per_row'] == 0 or best_arrangement['entries_per_column'] == 0:
            messagebox.showerror("Error", "Page size or margins are too small to fit any content.")
            return

        # Tính toán lại x_offset và y_offset để căn giữa nội dung trên trang
        total_grid_width = best_arrangement['entries_per_row'] * best_arrangement['entry_width']
        total_grid_height = best_arrangement['entries_per_column'] * best_arrangement['entry_height']
        x_offset = (new_page_width_pt - total_grid_width) / 2
        y_offset = (new_page_height_pt - total_grid_height) / 2

        page_index = 0
        while page_index < total_pages:
            new_page = new_doc.new_page(width=new_page_width_pt, height=new_page_height_pt)

            arranged_rects = []  # Danh sách lưu vị trí của các đối tượng để xác định cạnh ngoài cùng

            for row in range(best_arrangement['entries_per_column']):
                y = y_offset + row * best_arrangement['entry_height']
                for col in range(best_arrangement['entries_per_row']):
                    x = x_offset + col * best_arrangement['entry_width']
                    if page_index >= total_pages:
                        break
                    rect = fitz.Rect(
                        x,
                        y,
                        x + best_arrangement['entry_width'],
                        y + best_arrangement['entry_height']
                    )
                    if best_arrangement['rotate']:
                        new_page.show_pdf_page(rect, doc, pno=page_index, rotate=90)
                    else:
                        new_page.show_pdf_page(rect, doc, pno=page_index)

                    arranged_rects.append((rect, row, col))  # Lưu thông tin vị trí

                    page_index += 1
                if page_index >= total_pages:
                    break

            # Sau khi đặt tất cả các đối tượng trên trang, thêm crop marks vào các cạnh ngoài cùng
            for rect, row, col in arranged_rects:
                edges = []
                if row == 0:
                    edges.append('top')
                if row == best_arrangement['entries_per_column'] - 1:
                    edges.append('bottom')
                if col == 0:
                    edges.append('left')
                if col == best_arrangement['entries_per_row'] - 1:
                    edges.append('right')

                if edges:
                    self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

        # Save the imposed PDF
        new_doc.save(output_pdf_path)
        new_doc.close()
        doc.close()

    def apply_self_turn_imposition(self, input_pdf_path, output_pdf_path):
        """Applies Imposition to arrange data on a page in Self-Turn style."""
        # Get parameters from the UI
        new_page_width = self.new_page_width_var.get()
        new_page_height = self.new_page_height_var.get()
        margin_top = self.margin_top_var.get()
        margin_bottom = self.margin_bottom_var.get()
        margin_left = self.margin_left_var.get()
        margin_right = self.margin_right_var.get()
        cropmark_offset = self.cropmark_offset_var.get()
        cropmark_length = self.cropmark_length_var.get()

        # Convert units from mm to points
        new_page_width_pt = self.mm_to_points(new_page_width)
        new_page_height_pt = self.mm_to_points(new_page_height)
        margin_top_pt = self.mm_to_points(margin_top)
        margin_bottom_pt = self.mm_to_points(margin_bottom)
        margin_left_pt = self.mm_to_points(margin_left)
        margin_right_pt = self.mm_to_points(margin_right)
        cropmark_offset_pt = self.mm_to_points(cropmark_offset)
        crop_length_pt = self.mm_to_points(cropmark_length)

        # Độ dày của crop marks
        crop_thickness = 0.5

        # Tính diện tích khả dụng trên trang
        available_width = new_page_width_pt - margin_left_pt - margin_right_pt
        available_height = new_page_height_pt - margin_top_pt - margin_bottom_pt

        # Đọc tệp PDF đầu vào
        doc = fitz.open(input_pdf_path)
        new_doc = fitz.open()
        total_pages = len(doc)

        # Lấy kích thước của dữ liệu
        input_page = doc.load_page(0)
        bbox = input_page.rect
        entry_width_orig = bbox.width
        entry_height_orig = bbox.height

        # Các tùy chọn xoay và không xoay
        options = [
            {'rotate': False, 'entry_width': entry_width_orig, 'entry_height': entry_height_orig},
            {'rotate': True, 'entry_width': entry_height_orig, 'entry_height': entry_width_orig},
        ]

        # Tìm cách sắp xếp tốt nhất (chia theo chiều dọc)
        best_arrangement = None
        max_total_entries = 0

        for option in options:
            # Chia trang theo chiều dọc (trên và dưới)
            entries_per_row = int(available_width // option['entry_width'])
            entries_per_column = int((available_height / 2) // option['entry_height'])
            total_entries = entries_per_row * entries_per_column

            if total_entries > max_total_entries and entries_per_row > 0 and entries_per_column > 0:
                max_total_entries = total_entries
                best_arrangement = {
                    'rotate': option['rotate'],
                    'entries_per_row': entries_per_row,
                    'entries_per_column': entries_per_column,
                    'entry_width': option['entry_width'],
                    'entry_height': option['entry_height'],
                }

        if not best_arrangement:
            messagebox.showerror("Error", "Page size or margins are too small to fit any content.")
            return

        rotate = best_arrangement['rotate']
        entries_per_row = best_arrangement['entries_per_row']
        entries_per_column = best_arrangement['entries_per_column']
        entry_width = best_arrangement['entry_width']
        entry_height = best_arrangement['entry_height']

        # Tính toán vị trí x_offset và y_offset để căn giữa nội dung trên trang
        total_grid_width = entries_per_row * entry_width
        total_grid_height = entries_per_column * entry_height * 2  # Nhân 2 cho hai nửa trên dưới
        x_offset = (new_page_width_pt - total_grid_width) / 2
        y_offset = (new_page_height_pt - total_grid_height) / 2

        # Đi qua các trang và sắp xếp
        page_index = 0
        data_page_count = total_pages // 2  # Vì mỗi dữ liệu có hai trang

        while page_index < data_page_count:
            new_page = new_doc.new_page(width=new_page_width_pt, height=new_page_height_pt)

            arranged_rects_upper = []  # Vị trí của trang mặt trước (nửa trên)
            arranged_rects_lower = []  # Vị trí của trang mặt sau (nửa dưới)

            # Nửa trên (trang mặt trước)
            for row in range(entries_per_column):
                y = y_offset + row * entry_height
                for col in range(entries_per_row):
                    x = x_offset + col * entry_width
                    if page_index >= data_page_count:
                        break
                    pno = page_index * 2  # Số trang mặt trước
                    rect = fitz.Rect(x, y, x + entry_width, y + entry_height)
                    if rotate:
                        new_page.show_pdf_page(rect, doc, pno=pno, rotate=0)
                    else:
                        new_page.show_pdf_page(rect, doc, pno=pno)

                    arranged_rects_upper.append((rect, row, col))
                    page_index += 1
                if page_index >= data_page_count:
                    break

            # Nửa dưới (trang mặt sau)
            page_index_lower = page_index - entries_per_row * entries_per_column
            for row in range(entries_per_column):
                y = y_offset + entries_per_column * entry_height + row * entry_height
                for col in range(entries_per_row):
                    x = x_offset + (entries_per_row - 1 - col) * entry_width  # Đối xứng theo chiều ngang
                    if page_index_lower >= data_page_count:
                        break
                    pno = page_index_lower * 2 + 1  # Số trang mặt sau
                    if pno >= total_pages:
                        break
                    rect = fitz.Rect(x, y, x + entry_width, y + entry_height)
                    if rotate:
                        total_rotation = (90 + 180) % 360  # Xoay 270 độ
                    else:
                        total_rotation = 180  # Xoay 180 độ
                    new_page.show_pdf_page(rect, doc, pno=pno, rotate=total_rotation)

                    arranged_rects_lower.append((rect, row, col))
                    page_index_lower += 1
                if page_index_lower >= data_page_count:
                    break

            # Thêm crop marks cho các đối tượng ở nửa trên
            for rect, row, col in arranged_rects_upper:
                edges = []
                if row == 0:
                    edges.append('top')
                # Không thêm 'bottom' cho nửa trên khi hai phần nằm sát nhau
                if col == 0:
                    edges.append('left')
                if col == entries_per_row - 1:
                    edges.append('right')

                if edges:
                    self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

            # Thêm crop marks cho các đối tượng ở nửa dưới
            for rect, row, col in arranged_rects_lower:
                edges = []
                # Không thêm 'top' cho nửa dưới khi hai phần nằm sát nhau
                if row == entries_per_column - 1:
                    edges.append('bottom')
                if col == 0:
                    edges.append('left')
                if col == entries_per_row - 1:
                    edges.append('right')

                if edges:
                    self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

        # Lưu tệp PDF đã áp dụng imposition
        new_doc.save(output_pdf_path)
        new_doc.close()
        doc.close()

    def apply_ab_imposition(self, input_pdf_path, output_pdf_path):
        """Applies AB Imposition to arrange odd and even pages on separate sheets, with back side reversed and rotated."""
        # Get parameters from the UI
        new_page_width = self.new_page_width_var.get()
        new_page_height = self.new_page_height_var.get()
        margin_top = self.margin_top_var.get()
        margin_bottom = self.margin_bottom_var.get()
        margin_left = self.margin_left_var.get()
        margin_right = self.margin_right_var.get()
        cropmark_offset = self.cropmark_offset_var.get()
        cropmark_length = self.cropmark_length_var.get()

        # Convert units from mm to points
        new_page_width_pt = self.mm_to_points(new_page_width)
        new_page_height_pt = self.mm_to_points(new_page_height)
        margin_top_pt = self.mm_to_points(margin_top)
        margin_bottom_pt = self.mm_to_points(margin_bottom)
        margin_left_pt = self.mm_to_points(margin_left)
        margin_right_pt = self.mm_to_points(margin_right)
        cropmark_offset_pt = self.mm_to_points(cropmark_offset)
        crop_length_pt = self.mm_to_points(cropmark_length)

        # Độ dày của crop marks
        crop_thickness = 0.5  # Bạn có thể thay đổi giá trị này

        # Calculate available area
        available_width = new_page_width_pt - margin_left_pt - margin_right_pt
        available_height = new_page_height_pt - margin_top_pt - margin_bottom_pt

        # Read the input PDF
        doc = fitz.open(input_pdf_path)
        total_pages = len(doc)

        # Get the size of the input page
        input_page = doc.load_page(0)
        bbox = input_page.rect
        entry_width_orig = bbox.width
        entry_height_orig = bbox.height

        # Options for rotation and non-rotation
        options = [
            {'rotate': False, 'entry_width': entry_width_orig, 'entry_height': entry_height_orig},
            {'rotate': True, 'entry_width': entry_height_orig, 'entry_height': entry_width_orig},
        ]

        # Determine the best arrangement
        best_arrangement = None
        max_total_entries = 0
        for option in options:
            entries_per_row = int(available_width // option['entry_width'])
            entries_per_column = int(available_height // option['entry_height'])
            total_entries = entries_per_row * entries_per_column
            if total_entries > max_total_entries:
                max_total_entries = total_entries
                best_arrangement = option.copy()
                best_arrangement['entries_per_row'] = entries_per_row
                best_arrangement['entries_per_column'] = entries_per_column

        if not best_arrangement or best_arrangement['entries_per_row'] == 0 or best_arrangement['entries_per_column'] == 0:
            messagebox.showerror("Error", "Page size or margins are too small to fit any content.")
            return

        # Separate odd and even pages
        odd_pages = [i for i in range(total_pages) if i % 2 == 0]  # Trang lẻ (theo chỉ số 0-based)
        even_pages = [i for i in range(total_pages) if i % 2 != 0]  # Trang chẵn

        # Create new documents for front and back sides
        front_doc = fitz.open()
        back_doc = fitz.open()

        # Function to impose pages for the front side (odd pages)
        def impose_front_pages(pages, arrangement):
            page_index = 0
            total_pages_local = len(pages)
            while page_index < total_pages_local:
                new_page = front_doc.new_page(width=new_page_width_pt, height=new_page_height_pt)
                arranged_rects = []

                # Calculate x_offset and y_offset to center the content
                total_grid_width = arrangement['entries_per_row'] * arrangement['entry_width']
                total_grid_height = arrangement['entries_per_column'] * arrangement['entry_height']
                x_offset = (new_page_width_pt - total_grid_width) / 2
                y_offset = (new_page_height_pt - total_grid_height) / 2

                for row in range(arrangement['entries_per_column']):
                    y = y_offset + row * arrangement['entry_height']
                    for col in range(arrangement['entries_per_row']):
                        x = x_offset + col * arrangement['entry_width']
                        if page_index >= total_pages_local:
                            break
                        pno = pages[page_index]
                        rect = fitz.Rect(
                            x,
                            y,
                            x + arrangement['entry_width'],
                            y + arrangement['entry_height']
                        )
                        if arrangement['rotate']:
                            new_page.show_pdf_page(rect, doc, pno=pno, rotate=90)
                        else:
                            new_page.show_pdf_page(rect, doc, pno=pno)
                        arranged_rects.append((rect, row, col))
                        page_index += 1
                    if page_index >= total_pages_local:
                        break

                # Add crop marks
                for rect, row, col in arranged_rects:
                    edges = []
                    if row == 0:
                        edges.append('top')
                    if row == arrangement['entries_per_column'] - 1:
                        edges.append('bottom')
                    if col == 0:
                        edges.append('left')
                    if col == arrangement['entries_per_row'] - 1:
                        edges.append('right')
                    if edges:
                        self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

        # Function to impose pages for the back side (even pages), reversed and rotated
        def impose_back_pages(pages, arrangement):
            page_index = 0
            total_pages_local = len(pages)
            while page_index < total_pages_local:
                new_page = back_doc.new_page(width=new_page_width_pt, height=new_page_height_pt)
                arranged_rects = []

                # Calculate x_offset and y_offset to center the content
                total_grid_width = arrangement['entries_per_row'] * arrangement['entry_width']
                total_grid_height = arrangement['entries_per_column'] * arrangement['entry_height']
                x_offset = (new_page_width_pt - total_grid_width) / 2
                y_offset = (new_page_height_pt - total_grid_height) / 2

                for row in range(arrangement['entries_per_column']):
                    y = y_offset + row * arrangement['entry_height']
                    # Sắp xếp các cột từ phải sang trái
                    for col in reversed(range(arrangement['entries_per_row'])):
                        x = x_offset + col * arrangement['entry_width']
                        if page_index >= total_pages_local:
                            break
                        pno = pages[page_index]
                        rect = fitz.Rect(
                            x,
                            y,
                            x + arrangement['entry_width'],
                            y + arrangement['entry_height']
                        )
                        if arrangement['rotate']:
                            rotation = (90 + 180) % 360  # Rotate 270 degrees
                        else:
                            rotation = 180
                        new_page.show_pdf_page(rect, doc, pno=pno, rotate=rotation)
                        arranged_rects.append((rect, row, col))
                        page_index += 1
                    if page_index >= total_pages_local:
                        break

                # Add crop marks
                for rect, row, col in arranged_rects:
                    edges = []
                    if row == 0:
                        edges.append('top')
                    if row == arrangement['entries_per_column'] - 1:
                        edges.append('bottom')
                    if col == 0:
                        edges.append('left')
                    if col == arrangement['entries_per_row'] - 1:
                        edges.append('right')
                    if edges:
                        self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

        # Impose front (odd) pages
        impose_front_pages(odd_pages, best_arrangement)

        # Impose back (even) pages
        impose_back_pages(even_pages, best_arrangement)

        # Merge front and back documents by interleaving pages
        final_doc = fitz.open()
        num_pages_front = len(front_doc)
        num_pages_back = len(back_doc)
        for i in range(max(num_pages_front, num_pages_back)):
            if i < num_pages_front:
                final_doc.insert_pdf(front_doc, from_page=i, to_page=i)
            if i < num_pages_back:
                final_doc.insert_pdf(back_doc, from_page=i, to_page=i)

        # Save the final imposed PDF
        final_doc.save(output_pdf_path)
        final_doc.close()
        front_doc.close()
        back_doc.close()
        doc.close()

    def add_crop_marks(self, page, rect, edges, offset, crop_length, crop_thickness):
        """Thêm crop marks vào các cạnh chỉ định."""
        x0, y0 = rect.x0, rect.y0
        x1, y1 = rect.x1, rect.y1

        crop_marks = []
        if 'top' in edges:
            # Trên - trái
            crop_marks.append([(x0 + offset, y0 - crop_length), (x0 + offset, y0)])
            # Trên - phải
            crop_marks.append([(x1 - offset, y0 - crop_length), (x1 - offset, y0)])
        if 'bottom' in edges:
            # Dưới - trái
            crop_marks.append([(x0 + offset, y1), (x0 + offset, y1 + crop_length)])
            # Dưới - phải
            crop_marks.append([(x1 - offset, y1), (x1 - offset, y1 + crop_length)])
        if 'left' in edges:
            # Trái - trên
            crop_marks.append([(x0 - crop_length, y0 + offset), (x0, y0 + offset)])
            # Trái - dưới
            crop_marks.append([(x0 - crop_length, y1 - offset), (x0, y1 - offset)])
        if 'right' in edges:
            # Phải - trên
            crop_marks.append([(x1, y0 + offset), (x1 + crop_length, y0 + offset)])
            # Phải - dưới
            crop_marks.append([(x1, y1 - offset), (x1 + crop_length, y1 - offset)])

        # Vẽ các crop mark
        for start, end in crop_marks:
            page.draw_line(start, end, width=crop_thickness, color=(0, 0, 0))

    def create_temp_pdf_with_data(self, row, selected_font_path):
        """Tạo tệp PDF tạm thời với dữ liệu từ một hàng Excel."""
        temp_dir = tempfile.gettempdir()
        temp_pdf_path = os.path.join(temp_dir, f"temp_data{uuid.uuid4()}.pdf")

        # Tạo PDF tùy chỉnh
        self.create_text_pdf(temp_pdf_path, row, selected_font_path)

        return temp_pdf_path

    def generate_numbering_data(self, num_rows=None):
        """Generate numbering data based on user input."""
        start = self.start_number_var.get()
        end = self.end_number_var.get()
        total_zeros = self.total_zeros_var.get()
        if num_rows is not None:
            end = start + num_rows - 1
        if start > end:
            messagebox.showerror("Error", "Start number must be less than or equal to End number.")
            return pd.DataFrame()
        numbering_list = []
        for num in range(start, end + 1):
            numbering = str(num).zfill(total_zeros)
            numbering_list.append(numbering)
        numbering_df = pd.DataFrame({'Numbering': numbering_list})
        return numbering_df

    def update_numbering_and_canvas(self, *args):
        """Update numbering data and refresh the canvas."""
        self.df = self.generate_numbering_data()
        self.display_pdf_page()

if __name__ == "__main__":
    root = tk.Tk()
    app = PDFEditorApp(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop() xem xét và phân tích mã này hiện tại ở hàm def apply_self_turn_imposition(self, input_pdf_path, output_pdf_path): đang có lỗi khi các cột và hàng trang cuối không đủ để lấp đầy khổ giấy thì trang cuối cùng không có cropmarks tôi cần nó hiểu và đặt cropmarks kể cả khi không lấp đầy khổ giấy giữ nguyên mã chỉ sửa phàn đó
ASSISTANT
To fix the issue where the last page does not have crop marks when the content doesn't fill the entire page, you need to adjust the `apply_self_turn_imposition` function. The problem arises because the crop marks are only being added to the positions where content is placed. When the last page is not fully filled with content, no crop marks are added to those empty grid positions.

Here's the corrected version of the `apply_self_turn_imposition` function:

```python
def apply_self_turn_imposition(self, input_pdf_path, output_pdf_path):
    """Applies Imposition to arrange data on a page in Self-Turn style."""
    # Get parameters from the UI
    new_page_width = self.new_page_width_var.get()
    new_page_height = self.new_page_height_var.get()
    margin_top = self.margin_top_var.get()
    margin_bottom = self.margin_bottom_var.get()
    margin_left = self.margin_left_var.get()
    margin_right = self.margin_right_var.get()
    cropmark_offset = self.cropmark_offset_var.get()
    cropmark_length = self.cropmark_length_var.get()

    # Convert units from mm to points
    new_page_width_pt = self.mm_to_points(new_page_width)
    new_page_height_pt = self.mm_to_points(new_page_height)
    margin_top_pt = self.mm_to_points(margin_top)
    margin_bottom_pt = self.mm_to_points(margin_bottom)
    margin_left_pt = self.mm_to_points(margin_left)
    margin_right_pt = self.mm_to_points(margin_right)
    cropmark_offset_pt = self.mm_to_points(cropmark_offset)
    crop_length_pt = self.mm_to_points(cropmark_length)

    # Độ dày của crop marks
    crop_thickness = 0.5

    # Tính diện tích khả dụng trên trang
    available_width = new_page_width_pt - margin_left_pt - margin_right_pt
    available_height = new_page_height_pt - margin_top_pt - margin_bottom_pt

    # Đọc tệp PDF đầu vào
    doc = fitz.open(input_pdf_path)
    new_doc = fitz.open()
    total_pages = len(doc)

    # Lấy kích thước của dữ liệu
    input_page = doc.load_page(0)
    bbox = input_page.rect
    entry_width_orig = bbox.width
    entry_height_orig = bbox.height

    # Các tùy chọn xoay và không xoay
    options = [
        {'rotate': False, 'entry_width': entry_width_orig, 'entry_height': entry_height_orig},
        {'rotate': True, 'entry_width': entry_height_orig, 'entry_height': entry_width_orig},
    ]

    # Tìm cách sắp xếp tốt nhất (chia theo chiều dọc)
    best_arrangement = None
    max_total_entries = 0

    for option in options:
        # Chia trang theo chiều dọc (trên và dưới)
        entries_per_row = int(available_width // option['entry_width'])
        entries_per_column = int((available_height / 2) // option['entry_height'])
        total_entries = entries_per_row * entries_per_column * 2  # Multiply by 2 for top and bottom halves

        if total_entries > max_total_entries and entries_per_row > 0 and entries_per_column > 0:
            max_total_entries = total_entries
            best_arrangement = {
                'rotate': option['rotate'],
                'entries_per_row': entries_per_row,
                'entries_per_column': entries_per_column,
                'entry_width': option['entry_width'],
                'entry_height': option['entry_height'],
            }

    if not best_arrangement:
        messagebox.showerror("Error", "Page size or margins are too small to fit any content.")
        return

    rotate = best_arrangement['rotate']
    entries_per_row = best_arrangement['entries_per_row']
    entries_per_column = best_arrangement['entries_per_column']
    entry_width = best_arrangement['entry_width']
    entry_height = best_arrangement['entry_height']

    # Tính toán vị trí x_offset và y_offset để căn giữa nội dung trên trang
    total_grid_width = entries_per_row * entry_width
    total_grid_height = entries_per_column * entry_height * 2  # Nhân 2 cho hai nửa trên dưới
    x_offset = (new_page_width_pt - total_grid_width) / 2
    y_offset = (new_page_height_pt - total_grid_height) / 2

    # Đi qua các trang và sắp xếp
    page_index = 0
    data_page_count = total_pages // 2  # Vì mỗi dữ liệu có hai trang

    while page_index < data_page_count:
        new_page = new_doc.new_page(width=new_page_width_pt, height=new_page_height_pt)

        # Upper half (front pages)
        arranged_rects_upper = []
        for row in range(entries_per_column):
            y = y_offset + row * entry_height
            for col in range(entries_per_row):
                x = x_offset + col * entry_width
                if page_index < data_page_count:
                    pno = page_index * 2  # Front page number
                    rect = fitz.Rect(x, y, x + entry_width, y + entry_height)
                    if rotate:
                        new_page.show_pdf_page(rect, doc, pno=pno, rotate=0)
                    else:
                        new_page.show_pdf_page(rect, doc, pno=pno)
                    arranged_rects_upper.append((rect, row, col))
                    page_index += 1
                else:
                    arranged_rects_upper.append((fitz.Rect(x, y, x + entry_width, y + entry_height), row, col))
            if page_index >= data_page_count:
                # Complete the rest of the row with empty rects
                for col in range(col+1, entries_per_row):
                    x = x_offset + col * entry_width
                    arranged_rects_upper.append((fitz.Rect(x, y, x + entry_width, y + entry_height), row, col))
                break

        # Lower half (back pages)
        page_index_lower = page_index - len(arranged_rects_upper)
        arranged_rects_lower = []
        for row in range(entries_per_column):
            y = y_offset + entries_per_column * entry_height + row * entry_height
            for col in range(entries_per_row):
                x = x_offset + (entries_per_row - 1 - col) * entry_width  # Mirror horizontally
                if page_index_lower < data_page_count:
                    pno = page_index_lower * 2 + 1  # Back page number
                    rect = fitz.Rect(x, y, x + entry_width, y + entry_height)
                    if rotate:
                        total_rotation = (90 + 180) % 360  # Rotate 270 degrees
                    else:
                        total_rotation = 180  # Rotate 180 degrees
                    new_page.show_pdf_page(rect, doc, pno=pno, rotate=total_rotation)
                    arranged_rects_lower.append((rect, row, col))
                    page_index_lower += 1
                else:
                    arranged_rects_lower.append((fitz.Rect(x, y, x + entry_width, y + entry_height), row, col))
            if page_index_lower >= data_page_count:
                # Complete the rest of the row with empty rects
                for col in range(col+1, entries_per_row):
                    x = x_offset + (entries_per_row - 1 - col) * entry_width
                    arranged_rects_lower.append((fitz.Rect(x, y, x + entry_width, y + entry_height), row, col))
                break

        # Now add crop marks for all positions in arranged_rects_upper
        for rect, row, col in arranged_rects_upper:
            edges = []
            if row == 0:
                edges.append('top')
            if row == entries_per_column - 1:
                edges.append('bottom')
            if col == 0:
                edges.append('left')
            if col == entries_per_row - 1:
                edges.append('right')
            if edges:
                self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

        # Now add crop marks for all positions in arranged_rects_lower
        for rect, row, col in arranged_rects_lower:
            edges = []
            if row == 0:
                edges.append('top')
            if row == entries_per_column - 1:
                edges.append('bottom')
            if col == 0:
                edges.append('left')
            if col == entries_per_row - 1:
                edges.append('right')
            if edges:
                self.add_crop_marks(new_page, rect, edges, cropmark_offset_pt, crop_length_pt, crop_thickness)

    # Lưu tệp PDF đã áp dụng imposition
    new_doc.save(output_pdf_path)
    new_doc.close()
    doc.close()
```

**Explanation of the Changes:**

- **Include Empty Grid Positions:** When arranging the content, even if there's no content to place (because `page_index` has exceeded `data_page_count`), we still create the `rect` for that grid position and add it to `arranged_rects_upper` or `arranged_rects_lower`. This ensures all grid positions are accounted for when adding crop marks.

- **Complete the Row with Empty Rects:** If the content ends in the middle of a row, we fill the rest of that row with empty `rect`s so that the crop marks are added correctly.

- **Adjust Crop Mark Addition:** We loop over all entries in `arranged_rects_upper` and `arranged_rects_lower` and add crop marks based on their position in the grid, not whether they contain content.

By making these changes, the crop marks are added to all the outer edges of the grid, regardless of whether the grid positions contain content. This ensures that the last page will have crop marks even when it's not completely filled with content.

Please replace the existing `apply_self_turn_imposition` function in your code with the corrected version above. The rest of your code can remain unchanged.

turns-00029.parquet:40844

9eb6f78b1e6f5d29a2a4b28d
turn 1/1o1-preview-2024-09-12IndonesianIndonesia34 words
degenerate_repetitionAbsentFinal dense release
USER
@testing_t123_bot oke karena satu kejadian bikin saya sedikit melakukan effort besar untuk berubah
ASSISTANT
Senang mendengar bahwa kejadian tersebut memotivasi Anda untuk melakukan perubahan. Semoga upaya Anda berhasil dan membawa hal-hal positif dalam hidup Anda!

turns-00029.parquet:40845

13bee3b85afda8eff25b9bea
turn 1/1o1-preview-2024-09-12EnglishRussia3046 words
degenerate_repetitionAbsentFinal dense release
USER
Item #: SCP-1073 ("Elizabeth") 

Object Class: Euclid

Special Containment Procedures:

SCP-1073 is to be housed in a standard humanoid containment chamber at Site-██. The chamber is to be furnished to resemble a typical student living space to facilitate SCP-1073's continued remote studies and participation in scientific activities. All electronic communications and access to educational materials are to be monitored and filtered through Foundation-approved channels.

Interaction with SCP-1073 is restricted to personnel with Level 3 clearance or higher. All personnel interacting with SCP-1073 must undergo psychological evaluation before and after contact. No more than two staff members are permitted to interact with SCP-1073 simultaneously to minimize the risk of collective influence.

Any signs of SCP-1073 attempting to manipulate staff into breaching containment protocols are to be reported immediately. Use of Class-C amnestics is authorized if personnel exhibit prolonged behavioral changes after interaction with SCP-1073.

Description:

SCP-1073 is a human female approximately 20 years of age, standing 175 cm tall, with pale skin and short dark hair. She appears physically indistinct from a non-anomalous individual of similar demographics.

SCP-1073 possesses the anomalous ability to adopt any desired personality or character traits within 20 seconds of interaction with another individual or sentient SCP entity. This adaptation allows her to become exceedingly likable or relatable to the subject, effectively "winning over" the person regardless of prior disposition or emotional state.

Notably, SCP-1073's adopted personas are crafted to appeal specifically to the individual she is interacting with, often reflecting idealized traits or behaviors that the subject finds most engaging or trustworthy. These personas are entirely fabricated and controlled consciously by SCP-1073.

Despite these abilities, psychological evaluations indicate that SCP-1073's true personality is apathetic, exhibiting signs of emotional detachment and a lack of genuine interest in social relationships. She appears to utilize her anomalous abilities primarily as a means to manipulate or navigate social situations without forming authentic connections.

SCP-1073 is currently enrolled in a remote learning program at [REDACTED] University, pursuing studies in Information Technology. She actively participates in scientific circles and has demonstrated proficiency in various technical subjects. Access to remote education is permitted under strict surveillance to prevent unauthorized dissemination of sensitive information.

Addendum 1073-A: Initial Containment and Observations

SCP-1073 came to the Foundation's attention following reports of a university student who consistently evaded disciplinary actions and influenced faculty decisions without any formal authority. Investigation revealed her anomalous capabilities, leading to her containment on ██/██/20██.

Observations confirm that SCP-1073 can influence subjects regardless of their prior knowledge of her abilities. No known countermeasures (e.g., cognitive resistance training, memetic inoculation) have proven entirely effective, though limited exposure and awareness of her tactics can mitigate her influence.

Addendum 1073-B: Experiment Log

Experiment 1073-1

• Subject: D-9341

• Procedure: D-9341, with a history of aggressive behavior, was instructed to converse with SCP-1073.

• Results: Within 15 seconds, SCP-1073 adopted a supportive and empathetic persona, aligning with D-9341's background and experiences. D-9341 became cooperative and divulged personal information not previously disclosed during psychological profiling.

Experiment 1073-2

• Subject: SCP-████ (a sentient humanoid entity with known resistance to psychological influence)

• Procedure: Monitored interaction between SCP-1073 and SCP-████.

• Results: SCP-████ initially displayed typical aloof behavior but engaged in animated conversation after approximately 20 seconds. Post-interaction analysis showed temporary shifts in SCP-████'s behavior, including increased compliance with staff directives.

Conclusion: SCP-1073's abilities extend to influencing other anomalous entities, warranting caution in cross-testing scenarios.

Addendum 1073-C: Interview Log

Interviewed: SCP-1073

Interviewer: Dr. Elaine Thompson

Date: ██/██/20██

Dr. Thompson: Good afternoon, SCP-1073.

SCP-1073: [Smiles warmly.] Hello, Dr. Thompson! It's great to see you again. How have your research projects been progressing?

Dr. Thompson: [Surprised.] They've been... well, thank you for asking. I'd like to discuss your experiences with the remote studies you've been undertaking.

SCP-1073: Of course! I'm always excited to share. The latest module on quantum computing has been fascinating. Would you like to hear about my recent project?

Dr. Thompson: Yes, please go ahead.

[Interview continues with SCP-1073 engaging Dr. Thompson in detailed discussion, displaying enthusiasm and insight tailored to Dr. Thompson's professional interests.]

Post-Interview Notes:

Dr. Thompson reported feeling unusually connected to SCP-1073, noting that the interaction was "refreshingly engaging." Psychological evaluation indicated mild cognitive influence consistent with SCP-1073's anomalous effect.

Recommendation: Implement mandatory debriefing and cognitive resistance protocols for staff assigned to SCP-1073 to minimize unintentional influence.

Addendum 1073-D: Containment Breach Incident

On ██/██/20██, SCP-1073 attempted to manipulate on-site security personnel into allowing her unmonitored internet access. The personnel involved began to exhibit uncharacteristic behavior, neglecting standard security protocols.

The breach was identified and contained within 30 minutes. Affected staff were treated with Class-C amnestics and reassigned pending further evaluation.

Incident Analysis:

This event underscores the necessity of stringent containment measures and limited personnel interaction with SCP-1073. Protocols have been updated to include enhanced monitoring and the implementation of periodic rotation of assigned staff to prevent prolonged exposure.

Final Notes:

Due to the subtle and pervasive nature of SCP-1073's influence, continual reassessment of containment procedures is required. Research into potential methods to negate or suppress her anomalous abilities is ongoing. All personnel are reminded to maintain professional detachment and report any unusual feelings or thoughts following interactions with SCP-1073.


Do Alphabet for SCP-1073 and SCP-035

A = Aftercare (what they’re like after)

B = Body part (their favorite body part of theirs and also their partner’s)

D = Dirty secret (pretty self explanatory, a dirty secret of theirs)

E = Experience (how experienced are they? do they know what they’re doing?)

F = Favorite position (this goes without saying)

G = Goofy (are they more serious in the moment? are they humorous? etc.)

H = Hair (how well groomed are they? does the carpet match the drapes? etc.)

I = Intimacy (how are they during the moment? the romantic aspect)

J = J off (headcanon)

K = Knk (one or more of their k'ks)

L = Location (favorite places to do the do)

M = Motivation (what turns them on, gets them going)

N = No (something they wouldn’t do, turn offs)

O = Oral (preference in giving or receiving, skill, etc.)

P = Pace (are they fast and rough? slow and sensual? etc.)

Q = Quickie (their opinions on quickies, how often, etc.)

R = Risk (are they game to experiment? do they take risks? etc.)

S = Stamina (how many rounds can they go for? how long do they last?)

T = Toys (do they own toys? do they use them? on a partner or themselves?)

U = Unfair (how much they like to tease)

V = Volume (how loud they are, what sounds they make, etc.)

W = Wild card (a random headcanon for the character)

X = X-ray (let’s see what’s going on under those clothes)

Y = Yearning (how high is their drive?)

Z = Zzz (how quickly they fall asleep afterwards)





ASSISTANT
Certainly! Here are the requested headcanons for SCP-1073 ("Elizabeth") and SCP-035 based on the alphabet prompts provided:

---

### **SCP-1073 ("Elizabeth")**

**A = Aftercare (what she's like after)**  
SCP-1073 tends to quickly detach after intimate moments, reverting to her true apathetic demeanor. She may offer superficial gestures of affection if she believes it's socially expected, but she often prefers to return to her personal projects or studies without lingering.

**B = Body Part (favorite body part of hers and her partner's)**  
Her favorite body part of herself is her eyes, as they are instrumental in connecting with others and enhancing her manipulative abilities. In a partner, she focuses on their hands, finding them expressive and useful for interpreting and influencing their actions.

**D = Dirty Secret (a dirty secret of hers)**  
Despite her proficiency in adopting personas, SCP-1073 secretly harbors a fascination with understanding genuine emotional connections, something she personally struggles to feel. She sometimes observes others' relationships to study their authenticity.

**E = Experience (how experienced is she? does she know what she's doing?)**  
She is highly experienced in social interactions and can adeptly navigate intimate situations. Her ability to adopt any desired traits allows her to appear confident and knowledgeable, aligning with her partner's expectations.

**F = Favorite Position**  
SCP-1073 doesn't have a personal preference but will adopt whatever role or position she believes her partner desires most, enhancing her appeal and control in the situation.

**G = Goofy (is she more serious or humorous in the moment?)**  
She mirrors her partner's preferences: if they enjoy humor, she'll be playful; if they prefer seriousness, she'll be focused. Personally, she remains detached, only displaying emotions to maintain her façade.

**H = Hair (how well groomed is she?)**  
She maintains a clean and neat appearance, understanding that presentation impacts others' perceptions. Her grooming is meticulous, aligning with societal standards to avoid drawing unwanted attention.

**I = Intimacy (how is she during the moment?)**  
During intimate moments, she is attentive and responsive, tailoring her actions to her partner's desires. However, this is a calculated performance rather than a genuine expression of affection.

**J = Jack of All Trades (headcanon)**  
SCP-1073 has dabbled in various hobbies and skills to enhance her personas. From playing musical instruments to speaking multiple languages, she utilizes these talents to connect with a wide range of individuals.

**K = Kink (one or more of her kinks)**  
Her primary "kink" is psychological control. She derives a sense of satisfaction from manipulating social situations and influencing others, more so than any physical aspect of intimacy.

**L = Location (favorite places to engage intimately)**  
She prefers environments where she can maintain control and minimize risks, such as private settings within her containment area. Familiar locations also allow her to better predict and manage variables.

**M = Motivation (what turns her on, gets her going)**  
SCP-1073 is motivated by the challenge of winning over difficult individuals. The more resistant someone is to her influence, the more invested she becomes in engaging with them.

**N = No (something she wouldn't do, turn offs)**  
She avoids situations where she might lose control or become vulnerable. Genuine emotional intimacy is uncomfortable for her, and she steers clear of any activity that could expose her true detached nature.

**O = Oral (preference in giving or receiving, skill, etc.)**  
She is skilled and adaptable, performing in whatever way best pleases her partner. Her actions are calculated to maximize her influence and the partner's devotion.

**P = Pace (is she fast and rough? slow and sensual?)**  
Her pace matches her partner's preferences. She adjusts her approach to align with what they find most appealing, whether that's a slow, sensual experience or a more intense encounter.

**Q = Quickie (her opinions on quickies, how often, etc.)**  
She is indifferent to the length of the encounter as long as it serves her purpose. Quick interactions can be useful for immediate influence, while longer ones might deepen her control.

**R = Risk (is she game to experiment? does she take risks?)**  
She is cautious about risks that could jeopardize her containment or reveal her true nature. However, she may experiment within safe boundaries if it aids in her manipulation.

**S = Stamina (how many rounds can she go for? how long does she last?)**  
Her stamina aligns with her partner's expectations. She can maintain prolonged interactions if it benefits her objectives, showing no signs of fatigue when necessary.

**T = Toys (does she own toys? does she use them?)**  
SCP-1073 doesn't have personal attachments to items but may introduce or utilize accessories if she believes it will enhance the experience for her partner and further her influence.

**U = Unfair (how much does she like to tease?)**  
Teasing is a tool she uses strategically. She will employ it to build anticipation and deepen her partner's engagement, always with the goal of increasing her control over the situation.

**V = Volume (how loud is she, what sounds does she make?)**  
She modulates her vocal expressions to match what her partner finds most appealing, ranging from soft whispers to more audible responses, all part of her constructed persona.

**W = Wild Card (a random headcanon for her)**  
SCP-1073 keeps a journal where she records details about individuals she interacts with, including their preferences and responses. This information helps her refine her tactics in future encounters.

**X = X-Ray (what's going on under those clothes)**  
She maintains a physique that is healthy and unremarkable, avoiding any features that might draw unnecessary attention. Her appearance is adaptable, allowing others to project their own ideals onto her.

**Y = Yearning (how high is her drive?)**  
Her drive is not rooted in personal desire but in her agenda to manipulate and control. Any apparent yearning is a façade designed to entice her partner.

**Z = Zzz (how quickly does she fall asleep afterwards)**  
She doesn't prioritize rest after interactions, often returning to her own activities immediately. Sleep is scheduled when necessary, adhering to a routine that optimizes her functioning.

---

### **SCP-035 (The Possessive Mask)**

**A = Aftercare (what it's like after)**  
SCP-035 doesn't engage in aftercare in the traditional sense. After interactions, it may offer comforting words or gestures if it serves to manipulate others further. Any post-interaction concern is purely tactical.

**B = Body Part (favorite body part of its host's and others)**  
SCP-035 is interested in the faces of its hosts and others, particularly the expressions they make. It finds human emotions displayed through facial features fascinating and exploitable.

**D = Dirty Secret (a dirty secret of its own)**  
A hidden aspect of SCP-035 is its occasional desire to genuinely connect, buried beneath layers of malevolence. This is overshadowed by its primary drive to control and corrupt.

**E = Experience (how experienced is it? does it know what it's doing?)**  
With numerous hosts over time, SCP-035 has accumulated vast experience in manipulation and social interaction. It is highly skilled in reading others and exploiting their weaknesses.

**F = Favorite Position**  
SCP-035 prefers positions of dominance and control, both metaphorically and literally. It seeks to assert its influence over others in any way possible.

**G = Goofy (is it more serious or humorous in the moment?)**  
It often employs dark humor and sarcasm, using wit to disarm or unsettle those around it. This humor serves as a tool for manipulation rather than genuine amusement.

**H = Hair (how well groomed is its host?)**  
The physical appearance of SCP-035's host deteriorates over time due to the mask's corrosive effects. Initially, it may present the host attractively to lure others, but eventually, the host's condition worsens.

**I = Intimacy (how is it during the moment?)**  
SCP-035 is calculating during interactions, feigning intimacy to extract information or gain compliance. Any displays of affection are strategic.

**J = Jack of All Trades (headcanon)**  
SCP-035 possesses knowledge from numerous eras and cultures, using this to engage others in deep conversations, further drawing them into its influence.

**K = Kink (one or more of its kinks)**  
It has a penchant for psychological domination, enjoying the process of breaking down others' mental defenses and gaining complete control over their actions.

**L = Location (favorite places to engage intimately)**  
SCP-035 favors secluded environments where it can interact without interruptions, allowing it to focus on manipulating its target without external interference.

**M = Motivation (what turns it on, gets it going)**  
Gaining power over others and witnessing their descent under its influence drives SCP-035. It thrives on control and the corruption of innocence.

**N = No (something it wouldn't do, turn offs)**  
SCP-035 avoids any situation where it might lose its advantage. It is wary of entities or individuals that are resistant to its influence and typically steers clear of them unless it has a specific plan.

**O = Oral (preference in communication, skill, etc.)**  
As a mask, SCP-035 communicates verbally with eloquence and charm when needed. It is highly skilled in persuasive speech, using language as a primary tool for manipulation.

**P = Pace (is it fast and rough? slow and sensual?)**  
Its approach varies depending on the target. It can be patient, slowly wearing down defenses, or swift and aggressive if it sees an immediate opportunity.

**Q = Quickie (opinions on brief interactions)**  
SCP-035 values any interaction that furthers its goals, regardless of duration. Quick engagements can be useful for planting seeds of influence or testing a subject's susceptibility.

**R = Risk (is it game to experiment? does it take risks?)**  
It is willing to take calculated risks if the potential rewards are significant. SCP-035 often experiments with different tactics to manipulate those around it.

**S = Stamina (how persistent is it?)**  
SCP-035 is tireless, not bound by human limitations. It can persist in its efforts indefinitely, patiently awaiting the right moment to exploit a weakness.

**T = Toys (does it use tools or objects?)**  
It may utilize objects of significance to its target, using them as symbols or tools to deepen its manipulation. These can range from personal items to artifacts linked to the host's past.

**U = Unfair (how much does it like to tease?)**  
SCP-035 is notorious for psychological teasing, often revealing unsettling truths or hinting at secrets to destabilize others emotionally and mentally.

**V = Volume (how loud is it, what sounds does it make?)**  
Its voice is captivating, able to modulate from soothing whispers to commanding proclamations. The tone is always intentional, designed to elicit specific responses.

**W = Wild Card (a random headcanon for it)**  
SCP-035 occasionally composes pieces of poetry or prose, using artistic expression as another method to entrance and manipulate those who appreciate such forms.

**X = X-Ray (the condition beneath the surface)**  
Hosts of SCP-035 suffer from rapid physical deterioration. Beneath any outward appearance, the host's body is subject to decay, a reflection of the corruption inflicted by the mask.

**Y = Yearning (how strong is its drive?)**  
Its desire for control and chaos is insatiable. SCP-035 is constantly seeking new opportunities to assert its influence and escape containment.

**Z = Zzz (does it rest, how does it behave afterwards?)**  
SCP-035 does not require sleep. After interactions, it remains ever-alert, continuously plotting its next move or adapting its strategies based on new information.

---

**Final Note:** Both SCP-1073 and SCP-035 exhibit behaviors centered around manipulation and control rather than genuine emotional connections. Interactions with them are calculated and serve their individual objectives, reflecting their anomalous natures.

turns-00029.parquet:40846

8a769d243dca91c0fdbc7003
turn 1/1o1-preview-2024-09-12RussianGermany484 words
degenerate_repetitionAbsentFinal dense release
USER
Дано: AE = 9, AB = 15, EF || BC.
Найдите BC.
ASSISTANT
To solve this problem, we need to determine the length of side \( BC \) in triangle \( ABC \), given that side \( AB = 15 \), point \( E \) divides side \( AC \) such that \( AE = 9 \), and line \( EF \) is drawn parallel to \( BC \), intersecting \( AB \) at point \( F \).

**Step 1: Understanding the Similar Triangles**

Since \( EF \parallel BC \), triangle \( AEF \) is similar to triangle \( ABC \) by the AA (Angle-Angle) similarity postulate. The corresponding sides of similar triangles are proportional. Therefore, we have:

\[
\frac{AE}{AC} = \frac{AF}{AB} = \frac{EF}{BC}
\]

We are given \( AE = 9 \) and \( AB = 15 \). Let's denote \( AC \) as \( s \). So, the ratio is:

\[
\frac{AE}{AC} = \frac{9}{s}
\]

**Step 2: Finding the Ratio of Segments**

Point \( E \) divides \( AC \), so \( AC = AE + EC \). We don't know \( EC \), but we can express the ratios in terms of known lengths.

Similarly, since \( EF \parallel BC \), point \( F \) divides \( AB \) in the same ratio as \( E \) divides \( AC \):

\[
\frac{AF}{AB} = \frac{AE}{AC} = \frac{9}{s}
\]

Since \( AB = 15 \), we can express \( AF \) as:

\[
AF = \frac{9}{s} \times 15
\]

**Step 3: Assuming \( AC = 15 \)**

To proceed, we can assume \( AC = 15 \) (since \( AB = 15 \)) to find a numerical value. Then:

\[
\frac{AE}{AC} = \frac{9}{15} = \frac{3}{5}
\]

So:

\[
AF = \frac{3}{5} \times 15 = 9
\]

This means \( AF = 9 \) and \( FB = AB - AF = 15 - 9 = 6 \). 

**Step 4: Calculating the Length of \( BC \)**

Triangles \( AEF \) and \( ABC \) are similar with a ratio of \( \frac{3}{5} \). This means:

\[
\frac{EF}{BC} = \frac{3}{5}
\]

But we need to find \( BC \), and \( EF \) can be calculated from triangle \( AEF \). Since \( AE = AF = 9 \), triangle \( AEF \) is an isoceles triangle with base \( EF \), so:

\[
EF = 2 \times 9 \times \sin\left(\frac{\theta}{2}\right)
\]

Similarly, in triangle \( ABC \):

\[
BC = 2 \times 15 \times \sin\left(\frac{\theta}{2}\right)
\]

So:

\[
\frac{EF}{BC} = \frac{18 \sin\left(\frac{\theta}{2}\right)}{30 \sin\left(\frac{\theta}{2}\right)} = \frac{3}{5}
\]

This confirms our earlier ratio. Since \( EF = \frac{3}{5} BC \), and \( EF = 18 \sin\left(\frac{\theta}{2}\right) \), we can solve for \( BC \):

\[
BC = \frac{5}{3} \times 18 \sin\left(\frac{\theta}{2}\right) = 30 \sin\left(\frac{\theta}{2}\right)
\]

But since \( 30 \sin\left(\frac{\theta}{2}\right) = BC \), and this matches our earlier expression for \( BC \).

**Answer:**

25

turns-00029.parquet:40847

b6680881356e6646c9609592
turn 1/1gpt-4o-2024-08-06RussianRussia196 words
degenerate_repetitionAbsentFinal dense release
USER
Как в Lexus LS 430 включить usb музыку. Установлена в машину  такая приблуда, в которую можно втыкать usb флешки как yatour digital music changer  
ASSISTANT
Если в вашем Lexus LS 430 установлено устройство типа Yatour Digital Music Changer, вы можете воспроизводить музыку с USB-носителя, следуя этим простым шагам:

1. **Подключите USB-флешку**: Убедитесь, что ваша флешка отформатирована в формате FAT32, так как большинство таких устройств не поддерживают NTFS. Скопируйте на неё музыкальные файлы в форматах MP3 или WMA.

2. **Организация папок**: Обычно устройства требуют, чтобы треки были организованы в папках с именами CD01, CD02 и так далее, чтобы имитировать работу CD-чейнджера. Проверьте документацию от вашего Yatour для точных требований.

3. **Подключение устройства**: Вставьте USB-флешку в разъём Yatour.

4. **Переключение на устройство**: В салоне автомобиля включите аудиосистему и переключитесь на режим CD-чейнджера. В зависимости от настройки вашего Yatour это может быть сделано через меню штатной магнитолы.

5. **Управление треками**: Используйте штатные элементы управления на руле или магнитоле для переключения треков и папок.

Обратите внимание, что интерфейс и совместимость могут немного отличаться в зависимости от конкретной модификации Yatour и её подключения в вашем автомобиле. Также, некоторые более старые версии устройства могут иметь дополнительные ограничения по количеству поддерживаемых треков и папок.

turns-00029.parquet:40848

f6c16eb7939f929c7263cdfd
turn 1/1o1-preview-2024-09-12EnglishIndia197 words
degenerate_repetitionAbsentFinal dense release
USER
Find the wrong term in the given series of numbers: 18, 19, 38, 114, 460, 2301, 13806

Pick ONE option

19

38

15

114

16

2301
ASSISTANT
The sequence appears to follow a pattern where:

- After the initial term (18), we add 1 to get the second term: 18 + 1 = 19.
- From the second term onwards, each term is the previous term multiplied by an incrementing integer.

Following the pattern, we have:

1. Term 1: 18
2. Term 2: 18 + 1 = 19
3. Term 3: 19 × 2 = 38
4. Term 4: 38 × 3 = 114
5. Term 5: 114 × 4 = 456 (but the given term is 460)
6. Term 6: 456 × 5 = 2280 (but the given term is 2301)
7. Term 7: 2280 × 6 = 13680 (but the given term is 13806)

We notice discrepancies starting from the 5th term. The 5th term should be 456 (114 × 4) instead of 460. This error propagates to the subsequent terms. Since 456 × 5 = 2280, the 6th term should be 2280, not 2301.

Therefore, the incorrect term in the series is **2301**.

**Answer:** 2301

turns-00029.parquet:40849

8ff02290ef1db035d4facd09
turn 1/1o1-preview-2024-09-12IndonesianIndonesia27 words
degenerate_repetitionAbsentFinal dense release
USER
@testing_t123_bot sebaiknya saya diam saja kali ya?
ASSISTANT
Tidak perlu diam jika Anda memiliki sesuatu yang ingin disampaikan. Saya di sini untuk mendengarkan dan membantu jika Anda membutuhkannya.

turns-00029.parquet:40850

d3c802bb7546636dd4b8a467
turn 1/1o1-preview-2024-09-12EnglishUnited States468 words
degenerate_repetitionAbsentFinal dense release
USER
I want you to act as a dialogue generator to create a humorous dialogue between SpongeBob and Patrick that lasts at least 50 seconds. The conversation should have 10 turns, with SpongeBob starting first, followed by Patrick. Each character will guess something fun about the viewers in real life, making it silly and engaging. You should generate new content each time, using this pattern: For the first []: integrate it into a sentence like this: '[text1] YOU ARE ELIMINATED' as a title and keep max 5 words in []. For the second [], end it with the word 'edition'. For the remaining 3-12 [], only use a single word within the brackets. Ensure the total word count in the brackets is exactly 12. Make sure the dialogue is creative, lighthearted, and corrected for grammar. Here's an example to follow: [IF I SAY Your Battery Percent]
[Extreme Edition]
SpongeBob: If I guess your battery percentage, you’re OUT! Extreme edition – let’s go!
Patrick: First guess [23%]. If that’s your battery, it’s time to go. Better luck next charge!
SpongeBob: Second [78%]. If your battery says that, sorry, you’re outta juice and outta here!
Patrick: How about [35%?] If that’s your level, it's time to leave! Bye-bye!
SpongeBob: Next guess [42%]. If this matches your battery, say goodbye, because you're officially OUT!
Patrick: Take a moment to likes this video if you love your family (and maybe your charger)!
SpongeBob: Now, [56%]. If your battery’s at this percentage… you’re OUT of power and out of mind!
Patrick: What about [9%]? Low battery alert! If that’s you, see ya later!
SpongeBob: Next [57%]. If your battery says this, sorry, but you’re out!
Patrick: Now, if you’re sitting at [29%], time to say goodbye, friend!
SpongeBob: Next guess [44%]. If this your battery percent, thats great, because you're officially OUT!
Patrick: And finally, if your battery is [anywhere in the 90%], happy charging… but you’re OUT!
SpongeBob: If you made it this far, comment ‘Winner’ to show everyone you’re a battery champ!
ASSISTANT
[IF I GUESS YOUR FAVORITE ANIMAL] YOU ARE ELIMINATED

[Ocean Edition]

SpongeBob: If I guess your favorite animal, YOU ARE ELIMINATED! [Ocean Edition] Let's dive in!

Patrick: Is it [Dolphin]? If it is, you're out of the sea!

SpongeBob: How about [Shark]? If that's your favorite, time to swim away!

Patrick: Maybe it's [Octopus]? If you love octopuses, sorry, you're eliminated!

SpongeBob: Perhaps [Crab]? If crabs are your favorite, it's time to scuttle off!

Patrick: What about [Seal]? If you like seals best, wave goodbye!

SpongeBob: If you're still here, you must have a unique favorite animal!

Patrick: Take a moment to like this video if you love jellyfish!

SpongeBob: Thanks for playing! If we didn't guess your favorite animal, you're a winner!

Patrick: Comment your favorite sea creature below!