I'm struggling with flattening using iTextSharp 5.5.10. This code represent a variety of efforts to flatten this PDF following code found on SO and Google searches.
void Process()
{
PdfReader reader = new PdfReader(mInFileName);
// 1 type of fields
AcroFields formFields = reader.AcroFields;
int countAcroFields = formFields.Fields.Count;
// 2 another type of fields
PRAcroForm form = reader.AcroForm;
List<PRAcroForm.FieldInformation> fieldList = form.Fields;
// 3 i think this is the same as 2
PdfDictionary acroForm = reader.Catalog.GetAsDict(PdfName.ACROFORM);
PdfArray acroFields = (PdfArray)PdfReader.GetPdfObject(acroForm.Get(PdfName.FIELDS), acroForm);
// 4 another type of fields
XfaForm xfaForm = formFields.Xfa;
bool flatten = false;
if (countAcroFields > 0)
{
//No fields found
}
else if (fieldList.Count > 0)
{
//No fields found
}
else if (xfaForm.XfaPresent == true)
{
//No xfaForms found
ReadXfa(reader);
}
else
{
// Yes, there are annotations and this code extracts them but does NOT flatten them
PdfDictionary page = reader.GetPageN(1);
PdfArray annotsArray = page.GetAsArray(PdfName.ANNOTS);
if ( annotsArray == null)
return;
else
{
List<string> namedFieldToFlatten = new List<string>();
foreach (var item in annotsArray)
{
var annot = (PdfDictionary)PdfReader.GetPdfObject(item);
var type = PdfReader.GetPdfObject(annot.Get(PdfName.TYPE)).ToString(); //Expecting be /Annot
var subtype = PdfReader.GetPdfObject(annot.Get(PdfName.SUBTYPE)).ToString(); //Expecting be /Widget
var fieldType = PdfReader.GetPdfObject(annot.Get(PdfName.FT)).ToString(); //Expecting be /Tx
if (annot.Get(PdfName.TYPE).Equals(PdfName.ANNOT) &&
annot.Get(PdfName.SUBTYPE).Equals(PdfName.WIDGET))
{
if (annot.Get(PdfName.FT).Equals(PdfName.TX))
{
flatten = true;
var textLabel = PdfReader.GetPdfObject(annot.Get(PdfName.T)).ToString(); //Name of textbox field
namedFieldToFlatten.Add(textLabel);
var fieldValue = PdfReader.GetPdfObject(annot.Get(PdfName.V)).ToString(); //Value of textbox
Console.WriteLine($"Found Label={textLabel} Value={fieldValue}");
}
}
}
if (flatten == true)
{
// Flatten the PDF [11/9/2016 15:10:06]
string foldername = Path.GetDirectoryName(mInFileName);
string basename = Path.GetFileNameWithoutExtension(mInFileName);
string outName = $"{foldername}\\{basename}_flat.pdf";
using (var fStream = new FileStream(outName, FileMode.Create))
{
//This totally removes the fields instead of flattening them
var stamper = new PdfStamper(reader, fStream) { FormFlattening = true, FreeTextFlattening = true, AnnotationFlattening = true };
var stamperForm = stamper.AcroFields;
stamperForm.GenerateAppearances = true;
foreach (var item in namedFieldToFlatten)
{
stamper.PartialFormFlattening(item);
}
stamper.Close();
reader.Close();
}
}
}
}
}
Any tips on how to turn the "fields" into text as flatten does in Acrobat?
This is the PDF I'm trying to flatten.
This is what the output of this code.
The problem is that your form is broken. It indeed has /Annots, and those annotations are widget annotations that also have entries that are meant to be entries in a field dictionary, but there is no form in the PDF. Or rather: there is a form, but the /Fields array is empty.
As the /Fields array is empty, there are no fields to flatten. The widget annotations are removed and you don't see anything. This is not an iText bug: you need to fix the form before filling it out.
In Java, you'd fix the form like this:
PdfReader reader = new PdfReader(src);
PdfDictionary root = reader.getCatalog();
PdfDictionary form = root.getAsDict(PdfName.ACROFORM);
PdfArray fields = form.getAsArray(PdfName.FIELDS);
PdfDictionary page;
PdfArray annots;
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
page = reader.getPageN(i);
annots = page.getAsArray(PdfName.ANNOTS);
for (int j = 0; j < annots.size(); j++) {
fields.add(annots.getAsIndirectObject(j));
}
}
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
stamper.close();
reader.close();
It should be fairly easy to port this to C#. I'm not a C# developer, but this is an (untested) attempt at making a port:
PdfReader reader = new PdfReader(src);
PdfDictionary root = reader.Catalog;
PdfDictionary form = root.GetAsDict(PdfName.ACROFORM);
PdfArray fields = form.GetAsArray(PdfName.FIELDS);
PdfDictionary page;
PdfArray annots;
for (int i = 1; i <= reader.NumberOfPages; i++) {
page = reader.GetPageN(i);
annots = page.GetAsArray(PdfName.ANNOTS);
for (int j = 0; j < annots.Size; j++) {
fields.Add(annots.GetAsIndirectObject(j));
}
}
PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create));
stamper.Close();
reader.Close();
Once you've fixed the form like this, you'll be able to flatten it correctly.
Related
I'm a little bit lost with PDF standards for data extraction.
I'm using iText and looking for extraction of clickable HTML link within a PDF + Text that contains those links.
I achieved to get HTML links, but how can I get text from those links ?
public static List<string> linkCollector(string pdfPath)
{
List<string> linkCollection = new List<string>();
var reader = new PdfReader(pdfPath);
PdfDocument pdfDoc = new PdfDocument(reader);
for (int page = 1; page <= pdfDoc.GetNumberOfPages(); page++)
{
var pageDict = pdfDoc.GetPage(page);
var annotList = pageDict.GetAnnotations();
for (int i=0;i< annotList.Count(); i++)
{
PdfObject B = annotList[i].GetPdfObject();
var annotDict = B as PdfDictionary;
var linkDict = (PdfDictionary)annotDict.GetAsDictionary(PdfName.A);
var sUri = linkDict.Get(PdfName.URI).ToString();
linkCollection.Add(sUri);
}
}
return linkCollection;
}
I took this C# example and tried to get the attachments as a PdfDocument, but I couldn't figure out how to do it.
In the end I would like to simply merge every pdf file contained in a portfolio into a single "normal" pdf file. Every non-pdf attachment should be ignored.
Edit:
(Okay, sorry for being too vague. By saying what I want to achieve, I simply wanted to make it easier for you guys to help me. I did not want to make you write the program for me.)
So, here's part of the code from the linked example:
protected void ManipulatePdf(String dest)
{
PdfDocument pdfDoc = new PdfDocument(new PdfReader(SRC), new PdfWriter(dest));
PdfDictionary root = pdfDoc.GetCatalog().GetPdfObject();
PdfDictionary names = root.GetAsDictionary(PdfName.Names);
PdfDictionary embeddedFiles = names.GetAsDictionary(PdfName.EmbeddedFiles);
PdfArray namesArray = embeddedFiles.GetAsArray(PdfName.Names);
// Remove the description of the embedded file
namesArray.Remove(0);
// Remove the reference to the embedded file.
namesArray.Remove(0);
pdfDoc.Close();
}
Instead of removing anything from the source document, I would like to know how to get the PdfDocument object(s) out of the PdfArray if possible.
Sample file:
http://www.mediafire.com/file/c4tw07wci8swdx9/NPort_5000.pdf/file
Solution by mkl ported to C#:
PdfNameTree embeddedFilesTree = pdfDocument.GetCatalog().GetNameTree(PdfName.EmbeddedFiles);
IDictionary<string, PdfObject> embeddedFilesMap = embeddedFilesTree.GetNames();
List<PdfStream> embeddedPdfs = new List<PdfStream>();
foreach (PdfObject pdfObject in embeddedFilesMap.Values)
{
if (!(pdfObject is PdfDictionary))
continue;
PdfDictionary filespecDict = (PdfDictionary)pdfObject;
PdfDictionary embeddedFileDict = filespecDict.GetAsDictionary(PdfName.EF);
if (embeddedFileDict == null)
continue;
PdfStream embeddedFileStream = embeddedFileDict.GetAsStream(PdfName.F);
if (embeddedFileStream == null)
continue;
PdfName subtype = embeddedFileStream.GetAsName(PdfName.Subtype);
if (PdfName.ApplicationPdf.CompareTo(subtype) != 0)
continue;
embeddedPdfs.Add(embeddedFileStream);
}
if (embeddedPdfs.Count > 0)
{
PdfWriter pdfWriter = new PdfWriter("NPort_5000-flat.pdf", new WriterProperties().SetFullCompressionMode(true));
PdfDocument flatPdfDocument = new PdfDocument(pdfWriter);
PdfMerger pdfMerger = new PdfMerger(flatPdfDocument);
RandomAccessSourceFactory sourceFactory = new RandomAccessSourceFactory();
foreach (PdfStream pdfStream in embeddedPdfs)
{
PdfReader embeddedReader = new PdfReader(sourceFactory.CreateSource(pdfStream.GetBytes()), new ReaderProperties());
PdfDocument embeddedPdfDocument = new PdfDocument(embeddedReader);
pdfMerger.Merge(embeddedPdfDocument, 1, embeddedPdfDocument.GetNumberOfPages());
}
flatPdfDocument.Close();
}
To merge all pdf files from a PDF Portfolio to a normal pdf file you have to walk the name tree of EmbeddedFiles, retrieve the streams of all PDFs therein, and then merge all these PDFs.
You can do this as follows for a portfolio loaded in a PdfDocument pdfDocument (Java version; the OP edited a port to C# into his question body):
PdfNameTree embeddedFilesTree = pdfDocument.getCatalog().getNameTree(PdfName.EmbeddedFiles);
Map<String, PdfObject> embeddedFilesMap = embeddedFilesTree.getNames();
List<PdfStream> embeddedPdfs = new ArrayList<PdfStream>();
for (Map.Entry<String, PdfObject> entry : embeddedFilesMap.entrySet()) {
PdfObject pdfObject = entry.getValue();
if (!(pdfObject instanceof PdfDictionary))
continue;
PdfDictionary filespecDict = (PdfDictionary) pdfObject;
PdfDictionary embeddedFileDict = filespecDict.getAsDictionary(PdfName.EF);
if (embeddedFileDict == null)
continue;
PdfStream embeddedFileStream = embeddedFileDict.getAsStream(PdfName.F);
if (embeddedFileStream == null)
continue;
PdfName subtype = embeddedFileStream.getAsName(PdfName.Subtype);
if (!PdfName.ApplicationPdf.equals(subtype))
continue;
embeddedPdfs.add(embeddedFileStream);
}
Assert.assertFalse("No embedded PDFs found", embeddedPdfs.isEmpty());
try ( PdfWriter pdfWriter = new PdfWriter("NPort_5000-flat.pdf", new WriterProperties().setFullCompressionMode(true));
PdfDocument flatPdfDocument = new PdfDocument(pdfWriter) ) {
PdfMerger pdfMerger = new PdfMerger(flatPdfDocument);
RandomAccessSourceFactory sourceFactory = new RandomAccessSourceFactory();
for (PdfStream pdfStream : embeddedPdfs) {
try ( PdfReader embeddedReader = new PdfReader(sourceFactory.createSource(pdfStream.getBytes()), new ReaderProperties());
PdfDocument embeddedPdfDocument = new PdfDocument(embeddedReader)) {
pdfMerger.merge(embeddedPdfDocument, 1, embeddedPdfDocument.getNumberOfPages());
}
}
}
(FlattenPortfolio test testFlattenNPort_5000)
I need to create a Table Of Contents with page numbers, but I don't know how. Next format:
heading1 ----------------page number
subHeading1---------------page number
subHeading2---------------page number
heading2-----------------page number
I read a few articles and didn't understand. In particular, I mean this article, where "Named destinations" and "GoTo actions" I think it is useful for me, but I don't know how to use it in iTextSharp.
In my code, I have got a few "Chapter" and "Section", and I want to take it and create a TOC. I've understood that I need to use PdfPageEventHelper and OnChapter.
You've probably implemented this yourself by name, but I made a small example myself for the sake of completeness.
Please take a look at the CreateTOC example. It creates a PDF with some random text:
You can clearly see the titles and the content under the titles. After we have added all our content, we start a new page, and we add a table of contents:
The table of contents is composed by a series of key-value pairs, where the key is the title and the value is the page number. We create this list in a page event:
public class TOCEvent extends PdfPageEventHelper {
protected List<SimpleEntry<String, Integer>> toc = new ArrayList<>();
#Override
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
toc.add(new SimpleEntry(text, writer.getPageNumber()));
}
public List getTOC() {
return toc;
}
}
We use this page event like this:
public void createPdf(String dest) throws IOException, DocumentException {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
TOCEvent event = new TOCEvent();
writer.setPageEvent(event);
document.open();
for (int i = 0; i < 10; i++) {
String title = "This is title " + i;
Chunk c = new Chunk(title, titleFont);
c.setGenericTag(title);
document.add(new Paragraph(c));
for (int j = 0; j < 50; j++) {
document.add(new Paragraph("Line " + j + " of title " + i));
}
}
document.newPage();
document.add(new Paragraph("Table of Contents", titleFont));
Chunk dottedLine = new Chunk(new DottedLineSeparator());
List<SimpleEntry<String, Integer>> entries = event.getTOC();
Paragraph p;
for (SimpleEntry<String, Integer> entry : entries) {
p = new Paragraph(entry.getKey());
p.add(dottedLine);
p.add(String.valueOf(entry.getValue()));
document.add(p);
}
document.close();
}
First we create an instance of the event and we declare it to the writer:
TOCEvent event = new TOCEvent();
writer.setPageEvent(event);
We mark the titles using setGenericTag():
String title = "This is title " + i;
Chunk c = new Chunk(title, titleFont);
c.setGenericTag(title);
document.add(new Paragraph(c));
Once we've finished adding the content, we get all the entries:
List<SimpleEntry<String, Integer>> entries = event.getTOC();
We loop over this list and compose a Paragraph for every entry:
for (SimpleEntry<String, Integer> entry : entries) {
p = new Paragraph(entry.getKey());
p.add(dottedLine);
p.add(String.valueOf(entry.getValue()));
document.add(p);
}
No one can argue that this was difficult. The event class takes less than 10 lines of code. Adding support for subheadings will add a handful of lines, but that shouldn't be difficult too. It's a matter of building a tree structure, and introducing some indentation where necessary.
Thanks for the example, i needed this in C# and with multicolumn, so i rewrote this example as below:
namespace GerarPDF
{
public class GerarPDF
{
public const String DEST = "results/example.pdf";
public GerarPDF()
{
FileInfo file = new FileInfo(String.Concat(AppDomain.CurrentDomain.BaseDirectory, #"/", DEST));
file.Directory.Create();
this.createPdf(file.FullName);
}
public void createPdf(String dest)
{
FileStream fs = new FileStream(dest, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
Document document = new Document(PageSize.LETTER);
PdfWriter writer = PdfWriter.GetInstance(document, fs);
document.Open();
TOCEvent evento = new TOCEvent();
writer.PageEvent = evento;
for (int i = 0; i < 10; i++)
{
String title = "This is title " + i;
Chunk c = new Chunk(title, new Font());
c.SetGenericTag(title);
document.Add(new Paragraph(c));
for (int j = 0; j < 50; j++)
{
document.Add(new Paragraph("Line " + j + " of title " + i + " page: " + writer.PageNumber));
}
}
document.NewPage();
document.Add(new Paragraph("Table of Contents", new Font()));
Chunk dottedLine = new Chunk(new DottedLineSeparator());
List<PageIndex> entries = evento.getTOC();
MultiColumnText columns = new MultiColumnText();
columns.AddRegularColumns(72, 72 * 7.5f, 24, 2);
Paragraph p;
for (int i = 0; i < 10; i++)
{
foreach (PageIndex pageIndex in entries)
{
Chunk chunk = new Chunk(pageIndex.Text);
chunk.SetAction(PdfAction.GotoLocalPage(pageIndex.Name, false));
p = new Paragraph(chunk);
p.Add(dottedLine);
chunk = new Chunk(pageIndex.Page.ToString());
chunk.SetAction(PdfAction.GotoLocalPage(pageIndex.Name, false));
p.Add(chunk);
columns.AddElement(p);
}
}
document.Add(columns);
document.Close();
}
public class TOCEvent : PdfPageEventHelper
{
protected int counter = 0;
protected List<PageIndex> toc = new List<PageIndex>();
public override void OnGenericTag(PdfWriter writer, Document document, Rectangle rect, string text)
{
String name = "dest" + (counter++);
int page = writer.PageNumber;
toc.Add(new PageIndex() { Text = text, Name = name, Page = page });
writer.DirectContent.LocalDestination(name, new PdfDestination(PdfDestination.FITH, rect.GetTop(0)));
}
public List<PageIndex> getTOC()
{
return toc;
}
}
}
public class PageIndex
{
public string Text { get; set; }
public string Name { get; set; }
public int Page { get; set; }
}
}
this snipplet can it be recursive, the basic concept is:
List<PdfPCell> celdas = new List<PdfPCell>();
string urlSection=String.empty;
var estatus = new Phrase();
estatus.Leading = 25;
if (streams != null && streams.Any())
primero = streams.FirstOrDefault(x => x.Id == enlace.Id);
if (primero != null)
urlSection = primero.UrlSection;
//For the code and generate hyperlink:
Chunk espacioTab = new Chunk(" " + enlace.Name, baseFontBig );
espacioTab = Visor.Servicios.GeneracionPDF.PDFUtils.GenerarVinculo(" " + enlace.Name, urlSection, baseFontBig);
estatus.Add(espacioTab);
if (incluirPaginado)
{
if (primero != null)
actualPage = primero.TotalPages;
else
actualPage = 0;
///This is important, generate dots like "...." to chunk end
estatus.Add(new Chunk(new iTextSharp.text.pdf.draw.DottedLineSeparator()));
var linkPagina = new Chunk(actualPage.ToString());
linkPagina = Visor.Servicios.GeneracionPDF.PDFUtils.GenerarVinculo(actualPage.ToString(), urlSection, baseFontBig );
estatus.Add(linkPagina);
resultado.paginaFinal = actualPage;
}
//This is for add to your cell or table
PdfPCell rightCell = new PdfPCell()
{
Border = PdfPCell.NO_BORDER,
Colspan = 3,
PaddingLeft = espacioInicial.Length,
ExtraParagraphSpace = 10,
};
rightCell.AddElement(estatus);
celdas.Add(rightCell);
And create a new method, this create a hyperlinkand you can invoque when you want
/*Generar Vinculo (create hyperlink)**/
public static Chunk GenerarVinculo(String tituloMostrar, string urlDirecion, iTextSharp.text.Font fuente)
{
Chunk espacioTab = new Chunk();
try
{
if (String.IsNullOrEmpty(urlDirecion))
urlDirecion = "Indice de Contenido";
espacioTab = new Chunk(tituloMostrar, fuente);
var accion = PdfAction.GotoLocalPage(urlDirecion, false);
espacioTab.SetAction(accion);
}
catch (Exception error) { }
return espacioTab;
}
Hope helps someone
I have a pdf with buttons that take you out to web links. I used iTextSharp to split these into separate PDFs (1 per page) per outside requirements. ISSUE: Any button that has multiple positions, lost the actions.
QUESTION: Does anyone know how to update these actions? I can open the new file, but I'm not sure how to go about using the PdfStamper to add an AA to this Annotation
So when opening the original file, you could get to the Additional Action by doing this:
var r = new PdfReader(f.FullName);
var positionsOfThisButton = r.AcroFields.GetFieldPositions("14");
var field = r.AcroForm.GetField("14")
var targetObject = PdfReader.GetPdfObject(field.Ref);
var kids = targetObject.GetAsArray(PdfName.KIDS);
foreach (var k in kids){
var ko = (PdfDictionary)(k.IsIndirect() ? PdfReader.GetPdfObject(k) : k);
var aaObj = ko.Get(PdfName.AA);
//(aaObj is NULL in the new file)
var aa = (PdfDictionary)(aaObj.IsIndirect() ? PdfReader.GetPdfObject(aaObj) : aaObj);
var dObj = aa.Get(PdfName.D);
var d = (PdfDictionary)(dObj.IsIndirect() ? PdfReader.GetPdfObject(dObj) : dObj);
Debug.WriteLine("S:" + d.GetAsName(PdfName.S).ToString() );
//returns S:/Uri
Debug.WriteLine("URI:" + d.GetAsString(PdfName.URI).ToString() );
//returns URI:http://www.somesite.com/etc
}
Thanks for any help.
FYI ONLY - The following is how I split the files:
List<byte[]> Get(FileInfo f) {
List<byte[]> outputFiles = new List<byte[]>();
var reader = new PdfReader(f.FullName);
int n = reader.NumberOfPages;
reader.Close();
for (int i = n; i > 0; i--) {
reader = new PdfReader(f.FullName);
using (var document = new Document(reader.GetPageSizeWithRotation(1))) {
using (var outputStream = new MemoryStream()) {
using (var writer = new PdfCopy(document, outputStream)) {
writer.SetMergeFields();
writer.PdfVersion = '6';
document.Open();
writer.AddDocument(reader, new List<int> { i });
document.Close();
writer.Close();
}
outputFiles.Insert(0, outputStream.ToArray());
}
}
reader.Close();
}
return outputFiles;
}
The project is in C# and use iTextSharp.
I have a dictionary with a title (string) and file content (byte array). I loop through this dictionary and merge all files together. What I need now is to add bookmarks to the start of the first page in each file, but I should not add any new pages or text to the final document. I have tried different solutions, but all seem to add a table of contents page, a new page before each page or some text at the start of the page.
None of the files have bookmarks originally.
I am looking for a bookmarks structure that looks something like this:
File1
File2
SomeCategory
File3
File4
I would very much appreciate it if anyone could point me in the right direction.
My function for merging the files looks like this:
/// <summary>
/// Merge PDF files, and stamp certificates. This is a modified version of the example in the link below.
/// See: http://www.codeproject.com/Articles/28283/Simple-NET-PDF-Merger for more information.
/// </summary>
/// <param name="sourceFiles">Files to be merged</param>
/// <returns>Byte array with the combined files.</returns>
public static byte[] MergeFiles(Dictionary<string, byte[]> sourceFiles)
{
var document = new Document();
var output = new MemoryStream();
try
{
// Initialize pdf writer
var writer = PdfWriter.GetInstance(document, output);
writer.PageEvent = new PdfPageEvents();
// Open document to write
document.Open();
var content = writer.DirectContent;
// Iterate through all pdf documents
foreach (var sourceFile in sourceFiles)
{
// Create pdf reader
var reader = new PdfReader(sourceFile.Value);
var numberOfPages = reader.NumberOfPages;
// Iterate through all pages
for (var currentPageIndex = 1; currentPageIndex <=
numberOfPages; currentPageIndex++)
{
// Determine page size for the current page
document.SetPageSize(
reader.GetPageSizeWithRotation(currentPageIndex));
// Create page
document.NewPage();
var importedPage =
writer.GetImportedPage(reader, currentPageIndex);
// Determine page orientation
var pageOrientation = reader.GetPageRotation(currentPageIndex);
if ((pageOrientation == 90) || (pageOrientation == 270))
{
content.AddTemplate(importedPage, 0, -1f, 1f, 0, 0,
reader.GetPageSizeWithRotation(currentPageIndex).Height);
}
else
{
content.AddTemplate(importedPage, 1f, 0, 0, 1f, 0, 0);
}
// Add stamp to certificates
if (sourceFile.Key.IsValidDocumentReference())
AddStamp(content, document, sourceFile.Key, currentPageIndex, numberOfPages);
}
}
}
catch (Exception exception)
{
throw new Exception("An unexpected exception occured during the merging process", exception);
}
finally
{
document.Close();
}
return output.GetBuffer();
}
Thanks to Bruno Lowagie who pointed me in the right direction, I was able to produce a solution to my problem.
This is my solution:
public static byte[] MergeFilesAndAddBookmarks(Dictionary<PrintDocument, byte[]> sourceFiles)
{
using (var ms = new MemoryStream())
{
using (var document = new Document())
{
using (var copy = new PdfCopy(document, ms))
{
//Order the files by chapternumber
var files = sourceFiles.GroupBy(f => f.Key.ChapterNumber);
document.Open();
var outlines = new List<Dictionary<string, object>>();
var pageIndex = 1;
foreach (var chapterGroup in files)
{
var map = new Dictionary<string, object>();
outlines.Add(map);
map.Add("Title", chapterGroup.First().Key.ChapterName);
var kids = new List<Dictionary<string, object>>();
map.Add("Kids", kids);
foreach (var sourceFile in chapterGroup)
{
using (var reader = new PdfReader(sourceFile.Value))
{
// add the pages
var n = reader.NumberOfPages;
for (var page = 0; page < n;)
{
if (page == 0)
{
var kid = new Dictionary<string, object>();
kids.Add(kid);
kid["Title"] = sourceFile.Key.Title;
kid["Action"] = "GoTo";
kid["Page"] = String.Format("{0} Fit", pageIndex);
}
copy.AddPage(copy.GetImportedPage(reader, ++page));
}
pageIndex += n;
reader.Close();
}
}
}
copy.Outlines = outlines;
document.Close();
copy.Close();
ms.Close();
}
}
return ms.ToArray();
}
}
}
public class PrintDocument
{
public string Title { get; set; }
public string ChapterName { get; set; }
public int ChapterNumber { get; set; }
}