pdf-sign-kit 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. pdf_sign_kit-0.5.0/.gitignore +24 -0
  2. pdf_sign_kit-0.5.0/LICENSE +21 -0
  3. pdf_sign_kit-0.5.0/PKG-INFO +366 -0
  4. pdf_sign_kit-0.5.0/README.md +334 -0
  5. pdf_sign_kit-0.5.0/pyproject.toml +88 -0
  6. pdf_sign_kit-0.5.0/src/pdf_sign_kit/__init__.py +190 -0
  7. pdf_sign_kit-0.5.0/src/pdf_sign_kit/anchor.py +213 -0
  8. pdf_sign_kit-0.5.0/src/pdf_sign_kit/background.py +55 -0
  9. pdf_sign_kit-0.5.0/src/pdf_sign_kit/compose.py +142 -0
  10. pdf_sign_kit-0.5.0/src/pdf_sign_kit/exceptions.py +27 -0
  11. pdf_sign_kit-0.5.0/src/pdf_sign_kit/fonts.py +195 -0
  12. pdf_sign_kit-0.5.0/src/pdf_sign_kit/geometry.py +130 -0
  13. pdf_sign_kit-0.5.0/src/pdf_sign_kit/hashing.py +38 -0
  14. pdf_sign_kit-0.5.0/src/pdf_sign_kit/identity.py +45 -0
  15. pdf_sign_kit-0.5.0/src/pdf_sign_kit/keywords.py +70 -0
  16. pdf_sign_kit-0.5.0/src/pdf_sign_kit/logtable.py +199 -0
  17. pdf_sign_kit-0.5.0/src/pdf_sign_kit/placement.py +401 -0
  18. pdf_sign_kit-0.5.0/src/pdf_sign_kit/preview.py +89 -0
  19. pdf_sign_kit-0.5.0/src/pdf_sign_kit/scheme.py +398 -0
  20. pdf_sign_kit-0.5.0/src/pdf_sign_kit/seam.py +186 -0
  21. pdf_sign_kit-0.5.0/src/pdf_sign_kit/stamps.py +129 -0
  22. pdf_sign_kit-0.5.0/src/pdf_sign_kit/template.py +166 -0
  23. pdf_sign_kit-0.5.0/src/pdf_sign_kit/text.py +260 -0
  24. pdf_sign_kit-0.5.0/src/pdf_sign_kit/watermark.py +186 -0
  25. pdf_sign_kit-0.5.0/src/pdf_sign_kit/workflow.py +474 -0
  26. pdf_sign_kit-0.5.0/tests/__init__.py +0 -0
  27. pdf_sign_kit-0.5.0/tests/conftest.py +63 -0
  28. pdf_sign_kit-0.5.0/tests/test_background.py +39 -0
  29. pdf_sign_kit-0.5.0/tests/test_geometry.py +50 -0
  30. pdf_sign_kit-0.5.0/tests/test_hashing.py +24 -0
  31. pdf_sign_kit-0.5.0/tests/test_identity.py +21 -0
  32. pdf_sign_kit-0.5.0/tests/test_logtable_keywords.py +90 -0
  33. pdf_sign_kit-0.5.0/tests/test_new_features.py +343 -0
  34. pdf_sign_kit-0.5.0/tests/test_placement.py +121 -0
  35. pdf_sign_kit-0.5.0/tests/test_preview.py +29 -0
  36. pdf_sign_kit-0.5.0/tests/test_public_api.py +61 -0
  37. pdf_sign_kit-0.5.0/tests/test_scheme.py +385 -0
  38. pdf_sign_kit-0.5.0/tests/test_stamps.py +35 -0
  39. pdf_sign_kit-0.5.0/tests/test_text.py +35 -0
  40. pdf_sign_kit-0.5.0/tests/test_workflow.py +132 -0
  41. pdf_sign_kit-0.5.0/tests/test_workflow_edge_cases.py +81 -0
