From 9bc61d299aeda2477d05cd43f61cd261e2439141 Mon Sep 17 00:00:00 2001 From: Jinge Li <9894243+chinapandaman@users.noreply.github.com> Date: Sun, 9 Jun 2024 02:38:41 +0000 Subject: [PATCH 1/5] PPF-641: this is definitely not always on --- PyPDFForm/constants.py | 5 +++++ PyPDFForm/filler.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/PyPDFForm/constants.py b/PyPDFForm/constants.py index 1ada0040..44914272 100644 --- a/PyPDFForm/constants.py +++ b/PyPDFForm/constants.py @@ -54,6 +54,11 @@ Yes = "/Yes" Off = "/Off" +# For Adobe Acrobat +AcroForm = "/AcroForm" +Root = "/Root" +NeedAppearances = "/NeedAppearances" + # Field flag bits READ_ONLY = 1 << 0 MULTILINE = 1 << 12 diff --git a/PyPDFForm/filler.py b/PyPDFForm/filler.py index 5f114a8b..8f7aad1b 100644 --- a/PyPDFForm/filler.py +++ b/PyPDFForm/filler.py @@ -5,9 +5,10 @@ from typing import Dict, Tuple, Union, cast from pypdf import PdfReader, PdfWriter -from pypdf.generic import DictionaryObject +from pypdf.generic import (BooleanObject, DictionaryObject, IndirectObject, + NameObject) -from .constants import WIDGET_TYPES, Annots +from .constants import WIDGET_TYPES, AcroForm, Annots, NeedAppearances, Root from .coordinate import (get_draw_checkbox_radio_coordinates, get_draw_image_coordinates_resolutions, get_draw_text_coordinates, @@ -168,6 +169,11 @@ def simple_fill( """Fills a PDF form in place.""" pdf = PdfReader(stream_to_io(template)) + if AcroForm in pdf.trailer[Root]: + pdf.trailer[Root][AcroForm].update( + {NameObject(NeedAppearances): BooleanObject(True)} + ) + out = PdfWriter() out.append(pdf) From 40c812948bd85f6a1bd42ab5196ee6a83857dc04 Mon Sep 17 00:00:00 2001 From: Jinge Li <9894243+chinapandaman@users.noreply.github.com> Date: Sun, 9 Jun 2024 02:41:28 +0000 Subject: [PATCH 2/5] PPF-641: for now a feature flag --- PyPDFForm/filler.py | 3 ++- PyPDFForm/wrapper.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/PyPDFForm/filler.py b/PyPDFForm/filler.py index 8f7aad1b..81c7ccc3 100644 --- a/PyPDFForm/filler.py +++ b/PyPDFForm/filler.py @@ -165,11 +165,12 @@ def simple_fill( template: bytes, widgets: Dict[str, WIDGET_TYPES], flatten: bool = False, + adobe_mode: bool = False, ) -> bytes: """Fills a PDF form in place.""" pdf = PdfReader(stream_to_io(template)) - if AcroForm in pdf.trailer[Root]: + if adobe_mode and AcroForm in pdf.trailer[Root]: pdf.trailer[Root][AcroForm].update( {NameObject(NeedAppearances): BooleanObject(True)} ) diff --git a/PyPDFForm/wrapper.py b/PyPDFForm/wrapper.py index 911672e2..40e142e5 100644 --- a/PyPDFForm/wrapper.py +++ b/PyPDFForm/wrapper.py @@ -58,7 +58,7 @@ def fill( widgets[key].value = value self.stream = simple_fill( - self.read(), widgets, flatten=kwargs.get("flatten", False) + self.read(), widgets, flatten=kwargs.get("flatten", False), adobe_mode=kwargs.get("adobe_mode", False) ) return self From 5e20585542d086aea7a65d4792504ba9313c3980 Mon Sep 17 00:00:00 2001 From: Jinge Li <9894243+chinapandaman@users.noreply.github.com> Date: Sun, 9 Jun 2024 02:55:29 +0000 Subject: [PATCH 3/5] PPF-641: add a test case --- .../paragraph/sample_filled_sejda_complex.pdf | Bin 0 -> 26343 bytes tests/test_adobe_mode.py | 45 ++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 pdf_samples/adobe_mode/paragraph/sample_filled_sejda_complex.pdf create mode 100644 tests/test_adobe_mode.py diff --git a/pdf_samples/adobe_mode/paragraph/sample_filled_sejda_complex.pdf b/pdf_samples/adobe_mode/paragraph/sample_filled_sejda_complex.pdf new file mode 100644 index 0000000000000000000000000000000000000000..710657bbfc8257dd64d806d425f4f7e7920ccad0 GIT binary patch literal 26343 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 L`t}_@wk-7j;>xf6 literal 0 HcmV?d00001 diff --git a/tests/test_adobe_mode.py b/tests/test_adobe_mode.py new file mode 100644 index 00000000..a8bc94ac --- /dev/null +++ b/tests/test_adobe_mode.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +import os + +from PyPDFForm import FormWrapper + + +def test_fill_sejda_complex(sejda_template_complex, pdf_samples, request): + expected_path = os.path.join( + pdf_samples, "adobe_mode", "paragraph", "sample_filled_sejda_complex.pdf" + ) + with open(expected_path, "rb+") as f: + obj = FormWrapper(sejda_template_complex).fill( + { + "checkbox": True, + "radio": 0, + "dropdown_font_auto_left": 0, + "dropdown_font_auto_center": 1, + "dropdown_font_auto_right": 2, + "dropdown_font_ten_left": 0, + "dropdown_font_ten_center": 1, + "dropdown_font_ten_right": 2, + "paragraph_font_auto_left": "paragraph_font_auto_left", + "paragraph_font_auto_center": "paragraph_font_auto_center", + "paragraph_font_auto_right": "paragraph_font_auto_right", + "paragraph_font_ten_left": "paragraph_font_ten_left", + "paragraph_font_ten_center": "paragraph_font_ten_center", + "paragraph_font_ten_right": "paragraph_font_ten_right", + "text__font_auto_left": "test text", + "text_font_auto_center": "test text", + "text_font_auto_right": "test text", + "text_font_ten_left": "text_font_ten_left", + "text_font_ten_center": "text_font_ten_center", + "text_font_ten_right": "text_font_ten_right", + }, + adobe_mode=True, + ) + + 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 From 8e30f187b1df016a802e9995c14c7ea3b83d7acd Mon Sep 17 00:00:00 2001 From: Jinge Li <9894243+chinapandaman@users.noreply.github.com> Date: Sun, 9 Jun 2024 02:58:47 +0000 Subject: [PATCH 4/5] PPF-641: fix linting --- PyPDFForm/wrapper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PyPDFForm/wrapper.py b/PyPDFForm/wrapper.py index 40e142e5..738ece05 100644 --- a/PyPDFForm/wrapper.py +++ b/PyPDFForm/wrapper.py @@ -58,7 +58,10 @@ def fill( widgets[key].value = value self.stream = simple_fill( - self.read(), widgets, flatten=kwargs.get("flatten", False), adobe_mode=kwargs.get("adobe_mode", False) + self.read(), + widgets, + flatten=kwargs.get("flatten", False), + adobe_mode=kwargs.get("adobe_mode", False), ) return self From 30e256d38ed455872f8890a198ea806860810684 Mon Sep 17 00:00:00 2001 From: Jinge Li <9894243+chinapandaman@users.noreply.github.com> Date: Sun, 9 Jun 2024 03:03:33 +0000 Subject: [PATCH 5/5] PPF-641: will fix this later --- PyPDFForm/filler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PyPDFForm/filler.py b/PyPDFForm/filler.py index 81c7ccc3..ec73ac04 100644 --- a/PyPDFForm/filler.py +++ b/PyPDFForm/filler.py @@ -5,7 +5,7 @@ from typing import Dict, Tuple, Union, cast from pypdf import PdfReader, PdfWriter -from pypdf.generic import (BooleanObject, DictionaryObject, IndirectObject, +from pypdf.generic import (BooleanObject, DictionaryObject, NameObject) from .constants import WIDGET_TYPES, AcroForm, Annots, NeedAppearances, Root @@ -169,6 +169,7 @@ def simple_fill( ) -> bytes: """Fills a PDF form in place.""" + # pylint: disable=too-many-branches pdf = PdfReader(stream_to_io(template)) if adobe_mode and AcroForm in pdf.trailer[Root]: pdf.trailer[Root][AcroForm].update(