PyPDFForm 2.4.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/template.py CHANGED
@@ -1,90 +1,52 @@
1
1
  # -*- coding: utf-8 -*-
2
- """Provides template processing utilities for PDF forms.
3
-
4
- This module contains functions for:
5
- - Building and managing PDF form widgets
6
- - Handling widget properties and attributes
7
- - Processing text fields and paragraphs
8
- - Managing widget keys and names
9
- - Supporting comb fields and multiline text
2
+ """
3
+ Module for handling PDF form templates.
4
+
5
+ This module provides functionalities to extract, build, and update widgets
6
+ in PDF form templates. It leverages the pypdf library for PDF manipulation
7
+ and defines specific patterns for identifying and constructing different
8
+ types of widgets.
10
9
  """
11
10
 
12
11
  from functools import lru_cache
13
12
  from io import BytesIO
14
- from sys import maxsize
15
13
  from typing import Dict, List, Tuple, Union, cast
16
14
 
17
15
  from pypdf import PdfReader, PdfWriter
18
16
  from pypdf.generic import DictionaryObject
19
- from reportlab.pdfbase.pdfmetrics import stringWidth
20
-
21
- from .constants import (COMB, DEFAULT_BORDER_WIDTH, DEFAULT_FONT_SIZE,
22
- MULTILINE, NEW_LINE_SYMBOL, WIDGET_TYPES, Annots,
23
- MaxLen, Parent, Rect, T)
24
- from .font import (adjust_paragraph_font_size, adjust_text_field_font_size,
25
- auto_detect_font, get_text_field_font_color,
26
- get_text_field_font_size, text_field_font_size)
27
- from .middleware.checkbox import Checkbox
17
+
18
+ from .constants import WIDGET_TYPES, Annots, MaxLen, Parent, T
28
19
  from .middleware.dropdown import Dropdown
29
20
  from .middleware.radio import Radio
30
21
  from .middleware.text import Text
31
- from .patterns import (BACKGROUND_COLOR_PATTERNS, BORDER_COLOR_PATTERNS,
32
- BORDER_DASH_ARRAY_PATTERNS, BORDER_STYLE_PATTERNS,
33
- BORDER_WIDTH_PATTERNS, BUTTON_STYLE_PATTERNS,
34
- DROPDOWN_CHOICE_PATTERNS, TEXT_FIELD_FLAG_PATTERNS,
35
- WIDGET_DESCRIPTION_PATTERNS, WIDGET_KEY_PATTERNS,
36
- WIDGET_TYPE_PATTERNS, update_annotation_name)
37
- from .utils import (extract_widget_property, find_pattern_match, handle_color,
38
- stream_to_io)
22
+ from .patterns import (DROPDOWN_CHOICE_PATTERNS, WIDGET_DESCRIPTION_PATTERNS,
23
+ WIDGET_KEY_PATTERNS, WIDGET_TYPE_PATTERNS,
24
+ update_annotation_name)
25
+ from .utils import extract_widget_property, find_pattern_match, stream_to_io
39
26
 
40
27
 
41
- def set_character_x_paddings(
28
+ def build_widgets(
42
29
  pdf_stream: bytes,
43
- widgets: Dict[str, WIDGET_TYPES],
44
30
  use_full_widget_name: bool,
45
31
  ) -> Dict[str, WIDGET_TYPES]:
46
- """Calculates and sets character spacing for comb text fields in PDF forms.
47
-
48
- This function processes each widget in the PDF form and calculates the horizontal spacing
49
- between characters for comb text fields (fixed-width text fields). The spacing is stored
50
- in the widget's character_paddings property.
51
-
52
- Args:
53
- pdf_stream (bytes): PDF form as bytes
54
- widgets (Dict[str, WIDGET_TYPES]): Dictionary of widget middleware objects
55
- use_full_widget_name (bool): Whether to use full widget names including parent names
56
-
57
- Returns:
58
- Dict[str, WIDGET_TYPES]: Updated widget dictionary with character paddings set for comb fields
59
32
  """
33
+ Builds a dictionary of widgets from a PDF stream.
60
34
 
61
- for _widgets in get_widgets_by_page(pdf_stream).values():
62
- for widget in _widgets:
63
- key = get_widget_key(widget, use_full_widget_name)
64
- _widget = widgets[key]
65
-
66
- if isinstance(_widget, Text) and _widget.comb is True:
67
- _widget.character_paddings = get_character_x_paddings(widget, _widget)
68
-
69
- return widgets
70
-
71
-
72
- def build_widgets(
73
- pdf_stream: bytes,
74
- use_full_widget_name: bool,
75
- render_widgets: bool,
76
- ) -> Dict[str, WIDGET_TYPES]:
77
- """Constructs widget middleware objects from a PDF form.
35
+ This function parses a PDF stream to identify and construct widgets
36
+ present in the PDF form. It iterates through each page and its annotations,
37
+ extracting widget properties such as key, description, max length (for text fields),
38
+ and choices (for dropdowns). The constructed widgets are stored in a dictionary
39
+ where the keys are the widget keys and the values are the widget objects.
78
40
 
