Windows上的时候还有个acrobat可以转换和编辑PDF使用,到了麒麟上就啥也没有,有的都得要银子,本身每月就碎银几辆而已,实在穷的没办法,就自己搞一个吧。

一开始只搞了转换,分割,合并版本的,实在有失工具箱的名头,而且这种基础功能的软件一抓一大把,最主要的OCR识别和编辑,去水印这种全功能的才是刚需。

概述

PDF 全能工具是一款类似 Adobe Acrobat 的全功能 PDF 处理软件,面向国产操作系统(统信 UOS、银河麒麟等)和 Windows 平台,完全离线运行,无需联网。

核心特性

- **完全离线**:所有功能本地运行,无需上传文件,保障文档安全

- **全功能 PDF 编辑**:内容流级真实编辑,向 Acrobat 看齐

- **智能 OCR**:基于 PaddleOCR,支持中、英、日等 100+ 语言

- **版面分析**:自动识别标题、段落、表格、公式、图表,恢复阅读顺序

- **多格式转换**:支持 DOCX、TXT、HTML、Markdown、Excel、PPT、图片

- **国产系统适配**:支持 fcitx/ibus 输入法、中文字体、aarch64 架构

功能总览

| 类别 | 功能 |

| **编辑** | 文字编辑、添加文字、图片替换、OCR 识别、高亮/矩形标注、水印处理、页面管理、撤销 |

| **转换** | PDF → DOCX / TXT / HTML / Markdown / Excel / PPT / 图片 |

| **合并** | 多 PDF 合并、顺序调整 |

| **分割** | 每页一文件 / 按页数 / 按页面范围 |

| **OCR** | 单进程 / 多进程识别、页面范围选择、结果导出 |

| **版面分析** | 版面检测、表格识别、公式识别、阅读顺序恢复、图表分析 |

| **工具** | 页面旋转、加密、解密、添加水印、去除水印、清除密码 |

整体架构图

模块职责划分

| 模块 | 路径 | 职责 |

| **入口** | `pdftools/main.py` | QApplication 初始化、环境变量、字体、启动主窗口 |

| **配置** | `pdftools/config/settings.py` | 全局配置单例,读写 `~/.pdftools/config.json` |

| **引擎适配** | `pdftools/engines/` | 隔离 PaddleOCR / PP-Structure API 变化 |

| **服务层** | `pdftools/services/` | 业务逻辑(PDF 操作、转换、OCR、版面、编辑器) |

| **GUI** | `pdftools/gui/` | 用户界面、交互逻辑、异步 Worker |

| **模型管理** | `pdftools/models/model_manager.py` | 模型检查、下载、镜像源 |

| **工具** | `pdftools/utils/` | 文件、日志、内存、PDF 工具函数 |

双平台依赖矩阵

项目支持 **Windows(PySide2/Qt5)** 和 **Linux(PySide6/Qt6)** 两套依赖,针对国产系统优化。

#### Windows 依赖(`pdftools/requirements.txt`,Python 3.8 + Win7 兼容)

| 库 | 版本 | 用途 |

|-----|------|------|

| PyMuPDF | 1.23.8 | PDF 渲染、提取、编辑 |

| pypdf | 3.17.4 | PDF 合并/拆分/加密 |

| paddlepaddle | 2.6.2 | OCR 推理运行时 |

| paddleocr | 2.8.1 | OCR 文字识别 |

| pdf2docx | 0.5.8 | PDF 转 DOCX |

| python-docx | 1.1.2 | DOCX 生成 |

| python-pptx | 0.6.23 | PPT 生成 |

| Pillow | 9.5.0 | 图像处理 |

| **PySide2** | **5.15.2.1** | **GUI 框架(Qt5)** |

| markdown | 3.5.2 | Markdown 处理 |

| openpyxl | 3.1.5 | Excel 文件 |

| numpy | 1.22.4 | 数值计算 |

| pandas | 1.5.3 | 数据处理 |

| pypdfium2 | 4.30.0 | PDF 渲染备用 |

| opencv-python-headless | 4.8.1.78 | 图像处理(OCR) |

| shapely | 1.8.5 | 几何计算(表格) |

| lxml | 4.9.4 | XML 处理 |

