Metadata-Version: 2.5
Name: pdf-sign-kit
Version: 0.5.0
Summary: Framework-agnostic Python toolkit for stamping signatures and text onto PDF: automatic table-cell detection, text-anchor placement, seam stamps, watermarks, placement templates, a paperless workflow rule engine with configurable signing levels, log tables, cross-platform CJK fonts, page preview and integrity hashing.
Author-email: 兰衣 <iehjli@163.com>
License-Expression: MIT
License-File: LICENSE
Keywords: chinese,cjk,paperless,pdf,pymupdf,sign,signature,stamp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: numpy>=1.24
Requires-Dist: opencv-python-headless>=4.8
Requires-Dist: pillow>=10
Requires-Dist: pymupdf>=1.23
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

# pdf-sign-kit

[![PyPI version](https://img.shields.io/badge/pypi-v0.5.0-blue)](https://pypi.org/project/pdf-sign-kit/)
[![Python](https://img.shields.io/badge/python-3.9%2B-success)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)

[English](#english) | [中文文档](#中文文档)

---

<a id="english"></a>
## English

A framework-agnostic Python toolkit for stamping signatures and text onto PDF
files. It is extracted from the core of a production paperless-approval system
and works with **plain files** — no Django, no database, no network required.

### Features

- **Image stamps** — place any PNG (signature, seal) at a click point.
- **Automatic table-cell detection** — finds the enclosing cell from a single
  grayscale raster and scales the stamp proportionally; rotated pages are
  handled transparently.
- **Handwriting mode** — loose-fit scaling for handwritten signatures.
- **Raster text (600 DPI)** — mixed Chinese/Latin text, e.g.
  `编号 BZ-001`, rendered with auto-detected CJK and Latin fonts; supports
  automatic wrapping.
- **Built-in stamps** — "同意" (agree) and a check mark.
- **Background removal** — turn near-white backgrounds into transparent PNGs.
- **Page preview** — render any page to a PIL image / PNG bytes.
- **Integrity hashing** — SHA-256 / SHA-512 / MD5 digest and verification.
- **Workflow rule engine** — pure functions deciding who may sign now and
  whether every participant has finished; supports personal, personal
  countersign and department countersign levels.
- **Configurable signing schemes** — define any number of levels, name each
  one, mark a level as personal / countersign / department countersign; schemes
  round-trip through JSON, with a built-in 8-level default.
- **Signature log tables** — render the sign/rejection log as a PNG or a
  `data:` URL, with Chinese keyword extraction from rejection reasons.
- **Text-anchor placement** — locate a stamp position by searching visible
  text (`"签字："`) instead of table borders; supports "after / below / over"
  the matched text, for borderless contracts and blank templates.
- **Seam stamps (骑缝章)** — slice one seal into N strips placed on the edges
  of N consecutive pages; the strips reassemble when pages are fanned.
- **Diagonal watermarks** — rotated "作废" / "草稿" / "保密" captions,
  single centered mark or tiled grid, with RGBA opacity control.
- **Placement templates** — save reusable signature positions as JSON and
  instantiate them with a `ref -> image` mapping for batch signing of files
  sharing one layout.
- **Bitmap composition** — horizontal/vertical concatenation of images with
  alignment, gaps and proportional scaling.

### Installation

```bash
pip install pdf-sign-kit
```

### Quickstart

```python
import pdf_sign_kit as psk

# Stamp a signature image at (x, y), editing the file in place.
psk.stamp("form.pdf", "signature.png", x=200, y=627)

# Built-in stamps
psk.stamp_agree("form.pdf", x=200, y=627)
psk.stamp_checkmark("form.pdf", x=200, y=627)

# Mixed Chinese/Latin machine text
psk.stamp_text("form.pdf", "编号 BZ-001", x=200, y=627)

# Make a handwriting scan transparent
img = psk.remove_background("scan.png", threshold=235)
img.save("stamp.png")

# Render a preview / compute integrity hash
preview = psk.render_page("form.pdf", page=0, zoom=3)
digest = psk.hash_file("form.pdf")
```

### Define a signing scheme

```python
from pdf_sign_kit import FlowScheme, Initiator, build_metadata
from pdf_sign_kit import GROUP, DEPARTMENT

# Any number of levels, custom names; level 2 is personal countersign and
# level 3 is department countersign (each department needs one signer).
scheme = FlowScheme.create(
    3,
    names={1: "起草", 2: "会签", 3: "部门会签"},
    modes={2: GROUP, 3: DEPARTMENT},
)

metadata = build_metadata(
    scheme,
    {
        1: [{"id": "A", "name": "甲"}],
        2: [{"id": "B"}, {"id": "C"}],          # any one signs -> done
        3: [
            {"id": "D", "group": "D1", "group_name": "技术部"},
            {"id": "E", "group": "D2", "group_name": "质保部"},
        ],
    },
    initiator=Initiator(id="I", name="initiator"),
)

# Ask the rule engine who may sign / whether the document is complete.
psk.workflow.can_sign(metadata, "A")
psk.workflow.all_completed(metadata)

# Schemes serialize to plain JSON for storage and reloading.
restored = FlowScheme.from_dict(scheme.to_dict())
```

### Text anchor, seam stamp, watermark

```python
# Find visible text and stamp next to it (no table borders needed)
anchor = psk.find_text("contract.pdf", "签字：")
psk.stamp_at_anchor("contract.pdf", "signature.png", anchor)

# Seam stamp across all pages (right edges)
psk.stamp_seam("contract.pdf", "seal.png")

# Diagonal watermark on every page; RGBA color controls opacity
psk.stamp_watermark(
    "contract.pdf", "作废",
    pages=[0, 1],
    color=(220, 38, 38, 90),
    tile=True,
)

# Compose a name + date strip before stamping
combined = psk.hconcat([name_img, date_img], gap=8)
```

### Save and reuse placement templates

```python
from pdf_sign_kit import PlacementTemplate

# Capture positions from stamp_batch operations and store as JSON
template = PlacementTemplate.capture(operations, name="Form-A")
psk.save_template("form_a.json", template)

# Later: reuse the same positions on a new file of the same layout
template = psk.load_template("form_a.json")
operations = template.instantiate({"signature": "sig.png", "date": "date.png"})
psk.stamp_batch("another_file.pdf", operations)
```

### Coordinate system

- `x` / `y` are **PDF points** (1 pt = 1/72 inch) measured from the page's
  **bottom-left** corner in its display orientation.
- `page` indices are **zero-based**.
- Page rotation is handled internally.

### Notes & limitations

- These are **visual ("electronic") signatures** — raster images placed on the
  page — not PKI/certificate-based ("digital") signatures with legal
  non-repudiation guarantees.
- Raster text is flattened (not selectable), which matches the print-first
  quality requirement.
- No fonts are bundled: CJK/Latin fonts are auto-detected on the system; you
  may always pass an explicit `font_path`. On Debian/Ubuntu:
  `apt-get install fonts-noto-cjk`.

### License

[MIT](LICENSE) © 兰衣

---

<a id="中文文档"></a>
## 中文文档

一个与 Web 框架无关的 Python 工具包，用于在 PDF 文件上加盖签名图章与机打
文字。代码提炼自一套生产环境无纸化审批系统的核心内核，**只操作本地文件**
——不依赖 Django、数据库或网络。

### 功能特性

- **图片盖章**：在指定坐标放置任意 PNG（签名、印章）。
- **表格单元格自动检测**：通过一次灰度渲染识别点击位置所在的表格格子，
  并按格子比例自动缩放；自动处理页面旋转。
- **手写模式**：为手写签名提供宽松缩放策略。
- **机打文字（600 DPI）**：支持中英混排（如 `编号 BZ-001`），自动匹配中/英
  字体，支持自动换行。
- **内置图章**："同意" 章与打勾（check mark）。
- **去背景**：将近白背景转为透明 PNG。
- **页面预览**：把任意页面渲染为 PIL 图像 / PNG 字节。
- **完整性哈希**：支持 SHA-256 / SHA-512 / MD5 计算与校验。
- **签署流程规则引擎**：以纯函数判断当前谁可以签、是否全部签署完成，支持
  个人签署、个人会签与部门会签。
- **签署等级方案可配置**：可自定义任意数量的等级、为每个等级命名，标记某级
  为个人签署/会签/部门会签；方案支持 JSON 序列化，并内置一套 8 级默认方案。
- **签字日志表格**：将签字/驳回日志渲染为 PNG 或 `data:` URL，并支持从驳回
  原因中提取中文关键词。
- **文本锚点定位**：通过搜索可见文字（如「签字：」）确定盖章位置，而非依赖
  表格线；支持在匹配文字「其后/其下/覆盖」定位，适用于无表格线的合同与空白
  模板。
- **骑缝章**：将一枚印章切成 N 条，分别盖在连续 N 页的边缘，展开拼合后还原
  完整印章。
- **斜向水印**：「作废」「草稿」「保密」等旋转文字，支持单个居中章或整页平铺，
  RGBA 颜色直接控制透明度。
- **签署位置模板**：把可复用的签署位置保存为 JSON，通过「引用名 → 图片」映射
  实例化，用于同版式文件的批量签署。
- **位图拼接**：多图水平/垂直拼接，支持对齐、间距与等比缩放。

### 安装

```bash
pip install pdf-sign-kit
```

### 快速上手

```python
import pdf_sign_kit as psk

# 在 (x, y) 处加盖签名图片，原地修改文件
psk.stamp("表单.pdf", "签名.png", x=200, y=627)

# 内置图章
psk.stamp_agree("表单.pdf", x=200, y=627)
psk.stamp_checkmark("表单.pdf", x=200, y=627)

# 中英混排机打文字
psk.stamp_text("表单.pdf", "编号 BZ-001", x=200, y=627)

# 将手写扫描件去背景
img = psk.remove_background("扫描件.png", threshold=235)
img.save("图章.png")

# 渲染预览图 / 计算完整性哈希
preview = psk.render_page("表单.pdf", page=0, zoom=3)
digest = psk.hash_file("表单.pdf")
```

### 定义签署等级方案

```python
from pdf_sign_kit import FlowScheme, Initiator, build_metadata
from pdf_sign_kit import GROUP, DEPARTMENT

# 任意等级数量、自定义名称；第2级为个人会签，第3级为部门会签（每个部门
# 任一人签署即完成该部门）。
scheme = FlowScheme.create(
    3,
    names={1: "起草", 2: "会签", 3: "部门会签"},
    modes={2: GROUP, 3: DEPARTMENT},
)

metadata = build_metadata(
    scheme,
    {
        1: [{"id": "A", "name": "甲"}],
        2: [{"id": "B"}, {"id": "C"}],          # 任一人签即完成
        3: [
            {"id": "D", "group": "D1", "group_name": "技术部"},
            {"id": "E", "group": "D2", "group_name": "质保部"},
        ],
    },
    initiator=Initiator(id="I", name="发起人"),
)

# 由规则引擎判断谁可签 / 文件是否全部完成
psk.workflow.can_sign(metadata, "A")
psk.workflow.all_completed(metadata)

# 方案可序列化为普通 JSON 存储后再恢复
restored = FlowScheme.from_dict(scheme.to_dict())
```

### 文本锚点、骑缝章、水印

```python
# 搜索可见文字并在其后盖章（无需表格线）
anchor = psk.find_text("合同.pdf", "签字：")
psk.stamp_at_anchor("合同.pdf", "签名.png", anchor)

# 对所有页加盖骑缝章（右边缘）
psk.stamp_seam("合同.pdf", "印章.png")

# 在指定页加盖斜向水印；RGBA 颜色控制透明度
psk.stamp_watermark(
    "合同.pdf", "作废",
    pages=[0, 1],
    color=(220, 38, 38, 90),
    tile=True,
)

# 盖章前先拼接姓名 + 日期
combined = psk.hconcat([name_img, date_img], gap=8)
```

### 保存与复用签署位置模板

```python
from pdf_sign_kit import PlacementTemplate

# 从 stamp_batch 操作中提取位置并存为 JSON
template = PlacementTemplate.capture(operations, name="表单A")
psk.save_template("表单_a.json", template)

# 之后在同版式的新文件上复用相同位置
template = psk.load_template("表单_a.json")
operations = template.instantiate({"signature": "签名.png", "date": "日期.png"})
psk.stamp_batch("另一个文件.pdf", operations)
```

### 坐标系说明

- `x` / `y` 单位为 **PDF 点**（1 pt = 1/72 英寸），以页面显示方向的
  **左下角**为原点。
- `page` 页码从 **0** 开始。
- 页面旋转由库内部处理。

### 注意与限制

- 本包提供的是**可视化（"电子"）签名**——即放置到页面上的图片，而非基于
  PKI/证书、具备法律防抵赖能力的"数字签名"。
- 机打文字以栅格方式写入（不可选中复制），以满足高打印质量要求。
- 包不携带字体：会自动探测系统中的中/英字体，也可显式传入 `font_path`；
  Debian/Ubuntu 可执行 `apt-get install fonts-noto-cjk`。

### 许可证

[MIT](LICENSE) © 兰衣
