PyPDFForm 2.5.0__py3-none-any.whl → 3.0.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 +22 -6
- PyPDFForm/adapter.py +28 -26
- PyPDFForm/constants.py +29 -34
- PyPDFForm/coordinate.py +23 -399
- PyPDFForm/filler.py +79 -303
- PyPDFForm/font.py +166 -164
- PyPDFForm/hooks.py +109 -69
- PyPDFForm/image.py +72 -22
- PyPDFForm/middleware/base.py +42 -60
- PyPDFForm/middleware/checkbox.py +27 -58
- PyPDFForm/middleware/dropdown.py +41 -30
- PyPDFForm/middleware/image.py +10 -22
- PyPDFForm/middleware/radio.py +30 -31
- PyPDFForm/middleware/signature.py +32 -47
- PyPDFForm/middleware/text.py +54 -48
- PyPDFForm/patterns.py +61 -106
- PyPDFForm/template.py +80 -427
- PyPDFForm/utils.py +142 -128
- PyPDFForm/watermark.py +77 -208
- PyPDFForm/widgets/base.py +57 -76
- PyPDFForm/widgets/checkbox.py +18 -21
- PyPDFForm/widgets/dropdown.py +18 -25
- PyPDFForm/widgets/image.py +11 -9
- PyPDFForm/widgets/radio.py +25 -35
- PyPDFForm/widgets/signature.py +29 -40
- PyPDFForm/widgets/text.py +18 -17
- PyPDFForm/wrapper.py +351 -443
- {pypdfform-2.5.0.dist-info → pypdfform-3.0.0.dist-info}/METADATA +6 -7
- pypdfform-3.0.0.dist-info/RECORD +35 -0
- {pypdfform-2.5.0.dist-info → pypdfform-3.0.0.dist-info}/WHEEL +1 -1
- pypdfform-2.5.0.dist-info/RECORD +0 -35
- {pypdfform-2.5.0.dist-info → pypdfform-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-2.5.0.dist-info → pypdfform-3.0.0.dist-info}/top_level.txt +0 -0
PyPDFForm/__init__.py
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
PyPDFForm is a pure Python library designed to streamline the process of filling PDF forms programmatically.
|
|
4
|
+
|
|
5
|
+
It provides a simple and intuitive API for interacting with PDF forms, allowing users to:
|
|
6
|
+
|
|
7
|
+
- Fill text fields with custom data.
|
|
8
|
+
- Check or uncheck checkboxes.
|
|
9
|
+
- Select radio button options.
|
|
10
|
+
- Add images to image fields.
|
|
11
|
+
- Flatten the filled form to prevent further modifications.
|
|
12
|
+
|
|
13
|
+
The library supports various PDF form features, including:
|
|
14
|
+
|
|
15
|
+
- Text field alignment (left, center, right).
|
|
16
|
+
- Font customization (size, color, family).
|
|
17
|
+
- Image field resizing and positioning.
|
|
18
|
+
- Handling of complex form structures.
|
|
3
19
|
|
|
4
|
-
|
|
5
|
-
and manipulating PDF form elements programmatically.
|
|
20
|
+
PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
|
|
6
21
|
"""
|
|
7
22
|
|
|
8
|
-
__version__ = "
|
|
23
|
+
__version__ = "3.0.0"
|
|
9
24
|
|
|
10
|
-
from .
|
|
25
|
+
from .middleware.text import Text # exposing for setting global font attrs
|
|
26
|
+
from .wrapper import PdfWrapper
|
|
11
27
|
|
|
12
|
-
__all__ = ["
|
|
28
|
+
__all__ = ["PdfWrapper", "Text"]
|
PyPDFForm/adapter.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
The
|
|
10
|
-
|
|
2
|
+
"""
|
|
3
|
+
Module for adapting different types of input to a consistent byte stream.
|
|
4
|
+
|
|
5
|
+
This module provides utility functions to adapt various types of input,
|
|
6
|
+
such as file paths, file-like objects, and byte streams, into a consistent
|
|
7
|
+
byte stream format. This is particularly useful when dealing with PDF form
|
|
8
|
+
filling operations, where the input PDF template can be provided in different
|
|
9
|
+
forms. The module ensures that the input is properly converted into a byte
|
|
10
|
+
stream before further processing.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from os.path import isfile
|
|
@@ -15,41 +15,43 @@ from typing import Any, BinaryIO, Union
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def readable(obj: Any) -> bool:
|
|
18
|
-
"""
|
|
18
|
+
"""
|
|
19
|
+
Check if an object has a readable "read" attribute.
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
This function determines whether the provided object has a "read" attribute that is callable.
|
|
22
|
+
It is used to identify file-like objects or streams that can be read from.
|
|
22
23
|
|
|
23
24
|
Args:
|
|
24
|
-
obj: The object to check for read
|
|
25
|
+
obj (Any): The object to check for a readable "read" attribute.
|
|
25
26
|
|
|
26
27
|
Returns:
|
|
27
|
-
bool: True if the object has a callable read
|
|
28
|
+
bool: True if the object has a callable "read" attribute, indicating it is readable.
|
|
29
|
+
Returns False otherwise.
|
|
28
30
|
"""
|
|
29
|
-
|
|
30
31
|
return callable(getattr(obj, "read", None))
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def fp_or_f_obj_or_stream_to_stream(
|
|
34
35
|
fp_or_f_obj_or_stream: Union[bytes, str, BinaryIO],
|
|
35
36
|
) -> bytes:
|
|
36
|
-
"""
|
|
37
|
+
"""
|
|
38
|
+
Adapt a file path, file object, or stream to a byte stream.
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
This function takes a file path, a file object, or a byte stream and adapts it to a consistent byte stream.
|
|
41
|
+
It handles different input types, including:
|
|
42
|
+
- byte streams (bytes)
|
|
43
|
+
- file paths (str)
|
|
44
|
+
- file-like objects with a read() method (BinaryIO)
|
|
42
45
|
|
|
43
46
|
Args:
|
|
44
|
-
fp_or_f_obj_or_stream
|
|
45
|
-
|
|
46
|
-
- str: Path to PDF file
|
|
47
|
-
- BinaryIO: File-like object containing PDF data
|
|
47
|
+
fp_or_f_obj_or_stream (Union[bytes, str, BinaryIO]): The input to adapt.
|
|
48
|
+
It can be a byte stream, a file path (string), or a file object.
|
|
48
49
|
|
|
49
50
|
Returns:
|
|
50
|
-
bytes: The
|
|
51
|
+
bytes: The byte stream representation of the input.
|
|
52
|
+
Returns an empty byte string if the file path does not exist.
|
|
51
53
|
"""
|
|
52
|
-
|
|
54
|
+
# not cached to handle writing to the same disk file
|
|
53
55
|
result = b""
|
|
54
56
|
if isinstance(fp_or_f_obj_or_stream, bytes):
|
|
55
57
|
result = fp_or_f_obj_or_stream
|
PyPDFForm/constants.py
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
Module containing constants used throughout the PyPDFForm library.
|
|
4
|
+
|
|
5
|
+
This module defines a collection of constants that are used across various
|
|
6
|
+
modules within the PyPDFForm library. These constants include:
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- Field flag bitmasks
|
|
10
|
-
- Button style definitions
|
|
8
|
+
- String identifiers for PDF syntax elements (e.g., /Annots, /Rect, /FT).
|
|
9
|
+
- Numerical values representing field flags (e.g., READ_ONLY, MULTILINE).
|
|
10
|
+
- Default values for fonts, font sizes, and font colors.
|
|
11
|
+
- Identifiers for image fields and coordinate grid calculations.
|
|
12
|
+
- Version identifiers for PDF versions.
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
Using constants improves code readability and maintainability by providing
|
|
15
|
+
meaningful names for frequently used values and reducing the risk of typos.
|
|
13
16
|
"""
|
|
14
17
|
|
|
15
18
|
from typing import Union
|
|
@@ -54,25 +57,33 @@ I = "/I" # noqa: E741
|
|
|
54
57
|
N = "/N"
|
|
55
58
|
Sig = "/Sig"
|
|
56
59
|
DA = "/DA"
|
|
60
|
+
DR = "/DR"
|
|
57
61
|
DV = "/DV"
|
|
58
62
|
Btn = "/Btn"
|
|
59
63
|
MaxLen = "/MaxLen"
|
|
60
64
|
Q = "/Q"
|
|
61
65
|
Ch = "/Ch"
|
|
62
66
|
Opt = "/Opt"
|
|
63
|
-
MK = "/MK"
|
|
64
|
-
CA = "/CA"
|
|
65
|
-
BC = "/BC"
|
|
66
|
-
BG = "/BG"
|
|
67
|
-
BS = "/BS"
|
|
68
|
-
W = "/W"
|
|
69
|
-
S = "/S"
|
|
70
|
-
D = "/D"
|
|
71
|
-
U = "/U"
|
|
72
67
|
AS = "/AS"
|
|
73
68
|
Yes = "/Yes"
|
|
74
69
|
Off = "/Off"
|
|
75
70
|
|
|
71
|
+
# Font dict
|
|
72
|
+
Length1 = "/Length1"
|
|
73
|
+
Type = "/Type"
|
|
74
|
+
FontDescriptor = "/FontDescriptor"
|
|
75
|
+
FontName = "/FontName"
|
|
76
|
+
FontFile2 = "/FontFile2"
|
|
77
|
+
Font = "/Font"
|
|
78
|
+
Subtype = "/Subtype"
|
|
79
|
+
TrueType = "/TrueType"
|
|
80
|
+
BaseFont = "/BaseFont"
|
|
81
|
+
Encoding = "/Encoding"
|
|
82
|
+
WinAnsiEncoding = "/WinAnsiEncoding"
|
|
83
|
+
|
|
84
|
+
Resources = "/Resources"
|
|
85
|
+
FONT_NAME_PREFIX = "/F"
|
|
86
|
+
|
|
76
87
|
# For Adobe Acrobat
|
|
77
88
|
AcroForm = "/AcroForm"
|
|
78
89
|
Root = "/Root"
|
|
@@ -86,27 +97,11 @@ COMB = 1 << 24
|
|
|
86
97
|
|
|
87
98
|
FONT_SIZE_IDENTIFIER = "Tf"
|
|
88
99
|
FONT_COLOR_IDENTIFIER = " rg"
|
|
89
|
-
DEFAULT_BORDER_WIDTH = 1
|
|
90
100
|
DEFAULT_FONT = "Helvetica"
|
|
91
101
|
DEFAULT_FONT_SIZE = 12
|
|
92
102
|
DEFAULT_FONT_COLOR = (0, 0, 0)
|
|
93
|
-
PREVIEW_FONT_COLOR = (1, 0, 0)
|
|
94
|
-
|
|
95
|
-
NEW_LINE_SYMBOL = "\n"
|
|
96
103
|
|
|
97
104
|
IMAGE_FIELD_IDENTIFIER = "event.target.buttonImportIcon();"
|
|
98
105
|
|
|
99
|
-
DEFAULT_CHECKBOX_STYLE = "\u2713"
|
|
100
|
-
DEFAULT_RADIO_STYLE = "\u25cf"
|
|
101
|
-
BUTTON_STYLES = {
|
|
102
|
-
"4": "\u2713", # check
|
|
103
|
-
"5": "\u00d7", # cross
|
|
104
|
-
"l": "\u25cf", # circle
|
|
105
|
-
}
|
|
106
|
-
|
|
107
106
|
COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO = DEFAULT_FONT_SIZE / 100
|
|
108
107
|
UNIQUE_SUFFIX_LENGTH = 20
|
|
109
|
-
|
|
110
|
-
# Used for adjusting paragraph font size
|
|
111
|
-
FONT_SIZE_REDUCE_STEP = 0.5
|
|
112
|
-
MARGIN_BETWEEN_LINES = 2
|
PyPDFForm/coordinate.py
CHANGED
|
@@ -1,412 +1,44 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
This module contains functions for calculating positions and dimensions
|
|
5
|
-
for drawing various PDF form elements including:
|
|
6
|
-
- Text fields and paragraphs
|
|
7
|
-
- Checkboxes and radio buttons
|
|
8
|
-
- Images and signatures
|
|
9
|
-
- Borders and decorative elements
|
|
2
|
+
"""
|
|
3
|
+
Module for generating coordinate grids on PDFs.
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
This module provides functionality to generate coordinate grids on existing PDF documents.
|
|
6
|
+
It allows developers to visualize the coordinate system of each page in a PDF, which can be helpful
|
|
7
|
+
for debugging and precisely positioning elements when filling or drawing on PDF forms.
|
|
14
8
|
"""
|
|
15
9
|
|
|
16
|
-
from
|
|
17
|
-
from typing import List, Tuple, Union
|
|
10
|
+
from typing import Tuple
|
|
18
11
|
|
|
19
12
|
from pypdf import PdfReader
|
|
20
13
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
21
14
|
|
|
22
|
-
from .constants import
|
|
23
|
-
Rect)
|
|
15
|
+
from .constants import COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO, DEFAULT_FONT
|
|
24
16
|
from .middleware.text import Text
|
|
25
|
-
from .
|
|
26
|
-
from .template import get_char_rect_width, is_text_multiline
|
|
27
|
-
from .utils import extract_widget_property, handle_color, stream_to_io
|
|
17
|
+
from .utils import stream_to_io
|
|
28
18
|
from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
|
|
29
19
|
|
|
30
20
|
|
|
31
|
-
def get_draw_border_coordinates(widget: dict, shape: str) -> dict:
|
|
32
|
-
"""Calculates coordinates for drawing widget borders in PDF coordinate space.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
widget: PDF form widget dictionary containing Rect coordinates (in PDF points)
|
|
36
|
-
shape: Type of border to draw:
|
|
37
|
-
- "rect": Standard rectangular border
|
|
38
|
-
- "ellipse": Circular/oval border
|
|
39
|
-
- "line": Straight line border
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
dict: Coordinate dictionary with different keys depending on shape:
|
|
43
|
-
- For "rect":
|
|
44
|
-
{
|
|
45
|
-
"x": bottom-left x,
|
|
46
|
-
"y": bottom-left y,
|
|
47
|
-
"width": total width,
|
|
48
|
-
"height": total height
|
|
49
|
-
}
|
|
50
|
-
- For "ellipse":
|
|
51
|
-
{
|
|
52
|
-
"x1": left bound,
|
|
53
|
-
"y1": bottom bound,
|
|
54
|
-
"x2": right bound,
|
|
55
|
-
"y2": top bound
|
|
56
|
-
}
|
|
57
|
-
- For "line":
|
|
58
|
-
{
|
|
59
|
-
"src_x": start x,
|
|
60
|
-
"src_y": start y,
|
|
61
|
-
"dest_x": end x,
|
|
62
|
-
"dest_y": end y
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
Note:
|
|
66
|
-
All coordinates are in PDF points (1/72 inch) with origin (0,0) at bottom-left.
|
|
67
|
-
For ellipses, the bounds form a square that would contain the ellipse.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
result = {
|
|
71
|
-
"x": float(widget[Rect][0]),
|
|
72
|
-
"y": float(widget[Rect][1]),
|
|
73
|
-
"width": abs(float(widget[Rect][0]) - float(widget[Rect][2])),
|
|
74
|
-
"height": abs(float(widget[Rect][1]) - float(widget[Rect][3])),
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if shape == "ellipse":
|
|
78
|
-
width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
79
|
-
height = abs(float(widget[Rect][1]) - float(widget[Rect][3]))
|
|
80
|
-
|
|
81
|
-
width_mid = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
|
|
82
|
-
height_mid = (float(widget[Rect][1]) + float(widget[Rect][3])) / 2
|
|
83
|
-
|
|
84
|
-
less = min(width, height)
|
|
85
|
-
|
|
86
|
-
result = {
|
|
87
|
-
"x1": width_mid - less / 2,
|
|
88
|
-
"y1": height_mid - less / 2,
|
|
89
|
-
"x2": width_mid + less / 2,
|
|
90
|
-
"y2": height_mid + less / 2,
|
|
91
|
-
}
|
|
92
|
-
elif shape == "line":
|
|
93
|
-
result = {
|
|
94
|
-
"src_x": float(widget[Rect][0]),
|
|
95
|
-
"src_y": float(widget[Rect][1]),
|
|
96
|
-
"dest_x": float(widget[Rect][2]),
|
|
97
|
-
"dest_y": float(widget[Rect][1]),
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return result
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def get_draw_checkbox_radio_coordinates(
|
|
104
|
-
widget: dict,
|
|
105
|
-
widget_middleware: Text,
|
|
106
|
-
border_width: int,
|
|
107
|
-
) -> Tuple[Union[float, int], Union[float, int]]:
|
|
108
|
-
"""Calculates drawing coordinates for checkbox/radio button symbols.
|
|
109
|
-
|
|
110
|
-
Args:
|
|
111
|
-
widget: PDF form widget dictionary containing Rect coordinates
|
|
112
|
-
widget_middleware: Text middleware containing font properties
|
|
113
|
-
border_width: Width of widget border in points
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
Tuple[Union[float, int], Union[float, int]]: (x, y) coordinates
|
|
117
|
-
for drawing the checkbox/radio symbol
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
string_height = widget_middleware.font_size * 72 / 96
|
|
121
|
-
width_mid_point = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
|
|
122
|
-
half_widget_height = abs(float(widget[Rect][1]) - float(widget[Rect][3])) / 2
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
width_mid_point
|
|
126
|
-
- stringWidth(
|
|
127
|
-
widget_middleware.value,
|
|
128
|
-
widget_middleware.font,
|
|
129
|
-
widget_middleware.font_size,
|
|
130
|
-
)
|
|
131
|
-
/ 2,
|
|
132
|
-
float(widget[Rect][1])
|
|
133
|
-
+ (half_widget_height - string_height / 2)
|
|
134
|
-
+ border_width / 2,
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def get_draw_image_coordinates_resolutions(
|
|
139
|
-
widget: dict,
|
|
140
|
-
preserve_aspect_ratio: bool,
|
|
141
|
-
image_width: float,
|
|
142
|
-
image_height: float,
|
|
143
|
-
) -> Tuple[float, float, float, float]:
|
|
144
|
-
"""Calculates image drawing coordinates and scaling factors.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
widget: PDF form widget dictionary containing Rect coordinates
|
|
148
|
-
preserve_aspect_ratio: Whether to maintain image proportions
|
|
149
|
-
image_width: Original width of the image in points
|
|
150
|
-
image_height: Original height of the image in points
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
Tuple[float, float, float, float]: (x, y, width, height) where:
|
|
154
|
-
x,y: Bottom-left corner coordinates
|
|
155
|
-
width,height: Scaled dimensions for drawing
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
x = float(widget[Rect][0])
|
|
159
|
-
y = float(widget[Rect][1])
|
|
160
|
-
width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
161
|
-
height = abs(float(widget[Rect][1]) - float(widget[Rect][3]))
|
|
162
|
-
|
|
163
|
-
if preserve_aspect_ratio:
|
|
164
|
-
ratio = max(image_width / width, image_height / height)
|
|
165
|
-
|
|
166
|
-
new_width = image_width / ratio
|
|
167
|
-
new_height = image_height / ratio
|
|
168
|
-
|
|
169
|
-
x += abs(new_width - width) / 2
|
|
170
|
-
y += abs(new_height - height) / 2
|
|
171
|
-
|
|
172
|
-
width = new_width
|
|
173
|
-
height = new_height
|
|
174
|
-
|
|
175
|
-
return x, y, width, height
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def calculate_text_coord_x(
|
|
179
|
-
widget: dict,
|
|
180
|
-
widget_middleware: Text,
|
|
181
|
-
text_value: str,
|
|
182
|
-
length: int,
|
|
183
|
-
character_paddings: List[float],
|
|
184
|
-
) -> float:
|
|
185
|
-
"""
|
|
186
|
-
Calculate the horizontal (x) coordinate for text drawing within a PDF form field.
|
|
187
|
-
|
|
188
|
-
This function determines the x-coordinate based on:
|
|
189
|
-
- The widget's alignment setting (left, center, right)
|
|
190
|
-
- Whether the field uses comb formatting
|
|
191
|
-
- The width of the text string
|
|
192
|
-
- Character paddings for comb fields
|
|
193
|
-
|
|
194
|
-
Args:
|
|
195
|
-
widget: PDF form widget dictionary containing Rect and alignment info.
|
|
196
|
-
widget_middleware: Text middleware containing font and comb properties.
|
|
197
|
-
text_value: The text string to be drawn (already trimmed).
|
|
198
|
-
length: The length of the text string.
|
|
199
|
-
character_paddings: List of cumulative paddings for comb characters.
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
float: The calculated x-coordinate for the text baseline start.
|
|
203
|
-
"""
|
|
204
|
-
|
|
205
|
-
alignment = (
|
|
206
|
-
extract_widget_property(widget, WIDGET_ALIGNMENT_PATTERNS, None, int) or 0
|
|
207
|
-
)
|
|
208
|
-
# Default to left boundary
|
|
209
|
-
x = float(widget[Rect][0])
|
|
210
|
-
|
|
211
|
-
if int(alignment) == 0:
|
|
212
|
-
return x
|
|
213
|
-
|
|
214
|
-
# Calculate the horizontal midpoint of the widget rectangle
|
|
215
|
-
width_mid_point = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
|
|
216
|
-
|
|
217
|
-
# Calculate the width of the entire string in points
|
|
218
|
-
string_width = stringWidth(
|
|
219
|
-
text_value,
|
|
220
|
-
widget_middleware.font,
|
|
221
|
-
widget_middleware.font_size,
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
# If comb formatting, adjust string width to include last character's right padding
|
|
225
|
-
if widget_middleware.comb is True and length:
|
|
226
|
-
string_width = character_paddings[-1] + stringWidth(
|
|
227
|
-
text_value[-1],
|
|
228
|
-
widget_middleware.font,
|
|
229
|
-
widget_middleware.font_size,
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
if int(alignment) == 1: # Center alignment
|
|
233
|
-
# Center text by offsetting half the string width from the midpoint
|
|
234
|
-
x = width_mid_point - string_width / 2
|
|
235
|
-
elif int(alignment) == 2: # Right alignment
|
|
236
|
-
# Align text to the right edge minus the string width
|
|
237
|
-
x = float(widget[Rect][2]) - string_width
|
|
238
|
-
if length > 0 and widget_middleware.comb is True:
|
|
239
|
-
# For comb fields, adjust further by half the difference between comb box width and last character width
|
|
240
|
-
x -= (
|
|
241
|
-
get_char_rect_width(widget, widget_middleware)
|
|
242
|
-
- stringWidth(
|
|
243
|
-
text_value[-1],
|
|
244
|
-
widget_middleware.font,
|
|
245
|
-
widget_middleware.font_size,
|
|
246
|
-
)
|
|
247
|
-
) / 2
|
|
248
|
-
|
|
249
|
-
# Additional comb adjustment for center alignment
|
|
250
|
-
if int(alignment) == 1 and widget_middleware.comb is True and length != 0:
|
|
251
|
-
# Shift left by half the first character's padding
|
|
252
|
-
x -= character_paddings[0] / 2
|
|
253
|
-
if length % 2 == 0:
|
|
254
|
-
# For even-length comb text, shift further left by half the first char width plus padding
|
|
255
|
-
x -= (
|
|
256
|
-
character_paddings[0]
|
|
257
|
-
+ stringWidth(
|
|
258
|
-
text_value[:1],
|
|
259
|
-
widget_middleware.font,
|
|
260
|
-
widget_middleware.font_size,
|
|
261
|
-
)
|
|
262
|
-
/ 2
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
return x
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def calculate_text_coord_y(widget: dict, widget_middleware: Text) -> float:
|
|
269
|
-
"""
|
|
270
|
-
Calculate the vertical (y) coordinate for text drawing within a PDF form field.
|
|
271
|
-
|
|
272
|
-
This function determines the y-coordinate based on:
|
|
273
|
-
- The widget's rectangle height
|
|
274
|
-
- The font size
|
|
275
|
-
- Whether the field is multiline (which shifts text closer to the top)
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
widget: PDF form widget dictionary containing Rect info.
|
|
279
|
-
widget_middleware: Text middleware containing font size and multiline info.
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
float: The calculated y-coordinate for the text baseline.
|
|
283
|
-
"""
|
|
284
|
-
|
|
285
|
-
# Convert font size to PDF points (font size is in pixels, 96 dpi to 72 dpi)
|
|
286
|
-
string_height = widget_middleware.font_size * 96 / 72
|
|
287
|
-
|
|
288
|
-
# Calculate vertical midpoint of the widget rectangle
|
|
289
|
-
height_mid_point = (float(widget[Rect][1]) + float(widget[Rect][3])) / 2
|
|
290
|
-
|
|
291
|
-
# Default y: vertically center the text baseline within the widget
|
|
292
|
-
# This formula centers the text height around the vertical midpoint
|
|
293
|
-
y = (height_mid_point - string_height / 2 + height_mid_point) / 2
|
|
294
|
-
|
|
295
|
-
# If multiline, position baseline closer to the top edge for better appearance
|
|
296
|
-
if is_text_multiline(widget):
|
|
297
|
-
y = float(widget[Rect][3]) - string_height / 1.5
|
|
298
|
-
|
|
299
|
-
return y
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def get_draw_text_coordinates(
|
|
303
|
-
widget: dict, widget_middleware: Text
|
|
304
|
-
) -> Tuple[Union[float, int], Union[float, int]]:
|
|
305
|
-
"""
|
|
306
|
-
Calculate the (x, y) coordinates for drawing text within a PDF form field.
|
|
307
|
-
|
|
308
|
-
This function determines the starting position for rendering text,
|
|
309
|
-
taking into account:
|
|
310
|
-
- Preview mode (which offsets text above the field)
|
|
311
|
-
- Text length and wrapping
|
|
312
|
-
- Alignment (left, center, right)
|
|
313
|
-
- Comb formatting and character paddings
|
|
314
|
-
- Multiline adjustments for vertical position
|
|
315
|
-
|
|
316
|
-
Args:
|
|
317
|
-
widget: PDF form widget dictionary containing Rect and alignment info.
|
|
318
|
-
widget_middleware: Text middleware containing font, comb, and text properties.
|
|
319
|
-
|
|
320
|
-
Returns:
|
|
321
|
-
Tuple[Union[float, int], Union[float, int]]: The (x, y) coordinates
|
|
322
|
-
for the text baseline starting point.
|
|
323
|
-
"""
|
|
324
|
-
|
|
325
|
-
# If preview mode, draw slightly above the top boundary for visibility
|
|
326
|
-
if widget_middleware.preview:
|
|
327
|
-
return (
|
|
328
|
-
float(widget[Rect][0]),
|
|
329
|
-
float(widget[Rect][3]) + 5,
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
# Prepare text value, respecting max length
|
|
333
|
-
text_value = widget_middleware.value or ""
|
|
334
|
-
length = (
|
|
335
|
-
min(len(text_value), widget_middleware.max_length)
|
|
336
|
-
if widget_middleware.max_length is not None
|
|
337
|
-
else len(text_value)
|
|
338
|
-
)
|
|
339
|
-
text_value = text_value[:length]
|
|
340
|
-
|
|
341
|
-
# Further trim text if wrapping is enabled
|
|
342
|
-
if widget_middleware.text_wrap_length is not None:
|
|
343
|
-
text_value = text_value[: widget_middleware.text_wrap_length]
|
|
344
|
-
|
|
345
|
-
# Prepare character paddings for comb fields
|
|
346
|
-
character_paddings = (
|
|
347
|
-
widget_middleware.character_paddings[:length]
|
|
348
|
-
if widget_middleware.character_paddings is not None
|
|
349
|
-
else widget_middleware.character_paddings
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
# Calculate horizontal position based on alignment and comb settings
|
|
353
|
-
x = calculate_text_coord_x(
|
|
354
|
-
widget, widget_middleware, text_value, length, character_paddings
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
# Calculate vertical position based on font size and multiline
|
|
358
|
-
y = calculate_text_coord_y(widget, widget_middleware)
|
|
359
|
-
|
|
360
|
-
return x, y
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def get_text_line_x_coordinates(
|
|
364
|
-
widget: dict, widget_middleware: Text
|
|
365
|
-
) -> Union[List[float], None]:
|
|
366
|
-
"""Calculates x-coordinates for each line in a multiline text field.
|
|
367
|
-
|
|
368
|
-
Args:
|
|
369
|
-
widget: PDF form widget dictionary
|
|
370
|
-
widget_middleware: Text middleware with text_lines property
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
Union[List[float], None]: List of x-coordinates for each text line,
|
|
374
|
-
or None if not a multiline field
|
|
375
|
-
"""
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
widget_middleware.text_wrap_length is not None
|
|
379
|
-
and widget_middleware.text_lines is not None
|
|
380
|
-
and len(widget_middleware.text_lines)
|
|
381
|
-
and isinstance(widget_middleware.value, str)
|
|
382
|
-
and len(widget_middleware.value) > widget_middleware.text_wrap_length
|
|
383
|
-
):
|
|
384
|
-
result = []
|
|
385
|
-
_widget = deepcopy(widget_middleware)
|
|
386
|
-
for each in widget_middleware.text_lines:
|
|
387
|
-
_widget.value = each
|
|
388
|
-
_widget.text_wrap_length = None
|
|
389
|
-
result.append(get_draw_text_coordinates(widget, _widget)[0])
|
|
390
|
-
|
|
391
|
-
return result
|
|
392
|
-
|
|
393
|
-
return None
|
|
394
|
-
|
|
395
|
-
|
|
396
21
|
def generate_coordinate_grid(
|
|
397
22
|
pdf: bytes, color: Tuple[float, float, float], margin: float
|
|
398
23
|
) -> bytes:
|
|
399
|
-
"""
|
|
24
|
+
"""
|
|
25
|
+
Generates a coordinate grid overlay on a PDF document.
|
|
26
|
+
|
|
27
|
+
This function takes a PDF file as bytes, along with a color and margin, and generates
|
|
28
|
+
a coordinate grid on each page of the PDF. The grid consists of lines and text indicating
|
|
29
|
+
the X and Y coordinates. This can be useful for visualizing the layout and positioning
|
|
30
|
+
elements on the PDF.
|
|
400
31
|
|
|
401
32
|
Args:
|
|
402
|
-
pdf:
|
|
403
|
-
color:
|
|
404
|
-
|
|
33
|
+
pdf (bytes): The PDF file as bytes.
|
|
34
|
+
color (Tuple[float, float, float]): The color of the grid lines and text as a tuple of RGB values (0.0-1.0).
|
|
35
|
+
For example, (0.0, 0.0, 0.0) represents black.
|
|
36
|
+
margin (float): The margin between the grid lines and the edge of the page, in points.
|
|
37
|
+
This value determines the spacing of the grid.
|
|
405
38
|
|
|
406
39
|
Returns:
|
|
407
|
-
bytes:
|
|
40
|
+
bytes: The PDF file with the coordinate grid overlay as bytes.
|
|
408
41
|
"""
|
|
409
|
-
|
|
410
42
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
411
43
|
lines_by_page = {}
|
|
412
44
|
texts_by_page = {}
|
|
@@ -418,8 +50,6 @@ def generate_coordinate_grid(
|
|
|
418
50
|
width = float(page.mediabox[2])
|
|
419
51
|
height = float(page.mediabox[3])
|
|
420
52
|
|
|
421
|
-
r, g, b = color
|
|
422
|
-
|
|
423
53
|
current = margin
|
|
424
54
|
while current < width:
|
|
425
55
|
lines_by_page[i + 1].append(
|
|
@@ -428,10 +58,7 @@ def generate_coordinate_grid(
|
|
|
428
58
|
"src_y": 0,
|
|
429
59
|
"dest_x": current,
|
|
430
60
|
"dest_y": height,
|
|
431
|
-
"
|
|
432
|
-
"background_color": None,
|
|
433
|
-
"border_width": 1,
|
|
434
|
-
"dash_array": None,
|
|
61
|
+
"color": color,
|
|
435
62
|
}
|
|
436
63
|
)
|
|
437
64
|
current += margin
|
|
@@ -444,10 +71,7 @@ def generate_coordinate_grid(
|
|
|
444
71
|
"src_y": current,
|
|
445
72
|
"dest_x": width,
|
|
446
73
|
"dest_y": current,
|
|
447
|
-
"
|
|
448
|
-
"background_color": None,
|
|
449
|
-
"border_width": 1,
|
|
450
|
-
"dash_array": None,
|
|
74
|
+
"color": color,
|
|
451
75
|
}
|
|
452
76
|
)
|
|
453
77
|
current += margin
|