PyPDFForm 3.4.0__py3-none-any.whl → 3.5.0__py3-none-any.whl
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.
Potentially problematic release.
This version of PyPDFForm might be problematic. Click here for more details.
- PyPDFForm/__init__.py +3 -2
- PyPDFForm/constants.py +14 -0
- PyPDFForm/font.py +76 -7
- PyPDFForm/widgets/__init__.py +40 -0
- PyPDFForm/widgets/base.py +41 -6
- PyPDFForm/widgets/checkbox.py +41 -3
- PyPDFForm/widgets/dropdown.py +49 -2
- PyPDFForm/widgets/image.py +28 -5
- PyPDFForm/widgets/radio.py +37 -5
- PyPDFForm/widgets/signature.py +33 -5
- PyPDFForm/widgets/text.py +53 -3
- PyPDFForm/wrapper.py +54 -2
- {pypdfform-3.4.0.dist-info → pypdfform-3.5.0.dist-info}/METADATA +2 -1
- {pypdfform-3.4.0.dist-info → pypdfform-3.5.0.dist-info}/RECORD +17 -17
- {pypdfform-3.4.0.dist-info → pypdfform-3.5.0.dist-info}/WHEEL +0 -0
- {pypdfform-3.4.0.dist-info → pypdfform-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-3.4.0.dist-info → pypdfform-3.5.0.dist-info}/top_level.txt +0 -0
PyPDFForm/__init__.py
CHANGED
|
@@ -20,9 +20,10 @@ The library supports various PDF form features, including:
|
|
|
20
20
|
PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
__version__ = "3.
|
|
23
|
+
__version__ = "3.5.0"
|
|
24
24
|
|
|
25
25
|
from .middleware.text import Text # exposing for setting global font attrs
|
|
26
|
+
from .widgets import Fields
|
|
26
27
|
from .wrapper import PdfWrapper
|
|
27
28
|
|
|
28
|
-
__all__ = ["PdfWrapper", "Text"]
|
|
29
|
+
__all__ = ["PdfWrapper", "Text", "Fields"]
|
PyPDFForm/constants.py
CHANGED
|
@@ -83,6 +83,20 @@ Filter = "/Filter"
|
|
|
83
83
|
FlateDecode = "/FlateDecode"
|
|
84
84
|
Encoding = "/Encoding"
|
|
85
85
|
WinAnsiEncoding = "/WinAnsiEncoding"
|
|
86
|
+
Widths = "/Widths"
|
|
87
|
+
FirstChar = "/FirstChar"
|
|
88
|
+
LastChar = "/LastChar"
|
|
89
|
+
MissingWidth = "/MissingWidth"
|
|
90
|
+
FontHead = "head"
|
|
91
|
+
FontCmap = "cmap"
|
|
92
|
+
FontHmtx = "hmtx"
|
|
93
|
+
FontNotdef = ".notdef"
|
|
94
|
+
|
|
95
|
+
FIRST_CHAR_CODE = 0
|
|
96
|
+
LAST_CHAR_CODE = 255
|
|
97
|
+
ENCODING_TABLE_SIZE = 256
|
|
98
|
+
EM_TO_PDF_FACTOR = 1000
|
|
99
|
+
DEFAULT_ASSUMED_GLYPH_WIDTH = 300
|
|
86
100
|
|
|
87
101
|
Resources = "/Resources"
|
|
88
102
|
FONT_NAME_PREFIX = "/F"
|
PyPDFForm/font.py
CHANGED
|
@@ -16,16 +16,21 @@ from functools import lru_cache
|
|
|
16
16
|
from io import BytesIO
|
|
17
17
|
from zlib import compress
|
|
18
18
|
|
|
19
|
+
from fontTools.ttLib import TTFont as FT_TTFont
|
|
19
20
|
from pypdf import PdfReader, PdfWriter
|
|
20
|
-
from pypdf.generic import (ArrayObject, DictionaryObject,
|
|
21
|
-
NumberObject, StreamObject)
|
|
21
|
+
from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
|
|
22
|
+
NameObject, NumberObject, StreamObject)
|
|
22
23
|
from reportlab.pdfbase.pdfmetrics import registerFont
|
|
23
24
|
from reportlab.pdfbase.ttfonts import TTFError, TTFont
|
|
24
25
|
|
|
25
|
-
from .constants import (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
from .constants import (DEFAULT_ASSUMED_GLYPH_WIDTH, DR, EM_TO_PDF_FACTOR,
|
|
27
|
+
ENCODING_TABLE_SIZE, FIRST_CHAR_CODE, FONT_NAME_PREFIX,
|
|
28
|
+
LAST_CHAR_CODE, AcroForm, BaseFont, Encoding, Fields,
|
|
29
|
+
Filter, FirstChar, FlateDecode, Font, FontCmap,
|
|
30
|
+
FontDescriptor, FontFile2, FontHead, FontHmtx,
|
|
31
|
+
FontName, FontNotdef, LastChar, Length, Length1,
|
|
32
|
+
MissingWidth, Resources, Subtype, TrueType, Type,
|
|
33
|
+
Widths, WinAnsiEncoding)
|
|
29
34
|
from .utils import stream_to_io
|
|
30
35
|
|
|
31
36
|
|
|
@@ -83,8 +88,9 @@ def get_additional_font_params(pdf: bytes, base_font_name: str) -> tuple:
|
|
|
83
88
|
font_descriptor_params = {}
|
|
84
89
|
font_dict_params = {}
|
|
85
90
|
reader = PdfReader(stream_to_io(pdf))
|
|
91
|
+
first_page = reader.get_page(0)
|
|
86
92
|
|
|
87
|
-
for font in
|
|
93
|
+
for font in first_page[Resources][Font].values():
|
|
88
94
|
if base_font_name.replace("/", "") in font[BaseFont]:
|
|
89
95
|
font_descriptor_params = dict(font[FontDescriptor])
|
|
90
96
|
font_dict_params = dict(font)
|
|
@@ -93,6 +99,54 @@ def get_additional_font_params(pdf: bytes, base_font_name: str) -> tuple:
|
|
|
93
99
|
return font_descriptor_params, font_dict_params
|
|
94
100
|
|
|
95
101
|
|
|
102
|
+
def compute_font_glyph_widths(ttf_file: BytesIO, missing_width: float):
|
|
103
|
+
"""
|
|
104
|
+
Computes the advance widths for all glyphs in a TrueType font, scaled for PDF text space.
|
|
105
|
+
|
|
106
|
+
This function utilizes the `fontTools` library to parse the provided TTF stream
|
|
107
|
+
and extract necessary metrics from the 'head', 'cmap', and 'hmtx' tables.
|
|
108
|
+
It calculates the width for each glyph based on its advance width and the font's
|
|
109
|
+
`unitsPerEm`, then scales these widths to a 1000-unit text space, which is standard
|
|
110
|
+
for PDF font metrics.
|
|
111
|
+
|
|
112
|
+
If any of the required font tables ('head', 'cmap', 'hmtx') are missing or
|
|
113
|
+
cannot be accessed, the function returns a list populated with a specified
|
|
114
|
+
`missing_width` for all expected glyphs, ensuring a fallback mechanism.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
ttf_file (BytesIO): A BytesIO stream containing the TrueType Font (TTF) data.
|
|
118
|
+
This stream should be seekable and readable.
|
|
119
|
+
missing_width (float): The default width to be used for all glyphs if the
|
|
120
|
+
necessary font tables (head, cmap, hmtx) are not found
|
|
121
|
+
within the TTF file.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
list[float]: A list of floats, where each float represents the scaled advance
|
|
125
|
+
width of a glyph in PDF text space units (1000 units per EM).
|
|
126
|
+
The list covers glyphs from `FIRST_CHAR_CODE` to `LAST_CHAR_CODE`.
|
|
127
|
+
If font tables are missing, the list will be filled with `missing_width`.
|
|
128
|
+
"""
|
|
129
|
+
font = FT_TTFont(ttf_file)
|
|
130
|
+
head_table = font.get(FontHead)
|
|
131
|
+
cmap_table = font.get(FontCmap)
|
|
132
|
+
hmtx_table = font.get(FontHmtx)
|
|
133
|
+
|
|
134
|
+
widths: list[float] = []
|
|
135
|
+
if head_table and cmap_table and hmtx_table:
|
|
136
|
+
cmap = cmap_table.getBestCmap()
|
|
137
|
+
units_per_em: int = head_table.unitsPerEm or 1
|
|
138
|
+
|
|
139
|
+
for codepoint in range(ENCODING_TABLE_SIZE):
|
|
140
|
+
glyph_name: str = cmap.get(codepoint, FontNotdef)
|
|
141
|
+
advance_width, _ = hmtx_table[glyph_name]
|
|
142
|
+
pdf_width: float = (advance_width / units_per_em) * EM_TO_PDF_FACTOR
|
|
143
|
+
widths.append(pdf_width)
|
|
144
|
+
else:
|
|
145
|
+
widths: list[float] = [missing_width] * ENCODING_TABLE_SIZE
|
|
146
|
+
|
|
147
|
+
return widths
|
|
148
|
+
|
|
149
|
+
|
|
96
150
|
def register_font_acroform(pdf: bytes, ttf_stream: bytes, adobe_mode: bool) -> tuple:
|
|
97
151
|
"""
|
|
98
152
|
Registers a TrueType font within the PDF's AcroForm dictionary.
|
|
@@ -159,7 +213,22 @@ def register_font_acroform(pdf: bytes, ttf_stream: bytes, adobe_mode: bool) -> t
|
|
|
159
213
|
NameObject(Encoding): NameObject(WinAnsiEncoding),
|
|
160
214
|
}
|
|
161
215
|
)
|
|
216
|
+
|
|
162
217
|
font_dict.update({k: v for k, v in font_dict_params.items() if k not in font_dict})
|
|
218
|
+
|
|
219
|
+
if font_dict and Widths in font_dict:
|
|
220
|
+
ttf_bytes_io = BytesIO(ttf_stream)
|
|
221
|
+
missing_width = font_descriptor.get(MissingWidth, DEFAULT_ASSUMED_GLYPH_WIDTH)
|
|
222
|
+
widths = compute_font_glyph_widths(ttf_bytes_io, missing_width)
|
|
223
|
+
|
|
224
|
+
font_dict.update(
|
|
225
|
+
{
|
|
226
|
+
NameObject(FirstChar): NumberObject(FIRST_CHAR_CODE),
|
|
227
|
+
NameObject(LastChar): NumberObject(LAST_CHAR_CODE),
|
|
228
|
+
NameObject(Widths): ArrayObject(FloatObject(width) for width in widths),
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
|
|
163
232
|
font_dict_ref = writer._add_object(font_dict) # type: ignore # noqa: SLF001 # # pylint: disable=W0212
|
|
164
233
|
|
|
165
234
|
if AcroForm not in writer._root_object: # type: ignore # noqa: SLF001 # # pylint: disable=W0212
|
PyPDFForm/widgets/__init__.py
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The `widgets` package provides a collection of classes representing various types of PDF form fields (widgets).
|
|
3
|
+
|
|
4
|
+
It defines `FieldTypes` as a Union of all supported field types, allowing for flexible
|
|
5
|
+
type hinting when working with different widget configurations.
|
|
6
|
+
|
|
7
|
+
Classes within this package encapsulate the properties and behaviors of individual
|
|
8
|
+
form fields, facilitating their creation and manipulation within PDF documents.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
from .checkbox import CheckBoxField
|
|
15
|
+
from .dropdown import DropdownField
|
|
16
|
+
from .image import ImageField
|
|
17
|
+
from .radio import RadioGroup
|
|
18
|
+
from .signature import SignatureField
|
|
19
|
+
from .text import TextField
|
|
20
|
+
|
|
21
|
+
FieldTypes = Union[
|
|
22
|
+
TextField, CheckBoxField, RadioGroup, DropdownField, SignatureField, ImageField
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Fields:
|
|
28
|
+
"""
|
|
29
|
+
A container class that provides convenient access to all available PDF form field types.
|
|
30
|
+
|
|
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`).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
TextField = TextField
|
|
36
|
+
CheckBoxField = CheckBoxField
|
|
37
|
+
RadioGroup = RadioGroup
|
|
38
|
+
DropdownField = DropdownField
|
|
39
|
+
SignatureField = SignatureField
|
|
40
|
+
ImageField = ImageField
|
PyPDFForm/widgets/base.py
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the base
|
|
3
|
+
This module defines the base classes for all form fields and widgets in PyPDFForm.
|
|
4
4
|
|
|
5
|
-
It provides a
|
|
6
|
-
such as text fields, checkboxes,
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
It provides a foundational structure for representing and interacting with
|
|
6
|
+
different types of PDF form elements, such as text fields, checkboxes,
|
|
7
|
+
and radio buttons.
|
|
8
|
+
|
|
9
|
+
Classes:
|
|
10
|
+
- `Field`: A dataclass representing the common properties of a PDF form field.
|
|
11
|
+
- `Widget`: A base class for all widget implementations, providing core
|
|
12
|
+
functionality for rendering and manipulation.
|
|
9
13
|
"""
|
|
14
|
+
|
|
10
15
|
# TODO: In `watermarks`, `PdfReader(stream_to_io(stream))` is called, which re-parses the PDF for each widget. If multiple widgets are being processed, consider passing the `PdfReader` object directly to avoid redundant parsing.
|
|
11
16
|
# TODO: In `watermarks`, the list comprehension `[watermark.read() if i == self.page_number - 1 else b"" for i in range(page_count)]` creates a new `BytesIO` object and reads from it for each widget. If many widgets are created, this could be optimized by creating the `BytesIO` object once and passing it around, or by directly returning the watermark bytes and its page number.
|
|
12
17
|
|
|
18
|
+
from dataclasses import dataclass
|
|
13
19
|
from inspect import signature
|
|
14
20
|
from io import BytesIO
|
|
15
|
-
from typing import List, Union
|
|
21
|
+
from typing import List, Optional, Union
|
|
16
22
|
|
|
17
23
|
from pypdf import PdfReader
|
|
18
24
|
from reportlab.lib.colors import Color
|
|
@@ -22,6 +28,35 @@ from ..constants import fieldFlags, required
|
|
|
22
28
|
from ..utils import stream_to_io
|
|
23
29
|
|
|
24
30
|
|
|
31
|
+
@dataclass
|
|
32
|
+
class Field:
|
|
33
|
+
"""
|
|
34
|
+
Base dataclass for all PDF form fields.
|
|
35
|
+
|
|
36
|
+
This class defines the common properties that all types of form fields
|
|
37
|
+
(e.g., text fields, checkboxes, radio buttons) share. Specific field types
|
|
38
|
+
will extend this class to add their unique attributes.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
name (str): The name of the form field. This is used to identify the
|
|
42
|
+
field within the PDF document.
|
|
43
|
+
page_number (int): The 1-based page number on which the field is located.
|
|
44
|
+
x (float): The x-coordinate of the field's position on the page.
|
|
45
|
+
y (float): The y-coordinate of the field's position on the page.
|
|
46
|
+
required (Optional[bool]): Indicates whether the field is required to be
|
|
47
|
+
filled by the user. Defaults to None, meaning not explicitly set.
|
|
48
|
+
tooltip (Optional[str]): A tooltip message that appears when the user
|
|
49
|
+
hovers over the field. Defaults to None.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
page_number: int
|
|
54
|
+
x: float
|
|
55
|
+
y: float
|
|
56
|
+
required: Optional[bool] = None
|
|
57
|
+
tooltip: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
|
|
25
60
|
class Widget:
|
|
26
61
|
"""
|
|
27
62
|
Base class for all widgets in PyPDFForm.
|
PyPDFForm/widgets/checkbox.py
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the CheckBoxWidget
|
|
4
|
-
|
|
3
|
+
This module defines the `CheckBoxField` and `CheckBoxWidget` classes, which are
|
|
4
|
+
used to represent and manipulate checkbox form fields within PDF documents.
|
|
5
|
+
|
|
6
|
+
The `CheckBoxField` class is a dataclass that encapsulates the properties of a
|
|
7
|
+
checkbox field, such as its size, style, and colors.
|
|
8
|
+
|
|
9
|
+
The `CheckBoxWidget` class extends the base `Widget` class to provide specific
|
|
10
|
+
functionality for interacting with checkbox form fields in PDFs.
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
|
-
from
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from .base import Field, Widget
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CheckBoxField(Field):
|
|
21
|
+
"""
|
|
22
|
+
Represents a checkbox field in a PDF document.
|
|
23
|
+
|
|
24
|
+
This dataclass extends the `Field` base class and defines the specific
|
|
25
|
+
attributes that can be configured for a checkbox field.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
_field_type (str): The type of the field, fixed as "checkbox".
|
|
29
|
+
size (Optional[float]): The size of the checkbox.
|
|
30
|
+
button_style (Optional[str]): The visual style of the checkbox button
|
|
31
|
+
(e.g., "check", "circle", "cross").
|
|
32
|
+
tick_color (Optional[Tuple[float, ...]]): The color of the checkmark or tick.
|
|
33
|
+
bg_color (Optional[Tuple[float, ...]]): The background color of the checkbox.
|
|
34
|
+
border_color (Optional[Tuple[float, ...]]): The color of the checkbox's border.
|
|
35
|
+
border_width (Optional[float]): The width of the checkbox's border.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_field_type: str = "checkbox"
|
|
39
|
+
|
|
40
|
+
size: Optional[float] = None
|
|
41
|
+
button_style: Optional[str] = None
|
|
42
|
+
tick_color: Optional[Tuple[float, ...]] = None
|
|
43
|
+
bg_color: Optional[Tuple[float, ...]] = None
|
|
44
|
+
border_color: Optional[Tuple[float, ...]] = None
|
|
45
|
+
border_width: Optional[float] = None
|
|
8
46
|
|
|
9
47
|
|
|
10
48
|
class CheckBoxWidget(Widget):
|
PyPDFForm/widgets/dropdown.py
CHANGED
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the DropdownWidget
|
|
4
|
-
|
|
3
|
+
This module defines the `DropdownField` and `DropdownWidget` classes, which are
|
|
4
|
+
used to represent and manipulate dropdown form fields within PDF documents.
|
|
5
|
+
|
|
6
|
+
The `DropdownField` class is a dataclass that encapsulates the properties of a
|
|
7
|
+
dropdown field, such as its options, dimensions, and styling.
|
|
8
|
+
|
|
9
|
+
The `DropdownWidget` class extends the base `TextWidget` class to provide
|
|
10
|
+
specific functionality for interacting with dropdown form fields in PDFs.
|
|
5
11
|
"""
|
|
6
12
|
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import List, Optional, Tuple, Union
|
|
15
|
+
|
|
16
|
+
from .base import Field
|
|
7
17
|
from .text import TextWidget
|
|
8
18
|
|
|
9
19
|
|
|
20
|
+
@dataclass
|
|
21
|
+
class DropdownField(Field):
|
|
22
|
+
"""
|
|
23
|
+
Represents a dropdown field in a PDF document.
|
|
24
|
+
|
|
25
|
+
This dataclass extends the `Field` base class and defines the specific
|
|
26
|
+
attributes that can be configured for a dropdown selection field.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
_field_type (str): The type of the field, fixed as "dropdown".
|
|
30
|
+
options (Optional[List[Union[str, Tuple[str, str]]]]): A list of options
|
|
31
|
+
available in the dropdown. Each option can be a string (display value)
|
|
32
|
+
or a tuple of strings (display value, export value).
|
|
33
|
+
width (Optional[float]): The width of the dropdown field.
|
|
34
|
+
height (Optional[float]): The height of the dropdown field.
|
|
35
|
+
font (Optional[str]): The font to use for the dropdown text.
|
|
36
|
+
font_size (Optional[float]): The font size for the dropdown text.
|
|
37
|
+
font_color (Optional[Tuple[float, ...]]): The color of the font as an RGB or RGBA tuple.
|
|
38
|
+
bg_color (Optional[Tuple[float, ...]]): The background color of the dropdown field.
|
|
39
|
+
border_color (Optional[Tuple[float, ...]]): The color of the dropdown's border.
|
|
40
|
+
border_width (Optional[float]): The width of the dropdown's border.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_field_type: str = "dropdown"
|
|
44
|
+
|
|
45
|
+
options: Optional[List[Union[str, Tuple[str, str]]]] = None
|
|
46
|
+
width: Optional[float] = None
|
|
47
|
+
height: Optional[float] = None
|
|
48
|
+
# pylint: disable=R0801
|
|
49
|
+
font: Optional[str] = None
|
|
50
|
+
font_size: Optional[float] = None
|
|
51
|
+
font_color: Optional[Tuple[float, ...]] = None
|
|
52
|
+
bg_color: Optional[Tuple[float, ...]] = None
|
|
53
|
+
border_color: Optional[Tuple[float, ...]] = None
|
|
54
|
+
border_width: Optional[float] = None
|
|
55
|
+
|
|
56
|
+
|
|
10
57
|
class DropdownWidget(TextWidget):
|
|
11
58
|
"""
|
|
12
59
|
Represents a dropdown widget in a PDF form.
|
PyPDFForm/widgets/image.py
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the ImageWidget
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
This module defines the `ImageField` and `ImageWidget` classes, which are used
|
|
4
|
+
to represent and manipulate image form fields within PDF documents.
|
|
5
|
+
|
|
6
|
+
The `ImageField` class is a dataclass that encapsulates the properties of an
|
|
7
|
+
image field, inheriting from `SignatureField` for its dimensional attributes.
|
|
8
|
+
|
|
9
|
+
The `ImageWidget` class extends the base `SignatureWidget` class to provide
|
|
10
|
+
specific functionality for interacting with image form fields in PDFs,
|
|
11
|
+
leveraging the existing infrastructure for positioning and rendering.
|
|
7
12
|
"""
|
|
8
13
|
|
|
9
|
-
from
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
from .signature import SignatureField, SignatureWidget
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ImageField(SignatureField):
|
|
21
|
+
"""
|
|
22
|
+
Represents an image field in a PDF document.
|
|
23
|
+
|
|
24
|
+
This dataclass extends the `SignatureField` base class and defines the
|
|
25
|
+
specific attributes for an image input field. It inherits `width` and
|
|
26
|
+
`height` from `SignatureField` as images also have dimensions.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
_field_type (str): The type of the field, fixed as "image".
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_field_type: str = "image"
|
|
10
33
|
|
|
11
34
|
|
|
12
35
|
class ImageWidget(SignatureWidget):
|
PyPDFForm/widgets/radio.py
CHANGED
|
@@ -1,16 +1,48 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the RadioWidget
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This module defines the `RadioGroup` and `RadioWidget` classes, which are used
|
|
4
|
+
to represent and manipulate radio button groups within PDF documents.
|
|
5
|
+
|
|
6
|
+
The `RadioGroup` class is a dataclass that encapsulates the properties of a
|
|
7
|
+
radio button group, such as its coordinates and shape.
|
|
8
|
+
|
|
9
|
+
The `RadioWidget` class extends the base `CheckBoxWidget` class to provide
|
|
10
|
+
specific functionality for interacting with radio button form fields in PDFs.
|
|
6
11
|
"""
|
|
12
|
+
|
|
7
13
|
# TODO: In `canvas_operations`, `self.acro_form_params.copy()` creates a shallow copy of the dictionary in each iteration of the loop. For a large number of radio buttons, this repeated copying can be inefficient. Consider modifying the dictionary in place and then reverting changes if necessary, or restructuring the data to avoid repeated copying.
|
|
8
14
|
|
|
9
|
-
from
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import List, Optional
|
|
10
17
|
|
|
11
18
|
from reportlab.pdfgen.canvas import Canvas
|
|
12
19
|
|
|
13
|
-
from .checkbox import CheckBoxWidget
|
|
20
|
+
from .checkbox import CheckBoxField, CheckBoxWidget
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class RadioGroup(CheckBoxField):
|
|
25
|
+
"""
|
|
26
|
+
Represents a group of radio buttons in a PDF document.
|
|
27
|
+
|
|
28
|
+
This dataclass extends the `CheckBoxField` base class and defines the specific
|
|
29
|
+
attributes that can be configured for a radio button group. Unlike a single
|
|
30
|
+
checkbox, a radio group allows for multiple positions (x, y coordinates)
|
|
31
|
+
where individual radio buttons can be placed, but only one can be selected.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
_field_type (str): The type of the field, fixed as "radio".
|
|
35
|
+
x (List[float]): A list of x-coordinates for each radio button in the group.
|
|
36
|
+
y (List[float]): A list of y-coordinates for each radio button in the group.
|
|
37
|
+
shape (Optional[str]): The shape of the radio button. Valid values are
|
|
38
|
+
"circle" or "square". Defaults to None, which typically means a default circle shape.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_field_type: str = "radio"
|
|
42
|
+
|
|
43
|
+
x: List[float]
|
|
44
|
+
y: List[float]
|
|
45
|
+
shape: Optional[str] = None
|
|
14
46
|
|
|
15
47
|
|
|
16
48
|
class RadioWidget(CheckBoxWidget):
|
PyPDFForm/widgets/signature.py
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the SignatureWidget
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
This module defines the `SignatureField` and `SignatureWidget` classes, which are
|
|
4
|
+
used to represent and manipulate signature form fields within PDF documents.
|
|
5
|
+
|
|
6
|
+
The `SignatureField` class is a dataclass that encapsulates the properties of a
|
|
7
|
+
signature field, such as its dimensions.
|
|
8
|
+
|
|
9
|
+
The `SignatureWidget` class provides specific functionality for interacting with
|
|
10
|
+
signature form fields in PDFs, including handling their creation, rendering, and
|
|
11
|
+
integration into the document.
|
|
7
12
|
"""
|
|
13
|
+
|
|
8
14
|
# TODO: In `watermarks`, `PdfReader(stream_to_io(BEDROCK_PDF))` is called every time the method is invoked. If `BEDROCK_PDF` is static, consider parsing it once and caching the `PdfReader` object to avoid redundant I/O and parsing.
|
|
9
15
|
# TODO: In `watermarks`, the list comprehension `[f.read() if i == self.page_number - 1 else b"" for i in range(page_count)]` reads the entire `BytesIO` object `f` multiple times if `page_count` is large. Read `f` once into a variable and then use that variable in the list comprehension.
|
|
10
16
|
# TODO: The `input_pdf` is created in `watermarks` but only its page count is used. If the `PdfReader` object is not needed for other operations, consider a lighter way to get the page count or pass the `PdfReader` object from the caller if it's already available.
|
|
11
17
|
|
|
18
|
+
from dataclasses import dataclass
|
|
12
19
|
from io import BytesIO
|
|
13
|
-
from typing import List
|
|
20
|
+
from typing import List, Optional
|
|
14
21
|
|
|
15
22
|
from pypdf import PdfReader, PdfWriter
|
|
16
23
|
from pypdf.generic import (ArrayObject, FloatObject, NameObject,
|
|
@@ -19,9 +26,30 @@ from pypdf.generic import (ArrayObject, FloatObject, NameObject,
|
|
|
19
26
|
from ..constants import Annots, Rect, T
|
|
20
27
|
from ..template import get_widget_key
|
|
21
28
|
from ..utils import stream_to_io
|
|
29
|
+
from .base import Field
|
|
22
30
|
from .bedrock import BEDROCK_PDF
|
|
23
31
|
|
|
24
32
|
|
|
33
|
+
@dataclass
|
|
34
|
+
class SignatureField(Field):
|
|
35
|
+
"""
|
|
36
|
+
Represents a signature field in a PDF document.
|
|
37
|
+
|
|
38
|
+
This dataclass extends the `Field` base class and defines the specific
|
|
39
|
+
attributes that can be configured for a signature input field.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
_field_type (str): The type of the field, fixed as "signature".
|
|
43
|
+
width (Optional[float]): The width of the signature field.
|
|
44
|
+
height (Optional[float]): The height of the signature field.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
_field_type: str = "signature"
|
|
48
|
+
|
|
49
|
+
width: Optional[float] = None
|
|
50
|
+
height: Optional[float] = None
|
|
51
|
+
|
|
52
|
+
|
|
25
53
|
class SignatureWidget:
|
|
26
54
|
"""
|
|
27
55
|
Represents a signature widget in a PDF form.
|
PyPDFForm/widgets/text.py
CHANGED
|
@@ -1,10 +1,60 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
This module defines the TextWidget
|
|
4
|
-
|
|
3
|
+
This module defines the `TextField` and `TextWidget` classes, which are used to
|
|
4
|
+
represent and manipulate text fields within PDF documents.
|
|
5
|
+
|
|
6
|
+
The `TextField` class is a dataclass that encapsulates the properties of a text
|
|
7
|
+
field, such as its dimensions, styling, and behavior.
|
|
8
|
+
|
|
9
|
+
The `TextWidget` class extends the base `Widget` class to provide specific
|
|
10
|
+
functionality for interacting with text form fields in PDFs.
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
|
-
from
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from .base import Field, Widget
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class TextField(Field):
|
|
21
|
+
"""
|
|
22
|
+
Represents a text field in a PDF document.
|
|
23
|
+
|
|
24
|
+
This dataclass extends the `Field` base class and defines the specific
|
|
25
|
+
attributes that can be configured for a text input field.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
_field_type (str): The type of the field, fixed as "text".
|
|
29
|
+
width (Optional[float]): The width of the text field.
|
|
30
|
+
height (Optional[float]): The height of the text field.
|
|
31
|
+
max_length (Optional[int]): The maximum number of characters allowed in the text field.
|
|
32
|
+
comb (Optional[bool]): If True, the text field will display characters
|
|
33
|
+
individually in a row of boxes.
|
|
34
|
+
font (Optional[str]): The font to use for the text field.
|
|
35
|
+
font_size (Optional[float]): The font size for the text.
|
|
36
|
+
font_color (Optional[Tuple[float, ...]]): The color of the font as an RGB or RGBA tuple.
|
|
37
|
+
bg_color (Optional[Tuple[float, ...]]): The background color of the text field.
|
|
38
|
+
border_color (Optional[Tuple[float, ...]]): The color of the text field's border.
|
|
39
|
+
border_width (Optional[float]): The width of the text field's border.
|
|
40
|
+
alignment (Optional[int]): The text alignment within the field (e.g., 0 for left, 1 for center, 2 for right).
|
|
41
|
+
multiline (Optional[bool]): If True, the text field can display multiple lines of text.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
_field_type: str = "text"
|
|
45
|
+
|
|
46
|
+
width: Optional[float] = None
|
|
47
|
+
height: Optional[float] = None
|
|
48
|
+
max_length: Optional[int] = None
|
|
49
|
+
comb: Optional[bool] = None
|
|
50
|
+
font: Optional[str] = None
|
|
51
|
+
font_size: Optional[float] = None
|
|
52
|
+
font_color: Optional[Tuple[float, ...]] = None
|
|
53
|
+
bg_color: Optional[Tuple[float, ...]] = None
|
|
54
|
+
border_color: Optional[Tuple[float, ...]] = None
|
|
55
|
+
border_width: Optional[float] = None
|
|
56
|
+
alignment: Optional[int] = None
|
|
57
|
+
multiline: Optional[bool] = None
|
|
8
58
|
|
|
9
59
|
|
|
10
60
|
class TextWidget(Widget):
|
PyPDFForm/wrapper.py
CHANGED
|
@@ -29,12 +29,15 @@ underlying PDF manipulation.
|
|
|
29
29
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
|
+
from dataclasses import asdict
|
|
32
33
|
from functools import cached_property
|
|
33
|
-
from typing import BinaryIO, Dict, List, Sequence, Tuple, Union
|
|
34
|
+
from typing import TYPE_CHECKING, BinaryIO, Dict, List, Sequence, Tuple, Union
|
|
35
|
+
from warnings import warn
|
|
34
36
|
|
|
35
37
|
from .adapter import fp_or_f_obj_or_stream_to_stream
|
|
36
38
|
from .constants import (DEFAULT_FONT, DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
|
|
37
|
-
VERSION_IDENTIFIER_PREFIX,
|
|
39
|
+
DEPRECATION_NOTICE, VERSION_IDENTIFIER_PREFIX,
|
|
40
|
+
VERSION_IDENTIFIERS)
|
|
38
41
|
from .coordinate import generate_coordinate_grid
|
|
39
42
|
from .filler import fill
|
|
40
43
|
from .font import (get_all_available_fonts, register_font,
|
|
@@ -56,6 +59,9 @@ from .widgets.radio import RadioWidget
|
|
|
56
59
|
from .widgets.signature import SignatureWidget
|
|
57
60
|
from .widgets.text import TextWidget
|
|
58
61
|
|
|
62
|
+
if TYPE_CHECKING:
|
|
63
|
+
from .widgets import FieldTypes
|
|
64
|
+
|
|
59
65
|
|
|
60
66
|
class PdfWrapper:
|
|
61
67
|
"""
|
|
@@ -456,6 +462,42 @@ class PdfWrapper:
|
|
|
456
462
|
|
|
457
463
|
return self
|
|
458
464
|
|
|
465
|
+
def create_field(
|
|
466
|
+
self,
|
|
467
|
+
field: FieldTypes,
|
|
468
|
+
) -> PdfWrapper:
|
|
469
|
+
"""
|
|
470
|
+
Creates a new form field (widget) on the PDF using a `FieldTypes` object.
|
|
471
|
+
|
|
472
|
+
This method simplifies widget creation by taking a `FieldTypes` object,
|
|
473
|
+
extracting its properties, and then delegating to the `create_widget` method.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
field (FieldTypes): An object representing the field to create.
|
|
477
|
+
This object encapsulates all necessary properties like name,
|
|
478
|
+
page number, coordinates, and type of the field.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
field_dict = asdict(field)
|
|
485
|
+
widget_type = field_dict.pop("_field_type")
|
|
486
|
+
name = field_dict.pop("name")
|
|
487
|
+
page_number = field_dict.pop("page_number")
|
|
488
|
+
x = field_dict.pop("x")
|
|
489
|
+
y = field_dict.pop("y")
|
|
490
|
+
|
|
491
|
+
field_dict["suppress_deprecation_notice"] = True
|
|
492
|
+
return self.create_widget(
|
|
493
|
+
widget_type,
|
|
494
|
+
name,
|
|
495
|
+
page_number,
|
|
496
|
+
x,
|
|
497
|
+
y,
|
|
498
|
+
**{k: v for k, v in field_dict.items() if v is not None},
|
|
499
|
+
)
|
|
500
|
+
|
|
459
501
|
def create_widget(
|
|
460
502
|
self,
|
|
461
503
|
widget_type: str,
|
|
@@ -488,6 +530,16 @@ class PdfWrapper:
|
|
|
488
530
|
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
489
531
|
"""
|
|
490
532
|
|
|
533
|
+
if not kwargs.get("suppress_deprecation_notice"):
|
|
534
|
+
warn(
|
|
535
|
+
DEPRECATION_NOTICE.format(
|
|
536
|
+
f"{self.__class__.__name__}.create_widget()",
|
|
537
|
+
f"{self.__class__.__name__}.create_field()",
|
|
538
|
+
),
|
|
539
|
+
DeprecationWarning, # noqa: PT030
|
|
540
|
+
stacklevel=2,
|
|
541
|
+
)
|
|
542
|
+
|
|
491
543
|
_class = None
|
|
492
544
|
if widget_type == "text":
|
|
493
545
|
_class = TextWidget
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,6 +21,7 @@ Requires-Python: >=3.9
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: cryptography
|
|
24
|
+
Requires-Dist: fonttools
|
|
24
25
|
Requires-Dist: pillow
|
|
25
26
|
Requires-Dist: pypdf
|
|
26
27
|
Requires-Dist: reportlab
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
PyPDFForm/__init__.py,sha256=
|
|
1
|
+
PyPDFForm/__init__.py,sha256=TBcOLWb7xcC0r6Qo7teHtwcQA97qsTX5gUay7Vfq3LY,963
|
|
2
2
|
PyPDFForm/adapter.py,sha256=LBxHth0qJFB6rdByRJbsn4x0dftCOAolKVutZeFZm9E,2634
|
|
3
|
-
PyPDFForm/constants.py,sha256=
|
|
3
|
+
PyPDFForm/constants.py,sha256=tFtomm4bSsghxRqKaMP6Tln7ug9W_72e-AAU3sEU_no,2917
|
|
4
4
|
PyPDFForm/coordinate.py,sha256=veYOlRyFKIvzLISYA_f-drNBiKOzFwr0EIFCaUAzGgo,4428
|
|
5
5
|
PyPDFForm/filler.py,sha256=fqGIxT3FR3cWo3SMTDYud6Ocs9SZBmSpFv5yg1v19Wk,8450
|
|
6
|
-
PyPDFForm/font.py,sha256=
|
|
6
|
+
PyPDFForm/font.py,sha256=TkiGPHIf4NxDiDzkrlXIr1r-DZC1V4aYrQAzGFxBlLI,13423
|
|
7
7
|
PyPDFForm/hooks.py,sha256=tW4VGAFZupSMhtHWoGsoljOciGT2yBBuoqNCuyGsKzM,15697
|
|
8
8
|
PyPDFForm/image.py,sha256=P1P3Ejm8PVPQwpJFGAesjtwS5hxnVItrj75TE3WnFhM,4607
|
|
9
9
|
PyPDFForm/patterns.py,sha256=HbTqzFllQ1cW3CqyNEfVh0qUMeFerbvOd0-HQnkifQQ,9765
|
|
10
10
|
PyPDFForm/template.py,sha256=mmy4kVMe1plHxKtxFLbajBT1Mrv9g_oNCyzTUCWEyVw,11101
|
|
11
11
|
PyPDFForm/utils.py,sha256=JavhAO4HmYRdujlsPXcZWGXTf7wDXzj4uU1XGRFsAaA,13257
|
|
12
12
|
PyPDFForm/watermark.py,sha256=BJ8NeZLKf-MuJ2XusHiALaQpoqE8j6hHGbWcNhpjxN0,11299
|
|
13
|
-
PyPDFForm/wrapper.py,sha256=
|
|
13
|
+
PyPDFForm/wrapper.py,sha256=_KKE2jUBcXxnkMzL8T43JjMmf8fvuJ-TTmYEy_h7h-c,30603
|
|
14
14
|
PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
PyPDFForm/middleware/base.py,sha256=ZmJFh3nSxj6PFjqBqsLih0pXKtcm1o-ctJVWn0v6bbI,3278
|
|
16
16
|
PyPDFForm/middleware/checkbox.py,sha256=OCSZEFD8wQG_Y9qO7Os6VXTaxJCpkRYTxI4wDgG0GZc,1870
|
|
@@ -19,17 +19,17 @@ PyPDFForm/middleware/image.py,sha256=eKM7anU56jbaECnK6rq0jGsBRY3HW_fM86fgA3hq7xA
|
|
|
19
19
|
PyPDFForm/middleware/radio.py,sha256=PuGDJ8RN1C-MkL9Jf14ABWYV67cN18R66dI4nR-03DU,2211
|
|
20
20
|
PyPDFForm/middleware/signature.py,sha256=P6Mg9AZP5jML7GawsteVZjDaunKb9Yazu5iy0qF60bo,2432
|
|
21
21
|
PyPDFForm/middleware/text.py,sha256=WLK6Ae9zT3uUu1AzcWUhR-hs5rm0z9Ydz-fL8Qu-44o,3997
|
|
22
|
-
PyPDFForm/widgets/__init__.py,sha256=
|
|
23
|
-
PyPDFForm/widgets/base.py,sha256=
|
|
22
|
+
PyPDFForm/widgets/__init__.py,sha256=TvMMWGtrTC4xRPRokr4Pl8ZtvfYXRPUDgfHd3pnbPE8,1304
|
|
23
|
+
PyPDFForm/widgets/base.py,sha256=Y8cf8mR7VMdH2L3pmI43NWxm7M9FLHgHMBAf6wb549c,8486
|
|
24
24
|
PyPDFForm/widgets/bedrock.py,sha256=j6beU04kaQzpAIFZHI5VJLaDT5RVAAa6LzkU1luJpN8,137660
|
|
25
|
-
PyPDFForm/widgets/checkbox.py,sha256=
|
|
26
|
-
PyPDFForm/widgets/dropdown.py,sha256=
|
|
27
|
-
PyPDFForm/widgets/image.py,sha256=
|
|
28
|
-
PyPDFForm/widgets/radio.py,sha256=
|
|
29
|
-
PyPDFForm/widgets/signature.py,sha256=
|
|
30
|
-
PyPDFForm/widgets/text.py,sha256=
|
|
31
|
-
pypdfform-3.
|
|
32
|
-
pypdfform-3.
|
|
33
|
-
pypdfform-3.
|
|
34
|
-
pypdfform-3.
|
|
35
|
-
pypdfform-3.
|
|
25
|
+
PyPDFForm/widgets/checkbox.py,sha256=IwQvw2W6HuSLyZFv-qGvdvCHp8uAp2KjOAt7-Y73Y74,2870
|
|
26
|
+
PyPDFForm/widgets/dropdown.py,sha256=7yPxx-uWyZe8wxxVqYVXvomPHp_5CAygAEMldibe6eg,3528
|
|
27
|
+
PyPDFForm/widgets/image.py,sha256=SegpzNjTXPuWX4hk_20yKk_IDaOrc81X9b9B4qK-kME,1628
|
|
28
|
+
PyPDFForm/widgets/radio.py,sha256=pSOAgOakGAoi-190pFklMmLClFdPCWMDYWW1Ce6q__g,3933
|
|
29
|
+
PyPDFForm/widgets/signature.py,sha256=yye_CLYpgurLJ_rt7Td3xaY28uSAHc1YlqL1gmWqPqw,6034
|
|
30
|
+
PyPDFForm/widgets/text.py,sha256=EG2mR-POHGWt9I-t3QVsTFIiDB5yqKPDBTKkAytQGp8,3836
|
|
31
|
+
pypdfform-3.5.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
|
|
32
|
+
pypdfform-3.5.0.dist-info/METADATA,sha256=ojZtGj142xTZFvkwQN1iI7QFwjQFpoHxxYa-cctXQlA,4267
|
|
33
|
+
pypdfform-3.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
pypdfform-3.5.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
|
|
35
|
+
pypdfform-3.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|