Metadata-Version: 2.4
Name: pdfwright
Version: 0.1.2
Summary: 配置驱动的 PDF 生成库：通过 JSON/HTML 定义文档结构，支持表格、字体嵌入、页眉页脚、PDF/A、隐藏签章标记、Word/Excel 转换
Author: pdfwright
License-Expression: MIT
Keywords: pdf,reportlab,pdfa,document-generation,table
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Office/Business
Classifier: Topic :: Printing
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: reportlab<6.0,>=4.0
Requires-Dist: pikepdf<10.14,>=8.0
Requires-Dist: jsonschema<5.0,>=4.0
Requires-Dist: python-docx<2.0,>=1.0
Requires-Dist: openpyxl<4.0,>=3.1
Provides-Extra: charts
Requires-Dist: matplotlib>=3.7; extra == "charts"
Provides-Extra: barcode
Requires-Dist: python-barcode>=0.15; extra == "barcode"
Requires-Dist: qrcode>=7.4; extra == "barcode"
Provides-Extra: security
Requires-Dist: cryptography>=41.0; extra == "security"
Provides-Extra: web
Requires-Dist: fastapi>=0.100; extra == "web"
Requires-Dist: uvicorn[standard]>=0.23; extra == "web"
Requires-Dist: pydantic>=2.0; extra == "web"
Requires-Dist: httpx>=0.24; extra == "web"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pytest-xdist>=3.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.5; extra == "dev"
Requires-Dist: basedpyright>=1.0; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Requires-Dist: pre-commit>=3.0; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: isort>=5.12; extra == "dev"
Requires-Dist: flake8>=6.0; extra == "dev"
Dynamic: license-file

# pdfwright

配置驱动的 PDF 生成库：通过 JSON/HTML 配置定义文档结构，支持表格、字体嵌入、页眉页脚、PDF 版本控制、隐藏签章标记、Word/Excel 转换、模板引擎、低代码可视化编辑器。

## 功能特性

### 核心能力
- **配置驱动**：JSON 配置定义页面、样式、内容，无需写代码
- **排版引擎**：基于 ReportLab Platypus，自动换行、自动分页、自动撑开高度
- **长文本表格自动拆分**：单元格内容超过页面高度时自动按字符数拆分跨页，二分法精确计算拆分点
- **组件嵌套**：表格单元格、列表项支持嵌套段落、图片、列表、分隔线等组件

### 表格
- 合并单元格（colspan/rowspan）
- 表头跨页重复
- 多级表头
- 边框、内边距、单元格样式
- CJK 自动换行
- 配置+数据分离模式
- 动态数据绑定
- 自动序号列
- 表格宽度模式（auto/fixed/percent）
- 表格对齐方式

### 字体
- TTF/OTF/TTC 字体注册与嵌入
- 字体子集化减小体积
- 中文字体自动注册：自动扫描系统字体目录，识别宋体/黑体/微软雅黑/楷体/仿宋等并注册
- 公文字体支持：仿宋 GB2312、黑体、楷体、宋体
- 中文名称智能匹配
- 系统字体扫描（支持 370+ 系统字体）

### 页面与排版
- 页眉页脚：每页自动注入，支持 `{page}`/`{total}` 页码占位符
- 页边距：支持 pt/cm/mm/inch 单位
- 纸张大小：A4/A3/Letter/Legal 等
- 页面方向：纵向/横向
- PDF 版本：支持设置 1.4/1.5/1.6/1.7/2.0
- PDF/A 合规：支持 PDF/A-2a/2b/2u/3a/3b/3u
- 元数据：Title/Author/Subject/Keywords，DocumentInfo 与 XMP 同步

### 隐藏签章标记
- 在指定坐标放置不可见文本（渲染模式 3）
- 三重隐藏技术：渲染模式3（不可见）+ 白色文字 + 0.5pt 极小字号
- 第三方提取文本定位盖章
- 支持多个标记，支持页码指定（支持负数表示最后一页）