79
41
  Args:
80
- pdf_stream: PDF form as bytes
81
- use_full_widget_name: Whether to include parent widget names
82
- render_widgets: Whether widgets should be rendered visibly
42
+ pdf_stream (bytes): The PDF stream to parse.
43
+ use_full_widget_name (bool): Whether to use the full widget name
44
+ (including parent names) as the widget key.
83
45
 
84
46
  Returns:
85
- Dict[str, WIDGET_TYPES]: Dictionary mapping field names to widgets
47
+ Dict[str, WIDGET_TYPES]: A dictionary of widgets, where keys are widget
48
+ keys and values are widget objects.
86
49
  """
87
-
88
50
  results = {}
89
51
 
90
52
  for widgets in get_widgets_by_page(pdf_stream).values():
@@ -92,47 +54,24 @@ def build_widgets(
92
54
  key = get_widget_key(widget, use_full_widget_name)
93
55
  _widget = construct_widget(widget, key)
94
56
  if _widget is not None:
95
-
96
- _widget.render_widget = render_widgets
97
57
  _widget.desc = extract_widget_property(
98
58
  widget, WIDGET_DESCRIPTION_PATTERNS, None, str
99
59
  )
100
- _widget.border_color = extract_widget_property(
101
- widget, BORDER_COLOR_PATTERNS, None, handle_color
102
- )
103
- _widget.background_color = extract_widget_property(
104
- widget, BACKGROUND_COLOR_PATTERNS, None, handle_color
105
- )
106
- _widget.border_width = extract_widget_property(
107
- widget, BORDER_WIDTH_PATTERNS, DEFAULT_BORDER_WIDTH, float
108
- )
109
- _widget.border_style = extract_widget_property(
110
- widget, BORDER_STYLE_PATTERNS, None, str
111
- )
112
- _widget.dash_array = extract_widget_property(
113
- widget, BORDER_DASH_ARRAY_PATTERNS, None, list
114
- )
115
60
 
116
61
  if isinstance(_widget, Text):
62
+ # mostly for schema for now
117
63
  _widget.max_length = get_text_field_max_length(widget)
118
- if _widget.max_length is not None and is_text_field_comb(widget):
119
- _widget.comb = True
120
-
121
- if isinstance(_widget, (Checkbox, Radio)):
122
- _widget.button_style = (
123
- extract_widget_property(
124
- widget, BUTTON_STYLE_PATTERNS, None, str
125
- )
126
- or _widget.button_style
127
- )
128
64
 
129
65
  if isinstance(_widget, Dropdown):
130
- _widget.choices = get_dropdown_choices(widget)
66
+ # actually used for filling value
67
+ # doesn't trigger hook
68
+ _widget.__dict__["choices"] = get_dropdown_choices(widget)
131
69
 
132
70
  if isinstance(_widget, Radio):
133
71
  if key not in results:
134
72
  results[key] = _widget
135
73
 
74
+ # for schema
136
75
  results[key].number_of_options += 1
137
76
  continue
138
77
 
@@ -141,103 +80,22 @@ def build_widgets(
141
80
  return results
142
81
 
143
82
 
144
- def dropdown_to_text(dropdown: Dropdown) -> Text:
145
- """Converts a dropdown widget to a text widget while preserving properties.
146
-
147
- Args:
148
- dropdown: Dropdown widget to convert
149
-
150
- Returns:
151
- Text: New text widget with dropdown's selected value and styling
152
- """
153
-
154
- result = Text(dropdown.name)
155
- result.border_color = dropdown.border_color
156
- result.background_color = dropdown.background_color
157
- result.border_width = dropdown.border_width
158
- result.render_widget = dropdown.render_widget
159
-
160
- if dropdown.value is not None:
161
- result.value = (
162
- dropdown.choices[dropdown.value]
163
- if dropdown.value < len(dropdown.choices)
164
- else ""
165
- )
166
-
167
- return result
168
-
169
-
170
- def update_text_field_attributes(
171
- template_stream: bytes,
172
- widgets: Dict[str, WIDGET_TYPES],
173
- use_full_widget_name: bool,
174
- ) -> None:
175
- """Update text field properties in a PDF form template.
176
-
177
- Processes text field widgets in the template to update their visual attributes including:
178
- - Font properties (family, size, color)
179
- - Text alignment and wrapping behavior
180
- - Auto-sizing of text to fit within field bounds
181
- - Multi-line text field formatting
182
-
183
- Args:
184
- template_stream: Raw bytes of the PDF template containing form fields
185
- widgets: Dictionary mapping field names to widget objects to update
186
- use_full_widget_name: If True, uses full hierarchical widget names including parent keys.
187
- When False (default), uses simple field names.
188
-
189
- Returns:
190
- None: Modifies widget objects in-place
191
-
192
- Note:
193
- This function modifies the widget objects in-place and does not return anything.
194
- Changes include font properties, text alignment, and auto-wrapping settings.
195
- """
196
-
197
- for _widgets in get_widgets_by_page(template_stream).values():
198
- for _widget in _widgets:
199
- key = get_widget_key(_widget, use_full_widget_name)
200
-
201
- if isinstance(widgets[key], Text):
202
- should_adjust_font_size = False
203
- is_paragraph = is_text_multiline(_widget)
204
- if widgets[key].font is None:
205
- widgets[key].font = auto_detect_font(_widget)
206
- if widgets[key].font_size is None:
207
- template_font_size = get_text_field_font_size(_widget)
208
- widgets[key].font_size = template_font_size or (
209
- text_field_font_size(_widget)
210
- if not is_paragraph
211
- else DEFAULT_FONT_SIZE
212
- )
213
- should_adjust_font_size = (
214
- not template_font_size and widgets[key].max_length is None
215
- )
216
- if widgets[key].font_color is None:
217
- widgets[key].font_color = get_text_field_font_color(_widget)
218
- if is_paragraph and widgets[key].text_wrap_length is None:
219
- widgets[key].text_lines = get_paragraph_lines(_widget, widgets[key])
220
- widgets[key].text_wrap_length = get_paragraph_auto_wrap_length(
221
- widgets[key]
222
- )
223
- if widgets[key].value and should_adjust_font_size:
224
- if is_paragraph:
225
- adjust_paragraph_font_size(_widget, widgets[key])
226
- else:
227
- adjust_text_field_font_size(_widget, widgets[key])
228
-
229
-
230
83
  @lru_cache()
231
84
  def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
232
- """Extracts all form widgets from a PDF grouped by page number.
85
+ """
86
+ Retrieves widgets from a PDF stream, organized by page number.
87
+
88
+ This function parses a PDF stream and extracts all the widgets (annotations)
89
+ present on each page. It returns a dictionary where the keys are the page
90
+ numbers and the values are lists of widget dictionaries.
233
91
 
234
92
  Args:
235
- pdf: PDF form as bytes
93
+ pdf (bytes): The PDF stream to parse.
236
94
 
237
95
  Returns:
238
- Dict[int, List[dict]]: Mapping of page numbers to widget dictionaries
96
+ Dict[int, List[dict]]: A dictionary where keys are page numbers (1-indexed)
97
+ and values are lists of widget dictionaries.
239
98
  """
