PyPDFForm 1.4.13__py3-none-any.whl → 1.4.15__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 +1 -1
- PyPDFForm/constants.py +23 -22
- PyPDFForm/coordinate.py +15 -33
- PyPDFForm/filler.py +19 -22
- PyPDFForm/font.py +5 -12
- PyPDFForm/patterns.py +67 -28
- PyPDFForm/template.py +40 -84
- PyPDFForm/wrapper.py +5 -1
- {PyPDFForm-1.4.13.dist-info → PyPDFForm-1.4.15.dist-info}/METADATA +1 -1
- {PyPDFForm-1.4.13.dist-info → PyPDFForm-1.4.15.dist-info}/RECORD +13 -13
- {PyPDFForm-1.4.13.dist-info → PyPDFForm-1.4.15.dist-info}/LICENSE +0 -0
- {PyPDFForm-1.4.13.dist-info → PyPDFForm-1.4.15.dist-info}/WHEEL +0 -0
- {PyPDFForm-1.4.13.dist-info → PyPDFForm-1.4.15.dist-info}/top_level.txt +0 -0
PyPDFForm/__init__.py
CHANGED
PyPDFForm/constants.py
CHANGED
|
@@ -26,29 +26,30 @@ WIDGET_TYPES = Union[Text, Checkbox, Radio, Dropdown, Signature]
|
|
|
26
26
|
|
|
27
27
|
DEPRECATION_NOTICE = "{} will be deprecated soon. Use {} instead."
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
Annots = "/Annots"
|
|
30
|
+
T = "/T"
|
|
31
|
+
Rect = "/Rect"
|
|
32
|
+
Subtype = "/Subtype"
|
|
33
|
+
Widget = "/Widget"
|
|
34
|
+
FT = "/FT"
|
|
35
|
+
Parent = "/Parent"
|
|
36
|
+
Ff = "/Ff"
|
|
37
|
+
Tx = "/Tx"
|
|
38
|
+
V = "/V"
|
|
39
|
+
AP = "/AP"
|
|
40
|
+
Sig = "/Sig"
|
|
41
|
+
DA = "/DA"
|
|
42
|
+
Btn = "/Btn"
|
|
43
|
+
MaxLen = "/MaxLen"
|
|
44
|
+
Q = "/Q"
|
|
45
|
+
Ch = "/Ch"
|
|
46
|
+
Opt = "/Opt"
|
|
47
|
+
MK = "/MK"
|
|
48
|
+
CA = "/CA"
|
|
49
|
+
AS = "/AS"
|
|
50
50
|
|
|
51
51
|
# Field flag bits
|
|
52
|
+
READ_ONLY = 1 << 0
|
|
52
53
|
MULTILINE = 1 << 12
|
|
53
54
|
COMB = 1 << 24
|
|
54
55
|
|
|
@@ -69,6 +70,6 @@ BUTTON_STYLES = {
|
|
|
69
70
|
"l": "\u25CF", # circle
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
Yes = "/Yes"
|
|
73
74
|
|
|
74
75
|
COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO = DEFAULT_FONT_SIZE / 100
|
PyPDFForm/coordinate.py
CHANGED
|
@@ -7,8 +7,8 @@ from typing import List, Tuple, Union
|
|
|
7
7
|
from pypdf import PdfReader
|
|
8
8
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
9
9
|
|
|
10
|
-
from .constants import (
|
|
11
|
-
|
|
10
|
+
from .constants import (COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO, DEFAULT_FONT,
|
|
11
|
+
Rect)
|
|
12
12
|
from .middleware.text import Text
|
|
13
13
|
from .template import (get_char_rect_width, get_widget_alignment,
|
|
14
14
|
is_text_multiline)
|
|
@@ -23,14 +23,8 @@ def get_draw_checkbox_radio_coordinates(
|
|
|
23
23
|
"""Returns coordinates to draw at given a PDF form checkbox/radio widget."""
|
|
24
24
|
|
|
25
25
|
string_height = widget_middleware.font_size * 96 / 72
|
|
26
|
-
width_mid_point = (
|
|
27
|
-
|
|
28
|
-
+ float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
29
|
-
) / 2
|
|
30
|
-
height_mid_point = (
|
|
31
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][1])
|
|
32
|
-
+ float(widget[ANNOTATION_RECTANGLE_KEY][3])
|
|
33
|
-
) / 2
|
|
26
|
+
width_mid_point = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
|
|
27
|
+
height_mid_point = (float(widget[Rect][1]) + float(widget[Rect][3])) / 2
|
|
34
28
|
|
|
35
29
|
return (
|
|
36
30
|
width_mid_point
|
|
@@ -51,16 +45,10 @@ def get_draw_sig_coordinates_resolutions(
|
|
|
51
45
|
Returns coordinates and resolutions to draw signature at given a PDF form signature widget.
|
|
52
46
|
"""
|
|
53
47
|
|
|
54
|
-
x = float(widget[
|
|
55
|
-
y = float(widget[
|
|
56
|
-
width = abs(
|
|
57
|
-
|
|
58
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
59
|
-
)
|
|
60
|
-
height = abs(
|
|
61
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][1])
|
|
62
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][3])
|
|
63
|
-
)
|
|
48
|
+
x = float(widget[Rect][0])
|
|
49
|
+
y = float(widget[Rect][1])
|
|
50
|
+
width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
51
|
+
height = abs(float(widget[Rect][1]) - float(widget[Rect][3]))
|
|
64
52
|
|
|
65
53
|
return x, y, width, height
|
|
66
54
|
|
|
@@ -72,8 +60,8 @@ def get_draw_text_coordinates(
|
|
|
72
60
|
|
|
73
61
|
if widget_middleware.preview:
|
|
74
62
|
return (
|
|
75
|
-
float(widget[
|
|
76
|
-
float(widget[
|
|
63
|
+
float(widget[Rect][0]),
|
|
64
|
+
float(widget[Rect][3]) + 5,
|
|
77
65
|
)
|
|
78
66
|
|
|
79
67
|
text_value = widget_middleware.value or ""
|
|
@@ -94,13 +82,10 @@ def get_draw_text_coordinates(
|
|
|
94
82
|
)
|
|
95
83
|
|
|
96
84
|
alignment = get_widget_alignment(widget) or 0
|
|
97
|
-
x = float(widget[
|
|
85
|
+
x = float(widget[Rect][0])
|
|
98
86
|
|
|
99
87
|
if int(alignment) != 0:
|
|
100
|
-
width_mid_point = (
|
|
101
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
102
|
-
+ float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
103
|
-
) / 2
|
|
88
|
+
width_mid_point = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
|
|
104
89
|
string_width = stringWidth(
|
|
105
90
|
text_value,
|
|
106
91
|
widget_middleware.font,
|
|
@@ -116,7 +101,7 @@ def get_draw_text_coordinates(
|
|
|
116
101
|
if int(alignment) == 1:
|
|
117
102
|
x = width_mid_point - string_width / 2
|
|
118
103
|
elif int(alignment) == 2:
|
|
119
|
-
x = float(widget[
|
|
104
|
+
x = float(widget[Rect][2]) - string_width
|
|
120
105
|
if length > 0 and widget_middleware.comb is True:
|
|
121
106
|
x -= (
|
|
122
107
|
get_char_rect_width(widget, widget_middleware)
|
|
@@ -128,13 +113,10 @@ def get_draw_text_coordinates(
|
|
|
128
113
|
) / 2
|
|
129
114
|
|
|
130
115
|
string_height = widget_middleware.font_size * 96 / 72
|
|
131
|
-
height_mid_point = (
|
|
132
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][1])
|
|
133
|
-
+ float(widget[ANNOTATION_RECTANGLE_KEY][3])
|
|
134
|
-
) / 2
|
|
116
|
+
height_mid_point = (float(widget[Rect][1]) + float(widget[Rect][3])) / 2
|
|
135
117
|
y = (height_mid_point - string_height / 2 + height_mid_point) / 2
|
|
136
118
|
if is_text_multiline(widget):
|
|
137
|
-
y = float(widget[
|
|
119
|
+
y = float(widget[Rect][3]) - string_height / 1.5
|
|
138
120
|
|
|
139
121
|
if int(alignment) == 1 and widget_middleware.comb is True and length != 0:
|
|
140
122
|
x -= character_paddings[0] / 2
|
PyPDFForm/filler.py
CHANGED
|
@@ -5,11 +5,9 @@ from io import BytesIO
|
|
|
5
5
|
from typing import Dict, cast
|
|
6
6
|
|
|
7
7
|
from pypdf import PdfReader, PdfWriter
|
|
8
|
-
from pypdf.generic import DictionaryObject
|
|
8
|
+
from pypdf.generic import DictionaryObject
|
|
9
9
|
|
|
10
|
-
from .constants import
|
|
11
|
-
TEXT_VALUE_IDENTIFIER, TEXT_VALUE_SHOW_UP_IDENTIFIER,
|
|
12
|
-
WIDGET_TYPES)
|
|
10
|
+
from .constants import WIDGET_TYPES, Annots
|
|
13
11
|
from .coordinate import (get_draw_checkbox_radio_coordinates,
|
|
14
12
|
get_draw_sig_coordinates_resolutions,
|
|
15
13
|
get_draw_text_coordinates,
|
|
@@ -21,6 +19,10 @@ from .middleware.dropdown import Dropdown
|
|
|
21
19
|
from .middleware.radio import Radio
|
|
22
20
|
from .middleware.signature import Signature
|
|
23
21
|
from .middleware.text import Text
|
|
22
|
+
from .patterns import (simple_flatten_generic, simple_flatten_radio,
|
|
23
|
+
simple_update_checkbox_value,
|
|
24
|
+
simple_update_dropdown_value, simple_update_radio_value,
|
|
25
|
+
simple_update_text_value)
|
|
24
26
|
from .template import get_widget_key, get_widgets_by_page
|
|
25
27
|
from .utils import checkbox_radio_to_draw, stream_to_io
|
|
26
28
|
from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
|
|
@@ -130,6 +132,7 @@ def fill(
|
|
|
130
132
|
def simple_fill(
|
|
131
133
|
template: bytes,
|
|
132
134
|
widgets: Dict[str, WIDGET_TYPES],
|
|
135
|
+
flatten: bool = False,
|
|
133
136
|
) -> bytes:
|
|
134
137
|
"""Fills a PDF form in place."""
|
|
135
138
|
|
|
@@ -140,7 +143,7 @@ def simple_fill(
|
|
|
140
143
|
radio_button_tracker = {}
|
|
141
144
|
|
|
142
145
|
for page in out.pages:
|
|
143
|
-
for annot in page.get(
|
|
146
|
+
for annot in page.get(Annots, []): # noqa
|
|
144
147
|
annot = cast(DictionaryObject, annot.get_object())
|
|
145
148
|
key = get_widget_key(annot.get_object())
|
|
146
149
|
|
|
@@ -148,30 +151,24 @@ def simple_fill(
|
|
|
148
151
|
if widget is None:
|
|
149
152
|
continue
|
|
150
153
|
|
|
151
|
-
if
|
|
152
|
-
annot
|
|
154
|
+
if type(widget) is Checkbox and widget.value is True:
|
|
155
|
+
simple_update_checkbox_value(annot)
|
|
153
156
|
elif isinstance(widget, Radio):
|
|
154
157
|
if key not in radio_button_tracker:
|
|
155
158
|
radio_button_tracker[key] = 0
|
|
156
159
|
radio_button_tracker[key] += 1
|
|
157
160
|
if widget.value == radio_button_tracker[key] - 1:
|
|
158
|
-
annot
|
|
159
|
-
f"/{widget.value}"
|
|
160
|
-
)
|
|
161
|
+
simple_update_radio_value(annot, widget)
|
|
161
162
|
elif isinstance(widget, Dropdown) and widget.value is not None:
|
|
162
|
-
annot
|
|
163
|
-
widget.choices[widget.value]
|
|
164
|
-
)
|
|
165
|
-
annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(
|
|
166
|
-
widget.choices[widget.value]
|
|
167
|
-
)
|
|
163
|
+
simple_update_dropdown_value(annot, widget)
|
|
168
164
|
elif isinstance(widget, Text) and widget.value:
|
|
169
|
-
annot
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
simple_update_text_value(annot, widget)
|
|
166
|
+
|
|
167
|
+
if flatten:
|
|
168
|
+
if isinstance(widget, Radio):
|
|
169
|
+
simple_flatten_radio(annot)
|
|
170
|
+
else:
|
|
171
|
+
simple_flatten_generic(annot)
|
|
175
172
|
|
|
176
173
|
with BytesIO() as f:
|
|
177
174
|
out.write(f)
|
PyPDFForm/font.py
CHANGED
|
@@ -10,8 +10,8 @@ from reportlab.pdfbase.acroform import AcroForm
|
|
|
10
10
|
from reportlab.pdfbase.pdfmetrics import registerFont, standardFonts
|
|
11
11
|
from reportlab.pdfbase.ttfonts import TTFError, TTFont
|
|
12
12
|
|
|
13
|
-
from .constants import (
|
|
14
|
-
|
|
13
|
+
from .constants import (DEFAULT_FONT, FONT_COLOR_IDENTIFIER,
|
|
14
|
+
FONT_SIZE_IDENTIFIER, Rect)
|
|
15
15
|
from .patterns import TEXT_FIELD_APPEARANCE_PATTERNS
|
|
16
16
|
from .utils import traverse_pattern
|
|
17
17
|
|
|
@@ -82,10 +82,7 @@ def text_field_font_size(widget: dict) -> Union[float, int]:
|
|
|
82
82
|
given a text field widget.
|
|
83
83
|
"""
|
|
84
84
|
|
|
85
|
-
height = abs(
|
|
86
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][1])
|
|
87
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][3])
|
|
88
|
-
)
|
|
85
|
+
height = abs(float(widget[Rect][1]) - float(widget[Rect][3]))
|
|
89
86
|
|
|
90
87
|
return height * 2 / 3
|
|
91
88
|
|
|
@@ -96,12 +93,8 @@ def checkbox_radio_font_size(widget: dict) -> Union[float, int]:
|
|
|
96
93
|
given a checkbox/radio button widget.
|
|
97
94
|
"""
|
|
98
95
|
|
|
99
|
-
area = abs(
|
|
100
|
-
float(widget[
|
|
101
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
102
|
-
) * abs(
|
|
103
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][1])
|
|
104
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][3])
|
|
96
|
+
area = abs(float(widget[Rect][0]) - float(widget[Rect][2])) * abs(
|
|
97
|
+
float(widget[Rect][1]) - float(widget[Rect][3])
|
|
105
98
|
)
|
|
106
99
|
|
|
107
100
|
return sqrt(area) * 72 / 96
|
PyPDFForm/patterns.py
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""Contains patterns used for identifying properties of widgets."""
|
|
3
3
|
|
|
4
|
-
from .
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
TEXT_FIELD_APPEARANCE_IDENTIFIER,
|
|
10
|
-
TEXT_FIELD_IDENTIFIER, WIDGET_SUBTYPE_KEY,
|
|
11
|
-
WIDGET_TYPE_KEY)
|
|
4
|
+
from pypdf.generic import (DictionaryObject, NameObject, NumberObject,
|
|
5
|
+
TextStringObject)
|
|
6
|
+
|
|
7
|
+
from .constants import (AP, AS, CA, DA, FT, MK, READ_ONLY, Btn, Ch, Ff, Opt,
|
|
8
|
+
Parent, Q, Sig, Subtype, T, Tx, V, Widget, Yes)
|
|
12
9
|
from .middleware.checkbox import Checkbox
|
|
13
10
|
from .middleware.dropdown import Dropdown
|
|
14
11
|
from .middleware.radio import Radio
|
|
@@ -17,67 +14,109 @@ from .middleware.text import Text
|
|
|
17
14
|
|
|
18
15
|
WIDGET_TYPE_PATTERNS = [
|
|
19
16
|
(
|
|
20
|
-
({
|
|
17
|
+
({FT: Sig},),
|
|
21
18
|
Signature,
|
|
22
19
|
),
|
|
23
20
|
(
|
|
24
|
-
({
|
|
21
|
+
({FT: Tx},),
|
|
25
22
|
Text,
|
|
26
23
|
),
|
|
27
24
|
(
|
|
28
|
-
({
|
|
25
|
+
({FT: Btn},),
|
|
29
26
|
Checkbox,
|
|
30
27
|
),
|
|
31
28
|
(
|
|
32
|
-
({
|
|
29
|
+
({FT: Ch},),
|
|
33
30
|
Dropdown,
|
|
34
31
|
),
|
|
35
32
|
(
|
|
36
|
-
({
|
|
33
|
+
({Parent: {FT: Ch}},),
|
|
37
34
|
Dropdown,
|
|
38
35
|
),
|
|
39
36
|
(
|
|
40
|
-
({
|
|
37
|
+
({Parent: {FT: Tx}},),
|
|
41
38
|
Text,
|
|
42
39
|
),
|
|
43
40
|
(
|
|
44
41
|
(
|
|
45
|
-
{
|
|
46
|
-
{
|
|
42
|
+
{Parent: {FT: Btn}},
|
|
43
|
+
{Parent: {Subtype: Widget}},
|
|
47
44
|
),
|
|
48
45
|
Checkbox,
|
|
49
46
|
),
|
|
50
47
|
(
|
|
51
|
-
({
|
|
48
|
+
({Parent: {FT: Btn}},),
|
|
52
49
|
Radio,
|
|
53
50
|
),
|
|
54
51
|
]
|
|
55
52
|
|
|
56
53
|
WIDGET_KEY_PATTERNS = [
|
|
57
|
-
{
|
|
58
|
-
{
|
|
54
|
+
{T: True},
|
|
55
|
+
{Parent: {T: True}},
|
|
59
56
|
]
|
|
60
57
|
|
|
61
58
|
DROPDOWN_CHOICE_PATTERNS = [
|
|
62
|
-
{
|
|
63
|
-
{
|
|
59
|
+
{Opt: True},
|
|
60
|
+
{Parent: {Opt: True}},
|
|
64
61
|
]
|
|
65
62
|
|
|
66
63
|
WIDGET_ALIGNMENT_PATTERNS = [
|
|
67
|
-
{
|
|
68
|
-
{
|
|
64
|
+
{Q: True},
|
|
65
|
+
{Parent: {Q: True}},
|
|
69
66
|
]
|
|
70
67
|
|
|
71
68
|
TEXT_FIELD_FLAG_PATTERNS = [
|
|
72
|
-
{
|
|
73
|
-
{
|
|
69
|
+
{Ff: True},
|
|
70
|
+
{Parent: {Ff: True}},
|
|
74
71
|
]
|
|
75
72
|
|
|
76
73
|
TEXT_FIELD_APPEARANCE_PATTERNS = [
|
|
77
|
-
{
|
|
78
|
-
{
|
|
74
|
+
{DA: True},
|
|
75
|
+
{Parent: {DA: True}},
|
|
79
76
|
]
|
|
80
77
|
|
|
81
78
|
BUTTON_STYLE_PATTERNS = [
|
|
82
|
-
{
|
|
79
|
+
{MK: {CA: True}},
|
|
83
80
|
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def simple_update_checkbox_value(annot: DictionaryObject) -> None:
|
|
84
|
+
"""Patterns to update values for checkbox annotations."""
|
|
85
|
+
|
|
86
|
+
annot[NameObject(AS)] = NameObject(Yes)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def simple_update_radio_value(annot: DictionaryObject, widget: Radio) -> None:
|
|
90
|
+
"""Patterns to update values for radio annotations."""
|
|
91
|
+
|
|
92
|
+
annot[NameObject(AS)] = NameObject(f"/{widget.value}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def simple_update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
|
|
96
|
+
"""Patterns to update values for dropdown annotations."""
|
|
97
|
+
|
|
98
|
+
annot[NameObject(V)] = TextStringObject(widget.choices[widget.value])
|
|
99
|
+
annot[NameObject(AP)] = TextStringObject(widget.choices[widget.value])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def simple_update_text_value(annot: DictionaryObject, widget: Text) -> None:
|
|
103
|
+
"""Patterns to update values for text annotations."""
|
|
104
|
+
|
|
105
|
+
annot[NameObject(V)] = TextStringObject(widget.value)
|
|
106
|
+
annot[NameObject(AP)] = TextStringObject(widget.value)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def simple_flatten_radio(annot: DictionaryObject) -> None:
|
|
110
|
+
"""Patterns to flatten checkbox annotations."""
|
|
111
|
+
|
|
112
|
+
annot[NameObject(Parent)][NameObject(Ff)] = NumberObject( # noqa
|
|
113
|
+
int(annot[NameObject(Parent)].get(NameObject(Ff), 0)) | READ_ONLY # noqa
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def simple_flatten_generic(annot: DictionaryObject) -> None:
|
|
118
|
+
"""Patterns to flatten generic annotations."""
|
|
119
|
+
|
|
120
|
+
annot[NameObject(Ff)] = NumberObject(
|
|
121
|
+
int(annot.get(NameObject(Ff), 0)) | READ_ONLY # noqa
|
|
122
|
+
)
|
PyPDFForm/template.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""Contains helpers for generic template related processing."""
|
|
3
3
|
|
|
4
|
+
from sys import maxsize
|
|
4
5
|
from typing import Dict, List, Tuple, Union
|
|
5
6
|
|
|
6
7
|
from pypdf import PdfReader
|
|
7
8
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
8
9
|
|
|
9
|
-
from .constants import (
|
|
10
|
-
|
|
11
|
-
WIDGET_TYPES)
|
|
10
|
+
from .constants import (COMB, DEFAULT_FONT_SIZE, MULTILINE, NEW_LINE_SYMBOL,
|
|
11
|
+
WIDGET_TYPES, MaxLen, Rect)
|
|
12
12
|
from .font import (auto_detect_font, get_text_field_font_color,
|
|
13
13
|
get_text_field_font_size, text_field_font_size)
|
|
14
14
|
from .middleware.checkbox import Checkbox
|
|
@@ -81,7 +81,7 @@ def widget_rect_watermarks(pdf: bytes) -> List[bytes]:
|
|
|
81
81
|
for page, widgets in get_widgets_by_page(pdf).items():
|
|
82
82
|
to_draw = []
|
|
83
83
|
for widget in widgets:
|
|
84
|
-
rect = widget[
|
|
84
|
+
rect = widget[Rect]
|
|
85
85
|
x = rect[0]
|
|
86
86
|
y = rect[1]
|
|
87
87
|
width = abs(rect[0] - rect[2])
|
|
@@ -132,10 +132,10 @@ def update_text_field_attributes(
|
|
|
132
132
|
if widgets[key].font_color is None:
|
|
133
133
|
widgets[key].font_color = get_text_field_font_color(_widget)
|
|
134
134
|
if is_text_multiline(_widget) and widgets[key].text_wrap_length is None:
|
|
135
|
+
widgets[key].text_lines = get_paragraph_lines(_widget, widgets[key])
|
|
135
136
|
widgets[key].text_wrap_length = get_paragraph_auto_wrap_length(
|
|
136
|
-
|
|
137
|
+
widgets[key]
|
|
137
138
|
)
|
|
138
|
-
widgets[key].text_lines = get_paragraph_lines(_widget, widgets[key])
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
|
|
@@ -205,11 +205,7 @@ def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
|
|
|
205
205
|
def get_text_field_max_length(widget: dict) -> Union[int, None]:
|
|
206
206
|
"""Returns the max length of the text field if presented or None."""
|
|
207
207
|
|
|
208
|
-
return (
|
|
209
|
-
int(widget[TEXT_FIELD_MAX_LENGTH_KEY]) or None
|
|
210
|
-
if TEXT_FIELD_MAX_LENGTH_KEY in widget
|
|
211
|
-
else None
|
|
212
|
-
)
|
|
208
|
+
return int(widget[MaxLen]) or None if MaxLen in widget else None
|
|
213
209
|
|
|
214
210
|
|
|
215
211
|
def check_field_flag_bit(widget: dict, bit: int) -> bool:
|
|
@@ -268,10 +264,7 @@ def get_button_style(widget: dict) -> Union[str, None]:
|
|
|
268
264
|
def get_char_rect_width(widget: dict, widget_middleware: Text) -> float:
|
|
269
265
|
"""Returns rectangular width of each character for combed text fields."""
|
|
270
266
|
|
|
271
|
-
rect_width = abs(
|
|
272
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
273
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
274
|
-
)
|
|
267
|
+
rect_width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
275
268
|
return rect_width / widget_middleware.max_length
|
|
276
269
|
|
|
277
270
|
|
|
@@ -295,43 +288,17 @@ def get_character_x_paddings(widget: dict, widget_middleware: Text) -> List[floa
|
|
|
295
288
|
return result
|
|
296
289
|
|
|
297
290
|
|
|
298
|
-
def calculate_wrap_length(widget: dict, widget_middleware: Text, v: str) -> int:
|
|
299
|
-
"""Increments the substring until reaching maximum horizontal width."""
|
|
300
|
-
|
|
301
|
-
width = abs(
|
|
302
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
303
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
304
|
-
)
|
|
305
|
-
value = widget_middleware.value or ""
|
|
306
|
-
value = value.replace(NEW_LINE_SYMBOL, " ")
|
|
307
|
-
|
|
308
|
-
counter = 0
|
|
309
|
-
_width = 0
|
|
310
|
-
while _width <= width and counter < len(value):
|
|
311
|
-
counter += 1
|
|
312
|
-
_width = stringWidth(
|
|
313
|
-
v[:counter],
|
|
314
|
-
widget_middleware.font,
|
|
315
|
-
widget_middleware.font_size,
|
|
316
|
-
)
|
|
317
|
-
return counter - 1
|
|
318
|
-
|
|
319
|
-
|
|
320
291
|
def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
|
|
321
292
|
"""Splits the paragraph field's text to a list of lines."""
|
|
322
293
|
|
|
323
294
|
# pylint: disable=R0912
|
|
324
295
|
lines = []
|
|
325
296
|
result = []
|
|
326
|
-
text_wrap_length = widget_middleware.text_wrap_length
|
|
327
297
|
value = widget_middleware.value or ""
|
|
328
298
|
if widget_middleware.max_length is not None:
|
|
329
299
|
value = value[: widget_middleware.max_length]
|
|
330
300
|
|
|
331
|
-
width = abs(
|
|
332
|
-
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
333
|
-
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
334
|
-
)
|
|
301
|
+
width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
335
302
|
|
|
336
303
|
split_by_new_line_symbol = value.split(NEW_LINE_SYMBOL)
|
|
337
304
|
for line in split_by_new_line_symbol:
|
|
@@ -339,7 +306,12 @@ def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
|
|
|
339
306
|
current_line = ""
|
|
340
307
|
for each in characters:
|
|
341
308
|
line_extended = f"{current_line} {each}" if current_line else each
|
|
342
|
-
if
|
|
309
|
+
if (
|
|
310
|
+
stringWidth(
|
|
311
|
+
line_extended, widget_middleware.font, widget_middleware.font_size
|
|
312
|
+
)
|
|
313
|
+
<= width
|
|
314
|
+
):
|
|
343
315
|
current_line = line_extended
|
|
344
316
|
else:
|
|
345
317
|
lines.append(current_line)
|
|
@@ -350,25 +322,29 @@ def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
|
|
|
350
322
|
else current_line
|
|
351
323
|
)
|
|
352
324
|
|
|
353
|
-
for line in lines:
|
|
354
|
-
while (
|
|
355
|
-
stringWidth(
|
|
356
|
-
line[:text_wrap_length],
|
|
357
|
-
widget_middleware.font,
|
|
358
|
-
widget_middleware.font_size,
|
|
359
|
-
)
|
|
360
|
-
> width
|
|
361
|
-
):
|
|
362
|
-
text_wrap_length -= 1
|
|
363
|
-
|
|
364
325
|
for each in lines:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
326
|
+
tracker = ""
|
|
327
|
+
for char in each:
|
|
328
|
+
check = tracker + char
|
|
329
|
+
if (
|
|
330
|
+
stringWidth(check, widget_middleware.font, widget_middleware.font_size)
|
|
331
|
+
> width
|
|
332
|
+
):
|
|
333
|
+
result.append(tracker)
|
|
334
|
+
tracker = char
|
|
335
|
+
else:
|
|
336
|
+
tracker = check
|
|
337
|
+
|
|
338
|
+
each = tracker
|
|
368
339
|
if each:
|
|
369
340
|
if (
|
|
370
341
|
result
|
|
371
|
-
and
|
|
342
|
+
and stringWidth(
|
|
343
|
+
f"{each} {result[-1]}",
|
|
344
|
+
widget_middleware.font,
|
|
345
|
+
widget_middleware.font_size,
|
|
346
|
+
)
|
|
347
|
+
<= width
|
|
372
348
|
and NEW_LINE_SYMBOL not in result[-1]
|
|
373
349
|
):
|
|
374
350
|
result[-1] = f"{result[-1]}{each} "
|
|
@@ -384,31 +360,11 @@ def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
|
|
|
384
360
|
return result
|
|
385
361
|
|
|
386
362
|
|
|
387
|
-
def get_paragraph_auto_wrap_length(
|
|
363
|
+
def get_paragraph_auto_wrap_length(widget_middleware: Text) -> int:
|
|
388
364
|
"""Calculates the text wrap length of a paragraph field."""
|
|
389
365
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
)
|
|
396
|
-
text_width = stringWidth(
|
|
397
|
-
value,
|
|
398
|
-
widget_middleware.font,
|
|
399
|
-
widget_middleware.font_size,
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
lines = text_width / width
|
|
403
|
-
if lines > 1:
|
|
404
|
-
current_min = 0
|
|
405
|
-
while len(value) and current_min < len(value):
|
|
406
|
-
result = calculate_wrap_length(widget, widget_middleware, value)
|
|
407
|
-
value = value[result:]
|
|
408
|
-
if current_min == 0:
|
|
409
|
-
current_min = result
|
|
410
|
-
elif result < current_min:
|
|
411
|
-
current_min = result
|
|
412
|
-
return current_min
|
|
413
|
-
|
|
414
|
-
return len(value) + 1
|
|
366
|
+
result = maxsize
|
|
367
|
+
for line in widget_middleware.text_lines:
|
|
368
|
+
result = min(result, len(line))
|
|
369
|
+
|
|
370
|
+
return result
|
PyPDFForm/wrapper.py
CHANGED
|
@@ -45,6 +45,7 @@ class FormWrapper:
|
|
|
45
45
|
def fill(
|
|
46
46
|
self,
|
|
47
47
|
data: Dict[str, Union[str, bool, int]],
|
|
48
|
+
**kwargs,
|
|
48
49
|
) -> FormWrapper:
|
|
49
50
|
"""Fills a PDF form."""
|
|
50
51
|
|
|
@@ -54,7 +55,9 @@ class FormWrapper:
|
|
|
54
55
|
if key in widgets:
|
|
55
56
|
widgets[key].value = value
|
|
56
57
|
|
|
57
|
-
self.stream = simple_fill(
|
|
58
|
+
self.stream = simple_fill(
|
|
59
|
+
self.read(), widgets, flatten=kwargs.get("flatten", False)
|
|
60
|
+
)
|
|
58
61
|
|
|
59
62
|
return self
|
|
60
63
|
|
|
@@ -179,6 +182,7 @@ class PdfWrapper(FormWrapper):
|
|
|
179
182
|
def fill(
|
|
180
183
|
self,
|
|
181
184
|
data: Dict[str, Union[str, bool, int]],
|
|
185
|
+
**kwargs,
|
|
182
186
|
) -> PdfWrapper:
|
|
183
187
|
"""Fills a PDF form."""
|
|
184
188
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
PyPDFForm/__init__.py,sha256=
|
|
1
|
+
PyPDFForm/__init__.py,sha256=KgGvTymkLYyv87kBq8HFehcC47PtgmzHc1zc73Z526w,149
|
|
2
2
|
PyPDFForm/adapter.py,sha256=OgAIwcmukpBqHFBLymd3I7wA7wIdipRqgURZA2Y0Hgo,885
|
|
3
|
-
PyPDFForm/constants.py,sha256=
|
|
4
|
-
PyPDFForm/coordinate.py,sha256=
|
|
5
|
-
PyPDFForm/filler.py,sha256=
|
|
6
|
-
PyPDFForm/font.py,sha256=
|
|
3
|
+
PyPDFForm/constants.py,sha256=H-MBRnFhqvCrkVnwTCzYdt6ebe7rbUBTjzZl3GFdHtM,1431
|
|
4
|
+
PyPDFForm/coordinate.py,sha256=iOy_KEZO3DKsvwdclxZHLOJyrvbCVqJDK2UKmMpEM8E,7310
|
|
5
|
+
PyPDFForm/filler.py,sha256=qobLLpYF79YZaHmZz_VbjLICebg-6xdQa4btNZO4Li4,6430
|
|
6
|
+
PyPDFForm/font.py,sha256=y9vNvoaBfgkd5vz9EDp04ul8KDM2jY7J61-VWFng6GY,4155
|
|
7
7
|
PyPDFForm/image.py,sha256=0GV8PFgY5oLJFHmhPD1fG-_5SBEO7jVLLtxCc_Uodfo,1143
|
|
8
|
-
PyPDFForm/patterns.py,sha256=
|
|
9
|
-
PyPDFForm/template.py,sha256=
|
|
8
|
+
PyPDFForm/patterns.py,sha256=udF5Ka-ucEKgO13FvZ3VZv8mJbi-f-aAixAyJFR8cWI,2946
|
|
9
|
+
PyPDFForm/template.py,sha256=fuoqiYD_4xQXrBgw8XC677EMxJzHaXb9NcVVYbf0r-0,11782
|
|
10
10
|
PyPDFForm/utils.py,sha256=_gskLLNmwhfGF16gfMP00twVY6iYADYYAYWHMvKCDrg,4140
|
|
11
11
|
PyPDFForm/watermark.py,sha256=clYdRqCuWEE25IL-BUHnSo4HgLkHPSOl7FUJLhbAfGg,4755
|
|
12
|
-
PyPDFForm/wrapper.py,sha256=
|
|
12
|
+
PyPDFForm/wrapper.py,sha256=oO-CYX_RzNm1O2bpifkpWUMzJ326TbR-EHTXmb6OCSc,10328
|
|
13
13
|
PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
PyPDFForm/middleware/base.py,sha256=TmoO8XpjFa5NerAumrAGm_kUt2eFP3DyyF-Vx0uXloM,724
|
|
15
15
|
PyPDFForm/middleware/checkbox.py,sha256=Z92awlKjceMd_ryb9Fhrtyd4fLkU84Ii6fSum60rHcA,1305
|
|
@@ -21,8 +21,8 @@ PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
21
21
|
PyPDFForm/widgets/base.py,sha256=Stq-oQt-cfwQ6nilQ4NqpO5udFSKDNb1mAwryrWggZA,2076
|
|
22
22
|
PyPDFForm/widgets/checkbox.py,sha256=issx3onojq9plTHbNQwEnkbnhTo5vJKA5XSAoxRzQCg,264
|
|
23
23
|
PyPDFForm/widgets/text.py,sha256=lUVlfQ-ndemSelsKNzY6nS4Kuf_H4mDPxotFMQFV-so,484
|
|
24
|
-
PyPDFForm-1.4.
|
|
25
|
-
PyPDFForm-1.4.
|
|
26
|
-
PyPDFForm-1.4.
|
|
27
|
-
PyPDFForm-1.4.
|
|
28
|
-
PyPDFForm-1.4.
|
|
24
|
+
PyPDFForm-1.4.15.dist-info/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
|
|
25
|
+
PyPDFForm-1.4.15.dist-info/METADATA,sha256=63tamlVBtPHyMvkegLhDXBGlZ2Wp9qMQ_HTLCRRC9_o,5178
|
|
26
|
+
PyPDFForm-1.4.15.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
27
|
+
PyPDFForm-1.4.15.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
|
|
28
|
+
PyPDFForm-1.4.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|