pdfwright 0.1.2__tar.gz → 0.2.1__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.
- pdfwright-0.2.1/.gitignore +33 -0
- pdfwright-0.2.1/PKG-INFO +82 -0
- pdfwright-0.2.1/README.md +63 -0
- pdfwright-0.2.1/examples/contract.json +64 -0
- pdfwright-0.2.1/examples/cooperation_contract.json +803 -0
- pdfwright-0.2.1/examples/daily_report.json +962 -0
- pdfwright-0.2.1/examples/feature_tour.json +3381 -0
- pdfwright-0.2.1/examples/font_mixing.json +36 -0
- pdfwright-0.2.1/examples/generate_all.py +81 -0
- pdfwright-0.2.1/examples/generate_demo.py +32 -0
- pdfwright-0.2.1/examples/invoice.json +83 -0
- pdfwright-0.2.1/examples/output.html +59 -0
- pdfwright-0.2.1/examples/output.pdf +0 -0
- pdfwright-0.2.1/examples/path_security.json +36 -0
- pdfwright-0.2.1/examples/sample_document.json +89 -0
- pdfwright-0.2.1/examples/service_contract.json +614 -0
- pdfwright-0.2.1/examples/staff_form.json +106 -0
- pdfwright-0.2.1/pyproject.toml +98 -0
- pdfwright-0.2.1/src/pdfwright/__init__.py +58 -0
- pdfwright-0.2.1/src/pdfwright/batch.py +205 -0
- pdfwright-0.2.1/src/pdfwright/converters/__init__.py +28 -0
- pdfwright-0.2.1/src/pdfwright/converters/docx.py +1465 -0
- pdfwright-0.2.1/src/pdfwright/converters/html.py +822 -0
- pdfwright-0.2.1/src/pdfwright/converters/xlsx.py +192 -0
- pdfwright-0.2.1/src/pdfwright/errors.py +149 -0
- pdfwright-0.2.1/src/pdfwright/fonts/__init__.py +55 -0
- pdfwright-0.2.1/src/pdfwright/fonts/fallback.py +171 -0
- pdfwright-0.2.1/src/pdfwright/fonts/registry.py +736 -0
- pdfwright-0.2.1/src/pdfwright/fonts/scanner.py +535 -0
- pdfwright-0.2.1/src/pdfwright/fonts/sfnt.py +85 -0
- pdfwright-0.2.1/src/pdfwright/fonts/system_fonts.py +243 -0
- pdfwright-0.2.1/src/pdfwright/models/__init__.py +150 -0
- pdfwright-0.2.1/src/pdfwright/models/borders.py +214 -0
- pdfwright-0.2.1/src/pdfwright/models/constants.py +118 -0
- pdfwright-0.2.1/src/pdfwright/models/document.py +313 -0
- pdfwright-0.2.1/src/pdfwright/models/elements.py +474 -0
- pdfwright-0.2.1/src/pdfwright/models/style.py +161 -0
- pdfwright-0.2.1/src/pdfwright/postprocess/__init__.py +23 -0
- pdfwright-0.2.1/src/pdfwright/postprocess/metadata.py +180 -0
- pdfwright-0.2.1/src/pdfwright/postprocess/pdfa_verify.py +520 -0
- pdfwright-0.2.1/src/pdfwright/py.typed +0 -0
- pdfwright-0.2.1/src/pdfwright/render/__init__.py +12 -0
- pdfwright-0.2.1/src/pdfwright/render/base.py +300 -0
- pdfwright-0.2.1/src/pdfwright/render/codes.py +143 -0
- pdfwright-0.2.1/src/pdfwright/render/html/__init__.py +966 -0
- pdfwright-0.2.1/src/pdfwright/render/markup.py +112 -0
- pdfwright-0.2.1/src/pdfwright/render/pdf.py +2423 -0
- pdfwright-0.2.1/src/pdfwright/render/pdfa.py +292 -0
- pdfwright-0.2.1/src/pdfwright/render/table.py +862 -0
- pdfwright-0.2.1/src/pdfwright/security.py +249 -0
- pdfwright-0.2.1/src/pdfwright/template/__init__.py +16 -0
- pdfwright-0.2.1/src/pdfwright/template/context.py +38 -0
- pdfwright-0.2.1/src/pdfwright/template/engine.py +432 -0
- pdfwright-0.2.1/src/pdfwright/template/filters.py +258 -0
- pdfwright-0.2.1/src/pdfwright/template/format.py +99 -0
- pdfwright-0.2.1/src/pdfwright/utils.py +168 -0
- pdfwright-0.2.1/src/pdfwright/validate/__init__.py +407 -0
- pdfwright-0.2.1/tests/test_docx_converter.py +502 -0
- pdfwright-0.2.1/tests/test_examples.py +58 -0
- pdfwright-0.2.1/tests/test_fonts.py +174 -0
- pdfwright-0.2.1/tests/test_html_converter.py +152 -0
- pdfwright-0.2.1/tests/test_models.py +308 -0
- pdfwright-0.2.1/tests/test_render_html.py +570 -0
- pdfwright-0.2.1/tests/test_render_pdf.py +2418 -0
- pdfwright-0.2.1/tests/test_render_table.py +245 -0
- pdfwright-0.2.1/tests/test_security.py +121 -0
- pdfwright-0.2.1/tests/test_shared_vectors.py +44 -0
- pdfwright-0.2.1/tests/test_template.py +200 -0
- pdfwright-0.2.1/tests/test_validate.py +108 -0
- pdfwright-0.2.1/tests/test_xlsx_converter.py +78 -0
- pdfwright-0.2.1/uv.lock +1336 -0
- pdfwright-0.1.2/LICENSE +0 -21
- pdfwright-0.1.2/PKG-INFO +0 -917
- pdfwright-0.1.2/README.md +0 -868
- pdfwright-0.1.2/pdfwright/__init__.py +0 -255
- pdfwright-0.1.2/pdfwright/cli.py +0 -94
- pdfwright-0.1.2/pdfwright/concurrent/__init__.py +0 -23
- pdfwright-0.1.2/pdfwright/concurrent/batch.py +0 -311
- pdfwright-0.1.2/pdfwright/concurrent/cancel.py +0 -154
- pdfwright-0.1.2/pdfwright/concurrent/generator.py +0 -253
- pdfwright-0.1.2/pdfwright/concurrent/progress.py +0 -179
- pdfwright-0.1.2/pdfwright/config/__init__.py +0 -4
- pdfwright-0.1.2/pdfwright/config/loader.py +0 -1189
- pdfwright-0.1.2/pdfwright/config/schema.py +0 -174
- pdfwright-0.1.2/pdfwright/config/schema_export.py +0 -136
- pdfwright-0.1.2/pdfwright/converters/__init__.py +0 -71
- pdfwright-0.1.2/pdfwright/converters/docx_converter.py +0 -910
- pdfwright-0.1.2/pdfwright/converters/jinja_blocks.py +0 -443
- pdfwright-0.1.2/pdfwright/converters/libreoffice.py +0 -232
- pdfwright-0.1.2/pdfwright/converters/xlsx_converter.py +0 -603
- pdfwright-0.1.2/pdfwright/core/__init__.py +0 -42
- pdfwright-0.1.2/pdfwright/core/document.py +0 -203
- pdfwright-0.1.2/pdfwright/core/elements.py +0 -1508
- pdfwright-0.1.2/pdfwright/core/field_meta.py +0 -430
- pdfwright-0.1.2/pdfwright/core/html_parser.py +0 -515
- pdfwright-0.1.2/pdfwright/core/styles.py +0 -116
- pdfwright-0.1.2/pdfwright/core/type_registry.py +0 -78
- pdfwright-0.1.2/pdfwright/errors.py +0 -838
- pdfwright-0.1.2/pdfwright/extensions/__init__.py +0 -59
- pdfwright-0.1.2/pdfwright/extensions/bookmarks.py +0 -378
- pdfwright-0.1.2/pdfwright/extensions/graphics.py +0 -412
- pdfwright-0.1.2/pdfwright/extensions/toc.py +0 -264
- pdfwright-0.1.2/pdfwright/extensions/watermark.py +0 -337
- pdfwright-0.1.2/pdfwright/fonts/__init__.py +0 -3
- pdfwright-0.1.2/pdfwright/fonts/manager.py +0 -1271
- pdfwright-0.1.2/pdfwright/inspector/__init__.py +0 -58
- pdfwright-0.1.2/pdfwright/inspector/docx_inspector.py +0 -1207
- pdfwright-0.1.2/pdfwright/inspector/variables.py +0 -129
- pdfwright-0.1.2/pdfwright/inspector/xlsx_inspector.py +0 -266
- pdfwright-0.1.2/pdfwright/performance/__init__.py +0 -34
- pdfwright-0.1.2/pdfwright/performance/cache.py +0 -472
- pdfwright-0.1.2/pdfwright/performance/font_subset.py +0 -236
- pdfwright-0.1.2/pdfwright/postprocess/__init__.py +0 -30
- pdfwright-0.1.2/pdfwright/postprocess/form_fields.py +0 -146
- pdfwright-0.1.2/pdfwright/postprocess/icc.py +0 -179
- pdfwright-0.1.2/pdfwright/postprocess/metadata.py +0 -81
- pdfwright-0.1.2/pdfwright/postprocess/pdfa.py +0 -490
- pdfwright-0.1.2/pdfwright/postprocess/pipeline.py +0 -90
- pdfwright-0.1.2/pdfwright/postprocess/seal_marker.py +0 -190
- pdfwright-0.1.2/pdfwright/postprocess/version.py +0 -48
- pdfwright-0.1.2/pdfwright/renderers/__init__.py +0 -4
- pdfwright-0.1.2/pdfwright/renderers/base.py +0 -25
- pdfwright-0.1.2/pdfwright/renderers/flowables.py +0 -870
- pdfwright-0.1.2/pdfwright/renderers/reportlab_renderer.py +0 -4903
- pdfwright-0.1.2/pdfwright/security/__init__.py +0 -37
- pdfwright-0.1.2/pdfwright/security/sanitizer.py +0 -294
- pdfwright-0.1.2/pdfwright/security/validator.py +0 -513
- pdfwright-0.1.2/pdfwright/template/__init__.py +0 -25
- pdfwright-0.1.2/pdfwright/template/engine.py +0 -1165
- pdfwright-0.1.2/pdfwright/template/html_to_rl.py +0 -292
- pdfwright-0.1.2/pdfwright/template/loader.py +0 -169
- pdfwright-0.1.2/pdfwright/utils/__init__.py +0 -3
- pdfwright-0.1.2/pdfwright/utils/units.py +0 -80
- pdfwright-0.1.2/pdfwright.egg-info/PKG-INFO +0 -917
- pdfwright-0.1.2/pdfwright.egg-info/SOURCES.txt +0 -119
- pdfwright-0.1.2/pdfwright.egg-info/dependency_links.txt +0 -1
- pdfwright-0.1.2/pdfwright.egg-info/entry_points.txt +0 -2
- pdfwright-0.1.2/pdfwright.egg-info/requires.txt +0 -35
- pdfwright-0.1.2/pdfwright.egg-info/top_level.txt +0 -1
- pdfwright-0.1.2/pyproject.toml +0 -323
- pdfwright-0.1.2/setup.cfg +0 -4
- pdfwright-0.1.2/tests/test_all_types_render.py +0 -169
- pdfwright-0.1.2/tests/test_basic.py +0 -350
- pdfwright-0.1.2/tests/test_character_style_deep.py +0 -142
- pdfwright-0.1.2/tests/test_cli.py +0 -150
- pdfwright-0.1.2/tests/test_concurrent.py +0 -719
- pdfwright-0.1.2/tests/test_convert_comprehensive.py +0 -558
- pdfwright-0.1.2/tests/test_converters.py +0 -211
- pdfwright-0.1.2/tests/test_docx_converter_deep.py +0 -207
- pdfwright-0.1.2/tests/test_docx_inspector.py +0 -86
- pdfwright-0.1.2/tests/test_docx_inspector_deep.py +0 -170
- pdfwright-0.1.2/tests/test_elements.py +0 -157
- pdfwright-0.1.2/tests/test_elements_deep.py +0 -70
- pdfwright-0.1.2/tests/test_engine_edges.py +0 -181
- pdfwright-0.1.2/tests/test_errors.py +0 -148
- pdfwright-0.1.2/tests/test_extensions.py +0 -424
- pdfwright-0.1.2/tests/test_final_branches.py +0 -389
- pdfwright-0.1.2/tests/test_final_push.py +0 -241
- pdfwright-0.1.2/tests/test_final_render.py +0 -395
- pdfwright-0.1.2/tests/test_fonts.py +0 -308
- pdfwright-0.1.2/tests/test_fonts_deep.py +0 -118
- pdfwright-0.1.2/tests/test_form_fields.py +0 -175
- pdfwright-0.1.2/tests/test_generate_pipeline.py +0 -363
- pdfwright-0.1.2/tests/test_html_parser.py +0 -214
- pdfwright-0.1.2/tests/test_inspector.py +0 -289
- pdfwright-0.1.2/tests/test_inspector_deep.py +0 -112
- pdfwright-0.1.2/tests/test_jinja_office.py +0 -591
- pdfwright-0.1.2/tests/test_last_mile.py +0 -299
- pdfwright-0.1.2/tests/test_last_push.py +0 -337
- pdfwright-0.1.2/tests/test_nested_list_render.py +0 -159
- pdfwright-0.1.2/tests/test_pagination_policy.py +0 -262
- pdfwright-0.1.2/tests/test_pdfa.py +0 -373
- pdfwright-0.1.2/tests/test_performance.py +0 -424
- pdfwright-0.1.2/tests/test_push_70.py +0 -231
- pdfwright-0.1.2/tests/test_render_elements.py +0 -423
- pdfwright-0.1.2/tests/test_render_feature_fields.py +0 -318
- pdfwright-0.1.2/tests/test_renderer_final.py +0 -244
- pdfwright-0.1.2/tests/test_renderer_helpers.py +0 -1435
- pdfwright-0.1.2/tests/test_renderer_more.py +0 -280
- pdfwright-0.1.2/tests/test_renderer_table.py +0 -267
- pdfwright-0.1.2/tests/test_schema_export.py +0 -106
- pdfwright-0.1.2/tests/test_seal_marker.py +0 -125
- pdfwright-0.1.2/tests/test_seal_marker_element.py +0 -139
- pdfwright-0.1.2/tests/test_security.py +0 -600
- pdfwright-0.1.2/tests/test_table_borders_and_cell_fields.py +0 -163
- pdfwright-0.1.2/tests/test_table_pagination.py +0 -351
- pdfwright-0.1.2/tests/test_template.py +0 -409
- pdfwright-0.1.2/tests/test_template_rendering.py +0 -292
- pdfwright-0.1.2/tests/test_units.py +0 -110
- pdfwright-0.1.2/tests/test_visual_example.py +0 -50
- pdfwright-0.1.2/tests/test_watermark_deep.py +0 -413
- pdfwright-0.1.2/tests/test_xlsx_converter_deep.py +0 -104
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
bandit-report.json
|
|
14
|
+
|
|
15
|
+
# Node / 前端
|
|
16
|
+
node_modules/
|
|
17
|
+
pdfwright-web/frontend/dist/
|
|
18
|
+
pdfwright-web/frontend/.vite/
|
|
19
|
+
|
|
20
|
+
# 前端构建产物(由 pdfwright-web 构建时生成,不入库;构建命令见 README)
|
|
21
|
+
pdfwright-web/src/pdfwright_web/static/
|
|
22
|
+
|
|
23
|
+
# 示例生成的输出文件
|
|
24
|
+
pdfwright/examples/output/
|
|
25
|
+
|
|
26
|
+
# 其它
|
|
27
|
+
.DS_Store
|
|
28
|
+
*.log
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
|
|
32
|
+
# 本地发布凭据(scripts/publish.sh --config 默认读它)
|
|
33
|
+
.publish.env
|
pdfwright-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pdfwright
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: 配置驱动的 PDF 生成基础库:JSON 配置 → 模板求值(变量/循环/条件)→ 高保真 PDF。纯 Python 引擎(ReportLab + fontTools + Jinja2 + pikepdf),无外部软件依赖。
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: fonttools>=4.53
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: jinja2>=3.1
|
|
10
|
+
Requires-Dist: jsonschema>=4.23
|
|
11
|
+
Requires-Dist: openpyxl>=3.1
|
|
12
|
+
Requires-Dist: pikepdf>=8.0
|
|
13
|
+
Requires-Dist: pillow>=10.0
|
|
14
|
+
Requires-Dist: python-barcode>=0.16.1
|
|
15
|
+
Requires-Dist: python-docx>=1.1
|
|
16
|
+
Requires-Dist: reportlab<6,>=5.0
|
|
17
|
+
Requires-Dist: segno>=1.6.6
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# pdfwright — 配置驱动的 PDF 生成基础库
|
|
21
|
+
|
|
22
|
+
纯 Python 引擎(ReportLab + fontTools + Jinja2 + pikepdf),**无外部软件依赖**
|
|
23
|
+
(不依赖 pango/cairo/Chromium/LibreOffice 等系统级软件)。
|
|
24
|
+
|
|
25
|
+
## 定位
|
|
26
|
+
|
|
27
|
+
`pdfwright` 是**基础库**:输入「页面 JSON 配置(schema)+ 数据(data)」,输出
|
|
28
|
+
高保真 PDF 或 HTML 预览。**不包含任何 web 服务与存储**。
|
|
29
|
+
|
|
30
|
+
- 需要可视化配置编辑器 / 对外 API 服务 → 使用 [`pdfwright-web`](../pdfwright-web/)。
|
|
31
|
+
- 只需本地渲染 → 直接 import 本库即可。
|
|
32
|
+
|
|
33
|
+
## 使用
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from pdfwright import DocumentConfig, SchemaError, render_pdf, render_pdf_to_file
|
|
37
|
+
|
|
38
|
+
config = {
|
|
39
|
+
"version": "1.0",
|
|
40
|
+
"page": {"size": "A4", "margin": {"top": 40, "bottom": 40, "left": 40, "right": 40}},
|
|
41
|
+
"variables": [
|
|
42
|
+
{"name": "title", "type": "string", "label": "标题", "default": "示例文档"},
|
|
43
|
+
{"name": "items", "type": "array", "label": "明细"},
|
|
44
|
+
],
|
|
45
|
+
"body": [
|
|
46
|
+
{"type": "heading", "text": "{{ title }}", "style": {"textAlign": "center", "fontSize": 18}},
|
|
47
|
+
{"type": "for", "each": "items", "as": "item",
|
|
48
|
+
"children": [{"type": "paragraph", "text": "{{ loop.index }}. {{ item.name }}"}],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
}
|
|
52
|
+
doc = DocumentConfig.from_dict(config) # 解析 + 校验
|
|
53
|
+
data = {"items": [{"name": "A"}, {"name": "B"}]}
|
|
54
|
+
pdf_bytes = render_pdf(doc, data=data) # 服务器端生成,需要字体目录可用
|
|
55
|
+
render_pdf_to_file(doc, "out.pdf", data=data)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 核心能力
|
|
59
|
+
|
|
60
|
+
- **Schema 嵌套模型**:`table > thead/tbody/tfoot > tr > td/th`,单元格内容为任意元素树
|
|
61
|
+
(可再嵌表格 / 文本 / 控件 / for / if)。
|
|
62
|
+
- **模板引擎(Jinja2)**:`{{ }}` 表达式、`{% for %}` / `{% if %}` 标签任意层级摆放、
|
|
63
|
+
内置过滤器(数字/百分比/货币/截断/日期…),空变量宽松求值。
|
|
64
|
+
- **字体系统**:枚举系统字体(macOS/Linux/Windows),字符级回退
|
|
65
|
+
(CJK / 数字 / 拉丁 各自回退,类似 Word 字体绑定),中文字号(初号~八号),
|
|
66
|
+
TTC 抽取嵌入,PDF/A 输出。
|
|
67
|
+
- **双样式**:元素 `style`(通用/HTML 预览)+ `pdfStyle`(PDF 覆盖);
|
|
68
|
+
文档级 `html` / `pdf` 配置节。
|
|
69
|
+
- **布局**:流式布局 + 绝对定位(`position: "absolute"` + x/y)并存;
|
|
70
|
+
A4/A3/自定义任意尺寸;分页符、页眉页脚、页码。
|
|
71
|
+
|
|
72
|
+
## 开发
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv sync --all-extras # 安装(含 dev 依赖)
|
|
76
|
+
uv run pytest # 跑测试(真实渲染 PDF,禁止 mock)
|
|
77
|
+
uv run ruff check . # lint
|
|
78
|
+
uv run mypy src # 类型检查
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
质量红线:测试必须真实跑渲染(在 tmp_path 生成 PDF 并断言存在/页数/内容),
|
|
82
|
+
禁止 mock 渲染对象。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# pdfwright — 配置驱动的 PDF 生成基础库
|
|
2
|
+
|
|
3
|
+
纯 Python 引擎(ReportLab + fontTools + Jinja2 + pikepdf),**无外部软件依赖**
|
|
4
|
+
(不依赖 pango/cairo/Chromium/LibreOffice 等系统级软件)。
|
|
5
|
+
|
|
6
|
+
## 定位
|
|
7
|
+
|
|
8
|
+
`pdfwright` 是**基础库**:输入「页面 JSON 配置(schema)+ 数据(data)」,输出
|
|
9
|
+
高保真 PDF 或 HTML 预览。**不包含任何 web 服务与存储**。
|
|
10
|
+
|
|
11
|
+
- 需要可视化配置编辑器 / 对外 API 服务 → 使用 [`pdfwright-web`](../pdfwright-web/)。
|
|
12
|
+
- 只需本地渲染 → 直接 import 本库即可。
|
|
13
|
+
|
|
14
|
+
## 使用
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from pdfwright import DocumentConfig, SchemaError, render_pdf, render_pdf_to_file
|
|
18
|
+
|
|
19
|
+
config = {
|
|
20
|
+
"version": "1.0",
|
|
21
|
+
"page": {"size": "A4", "margin": {"top": 40, "bottom": 40, "left": 40, "right": 40}},
|
|
22
|
+
"variables": [
|
|
23
|
+
{"name": "title", "type": "string", "label": "标题", "default": "示例文档"},
|
|
24
|
+
{"name": "items", "type": "array", "label": "明细"},
|
|
25
|
+
],
|
|
26
|
+
"body": [
|
|
27
|
+
{"type": "heading", "text": "{{ title }}", "style": {"textAlign": "center", "fontSize": 18}},
|
|
28
|
+
{"type": "for", "each": "items", "as": "item",
|
|
29
|
+
"children": [{"type": "paragraph", "text": "{{ loop.index }}. {{ item.name }}"}],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
doc = DocumentConfig.from_dict(config) # 解析 + 校验
|
|
34
|
+
data = {"items": [{"name": "A"}, {"name": "B"}]}
|
|
35
|
+
pdf_bytes = render_pdf(doc, data=data) # 服务器端生成,需要字体目录可用
|
|
36
|
+
render_pdf_to_file(doc, "out.pdf", data=data)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 核心能力
|
|
40
|
+
|
|
41
|
+
- **Schema 嵌套模型**:`table > thead/tbody/tfoot > tr > td/th`,单元格内容为任意元素树
|
|
42
|
+
(可再嵌表格 / 文本 / 控件 / for / if)。
|
|
43
|
+
- **模板引擎(Jinja2)**:`{{ }}` 表达式、`{% for %}` / `{% if %}` 标签任意层级摆放、
|
|
44
|
+
内置过滤器(数字/百分比/货币/截断/日期…),空变量宽松求值。
|
|
45
|
+
- **字体系统**:枚举系统字体(macOS/Linux/Windows),字符级回退
|
|
46
|
+
(CJK / 数字 / 拉丁 各自回退,类似 Word 字体绑定),中文字号(初号~八号),
|
|
47
|
+
TTC 抽取嵌入,PDF/A 输出。
|
|
48
|
+
- **双样式**:元素 `style`(通用/HTML 预览)+ `pdfStyle`(PDF 覆盖);
|
|
49
|
+
文档级 `html` / `pdf` 配置节。
|
|
50
|
+
- **布局**:流式布局 + 绝对定位(`position: "absolute"` + x/y)并存;
|
|
51
|
+
A4/A3/自定义任意尺寸;分页符、页眉页脚、页码。
|
|
52
|
+
|
|
53
|
+
## 开发
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv sync --all-extras # 安装(含 dev 依赖)
|
|
57
|
+
uv run pytest # 跑测试(真实渲染 PDF,禁止 mock)
|
|
58
|
+
uv run ruff check . # lint
|
|
59
|
+
uv run mypy src # 类型检查
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
质量红线:测试必须真实跑渲染(在 tmp_path 生成 PDF 并断言存在/页数/内容),
|
|
63
|
+
禁止 mock 渲染对象。
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"meta": { "title": "劳动合同书" },
|
|
4
|
+
"page": {
|
|
5
|
+
"size": "A4",
|
|
6
|
+
"margin": { "top": 60, "right": 60, "bottom": 60, "left": 60 },
|
|
7
|
+
"header": { "text": "劳动合同书 · 编号 {{ contractNo }}", "pageNumber": true },
|
|
8
|
+
"footer": { "text": "xxx公司", "pageNumber": true }
|
|
9
|
+
},
|
|
10
|
+
"variables": [
|
|
11
|
+
{ "name": "contractNo", "type": "string", "label": "合同编号", "default": "HT-2026-0118" },
|
|
12
|
+
{ "name": "company", "type": "string", "label": "用人单位", "default": "xxx公司" },
|
|
13
|
+
{ "name": "employee", "type": "string", "label": "劳动者姓名", "default": "xxx" },
|
|
14
|
+
{ "name": "idNo", "type": "string", "label": "身份证号", "default": "3301**********1234" },
|
|
15
|
+
{ "name": "position", "type": "string", "label": "岗位", "default": "高级前端工程师" },
|
|
16
|
+
{ "name": "salary", "type": "number", "label": "月薪", "default": 32000 },
|
|
17
|
+
{ "name": "startDate", "type": "date", "label": "入职日期", "default": "2026-02-01" },
|
|
18
|
+
{ "name": "months", "type": "number", "label": "合同期限(月)", "default": 36 },
|
|
19
|
+
{ "name": "terms", "type": "array", "label": "条款", "default": [
|
|
20
|
+
{ "title": "工作内容与地点", "content": "乙方在甲方指定地点从事约定的岗位工作,甲方可根据经营需要合理调整。" },
|
|
21
|
+
{ "title": "劳动报酬", "content": "甲方每月 10 日前以货币形式支付乙方上月工资,依法代扣代缴个人所得税。" },
|
|
22
|
+
{ "title": "社会保险", "content": "甲乙双方依法参加社会保险,甲方为乙方办理参保手续并按时足额缴纳。" },
|
|
23
|
+
{ "title": "保密义务", "content": "乙方对甲方商业秘密、技术资料负有保密义务,该义务在合同终止后继续有效。" }
|
|
24
|
+
]},
|
|
25
|
+
{ "name": "signDate", "type": "date", "label": "签署日期", "default": "2026-01-18" },
|
|
26
|
+
{ "name": "signature", "type": "image", "label": "乙方签名" }
|
|
27
|
+
],
|
|
28
|
+
"body": [
|
|
29
|
+
{ "type": "heading", "text": "劳动合同书", "style": { "textAlign": "center", "fontSize": "一号", "fontFamily": "serif", "margin": { "bottom": 6 } } },
|
|
30
|
+
{ "type": "paragraph", "text": "合同编号:{{ contractNo }}", "style": { "textAlign": "center", "fontSize": 10, "color": "#94a3b8", "margin": { "bottom": 14 } } },
|
|
31
|
+
|
|
32
|
+
{ "type": "section", "style": { "padding": { "top": 8, "bottom": 8, "left": 10, "right": 10 }, "backgroundColor": "#f8fafc", "borderWidth": 0.5, "borderColor": "#cbd5e1" }, "children": [
|
|
33
|
+
{ "type": "paragraph", "text": "甲方(用人单位):{{ company }}", "style": { "fontSize": 11, "lineHeight": 1.7 } },
|
|
34
|
+
{ "type": "paragraph", "text": "乙方(劳动者):{{ employee }} 身份证号:{{ idNo }}", "style": { "fontSize": 11, "lineHeight": 1.7 } },
|
|
35
|
+
{ "type": "paragraph", "text": "岗位:{{ position }} 月薪:{{ salary | currency('¥') }} 入职日期:{{ startDate | date('YYYY年MM月DD日') }}", "style": { "fontSize": 11, "lineHeight": 1.7 } }
|
|
36
|
+
]},
|
|
37
|
+
|
|
38
|
+
{ "type": "paragraph", "text": "依据《中华人民共和国劳动法》《中华人民共和国劳动合同法》,甲乙双方经平等协商,订立本合同,共同遵守。", "style": { "fontSize": 11, "lineHeight": 1.8, "margin": { "top": 12, "bottom": 8 } } },
|
|
39
|
+
|
|
40
|
+
{ "type": "for", "each": "terms", "as": "term", "children": [
|
|
41
|
+
{ "type": "paragraph", "text": "第 {{ loop.index }} 条 {{ term.title }}", "style": { "fontSize": 12, "fontWeight": "bold", "margin": { "top": 8, "bottom": 2 } } },
|
|
42
|
+
{ "type": "paragraph", "text": "{{ term.content }}", "style": { "fontSize": 11, "lineHeight": 1.8, "padding": { "left": 12 } } }
|
|
43
|
+
]},
|
|
44
|
+
|
|
45
|
+
{ "type": "paragraph", "text": "本合同期限 {{ months }} 个月,自 {{ startDate | date('YYYY年MM月DD日') }} 起。", "style": { "fontSize": 11, "lineHeight": 1.8, "margin": { "top": 10 } } },
|
|
46
|
+
|
|
47
|
+
{ "type": "divider", "style": { "borderWidth": 0.5, "borderColor": "#cbd5e1", "margin": { "top": 16, "bottom": 12 } } },
|
|
48
|
+
|
|
49
|
+
{ "type": "row", "children": [
|
|
50
|
+
{ "type": "section", "style": { "padding": { "top": 4, "right": 8, "bottom": 4, "left": 0 } }, "children": [
|
|
51
|
+
{ "type": "paragraph", "text": "甲方(盖章):{{ company }}", "style": { "fontSize": 11, "lineHeight": 1.9 } },
|
|
52
|
+
{ "type": "paragraph", "text": "法定代表人:____________________", "style": { "fontSize": 11, "lineHeight": 1.9 } },
|
|
53
|
+
{ "type": "paragraph", "text": "日期:{{ signDate | date('YYYY年MM月DD日') }}", "style": { "fontSize": 11, "lineHeight": 1.9 } }
|
|
54
|
+
]},
|
|
55
|
+
{ "type": "section", "style": { "padding": { "top": 4, "right": 0, "bottom": 4, "left": 8 } }, "children": [
|
|
56
|
+
{ "type": "paragraph", "text": "乙方(签字):", "style": { "fontSize": 11, "lineHeight": 1.9 } },
|
|
57
|
+
{ "type": "image", "name": "signature", "label": "签名", "style": { "width": 110, "height": 44 } },
|
|
58
|
+
{ "type": "paragraph", "text": "日期:{{ signDate | date('YYYY年MM月DD日') }}", "style": { "fontSize": 11, "lineHeight": 1.9 } }
|
|
59
|
+
]}
|
|
60
|
+
]},
|
|
61
|
+
|
|
62
|
+
{ "type": "text", "text": "合同专用", "position": "absolute", "x": 60, "y": 330, "style": { "fontSize": 40, "color": "#fee2e2", "fontWeight": "bold" } }
|
|
63
|
+
]
|
|
64
|
+
}
|