PyPDFForm 1.4.14__py3-none-any.whl → 1.4.16__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 -23
- PyPDFForm/coordinate.py +15 -33
- PyPDFForm/filler.py +13 -37
- PyPDFForm/font.py +5 -12
- PyPDFForm/patterns.py +90 -28
- PyPDFForm/template.py +40 -84
- {PyPDFForm-1.4.14.dist-info → PyPDFForm-1.4.16.dist-info}/METADATA +1 -1
- {PyPDFForm-1.4.14.dist-info → PyPDFForm-1.4.16.dist-info}/RECORD +12 -12
- {PyPDFForm-1.4.14.dist-info → PyPDFForm-1.4.16.dist-info}/LICENSE +0 -0
- {PyPDFForm-1.4.14.dist-info → PyPDFForm-1.4.16.dist-info}/WHEEL +0 -0
- {PyPDFForm-1.4.14.dist-info → PyPDFForm-1.4.16.dist-info}/top_level.txt +0 -0
PyPDFForm/__init__.py
CHANGED
PyPDFForm/constants.py
CHANGED
|
@@ -26,27 +26,29 @@ 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
|
+
D = "/D"
|
|
41
|
+
Sig = "/Sig"
|
|
42
|
+
DA = "/DA"
|
|
43
|
+
Btn = "/Btn"
|
|
44
|
+
MaxLen = "/MaxLen"
|
|
45
|
+
Q = "/Q"
|
|
46
|
+
Ch = "/Ch"
|
|
47
|
+
Opt = "/Opt"
|
|
48
|
+
MK = "/MK"
|
|
49
|
+
CA = "/CA"
|
|
50
|
+
AS = "/AS"
|
|
51
|
+
Off = "/Off"
|
|
50
52
|
|
|
51
53
|
# Field flag bits
|
|
52
54
|
READ_ONLY = 1 << 0
|
|
@@ -70,6 +72,4 @@ BUTTON_STYLES = {
|
|
|
70
72
|
"l": "\u25CF", # circle
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
CHECKBOX_SELECT = "/Yes"
|
|
74
|
-
|
|
75
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,13 +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
|
|
9
|
-
TextStringObject)
|
|
8
|
+
from pypdf.generic import DictionaryObject
|
|
10
9
|
|
|
11
|
-
from .constants import
|
|
12
|
-
PARENT_KEY, READ_ONLY, SELECTED_IDENTIFIER,
|
|
13
|
-
TEXT_VALUE_IDENTIFIER, TEXT_VALUE_SHOW_UP_IDENTIFIER,
|
|
14
|
-
WIDGET_TYPES)
|
|
10
|
+
from .constants import WIDGET_TYPES, Annots
|
|
15
11
|
from .coordinate import (get_draw_checkbox_radio_coordinates,
|
|
16
12
|
get_draw_sig_coordinates_resolutions,
|
|
17
13
|
get_draw_text_coordinates,
|
|
@@ -23,6 +19,10 @@ from .middleware.dropdown import Dropdown
|
|
|
23
19
|
from .middleware.radio import Radio
|
|
24
20
|
from .middleware.signature import Signature
|
|
25
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)
|
|
26
26
|
from .template import get_widget_key, get_widgets_by_page
|
|
27
27
|
from .utils import checkbox_radio_to_draw, stream_to_io
|
|
28
28
|
from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
|
|
@@ -143,7 +143,7 @@ def simple_fill(
|
|
|
143
143
|
radio_button_tracker = {}
|
|
144
144
|
|
|
145
145
|
for page in out.pages:
|
|
146
|
-
for annot in page.get(
|
|
146
|
+
for annot in page.get(Annots, []): # noqa
|
|
147
147
|
annot = cast(DictionaryObject, annot.get_object())
|
|
148
148
|
key = get_widget_key(annot.get_object())
|
|
149
149
|
|
|
@@ -152,47 +152,23 @@ def simple_fill(
|
|
|
152
152
|
continue
|
|
153
153
|
|
|
154
154
|
if type(widget) is Checkbox and widget.value is True:
|
|
155
|
-
annot
|
|
155
|
+
simple_update_checkbox_value(annot)
|
|
156
156
|
elif isinstance(widget, Radio):
|
|
157
157
|
if key not in radio_button_tracker:
|
|
158
158
|
radio_button_tracker[key] = 0
|
|
159
159
|
radio_button_tracker[key] += 1
|
|
160
160
|
if widget.value == radio_button_tracker[key] - 1:
|
|
161
|
-
annot
|
|
162
|
-
f"/{widget.value}"
|
|
163
|
-
)
|
|
161
|
+
simple_update_radio_value(annot)
|
|
164
162
|
elif isinstance(widget, Dropdown) and widget.value is not None:
|
|
165
|
-
annot
|
|
166
|
-
widget.choices[widget.value]
|
|
167
|
-
)
|
|
168
|
-
annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(
|
|
169
|
-
widget.choices[widget.value]
|
|
170
|
-
)
|
|
163
|
+
simple_update_dropdown_value(annot, widget)
|
|
171
164
|
elif isinstance(widget, Text) and widget.value:
|
|
172
|
-
annot
|
|
173
|
-
widget.value
|
|
174
|
-
)
|
|
175
|
-
annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(
|
|
176
|
-
widget.value
|
|
177
|
-
)
|
|
165
|
+
simple_update_text_value(annot, widget)
|
|
178
166
|
|
|
179
167
|
if flatten:
|
|
180
168
|
if isinstance(widget, Radio):
|
|
181
|
-
annot
|
|
182
|
-
NameObject(FIELD_FLAG_KEY)
|
|
183
|
-
] = NumberObject(
|
|
184
|
-
int(
|
|
185
|
-
annot[NameObject(PARENT_KEY)].get( # noqa
|
|
186
|
-
NameObject(FIELD_FLAG_KEY), 0
|
|
187
|
-
)
|
|
188
|
-
)
|
|
189
|
-
| READ_ONLY
|
|
190
|
-
)
|
|
169
|
+
simple_flatten_radio(annot)
|
|
191
170
|
else:
|
|
192
|
-
annot
|
|
193
|
-
int(annot.get(NameObject(FIELD_FLAG_KEY), 0))
|
|
194
|
-
| READ_ONLY # noqa
|
|
195
|
-
)
|
|
171
|
+
simple_flatten_generic(annot)
|
|
196
172
|
|
|
197
173
|
with BytesIO() as f:
|
|
198
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, D, Ff, Off,
|
|
8
|
+
Opt, Parent, Q, Sig, Subtype, T, Tx, V, Widget)
|
|
12
9
|
from .middleware.checkbox import Checkbox
|
|
13
10
|
from .middleware.dropdown import Dropdown
|
|
14
11
|
from .middleware.radio import Radio
|
|
@@ -17,67 +14,132 @@ 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
|
+
for each in annot[AP][D]: # noqa
|
|
87
|
+
if str(each) != Off:
|
|
88
|
+
annot[NameObject(AS)] = NameObject(each)
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def simple_update_radio_value(annot: DictionaryObject) -> None:
|
|
93
|
+
"""Patterns to update values for radio annotations."""
|
|
94
|
+
|
|
95
|
+
for each in annot[AP][D]: # noqa
|
|
96
|
+
if str(each) != Off:
|
|
97
|
+
annot[NameObject(AS)] = NameObject(each)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def simple_update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
|
|
102
|
+
"""Patterns to update values for dropdown annotations."""
|
|
103
|
+
|
|
104
|
+
if Parent in annot and T not in annot:
|
|
105
|
+
annot[NameObject(Parent)][NameObject(V)] = TextStringObject( # noqa
|
|
106
|
+
widget.choices[widget.value]
|
|
107
|
+
)
|
|
108
|
+
annot[NameObject(AP)] = TextStringObject(widget.choices[widget.value])
|
|
109
|
+
else:
|
|
110
|
+
annot[NameObject(V)] = TextStringObject(widget.choices[widget.value])
|
|
111
|
+
annot[NameObject(AP)] = TextStringObject(widget.choices[widget.value])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def simple_update_text_value(annot: DictionaryObject, widget: Text) -> None:
|
|
115
|
+
"""Patterns to update values for text annotations."""
|
|
116
|
+
|
|
117
|
+
if Parent in annot and T not in annot:
|
|
118
|
+
annot[NameObject(Parent)][NameObject(V)] = TextStringObject( # noqa
|
|
119
|
+
widget.value
|
|
120
|
+
)
|
|
121
|
+
annot[NameObject(AP)] = TextStringObject(widget.value)
|
|
122
|
+
else:
|
|
123
|
+
annot[NameObject(V)] = TextStringObject(widget.value)
|
|
124
|
+
annot[NameObject(AP)] = TextStringObject(widget.value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def simple_flatten_radio(annot: DictionaryObject) -> None:
|
|
128
|
+
"""Patterns to flatten checkbox annotations."""
|
|
129
|
+
|
|
130
|
+
annot[NameObject(Parent)][NameObject(Ff)] = NumberObject( # noqa
|
|
131
|
+
int(annot[NameObject(Parent)].get(NameObject(Ff), 0)) | READ_ONLY # noqa
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def simple_flatten_generic(annot: DictionaryObject) -> None:
|
|
136
|
+
"""Patterns to flatten generic annotations."""
|
|
137
|
+
|
|
138
|
+
if Parent in annot and Ff not in annot:
|
|
139
|
+
annot[NameObject(Parent)][NameObject(Ff)] = NumberObject( # noqa
|
|
140
|
+
int(annot.get(NameObject(Ff), 0)) | READ_ONLY # noqa
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
annot[NameObject(Ff)] = NumberObject(
|
|
144
|
+
int(annot.get(NameObject(Ff), 0)) | READ_ONLY # noqa
|
|
145
|
+
)
|
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
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
PyPDFForm/__init__.py,sha256=
|
|
1
|
+
PyPDFForm/__init__.py,sha256=G5VFxKnti5RCcw-SqAk6LjAGVJ9fEuPt1CYJQb0QMeA,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=D2lIBZllCdEcLVsSZ6fRGxEYFaTZj9Ex7ksDxbs7g44,1439
|
|
4
|
+
PyPDFForm/coordinate.py,sha256=iOy_KEZO3DKsvwdclxZHLOJyrvbCVqJDK2UKmMpEM8E,7310
|
|
5
|
+
PyPDFForm/filler.py,sha256=DdPGjVxkFNf1XqLmf-Qjr3xOFL0FX2afTCNC75JwJoc,6422
|
|
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=3m_lJ3LmlHY0TxiqrrQzjUCDqrZvhIp9NbMQV1OH28o,3827
|
|
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
12
|
PyPDFForm/wrapper.py,sha256=oO-CYX_RzNm1O2bpifkpWUMzJ326TbR-EHTXmb6OCSc,10328
|
|
@@ -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.16.dist-info/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
|
|
25
|
+
PyPDFForm-1.4.16.dist-info/METADATA,sha256=0AzKUrTQVCkTkdiX_qABWj2EwhnBzEJK51mQLWn7Ajs,5178
|
|
26
|
+
PyPDFForm-1.4.16.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
27
|
+
PyPDFForm-1.4.16.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
|
|
28
|
+
PyPDFForm-1.4.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|