### 格式转换
- **Word/Excel 转 PDF**：纯 Python 实现（python-docx + openpyxl + ReportLab），无需外部程序
- 支持 docx/xlsx、合并单元格、多工作表、图片、段落样式
- **文档结构自动解析**：将 docx/xlsx 自动解析为 pdfwright JSON schema 配置，可编辑后重新生成 PDF
- Word 字段解析：页码、目录、分页符、换行符
- Word 样式继承：五级字体/字号继承链
- **Jinja2 模板数据渲染**：`convert_to_pdf(input, output, data=...)` 直接消费含
  Jinja2 语法的 docx/xlsx 模板（与 docxtpl/xltpl 模板资产写法兼容，不依赖
  docxtpl/xltpl/jinja2）：
  - 段落/单元格 `{{var}}`；段落级 `{% for %}/{% if %}/{% else %}` 跨段块；
  - 表格跨行 `{% for %}…{% endfor %}` 行循环（支持嵌套、标记行独占格/与数据
    同行两种写法、循环体内 loop.index）；
  - CLI：`pdfwright convert 模板.docx --data data.json`；
  - 示例见 `examples/jinja_template_demo.py`；
  - 已知限制：函数调用语法（`{{get_image(x)}}`）不支持，图片请用 docx 内嵌图。

### 模板引擎
- 变量替换：`{{ name }}`
- 过滤器：40+ 内置过滤器（currency/date/upper/lower/truncate 等）
- 条件判断：`{% if %}`/`{% elif %}`/`{% else %}`/`{% endif %}`
- 循环：`{% for item in items %}`/`{% endfor %}`，支持 loop.index/loop.first/loop.last
- 注释：`{# comment #}`
- 安全限制：最大循环次数 100000，最大递归深度 50

### 性能与并发
- LRU 缓存：渲染缓存、图片缓存、字体度量缓存、样式缓存
- 字体子集化：减小 PDF 体积
- 异步生成器：AsyncPdfGenerator
- 批量并行生成：BatchGenerator
- 取消令牌：CancelToken
- 进度跟踪：RenderProgress/ProgressTracker

### 安全加固
- 输入验证器：InputValidator
- 清理函数：sanitize_filename/sanitize_text/sanitize_json
- 路径遍历防护
- 无限循环防护
- 恶意图片验证（扩展名+大小 50MB）
- 伪造文件验证（ZIP 魔数）
- JSON 注入防护

### 能力增强
- 矢量图形绘制：GraphicsContext
- 水印：TextWatermark/ImageWatermark
- 目录自动生成：TableOfContents
- 书签/大纲：Bookmark

### 低代码可视化编辑器（本地工具）
- 三栏布局：左侧元素列表/组件库/全局设置、中间 PDF 预览、右侧属性面板
- 上传解析：Word/Excel 自动解析为 schema
- 导入/导出 JSON
- 实时预览：修改属性后自动重新渲染
- 元素操作：添加/删除/上移/下移/复制/拖拽排序
- 表格编辑器：列定义编辑、多级表头、单元格样式配置、数据编辑
- 变量选择器：减少手动输入，支持段落文本、表格数据源、图片路径、表格单元格
- 样式预设库：16 种段落样式 + 9 种表格样式（含公务风格）
- 操作引导：首次使用显示操作提示和快捷键
- 预览缩放：30%-300%
- 撤销/重做：Ctrl+Z/Ctrl+Y，最多 50 步
- 一键文档格式：公文/报告/信函
- 数据标签页：模拟数据编辑，配置与数据分离
- 自动热重载：代码改动自动重启服务

## 安装

```bash
# 核心依赖（纯 Python，无需外部程序）
pip install reportlab pikepdf jsonschema python-docx openpyxl

# 以开发模式安装本库
pip install -e .

# 可选：开发依赖
pip install -e ".[dev]"
```

> **设计原则**：核心功能（含 PDF/A 合规）全部基于纯 Python 库实现，不依赖
> LibreOffice、Ghostscript、pdftopdfa 等外部程序。旧版 .doc/.xls 格式需先另存为 .docx/.xlsx。

## 快速开始

### 1. 编写配置文件

