Skip to content

Commit

Permalink
Merge pull request #740 from chinapandaman/PPF-738
Browse files Browse the repository at this point in the history
PPF-738: implement modify widget key
  • Loading branch information
chinapandaman authored Sep 28, 2024
2 parents 606c789 + 7a11919 commit 3e8ebb1
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[MESSAGES CONTROL]
disable=C0103, R0913, R0902, R0903, R0914, C0209, C0123, R0917
disable=C0103, R0913, R0902, R0903, R0914, C0209, C0123, R0917, C2801
13 changes: 13 additions & 0 deletions PyPDFForm/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ def simple_flatten_generic(annot: DictionaryObject) -> None:
)


def update_annotation_name(annot: DictionaryObject, val: str) -> None:
"""Patterns to update the name of an annotation."""

if Parent in annot and T not in annot:
annot[NameObject(Parent)][NameObject(T)] = TextStringObject( # noqa
val
)
else:
annot[NameObject(T)] = TextStringObject( # noqa
val
)


def update_created_text_field_alignment(annot: DictionaryObject, val: int) -> None:
"""Patterns to update text alignment for text annotations created by the library."""

Expand Down
50 changes: 46 additions & 4 deletions PyPDFForm/template.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# -*- coding: utf-8 -*-
"""Contains helpers for generic template related processing."""

from io import BytesIO
from functools import lru_cache
from sys import maxsize
from typing import Dict, List, Tuple, Union
from typing import Dict, List, Tuple, Union, cast

from pypdf import PdfReader
from pypdf import PdfReader, PdfWriter
from pypdf.generic import DictionaryObject
from reportlab.pdfbase.pdfmetrics import stringWidth

from .constants import (COMB, DEFAULT_FONT_SIZE, MULTILINE, NEW_LINE_SYMBOL,
WIDGET_TYPES, MaxLen, Rect)
WIDGET_TYPES, MaxLen, Rect, Annots)
from .font import (adjust_paragraph_font_size, adjust_text_field_font_size,
auto_detect_font, get_text_field_font_color,
get_text_field_font_size, text_field_font_size)
Expand All @@ -19,7 +21,7 @@
from .middleware.text import Text
from .patterns import (BUTTON_STYLE_PATTERNS, DROPDOWN_CHOICE_PATTERNS,
TEXT_FIELD_FLAG_PATTERNS, WIDGET_ALIGNMENT_PATTERNS,
WIDGET_KEY_PATTERNS, WIDGET_TYPE_PATTERNS)
WIDGET_KEY_PATTERNS, WIDGET_TYPE_PATTERNS, update_annotation_name)
from .utils import find_pattern_match, stream_to_io, traverse_pattern
from .watermark import create_watermarks_and_draw

Expand Down Expand Up @@ -403,3 +405,43 @@ def get_paragraph_auto_wrap_length(widget_middleware: Text) -> int:
result = min(result, len(line))

return result


def update_widget_key(
template: bytes,
widgets: Dict[str, WIDGET_TYPES],
old_key: str,
new_key: str,
index: int,
) -> bytes:
"""Updates the key of a widget."""
# pylint: disable=R0801

pdf = PdfReader(stream_to_io(template))
out = PdfWriter()
out.append(pdf)

tracker = -1

for page in out.pages:
for annot in page.get(Annots, []): # noqa
annot = cast(DictionaryObject, annot.get_object())
key = get_widget_key(annot.get_object())

widget = widgets.get(key)
if widget is None:
continue

if old_key != key:
continue

tracker += 1
if not isinstance(widget, Radio) and tracker != index:
continue

update_annotation_name(annot, new_key)

with BytesIO() as f:
out.write(f)
f.seek(0)
return f.read()
14 changes: 13 additions & 1 deletion PyPDFForm/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .middleware.text import Text
from .template import (build_widgets, dropdown_to_text,
set_character_x_paddings, update_text_field_attributes,
widget_rect_watermarks)
widget_rect_watermarks, update_widget_key)
from .utils import (get_page_streams, merge_two_pdfs, preview_widget_to_draw,
remove_all_widgets)
from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
Expand Down Expand Up @@ -235,6 +235,18 @@ def create_widget(

return self

def update_widget_key(self, old_key: str, new_key: str, index: int = 0) -> PdfWrapper:
"""Updates the key of an existed widget on a PDF form."""

self.__init__(
template=update_widget_key(self.read(), self.widgets, old_key, new_key, index),
global_font=self.global_font,
global_font_size=self.global_font_size,
global_font_color=self.global_font_color,
)

return self

def draw_text(
self,
text: str,
Expand Down
Binary file added pdf_samples/scenario/issues/733.pdf
Binary file not shown.
Binary file added pdf_samples/scenario/issues/733_expected.pdf
Binary file not shown.
Binary file added pdf_samples/test_update_radio_key.pdf
Binary file not shown.
Binary file added pdf_samples/test_update_sejda_key.pdf
Binary file not shown.
21 changes: 21 additions & 0 deletions tests/scenario/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,24 @@ def test_sejda_checkbox(issue_pdf_directory, request):
expected = f.read()
assert len(obj.read()) == len(expected)
assert obj.read() == expected


def test_update_key(issue_pdf_directory, request):
obj = PdfWrapper(os.path.join(issue_pdf_directory, "733.pdf"))

for i in range(1, 10):
obj.update_widget_key("Description[0]", f"Description[{i}]", 1)
obj.update_widget_key("symbol[0]", f"symbol[{i}]", 1)
obj.update_widget_key("tradedate[0]", f"tradedate[{i}]", 1)
obj.update_widget_key("settlementdate[0]", f"settlementdate[{i}]", 1)
obj.update_widget_key("quantity[0]", f"quantity[{i}]", 1)
obj.update_widget_key("costperunit[0]", f"costperunit[{i}]", 1)
obj.update_widget_key("costabasis[0]", f"costabasis[{i}]", 1)

expected_path = os.path.join(issue_pdf_directory, "733_expected.pdf")
request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()
with open(expected_path, "rb+") as f:
expected = f.read()
assert len(obj.preview) == len(expected)
assert obj.preview == expected
37 changes: 37 additions & 0 deletions tests/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,3 +626,40 @@ def test_fill_image(

assert len(obj.read()) == len(expected)
assert obj.stream == expected


def test_update_radio_key(
template_with_radiobutton_stream, pdf_samples, request
):
expected_path = os.path.join(pdf_samples, "test_update_radio_key.pdf")
with open(expected_path, "rb+") as f:
obj = PdfWrapper(template_with_radiobutton_stream)
obj.update_widget_key("radio_3", "RADIO")

request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()

expected = f.read()

assert len(obj.preview) == len(expected)
assert obj.preview == expected


def test_update_sejda_key(
sejda_template, pdf_samples, request
):
expected_path = os.path.join(pdf_samples, "test_update_sejda_key.pdf")
with open(expected_path, "rb+") as f:
obj = PdfWrapper(sejda_template)
obj.update_widget_key("year", "YEAR")
obj.update_widget_key("at_future_date", "FUTURE_DATE")
obj.update_widget_key("purchase_option", "PURCHASE_OPTION")
obj.update_widget_key("buyer_signed_date", "BUYER_SIGNED_DATE")

request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()

expected = f.read()

assert len(obj.preview) == len(expected)
assert obj.preview == expected

0 comments on commit 3e8ebb1

Please sign in to comment.