taji01
taji01

Reputation: 2615

Write to a PDF file with fields multiple times using iTextSharp

I have a PDF document with 3 Fields txt_FirstName, txt_MiddleName and txt_LastName that I write into using iTextSharp.

I have a loop that creates the output file, writes to it, and closes the file.

The first time in the loop the file writes the first name and the middle name.

The second time in the loop the file should have the first name, middle name, and write the last name.

Issue: The problem is, when it goes to the loop the 2nd time around and writes the lastname the first name, and middle names disappear.

Goal: The main thing I want to do is write to the same PDF documents multiple times

Download PDF template: https://www.scribd.com/document/412586469/Testing-Doc

    public static string templatePath = "C:\\temp\\template.pdf";
    public static string OutputPath = "C:\\Output\\";

    private static void Fill_PDF()
    {
        string outputFile = "output.pdf";
        int counter = 1;

        for (int i = 0; i < 2; i++)
        {
            PdfStamper pdfStamper;
            PdfReader reader;

            reader = new PdfReader(File.ReadAllBytes(templatePath));
            PdfReader.unethicalreading = true;

            if (File.Exists(OutputPath + outputFile))
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                        FileMode.Append, FileAccess.Write));
            }
            else
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                        FileMode.Create));
            }
            AcroFields pdfFormFields = pdfStamper.AcroFields;

            if (counter == 1)
            {
                pdfFormFields.SetField("txt_FirstName", "Scooby");
                pdfFormFields.SetField("txt_MiddleName", "Dooby");
                counter++;
            }
            else if (counter == 2)
            {
                pdfFormFields.SetField("txt_LastName", "Doo");
            }
            pdfStamper.Close();
        }
    }

Upvotes: 3

Views: 1554

Answers (3)

Gowshik
Gowshik

Reputation: 330

The problem is with using the template file again for the second iteration.

First iteration: works fine as expected!

Second iteration: you are reading the same file and writing only the last name. Finally, the output file created in the first iteration is being replaced.

Fix: After knowing if the output file exists in the location, choose the file source to read like below. This should fix the problem. Checked it personally and it worked!

if (File.Exists(OutputPath + outputFile))
{
    reader = new PdfReader(File.ReadAllBytes(OutputPath + outputFile));
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                            FileMode.Append, FileAccess.Write));
}
else
{
    reader = new PdfReader(File.ReadAllBytes(templatePath));
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                            FileMode.Create));
}

Upvotes: -1

mkl
mkl

Reputation: 95918

There are two major issues with the code. @Nick in his answer already pointed out the first: If in your second pass you want to edit a version of your document containing the changes from the first pass, you have to take the output document of the first pass as input of the second pass, not again the original template. He also presented code that fixed this issue.

The second issue is located here:

if (File.Exists(OutputPath + outputFile))
{
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                            FileMode.Append, FileAccess.Write));
}
else
{
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                            FileMode.Create));
}

If the output file already exists, you append the output of your PdfStamper to it. This is wrong! The output of the PdfStamper already contains the contents of the original PDF (from the PdfReader) as far as they have not being changed. Thus, your code effectively produces a concatenation of the complete output PDF of the first pass and the complete output PDF of the second pass.

PDF is a binary format for which concatenating files like that does not result in a valid PDF file. Thus, a PDF viewer loading your final result tries to repair this double PDF assuming it is a single one. The result may or may not look like you want.

To fix the second issue, simply replace the if{...}else{...} above by the contents of the else branch only:

pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                        FileMode.Create));

(FileMode.Create is defined as

Specifies that the operating system should create a new file. If the file already exists, it will be overwritten. This requires Write permission. FileMode.Create is equivalent to requesting that if the file does not exist, use CreateNew; otherwise, use Truncate. If the file already exists but is a hidden file, an UnauthorizedAccessException exception is thrown.

Thus, it will also do the required if there already is a file.)

You can recognize the problems of the code with the Append in it by running it a few times and watch the output file grow and grow beyond need. Furthermore, if you open that file in Adobe Reader and close again, Adobe Reader offers to save the changes; the changes are the repair work.


You may have heard about incremental updates of PDFs where changes are appended to the original PDF. But this is different from a mere concatenation, the revisions in the result are linked specially and the offsets are always calculated from the start of the first revision, not from the start of the current revision. Furthermore, incremental updates should only contain changed objects.

iText contains a PdfStamper constructor with 4 parameters, including a final boolean parameter append. Using that constructor and setting append to true makes iText creates incremental updates. But even here you don't use FileMode.Append...

Upvotes: 1

Nick Garyu
Nick Garyu

Reputation: 506

This seems like a straightforward bug. The first time through the loop, you load up the blank template and write the first and middle name. The second time through the loop, you load up the blank template again and write only the last name to it, then save to the same filename, overwriting it. If, during the second time through the loop, you want to load the file that already contains the first and middle name, you have to load up the output file you wrote the first time around, not the blank template again. Or if you want to load the blank template again, inside your if (counter == 2) clause, you're going to have to write all 3 names, not just the last name.

I reproduced your bug, and got it working. Here's the code to the first solution I described (minor modification of your code):

    public static string templatePath = "C:\\temp\\template.pdf";
    public static string OutputPath = "C:\\temp\\output\\";

    private static void Fill_PDF()
    {
        string outputFile = "output.pdf";
        int counter = 1;

        for (int i = 0; i < 2; i++)
        {
            PdfStamper pdfStamper;
            PdfReader reader = null;

            /********** here's the changed part */
            if (counter == 1)
            {
                reader = new PdfReader(File.ReadAllBytes(templatePath));
            } else if (counter == 2)
            {
                reader = new PdfReader(File.ReadAllBytes(OutputPath + outputFile));
            }
            /************ end changed part */

            PdfReader.unethicalreading = true;

            if (File.Exists(OutputPath + outputFile))
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                    FileMode.Append, FileAccess.Write));
            }
            else
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                    FileMode.Create));
            }
            AcroFields pdfFormFields = pdfStamper.AcroFields;

            if (counter == 1)
            {
                pdfFormFields.SetField("txt_FirstName", "Scooby");
                pdfFormFields.SetField("txt_MiddleName", "Dooby");
                counter++;
            }
            else if (counter == 2)
            {
                pdfFormFields.SetField("txt_LastName", "Doo");
            }
            pdfStamper.Close();
        }
    }

Upvotes: 6

Related Questions