| pyclipper | 1.3.0.post6 | 多边形裁剪 |

| PyYAML | 6.0.2 | YAML 配置 |

| tqdm | 4.66.5 | 进度条 |

| pyinstaller | 5.13.2 | 打包 |

#### Linux 依赖(`pdftools/requirements_linux.txt`,Python 3.10+,麒麟/UOS)

| 库 | 版本 | 用途 |

|-----|------|------|

| PyMuPDF | >=1.24.0 | PDF 渲染 |

| pypdf | >=5.0.0 | PDF 操作 |

| paddlepaddle | >=2.6.0 | OCR 运行时 |

| paddleocr | >=2.9.0 | OCR 识别 |

| pdf2docx | >=0.5.0 | PDF 转 DOCX |

| python-docx | >=1.1.0 | DOCX 生成 |

| Pillow | >=10.0.0 | 图像处理 |

| **PySide6** | **>=6.5.0** | **GUI 框架(Qt6)** |

| markdown | >=3.5.0 | Markdown 处理 |

| openpyxl | >=3.1.0 | Excel 文件 |

| numpy | >=1.24.0 | 数值计算 |

| pandas | >=2.0.0 | 数据处理 |

| pypdfium2 | >=4.0.0 | PDF 渲染备用 |

主要说一下OCR和编辑部分

OCRService(OCR 识别服务)

**文件**: `pdftools/services/ocr_service.py`

封装 `PaddleOCREngineAdapter`,支持单进程和多进程模式。

#### 核心方法

```python
class OCRService:
    def __init__(self, lang: str = 'ch', use_gpu: bool = None)
    
    @property
    def engine_adapter(self) -> OCREngineAdapter
    
    def is_available(self) -> bool
    
    def ocr_image(self, image_path: str) -> List[Dict]
    
    def ocr_pdf(
        self,
        pdf_path: str,
        page_numbers: List[int] = None      # None = 全部
    ) -> List[Dict]                          # [{'page': i, 'result': [...]}]
    
    def ocr_pdf_page_by_page(
        self,
        pdf_path: str,
        page_numbers: List[int] = None,
        progress_callback=None,              # callback(current, total)
        status_callback=None                 # callback(str)
    ) -> List[Dict]
    
    def ocr_pdf_multiprocess(
        self,
        pdf_path: str,
        page_numbers: List[int] = None,
        progress_callback=None
    ) -> List[Dict]                          # 多进程加速版
    
    def extract_text_from_ocr_result(
        self,
        ocr_result: List
    ) -> str                                 # 提取纯文本
```

 LayoutService(版面分析服务)

**文件**: `pdftools/services/layout_service.py`

基于 `PPStructureEngineAdapter`,支持版面检测、表格识别、公式识别、图表分析、阅读顺序恢复。

#### 核心方法

```python
class LayoutService:
    def __init__(self, lang: str = 'ch', use_gpu: bool = None)
    
    @property
    def engine_adapter(self) -> StructureEngineAdapter
    
    def is_available(self) -> bool
    
    def detect_layout(
        self,
        pdf_path: str,
        page_numbers: List[int] = None,
        progress_callback=None
    ) -> List[Dict]                          # [{'page': i, 'regions': [...]}]
    
    def detect_layout_fast(
        self,
        pdf_path: str,
        page_numbers: List[int] = None,
        progress_callback=None
    ) -> List[Dict]                          # PyMuPDF 内置快速版(精度较低)
    
    def recognize_tables(
        self,
        pdf_path: str,
        page_numbers: List[int] = None
    ) -> List[Dict]                          # [{'page': i, 'tables': [...]}]
    
    def export_tables_to_excel(
        self,
        pdf_path: str,
        output_path: str = None,
        progress_callback=None
    ) -> str
    
    def export_tables_to_markdown(
        self,
        pdf_path: str,
        output_path: str = None
    ) -> str
    
    def recognize_formulas(
        self,
        pdf_path: str,
        page_numbers: List[int] = None
    ) -> List[Dict]                          # [{'page': i, 'formulas': [{'content': 'LaTeX', ...}]}]
    
    def restore_reading_order(
        self,
        pdf_path: str,
        page_numbers: List[int] = None
    ) -> List[Dict]                          # [{'page': i, 'column_count': N, 'elements': [...]}]
    
    def analyze_charts(
        self,
        pdf_path: str,
        output_dir: str = None,              # 图表截图保存目录
        page_numbers: List[int] = None
    ) -> List[Dict]                          # [{'page': i, 'caption': ..., 'image_path': ...}]
    
    def pdf_to_markdown(
        self,
        pdf_path: str,
        output_path: str = None,
        use_ocr: bool = False
    ) -> str
```
EditorService(PDF 编辑器服务)