240
-
241
99
  pdf_file = PdfReader(stream_to_io(pdf))
242
100
 
243
101
  result = {}
@@ -261,19 +119,21 @@ def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
261
119
 
262
120
 
263
121
  def get_widget_key(widget: dict, use_full_widget_name: bool) -> str:
264
- """Constructs a widget's key, optionally including parent names based on `use_full_widget_name`.
122
+ """
123
+ Extracts the widget key from a widget dictionary.
265
124
 
266
- This function recursively builds the full key by appending parent names if `use_full_widget_name` is True.
267
- If `use_full_widget_name` is False, it returns the widget's key directly.
125
+ This function extracts the widget key from a widget dictionary based on
126
+ predefined patterns. If `use_full_widget_name` is True, it recursively
127
+ constructs the full widget name by concatenating the parent widget keys.
268
128
 
269
129
  Args:
270
- widget (dict): PDF widget dictionary.
271
- use_full_widget_name (bool): Whether to include parent widget names in the key.
130
+ widget (dict): The widget dictionary to extract the key from.
131
+ use_full_widget_name (bool): Whether to use the full widget name
132
+ (including parent names) as the widget key.
272
133
 
273
134
  Returns:
274
- str: Full key in format "parent.child" if parent exists and `use_full_widget_name` is True, otherwise the widget's key.
135
+ str: The extracted widget key.
275
136
  """
276
-
277
137
  key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
278
138
  if not use_full_widget_name:
279
139
  return key
@@ -281,7 +141,7 @@ def get_widget_key(widget: dict, use_full_widget_name: bool) -> str:
281
141
  if (
282
142
  Parent in widget
283
143
  and T in widget[Parent].get_object()
284
- and widget[Parent].get_object()[T] != key
144
+ and widget[Parent].get_object()[T] != key # sejda case
285
145
  ):
286
146
  key = (
287
147
  f"{get_widget_key(widget[Parent].get_object(), use_full_widget_name)}.{key}"
@@ -291,17 +151,20 @@ def get_widget_key(widget: dict, use_full_widget_name: bool) -> str:
291
151
 
292
152
 
293
153
  def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
294
- """Creates appropriate widget middleware based on PDF widget type.
154
+ """
155
+ Constructs a widget object based on the widget dictionary and key.
156
+
157
+ This function determines the type of widget based on predefined patterns
158
+ and constructs the corresponding widget object.
295
159
 
296
160
  Args:
297
- widget: PDF widget dictionary
298
- key: Field name/key for the widget
161
+ widget (dict): The widget dictionary to construct the object from.
162
+ key (str): The key of the widget.
299
163
 
300
164
  Returns:
301
- Union[WIDGET_TYPES, None]: Appropriate widget middleware instance
302
- or None if type not recognized
165
+ Union[WIDGET_TYPES, None]: The constructed widget object, or None
166
+ if the widget type is not recognized.
303
167
  """
