I am trying to convert a FlowDocument to XPS. Here is how the FlowDocument is defined:
<FlowDocument PageHeight="29.7cm" PageWidth="21cm" PagePadding="2cm,2cm,2cm,2cm">
</FlowDocument>
It is inside a RichTextBox and is populated by a user. It is saved as a .rtf file (.xamlgave me the same results).
Here is the method I am using to save the doc:
public void UploadTemplate(TextRange content, string filename)
{
string destPath = Path.Combine(default_template_path, filename + ".rtf");
if (content.CanSave(DataFormats.Rtf))
{
using (var stream = new FileStream(destPath, FileMode.Create))
{
content.Save(stream, DataFormats.Rtf);
stream.Close();
}
}
}
And here is how I load the doc:
public void LoadTemplate(string template_path, TextRange content)
{
if (content.CanLoad(DataFormats.Rtf))
{
using (var stream = new FileStream(template_path, FileMode.Open))
{
content.Load(stream, DataFormats.Rtf);
stream.Close();
}
}
}
Finally this is the code I use for converting the FlowDocument to XPS:
public static MemoryStream FlowDocumentToXPS(FlowDocument flowDocument)
{
MemoryStream stream = new MemoryStream();
using (Package package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite))
{
using (XpsDocument xpsDoc = new XpsDocument(package, CompressionOption.Maximum))
{
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false);
DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;
paginator.PageSize = new Size(flowDocument.PageWidth, flowDocument.PageHeight);
rsm.SaveAsXaml(paginator);
rsm.Commit();
}
}
stream.Position = 0;
Console.WriteLine(stream.Length);
Console.WriteLine(stream.Position);
return stream;
}
After saving this stream to a .xps file the document pages seem like they are divided in 2 columns. This is what I get from that export. Can someone help me figure this out ?
For anyone that might stumble upon this. You must set the FlowDocument.ColumnWidth property when converting to XPS. In this case it would need to be something like this:
public static MemoryStream FlowDocumentToXPS(FlowDocument flowDocument)
{
flowDocument.ColumnWidth = (int)YourColumnWidth; // THIS IS THE KEY LINE
MemoryStream stream = new MemoryStream();
using (Package package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite))
{
using (XpsDocument xpsDoc = new XpsDocument(package, CompressionOption.Maximum))
{
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false);
DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;
paginator.PageSize = new Size(flowDocument.PageWidth, flowDocument.PageHeight);
rsm.SaveAsXaml(paginator);
rsm.Commit();
}
}
stream.Position = 0;
Console.WriteLine(stream.Length);
Console.WriteLine(stream.Position);
return stream;
}
I am trying to merge PDF documents and add extra pages to some of them. The merge part is working fine and now I am trying to figure out how to add an extra page by passing a link to the preexisting PDF page. How can I pass a link to a PDF page to targetDoc.AddPage(LINK)?
public static void MergePDFs(string targetPath, DataTable pdfs)
{
try
{
using (PdfSharp.Pdf.PdfDocument targetDoc = new PdfSharp.Pdf.PdfDocument())
{
foreach (DataRow pdf in pdfs.Rows)
{
using (PdfSharp.Pdf.PdfDocument pdfDoc = PdfSharp.Pdf.IO.PdfReader.Open(pdf["link"].ToString(), PdfDocumentOpenMode.Import))
{
for (int i = 0; i < pdfDoc.PageCount; i++)
{
targetDoc.AddPage(pdfDoc.Pages[i]);
}
}
}
targetDoc.Save(targetPath);
}
}
catch(Exception ex)
{
Console.Write(ex);
}
}
Stamping method
using (Stream pdfStream = new FileStream(sourceFileName, FileMode.Open))
{
using (Stream newpdfStream = new FileStream(newFileNameWithPath, FileMode.Create, FileAccess.ReadWrite))
{
iTextSharp.text.pdf.PdfReader pdfReader = new iTextSharp.text.pdf.PdfReader(pdfStream);
PdfStamper pdfStamper = new PdfStamper(pdfReader, newpdfStream);
PdfContentByte pdfContentByte = pdfStamper.GetOverContent(pageNumber);
BaseFont baseFont = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1250, BaseFont.NOT_EMBEDDED);
pdfContentByte.SetColorFill(BaseColor.RED);
pdfContentByte.SetFontAndSize(baseFont, 12);
pdfContentByte.BeginText();
pdfContentByte.ShowTextAligned(PdfContentByte.ALIGN_CENTER, inputText, Convert.ToInt32(xCoordinate), Convert.ToInt32(yCoordinate), 0);
pdfContentByte.EndText();
pdfStamper.Close();
}
}
To create a new, empty page call AddPage() without parameters.
targetDoc.AddPage();
You might need Clone() to create multiple copies of an existing imported page (also add (PdfPage)):
targetDoc.AddPage((PdfPage)pdfDoc.Pages[i].Clone());
I have the following string of code setting bacground for all pdf fields:
using (var pdfReader = new PdfReader(template))
{
using (var pdfStamper = new PdfStamper(pdfReader, new FileStream(fileName, FileMode.Create)){FormFlattening = true})
{
var fields = pdfStamper.AcroFields;
foreach (var field in fields.Fields)
{
fields.SetFieldProperty(field.Key, "bgcolor", new BaseColor(Color.Red), null);
}
}
}
However, the problem is that background is being set not for all fields, just for some of them. I debugged, and the method is being run for all fields, and returning true every time. Fields are having exact same properties (only name is different). I ran out of ideas, somebody pls help.
Thanks, but after 4 hours trying I figured it out. It's not filling fields having no value. So you have to put at least String.Empty as a value, and background is getting filled after that. May be it will be useful and will save couple of hours for somebody.
If you want to change a field property, after you changed it, you have to regenerate the field for making the edit effective . http://developers.itextpdf.com/question/how-change-text-color-acroform-field
Stream inputPdfStream = new FileStream(BASE_PATH + inputFileName, FileMode.Open, FileAccess.Read, FileShare.None);
using (Stream outputPdfStream = new FileStream(BASE_PATH + outputFileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
Document doc = new Document();
doc.Open();
var reader = new PdfReader(inputPdfStream);
var stamper = new PdfStamper(reader, outputPdfStream);
PdfWriter.GetInstance(doc, outputPdfStream);
AcroFields af = stamper.AcroFields;
if (colorInfos != null && colorInfos.Count > 0)
{
af.SetField("color_level_title", colorInfos[""]);
af.SetField("color_level_up_title", colorInfos[""]);
af.SetField("color_level_low_title", colorInfos[""]);
}
af.SetFieldProperty("hide_low_content", "bgcolor", BaseColor.WHITE, null);
af.RegenerateField("hide_low_content");
stamper.FormFlattening = true;
stamper.Close();
}
I'm using itext sharp to fill my form fields on my template with values.
I created the template using pdfescape.com
Here is my code that I use to place the values in the pdf template.
private static byte[] GeneratePdf(Dictionary<String, String> formKeys, String pdfPath)
{
var templatePath = System.Web.HttpContext.Current.Server.MapPath(pdfPath);
var reader = new PdfReader(templatePath);
var outStream = new MemoryStream();
var stamper = new PdfStamper(reader, outStream);
var form = stamper.AcroFields;
var fieldKeys = form.Fields.Keys;
// "Flatten" the form so it wont be editable/usable anymore
// stamper.FormFlattening = true;
foreach (KeyValuePair<String, String> pair in formKeys)
{
if (fieldKeys.Any(f => f == pair.Key))
{
form.SetField(pair.Key, pair.Value);
form.SetFieldProperty(pair.Key, "setfflags", PdfFormField.FF_READ_ONLY, null);
}
}
stamper.Close();
reader.Close();
return outStream.ToArray();
}
I first used the stamper.FormFlattening = true, but then the values weren't visible. So Instead of using the form flattening I just set the values as ready only and everything works fine.
Now I want to merge multiple of these pdf files using the the pdf merger by smart-soft
Once the merging is complete the values aren't visible. When I highlight over the form it highlights all the text, but I can't read it.I did research on this and read that the fields need to be flattened.
Here is an image of how it looks on the pdf when I highlight everything:
I don't know why my fields aren't visible when they are flattened, even if I don't use the merger. Is there something wrong with the code or the template? Alternatives will also be appreciated.
Btw my project is an asp-mvc project if that is relevant.
EDIT
I added the following code so that I first read the template, write the values to the form fields, close it, reopen it, flatten and then close it again as suggested by one of the comments. I just pass the result I get from the GeneratePdf function to this function:
private static byte[] flattenPdf(byte[] pdf)
{
var reader = new PdfReader(pdf);
var outStream = new MemoryStream();
var stamper = new PdfStamper(reader, outStream);
stamper.FormFlattening = true;
stamper.Close();
reader.Close();
return outStream.ToArray();
}
I still get the same result
I found a solution to this problem thanks to this answer by rhens
All I had to do is modify my GeneratePdf function by adding one line:
form.GenerateAppearances = true;
Here is the end result:
private static byte[] GeneratePdf(Dictionary<String, String> formKeys, String pdfPath)
{
var templatePath = System.Web.HttpContext.Current.Server.MapPath(pdfPath);
var reader = new PdfReader(templatePath);
var outStream = new MemoryStream();
var stamper = new PdfStamper(reader, outStream);
var form = stamper.AcroFields;
form.GenerateAppearances = true; //Added this line, fixed my problem
var fieldKeys = form.Fields.Keys;
foreach (KeyValuePair<String, String> pair in formKeys)
{
if (fieldKeys.Any(f => f == pair.Key))
{
form.SetField(pair.Key, pair.Value);
}
}
stamper.Close();
reader.Close();
return flattenPdf(outStream.ToArray());
}
and the flattenPdf stays the same as in my question.
Background: I need to provide a weekly report package for my sales staff. This package contains several (5-10) crystal reports.
Problem:
I would like to allow a user to run all reports and also just run a single report. I was thinking I could do this by creating the reports and then doing:
List<ReportClass> reports = new List<ReportClass>();
reports.Add(new WeeklyReport1());
reports.Add(new WeeklyReport2());
reports.Add(new WeeklyReport3());
<snip>
foreach (ReportClass report in reports)
{
report.ExportToDisk(ExportFormatType.PortableDocFormat, #"c:\reports\" + report.ResourceName + ".pdf");
}
This would provide me a folder full of the reports, but I would like to email everyone a single PDF with all the weekly reports. So I need to combine them.
Is there an easy way to do this without install any more third party controls? I already have DevExpress & CrystalReports and I'd prefer not to add too many more.
Would it be best to combine them in the foreach loop or in a seperate loop? (or an alternate way)
I had to solve a similar problem and what I ended up doing was creating a small pdfmerge utility that uses the PDFSharp project which is essentially MIT licensed.
The code is dead simple, I needed a cmdline utility so I have more code dedicated to parsing the arguments than I do for the PDF merging:
using (PdfDocument one = PdfReader.Open("file1.pdf", PdfDocumentOpenMode.Import))
using (PdfDocument two = PdfReader.Open("file2.pdf", PdfDocumentOpenMode.Import))
using (PdfDocument outPdf = new PdfDocument())
{
CopyPages(one, outPdf);
CopyPages(two, outPdf);
outPdf.Save("file1and2.pdf");
}
void CopyPages(PdfDocument from, PdfDocument to)
{
for (int i = 0; i < from.PageCount; i++)
{
to.AddPage(from.Pages[i]);
}
}
Here is a single function that will merge X amount of PDFs using PDFSharp
using PdfSharp;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
public static void MergePDFs(string targetPath, params string[] pdfs) {
using(var targetDoc = new PdfDocument()){
foreach (var pdf in pdfs) {
using (var pdfDoc = PdfReader.Open(pdf, PdfDocumentOpenMode.Import)) {
for (var i = 0; i < pdfDoc.PageCount; i++)
targetDoc.AddPage(pdfDoc.Pages[i]);
}
}
targetDoc.Save(targetPath);
}
}
This is something that I figured out, and wanted to share with you, using PdfSharp.
Here you can join multiple Pdfs in one, without the need of an output directory (following the input list order)
public static byte[] MergePdf(List<byte[]> pdfs)
{
List<PdfSharp.Pdf.PdfDocument> lstDocuments = new List<PdfSharp.Pdf.PdfDocument>();
foreach (var pdf in pdfs)
{
lstDocuments.Add(PdfReader.Open(new MemoryStream(pdf), PdfDocumentOpenMode.Import));
}
using (PdfSharp.Pdf.PdfDocument outPdf = new PdfSharp.Pdf.PdfDocument())
{
for(int i = 1; i<= lstDocuments.Count; i++)
{
foreach(PdfSharp.Pdf.PdfPage page in lstDocuments[i-1].Pages)
{
outPdf.AddPage(page);
}
}
MemoryStream stream = new MemoryStream();
outPdf.Save(stream, false);
byte[] bytes = stream.ToArray();
return bytes;
}
}
I used iTextsharp with c# to combine pdf files. This is the code I used.
string[] lstFiles=new string[3];
lstFiles[0]=#"C:/pdf/1.pdf";
lstFiles[1]=#"C:/pdf/2.pdf";
lstFiles[2]=#"C:/pdf/3.pdf";
PdfReader reader = null;
Document sourceDocument = null;
PdfCopy pdfCopyProvider = null;
PdfImportedPage importedPage;
string outputPdfPath=#"C:/pdf/new.pdf";
sourceDocument = new Document();
pdfCopyProvider = new PdfCopy(sourceDocument, new System.IO.FileStream(outputPdfPath, System.IO.FileMode.Create));
//Open the output file
sourceDocument.Open();
try
{
//Loop through the files list
for (int f = 0; f < lstFiles.Length-1; f++)
{
int pages =get_pageCcount(lstFiles[f]);
reader = new PdfReader(lstFiles[f]);
//Add pages of current file
for (int i = 1; i <= pages; i++)
{
importedPage = pdfCopyProvider.GetImportedPage(reader, i);
pdfCopyProvider.AddPage(importedPage);
}
reader.Close();
}
//At the end save the output file
sourceDocument.Close();
}
catch (Exception ex)
{
throw ex;
}
private int get_pageCcount(string file)
{
using (StreamReader sr = new StreamReader(File.OpenRead(file)))
{
Regex regex = new Regex(#"/Type\s*/Page[^s]");
MatchCollection matches = regex.Matches(sr.ReadToEnd());
return matches.Count;
}
}
Here is a example using iTextSharp
public static void MergePdf(Stream outputPdfStream, IEnumerable<string> pdfFilePaths)
{
using (var document = new Document())
using (var pdfCopy = new PdfCopy(document, outputPdfStream))
{
pdfCopy.CloseStream = false;
try
{
document.Open();
foreach (var pdfFilePath in pdfFilePaths)
{
using (var pdfReader = new PdfReader(pdfFilePath))
{
pdfCopy.AddDocument(pdfReader);
pdfReader.Close();
}
}
}
finally
{
document?.Close();
}
}
}
The PdfReader constructor has many overloads. It's possible to replace the parameter type IEnumerable<string> with IEnumerable<Stream> and it should work as well. Please notice that the method does not close the OutputStream, it delegates that task to the Stream creator.
PDFsharp seems to allow merging multiple PDF documents into one.
And the same is also possible with ITextSharp.
Combining two byte[] using iTextSharp up to version 5.x:
internal static MemoryStream mergePdfs(byte[] pdf1, byte[] pdf2)
{
MemoryStream outStream = new MemoryStream();
using (Document document = new Document())
using (PdfCopy copy = new PdfCopy(document, outStream))
{
document.Open();
copy.AddDocument(new PdfReader(pdf1));
copy.AddDocument(new PdfReader(pdf2));
}
return outStream;
}
Instead of the byte[]'s it's possible to pass also Stream's
There's some good answers here already, but I thought I might mention that pdftk might be useful for this task. Instead of producing one PDF directly, you could produce each PDF you need and then combine them together as a post-process with pdftk. This could even be done from within your program using a system() or ShellExecute() call.
You could try pdf-shuffler gtk-apps.org
I know a lot of people have recommended PDF Sharp, however it doesn't look like that project has been updated since june of 2008. Further, source isn't available.
Personally, I've been playing with iTextSharp which has been pretty easy to work with.
I combined the two above, because I needed to merge 3 pdfbytes and return a byte
internal static byte[] mergePdfs(byte[] pdf1, byte[] pdf2,byte[] pdf3)
{
MemoryStream outStream = new MemoryStream();
using (Document document = new Document())
using (PdfCopy copy = new PdfCopy(document, outStream))
{
document.Open();
copy.AddDocument(new PdfReader(pdf1));
copy.AddDocument(new PdfReader(pdf2));
copy.AddDocument(new PdfReader(pdf3));
}
return outStream.ToArray();
}
Following method gets a List of byte array which is PDF byte array and then returns a byte array.
using ...;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
public static class PdfHelper
{
public static byte[] PdfConcat(List<byte[]> lstPdfBytes)
{
byte[] res;
using (var outPdf = new PdfDocument())
{
foreach (var pdf in lstPdfBytes)
{
using (var pdfStream = new MemoryStream(pdf))
using (var pdfDoc = PdfReader.Open(pdfStream, PdfDocumentOpenMode.Import))
for (var i = 0; i < pdfDoc.PageCount; i++)
outPdf.AddPage(pdfDoc.Pages[i]);
}
using (var memoryStreamOut = new MemoryStream())
{
outPdf.Save(memoryStreamOut, false);
res = Stream2Bytes(memoryStreamOut);
}
}
return res;
}
public static void DownloadAsPdfFile(string fileName, byte[] content)
{
var ms = new MemoryStream(content);
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("content-disposition", $"attachment;filename={fileName}.pdf");
HttpContext.Current.Response.Buffer = true;
ms.WriteTo(HttpContext.Current.Response.OutputStream);
HttpContext.Current.Response.End();
}
private static byte[] Stream2Bytes(Stream input)
{
var buffer = new byte[input.Length];
using (var ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
ms.Write(buffer, 0, read);
return ms.ToArray();
}
}
}
So, the result of PdfHelper.PdfConcat method is passed to PdfHelper.DownloadAsPdfFile method.
PS: A NuGet package named [PdfSharp][1] need to be installed. So in the Package Manage Console window type:
Install-Package PdfSharp
Following method merges two pdfs( f1 and f2) using iTextSharp. The second pdf is appended after a specific index of f1.
string f1 = "D:\\a.pdf";
string f2 = "D:\\Iso.pdf";
string outfile = "D:\\c.pdf";
appendPagesFromPdf(f1, f2, outfile, 3);
public static void appendPagesFromPdf(String f1,string f2, String destinationFile, int startingindex)
{
PdfReader p1 = new PdfReader(f1);
PdfReader p2 = new PdfReader(f2);
int l1 = p1.NumberOfPages, l2 = p2.NumberOfPages;
//Create our destination file
using (FileStream fs = new FileStream(destinationFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
Document doc = new Document();
PdfWriter w = PdfWriter.GetInstance(doc, fs);
doc.Open();
for (int page = 1; page <= startingindex; page++)
{
doc.NewPage();
w.DirectContent.AddTemplate(w.GetImportedPage(p1, page), 0, 0);
//Used to pull individual pages from our source
}// copied pages from first pdf till startingIndex
for (int i = 1; i <= l2;i++)
{
doc.NewPage();
w.DirectContent.AddTemplate(w.GetImportedPage(p2, i), 0, 0);
}// merges second pdf after startingIndex
for (int i = startingindex+1; i <= l1;i++)
{
doc.NewPage();
w.DirectContent.AddTemplate(w.GetImportedPage(p1, i), 0, 0);
}// continuing from where we left in pdf1
doc.Close();
p1.Close();
p2.Close();
}
}
To solve a similar problem i used iTextSharp like this:
//Create the document which will contain the combined PDF's
Document document = new Document();
//Create a writer for de document
PdfCopy writer = new PdfCopy(document, new FileStream(OutPutFilePath, FileMode.Create));
if (writer == null)
{
return;
}
//Open the document
document.Open();
//Get the files you want to combine
string[] filePaths = Directory.GetFiles(DirectoryPathWhereYouHaveYourFiles);
foreach (string filePath in filePaths)
{
//Read the PDF file
using (PdfReader reader = new PdfReader(vls_FilePath))
{
//Add the file to the combined one
writer.AddDocument(reader);
}
}
//Finally close the document and writer
writer.Close();
document.Close();
Here is a link to an example using PDFSharp and ConcatenateDocuments
Here the solution http://www.wacdesigns.com/2008/10/03/merge-pdf-files-using-c
It use free open source iTextSharp library http://sourceforge.net/projects/itextsharp
I've done this with PDFBox. I suppose it works similarly to iTextSharp.