Skip to content

Commit

Permalink
Merge pull request #708 from chinapandaman/PPF-707
Browse files Browse the repository at this point in the history
PPF-707: support create center/right alignment text field
  • Loading branch information
chinapandaman authored Aug 21, 2024
2 parents 03300c7 + 70b3046 commit c6dc95f
Show file tree
Hide file tree
Showing 17 changed files with 102 additions and 5 deletions.
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

0 comments on commit c6dc95f

Please sign in to comment.