# -*- coding: utf-8 -*-
import sys
import os
import numpy as np
import cv2
import gc
import json
import time
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QPushButton, QLabel, QFileDialog, QTableWidget, QTableWidgetItem,
                             QHeaderView, QSplitter, QFrame, QProgressBar, QMessageBox, QComboBox,
                             QScrollArea, QCheckBox, QGroupBox, QRadioButton, QButtonGroup, QSlider,
                             QDialog, QListWidget, QDialogButtonBox, QTabWidget, QGridLayout,
                             QLineEdit, QSpinBox, QFormLayout, QColorDialog, QDoubleSpinBox)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize, QPoint, QRect, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap, QFont, QColor, QPainter, QPen, QBrush, QWheelEvent, QMouseEvent
from paddleocr import LayoutDetection
import fitz  # PyMuPDF
import tempfile
import shutil
from concurrent.futures import ThreadPoolExecutor
import queue
from sklearn.cluster import KMeans  # 用于文本区块聚类分析


class ScrollableImageLabel(QScrollArea):
    """可缩放和拖动的图像显示控件"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.image_label = QLabel()
        self.image_label.setAlignment(Qt.AlignCenter)
        self.setWidget(self.image_label)
        self.setWidgetResizable(True)
        self.setStyleSheet("border: 1px solid #ddd; border-radius: 6px;")
        # 图像和缩放相关变量
        self.original_pixmap = None
        self.scale_factor = 1.0
        self.drag_position = QPoint()
        self.is_dragging = False
        # 设置鼠标跟踪
        self.setMouseTracking(True)
        self.image_label.setMouseTracking(True)
        self.setCursor(Qt.OpenHandCursor)
        # 连接鼠标事件
        self.image_label.mousePressEvent = self.mousePressEvent
        self.image_label.mouseReleaseEvent = self.mouseReleaseEvent
        self.image_label.mouseMoveEvent = self.mouseMoveEvent
        self.image_label.wheelEvent = self.wheelEvent

    def set_image(self, img_array):
        """设置图像"""
        # 确保图像是RGB格式
        if len(img_array.shape) == 2:  # 灰度图
            img_array = cv2.cvtColor(img_array, cv2.COLOR_GRAY2RGB)
        elif img_array.shape[2] == 4:  # RGBA
            img_array = cv2.cvtColor(img_array, cv2.COLOR_RGBA2RGB)
        elif img_array.shape[2] == 3:  # BGR (OpenCV默认)
            img_array = cv2.cvtColor(img_array, cv2.COLOR_BGR2RGB)
        height, width, channel = img_array.shape
        bytes_per_line = 3 * width
        q_img = QImage(img_array.data, width, height, bytes_per_line, QImage.Format_RGB888)
        # 保存原始图像
        self.original_pixmap = QPixmap.fromImage(q_img)
        # 适应窗口大小显示
        self.fit_to_window()

    def fit_to_window(self):
        """适应窗口大小显示图像"""
        if not self.original_pixmap:
            return
        # 计算缩放比例以适应窗口
        window_size = self.viewport().size()
        pixmap_size = self.original_pixmap.size()
        width_ratio = window_size.width() / pixmap_size.width()
        height_ratio = window_size.height() / pixmap_size.height()
        # 取较小的比例，确保图像完全显示在窗口内
        self.scale_factor = min(width_ratio, height_ratio)
        # 缩放图像
        self.update_scaled_pixmap()

    def update_scaled_pixmap(self):
        """更新缩放后的图像"""
        if not self.original_pixmap:
            return
        # 计算新尺寸
        new_size = self.original_pixmap.size() * self.scale_factor
        # 缩放图像
        scaled_pixmap = self.original_pixmap.scaled(
            new_size,
            Qt.KeepAspectRatio,
            Qt.SmoothTransformation
        )
        # 更新显示
        self.image_label.setPixmap(scaled_pixmap)
        self.image_label.setFixedSize(scaled_pixmap.size())

    def set_scale(self, scale):
        """设置缩放比例"""
        if not self.original_pixmap:
            return
        # 限制缩放范围
        self.scale_factor = max(0.1, min(5.0, scale))
        # 更新缩放后的图像
        self.update_scaled_pixmap()

    def zoom_in(self):
        """放大图像"""
        self.set_scale(self.scale_factor * 1.25)

    def zoom_out(self):
        """缩小图像"""
        self.set_scale(self.scale_factor * 0.8)

    def mousePressEvent(self, event: QMouseEvent):
        """鼠标按下事件"""
        if event.button() == Qt.LeftButton:
            # 开始拖动
            self.is_dragging = True
            self.drag_position = event.pos()
            self.setCursor(Qt.ClosedHandCursor)

    def mouseReleaseEvent(self, event: QMouseEvent):
        """鼠标释放事件"""
        if event.button() == Qt.LeftButton:
            # 结束拖动
            self.is_dragging = False
            self.setCursor(Qt.OpenHandCursor)

    def mouseMoveEvent(self, event: QMouseEvent):
        """鼠标移动事件"""
        if self.is_dragging:
            # 计算移动距离
            delta = event.pos() - self.drag_position
            # 移动滚动条
            self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x())
            self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y())
            # 更新拖动位置
            self.drag_position = event.pos()

    def wheelEvent(self, event: QWheelEvent):
        """鼠标滚轮事件，用于缩放"""
        # 获取滚轮角度
        angle_delta = event.angleDelta().y()
        # 根据滚轮方向调整缩放比例
        if angle_delta > 0:
            self.zoom_in()
        else:
            self.zoom_out()


class PageSelectionDialog(QDialog):
    """页面选择对话框"""

    def __init__(self, total_pages, current_page, parent=None):
        super().__init__(parent)
        self.setWindowTitle("选择页面")
        self.setMinimumWidth(400)
        self.setMinimumHeight(300)
        layout = QVBoxLayout()
        # 选择模式
        mode_group = QGroupBox("选择模式")
        mode_layout = QVBoxLayout()
        self.all_pages_radio = QRadioButton("所有页面")
        self.all_pages_radio.setChecked(True)
        mode_layout.addWidget(self.all_pages_radio)
        self.current_page_radio = QRadioButton("当前页面")
        mode_layout.addWidget(self.current_page_radio)
        mode_group.setLayout(mode_layout)
        layout.addWidget(mode_group)
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)

    def get_selected_pages(self):
        """获取选中的页面"""
        if self.all_pages_radio.isChecked():
            return "all"
        elif self.current_page_radio.isChecked():
            return "current"
        else:
            return None


class PageTestDialog(QDialog):
    """页面测试对话框"""

    def __init__(self, total_pages, parent=None):
        super().__init__(parent)
        self.setWindowTitle("页面测试设置")
        self.setMinimumWidth(450)
        self.setMinimumHeight(350)
        layout = QVBoxLayout()
        # 测试模式选择
        mode_group = QGroupBox("测试模式")
        mode_layout = QVBoxLayout()
        # 按比例选择
        self.proportion_radio = QRadioButton("按比例选择")
        self.proportion_radio.setChecked(True)
        mode_layout.addWidget(self.proportion_radio)
        proportion_layout = QHBoxLayout()
        proportion_layout.addWidget(QLabel("选择比例:"))
        self.proportion_spin = QSpinBox()
        self.proportion_spin.setRange(1, 100)
        self.proportion_spin.setValue(10)
        self.proportion_spin.setSuffix("%")
        proportion_layout.addWidget(self.proportion_spin)
        proportion_layout.addWidget(QLabel("(从文档开头选择指定比例的页面)"))
        proportion_layout.addStretch()
        mode_layout.addLayout(proportion_layout)
        # 按数量选择
        self.count_radio = QRadioButton("按数量选择")
        mode_layout.addWidget(self.count_radio)
        count_layout = QHBoxLayout()
        count_layout.addWidget(QLabel("选择数量:"))
        self.count_spin = QSpinBox()
        self.count_spin.setRange(1, total_pages if total_pages > 0 else 1)
        self.count_spin.setValue(min(5, total_pages if total_pages > 0 else 1))
        count_layout.addWidget(self.count_spin)
        count_layout.addWidget(QLabel("(从文档开头选择指定数量的页面)"))
        count_layout.addStretch()
        mode_layout.addLayout(count_layout)
        # 按范围选择
        self.range_radio = QRadioButton("按范围选择")
        mode_layout.addWidget(self.range_radio)
        range_layout = QGridLayout()
        range_layout.addWidget(QLabel("起始页码:"), 0, 0)
        self.start_page_spin = QSpinBox()
        self.start_page_spin.setRange(1, total_pages if total_pages > 0 else 1)
        self.start_page_spin.setValue(1)
        range_layout.addWidget(self.start_page_spin, 0, 1)
        range_layout.addWidget(QLabel("结束页码:"), 1, 0)
        self.end_page_spin = QSpinBox()
        self.end_page_spin.setRange(1, total_pages if total_pages > 0 else 1)
        self.end_page_spin.setValue(min(5, total_pages if total_pages > 0 else 1))
        range_layout.addWidget(self.end_page_spin, 1, 1)
        range_layout.addWidget(QLabel("(选择指定范围内的页面)"), 2, 0, 1, 2)
        mode_layout.addLayout(range_layout)
        mode_group.setLayout(mode_layout)
        layout.addWidget(mode_group)
        # 连接信号，根据选择模式启用/禁用相应控件
        self.proportion_radio.toggled.connect(
            lambda checked: (
                self.proportion_spin.setEnabled(checked),
                self.count_spin.setEnabled(not checked and not self.range_radio.isChecked()),
                self.start_page_spin.setEnabled(not checked and self.range_radio.isChecked()),
                self.end_page_spin.setEnabled(not checked and self.range_radio.isChecked())
            )
        )
        self.count_radio.toggled.connect(
            lambda checked: (
                self.count_spin.setEnabled(checked),
                self.proportion_spin.setEnabled(not checked and not self.range_radio.isChecked()),
                self.start_page_spin.setEnabled(not checked and self.range_radio.isChecked()),
                self.end_page_spin.setEnabled(not checked and self.range_radio.isChecked())
            )
        )
        self.range_radio.toggled.connect(
            lambda checked: (
                self.start_page_spin.setEnabled(checked),
                self.end_page_spin.setEnabled(checked),
                self.proportion_spin.setEnabled(not checked and not self.count_radio.isChecked()),
                self.count_spin.setEnabled(not checked and not self.proportion_radio.isChecked())
            )
        )
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)
        self.total_pages = total_pages

    def get_test_pages(self):
        """获取测试页面范围"""
        if self.proportion_radio.isChecked():
            # 按比例选择
            proportion = self.proportion_spin.value()
            count = max(1, int(self.total_pages * proportion / 100))
            return list(range(count))
        elif self.count_radio.isChecked():
            # 按数量选择
            count = self.count_spin.value()
            return list(range(min(count, self.total_pages)))
        elif self.range_radio.isChecked():
            # 按范围选择
            start = self.start_page_spin.value() - 1  # 转换为0-based索引
            end = self.end_page_spin.value() - 1
            # 确保范围有效
            start = max(0, min(start, self.total_pages - 1))
            end = max(start, min(end, self.total_pages - 1))
            return list(range(start, end + 1))
        else:
            # 默认返回前5页
            return list(range(min(5, self.total_pages)))


class AdvancedSettingsDialog(QDialog):
    """高级参数设置对话框"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("高级参数设置")
        self.setMinimumWidth(900)
        self.setMinimumHeight(600)
        layout = QVBoxLayout()
        # 创建一个滚动区域，以便在参数较多时可以滚动
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_content = QWidget()
        scroll_layout = QVBoxLayout(scroll_content)
        # img_size参数设置
        img_size_group = QGroupBox("输入图像大小 (img_size)")
        img_size_layout = QVBoxLayout()
        # 默认选项
        default_layout = QHBoxLayout()
        self.img_size_default_radio = QRadioButton("默认")
        self.img_size_default_radio.setChecked(True)
        default_layout.addWidget(self.img_size_default_radio)
        self.img_size_default_radio.toggled.connect(lambda: self.update_param_inputs("img_size"))
        default_layout.addStretch()
        img_size_layout.addLayout(default_layout)
        # 单值选项
        single_layout = QHBoxLayout()
        self.img_size_single_radio = QRadioButton("单值")
        single_layout.addWidget(self.img_size_single_radio)
        self.img_size_single_radio.toggled.connect(lambda: self.update_param_inputs("img_size"))
        single_layout.addWidget(QLabel("图像大小:"))
        self.img_size_single_spin = QSpinBox()
        self.img_size_single_spin.setRange(100, 2048)
        self.img_size_single_spin.setValue(800)
        self.img_size_single_spin.setEnabled(False)
        single_layout.addWidget(self.img_size_single_spin)
        single_layout.addWidget(QLabel("像素"))
        single_layout.addStretch()
        img_size_layout.addLayout(single_layout)
        # 列表选项
        list_layout = QHBoxLayout()
        self.img_size_list_radio = QRadioButton("列表")
        list_layout.addWidget(self.img_size_list_radio)
        self.img_size_list_radio.toggled.connect(lambda: self.update_param_inputs("img_size"))
        list_layout.addWidget(QLabel("宽高尺寸:"))
        self.img_size_list_edit = QLineEdit()
        self.img_size_list_edit.setPlaceholderText("例如: 800, 600")
        self.img_size_list_edit.setEnabled(False)
        list_layout.addWidget(self.img_size_list_edit)
        list_layout.addWidget(QLabel("(宽度, 高度)"))
        list_layout.addStretch()
        img_size_layout.addLayout(list_layout)
        img_size_group.setLayout(img_size_layout)
        scroll_layout.addWidget(img_size_group)
        # threshold参数设置
        threshold_group = QGroupBox("置信度阈值 (threshold)")
        threshold_layout = QVBoxLayout()
        # 默认选项
        default_layout = QHBoxLayout()
        self.threshold_default_radio = QRadioButton("默认")
        self.threshold_default_radio.setChecked(True)
        default_layout.addWidget(self.threshold_default_radio)
        self.threshold_default_radio.toggled.connect(lambda: self.update_param_inputs("threshold"))
        default_layout.addStretch()
        threshold_layout.addLayout(default_layout)
        # 单值选项
        single_layout = QHBoxLayout()
        self.threshold_single_radio = QRadioButton("单值")
        single_layout.addWidget(self.threshold_single_radio)
        self.threshold_single_radio.toggled.connect(lambda: self.update_param_inputs("threshold"))
        single_layout.addWidget(QLabel("阈值:"))
        self.threshold_single_spin = QDoubleSpinBox()
        self.threshold_single_spin.setRange(0.0, 1.0)
        self.threshold_single_spin.setSingleStep(0.05)
        self.threshold_single_spin.setValue(0.5)
        self.threshold_single_spin.setEnabled(False)
        single_layout.addWidget(self.threshold_single_spin)
        single_layout.addWidget(QLabel("(例如: 0.5)"))
        single_layout.addStretch()
        threshold_layout.addLayout(single_layout)
        # 字典选项
        dict_layout = QHBoxLayout()
        self.threshold_dict_radio = QRadioButton("字典")
        dict_layout.addWidget(self.threshold_dict_radio)
        self.threshold_dict_radio.toggled.connect(lambda: self.update_param_inputs("threshold"))
        dict_layout.addWidget(QLabel("类别指定:"))
        self.threshold_dict_edit = QLineEdit()
        self.threshold_dict_edit.setPlaceholderText("例如: 0: 0.4, 2: 0.6")
        self.threshold_dict_edit.setEnabled(False)
        dict_layout.addWidget(self.threshold_dict_edit)
        dict_layout.addWidget(QLabel("(类别ID: 阈值)"))
        dict_layout.addStretch()
        threshold_layout.addLayout(dict_layout)
        threshold_group.setLayout(threshold_layout)
        scroll_layout.addWidget(threshold_group)
        # unclip_ratio参数设置
        unclip_ratio_group = QGroupBox("检测框缩放比例 (layout_unclip_ratio)")
        unclip_ratio_layout = QVBoxLayout()
        # 默认选项
        default_layout = QHBoxLayout()
        self.unclip_ratio_default_radio = QRadioButton("默认")
        self.unclip_ratio_default_radio.setChecked(True)
        default_layout.addWidget(self.unclip_ratio_default_radio)
        self.unclip_ratio_default_radio.toggled.connect(lambda: self.update_param_inputs("unclip_ratio"))
        default_layout.addStretch()
        unclip_ratio_layout.addLayout(default_layout)
        # 单值选项
        single_layout = QHBoxLayout()
        self.unclip_ratio_single_radio = QRadioButton("单值")
        single_layout.addWidget(self.unclip_ratio_single_radio)
        self.unclip_ratio_single_radio.toggled.connect(lambda: self.update_param_inputs("unclip_ratio"))
        single_layout.addWidget(QLabel("缩放比例:"))
        self.unclip_ratio_single_spin = QDoubleSpinBox()
        self.unclip_ratio_single_spin.setRange(0.5, 3.0)
        self.unclip_ratio_single_spin.setSingleStep(0.1)
        self.unclip_ratio_single_spin.setValue(1.0)
        self.unclip_ratio_single_spin.setEnabled(False)
        single_layout.addWidget(self.unclip_ratio_single_spin)
        single_layout.addWidget(QLabel("(例如: 1.2)"))
        single_layout.addStretch()
        unclip_ratio_layout.addLayout(single_layout)
        # 列表选项
        list_layout = QHBoxLayout()
        self.unclip_ratio_list_radio = QRadioButton("列表")
        list_layout.addWidget(self.unclip_ratio_list_radio)
        self.unclip_ratio_list_radio.toggled.connect(lambda: self.update_param_inputs("unclip_ratio"))
        list_layout.addWidget(QLabel("宽高比例:"))
        self.unclip_ratio_list_edit = QLineEdit()
        self.unclip_ratio_list_edit.setPlaceholderText("例如: 1.1, 1.2")
        self.unclip_ratio_list_edit.setEnabled(False)
        list_layout.addWidget(self.unclip_ratio_list_edit)
        list_layout.addWidget(QLabel("(宽度比例, 高度比例)"))
        list_layout.addStretch()
        unclip_ratio_layout.addLayout(list_layout)
        # 字典选项
        dict_layout = QHBoxLayout()
        self.unclip_ratio_dict_radio = QRadioButton("字典")
        dict_layout.addWidget(self.unclip_ratio_dict_radio)
        self.unclip_ratio_dict_radio.toggled.connect(lambda: self.update_param_inputs("unclip_ratio"))
        dict_layout.addWidget(QLabel("类别指定:"))
        self.unclip_ratio_dict_edit = QLineEdit()
        self.unclip_ratio_dict_edit.setPlaceholderText("例如: 0: (1.1, 2.0), 1: (1.2, 1.5)")
        self.unclip_ratio_dict_edit.setEnabled(False)
        dict_layout.addWidget(self.unclip_ratio_dict_edit)
        dict_layout.addWidget(QLabel("(类别ID: (宽比例, 高度比例))"))
        # 格式化按钮
        self.format_unclip_ratio_btn = QPushButton("格式化")
        self.format_unclip_ratio_btn.setEnabled(False)
        self.format_unclip_ratio_btn.clicked.connect(lambda: self.format_param_input("unclip_ratio"))
        dict_layout.addWidget(self.format_unclip_ratio_btn)
        dict_layout.addStretch()
        unclip_ratio_layout.addLayout(dict_layout)
        unclip_ratio_group.setLayout(unclip_ratio_layout)
        scroll_layout.addWidget(unclip_ratio_group)
        # merge_bboxes_mode参数设置
        merge_mode_group = QGroupBox("框合并模式 (layout_merge_bboxes_mode)")
        merge_mode_layout = QVBoxLayout()
        # 默认选项
        default_layout = QHBoxLayout()
        self.merge_mode_default_radio = QRadioButton("默认")
        self.merge_mode_default_radio.setChecked(True)
        default_layout.addWidget(self.merge_mode_default_radio)
        self.merge_mode_default_radio.toggled.connect(lambda: self.update_param_inputs("merge_mode"))
        default_layout.addStretch()
        merge_mode_layout.addLayout(default_layout)
        # 单值选项
        single_layout = QHBoxLayout()
        self.merge_mode_single_radio = QRadioButton("单值")
        single_layout.addWidget(self.merge_mode_single_radio)
        self.merge_mode_single_radio.toggled.connect(lambda: self.update_param_inputs("merge_mode"))
        single_layout.addWidget(QLabel("合并模式:"))
        self.merge_mode_combo = QComboBox()
        self.merge_mode_combo.addItems(["union", "large", "small"])
        self.merge_mode_combo.setCurrentText("union")
        self.merge_mode_combo.setEnabled(False)
        single_layout.addWidget(self.merge_mode_combo)
        single_layout.addWidget(QLabel("(union/large/small)"))
        single_layout.addStretch()
        merge_mode_layout.addLayout(single_layout)
        # 字典选项
        dict_layout = QHBoxLayout()
        self.merge_mode_dict_radio = QRadioButton("字典")
        dict_layout.addWidget(self.merge_mode_dict_radio)
        self.merge_mode_dict_radio.toggled.connect(lambda: self.update_param_inputs("merge_mode"))
        dict_layout.addWidget(QLabel("类别指定:"))
        self.merge_mode_dict_edit = QLineEdit()
        self.merge_mode_dict_edit.setPlaceholderText("例如: 0: large, 2: small")
        self.merge_mode_dict_edit.setEnabled(False)
        dict_layout.addWidget(self.merge_mode_dict_edit)
        dict_layout.addWidget(QLabel("(类别ID: 合并模式)"))
        # 格式化按钮
        self.format_merge_mode_btn = QPushButton("格式化")
        self.format_merge_mode_btn.setEnabled(False)
        self.format_merge_mode_btn.clicked.connect(lambda: self.format_param_input("merge_mode"))
        dict_layout.addWidget(self.format_merge_mode_btn)
        dict_layout.addStretch()
        merge_mode_layout.addLayout(dict_layout)
        merge_mode_group.setLayout(merge_mode_layout)
        scroll_layout.addWidget(merge_mode_group)
        scroll_area.setWidget(scroll_content)
        layout.addWidget(scroll_area)
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)

    def update_param_inputs(self, param_type):
        """更新参数输入控件的启用状态"""
        if param_type == "img_size":
            # 更新img_size相关控件
            self.img_size_single_spin.setEnabled(self.img_size_single_radio.isChecked())
            self.img_size_list_edit.setEnabled(self.img_size_list_radio.isChecked())
        elif param_type == "threshold":
            # 更新threshold相关控件
            self.threshold_single_spin.setEnabled(self.threshold_single_radio.isChecked())
            self.threshold_dict_edit.setEnabled(self.threshold_dict_radio.isChecked())
        elif param_type == "unclip_ratio":
            # 更新unclip_ratio相关控件
            self.unclip_ratio_single_spin.setEnabled(self.unclip_ratio_single_radio.isChecked())
            self.unclip_ratio_list_edit.setEnabled(self.unclip_ratio_list_radio.isChecked())
            self.unclip_ratio_dict_edit.setEnabled(self.unclip_ratio_dict_radio.isChecked())
            self.format_unclip_ratio_btn.setEnabled(self.unclip_ratio_dict_radio.isChecked())
        elif param_type == "merge_mode":
            # 更新merge_mode相关控件
            self.merge_mode_combo.setEnabled(self.merge_mode_single_radio.isChecked())
            self.merge_mode_dict_edit.setEnabled(self.merge_mode_dict_radio.isChecked())
            self.format_merge_mode_btn.setEnabled(self.merge_mode_dict_radio.isChecked())

    def format_param_input(self, param_type):
        """格式化参数输入"""
        try:
            if param_type == "img_size":
                if self.img_size_list_radio.isChecked():
                    text = self.img_size_list_edit.text()
                    if text:
                        # 解析并重新格式化
                        result = self.parse_list_input(text, "img_size")
                        self.img_size_list_edit.setText(f"{result[0]}, {result[1]}")
            elif param_type == "threshold":
                if self.threshold_dict_radio.isChecked():
                    text = self.threshold_dict_edit.text()
                    if text:
                        # 解析并重新格式化
                        result = self.parse_threshold_dict_input(text)
                        # 重新格式化为标准格式
                        formatted_items = []
                        for key, value in result.items():
                            formatted_items.append(f"{key}: {value}")
                        formatted_text = ", ".join(formatted_items)
                        self.threshold_dict_edit.setText(formatted_text)
            elif param_type == "unclip_ratio":
                if self.unclip_ratio_list_radio.isChecked():
                    text = self.unclip_ratio_list_edit.text()
                    if text:
                        # 解析并重新格式化
                        result = self.parse_list_input(text, "unclip_ratio")
                        self.unclip_ratio_list_edit.setText(f"{result[0]}, {result[1]}")
                elif self.unclip_ratio_dict_radio.isChecked():
                    text = self.unclip_ratio_dict_edit.text()
                    if text:
                        # 解析并重新格式化
                        result = self.parse_unclip_ratio_dict_input(text)
                        # 重新格式化为标准格式
                        formatted_items = []
                        for key, value in result.items():
                            formatted_items.append(f"{key}: ({value[0]}, {value[1]})")
                        formatted_text = ", ".join(formatted_items)
                        self.unclip_ratio_dict_edit.setText(formatted_text)
            elif param_type == "merge_mode":
                if self.merge_mode_dict_radio.isChecked():
                    text = self.merge_mode_dict_edit.text()
                    if text:
                        # 解析并重新格式化
                        result = self.parse_merge_mode_dict_input(text)
                        # 重新格式化为标准格式
                        formatted_items = []
                        for key, value in result.items():
                            formatted_items.append(f"{key}: {value}")
                        formatted_text = ", ".join(formatted_items)
                        self.merge_mode_dict_edit.setText(formatted_text)
        except ValueError as e:
            QMessageBox.warning(self, "警告", f"格式化失败: {str(e)}")

    def parse_list_input(self, text, param_type="img_size"):
        """解析列表形式的输入，根据参数类型处理不同的数据类型"""
        try:
            print(f"解析列表输入: {text} (参数类型: {param_type})")  # 调试信息
            # 移除所有空格，包括全角空格
            text = text.replace(" ", "").replace("　", "")
            print(f"去除空格后: {text}")  # 调试信息
            # 处理可能的方括号
            if text.startswith('[') and text.endswith(']'):
                text = text[1:-1]
                print(f"去除方括号后: {text}")  # 调试信息
            # 分割字符串
            parts = text.split(',')
            print(f"分割后的部分: {parts}")  # 调试信息
            if len(parts) != 2:
                raise ValueError("列表必须包含两个值")
            # 根据参数类型决定转换方式
            if param_type == "img_size":
                # img_size参数需要整数
                try:
                    value1 = int(parts[0])
                    value2 = int(parts[1])
                except ValueError:
                    raise ValueError("img_size列表值必须是整数")
                # 检查范围
                if value1 < 100 or value1 > 4096 or value2 < 100 or value2 > 4096:
                    raise ValueError("img_size宽度和高度必须在100-4096之间")
            elif param_type == "unclip_ratio":
                # unclip_ratio参数可以是浮点数
                try:
                    value1 = float(parts[0])
                    value2 = float(parts[1])
                except ValueError:
                    raise ValueError("unclip_ratio列表值必须是数字")
                # 检查范围
                if value1 <= 0 or value2 <= 0:
                    raise ValueError("unclip_ratio宽度和高度比例必须大于0")
            else:
                # 默认处理为浮点数
                try:
                    value1 = float(parts[0])
                    value2 = float(parts[1])
                except ValueError:
                    raise ValueError("列表值必须是数字")
            result = [value1, value2]
            print(f"解析结果: {result}")  # 调试信息
            return result
        except Exception as e:
            print(f"解析错误: {str(e)}")  # 调试信息
            raise ValueError(f"列表格式错误: {str(e)}")

    def parse_threshold_dict_input(self, text):
        """解析阈值字典形式的输入，如 '0: 0.4, 2: 0.6' -> {0: 0.4, 2: 0.6}"""
        try:
            print(f"解析阈值字典输入: {text}")  # 调试信息
            # 处理可能的花括号
            if text.startswith('{') and text.endswith('}'):
                text = text[1:-1]
                print(f"去除花括号后: {text}")  # 调试信息
            result = {}
            # 分割键值对
            pairs = text.split(',')
            print(f"分割后的键值对: {pairs}")  # 调试信息
            for pair in pairs:
                pair = pair.strip()
                if not pair:
                    continue  # 跳过空字符串
                print(f"处理键值对: {pair}")  # 调试信息
                # 分割键和值，只分割第一个冒号
                if ':' not in pair:
                    raise ValueError(f"键值对格式错误: {pair}")
                key_part, value_part = pair.split(':', 1)
                key = key_part.strip()
                value = value_part.strip()
                print(f"键: {key}, 值: {value}")  # 调试信息
                # 解析键
                try:
                    key = int(key)
                except ValueError:
                    raise ValueError(f"键必须是整数: {key}")
                # 解析值
                try:
                    value = float(value)
                except ValueError:
                    raise ValueError(f"值必须是数字: {value}")
                # 检查值范围
                if value < 0 or value > 1:
                    raise ValueError(f"阈值必须在0-1之间: {value}")
                result[key] = value
            print(f"解析结果: {result}")  # 调试信息
            return result
        except Exception as e:
            print(f"解析错误: {str(e)}")  # 调试信息
            raise ValueError(f"阈值字典格式错误: {str(e)}")

    def parse_unclip_ratio_dict_input(self, text):
        """解析缩放比例字典形式的输入，支持多类别设置"""
        try:
            print(f"解析缩放比例字典输入: {text}")  # 调试信息
            # 处理可能的花括号
            if text.startswith('{') and text.endswith('}'):
                text = text[1:-1]
                print(f"去除花括号后: {text}")  # 调试信息
            result = {}
            # 先分割键值对，但要确保不在元组内部分割
            pairs = []
            current_pair = ""
            in_tuple = False
            paren_count = 0
            for char in text:
                if char == '(':
                    in_tuple = True
                    paren_count += 1
                elif char == ')':
                    paren_count -= 1
                    if paren_count == 0:
                        in_tuple = False
                if char == ',' and not in_tuple:
                    # 只有不在元组内部时才按逗号分割
                    pairs.append(current_pair.strip())
                    current_pair = ""
                else:
                    current_pair += char
            # 添加最后一个键值对
            if current_pair:
                pairs.append(current_pair.strip())
            print(f"分割后的键值对: {pairs}")  # 调试信息
            for pair in pairs:
                if not pair:
                    continue  # 跳过空字符串
                print(f"处理键值对: {pair}")  # 调试信息
                # 分割键和值，只分割第一个冒号
                if ':' not in pair:
                    raise ValueError(f"键值对格式错误: {pair}")
                key_part, value_part = pair.split(':', 1)
                key = key_part.strip()
                value = value_part.strip()
                print(f"键: {key}, 值: {value}")  # 调试信息
                # 解析键
                try:
                    key = int(key)
                except ValueError:
                    raise ValueError(f"键必须是整数: {key}")
                # 解析元组值，如 (1.1, 1.2)
                if not (value.startswith('(') and value.endswith(')')):
                    raise ValueError(f"值必须是元组格式: {value}")
                # 提取元组内容
                tuple_content = value[1:-1].strip()
                print(f"元组内容: {tuple_content}")  # 调试信息
                if not tuple_content:
                    raise ValueError("元组内容为空")
                # 分割元组内容
                tuple_parts = tuple_content.split(',')
                print(f"分割后的元组部分: {tuple_parts}")  # 调试信息
                if len(tuple_parts) != 2:
                    raise ValueError(f"元组必须包含两个值: {tuple_content}")
                try:
                    value1 = float(tuple_parts[0].strip())
                    value2 = float(tuple_parts[1].strip())
                except ValueError:
                    raise ValueError(f"元组值必须是数字: {tuple_content}")
                # 检查值范围
                if value1 <= 0 or value2 <= 0:
                    raise ValueError(f"缩放比例必须大于0: ({value1}, {value2})")
                tuple_value = (value1, value2)
                print(f"解析后的元组值: {tuple_value}")  # 调试信息
                result[key] = tuple_value
            print(f"最终结果: {result}")  # 调试信息
            return result
        except Exception as e:
            print(f"解析错误: {str(e)}")  # 调试信息
            raise ValueError(f"缩放比例字典格式错误: {str(e)}")

    def parse_merge_mode_dict_input(self, text):
        """解析合并模式字典形式的输入，如 '0: large, 2: small' -> {0: 'large', 2: 'small'}"""
        try:
            print(f"解析合并模式字典输入: {text}")  # 调试信息
            # 处理可能的花括号
            if text.startswith('{') and text.endswith('}'):
                text = text[1:-1]
                print(f"去除花括号后: {text}")  # 调试信息
            result = {}
            # 分割键值对
            pairs = text.split(',')
            print(f"分割后的键值对: {pairs}")  # 调试信息
            for pair in pairs:
                pair = pair.strip()
                if not pair:
                    continue  # 跳过空字符串
                print(f"处理键值对: {pair}")  # 调试信息
                # 分割键和值，只分割第一个冒号
                if ':' not in pair:
                    raise ValueError(f"键值对格式错误: {pair}")
                key_part, value_part = pair.split(':', 1)
                key = key_part.strip()
                value = value_part.strip()
                print(f"键: {key}, 值: {value}")  # 调试信息
                # 解析键
                try:
                    key = int(key)
                except ValueError:
                    raise ValueError(f"键必须是整数: {key}")
                # 解析值，移除可能的引号
                value = value.strip().strip("'\"")
                print(f"处理后的值: {value}")  # 调试信息
                if value not in ["union", "large", "small"]:
                    raise ValueError(f"无效的合并模式: {value}")
                result[key] = value
            print(f"解析结果: {result}")  # 调试信息
            return result
        except Exception as e:
            print(f"解析错误: {str(e)}")  # 调试信息
            raise ValueError(f"合并模式字典格式错误: {str(e)}")

    def get_parameters(self):
        """获取参数设置"""
        try:
            # img_size参数
            if self.img_size_default_radio.isChecked():
                img_size = None
            elif self.img_size_single_radio.isChecked():
                img_size = self.img_size_single_spin.value()
            elif self.img_size_list_radio.isChecked():
                img_size = self.parse_list_input(self.img_size_list_edit.text(), "img_size")
            # threshold参数
            if self.threshold_default_radio.isChecked():
                threshold = None
            elif self.threshold_single_radio.isChecked():
                threshold = self.threshold_single_spin.value()
            elif self.threshold_dict_radio.isChecked():
                threshold = self.parse_threshold_dict_input(self.threshold_dict_edit.text())
            # unclip_ratio参数
            if self.unclip_ratio_default_radio.isChecked():
                layout_unclip_ratio = None
            elif self.unclip_ratio_single_radio.isChecked():
                layout_unclip_ratio = self.unclip_ratio_single_spin.value()
            elif self.unclip_ratio_list_radio.isChecked():
                layout_unclip_ratio = self.parse_list_input(self.unclip_ratio_list_edit.text(), "unclip_ratio")
            elif self.unclip_ratio_dict_radio.isChecked():
                layout_unclip_ratio = self.parse_unclip_ratio_dict_input(self.unclip_ratio_dict_edit.text())
            # merge_bboxes_mode参数
            if self.merge_mode_default_radio.isChecked():
                layout_merge_bboxes_mode = None
            elif self.merge_mode_single_radio.isChecked():
                layout_merge_bboxes_mode = self.merge_mode_combo.currentText()
            elif self.merge_mode_dict_radio.isChecked():
                layout_merge_bboxes_mode = self.parse_merge_mode_dict_input(self.merge_mode_dict_edit.text())
            return {
                'img_size': img_size,
                'threshold': threshold,
                'layout_unclip_ratio': layout_unclip_ratio,
                'layout_merge_bboxes_mode': layout_merge_bboxes_mode
            }
        except ValueError as e:
            QMessageBox.warning(self, "参数错误", str(e))
            return None

    def set_parameters(self, parameters):
        """设置对话框中的参数值"""
        if not parameters:
            return
        # 设置img_size参数
        img_size = parameters.get('img_size')
        if img_size is None:
            self.img_size_default_radio.setChecked(True)
        elif isinstance(img_size, int):
            self.img_size_single_radio.setChecked(True)
            self.img_size_single_spin.setValue(img_size)
        elif isinstance(img_size, list) and len(img_size) == 2:
            self.img_size_list_radio.setChecked(True)
            self.img_size_list_edit.setText(f"{img_size[0]}, {img_size[1]}")
        # 设置threshold参数
        threshold = parameters.get('threshold')
        if threshold is None:
            self.threshold_default_radio.setChecked(True)
        elif isinstance(threshold, float):
            self.threshold_single_radio.setChecked(True)
            self.threshold_single_spin.setValue(threshold)
        elif isinstance(threshold, dict):
            self.threshold_dict_radio.setChecked(True)
            # 重新格式化为标准格式
            formatted_items = []
            for key, value in threshold.items():
                formatted_items.append(f"{key}: {value}")
            formatted_text = ", ".join(formatted_items)
            self.threshold_dict_edit.setText(formatted_text)
        # 设置unclip_ratio参数
        layout_unclip_ratio = parameters.get('layout_unclip_ratio')
        if layout_unclip_ratio is None:
            self.unclip_ratio_default_radio.setChecked(True)
        elif isinstance(layout_unclip_ratio, float):
            self.unclip_ratio_single_radio.setChecked(True)
            self.unclip_ratio_single_spin.setValue(layout_unclip_ratio)
        elif isinstance(layout_unclip_ratio, list) and len(layout_unclip_ratio) == 2:
            self.unclip_ratio_list_radio.setChecked(True)
            self.unclip_ratio_list_edit.setText(f"{layout_unclip_ratio[0]}, {layout_unclip_ratio[1]}")
        elif isinstance(layout_unclip_ratio, dict):
            self.unclip_ratio_dict_radio.setChecked(True)
            # 重新格式化为标准格式
            formatted_items = []
            for key, value in layout_unclip_ratio.items():
                formatted_items.append(f"{key}: ({value[0]}, {value[1]})")
            formatted_text = ", ".join(formatted_items)
            self.unclip_ratio_dict_edit.setText(formatted_text)
        # 设置merge_bboxes_mode参数
        layout_merge_bboxes_mode = parameters.get('layout_merge_bboxes_mode')
        if layout_merge_bboxes_mode is None:
            self.merge_mode_default_radio.setChecked(True)
        elif isinstance(layout_merge_bboxes_mode, str):
            self.merge_mode_single_radio.setChecked(True)
            self.merge_mode_combo.setCurrentText(layout_merge_bboxes_mode)
        elif isinstance(layout_merge_bboxes_mode, dict):
            self.merge_mode_dict_radio.setChecked(True)
            # 重新格式化为标准格式
            formatted_items = []
            for key, value in layout_merge_bboxes_mode.items():
                formatted_items.append(f"{key}: {value}")
            formatted_text = ", ".join(formatted_items)
            self.merge_mode_dict_edit.setText(formatted_text)
        # 更新参数输入控件的启用状态
        self.update_param_inputs("img_size")
        self.update_param_inputs("threshold")
        self.update_param_inputs("unclip_ratio")
        self.update_param_inputs("merge_mode")


