PyPDFForm 4.7.9__tar.gz → 4.8.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.
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PKG-INFO +7 -7
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/__init__.py +1 -1
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/cli/__init__.py +67 -4
- pypdfform-4.8.0/PyPDFForm/cli/coordinate.py +165 -0
- pypdfform-4.8.0/PyPDFForm/cli/create.py +169 -0
- pypdfform-4.8.0/PyPDFForm/cli/inspect.py +53 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/cli/update.py +7 -8
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/base.py +1 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/template.py +13 -5
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm.egg-info/PKG-INFO +7 -7
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm.egg-info/SOURCES.txt +3 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm.egg-info/requires.txt +6 -6
- {pypdfform-4.7.9 → pypdfform-4.8.0}/pyproject.toml +7 -6
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_bulk_create_fields.py +15 -0
- pypdfform-4.8.0/tests/test_cli.py +71 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_functional.py +6 -0
- pypdfform-4.7.9/tests/test_cli.py +0 -35
- {pypdfform-4.7.9 → pypdfform-4.8.0}/LICENSE +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/adapter.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/annotations/link.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/assets/bedrock.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/constants.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/deprecation.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/egress.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/font.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/image.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/checkbox.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/dropdown.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/middleware/text.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/patterns.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/image.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/rect.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/types.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/utils.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/watermark.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/base.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/dropdown.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/image.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/signature.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm/lib/wrapper.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm.egg-info/entry_points.txt +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/README.md +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/setup.cfg +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_create_widget.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_draw_elements.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_dropdown.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_font_widths.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_js.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_need_appearances.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_paragraph.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_signature.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-4.7.9 → pypdfform-4.8.0}/tests/test_widget_attr_trigger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.8.0
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,26 +21,26 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: cryptography<47.0.0,>=46.0.3
|
|
24
|
-
Requires-Dist: fonttools<5.0.0,>=4.
|
|
24
|
+
Requires-Dist: fonttools<5.0.0,>=4.62.1
|
|
25
25
|
Requires-Dist: pikepdf<11.0.0,>=10.5.0
|
|
26
26
|
Requires-Dist: pillow<13.0.0,>=12.0.0
|
|
27
|
-
Requires-Dist: pypdf<7.0.0,>=6.
|
|
27
|
+
Requires-Dist: pypdf<7.0.0,>=6.10.1
|
|
28
28
|
Requires-Dist: reportlab<5.0.0,>=4.4.6
|
|
29
29
|
Provides-Extra: cli
|
|
30
30
|
Requires-Dist: typer<1.0.0,>=0.24.1; extra == "cli"
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: black<27.0.0,>=25.11.0; extra == "dev"
|
|
33
33
|
Requires-Dist: coverage<8.0.0,>=7.12.0; extra == "dev"
|
|
34
|
-
Requires-Dist: isort<9.0.0,>=
|
|
35
|
-
Requires-Dist: jsonschema<5.0.0,>=4.
|
|
34
|
+
Requires-Dist: isort<9.0.0,>=8.0.1; extra == "dev"
|
|
35
|
+
Requires-Dist: jsonschema<5.0.0,>=4.26.0; extra == "dev"
|
|
36
36
|
Requires-Dist: mike<3.0.0,>=2.1.3; extra == "dev"
|
|
37
37
|
Requires-Dist: mkdocs<2.0.0,>=1.6.1; extra == "dev"
|
|
38
38
|
Requires-Dist: mkdocs-material<10.0.0,>=9.7.0; extra == "dev"
|
|
39
39
|
Requires-Dist: pudb<2026.0.0,>=2025.1.3; extra == "dev"
|
|
40
40
|
Requires-Dist: pylint<5.0.0,>=4.0.3; extra == "dev"
|
|
41
41
|
Requires-Dist: pyright<2.0.0,>=1.1.407; extra == "dev"
|
|
42
|
-
Requires-Dist: pytest<10.0.0,>=9.0.
|
|
43
|
-
Requires-Dist: requests<3.0.0,>=2.
|
|
42
|
+
Requires-Dist: pytest<10.0.0,>=9.0.3; extra == "dev"
|
|
43
|
+
Requires-Dist: requests<3.0.0,>=2.33.1; extra == "dev"
|
|
44
44
|
Requires-Dist: ruff<1.0.0,>=0.14.6; extra == "dev"
|
|
45
45
|
Dynamic: license-file
|
|
46
46
|
|
|
@@ -6,16 +6,35 @@ It defines the CLI application using Typer, providing commands for
|
|
|
6
6
|
interacting with PyPDFForm functionality from the terminal.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import json
|
|
9
10
|
from typing import Annotated
|
|
10
11
|
|
|
11
12
|
import typer
|
|
12
13
|
|
|
13
|
-
from .. import __version__
|
|
14
|
+
from .. import PdfWrapper, Widgets, __version__
|
|
15
|
+
from .coordinate import coordinate_cli
|
|
16
|
+
from .create import create_cli
|
|
17
|
+
from .inspect import inspect_cli
|
|
14
18
|
from .update import update_cli
|
|
15
19
|
|
|
16
20
|
cli_app = typer.Typer(
|
|
17
21
|
context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
|
|
18
22
|
)
|
|
23
|
+
cli_app.add_typer(
|
|
24
|
+
coordinate_cli,
|
|
25
|
+
name="coordinate",
|
|
26
|
+
help="Subcommands for interacting with PDF coordinates and dimensions.",
|
|
27
|
+
)
|
|
28
|
+
cli_app.add_typer(
|
|
29
|
+
create_cli,
|
|
30
|
+
name="create",
|
|
31
|
+
help="Subcommands for creating elements on PDF forms.",
|
|
32
|
+
)
|
|
33
|
+
cli_app.add_typer(
|
|
34
|
+
inspect_cli,
|
|
35
|
+
name="inspect",
|
|
36
|
+
help="Subcommands for inspecting PDF forms.",
|
|
37
|
+
)
|
|
19
38
|
cli_app.add_typer(
|
|
20
39
|
update_cli,
|
|
21
40
|
name="update",
|
|
@@ -114,7 +133,7 @@ def use_full_widget_name_callback(ctx: typer.Context, value: bool) -> None:
|
|
|
114
133
|
ctx.obj["use_full_widget_name"] = value
|
|
115
134
|
|
|
116
135
|
|
|
117
|
-
@cli_app.callback(invoke_without_command=True, help="
|
|
136
|
+
@cli_app.callback(invoke_without_command=True, help="PyPDFForm command-line interface.")
|
|
118
137
|
def main(
|
|
119
138
|
version: Annotated[ # pylint: disable=W0613
|
|
120
139
|
bool,
|
|
@@ -123,7 +142,7 @@ def main(
|
|
|
123
142
|
"-v",
|
|
124
143
|
callback=version_callback,
|
|
125
144
|
is_eager=True,
|
|
126
|
-
help="Show current version of the CLI and exit.",
|
|
145
|
+
help="Show the current version of the CLI and exit.",
|
|
127
146
|
),
|
|
128
147
|
] = False,
|
|
129
148
|
need_appearances: Annotated[ # pylint: disable=W0613
|
|
@@ -147,7 +166,7 @@ def main(
|
|
|
147
166
|
typer.Option(
|
|
148
167
|
"--preserve-metadata",
|
|
149
168
|
callback=preserve_metadata_callback,
|
|
150
|
-
help="Preserve PDF metadata in output.",
|
|
169
|
+
help="Preserve PDF metadata in the output.",
|
|
151
170
|
),
|
|
152
171
|
] = False,
|
|
153
172
|
use_full_widget_name: Annotated[ # pylint: disable=W0613
|
|
@@ -163,4 +182,48 @@ def main(
|
|
|
163
182
|
...
|
|
164
183
|
|
|
165
184
|
|
|
185
|
+
@cli_app.command(no_args_is_help=True)
|
|
186
|
+
def fill(
|
|
187
|
+
ctx: typer.Context,
|
|
188
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
189
|
+
data: Annotated[
|
|
190
|
+
str,
|
|
191
|
+
typer.Option(
|
|
192
|
+
"--file",
|
|
193
|
+
"-f",
|
|
194
|
+
help="Path to the JSON file representing the filling data.",
|
|
195
|
+
),
|
|
196
|
+
],
|
|
197
|
+
output: Annotated[
|
|
198
|
+
str,
|
|
199
|
+
typer.Option(
|
|
200
|
+
"--output",
|
|
201
|
+
"-o",
|
|
202
|
+
help="Path to save the output PDF. Defaults to the original path if not specified.",
|
|
203
|
+
),
|
|
204
|
+
] = None,
|
|
205
|
+
flatten: Annotated[
|
|
206
|
+
bool,
|
|
207
|
+
typer.Option(
|
|
208
|
+
"--flatten", help="Whether to flatten the filled PDF form or not."
|
|
209
|
+
),
|
|
210
|
+
] = None,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Fill a PDF form.
|
|
214
|
+
"""
|
|
215
|
+
with open(data, "r", encoding="utf-8") as f:
|
|
216
|
+
input_data = json.load(f)
|
|
217
|
+
|
|
218
|
+
obj = PdfWrapper(pdf, **ctx.obj)
|
|
219
|
+
for k, each in obj.widgets.items():
|
|
220
|
+
if k in input_data and isinstance(each, (Widgets.Image, Widgets.Signature)):
|
|
221
|
+
each.preserve_aspect_ratio = input_data.get(k, {}).get(
|
|
222
|
+
"preserve_aspect_ratio", each.preserve_aspect_ratio
|
|
223
|
+
)
|
|
224
|
+
input_data[k] = input_data[k]["path"]
|
|
225
|
+
|
|
226
|
+
obj.fill(input_data, flatten=flatten).write(output or pdf)
|
|
227
|
+
|
|
228
|
+
|
|
166
229
|
__all__ = ["cli_app"]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
CLI commands for interacting with PDF coordinates.
|
|
4
|
+
|
|
5
|
+
This module provides command-line interface commands for working with
|
|
6
|
+
PDF coordinates and dimensions, such as generating a coordinate grid view.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
from .. import PdfWrapper
|
|
15
|
+
|
|
16
|
+
coordinate_cli = typer.Typer(
|
|
17
|
+
context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@coordinate_cli.command(no_args_is_help=True)
|
|
22
|
+
def grid(
|
|
23
|
+
ctx: typer.Context,
|
|
24
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
25
|
+
output: Annotated[
|
|
26
|
+
str,
|
|
27
|
+
typer.Option(
|
|
28
|
+
"--output",
|
|
29
|
+
"-o",
|
|
30
|
+
help="Path to save the output PDF. Defaults to the original path if not specified.",
|
|
31
|
+
),
|
|
32
|
+
] = None,
|
|
33
|
+
red: Annotated[
|
|
34
|
+
float,
|
|
35
|
+
typer.Option(
|
|
36
|
+
"--red",
|
|
37
|
+
"-r",
|
|
38
|
+
help="Red channel of the RGB color.",
|
|
39
|
+
),
|
|
40
|
+
] = None,
|
|
41
|
+
green: Annotated[
|
|
42
|
+
float,
|
|
43
|
+
typer.Option(
|
|
44
|
+
"--green",
|
|
45
|
+
"-g",
|
|
46
|
+
help="Green channel of the RGB color.",
|
|
47
|
+
),
|
|
48
|
+
] = None,
|
|
49
|
+
blue: Annotated[
|
|
50
|
+
float,
|
|
51
|
+
typer.Option(
|
|
52
|
+
"--blue",
|
|
53
|
+
"-b",
|
|
54
|
+
help="Blue channel of the RGB color.",
|
|
55
|
+
),
|
|
56
|
+
] = None,
|
|
57
|
+
margin: Annotated[
|
|
58
|
+
float,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--margin",
|
|
61
|
+
"-m",
|
|
62
|
+
help="Margin of the grid view in points.",
|
|
63
|
+
),
|
|
64
|
+
] = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Generate a coordinate grid view for a PDF.
|
|
68
|
+
"""
|
|
69
|
+
params = {}
|
|
70
|
+
if any(
|
|
71
|
+
[
|
|
72
|
+
red is not None,
|
|
73
|
+
green is not None,
|
|
74
|
+
blue is not None,
|
|
75
|
+
]
|
|
76
|
+
):
|
|
77
|
+
params["color"] = (red or 0, green or 0, blue or 0)
|
|
78
|
+
|
|
79
|
+
if margin is not None:
|
|
80
|
+
params["margin"] = int(margin) if margin.is_integer() else margin
|
|
81
|
+
PdfWrapper(pdf, **ctx.obj).generate_coordinate_grid(**params).write(output or pdf)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@coordinate_cli.command(no_args_is_help=True)
|
|
85
|
+
def inspect(
|
|
86
|
+
ctx: typer.Context,
|
|
87
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
88
|
+
field: Annotated[
|
|
89
|
+
str, typer.Option("--field", "-f", help="Name of the form field to inspect.")
|
|
90
|
+
],
|
|
91
|
+
) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Inspect the page number, coordinates, and dimensions of a form field's rectangular bounding box.
|
|
94
|
+
"""
|
|
95
|
+
f = PdfWrapper(pdf, **ctx.obj).widgets[field]
|
|
96
|
+
|
|
97
|
+
print(
|
|
98
|
+
json.dumps(
|
|
99
|
+
{
|
|
100
|
+
"page_number": f.page_number,
|
|
101
|
+
"x": f.x,
|
|
102
|
+
"y": f.y,
|
|
103
|
+
"width": f.width,
|
|
104
|
+
"height": f.height,
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@coordinate_cli.command(no_args_is_help=True)
|
|
111
|
+
def modify(
|
|
112
|
+
ctx: typer.Context,
|
|
113
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
114
|
+
field: Annotated[
|
|
115
|
+
str, typer.Option("--field", "-f", help="Name of the form field to modify.")
|
|
116
|
+
],
|
|
117
|
+
output: Annotated[
|
|
118
|
+
str,
|
|
119
|
+
typer.Option(
|
|
120
|
+
"--output",
|
|
121
|
+
"-o",
|
|
122
|
+
help="Path to save the output PDF. Defaults to the original path if not specified.",
|
|
123
|
+
),
|
|
124
|
+
] = None,
|
|
125
|
+
x: Annotated[
|
|
126
|
+
float,
|
|
127
|
+
typer.Option(
|
|
128
|
+
"--x",
|
|
129
|
+
help="New x coordinate.",
|
|
130
|
+
),
|
|
131
|
+
] = None,
|
|
132
|
+
y: Annotated[
|
|
133
|
+
float,
|
|
134
|
+
typer.Option(
|
|
135
|
+
"--y",
|
|
136
|
+
help="New y coordinate.",
|
|
137
|
+
),
|
|
138
|
+
] = None,
|
|
139
|
+
width: Annotated[
|
|
140
|
+
float,
|
|
141
|
+
typer.Option(
|
|
142
|
+
"--width",
|
|
143
|
+
help="New width.",
|
|
144
|
+
),
|
|
145
|
+
] = None,
|
|
146
|
+
height: Annotated[
|
|
147
|
+
float,
|
|
148
|
+
typer.Option(
|
|
149
|
+
"--height",
|
|
150
|
+
help="New height.",
|
|
151
|
+
),
|
|
152
|
+
] = None,
|
|
153
|
+
) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Modify the coordinates and dimensions of a form field's rectangular bounding box.
|
|
156
|
+
"""
|
|
157
|
+
obj = PdfWrapper(pdf, **ctx.obj)
|
|
158
|
+
f = obj.widgets[field]
|
|
159
|
+
|
|
160
|
+
f.x = x if x is not None else f.x
|
|
161
|
+
f.y = y if y is not None else f.y
|
|
162
|
+
f.width = width if width is not None else f.width
|
|
163
|
+
f.height = height if height is not None else f.height
|
|
164
|
+
|
|
165
|
+
obj.write(output or pdf)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
CLI module for creating PDF form fields.
|
|
4
|
+
|
|
5
|
+
This module provides command-line interfaces to create various types of PDF form fields
|
|
6
|
+
(such as text fields, checkboxes, radio buttons, dropdowns, signatures, and images)
|
|
7
|
+
in an existing PDF. It aims to mimic the field creation features available via the
|
|
8
|
+
Python API as described in the preparation documentation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from .. import BlankPage, Fields, PdfWrapper, RawElements
|
|
17
|
+
|
|
18
|
+
create_cli = typer.Typer(
|
|
19
|
+
context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _create_elements_from_file(
|
|
24
|
+
pdf: str,
|
|
25
|
+
data: str,
|
|
26
|
+
element_map: dict,
|
|
27
|
+
method_name: str,
|
|
28
|
+
ctx: typer.Context,
|
|
29
|
+
output: str = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Create PDF elements from a JSON file.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
pdf: Path to the input PDF file.
|
|
36
|
+
data: Path to the JSON file containing element parameters.
|
|
37
|
+
element_map: Mapping of element type names to element classes.
|
|
38
|
+
method_name: Name of the method to call on PdfWrapper (e.g., "bulk_create_fields", "draw").
|
|
39
|
+
ctx: Typer context containing configuration options.
|
|
40
|
+
output: Path to save the output PDF. Defaults to the original path if not specified.
|
|
41
|
+
"""
|
|
42
|
+
with open(data, "r", encoding="utf-8") as f:
|
|
43
|
+
input_data = json.load(f)
|
|
44
|
+
|
|
45
|
+
obj = PdfWrapper(pdf, **ctx.obj)
|
|
46
|
+
ungrouped_input = []
|
|
47
|
+
registered_font = {}
|
|
48
|
+
for k, v in input_data.items():
|
|
49
|
+
for each in v:
|
|
50
|
+
if "font" in each:
|
|
51
|
+
if each["font"] not in registered_font:
|
|
52
|
+
font_name = f"new_font_{len(registered_font)}"
|
|
53
|
+
obj.register_font(font_name, each["font"])
|
|
54
|
+
registered_font[each["font"]] = font_name
|
|
55
|
+
each["font"] = registered_font[each["font"]]
|
|
56
|
+
ungrouped_input.append(element_map[k](**each))
|
|
57
|
+
|
|
58
|
+
getattr(obj, method_name)(ungrouped_input).write(output or pdf)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@create_cli.command(no_args_is_help=True)
|
|
62
|
+
def field(
|
|
63
|
+
ctx: typer.Context,
|
|
64
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
65
|
+
data: Annotated[
|
|
66
|
+
str,
|
|
67
|
+
typer.Option(
|
|
68
|
+
"--file",
|
|
69
|
+
"-f",
|
|
70
|
+
help="Path to the JSON file representing the field creation parameters.",
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
output: Annotated[
|
|
74
|
+
str,
|
|
75
|
+
typer.Option(
|
|
76
|
+
"--output",
|
|
77
|
+
"-o",
|
|
78
|
+
help="Path to save the output PDF. Defaults to the original path if not specified.",
|
|
79
|
+
),
|
|
80
|
+
] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Create PDF form fields.
|
|
84
|
+
"""
|
|
85
|
+
field_map = {
|
|
86
|
+
"text": Fields.TextField,
|
|
87
|
+
"check": Fields.CheckBoxField,
|
|
88
|
+
"radio": Fields.RadioGroup,
|
|
89
|
+
"dropdown": Fields.DropdownField,
|
|
90
|
+
"image": Fields.ImageField,
|
|
91
|
+
"signature": Fields.SignatureField,
|
|
92
|
+
}
|
|
93
|
+
_create_elements_from_file(pdf, data, field_map, "bulk_create_fields", ctx, output)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@create_cli.command(no_args_is_help=True)
|
|
97
|
+
def raw(
|
|
98
|
+
ctx: typer.Context,
|
|
99
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
100
|
+
data: Annotated[
|
|
101
|
+
str,
|
|
102
|
+
typer.Option(
|
|
103
|
+
"--file",
|
|
104
|
+
"-f",
|
|
105
|
+
help="Path to the JSON file representing the draw parameters.",
|
|
106
|
+
),
|
|
107
|
+
],
|
|
108
|
+
output: Annotated[
|
|
109
|
+
str,
|
|
110
|
+
typer.Option(
|
|
111
|
+
"--output",
|
|
112
|
+
"-o",
|
|
113
|
+
help="Path to save the output PDF. Defaults to the original path if not specified.",
|
|
114
|
+
),
|
|
115
|
+
] = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Draw raw PDF elements.
|
|
119
|
+
"""
|
|
120
|
+
raw_element_map = {
|
|
121
|
+
"text": RawElements.RawText,
|
|
122
|
+
"image": RawElements.RawImage,
|
|
123
|
+
"line": RawElements.RawLine,
|
|
124
|
+
"rectangle": RawElements.RawRectangle,
|
|
125
|
+
"circle": RawElements.RawCircle,
|
|
126
|
+
"ellipse": RawElements.RawEllipse,
|
|
127
|
+
}
|
|
128
|
+
_create_elements_from_file(pdf, data, raw_element_map, "draw", ctx, output)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@create_cli.command(no_args_is_help=True)
|
|
132
|
+
def blank(
|
|
133
|
+
ctx: typer.Context,
|
|
134
|
+
output: Annotated[
|
|
135
|
+
str,
|
|
136
|
+
typer.Option(
|
|
137
|
+
"--output",
|
|
138
|
+
"-o",
|
|
139
|
+
help="Path to save the output PDF.",
|
|
140
|
+
),
|
|
141
|
+
],
|
|
142
|
+
count: Annotated[
|
|
143
|
+
int, typer.Option("--count", "-c", help="Number of blank pages.")
|
|
144
|
+
] = None,
|
|
145
|
+
width: Annotated[
|
|
146
|
+
float,
|
|
147
|
+
typer.Option(
|
|
148
|
+
"--width",
|
|
149
|
+
help="Width of the blank PDF.",
|
|
150
|
+
),
|
|
151
|
+
] = None,
|
|
152
|
+
height: Annotated[
|
|
153
|
+
float, typer.Option("--height", help="Height of the blank PDF.")
|
|
154
|
+
] = None,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Create a new blank PDF.
|
|
158
|
+
"""
|
|
159
|
+
params = {}
|
|
160
|
+
if width is not None:
|
|
161
|
+
params["width"] = width
|
|
162
|
+
if height is not None:
|
|
163
|
+
params["height"] = height
|
|
164
|
+
|
|
165
|
+
obj = BlankPage(**params)
|
|
166
|
+
if count is not None and count > 1:
|
|
167
|
+
obj = BlankPage(**params) * count
|
|
168
|
+
|
|
169
|
+
PdfWrapper(obj, **ctx.obj).write(output)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
CLI commands for inspecting PDF form field data.
|
|
4
|
+
|
|
5
|
+
This module provides command-line interface commands for extracting
|
|
6
|
+
information from PDF forms. Features include generating a JSON schema
|
|
7
|
+
describing the form fields, inspecting the current filled data of a
|
|
8
|
+
PDF form, and generating sample data for filling a form.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from .. import PdfWrapper
|
|
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(no_args_is_help=True)
|
|
24
|
+
def schema(
|
|
25
|
+
ctx: typer.Context,
|
|
26
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Generate a JSON schema that describes a PDF form.
|
|
30
|
+
"""
|
|
31
|
+
print(json.dumps(PdfWrapper(pdf, **ctx.obj).schema))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@inspect_cli.command(no_args_is_help=True)
|
|
35
|
+
def data(
|
|
36
|
+
ctx: typer.Context,
|
|
37
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
38
|
+
) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Inspect the current filled data of a PDF form.
|
|
41
|
+
"""
|
|
42
|
+
print(json.dumps(PdfWrapper(pdf, **ctx.obj).data))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@inspect_cli.command(no_args_is_help=True)
|
|
46
|
+
def sample(
|
|
47
|
+
ctx: typer.Context,
|
|
48
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Generate sample data for filling a PDF form.
|
|
52
|
+
"""
|
|
53
|
+
print(json.dumps(PdfWrapper(pdf, **ctx.obj).sample_data))
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
# TODO: fix this docstring
|
|
3
2
|
"""
|
|
4
|
-
CLI commands for updating
|
|
3
|
+
CLI commands for updating PDFs.
|
|
5
4
|
|
|
6
5
|
This module provides command-line interface commands for modifying
|
|
7
|
-
PDF
|
|
6
|
+
PDF files, such as updating metadata or other elements.
|
|
8
7
|
These commands allow users to update PDF documents directly from
|
|
9
|
-
the terminal without needing to
|
|
8
|
+
the terminal without needing to write Python code.
|
|
10
9
|
|
|
11
10
|
The commands in this module wrap the functionality provided by
|
|
12
11
|
the PdfWrapper class, exposing it through a Typer-based CLI for
|
|
@@ -27,20 +26,20 @@ update_cli = typer.Typer(
|
|
|
27
26
|
@update_cli.command(no_args_is_help=True)
|
|
28
27
|
def title(
|
|
29
28
|
ctx: typer.Context,
|
|
30
|
-
pdf: Annotated[str, typer.Argument(help="
|
|
29
|
+
pdf: Annotated[str, typer.Argument(help="Path to the input PDF file.")],
|
|
31
30
|
new_title: Annotated[
|
|
32
|
-
str, typer.Option("--title", "-t", help="The new title for the PDF.")
|
|
31
|
+
str, typer.Option("--title", "-t", help="The new title for the PDF file.")
|
|
33
32
|
],
|
|
34
33
|
output: Annotated[
|
|
35
34
|
str,
|
|
36
35
|
typer.Option(
|
|
37
36
|
"--output",
|
|
38
37
|
"-o",
|
|
39
|
-
help="
|
|
38
|
+
help="Path to save the output PDF. Defaults to the original path if not specified.",
|
|
40
39
|
),
|
|
41
40
|
] = None,
|
|
42
41
|
) -> None:
|
|
43
42
|
"""
|
|
44
|
-
Update the title of a PDF.
|
|
43
|
+
Update the title of a PDF file.
|
|
45
44
|
"""
|
|
46
45
|
PdfWrapper(pdf, title=new_title, **ctx.obj).write(output or pdf)
|
|
@@ -62,6 +62,7 @@ class Widget:
|
|
|
62
62
|
self.hooks_to_trigger: list = []
|
|
63
63
|
|
|
64
64
|
# coordinate & dimension
|
|
65
|
+
self.page_number: Optional[int] = None
|
|
65
66
|
self.x: Optional[float | List[float]] = None
|
|
66
67
|
self.y: Optional[float | List[float]] = None
|
|
67
68
|
self.width: Optional[float | List[float]] = None
|
|
@@ -73,28 +73,32 @@ def build_widgets(
|
|
|
73
73
|
"""
|
|
74
74
|
results = {}
|
|
75
75
|
|
|
76
|
-
for widgets in get_widgets_by_page(pdf_stream).
|
|
76
|
+
for page_num, widgets in get_widgets_by_page(pdf_stream).items():
|
|
77
77
|
for widget in widgets:
|
|
78
|
-
_process_widget(widget, use_full_widget_name, results)
|
|
78
|
+
_process_widget(widget, page_num, use_full_widget_name, results)
|
|
79
79
|
|
|
80
80
|
return results
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def _process_widget(
|
|
84
|
-
widget: dict,
|
|
84
|
+
widget: dict,
|
|
85
|
+
page_number: int,
|
|
86
|
+
use_full_widget_name: bool,
|
|
87
|
+
results: Dict[str, WIDGET_TYPES],
|
|
85
88
|
) -> None:
|
|
86
89
|
"""
|
|
87
90
|
Processes a single widget and adds it to the results dictionary.
|
|
88
91
|
|
|
89
92
|
Args:
|
|
90
93
|
widget (dict): The widget dictionary from the PDF.
|
|
94
|
+
page_number (int): The 1-indexed page number the widget appears on.
|
|
91
95
|
use_full_widget_name (bool): Whether to use the full widget name.
|
|
92
96
|
results (Dict[str, WIDGET_TYPES]): The dictionary of widgets being built.
|
|
93
97
|
"""
|
|
94
98
|
key = get_widget_key(widget, use_full_widget_name)
|
|
95
99
|
_widget = construct_widget(widget, key)
|
|
96
100
|
if _widget is not None:
|
|
97
|
-
_populate_common_properties(widget, _widget)
|
|
101
|
+
_populate_common_properties(widget, page_number, _widget)
|
|
98
102
|
|
|
99
103
|
if isinstance(_widget, Text):
|
|
100
104
|
_populate_text_properties(widget, _widget)
|
|
@@ -111,15 +115,19 @@ def _process_widget(
|
|
|
111
115
|
results[key] = _widget
|
|
112
116
|
|
|
113
117
|
|
|
114
|
-
def _populate_common_properties(
|
|
118
|
+
def _populate_common_properties(
|
|
119
|
+
widget: dict, page_number: int, _widget: WIDGET_TYPES
|
|
120
|
+
) -> None:
|
|
115
121
|
"""
|
|
116
122
|
Populates common properties for a widget.
|
|
117
123
|
|
|
118
124
|
Args:
|
|
119
125
|
widget (dict): The widget dictionary from the PDF.
|
|
126
|
+
page_number (int): The 1-indexed page number the widget appears on.
|
|
120
127
|
_widget (WIDGET_TYPES): The widget object to populate.
|
|
121
128
|
"""
|
|
122
129
|
# widget property extractions don't trigger hooks in this function
|
|
130
|
+
_widget.__dict__["page_number"] = page_number
|
|
123
131
|
_widget.__dict__["tooltip"] = extract_widget_property(
|
|
124
132
|
widget, WIDGET_DESCRIPTION_PATTERNS, None, str
|
|
125
133
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.8.0
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,26 +21,26 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: cryptography<47.0.0,>=46.0.3
|
|
24
|
-
Requires-Dist: fonttools<5.0.0,>=4.
|
|
24
|
+
Requires-Dist: fonttools<5.0.0,>=4.62.1
|
|
25
25
|
Requires-Dist: pikepdf<11.0.0,>=10.5.0
|
|
26
26
|
Requires-Dist: pillow<13.0.0,>=12.0.0
|
|
27
|
-
Requires-Dist: pypdf<7.0.0,>=6.
|
|
27
|
+
Requires-Dist: pypdf<7.0.0,>=6.10.1
|
|
28
28
|
Requires-Dist: reportlab<5.0.0,>=4.4.6
|
|
29
29
|
Provides-Extra: cli
|
|
30
30
|
Requires-Dist: typer<1.0.0,>=0.24.1; extra == "cli"
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: black<27.0.0,>=25.11.0; extra == "dev"
|
|
33
33
|
Requires-Dist: coverage<8.0.0,>=7.12.0; extra == "dev"
|
|
34
|
-
Requires-Dist: isort<9.0.0,>=
|
|
35
|
-
Requires-Dist: jsonschema<5.0.0,>=4.
|
|
34
|
+
Requires-Dist: isort<9.0.0,>=8.0.1; extra == "dev"
|
|
35
|
+
Requires-Dist: jsonschema<5.0.0,>=4.26.0; extra == "dev"
|
|
36
36
|
Requires-Dist: mike<3.0.0,>=2.1.3; extra == "dev"
|
|
37
37
|
Requires-Dist: mkdocs<2.0.0,>=1.6.1; extra == "dev"
|
|
38
38
|
Requires-Dist: mkdocs-material<10.0.0,>=9.7.0; extra == "dev"
|
|
39
39
|
Requires-Dist: pudb<2026.0.0,>=2025.1.3; extra == "dev"
|
|
40
40
|
Requires-Dist: pylint<5.0.0,>=4.0.3; extra == "dev"
|
|
41
41
|
Requires-Dist: pyright<2.0.0,>=1.1.407; extra == "dev"
|
|
42
|
-
Requires-Dist: pytest<10.0.0,>=9.0.
|
|
43
|
-
Requires-Dist: requests<3.0.0,>=2.
|
|
42
|
+
Requires-Dist: pytest<10.0.0,>=9.0.3; extra == "dev"
|
|
43
|
+
Requires-Dist: requests<3.0.0,>=2.33.1; extra == "dev"
|
|
44
44
|
Requires-Dist: ruff<1.0.0,>=0.14.6; extra == "dev"
|
|
45
45
|
Dynamic: license-file
|
|
46
46
|
|
|
@@ -9,6 +9,9 @@ PyPDFForm.egg-info/entry_points.txt
|
|
|
9
9
|
PyPDFForm.egg-info/requires.txt
|
|
10
10
|
PyPDFForm.egg-info/top_level.txt
|
|
11
11
|
PyPDFForm/cli/__init__.py
|
|
12
|
+
PyPDFForm/cli/coordinate.py
|
|
13
|
+
PyPDFForm/cli/create.py
|
|
14
|
+
PyPDFForm/cli/inspect.py
|
|
12
15
|
PyPDFForm/cli/update.py
|
|
13
16
|
PyPDFForm/lib/__init__.py
|
|
14
17
|
PyPDFForm/lib/adapter.py
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
cryptography<47.0.0,>=46.0.3
|
|
2
|
-
fonttools<5.0.0,>=4.
|
|
2
|
+
fonttools<5.0.0,>=4.62.1
|
|
3
3
|
pikepdf<11.0.0,>=10.5.0
|
|
4
4
|
pillow<13.0.0,>=12.0.0
|
|
5
|
-
pypdf<7.0.0,>=6.
|
|
5
|
+
pypdf<7.0.0,>=6.10.1
|
|
6
6
|
reportlab<5.0.0,>=4.4.6
|
|
7
7
|
|
|
8
8
|
[cli]
|
|
@@ -11,14 +11,14 @@ typer<1.0.0,>=0.24.1
|
|
|
11
11
|
[dev]
|
|
12
12
|
black<27.0.0,>=25.11.0
|
|
13
13
|
coverage<8.0.0,>=7.12.0
|
|
14
|
-
isort<9.0.0,>=
|
|
15
|
-
jsonschema<5.0.0,>=4.
|
|
14
|
+
isort<9.0.0,>=8.0.1
|
|
15
|
+
jsonschema<5.0.0,>=4.26.0
|
|
16
16
|
mike<3.0.0,>=2.1.3
|
|
17
17
|
mkdocs<2.0.0,>=1.6.1
|
|
18
18
|
mkdocs-material<10.0.0,>=9.7.0
|
|
19
19
|
pudb<2026.0.0,>=2025.1.3
|
|
20
20
|
pylint<5.0.0,>=4.0.3
|
|
21
21
|
pyright<2.0.0,>=1.1.407
|
|
22
|
-
pytest<10.0.0,>=9.0.
|
|
23
|
-
requests<3.0.0,>=2.
|
|
22
|
+
pytest<10.0.0,>=9.0.3
|
|
23
|
+
requests<3.0.0,>=2.33.1
|
|
24
24
|
ruff<1.0.0,>=0.14.6
|
|
@@ -26,10 +26,10 @@ classifiers = [
|
|
|
26
26
|
requires-python = ">=3.10"
|
|
27
27
|
dependencies = [
|
|
28
28
|
"cryptography>=46.0.3,<47.0.0",
|
|
29
|
-
"fonttools>=4.
|
|
29
|
+
"fonttools>=4.62.1,<5.0.0",
|
|
30
30
|
"pikepdf>=10.5.0,<11.0.0",
|
|
31
31
|
"pillow>=12.0.0,<13.0.0",
|
|
32
|
-
"pypdf>=6.
|
|
32
|
+
"pypdf>=6.10.1,<7.0.0",
|
|
33
33
|
"reportlab>=4.4.6,<5.0.0",
|
|
34
34
|
]
|
|
35
35
|
|
|
@@ -42,16 +42,16 @@ cli = ["typer>=0.24.1,<1.0.0"]
|
|
|
42
42
|
dev = [
|
|
43
43
|
"black>=25.11.0,<27.0.0",
|
|
44
44
|
"coverage>=7.12.0,<8.0.0",
|
|
45
|
-
"isort>=
|
|
46
|
-
"jsonschema>=4.
|
|
45
|
+
"isort>=8.0.1,<9.0.0",
|
|
46
|
+
"jsonschema>=4.26.0,<5.0.0",
|
|
47
47
|
"mike>=2.1.3,<3.0.0",
|
|
48
48
|
"mkdocs>=1.6.1,<2.0.0",
|
|
49
49
|
"mkdocs-material>=9.7.0,<10.0.0",
|
|
50
50
|
"pudb>=2025.1.3,<2026.0.0",
|
|
51
51
|
"pylint>=4.0.3,<5.0.0",
|
|
52
52
|
"pyright>=1.1.407,<2.0.0",
|
|
53
|
-
"pytest>=9.0.
|
|
54
|
-
"requests>=2.
|
|
53
|
+
"pytest>=9.0.3,<10.0.0",
|
|
54
|
+
"requests>=2.33.1,<3.0.0",
|
|
55
55
|
"ruff>=0.14.6,<1.0.0",
|
|
56
56
|
]
|
|
57
57
|
|
|
@@ -139,4 +139,5 @@ include = ["PyPDFForm*"]
|
|
|
139
139
|
[tool.pytest.ini_options]
|
|
140
140
|
markers = [
|
|
141
141
|
"posix_only", # mainly because of zlib vs zlib-ng
|
|
142
|
+
"cli_test",
|
|
142
143
|
]
|
|
@@ -62,6 +62,21 @@ def test_bulk_create_fields_stress_max(pdf_samples, request):
|
|
|
62
62
|
obj += PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
|
|
63
63
|
obj.bulk_create_fields(fields)
|
|
64
64
|
|
|
65
|
+
prefix_to_page = {
|
|
66
|
+
"text_": 1,
|
|
67
|
+
"check_": 2,
|
|
68
|
+
"dropdown_": 3,
|
|
69
|
+
"radio_": 4,
|
|
70
|
+
"image_": 5,
|
|
71
|
+
"signature_": 6,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for k, v in obj.widgets.items():
|
|
75
|
+
for each, page in prefix_to_page.items():
|
|
76
|
+
if k.startswith(each):
|
|
77
|
+
assert v.page_number == page
|
|
78
|
+
break
|
|
79
|
+
|
|
65
80
|
request.config.results["expected_path"] = expected_path
|
|
66
81
|
request.config.results["stream"] = obj.read()
|
|
67
82
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from typer.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from PyPDFForm import __version__
|
|
7
|
+
from PyPDFForm.cli import cli_app
|
|
8
|
+
|
|
9
|
+
runner = CliRunner()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.cli_test
|
|
13
|
+
def test_root_command():
|
|
14
|
+
result = runner.invoke(cli_app)
|
|
15
|
+
assert result.exit_code == 2
|
|
16
|
+
|
|
17
|
+
assert "PyPDFForm command-line interface." in result.output
|
|
18
|
+
assert "Usage:" in result.output
|
|
19
|
+
assert "main" not in result.output
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.cli_test
|
|
23
|
+
def test_root_command_with_version():
|
|
24
|
+
long = runner.invoke(cli_app, ["--version"])
|
|
25
|
+
short = runner.invoke(cli_app, ["-v"])
|
|
26
|
+
|
|
27
|
+
assert long.exit_code == 0
|
|
28
|
+
assert short.exit_code == 0
|
|
29
|
+
|
|
30
|
+
assert long.output == f"v{__version__}\n"
|
|
31
|
+
assert long.output == short.output
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.cli_test
|
|
35
|
+
def test_fill_command():
|
|
36
|
+
result = runner.invoke(cli_app, ["fill"])
|
|
37
|
+
assert result.exit_code == 2
|
|
38
|
+
|
|
39
|
+
assert "Usage:" in result.output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.cli_test
|
|
43
|
+
def test_coordinate_command():
|
|
44
|
+
result = runner.invoke(cli_app, ["coordinate"])
|
|
45
|
+
assert result.exit_code == 2
|
|
46
|
+
|
|
47
|
+
assert "Usage:" in result.output
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.cli_test
|
|
51
|
+
def test_create_command():
|
|
52
|
+
result = runner.invoke(cli_app, ["create"])
|
|
53
|
+
assert result.exit_code == 2
|
|
54
|
+
|
|
55
|
+
assert "Usage:" in result.output
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.cli_test
|
|
59
|
+
def test_inspect_command():
|
|
60
|
+
result = runner.invoke(cli_app, ["inspect"])
|
|
61
|
+
assert result.exit_code == 2
|
|
62
|
+
|
|
63
|
+
assert "Usage:" in result.output
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.cli_test
|
|
67
|
+
def test_update_command():
|
|
68
|
+
result = runner.invoke(cli_app, ["update"])
|
|
69
|
+
assert result.exit_code == 2
|
|
70
|
+
|
|
71
|
+
assert "Usage:" in result.output
|
|
@@ -747,31 +747,37 @@ def test_widget_coord_resolution():
|
|
|
747
747
|
]
|
|
748
748
|
)
|
|
749
749
|
|
|
750
|
+
assert obj.widgets["text"].page_number == 1
|
|
750
751
|
assert obj.widgets["text"].x == 50
|
|
751
752
|
assert obj.widgets["text"].y == 100
|
|
752
753
|
assert obj.widgets["text"].width == 200
|
|
753
754
|
assert obj.widgets["text"].height == 150
|
|
754
755
|
|
|
756
|
+
assert obj.widgets["check"].page_number == 1
|
|
755
757
|
assert obj.widgets["check"].x == 150
|
|
756
758
|
assert obj.widgets["check"].y == 200
|
|
757
759
|
assert obj.widgets["check"].width == 60
|
|
758
760
|
assert obj.widgets["check"].height == 60
|
|
759
761
|
|
|
762
|
+
assert obj.widgets["radio"].page_number == 1
|
|
760
763
|
assert obj.widgets["radio"].x == [400, 500, 600]
|
|
761
764
|
assert obj.widgets["radio"].y == [450, 550, 650]
|
|
762
765
|
assert obj.widgets["radio"].width == 10
|
|
763
766
|
assert obj.widgets["radio"].height == 10
|
|
764
767
|
|
|
768
|
+
assert obj.widgets["dropdown"].page_number == 1
|
|
765
769
|
assert obj.widgets["dropdown"].x == 400
|
|
766
770
|
assert obj.widgets["dropdown"].y == 100
|
|
767
771
|
assert obj.widgets["dropdown"].width == 250
|
|
768
772
|
assert obj.widgets["dropdown"].height == 200
|
|
769
773
|
|
|
774
|
+
assert obj.widgets["image"].page_number == 1
|
|
770
775
|
assert obj.widgets["image"].x == 300
|
|
771
776
|
assert obj.widgets["image"].y == 400
|
|
772
777
|
assert obj.widgets["image"].width == 400
|
|
773
778
|
assert obj.widgets["image"].height == 300
|
|
774
779
|
|
|
780
|
+
assert obj.widgets["signature"].page_number == 1
|
|
775
781
|
assert obj.widgets["signature"].x == 500
|
|
776
782
|
assert obj.widgets["signature"].y == 600
|
|
777
783
|
assert obj.widgets["signature"].width == 600
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
from typer.testing import CliRunner
|
|
4
|
-
|
|
5
|
-
from PyPDFForm import __version__
|
|
6
|
-
from PyPDFForm.cli import cli_app
|
|
7
|
-
|
|
8
|
-
runner = CliRunner()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_root_command():
|
|
12
|
-
result = runner.invoke(cli_app)
|
|
13
|
-
assert result.exit_code == 2
|
|
14
|
-
|
|
15
|
-
assert "Welcome to the PyPDFForm CLI!" in result.output
|
|
16
|
-
assert "Usage:" in result.output
|
|
17
|
-
assert "main" not in result.output
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_root_command_with_version():
|
|
21
|
-
long = runner.invoke(cli_app, ["--version"])
|
|
22
|
-
short = runner.invoke(cli_app, ["-v"])
|
|
23
|
-
|
|
24
|
-
assert long.exit_code == 0
|
|
25
|
-
assert short.exit_code == 0
|
|
26
|
-
|
|
27
|
-
assert long.output == f"v{__version__}\n"
|
|
28
|
-
assert long.output == short.output
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_update_command():
|
|
32
|
-
result = runner.invoke(cli_app, ["update"])
|
|
33
|
-
assert result.exit_code == 2
|
|
34
|
-
|
|
35
|
-
assert "Usage:" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|