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

[#578] Add binding for AcroFormDocumentHelper::setFormFieldName method #580

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
50 changes: 50 additions & 0 deletions src/core/acroform.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2022 James R. Barlow
// SPDX-License-Identifier: MPL-2.0

#include <qpdf/QPDFAcroFormDocumentHelper.hh>

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "pikepdf.h"

void init_acroform(py::module_ &m)
{
py::class_<QPDFAcroFormDocumentHelper,
std::shared_ptr<QPDFAcroFormDocumentHelper>,
QPDFDocumentHelper>(m, "AcroFormDocument")
.def(
py::init([](QPDF &q) {
QPDFAcroFormDocumentHelper afdh(q);

return afdh;
}),
py::keep_alive<0, 1>(), // LCOV_EXCL_LINE
py::arg("q")
)
.def(
"set_form_field_name",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this to be useful to anyone who say, wants to know what form fields are present before changing their names, you'll need to bind QPDFFormFieldObjectHelper as well. Then one should be able to retrieve form fields from the form and manipulate the field name from there.

I'm not going to accept a PR that solves your issue alone and isn't helpful to others. It needs to be of general use. All of the methods of QPDFFormFieldObjectHelper and QPDFAcroFormDocumentHelper should be exposed. You're probably going to need them anyway.

Please bind get/set functions as def_property which is equivalent to setting a @property with a pythonic getter and setter. Then one would use form.fields[...].name = 'MyName' etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the method getFormFields, although it is mandatory for the usage that I solve. You could simplify use it as I showed in the test.
I dont know how I should do the binding with def_property.
If I understand you would me to add it to the binding of the class QPDFFormFieldObjectHelper but the method is on the class QPDFAcroFormDocumentHelper.

Implementing all the methods would take me too much time as I'm completely a novice in python and just discovered how pybind11 works, we could merge the base that I implemented and someone could improve it in the future

[](QPDFAcroFormDocumentHelper &afdh, QPDFObjectHandle annot, std::string const& name) {
QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(annot);
auto ffh_oh = ffh.getObjectHandle();
if (ffh_oh.hasKey("/Parent")) {
QPDFObjectHandle parent = ffh_oh.getKey("/Parent");
QPDFFormFieldObjectHelper ph(parent);
afdh.setFormFieldName(ph, name);
} else {
afdh.setFormFieldName(ffh, name);
}
}
)
.def(
"get_form_fields",
[](QPDFAcroFormDocumentHelper &afdh) {
return afdh.getFormFields();
},
py::return_value_policy::reference_internal
);

py::class_<QPDFFormFieldObjectHelper,
std::shared_ptr<QPDFFormFieldObjectHelper>,
QPDFObjectHelper>(m, "FormField");
}
13 changes: 13 additions & 0 deletions src/core/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QPDFDocumentHelper.hh>

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
Expand Down Expand Up @@ -895,6 +896,18 @@ void init_object(py::module_ &m)
.def_property_readonly("obj", [](QPDFObjectHelper &poh) -> QPDFObjectHandle {
return poh.getObjectHandle();
});
py::class_<QPDFDocumentHelper, std::shared_ptr<QPDFDocumentHelper>>(m, "DocumentHelper");
// .def(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out? It breaks existing functionality.

// "__eq__",
// [](QPDFObjectHelper &self, QPDFObjectHelper &other) {
// // Object helpers are equal if their object handles are equal
// return objecthandle_equal(
// self.getObjectHandle(), other.getObjectHandle());
// },
// py::is_operator())
// .def_property_readonly("obj", [](QPDFObjectHelper &poh) -> QPDFObjectHandle {
// return poh.getObjectHandle();
// });

m.def("_encode", [](py::handle handle) { return objecthandle_encode(handle); });
m.def("unparse", [](py::object obj) -> py::bytes {
Expand Down
1 change: 1 addition & 0 deletions src/core/pikepdf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ PYBIND11_MODULE(_core, m)
init_job(m);

// -- Support objects (alphabetize order) --
init_acroform(m);
init_annotation(m);
init_embeddedfiles(m);
init_matrix(m);
Expand Down
2 changes: 2 additions & 0 deletions src/core/pikepdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ QPDFObjectHandle objecthandle_encode(const py::handle handle);
std::vector<QPDFObjectHandle> array_builder(const py::iterable iter);
std::map<std::string, QPDFObjectHandle> dict_builder(const py::dict dict);

// From acroform.cpp
void init_acroform(py::module_ &m);
// From annotation.cpp
void init_annotation(py::module_ &m);
// From embeddedfiles.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/pikepdf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from pikepdf._core import (
AccessMode,
AcroFormDocument,
Annotation,
AttachedFileSpec,
ContentStreamInlineImage,
Expand Down Expand Up @@ -90,6 +91,7 @@

__all__ = [
'AccessMode',
'AcroFormDocument',
'Annotation',
'AttachedFileSpec',
'ContentStreamInlineImage',
Expand Down
17 changes: 17 additions & 0 deletions src/pikepdf/_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ class ObjectHelper:
def obj(self) -> Dictionary:
"""Get the underlying PDF object (typically a Dictionary)."""

class DocumentHelper:
"""Base class for wrapper/helper around a Document.
"""

class _ObjectList:
"""A list whose elements are always pikepdf.Object.

Expand Down Expand Up @@ -684,6 +688,19 @@ class AttachedFile:
def size(self) -> int:
"""Get length of the attached file in bytes according to the PDF creator."""

class AcroFormDocument(DocumentHelper):
def __init__(
self
) -> None:
"""Construct an AcroFormDocumentHelper.
"""
def set_form_field_name(self, annot: Object, name: str) -> None:
"""Set form field name
"""
def get_form_fields(self) -> list[ObjectHelper]:
"""Get form fields
"""

class AttachedFileSpec(ObjectHelper):
r"""In a PDF, a file specification provides name and metadata for a target file.

Expand Down
26 changes: 26 additions & 0 deletions tests/test_acroform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2022 James R. Barlow
# SPDX-License-Identifier: CC0-1.0

from __future__ import annotations

import pytest

from pikepdf import AcroFormDocument, Pdf


@pytest.fixture
def form(resources):
with Pdf.open(resources / 'form.pdf') as pdf:
yield pdf


def test_set_form_field_name(form):
afd = AcroFormDocument(form)
field = form.Root.AcroForm.Fields[0]
assert field.T == 'Text1'
afd.set_form_field_name(field, 'new_field_name')
assert field.T == 'new_field_name'

def test_get_form_fields(form):
afd = AcroFormDocument(form)
assert len(afd.get_form_fields()) == 4
Loading