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-707: support create center/right alignment text field #708

Merged
merged 8 commits into from
Aug 21, 2024
Merged
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
11 changes: 11 additions & 0 deletions PyPDFForm/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,14 @@ def simple_flatten_generic(annot: DictionaryObject) -> None:
annot[NameObject(Ff)] = NumberObject(
int(annot.get(NameObject(Ff), 0)) | READ_ONLY # noqa
)


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

annot[NameObject(Q)] = NumberObject(val)


NON_ACRO_FORM_PARAM_TO_FUNC = {
"alignment": update_created_text_field_alignment
}
37 changes: 35 additions & 2 deletions PyPDFForm/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
"""Contains base class for all widgets to create."""

from io import BytesIO
from typing import List
from typing import List, cast

from pypdf import PdfReader
from pypdf import PdfReader, PdfWriter
from pypdf.generic import DictionaryObject
from reportlab.lib.colors import Color
from reportlab.pdfgen.canvas import Canvas

from ..constants import Annots
from ..template import get_widget_key
from ..patterns import NON_ACRO_FORM_PARAM_TO_FUNC
from ..utils import stream_to_io


Expand All @@ -16,6 +20,7 @@ class Widget:

USER_PARAMS = []
COLOR_PARAMS = []
ALLOWED_NON_ACRO_FORM_PARAMS = []
NONE_DEFAULTS = []
ACRO_FORM_FUNC = ""

Expand All @@ -36,6 +41,7 @@ def __init__(
"x": x,
"y": y,
}
self.non_acro_form_params = []

for each in self.USER_PARAMS:
user_input, param = each
Expand All @@ -51,6 +57,10 @@ def __init__(
elif user_input in self.NONE_DEFAULTS:
self.acro_form_params[param] = None

for each in self.ALLOWED_NON_ACRO_FORM_PARAMS:
if each in kwargs:
self.non_acro_form_params.append((each, kwargs.get(each)))

def watermarks(self, stream: bytes) -> List[bytes]:
"""Returns a list of watermarks after creating the widget."""

Expand All @@ -76,3 +86,26 @@ def watermarks(self, stream: bytes) -> List[bytes]:
watermark.read() if i == self.page_number - 1 else b""
for i in range(page_count)
]


def handle_non_acro_form_params(pdf: bytes, key: str, params: list) -> bytes:
"""Handles non acro form parameters when creating a widget."""

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

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 == key:
for param in params:
if param[0] in NON_ACRO_FORM_PARAM_TO_FUNC:
NON_ACRO_FORM_PARAM_TO_FUNC[param[0]](annot, param[1])

with BytesIO() as f:
out.write(f)
f.seek(0)
return f.read()
1 change: 1 addition & 0 deletions PyPDFForm/widgets/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ class TextWidget(Widget):
("max_length", "maxlen"),
]
COLOR_PARAMS = ["font_color", "bg_color", "border_color"]
ALLOWED_NON_ACRO_FORM_PARAMS = ["alignment"]
NONE_DEFAULTS = ["max_length"]
ACRO_FORM_FUNC = "textfield"
11 changes: 9 additions & 2 deletions PyPDFForm/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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
from .widgets.base import handle_non_acro_form_params
from .widgets.checkbox import CheckBoxWidget
from .widgets.dropdown import DropdownWidget
from .widgets.text import TextWidget
Expand Down Expand Up @@ -213,11 +214,17 @@ def create_widget(
if _class is None:
return self

watermarks = _class(
obj = _class(
name=name, page_number=page_number, x=x, y=y, **kwargs
).watermarks(self.read())
)
watermarks = obj.watermarks(self.read())

self.stream = merge_watermarks_with_pdf(self.read(), watermarks)
if obj.non_acro_form_params:
self.stream = handle_non_acro_form_params(self.stream,
name,
obj.non_acro_form_params)

new_widgets = build_widgets(self.read())
for k, v in self.widgets.items():
if k in new_widgets:
Expand Down
3 changes: 2 additions & 1 deletion docs/prepare.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ new_form = PdfWrapper("dummy.pdf").create_widget(
font_color=(1, 0, 0), # optional
bg_color=(0, 0, 1), # optional
border_color=(1, 0, 0), # optional
border_width=5 # optional
border_width=5, # optional
alignment=0 # optional, 0=left, 1=center, 2=right
)

with open("output.pdf", "wb+") as output:
Expand Down
Binary file modified pdf_samples/scenario/issues/PPF-627-expected-0.pdf
Binary file not shown.
Binary file modified pdf_samples/scenario/issues/PPF-627-expected-1.pdf
Binary file not shown.
Binary file modified pdf_samples/scenario/issues/PPF-627-expected-2.pdf
Binary file not shown.
Binary file modified pdf_samples/scenario/issues/PPF-627-expected-3.pdf
Binary file not shown.
Binary file modified pdf_samples/simple/scenario/issues/437_expected.pdf
Binary file not shown.
Binary file modified pdf_samples/simple/scenario/issues/PPF-627-expected-0.pdf
Binary file not shown.
Binary file modified pdf_samples/simple/scenario/issues/PPF-627-expected-1.pdf
Binary file not shown.
Binary file modified pdf_samples/simple/scenario/issues/PPF-627-expected-2.pdf
Binary file not shown.
Binary file modified pdf_samples/simple/scenario/issues/PPF-627-expected-3.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_text_align_center.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_text_align_right.pdf
Binary file not shown.
44 changes: 44 additions & 0 deletions tests/test_create_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,50 @@ def test_create_text_default(template_stream, pdf_samples, request):
assert obj.stream == expected


def test_create_text_align_center(template_stream, pdf_samples, request):
expected_path = os.path.join(pdf_samples, "widget", "create_text_align_center.pdf")
with open(expected_path, "rb+") as f:
obj = PdfWrapper(template_stream).create_widget(
"text",
"foo",
1,
100,
100,
alignment=1,
)
assert obj.schema["properties"]["foo"]["type"] == "string"

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

expected = f.read()

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


def test_create_text_align_right(template_stream, pdf_samples, request):
expected_path = os.path.join(pdf_samples, "widget", "create_text_align_right.pdf")
with open(expected_path, "rb+") as f:
obj = PdfWrapper(template_stream).create_widget(
"text",
"foo",
1,
100,
100,
alignment=2,
)
assert obj.schema["properties"]["foo"]["type"] == "string"

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

expected = f.read()

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


def test_create_text_default_filled(template_stream, pdf_samples, request):
expected_path = os.path.join(
pdf_samples, "widget", "create_text_default_filled.pdf"
Expand Down
Loading