Adding action to XFA PDF Form in C# - c#

here is my code.
public void customGenerate(string sourceFilePath, string destinationtFilePath, string replacementXmlFilePath)
{
PdfReader pdfReader = new PdfReader(sourceFilePath);
using (MemoryStream ms = new MemoryStream())
{
//using (PdfStamper stamper = new PdfStamper(pdfReader, ms, '\0', true))
using (PdfStamper stamper = new PdfStamper(pdfReader, ms))
{
XfaForm xfaForm = new XfaForm(pdfReader);
XmlDocument doc = new XmlDocument();
doc.Load(replacementXmlFilePath);
xfaForm.DomDocument = doc;
xfaForm.Changed = true;
XfaForm.SetXfa(xfaForm, stamper.Reader, stamper.Writer);
stamper.Close();
}
var bytes = ms.ToArray();
System.IO.File.WriteAllBytes(destinationtFilePath, bytes);
ms.Close();
}
pdfReader.Close();
}
I'm not sure that it is right.
when set XFA form data(action javascript), using (PdfStamper stamper = new PdfStamper(pdfReader, ms, '\0', true)), in this case it doesn't work to me.
using ItextSharp. if using using (PdfStamper stamper = new PdfStamper(pdfReader, ms)), it works for me well.
I would like to know how to solve this problem.

Related

Cannot access a closed Stream. When using PDFReader

I have this file, which is a Stream:
var streamFile = await graphClient.Me.Drive.Items["id"].Content.Request().GetAsync();
Now I am trying to use PdfReader and PdfStamper to set Fields like so:
MemoryStream outFile = new MemoryStream();
PdfReader pdfReader = new PdfReader(streamFile);
PdfStamper pdfStamper = new PdfStamper(pdfReader, outFile);
AcroFields fields = pdfStamper.AcroFields;
fields.SetField("Full_Names", "JIMMMMMMAYYYYY");
pdfStamper.Close();
pdfReader.Close();
But when I try to do this, I get this error:
Cannot access a closed Stream.
On this line:
pdfReader.Close();
What am I doing wrong?
UPDATE
I tried this, still getting the same error:
using (MemoryStream outFile = new MemoryStream())
{
var streamFile = await graphClient.Me.Drive.Items["item-id"].Content.Request().GetAsync();
using (PdfReader pdfReader = new PdfReader(streamFile))
{
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, outFile))
{
AcroFields fields = pdfStamper.AcroFields;
fields.SetField("Full_Names", "JIMMMMMMAYYYYY");
}
}
outFile.Position = 0;
await graphClient.Me.Drive.Items["item-id"].ItemWithPath("NewDocument-2.pdf").Content.Request().PutAsync<DriveItem>(outFile);
}
UPDATE
I have tried converting the Stream to bytes like so:
var streamFile = await graphClient.Me.Drive.Items["item-id"].Content.Request().GetAsync();
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = streamFile.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
using (PdfReader pdfReader = new PdfReader(ms.ToArray()))
{
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, ms))
{
AcroFields fields = pdfStamper.AcroFields;
fields.SetField("Full_Names", "JIMMMMMMAYYYYY");
}
}
await graphClient.Me.Drive.Items["item-id"].ItemWithPath("NewDocument-2.pdf").Content.Request().PutAsync<DriveItem>(ms);
}
Same result...Cannot access a closed Stream on this line:
await graphClient.Me.Drive.Items["item-id"].ItemWithPath("NewDocument-2.pdf").Content.Request().PutAsync<DriveItem>(ms);
The PutAsync is expecting a Stream as well
So when I do this:
var streamFile = await graphClient.Me.Drive.Items["item-id"].Content.Request().GetAsync();
await graphClient.Me.Drive.Items["item-id"].ItemWithPath("NewDocument-2.pdf").Content.Request().PutAsync<DriveItem>(streamFile);
It uploads the file no problem. So I do believe the problem is trying to edit the PDF with iTextSharp.
In my case I wanted to create the document in memory and add WaterMark after creation of document, but without saving a physical file as an intermediate step (which works as well but not nearly as neat).
public byte[] AsArray(List<DocumentData> list)
{
MemoryStream streamIn = new MemoryStream(); // Set the initial stream for the document
MemoryStream streamOut = new MemoryStream(); // Set the result output stream
PdfWriter writer = new PdfWriter(streamIn); // create the writer for document
CreateDocument(writer, list); // Method where the document actually get's made
// Now the tricky bit
// Translate the `streamIn` (that now contains the document stream) into a PdfReader
// use the byte[] from streamIn to create a new MemoryStream()
PdfReader reader = new PdfReader(new MemoryStream(streamIn.ToArray()));
writer = new PdfWriter(streamOut); // Set the writer stream to be the streamOut
SetWaterMark(reader, writer); // Method to read through the document and add watermarks
return streamOut.ToArray();
}
and hey Presto, succes !
You can try the following:
var streamFile = await graphClient.Me.Drive.Items["item-id"].Content.Request().GetAsync();
byte[] buffer = new byte[16 * 1024];
try
{
PdfReader pdfReader = null;
PdfStamper pdfStamper = null;
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = streamFile.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
pdfReader = new PdfReader(ms.ToArray());
pdfStamper = new PdfStamper(pdfReader, ms);
AcroFields fields = pdfStamper.AcroFields;
fields.SetField("Full_Names", "JIMMMMMMAYYYYY");
await graphClient.Me.Drive.Items["item-id"].ItemWithPath("NewDocument-2.pdf").Content.Request().PutAsync<DriveItem>(ms);
}
}
finally
{
if (pdfReader != null) pdfReader.Dispose();
if (pdfStamper != null) pdfStamper.Dispose();
}
Dispose the pdfReader and pdfStamper after the await is done.

