PyPDFForm 5.2.1__tar.gz → 5.2.3__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 (88) hide show
  1. {pypdfform-5.2.1 → pypdfform-5.2.3}/PKG-INFO +9 -3
  2. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/create.py +148 -14
  4. pypdfform-5.2.3/PyPDFForm/cli/inspect.py +129 -0
  5. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/remove.py +23 -2
  6. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/root.py +41 -3
  7. pypdfform-5.2.3/PyPDFForm/cli/update.py +331 -0
  8. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/adapter.py +5 -0
  9. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/egress.py +10 -5
  10. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/filler.py +16 -10
  11. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/font.py +45 -23
  12. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/hooks.py +25 -15
  13. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/image.py +7 -8
  14. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/base.py +14 -9
  15. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/checkbox.py +3 -2
  16. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/dropdown.py +12 -5
  17. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/signature.py +6 -6
  18. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/patterns.py +21 -10
  19. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/image.py +2 -1
  20. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/text.py +3 -3
  21. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/template.py +70 -19
  22. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/types.py +3 -2
  23. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/utils.py +29 -19
  24. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/watermark.py +57 -25
  25. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/base.py +20 -18
  26. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/dropdown.py +11 -4
  27. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/image.py +6 -6
  28. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/radio.py +6 -2
  29. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/signature.py +17 -10
  30. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/wrapper.py +117 -40
  31. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm.egg-info/PKG-INFO +9 -3
  32. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm.egg-info/requires.txt +1 -1
  33. {pypdfform-5.2.1 → pypdfform-5.2.3}/README.md +7 -1
  34. {pypdfform-5.2.1 → pypdfform-5.2.3}/pyproject.toml +1 -1
  35. pypdfform-5.2.1/PyPDFForm/cli/inspect.py +0 -69
  36. pypdfform-5.2.1/PyPDFForm/cli/update.py +0 -206
  37. {pypdfform-5.2.1 → pypdfform-5.2.3}/LICENSE +0 -0
  38. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/__init__.py +0 -0
  39. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/common.py +0 -0
  40. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/entry.py +0 -0
  41. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/schemas/__init__.py +0 -0
  42. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/schemas/create.py +0 -0
  43. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/cli/schemas/update.py +0 -0
  44. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/__init__.py +0 -0
  45. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/annotations/__init__.py +0 -0
  46. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/annotations/base.py +0 -0
  47. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/annotations/link.py +0 -0
  48. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/annotations/stamp.py +0 -0
  49. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/annotations/text.py +0 -0
  50. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/annotations/text_markup.py +0 -0
  51. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/assets/__init__.py +0 -0
  52. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/assets/bedrock.py +0 -0
  53. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/assets/blank.py +0 -0
  54. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/constants.py +0 -0
  55. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/coordinate.py +0 -0
  56. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/deprecation.py +0 -0
  57. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/__init__.py +0 -0
  58. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/image.py +0 -0
  59. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/radio.py +0 -0
  60. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/middleware/text.py +0 -0
  61. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/__init__.py +0 -0
  62. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/circle.py +0 -0
  63. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/ellipse.py +0 -0
  64. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/line.py +0 -0
  65. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/raw/rect.py +0 -0
  66. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/__init__.py +0 -0
  67. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/checkbox.py +0 -0
  68. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm/lib/widgets/text.py +0 -0
  69. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  70. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  71. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm.egg-info/entry_points.txt +0 -0
  72. {pypdfform-5.2.1 → pypdfform-5.2.3}/PyPDFForm.egg-info/top_level.txt +0 -0
  73. {pypdfform-5.2.1 → pypdfform-5.2.3}/setup.cfg +0 -0
  74. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_bulk_create_fields.py +0 -0
  75. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_create_widget.py +0 -0
  76. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_draw_elements.py +0 -0
  77. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_dropdown.py +0 -0
  78. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_extract_middleware_attributes.py +0 -0
  79. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_fill_max_length_text_field.py +0 -0
  80. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_font_widths.py +0 -0
  81. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_functional.py +0 -0
  82. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_generate_appearance_streams.py +0 -0
  83. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_js.py +0 -0
  84. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_need_appearances.py +0 -0
  85. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_paragraph.py +0 -0
  86. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_signature.py +0 -0
  87. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_use_full_widget_name.py +0 -0
  88. {pypdfform-5.2.1 → pypdfform-5.2.3}/tests/test_widget_attr_trigger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 5.2.1
