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 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.4.0"
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, NameObject,
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 (DR, FONT_NAME_PREFIX, AcroForm, BaseFont, Encoding,
26
- Fields, Filter, FlateDecode, Font, FontDescriptor,
27
- FontFile2, FontName, Length, Length1, Resources,
28
- Subtype, TrueType, Type, WinAnsiEncoding)
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 reader.pages[0][Resources][Font].values():
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
@@ -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 class for all widgets in PyPDFForm.
3
+ This module defines the base classes for all form fields and widgets in PyPDFForm.
4
4
 
5
- It provides a common interface for interacting with different types of form fields,
6
- such as text fields, checkboxes, and radio buttons. The Widget class handles
7
- basic properties like name, page number, and coordinates, and provides methods
8
- for rendering the widget on a PDF page.
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.
@@ -1,10 +1,48 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- This module defines the CheckBoxWidget class, which is a subclass of the
4
- Widget class. It represents a checkbox form field in a PDF document.
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 .base import Widget
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):
@@ -1,12 +1,59 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- This module defines the DropdownWidget class, which is a subclass of the
4
- TextWidget class. It represents a dropdown form field in a PDF document.
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.
@@ -1,12 +1,35 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- This module defines the ImageWidget class, which is a subclass of the
4
- SignatureWidget class. It represents an image field in a PDF document.
5
- The ImageWidget leverages the SignatureWidget's functionality to handle
6
- image insertion into PDF forms.
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 .signature import SignatureWidget
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):
@@ -1,16 +1,48 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- This module defines the RadioWidget class, which is a subclass of the
4
- CheckBoxWidget class. It represents a radio button form field in a PDF
5
- document.
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 typing import List
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):
@@ -1,16 +1,23 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- This module defines the SignatureWidget class, which is responsible for
4
- representing signature fields in a PDF form. It handles the creation and
5
- rendering of signature widgets, as well as the integration of signatures
6
- into the PDF document.
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 class, which is a subclass of the
4
- Widget class. It represents a text field in a PDF document.
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 .base import Widget
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, VERSION_IDENTIFIERS)
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.4.0
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=SV7z1wBkJS8IDuoQvsqd9oYq5u8DtiRxg-zA86EWz2Y,925
1
+ PyPDFForm/__init__.py,sha256=TBcOLWb7xcC0r6Qo7teHtwcQA97qsTX5gUay7Vfq3LY,963
2
2
  PyPDFForm/adapter.py,sha256=LBxHth0qJFB6rdByRJbsn4x0dftCOAolKVutZeFZm9E,2634
3
- PyPDFForm/constants.py,sha256=rxE9KyygdMkuPfpO6PRV6DasvIta0sZqadVPtX9Q_1U,2616
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=opZjAacsIRFcERXWegPXkOSpmnRrv4y_50yD0_BjWPM,10273
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=KTFou6cXrHtLHVKwngoIr4Pwu4vOfjXY0cWRNNDlW0U,28866
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=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- PyPDFForm/widgets/base.py,sha256=iWsZ4LDtFVmrVUwvG_ORnH8fd614saI6wfNN4LrgOjQ,7230
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=gqqulFdjrLd6Ry-BM4sHwGu0QogfKnG_6v3RcYLeYMo,1347
26
- PyPDFForm/widgets/dropdown.py,sha256=6zZwt6eU9Hgwl-57QfyT3G6c37FkQTJ-XSsXGluWevs,1459
27
- PyPDFForm/widgets/image.py,sha256=aSD-3MEZFIRL7HYVuO6Os8irfSUOLHA_rHGkqcEIPPA,855
28
- PyPDFForm/widgets/radio.py,sha256=oFw8Um4g414UH93QJv6dZHRxpq0yuYog09B2W3eE8wo,2612
29
- PyPDFForm/widgets/signature.py,sha256=L4Et6pxtrEh7U-lnnLZrnvb_dKwGNpI6TZ11HCD0lvY,5147
30
- PyPDFForm/widgets/text.py,sha256=rmI0EYUTVf06AKsnxsVqewdTjt_mhBASEHyj5nAvYjE,1606
31
- pypdfform-3.4.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
32
- pypdfform-3.4.0.dist-info/METADATA,sha256=uOBdsdX7B1B9QU8KoIIem38mkRDS38t31g22gB8IQzY,4242
33
- pypdfform-3.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- pypdfform-3.4.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
35
- pypdfform-3.4.0.dist-info/RECORD,,
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,,