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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains any object users might need."""
3
3
 
4
- __version__ = "1.4.13"
4
+ __version__ = "1.4.15"
5
5
 
6
6
  from .wrapper import FormWrapper, PdfWrapper, PyPDFForm
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
- ANNOTATION_KEY = "/Annots"
30
- ANNOTATION_FIELD_KEY = "/T"
31
- ANNOTATION_RECTANGLE_KEY = "/Rect"
32
- SUBTYPE_KEY = "/Subtype"
33
- WIDGET_SUBTYPE_KEY = "/Widget"
34
- WIDGET_TYPE_KEY = "/FT"
35
- PARENT_KEY = "/Parent"
36
- FIELD_FLAG_KEY = "/Ff"
37
- TEXT_FIELD_IDENTIFIER = "/Tx"
38
- TEXT_VALUE_IDENTIFIER = "/V"
39
- TEXT_VALUE_SHOW_UP_IDENTIFIER = "/AP"
40
- SIGNATURE_FIELD_IDENTIFIER = "/Sig"
41
- TEXT_FIELD_APPEARANCE_IDENTIFIER = "/DA"
42
- SELECTABLE_IDENTIFIER = "/Btn"
43
- TEXT_FIELD_MAX_LENGTH_KEY = "/MaxLen"
44
- TEXT_FIELD_ALIGNMENT_IDENTIFIER = "/Q"
45
- CHOICE_FIELD_IDENTIFIER = "/Ch"
46
- CHOICES_IDENTIFIER = "/Opt"
47
- BUTTON_IDENTIFIER = "/MK"
48
- BUTTON_STYLE_IDENTIFIER = "/CA"
49
- SELECTED_IDENTIFIER = "/AS"
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
- CHECKBOX_SELECT = "/Yes"
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 (ANNOTATION_RECTANGLE_KEY,
11
- COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO, DEFAULT_FONT)
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
- float(widget[ANNOTATION_RECTANGLE_KEY][0])
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[ANNOTATION_RECTANGLE_KEY][0])
55
- y = float(widget[ANNOTATION_RECTANGLE_KEY][1])
56
- width = abs(
57
- float(widget[ANNOTATION_RECTANGLE_KEY][0])
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[ANNOTATION_RECTANGLE_KEY][0]),
76
- float(widget[ANNOTATION_RECTANGLE_KEY][3]) + 5,
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[ANNOTATION_RECTANGLE_KEY][0])
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[ANNOTATION_RECTANGLE_KEY][2]) - string_width
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[ANNOTATION_RECTANGLE_KEY][3]) - string_height / 1.5
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, NameObject, TextStringObject
8
+ from pypdf.generic import DictionaryObject
9
9
 
10
- from .constants import (ANNOTATION_KEY, CHECKBOX_SELECT, SELECTED_IDENTIFIER,
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(ANNOTATION_KEY, []): # noqa
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 isinstance(widget, Checkbox) and widget.value is True:
152
- annot[NameObject(SELECTED_IDENTIFIER)] = NameObject(CHECKBOX_SELECT)
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[NameObject(SELECTED_IDENTIFIER)] = NameObject(
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[NameObject(TEXT_VALUE_IDENTIFIER)] = TextStringObject(
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[NameObject(TEXT_VALUE_IDENTIFIER)] = TextStringObject(
170
- widget.value
171
- )
172
- annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(
173
- widget.value
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 (ANNOTATION_RECTANGLE_KEY, DEFAULT_FONT,
14
- FONT_COLOR_IDENTIFIER, FONT_SIZE_IDENTIFIER)
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[ANNOTATION_RECTANGLE_KEY][0])
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 .constants import (ANNOTATION_FIELD_KEY, BUTTON_IDENTIFIER,
5
- BUTTON_STYLE_IDENTIFIER, CHOICE_FIELD_IDENTIFIER,
6
- CHOICES_IDENTIFIER, FIELD_FLAG_KEY, PARENT_KEY,
7
- SELECTABLE_IDENTIFIER, SIGNATURE_FIELD_IDENTIFIER,
8
- SUBTYPE_KEY, TEXT_FIELD_ALIGNMENT_IDENTIFIER,
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
- ({WIDGET_TYPE_KEY: SIGNATURE_FIELD_IDENTIFIER},),
17
+ ({FT: Sig},),
21
18
  Signature,
22
19
  ),
23
20
  (
24
- ({WIDGET_TYPE_KEY: TEXT_FIELD_IDENTIFIER},),
21
+ ({FT: Tx},),
25
22
  Text,
26
23
  ),
27
24
  (
28
- ({WIDGET_TYPE_KEY: SELECTABLE_IDENTIFIER},),
25
+ ({FT: Btn},),
29
26
  Checkbox,
30
27
  ),
31
28
  (
32
- ({WIDGET_TYPE_KEY: CHOICE_FIELD_IDENTIFIER},),
29
+ ({FT: Ch},),
33
30
  Dropdown,
34
31
  ),
35
32
  (
36
- ({PARENT_KEY: {WIDGET_TYPE_KEY: CHOICE_FIELD_IDENTIFIER}},),
33
+ ({Parent: {FT: Ch}},),
37
34
  Dropdown,
38
35
  ),
39
36
  (
40
- ({PARENT_KEY: {WIDGET_TYPE_KEY: TEXT_FIELD_IDENTIFIER}},),
37
+ ({Parent: {FT: Tx}},),
41
38
  Text,
42
39
  ),
43
40
  (
44
41
  (
45
- {PARENT_KEY: {WIDGET_TYPE_KEY: SELECTABLE_IDENTIFIER}},
46
- {PARENT_KEY: {SUBTYPE_KEY: WIDGET_SUBTYPE_KEY}},
42
+ {Parent: {FT: Btn}},
43
+ {Parent: {Subtype: Widget}},
47
44
  ),
48
45
  Checkbox,
49
46
  ),
50
47
  (
51
- ({PARENT_KEY: {WIDGET_TYPE_KEY: SELECTABLE_IDENTIFIER}},),
48
+ ({Parent: {FT: Btn}},),
52
49
  Radio,
53
50
  ),
54
51
  ]
55
52
 
56
53
  WIDGET_KEY_PATTERNS = [
57
- {ANNOTATION_FIELD_KEY: True},
58
- {PARENT_KEY: {ANNOTATION_FIELD_KEY: True}},
54
+ {T: True},
55
+ {Parent: {T: True}},
59
56
  ]
60
57
 
61
58
  DROPDOWN_CHOICE_PATTERNS = [
62
- {CHOICES_IDENTIFIER: True},
63
- {PARENT_KEY: {CHOICES_IDENTIFIER: True}},
59
+ {Opt: True},
60
+ {Parent: {Opt: True}},
64
61
  ]
65
62
 
66
63
  WIDGET_ALIGNMENT_PATTERNS = [
67
- {TEXT_FIELD_ALIGNMENT_IDENTIFIER: True},
68
- {PARENT_KEY: {TEXT_FIELD_ALIGNMENT_IDENTIFIER: True}},
64
+ {Q: True},
65
+ {Parent: {Q: True}},
69
66
  ]
70
67
 
71
68
  TEXT_FIELD_FLAG_PATTERNS = [
72
- {FIELD_FLAG_KEY: True},
73
- {PARENT_KEY: {FIELD_FLAG_KEY: True}},
69
+ {Ff: True},
70
+ {Parent: {Ff: True}},
74
71
  ]
75
72
 
76
73
  TEXT_FIELD_APPEARANCE_PATTERNS = [
77
- {TEXT_FIELD_APPEARANCE_IDENTIFIER: True},
78
- {PARENT_KEY: {TEXT_FIELD_APPEARANCE_IDENTIFIER: True}},
74
+ {DA: True},
75
+ {Parent: {DA: True}},
79
76
  ]
80
77
 
81
78
  BUTTON_STYLE_PATTERNS = [
82
- {BUTTON_IDENTIFIER: {BUTTON_STYLE_IDENTIFIER: True}},
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 (ANNOTATION_RECTANGLE_KEY, COMB, DEFAULT_FONT_SIZE,
10
- MULTILINE, NEW_LINE_SYMBOL, TEXT_FIELD_MAX_LENGTH_KEY,
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[ANNOTATION_RECTANGLE_KEY]
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
- _widget, widgets[key]
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 len(line_extended) <= text_wrap_length:
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
- while len(each) > text_wrap_length:
366
- result.append(each[:text_wrap_length])
367
- each = each[text_wrap_length:]
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 len(each) + 1 + len(result[-1]) <= text_wrap_length
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(widget: dict, widget_middleware: Text) -> int:
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
- value = widget_middleware.value or ""
391
- value = value.replace(NEW_LINE_SYMBOL, " ")
392
- width = abs(
393
- float(widget[ANNOTATION_RECTANGLE_KEY][0])
394
- - float(widget[ANNOTATION_RECTANGLE_KEY][2])
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(self.read(), widgets)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPDFForm
3
- Version: 1.4.13
3
+ Version: 1.4.15
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -1,15 +1,15 @@
1
- PyPDFForm/__init__.py,sha256=KAGYHIP-vZnPpNmy2UPIQSULuxgByHXmJhj303FFmCg,149
1
+ PyPDFForm/__init__.py,sha256=KgGvTymkLYyv87kBq8HFehcC47PtgmzHc1zc73Z526w,149
2
2
  PyPDFForm/adapter.py,sha256=OgAIwcmukpBqHFBLymd3I7wA7wIdipRqgURZA2Y0Hgo,885
3
- PyPDFForm/constants.py,sha256=qxC6FN_K5kWD5XmwypeTyGLBG778geIGf2kHnHGcXAQ,1791
4
- PyPDFForm/coordinate.py,sha256=EZxqeM3mkPSvhrsdOa-R2wPylmIECvUwMIlpigWRSbI,7854
5
- PyPDFForm/filler.py,sha256=aVmzJZFW_XwX19KLbRWuDCBZ8ek2s8BapLydf44ZbxY,6707
6
- PyPDFForm/font.py,sha256=iXsih_EJ9qe_penqakbQWuP44yUdxhWlQzlpZlMl3uw,4347
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=LQRwSk7jImTYZFfN_DC38mCWFrPrXDgkYtu-yQLI7PA,2340
9
- PyPDFForm/template.py,sha256=bvjrrIz6RZZl2h7MIjpI1g1FGD0mcabCjtseTvVUQTA,13176
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=pXLY84cXuL4X81hvGdmcatp_GV07XXeLfGMdjOT2sx4,10232
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.13.dist-info/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
25
- PyPDFForm-1.4.13.dist-info/METADATA,sha256=1RfinIDEtIhuU7zhUfbvhAzp479KlQ0MH2GrLy7QxuA,5178
26
- PyPDFForm-1.4.13.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
27
- PyPDFForm-1.4.13.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
28
- PyPDFForm-1.4.13.dist-info/RECORD,,
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,,