```json
{
  "page": {
    "size": "A4",
    "margins": { "top": "25mm", "bottom": "25mm", "left": "20mm", "right": "20mm" }
  },
  "styles": {
    "title": { "font": "SimHei", "font_size": 18, "align": "center", "space_after": 20 },
    "body": { "font": "SimSun", "font_size": 10.5, "leading": 16, "align": "justify", "word_wrap": "CJK" }
  },
  "header": { "content": "机密文档", "font_size": 8, "color": "#999999", "align": "right" },
  "footer": { "content": "第 {page} 页 / 共 {total} 页", "font_size": 8, "align": "center" },
  "body": [
    { "type": "paragraph", "body": "项目合作协议书", "style": "title" },
    { "type": "paragraph", "body": "甲方：示例甲方科技有限公司", "style": "body" },
    {
      "type": "table",
      "config": {
        "columns": [
          { "key": "no", "label": "序号", "width": 60 },
          { "key": "content", "label": "工作内容", "width": 180 },
          { "key": "deliverable", "label": "交付物", "width": 100 },
          { "key": "duration", "label": "工期", "width": 80 }
        ],
        "show_header": true,
        "pagination": { "repeat_header": true }
      },
      "data": [
        { "no": "1", "content": "需求调研", "deliverable": "需求规格说明书", "duration": "2周" },
        { "no": "2", "content": "系统设计", "deliverable": "系统设计文档", "duration": "3周" },
        { "no": "3", "content": "开发实现", "deliverable": "源代码", "duration": "8周" }
      ]
    }
  ],
  "seal_markers": [
    { "text": "SEAL_PARTY_A_001", "page": 1, "x": 400, "y": 100, "font_size": 0.5, "hidden": true }
  ]
}
```

### 2. 生成 PDF

```python
from pdfwright import PdfGenerator

# 从字典加载配置
generator = PdfGenerator.from_dict(config)

# 生成 PDF
output_path = generator.generate("output.pdf")
print(f"PDF 已生成: {output_path}")
```

### 3. 从 JSON 文件加载

```python
from pdfwright import PdfGenerator

generator = PdfGenerator.from_json("config.json")
generator.generate("output.pdf")
```

### 4. 使用模板引擎

```python
from pdfwright import PdfGenerator
from pdfwright.template import TemplateEngine

# 模板配置
template_config = {
    "page": {"size": "A4"},
    "body": [
        {"type": "paragraph", "body": "{{ title }}", "style": "title"},
        {"type": "paragraph", "body": "申请人：{{ applicant.name }}", "style": "body"},
        {"type": "paragraph", "body": "申请日期：{{ apply_date | date }}", "style": "body"},
        {
            "type": "table",
            "body": [
                ["项目", "金额"],
                {
                    "type": "loop",
                    "for": "it in items",
                    "body": [["{{ it.item }}", "{{ it.amount }}"]],
                },
            ],
        },
    ],
}

# 渲染模板
engine = TemplateEngine()
rendered_config = engine.render(
    template_config,
    {
        "title": "采购申请单",
        "applicant": {"name": "张三"},
        "apply_date": "2026-09-05",
        "items": [
            {"item": "笔记本电脑", "amount": "¥8,000"},
            {"item": "显示器", "amount": "¥2,000"},
        ],
    },
)

# 生成 PDF
generator = PdfGenerator.from_dict(rendered_config)
generator.generate("output.pdf")
```

### 5. Word/Excel 转 PDF

```python
from pdfwright.converters import DocxConverter, XlsxConverter

# Word 转 PDF
converter = DocxConverter()
converter.convert("input.docx", "output.pdf")

# Excel 转 PDF
converter = XlsxConverter()
converter.convert("input.xlsx", "output.pdf")
```

### 6. 自动解析为 schema

```python
from pdfwright.inspector import DocxInspector, XlsxInspector

# 解析 Word 文档为 schema
inspector = DocxInspector()
config = inspector.inspect("input.docx")
# config 可以编辑后重新生成 PDF

# 解析 Excel 文档为 schema
inspector = XlsxInspector()
config = inspector.inspect("input.xlsx")
```