304
-
305
168
  result = None
306
169
  for each in WIDGET_TYPE_PATTERNS:
307
170
  patterns, _type = each
@@ -315,74 +178,31 @@ def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
315
178
 
316
179
 
317
180
  def get_text_field_max_length(widget: dict) -> Union[int, None]:
318
- """Extracts max length constraint from a text field widget.
319
-
320
- Args:
321
- widget: PDF widget dictionary
322
-
323
- Returns:
324
- Union[int, None]: Max character length if specified, otherwise None
325
181
  """
326
-
327
- return int(widget[MaxLen]) or None if MaxLen in widget else None
328
-
329
-
330
- def check_field_flag_bit(widget: dict, bit: int) -> bool:
331
- """Tests whether a specific flag bit is set in a widget's field flags.
182
+ Extracts the maximum length of a text field from a widget dictionary.
332
183
 
333
184
  Args:
334
- widget: PDF widget dictionary
335
- bit: Flag bit to check (e.g. COMB, MULTILINE)
185
+ widget (dict): The widget dictionary to extract the max length from.
336
186
 
337
187
  Returns:
338
- bool: True if the bit is set, False otherwise
188
+ Union[int, None]: The maximum length of the text field, or None
189
+ if the max length is not specified.
339
190
  """
191
+ return int(widget[MaxLen]) or None if MaxLen in widget else None
340
192
 
