diff --git a/PyPDFForm/__init__.py b/PyPDFForm/__init__.py index 6ac4e664..bd283126 100644 --- a/PyPDFForm/__init__.py +++ b/PyPDFForm/__init__.py @@ -3,4 +3,4 @@ __version__ = "1.4.12" -from .wrapper import PdfWrapper, PyPDFForm +from .wrapper import FormWrapper, PdfWrapper, PyPDFForm diff --git a/PyPDFForm/constants.py b/PyPDFForm/constants.py index b6ba9c85..1546fbe5 100644 --- a/PyPDFForm/constants.py +++ b/PyPDFForm/constants.py @@ -26,6 +26,7 @@ DEPRECATION_NOTICE = "{} will be deprecated soon. Use {} instead." +ANNOTATION_KEY = "/Annots" ANNOTATION_FIELD_KEY = "/T" ANNOTATION_RECTANGLE_KEY = "/Rect" SUBTYPE_KEY = "/Subtype" @@ -34,6 +35,8 @@ PARENT_KEY = "/Parent" FIELD_FLAG_KEY = "/Ff" TEXT_FIELD_IDENTIFIER = "/Tx" +TEXT_VALUE_IDENTIFIER = "/V" +TEXT_VALUE_SHOW_UP_IDENTIFIER = "/AP" SIGNATURE_FIELD_IDENTIFIER = "/Sig" TEXT_FIELD_APPEARANCE_IDENTIFIER = "/DA" SELECTABLE_IDENTIFIER = "/Btn" @@ -43,6 +46,7 @@ CHOICES_IDENTIFIER = "/Opt" BUTTON_IDENTIFIER = "/MK" BUTTON_STYLE_IDENTIFIER = "/CA" +SELECTED_IDENTIFIER = "/AS" # Field flag bits MULTILINE = 1 << 12 @@ -65,4 +69,6 @@ "l": "\u25CF", # circle } +CHECKBOX_SELECT = "/Yes" + COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO = DEFAULT_FONT_SIZE / 100 diff --git a/PyPDFForm/filler.py b/PyPDFForm/filler.py index 9e6d9b5c..8c43129e 100644 --- a/PyPDFForm/filler.py +++ b/PyPDFForm/filler.py @@ -1,9 +1,15 @@ # -*- coding: utf-8 -*- """Contains helpers for filling a PDF form.""" -from typing import Dict +from io import BytesIO +from typing import cast, Dict -from .constants import WIDGET_TYPES +from pypdf import PdfReader, PdfWriter +from pypdf.generic import DictionaryObject, NameObject, TextStringObject + +from .constants import (CHECKBOX_SELECT, WIDGET_TYPES, + ANNOTATION_KEY, SELECTED_IDENTIFIER, TEXT_VALUE_IDENTIFIER, + TEXT_VALUE_SHOW_UP_IDENTIFIER) from .coordinate import (get_draw_checkbox_radio_coordinates, get_draw_sig_coordinates_resolutions, get_draw_text_coordinates, @@ -11,10 +17,12 @@ from .font import checkbox_radio_font_size from .image import any_image_to_jpg from .middleware.checkbox import Checkbox +from .middleware.dropdown import Dropdown from .middleware.radio import Radio from .middleware.signature import Signature +from .middleware.text import Text from .template import get_widget_key, get_widgets_by_page -from .utils import checkbox_radio_to_draw +from .utils import checkbox_radio_to_draw, stream_to_io from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf @@ -117,3 +125,47 @@ def fill( result = merge_watermarks_with_pdf(result, image_watermarks) return result + + +def simple_fill( + template: bytes, + widgets: Dict[str, WIDGET_TYPES], +) -> bytes: + """Fills a PDF form in place.""" + + pdf = PdfReader(stream_to_io(template)) + out = PdfWriter() + out.append(pdf) + + radio_button_tracker = {} + + for page in out.pages: + for annot in page.get(ANNOTATION_KEY, []): # noqa + annot = cast(DictionaryObject, annot.get_object()) + key = get_widget_key(annot.get_object()) + + widget = widgets.get(key) + if widget is None: + continue + + if isinstance(widget, Checkbox) and widget.value is True: + annot[NameObject(SELECTED_IDENTIFIER)] = NameObject(CHECKBOX_SELECT) + elif isinstance(widget, Radio): + if key not in radio_button_tracker: + radio_button_tracker[key] = 0 + radio_button_tracker[key] += 1 + if widget.value == radio_button_tracker[key] - 1: + annot[NameObject(SELECTED_IDENTIFIER)] = NameObject(f"/{widget.value}") + elif isinstance(widget, Dropdown) and widget.value is not None: + annot[NameObject(TEXT_VALUE_IDENTIFIER)] = ( + TextStringObject(widget.choices[widget.value])) + annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = ( + TextStringObject(widget.choices[widget.value])) + elif isinstance(widget, Text) and widget.value: + annot[NameObject(TEXT_VALUE_IDENTIFIER)] = TextStringObject(widget.value) + annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(widget.value) + + with BytesIO() as f: + out.write(f) + f.seek(0) + return f.read() diff --git a/PyPDFForm/wrapper.py b/PyPDFForm/wrapper.py index 74fd7ee5..8e6c87e6 100644 --- a/PyPDFForm/wrapper.py +++ b/PyPDFForm/wrapper.py @@ -11,7 +11,7 @@ DEPRECATION_NOTICE, VERSION_IDENTIFIER_PREFIX, VERSION_IDENTIFIERS) from .coordinate import generate_coordinate_grid -from .filler import fill +from .filler import fill, simple_fill from .font import register_font from .image import any_image_to_jpg, rotate_image from .middleware.dropdown import Dropdown @@ -26,7 +26,40 @@ from .widgets.text import TextWidget -class PdfWrapper: +class FormWrapper: + """A simple base wrapper for just filling a PDF form.""" + + def __init__( + self, + template: Union[bytes, str, BinaryIO] = b"", + ) -> None: + """Constructs all attributes for the object.""" + + self.stream = fp_or_f_obj_or_stream_to_stream(template) + + def read(self) -> bytes: + """Reads the file stream of a PDF form.""" + + return self.stream + + def fill( + self, + data: Dict[str, Union[str, bool, int]], + ) -> 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 + + self.stream = simple_fill(self.read(), widgets) + + return self + + +class PdfWrapper(FormWrapper): """A class to represent a PDF form.""" def __init__( @@ -36,7 +69,7 @@ def __init__( ) -> None: """Constructs all attributes for the object.""" - self.stream = fp_or_f_obj_or_stream_to_stream(template) + super().__init__(template) self.widgets = build_widgets(self.stream) if self.stream else {} self.global_font = kwargs.get("global_font") @@ -49,11 +82,6 @@ def __init__( each.font_size = self.global_font_size each.font_color = self.global_font_color - def read(self) -> bytes: - """Reads the file stream of a PDF form.""" - - return self.stream - @property def elements(self) -> dict: """ToDo: deprecate this.""" diff --git a/pdf_samples/scenario/issues/521.pdf b/pdf_samples/scenario/issues/521.pdf new file mode 100644 index 00000000..67780328 Binary files /dev/null and b/pdf_samples/scenario/issues/521.pdf differ diff --git a/pdf_samples/simple/dropdown/dropdown_alignment_expected.pdf b/pdf_samples/simple/dropdown/dropdown_alignment_expected.pdf new file mode 100644 index 00000000..8a4fbc8c Binary files /dev/null and b/pdf_samples/simple/dropdown/dropdown_alignment_expected.pdf differ diff --git a/pdf_samples/simple/dropdown/dropdown_four.pdf b/pdf_samples/simple/dropdown/dropdown_four.pdf new file mode 100644 index 00000000..436f93d5 Binary files /dev/null and b/pdf_samples/simple/dropdown/dropdown_four.pdf differ diff --git a/pdf_samples/simple/dropdown/dropdown_one.pdf b/pdf_samples/simple/dropdown/dropdown_one.pdf new file mode 100644 index 00000000..7220c4e8 Binary files /dev/null and b/pdf_samples/simple/dropdown/dropdown_one.pdf differ diff --git a/pdf_samples/simple/dropdown/dropdown_three.pdf b/pdf_samples/simple/dropdown/dropdown_three.pdf new file mode 100644 index 00000000..37fa892c Binary files /dev/null and b/pdf_samples/simple/dropdown/dropdown_three.pdf differ diff --git a/pdf_samples/simple/dropdown/dropdown_two.pdf b/pdf_samples/simple/dropdown/dropdown_two.pdf new file mode 100644 index 00000000..f854eda8 Binary files /dev/null and b/pdf_samples/simple/dropdown/dropdown_two.pdf differ diff --git a/pdf_samples/simple/max_length_text_field_related/max_length_text_field_all_chars.pdf b/pdf_samples/simple/max_length_text_field_related/max_length_text_field_all_chars.pdf new file mode 100644 index 00000000..b9ee238d Binary files /dev/null and b/pdf_samples/simple/max_length_text_field_related/max_length_text_field_all_chars.pdf differ diff --git a/pdf_samples/simple/max_length_text_field_related/max_length_text_field_even_chars.pdf b/pdf_samples/simple/max_length_text_field_related/max_length_text_field_even_chars.pdf new file mode 100644 index 00000000..19d39c55 Binary files /dev/null and b/pdf_samples/simple/max_length_text_field_related/max_length_text_field_even_chars.pdf differ diff --git a/pdf_samples/simple/max_length_text_field_related/max_length_text_field_odd_chars.pdf b/pdf_samples/simple/max_length_text_field_related/max_length_text_field_odd_chars.pdf new file mode 100644 index 00000000..86453320 Binary files /dev/null and b/pdf_samples/simple/max_length_text_field_related/max_length_text_field_odd_chars.pdf differ diff --git a/pdf_samples/simple/paragraph/test_paragraph_auto_font.pdf b/pdf_samples/simple/paragraph/test_paragraph_auto_font.pdf new file mode 100644 index 00000000..10a9d954 Binary files /dev/null and b/pdf_samples/simple/paragraph/test_paragraph_auto_font.pdf differ diff --git a/pdf_samples/simple/paragraph/test_paragraph_auto_font_auto_wrap.pdf b/pdf_samples/simple/paragraph/test_paragraph_auto_font_auto_wrap.pdf new file mode 100644 index 00000000..fd9bead7 Binary files /dev/null and b/pdf_samples/simple/paragraph/test_paragraph_auto_font_auto_wrap.pdf differ diff --git a/pdf_samples/simple/paragraph/test_paragraph_auto_wrap.pdf b/pdf_samples/simple/paragraph/test_paragraph_auto_wrap.pdf new file mode 100644 index 00000000..2656851f Binary files /dev/null and b/pdf_samples/simple/paragraph/test_paragraph_auto_wrap.pdf differ diff --git a/pdf_samples/simple/paragraph/test_paragraph_complex.pdf b/pdf_samples/simple/paragraph/test_paragraph_complex.pdf new file mode 100644 index 00000000..39f49fa5 Binary files /dev/null and b/pdf_samples/simple/paragraph/test_paragraph_complex.pdf differ diff --git a/pdf_samples/simple/paragraph/test_paragraph_max_length.pdf b/pdf_samples/simple/paragraph/test_paragraph_max_length.pdf new file mode 100644 index 00000000..43389746 Binary files /dev/null and b/pdf_samples/simple/paragraph/test_paragraph_max_length.pdf differ diff --git a/pdf_samples/simple/paragraph/test_paragraph_y_coordinate.pdf b/pdf_samples/simple/paragraph/test_paragraph_y_coordinate.pdf new file mode 100644 index 00000000..1f22590c Binary files /dev/null and b/pdf_samples/simple/paragraph/test_paragraph_y_coordinate.pdf differ diff --git a/pdf_samples/simple/sample_filled.pdf b/pdf_samples/simple/sample_filled.pdf new file mode 100644 index 00000000..4305f5a4 Binary files /dev/null and b/pdf_samples/simple/sample_filled.pdf differ diff --git a/pdf_samples/simple/sample_filled_radiobutton.pdf b/pdf_samples/simple/sample_filled_radiobutton.pdf new file mode 100644 index 00000000..df948457 Binary files /dev/null and b/pdf_samples/simple/sample_filled_radiobutton.pdf differ diff --git a/pdf_samples/simple/sample_filled_right_aligned.pdf b/pdf_samples/simple/sample_filled_right_aligned.pdf new file mode 100644 index 00000000..4942b6d0 Binary files /dev/null and b/pdf_samples/simple/sample_filled_right_aligned.pdf differ diff --git a/pdf_samples/simple/scenario/existed/DS82_expected.pdf b/pdf_samples/simple/scenario/existed/DS82_expected.pdf new file mode 100644 index 00000000..dcc13de3 Binary files /dev/null and b/pdf_samples/simple/scenario/existed/DS82_expected.pdf differ diff --git a/pdf_samples/simple/scenario/existed/DS82_expected_all_chars_lowercase.pdf b/pdf_samples/simple/scenario/existed/DS82_expected_all_chars_lowercase.pdf new file mode 100644 index 00000000..e9daeee3 Binary files /dev/null and b/pdf_samples/simple/scenario/existed/DS82_expected_all_chars_lowercase.pdf differ diff --git a/pdf_samples/simple/scenario/existed/DS82_expected_all_chars_uppercase.pdf b/pdf_samples/simple/scenario/existed/DS82_expected_all_chars_uppercase.pdf new file mode 100644 index 00000000..983210ae Binary files /dev/null and b/pdf_samples/simple/scenario/existed/DS82_expected_all_chars_uppercase.pdf differ diff --git a/pdf_samples/simple/scenario/existed/DS82_expected_mixed_case.pdf b/pdf_samples/simple/scenario/existed/DS82_expected_mixed_case.pdf new file mode 100644 index 00000000..57dfc0d2 Binary files /dev/null and b/pdf_samples/simple/scenario/existed/DS82_expected_mixed_case.pdf differ diff --git a/pdf_samples/simple/scenario/existed/illinois-real-estate-power-of-attorney-form_expected.pdf b/pdf_samples/simple/scenario/existed/illinois-real-estate-power-of-attorney-form_expected.pdf new file mode 100644 index 00000000..ce646f63 Binary files /dev/null and b/pdf_samples/simple/scenario/existed/illinois-real-estate-power-of-attorney-form_expected.pdf differ diff --git a/pdf_samples/simple/scenario/issues/521-expected.pdf b/pdf_samples/simple/scenario/issues/521-expected.pdf new file mode 100644 index 00000000..d0127d6a Binary files /dev/null and b/pdf_samples/simple/scenario/issues/521-expected.pdf differ diff --git a/pdf_samples/simple/scenario/issues/PPF-285-expected.pdf b/pdf_samples/simple/scenario/issues/PPF-285-expected.pdf new file mode 100644 index 00000000..488e3369 Binary files /dev/null and b/pdf_samples/simple/scenario/issues/PPF-285-expected.pdf differ diff --git a/pdf_samples/simple/scenario/issues/PPF-415-2-expected.pdf b/pdf_samples/simple/scenario/issues/PPF-415-2-expected.pdf new file mode 100644 index 00000000..4c1d5b67 Binary files /dev/null and b/pdf_samples/simple/scenario/issues/PPF-415-2-expected.pdf differ diff --git a/pdf_samples/simple/scenario/tools/docfly_expected.pdf b/pdf_samples/simple/scenario/tools/docfly_expected.pdf new file mode 100644 index 00000000..671afc77 Binary files /dev/null and b/pdf_samples/simple/scenario/tools/docfly_expected.pdf differ diff --git a/pdf_samples/simple/scenario/tools/pdf_escape_expected.pdf b/pdf_samples/simple/scenario/tools/pdf_escape_expected.pdf new file mode 100644 index 00000000..17dd0f32 Binary files /dev/null and b/pdf_samples/simple/scenario/tools/pdf_escape_expected.pdf differ diff --git a/pdf_samples/simple/scenario/tools/soda_expected.pdf b/pdf_samples/simple/scenario/tools/soda_expected.pdf new file mode 100644 index 00000000..a010db48 Binary files /dev/null and b/pdf_samples/simple/scenario/tools/soda_expected.pdf differ diff --git a/pdf_samples/simple/test_fill_complex_fonts.pdf b/pdf_samples/simple/test_fill_complex_fonts.pdf new file mode 100644 index 00000000..9c9934ba Binary files /dev/null and b/pdf_samples/simple/test_fill_complex_fonts.pdf differ diff --git a/pdf_samples/simple/test_fill_font_color.pdf b/pdf_samples/simple/test_fill_font_color.pdf new file mode 100644 index 00000000..97d2330e Binary files /dev/null and b/pdf_samples/simple/test_fill_font_color.pdf differ diff --git a/tests/scenario/test_existed_simple.py b/tests/scenario/test_existed_simple.py new file mode 100644 index 00000000..3817a204 --- /dev/null +++ b/tests/scenario/test_existed_simple.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- + +import os + +from PyPDFForm import FormWrapper + + +def test_ds82(existed_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "existed", "DS82_expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(existed_pdf_directory, "DS82.pdf")).fill( + { + "LastName": "Smith", + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_ds82_all_chars_lowercase(existed_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "existed", "DS82_expected_all_chars_lowercase.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(existed_pdf_directory, "DS82.pdf")).fill( + { + "LastName": "x" * 30, + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_ds82_all_chars_uppercase(existed_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "existed", "DS82_expected_all_chars_uppercase.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(existed_pdf_directory, "DS82.pdf")).fill( + { + "LastName": "X" * 30, + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_ds82_mixed_case(existed_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "existed", "DS82_expected_mixed_case.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(existed_pdf_directory, "DS82.pdf")).fill( + { + "LastName": "xX" * 10, + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_illinois_real_estate_power_of_attorney_form(existed_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "existed", + "illinois-real-estate-power-of-attorney-form_expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(existed_pdf_directory, "illinois-real-estate-power-of-attorney-form.pdf")).fill( + { + "undefined": "John Doe", + "State of": "Chicago", + "undefined_2": "Illinois", + "of": "Michael Smith", + "Illinois as my Attorneyin": "Chicago", + "with full power and": "Random", + "is as": "Not Random", + "Address of Principal": "1 N Central, Chicago, IL 60000", + "Phone number where Principal can be contacted": "(000)000-0000", + "Email address of Principal": "msmith@example.com", + "Text3": "Someone", + "Dated": "2018-01-01", + "Text4": "Sometwo", + "Text5": "Somethree", + "Text6": "Somefour", + "Dated 1": "2019-01-01", + "My commission expires": "NOW", + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected diff --git a/tests/scenario/test_issues_simple.py b/tests/scenario/test_issues_simple.py new file mode 100644 index 00000000..ff4eef22 --- /dev/null +++ b/tests/scenario/test_issues_simple.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long + +import os + +from PyPDFForm import FormWrapper + + +def test_pdf_form_with_central_aligned_text_fields(issue_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "issues", "PPF-285-expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(issue_pdf_directory, "PPF-285.pdf")).fill( + { + "name": "Hans Mustermann", + "fulladdress": "Musterstr. 12, 82903 Musterdorf, Musterland", + "advisorname": "Karl Test", + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_pdf_form_with_paragraph_fields_new_line_symbol_text_overflow(issue_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "issues", "PPF-415-2-expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(issue_pdf_directory, "PPF-415-2.pdf")).fill( + { + "multiline-text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Malesuada proin libero nunc consequat interdum varius sit amet mattis. Nec tincidunt praesent semper feugiat nibh sed.\nSed libero enim sed faucibus turpis. Cursus in hac habitasse platea dictumst quisque sagittis. Placerat in egestas erat imperdiet sed euismod. Id aliquet risus feugiat in ante metus dictum at. Proin fermentum leo vel orci porta non pulvinar. Consequat semper viverra nam libero justo.\nPellentesque massa placerat duis ultricies lacus sed. Amet est placerat in egestas erat imperdiet sed euismod nisi. Id cursus metus aliquam eleifend mi. Massa massa ultricies mi quis. Volutpat consequat mauris nunc congue nisi vitae suscipit tellus. Ut tellus elementum sagittis vitae.\n\nEtiam sit amet nisl purus in mollis nunc. Vel turpis nunc eget lorem dolor sed. Ultrices dui sapien eget mi proin sed libero enim. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant. Libero volutpat sed cras ornare arcu. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Nisi est sit amet facilisis magna etiam. In iaculis nunc sed augue.\nSapien pellentesque habitant morbi tristique.\nCondimentum mattis pellentesque id nibh tortor id aliquet. Porttitor massa id neque aliquam vestibulum. Feugiat in fermentum posuere urna nec tincidunt praesent semper. Malesuada fames ac turpis egestas integer. Aenean vel elit scelerisque mauris pellentesque. Vel turpis nunc eget lorem dolor sed viverra. Nec feugiat nisl pretium fusce id velit ut tortor." # noqa + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_521(issue_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "issues", "521-expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(issue_pdf_directory, "521.pdf")).fill( + { + "Text1": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", # noqa + "Text2": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. NEMO ENIM IPSAM VOLUPTATEM QUIA VOLUPTAS SIT ASPERNATUR AUT ODIT AUT FUGIT, SED QUIA CONSEQUUNTUR MAGNI DOLORES EOS QUI RATIONE VOLUPTATEM SEQUI NESCIUNT. NEQUE PORRO QUISQUAM EST, QUI DOLOREM IPSUM QUIA DOLOR SIT AMET, CONSECTETUR, ADIPISCI VELIT, SED QUIA NON NUMQUAM EIUS MODI TEMPORA INCIDUNT UT LABORE ET DOLORE MAGNAM ALIQUAM QUAERAT VOLUPTATEM. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", # noqa + "Text3": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", # noqa + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected diff --git a/tests/scenario/test_tools_simple.py b/tests/scenario/test_tools_simple.py new file mode 100644 index 00000000..efe6a36f --- /dev/null +++ b/tests/scenario/test_tools_simple.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +import os + +from PyPDFForm import FormWrapper + + +def test_filling_pdf_escape_pdf_form(tool_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "tools", "pdf_escape_expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(tool_pdf_directory, "pdf_escape.pdf")).fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 2, + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_filling_docfly_pdf_form(tool_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "tools", "docfly_expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(tool_pdf_directory, "docfly.pdf")).fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 1, + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_filling_soda_pdf_form(tool_pdf_directory, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "scenario", "tools", "soda_expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(os.path.join(tool_pdf_directory, "soda.pdf")).fill( + { + "Text1": "Helvetica 8", + "Text2": "Helvetica 12", + "Text3": "Helvetica 24", + "Text4": "Helvetica 8", + "Text5": "Helvetica 12", + "Text6": "Helvetica 24", + "Text7": "Helvetica 8", + "Text8": "Helvetica 12", + "Text9": "Helvetica 24", + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected diff --git a/tests/test_dropdown_simple.py b/tests/test_dropdown_simple.py new file mode 100644 index 00000000..ddee2f12 --- /dev/null +++ b/tests/test_dropdown_simple.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +import os + +from PyPDFForm import FormWrapper + + +def test_dropdown_not_specified(sample_template_with_dropdown): + assert ( + FormWrapper(sample_template_with_dropdown) + .fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 1, + } + ) + .read() + ) + + +def test_dropdown_one(sample_template_with_dropdown, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "dropdown", "dropdown_one.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_dropdown).fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 1, + "dropdown_1": 0, + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_dropdown_two(sample_template_with_dropdown, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "dropdown", "dropdown_two.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_dropdown).fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 1, + "dropdown_1": 1, + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_dropdown_three(sample_template_with_dropdown, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "dropdown", "dropdown_three.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_dropdown).fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 1, + "dropdown_1": 2, + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_dropdown_four(sample_template_with_dropdown, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "dropdown", "dropdown_four.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_dropdown).fill( + { + "test_1": "test_1", + "test_2": "test_2", + "test_3": "test_3", + "check_1": True, + "check_2": True, + "check_3": True, + "radio_1": 1, + "dropdown_1": 3, + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_dropdown_alignment(dropdown_alignment, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "dropdown", "dropdown_alignment_expected.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(dropdown_alignment).fill( + { + "dropdown_left": 0, + "dropdown_center": 1, + "dropdown_right": 2, + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected diff --git a/tests/test_fill_max_length_text_field_simple.py b/tests/test_fill_max_length_text_field_simple.py new file mode 100644 index 00000000..46dab78a --- /dev/null +++ b/tests/test_fill_max_length_text_field_simple.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +import os + +from PyPDFForm import FormWrapper + + +def test_fill_max_length_text_field_all_chars( + sample_template_with_max_length_text_field, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "max_length_text_field_related", + "max_length_text_field_all_chars.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_max_length_text_field).fill( + { + "FirstName": "John", + "MiddleName": "Joe", + "LastName": "XXXXXXXXXX", + "Awesomeness": True, + "Gender": 0, + } + ) + + 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_fill_max_length_text_field_odd_chars( + sample_template_with_max_length_text_field, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "max_length_text_field_related", + "max_length_text_field_odd_chars.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_max_length_text_field).fill( + { + "FirstName": "John", + "MiddleName": "Joe", + "LastName": "XXX", + "Awesomeness": True, + "Gender": 0, + } + ) + + 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_fill_max_length_text_field_even_chars( + sample_template_with_max_length_text_field, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "max_length_text_field_related", + "max_length_text_field_even_chars.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_max_length_text_field).fill( + { + "FirstName": "John", + "MiddleName": "Joe", + "LastName": "XXXX", + "Awesomeness": True, + "Gender": 0, + } + ) + + 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 diff --git a/tests/test_functional_simple.py b/tests/test_functional_simple.py new file mode 100644 index 00000000..03ef13d0 --- /dev/null +++ b/tests/test_functional_simple.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +import os + +from PyPDFForm import FormWrapper + + +def test_fill(template_stream, pdf_samples, data_dict, request): + expected_path = os.path.join(pdf_samples, "simple", "sample_filled.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(template_stream).fill( + data_dict + ) + + 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_fill_radiobutton(pdf_samples, template_with_radiobutton_stream, request): + expected_path = os.path.join(pdf_samples, "simple", "sample_filled_radiobutton.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(template_with_radiobutton_stream).fill( + { + "radio_1": 0, + "radio_2": 1, + "radio_3": 2, + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_fill_right_aligned( + sample_template_with_right_aligned_text_field, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "sample_filled_right_aligned.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_right_aligned_text_field).fill( + { + "name": "Hans Mustermann", + "fulladdress": "Musterstr. 12, 82903 Musterdorf, Musterland", + "advisorname": "Karl Test", + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_fill_font_color(sample_template_with_font_colors, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "test_fill_font_color.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_font_colors).fill( + { + "red_12": "red", + "green_14": "green", + "blue_16": "blue", + "mixed_auto": "mixed", + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_fill_complex_fonts(sample_template_with_complex_fonts, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "test_fill_complex_fonts.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_complex_fonts).fill( + { + "Courier": "Test", + "Courier-Bold": "Test", + "Courier-BoldOblique": "Test", + "Courier-Oblique": "Test", + "Helvetica": "Test", + "Helvetica-Bold": "Test", + "Helvetica-BoldOblique": "Test", + "Helvetica-Oblique": "Test", + "Times-Bold": "Test", + "Times-BoldItalic": "Test", + "Times-Italic": "Test", + "Times-Roman": "Test", + }, + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected diff --git a/tests/test_paragraph_simple.py b/tests/test_paragraph_simple.py new file mode 100644 index 00000000..0909483d --- /dev/null +++ b/tests/test_paragraph_simple.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long + +import os + +from PyPDFForm import FormWrapper + + +def test_paragraph_y_coordinate(sample_template_with_paragraph, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "paragraph", "test_paragraph_y_coordinate.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_paragraph).fill( + {"paragraph_1": "test paragraph"} + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_paragraph_auto_wrap(sample_template_with_paragraph, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "paragraph", "test_paragraph_auto_wrap.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_paragraph).fill( + { + "paragraph_1": "t xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx t" + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_paragraph_auto_font( + sample_template_with_paragraph_auto_font, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "paragraph", "test_paragraph_auto_font.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_paragraph_auto_font).fill( + {"paragraph": "test paragraph"} + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_paragraph_auto_font_auto_wrap( + sample_template_with_paragraph_auto_font, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "paragraph", "test_paragraph_auto_font_auto_wrap.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_paragraph_auto_font).fill( + { + "paragraph": "t xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx t" + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_paragraph_complex(sample_template_paragraph_complex, pdf_samples, request): + expected_path = os.path.join(pdf_samples, "simple", "paragraph", "test_paragraph_complex.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_paragraph_complex).fill( + { + "paragraph_font_auto_left": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "paragraph_font_auto_right": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "paragraph_font_auto_center": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "paragraph_font_ten_left": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "paragraph_font_ten_right": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "paragraph_font_ten_center": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected + + +def test_paragraph_max_length( + sample_template_with_paragraph_max_length, pdf_samples, request +): + expected_path = os.path.join(pdf_samples, "simple", "paragraph", "test_paragraph_max_length.pdf") + with open(expected_path, "rb+") as f: + obj = FormWrapper(sample_template_with_paragraph_max_length).fill( + { + "paragraph": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + } + ) + + request.config.results["expected_path"] = expected_path + request.config.results["stream"] = obj.read() + + expected = f.read() + + if os.name != "nt": + assert len(obj.read()) == len(expected) + assert obj.stream == expected