Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PPF-560 add pushbutton field, possibility to trasform it in image field #744

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion PyPDFForm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .middleware.checkbox import Checkbox
from .middleware.dropdown import Dropdown
from .middleware.image import Image
from .middleware.pushbutton import Pushbutton
from .middleware.radio import Radio
from .middleware.signature import Signature
from .middleware.text import Text
Expand All @@ -23,13 +24,17 @@
]
VERSION_IDENTIFIER_PREFIX = b"%PDF-"

WIDGET_TYPES = Union[Text, Checkbox, Radio, Dropdown, Signature, Image]
WIDGET_TYPES = Union[Text, Checkbox, Radio, Dropdown, Signature, Image, Pushbutton]

DEPRECATION_NOTICE = "{} will be deprecated soon. Use {} instead."

Annots = "/Annots"
A = "/A"
JS = "/JS"
Type = "/Type"
Action = "/Action"
S = "/S"
JavaScript = "/JavaScript"
T = "/T"
Rect = "/Rect"
FT = "/FT"
Expand Down
26 changes: 23 additions & 3 deletions PyPDFForm/filler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
from .image import any_image_to_jpg
from .middleware.checkbox import Checkbox
from .middleware.dropdown import Dropdown
from .middleware.pushbutton import Pushbutton
from .middleware.image import Image
from .middleware.radio import Radio
from .middleware.signature import Signature
from .middleware.text import Text
from .patterns import (simple_flatten_generic, simple_flatten_radio,
simple_update_checkbox_value,
simple_update_dropdown_value, simple_update_radio_value,
simple_update_text_value)
simple_update_text_value, simple_set_image_field)
from .template import get_widget_key, get_widgets_by_page
from .utils import checkbox_radio_to_draw, stream_to_io
from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
Expand Down Expand Up @@ -75,6 +76,23 @@ def signature_image_handler(

return any_image_to_draw

def pushbutton_to_image_handler(template: bytes, widget_name: str) -> bytes:
pdf = PdfReader(stream_to_io(template))
out = PdfWriter()
out.append(pdf)

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())
if key != widget_name:
continue
simple_set_image_field(annot)

with BytesIO() as f:
out.write(f)
f.seek(0)
return f.read()