341
- field_flag = extract_widget_property(widget, TEXT_FIELD_FLAG_PATTERNS, None, int)
342
-
343
- if field_flag is None:
344
- return False
345
-
346
- return bool(int(field_flag) & bit)
347
-
348
-
349
- def is_text_field_comb(widget: dict) -> bool:
350
- """Determines if a text field uses comb formatting (fixed character spacing).
351
-
352
- Args:
353
- widget: PDF widget dictionary
354
-
355
- Returns:
356
- bool: True if field uses comb formatting
357
- """
358
-
359
- return check_field_flag_bit(widget, COMB)
360
-
361
-
362
- def is_text_multiline(widget: dict) -> bool:
363
- """Determines if a text field supports multiple lines/paragraphs.
364
-
365
- Args:
366
- widget: PDF widget dictionary
367
193
 
368
- Returns:
369
- bool: True if field supports multiline text
194
+ def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
370
195
  """
196
+ Extracts the choices from a dropdown widget dictionary.
371
197
 
372
- return check_field_flag_bit(widget, MULTILINE)
373
-
374
-
375
- def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
376
- """Extracts available choices from a dropdown widget.
198
+ This function extracts the choices from a dropdown widget dictionary.
377
199
 
378
200
  Args:
379
- widget: PDF widget dictionary
201
+ widget (dict): The widget dictionary to extract the choices from.
380
202
 
381
203
  Returns:
382
- Union[Tuple[str, ...], None]: Tuple of choice strings if found,
383
- otherwise None
204
+ Union[Tuple[str, ...], None]: A tuple of strings representing the choices in the dropdown, or None if the choices are not specified.
384
205
  """