**文件**: `pdftools/services/editor_service.py`

**核心服务**,基于 PyMuPDF 实现内容流级真实编辑。

#### 类属性与初始化

```python
class EditorService:
    def __init__(self):
        self._doc = None                    # PyMuPDF 文档对象
        self._history = []                  # 撤销历史
        self._max_history = 20              # 最多 20 步
        self._ocr_cache: Dict[int, List[Dict]] = {}  # OCR 缓存 {page_num: [results]}
        self._analyzed_pages: set = set()   # 已分析的页面
        self._needs_full_save: bool = False # 完整保存标记

    @property
    def is_open(self) -> bool
    @property
    def page_count(self) -> int
    @property
    def pdf_path(self) -> str
    @property
    def history_count(self) -> int          # 可撤销步数
```

 页面分析与 OCR

```python
def is_page_analyzed(self, page_num: int) -> bool
def analyze_page(self, page_num: int, progress_callback=None) -> Dict
# 返回:{'ocr_items': N, 'text_blocks': N, 'image_regions': N}

def get_all_text_blocks(self, page_num: int) -> List[Dict]
# 返回所有文字块(PDF原生 + OCR缓存,含水印标记)

def get_editable_text_blocks(self, page_num: int) -> List[Dict]
# 排除水印的文字块

def get_watermark_blocks(self, page_num: int) -> List[Dict]
# 仅水印文字块

def clear_analysis_cache(self, page_num: int = None) -> None
```

#### 文字块结构

```python
{
    "bbox": (x0, y0, x1, y1),              # PDF 坐标系
    "text": str,
    "size": float,                          # 字号
    "font": str,                            # 字体名
    "color": int,                           # sRGB int
    "source": "pdf" | "ocr" | "watermark",
    "is_watermark": bool,
    "watermark_score": int,                 # 水印评分
}
```

#### 文字编辑

```python
def replace_text(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    new_text: str,
    font_size: float = None,                # None = 自动估算
    font_name: str = None,                  # 系统显示名或 PyMuPDF 内置名
    color: tuple = (0, 0, 0),               # RGB 0-1
    font_file: str = None                   # 字体文件路径(优先使用,避免冲突)
) -> bool
# 内容流级真实替换:redaction 删除原文字 → 相同位置写入新文字

def move_text(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    new_x: float,
    new_y: float,
    text: str,
    font_size: float = None,
    font_name: str = None,
    color: tuple = (0, 0, 0),
    font_file: str = None,
    source: str = "pdf"                     # "pdf" 或 "ocr"
) -> bool
# 真实移动:原位置删除 → 新坐标插入,保留格式属性

def add_text(
    self,
    page_num: int,
    point: Tuple[float, float],             # (x, y)
    text: str,
    font_size: float = 14,
    font_name: str = None,
    color: tuple = (0, 0, 0),
    font_file: str = None
) -> bool
```

#### 图片操作

```python
def get_images(self, page_num: int) -> List[Dict]
# [{'bbox': ..., 'width': ..., 'height': ..., 'xref': ...}]

def replace_image(self, page_num: int, img_index: int, new_image_path: str) -> bool
# 删除原图 XRef → 同位置插入新图

def add_image(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    image_path: str
) -> bool
```

#### OCR 识别

```python
def ocr_region(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float]
) -> List[Dict]
# 区域 OCR,坐标转换回 PDF 坐标系

def ocr_page(self, page_num: int) -> List[Dict]
# 整页 OCR

def ocr_to_text_layer(self, page_num: int) -> int
# OCR 并写入可编辑文字层
# 自动选择中文字体(china-s)或西文字体(helv)
```

#### 标注