## 配置说明

### 页面配置

```json
{
  "page": {
    "size": "A4",
    "orientation": "portrait",
    "margins": {
      "top": "25mm",
      "bottom": "25mm",
      "left": "20mm",
      "right": "20mm"
    }
  }
}
```

支持的纸张大小：A0-A10、B0-B10、Letter、Legal、Ledger、Tabloid

支持的边距单位：pt、mm、cm、inch

### 样式配置

```json
{
  "styles": {
    "body": {
      "font": "SimSun",
      "font_size": 10.5,
      "leading": 16,
      "align": "justify",
      "color": "#333333",
      "word_wrap": "CJK",
      "first_line_indent": 21,
      "left_indent": 0,
      "right_indent": 0,
      "space_before": 0,
      "space_after": 6
    }
  }
}
```

### 元素类型

#### 段落（paragraph）

```json
{
  "type": "paragraph",
  "text": "段落内容",
  "style": "body",
  "font": "SimSun",
  "font_size": 10.5,
  "bold": false,
  "italic": false,
  "color": "#333333",
  "align": "left",
  "line_spacing": 1.5,
  "space_before": 0,
  "space_after": 6,
  "left_indent": 0,
  "right_indent": 0,
  "first_line_indent": 21
}
```

#### 表格（table）

```json
{
  "type": "table",
  "width_mode": "percent",
  "width_percent": 80,
  "align": "center",
  "cell_padding": 6,
  "pagination": { "repeat_header": true },
  "body": [
    [
      { "body": "姓名", "style": { "bold": true, "align": "center", "background_color": "#f0f0f0" } },
      { "body": "头像", "style": { "bold": true, "align": "center" } }
    ],
    ["张三", { "type": "image_cell", "body": "__IMAGE__:/path/to/avatar.jpg|w=60" }],
    { "type": "loop", "for": "row in users", "body": [["{{ row.name }}", "{{ row.avatar }}"]] }
  ]
}
```

说明：
- `body` 是二维行数组，每行是单元格列表；单元格可以是字符串或
  `{body, colspan, rowspan, style}` 对象（合并单元格）
- 行级 `{type: "loop"}`/`{type: "if"}` 控制元素支持数据驱动行循环
  （渲染期从 context 注入，`data_source` 场景等价写法）
- `width_mode: "percent"`（默认）按百分比占满页面宽度（fit）

#### 图片（image）

```json
{
  "type": "image",
  "src": "/path/to/image.jpg",
  "width": 400,
  "height": 300,
  "align": "center"
}
```

#### 列表（list）

```json
{
  "type": "list",
  "ordered": true,
  "start_number": 1,
  "bullet_char": "•",
  "indent": 20,
  "body": [
    "第一项",
    "第二项",
    { "type": "paragraph", "body": "第三项（含嵌套）" },
    { "type": "paragraph", "body": "嵌套段落" }
  ]
}
```

#### 分隔线（line/divider）

```json
{
  "type": "line",
  "color": "#cccccc",
  "thickness": 1,
  "space_before": 12,
  "space_after": 12
}
```

#### 间距（spacer）

```json
{
  "type": "spacer",
  "height": 20
}
```

#### 分页符（page_break）

```json
{
  "type": "page_break"
}
```

#### 目录（table_of_contents）

```json
{
  "type": "table_of_contents",
  "title": "目录",
  "show_page_numbers": true,
  "dot_leader": true
}
```

### 页眉页脚配置

```json
{
  "header": {
    "content": "页眉内容，支持 {page} 和 {total} 占位符",
    "font": "SimSun",
    "font_size": 9,
    "color": "#666666",
    "align": "center"
  },
  "footer": {
    "content": "第 {page} 页 / 共 {total} 页",
    "font": "SimSun",
    "font_size": 9,
    "color": "#666666",
    "align": "center"
  }
}
```

### 隐藏签章标记配置

```json
{
  "seal_markers": [
    {
      "text": "SEAL_APPLICANT_001",
      "page": 1,
      "x": 400,
      "y": 100,
      "font_size": 0.5,
      "hidden": true
    }
  ]
}
```