3
+ Version: 5.2.3
4
4
  Summary: The Python library & CLI for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -29,7 +29,7 @@ Requires-Dist: fonttools<5.0.0,>=4.63.0
29
29
  Requires-Dist: pikepdf<11.0.0,>=10.7.2
30
30
  Requires-Dist: pillow<13.0.0,>=12.2.0
31
31
  Requires-Dist: pypdf<7.0.0,>=6.12.2
32
- Requires-Dist: reportlab<5.0.0,>=4.5.1
32
+ Requires-Dist: reportlab<6.0.0,>=4.5.1
33
33
  Provides-Extra: cli
34
34
  Requires-Dist: typer<1.0.0,>=0.26.1; extra == "cli"
35
35
  Requires-Dist: jsonschema<5.0.0,>=4.26.0; extra == "cli"
@@ -125,4 +125,10 @@ The official documentation can be found on [the GitHub page](https://chinapandam
125
125
 
126
126
  This project is maintained entirely in my spare time. If you like the project please consider starring the GitHub repository. It is the best way to keep me motivated and continue making the project better.
127
127
 
128
- [![Stargazers over time](https://starchart.cc/chinapandaman/PyPDFForm.svg?variant=adaptive)](https://starchart.cc/chinapandaman/PyPDFForm)
128
+ <a href="https://www.star-history.com/?repos=chinapandaman%2FPyPDFForm&type=date&logscale=&legend=top-left">
129
+ <picture>
130
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=chinapandaman/PyPDFForm&type=date&theme=dark&legend=top-left" />
131
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=chinapandaman/PyPDFForm&type=date&legend=top-left" />
132
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=chinapandaman/PyPDFForm&type=date&legend=top-left" />
133
+ </picture>
134
+ </a>
@@ -18,7 +18,7 @@ from Python code or from the command line.
18
18
 
19
19
  import logging
20
20
 
21
- __version__ = "5.2.1"
21
+ __version__ = "5.2.3"
22
22
 
23
23
  from .lib.annotations import Annotations
24
24
  from .lib.assets.blank import BlankPage
@@ -30,7 +30,10 @@ create_cli = typer.Typer(
30
30
  )
31
31
 
32
32
 
33
- @create_cli.command(no_args_is_help=True)
33
+ @create_cli.command(
34
+ no_args_is_help=True,
35
+ help="Create a new blank PDF.",
36
+ )
34
37
  def blank(
35
38
  ctx: typer.Context,
36
39
  output: REQUIRED_OUTPUT_PDF,
@@ -60,7 +63,23 @@ def blank(
60
63
  ),
61
64
  ] = None,
62
65
  ) -> None:
63
- """Create a new blank PDF."""
66
+ """
67
+ Create a blank PDF with optional page size and page count.
68
+
69
+ The command builds a `BlankPage` using the supplied dimensions, duplicates
70
+ it when multiple pages are requested, wraps the result with the global CLI
71
+ options stored in `ctx.obj`, and writes the new PDF to the required output
72
+ path.
73
+
74
+ Args:
75
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
76
+ options in `ctx.obj`.
77
+ output (Path): Output PDF path.
78
+ count (int, optional): Number of blank pages to create. Defaults to
79
+ None.
80
+ width (float, optional): Page width in points. Defaults to None.
81
+ height (float, optional): Page height in points. Defaults to None.
82
+ """
64
83
  params = {}
65
84
  if width is not None:
66
85
  params["width"] = width
@@ -74,7 +93,10 @@ def blank(
74
93
  PdfWrapper(obj, **ctx.obj).write(output)
75
94
 
76
95
 
77
- @create_cli.command(no_args_is_help=True)
96
+ @create_cli.command(
97
+ no_args_is_help=True,
98
+ help="Extract pages from an existing PDF.",
99
+ )
78
100
  def extract(
79
101
  ctx: typer.Context,
80
102
  pdf: INPUT_PDF,
@@ -98,7 +120,28 @@ def extract(
98
120
  ),
99
121
  ] = None,
100
122
  ) -> None:
101
- """Extract pages from an existing PDF."""
123
+ """
124
+ Extract a page range from an existing PDF.
125
+
126
+ The command validates that the requested end page does not precede the
127
+ start page, converts the 1-based CLI page numbers into the slice expected
128
+ by `PdfWrapper.pages`, and writes the extracted pages to the required
129
+ output path.
130
+
131
+ Args:
132
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
133
+ options in `ctx.obj`.
134
+ pdf (Path): Input PDF path.
135
+ output (Path): Output PDF path.
136
+ start (int, optional): First page to extract, starting at 1. Defaults
137
+ to None.
138
+ end (int, optional): Last page to extract, starting at 1. Defaults to
139
+ None.
140
+
141
+ Raises:
142
+ typer.BadParameter: Raised when both page bounds are supplied and the
143
+ start page is after the end page.
144
+ """
102
145
  if start is not None and end is not None and start > end:
103
146
  message = "End page must be greater than or equal to start page."
104
147
  cli_bad_parameter(
@@ -109,7 +152,10 @@ def extract(
109
152
  PdfWrapper(str(pdf), **ctx.obj).pages[slice((start or 1) - 1, end)].write(output)
110
153
 
111
154
 
112
- @create_cli.command(no_args_is_help=True)
155
+ @create_cli.command(
156
+ no_args_is_help=True,
157
+ help="Merge multiple PDFs into one.",
158
+ )
113
159
  def merge(
114
160
  ctx: typer.Context,
115
161
  pdfs: Annotated[
@@ -125,18 +171,48 @@ def merge(
125
171
  ],
126
172
  output: REQUIRED_OUTPUT_PDF,
127
173
  ) -> None:
128
- """Merge multiple PDFs into one."""
174
+ """
175
+ Merge input PDFs in the order provided on the command line.
176
+
177
+ Each path is loaded into a `PdfWrapper` with the global CLI options, passed
178
+ to `PdfArray`, merged into one document, and written to the required output
179
+ path.
180
+
181
+ Args:
182
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
183
+ options in `ctx.obj`.
184
+ pdfs (list[Path]): Input PDF paths in merge order.
185
+ output (Path): Output PDF path.
186
+ """
129
187
  PdfArray([PdfWrapper(str(pdf), **ctx.obj) for pdf in pdfs]).merge().write(output)
130
188
 
131
189
 
132
- @create_cli.command(no_args_is_help=True)
190
+ @create_cli.command(
191
+ no_args_is_help=True,
192
+ help="Add form fields to a PDF.",
193
+ )
133
194
  def field(
134
195
  ctx: typer.Context,
135
196
  pdf: INPUT_PDF,
136
197
  data: Annotated[Path, json_file_option("JSON file with form field definitions.")],
137
198
  output: OPTIONAL_OUTPUT_PDF = None,
138
199
  ) -> None:
139
- """Add form fields to a PDF."""
200
+ """
201
+ Add form fields described by grouped JSON definitions.
202
+
203
+ The command maps JSON groups such as `text`, `check`, and `signature` to
204
+ PyPDFForm field classes, validates the input file against the CLI field
205
+ schema, creates the corresponding field objects, and calls
206
+ `PdfWrapper.bulk_create_fields` before writing the modified PDF.
207
+
208
+ Args:
209
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
210
+ options in `ctx.obj`.
211
+ pdf (Path): Input PDF path.
212
+ data (Path): JSON file containing grouped form field definitions.
213
+ output (Path, optional): Output PDF path. If omitted, the input PDF is
214
+ overwritten. Defaults to None.
215
+ """
140
216
  field_map = {
141
217
  "text": Fields.TextField,
142
218
  "check": Fields.CheckBoxField,
@@ -157,14 +233,32 @@ def field(
157
233
  )
158
234
 
159
235
 
160
- @create_cli.command(no_args_is_help=True)
236
+ @create_cli.command(
237
+ no_args_is_help=True,
238
+ help="Draw text, images, and shapes on a PDF.",
239
+ )
161
240
  def raw(
162
241
  ctx: typer.Context,
163
242
  pdf: INPUT_PDF,
164
243
  data: Annotated[Path, json_file_option("JSON file with raw element definitions.")],
165
244
  output: OPTIONAL_OUTPUT_PDF = None,
166
245
  ) -> None:
167
- """Draw text, images, and shapes on a PDF."""
246
+ """
247
+ Draw raw elements described by grouped JSON definitions.
248
+
249
+ The command maps JSON groups such as `text`, `image`, and `rectangle` to
250
+ raw element classes, validates the input file against the CLI raw element
251
+ schema, creates the corresponding drawable objects, and calls
252
+ `PdfWrapper.draw` before writing the modified PDF.
253
+
254
+ Args:
255
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
256
+ options in `ctx.obj`.
257
+ pdf (Path): Input PDF path.
258
+ data (Path): JSON file containing grouped raw element definitions.
259
+ output (Path, optional): Output PDF path. If omitted, the input PDF is
260
+ overwritten. Defaults to None.
261
+ """
168
262
  raw_element_map = {
169
263
  "text": RawElements.RawText,
170
264
  "image": RawElements.RawImage,
@@ -185,14 +279,32 @@ def raw(
185
279
  )
186
280
 
187
281
 
188
- @create_cli.command(no_args_is_help=True)
282
+ @create_cli.command(
283
+ no_args_is_help=True,
284
+ help="Add annotations to a PDF.",
285
+ )
189
286
  def annotation(
190
287
  ctx: typer.Context,
191
288
  pdf: INPUT_PDF,
192
289
  data: Annotated[Path, json_file_option("JSON file with annotation definitions.")],
193
290
  output: OPTIONAL_OUTPUT_PDF = None,
194
291
  ) -> None:
195
- """Add annotations to a PDF."""
292
+ """
293
+ Add annotations described by grouped JSON definitions.
294
+
295
+ The command maps JSON groups such as `text`, `link`, and `highlight` to
296
+ annotation classes, validates the input file against the CLI annotation
297
+ schema, creates the corresponding annotation objects, and calls
298
+ `PdfWrapper.annotate` before writing the modified PDF.
299
+
300
+ Args:
301
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
302
+ options in `ctx.obj`.
303
+ pdf (Path): Input PDF path.
304
+ data (Path): JSON file containing grouped annotation definitions.
305
+ output (Path, optional): Output PDF path. If omitted, the input PDF is
306
+ overwritten. Defaults to None.
307
+ """
196
308
  annotation_map = {
197
309
  "text": Annotations.TextAnnotation,
198
310
  "link": Annotations.LinkAnnotation,
@@ -214,7 +326,10 @@ def annotation(
214
326
  )
215
327
 
216
328
 
217
- @create_cli.command(no_args_is_help=True)
329
+ @create_cli.command(
330
+ no_args_is_help=True,
331
+ help="Add a coordinate grid to a PDF.",
332
+ )
218
333
  def grid(
219
334
  ctx: typer.Context,
220
335
  pdf: INPUT_PDF,
@@ -259,7 +374,26 @@ def grid(
259
374
  ),
260
375
  ] = None,
261
376
  ) -> None:
262
- """Add a coordinate grid to a PDF."""
377
+ """
378
+ Overlay a coordinate grid on an existing PDF.
379
+
380
+ The command collects optional RGB color components and margin values,
381
+ normalizes whole-number margins to integers for stable output, generates a
382
+ coordinate grid through `PdfWrapper.generate_coordinate_grid`, and writes
383
+ the result to the requested output path or back to the input file.
384
+
385
+ Args:
386
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
387
+ options in `ctx.obj`.
388
+ pdf (Path): Input PDF path.
389
+ output (Path, optional): Output PDF path. If omitted, the input PDF is
390
+ overwritten. Defaults to None.
391
+ red (float, optional): Grid red value from 0 to 1. Defaults to None.
392
+ green (float, optional): Grid green value from 0 to 1. Defaults to
393
+ None.
394
+ blue (float, optional): Grid blue value from 0 to 1. Defaults to None.
395
+ margin (float, optional): Grid margin in points. Defaults to None.
396
+ """
263
397
  params = {}
264
398
  if any(
265
399
  [
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module defines CLI commands for inspecting PDF form information.
4
+
5
+ It exposes the `inspect` command group, which prints JSON for form schemas,
6
+ current form values, generated sample data, and field rectangle metadata.
7
+ Each command wraps read-only `PdfWrapper` properties so users can inspect forms
8
+ from the terminal without writing Python code.
9
+ """
10
+
11
+ import json
12
+
13
+ import typer
14
+
15
+ from .. import PdfWrapper
16
+ from .common import FIELD_NAME, INPUT_PDF, get_widget
17
+
18
+ inspect_cli = typer.Typer(
19
+ context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
20
+ )
21
+
22
+
23
+ @inspect_cli.command(
24
+ no_args_is_help=True,
25
+ help="Print the form schema as JSON.",
26
+ )
27
+ def schema(
28
+ ctx: typer.Context,
29
+ pdf: INPUT_PDF,
30
+ ) -> None:
31
+ """
32
+ Print the JSON schema for filling an existing PDF form.
33
+
34
+ The command loads the PDF with the global CLI options stored in `ctx.obj`,
35
+ reads `PdfWrapper.schema`, serializes it as JSON, and writes it to standard
36
+ output.
37
+
38
+ Args:
39
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
40
+ options in `ctx.obj`.
41
+ pdf (Path): Input PDF path.
42
+ """
43
+ typer.echo(json.dumps(PdfWrapper(str(pdf), **ctx.obj).schema))
44
+
45
+
46
+ @inspect_cli.command(
47
+ no_args_is_help=True,
48
+ help="Print current form data as JSON.",
49
+ )
50
+ def data(
51
+ ctx: typer.Context,
52
+ pdf: INPUT_PDF,
53
+ ) -> None:
54
+ """
55
+ Print the current field values from an existing PDF form.
56
+
57
+ The command loads the PDF with the global CLI options stored in `ctx.obj`,
58
+ reads `PdfWrapper.data`, serializes it as JSON, and writes it to standard
59
+ output.
60
+
61
+ Args:
62
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
63
+ options in `ctx.obj`.
64
+ pdf (Path): Input PDF path.
65
+ """
66
+ typer.echo(json.dumps(PdfWrapper(str(pdf), **ctx.obj).data))
67
+
68
+
69
+ @inspect_cli.command(
70
+ no_args_is_help=True,
71
+ help="Print sample fill data as JSON.",
72
+ )
73
+ def sample(
74
+ ctx: typer.Context,
75
+ pdf: INPUT_PDF,
76
+ ) -> None:
77
+ """
78
+ Print generated sample data for filling an existing PDF form.
79
+
80
+ The command loads the PDF with the global CLI options stored in `ctx.obj`,
81
+ reads `PdfWrapper.sample_data`, serializes it as JSON, and writes it to
82
+ standard output.
83
+
84
+ Args:
85
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
86
+ options in `ctx.obj`.
87
+ pdf (Path): Input PDF path.
88
+ """
89
+ typer.echo(json.dumps(PdfWrapper(str(pdf), **ctx.obj).sample_data))
90
+
91
+
92
+ @inspect_cli.command(
93
+ no_args_is_help=True,
94
+ help="Print a form field's location and size as JSON.",
95
+ )
96
+ def location(
97
+ ctx: typer.Context,
98
+ pdf: INPUT_PDF,
99
+ field: FIELD_NAME,
100
+ ) -> None:
101
+ """
102
+ Print geometry metadata for a single form field.
103
+
104
+ The command loads the PDF with the global CLI options stored in `ctx.obj`,
105
+ resolves the requested widget name, and prints a JSON object containing the
106
+ page number, x-coordinate, y-coordinate, width, and height.
107
+
108
+ Args:
109
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
110
+ options in `ctx.obj`.
111
+ pdf (Path): Input PDF path.
112
+ field (str): Form field name to inspect.
113
+
114
+ Raises:
115
+ typer.BadParameter: Raised when the requested field does not exist.
116
+ """
117
+ f = get_widget(PdfWrapper(str(pdf), **ctx.obj), field, "--field")
118
+
119
+ typer.echo(
120
+ json.dumps(
121
+ {
122
+ "page_number": f.page_number,
123
+ "x": f.x,
124
+ "y": f.y,
125
+ "width": f.width,
126
+ "height": f.height,
127
+ }
128
+ )
129
+ )
@@ -18,14 +18,35 @@ remove_cli = typer.Typer(
18
18
  )
19
19
 
20
20
 
21
- @remove_cli.command(no_args_is_help=True)
21
+ @remove_cli.command(
22
+ no_args_is_help=True,
23
+ help="Remove form fields from a PDF.",
24
+ )
22
25
  def field(
23
26
  ctx: typer.Context,
24
27
  pdf: INPUT_PDF,
25
28
  fields: FIELD_NAMES,
26
29
  output: OPTIONAL_OUTPUT_PDF = None,
27
30
  ) -> None:
28
- """Remove form fields from a PDF."""
31
+ """
32
+ Remove one or more named form fields from an existing PDF.
33
+
34
+ The command loads the PDF with the global CLI options stored in `ctx.obj`,
35
+ validates each requested field name before changing the document, removes
36
+ the fields through `PdfWrapper.remove_fields`, and writes the modified PDF
37
+ to the requested output path or back to the input file.
38
+
39
+ Args:
40
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
41
+ options in `ctx.obj`.
42
+ pdf (Path): Input PDF path.
43
+ fields (list[str]): Form field names to remove.
44
+ output (Path, optional): Output PDF path. If omitted, the input PDF is
45
+ overwritten. Defaults to None.
46
+
47
+ Raises:
48
+ typer.BadParameter: Raised when any requested field does not exist.
49
+ """
29
50
  obj = PdfWrapper(str(pdf), **ctx.obj)
30
51
  for field_name in fields:
31
52
  get_widget(obj, field_name, "--field")
@@ -117,7 +117,24 @@ def main(
117
117
  ),
118
118
  ] = False,
119
119
  ) -> None:
120
- """Work with PDF forms from the command line."""
120
+ """
121
+ Initialize shared CLI options for the selected command.
122
+
123
+ Typer runs this callback before dispatching to a subcommand. The callback
124
+ stores global PDF handling options on `ctx.obj` so command groups can pass
125
+ a consistent set of keyword arguments to `PdfWrapper`.
126
+
127
+ Args:
128
+ ctx (typer.Context): Typer context for the current CLI invocation.
129
+ version (bool): Whether to print the package version and exit.
130
+ need_appearances (bool): Whether to ask PDF viewers to render form
131
+ field appearances.
132
+ generate_appearance_streams (bool): Whether to generate form field
133
+ appearance streams while handling PDFs.
134
+ preserve_metadata (bool): Whether to preserve input PDF metadata.
135
+ use_full_widget_name (bool): Whether widget lookups should use full
136
+ form field names.
137
+ """
121
138
  ctx.obj = {
122
139
  "need_appearances": need_appearances,
123
140
  "generate_appearance_streams": generate_appearance_streams,
@@ -126,7 +143,10 @@ def main(
126
143
  }
127
144
 
128
145
 
129
- @cli_app.command(no_args_is_help=True)
146
+ @cli_app.command(
147
+ no_args_is_help=True,
148
+ help="Fill a PDF form with JSON data.",
149
+ )
130
150
  def fill(
131
151
  ctx: typer.Context,
132
152
  pdf: INPUT_PDF,
@@ -137,7 +157,25 @@ def fill(
137
157
  typer.Option("--flatten", help="Flatten form fields after filling."),
138
158
  ] = None,
139
159
  ) -> None:
140
- """Fill a PDF form with JSON data."""
160
+ """
161
+ Fill an existing PDF form from a validated JSON file.
162
+
163
+ The command loads the input PDF with the global options stored by the root
164
+ callback, expands the generated schema so image and signature widgets can
165
+ accept path objects, validates the JSON input, normalizes image and
166
+ signature values, and writes the filled PDF to the requested output path or
167
+ back to the input file.
168
+
169
+ Args:
170
+ ctx (typer.Context): Typer context containing global `PdfWrapper`
171
+ options in `ctx.obj`.
172
+ pdf (Path): Input PDF form path.
173
+ data (Path): JSON file containing form field values.
174
+ output (Path, optional): Output PDF path. If omitted, the input PDF is
175
+ overwritten. Defaults to None.
176
+ flatten (bool, optional): Whether to flatten form fields after filling.
177
+ Defaults to None.
178
+ """
141
179
  obj = PdfWrapper(str(pdf), **ctx.obj)
142
180
 
143
181
  schema = obj.schema