```python
def add_highlight(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    color: tuple = (1, 0.9, 0)              # 默认黄色
) -> bool

def add_rect_annotation(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    color: tuple = (1, 0, 0),               # 默认红色
    width: float = 2,
    fill: tuple = None                      # 填充色
) -> bool

def add_underline(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    color: tuple = (0, 0, 1)                # 默认蓝色
) -> bool
```

水印处理

水印检测与处理

```python
def detect_watermarks(self, page_num: int = None) -> List[Dict]
# page_num=None 检测全部页
# 返回水印对象列表,类型:artifact/xobject/repeated_text/repeated_image

def remove_watermark(self, page_num: int, watermark: Dict) -> bool
# 对象级删除(Artifact/XObject/redact)

def remove_all_watermarks(self, page_num: int = None) -> int
# 删除所有水印,返回数量

def remove_watermark_block(
    self,
    page_num: int,
    bbox: Tuple[float, float, float, float],
    watermark_text: str = None,
    watermark_size: float = None,
    is_rotated: bool = None
) -> bool
# 文字块级内容流级精确移除(三重特征验证)

def remove_watermark_blocks_batch(
    self,
    page_num: int,
    blocks: List[Dict]
) -> int
# 批量删除多个水印文字块(单次内容流扫描)

def extract_watermark(self, page_num: int, watermark: Dict) -> bytes | None
# 提取水印为 PNG 字节
```

#### 水印检测策略详解

```python
# === 对象级水印检测 (detect_watermarks) ===
# _detect_artifact_watermarks()   - Artifact 标记水印
# _detect_xobject_watermarks()    - Form XObject 水印
# _detect_repeated_content()      - 跨页重复内容(≥3页)
# _extract_bbox_from_stream()     - 从内容流提取 bbox
# _get_xobject_bbox_on_page()     - 获取 XObject 在页面上的 bbox

# === 文字块级水印识别 (_identify_watermark_spans) ===
# 评分制(总分 >= 3 判定为水印):
#   旋转文字 (+3)  - line_dir 的 dy != 0
#   浅色文字 (+2)  - RGB 在 180-240 之间且三通道相近
#   异常大字号 (+1)- 字号 > 中位数×2 且 > 40
#   跨页重复 (+2)  - 相同文字在 3 页以上出现
#   Artifact标记(+5)- 内容流中明确标记

# 任一策略命中即标记 is_watermark=True, source="watermark"

# _build_watermark_text_cache()   - 构建跨页重复文字缓存
# _get_artifact_text_set()        - 获取 Artifact 标记的文字集合
```

#### 水印删除策略详解

```python
# === 对象级删除 ===
# _remove_artifact_watermark() - 正则匹配并移除 BDC...EMC 块
# _remove_xobject_watermark()  - 移除 /Name Do 引用块

# === 文字块级删除 (_remove_text_by_features) ===
# 核心原理:在内容流中通过三重特征验证定位 BT...ET 块,
# 将文字替换为空格(绝不使用 redact,避免误删重叠正文)
#
# 三重验证(全部通过才执行移除):
# 1. 旋转特征:Tm 矩阵的 b/c 分量 ≠ 0
# 2. 字号匹配:Tf 操作符的字号与水印一致(容差 2pt)
# 3. 坐标匹配:Tm 的 (e,f) 转换到页面坐标系后在水印 bbox 内
#    page_y = page_height - pdf_y
#    位置容差:max(字号×0.5, 10)pt
#
# 文字编码支持:
# - 字面字符串: (text) Tj
# - 十六进制字符串: <hex> Tj(中文水印最常见)
# - 数组形式: [(str) <hex>] TJ
# - 兜底机制:三重验证通过但未找到 Tj/TJ 时,整块替换
#
# 多水印处理:
# - 同一 BT...ET 块内可能包含多个 Tm+Tj 对
# - 逐个 Tm 独立做三重验证,只替换匹配 Tm 对应的文字段
#
# 批量删除 (remove_watermark_blocks_batch):
# - 所有水印特征一次性传入 _remove_text_by_features
# - 单次 clean_contents + 单次扫描 + 单次 update_stream
# - 避免逐个删除时内容流重组导致后续水印匹配失败
```

引擎适配层

 OCREngineAdapter(`pdftools/engines/ocr_engine_adapter.py`)

