I'm using iText to generate a pdf with textfields populated with data, from an ASP.NET web form. For some reason, when the pdf is generated, the textfields show Empty, but when I put the cursor on the field, the text shows. If I click somewhere else, then the text in the textfield disappears again. This is the code, adding a textfield called "Field_0_0". The text "hello1" is set to the textfield, but it doesn't display until clicking the textbox. The only way to get the text in the textfield is by typing something, and then the text stays there after losing focus.
byte[] templatebytes = null;
//Dim doc As New Document(iTextSharp.text.PageSize.LETTER, 10, 10, 42, 35)
Document doc = new Document(iTextSharp.text.PageSize.LETTER);
MemoryStream stream = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(doc, stream);
doc.Open();
Paragraph para = new Paragraph("This is my first paragraph");
Phrase pharse = new Phrase("This is my first phrase");
Chunk chunk_ = new Chunk("This is my first chunk");
doc.Add(para);
doc.Add(pharse);
doc.Add(chunk_);
doc.Close();
templatebytes = stream.ToArray();
//==============================================================================================================================================
Stream stream2 = new MemoryStream(templatebytes);
byte[] finalbytes = null;
using (MemoryStream outputPdfStream = new MemoryStream())
{
iTextSharp.text.pdf.PdfReader pdfReader = new iTextSharp.text.pdf.PdfReader(stream2);
iTextSharp.text.pdf.PdfStamper pdfStamper = new iTextSharp.text.pdf.PdfStamper(pdfReader, outputPdfStream);
TextField tf1 = new TextField(writer, new iTextSharp.text.Rectangle(20, 20, 200, 100), "Field_0_0");
tf1.DefaultText = " ";
tf1.Options = TextField.MULTILINE | TextField.VISIBLE;
tf1.TextColor = BaseColor.BLACK;
tf1.FontSize = 10;
tf1.BorderWidth = 1;
tf1.BorderColor = BaseColor.BLUE;
pdfStamper.AddAnnotation(tf1.GetTextField(), 1);
pdfStamper.Close();
finalbytes = outputPdfStream.ToArray();
}
//====================================== set data ========================================
Stream stream3 = new MemoryStream(finalbytes);
byte[] finalbytes3 = null;
using (MemoryStream outputPdfStream = new MemoryStream())
{
iTextSharp.text.pdf.PdfReader pdfReader = new iTextSharp.text.pdf.PdfReader(stream3);
iTextSharp.text.pdf.PdfStamper pdfStamper = new iTextSharp.text.pdf.PdfStamper(pdfReader, outputPdfStream);
//Set fields with data
iTextSharp.text.pdf.AcroFields form = pdfStamper.AcroFields;
IDictionary<string, iTextSharp.text.pdf.AcroFields.Item> fields = form.Fields;
string value = "Hello1"; //$"Line1{Environment.NewLine}Line2";
form.SetField("Field_0_0", value); //$"Line1{Environment.NewLine}Line2"
pdfStamper.Close();
finalbytes3 = outputPdfStream.ToArray();
}
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=GeneratedPDF.pdf");
Response.AddHeader("Content-Length", finalbytes3.Length.ToString());
Response.BinaryWrite(finalbytes3);
Response.End();
Hopefully I explained this right. I can't find a solution online to solve this problem.
Thanks
Set property of pdfStamper follow this:
pdfStamper.AcroFields.GenerateAppearances = false;
Related
I have an XFA form that I can successfully fill in by extracting the XML modifying and writing back. Works great if you have the full Adobe Acrobat, but fails with Adobe Reader. I have seen various questions on the same thing with answers but they were some time ago so updating an XFA that is readable by Adobe Reader may no longer be doable?
I use this code below and I've utilised the StampingProperties of append as in the iText example but still failing. I'm using iText 7.1.15.
//open file and write to temp one
PdfDocument pdf = new(new PdfReader(FileToProcess), new PdfWriter(NewPDF), new StampingProperties().UseAppendMode());
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdf, true);
XfaForm xfa = form.GetXfaForm();
XElement node = xfa.GetDatasetsNode();
IEnumerable<XNode> list = node.Nodes();
foreach (XNode item in list)
{
if (item is XElement element && "data".Equals(element.Name.LocalName))
{
node = element;
break;
}
}
XmlWriterSettings settings = new() { Indent = true };
using XmlWriter writer = XmlWriter.Create(XMLOutput, settings);
{
node.WriteTo(writer);
writer.Flush();
writer.Close();
}
//We now how to strip an extra xfa line if updating
if(update)
{
string TempXML= CSTrackerHelper.MakePath($"{AppContext.BaseDirectory}Temp", $"{Guid.NewGuid()}.XML");
StreamReader fsin = new(XMLOutput);
StreamWriter fsout = new(TempXML);
string linedata = string.Empty;
int cnt = 0;
while (!fsin.EndOfStream)
{
if (cnt != 3 && linedata != string.Empty)
{
fsout.WriteLine(linedata);
}
linedata = fsin.ReadLine();
cnt++;
}
fsout.Close();
fsin.Close();
XMLOutput = TempXML;
}
xlogger.Info("Populating pdf fields");
//Now loop through our field data and update the XML
XmlDocument xmldoc = new();
xmldoc.Load(XMLOutput);
XmlNamespaceManager xmlnsManager = new(xmldoc.NameTable);
xmlnsManager.AddNamespace("xfa", #"http://www.xfa.org/schema/xfa-data/1.0/");
string[] FieldValues;
string[] MultiNodes;
foreach (KeyValuePair<string, DocumentFieldData> v in DocumentData.FieldData)
{
if (!string.IsNullOrEmpty(v.Value.Field))
{
FieldValues = v.Value.Field.Contains(";") ? v.Value.Field.Split(';') : (new string[] { v.Value.Field });
foreach (string FValue in FieldValues)
{
XmlNodeList aNodes;
if (FValue.Contains("{"))
{
aNodes = xmldoc.SelectNodes(FValue.Substring(0, FValue.LastIndexOf("{")), xmlnsManager);
if (aNodes.Count > 1)
{
//We have a multinode
MultiNodes = FValue.Split('{');
int NodeIndex = int.Parse(MultiNodes[1].Replace("}", ""));
aNodes[NodeIndex].InnerText = v.Value.Data;
}
}
else
{
aNodes = xmldoc.SelectNodes(FValue, xmlnsManager);
if (aNodes.Count >= 1)
{
aNodes[0].InnerText = v.Value.Data;
}
}
}
}
}
xmldoc.Save(XMLOutput);
//Now we've updated the XML apply it to the pdf
xfa.FillXfaForm(new FileStream(XMLOutput, FileMode.Open, FileAccess.Read));
xfa.Write(pdf);
pdf.Close();
FYI I've also tried to set a field directly also with the same results.
PdfReader preader = new PdfReader(source);
PdfDocument pdfDoc=new PdfDocument(preader, new PdfWriter(dest), new StampingProperties().UseAppendMode());
PdfAcroForm pdfForm = PdfAcroForm.GetAcroForm(pdfDoc, true);
XfaForm xform = pdfForm.GetXfaForm();
xform.SetXfaFieldValue("VRM[0].CoverPage[0].Wrap2[0].Table[0].CSID[0]", "Test");
xform.Write(pdfForm);
pdfDoc.Close();
If anyone has any ideas it would be appreciated.
Cheers
I ran into a very similar issue. I was attempting to auto fill an XFA that was password protected while not breaking the certificate or usage rights (it allowed filling). iText7 seems to have made this not possible for legal/practical reasons, however it is still very much possible with iText5. I wrote the following working codeusing iTextSharp (C# version if iText5):
using iTextSharp.text;
using iTextSharp.text.pdf;
string pathToRead = "/Users/home/Desktop/c#pdfParser/encrypted_empty.pdf";
string pathToSave = "/Users/home/Desktop/c#pdfParser/xfa_encrypted_filled.pdf";
string data = "/Users/home/Desktop/c#pdfParser/sample_data.xml";
FillByItextSharp5(pathToRead, pathToSave, data);
static void FillByItextSharp5(string pathToRead, string pathToSave, string data)
{
using (FileStream pdf = new FileStream(pathToRead, FileMode.Open))
using (FileStream xml = new FileStream(data, FileMode.Open))
using (FileStream filledPdf = new FileStream(pathToSave, FileMode.Create))
{
PdfReader.unethicalreading = true;
PdfReader pdfReader = new PdfReader(pdf);
PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true);
stamper.AcroFields.Xfa.FillXfaForm(xml, true);
stamper.Close();
pdfReader.Close();
}
}
PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true)
you have to use this line.
I'm using iText 5 to fill existing pdf forms with content and then merge them into a single pdf. I also want to turn on/off layers, but after merging all layers are visible.
This code shows the problem without using existing pdf forms. I would like to hide layer two but it seems not working.
static void Main(string[] args)
{
byte[] pdfPage = CreatePage();
byte[] result = Merge(new byte[][] { pdfPage, pdfPage });
File.WriteAllBytes(#"c:\test1.pdf", result);
}
private static byte[] CreatePage()
{
Document doc = new Document();
MemoryStream ms = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(doc, ms);
doc.Open();
PdfLayer layer1 = new PdfLayer("Layer 1", writer);
PdfLayer layer2 = new PdfLayer("Layer 2", writer);
PdfContentByte cb = writer.DirectContent;
cb.BeginLayer(layer1);
ColumnText.ShowTextAligned(cb, Element.ALIGN_LEFT, new Phrase("Layertext 1"), 100, 700, 0);
cb.EndLayer();
cb.BeginLayer(layer2);
ColumnText.ShowTextAligned(cb, Element.ALIGN_LEFT, new Phrase("Layertext 2"), 100, 600, 0);
cb.EndLayer();
layer1.On = true;
// turn off layer 2
layer2.On = false;
doc.Close();
return ms.ToArray();
}
private static byte[] Merge(byte[][] pages)
{
Document doc = new Document();
MemoryStream ms = null;
using (ms = new MemoryStream())
{
PdfCopy copy = new PdfCopy(doc, ms);
doc.Open();
foreach (byte[] page in pages)
{
PdfReader reader = new PdfReader(new MemoryStream(page));
PdfImportedPage imp = copy.GetImportedPage(reader, 1);
copy.AddPage(imp);
reader.Close();
}
doc.Close();
}
return ms.ToArray();
}
I am using itextsharp in ASP.NET. We populate a PDF with fields that are taken from one of our online forms. I need to change the way we handle the documents - we need to be able to use some of the fields as the name of the document(firstname-lastname.pdf), and to save that PDF into a directory. Here is the code I am using now:
PdfStamper ps = null;
DataTable dt = BindData();
if (dt.Rows.Count > 0)
{
PdfReader r = new PdfReader(new RandomAccessFileOrArray("http://www.example.com/Documents/ppd-certificate.pdf"), null);
ps = new PdfStamper(r, Response.OutputStream);
AcroFields af = ps.AcroFields;
af.SetField("fullName", dt.Rows[0]["fullName"].ToString());
af.SetField("presentationTitle", dt.Rows[0]["presentationTitle"].ToString());
af.SetField("presenterName", dt.Rows[0]["presenterFullName"].ToString());
af.SetField("date", Convert.ToDateTime(dt.Rows[0]["date"]).ToString("MM/dd/yyyy"));
ps.FormFlattening = true;
ps.Close();
}
PdfStamper and PdfWriter both use the generic Stream class so instead of Response.OutputStream you can use a FileStream or a MemoryStream
This example writes directly to disk. Set testFile to whatever you want, I'm using the desktop here
//Your file path here:
var testFile = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
using (var fs = new FileStream(testFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
PdfReader r = new PdfReader(new RandomAccessFileOrArray("http://www.example.com/Documents/ppd-certificate.pdf"), null);
var ps = new PdfStamper(r, fs);
//..code
}
This next example is my preferred method. It creates a MemoryStream, then creates a PDF inside of it and finally grabs the raw bytes. Once you've got raw bytes you can both write them to disk AND Response.BinaryWrite() then.
byte[] bytes;
using (var ms = new MemoryStream()) {
PdfReader r = new PdfReader(new RandomAccessFileOrArray("http://www.example.com/Documents/ppd-certificate.pdf"), null);
var ps = new PdfStamper(r, ms);
//..code
bytes = ms.ToArray();
}
//Your file path here:
var testFile = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
//Write to disk
System.IO.File.WriteAllBytes(testFile, bytes);
//Send to HTTP client
Response.BinaryWrite(bytes);
I have written some code that merges together multiple PDF's into a single PDF that I then display from the MemoryStream. This works great. What I need to do is add a table of contents to the end of the file with links to the start of each of the individual PDF's. I planned on doing this using the GotoLocalPage action which has an option for page numbers but it doesn't seem to work. If I change the action to the code below to one of the presset ones like PDFAction.FIRSTPAGE it works fine. Does this not work because I am using the PDFCopy object for the writer parameter of GotoLocalPage?
Document mergedDoc = new Document();
MemoryStream ms = new MemoryStream();
PdfCopy copy = new PdfCopy(mergedDoc, ms);
mergedDoc.Open();
MemoryStream tocMS = new MemoryStream();
Document tocDoc = null;
PdfWriter tocWriter = null;
for (int i = 0; i < filesToMerge.Length; i++)
{
string filename = filesToMerge[i];
PdfReader reader = new PdfReader(filename);
copy.AddDocument(reader);
// Initialise TOC document based off first file
if (i == 0)
{
tocDoc = new Document(reader.GetPageSizeWithRotation(1));
tocWriter = PdfWriter.GetInstance(tocDoc, tocMS);
tocDoc.Open();
}
// Create link for TOC, added random number of 3 for now
Chunk link = new Chunk(filename);
PdfAction action = PdfAction.GotoLocalPage(3, new PdfDestination(PdfDestination.FIT), copy);
link.SetAction(action);
tocDoc.Add(new Paragraph(link));
}
// Add TOC to end of merged PDF
tocDoc.Close();
PdfReader tocReader = new PdfReader(tocMS.ToArray());
copy.AddDocument(tocReader);
copy.Close();
displayPDF(ms.ToArray());
I guess an alternative would be to link to a named element (instead of page number) but I can't see how to add an 'invisible' element to the start of each file before adding to the merged document?
I would just go with two passes. In your first pass, do the merge as you are but also record the filename and page number it should link to. In your second pass, use a PdfStamper which will give you access to a ColumnText that you can use general abstractions like Paragraph in. Below is a sample that shows this off:
Since I don't have your documents, the below code creates 10 documents with a random number of pages each just for testing purposes. (You obviously don't need to do this part.) It also creates a simple dictionary with a fake file name as the key and the raw bytes from the PDF as a value. You have a true file collection to work with but you should be able to adapt that part.
//Create a bunch of files, nothing special here
//files will be a dictionary of names and the raw PDF bytes
Dictionary<string, byte[]> Files = new Dictionary<string, byte[]>();
var r = new Random();
for (var i = 1; i <= 10; i++) {
using (var ms = new MemoryStream()) {
using (var doc = new Document()) {
using (var writer = PdfWriter.GetInstance(doc, ms)) {
doc.Open();
//Create a random number of pages
for (var j = 1; j <= r.Next(1, 5); j++) {
doc.NewPage();
doc.Add(new Paragraph(String.Format("Hello from document {0} page {1}", i, j)));
}
doc.Close();
}
}
Files.Add("File " + i.ToString(), ms.ToArray());
}
}
This next block merges the PDFs. This is mostly the same as your code except that instead of writing a TOC here I'm just keeping track of what I want to write in the future. Where I'm using file.value you'd use your full file path and where I'm using file.key you'd use your file's name instead.
//Dictionary of file names (for display purposes) and their page numbers
var pages = new Dictionary<string, int>();
//PDFs start at page 1
var lastPageNumber = 1;
//Will hold the final merged PDF bytes
byte[] mergedBytes;
//Most everything else below is standard
using (var ms = new MemoryStream()) {
using (var document = new Document()) {
using (var writer = new PdfCopy(document, ms)) {
document.Open();
foreach (var file in Files) {
//Add the current page at the previous page number
pages.Add(file.Key, lastPageNumber);
using (var reader = new PdfReader(file.Value)) {
writer.AddDocument(reader);
//Increment our current page index
lastPageNumber += reader.NumberOfPages;
}
}
}
}
mergedBytes = ms.ToArray();
}
This last block actually writes the TOC. If we use a PdfStamper we can create a ColumnText which allows us to use Paragraphs
//Will hold the final PDF
byte[] finalBytes;
using (var ms = new MemoryStream()) {
using (var reader = new PdfReader(mergedBytes)) {
using (var stamper = new PdfStamper(reader, ms)) {
//The page number to insert our TOC into
var tocPageNum = reader.NumberOfPages + 1;
//Arbitrarily pick one page to use as the size of the PDF
//Additional logic could be added or this could just be set to something like PageSize.LETTER
var tocPageSize = reader.GetPageSize(1);
//Arbitrary margin for the page
var tocMargin = 20;
//Create our new page
stamper.InsertPage(tocPageNum, tocPageSize);
//Create a ColumnText object so that we can use abstractions like Paragraph
var ct = new ColumnText(stamper.GetOverContent(tocPageNum));
//Set the working area
ct.SetSimpleColumn(tocPageSize.GetLeft(tocMargin), tocPageSize.GetBottom(tocMargin), tocPageSize.GetRight(tocMargin), tocPageSize.GetTop(tocMargin));
//Loop through each page
foreach (var page in pages) {
var link = new Chunk(page.Key);
var action = PdfAction.GotoLocalPage(page.Value, new PdfDestination(PdfDestination.FIT), stamper.Writer);
link.SetAction(action);
ct.AddElement(new Paragraph(link));
}
ct.Go();
}
}
finalBytes = ms.ToArray();
}
After a long time of struggling with this not-so-friendly API, I am finally making progress, but now I've come to a really nasty issue.. I have placed "dir" attributes in various places in my html with the value being "rtl".. but the XMLWorker doesn't seem to respect that at all. Does anyone know of a workaround? Here's my method:
public static void Generate<TModel>(string templateFile, TModel model, string outputFile, IEnumerable<string> fonts)
{
string template = System.IO.File.ReadAllText(templateFile);
string result = Razor.Parse(template, model);
using (var fsOut = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
using (var stringReader = new StringReader(result))
{
var document = new Document();
var pdfWriter = PdfWriter.GetInstance(document, fsOut);
pdfWriter.InitialLeading = 12.5f;
document.Open();
var xmlWorkerHelper = XMLWorkerHelper.GetInstance();
var cssResolver = new StyleAttrCSSResolver();
//cssResolver.AddCss(cssFile);
var xmlWorkerFontProvider = new XMLWorkerFontProvider();
foreach (string font in fonts)
{
xmlWorkerFontProvider.Register(font);
}
var cssAppliers = new CssAppliersImpl(xmlWorkerFontProvider);
var htmlContext = new HtmlPipelineContext(cssAppliers);
htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
PdfWriterPipeline pdfWriterPipeline = new PdfWriterPipeline(document, pdfWriter);
HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, pdfWriterPipeline);
CssResolverPipeline cssResolverPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
XMLWorker xmlWorker = new XMLWorker(cssResolverPipeline, true);
XMLParser xmlParser = new XMLParser(xmlWorker);
xmlParser.Parse(stringReader);
document.Close();
}
}
I've created a sample to show how to parse and display RTL data using XMLWorker. Download it from here.