Search code examples
c#pdfitextacrofields

Creating Image Form Fields for PDFs in iText 7 .NET/C#


I am trying to create a form field in a PDF where the user can insert an image file and save the document so that the image is persistent (in a new PDF document, as opposed to altering an existing document). I know this is possible, because I've seen it done in other PDFs, but I can't work out how it's supposed to be done in iText 7 for .NET/C#.

I found this on Google, which seems to at least provide the JavaScript and outline of a solution, but I don't know how to edit the "Layout" of an iText PdfButtonFormField object. I have also tried this answer from the iText website, but it's geared towards adding to an existing document, and I couldn't get it to work anyway (some more elusive System.NullReferenceException errors).

Using the idea of creating a button and replacing the image, so far I have tried:

PdfWriter writer = new PdfWriter("myfile.pdf");
PdfDocument document = new PdfDocument(writer);
PdfPage pdfPage = document.AddNewPage(PageSize.A4);
PdfCanvas canvas = new PdfCanvas(pdfPage);

PdfAcroForm form = canvas.GetForm();

PdfButtonFormField button = PdfFormField.CreateButton(document, new Rectangle(50, 50), 0);

button.SetAction(PdfAction.CreateJavaScript("event.target.buttonImportIcon();"));

form.AddField(button); // <-- Error on this line

document.Close();
writer.Close();

In the hope that the buttonImportIcon() would be enough to override the buttons appearance. But I get a System.NullReferenceException: 'Object reference not set to an instance of an object.' error at the indicated line (unfortunately it is no more specific than that), with a slightly unhelpful stacktrace:

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
    at iText.Forms.PdfAcroForm.AddField(PdfFormField field, PdfPage page)
    at iText.Forms.PdfAcroForm.AddField(PdfFormField field)
    at ReplaceIcon.Main(String[] args) in ReplaceIcon.cs:line 65

I also tried replacing the CreateButton with CreatePushButton, as in:

PdfButtonFormField button = PdfFormField.CreatePushButton(document, new Rectangle(50, 50), "name", "caption");

Using which the code compiles, and I get a "Select Image" dialogue box when I click on the button in the PDF, but the button remains just a grey square with "caption" written on it, rather than being replaced by the selected image. But I suspect that a generic button is required so you can overwrite the layout (somehow).

If anyone knows how this is supposed to be done, either using this button approach or another way, I would greatly appreciate some pointers. As I said, I am specifically interested in creating these fields in a newly generated PDF document, using iText 7 in a C# program.


Solution

  • But I get a System.NullReferenceException: 'Object reference not set to an instance of an object.'

    This is the bug in the Acroform#addField method. NPE is being thrown every time it gets a nameless field as a parameter. To avoid it, just set the field name before adding to the form (field#setName).

    Using which the code compiles, and I get a "Select Image" dialogue box when I click on the button in the PDF, but the button remains just a grey square with "caption" written on it, rather than being replaced by the selected image. But I suspect that a generic button is required so you can overwrite the layout (somehow).

    the PdfFormField.CreateButton method does not give you any advantages here. That method in iText creates an empty PdfButtonFormField (appearance and behavior should be defined by the developer after a field creation).

    On the other side, CreatePushButton does almost what you need. The only thing must be adjusted is a layout. By default, the created push button has the "label only" layout.

      public void Generate()
        {
            PdfWriter writer = new PdfWriter("myfile.pdf");
            PdfDocument document = new PdfDocument(writer);
    
            PdfAcroForm form = PdfAcroForm.GetAcroForm(document, true);
            PdfButtonFormField button = PdfFormField.CreatePushButton(document, new Rectangle(20, 500, 50, 50), "btn",
                "load");
            button.SetAction(PdfAction.CreateJavaScript("event.target.buttonImportIcon();"));
    //change the layout type.   
    PdfDictionary widget = (PdfDictionary) button.GetKids().Get(0).GetIndirectReference().GetRefersTo();
        widget.GetAsDictionary(PdfName.MK).Put(PdfName.TP, new PdfNumber((int) PushButtonLayouts.ICON_ONLY));
    
            form.AddField(button); // <-- Error on this line
    
            document.Close();
        }
    
        enum PushButtonLayouts
        {
            LABEL_ONLY = 0, //No icon; caption only
            ICON_ONLY = 1, //No caption; icon only
            ICON_TOP_LABEL_BOTTOM = 2, // Caption below the icon
            LABEL_TOP_ICON_BOTTOM = 3, // Caption above the icon
            ICON_LEFT_LABEL_RIGHT = 4, //Caption to the right of the icon
            LABEL_LEFT_ICON_RIGHT = 5, //Caption to the left of the icon
            LABEL_OVER = 6 // Caption overlaid directly on the icon
        }