```python
class OCREngineAdapter(ABC):
    @abstractmethod
    def ocr(self, image_path: str) -> List
    @abstractmethod
    def ocr_pdf(self, pdf_path: str, page_numbers: List[int] = None) -> List
    @abstractmethod
    def is_available(self) -> bool

class PaddleOCREngineAdapter(OCREngineAdapter):
    def __init__(self, lang: str = None, use_gpu: bool = None)
    # - 自动从 settings 读取默认值
    # - _init_engine() 初始化 PaddleOCR
    # - _filter_valid_params() 过滤函数签名中不存在的参数
    #   (兼容不同版本 PaddleOCR API)
    # - 模型路径从 settings 读取,不存在则自动创建
    
    def ocr(self, image_path: str) -> List
    def ocr_pdf(self, pdf_path: str, page_numbers: List[int] = None) -> List
    # PDF OCR 流程:
    # 1. fitz.open() 打开 PDF
    # 2. 逐页 get_pixmap(dpi=200) 渲染为图片
    # 3. 保存为临时 PNG
    # 4. 调用 ocr() 识别
    # 5. 清理临时文件
    
    def is_available(self) -> bool
```

StructureEngineAdapter(`pdftools/engines/structure_engine_adapter.py`)

```python
class StructureEngineAdapter(ABC):
    @abstractmethod
    def predict(self, pdf_path: str) -> List
    @abstractmethod
    def is_available(self) -> bool

class PPStructureEngineAdapter(StructureEngineAdapter):
    def __init__(self, lang: str = None, use_gpu: bool = None)
    
    def predict_fast(self, pdf_path: str, progress_callback=None) -> List
    # 快速版面分析(PyMuPDF 内置):
    # - page.get_text('blocks') 获取文字/图片块
    # - page.find_tables() 查找表格
    # - 速度快但精度较低,作为 PP-Structure 回退方案
    
    def predict(self, pdf_path: str, progress_callback=None) -> List
    # PP-Structure 版面分析:
    # - ≤3 页:一次性分析
    # - >3 页:逐页分析(单页导出临时 PDF → 分析 → 清理)
    # - 实时报告进度
    
    def _init_engine(self)
    # 初始化 PPStructure
    # - 配置参数:lang, use_gpu, table, show_log
    # - 模型路径:det/rec/cls/layout/table/formula
    # - _filter_valid_params() 过滤无效参数
    
    def is_available(self) -> bool
```

关于模型

模型文件结构

```
models/
├── ocr/
│   ├── ch_PP-OCRv4_det_infer/        # 文字检测模型
│   ├── ch_PP-OCRv4_rec_infer/        # 文字识别模型
│   └── ch_ppocr_mobile_v2.0_cls_infer/  # 方向分类模型
└── layout/
    ├── picodet_lcnet_x1_0_fgd_layout_cdla_infer/  # 版面检测模型
    ├── ch_ppstructure_mobile_v2.0_SLANet_infer/    # 表格识别模型
    └── rec_latex_ocr_infer/                        # 公式识别模型
```
核心依赖文档

| 库 | 文档地址 |
|-----|---------|
| PyMuPDF | https://pymupdf.readthedocs.io/ |
| pypdf | https://pypdf.readthedocs.io/ |
| PaddleOCR | https://www.paddleocr.ai/ |
| PP-Structure | https://github.com/PaddlePaddle/PaddleOCR |
| pdf2docx | https://github.com/ArtifexSoftware/pdf2docx |
| python-docx | https://python-docx.readthedocs.io/ |
| openpyxl | https://openpyxl.readthedocs.io/ |
| python-pptx | https://python-pptx.readthedocs.io/ |
| PySide2 | https://doc.qt.io/qtforpython-5/ |
| PySide6 | https://doc.qt.io/qtforpython-6/ |

系统要求

| 平台 | 最低配置 | 推荐配置 |

| **Windows** | Win7 SP1 x64, 4GB RAM | Win10+, 8GB RAM, SSD |

| **Linux** | 麒麟/UOS, 4GB RAM | 8GB RAM, SSD, aarch64/x86_64 |

| **磁盘空间** | 2GB(含模型) | 5GB(含缓存空间) |

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