385
-
386
206
  return tuple(
387
207
  (each if isinstance(each, str) else str(each[1]))
388
208
  for each in extract_widget_property(
@@ -391,176 +211,6 @@ def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
391
211
  )
392
212
 
393
213
 
394
- def get_char_rect_width(widget: dict, widget_middleware: Text) -> float:
395
- """Calculates per-character width for comb text fields.
396
-
397
- Args:
398
- widget: PDF widget dictionary
399
- widget_middleware: Text middleware instance
400
-
401
- Returns:
402
- float: Width in points allocated per character
403
- """
404
-
405
- rect_width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
406
- return rect_width / widget_middleware.max_length
407
-
408
-
409
- def get_character_x_paddings(widget: dict, widget_middleware: Text) -> List[float]:
410
- """Calculates horizontal positioning for each character in comb fields.
411
-
412
- Args:
413
- widget: PDF widget dictionary
414
- widget_middleware: Text middleware instance
415
-
416
- Returns:
417
- List[float]: X-offsets for centering each character in its comb cell
418
- """
419
-
420
- length = min(len(widget_middleware.value or ""), widget_middleware.max_length)
421
- char_rect_width = get_char_rect_width(widget, widget_middleware)
422
-
423
- result = []
424
-
425
- current_x = 0
426
- for char in (widget_middleware.value or "")[:length]:
427
- current_mid_point = current_x + char_rect_width / 2
428
- result.append(
429
- current_mid_point
430
- - stringWidth(char, widget_middleware.font, widget_middleware.font_size) / 2
431
- )
432
- current_x += char_rect_width
433
-
434
- return result
435
-
436
-
437
- def split_characters_into_lines(
438
- split_by_new_line_symbol: List[str], middleware: Text, width: float
439
- ) -> List[str]:
440
- """Splits text into lines that fit within a paragraph field's width.
441
-
442
- Args:
443
- split_by_new_line_symbol: Text already split by newlines
444
- middleware: Text middleware with font properties
445
- width: Available width in points
446
-
447
- Returns:
448
- List[str]: Lines of text fitting within the specified width
449
- """
450
-
451
- lines = []
452
- for line in split_by_new_line_symbol:
453
- characters = line.split(" ")
454
- current_line = ""
455
- for each in characters:
456
- line_extended = f"{current_line} {each}" if current_line else each
457
- if (
458
- stringWidth(line_extended, middleware.font, middleware.font_size)
459
- <= width
460
- ):
461
- current_line = line_extended
462
- else:
463
- lines.append(current_line)
464
- current_line = each
465
- lines.append(
466
- current_line + NEW_LINE_SYMBOL
467
- if len(split_by_new_line_symbol) > 1
468
- else current_line
469
- )
470
-
471
- return lines
472
-
473
-
474
- def adjust_each_line(lines: List[str], middleware: Text, width: float) -> List[str]:
475
- """Optimizes line breaks to minimize empty space in paragraph fields.
476
-
477
- Args:
478
- lines: Text lines from split_characters_into_lines()
479
- middleware: Text middleware with font properties
480
- width: Available width in points
481
-
482
- Returns:
483
- List[str]: Optimized lines with balanced word wrapping
484
- """
485
-
486
- result = []
487
- for each in lines:
488
- tracker = ""
489
- for char in each:
490
- check = tracker + char
491
- if stringWidth(check, middleware.font, middleware.font_size) > width:
492
- result.append(tracker)
493
- tracker = char
494
- else:
495
- tracker = check
496
-
497
- each = tracker
498
- if each:
499
- if (
500
- result
501
- and stringWidth(
502
- f"{each} {result[-1]}",
503
- middleware.font,
504
- middleware.font_size,
505
- )
506
- <= width
507
- and NEW_LINE_SYMBOL not in result[-1]
508
- ):
509
- result[-1] = f"{result[-1]}{each} "
510
- else:
511
- result.append(f"{each} ")
512
-
513
- for i, each in enumerate(result):
514
- result[i] = each.replace(NEW_LINE_SYMBOL, "")
515
-
516
- if result:
517
- result[-1] = result[-1][:-1]
518
-
519
- return result
520
-
521
-
522
- def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
523
- """Generates properly wrapped lines for a paragraph text field.
524
-
525
- Args:
526
- widget: PDF widget dictionary
527
- widget_middleware: Text middleware instance
528
-
529
- Returns:
530
- List[str]: Wrapped lines fitting the paragraph field dimensions
531
- """
532
-
533
- value = widget_middleware.value or ""
534
- if widget_middleware.max_length is not None:
535
- value = value[: widget_middleware.max_length]
536
-
537
- width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
538
-
539
- split_by_new_line_symbol = value.split(NEW_LINE_SYMBOL)
540
- lines = split_characters_into_lines(
541
- split_by_new_line_symbol, widget_middleware, width
542
- )
543
-
544
- return adjust_each_line(lines, widget_middleware, width)
545
-
546
-
547
- def get_paragraph_auto_wrap_length(widget_middleware: Text) -> int:
548
- """Determines optimal line length for paragraph text wrapping.
549
-
550
- Args:
551
- widget_middleware: Text middleware instance
552
-
553
- Returns:
554
- int: Suggested maximum characters per line
555
- """
556
-
557
- result = maxsize
558
- for line in widget_middleware.text_lines:
559
- result = min(result, len(line))
560
-
561
- return result
562
-
563
-
564
214
  def update_widget_keys(
565
215
  template: bytes,
566
216
  widgets: Dict[str, WIDGET_TYPES],
@@ -568,20 +218,23 @@ def update_widget_keys(
568
218
  new_keys: List[str],
569
219
  indices: List[int],
570
220
  ) -> bytes:
571
- """Renames widget fields in a PDF template.
221
+ """
222
+ Updates the keys of widgets in a PDF template.
223
+
224
+ This function updates the keys of widgets in a PDF template based on the provided old keys and new keys.
225
+ It iterates through each page and annotation, finds the widgets with the old keys, and updates their names with the corresponding new keys.
226
+ The `indices` parameter is used when multiple widgets have the same name, to differentiate which one to update.
572
227
 
573
228
  Args:
574
- template: PDF form as bytes
575
- widgets: Dictionary of widget middleware
576
- old_keys: List of current field names
577
- new_keys: List of new field names
578
- indices: List of indices for handling radio button groups
229
+ template (bytes): The PDF template to update.
230
+ widgets (Dict[str, WIDGET_TYPES]): A dictionary of widgets in the template.
231
+ old_keys (List[str]): A list of the old widget keys to be replaced.
232
+ new_keys (List[str]): A list of the new widget keys to replace the old keys.
233
+ indices (List[int]): A list of indices to handle the case where multiple widgets have the same name.
579
234
 
580
235
  Returns:
581
- bytes: Modified PDF with updated field names
236
+ bytes: The updated PDF template as a byte stream.
582
237
  """
583
- # pylint: disable=R0801
584
-
585
238
  pdf = PdfReader(stream_to_io(template))
586
239
  out = PdfWriter()
587
240
  out.append(pdf)