class ImageExportOptionsDialog(QDialog):
    """图像导出选项对话框"""

    def __init__(self, block_types, parent=None):
        super().__init__(parent)
        self.setWindowTitle("导出区块图像选项")
        self.setMinimumWidth(400)
        layout = QVBoxLayout()
        # 区块类型选择
        type_group = QGroupBox("选择要导出的区块类型")
        type_layout = QVBoxLayout()
        # 全选/取消全选
        select_layout = QHBoxLayout()
        self.select_all_btn = QPushButton("全选")
        self.select_all_btn.clicked.connect(self.select_all)
        select_layout.addWidget(self.select_all_btn)
        self.deselect_all_btn = QPushButton("取消全选")
        self.deselect_all_btn.clicked.connect(self.deselect_all)
        select_layout.addWidget(self.deselect_all_btn)
        select_layout.addStretch()
        type_layout.addLayout(select_layout)
        # 区块类型复选框
        self.type_checkboxes = []
        for block_type in block_types:
            checkbox = QCheckBox(block_type)
            checkbox.setChecked(True)  # 默认全选
            type_layout.addWidget(checkbox)
            self.type_checkboxes.append(checkbox)
        type_group.setLayout(type_layout)
        layout.addWidget(type_group)
        # 页面选择
        page_group = QGroupBox("页面选择")
        page_layout = QVBoxLayout()
        self.all_pages_radio = QRadioButton("所有页面")
        self.all_pages_radio.setChecked(True)
        page_layout.addWidget(self.all_pages_radio)
        self.current_page_radio = QRadioButton("当前页面")
        page_layout.addWidget(self.current_page_radio)
        page_group.setLayout(page_layout)
        layout.addWidget(page_group)
        # 文本布局选项
        layout_group = QGroupBox("文本布局")
        layout_layout = QVBoxLayout()
        self.single_column_radio = QRadioButton("单栏文本")
        self.single_column_radio.setChecked(True)
        layout_layout.addWidget(self.single_column_radio)
        self.double_column_radio = QRadioButton("双栏文本")
        layout_layout.addWidget(self.double_column_radio)
        self.triple_column_radio = QRadioButton("三栏文本")
        layout_layout.addWidget(self.triple_column_radio)
        layout_group.setLayout(layout_layout)
        layout.addWidget(layout_group)
        # 导出格式选项
        format_group = QGroupBox("导出格式")
        format_layout = QVBoxLayout()
        self.export_image_radio = QRadioButton("图像格式")
        self.export_image_radio.setChecked(True)
        format_layout.addWidget(self.export_image_radio)
        self.export_pdf_radio = QRadioButton("PDF格式")
        format_layout.addWidget(self.export_pdf_radio)
        format_group.setLayout(format_layout)
        layout.addWidget(format_group)
        # 图像格式选项（仅当导出为图像时显示）
        self.image_format_group = QGroupBox("图像格式")
        image_format_layout = QFormLayout()
        self.format_combo = QComboBox()
        self.format_combo.addItems(["JPEG", "PNG"])
        image_format_layout.addRow("图像格式:", self.format_combo)
        # 图像质量选项
        self.quality_slider = QSlider(Qt.Horizontal)
        self.quality_slider.setMinimum(1)
        self.quality_slider.setMaximum(100)
        self.quality_slider.setValue(90)
        self.quality_label = QLabel("90%")
        quality_layout = QHBoxLayout()
        quality_layout.addWidget(self.quality_slider)
        quality_layout.addWidget(self.quality_label)
        self.quality_slider.valueChanged.connect(lambda v: self.quality_label.setText(f"{v}%"))
        image_format_layout.addRow("图像质量:", quality_layout)
        # 尺寸限制选项
        self.limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.limit_size_checkbox.setChecked(False)
        image_format_layout.addRow(self.limit_size_checkbox)
        size_layout = QHBoxLayout()
        self.max_width_spin = QSpinBox()
        self.max_width_spin.setRange(100, 5000)
        self.max_width_spin.setValue(1920)
        self.max_width_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大宽度:"))
        size_layout.addWidget(self.max_width_spin)
        size_layout.addWidget(QLabel("像素"))
        self.max_height_spin = QSpinBox()
        self.max_height_spin.setRange(100, 5000)
        self.max_height_spin.setValue(1080)
        self.max_height_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大高度:"))
        size_layout.addWidget(self.max_height_spin)
        size_layout.addWidget(QLabel("像素"))
        image_format_layout.addRow(size_layout)
        self.limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.max_width_spin.setEnabled(checked),
                self.max_height_spin.setEnabled(checked)
            )
        )
        self.image_format_group.setLayout(image_format_layout)
        layout.addWidget(self.image_format_group)
        # PDF压缩选项（仅当导出为PDF时显示）
        self.pdf_format_group = QGroupBox("PDF压缩选项")
        pdf_format_layout = QFormLayout()
        # PDF图像质量选项
        self.pdf_quality_slider = QSlider(Qt.Horizontal)
        self.pdf_quality_slider.setMinimum(1)
        self.pdf_quality_slider.setMaximum(100)
        self.pdf_quality_slider.setValue(75)
        self.pdf_quality_label = QLabel("75%")
        pdf_quality_layout = QHBoxLayout()
        pdf_quality_layout.addWidget(self.pdf_quality_slider)
        pdf_quality_layout.addWidget(self.pdf_quality_label)
        self.pdf_quality_slider.valueChanged.connect(lambda v: self.pdf_quality_label.setText(f"{v}%"))
        pdf_format_layout.addRow("图像质量:", pdf_quality_layout)
        # PDF尺寸限制选项
        self.pdf_limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.pdf_limit_size_checkbox.setChecked(False)
        pdf_format_layout.addRow(self.pdf_limit_size_checkbox)
        pdf_size_layout = QHBoxLayout()
        self.pdf_max_width_spin = QSpinBox()
        self.pdf_max_width_spin.setRange(100, 5000)
        self.pdf_max_width_spin.setValue(1920)
        self.pdf_max_width_spin.setEnabled(False)
        pdf_size_layout.addWidget(QLabel("最大宽度:"))
        pdf_size_layout.addWidget(self.pdf_max_width_spin)
        pdf_size_layout.addWidget(QLabel("像素"))
        self.pdf_max_height_spin = QSpinBox()
        self.pdf_max_height_spin.setRange(100, 5000)
        self.pdf_max_height_spin.setValue(1080)
        self.pdf_max_height_spin.setEnabled(False)
        pdf_size_layout.addWidget(QLabel("最大高度:"))
        pdf_size_layout.addWidget(self.pdf_max_height_spin)
        pdf_size_layout.addWidget(QLabel("像素"))
        pdf_format_layout.addRow(pdf_size_layout)
        self.pdf_limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.pdf_max_width_spin.setEnabled(checked),
                self.pdf_max_height_spin.setEnabled(checked)
            )
        )
        self.pdf_format_group.setLayout(pdf_format_layout)
        layout.addWidget(self.pdf_format_group)
        # 连接信号，根据导出格式显示/隐藏相应选项
        self.export_image_radio.toggled.connect(self.image_format_group.setVisible)
        self.export_pdf_radio.toggled.connect(self.pdf_format_group.setVisible)
        self.export_image_radio.toggled.connect(lambda checked: self.pdf_format_group.setHidden(checked))
        self.export_pdf_radio.toggled.connect(lambda checked: self.image_format_group.setHidden(checked))
        # 输出目录选择
        dir_group = QGroupBox("输出目录")
        dir_layout = QVBoxLayout()
        path_layout = QHBoxLayout()
        self.output_dir_edit = QLineEdit()
        path_layout.addWidget(self.output_dir_edit)
        self.browse_dir_btn = QPushButton("浏览...")
        self.browse_dir_btn.clicked.connect(self.browse_output_dir)
        path_layout.addWidget(self.browse_dir_btn)
        dir_layout.addLayout(path_layout)
        dir_group.setLayout(dir_layout)
        layout.addWidget(dir_group)
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)
        # 初始化变量
        self.output_dir = ""
        # 初始状态设置
        self.image_format_group.setVisible(True)
        self.pdf_format_group.setVisible(False)

    def select_all(self):
        """全选"""
        for checkbox in self.type_checkboxes:
            checkbox.setChecked(True)

    def deselect_all(self):
        """取消全选"""
        for checkbox in self.type_checkboxes:
            checkbox.setChecked(False)

    def browse_output_dir(self):
        """选择输出目录"""
        dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录")
        if dir_path:
            self.output_dir = dir_path
            self.output_dir_edit.setText(dir_path)

    def get_export_options(self):
        """获取导出选项"""
        # 获取选中的区块类型
        selected_types = []
        for checkbox in self.type_checkboxes:
            if checkbox.isChecked():
                selected_types.append(checkbox.text())
        # 获取页面选择
        if self.all_pages_radio.isChecked():
            page_selection = "all"
        elif self.current_page_radio.isChecked():
            page_selection = "current"
        else:
            page_selection = "select"  # 实际页面选择将在主窗口中处理
        # 获取文本布局
        if self.single_column_radio.isChecked():
            layout_type = "single"
        elif self.double_column_radio.isChecked():
            layout_type = "double"
        else:
            layout_type = "triple"
        # 获取导出格式
        export_format = "image" if self.export_image_radio.isChecked() else "pdf"
        # 获取图像格式和质量（仅当导出为图像时）
        image_format = None
        image_quality = None
        limit_size = False
        max_width = None
        max_height = None
        if export_format == "image":
            image_format = self.format_combo.currentText().lower()
            image_quality = self.quality_slider.value()
            limit_size = self.limit_size_checkbox.isChecked()
            max_width = self.max_width_spin.value() if limit_size else None
            max_height = self.max_height_spin.value() if limit_size else None
        else:  # PDF
            image_quality = self.pdf_quality_slider.value()
            limit_size = self.pdf_limit_size_checkbox.isChecked()
            max_width = self.pdf_max_width_spin.value() if limit_size else None
            max_height = self.pdf_max_height_spin.value() if limit_size else None
        # 获取输出目录
        output_dir = self.output_dir_edit.text()
        return {
            "selected_types": selected_types,
            "page_selection": page_selection,
            "layout_type": layout_type,
            "export_format": export_format,
            "image_format": image_format,
            "image_quality": image_quality,
            "limit_size": limit_size,
            "max_width": max_width,
            "max_height": max_height,
            "output_dir": output_dir
        }