iTextSharp form filler works ... kinda

I have an XFA enabled PDF document that I would like to fill out programatically. I'm using C# and iTextSharp 5.5.12.0
The document is password protected so I set PdfReader.unethicalreading = true;
The file I'm working with is the N-400.
The filled document has the fields set properly when I view it in Adobe Acrobat Reader. But if I open it in a browser (say IE or Chrome) the fields are empty.
I am opening the file in append mode.
Here's my code (SRC, XML and DEST are strings with full paths to the files).
using (FileStream pdf = new FileStream(SRC, FileMode.Open))
using (FileStream xml = new FileStream(XML, FileMode.Open))
using (FileStream filledPdf = new FileStream(DEST, FileMode.Create))
{
PdfReader.unethicalreading = true;
PdfReader pdfReader = new PdfReader(pdf);
pdfReader.RemoveUsageRights();
PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true);
string[] fields = stamper.AcroFields.Fields.Select(x => x.Key).ToArray();
for (int key = 0; key <= fields.Count() - 1; key++)
{
stamper.AcroFields.SetFieldProperty(fields[key], "setfflags", PdfFormField.FF_READ_ONLY, null);
}
stamper.Writer.CloseStream = false;
stamper.AcroFields.Xfa.FillXfaForm(xml);
stamper.Close();
pdfReader.Close();
}

How to save PDF using ITextSharp?

