pdfwright 0.1.2__tar.gz → 0.2.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 (192) hide show
  1. pdfwright-0.2.0/.gitignore +33 -0
  2. pdfwright-0.2.0/PKG-INFO +82 -0
  3. pdfwright-0.2.0/README.md +63 -0
  4. pdfwright-0.2.0/examples/contract.json +64 -0
  5. pdfwright-0.2.0/examples/cooperation_contract.json +803 -0
  6. pdfwright-0.2.0/examples/daily_report.json +962 -0
  7. pdfwright-0.2.0/examples/feature_tour.json +3381 -0
  8. pdfwright-0.2.0/examples/font_mixing.json +36 -0
  9. pdfwright-0.2.0/examples/generate_all.py +81 -0
  10. pdfwright-0.2.0/examples/generate_demo.py +32 -0
  11. pdfwright-0.2.0/examples/invoice.json +83 -0
  12. pdfwright-0.2.0/examples/output.html +59 -0
  13. pdfwright-0.2.0/examples/output.pdf +0 -0
  14. pdfwright-0.2.0/examples/path_security.json +36 -0
  15. pdfwright-0.2.0/examples/sample_document.json +89 -0
  16. pdfwright-0.2.0/examples/service_contract.json +614 -0
  17. pdfwright-0.2.0/examples/staff_form.json +106 -0
  18. pdfwright-0.2.0/pyproject.toml +98 -0
  19. pdfwright-0.2.0/src/pdfwright/__init__.py +58 -0
  20. pdfwright-0.2.0/src/pdfwright/batch.py +205 -0
  21. pdfwright-0.2.0/src/pdfwright/converters/__init__.py +28 -0
  22. pdfwright-0.2.0/src/pdfwright/converters/docx.py +1465 -0
  23. pdfwright-0.2.0/src/pdfwright/converters/html.py +822 -0
  24. pdfwright-0.2.0/src/pdfwright/converters/xlsx.py +192 -0
  25. pdfwright-0.2.0/src/pdfwright/errors.py +149 -0
  26. pdfwright-0.2.0/src/pdfwright/fonts/__init__.py +55 -0
  27. pdfwright-0.2.0/src/pdfwright/fonts/fallback.py +171 -0
  28. pdfwright-0.2.0/src/pdfwright/fonts/registry.py +736 -0
  29. pdfwright-0.2.0/src/pdfwright/fonts/scanner.py +535 -0
  30. pdfwright-0.2.0/src/pdfwright/fonts/sfnt.py +85 -0
  31. pdfwright-0.2.0/src/pdfwright/fonts/system_fonts.py +243 -0
  32. pdfwright-0.2.0/src/pdfwright/models/__init__.py +150 -0
  33. pdfwright-0.2.0/src/pdfwright/models/borders.py +214 -0
  34. pdfwright-0.2.0/src/pdfwright/models/constants.py +118 -0
  35. pdfwright-0.2.0/src/pdfwright/models/document.py +313 -0
  36. pdfwright-0.2.0/src/pdfwright/models/elements.py +474 -0
  37. pdfwright-0.2.0/src/pdfwright/models/style.py +161 -0
  38. pdfwright-0.2.0/src/pdfwright/postprocess/__init__.py +23 -0
  39. pdfwright-0.2.0/src/pdfwright/postprocess/metadata.py +180 -0
  40. pdfwright-0.2.0/src/pdfwright/postprocess/pdfa_verify.py +520 -0
  41. pdfwright-0.2.0/src/pdfwright/py.typed +0 -0
  42. pdfwright-0.2.0/src/pdfwright/render/__init__.py +12 -0
  43. pdfwright-0.2.0/src/pdfwright/render/base.py +300 -0
  44. pdfwright-0.2.0/src/pdfwright/render/codes.py +143 -0
  45. pdfwright-0.2.0/src/pdfwright/render/html/__init__.py +966 -0
  46. pdfwright-0.2.0/src/pdfwright/render/markup.py +112 -0
  47. pdfwright-0.2.0/src/pdfwright/render/pdf.py +2423 -0
  48. pdfwright-0.2.0/src/pdfwright/render/pdfa.py +292 -0
  49. pdfwright-0.2.0/src/pdfwright/render/table.py +862 -0
  50. pdfwright-0.2.0/src/pdfwright/security.py +249 -0
  51. pdfwright-0.2.0/src/pdfwright/template/__init__.py +16 -0
  52. pdfwright-0.2.0/src/pdfwright/template/context.py +38 -0
  53. pdfwright-0.2.0/src/pdfwright/template/engine.py +432 -0
  54. pdfwright-0.2.0/src/pdfwright/template/filters.py +258 -0
  55. pdfwright-0.2.0/src/pdfwright/template/format.py +99 -0
  56. pdfwright-0.2.0/src/pdfwright/utils.py +168 -0
  57. pdfwright-0.2.0/src/pdfwright/validate/__init__.py +407 -0
  58. pdfwright-0.2.0/tests/test_docx_converter.py +502 -0
  59. pdfwright-0.2.0/tests/test_examples.py +58 -0
  60. pdfwright-0.2.0/tests/test_fonts.py +174 -0
  61. pdfwright-0.2.0/tests/test_html_converter.py +152 -0
  62. pdfwright-0.2.0/tests/test_models.py +308 -0
  63. pdfwright-0.2.0/tests/test_render_html.py +570 -0
  64. pdfwright-0.2.0/tests/test_render_pdf.py +2418 -0
  65. pdfwright-0.2.0/tests/test_render_table.py +245 -0
  66. pdfwright-0.2.0/tests/test_security.py +121 -0
  67. pdfwright-0.2.0/tests/test_shared_vectors.py +44 -0
  68. pdfwright-0.2.0/tests/test_template.py +200 -0
  69. pdfwright-0.2.0/tests/test_validate.py +108 -0
  70. pdfwright-0.2.0/tests/test_xlsx_converter.py +78 -0
  71. pdfwright-0.2.0/uv.lock +1336 -0
  72. pdfwright-0.1.2/LICENSE +0 -21
  73. pdfwright-0.1.2/PKG-INFO +0 -917
  74. pdfwright-0.1.2/README.md +0 -868
  75. pdfwright-0.1.2/pdfwright/__init__.py +0 -255
  76. pdfwright-0.1.2/pdfwright/cli.py +0 -94
  77. pdfwright-0.1.2/pdfwright/concurrent/__init__.py +0 -23
  78. pdfwright-0.1.2/pdfwright/concurrent/batch.py +0 -311
  79. pdfwright-0.1.2/pdfwright/concurrent/cancel.py +0 -154
  80. pdfwright-0.1.2/pdfwright/concurrent/generator.py +0 -253
  81. pdfwright-0.1.2/pdfwright/concurrent/progress.py +0 -179
  82. pdfwright-0.1.2/pdfwright/config/__init__.py +0 -4
  83. pdfwright-0.1.2/pdfwright/config/loader.py +0 -1189
  84. pdfwright-0.1.2/pdfwright/config/schema.py +0 -174
  85. pdfwright-0.1.2/pdfwright/config/schema_export.py +0 -136
  86. pdfwright-0.1.2/pdfwright/converters/__init__.py +0 -71
  87. pdfwright-0.1.2/pdfwright/converters/docx_converter.py +0 -910
  88. pdfwright-0.1.2/pdfwright/converters/jinja_blocks.py +0 -443
  89. pdfwright-0.1.2/pdfwright/converters/libreoffice.py +0 -232
  90. pdfwright-0.1.2/pdfwright/converters/xlsx_converter.py +0 -603
  91. pdfwright-0.1.2/pdfwright/core/__init__.py +0 -42
  92. pdfwright-0.1.2/pdfwright/core/document.py +0 -203
  93. pdfwright-0.1.2/pdfwright/core/elements.py +0 -1508
  94. pdfwright-0.1.2/pdfwright/core/field_meta.py +0 -430
  95. pdfwright-0.1.2/pdfwright/core/html_parser.py +0 -515
  96. pdfwright-0.1.2/pdfwright/core/styles.py +0 -116
  97. pdfwright-0.1.2/pdfwright/core/type_registry.py +0 -78
  98. pdfwright-0.1.2/pdfwright/errors.py +0 -838
  99. pdfwright-0.1.2/pdfwright/extensions/__init__.py +0 -59
  100. pdfwright-0.1.2/pdfwright/extensions/bookmarks.py +0 -378
  101. pdfwright-0.1.2/pdfwright/extensions/graphics.py +0 -412
  102. pdfwright-0.1.2/pdfwright/extensions/toc.py +0 -264
  103. pdfwright-0.1.2/pdfwright/extensions/watermark.py +0 -337
  104. pdfwright-0.1.2/pdfwright/fonts/__init__.py +0 -3
  105. pdfwright-0.1.2/pdfwright/fonts/manager.py +0 -1271
  106. pdfwright-0.1.2/pdfwright/inspector/__init__.py +0 -58
  107. pdfwright-0.1.2/pdfwright/inspector/docx_inspector.py +0 -1207
  108. pdfwright-0.1.2/pdfwright/inspector/variables.py +0 -129
  109. pdfwright-0.1.2/pdfwright/inspector/xlsx_inspector.py +0 -266
  110. pdfwright-0.1.2/pdfwright/performance/__init__.py +0 -34
  111. pdfwright-0.1.2/pdfwright/performance/cache.py +0 -472
  112. pdfwright-0.1.2/pdfwright/performance/font_subset.py +0 -236
  113. pdfwright-0.1.2/pdfwright/postprocess/__init__.py +0 -30
  114. pdfwright-0.1.2/pdfwright/postprocess/form_fields.py +0 -146
  115. pdfwright-0.1.2/pdfwright/postprocess/icc.py +0 -179
  116. pdfwright-0.1.2/pdfwright/postprocess/metadata.py +0 -81
  117. pdfwright-0.1.2/pdfwright/postprocess/pdfa.py +0 -490
  118. pdfwright-0.1.2/pdfwright/postprocess/pipeline.py +0 -90
  119. pdfwright-0.1.2/pdfwright/postprocess/seal_marker.py +0 -190
  120. pdfwright-0.1.2/pdfwright/postprocess/version.py +0 -48
  121. pdfwright-0.1.2/pdfwright/renderers/__init__.py +0 -4
  122. pdfwright-0.1.2/pdfwright/renderers/base.py +0 -25
  123. pdfwright-0.1.2/pdfwright/renderers/flowables.py +0 -870
  124. pdfwright-0.1.2/pdfwright/renderers/reportlab_renderer.py +0 -4903
  125. pdfwright-0.1.2/pdfwright/security/__init__.py +0 -37
  126. pdfwright-0.1.2/pdfwright/security/sanitizer.py +0 -294
  127. pdfwright-0.1.2/pdfwright/security/validator.py +0 -513
  128. pdfwright-0.1.2/pdfwright/template/__init__.py +0 -25
  129. pdfwright-0.1.2/pdfwright/template/engine.py +0 -1165
  130. pdfwright-0.1.2/pdfwright/template/html_to_rl.py +0 -292
  131. pdfwright-0.1.2/pdfwright/template/loader.py +0 -169
  132. pdfwright-0.1.2/pdfwright/utils/__init__.py +0 -3
  133. pdfwright-0.1.2/pdfwright/utils/units.py +0 -80
  134. pdfwright-0.1.2/pdfwright.egg-info/PKG-INFO +0 -917
  135. pdfwright-0.1.2/pdfwright.egg-info/SOURCES.txt +0 -119
  136. pdfwright-0.1.2/pdfwright.egg-info/dependency_links.txt +0 -1
  137. pdfwright-0.1.2/pdfwright.egg-info/entry_points.txt +0 -2
  138. pdfwright-0.1.2/pdfwright.egg-info/requires.txt +0 -35
  139. pdfwright-0.1.2/pdfwright.egg-info/top_level.txt +0 -1
  140. pdfwright-0.1.2/pyproject.toml +0 -323
  141. pdfwright-0.1.2/setup.cfg +0 -4
  142. pdfwright-0.1.2/tests/test_all_types_render.py +0 -169
  143. pdfwright-0.1.2/tests/test_basic.py +0 -350
  144. pdfwright-0.1.2/tests/test_character_style_deep.py +0 -142
  145. pdfwright-0.1.2/tests/test_cli.py +0 -150
  146. pdfwright-0.1.2/tests/test_concurrent.py +0 -719
  147. pdfwright-0.1.2/tests/test_convert_comprehensive.py +0 -558
  148. pdfwright-0.1.2/tests/test_converters.py +0 -211
  149. pdfwright-0.1.2/tests/test_docx_converter_deep.py +0 -207
  150. pdfwright-0.1.2/tests/test_docx_inspector.py +0 -86
  151. pdfwright-0.1.2/tests/test_docx_inspector_deep.py +0 -170
  152. pdfwright-0.1.2/tests/test_elements.py +0 -157
  153. pdfwright-0.1.2/tests/test_elements_deep.py +0 -70
  154. pdfwright-0.1.2/tests/test_engine_edges.py +0 -181
  155. pdfwright-0.1.2/tests/test_errors.py +0 -148
  156. pdfwright-0.1.2/tests/test_extensions.py +0 -424
  157. pdfwright-0.1.2/tests/test_final_branches.py +0 -389
  158. pdfwright-0.1.2/tests/test_final_push.py +0 -241
  159. pdfwright-0.1.2/tests/test_final_render.py +0 -395
  160. pdfwright-0.1.2/tests/test_fonts.py +0 -308
  161. pdfwright-0.1.2/tests/test_fonts_deep.py +0 -118
  162. pdfwright-0.1.2/tests/test_form_fields.py +0 -175
  163. pdfwright-0.1.2/tests/test_generate_pipeline.py +0 -363
  164. pdfwright-0.1.2/tests/test_html_parser.py +0 -214
  165. pdfwright-0.1.2/tests/test_inspector.py +0 -289
  166. pdfwright-0.1.2/tests/test_inspector_deep.py +0 -112
  167. pdfwright-0.1.2/tests/test_jinja_office.py +0 -591
  168. pdfwright-0.1.2/tests/test_last_mile.py +0 -299
  169. pdfwright-0.1.2/tests/test_last_push.py +0 -337
  170. pdfwright-0.1.2/tests/test_nested_list_render.py +0 -159
  171. pdfwright-0.1.2/tests/test_pagination_policy.py +0 -262
  172. pdfwright-0.1.2/tests/test_pdfa.py +0 -373
  173. pdfwright-0.1.2/tests/test_performance.py +0 -424
  174. pdfwright-0.1.2/tests/test_push_70.py +0 -231
  175. pdfwright-0.1.2/tests/test_render_elements.py +0 -423
  176. pdfwright-0.1.2/tests/test_render_feature_fields.py +0 -318
  177. pdfwright-0.1.2/tests/test_renderer_final.py +0 -244
  178. pdfwright-0.1.2/tests/test_renderer_helpers.py +0 -1435
  179. pdfwright-0.1.2/tests/test_renderer_more.py +0 -280
  180. pdfwright-0.1.2/tests/test_renderer_table.py +0 -267
  181. pdfwright-0.1.2/tests/test_schema_export.py +0 -106
  182. pdfwright-0.1.2/tests/test_seal_marker.py +0 -125
  183. pdfwright-0.1.2/tests/test_seal_marker_element.py +0 -139
  184. pdfwright-0.1.2/tests/test_security.py +0 -600
  185. pdfwright-0.1.2/tests/test_table_borders_and_cell_fields.py +0 -163
  186. pdfwright-0.1.2/tests/test_table_pagination.py +0 -351
  187. pdfwright-0.1.2/tests/test_template.py +0 -409
  188. pdfwright-0.1.2/tests/test_template_rendering.py +0 -292
  189. pdfwright-0.1.2/tests/test_units.py +0 -110
  190. pdfwright-0.1.2/tests/test_visual_example.py +0 -50
  191. pdfwright-0.1.2/tests/test_watermark_deep.py +0 -413
  192. 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
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.5
2
+ Name: pdfwright
3
+ Version: 0.2.0
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<10.14,>=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<5,>=4.2
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
+ }