PyPDFForm 4.7.8__tar.gz → 4.7.9__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.8 → pypdfform-4.7.9}/PKG-INFO +3 -1
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/__init__.py +1 -1
- pypdfform-4.7.9/PyPDFForm/cli/__init__.py +166 -0
- pypdfform-4.7.9/PyPDFForm/cli/update.py +46 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/watermark.py +1 -3
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/base.py +3 -3
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/signature.py +1 -4
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm.egg-info/PKG-INFO +3 -1
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm.egg-info/SOURCES.txt +4 -0
- pypdfform-4.7.9/PyPDFForm.egg-info/entry_points.txt +2 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm.egg-info/requires.txt +3 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/pyproject.toml +6 -4
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_bulk_create_fields.py +52 -1
- pypdfform-4.7.9/tests/test_cli.py +35 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/LICENSE +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/adapter.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/annotations/link.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/assets/bedrock.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/constants.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/deprecation.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/egress.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/font.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/image.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/base.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/checkbox.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/dropdown.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/middleware/text.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/patterns.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/image.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/rect.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/template.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/types.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/utils.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/dropdown.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/image.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm/lib/wrapper.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/README.md +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/setup.cfg +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_create_widget.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_draw_elements.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_dropdown.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_font_widths.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_functional.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_js.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_need_appearances.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_paragraph.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_signature.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-4.7.8 → pypdfform-4.7.9}/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.7.
|
|
3
|
+
Version: 4.7.9
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,6 +26,8 @@ Requires-Dist: pikepdf<11.0.0,>=10.5.0
|
|
|
26
26
|
Requires-Dist: pillow<13.0.0,>=12.0.0
|
|
27
27
|
Requires-Dist: pypdf<7.0.0,>=6.9.0
|
|
28
28
|
Requires-Dist: reportlab<5.0.0,>=4.4.6
|
|
29
|
+
Provides-Extra: cli
|
|
30
|
+
Requires-Dist: typer<1.0.0,>=0.24.1; extra == "cli"
|
|
29
31
|
Provides-Extra: dev
|
|
30
32
|
Requires-Dist: black<27.0.0,>=25.11.0; extra == "dev"
|
|
31
33
|
Requires-Dist: coverage<8.0.0,>=7.12.0; extra == "dev"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This module provides the command-line interface for PyPDFForm.
|
|
4
|
+
|
|
5
|
+
It defines the CLI application using Typer, providing commands for
|
|
6
|
+
interacting with PyPDFForm functionality from the terminal.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from .. import __version__
|
|
14
|
+
from .update import update_cli
|
|
15
|
+
|
|
16
|
+
cli_app = typer.Typer(
|
|
17
|
+
context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
|
|
18
|
+
)
|
|
19
|
+
cli_app.add_typer(
|
|
20
|
+
update_cli,
|
|
21
|
+
name="update",
|
|
22
|
+
help="Subcommands for updating PDF files and their elements.",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def version_callback(value: bool) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Callback function to handle the version option.
|
|
29
|
+
|
|
30
|
+
This is triggered when the --version or -v flag is passed to the CLI.
|
|
31
|
+
It prints the current version of PyPDFForm and exits the application.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
value (bool): The value passed to the version option. If True,
|
|
35
|
+
the version information is displayed and the application exits.
|
|
36
|
+
"""
|
|
37
|
+
if value:
|
|
38
|
+
print(f"v{__version__}")
|
|
39
|
+
raise typer.Exit
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def need_appearances_callback(ctx: typer.Context, value: bool) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Callback function to handle the need_appearances option.
|
|
45
|
+
|
|
46
|
+
This is triggered when the --need-appearances flag is passed to the CLI.
|
|
47
|
+
It stores the value in the context object for use by subcommands.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
ctx (typer.Context): The Typer context object used to pass data
|
|
51
|
+
between callbacks and commands.
|
|
52
|
+
value (bool): The value passed to the need_appearances option.
|
|
53
|
+
If True, PDF viewers will be instructed to generate appearance
|
|
54
|
+
streams for the output.
|
|
55
|
+
"""
|
|
56
|
+
if not ctx.obj:
|
|
57
|
+
ctx.obj = {}
|
|
58
|
+
ctx.obj["need_appearances"] = value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def generate_appearance_streams_callback(ctx: typer.Context, value: bool) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Callback function to handle the generate_appearance_streams option.
|
|
64
|
+
|
|
65
|
+
This is triggered when the --generate-appearance-streams flag is passed
|
|
66
|
+
to the CLI. It stores the value in the context object for use by subcommands.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
ctx (typer.Context): The Typer context object used to pass data
|
|
70
|
+
between callbacks and commands.
|
|
71
|
+
value (bool): The value passed to the generate_appearance_streams
|
|
72
|
+
option. If True, appearance streams will be explicitly generated
|
|
73
|
+
for all form fields in output PDFs using pikepdf.
|
|
74
|
+
"""
|
|
75
|
+
if not ctx.obj:
|
|
76
|
+
ctx.obj = {}
|
|
77
|
+
ctx.obj["generate_appearance_streams"] = value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def preserve_metadata_callback(ctx: typer.Context, value: bool) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Callback function to handle the preserve_metadata option.
|
|
83
|
+
|
|
84
|
+
This is triggered when the --preserve-metadata flag is passed to the CLI.
|
|
85
|
+
It stores the value in the context object for use by subcommands.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
ctx (typer.Context): The Typer context object used to pass data
|
|
89
|
+
between callbacks and commands.
|
|
90
|
+
value (bool): The value passed to the preserve_metadata option.
|
|
91
|
+
If True, metadata will be preserved in output PDFs.
|
|
92
|
+
"""
|
|
93
|
+
if not ctx.obj:
|
|
94
|
+
ctx.obj = {}
|
|
95
|
+
ctx.obj["preserve_metadata"] = value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def use_full_widget_name_callback(ctx: typer.Context, value: bool) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Callback function to handle the use_full_widget_name option.
|
|
101
|
+
|
|
102
|
+
This is triggered when the --use-full-widget-name flag is passed to the CLI.
|
|
103
|
+
It stores the value in the context object for use by subcommands.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
ctx (typer.Context): The Typer context object used to pass data
|
|
107
|
+
between callbacks and commands.
|
|
108
|
+
value (bool): The value passed to the use_full_widget_name option.
|
|
109
|
+
If True, fully qualified names (including parent field names)
|
|
110
|
+
will be used when looking up form fields.
|
|
111
|
+
"""
|
|
112
|
+
if not ctx.obj:
|
|
113
|
+
ctx.obj = {}
|
|
114
|
+
ctx.obj["use_full_widget_name"] = value
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@cli_app.callback(invoke_without_command=True, help="Welcome to the PyPDFForm CLI!")
|
|
118
|
+
def main(
|
|
119
|
+
version: Annotated[ # pylint: disable=W0613
|
|
120
|
+
bool,
|
|
121
|
+
typer.Option(
|
|
122
|
+
"--version",
|
|
123
|
+
"-v",
|
|
124
|
+
callback=version_callback,
|
|
125
|
+
is_eager=True,
|
|
126
|
+
help="Show current version of the CLI and exit.",
|
|
127
|
+
),
|
|
128
|
+
] = False,
|
|
129
|
+
need_appearances: Annotated[ # pylint: disable=W0613
|
|
130
|
+
bool,
|
|
131
|
+
typer.Option(
|
|
132
|
+
"--need-appearances",
|
|
133
|
+
callback=need_appearances_callback,
|
|
134
|
+
help="Instruct PDF viewers to generate appearance streams for any output PDF.",
|
|
135
|
+
),
|
|
136
|
+
] = False,
|
|
137
|
+
generate_appearance_streams: Annotated[ # pylint: disable=W0613
|
|
138
|
+
bool,
|
|
139
|
+
typer.Option(
|
|
140
|
+
"--generate-appearance-streams",
|
|
141
|
+
callback=generate_appearance_streams_callback,
|
|
142
|
+
help="Generate appearance streams for any output PDF.",
|
|
143
|
+
),
|
|
144
|
+
] = False,
|
|
145
|
+
preserve_metadata: Annotated[ # pylint: disable=W0613
|
|
146
|
+
bool,
|
|
147
|
+
typer.Option(
|
|
148
|
+
"--preserve-metadata",
|
|
149
|
+
callback=preserve_metadata_callback,
|
|
150
|
+
help="Preserve PDF metadata in output.",
|
|
151
|
+
),
|
|
152
|
+
] = False,
|
|
153
|
+
use_full_widget_name: Annotated[ # pylint: disable=W0613
|
|
154
|
+
bool,
|
|
155
|
+
typer.Option(
|
|
156
|
+
"--use-full-widget-name",
|
|
157
|
+
callback=use_full_widget_name_callback,
|
|
158
|
+
help="Use fully qualified names when accessing form fields.",
|
|
159
|
+
),
|
|
160
|
+
] = False,
|
|
161
|
+
) -> None:
|
|
162
|
+
# pylint: disable=C0116
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
__all__ = ["cli_app"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# TODO: fix this docstring
|
|
3
|
+
"""
|
|
4
|
+
CLI commands for updating PDF metadata.
|
|
5
|
+
|
|
6
|
+
This module provides command-line interface commands for modifying
|
|
7
|
+
PDF file metadata such as title, author, subject, and other properties.
|
|
8
|
+
These commands allow users to update PDF documents directly from
|
|
9
|
+
the terminal without needing to use Python code.
|
|
10
|
+
|
|
11
|
+
The commands in this module wrap the functionality provided by
|
|
12
|
+
the PdfWrapper class, exposing it through a Typer-based CLI for
|
|
13
|
+
ease of use.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
|
|
20
|
+
from .. import PdfWrapper
|
|
21
|
+
|
|
22
|
+
update_cli = typer.Typer(
|
|
23
|
+
context_settings={"help_option_names": ["--help", "-h"]}, no_args_is_help=True
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@update_cli.command(no_args_is_help=True)
|
|
28
|
+
def title(
|
|
29
|
+
ctx: typer.Context,
|
|
30
|
+
pdf: Annotated[str, typer.Argument(help="The local path to a PDF.")],
|
|
31
|
+
new_title: Annotated[
|
|
32
|
+
str, typer.Option("--title", "-t", help="The new title for the PDF.")
|
|
33
|
+
],
|
|
34
|
+
output: Annotated[
|
|
35
|
+
str,
|
|
36
|
+
typer.Option(
|
|
37
|
+
"--output",
|
|
38
|
+
"-o",
|
|
39
|
+
help="The location to save the PDF to. Defaults to the original path if unspecified.",
|
|
40
|
+
),
|
|
41
|
+
] = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Update the title of a PDF.
|
|
45
|
+
"""
|
|
46
|
+
PdfWrapper(pdf, title=new_title, **ctx.obj).write(output or pdf)
|
|
@@ -260,7 +260,6 @@ def create_watermarks_and_draw(
|
|
|
260
260
|
page_to_to_draw[each["page_number"]].append(each)
|
|
261
261
|
|
|
262
262
|
pdf_file = PdfReader(BytesIO(pdf))
|
|
263
|
-
buff = BytesIO()
|
|
264
263
|
|
|
265
264
|
for i, page in enumerate(pdf_file.pages):
|
|
266
265
|
elements = page_to_to_draw[i + 1]
|
|
@@ -268,8 +267,7 @@ def create_watermarks_and_draw(
|
|
|
268
267
|
result.append(b"")
|
|
269
268
|
continue
|
|
270
269
|
|
|
271
|
-
buff
|
|
272
|
-
buff.flush()
|
|
270
|
+
buff = BytesIO()
|
|
273
271
|
|
|
274
272
|
canvas = Canvas(
|
|
275
273
|
buff,
|
|
@@ -177,7 +177,6 @@ class Widget:
|
|
|
177
177
|
result = []
|
|
178
178
|
|
|
179
179
|
pdf = PdfReader(BytesIO(stream))
|
|
180
|
-
watermark = BytesIO()
|
|
181
180
|
|
|
182
181
|
widgets_by_page = {}
|
|
183
182
|
for widget in widgets:
|
|
@@ -191,8 +190,9 @@ class Widget:
|
|
|
191
190
|
result.append(b"")
|
|
192
191
|
continue
|
|
193
192
|
|
|
194
|
-
|
|
195
|
-
watermark.
|
|
193
|
+
# Use a fresh buffer per page to avoid stale trailing bytes
|
|
194
|
+
# when the current page watermark is smaller than a previous page.
|
|
195
|
+
watermark = BytesIO()
|
|
196
196
|
|
|
197
197
|
canvas = Canvas(
|
|
198
198
|
watermark,
|
|
@@ -120,12 +120,9 @@ class SignatureWidget:
|
|
|
120
120
|
key = get_widget_key(annot.get_object(), False)
|
|
121
121
|
annot_type_to_annot[key] = annot.get_object()
|
|
122
122
|
|
|
123
|
-
watermark = BytesIO()
|
|
124
|
-
|
|
125
123
|
for i, p in enumerate(input_pdf.pages):
|
|
126
124
|
# pylint: disable=R0801
|
|
127
|
-
watermark
|
|
128
|
-
watermark.flush()
|
|
125
|
+
watermark = BytesIO()
|
|
129
126
|
canvas = Canvas(
|
|
130
127
|
watermark,
|
|
131
128
|
pagesize=(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 4.7.
|
|
3
|
+
Version: 4.7.9
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,6 +26,8 @@ Requires-Dist: pikepdf<11.0.0,>=10.5.0
|
|
|
26
26
|
Requires-Dist: pillow<13.0.0,>=12.0.0
|
|
27
27
|
Requires-Dist: pypdf<7.0.0,>=6.9.0
|
|
28
28
|
Requires-Dist: reportlab<5.0.0,>=4.4.6
|
|
29
|
+
Provides-Extra: cli
|
|
30
|
+
Requires-Dist: typer<1.0.0,>=0.24.1; extra == "cli"
|
|
29
31
|
Provides-Extra: dev
|
|
30
32
|
Requires-Dist: black<27.0.0,>=25.11.0; extra == "dev"
|
|
31
33
|
Requires-Dist: coverage<8.0.0,>=7.12.0; extra == "dev"
|
|
@@ -5,8 +5,11 @@ PyPDFForm/__init__.py
|
|
|
5
5
|
PyPDFForm.egg-info/PKG-INFO
|
|
6
6
|
PyPDFForm.egg-info/SOURCES.txt
|
|
7
7
|
PyPDFForm.egg-info/dependency_links.txt
|
|
8
|
+
PyPDFForm.egg-info/entry_points.txt
|
|
8
9
|
PyPDFForm.egg-info/requires.txt
|
|
9
10
|
PyPDFForm.egg-info/top_level.txt
|
|
11
|
+
PyPDFForm/cli/__init__.py
|
|
12
|
+
PyPDFForm/cli/update.py
|
|
10
13
|
PyPDFForm/lib/__init__.py
|
|
11
14
|
PyPDFForm/lib/adapter.py
|
|
12
15
|
PyPDFForm/lib/constants.py
|
|
@@ -56,6 +59,7 @@ PyPDFForm/lib/widgets/radio.py
|
|
|
56
59
|
PyPDFForm/lib/widgets/signature.py
|
|
57
60
|
PyPDFForm/lib/widgets/text.py
|
|
58
61
|
tests/test_bulk_create_fields.py
|
|
62
|
+
tests/test_cli.py
|
|
59
63
|
tests/test_create_widget.py
|
|
60
64
|
tests/test_draw_elements.py
|
|
61
65
|
tests/test_dropdown.py
|
|
@@ -7,9 +7,7 @@ name = "PyPDFForm"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "The Python library for PDF forms."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
authors = [
|
|
11
|
-
{name = "Jinge Li"}
|
|
12
|
-
]
|
|
10
|
+
authors = [{ name = "Jinge Li" }]
|
|
13
11
|
license = "MIT"
|
|
14
12
|
license-files = ["LICENSE"]
|
|
15
13
|
classifiers = [
|
|
@@ -40,6 +38,7 @@ Homepage = "https://github.com/chinapandaman/PyPDFForm"
|
|
|
40
38
|
Documentation = "https://chinapandaman.github.io/PyPDFForm/"
|
|
41
39
|
|
|
42
40
|
[project.optional-dependencies]
|
|
41
|
+
cli = ["typer>=0.24.1,<1.0.0"]
|
|
43
42
|
dev = [
|
|
44
43
|
"black>=25.11.0,<27.0.0",
|
|
45
44
|
"coverage>=7.12.0,<8.0.0",
|
|
@@ -56,6 +55,9 @@ dev = [
|
|
|
56
55
|
"ruff>=0.14.6,<1.0.0",
|
|
57
56
|
]
|
|
58
57
|
|
|
58
|
+
[project.scripts]
|
|
59
|
+
pypdfform = "PyPDFForm.cli:cli_app"
|
|
60
|
+
|
|
59
61
|
[tool.coverage.run]
|
|
60
62
|
include = ["PyPDFForm/*"]
|
|
61
63
|
|
|
@@ -129,7 +131,7 @@ reportAssignmentType = "error"
|
|
|
129
131
|
reportOptionalSubscript = "error"
|
|
130
132
|
|
|
131
133
|
[tool.setuptools.dynamic]
|
|
132
|
-
version = {attr = "PyPDFForm.__version__"}
|
|
134
|
+
version = { attr = "PyPDFForm.__version__" }
|
|
133
135
|
|
|
134
136
|
[tool.setuptools.packages.find]
|
|
135
137
|
include = ["PyPDFForm*"]
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from io import BytesIO
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
7
|
+
from pypdf import PdfReader
|
|
6
8
|
|
|
7
|
-
from PyPDFForm import Fields, PdfWrapper
|
|
9
|
+
from PyPDFForm import BlankPage, Fields, PdfWrapper
|
|
10
|
+
from PyPDFForm.lib.constants import Annots, Subtype, Widget
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
@pytest.mark.posix_only
|
|
@@ -144,3 +147,51 @@ def test_bulk_create_fields_stress_max_mixed(pdf_samples, request):
|
|
|
144
147
|
|
|
145
148
|
assert len(obj.read()) == len(expected)
|
|
146
149
|
assert obj.read() == expected
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_bulk_create_fields_does_not_duplicate_widgets():
|
|
153
|
+
wrapper = PdfWrapper(BlankPage() * 2)
|
|
154
|
+
|
|
155
|
+
fields = []
|
|
156
|
+
y = 760.0
|
|
157
|
+
for i in range(35):
|
|
158
|
+
fields.append(
|
|
159
|
+
Fields.TextField(
|
|
160
|
+
f"p1_field_{i}",
|
|
161
|
+
1,
|
|
162
|
+
50.0,
|
|
163
|
+
y,
|
|
164
|
+
width=220.0,
|
|
165
|
+
height=16.0,
|
|
166
|
+
font_size=9.0,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
y -= 20.0
|
|
170
|
+
|
|
171
|
+
y = 760.0
|
|
172
|
+
for i in range(4):
|
|
173
|
+
fields.append(
|
|
174
|
+
Fields.TextField(
|
|
175
|
+
f"p2_field_{i}",
|
|
176
|
+
2,
|
|
177
|
+
50.0,
|
|
178
|
+
y,
|
|
179
|
+
width=220.0,
|
|
180
|
+
height=16.0,
|
|
181
|
+
font_size=9.0,
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
y -= 24.0
|
|
185
|
+
|
|
186
|
+
output = wrapper.bulk_create_fields(fields).read()
|
|
187
|
+
reader = PdfReader(BytesIO(output))
|
|
188
|
+
|
|
189
|
+
widget_count = 0
|
|
190
|
+
for page in reader.pages:
|
|
191
|
+
annots = page.get(Annots) or []
|
|
192
|
+
for annot_ref in annots:
|
|
193
|
+
annot = annot_ref.get_object()
|
|
194
|
+
if str(annot.get(Subtype)) == Widget:
|
|
195
|
+
widget_count += 1
|
|
196
|
+
|
|
197
|
+
assert widget_count == len(fields)
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|