- `page`：页码（1基），支持负数（-1 表示最后一页）
- `x`/`y`：坐标（pt，左下角原点）
- `font_size`：字体大小（默认 0.5pt，极小字号）
- `hidden`：是否使用渲染模式 3（完全不可见）

### PDF 版本与 PDF/A

```json
{
  "pdf_version": "1.7",
  "pdfa_compliance": "PDF/A-2b"
}
```

支持的 PDF 版本：1.4、1.5、1.6、1.7、2.0

支持的 PDF/A 配置：PDF/A-2a、PDF/A-2b、PDF/A-2u、PDF/A-3a、PDF/A-3b、PDF/A-3u
（默认 `PDF/A-2b`，设为 `null` 关闭）

PDF/A 合规要点（默认开启即生效）：

- 输出意图内嵌 sRGB ICC profile（优先复用系统 sRGB ICC，否则使用内置的
  符合 IEC 61966-2.1 的最小合法 profile）
- XMP 写入 `pdfaid:part` / `pdfaid:conformance`，并与 DocInfo 保持一致
- 标准字体（Helvetica/Times/Courier 等）与内置 CID 字体自动替换为系统
  可嵌入 TTF；系统缺少可嵌入字体时明确报错（PDF/A 要求全字体嵌入）
- 移除 JavaScript 动作；拒绝加密输入
- **诚实边界**：`-b` 级完整达成；`-u` 级依赖 ReportLab TTF 的 ToUnicode 映射；
  `-a` 级要求结构标记（tagged PDF），ReportLab 不产出结构树，选 a 级会得到
  b 级文件 + a 级声明（`verify_pdfa` 不校验结构树）。正式认证请用 veraPDF。

路径安全（可选）：

```json
{
  "security": {
    "allowed_roots": ["~/Work/assets"],
    "max_image_size_mb": 20
  }
}
```

配置后，图片/水印图片路径必须位于 `allowed_roots` 内，单张图片不得超过
`max_image_size_mb`；未配置时保持原有行为。

## API 文档

### PdfGenerator

```python
from pdfwright import PdfGenerator

# 从字典加载配置
generator = PdfGenerator.from_dict(config, context=None)

# 从 JSON 文件加载
generator = PdfGenerator.from_json(file_path, context=None)

# 生成 PDF
output_path = generator.generate(output_path)

# 生成 PDF 并返回详细结果
result = generator.generate_with_result(output_path)
```

### 模板引擎

```python
from pdfwright.template import TemplateEngine

engine = TemplateEngine()

# 渲染模板配置
rendered_config = engine.render(template_config, context)

# 渲染模板字符串
rendered_text = engine.render_string("{{ name }}", {"name": "张三"})

# 注册自定义过滤器
engine.register_filter("my_filter", lambda x: x.upper())
```

### 转换器

```python
from pdfwright.converters import DocxConverter, XlsxConverter

# Word 转 PDF
converter = DocxConverter()
converter.convert(input_path, output_path)

# Excel 转 PDF
converter = XlsxConverter()
converter.convert(input_path, output_path)
```

### 解析器

```python
from pdfwright.inspector import DocxInspector, XlsxInspector

# 解析 Word 文档为 schema
inspector = DocxInspector()
config = inspector.inspect(input_path)

# 解析 Excel 文档为 schema
inspector = XlsxInspector()
config = inspector.inspect(input_path)
```

### 字体管理器

```python
from pdfwright.fonts import FontManager

font_manager = FontManager()

# 注册字体
font_manager.register("SimSun", "/path/to/simsun.ttf")

# 自动注册中文字体
font_manager.auto_register_chinese_fonts()

# 注册公文字体
font_manager.register_official_fonts()

# 扫描系统字体
fonts = font_manager.scan_system_fonts()

# 解析字体名称
font_name = font_manager.resolve_font_name("宋体")  # 返回 "SimSun"
```

### 后处理