I working with PDF annotations using ITextSharp. I was able to add annotations pretty smoothly.
But now I'm trying to edit them. It looks like my PdfReader object is actually updated. But for some reason I can't save it. As shown in the snippet below, I try to get the byte array from using a stamper. The byte array is only 1 byte longer than the previous version no matter how long is the annotation. And when I open the PDF saved on the file system, I still have the old annotation...
private void UpdatePDFAnnotation(string title, string body)
{
byte[] newBuffer;
using (PdfReader pdfReader = new PdfReader(dataBuffer))
{
int pageIndex = 1;
int annotIndex = 0;
PdfDictionary pageDict = pdfReader.GetPageN(pageIndex);
var annots = pageDict.GetAsArray(PdfName.ANNOTS);
if (annots != null)
{
PdfDictionary annot = annots.GetAsDict(annotIndex);
annot.Put(PdfName.T, new PdfString(title));
annot.Put(PdfName.CONTENTS, new PdfString(body));
}
// ********************************
// this line shows the new annotation is in here. Just have to save it somehow !!
var updatedBody = pdfReader.GetPageN(pageIndex).GetAsArray(PdfName.ANNOTS).GetAsDict(0).GetAsString(PdfName.CONTENTS);
Debug.Assert(newBody == updatedBody.ToString(), "Annotation body should be equal");
using (MemoryStream outStream = new MemoryStream())
{
using (PdfStamper stamp = new PdfStamper(pdfReader, outStream, '\0', true))
{
newBuffer = outStream.ToArray();
}
}
}
File.WriteAllBytes( #"Assets\Documents\AnnotedPdf.pdf", newBuffer);
}
Any idea what's wrong with my code?
PdfStamper does much of the writing at the time it is being closed. This implicitly happens at the end of its using block. But you retrieve the MemoryStream contents already in that block. Thus, the PDF is not yet written to the retrieved byte[].
Instead either explicitly close the PdfStamper instance before retrieving the byte[]:
using (PdfStamper stamp = new PdfStamper(pdfReader, outStream, '\0', true))
{
stamp.Close();
newBuffer = outStream.ToArray();
}
or retrieve the byte[] after that using block:
using (PdfStamper stamp = new PdfStamper(pdfReader, outStream, '\0', true))
{
}
newBuffer = outStream.ToArray();
Allright, I finally got it to work. The trick was the two last parameter in the PdfStamper instantiation. I tried it before with only 2 parameters and ended up with a corrupted file. Then I tried again and now it works... here's the snippet
private void UpdatePDFAnnotation(string title, string body)
{
using (PdfReader pdfReader = new PdfReader(dataBuffer))
{
PdfDictionary pageDict = pdfReader.GetPageN(pageIndex);
var annots = pageDict.GetAsArray(PdfName.ANNOTS);
PdfDictionary annot = annots.GetAsDict(annotIndex);
annot.Put(PdfName.T, new PdfString(title));
annot.Put(PdfName.CONTENTS, new PdfString(body));
using (MemoryStream ms = new MemoryStream())
{
PdfStamper stamp = new PdfStamper(pdfReader, ms);
stamp.Dispose();
dataBuffer = ms.ToArray();
}
}
}

Convert html to pdf and merge it with existing pdfs

I have a System.Net.Mail.MailMessage which shall have it's html body and pdf attachments converted into one single pdf.
Converting the html body to pdf works for me with this answer
Converting the pdf attachments into one pdf works for me with this answer
However after ~10 hours of trying I can not come up with a combined solution which does both. All I'm getting are NullReferenceExceptions somewhere in IText source, "the document is not open", etc...
For example, this will throw no error but the resulting pdf will only contain the attachments but not the html email body:
Document document = new Document();
StringReader sr = new StringReader(mail.Body);
HTMLWorker htmlparser = new HTMLWorker(document);
using (FileStream fs = new FileStream(targetPath, FileMode.Create))
{
PdfCopy writer = new PdfCopy(document, fs);
document.Open();
htmlparser.Parse(sr);
foreach (string fileName in pdfList)
{
PdfReader reader = new PdfReader(fileName);
reader.ConsolidateNamedDestinations();
for (int i = 1; i <= reader.NumberOfPages; i++)
{
PdfImportedPage page = writer.GetImportedPage(reader, i);
writer.AddPage(page);
}
PRAcroForm form = reader.AcroForm;
if (form != null)
{
writer.CopyAcroForm(reader);
}
reader.Close();
}
writer.Close();
document.Close();
}
I'm using the LGPL licensed ITextSharp 4.1.6
From v4.1.6 fanboy to v4.1.6 fanboy :D
Looks like the HTMLWorker is closing the documents stream right after parsing. So as a workaround, you could create a pdf from your mailbody in memory. And then add this one together with the attachment to your final pdf.
Here is some code, that should do the trick:
StringReader htmlStringReader = new StringReader("<html><body>Hello World!!!!!!</body></html>");
byte[] htmlResult;
using (MemoryStream htmlStream = new MemoryStream())
{
Document htmlDoc = new Document();
PdfWriter htmlWriter = PdfWriter.GetInstance(htmlDoc, htmlStream);
htmlDoc.Open();
HTMLWorker htmlWorker = new HTMLWorker(htmlDoc);
htmlWorker.Parse(htmlStringReader);
htmlDoc.Close();
htmlResult = htmlStream.ToArray();
}
byte[] pdfResult;
using (MemoryStream pdfStream = new MemoryStream())
{
Document doc = new Document();
PdfCopy copyWriter = new PdfCopy(doc, pdfStream);
doc.Open();
PdfReader htmlPdfReader = new PdfReader(htmlResult);
AppendPdf(copyWriter, htmlPdfReader); // your foreach pdf code here
htmlPdfReader.Close();
PdfReader attachmentReader = new PdfReader("C:\\temp\\test.pdf");
AppendPdf(copyWriter, attachmentReader);
attachmentReader.Close();
doc.Close();
pdfResult = pdfStream.ToArray();
}
using (FileStream fs = new FileStream("C:\\temp\\test2.pdf", FileMode.Create, FileAccess.Write))
{
fs.Write(pdfResult, 0, pdfResult.Length);
}
private void AppendPdf(PdfCopy writer, PdfReader reader)
{
for (int i = 1; i <= reader.NumberOfPages; i++)
{
PdfImportedPage page = writer.GetImportedPage(reader, i);
writer.AddPage(page);
}
}
Ofc you could directly use a FileStream for the final document instead of a MemoryStream as well.

Save image in Existing PDF using iTextSharp. Not Working

I am using the below mentioned code. Due to some reasons I am not able to save the image in output PDF. is there anything I am missing ?
string imageFileName = Path.Combine(Application.StartupPath, "a.jpg");
var inputpdf = Path.Combine(Application.StartupPath, "b.pdf");
var outputpdf = Path.Combine(Application.StartupPath, "output.pdf");
using (Stream inputPdfStream = new FileStream(inputpdf, FileMode.Open, FileAccess.Read,
FileShare.Read))
{
using (Stream inputImageStream = new FileStream(imageFileName, FileMode.Open,
FileAccess.Read, FileShare.Read))
{
var reader = new PdfReader(inputPdfStream);
var stamper = new PdfStamper(reader, new FileStream(outputpdf, FileMode.Create),
'\0', true);
var pdfContentByte = stamper.GetOverContent(1);
iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(inputImageStream);
image.SetAbsolutePosition(10, 10);
pdfContentByte.AddImage(image);
stamper.Close();
}
}

Categories