def text_handler(
widget: dict, middleware: Text
Expand Down Expand Up @@ -128,9 +146,11 @@ def fill(
widget_dict, widgets[key], radio_button_tracker
)
elif isinstance(widgets[key], (Signature, Image)):
any_image_to_draw |= signature_image_handler(
any_image_to_draw = signature_image_handler(
widget_dict, widgets[key], images_to_draw[page]
)
) or any_image_to_draw
elif isinstance(widgets[key], (Pushbutton,)):
pass
else:
to_draw, x, y, text_needs_to_be_drawn = text_handler(
widget_dict, widgets[key]
Expand Down
8 changes: 8 additions & 0 deletions PyPDFForm/middleware/pushbutton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""Contains Pushbutton middleware."""

from .base import Widget


class Pushbutton(Widget):
"""A class to represent a pushbutton widget."""
8 changes: 7 additions & 1 deletion PyPDFForm/middleware/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ def sample_value(self) -> str:
def stream(self) -> Union[bytes, None]:
"""Converts the value of the signature field image to a stream."""

return (
stream = (
fp_or_f_obj_or_stream_to_stream(self.value)
if self.value is not None
else None
)

return (
stream
if stream != b''
else None
)
19 changes: 18 additions & 1 deletion PyPDFForm/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

from .constants import (AP, AS, CA, DA, DV, FT, IMAGE_FIELD_IDENTIFIER, JS, MK,
MULTILINE, READ_ONLY, A, Btn, Ch, Ff, N, Off, Opt,
Parent, Q, Sig, T, Tx, V, Yes)
Parent, Q, Sig, T, Tx, V, Yes, Type, Action, S, JavaScript)
from .middleware.checkbox import Checkbox
from .middleware.dropdown import Dropdown
from .middleware.image import Image
from .middleware.pushbutton import Pushbutton
from .middleware.radio import Radio
from .middleware.signature import Signature
from .middleware.text import Text
Expand All @@ -19,6 +20,13 @@
({A: {JS: IMAGE_FIELD_IDENTIFIER}},),
Image,
),
(
(
{FT: Btn},
{Ff: 17},
),
Pushbutton,
),
(
({FT: Sig},),
Signature,
Expand Down Expand Up @@ -139,6 +147,15 @@ def simple_update_text_value(annot: DictionaryObject, widget: Text) -> None:
annot[NameObject(AP)] = TextStringObject(widget.value)


def simple_set_image_field(annot: DictionaryObject) -> None:
"""Patterns to set for an image field."""

annot[NameObject(A)] = DictionaryObject({
NameObject(Type): NameObject(Action),
NameObject(S): NameObject(JavaScript),
NameObject(JS): TextStringObject(IMAGE_FIELD_IDENTIFIER),
})

def simple_flatten_radio(annot: DictionaryObject) -> None:
"""Patterns to flatten checkbox annotations."""

Expand Down
9 changes: 9 additions & 0 deletions PyPDFForm/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
auto_detect_font, get_text_field_font_color,
get_text_field_font_size, text_field_font_size)
from .middleware.checkbox import Checkbox
from .middleware.pushbutton import Pushbutton
from .middleware.image import Image
from .middleware.dropdown import Dropdown
from .middleware.radio import Radio
from .middleware.text import Text
Expand Down Expand Up @@ -111,6 +113,13 @@ def dropdown_to_text(dropdown: Dropdown) -> Text:

return result

def pushbutton_to_image(pushbutton: Pushbutton) -> Image:
"""Converts a dropdown widget to a text widget."""

result = Image(pushbutton.name)

return result


def update_text_field_attributes(
template_stream: bytes,
Expand Down
17 changes: 15 additions & 2 deletions PyPDFForm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from .constants import (BUTTON_STYLES, DEFAULT_CHECKBOX_STYLE, DEFAULT_FONT,
DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
DEFAULT_RADIO_STYLE, PREVIEW_FONT_COLOR, WIDGET_TYPES)
DEFAULT_RADIO_STYLE, PREVIEW_FONT_COLOR, WIDGET_TYPES, Ff)
from .middleware.checkbox import Checkbox
from .middleware.radio import Radio
from .middleware.text import Text
Expand Down Expand Up @@ -122,14 +122,27 @@ def find_pattern_match(pattern: dict, widget: Union[dict, DictionaryObject]) ->
):
result = find_pattern_match(pattern[key], value)
else:
if isinstance(pattern[key], tuple):
if key == Ff:
result = check_feature_flags(value, pattern[key])
elif isinstance(pattern[key], tuple):
result = value in pattern[key]
else:
result = pattern[key] == value
if result:
return result
return False

def check_feature_flags(value: int, bits: int|tuple) -> bool:
"""Checks if a int value has a bit set """

if not isinstance(bits, tuple):
bits = (bits,)

for bit in bits:
if not (value & 2**(bit - 1)):
return False

return True

def traverse_pattern(
pattern: dict, widget: Union[dict, DictionaryObject]
Expand Down
18 changes: 10 additions & 8 deletions PyPDFForm/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
NEW_LINE_SYMBOL, VERSION_IDENTIFIER_PREFIX,
VERSION_IDENTIFIERS)
from .coordinate import generate_coordinate_grid
from .filler import fill, simple_fill
from .filler import fill, simple_fill, pushbutton_to_image_handler
from .font import register_font
from .image import any_image_to_jpg, rotate_image
from .middleware.dropdown import Dropdown
from .middleware.text import Text
from .template import (build_widgets, dropdown_to_text,
from .template import (build_widgets, dropdown_to_text, pushbutton_to_image,
set_character_x_paddings, update_text_field_attributes,
widget_rect_watermarks)
from .utils import (get_page_streams, merge_two_pdfs, preview_widget_to_draw,
Expand All @@ -39,28 +39,31 @@ def __init__(

super().__init__()
self.stream = fp_or_f_obj_or_stream_to_stream(template)
self.widgets = build_widgets(self.stream) if self.stream else {}

def read(self) -> bytes:
"""Reads the file stream of a PDF form."""

return self.stream

def pushbutton_field_to_image_field(self, widget_name: str):
self.widgets[widget_name] = pushbutton_to_image(self.widgets[widget_name])
self.stream = pushbutton_to_image_handler(self.read(), widget_name)

def fill(
self,
data: Dict[str, Union[str, bool, int]],
**kwargs,
) -> FormWrapper:
"""Fills a PDF form."""

widgets = build_widgets(self.stream) if self.stream else {}

for key, value in data.items():
if key in widgets:
widgets[key].value = value
if key in self.widgets:
self.widgets[key].value = value

self.stream = simple_fill(
self.read(),
widgets,
self.widgets,
flatten=kwargs.get("flatten", False),
adobe_mode=kwargs.get("adobe_mode", False),
)
Expand All @@ -79,7 +82,6 @@ def __init__(
"""Constructs all attributes for the object."""

super().__init__(template)
self.widgets = build_widgets(self.stream) if self.stream else {}

self.global_font = kwargs.get("global_font")
self.global_font_size = kwargs.get("global_font_size")
Expand Down
3 changes: 2 additions & 1 deletion tests/scenario/test_existed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os

from PyPDFForm import PdfWrapper
from PyPDFForm.middleware import pushbutton


def test_illinois_gun_bill_of_sale(existed_pdf_directory, request):
Expand Down Expand Up @@ -189,4 +190,4 @@ def test_illinois_real_estate_power_of_attorney_form(existed_pdf_directory, requ
def test_clear_form_button_not_checkbox(existed_pdf_directory):
obj = PdfWrapper(os.path.join(existed_pdf_directory, "ds11_pdf.pdf"))

assert "Clear" not in obj.widgets
assert "Clear" in obj.widgets and isinstance(obj.widgets["Clear"], pushbutton.Pushbutton)