```python
from pdfwright.postprocess import (
    inject_seal_markers,
    set_pdf_version,
    set_metadata,
    convert_to_pdfa,
    verify_pdfa,
)

# 注入隐藏签章标记
inject_seal_markers(pdf_path, markers, output_path)

# 设置 PDF 版本
set_pdf_version(pdf_path, "1.7", output_path)

# 设置元数据
set_metadata(pdf_path, metadata, output_path)

# 转换为 PDF/A
convert_to_pdfa(pdf_path, "PDF/A-2b", output_path)

# 验证 PDF/A 合规
result = verify_pdfa(pdf_path)
```

### 并发异步

```python
from pdfwright.concurrent import AsyncPdfGenerator, BatchGenerator

# 异步生成
generator = AsyncPdfGenerator()
await generator.generate_async(config, output_path)

# 批量并行生成
batch = BatchGenerator(max_workers=4)
results = batch.generate_batch([(config1, out1), (config2, out2)])
```

## 命令行使用

```bash
# 生成 PDF
pdfwright generate config.json output.pdf

# 转换 Word 为 PDF
pdfwright convert input.docx output.pdf

# 转换 Excel 为 PDF
pdfwright convert input.xlsx output.pdf

# 解析 Word 为 schema
pdfwright inspect input.docx config.json

# 解析 Excel 为 schema
pdfwright inspect input.xlsx config.json
```

## 低代码可视化编辑器

可视化编辑器是本地工具，不包含在库发布包中。

### 启动服务

```bash
# 安装服务包后直接启动
pdfwright-web --port 8765

# 开发模式（启用热重载）
pdfwright-web --dev --port 8765
```

### 访问地址

- 主页面：http://127.0.0.1:8765
- 模板编辑器：http://127.0.0.1:8765/template
- 低代码可视化编辑器：http://127.0.0.1:8765/visual

### 功能特性

- 三栏布局：左侧元素列表/组件库/全局设置、中间 PDF 预览、右侧属性面板
- 上传解析：Word/Excel 自动解析为 schema
- 导入/导出 JSON
- 实时预览：修改属性后自动重新渲染
- 元素操作：添加/删除/上移/下移/复制/拖拽排序
- 表格编辑器：列定义编辑、多级表头、单元格样式配置、数据编辑
- 变量选择器：减少手动输入
- 样式预设库：16 种段落样式 + 9 种表格样式
- 操作引导：首次使用显示操作提示和快捷键
- 预览缩放：30%-300%
- 撤销/重做：Ctrl+Z/Ctrl+Y
- 一键文档格式：公文/报告/信函
- 数据标签页：模拟数据编辑
- 自动热重载：代码改动自动重启服务

## 开发指南

### 项目结构

```
pdfwright/
├── __init__.py              # 入口，导出 PdfGenerator
├── cli.py                   # 命令行接口
├── errors.py                # 统一异常体系
├── config/                  # 配置层
│   ├── schema.py            # JSON Schema 定义
│   └── loader.py            # 配置加载校验
├── core/                    # 核心模型
│   ├── document.py          # 文档模型
│   ├── elements.py          # 元素模型（段落、表格、图片等）
│   └── styles.py            # 样式模型
├── renderers/               # 渲染层
│   ├── base.py              # 渲染器基类
│   └── reportlab_renderer.py # ReportLab 渲染器
├── postprocess/             # 后处理
│   ├── seal_marker.py       # 隐藏签章标记
│   ├── version.py           # PDF 版本设置
│   ├── metadata.py          # 元数据设置
│   └── pdfa.py              # PDF/A 转换
├── converters/              # 格式转换
│   ├── docx_converter.py    # Word 转 PDF
│   ├── xlsx_converter.py    # Excel 转 PDF
│   └── libreoffice.py       # LibreOffice 转换（可选）
├── fonts/                   # 字体管理
│   └── manager.py           # 字体管理器
├── inspector/               # 文档结构解析
│   ├── docx_inspector.py    # Word 解析为 schema
│   └── xlsx_inspector.py    # Excel 解析为 schema
├── template/                # 模板引擎
│   ├── engine.py            # 模板引擎
│   └── loader.py            # 模板加载器
├── performance/             # 性能优化
│   ├── cache.py             # 缓存（LRU/渲染/图片/字体度量/样式）
│   └── font_subset.py       # 字体子集化
├── extensions/              # 能力增强
│   ├── graphics.py          # 矢量图形绘制
│   ├── watermark.py         # 水印
│   ├── toc.py               # 目录自动生成
│   └── bookmarks.py         # 书签/大纲
├── concurrent/              # 并发异步
│   ├── generator.py         # 异步生成器
│   ├── batch.py             # 批量并行生成
│   ├── cancel.py            # 取消令牌
│   └── progress.py          # 进度跟踪
├── security/                # 安全加固
│   ├── validator.py         # 输入验证器
│   └── sanitizer.py         # 清理函数
└── utils/                   # 工具
    └── units.py             # 单位转换

tools/                       # 本地工具（不包含在库发布包中）
└── web/                     # 可视化编辑器前端源码
    └── frontend/            # Vue3 + Vite 前端工程

pdfwright-web/               # 可视化编辑器服务包（独立发布）
└── pdfwright_web/           # 后端服务 + 前端构建产物
    ├── server.py            # HTTP 服务（含 --dev 热重载模式）
    ├── cli.py               # CLI 入口（pdfwright-web 命令）
    └── static/visual-app/   # 前端构建产物（随 wheel 分发）

tests/                       # 测试
examples/                    # 示例
```

