3

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.

Gebodal
  • 345
  • 2
  • 12
  • Can you share the stack trace when you hit that null reference? Please [edit] that into your question. – rene May 17 '20 at 08:07
  • I've included it, but I'm not sure how helpful it is here - it's quite an opaque stacktrace, unfortunately. At least, it is to me... There is always a high probability I'm missing something. – Gebodal May 17 '20 at 19:56
  • Can you try `form.AddField(button, pdfPage);` and see how that goes. – rene May 17 '20 at 20:07
  • Almost exactly the same stacktrace again, I'm afraid, just missing the `at iText.Forms.PdfAcroForm.AddField(PdfFormField field)` line. – Gebodal May 17 '20 at 20:22
  • Okay, it was worth a try. I'm afraid this needs one of the itext followers to sort out. It might be something with the page you're adding this on, mainly because that null pointer is raised inside the itext library. – rene May 17 '20 at 20:30

1 Answers1

4

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
    }
  • Ah, that's fantastic, thank you. If I may ask a follow-up (I'm unsure of the etiquette about this on StackOverflow, so apologies if this is in the wrong place), do you happen to know if there's a way to set an initial image on the button, rather than just text or blank? Confusingly, the `SetImage` function only takes a string, and I have no idea how to approach `SetImageAsForm(PdfFormXObject form)` method (as I have no idea how to convert a PdfXObject made with an ImageData into a PdfFormXObject). – Gebodal May 18 '20 at 08:50
  • Actually setImage just sets Base64 encoded image as a button value by the setValue() method. – Pavel Chermyanin May 19 '20 at 19:02
  • I'm afraid you've lost me there. `SetValue()` is not the same as setting the icon, right? And can you convert an ImageData to a Base64 encoding string, or do you have to use some additional machinery? – Gebodal May 20 '20 at 20:14
  • If you have a look at the PdfButtonFormFIeld class: https://github.com/itext/itext7-dotnet/blob/develop/itext/itext.forms/itext/forms/fields/PdfButtonFormField.cs you can find that the setImage method calls setValue under the hood (line 198). – Pavel Chermyanin May 21 '20 at 01:07