@@ -0,0 +1,24 @@
1
+ # Byte-compiled / optimized files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+
16
+ # Test / coverage
17
+ .pytest_cache/
18
+ .coverage
19
+ htmlcov/
20
+
21
+ # OS / editor
22
+ .DS_Store
23
+ .idea/
24
+ .vscode/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 兰衣
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,366 @@
1
+ Metadata-Version: 2.5
2
+ Name: pdf-sign-kit
3
+ Version: 0.5.0
4
+ 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.
5
+ Author-email: 兰衣 <iehjli@163.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: chinese,cjk,paperless,pdf,pymupdf,sign,signature,stamp
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Office/Business
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: numpy>=1.24
23
+ Requires-Dist: opencv-python-headless>=4.8
24
+ Requires-Dist: pillow>=10
25
+ Requires-Dist: pymupdf>=1.23
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == 'dev'
28
+ Requires-Dist: pytest>=7; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Requires-Dist: twine>=5; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # pdf-sign-kit
34
+
35
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.5.0-blue)](https://pypi.org/project/pdf-sign-kit/)
36
+ [![Python](https://img.shields.io/badge/python-3.9%2B-success)](https://www.python.org/)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
38
+
39
+ [English](#english) | [中文文档](#中文文档)
40
+
41
+ ---
42
+
43
+ <a id="english"></a>
44
+ ## English
45
+
46
+ A framework-agnostic Python toolkit for stamping signatures and text onto PDF
47
+ files. It is extracted from the core of a production paperless-approval system
48
+ and works with **plain files** — no Django, no database, no network required.
49
+
50
+ ### Features
51
+
52
+ - **Image stamps** — place any PNG (signature, seal) at a click point.
53
+ - **Automatic table-cell detection** — finds the enclosing cell from a single
54
+ grayscale raster and scales the stamp proportionally; rotated pages are
55
+ handled transparently.
56
+ - **Handwriting mode** — loose-fit scaling for handwritten signatures.
57
+ - **Raster text (600 DPI)** — mixed Chinese/Latin text, e.g.
58
+ `编号 BZ-001`, rendered with auto-detected CJK and Latin fonts; supports
59
+ automatic wrapping.
60
+ - **Built-in stamps** — "同意" (agree) and a check mark.
61
+ - **Background removal** — turn near-white backgrounds into transparent PNGs.
62
+ - **Page preview** — render any page to a PIL image / PNG bytes.
63
+ - **Integrity hashing** — SHA-256 / SHA-512 / MD5 digest and verification.
64
+ - **Workflow rule engine** — pure functions deciding who may sign now and
65
+ whether every participant has finished; supports personal, personal
66
+ countersign and department countersign levels.
67
+ - **Configurable signing schemes** — define any number of levels, name each
68
+ one, mark a level as personal / countersign / department countersign; schemes
69
+ round-trip through JSON, with a built-in 8-level default.
70
+ - **Signature log tables** — render the sign/rejection log as a PNG or a
71
+ `data:` URL, with Chinese keyword extraction from rejection reasons.
72
+ - **Text-anchor placement** — locate a stamp position by searching visible
73
+ text (`"签字:"`) instead of table borders; supports "after / below / over"
74
+ the matched text, for borderless contracts and blank templates.
75
+ - **Seam stamps (骑缝章)** — slice one seal into N strips placed on the edges
76
+ of N consecutive pages; the strips reassemble when pages are fanned.
77
+ - **Diagonal watermarks** — rotated "作废" / "草稿" / "保密" captions,
78
+ single centered mark or tiled grid, with RGBA opacity control.
79
+ - **Placement templates** — save reusable signature positions as JSON and
80
+ instantiate them with a `ref -> image` mapping for batch signing of files
81
+ sharing one layout.
82
+ - **Bitmap composition** — horizontal/vertical concatenation of images with
83
+ alignment, gaps and proportional scaling.
84
+
85
+ ### Installation
86
+
87
+ ```bash
88
+ pip install pdf-sign-kit
89
+ ```
90
+
91
+ ### Quickstart
92
+
93
+ ```python
94
+ import pdf_sign_kit as psk
95
+
96
+ # Stamp a signature image at (x, y), editing the file in place.
97
+ psk.stamp("form.pdf", "signature.png", x=200, y=627)
98
+
99
+ # Built-in stamps
100
+ psk.stamp_agree("form.pdf", x=200, y=627)
101
+ psk.stamp_checkmark("form.pdf", x=200, y=627)
102
+
103
+ # Mixed Chinese/Latin machine text
104
+ psk.stamp_text("form.pdf", "编号 BZ-001", x=200, y=627)
105
+
106
+ # Make a handwriting scan transparent
107
+ img = psk.remove_background("scan.png", threshold=235)
108
+ img.save("stamp.png")
109
+
110
+ # Render a preview / compute integrity hash
111
+ preview = psk.render_page("form.pdf", page=0, zoom=3)
112
+ digest = psk.hash_file("form.pdf")
113
+ ```
114
+
115
+ ### Define a signing scheme
116
+
117
+ ```python
118
+ from pdf_sign_kit import FlowScheme, Initiator, build_metadata
119
+ from pdf_sign_kit import GROUP, DEPARTMENT
120
+
121
+ # Any number of levels, custom names; level 2 is personal countersign and
122
+ # level 3 is department countersign (each department needs one signer).
123
+ scheme = FlowScheme.create(
124
+ 3,
125
+ names={1: "起草", 2: "会签", 3: "部门会签"},
126
+ modes={2: GROUP, 3: DEPARTMENT},
127
+ )
128
+
129
+ metadata = build_metadata(
130
+ scheme,
131
+ {
132
+ 1: [{"id": "A", "name": "甲"}],
133
+ 2: [{"id": "B"}, {"id": "C"}], # any one signs -> done
134
+ 3: [
135
+ {"id": "D", "group": "D1", "group_name": "技术部"},
136
+ {"id": "E", "group": "D2", "group_name": "质保部"},
137
+ ],
138
+ },
139
+ initiator=Initiator(id="I", name="initiator"),
140
+ )
141
+
142
+ # Ask the rule engine who may sign / whether the document is complete.
143
+ psk.workflow.can_sign(metadata, "A")
144
+ psk.workflow.all_completed(metadata)
145
+
146
+ # Schemes serialize to plain JSON for storage and reloading.
147
+ restored = FlowScheme.from_dict(scheme.to_dict())
148
+ ```
149
+
150
+ ### Text anchor, seam stamp, watermark
151
+
152
+ ```python
153
+ # Find visible text and stamp next to it (no table borders needed)
154
+ anchor = psk.find_text("contract.pdf", "签字:")
155
+ psk.stamp_at_anchor("contract.pdf", "signature.png", anchor)
156
+
157
+ # Seam stamp across all pages (right edges)
158
+ psk.stamp_seam("contract.pdf", "seal.png")
159
+
160
+ # Diagonal watermark on every page; RGBA color controls opacity
161
+ psk.stamp_watermark(
162
+ "contract.pdf", "作废",
163
+ pages=[0, 1],
164
+ color=(220, 38, 38, 90),
165
+ tile=True,
166
+ )
167
+
168
+ # Compose a name + date strip before stamping
169
+ combined = psk.hconcat([name_img, date_img], gap=8)
170
+ ```
171
+
172
+ ### Save and reuse placement templates
173
+
174
+ ```python
175
+ from pdf_sign_kit import PlacementTemplate
176
+
177
+ # Capture positions from stamp_batch operations and store as JSON
178
+ template = PlacementTemplate.capture(operations, name="Form-A")
179
+ psk.save_template("form_a.json", template)
180
+
181
+ # Later: reuse the same positions on a new file of the same layout
182
+ template = psk.load_template("form_a.json")
183
+ operations = template.instantiate({"signature": "sig.png", "date": "date.png"})
184
+ psk.stamp_batch("another_file.pdf", operations)
185
+ ```
186
+
187
+ ### Coordinate system
188
+
189
+ - `x` / `y` are **PDF points** (1 pt = 1/72 inch) measured from the page's
190
+ **bottom-left** corner in its display orientation.
191
+ - `page` indices are **zero-based**.
192
+ - Page rotation is handled internally.
193
+
194
+ ### Notes & limitations
195
+
196
+ - These are **visual ("electronic") signatures** — raster images placed on the
197
+ page — not PKI/certificate-based ("digital") signatures with legal
198
+ non-repudiation guarantees.
199
+ - Raster text is flattened (not selectable), which matches the print-first
200
+ quality requirement.
201
+ - No fonts are bundled: CJK/Latin fonts are auto-detected on the system; you
202
+ may always pass an explicit `font_path`. On Debian/Ubuntu:
203
+ `apt-get install fonts-noto-cjk`.
204
+
205
+ ### License
206
+
207
+ [MIT](LICENSE) © 兰衣
208
+
209
+ ---
210
+
211
+ <a id="中文文档"></a>
212
+ ## 中文文档
213
+
214
+ 一个与 Web 框架无关的 Python 工具包,用于在 PDF 文件上加盖签名图章与机打
215
+ 文字。代码提炼自一套生产环境无纸化审批系统的核心内核,**只操作本地文件**
216
+ ——不依赖 Django、数据库或网络。
217
+
218
+ ### 功能特性
219
+
220
+ - **图片盖章**:在指定坐标放置任意 PNG(签名、印章)。
221
+ - **表格单元格自动检测**:通过一次灰度渲染识别点击位置所在的表格格子,
222
+ 并按格子比例自动缩放;自动处理页面旋转。
223
+ - **手写模式**:为手写签名提供宽松缩放策略。
224
+ - **机打文字(600 DPI)**:支持中英混排(如 `编号 BZ-001`),自动匹配中/英
225
+ 字体,支持自动换行。
226
+ - **内置图章**:"同意" 章与打勾(check mark)。
227
+ - **去背景**:将近白背景转为透明 PNG。
228
+ - **页面预览**:把任意页面渲染为 PIL 图像 / PNG 字节。
229
+ - **完整性哈希**:支持 SHA-256 / SHA-512 / MD5 计算与校验。
230
+ - **签署流程规则引擎**:以纯函数判断当前谁可以签、是否全部签署完成,支持
231
+ 个人签署、个人会签与部门会签。
232
+ - **签署等级方案可配置**:可自定义任意数量的等级、为每个等级命名,标记某级
233
+ 为个人签署/会签/部门会签;方案支持 JSON 序列化,并内置一套 8 级默认方案。
234
+ - **签字日志表格**:将签字/驳回日志渲染为 PNG 或 `data:` URL,并支持从驳回
235
+ 原因中提取中文关键词。
236
+ - **文本锚点定位**:通过搜索可见文字(如「签字:」)确定盖章位置,而非依赖
237
+ 表格线;支持在匹配文字「其后/其下/覆盖」定位,适用于无表格线的合同与空白
238
+ 模板。
239
+ - **骑缝章**:将一枚印章切成 N 条,分别盖在连续 N 页的边缘,展开拼合后还原
240
+ 完整印章。
241
+ - **斜向水印**:「作废」「草稿」「保密」等旋转文字,支持单个居中章或整页平铺,
242
+ RGBA 颜色直接控制透明度。
243
+ - **签署位置模板**:把可复用的签署位置保存为 JSON,通过「引用名 → 图片」映射
244
+ 实例化,用于同版式文件的批量签署。
245
+ - **位图拼接**:多图水平/垂直拼接,支持对齐、间距与等比缩放。
246
+
247
+ ### 安装
248
+
249
+ ```bash
250
+ pip install pdf-sign-kit
251
+ ```
252
+
253
+ ### 快速上手
254
+
255
+ ```python
256
+ import pdf_sign_kit as psk
257
+
258
+ # 在 (x, y) 处加盖签名图片,原地修改文件
259
+ psk.stamp("表单.pdf", "签名.png", x=200, y=627)
260
+
261
+ # 内置图章
262
+ psk.stamp_agree("表单.pdf", x=200, y=627)
263
+ psk.stamp_checkmark("表单.pdf", x=200, y=627)
264
+
265
+ # 中英混排机打文字
266
+ psk.stamp_text("表单.pdf", "编号 BZ-001", x=200, y=627)
267
+
268
+ # 将手写扫描件去背景
269
+ img = psk.remove_background("扫描件.png", threshold=235)
270
+ img.save("图章.png")
271
+
272
+ # 渲染预览图 / 计算完整性哈希
273
+ preview = psk.render_page("表单.pdf", page=0, zoom=3)
274
+ digest = psk.hash_file("表单.pdf")
275
+ ```
276
+
277
+ ### 定义签署等级方案
278
+
279
+ ```python
280
+ from pdf_sign_kit import FlowScheme, Initiator, build_metadata
281
+ from pdf_sign_kit import GROUP, DEPARTMENT
282
+
283
+ # 任意等级数量、自定义名称;第2级为个人会签,第3级为部门会签(每个部门
284
+ # 任一人签署即完成该部门)。
285
+ scheme = FlowScheme.create(
286
+ 3,
287
+ names={1: "起草", 2: "会签", 3: "部门会签"},
288
+ modes={2: GROUP, 3: DEPARTMENT},
289
+ )
290
+
291
+ metadata = build_metadata(
292
+ scheme,
293
+ {
294
+ 1: [{"id": "A", "name": "甲"}],
295
+ 2: [{"id": "B"}, {"id": "C"}], # 任一人签即完成
296
+ 3: [
297
+ {"id": "D", "group": "D1", "group_name": "技术部"},
298
+ {"id": "E", "group": "D2", "group_name": "质保部"},
299
+ ],
300
+ },
301
+ initiator=Initiator(id="I", name="发起人"),
302
+ )
303
+
304
+ # 由规则引擎判断谁可签 / 文件是否全部完成
305
+ psk.workflow.can_sign(metadata, "A")
306
+ psk.workflow.all_completed(metadata)
307
+
308
+ # 方案可序列化为普通 JSON 存储后再恢复
309
+ restored = FlowScheme.from_dict(scheme.to_dict())
310
+ ```
311
+
312
+ ### 文本锚点、骑缝章、水印
313
+
314
+ ```python
315
+ # 搜索可见文字并在其后盖章(无需表格线)
316
+ anchor = psk.find_text("合同.pdf", "签字:")
317
+ psk.stamp_at_anchor("合同.pdf", "签名.png", anchor)
318
+
319
+ # 对所有页加盖骑缝章(右边缘)
320
+ psk.stamp_seam("合同.pdf", "印章.png")
321
+
322
+ # 在指定页加盖斜向水印;RGBA 颜色控制透明度
323
+ psk.stamp_watermark(
324
+ "合同.pdf", "作废",
325
+ pages=[0, 1],
326
+ color=(220, 38, 38, 90),
327
+ tile=True,
328
+ )
329
+
330
+ # 盖章前先拼接姓名 + 日期
331
+ combined = psk.hconcat([name_img, date_img], gap=8)
332
+ ```
333
+
334
+ ### 保存与复用签署位置模板
335
+
336
+ ```python
337
+ from pdf_sign_kit import PlacementTemplate
338
+
339
+ # 从 stamp_batch 操作中提取位置并存为 JSON
340
+ template = PlacementTemplate.capture(operations, name="表单A")
341
+ psk.save_template("表单_a.json", template)
342
+
343
+ # 之后在同版式的新文件上复用相同位置
344
+ template = psk.load_template("表单_a.json")
345
+ operations = template.instantiate({"signature": "签名.png", "date": "日期.png"})
346
+ psk.stamp_batch("另一个文件.pdf", operations)
347
+ ```
348
+
349
+ ### 坐标系说明
350
+
351
+ - `x` / `y` 单位为 **PDF 点**(1 pt = 1/72 英寸),以页面显示方向的
352
+ **左下角**为原点。
353
+ - `page` 页码从 **0** 开始。
354
+ - 页面旋转由库内部处理。
355
+
356
+ ### 注意与限制
357
+
358
+ - 本包提供的是**可视化("电子")签名**——即放置到页面上的图片,而非基于
359
+ PKI/证书、具备法律防抵赖能力的"数字签名"。
360
+ - 机打文字以栅格方式写入(不可选中复制),以满足高打印质量要求。
361
+ - 包不携带字体:会自动探测系统中的中/英字体,也可显式传入 `font_path`;
362
+ Debian/Ubuntu 可执行 `apt-get install fonts-noto-cjk`。
363
+
364
+ ### 许可证
365
+
366
+ [MIT](LICENSE) © 兰衣