### 代码质量工具

本项目使用 [uv](https://docs.astral.sh/uv/) 管理依赖，所有质量检查走
`scripts/quality/lint.py` 和 `scripts/quality/test.py`（跨平台 Python，
不依赖 bash）。

```bash
# 一次性同步开发依赖（含 charts / barcode / security extras）
uv sync --all-extras --frozen

# 代码格式检查（CI 友好）
uv run python scripts/quality/lint.py format --check .
uv run python scripts/quality/lint.py check .

# 类型检查
uv run python scripts/quality/lint.py typecheck .

# 安全扫描
uv run python scripts/quality/lint.py bandit .

# 运行测试
uv run python scripts/quality/test.py -u -v

# 代码覆盖率（HTML 报告）
uv run python scripts/quality/test.py -u -c -r
```

### 预提交钩子

```bash
# 安装钩子（uv 已通过 dev extra 装好 pre-commit）
uv run pre-commit install

# 手动跑全部 hook
uv run pre-commit run --all-files
```

### CI/CD

项目包含 GitHub Actions 配置（`.github/workflows/ci.yml`），自动运行：
- 代码格式检查（ruff）
- 类型检查（mypy/basedpyright）
- 安全扫描（bandit）
- 测试（pytest）
- 代码覆盖率

## 示例

查看 `examples/` 目录获取完整示例：

- `contract.json`：合同示例配置
- `generate.py`：基础生成脚本
- `inspect_demo.py`：自动解析演示脚本
- `template_demo.py`：模板化示例脚本
- `contract_template.json.tpl`：合同模板示例

## 常见问题

### Q: 中文显示为方块？
A: 确保已注册中文字体。使用 `FontManager.auto_register_chinese_fonts()` 自动扫描系统字体，或手动注册：`font_manager.register("SimSun", "/path/to/simsun.ttf")`。

### Q: 表格内容超过页面高度报错？
A: pdfwright 支持长文本表格自动拆分，单元格内容超过页面高度时会自动按字符数拆分跨页。确保使用配置+数据分离模式。

### Q: 如何添加隐藏签章标记？
A: 在配置中添加 `seal_markers` 字段，指定标记文本、页码、坐标。第三方签名工具可通过文本提取定位盖章。

### Q: 支持哪些 PDF 版本？
A: 支持 1.4、1.5、1.6、1.7、2.0，以及 PDF/A-2a/2b/2u/3a/3b/3u。

### Q: Word/Excel 转换需要安装 LibreOffice 吗？
A: 不需要。pdfwright 使用纯 Python 实现（python-docx + openpyxl + ReportLab），无需外部程序。

## 许可证

MIT License

Copyright (c) 2026 pdfwright

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
