I have an ActionResult that is working fine to return a PDF.
public ActionResult TrainingHours(int id)
{
EmployeeModel model = new EmployeeModel(id);
if (!model.CheckEmployeeAccess())
throw new HttpException(403, "Access Denied");
byte[] report = model.GetTrainingHoursReport(id);
return File(report, Constants.PdfMimeType, model.EmployeeHoursReportFileName);
}
The byte[] is returned from a LocalReport's Render("PDF", DeviceInfoString) method and looks perfect when it is downloaded.
The customer wants to be able to click a link and have the same report run for a group of people and merged into one pdf for download.
public ActionResult MassTrainingHours(int[] employeeIds)
{
if (employeeIds == null)
return new EmptyResult();
List<byte[]> reportsList = new List<byte[]>();
foreach (int employeeId in employeeIds)
{
EmployeeModel model = new EmployeeModel(employeeId);
reportsList.Add(model.GetTrainingHoursReport(employeeId));
}
Post.Domain.Services.PdfMerger merger = new Post.Domain.Services.PdfMerger();
byte[] report = merger.MergeFiles(reportsList);
return File(report, Constants.PdfMimeType, "TrainingHours.pdf");
Here's my class that merges the pdfs using iTextSharp.
public class PdfMerger
{
public byte[] MergeFiles(List<byte[]> inputFiles)
{
MemoryStream outputMS = new MemoryStream();
Document document = new Document();
PdfCopy writer = new PdfCopy(document, outputMS);
writer.CloseStream = false;
PdfImportedPage page = null;
document.Open();
foreach (byte[] fileData in inputFiles)
{
PdfReader reader = new PdfReader(fileData);
int n = reader.NumberOfPages;
for (int i = 1; i <= n; i++)
{
page = writer.GetImportedPage(reader, i);
writer.AddPage(page);
}
PRAcroForm form = reader.AcroForm;
if (form != null)
writer.CopyAcroForm(reader);
}
document.Close();
return outputMS.ToArray();
}
}
This all seems to run with no errors but I get absolutely nothing returned from the ActionResult.
If I use the MergeFiles method slightly modified to a FileStream on a winforms application the process runs smoothly and I get the output file I'm looking for.
Related
Basically, i have original XFA file with many Custom > Properties and i can convert it to PDF-A but all of properties have lost after convert.
Here is my ConvertToPDFa Code:
public byte[] ConvertToPDFa3(byte[] OriginPDF,byte[] FileStore,string XML)
{
//***************************************
// Convert PDF to PDFa3 and Attach XML .
//***************************************
//Create Reader for Reading PDF in byte[]
using (var reader = new PdfReader(OriginPDF))
{
// Open Stream for Converting
using (var Convertstream = new MemoryStream())
{
// New Doc for PDF/A-3
Document pdfAdocument = new Document();
//Init instance for pdfAdocument
PdfAWriter writer = PdfAWriter.GetInstance(pdfAdocument, Convertstream, PdfAConformanceLevel.PDF_A_3U);
writer.CreateXmpMetadata();
if (!pdfAdocument.IsOpen())
pdfAdocument.Open();
PdfContentByte cb = writer.DirectContent; // Holds the PDF data
//Count original PDF pages and uses for pdfa-3
PdfImportedPage page;
int pageCount = reader.NumberOfPages;
for (int i = 0; i < pageCount; i++)
{
pdfAdocument.NewPage();
page = writer.GetImportedPage(reader, i + 1);
cb.AddTemplate(page, 0, 0);
}
//Create Output Intents
ICC_Profile icc = ICC_Profile.GetInstance(AppDomain.CurrentDomain.BaseDirectory + "res\\sRGB Color Space Profile.icm");
writer.SetOutputIntents("sRGB IEC61966-2.1", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
//Embedded File to PDFa3
writer.AddFileAttachment("XML for : " + certRefNumber, FileStore, XML, lblRefNo.Text + ".xml");
//Close all file to finish
pdfAdocument.Close();
reader.Close();
//Test writes output in Phys path
//File.WriteAllBytes(#"OutPDFa.pdf", Convertstream.ToArray());
byte[] filledPDfa = Convertstream.ToArray();
return filledPDfa;
}
}
// End Convert and Attached
}
i tried get origin properties with these code :
static Dictionary<string, string> GetPdfProperties(byte[] originfile)
{
Dictionary<string, string> propertyInfo = null;
using (PdfReader reader = new PdfReader(originfile))
{
propertyInfo = reader.Info;
reader.Close();
}
return propertyInfo;
}
And Copy origin properties to PDF-A like this :
static byte[] CopyProps(byte[] fileinput,byte[] fileOrigin,PdfAWriter wr)
{
using (var rd = new PdfReader(fileinput))
{
using (var outp = new MemoryStream())
{
using (var stamper = new PdfAStamper(rd, outp,PdfAConformanceLevel.PDF_A_3U))
{
var info = rd.Info;
Dictionary<string, string> propertyInfo = GetPdfProperties(fileOrigin);
foreach (KeyValuePair<string, string> property in propertyInfo)
{
info[property.Key] = property.Value;
}
stamper.MoreInfo = info;
using (var ms = new MemoryStream())
{
wr.CreateXmpWriter(ms,info); //****error was here
stamper.XmpMetadata = ms.ToArray();
}
}
return outp.ToArray();
}
}
}
but found the error was inaccesable due to its protected level
Should i place the error line to another ?
Or
Have another solution for these problem ?
Please Help :(
i use iTextSharp 5.5.13
How would I merge several pdf pages into one with iTextSharp which also supports merging pages having form elements like textboxes, checkboxes, etc.
I have tried so many by googling, but nothing has worked well.
See my answer here Merging Memory Streams. I give an example of how to merge PDFs with itextsharp.
For updating form field names add this code that uses the stamper to change the form field names.
/// <summary>
/// Merges pdf files from a byte list
/// </summary>
/// <param name="files">list of files to merge</param>
/// <returns>memory stream containing combined pdf</returns>
public MemoryStream MergePdfForms(List<byte[]> files)
{
if (files.Count > 1)
{
string[] names;
PdfStamper stamper;
MemoryStream msTemp = null;
PdfReader pdfTemplate = null;
PdfReader pdfFile;
Document doc;
PdfWriter pCopy;
MemoryStream msOutput = new MemoryStream();
pdfFile = new PdfReader(files[0]);
doc = new Document();
pCopy = new PdfSmartCopy(doc, msOutput);
pCopy.PdfVersion = PdfWriter.VERSION_1_7;
doc.Open();
for (int k = 0; k < files.Count; k++)
{
for (int i = 1; i < pdfFile.NumberOfPages + 1; i++)
{
msTemp = new MemoryStream();
pdfTemplate = new PdfReader(files[k]);
stamper = new PdfStamper(pdfTemplate, msTemp);
names = new string[stamper.AcroFields.Fields.Keys.Count];
stamper.AcroFields.Fields.Keys.CopyTo(names, 0);
foreach (string name in names)
{
stamper.AcroFields.RenameField(name, name + "_file" + k.ToString());
}
stamper.Close();
pdfFile = new PdfReader(msTemp.ToArray());
((PdfSmartCopy)pCopy).AddPage(pCopy.GetImportedPage(pdfFile, i));
pCopy.FreeReader(pdfFile);
}
}
pdfFile.Close();
pCopy.Close();
doc.Close();
return msOutput;
}
else if (files.Count == 1)
{
return new MemoryStream(files[0]);
}
return null;
}
Here is my simplified version of Jonathan's Merge code with namespaces added, and stamping removed.
public IO.MemoryStream MergePdfForms(System.Collections.Generic.List<byte[]> files)
{
if (files.Count > 1) {
using (System.IO.MemoryStream msOutput = new System.IO.MemoryStream()) {
using (iTextSharp.text.Document doc = new iTextSharp.text.Document()) {
using (iTextSharp.text.pdf.PdfSmartCopy pCopy = new iTextSharp.text.pdf.PdfSmartCopy(doc, msOutput) { PdfVersion = iTextSharp.text.pdf.PdfWriter.VERSION_1_7 }) {
doc.Open();
foreach (byte[] oFile in files) {
using (iTextSharp.text.pdf.PdfReader pdfFile = new iTextSharp.text.pdf.PdfReader(oFile)) {
for (i = 1; i <= pdfFile.NumberOfPages; i++) {
pCopy.AddPage(pCopy.GetImportedPage(pdfFile, i));
pCopy.FreeReader(pdfFile);
}
}
}
}
}
return msOutput;
}
} else if (files.Count == 1) {
return new System.IO.MemoryStream(files[0]);
}
return null;
}
to merge PDF see "Merging two pdf pages into one using itextsharp"
Below is my code for pdf merging.Thanks Jonathan for giving suggestion abt renaming fields,which resolved the issues while merging pdf pages with form fields.
private static void CombineAndSavePdf(string savePath, List<string> lstPdfFiles)
{
using (Stream outputPdfStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
Document document = new Document();
PdfSmartCopy copy = new PdfSmartCopy(document, outputPdfStream);
document.Open();
PdfReader reader;
int totalPageCnt;
PdfStamper stamper;
string[] fieldNames;
foreach (string file in lstPdfFiles)
{
reader = new PdfReader(file);
totalPageCnt = reader.NumberOfPages;
for (int pageCnt = 0; pageCnt < totalPageCnt; )
{
//have to create a new reader for each page or PdfStamper will throw error
reader = new PdfReader(file);
stamper = new PdfStamper(reader, outputPdfStream);
fieldNames = new string[stamper.AcroFields.Fields.Keys.Count];
stamper.AcroFields.Fields.Keys.CopyTo(fieldNames, 0);
foreach (string name in fieldNames)
{
stamper.AcroFields.RenameField(name, name + "_file" + pageCnt.ToString());
}
copy.AddPage(copy.GetImportedPage(reader, ++pageCnt));
}
copy.FreeReader(reader);
}
document.Close();
}
}
Hi My applion is MVC3 c#, I am using itextsharp to produce PDF files for pre disgned forms. In this application I have to different forms. To generate a form I use:
public ActionResult TestPDF(long learnerID = 211, long courseID = 11)
{
var caseList = _studyCaseSvc.ListStudyCases().Where(x => x.Course_ID == courseID);
try
{
MemoryStream memoryStream = new MemoryStream();
PdfConcatenate whole = new PdfConcatenate(memoryStream);
foreach (var ca in caseList)
{
byte[] part = null;
if (ca.CaseType == "CTA")
{
part = GenerateEvaluationCAT_PDF(learnerID, ca.ID);
}
else if (ca.CaseType == "CTAH")
{
part = GenerateEvaluationCATH_PDF(learnerID, ca.ID);
}
else
{
part = null;
}
if (part != null)
{
PdfReader partReader = new PdfReader(part);
whole.AddPages(partReader);
partReader.Close();
}
}
whole.Close();
byte[] byteInfo = memoryStream.ToArray();
SendPdfToBrowser(byteInfo);
}
catch (Exception ex)
{
}
return null;
}
I get this error: An item with the same key has already been added. The error happens at AddPages. So I developed this simpler test:
private void merge()
{
try
{
FileStream output = new FileStream("p3.pdf", FileMode.Create);
PdfConcatenate pdfConcatenate = new PdfConcatenate(output, true);
PdfReader r1 = new PdfReader("p2.pdf");
MemoryStream memoryStream = new MemoryStream();
PdfStamper pdfStamper = new PdfStamper(r1, memoryStream);
pdfStamper.FormFlattening = true;
pdfStamper.Close();
PdfReader r2 = new PdfReader(memoryStream.ToArray());
//pdfConcatenate.AddPages(tempReader);
pdfConcatenate.Open();
int n = r1.NumberOfPages;
for (int i = 1; i <= n; i++)
{
PdfImportedPage imp = pdfConcatenate.Writer.GetImportedPage(r1, i);
pdfConcatenate.Writer.AddPage(imp);
}
pdfConcatenate.Writer.FreeReader(r1);
pdfStamper.Close();
r1.Close();
pdfConcatenate.Close();
}
catch (Exception ex)
{
}
}
Same error.
Well, the problem is the misconception that you can combine multiple PDF files into one by simply concatenating them. That is wrong for PDFs (just like it is wrong for most binary file formats).
Thus, you should update your GenerateAllEvaluation_PDF method to have a PdfConcatenate instance instead of your byte Array whole, cf. http://api.itextpdf.com/itext/com/itextpdf/text/pdf/PdfConcatenate.html, open each byte array returned by your GenerateEvaluationCATH_PDF method in a PdfReader, add all pages of these readers to the PdfConcatenate and eventually return the bytes generated by that class.
EDIT (I'm more into Java than C#, thus forgive minor errors)
PdfConcatenate whole = new PdfConcatenate(...);
foreach (var ca in caseList)
{
byte[] part = null;
if (ca.CaseType == "CTA")
{
part = GenerateEvaluationCAT_PDF(learnerID, ca.ID);
}
else if (ca.CaseType == "CTAH")
{
part = GenerateEvaluationCATH_PDF(learnerID, ca.ID);
}
else
{
part = ???;
}
PdfReader partReader = new PdfReader(part);
whole.AddPages(partReader);
partReader.Close();
}
The PdfConcatenate can be constructed with a MemoryStream from which you can retrieve the final byte[].
PS: PdfConcatenate may not be a part of iTextSharp version 4.x yet but it is merely a convenience wrapper of PdfCopy and PdfSmartCopy. Thus, you may simply have a look at the sources of iTextSharp (OSS after all) and be inspired: PdfConcatenate.cs.
We are using itextsharp to create a single PDF from multiple PDF files. How do I insert a new page into a PDF file that has multiple pages already in the file? When I use add page it is overwriting the existing pages and only saves the 1 page that was selected.
Here is the code that I am using to add the page to the existing PDF:
PdfReader reader = new PdfReader(sourcePdfPath);
Document document = new Document(reader.GetPageSizeWithRotation(1));
PdfCopy pdfCopy = new PdfCopy(document, new System.IO.FileStream(outputPdfPath, System.IO.FileMode.Create));
MemoryStream memoryStream = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(document, memoryStream);
document.AddDocListener(writer);
document.Open();
for (int p = 1; p <= reader.NumberOfPages; p++)
{
if (pagesToExtract.FindIndex(s => s == p) == -1) continue;
document.SetPageSize(reader.GetPageSize(p));
document.NewPage();
PdfContentByte cb = writer.DirectContent;
PdfImportedPage pageImport = writer.GetImportedPage(reader, p);
int rot = reader.GetPageRotation(p);
if (rot == 90 || rot == 270)
{
cb.AddTemplate(pageImport, 0, -1.0F, 1.0F, 0, 0, reader.GetPageSizeWithRotation(p).Height);
}
else
{
cb.AddTemplate(pageImport, 1.0F, 0, 0, 1.0F, 0, 0);
}
pdfCopy.AddPage(pageImport);
}
pdfCopy.Close();
This code works. You need to have a different file to output the results.
private static void AppendToDocument(string sourcePdfPath1, string sourcePdfPath2, string outputPdfPath)
{
using (var sourceDocumentStream1 = new FileStream(sourcePdfPath1, FileMode.Open))
{
using (var sourceDocumentStream2 = new FileStream(sourcePdfPath2, FileMode.Open))
{
using (var destinationDocumentStream = new FileStream(outputPdfPath, FileMode.Create))
{
var pdfConcat = new PdfConcatenate(destinationDocumentStream);
var pdfReader = new PdfReader(sourceDocumentStream1);
var pages = new List<int>();
for (int i = 0; i < pdfReader.NumberOfPages; i++)
{
pages.Add(i);
}
pdfReader.SelectPages(pages);
pdfConcat.AddPages(pdfReader);
pdfReader = new PdfReader(sourceDocumentStream2);
pages = new List<int>();
for (int i = 0; i < pdfReader.NumberOfPages; i++)
{
pages.Add(i);
}
pdfReader.SelectPages(pages);
pdfConcat.AddPages(pdfReader);
pdfReader.Close();
pdfConcat.Close();
}
}
}
}
I've tried this code, and it works for me, but don't forget to do some validations of the number of pages and existence of the paths you use
here is the code:
private static void AppendToDocument(string sourcePdfPath, string outputPdfPath, List<int> neededPages)
{
var sourceDocumentStream = new FileStream(sourcePdfPath, FileMode.Open);
var destinationDocumentStream = new FileStream(outputPdfPath, FileMode.Create);
var pdfConcat = new PdfConcatenate(destinationDocumentStream);
var pdfReader = new PdfReader(sourceDocumentStream);
pdfReader.SelectPages(neededPages);
pdfConcat.AddPages(pdfReader);
pdfReader.Close();
pdfConcat.Close();
}
You could use something like this, where src is the IEnumerable<string> of input pdf filenames. Just make sure that your existing pdf file is one of those sources.
The PdfConcatenate class is in the latest iTextSharp release.
var result = "combined.pdf";
var fs = new FileStream(result, FileMode.Create);
var conc = new PdfConcatenate(fs, true);
foreach(var s in src) {
var r = new PdfReader(s);
conc.AddPages(r);
}
conc.Close();
PdfCopy is intended for use with an empty Document. You should add everything you want, one page at a time.
The alternative is to use PdfStamper.InsertPage(pageNum, rectangle) and then draw a PdfImportedPage onto that new page.
Note that PdfImportedPage only includes the page contents, not the annotations or doc-level information ("document structure", doc-level javascripts, etc) that page may have originally used... unless you use one with PdfCopy.
A Stamper would probably be more efficient and use less code, but PdfCopy will import all the page-level info, not just the page's contents.
This might be important, it might not. It depends on what page you're trying to import.
Had to even out the page count with a multiple of 4:
private static void AppendToDocument(string sourcePdfPath)
{
var tempFileLocation = Path.GetTempFileName();
var bytes = File.ReadAllBytes(sourcePdfPath);
using (var reader = new PdfReader(bytes))
{
var numberofPages = reader.NumberOfPages;
var modPages = (numberofPages % 4);
var pages = modPages == 0 ? 0 : 4 - modPages;
if (pages == 0)
return;
using (var fileStream = new FileStream(tempFileLocation, FileMode.Create, FileAccess.Write))
{
using (var stamper = new PdfStamper(reader, fileStream))
{
var rectangle = reader.GetPageSize(1);
for (var i = 1; i <= pages; i++)
stamper.InsertPage(numberofPages + i, rectangle);
}
}
}
File.Delete(sourcePdfPath);
File.Move(tempFileLocation, sourcePdfPath);
}
I know I'm really late to the part here, but I mixed a bit of the two best answers and created a method if anyone needs it that adds a list of source PDF documents to a single document using itextsharp.
private static void appendToDocument(List<string> sourcePDFList, string outputPdfPath)
{
//Output document name and creation
FileStream destinationDocumentStream = new FileStream(outputPdfPath, FileMode.Create);
//Object to concat source pdf's to output pdf
PdfConcatenate pdfConcat = new PdfConcatenate(destinationDocumentStream);
//For each source pdf in list...
foreach (string sourcePdfPath in sourcePDFList)
{
//If file exists...
if (File.Exists(sourcePdfPath))
{
//Open the document
FileStream sourceDocumentStream = new FileStream(sourcePdfPath, FileMode.Open);
//Read the document
PdfReader pdfReader = new PdfReader(sourceDocumentStream);
//Create an int list
List<int> pages = new List<int>();
//for each page in pdfReader
for (int i = 1; i < pdfReader.NumberOfPages + 1; i++)
{
//Add that page to the list
pages.Add(i);
}
//Add that page to the pages to add to ouput document
pdfReader.SelectPages(pages);
//Add pages to output page
pdfConcat.AddPages(pdfReader);
//Close reader
pdfReader.Close();
}
}
//Close pdfconcat
pdfConcat.Close();
}
The code snippet below returns a corrupt PDF document however if I return mergedDocument instead it always returns a valid PDF. mergedDocument is based on a PDF file i created using Word, whereas completed document is entirely programmatically generated. The code "works" in that it throws no exceptions. Why is iTextSharp creating a corrupt PDF?
byte[] completedDocument = null;
using (MemoryStream streamCompleted = new MemoryStream())
{
using (Document document = new Document())
{
PdfCopy copy = new PdfCopy(document, streamCompleted);
document.Open();
copy.Open();
foreach (var item in eventItems)
{
byte[] mergedDocument = null;
PdfReader reader = new PdfReader(pdfTemplates[item.DataTokens[NotifyTokenType.OrganisationID]]);
using (MemoryStream streamTemplate = new MemoryStream())
{
using (PdfStamper stamper = new PdfStamper(reader, streamTemplate))
{
foreach (var token in item.DataTokens)
{
if (stamper.AcroFields.Fields.Any(fld => fld.Key == token.Key.ToString()))
{
stamper.AcroFields.SetField(token.Key.ToString(), token.Value);
}
}
stamper.FormFlattening = true;
stamper.Writer.CloseStream = false;
}
mergedDocument = new byte[streamTemplate.Length];
streamTemplate.Position = 0;
streamTemplate.Read(mergedDocument, 0, (int)streamTemplate.Length);
}
reader = new PdfReader(mergedDocument);
for (int i = 1; i <= reader.NumberOfPages; i++)
{
document.SetPageSize(PageSize.A4);
copy.AddPage(copy.GetImportedPage(reader, i));
}
}
completedDocument = new byte[streamCompleted.Length];
streamCompleted.Position = 0;
streamCompleted.Read(completedDocument, 0, (int)streamCompleted.Length);
}
}
return completedDocument;
You need to close the document and copy objects to flush the PDF writing buffer. This, however, causes some problems when trying to read the stream into an array. The fix for that is to use the ToArray() method of the MemoryStream which still works on closed streams. The changes I made have comments on them.
byte[] completedDocument = null;
using (MemoryStream streamCompleted = new MemoryStream())
{
using (Document document = new Document())
{
PdfCopy copy = new PdfCopy(document, streamCompleted);
document.Open();
copy.Open();
foreach (var item in eventItems)
{
byte[] mergedDocument = null;
PdfReader reader = new PdfReader(pdfTemplates[item.DataTokens[NotifyTokenType.OrganisationID]]);
using (MemoryStream streamTemplate = new MemoryStream())
{
using (PdfStamper stamper = new PdfStamper(reader, streamTemplate))
{
foreach (var token in item.DataTokens)
{
if (stamper.AcroFields.Fields.Any(fld => fld.Key == token.Key.ToString()))
{
stamper.AcroFields.SetField(token.Key.ToString(), token.Value);
}
}
stamper.FormFlattening = true;
stamper.Writer.CloseStream = false;
}
//Copy the stream's bytes
mergedDocument = streamTemplate.ToArray();
}
reader = new PdfReader(mergedDocument);
for (int i = 1; i <= reader.NumberOfPages; i++)
{
document.SetPageSize(PageSize.A4);
copy.AddPage(copy.GetImportedPage(reader, i));
}
//Close the document and the copy
document.Close();
copy.Close();
}
//ToArray() can operate on closed streams
completedDocument = streamCompleted.ToArray();
}
}
return completedDocument;
Also make sure your html doesn't contains hr tag while converting html to pdf
hdnEditorText.Value.Replace("\"", "'").Replace("<hr />", "").Replace("<hr/>", "")