PyPDFForm 4.7.7__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.
Files changed (79) hide show
  1. {pypdfform-4.7.7 → pypdfform-4.7.9}/PKG-INFO +3 -1
  2. {pypdfform-4.7.7 → pypdfform-4.7.9}/PyPDFForm/__init__.py +9 -9
  3. pypdfform-4.7.9/PyPDFForm/cli/__init__.py +166 -0
  4. pypdfform-4.7.9/PyPDFForm/cli/update.py +46 -0
  5. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/annotations/__init__.py +1 -1
  6. pypdfform-4.7.9/PyPDFForm/lib/assets/__init__.py +0 -0
  7. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/__init__.py +1 -1
  8. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/__init__.py +1 -1
  9. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/template.py +1 -1
  10. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/watermark.py +1 -3
  11. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/__init__.py +1 -1
  12. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/base.py +3 -3
  13. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/signature.py +1 -4
  14. {pypdfform-4.7.7 → pypdfform-4.7.9}/PyPDFForm.egg-info/PKG-INFO +3 -1
  15. pypdfform-4.7.9/PyPDFForm.egg-info/SOURCES.txt +76 -0
  16. pypdfform-4.7.9/PyPDFForm.egg-info/entry_points.txt +2 -0
  17. {pypdfform-4.7.7 → pypdfform-4.7.9}/PyPDFForm.egg-info/requires.txt +3 -0
  18. {pypdfform-4.7.7 → pypdfform-4.7.9}/pyproject.toml +6 -4
  19. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_bulk_create_fields.py +52 -1
  20. pypdfform-4.7.9/tests/test_cli.py +35 -0
  21. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_font_widths.py +5 -5
  22. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_functional.py +4 -4
  23. pypdfform-4.7.7/PyPDFForm.egg-info/SOURCES.txt +0 -71
  24. {pypdfform-4.7.7 → pypdfform-4.7.9}/LICENSE +0 -0
  25. {pypdfform-4.7.7/PyPDFForm/assets → pypdfform-4.7.9/PyPDFForm/lib}/__init__.py +0 -0
  26. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/adapter.py +0 -0
  27. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/annotations/base.py +0 -0
  28. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/annotations/link.py +0 -0
  29. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/annotations/stamp.py +0 -0
  30. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/annotations/text.py +0 -0
  31. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/annotations/text_markup.py +0 -0
  32. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/assets/bedrock.py +0 -0
  33. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/assets/blank.py +0 -0
  34. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/constants.py +0 -0
  35. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/coordinate.py +0 -0
  36. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/deprecation.py +0 -0
  37. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/egress.py +0 -0
  38. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/filler.py +0 -0
  39. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/font.py +0 -0
  40. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/hooks.py +0 -0
  41. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/image.py +0 -0
  42. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/base.py +0 -0
  43. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/checkbox.py +0 -0
  44. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/dropdown.py +0 -0
  45. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/image.py +0 -0
  46. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/radio.py +0 -0
  47. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/signature.py +0 -0
  48. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/middleware/text.py +0 -0
  49. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/patterns.py +0 -0
  50. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/circle.py +0 -0
  51. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/ellipse.py +0 -0
  52. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/image.py +0 -0
  53. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/line.py +0 -0
  54. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/rect.py +0 -0
  55. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/raw/text.py +0 -0
  56. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/types.py +0 -0
  57. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/utils.py +0 -0
  58. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/checkbox.py +0 -0
  59. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/dropdown.py +0 -0
  60. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/image.py +0 -0
  61. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/radio.py +0 -0
  62. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/widgets/text.py +0 -0
  63. {pypdfform-4.7.7/PyPDFForm → pypdfform-4.7.9/PyPDFForm/lib}/wrapper.py +0 -0
  64. {pypdfform-4.7.7 → pypdfform-4.7.9}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  65. {pypdfform-4.7.7 → pypdfform-4.7.9}/PyPDFForm.egg-info/top_level.txt +0 -0
  66. {pypdfform-4.7.7 → pypdfform-4.7.9}/README.md +0 -0
  67. {pypdfform-4.7.7 → pypdfform-4.7.9}/setup.cfg +0 -0
  68. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_create_widget.py +0 -0
  69. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_draw_elements.py +0 -0
  70. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_dropdown.py +0 -0
  71. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_extract_middleware_attributes.py +0 -0
  72. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_fill_max_length_text_field.py +0 -0
  73. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_generate_appearance_streams.py +0 -0
  74. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_js.py +0 -0
  75. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_need_appearances.py +0 -0
  76. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_paragraph.py +0 -0
  77. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_signature.py +0 -0
  78. {pypdfform-4.7.7 → pypdfform-4.7.9}/tests/test_use_full_widget_name.py +0 -0
  79. {pypdfform-4.7.7 → 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.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"
@@ -22,15 +22,15 @@ PyPDFForm aims to simplify PDF form manipulation, making it accessible to develo
22
22
 
23
23
  import logging
24
24
 
25
- __version__ = "4.7.7"
26
-
27
- from .annotations import Annotations
28
- from .assets.blank import BlankPage
29
- from .middleware import Widgets
30
- from .raw import RawElements
31
- from .types import PdfArray
32
- from .widgets import Fields
33
- from .wrapper import PdfWrapper
25
+ __version__ = "4.7.9"
26
+
27
+ from .lib.annotations import Annotations
28
+ from .lib.assets.blank import BlankPage
29
+ from .lib.middleware import Widgets
30
+ from .lib.raw import RawElements
31
+ from .lib.types import PdfArray
32
+ from .lib.widgets import Fields
33
+ from .lib.wrapper import PdfWrapper
34
34
 
35
35
  # TODO: figure out why `Annotation sizes differ:`
36
36
  for logger in [
@@ -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)
@@ -34,7 +34,7 @@ class Annotations:
34
34
  A container class that provides convenient access to all available PDF annotation types.
35
35
 
36
36
  This class acts as a namespace for the various `Annotation` classes defined in the
37
- `PyPDFForm.annotations` package, making it easier to reference them (e.g., `Annotations.TextAnnotation`).
37
+ `PyPDFForm.lib.annotations` package, making it easier to reference them (e.g., `Annotations.TextAnnotation`).
38
38
  """
39
39
 
40
40
  TextAnnotation = TextAnnotation
File without changes
@@ -26,7 +26,7 @@ class Widgets:
26
26
  A container class that provides convenient access to all available middleware widget classes.
27
27
 
28
28
  This class acts as a namespace for the various middleware classes defined in the
29
- `PyPDFForm.middleware` package, making it easier to reference them (e.g., `Widgets.Text`).
29
+ `PyPDFForm.lib.middleware` package, making it easier to reference them (e.g., `Widgets.Text`).
30
30
  """
31
31
 
32
32
  Text = Text
@@ -25,7 +25,7 @@ class RawElements:
25
25
  A container class that provides convenient access to all available raw drawable elements.
26
26
 
27
27
  This class acts as a namespace for the various `Raw` classes defined in the
28
- `PyPDFForm.raw` package, making it easier to reference them (e.g., `RawElements.RawText`).
28
+ `PyPDFForm.lib.raw` package, making it easier to reference them (e.g., `RawElements.RawText`).
29
29
  """
30
30
 
31
31
  RawText = RawText
@@ -325,7 +325,7 @@ def create_annotations(
325
325
 
326
326
  This function takes a PDF template and a list of annotation objects, and
327
327
  renders each annotation onto its specified page in the PDF. It supports
328
- various annotation types defined in the `PyPDFForm.annotations` package.
328
+ various annotation types defined in the `PyPDFForm.lib.annotations` package.
329
329
 
330
330
  Args:
331
331
  template (bytes): The PDF template to add annotations to.
@@ -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.seek(0)
272
- buff.flush()
270
+ buff = BytesIO()
273
271
 
274
272
  canvas = Canvas(
275
273
  buff,
@@ -29,7 +29,7 @@ class Fields:
29
29
  A container class that provides convenient access to all available PDF form field types.
30
30
 
31
31
  This class acts as a namespace for the various `Field` classes defined in the
32
- `PyPDFForm.widgets` package, making it easier to reference them (e.g., `Fields.TextField`).
32
+ `PyPDFForm.lib.widgets` package, making it easier to reference them (e.g., `Fields.TextField`).
33
33
  """
34
34
 
35
35
  TextField = TextField
@@ -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
- watermark.seek(0)
195
- watermark.flush()
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.seek(0)
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.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,76 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ PyPDFForm/__init__.py
5
+ PyPDFForm.egg-info/PKG-INFO
6
+ PyPDFForm.egg-info/SOURCES.txt
7
+ PyPDFForm.egg-info/dependency_links.txt
8
+ PyPDFForm.egg-info/entry_points.txt
9
+ PyPDFForm.egg-info/requires.txt
10
+ PyPDFForm.egg-info/top_level.txt
11
+ PyPDFForm/cli/__init__.py
12
+ PyPDFForm/cli/update.py
13
+ PyPDFForm/lib/__init__.py
14
+ PyPDFForm/lib/adapter.py
15
+ PyPDFForm/lib/constants.py
16
+ PyPDFForm/lib/coordinate.py
17
+ PyPDFForm/lib/deprecation.py
18
+ PyPDFForm/lib/egress.py
19
+ PyPDFForm/lib/filler.py
20
+ PyPDFForm/lib/font.py
21
+ PyPDFForm/lib/hooks.py
22
+ PyPDFForm/lib/image.py
23
+ PyPDFForm/lib/patterns.py
24
+ PyPDFForm/lib/template.py
25
+ PyPDFForm/lib/types.py
26
+ PyPDFForm/lib/utils.py
27
+ PyPDFForm/lib/watermark.py
28
+ PyPDFForm/lib/wrapper.py
29
+ PyPDFForm/lib/annotations/__init__.py
30
+ PyPDFForm/lib/annotations/base.py
31
+ PyPDFForm/lib/annotations/link.py
32
+ PyPDFForm/lib/annotations/stamp.py
33
+ PyPDFForm/lib/annotations/text.py
34
+ PyPDFForm/lib/annotations/text_markup.py
35
+ PyPDFForm/lib/assets/__init__.py
36
+ PyPDFForm/lib/assets/bedrock.py
37
+ PyPDFForm/lib/assets/blank.py
38
+ PyPDFForm/lib/middleware/__init__.py
39
+ PyPDFForm/lib/middleware/base.py
40
+ PyPDFForm/lib/middleware/checkbox.py
41
+ PyPDFForm/lib/middleware/dropdown.py
42
+ PyPDFForm/lib/middleware/image.py
43
+ PyPDFForm/lib/middleware/radio.py
44
+ PyPDFForm/lib/middleware/signature.py
45
+ PyPDFForm/lib/middleware/text.py
46
+ PyPDFForm/lib/raw/__init__.py
47
+ PyPDFForm/lib/raw/circle.py
48
+ PyPDFForm/lib/raw/ellipse.py
49
+ PyPDFForm/lib/raw/image.py
50
+ PyPDFForm/lib/raw/line.py
51
+ PyPDFForm/lib/raw/rect.py
52
+ PyPDFForm/lib/raw/text.py
53
+ PyPDFForm/lib/widgets/__init__.py
54
+ PyPDFForm/lib/widgets/base.py
55
+ PyPDFForm/lib/widgets/checkbox.py
56
+ PyPDFForm/lib/widgets/dropdown.py
57
+ PyPDFForm/lib/widgets/image.py
58
+ PyPDFForm/lib/widgets/radio.py
59
+ PyPDFForm/lib/widgets/signature.py
60
+ PyPDFForm/lib/widgets/text.py
61
+ tests/test_bulk_create_fields.py
62
+ tests/test_cli.py
63
+ tests/test_create_widget.py
64
+ tests/test_draw_elements.py
65
+ tests/test_dropdown.py
66
+ tests/test_extract_middleware_attributes.py
67
+ tests/test_fill_max_length_text_field.py
68
+ tests/test_font_widths.py
69
+ tests/test_functional.py
70
+ tests/test_generate_appearance_streams.py
71
+ tests/test_js.py
72
+ tests/test_need_appearances.py
73
+ tests/test_paragraph.py
74
+ tests/test_signature.py
75
+ tests/test_use_full_widget_name.py
76
+ tests/test_widget_attr_trigger.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pypdfform = PyPDFForm.cli:cli_app
@@ -5,6 +5,9 @@ pillow<13.0.0,>=12.0.0
5
5
  pypdf<7.0.0,>=6.9.0
6
6
  reportlab<5.0.0,>=4.4.6
7
7
 
8
+ [cli]
9
+ typer<1.0.0,>=0.24.1
10
+
8
11
  [dev]
9
12
  black<27.0.0,>=25.11.0
10
13
  coverage<8.0.0,>=7.12.0
@@ -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
@@ -13,10 +13,10 @@ from fontTools.ttLib import TTLibError
13
13
  from pypdf import PdfWriter
14
14
 
15
15
  from PyPDFForm import PdfWrapper
16
- from PyPDFForm.constants import (DEFAULT_ASSUMED_GLYPH_WIDTH, DR,
17
- ENCODING_TABLE_SIZE, AcroForm, Font,
18
- FontDescriptor, MissingWidth, Widths)
19
- from PyPDFForm.font import compute_font_glyph_widths
16
+ from PyPDFForm.lib.constants import (DEFAULT_ASSUMED_GLYPH_WIDTH, DR,
17
+ ENCODING_TABLE_SIZE, AcroForm, Font,
18
+ FontDescriptor, MissingWidth, Widths)
19
+ from PyPDFForm.lib.font import compute_font_glyph_widths
20
20
 
21
21
 
22
22
  @pytest.fixture
@@ -72,7 +72,7 @@ def test_compute_font_widths_raises_for_invalid_ttf():
72
72
 
73
73
 
74
74
  def test_compute_font_glyph_widths_with_missing_tables():
75
- with patch("PyPDFForm.font.FT_TTFont") as mock_ttfont:
75
+ with patch("PyPDFForm.lib.font.FT_TTFont") as mock_ttfont:
76
76
  mock_font = MagicMock()
77
77
  mock_font.get.side_effect = lambda table: None
78
78
  mock_ttfont.return_value = mock_font
@@ -7,10 +7,10 @@ import pytest
7
7
  from jsonschema import ValidationError, validate
8
8
 
9
9
  from PyPDFForm import Annotations, BlankPage, Fields, PdfArray, PdfWrapper
10
- from PyPDFForm.constants import DA, UNIQUE_SUFFIX_LENGTH, T, V
11
- from PyPDFForm.deprecation import deprecation_notice
12
- from PyPDFForm.middleware.base import Widget
13
- from PyPDFForm.template import get_widgets_by_page
10
+ from PyPDFForm.lib.constants import DA, UNIQUE_SUFFIX_LENGTH, T, V
11
+ from PyPDFForm.lib.deprecation import deprecation_notice
12
+ from PyPDFForm.lib.middleware.base import Widget
13
+ from PyPDFForm.lib.template import get_widgets_by_page
14
14
 
15
15
 
16
16
  def test_deprecation_warning():
@@ -1,71 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- PyPDFForm/__init__.py
5
- PyPDFForm/adapter.py
6
- PyPDFForm/constants.py
7
- PyPDFForm/coordinate.py
8
- PyPDFForm/deprecation.py
9
- PyPDFForm/egress.py
10
- PyPDFForm/filler.py
11
- PyPDFForm/font.py
12
- PyPDFForm/hooks.py
13
- PyPDFForm/image.py
14
- PyPDFForm/patterns.py
15
- PyPDFForm/template.py
16
- PyPDFForm/types.py
17
- PyPDFForm/utils.py
18
- PyPDFForm/watermark.py
19
- PyPDFForm/wrapper.py
20
- PyPDFForm.egg-info/PKG-INFO
21
- PyPDFForm.egg-info/SOURCES.txt
22
- PyPDFForm.egg-info/dependency_links.txt
23
- PyPDFForm.egg-info/requires.txt
24
- PyPDFForm.egg-info/top_level.txt
25
- PyPDFForm/annotations/__init__.py
26
- PyPDFForm/annotations/base.py
27
- PyPDFForm/annotations/link.py
28
- PyPDFForm/annotations/stamp.py
29
- PyPDFForm/annotations/text.py
30
- PyPDFForm/annotations/text_markup.py
31
- PyPDFForm/assets/__init__.py
32
- PyPDFForm/assets/bedrock.py
33
- PyPDFForm/assets/blank.py
34
- PyPDFForm/middleware/__init__.py
35
- PyPDFForm/middleware/base.py
36
- PyPDFForm/middleware/checkbox.py
37
- PyPDFForm/middleware/dropdown.py
38
- PyPDFForm/middleware/image.py
39
- PyPDFForm/middleware/radio.py
40
- PyPDFForm/middleware/signature.py
41
- PyPDFForm/middleware/text.py
42
- PyPDFForm/raw/__init__.py
43
- PyPDFForm/raw/circle.py
44
- PyPDFForm/raw/ellipse.py
45
- PyPDFForm/raw/image.py
46
- PyPDFForm/raw/line.py
47
- PyPDFForm/raw/rect.py
48
- PyPDFForm/raw/text.py
49
- PyPDFForm/widgets/__init__.py
50
- PyPDFForm/widgets/base.py
51
- PyPDFForm/widgets/checkbox.py
52
- PyPDFForm/widgets/dropdown.py
53
- PyPDFForm/widgets/image.py
54
- PyPDFForm/widgets/radio.py
55
- PyPDFForm/widgets/signature.py
56
- PyPDFForm/widgets/text.py
57
- tests/test_bulk_create_fields.py
58
- tests/test_create_widget.py
59
- tests/test_draw_elements.py
60
- tests/test_dropdown.py
61
- tests/test_extract_middleware_attributes.py
62
- tests/test_fill_max_length_text_field.py
63
- tests/test_font_widths.py
64
- tests/test_functional.py
65
- tests/test_generate_appearance_streams.py
66
- tests/test_js.py
67
- tests/test_need_appearances.py
68
- tests/test_paragraph.py
69
- tests/test_signature.py
70
- tests/test_use_full_widget_name.py
71
- tests/test_widget_attr_trigger.py
File without changes
File without changes
File without changes
File without changes