class PDFExportOptionsDialog(QDialog):
    """PDF导出选项对话框"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("导出标注PDF选项")
        self.setMinimumWidth(400)
        layout = QVBoxLayout()
        # 页面选择
        page_group = QGroupBox("页面选择")
        page_layout = QVBoxLayout()
        self.all_pages_radio = QRadioButton("所有页面")
        self.all_pages_radio.setChecked(True)
        page_layout.addWidget(self.all_pages_radio)
        self.current_page_radio = QRadioButton("当前页面")
        page_layout.addWidget(self.current_page_radio)
        page_group.setLayout(page_layout)
        layout.addWidget(page_group)
        # 图像压缩选项
        compression_group = QGroupBox("图像压缩选项")
        compression_layout = QFormLayout()
        # 图像质量选项
        self.quality_slider = QSlider(Qt.Horizontal)
        self.quality_slider.setMinimum(1)
        self.quality_slider.setMaximum(100)
        self.quality_slider.setValue(75)
        self.quality_label = QLabel("75%")
        quality_layout = QHBoxLayout()
        quality_layout.addWidget(self.quality_slider)
        quality_layout.addWidget(self.quality_label)
        self.quality_slider.valueChanged.connect(lambda v: self.quality_label.setText(f"{v}%"))
        compression_layout.addRow("图像质量:", quality_layout)
        # 尺寸限制选项
        self.limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.limit_size_checkbox.setChecked(False)
        compression_layout.addRow(self.limit_size_checkbox)
        size_layout = QHBoxLayout()
        self.max_width_spin = QSpinBox()
        self.max_width_spin.setRange(100, 5000)
        self.max_width_spin.setValue(1920)
        self.max_width_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大宽度:"))
        size_layout.addWidget(self.max_width_spin)
        size_layout.addWidget(QLabel("像素"))
        self.max_height_spin = QSpinBox()
        self.max_height_spin.setRange(100, 5000)
        self.max_height_spin.setValue(1080)
        self.max_height_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大高度:"))
        size_layout.addWidget(self.max_height_spin)
        size_layout.addWidget(QLabel("像素"))
        compression_layout.addRow(size_layout)
        self.limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.max_width_spin.setEnabled(checked),
                self.max_height_spin.setEnabled(checked)
            )
        )
        compression_group.setLayout(compression_layout)
        layout.addWidget(compression_group)
        # 输出文件选择
        file_group = QGroupBox("输出文件")
        file_layout = QVBoxLayout()
        path_layout = QHBoxLayout()
        self.output_file_edit = QLineEdit()
        path_layout.addWidget(self.output_file_edit)
        self.browse_file_btn = QPushButton("浏览...")
        self.browse_file_btn.clicked.connect(self.browse_output_file)
        path_layout.addWidget(self.browse_file_btn)
        file_layout.addLayout(path_layout)
        file_group.setLayout(file_layout)
        layout.addWidget(file_group)
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)
        # 初始化变量
        self.output_file = ""

    def browse_output_file(self):
        """选择输出文件"""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "选择输出文件", "", "PDF文件 (*.pdf)"
        )
        if file_path:
            self.output_file = file_path
            self.output_file_edit.setText(file_path)

    def get_export_options(self):
        """获取导出选项"""
        # 获取页面选择
        if self.all_pages_radio.isChecked():
            page_selection = "all"
        elif self.current_page_radio.isChecked():
            page_selection = "current"
        else:
            page_selection = "select"  # 实际页面选择将在主窗口中处理
        # 获取图像质量
        image_quality = self.quality_slider.value()
        # 获取尺寸限制
        limit_size = self.limit_size_checkbox.isChecked()
        max_width = self.max_width_spin.value() if limit_size else None
        max_height = self.max_height_spin.value() if limit_size else None
        # 获取输出文件
        output_file = self.output_file_edit.text()
        return {
            "page_selection": page_selection,
            "image_quality": image_quality,
            "limit_size": limit_size,
            "max_width": max_width,
            "max_height": max_height,
            "output_file": output_file
        }


class ColumnCropDialog(QDialog):
    """分栏裁切选项对话框"""

    def __init__(self, block_types, parent=None):
        super().__init__(parent)
        self.setWindowTitle("分栏裁切选项")
        self.setMinimumWidth(500)
        layout = QVBoxLayout()
        # 区块类型选择（用于马赛克处理）
        mosaic_group = QGroupBox("选择需要马赛克处理的区块类型")
        mosaic_layout = QVBoxLayout()
        # 全选/取消全选
        select_layout = QHBoxLayout()
        self.select_all_btn = QPushButton("全选")
        self.select_all_btn.clicked.connect(self.select_all)
        select_layout.addWidget(self.select_all_btn)
        self.deselect_all_btn = QPushButton("取消全选")
        self.deselect_all_btn.clicked.connect(self.deselect_all)
        select_layout.addWidget(self.deselect_all_btn)
        select_layout.addStretch()
        mosaic_layout.addLayout(select_layout)
        # 区块类型复选框
        self.type_checkboxes = []
        for block_type in block_types:
            checkbox = QCheckBox(block_type)
            checkbox.setChecked(False)  # 默认不选
            mosaic_layout.addWidget(checkbox)
            self.type_checkboxes.append(checkbox)
        mosaic_group.setLayout(mosaic_layout)
        layout.addWidget(mosaic_group)
        # 马赛克颜色选择
        color_group = QGroupBox("马赛克颜色")
        color_layout = QVBoxLayout()
        self.white_radio = QRadioButton("纯白色")
        self.white_radio.setChecked(True)
        color_layout.addWidget(self.white_radio)
        self.gray_radio = QRadioButton("纯灰色")
        color_layout.addWidget(self.gray_radio)
        self.custom_radio = QRadioButton("自定义颜色")
        color_layout.addWidget(self.custom_radio)
        # 自定义颜色选择
        custom_color_layout = QHBoxLayout()
        self.color_label = QLabel("颜色:")
        custom_color_layout.addWidget(self.color_label)
        self.color_button = QPushButton()
        self.color_button.setStyleSheet("background-color: #000000;")
        self.color_button.setFixedSize(30, 20)
        self.color_button.clicked.connect(self.choose_color)
        custom_color_layout.addWidget(self.color_button)
        custom_color_layout.addStretch()
        color_layout.addLayout(custom_color_layout)
        color_group.setLayout(color_layout)
        layout.addWidget(color_group)
        # 页面选择
        page_group = QGroupBox("页面选择")
        page_layout = QVBoxLayout()
        self.all_pages_radio = QRadioButton("所有页面")
        self.all_pages_radio.setChecked(True)
        page_layout.addWidget(self.all_pages_radio)
        self.current_page_radio = QRadioButton("当前页面")
        page_layout.addWidget(self.current_page_radio)
        page_group.setLayout(page_layout)
        layout.addWidget(page_group)
        # 分栏选项
        column_group = QGroupBox("分栏设置")
        column_layout = QVBoxLayout()
        self.double_column_radio = QRadioButton("双栏")
        self.double_column_radio.setChecked(True)
        column_layout.addWidget(self.double_column_radio)
        self.triple_column_radio = QRadioButton("三栏")
        column_layout.addWidget(self.triple_column_radio)
        # 添加智能分栏选项
        smart_layout = QHBoxLayout()
        self.smart_column_checkbox = QCheckBox("启用智能分栏（基于文本区块位置）")
        self.smart_column_checkbox.setChecked(True)
        smart_layout.addWidget(self.smart_column_checkbox)
        smart_layout.addStretch()
        column_layout.addLayout(smart_layout)
        column_group.setLayout(column_layout)
        layout.addWidget(column_group)
        # 导出选项
        export_group = QGroupBox("导出选项")
        export_layout = QVBoxLayout()
        self.export_image_radio = QRadioButton("导出为图像")
        self.export_image_radio.setChecked(True)
        export_layout.addWidget(self.export_image_radio)
        self.export_pdf_radio = QRadioButton("导出为PDF")
        export_layout.addWidget(self.export_pdf_radio)
        export_group.setLayout(export_layout)
        layout.addWidget(export_group)
        # 图像格式选项（仅当导出为图像时显示）
        self.format_group = QGroupBox("图像格式")
        format_layout = QFormLayout()
        self.format_combo = QComboBox()
        self.format_combo.addItems(["JPEG", "PNG"])
        format_layout.addRow("图像格式:", self.format_combo)
        # 图像质量选项
        self.quality_slider = QSlider(Qt.Horizontal)
        self.quality_slider.setMinimum(1)
        self.quality_slider.setMaximum(100)
        self.quality_slider.setValue(90)
        self.quality_label = QLabel("90%")
        quality_layout = QHBoxLayout()
        quality_layout.addWidget(self.quality_slider)
        quality_layout.addWidget(self.quality_label)
        self.quality_slider.valueChanged.connect(lambda v: self.quality_label.setText(f"{v}%"))
        format_layout.addRow("图像质量:", quality_layout)
        # 尺寸限制选项
        self.limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.limit_size_checkbox.setChecked(False)
        format_layout.addRow(self.limit_size_checkbox)
        size_layout = QHBoxLayout()
        self.max_width_spin = QSpinBox()
        self.max_width_spin.setRange(100, 5000)
        self.max_width_spin.setValue(1920)
        self.max_width_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大宽度:"))
        size_layout.addWidget(self.max_width_spin)
        size_layout.addWidget(QLabel("像素"))
        self.max_height_spin = QSpinBox()
        self.max_height_spin.setRange(100, 5000)
        self.max_height_spin.setValue(1080)
        self.max_height_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大高度:"))
        size_layout.addWidget(self.max_height_spin)
        size_layout.addWidget(QLabel("像素"))
        format_layout.addRow(size_layout)
        self.limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.max_width_spin.setEnabled(checked),
                self.max_height_spin.setEnabled(checked)
            )
        )
        self.format_group.setLayout(format_layout)
        layout.addWidget(self.format_group)
        # PDF压缩选项（仅当导出为PDF时显示）
        self.pdf_format_group = QGroupBox("PDF压缩选项")
        pdf_format_layout = QFormLayout()
        # PDF图像质量选项
        self.pdf_quality_slider = QSlider(Qt.Horizontal)
        self.pdf_quality_slider.setMinimum(1)
        self.pdf_quality_slider.setMaximum(100)
        self.pdf_quality_slider.setValue(75)
        self.pdf_quality_label = QLabel("75%")
        pdf_quality_layout = QHBoxLayout()
        pdf_quality_layout.addWidget(self.pdf_quality_slider)
        pdf_quality_layout.addWidget(self.pdf_quality_label)
        self.pdf_quality_slider.valueChanged.connect(lambda v: self.pdf_quality_label.setText(f"{v}%"))
        pdf_format_layout.addRow("图像质量:", pdf_quality_layout)
        # PDF尺寸限制选项
        self.pdf_limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.pdf_limit_size_checkbox.setChecked(False)
        pdf_format_layout.addRow(self.pdf_limit_size_checkbox)
        pdf_size_layout = QHBoxLayout()
        self.pdf_max_width_spin = QSpinBox()
        self.pdf_max_width_spin.setRange(100, 5000)
        self.pdf_max_width_spin.setValue(1920)
        self.pdf_max_width_spin.setEnabled(False)
        pdf_size_layout.addWidget(QLabel("最大宽度:"))
        pdf_size_layout.addWidget(self.pdf_max_width_spin)
        pdf_size_layout.addWidget(QLabel("像素"))
        self.pdf_max_height_spin = QSpinBox()
        self.pdf_max_height_spin.setRange(100, 5000)
        self.pdf_max_height_spin.setValue(1080)
        self.pdf_max_height_spin.setEnabled(False)
        pdf_size_layout.addWidget(QLabel("最大高度:"))
        pdf_size_layout.addWidget(self.pdf_max_height_spin)
        pdf_size_layout.addWidget(QLabel("像素"))
        pdf_format_layout.addRow(pdf_size_layout)
        self.pdf_limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.pdf_max_width_spin.setEnabled(checked),
                self.pdf_max_height_spin.setEnabled(checked)
            )
        )
        self.pdf_format_group.setLayout(pdf_format_layout)
        layout.addWidget(self.pdf_format_group)
        # 连接信号，根据导出格式显示/隐藏相应选项
        self.export_image_radio.toggled.connect(self.format_group.setVisible)
        self.export_pdf_radio.toggled.connect(self.pdf_format_group.setVisible)
        self.export_image_radio.toggled.connect(lambda checked: self.pdf_format_group.setHidden(checked))
        self.export_pdf_radio.toggled.connect(lambda checked: self.format_group.setHidden(checked))
        # 输出目录选择
        dir_group = QGroupBox("输出目录")
        dir_layout = QVBoxLayout()
        path_layout = QHBoxLayout()
        self.output_dir_edit = QLineEdit()
        path_layout.addWidget(self.output_dir_edit)
        self.browse_dir_btn = QPushButton("浏览...")
        self.browse_dir_btn.clicked.connect(self.browse_output_dir)
        path_layout.addWidget(self.browse_dir_btn)
        dir_layout.addLayout(path_layout)
        dir_group.setLayout(dir_layout)
        layout.addWidget(dir_group)
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)
        # 初始化变量
        self.output_dir = ""
        self.custom_color = QColor(0, 0, 0)  # 默认黑色
        # 初始状态设置
        self.format_group.setVisible(True)
        self.pdf_format_group.setVisible(False)

    def select_all(self):
        """全选"""
        for checkbox in self.type_checkboxes:
            checkbox.setChecked(True)

    def deselect_all(self):
        """取消全选"""
        for checkbox in self.type_checkboxes:
            checkbox.setChecked(False)

    def choose_color(self):
        """选择自定义颜色"""
        color = QColorDialog.getColor(self.custom_color, self)
        if color.isValid():
            self.custom_color = color
            self.color_button.setStyleSheet(f"background-color: {color.name()};")

    def browse_output_dir(self):
        """选择输出目录"""
        dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录")
        if dir_path:
            self.output_dir = dir_path
            self.output_dir_edit.setText(dir_path)

    def get_crop_options(self):
        """获取分栏裁切选项"""
        # 获取选中的区块类型（用于马赛克）
        mosaic_types = []
        for checkbox in self.type_checkboxes:
            if checkbox.isChecked():
                mosaic_types.append(checkbox.text())
        # 获取马赛克颜色
        if self.white_radio.isChecked():
            mosaic_color = "white"
        elif self.gray_radio.isChecked():
            mosaic_color = "gray"
        else:
            mosaic_color = self.custom_color
        # 获取页面选择
        if self.all_pages_radio.isChecked():
            page_selection = "all"
        elif self.current_page_radio.isChecked():
            page_selection = "current"
        else:
            page_selection = "all"  # 默认所有页面
        # 获取分栏数
        column_count = 2 if self.double_column_radio.isChecked() else 3
        # 获取智能分栏选项
        smart_column = self.smart_column_checkbox.isChecked()
        # 获取导出格式
        export_format = "image" if self.export_image_radio.isChecked() else "pdf"
        # 获取图像格式和质量（仅当导出为图像时）
        image_format = None
        image_quality = None
        limit_size = False
        max_width = None
        max_height = None
        if export_format == "image":
            image_format = self.format_combo.currentText().lower()
            image_quality = self.quality_slider.value()
            limit_size = self.limit_size_checkbox.isChecked()
            max_width = self.max_width_spin.value() if limit_size else None
            max_height = self.max_height_spin.value() if limit_size else None
        else:  # PDF
            image_quality = self.pdf_quality_slider.value()
            limit_size = self.pdf_limit_size_checkbox.isChecked()
            max_width = self.pdf_max_width_spin.value() if limit_size else None
            max_height = self.pdf_max_height_spin.value() if limit_size else None
        # 获取输出目录
        output_dir = self.output_dir_edit.text()
        return {
            "mosaic_types": mosaic_types,
            "mosaic_color": mosaic_color,
            "page_selection": page_selection,
            "column_count": column_count,
            "smart_column": smart_column,
            "export_format": export_format,
            "image_format": image_format,
            "image_quality": image_quality,
            "limit_size": limit_size,
            "max_width": max_width,
            "max_height": max_height,
            "output_dir": output_dir
        }


class ImageMaskDialog(QDialog):
    """图像掩码选项对话框"""

    def __init__(self, block_types, parent=None):
        super().__init__(parent)
        self.setWindowTitle("图像掩码选项")
        self.setMinimumWidth(500)
        layout = QVBoxLayout()
        # 区块类型选择（用于马赛克处理）
        mosaic_group = QGroupBox("选择需要马赛克处理的区块类型")
        mosaic_layout = QVBoxLayout()
        # 全选/取消全选
        select_layout = QHBoxLayout()
        self.select_all_btn = QPushButton("全选")
        self.select_all_btn.clicked.connect(self.select_all)
        select_layout.addWidget(self.select_all_btn)
        self.deselect_all_btn = QPushButton("取消全选")
        self.deselect_all_btn.clicked.connect(self.deselect_all)
        select_layout.addWidget(self.deselect_all_btn)
        select_layout.addStretch()
        mosaic_layout.addLayout(select_layout)
        # 区块类型复选框
        self.type_checkboxes = []
        for block_type in block_types:
            checkbox = QCheckBox(block_type)
            checkbox.setChecked(False)  # 默认不选
            mosaic_layout.addWidget(checkbox)
            self.type_checkboxes.append(checkbox)
        mosaic_group.setLayout(mosaic_layout)
        layout.addWidget(mosaic_group)
        # 马赛克颜色选择
        color_group = QGroupBox("马赛克颜色")
        color_layout = QVBoxLayout()
        self.white_radio = QRadioButton("纯白色")
        self.white_radio.setChecked(True)
        color_layout.addWidget(self.white_radio)
        self.gray_radio = QRadioButton("纯灰色")
        color_layout.addWidget(self.gray_radio)
        self.custom_radio = QRadioButton("自定义颜色")
        color_layout.addWidget(self.custom_radio)
        # 自定义颜色选择
        custom_color_layout = QHBoxLayout()
        self.color_label = QLabel("颜色:")
        custom_color_layout.addWidget(self.color_label)
        self.color_button = QPushButton()
        self.color_button.setStyleSheet("background-color: #000000;")
        self.color_button.setFixedSize(30, 20)
        self.color_button.clicked.connect(self.choose_color)
        custom_color_layout.addWidget(self.color_button)
        custom_color_layout.addStretch()
        color_layout.addLayout(custom_color_layout)
        color_group.setLayout(color_layout)
        layout.addWidget(color_group)
        # 页面选择
        page_group = QGroupBox("页面选择")
        page_layout = QVBoxLayout()
        self.all_pages_radio = QRadioButton("所有页面")
        self.all_pages_radio.setChecked(True)
        page_layout.addWidget(self.all_pages_radio)
        self.current_page_radio = QRadioButton("当前页面")
        page_layout.addWidget(self.current_page_radio)
        page_group.setLayout(page_layout)
        layout.addWidget(page_group)
        # 导出选项
        export_group = QGroupBox("导出选项")
        export_layout = QVBoxLayout()
        self.export_image_radio = QRadioButton("导出为图像")
        self.export_image_radio.setChecked(True)
        export_layout.addWidget(self.export_image_radio)
        self.export_pdf_radio = QRadioButton("导出为PDF")
        export_layout.addWidget(self.export_pdf_radio)
        export_group.setLayout(export_layout)
        layout.addWidget(export_group)
        # 图像格式选项（仅当导出为图像时显示）
        self.format_group = QGroupBox("图像格式")
        format_layout = QFormLayout()
        self.format_combo = QComboBox()
        self.format_combo.addItems(["JPEG", "PNG"])
        format_layout.addRow("图像格式:", self.format_combo)
        # 图像质量选项
        self.quality_slider = QSlider(Qt.Horizontal)
        self.quality_slider.setMinimum(1)
        self.quality_slider.setMaximum(100)
        self.quality_slider.setValue(90)
        self.quality_label = QLabel("90%")
        quality_layout = QHBoxLayout()
        quality_layout.addWidget(self.quality_slider)
        quality_layout.addWidget(self.quality_label)
        self.quality_slider.valueChanged.connect(lambda v: self.quality_label.setText(f"{v}%"))
        format_layout.addRow("图像质量:", quality_layout)
        # 尺寸限制选项
        self.limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.limit_size_checkbox.setChecked(False)
        format_layout.addRow(self.limit_size_checkbox)
        size_layout = QHBoxLayout()
        self.max_width_spin = QSpinBox()
        self.max_width_spin.setRange(100, 5000)
        self.max_width_spin.setValue(1920)
        self.max_width_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大宽度:"))
        size_layout.addWidget(self.max_width_spin)
        size_layout.addWidget(QLabel("像素"))
        self.max_height_spin = QSpinBox()
        self.max_height_spin.setRange(100, 5000)
        self.max_height_spin.setValue(1080)
        self.max_height_spin.setEnabled(False)
        size_layout.addWidget(QLabel("最大高度:"))
        size_layout.addWidget(self.max_height_spin)
        size_layout.addWidget(QLabel("像素"))
        format_layout.addRow(size_layout)
        self.limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.max_width_spin.setEnabled(checked),
                self.max_height_spin.setEnabled(checked)
            )
        )
        self.format_group.setLayout(format_layout)
        layout.addWidget(self.format_group)
        # PDF压缩选项（仅当导出为PDF时显示）
        self.pdf_format_group = QGroupBox("PDF压缩选项")
        pdf_format_layout = QFormLayout()
        # PDF图像质量选项
        self.pdf_quality_slider = QSlider(Qt.Horizontal)
        self.pdf_quality_slider.setMinimum(1)
        self.pdf_quality_slider.setMaximum(100)
        self.pdf_quality_slider.setValue(75)
        self.pdf_quality_label = QLabel("75%")
        pdf_quality_layout = QHBoxLayout()
        pdf_quality_layout.addWidget(self.pdf_quality_slider)
        pdf_quality_layout.addWidget(self.pdf_quality_label)
        self.pdf_quality_slider.valueChanged.connect(lambda v: self.pdf_quality_label.setText(f"{v}%"))
        pdf_format_layout.addRow("图像质量:", pdf_quality_layout)
        # PDF尺寸限制选项
        self.pdf_limit_size_checkbox = QCheckBox("限制最大尺寸")
        self.pdf_limit_size_checkbox.setChecked(False)
        pdf_format_layout.addRow(self.pdf_limit_size_checkbox)
        pdf_size_layout = QHBoxLayout()
        self.pdf_max_width_spin = QSpinBox()
        self.pdf_max_width_spin.setRange(100, 5000)
        self.pdf_max_width_spin.setValue(1920)
        self.pdf_max_width_spin.setEnabled(False)
        pdf_size_layout.addWidget(QLabel("最大宽度:"))
        pdf_size_layout.addWidget(self.pdf_max_width_spin)
        pdf_size_layout.addWidget(QLabel("像素"))
        self.pdf_max_height_spin = QSpinBox()
        self.pdf_max_height_spin.setRange(100, 5000)
        self.pdf_max_height_spin.setValue(1080)
        self.pdf_max_height_spin.setEnabled(False)
        pdf_size_layout.addWidget(QLabel("最大高度:"))
        pdf_size_layout.addWidget(self.pdf_max_height_spin)
        pdf_size_layout.addWidget(QLabel("像素"))
        pdf_format_layout.addRow(pdf_size_layout)
        self.pdf_limit_size_checkbox.toggled.connect(
            lambda checked: (
                self.pdf_max_width_spin.setEnabled(checked),
                self.pdf_max_height_spin.setEnabled(checked)
            )
        )
        self.pdf_format_group.setLayout(pdf_format_layout)
        layout.addWidget(self.pdf_format_group)
        # 连接信号，根据导出格式显示/隐藏相应选项
        self.export_image_radio.toggled.connect(self.format_group.setVisible)
        self.export_pdf_radio.toggled.connect(self.pdf_format_group.setVisible)
        self.export_image_radio.toggled.connect(lambda checked: self.pdf_format_group.setHidden(checked))
        self.export_pdf_radio.toggled.connect(lambda checked: self.format_group.setHidden(checked))
        # 输出目录选择
        dir_group = QGroupBox("输出目录")
        dir_layout = QVBoxLayout()
        path_layout = QHBoxLayout()
        self.output_dir_edit = QLineEdit()
        path_layout.addWidget(self.output_dir_edit)
        self.browse_dir_btn = QPushButton("浏览...")
        self.browse_dir_btn.clicked.connect(self.browse_output_dir)
        path_layout.addWidget(self.browse_dir_btn)
        dir_layout.addLayout(path_layout)
        dir_group.setLayout(dir_layout)
        layout.addWidget(dir_group)
        # 按钮
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)
        # 初始化变量
        self.output_dir = ""
        self.custom_color = QColor(0, 0, 0)  # 默认黑色
        # 初始状态设置
        self.format_group.setVisible(True)
        self.pdf_format_group.setVisible(False)

    def select_all(self):
        """全选"""
        for checkbox in self.type_checkboxes:
            checkbox.setChecked(True)

    def deselect_all(self):
        """取消全选"""
        for checkbox in self.type_checkboxes:
            checkbox.setChecked(False)

    def choose_color(self):
        """选择自定义颜色"""
        color = QColorDialog.getColor(self.custom_color, self)
        if color.isValid():
            self.custom_color = color
            self.color_button.setStyleSheet(f"background-color: {color.name()};")

    def browse_output_dir(self):
        """选择输出目录"""
        dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录")
        if dir_path:
            self.output_dir = dir_path
            self.output_dir_edit.setText(dir_path)

    def get_mask_options(self):
        """获取图像掩码选项"""
        # 获取选中的区块类型（用于马赛克）
        mosaic_types = []
        for checkbox in self.type_checkboxes:
            if checkbox.isChecked():
                mosaic_types.append(checkbox.text())
        # 获取马赛克颜色
        if self.white_radio.isChecked():
            mosaic_color = "white"
        elif self.gray_radio.isChecked():
            mosaic_color = "gray"
        else:
            mosaic_color = self.custom_color
        # 获取页面选择
        if self.all_pages_radio.isChecked():
            page_selection = "all"
        elif self.current_page_radio.isChecked():
            page_selection = "current"
        else:
            page_selection = "select"  # 实际页面选择将在主窗口中处理
        # 获取导出格式
        export_format = "image" if self.export_image_radio.isChecked() else "pdf"
        # 获取图像格式和质量（仅当导出为图像时）
        image_format = None
        image_quality = None
        limit_size = False
        max_width = None
        max_height = None
        if export_format == "image":
            image_format = self.format_combo.currentText().lower()
            image_quality = self.quality_slider.value()
            limit_size = self.limit_size_checkbox.isChecked()
            max_width = self.max_width_spin.value() if limit_size else None
            max_height = self.max_height_spin.value() if limit_size else None
        else:  # PDF
            image_quality = self.pdf_quality_slider.value()
            limit_size = self.pdf_limit_size_checkbox.isChecked()
            max_width = self.pdf_max_width_spin.value() if limit_size else None
            max_height = self.pdf_max_height_spin.value() if limit_size else None
        # 获取输出目录
        output_dir = self.output_dir_edit.text()
        return {
            "mosaic_types": mosaic_types,
            "mosaic_color": mosaic_color,
            "page_selection": page_selection,
            "export_format": export_format,
            "image_format": image_format,
            "image_quality": image_quality,
            "limit_size": limit_size,
            "max_width": max_width,
            "max_height": max_height,
            "output_dir": output_dir
        }


class LayoutAnalyzerThread(QThread):
    """后台线程处理版面分析任务"""
    progress_updated = pyqtSignal(int, str)  # 进度百分比和描述信息
    analysis_complete = pyqtSignal(dict, list)
    error_occurred = pyqtSignal(str)

    def __init__(self, pdf_path, model_name, model_dir=None, img_size=None, threshold=None,
                 layout_unclip_ratio=None, layout_merge_bboxes_mode=None, device=None,
                 enable_hpi=False, use_tensorrt=False, precision="fp32", enable_mkldnn=True,
                 mkldnn_cache_capacity=10, cpu_threads=6, layout_nms=True, page_range=None):
        super().__init__()
        self.pdf_path = pdf_path
        self.model_name = model_name
        self.model_dir = model_dir
        self.img_size = img_size
        self.threshold = threshold
        self.layout_unclip_ratio = layout_unclip_ratio
        self.layout_merge_bboxes_mode = layout_merge_bboxes_mode
        # 新增参数（仅在基础代码中体现，不在UI中显示）
        self.device = device
        self.enable_hpi = enable_hpi
        self.use_tensorrt = use_tensorrt
        self.precision = precision
        self.enable_mkldnn = enable_mkldnn
        self.mkldnn_cache_capacity = mkldnn_cache_capacity
        self.cpu_threads = cpu_threads
        self.layout_nms = layout_nms
        self.page_range = page_range  # 新增页面范围参数
        self._is_running = True
        self.start_time = 0  # 记录开始时间
        self.last_progress_time = 0  # 记录上次进度更新时间

    def convert_pdf_to_images(self, pdf_path):
        """使用PyMuPDF将PDF转换为图像列表"""
        try:
            doc = fitz.open(pdf_path)
            images = []
            total_pages = len(doc)
            # 确定要处理的页面范围
            if self.page_range is None:
                pages_to_process = range(total_pages)
            else:
                pages_to_process = self.page_range
            for page_num in pages_to_process:
                if not self._is_running:
                    break
                # 加载页面
                page = doc.load_page(page_num)
                # 获取页面图像，提高分辨率以获得更好的OCR效果
                zoom = 2  # 缩放因子
                mat = fitz.Matrix(zoom, zoom)
                pix = page.get_pixmap(matrix=mat)
                # 转换为numpy数组
                img = np.frombuffer(pix.samples, dtype=np.uint8).reshape((pix.height, pix.width, pix.n))
                # 如果是RGBA格式，转换为RGB
                if pix.n == 4:
                    img = img[:, :, :3]
                # 如果是灰度图，转换为RGB
                elif pix.n == 1:
                    img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
                images.append((page_num, img))  # 保存原始页码和图像
            doc.close()
            return images
        except Exception as e:
            raise Exception(f"PDF转换失败: {str(e)}")

    def run(self):
        try:
            # 记录开始时间
            self.start_time = time.time()
            self.last_progress_time = self.start_time
            # 初始化阶段 - 0% -> 5%
            self.progress_updated.emit(5, "初始化分析环境...")
            # 模型加载阶段 - 5% -> 15%
            self.progress_updated.emit(10, "正在加载模型...")
            try:
                if self.model_dir and os.path.exists(os.path.join(self.model_dir, self.model_name)):
                    model_path = os.path.join(self.model_dir, self.model_name)
                    model = LayoutDetection(
                        model_name=self.model_name,
                        model_dir=model_path,
                        threshold=self.threshold,
                        layout_unclip_ratio=self.layout_unclip_ratio,
                        layout_merge_bboxes_mode=self.layout_merge_bboxes_mode,
                        device=self.device,
                        enable_hpi=self.enable_hpi,
                        use_tensorrt=self.use_tensorrt,
                        precision=self.precision,
                        enable_mkldnn=self.enable_mkldnn,
                        mkldnn_cache_capacity=self.mkldnn_cache_capacity,
                        cpu_threads=self.cpu_threads,
                        layout_nms=self.layout_nms
                    )
                    print(f"使用本地模型: {model_path}")
                else:
                    model = LayoutDetection(
                        model_name=self.model_name,
                        threshold=self.threshold,
                        layout_unclip_ratio=self.layout_unclip_ratio,
                        layout_merge_bboxes_mode=self.layout_merge_bboxes_mode,
                        device=self.device,
                        enable_hpi=self.enable_hpi,
                        use_tensorrt=self.use_tensorrt,
                        precision=self.precision,
                        enable_mkldnn=self.enable_mkldnn,
                        mkldnn_cache_capacity=self.mkldnn_cache_capacity,
                        cpu_threads=self.cpu_threads,
                        layout_nms=self.layout_nms
                    )
                    print(f"使用在线模型: {self.model_name}")
            except Exception as e:
                print(f"本地模型加载失败，尝试使用在线模型: {str(e)}")
                model = LayoutDetection(
                    model_name=self.model_name,
                    threshold=self.threshold,
                    layout_unclip_ratio=self.layout_unclip_ratio,
                    layout_merge_bboxes_mode=self.layout_merge_bboxes_mode,
                    device=self.device,
                    enable_hpi=self.enable_hpi,
                    use_tensorrt=self.use_tensorrt,
                    precision=self.precision,
                    enable_mkldnn=self.enable_mkldnn,
                    mkldnn_cache_capacity=self.mkldnn_cache_capacity,
                    cpu_threads=self.cpu_threads,
                    layout_nms=self.layout_nms
                )
            self.progress_updated.emit(15, "模型加载完成")
            # PDF转换阶段 - 15% -> 30%
            self.progress_updated.emit(20, "开始转换PDF为图像...")
            images = self.convert_pdf_to_images(self.pdf_path)
            total_pages = len(images)
            if total_pages == 0:
                raise Exception("PDF文件为空或无法读取")
            self.progress_updated.emit(30, f"PDF转换完成，共{total_pages}页")
            # 页面处理阶段 - 30% -> 90%
            results = []
            all_detections = []
            # 获取PDF总页数，用于创建结果数组
            doc = fitz.open(self.pdf_path)
            pdf_total_pages = len(doc)
            doc.close()
            # 初始化结果数组，用于保存所有页面的结果
            full_results = [None] * pdf_total_pages
            for i, (page_num, image) in enumerate(images):
                if not self._is_running:
                    break
                # 计算当前进度
                base_progress = 30
                page_progress_range = 60  # 页面处理占总进度的60%
                current_page_progress = base_progress + int(page_progress_range * (i + 1) / total_pages)
                # 计算预估剩余时间
                elapsed_time = time.time() - self.start_time
                if i > 0 and elapsed_time > 0:
                    progress_per_second = (current_page_progress - 30) / elapsed_time
                    remaining_progress = 100 - current_page_progress
                    estimated_seconds = remaining_progress / progress_per_second if progress_per_second > 0 else 0
                    if estimated_seconds > 0:
                        if estimated_seconds < 60:
                            time_str = f"约 {int(estimated_seconds)} 秒"
                        elif estimated_seconds < 3600:
                            time_str = f"约 {int(estimated_seconds / 60)} 分钟"
                        else:
                            time_str = f"约 {int(estimated_seconds / 3600)} 小时"
                    else:
                        time_str = "即将完成"
                else:
                    time_str = "计算中..."
                # 更新进度，显示当前处理的页面和预估剩余时间
                self.progress_updated.emit(
                    current_page_progress,
                    f"正在处理第 {page_num + 1} 页 (共{total_pages}页)... 预计剩余时间: {time_str}"
                )
                # 确保图像是numpy数组格式
                if not isinstance(image, np.ndarray):
                    img_np = np.array(image)
                else:
                    img_np = image
                # 执行版面分析
                output = model.predict(img_np, batch_size=1, layout_nms=self.layout_nms)
                # 处理结果
                page_result = {
                    'page_index': page_num,
                    'image': img_np,
                    'detections': []
                }
                # 正确处理PaddleOCR的返回结果
                if output and len(output) > 0:
                    # 获取第一个结果
                    result = output[0]
                    # 检查结果类型并提取数据
                    if hasattr(result, 'data'):
                        # 如果是Result对象，获取data属性
                        data = result.data
                        if isinstance(data, dict) and 'boxes' in data:
                            boxes = data['boxes']
                        else:
                            boxes = []
                    elif isinstance(result, dict):
                        # 如果是字典，直接获取boxes
                        boxes = result.get('boxes', [])
                    else:
                        # 其他情况，尝试转换为字典
                        try:
                            result_dict = dict(result)
                            boxes = result_dict.get('boxes', [])
                        except:
                            boxes = []
                    # 处理每个检测框
                    for box in boxes:
                        if isinstance(box, dict):
                            detection = {
                                'label': box.get('label', 'unknown'),
                                'confidence': box.get('score', 0.0),
                                'coordinates': box.get('coordinate', [0, 0, 0, 0]),
                                'cls_id': box.get('cls_id', -1)
                            }
                            page_result['detections'].append(detection)
                            all_detections.append(detection)
                # 保存结果到对应位置
                full_results[page_num] = page_result
            # 过滤掉None值，只保存有结果的页面
            results = [r for r in full_results if r is not None]
            # 最终处理阶段 - 90% -> 100%
            self.progress_updated.emit(95, "正在整理分析结果...")
            # ... 最终处理代码 ...
            self.progress_updated.emit(100, "分析完成")
            self.analysis_complete.emit({'pages': results}, all_detections)
        except Exception as e:
            self.error_occurred.emit(f"分析过程中出错: {str(e)}")

    def stop(self):
        self._is_running = False


class ImageExportWorker:
    """图像导出工作器 - 使用多线程处理"""

    def __init__(self, progress_callback):
        self.progress_callback = progress_callback
        self.stop_flag = False

    def process_page(self, page_data, page_idx, export_options):
        """处理单个页面"""
        if self.stop_flag:
            return None
        try:
            # 裁切区块图像
            self.crop_blocks_from_page(page_data, page_idx, export_options)
            return (page_idx, True)  # 返回成功状态
        except Exception as e:
            print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            return (page_idx, False)

    def validate_export_params(self, export_options, page_data):
        """验证导出参数是否适用"""
        # 检查图像尺寸限制
        limit_size = export_options.get("limit_size", False)
        if limit_size:
            max_width = export_options.get("max_width")
            max_height = export_options.get("max_height")
            img_height, img_width = page_data['image'].shape[:2]
            if max_width and img_width > max_width:
                print(f"警告: 图像宽度({img_width})超过限制({max_width})，将进行缩放")
            if max_height and img_height > max_height:
                print(f"警告: 图像高度({img_height})超过限制({max_height})，将进行缩放")
        # 检查图像质量设置
        image_quality = export_options.get("image_quality", 90)
        if image_quality < 30 or image_quality > 100:
            print(f"警告: 图像质量设置({image_quality})可能不在最佳范围内(30-100)")
        return True

    def crop_blocks_from_page(self, page_data, page_idx, export_options):
        """从页面裁切区块图像"""
        # 获取导出选项
        selected_types = export_options.get("selected_types", [])
        layout_type = export_options.get("layout_type", "single")
        export_format = export_options.get("export_format", "image")
        image_format = export_options.get("image_format", "jpeg")
        image_quality = export_options.get("image_quality", 90)
        limit_size = export_options.get("limit_size", False)
        max_width = export_options.get("max_width", None)
        max_height = export_options.get("max_height", None)
        output_dir = export_options.get("output_dir", "")
        if not output_dir or not os.path.exists(output_dir):
            return
        # 验证导出参数
        self.validate_export_params(export_options, page_data)
        # 创建类别目录
        type_dirs = {}
        for detection in page_data['detections']:
            block_type = detection['label']
            if block_type in selected_types and block_type not in type_dirs:
                type_dir = os.path.join(output_dir, block_type)
                os.makedirs(type_dir, exist_ok=True)
                type_dirs[block_type] = type_dir
        # 获取页面图像
        img = page_data['image']
        # 确保图像是RGB格式
        if len(img.shape) == 2:  # 灰度图
            img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
        elif img.shape[2] == 4:  # RGBA
            img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
        elif img.shape[2] == 3:  # BGR (OpenCV默认)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        # 按区块类型分组
        blocks_by_type = {}
        for detection in page_data['detections']:
            block_type = detection['label']
            if block_type in selected_types:
                if block_type not in blocks_by_type:
                    blocks_by_type[block_type] = []
                blocks_by_type[block_type].append(detection)
        # 获取原始页码，优先使用page_index，否则使用page_idx
        original_page_num = page_data.get('page_index', page_idx) + 1
        # 对每种类型的区块进行处理
        for block_type, blocks in blocks_by_type.items():
            # 按位置排序区块：先按Y坐标（从上到下），再按X坐标（从左到右）
            blocks.sort(key=lambda b: (b['coordinates'][1], b['coordinates'][0]))
            # 确定栏位
            img_width = img.shape[1]
            columns = []
            if layout_type == "single":
                # 单栏，只有一个栏位
                columns.append(("single", 0, img_width))
            elif layout_type == "double":
                # 双栏，平均分为左右两栏
                mid_x = img_width // 2
                columns.append(("left", 0, mid_x))
                columns.append(("right", mid_x, img_width))
            elif layout_type == "triple":
                # 三栏，平均分为三栏
                one_third = img_width // 3
                two_thirds = 2 * img_width // 3
                columns.append(("left", 0, one_third))
                columns.append(("middle", one_third, two_thirds))
                columns.append(("right", two_thirds, img_width))
            # 为每栏创建区块计数器
            column_counters = {}
            for name, _, _ in columns:
                column_counters[name] = 1
            # 为每个区块裁切图像并保存
            for block in blocks:
                coords = block['coordinates']
                x1, y1, x2, y2 = map(int, coords)
                # 裁切区块
                block_img = img[y1:y2, x1:x2]
                # 确定栏位
                block_center_x = (x1 + x2) // 2
                column_name = "single"
                for name, col_start, col_end in columns:
                    if col_start <= block_center_x < col_end:
                        column_name = name
                        break
                # 应用尺寸限制（如果需要）
                if limit_size and (max_width or max_height):
                    h, w = block_img.shape[:2]
                    scale = 1.0
                    if max_width and w > max_width:
                        scale = max_width / w
                    if max_height and h > max_height:
                        scale = min(scale, max_height / h)
                    if scale != 1.0:
                        new_width = int(w * scale)
                        new_height = int(h * scale)
                        block_img = cv2.resize(block_img, (new_width, new_height), interpolation=cv2.INTER_AREA)
                # 获取当前栏的序号
                sequence_num = column_counters[column_name]
                column_counters[column_name] += 1
                # 生成文件名：使用原始页码
                if layout_type == "single":
                    filename = f"page_{original_page_num:03d}_single_{sequence_num:02d}.{image_format}"
                else:
                    filename = f"page_{original_page_num:03d}_{column_name}_{sequence_num:02d}.{image_format}"
                filepath = os.path.join(type_dirs[block_type], filename)
                # 根据导出格式保存
                if export_format == "image":
                    # 根据格式保存图像
                    if image_format.lower() == "jpeg" or image_format.lower() == "jpg":
                        cv2.imwrite(filepath, cv2.cvtColor(block_img, cv2.COLOR_RGB2BGR),
                                    [int(cv2.IMWRITE_JPEG_QUALITY), image_quality])
                    elif image_format.lower() == "png":
                        cv2.imwrite(filepath, cv2.cvtColor(block_img, cv2.COLOR_RGB2BGR),
                                    [int(cv2.IMWRITE_PNG_COMPRESSION), 10 - (image_quality // 10)])
                else:  # PDF
                    # 保存为PDF
                    pdf_path = os.path.join(type_dirs[block_type],
                                            f"page_{original_page_num:03d}_{column_name}_{sequence_num:02d}.pdf")
                    # 创建PDF文档
                    doc = fitz.open()
                    page = doc.new_page(width=block_img.shape[1], height=block_img.shape[0])
                    # 将图像转换为字节流
                    img_bytes = cv2.imencode('.jpg', block_img, [int(cv2.IMWRITE_JPEG_QUALITY), image_quality])[
                        1].tobytes()
                    # 插入图像
                    rect = fitz.Rect(0, 0, block_img.shape[1], block_img.shape[0])
                    page.insert_image(rect, stream=img_bytes)
                    # 保存PDF
                    doc.save(pdf_path)
                    doc.close()


class ImageExportThread(QThread):
    """图像导出线程"""
    progress_updated = pyqtSignal(int, str)  # 进度百分比和描述信息
    export_complete = pyqtSignal(str)
    export_failed = pyqtSignal(str)

    def __init__(self, analysis_results, export_options):
        super().__init__()
        self.analysis_results = analysis_results
        self.export_options = export_options
        self._is_running = True
        self.worker = None
        self.start_time = 0  # 记录开始时间

    def run(self):
        try:
            # 记录开始时间
            self.start_time = time.time()
            # 确定要处理的页面
            if self.export_options["page_selection"] == "all":
                pages_to_process = range(len(self.analysis_results))
            elif self.export_options["page_selection"] == "current":
                pages_to_process = [self.export_options.get("current_page", 0)]
            else:
                pages_to_process = self.export_options.get("selected_pages", [])
            total_pages = len(pages_to_process)
            if total_pages == 0:
                self.export_failed.emit("没有选择要处理的页面")
                return
            # 预估每个页面的处理复杂度（基于检测到的区块数量）
            page_complexity = []
            for page_idx in pages_to_process:
                page_data = self.analysis_results[page_idx]
                # 计算页面复杂度（基于区块数量）
                complexity = len(page_data['detections'])
                page_complexity.append((page_idx, complexity))
            # 按复杂度排序，先处理简单的页面
            page_complexity.sort(key=lambda x: x[1])
            total_complexity = sum(complexity for _, complexity in page_complexity)
            processed_complexity = 0
            # 创建工作器
            self.worker = ImageExportWorker(self.progress_updated.emit)
            # 使用线程池处理页面
            with ThreadPoolExecutor(max_workers=4) as executor:
                # 提交所有页面处理任务
                future_to_page = {
                    executor.submit(
                        self.worker.process_page,
                        self.analysis_results[page_idx],
                        page_idx,
                        self.export_options
                    ): (page_idx, complexity) for page_idx, complexity in page_complexity
                }
                # 收集处理结果
                completed = 0
                for future in future_to_page:
                    if not self._is_running:
                        break
                    page_idx, complexity = future_to_page[future]
                    try:
                        result = future.result(timeout=30)  # 设置超时
                        processed_complexity += complexity
                        completed += 1
                        # 基于复杂度计算进度
                        progress = int(processed_complexity / total_complexity * 100)
                        # 计算预估剩余时间
                        elapsed_time = time.time() - self.start_time
                        if completed > 0 and elapsed_time > 0:
                            progress_per_second = progress / elapsed_time
                            remaining_progress = 100 - progress
                            estimated_seconds = remaining_progress / progress_per_second if progress_per_second > 0 else 0
                            if estimated_seconds > 0:
                                if estimated_seconds < 60:
                                    time_str = f"约 {int(estimated_seconds)} 秒"
                                elif estimated_seconds < 3600:
                                    time_str = f"约 {int(estimated_seconds / 60)} 分钟"
                                else:
                                    time_str = f"约 {int(estimated_seconds / 3600)} 小时"
                            else:
                                time_str = "即将完成"
                        else:
                            time_str = "计算中..."
                        self.progress_updated.emit(
                            progress,
                            f"已处理 {completed}/{total_pages} 页，当前处理第 {page_idx + 1} 页 ({complexity} 个区块)... 预计剩余时间: {time_str}"
                        )
                        # 定期回收内存
                        if completed % 5 == 0:
                            gc.collect()
                    except Exception as e:
                        print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            if not self._is_running:
                return
            # 导出完成
            output_dir = self.export_options.get("output_dir", "")
            self.export_complete.emit(f"区块图像已成功导出到:\n{output_dir}")
        except Exception as e:
            self.export_failed.emit(str(e))

    def stop(self):
        self._is_running = False
        if self.worker:
            self.worker.stop_flag = True


class PDFExportWorker:
    """PDF导出工作器 - 使用多线程处理"""

    def __init__(self, progress_callback):
        self.progress_callback = progress_callback
        self.stop_flag = False

    def process_page(self, page_data, page_idx, export_options):
        """处理单个页面"""
        if self.stop_flag:
            return None
        try:
            # 获取图像质量选项
            image_quality = export_options.get("image_quality", 75)
            limit_size = export_options.get("limit_size", False)
            max_width = export_options.get("max_width", None)
            max_height = export_options.get("max_height", None)
            # 创建标注图像
            img = self.create_annotated_image(page_data['image'], page_data['detections'])
            # 应用尺寸限制（如果需要）
            if limit_size and (max_width or max_height):
                h, w = img.shape[:2]
                scale = 1.0
                if max_width and w > max_width:
                    scale = max_width / w
                if max_height and h > max_height:
                    scale = min(scale, max_height / h)
                if scale != 1.0:
                    new_width = int(w * scale)
                    new_height = int(h * scale)
                    img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)
            return (page_idx, img, image_quality)
        except Exception as e:
            print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            return None

    def create_annotated_image(self, img_array, detections):
        # 创建图像副本
        annotated_img = img_array.copy()
        # 确保图像是BGR格式（OpenCV默认）
        if len(annotated_img.shape) == 2:  # 灰度图
            annotated_img = cv2.cvtColor(annotated_img, cv2.COLOR_GRAY2BGR)
        elif annotated_img.shape[2] == 4:  # RGBA
            annotated_img = cv2.cvtColor(annotated_img, cv2.COLOR_RGBA2BGR)
        elif annotated_img.shape[2] == 3 and not np.array_equal(annotated_img[0, 0], img_array[0, 0]):  # 可能是RGB
            annotated_img = cv2.cvtColor(annotated_img, cv2.COLOR_RGB2BGR)
        # 为不同区块类型定义颜色
        colors = {
            # 基础文本元素
            'doc_title': (0, 0, 255),  # 红色 - 文档标题
            'paragraph_title': (255, 0, 128),  # 玫瑰红 - 段落标题
            'text': (0, 255, 0),  # 绿色 - 文本
            'number': (128, 128, 128),  # 灰色 - 页码
            # 文档结构元素
            'abstract': (128, 128, 0),  # 橄榄色 - 摘要
            'content': (64, 128, 0),  # 深橄榄绿 - 目录
            'reference': (42, 42, 165),  # 深蓝 - 参考文献
            'footnote': (192, 192, 192),  # 银色 - 脚注
            'header': (255, 192, 203),  # 粉色 - 页眉
            'footer': (173, 216, 230),  # 浅蓝 - 页脚
            # 特殊内容元素
            'algorithm': (75, 0, 130),  # 靛蓝 - 算法
            'formula': (255, 255, 0),  # 黄色 - 公式
            'formula_number': (128, 0, 255),  # 紫罗兰 - 公式编号
            # 图像和表格元素
            'image': (255, 128, 0),  # 深橙 - 图像
            'figure_title': (255, 0, 64),  # 深玫瑰红 - 图像标题
            'chart': (0, 128, 255),  # 天蓝 - 图表
            'chart_title': (0, 255, 128),  # 春绿色 - 图表标题
            'table': (0, 0, 192),  # 深红色 - 表格
            'table_title': (128, 255, 255),  # 浅青色 - 表格标题
            # 其他元素
            'seal': (128, 0, 128),  # 深紫色 - 印章
            'header_image': (192, 255, 62),  # 酸橙绿 - 页眉图像
            'footer_image': (62, 255, 192),  # 蓝绿 - 页脚图像
            'aside_text': (128, 64, 0),  # 棕色 - 侧栏文本
        }
        # 绘制边界框
        for detection in detections:
            label = detection['label']
            coords = detection['coordinates']
            x1, y1, x2, y2 = map(int, coords)
            # 获取颜色，如果不在字典中则使用白色
            color = colors.get(label, (255, 255, 255))
            # 绘制矩形
            cv2.rectangle(annotated_img, (x1, y1), (x2, y2), color, 2)
            # 添加标签
            label_text = f"{label} ({detection['confidence']:.2f})"
            # 确保文本不会超出图像边界
            text_y = max(y1 - 10, 20)
            cv2.putText(
                annotated_img,
                label_text,
                (x1, text_y),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.5,
                color,
                1
            )
        return annotated_img


class PDFExportThread(QThread):
    """PDF导出线程"""
    progress_updated = pyqtSignal(int, str)  # 进度百分比和描述信息
    export_complete = pyqtSignal(str)
    export_failed = pyqtSignal(str)

    def __init__(self, analysis_results, export_options, file_path=None):
        super().__init__()
        self.analysis_results = analysis_results
        self.export_options = export_options
        self.file_path = file_path  # PDF输出文件路径
        self._is_running = True
        self.worker = None
        self.start_time = 0  # 记录开始时间

    def run(self):
        try:
            # 记录开始时间
            self.start_time = time.time()
            # 确定要处理的页面
            if self.export_options["page_selection"] == "all":
                pages_to_process = range(len(self.analysis_results))
            elif self.export_options["page_selection"] == "current":
                pages_to_process = [self.export_options.get("current_page", 0)]
            else:
                pages_to_process = self.export_options.get("selected_pages", [])
            total_pages = len(pages_to_process)
            if total_pages == 0:
                self.export_failed.emit("没有选择要处理的页面")
                return
            # 预估每个页面的处理复杂度（基于图像尺寸）
            page_complexity = []
            for page_idx in pages_to_process:
                page_data = self.analysis_results[page_idx]
                # 计算页面复杂度（基于图像尺寸）
                img = page_data['image']
                complexity = img.shape[0] * img.shape[1]  # 像素数量
                page_complexity.append((page_idx, complexity))
            # 按复杂度排序，先处理简单的页面
            page_complexity.sort(key=lambda x: x[1])
            total_complexity = sum(complexity for _, complexity in page_complexity)
            processed_complexity = 0
            # 创建工作器
            self.worker = PDFExportWorker(self.progress_updated.emit)
            # 使用线程池处理页面
            with ThreadPoolExecutor(max_workers=4) as executor:
                # 提交所有页面处理任务
                future_to_page = {
                    executor.submit(
                        self.worker.process_page,
                        self.analysis_results[page_idx],
                        page_idx,
                        self.export_options
                    ): (page_idx, complexity) for page_idx, complexity in page_complexity
                }
                # 收集处理结果
                processed_pages = {}
                completed = 0
                for future in future_to_page:
                    if not self._is_running:
                        break
                    page_idx, complexity = future_to_page[future]
                    try:
                        result = future.result(timeout=30)  # 设置超时
                        if result is not None:
                            processed_pages[result[0]] = (result[1], result[2])  # (图像, 质量)
                            processed_complexity += complexity
                            completed += 1
                            # 基于复杂度计算进度
                            progress = int(processed_complexity / total_complexity * 100)
                            # 计算预估剩余时间
                            elapsed_time = time.time() - self.start_time
                            if completed > 0 and elapsed_time > 0:
                                progress_per_second = progress / elapsed_time
                                remaining_progress = 100 - progress
                                estimated_seconds = remaining_progress / progress_per_second if progress_per_second > 0 else 0
                                if estimated_seconds > 0:
                                    if estimated_seconds < 60:
                                        time_str = f"约 {int(estimated_seconds)} 秒"
                                    elif estimated_seconds < 3600:
                                        time_str = f"约 {int(estimated_seconds / 60)} 分钟"
                                    else:
                                        time_str = f"约 {int(estimated_seconds / 3600)} 小时"
                                else:
                                    time_str = "即将完成"
                            else:
                                time_str = "计算中..."
                            self.progress_updated.emit(
                                progress,
                                f"已处理 {completed}/{total_pages} 页，当前处理第 {page_idx + 1} 页... 预计剩余时间: {time_str}"
                            )
                            # 定期回收内存
                            if completed % 5 == 0:
                                gc.collect()
                    except Exception as e:
                        print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            if not self._is_running:
                return
            if not processed_pages:
                self.export_failed.emit("没有可导出的内容")
                return
            # 按页面顺序排序
            sorted_pages = sorted(processed_pages.items())
            # 创建最终PDF文档
            self.progress_updated.emit(100, "正在生成PDF文件...")
            final_doc = fitz.open()
            # 分批处理页面，避免内存问题
            batch_size = 10
            for i in range(0, len(sorted_pages), batch_size):
                if not self._is_running:
                    break
                batch = sorted_pages[i:i + batch_size]
                for page_idx, (img, quality) in batch:
                    if not self._is_running:
                        break
                    # 创建新页面
                    page = final_doc.new_page(width=img.shape[1], height=img.shape[0])
                    # 添加页码标签（如果是测试模式）
                    if self.export_options.get("is_test_mode", False):
                        # 获取原始页码
                        original_page_num = page_idx + 1  # 这里假设page_idx已经是原始页码
                        page.insert_text((50, 30), f"原始页码: {original_page_num}", fontsize=12, color=(0, 0, 0))
                    # 将图像转换为字节流
                    img_bytes = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality])[1].tobytes()
                    # 插入图像
                    rect = fitz.Rect(0, 50, img.shape[1], img.shape[0] + 50)  # 为页码标签留出空间
                    page.insert_image(rect, stream=img_bytes)
                    # 释放图像内存
                    del img
                    img = None
                # 定期保存和清理内存
                if i % 20 == 0:
                    gc.collect()
            # 保存最终PDF
            final_doc.save(self.file_path, deflate=True, garbage=1)
            final_doc.close()
            # 检查文件大小
            file_size = os.path.getsize(self.file_path) / (1024 * 1024)  # MB
            self.export_complete.emit(f"{self.file_path}\n\n文件大小: {file_size:.2f} MB")
        except Exception as e:
            self.export_failed.emit(str(e))

    def stop(self):
        self._is_running = False
        if self.worker:
            self.worker.stop_flag = True


class ColumnCropWorker:
    """分栏裁切工作器 - 智能分栏算法实现"""

    def __init__(self, progress_callback):
        self.progress_callback = progress_callback
        self.stop_flag = False

    def detect_skew_angle(self, page_data):
        """检测文档的倾斜角度"""
        # 获取所有文本区块
        text_blocks = []
        for detection in page_data['detections']:
            if detection['label'] == 'text':
                coords = detection['coordinates']
                x1, y1, x2, y2 = map(int, coords)
                text_blocks.append({
                    'x1': x1,
                    'y1': y1,
                    'x2': x2,
                    'y2': y2,
                    'center_x': (x1 + x2) / 2,
                    'center_y': (y1 + y2) / 2
                })

        if len(text_blocks) < 2:
            return 0  # 文本区块太少，无法检测倾斜

        # 计算文本区块的倾斜角度
        angles = []

        # 按Y坐标排序文本区块
        text_blocks.sort(key=lambda b: b['y1'])

        # 计算相邻文本区块之间的角度
        for i in range(1, len(text_blocks)):
            prev_block = text_blocks[i - 1]
            curr_block = text_blocks[i]

            # 计算两个区块中心点之间的角度
            dx = curr_block['center_x'] - prev_block['center_x']
            dy = curr_block['center_y'] - prev_block['center_y']

            # 避免除以零
            if dy == 0:
                continue

            # 计算角度（弧度）
            angle = np.arctan(dx / dy)

            # 转换为角度
            angle_deg = np.degrees(angle)

            # 只考虑较小的角度（-30度到30度之间）
            if -30 <= angle_deg <= 30:
                angles.append(angle_deg)

        if not angles:
            return 0

        # 使用中位数作为最终角度，减少异常值的影响
        skew_angle = np.median(angles)

        return skew_angle

    def calculate_optimal_dividers(self, page_data, column_count):
        """计算最优分割线位置，避免切割文本区块"""
        # 获取页面图像尺寸
        img_height, img_width = page_data['image'].shape[:2]

        # 获取所有文本区块
        text_blocks = []
        for detection in page_data['detections']:
            if detection['label'] == 'text':
                coords = detection['coordinates']
                x1, y1, x2, y2 = map(int, coords)
                text_blocks.append({
                    'x1': x1,
                    'y1': y1,
                    'x2': x2,
                    'y2': y2,
                    'center_x': (x1 + x2) / 2,
                    'center_y': (y1 + y2) / 2
                })

        if not text_blocks:
            # 如果没有文本区块，使用简单的等宽分栏
            dividers = []
            for i in range(1, column_count):
                dividers.append(img_width * i / column_count)
            return dividers

        # 检测文档倾斜角度
        skew_angle = self.detect_skew_angle(page_data)

        # 按x坐标排序文本区块
        text_blocks.sort(key=lambda b: b['x1'])

        # 初始分割线位置（等宽）
        initial_dividers = [img_width * i / column_count for i in range(1, column_count)]

        # 优化分割线位置，避免切割文本区块
        optimized_dividers = []

        # 使用KMeans聚类分析文本区块的分布
        if len(text_blocks) >= column_count:
            # 提取文本区块的x坐标作为聚类特征
            X = np.array([[block['center_x']] for block in text_blocks])

            # 使用KMeans聚类
            kmeans = KMeans(n_clusters=column_count, random_state=0).fit(X)

            # 获取聚类中心
            cluster_centers = sorted(kmeans.cluster_centers_.flatten())

            # 基于聚类中心计算分割线位置
            for i in range(1, column_count):
                # 分割线位于相邻聚类中心的中点
                divider = (cluster_centers[i - 1] + cluster_centers[i]) / 2
                optimized_dividers.append(divider)
        else:
            # 如果文本区块数量少于栏数，使用初始分割线
            optimized_dividers = initial_dividers.copy()

        # 进一步优化分割线位置，考虑倾斜角度
        final_dividers = []
        for divider in optimized_dividers:
            # 寻找最佳分割线位置，考虑垂直和倾斜分割线
            best_position = divider
            best_angle = 0  # 默认垂直分割线
            min_penalty = float('inf')

            # 尝试不同的角度（-15度到15度）
            angles_to_try = [0]  # 总是尝试垂直分割线
            if abs(skew_angle) > 1:  # 如果检测到明显的倾斜
                angles_to_try.extend([skew_angle, -skew_angle])

            for angle in angles_to_try:
                # 在初始位置附近搜索最佳位置
                search_range = img_width / (column_count * 2)  # 搜索范围为半栏宽度
                step = search_range / 20  # 搜索步长

                for pos in np.arange(divider - search_range, divider + search_range, step):
                    if pos <= 0 or pos >= img_width:
                        continue

                    # 计算惩罚值（分割线与文本区块的距离）
                    penalty = 0
                    for block in text_blocks:
                        # 计算文本区块相对于分割线的位置
                        if angle == 0:  # 垂直分割线
                            # 如果分割线穿过文本区块，增加惩罚值
                            if block['x1'] < pos < block['x2']:
                                penalty += 1000  # 高惩罚
                            else:
                                # 计算到最近文本区块边缘的距离
                                distance = min(abs(pos - block['x1']), abs(pos - block['x2']))
                                penalty += 1 / (distance + 1)  # 距离越近，惩罚越大
                        else:  # 倾斜分割线
                            # 将角度转换为弧度
                            angle_rad = np.radians(angle)

                            # 计算文本区块四个角相对于分割线的位置
                            corners = [
                                (block['x1'], block['y1']),
                                (block['x2'], block['y1']),
                                (block['x1'], block['y2']),
                                (block['x2'], block['y2'])
                            ]

                            # 检查分割线是否穿过文本区块
                            crosses_block = False
                            min_distance = float('inf')

                            for x, y in corners:
                                # 计算点到倾斜分割线的距离
                                # 分割线方程: x - y * tan(angle_rad) = pos
                                distance = abs(x - y * np.tan(angle_rad) - pos) / np.sqrt(1 + np.tan(angle_rad) ** 2)
                                min_distance = min(min_distance, distance)

                            # 检查分割线是否穿过文本区块
                            # 计算文本区块中心相对于分割线的位置
                            center_x = block['center_x']
                            center_y = block['center_y']
                            center_relative_pos = center_x - center_y * np.tan(angle_rad)

                            # 如果文本区块中心在分割线附近，且分割线穿过文本区块的Y范围
                            if (block['y1'] <= center_y <= block['y2'] and
                                    block['x1'] <= center_relative_pos <= block['x2']):
                                crosses_block = True

                            if crosses_block:
                                penalty += 1000  # 高惩罚
                            else:
                                penalty += 1 / (min_distance + 1)  # 距离越近，惩罚越大

                    # 考虑与理想位置的距离
                    ideal_distance = abs(pos - divider)
                    penalty += ideal_distance / img_width * 10  # 较小的惩罚

                    # 考虑与垂直线的偏差（如果使用倾斜分割线）
                    if angle != 0:
                        penalty += abs(angle) * 5  # 倾斜角度越大，惩罚越大

                    if penalty < min_penalty:
                        min_penalty = penalty
                        best_position = pos
                        best_angle = angle

            final_dividers.append((best_position, best_angle))

        return final_dividers

    def process_page(self, page_data, page_idx, crop_options):
        """处理单个页面"""
        if self.stop_flag:
            return None
        try:
            # 获取页面图像
            img = page_data['image'].copy()
            # 确保图像是RGB格式
            if len(img.shape) == 2:  # 灰度图
                img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
            elif img.shape[2] == 4:  # RGBA
                img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
            elif img.shape[2] == 3 and not np.array_equal(img[0, 0], page_data['image'][0, 0]):  # 可能是BGR
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

            # 对选中的区块进行马赛克处理
            mosaic_types = crop_options.get("mosaic_types", [])
            mosaic_color = crop_options.get("mosaic_color", "white")
            if mosaic_types:
                for detection in page_data['detections']:
                    if detection['label'] in mosaic_types:
                        coords = detection['coordinates']
                        x1, y1, x2, y2 = map(int, coords)
                        # 根据选择的颜色进行马赛克
                        if mosaic_color == "white":
                            color = (255, 255, 255)
                        elif mosaic_color == "gray":
                            color = (128, 128, 128)
                        else:  # 自定义颜色
                            color = (mosaic_color.red(), mosaic_color.green(), mosaic_color.blue())
                        # 填充矩形
                        cv2.rectangle(img, (x1, y1), (x2, y2), color, -1)

            # 获取分栏数
            column_count = crop_options.get("column_count", 2)

            # 计算分割线位置
            if crop_options.get("smart_column", True):
                # 使用智能分栏算法
                dividers = self.calculate_optimal_dividers(page_data, column_count)
            else:
                # 使用简单等宽分栏
                img_height, img_width = img.shape[:2]
                dividers = [(img_width * i / column_count, 0) for i in range(1, column_count)]  # 添加角度0

            # 获取图像尺寸
            img_height, img_width = img.shape[:2]

            # 计算每栏的边界
            column_boundaries = []
            prev_x = 0
            for divider_info in dividers:
                if isinstance(divider_info, tuple) and len(divider_info) == 2:
                    divider, angle = divider_info
                else:
                    divider = divider_info
                    angle = 0

                column_boundaries.append((prev_x, divider, angle))
                prev_x = divider
            column_boundaries.append((prev_x, img_width, 0))

            # 裁切每栏
            column_images = []
            for i, (x_start, x_end, angle) in enumerate(column_boundaries):
                if angle == 0:
                    # 垂直分割，直接裁切
                    column_img = img[:, int(x_start):int(x_end)]
                else:
                    # 倾斜分割，需要旋转图像后再裁切
                    # 计算旋转矩阵
                    center = (img_width // 2, img_height // 2)
                    rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)

                    # 旋转图像
                    rotated_img = cv2.warpAffine(img, rotation_matrix, (img_width, img_height), flags=cv2.INTER_LINEAR)

                    # 计算旋转后的x坐标
                    x_start_rotated = int(x_start)
                    x_end_rotated = int(x_end)

                    # 确保坐标在图像范围内
                    x_start_rotated = max(0, min(x_start_rotated, img_width))
                    x_end_rotated = max(0, min(x_end_rotated, img_width))

                    # 裁切旋转后的图像
                    column_img = rotated_img[:, x_start_rotated:x_end_rotated]

                    # 将图像旋转回原始方向
                    center_col = (column_img.shape[1] // 2, column_img.shape[0] // 2)
                    rotation_matrix_inv = cv2.getRotationMatrix2D(center_col, -angle, 1.0)
                    column_img = cv2.warpAffine(column_img, rotation_matrix_inv,
                                                (column_img.shape[1], column_img.shape[0]), flags=cv2.INTER_LINEAR)

                column_images.append(column_img)

            # 获取原始页码，优先使用page_index，否则使用page_idx
            original_page_num = page_data.get('page_index', page_idx) + 1

            # 返回结果，包括分割线位置和倾斜角度
            return (page_idx, column_images, original_page_num, dividers)
        except Exception as e:
            print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            return None


class ColumnCropThread(QThread):
    """分栏裁切线程"""
    progress_updated = pyqtSignal(int, str)  # 进度百分比和描述信息
    crop_complete = pyqtSignal(str)
    crop_failed = pyqtSignal(str)

    def __init__(self, analysis_results, crop_options):
        super().__init__()
        self.analysis_results = analysis_results
        self.crop_options = crop_options
        self._is_running = True
        self.worker = None
        self.start_time = 0  # 记录开始时间

    def run(self):
        try:
            # 记录开始时间
            self.start_time = time.time()
            # 确定要处理的页面
            if self.crop_options["page_selection"] == "all":
                pages_to_process = range(len(self.analysis_results))
            elif self.crop_options["page_selection"] == "current":
                pages_to_process = [self.crop_options.get("current_page", 0)]
            else:
                pages_to_process = range(len(self.analysis_results))  # 默认所有页面
            total_pages = len(pages_to_process)
            if total_pages == 0:
                self.crop_failed.emit("没有选择要处理的页面")
                return
            output_dir = self.crop_options["output_dir"]
            column_count = self.crop_options["column_count"]
            export_format = self.crop_options["export_format"]
            image_format = self.crop_options.get("image_format", "jpeg")
            image_quality = self.crop_options.get("image_quality", 90)
            limit_size = self.crop_options.get("limit_size", False)
            max_width = self.crop_options.get("max_width", None)
            max_height = self.crop_options.get("max_height", None)
            # 预估每个页面的处理复杂度（基于图像尺寸）
            page_complexity = []
            for page_idx in pages_to_process:
                page_data = self.analysis_results[page_idx]
                # 计算页面复杂度（基于图像尺寸）
                img = page_data['image']
                complexity = img.shape[0] * img.shape[1]  # 像素数量
                page_complexity.append((page_idx, complexity))
            # 按复杂度排序，先处理简单的页面
            page_complexity.sort(key=lambda x: x[1])
            total_complexity = sum(complexity for _, complexity in page_complexity)
            processed_complexity = 0
            # 创建工作器
            self.worker = ColumnCropWorker(self.progress_updated.emit)
            # 使用线程池处理页面
            with ThreadPoolExecutor(max_workers=4) as executor:
                # 提交所有页面处理任务
                future_to_page = {
                    executor.submit(
                        self.worker.process_page,
                        self.analysis_results[page_idx],
                        page_idx,
                        self.crop_options
                    ): (page_idx, complexity) for page_idx, complexity in page_complexity
                }
                # 收集处理结果
                processed_pages = {}
                completed = 0
                for future in future_to_page:
                    if not self._is_running:
                        break
                    page_idx, complexity = future_to_page[future]
                    try:
                        result = future.result(timeout=30)  # 设置超时
                        if result is not None:
                            # 存储原始页码、分割线位置和倾斜角度
                            processed_pages[result[0]] = (result[1], result[2],
                                                          result[3])  # (column_images, original_page_num, dividers)
                            processed_complexity += complexity
                            completed += 1
                            # 基于复杂度计算进度
                            progress = int(processed_complexity / total_complexity * 100)
                            # 计算预估剩余时间
                            elapsed_time = time.time() - self.start_time
                            if completed > 0 and elapsed_time > 0:
                                progress_per_second = progress / elapsed_time
                                remaining_progress = 100 - progress
                                estimated_seconds = remaining_progress / progress_per_second if progress_per_second > 0 else 0
                                if estimated_seconds > 0:
                                    if estimated_seconds < 60:
                                        time_str = f"约 {int(estimated_seconds)} 秒"
                                    elif estimated_seconds < 3600:
                                        time_str = f"约 {int(estimated_seconds / 60)} 分钟"
                                    else:
                                        time_str = f"约 {int(estimated_seconds / 3600)} 小时"
                                else:
                                    time_str = "即将完成"
                            else:
                                time_str = "计算中..."
                            self.progress_updated.emit(
                                progress,
                                f"已处理 {completed}/{total_pages} 页，当前处理第 {page_idx + 1} 页... 预计剩余时间: {time_str}"
                            )
                            # 定期回收内存
                            if completed % 5 == 0:
                                gc.collect()
                    except Exception as e:
                        print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            if not self._is_running:
                return
            if not processed_pages:
                self.crop_failed.emit("没有可裁切的内容")
                return
            # 按页面顺序排序
            sorted_pages = sorted(processed_pages.items())
            # 导出结果
            self.progress_updated.emit(100, "正在导出结果...")
            # 创建输出目录
            os.makedirs(output_dir, exist_ok=True)
            # 为每页的每栏导出
            for page_idx, (column_images, original_page_num, dividers) in sorted_pages:
                for col, column_img in enumerate(column_images):
                    # 应用尺寸限制（如果需要）
                    if limit_size and (max_width or max_height):
                        h, w = column_img.shape[:2]
                        scale = 1.0
                        if max_width and w > max_width:
                            scale = max_width / w
                        if max_height and h > max_height:
                            scale = min(scale, max_height / h)
                        if scale != 1.0:
                            new_width = int(w * scale)
                            new_height = int(h * scale)
                            column_img = cv2.resize(column_img, (new_width, new_height), interpolation=cv2.INTER_AREA)
                    # 生成文件名：使用原始页码
                    filename = f"page_{original_page_num:03d}_col{col + 1}"
                    if export_format == "image":
                        # 保存为图像
                        if image_format.lower() == "jpeg" or image_format.lower() == "jpg":
                            output_path = os.path.join(output_dir, f"{filename}.jpg")
                            cv2.imwrite(output_path, cv2.cvtColor(column_img, cv2.COLOR_RGB2BGR),
                                        [int(cv2.IMWRITE_JPEG_QUALITY), image_quality])
                        elif image_format.lower() == "png":
                            output_path = os.path.join(output_dir, f"{filename}.png")
                            cv2.imwrite(output_path, cv2.cvtColor(column_img, cv2.COLOR_RGB2BGR),
                                        [int(cv2.IMWRITE_PNG_COMPRESSION), 10 - (image_quality // 10)])
                    else:  # PDF
                        # 保存为PDF
                        output_path = os.path.join(output_dir, f"{filename}.pdf")
                        # 创建PDF文档
                        doc = fitz.open()
                        page = doc.new_page(width=column_img.shape[1], height=column_img.shape[0])
                        # 将图像转换为字节流
                        img_bytes = cv2.imencode('.jpg', column_img, [int(cv2.IMWRITE_JPEG_QUALITY), image_quality])[
                            1].tobytes()
                        # 插入图像
                        rect = fitz.Rect(0, 0, column_img.shape[1], column_img.shape[0])
                        page.insert_image(rect, stream=img_bytes)
                        # 保存PDF
                        doc.save(output_path)
                        doc.close()
            if self._is_running:
                self.crop_complete.emit(output_dir)
        except Exception as e:
            self.crop_failed.emit(str(e))

    def stop(self):
        self._is_running = False
        if self.worker:
            self.worker.stop_flag = True


class ImageMaskWorker:
    """图像掩码工作器"""

    def __init__(self, progress_callback):
        self.progress_callback = progress_callback
        self.stop_flag = False

    def process_page(self, page_data, page_idx, mask_options):
        """处理单个页面"""
        if self.stop_flag:
            return None
        try:
            # 获取页面图像
            img = page_data['image'].copy()
            # 确保图像是RGB格式
            if len(img.shape) == 2:  # 灰度图
                img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
            elif img.shape[2] == 4:  # RGBA
                img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
            elif img.shape[2] == 3 and not np.array_equal(img[0, 0], page_data['image'][0, 0]):  # 可能是BGR
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            # 对选中的区块进行马赛克处理
            mosaic_types = mask_options.get("mosaic_types", [])
            mosaic_color = mask_options.get("mosaic_color", "white")
            if mosaic_types:
                for detection in page_data['detections']:
                    if detection['label'] in mosaic_types:
                        coords = detection['coordinates']
                        x1, y1, x2, y2 = map(int, coords)
                        # 根据选择的颜色进行马赛克
                        if mosaic_color == "white":
                            color = (255, 255, 255)
                        elif mosaic_color == "gray":
                            color = (128, 128, 128)
                        else:  # 自定义颜色
                            color = (mosaic_color.red(), mosaic_color.green(), mosaic_color.blue())
                        # 填充矩形
                        cv2.rectangle(img, (x1, y1), (x2, y2), color, -1)
            # 获取原始页码，优先使用page_index，否则使用page_idx
            original_page_num = page_data.get('page_index', page_idx) + 1
            return (page_idx, img, original_page_num)
        except Exception as e:
            print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            return None


class ImageMaskThread(QThread):
    """图像掩码线程"""
    progress_updated = pyqtSignal(int, str)  # 进度百分比和描述信息
    mask_complete = pyqtSignal(str)
    mask_failed = pyqtSignal(str)

    def __init__(self, analysis_results, mask_options):
        super().__init__()
        self.analysis_results = analysis_results
        self.mask_options = mask_options
        self._is_running = True
        self.worker = None
        self.start_time = 0  # 记录开始时间

    def run(self):
        try:
            # 记录开始时间
            self.start_time = time.time()
            # 确定要处理的页面
            if self.mask_options["page_selection"] == "all":
                pages_to_process = range(len(self.analysis_results))
            elif self.mask_options["page_selection"] == "current":
                pages_to_process = [self.mask_options.get("current_page", 0)]
            else:
                pages_to_process = self.mask_options.get("selected_pages", [])
            total_pages = len(pages_to_process)
            if total_pages == 0:
                self.mask_failed.emit("没有选择要处理的页面")
                return
            # 预估每个页面的处理复杂度（基于图像尺寸）
            page_complexity = []
            for page_idx in pages_to_process:
                page_data = self.analysis_results[page_idx]
                # 计算页面复杂度（基于图像尺寸）
                img = page_data['image']
                complexity = img.shape[0] * img.shape[1]  # 像素数量
                page_complexity.append((page_idx, complexity))
            # 按复杂度排序，先处理简单的页面
            page_complexity.sort(key=lambda x: x[1])
            total_complexity = sum(complexity for _, complexity in page_complexity)
            processed_complexity = 0
            # 创建工作器
            self.worker = ImageMaskWorker(self.progress_updated.emit)
            # 使用线程池处理页面
            with ThreadPoolExecutor(max_workers=4) as executor:
                # 提交所有页面处理任务
                future_to_page = {
                    executor.submit(
                        self.worker.process_page,
                        self.analysis_results[page_idx],
                        page_idx,
                        self.mask_options
                    ): (page_idx, complexity) for page_idx, complexity in page_complexity
                }
                # 收集处理结果
                processed_pages = {}
                completed = 0
                for future in future_to_page:
                    if not self._is_running:
                        break
                    page_idx, complexity = future_to_page[future]
                    try:
                        result = future.result(timeout=30)  # 设置超时
                        if result is not None:
                            # 存储原始页码
                            processed_pages[result[0]] = (result[1], result[2])  # (img, original_page_num)
                            processed_complexity += complexity
                            completed += 1
                            # 基于复杂度计算进度
                            progress = int(processed_complexity / total_complexity * 100)
                            # 计算预估剩余时间
                            elapsed_time = time.time() - self.start_time
                            if completed > 0 and elapsed_time > 0:
                                progress_per_second = progress / elapsed_time
                                remaining_progress = 100 - progress
                                estimated_seconds = remaining_progress / progress_per_second if progress_per_second > 0 else 0
                                if estimated_seconds > 0:
                                    if estimated_seconds < 60:
                                        time_str = f"约 {int(estimated_seconds)} 秒"
                                    elif estimated_seconds < 3600:
                                        time_str = f"约 {int(estimated_seconds / 60)} 分钟"
                                    else:
                                        time_str = f"约 {int(estimated_seconds / 3600)} 小时"
                                else:
                                    time_str = "即将完成"
                            else:
                                time_str = "计算中..."
                            self.progress_updated.emit(
                                progress,
                                f"已处理 {completed}/{total_pages} 页，当前处理第 {page_idx + 1} 页... 预计剩余时间: {time_str}"
                            )
                            # 定期回收内存
                            if completed % 5 == 0:
                                gc.collect()
                    except Exception as e:
                        print(f"处理第 {page_idx + 1} 页时出错: {str(e)}")
            if not self._is_running:
                return
            if not processed_pages:
                self.mask_failed.emit("没有可处理的内容")
                return
            # 按页面顺序排序
            sorted_pages = sorted(processed_pages.items())
            # 导出结果
            self.progress_updated.emit(100, "正在导出结果...")
            # 获取导出选项
            export_format = self.mask_options.get("export_format", "image")
            image_format = self.mask_options.get("image_format", "jpeg")
            image_quality = self.mask_options.get("image_quality", 90)
            limit_size = self.mask_options.get("limit_size", False)
            max_width = self.mask_options.get("max_width", None)
            max_height = self.mask_options.get("max_height", None)
            output_dir = self.mask_options.get("output_dir", "")
            # 创建输出目录
            os.makedirs(output_dir, exist_ok=True)
            # 导出每页
            for page_idx, (img, original_page_num) in sorted_pages:
                # 应用尺寸限制（如果需要）
                if limit_size and (max_width or max_height):
                    h, w = img.shape[:2]
                    scale = 1.0
                    if max_width and w > max_width:
                        scale = max_width / w
                    if max_height and h > max_height:
                        scale = min(scale, max_height / h)
                    if scale != 1.0:
                        new_width = int(w * scale)
                        new_height = int(h * scale)
                        img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)
                # 生成文件名：使用原始页码
                filename = f"masked_page_{original_page_num:03d}"
                if export_format == "image":
                    # 保存为图像
                    if image_format.lower() == "jpeg" or image_format.lower() == "jpg":
                        output_path = os.path.join(output_dir, f"{filename}.jpg")
                        cv2.imwrite(output_path, cv2.cvtColor(img, cv2.COLOR_RGB2BGR),
                                    [int(cv2.IMWRITE_JPEG_QUALITY), image_quality])
                    elif image_format.lower() == "png":
                        output_path = os.path.join(output_dir, f"{filename}.png")
                        cv2.imwrite(output_path, cv2.cvtColor(img, cv2.COLOR_RGB2BGR),
                                    [int(cv2.IMWRITE_PNG_COMPRESSION), 10 - (image_quality // 10)])
                else:  # PDF
                    # 保存为PDF
                    output_path = os.path.join(output_dir, f"{filename}.pdf")
                    # 创建PDF文档
                    doc = fitz.open()
                    page = doc.new_page(width=img.shape[1], height=img.shape[0])
                    # 将图像转换为字节流
                    img_bytes = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), image_quality])[1].tobytes()
                    # 插入图像
                    rect = fitz.Rect(0, 0, img.shape[1], img.shape[0])
                    page.insert_image(rect, stream=img_bytes)
                    # 保存PDF
                    doc.save(output_path)
                    doc.close()
            if self._is_running:
                self.mask_complete.emit(output_dir)
        except Exception as e:
            self.mask_failed.emit(str(e))

    def stop(self):
        self._is_running = False
        if self.worker:
            self.worker.stop_flag = True


class PDFLayoutAnalyzer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PDF版面分析工作台 - PP-StructureV3")
        self.setGeometry(100, 100, 1400, 900)
        # 初始化变量
        self.current_pdf_path = ""
        self.analysis_results = []
        self.all_detections = []
        self.test_results = []  # 存储测试分析结果
        self.test_all_detections = []  # 存储测试分析的所有检测结果
        self.current_page = 0
        self.model = None
        self.worker_thread = None
        self.image_export_thread = None
        self.pdf_export_thread = None
        self.column_crop_thread = None
        self.image_mask_thread = None
        self.model_dir = "C:\\Users\\lbl12\\.paddlex\\official_models"  # 本地模型目录
        self.is_test_mode = False  # 标记当前是否为测试模式
        self.current_dividers = []  # 存储当前页面的分割线位置
        self.current_skew_angle = 0  # 存储当前页面的倾斜角度
        # 加载上次保存的参数
        self.parameters = self.load_parameters()
        # 设置样式
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f5f5f7;
            }
            QLabel {
                color: #333;
                font-size: 14px;
            }
            QPushButton {
                background-color: #007aff;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 6px;
                font-weight: bold;
                font-size: 14px;
            }
            QPushButton:hover {
                background-color: #0051d5;
            }
            QPushButton:pressed {
                background-color: #003d99;
            }
            QPushButton:disabled {
                background-color: #c7c7cc;
                color: #999;
            }
            QTableWidget {
                background-color: white;
                border: 1px solid #ddd;
                border-radius: 6px;
                gridline-color: #eee;
            }
            QHeaderView::section {
                background-color: #f5f5f7;
                padding: 5px;
                border: none;
                border-right: 1px solid #ddd;
                border-bottom: 1px solid #ddd;
                font-weight: bold;
            }
            QProgressBar {
                border: 1px solid #ddd;
                border-radius: 5px;
                text-align: center;
                font-weight: bold;
            }
            QProgressBar::chunk {
                background-color: #007aff;
                border-radius: 4px;
            }
            QComboBox {
                padding: 5px;
                border: 1px solid #ddd;
                border-radius: 5px;
                background-color: white;
            }
            QCheckBox {
                spacing: 8px;
            }
            QRadioButton {
                spacing: 8px;
            }
            QGroupBox {
                font-weight: bold;
                border: 1px solid #ddd;
                border-radius: 6px;
                margin-top: 10px;
                padding-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
            QListWidget {
                border: 1px solid #ddd;
                border-radius: 6px;
            }
            QTabWidget::pane {
                border: 1px solid #ddd;
                border-radius: 6px;
            }
            QTabBar::tab {
                padding: 8px 16px;
                background-color: #f5f5f7;
                border: 1px solid #ddd;
                border-bottom: none;
                border-top-left-radius: 6px;
                border-top-right-radius: 6px;
            }
            QTabBar::tab:selected {
                background-color: white;
                border-bottom: none;
            }
            QScrollArea {
                border: 1px solid #ddd;
                border-radius: 6px;
            }
            QSpinBox {
                padding: 5px;
                border: 1px solid #ddd;
                border-radius: 5px;
            }
            QLineEdit {
                padding: 5px;
                border: 1px solid #ddd;
                border-radius: 5px;
            }
            QDoubleSpinBox {
                padding: 5px;
                border: 1px solid #ddd;
                border-radius: 5px;
            }
        """)
        self.init_ui()

    def init_ui(self):
        # 主窗口部件
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
        # 顶部控制面板
        control_panel = QFrame()
        control_panel.setFrameShape(QFrame.StyledPanel)
        control_panel.setMaximumHeight(150)  # 限制最大高度
        control_layout = QHBoxLayout()
        control_panel.setLayout(control_layout)
        control_layout.setContentsMargins(5, 5, 5, 5)  # 减小内边距
        # 文件选择按钮
        self.file_btn = QPushButton("选择PDF文件")
        self.file_btn.clicked.connect(self.select_pdf_file)
        control_layout.addWidget(self.file_btn)
        # 文件路径显示
        self.file_path_label = QLabel("未选择文件")
        self.file_path_label.setMinimumWidth(300)
        control_layout.addWidget(self.file_path_label)
        # 模型选择
        control_layout.addWidget(QLabel("选择模型:"))
        self.model_combo = QComboBox()
        # 定义所有官方重点模型
        self.official_models = {
            "PP-DocLayout_plus-L": {
                "name": "PP-DocLayout_plus-L (最高精度)",
                "description": "基于RT-DETR-L的高精度版面区域定位模型",
                "mAP": "83.2",
                "size": "126.01 M"
            },
            "PP-DocBlockLayout": {
                "name": "PP-DocBlockLayout (子模块检测)",
                "description": "基于RT-DETR-L的文档图像版面子模块检测模型",
                "mAP": "95.9",
                "size": "123.92 M"
            },
            "PP-DocLayout-L": {
                "name": "PP-DocLayout-L (高精度)",
                "description": "基于RT-DETR-L的高精度版面区域定位模型",
                "mAP": "90.4",
                "size": "123.76 M"
            },
            "PP-DocLayout-M": {
                "name": "PP-DocLayout-M (平衡)",
                "description": "基于PicoDet-L的精度效率平衡模型",
                "mAP": "75.2",
                "size": "22.578 M"
            },
            "PP-DocLayout-S": {
                "name": "PP-DocLayout-S (快速)",
                "description": "基于PicoDet-S的高效率版面区域定位模型",
                "mAP": "70.9",
                "size": "4.834 M"
            }
        }
        # 检查本地模型目录
        self.available_models = {}
        if os.path.exists(self.model_dir):
            for model_id in self.official_models:
                model_path = os.path.join(self.model_dir, model_id)
                if os.path.exists(model_path):
                    self.available_models[model_id] = self.official_models[model_id]
                    self.available_models[model_id]["local"] = True
                    self.available_models[model_id]["path"] = model_path
                else:
                    self.available_models[model_id] = self.official_models[model_id]
                    self.available_models[model_id]["local"] = False
        else:
            self.available_models = self.official_models.copy()
            for model_id in self.available_models:
                self.available_models[model_id]["local"] = False
        # 添加模型到下拉框
        for model_id, model_info in self.available_models.items():
            display_name = model_info["name"]
            if model_info["local"]:
                display_name += " [本地]"
            self.model_combo.addItem(display_name, model_id)
        self.model_combo.setCurrentIndex(0)
        control_layout.addWidget(self.model_combo)
        # 模型信息标签
        self.model_info_label = QLabel("")
        self.model_info_label.setMinimumWidth(200)
        control_layout.addWidget(self.model_info_label)
        # 更新模型信息
        self.update_model_info()
        self.model_combo.currentIndexChanged.connect(self.update_model_info)
        # 高级参数设置按钮
        self.advanced_settings_btn = QPushButton("高级参数设置")
        self.advanced_settings_btn.clicked.connect(self.open_advanced_settings)
        control_layout.addWidget(self.advanced_settings_btn)
        # 分析按钮组
        analysis_group = QGroupBox("分析操作")
        analysis_layout = QHBoxLayout()
        # 局部分析按钮
        self.local_analysis_btn = QPushButton("局部分析")
        self.local_analysis_btn.clicked.connect(self.start_local_analysis)
        self.local_analysis_btn.setEnabled(False)
        analysis_layout.addWidget(self.local_analysis_btn)
        # 全局分析按钮
        self.global_analysis_btn = QPushButton("全局分析")
        self.global_analysis_btn.clicked.connect(self.start_global_analysis)
        self.global_analysis_btn.setEnabled(False)
        analysis_layout.addWidget(self.global_analysis_btn)
        analysis_group.setLayout(analysis_layout)
        control_layout.addWidget(analysis_group)
        control_layout.addStretch()
        main_layout.addWidget(control_panel)
        # 进度条区域
        self.progress_group = QGroupBox("处理进度")
        self.progress_group.setMaximumHeight(80)  # 设置最大高度
        progress_layout = QVBoxLayout()
        progress_layout.setContentsMargins(5, 3, 5, 3)  # 减小内边距
        progress_layout.setSpacing(3)  # 减小元素间距
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setMaximumHeight(20)  # 限制进度条高度
        progress_layout.addWidget(self.progress_bar)
        # 进度详情标签
        self.progress_detail_label = QLabel("")
        self.progress_detail_label.setVisible(False)
        self.progress_detail_label.setWordWrap(True)
        progress_layout.addWidget(self.progress_detail_label)
        self.progress_group.setLayout(progress_layout)
        self.progress_detail_label.setMaximumHeight(40)  # 限制详情标签高度
        main_layout.addWidget(self.progress_group)
        # 主内容区域
        content_splitter = QSplitter(Qt.Horizontal)
        # 左侧图像显示区域
        image_widget = QWidget()
        image_layout = QVBoxLayout()
        image_widget.setLayout(image_layout)
        # 图像控制面板
        image_control_panel = QFrame()
        image_control_layout = QHBoxLayout()
        image_control_panel.setLayout(image_control_layout)
        # 缩放控制
        image_control_layout.addWidget(QLabel("缩放:"))
        self.zoom_out_btn = QPushButton("-")
        self.zoom_out_btn.setMaximumWidth(30)
        self.zoom_out_btn.clicked.connect(self.zoom_out)
        image_control_layout.addWidget(self.zoom_out_btn)
        self.zoom_slider = QSlider(Qt.Horizontal)
        self.zoom_slider.setMinimum(10)
        self.zoom_slider.setMaximum(500)
        self.zoom_slider.setValue(100)
        self.zoom_slider.setTickPosition(QSlider.TicksBelow)
        self.zoom_slider.setTickInterval(50)
        self.zoom_slider.valueChanged.connect(self.on_zoom_changed)
        image_control_layout.addWidget(self.zoom_slider)
        self.zoom_in_btn = QPushButton("+")
        self.zoom_in_btn.setMaximumWidth(30)
        self.zoom_in_btn.clicked.connect(self.zoom_in)
        image_control_layout.addWidget(self.zoom_in_btn)
        self.fit_window_btn = QPushButton("适应窗口")
        self.fit_window_btn.clicked.connect(self.fit_to_window)
        image_control_layout.addWidget(self.fit_window_btn)
        image_control_layout.addStretch()
        image_layout.addWidget(image_control_panel)
        # 标签页
        self.image_tabs = QTabWidget()
        # 原始图像标签页
        self.original_image_label = ScrollableImageLabel()
        self.image_tabs.addTab(self.original_image_label, "原始图像")
        # 标注图像标签页
        self.annotated_image_label = ScrollableImageLabel()
        self.image_tabs.addTab(self.annotated_image_label, "版面标注图像")
        image_layout.addWidget(self.image_tabs)
        # 页面导航和导出控制面板
        nav_export_panel = QFrame()
        nav_export_layout = QHBoxLayout()
        nav_export_panel.setLayout(nav_export_layout)
        # 页面导航
        nav_export_layout.addWidget(QLabel("页面:"))
        self.page_combo = QComboBox()
        self.page_combo.setMinimumWidth(80)
        self.page_combo.currentIndexChanged.connect(self.on_page_changed)
        nav_export_layout.addWidget(self.page_combo)
        self.page_label = QLabel("第 0 页，共 0 页")
        nav_export_layout.addWidget(self.page_label)
        self.prev_btn = QPushButton("上一页")
        self.prev_btn.clicked.connect(self.prev_page)
        self.prev_btn.setEnabled(False)
        nav_export_layout.addWidget(self.prev_btn)
        self.next_btn = QPushButton("下一页")
        self.next_btn.clicked.connect(self.next_page)
        self.next_btn.setEnabled(False)
        nav_export_layout.addWidget(self.next_btn)
        nav_export_layout.addStretch()
        # 导出按钮
        self.export_image_btn = QPushButton("导出区块图像")
        self.export_image_btn.clicked.connect(self.export_block_images)
        self.export_image_btn.setEnabled(False)
        nav_export_layout.addWidget(self.export_image_btn)
        self.export_pdf_btn = QPushButton("导出标注PDF")
        self.export_pdf_btn.clicked.connect(self.export_annotated_pdf)
        self.export_pdf_btn.setEnabled(False)
        nav_export_layout.addWidget(self.export_pdf_btn)
        # 预览分割线按钮
        self.preview_dividers_btn = QPushButton("预览分割线")
        self.preview_dividers_btn.clicked.connect(self.show_column_dividers_preview)
        self.preview_dividers_btn.setEnabled(False)
        nav_export_layout.addWidget(self.preview_dividers_btn)
        # 分栏裁切按钮
        self.column_crop_btn = QPushButton("分栏裁切")
        self.column_crop_btn.clicked.connect(self.column_crop)
        self.column_crop_btn.setEnabled(False)
        nav_export_layout.addWidget(self.column_crop_btn)
        # 图像掩码按钮
        self.image_mask_btn = QPushButton("图像掩码")
        self.image_mask_btn.clicked.connect(self.image_mask)
        self.image_mask_btn.setEnabled(False)
        nav_export_layout.addWidget(self.image_mask_btn)
        image_layout.addWidget(nav_export_panel)
        content_splitter.addWidget(image_widget)
        # 右侧检测结果区域
        result_widget = QWidget()
        result_layout = QVBoxLayout()
        result_widget.setLayout(result_layout)
        # 模式指示器
        self.mode_indicator = QLabel("<b>当前模式: 未分析</b>")
        self.mode_indicator.setStyleSheet("background-color: #f0f0f0; padding: 5px; border-radius: 4px;")
        result_layout.addWidget(self.mode_indicator)
        result_layout.addWidget(QLabel("<b>检测结果</b>"))
        # 检测结果表格
        self.result_table = QTableWidget()
        self.result_table.setColumnCount(5)
        self.result_table.setHorizontalHeaderLabels(["区块类型", "置信度", "X坐标", "Y坐标", "宽高"])
        self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.result_table.setAlternatingRowColors(True)
        self.result_table.setSelectionBehavior(QTableWidget.SelectRows)
        self.result_table.setEditTriggers(QTableWidget.NoEditTriggers)
        result_layout.addWidget(self.result_table)
        # 统计信息
        self.stats_label = QLabel("统计信息: 未分析")
        result_layout.addWidget(self.stats_label)
        content_splitter.addWidget(result_widget)
        content_splitter.setSizes([800, 600])
        main_layout.addWidget(content_splitter)
        # 设置默认文件路径
        default_path = "E:\\新建12.pdf"
        if os.path.exists(default_path):
            self.current_pdf_path = default_path
            self.file_path_label.setText(f"已选择: {default_path}")
            self.local_analysis_btn.setEnabled(True)
            self.global_analysis_btn.setEnabled(True)

    def save_parameters(self, parameters):
        """保存参数到配置文件"""
        try:
            # 创建配置目录（如果不存在）
            config_dir = os.path.join(os.path.expanduser("~"), ".pdf_layout_analyzer")
            os.makedirs(config_dir, exist_ok=True)
            # 配置文件路径
            config_file = os.path.join(config_dir, "config.json")
            # 保存参数
            with open(config_file, 'w', encoding='utf-8') as f:
                json.dump(parameters, f, ensure_ascii=False, indent=4)
            return True
        except Exception as e:
            print(f"保存参数失败: {str(e)}")
            return False

    def load_parameters(self):
        """从配置文件加载参数"""
        try:
            # 配置文件路径
            config_file = os.path.join(os.path.expanduser("~"), ".pdf_layout_analyzer", "config.json")
            # 检查配置文件是否存在
            if not os.path.exists(config_file):
                return None
            # 加载参数
            with open(config_file, 'r', encoding='utf-8') as f:
                parameters = json.load(f)
            return parameters
        except Exception as e:
            print(f"加载参数失败: {str(e)}")
            return None

    def update_model_info(self):
        """更新模型信息显示"""
        current_index = self.model_combo.currentIndex()
        if current_index >= 0:
            model_id = self.model_combo.itemData(current_index)
            model_info = self.available_models[model_id]
            info_text = f"mAP: {model_info['mAP']} | 大小: {model_info['size']}"
            if model_info["local"]:
                info_text += " | 已下载"
            else:
                info_text += " | 在线加载"
            self.model_info_label.setText(info_text)

    def select_pdf_file(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择PDF文件", "", "PDF文件 (*.pdf);;所有文件 (*)"
        )
        if file_path:
            self.current_pdf_path = file_path
            self.file_path_label.setText(f"已选择: {file_path}")
            self.local_analysis_btn.setEnabled(True)
            self.global_analysis_btn.setEnabled(True)
            self.reset_ui()

    def open_advanced_settings(self):
        """打开高级参数设置对话框"""
        dialog = AdvancedSettingsDialog(self)
        # 尝试加载上次保存的参数
        last_parameters = self.load_parameters()
        if last_parameters:
            # 设置对话框中的参数值
            dialog.set_parameters(last_parameters)
        if dialog.exec_() == QDialog.Accepted:
            # 获取参数设置
            self.parameters = dialog.get_parameters()
            if self.parameters:
                # 保存参数
                self.save_parameters(self.parameters)
                QMessageBox.information(self, "参数设置", "高级参数已成功设置")
            else:
                QMessageBox.warning(self, "参数设置", "参数设置失败，请检查输入")

    def start_local_analysis(self):
        """开始局部分析（测试分析）"""
        if not self.current_pdf_path:
            QMessageBox.warning(self, "警告", "请先选择PDF文件")
            return
        # 获取PDF总页数
        try:
            doc = fitz.open(self.current_pdf_path)
            total_pages = len(doc)
            doc.close()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"无法读取PDF文件: {str(e)}")
            return
        # 显示页面测试对话框
        test_dialog = PageTestDialog(total_pages, self)
        if test_dialog.exec_() != QDialog.Accepted:
            return
        # 获取测试页面范围
        test_pages = test_dialog.get_test_pages()
        # 重置UI
        self.reset_ui()
        # 显示进度区域
        self.progress_group.setVisible(True)
        self.progress_bar.setVisible(True)
        self.progress_detail_label.setVisible(True)
        self.progress_bar.setValue(0)
        self.progress_detail_label.setText("准备开始局部分析...")
        self.local_analysis_btn.setEnabled(False)
        self.global_analysis_btn.setEnabled(False)
        self.file_btn.setEnabled(False)
        # 设置测试模式
        self.is_test_mode = True
        self.mode_indicator.setText("<b>当前模式: 局部分析（测试）</b>")
        self.mode_indicator.setStyleSheet(
            "background-color: #fff3cd; padding: 5px; border-radius: 4px; color: #856404;")
        # 获取参数设置
        parameters = getattr(self, 'parameters', {})
        # 获取当前选中的模型
        current_index = self.model_combo.currentIndex()
        model_id = self.model_combo.itemData(current_index)
        model_info = self.available_models[model_id]
        # 创建并启动工作线程
        self.worker_thread = LayoutAnalyzerThread(
            self.current_pdf_path,
            model_id,
            model_info["path"] if model_info["local"] else None,
            img_size=parameters.get('img_size'),
            threshold=parameters.get('threshold'),
            layout_unclip_ratio=parameters.get('layout_unclip_ratio'),
            layout_merge_bboxes_mode=parameters.get('layout_merge_bboxes_mode'),
            # 新增参数（仅在基础代码中体现，不在UI中显示）
            device="cpu",  # CPU设备
            enable_hpi=False,  # 不启用高性能推理
            use_tensorrt=False,  # 不使用TensorRT
            precision="fp32",  # 使用FP32精度
            enable_mkldnn=True,  # 启用MKL-DNN加速
            mkldnn_cache_capacity=10,  # MKL-DNN缓存容量
            cpu_threads=6,  # CPU线程数
            layout_nms=True,  # 使用NMS后处理
            page_range=test_pages  # 指定测试页面范围
        )
        self.worker_thread.progress_updated.connect(self.update_progress)
        self.worker_thread.analysis_complete.connect(self.local_analysis_finished)
        self.worker_thread.error_occurred.connect(self.show_error)
        self.worker_thread.start()

    def start_global_analysis(self):
        """开始全局分析"""
        if not self.current_pdf_path:
            QMessageBox.warning(self, "警告", "请先选择PDF文件")
            return
        # 重置UI
        self.reset_ui()
        # 显示进度区域
        self.progress_group.setVisible(True)
        self.progress_bar.setVisible(True)
        self.progress_detail_label.setVisible(True)
        self.progress_bar.setValue(0)
        self.progress_detail_label.setText("准备开始全局分析...")
        self.local_analysis_btn.setEnabled(False)
        self.global_analysis_btn.setEnabled(False)
        self.file_btn.setEnabled(False)
        # 设置全局分析模式
        self.is_test_mode = False
        self.mode_indicator.setText("<b>当前模式: 全局分析</b>")
        self.mode_indicator.setStyleSheet(
            "background-color: #d4edda; padding: 5px; border-radius: 4px; color: #155724;")
        # 获取参数设置
        parameters = getattr(self, 'parameters', {})
        # 获取当前选中的模型
        current_index = self.model_combo.currentIndex()
        model_id = self.model_combo.itemData(current_index)
        model_info = self.available_models[model_id]
        # 创建并启动工作线程
        self.worker_thread = LayoutAnalyzerThread(
            self.current_pdf_path,
            model_id,
            model_info["path"] if model_info["local"] else None,
            img_size=parameters.get('img_size'),
            threshold=parameters.get('threshold'),
            layout_unclip_ratio=parameters.get('layout_unclip_ratio'),
            layout_merge_bboxes_mode=parameters.get('layout_merge_bboxes_mode'),
            # 新增参数（仅在基础代码中体现，不在UI中显示）
            device="cpu",  # CPU设备
            enable_hpi=False,  # 不启用高性能推理
            use_tensorrt=False,  # 不使用TensorRT
            precision="fp32",  # 使用FP32精度
            enable_mkldnn=True,  # 启用MKL-DNN加速
            mkldnn_cache_capacity=10,  # MKL-DNN缓存容量
            cpu_threads=6,  # CPU线程数
            layout_nms=True  # 使用NMS后处理
            # 不指定page_range，表示处理所有页面
        )
        self.worker_thread.progress_updated.connect(self.update_progress)
        self.worker_thread.analysis_complete.connect(self.global_analysis_finished)
        self.worker_thread.error_occurred.connect(self.show_error)
        self.worker_thread.start()

    def local_analysis_finished(self, results, all_detections):
        """局部分析完成"""
        self.test_results = results['pages']
        self.test_all_detections = all_detections
        self.current_page = 0
        # 更新UI
        # 隐藏进度区域
        self.progress_group.setVisible(False)
        self.progress_bar.setVisible(False)
        self.progress_detail_label.setVisible(False)
        self.local_analysis_btn.setEnabled(True)
        self.global_analysis_btn.setEnabled(True)
        self.file_btn.setEnabled(True)
        # 更新页面导航
        total_pages = len(self.test_results)
        self.page_label.setText(f"第 {self.current_page + 1} 页，共 {total_pages} 页 (测试)")
        # 更新页面下拉框
        self.page_combo.clear()
        for i in range(total_pages):
            # 显示原始页码
            original_page_idx = self.test_results[i]['page_index']
            self.page_combo.addItem(f"第 {original_page_idx + 1} 页")
        self.page_combo.setCurrentIndex(self.current_page)
        self.prev_btn.setEnabled(self.current_page > 0)
        self.next_btn.setEnabled(self.current_page < total_pages - 1)
        # 显示当前页
        self.display_current_page()
        # 更新统计信息
        self.update_test_statistics()
        # 启用预览分割线按钮
        self.preview_dividers_btn.setEnabled(True)
        # 提示用户这是测试结果 - 修复字符串编码问题
        QMessageBox.information(self, "局部分析完成",
                                u"已成功完成对 {} 页的测试分析。\n\n"
                                u"您可以查看标注效果，如果满意，请点击\"全局分析\"按钮处理全部页面。".format(total_pages))

    def global_analysis_finished(self, results, all_detections):
        """全局分析完成"""
        self.analysis_results = results['pages']
        self.all_detections = all_detections
        self.current_page = 0
        # 更新UI
        # 隐藏进度区域
        self.progress_group.setVisible(False)
        self.progress_bar.setVisible(False)
        self.progress_detail_label.setVisible(False)
        self.local_analysis_btn.setEnabled(True)
        self.global_analysis_btn.setEnabled(True)
        self.file_btn.setEnabled(True)
        self.export_image_btn.setEnabled(True)
        self.export_pdf_btn.setEnabled(True)
        self.column_crop_btn.setEnabled(True)
        self.image_mask_btn.setEnabled(True)
        # 更新页面导航
        total_pages = len(self.analysis_results)
        self.page_label.setText(f"第 {self.current_page + 1} 页，共 {total_pages} 页")
        # 更新页面下拉框
        self.page_combo.clear()
        for i in range(total_pages):
            self.page_combo.addItem(f"第 {i + 1} 页")
        self.page_combo.setCurrentIndex(self.current_page)
        self.prev_btn.setEnabled(self.current_page > 0)
        self.next_btn.setEnabled(self.current_page < total_pages - 1)
        # 显示当前页
        self.display_current_page()
        # 更新统计信息
        self.update_statistics()
        # 启用预览分割线按钮
        self.preview_dividers_btn.setEnabled(True)

    def update_progress(self, value, text=""):
        """更新进度条和详细信息"""
        self.progress_bar.setValue(value)
        self.progress_bar.setVisible(True)
        # 更新进度条格式
        self.progress_bar.setFormat(f"{value}%")
        # 更新详细信息标签
        if text:
            self.progress_detail_label.setText(text)
            self.progress_detail_label.setVisible(True)
        else:
            self.progress_detail_label.setVisible(False)

    def show_error(self, message):
        self.progress_group.setVisible(False)
        self.progress_bar.setVisible(False)
        self.progress_detail_label.setVisible(False)
        self.local_analysis_btn.setEnabled(True)
        self.global_analysis_btn.setEnabled(True)
        self.file_btn.setEnabled(True)
        QMessageBox.critical(self, "错误", message)

    def reset_ui(self):
        self.original_image_label.set_image(np.zeros((100, 100, 3), dtype=np.uint8))
        self.annotated_image_label.set_image(np.zeros((100, 100, 3), dtype=np.uint8))
        self.result_table.setRowCount(0)
        self.page_label.setText("第 0 页，共 0 页")
        self.page_combo.clear()
        self.prev_btn.setEnabled(False)
        self.next_btn.setEnabled(False)
        self.stats_label.setText("统计信息: 未分析")
        self.export_image_btn.setEnabled(False)
        self.export_pdf_btn.setEnabled(False)
        self.column_crop_btn.setEnabled(False)
        self.image_mask_btn.setEnabled(False)
        self.preview_dividers_btn.setEnabled(False)
        self.mode_indicator.setText("<b>当前模式: 未分析</b>")
        self.mode_indicator.setStyleSheet("background-color: #f0f0f0; padding: 5px; border-radius: 4px;")
        # 隐藏进度区域
        self.progress_group.setVisible(False)
        self.progress_bar.setVisible(False)
        self.progress_detail_label.setVisible(False)
        # 重置分割线信息
        self.current_dividers = []
        self.current_skew_angle = 0

    def display_current_page(self):
        """显示当前页面"""
        if self.is_test_mode and self.test_results:
            # 显示测试结果
            if self.current_page >= len(self.test_results):
                return
            page_data = self.test_results[self.current_page]
        elif not self.is_test_mode and self.analysis_results:
            # 显示全局分析结果
            if self.current_page >= len(self.analysis_results):
                return
            page_data = self.analysis_results[self.current_page]
        else:
            return
        # 显示原始图像
        original_img = page_data['image']
        self.original_image_label.set_image(original_img)
        # 显示标注图像
        annotated_img = self.create_annotated_image(original_img, page_data['detections'])
        self.annotated_image_label.set_image(annotated_img)
        # 更新结果表格
        self.update_result_table(page_data['detections'])

    def create_annotated_image(self, img_array, detections, dividers=None, skew_angle=0):
        # 创建图像副本
        annotated_img = img_array.copy()
        # 确保图像是BGR格式（OpenCV默认）
        if len(annotated_img.shape) == 2:  # 灰度图
            annotated_img = cv2.cvtColor(annotated_img, cv2.COLOR_GRAY2BGR)
        elif annotated_img.shape[2] == 4:  # RGBA
            annotated_img = cv2.cvtColor(annotated_img, cv2.COLOR_RGBA2BGR)
        elif annotated_img.shape[2] == 3 and not np.array_equal(annotated_img[0, 0], img_array[0, 0]):  # 可能是RGB
            annotated_img = cv2.cvtColor(annotated_img, cv2.COLOR_RGB2BGR)
        # 为不同区块类型定义颜色
        colors = {
            # 基础文本元素
            'doc_title': (0, 0, 255),  # 红色 - 文档标题
            'paragraph_title': (255, 0, 128),  # 玫瑰红 - 段落标题
            'text': (0, 255, 0),  # 绿色 - 文本
            'number': (128, 128, 128),  # 灰色 - 页码
            # 文档结构元素
            'abstract': (128, 128, 0),  # 橄榄色 - 摘要
            'content': (64, 128, 0),  # 深橄榄绿 - 目录
            'reference': (42, 42, 165),  # 深蓝 - 参考文献
            'footnote': (192, 192, 192),  # 银色 - 脚注
            'header': (255, 192, 203),  # 粉色 - 页眉
            'footer': (173, 216, 230),  # 浅蓝 - 页脚
            # 特殊内容元素
            'algorithm': (75, 0, 130),  # 靛蓝 - 算法
            'formula': (255, 255, 0),  # 黄色 - 公式
            'formula_number': (128, 0, 255),  # 紫罗兰 - 公式编号
            # 图像和表格元素
            'image': (255, 128, 0),  # 深橙 - 图像
            'figure_title': (255, 0, 64),  # 深玫瑰红 - 图像标题
            'chart': (0, 128, 255),  # 天蓝 - 图表
            'chart_title': (0, 255, 128),  # 春绿色 - 图表标题
            'table': (0, 0, 192),  # 深红色 - 表格
            'table_title': (128, 255, 255),  # 浅青色 - 表格标题
            # 其他元素
            'seal': (128, 0, 128),  # 深紫色 - 印章
            'header_image': (192, 255, 62),  # 酸橙绿 - 页眉图像
            'footer_image': (62, 255, 192),  # 蓝绿 - 页脚图像
            'aside_text': (128, 64, 0),  # 棕色 - 侧栏文本
        }
        # 绘制边界框
        for detection in detections:
            label = detection['label']
            coords = detection['coordinates']
            x1, y1, x2, y2 = map(int, coords)
            # 获取颜色
            color = colors.get(label, (255, 255, 255))
            # 绘制矩形
            cv2.rectangle(annotated_img, (x1, y1), (x2, y2), color, 2)
            # 添加标签
            label_text = f"{label} ({detection['confidence']:.2f})"
            # 确保文本不会超出图像边界
            text_y = max(y1 - 10, 20)
            cv2.putText(
                annotated_img,
                label_text,
                (x1, text_y),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.5,
                color,
                1
            )
        # 如果提供了分割线位置，绘制分割线
        if dividers:
            height, width = annotated_img.shape[:2]
            for divider_info in dividers:
                if isinstance(divider_info, tuple) and len(divider_info) == 2:
                    # 新格式：(位置, 角度)
                    divider, angle = divider_info
                else:
                    # 旧格式：只有位置
                    divider = divider_info
                    angle = 0

                x = int(divider)

                # 如果有倾斜角度，计算倾斜分割线
                if angle != 0:
                    # 将角度转换为弧度
                    angle_rad = np.radians(angle)

                    # 计算分割线在图像顶部和底部的x坐标
                    y_top = 0
                    y_bottom = height
                    x_top = x - y_top * np.tan(angle_rad)
                    x_bottom = x - y_bottom * np.tan(angle_rad)

                    # 确保分割线在图像范围内
                    if (0 <= x_top <= width or 0 <= x_bottom <= width):
                        # 如果分割线部分在图像外，计算与图像边界的交点
                        if x_top < 0:
                            # 计算与左边界的交点
                            y_intersect = x / np.tan(angle_rad)
                            if 0 <= y_intersect <= height:
                                x_top = 0
                                y_top = y_intersect
                        elif x_top > width:
                            # 计算与右边界的交点
                            y_intersect = (width - x) / np.tan(angle_rad)
                            if 0 <= y_intersect <= height:
                                x_top = width
                                y_top = y_intersect

                        if x_bottom < 0:
                            # 计算与左边界的交点
                            y_intersect = x / np.tan(angle_rad)
                            if 0 <= y_intersect <= height:
                                x_bottom = 0
                                y_bottom = y_intersect
                        elif x_bottom > width:
                            # 计算与右边界的交点
                            y_intersect = (width - x) / np.tan(angle_rad)
                            if 0 <= y_intersect <= height:
                                x_bottom = width
                                y_bottom = y_intersect

                        # 绘制倾斜分割线
                        cv2.line(annotated_img, (int(x_top), int(y_top)), (int(x_bottom), int(y_bottom)), (255, 0, 0),
                                 2)  # 红色分割线
                else:
                    # 垂直分割线
                    cv2.line(annotated_img, (x, 0), (x, height), (255, 0, 0), 2)  # 红色分割线
        return annotated_img

    def update_result_table(self, detections):
        self.result_table.setRowCount(len(detections))
        for i, detection in enumerate(detections):
            # 区块类型
            self.result_table.setItem(i, 0, QTableWidgetItem(detection['label']))
            # 置信度
            conf_item = QTableWidgetItem(f"{detection['confidence']:.4f}")
            conf_item.setTextAlignment(Qt.AlignCenter)
            self.result_table.setItem(i, 1, conf_item)
            # 坐标
            coords = detection['coordinates']
            x1, y1, x2, y2 = map(int, coords)
            self.result_table.setItem(i, 2, QTableWidgetItem(f"{x1}, {x2}"))
            self.result_table.setItem(i, 3, QTableWidgetItem(f"{y1}, {y2}"))
            # 宽高
            width = x2 - x1
            height = y2 - y1
            self.result_table.setItem(i, 4, QTableWidgetItem(f"{width} × {height}"))
            # 设置行高
            self.result_table.setRowHeight(i, 25)

    def update_statistics(self):
        """更新全局分析的统计信息"""
        if not self.all_detections:
            self.stats_label.setText("统计信息: 未分析")
            return
        # 统计各类型区块数量
        type_counts = {}
        for detection in self.all_detections:
            label = detection['label']
            type_counts[label] = type_counts.get(label, 0) + 1
        # 构建统计文本
        stats_text = "统计信息: "
        stats_items = []
        for label, count in sorted(type_counts.items()):
            stats_items.append(f"{label}: {count}")
        stats_text += " | ".join(stats_items)
        self.stats_label.setText(stats_text)

    def update_test_statistics(self):
        """更新测试分析的统计信息"""
        if not hasattr(self, 'test_all_detections') or not self.test_all_detections:
            self.stats_label.setText("统计信息: 未分析")
            return
        # 统计各类型区块数量
        type_counts = {}
        for detection in self.test_all_detections:
            label = detection['label']
            type_counts[label] = type_counts.get(label, 0) + 1
        # 构建统计文本
        stats_text = "统计信息 (测试): "
        stats_items = []
        for label, count in sorted(type_counts.items()):
            stats_items.append(f"{label}: {count}")
        stats_text += " | ".join(stats_items)
        self.stats_label.setText(stats_text)

    def on_page_changed(self, index):
        """页面下拉框改变"""
        if index >= 0:
            if self.is_test_mode and index < len(self.test_results):
                self.current_page = index
            elif not self.is_test_mode and index < len(self.analysis_results):
                self.current_page = index
            self.update_page_display()

    def prev_page(self):
        """上一页"""
        if self.current_page > 0:
            self.current_page -= 1
            self.update_page_display()

    def next_page(self):
        """下一页"""
        if self.is_test_mode and self.current_page < len(self.test_results) - 1:
            self.current_page += 1
            self.update_page_display()
        elif not self.is_test_mode and self.current_page < len(self.analysis_results) - 1:
            self.current_page += 1
            self.update_page_display()

    def update_page_display(self):
        """更新页面显示"""
        # 更新页面标签
        if self.is_test_mode:
            total_pages = len(self.test_results)
            original_page_idx = self.test_results[self.current_page]['page_index']
            self.page_label.setText(f"第 {original_page_idx + 1} 页，共 {total_pages} 页 (测试)")
        else:
            total_pages = len(self.analysis_results)
            self.page_label.setText(f"第 {self.current_page + 1} 页，共 {total_pages} 页")
        # 更新页面下拉框
        self.page_combo.setCurrentIndex(self.current_page)
        # 更新按钮状态
        self.prev_btn.setEnabled(self.current_page > 0)
        if self.is_test_mode:
            self.next_btn.setEnabled(self.current_page < len(self.test_results) - 1)
        else:
            self.next_btn.setEnabled(self.current_page < len(self.analysis_results) - 1)
        # 显示当前页
        self.display_current_page()

    # 图像缩放和拖动功能
    def on_zoom_changed(self, value):
        """缩放滑块值改变"""
        scale = value / 100.0
        # 获取当前标签页
        current_tab = self.image_tabs.currentWidget()
        if current_tab == self.original_image_label:
            self.original_image_label.set_scale(scale)
        elif current_tab == self.annotated_image_label:
            self.annotated_image_label.set_scale(scale)

    def zoom_in(self):
        """放大图像"""
        # 获取当前标签页
        current_tab = self.image_tabs.currentWidget()
        if current_tab == self.original_image_label:
            self.original_image_label.zoom_in()
        elif current_tab == self.annotated_image_label:
            self.annotated_image_label.zoom_in()
        # 更新滑块值
        self.zoom_slider.setValue(int(self.zoom_slider.value() * 1.25))

    def zoom_out(self):
        """缩小图像"""
        # 获取当前标签页
        current_tab = self.image_tabs.currentWidget()
        if current_tab == self.original_image_label:
            self.original_image_label.zoom_out()
        elif current_tab == self.annotated_image_label:
            self.annotated_image_label.zoom_out()
        # 更新滑块值
        self.zoom_slider.setValue(int(self.zoom_slider.value() * 0.8))

    def fit_to_window(self):
        """适应窗口大小"""
        # 获取当前标签页
        current_tab = self.image_tabs.currentWidget()
        if current_tab == self.original_image_label:
            self.original_image_label.fit_to_window()
        elif current_tab == self.annotated_image_label:
            self.annotated_image_label.fit_to_window()
        # 更新滑块值
        self.zoom_slider.setValue(100)

    # 导出区块图像功能
    def export_block_images(self):
        """导出区块图像"""
        # 确定使用哪个结果集
        if self.is_test_mode:
            if not self.test_results:
                QMessageBox.warning(self, "警告", "没有可导出的内容")
                return
            analysis_results = self.test_results
        else:
            if not self.analysis_results:
                QMessageBox.warning(self, "警告", "没有可导出的内容")
                return
            analysis_results = self.analysis_results
        # 获取所有区块类型
        block_types = set()
        for page in analysis_results:
            for detection in page['detections']:
                block_types.add(detection['label'])
        block_types = sorted(list(block_types))
        # 显示图像导出选项对话框
        export_dialog = ImageExportOptionsDialog(block_types, self)
        if export_dialog.exec_() != QDialog.Accepted:
            return
        export_options = export_dialog.get_export_options()
        # 检查是否选择了区块类型
        if not export_options["selected_types"]:
            QMessageBox.warning(self, "警告", "请至少选择一种区块类型")
            return
        # 检查是否选择了输出目录
        if not export_options["output_dir"]:
            QMessageBox.warning(self, "警告", "请选择输出目录")
            return
        # 确定要处理的页面
        if export_options["page_selection"] == "all":
            pages_to_process = range(len(analysis_results))
        elif export_options["page_selection"] == "current":
            pages_to_process = [self.current_page]
        else:
            # 显示页面选择对话框
            page_dialog = PageSelectionDialog(len(analysis_results), self.current_page, self)
            if page_dialog.exec_() != QDialog.Accepted:
                return
            selected_pages = page_dialog.get_selected_pages()
            if selected_pages is None:
                QMessageBox.warning(self, "警告", "请至少选择一页")
                return
            if selected_pages == "all":
                pages_to_process = range(len(analysis_results))
            elif selected_pages == "current":
                pages_to_process = [self.current_page]
            else:
                pages_to_process = selected_pages
        # 添加当前页面和选择的页面到导出选项
        export_options["current_page"] = self.current_page
        export_options["selected_pages"] = pages_to_process
        # 创建输出目录结构
        os.makedirs(export_options["output_dir"], exist_ok=True)
        # 为每种区块类型创建子目录
        for block_type in export_options["selected_types"]:
            type_dir = os.path.join(export_options["output_dir"], block_type)
            os.makedirs(type_dir, exist_ok=True)
        # 显示进度对话框
        progress_dialog = QDialog(self)
        progress_dialog.setWindowTitle("导出进度")
        progress_dialog.setFixedSize(400, 150)
        progress_layout = QVBoxLayout()
        progress_label = QLabel("准备导出区块图像...")
        progress_label.setAlignment(Qt.AlignCenter)
        progress_layout.addWidget(progress_label)
        progress_bar = QProgressBar()
        progress_layout.addWidget(progress_bar)
        # 添加取消按钮
        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(self.cancel_image_export)
        progress_layout.addWidget(cancel_btn)
        progress_dialog.setLayout(progress_layout)
        progress_dialog.show()
        # 创建并启动导出线程
        self.image_export_thread = ImageExportThread(
            analysis_results,
            export_options
        )
        self.image_export_thread.progress_updated.connect(
            lambda value, text: (
                progress_bar.setValue(value),
                progress_label.setText(text)
            )
        )
        self.image_export_thread.export_complete.connect(
            lambda result: (
                progress_dialog.close(),
                QMessageBox.information(self, "导出成功", f"区块图像已成功导出到:\n{result}")
            )
        )
        self.image_export_thread.export_failed.connect(
            lambda error: (
                progress_dialog.close(),
                QMessageBox.critical(self, "导出失败", f"导出区块图像时出错:\n{error}")
            )
        )
        self.image_export_thread.start()

    def cancel_image_export(self):
        """取消图像导出操作"""
        if hasattr(self, 'image_export_thread') and self.image_export_thread.isRunning():
            self.image_export_thread.stop()
            self.image_export_thread.wait()

    # 导出标注PDF功能
    def export_annotated_pdf(self):
        """导出标注PDF"""
        # 确定使用哪个结果集
        if self.is_test_mode:
            if not self.test_results:
                QMessageBox.warning(self, "警告", "没有可导出的内容")
                return
            analysis_results = self.test_results
        else:
            if not self.analysis_results:
                QMessageBox.warning(self, "警告", "没有可导出的内容")
                return
            analysis_results = self.analysis_results
        # 显示PDF导出选项对话框
        export_dialog = PDFExportOptionsDialog(self)
        if export_dialog.exec_() != QDialog.Accepted:
            return
        export_options = export_dialog.get_export_options()
        # 检查是否选择了输出文件
        if not export_options["output_file"]:
            QMessageBox.warning(self, "警告", "请选择输出文件")
            return
        # 确定要处理的页面
        if export_options["page_selection"] == "all":
            pages_to_process = range(len(analysis_results))
        elif export_options["page_selection"] == "current":
            pages_to_process = [self.current_page]
        else:
            # 显示页面选择对话框
            page_dialog = PageSelectionDialog(len(analysis_results), self.current_page, self)
            if page_dialog.exec_() != QDialog.Accepted:
                return
            selected_pages = page_dialog.get_selected_pages()
            if selected_pages is None:
                QMessageBox.warning(self, "警告", "请至少选择一页")
                return
            if selected_pages == "all":
                pages_to_process = range(len(analysis_results))
            elif selected_pages == "current":
                pages_to_process = [self.current_page]
            else:
                pages_to_process = selected_pages
        # 添加当前页面和选择的页面到导出选项
        export_options["current_page"] = self.current_page
        export_options["selected_pages"] = pages_to_process
        export_options["is_test_mode"] = self.is_test_mode  # 添加测试模式标记
        # 显示进度对话框
        progress_dialog = QDialog(self)
        progress_dialog.setWindowTitle("导出进度")
        progress_dialog.setFixedSize(400, 150)
        progress_layout = QVBoxLayout()
        progress_label = QLabel("准备导出PDF...")
        progress_label.setAlignment(Qt.AlignCenter)
        progress_layout.addWidget(progress_label)
        progress_bar = QProgressBar()
        progress_layout.addWidget(progress_bar)
        # 添加取消按钮
        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(self.cancel_pdf_export)
        progress_layout.addWidget(cancel_btn)
        progress_dialog.setLayout(progress_layout)
        progress_dialog.show()
        # 创建并启动导出线程
        self.pdf_export_thread = PDFExportThread(
            analysis_results,
            export_options,
            file_path=export_options["output_file"]
        )
        self.pdf_export_thread.progress_updated.connect(
            lambda value, text: (
                progress_bar.setValue(value),
                progress_label.setText(text)
            )
        )
        self.pdf_export_thread.export_complete.connect(
            lambda result: (
                progress_dialog.close(),
                QMessageBox.information(self, "导出成功", f"PDF已成功导出到:\n{result}")
            )
        )
        self.pdf_export_thread.export_failed.connect(
            lambda error: (
                progress_dialog.close(),
                QMessageBox.critical(self, "导出失败", f"导出PDF时出错:\n{error}")
            )
        )
        self.pdf_export_thread.start()

    def cancel_pdf_export(self):
        """取消PDF导出操作"""
        if hasattr(self, 'pdf_export_thread') and self.pdf_export_thread.isRunning():
            self.pdf_export_thread.stop()
            self.pdf_export_thread.wait()

    # 显示栏位分割线预览功能
    def show_column_dividers_preview(self):
        """显示栏位分割线预览"""
        # 确定使用哪个结果集
        if self.is_test_mode:
            if not self.test_results:
                QMessageBox.warning(self, "警告", "没有可预览的内容")
                return
            page_data = self.test_results[self.current_page]
        else:
            if not self.analysis_results:
                QMessageBox.warning(self, "警告", "没有可预览的内容")
                return
            page_data = self.analysis_results[self.current_page]

        # 创建工作器
        worker = ColumnCropWorker(None)

        # 计算分割线位置
        column_count = 2  # 默认双栏
        dividers = worker.calculate_optimal_dividers(page_data, column_count)

        # 保存分割线位置和倾斜角度
        self.current_dividers = dividers

        # 获取页面图像
        img = page_data['image'].copy()

        # 在图像上绘制分割线
        annotated_img = self.create_annotated_image(img, page_data['detections'], dividers)

        # 显示预览图像
        self.annotated_image_label.set_image(annotated_img)

        # 切换到标注图像标签页
        self.image_tabs.setCurrentIndex(1)  # 标注图像标签页的索引

        # 提示用户
        QMessageBox.information(self, "分割线预览",
                                "已显示智能分栏分割线预览。\n\n"
                                "红色线条表示计算出的最优分割线位置，"
                                "这些分割线会避免穿过文本区块，确保分栏裁切时文本不会被切割。")

    # 分栏裁切功能
    def column_crop(self):
        """分栏裁切"""
        # 确定使用哪个结果集
        if self.is_test_mode:
            if not self.test_results:
                QMessageBox.warning(self, "警告", "没有可裁切的内容")
                return
            analysis_results = self.test_results
        else:
            if not self.analysis_results:
                QMessageBox.warning(self, "警告", "没有可裁切的内容")
                return
            analysis_results = self.analysis_results
        # 获取所有区块类型
        block_types = set()
        for page in analysis_results:
            for detection in page['detections']:
                block_types.add(detection['label'])
        block_types = sorted(list(block_types))
        # 显示分栏裁切选项对话框
        crop_dialog = ColumnCropDialog(block_types, self)
        if crop_dialog.exec_() != QDialog.Accepted:
            return
        crop_options = crop_dialog.get_crop_options()
        # 检查是否选择了输出目录
        if not crop_options["output_dir"]:
            QMessageBox.warning(self, "警告", "请选择输出目录")
            return
        # 确定要处理的页面
        if crop_options["page_selection"] == "all":
            pages_to_process = range(len(analysis_results))
        elif crop_options["page_selection"] == "current":
            pages_to_process = [self.current_page]
        else:
            pages_to_process = range(len(analysis_results))  # 默认所有页面
        # 添加当前页面和选择的页面到裁切选项
        crop_options["current_page"] = self.current_page
        crop_options["selected_pages"] = pages_to_process
        # 创建输出目录
        os.makedirs(crop_options["output_dir"], exist_ok=True)
        # 显示进度对话框
        progress_dialog = QDialog(self)
        progress_dialog.setWindowTitle("分栏裁切进度")
        progress_dialog.setFixedSize(400, 150)
        progress_layout = QVBoxLayout()
        progress_label = QLabel("准备分栏裁切...")
        progress_label.setAlignment(Qt.AlignCenter)
        progress_layout.addWidget(progress_label)
        progress_bar = QProgressBar()
        progress_layout.addWidget(progress_bar)
        # 添加取消按钮
        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(self.cancel_column_crop)
        progress_layout.addWidget(cancel_btn)
        progress_dialog.setLayout(progress_layout)
        progress_dialog.show()
        # 创建并启动分栏裁切线程
        self.column_crop_thread = ColumnCropThread(
            analysis_results,
            crop_options
        )
        self.column_crop_thread.progress_updated.connect(
            lambda value, text: (
                progress_bar.setValue(value),
                progress_label.setText(text)
            )
        )
        self.column_crop_thread.crop_complete.connect(
            lambda result: (
                progress_dialog.close(),
                QMessageBox.information(self, "裁切完成", f"分栏裁切已完成，结果保存在:\n{result}")
            )
        )
        self.column_crop_thread.crop_failed.connect(
            lambda error: (
                progress_dialog.close(),
                QMessageBox.critical(self, "裁切失败", f"分栏裁切时出错:\n{error}")
            )
        )
        self.column_crop_thread.start()

    def cancel_column_crop(self):
        """取消分栏裁切操作"""
        if hasattr(self, 'column_crop_thread') and self.column_crop_thread.isRunning():
            self.column_crop_thread.stop()
            self.column_crop_thread.wait()

    # 图像掩码功能
    def image_mask(self):
        """图像掩码"""
        # 确定使用哪个结果集
        if self.is_test_mode:
            if not self.test_results:
                QMessageBox.warning(self, "警告", "没有可处理的内容")
                return
            analysis_results = self.test_results
        else:
            if not self.analysis_results:
                QMessageBox.warning(self, "警告", "没有可处理的内容")
                return
            analysis_results = self.analysis_results
        # 获取所有区块类型
        block_types = set()
        for page in analysis_results:
            for detection in page['detections']:
                block_types.add(detection['label'])
        block_types = sorted(list(block_types))
        # 显示图像掩码选项对话框
        mask_dialog = ImageMaskDialog(block_types, self)
        if mask_dialog.exec_() != QDialog.Accepted:
            return
        mask_options = mask_dialog.get_mask_options()
        # 检查是否选择了区块类型
        if not mask_options["mosaic_types"]:
            QMessageBox.warning(self, "警告", "请至少选择一种区块类型进行马赛克处理")
            return
        # 检查是否选择了输出目录
        if not mask_options["output_dir"]:
            QMessageBox.warning(self, "警告", "请选择输出目录")
            return
        # 确定要处理的页面
        if mask_options["page_selection"] == "all":
            pages_to_process = range(len(analysis_results))
        elif mask_options["page_selection"] == "current":
            pages_to_process = [self.current_page]
        else:
            # 显示页面选择对话框
            page_dialog = PageSelectionDialog(len(analysis_results), self.current_page, self)
            if page_dialog.exec_() != QDialog.Accepted:
                return
            selected_pages = page_dialog.get_selected_pages()
            if selected_pages is None:
                QMessageBox.warning(self, "警告", "请至少选择一页")
                return
            if selected_pages == "all":
                pages_to_process = range(len(analysis_results))
            elif selected_pages == "current":
                pages_to_process = [self.current_page]
            else:
                pages_to_process = selected_pages
        # 添加当前页面和选择的页面到掩码选项
        mask_options["current_page"] = self.current_page
        mask_options["selected_pages"] = pages_to_process
        # 创建输出目录
        os.makedirs(mask_options["output_dir"], exist_ok=True)
        # 显示进度对话框
        progress_dialog = QDialog(self)
        progress_dialog.setWindowTitle("图像掩码进度")
        progress_dialog.setFixedSize(400, 150)
        progress_layout = QVBoxLayout()
        progress_label = QLabel("准备进行图像掩码处理...")
        progress_label.setAlignment(Qt.AlignCenter)
        progress_layout.addWidget(progress_label)
        progress_bar = QProgressBar()
        progress_layout.addWidget(progress_bar)
        # 添加取消按钮
        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(self.cancel_image_mask)
        progress_layout.addWidget(cancel_btn)
        progress_dialog.setLayout(progress_layout)
        progress_dialog.show()
        # 创建并启动图像掩码线程
        self.image_mask_thread = ImageMaskThread(
            analysis_results,
            mask_options
        )
        self.image_mask_thread.progress_updated.connect(
            lambda value, text: (
                progress_bar.setValue(value),
                progress_label.setText(text)
            )
        )
        self.image_mask_thread.mask_complete.connect(
            lambda result: (
                progress_dialog.close(),
                QMessageBox.information(self, "处理完成", f"图像掩码处理已完成，结果保存在:\n{result}")
            )
        )
        self.image_mask_thread.mask_failed.connect(
            lambda error: (
                progress_dialog.close(),
                QMessageBox.critical(self, "处理失败", f"图像掩码处理时出错:\n{error}")
            )
        )
        self.image_mask_thread.start()

    def cancel_image_mask(self):
        """取消图像掩码操作"""
        if hasattr(self, 'image_mask_thread') and self.image_mask_thread.isRunning():
            self.image_mask_thread.stop()
            self.image_mask_thread.wait()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    # 设置应用程序样式
    app.setStyle("Fusion")
    # 创建主窗口
    window = PDFLayoutAnalyzer()
    window.show()
    sys.exit(app.exec_())