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
Related
Following the pattern in this example https://kb.itextpdf.com/home/it7kb/examples/toc-as-first-page
I implemented the table of contents (toc) within my own code. Everything works fine, with the following exception:
My toc has 3 pages and could grow
I can move the last two pages of pdf (2 pages of toc) to the top of
the document but when I try to move the first page of the toc to the
top I get a null exception
int tocPageNumber = pdfDoc.GetNumberOfPages();
pdfDoc.MovePage(tocPageNumber, 1);
pdfDoc.MovePage(tocPageNumber, 1);
pdfDoc.MovePage(tocPageNumber, 1); // Null Exception here
doc.Close();
This code is proof of concept. I'd have logic to determine how many pages there are in the toc and move them in a loop.
Screen shot:
Stack Trace:
at KernelExtensions.Get[TKey,TValue](IDictionary`2 col, TKey key)
at iText.Kernel.Pdf.PdfDictionary.Put(PdfName key, PdfObject value)
at iText.Kernel.Pdf.PdfPages.AddPage(Int32 index, PdfPage pdfPage)
at iText.Kernel.Pdf.PdfPagesTree.AddPage(Int32 index, PdfPage pdfPage)
at iText.Kernel.Pdf.PdfDocument.MovePage(Int32 pageNumber, Int32 insertBefore)
at BlueCoatExtractor.PDFHelper.buildPDFNatively1(String header, String dtStamp, Dictionary`2 bcPoliciesDict, String dest) in C:\Users\xyz\source\repos\bbb\bbb\PDFHelper.cs:line 216
Full Sample Code to recreate the issue:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using iText.IO.Font.Constants;
using iText.Kernel.Font;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Action;
using iText.Kernel.Pdf.Canvas.Draw;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Hyphenation;
using iText.Layout.Layout;
using iText.Layout.Properties;
using iText.Layout.Renderer;
using iText.Kernel.Utils;
namespace BlueCoatExtractor
{
public class C06E04_TOC_GoToNamed
{
public const String SRC = "Logs/jekyll_hyde.txt";
public const String DEST = "Output/jekyll_hyde_toc2.pdf";
internal static void initialize()
{
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
//new C06E04_TOC_GoToNamed().CreatePdf(DEST);
}
public static void CreatePdf()
{
//Initialize PDF document
PdfDocument pdf = new PdfDocument(new PdfWriter(DEST));
// Initialize document
Document document = new Document(pdf);
PdfFont font = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN);
PdfFont bold = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD);
document.SetTextAlignment(TextAlignment.JUSTIFIED).SetHyphenation(new HyphenationConfig("en", "uk", 3, 3))
.SetFont(font).SetFontSize(11);
StreamReader sr = File.OpenText(SRC);
String name;
String line;
Paragraph p;
bool title = true;
int counter = 0;
IList<KeyValuePair<String, KeyValuePair<String, int>>> toc = new List<KeyValuePair
<String, KeyValuePair<String, int>>>();
while ((line = sr.ReadLine()) != null)
{
p = new Paragraph(line);
p.SetKeepTogether(true);
if (title)
{
name = String.Format("title{0:00}", counter++);
KeyValuePair<String, int> titlePage = new KeyValuePair<string, int>(line, pdf.GetNumberOfPages());
p.SetFont(bold).SetFontSize(12).SetKeepWithNext(true).SetDestination(name).SetNextRenderer(new UpdatePageRenderer(p, titlePage));
title = false;
document.Add(p);
toc.Add(new KeyValuePair<string, KeyValuePair<string, int>>(name, titlePage));
}
else
{
p.SetFirstLineIndent(36);
if (String.IsNullOrEmpty(line))
{
p.SetMarginBottom(12);
title = true;
}
else
{
p.SetMarginBottom(0);
}
document.Add(p);
}
}
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
p = new Paragraph().SetFont(bold).Add("Table of Contents").SetDestination("toc");
document.Add(p);
toc.RemoveAt(0);
IList<TabStop> tabstops = new List<TabStop>();
tabstops.Add(new TabStop(580, TabAlignment.RIGHT, new DottedLine()));
foreach (KeyValuePair<String, KeyValuePair<String, int>> entry in toc)
{
KeyValuePair<String, int> text = entry.Value;
p = new Paragraph().AddTabStops(tabstops).Add(text.Key).Add(new Tab()).Add(text.Value.ToString()).SetAction
(PdfAction.CreateGoTo(entry.Key));
document.Add(p);
}
//Close document
int tocPageNumber = pdf.GetNumberOfPages();
pdf.MovePage(tocPageNumber, 1);
pdf.MovePage(tocPageNumber, 1);
pdf.MovePage(tocPageNumber, 1); // null exception
document.Close();
}
protected internal class UpdatePageRenderer : ParagraphRenderer
{
protected internal KeyValuePair<String, int> entry;
public UpdatePageRenderer(Paragraph modelElement, KeyValuePair
<String, int> entry)
: base(modelElement)
{
this.entry = entry;
}
public override LayoutResult Layout(LayoutContext layoutContext)
{
LayoutResult result = base.Layout(layoutContext);
int pageNumber = layoutContext.GetArea().GetPageNumber();
entry = new KeyValuePair<string, int>(entry.Key, pageNumber);
return result;
}
}
}
}
Use the following text file: https://github.com/itext/i7js-highlevel/blob/develop/src/main/resources/txt/jekyll_hyde.txt
Based on the pattern in this code: https://github.com/itext/i7ns-samples/blob/develop/itext/itext.samples/itext/samples/sandbox/stamper/ReorderPages.cs
I created a new document and copied the content from the original into it. The table of content links still functioned as expected.
fs = File.Create("Output/resultDoc.pdf");
fs.Dispose();
PdfDocument srcDoc = new PdfDocument(new PdfReader(dest));
var srcTotalPages = srcDoc.GetNumberOfPages();
PdfDocument resultDoc = new PdfDocument(new PdfWriter("Output/resultDoc.pdf"));
resultDoc.InitializeOutlines();
IList<int> pages = new List<int>();
pages.Add(101);
pages.Add(102);
pages.Add(103);
for (int i = 1; i <= 100; i++)
{
pages.Add(i);
}
srcDoc.CopyPagesTo(pages, resultDoc);
resultDoc.Close();
srcDoc.Close();
I'm using IText 7 in dotnet core to manipulate a PDF file.
Once the process has completed I'm finding myself with a 5-page PDF at 14.2MB.
When I open the generated PDF in Adobe Reader I am prompted to save when closing Reader. If i choose Yes it shrinks the pdf by 50%.
Here is the pertinent code:
// ASDLine Model
public class ASDLine
{
public long ARInvoiceHeaderKey { get; set; }
public string BillingCode { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public bool? MatchProfile { get; set; }
}
public AuditSummaryDocument Create()
{
// Load in the template
MemoryStream outputDoc = new MemoryStream();
// Attempt to shrink the PDF..(didn't make any difference)
var w = new PdfWriter(outputDoc);
var wb = w.IsFullCompression();
w.SetCompressionLevel(9);
w.SetSmartMode(true);
PdfDocument pdfDoc = new PdfDocument(new PdfReader("mytemplate.pdf"), w);
font = PdfFontFactory.CreateRegisteredFont("Calibri");
Document doc = new Document(pdfDoc);
PdfAcroForm pdfAcroForm = PdfAcroForm.GetAcroForm(pdfDoc, true);
var fields = pdfAcroForm.GetFormFields();
//header:
fields["generated"].SetValue($"{DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()}");
// .... lots of fields
pdfAcroForm.FlattenFields();
// Add lines
Table table = GenerateTable();
table.SetRelativePosition(0, 510, 0, 0);
doc.Add(table);
// attempt to shrink PDF
pdfDoc.SetFlushUnusedObjects(true);
pdfDoc.Close();
//outputDoc.Position = 0;
var r = new AuditSummaryDocument
{
Data = outputDoc.ToArray()
};
return r;
}
The table looks like this:
private Table GenerateTable()
{
// Example list of ASDLine
var list = new List<ASDLine>();
list.Add(new ASDLine { Amount = 20, ARInvoiceHeaderKey = 1, BillingCode = "ABC123", Description = "A billing code 1", MatchProfile = null });
list.Add(new ASDLine { Amount = 40, ARInvoiceHeaderKey = 1, BillingCode = "ABC456", Description = "A billing code 2", MatchProfile = true });
list.Add(new ASDLine { Amount = 60, ARInvoiceHeaderKey = 1, BillingCode = "ABC789", Description = "A billing code 3", MatchProfile = false });
list.Add(new ASDLine { Amount = 80, ARInvoiceHeaderKey = 1, BillingCode = "ABC987", Description = "A billing code 4", MatchProfile = true });
Table table = new Table(new UnitValue[] { new UnitValue(UnitValue.POINT, 80)
,new UnitValue(UnitValue.POINT, 370)
,new UnitValue(UnitValue.POINT, 80)
,new UnitValue(UnitValue.POINT, 80)});
table.SetWidth(new UnitValue(UnitValue.PERCENT, 102));
Style hs = new Style();
hs.SetFont(font);
hs.SetFontSize(10);
hs.SetBold();
hs.SetTextAlignment(TextAlignment.CENTER);
Style cs = new Style();
cs.SetFont(font);
cs.SetFontSize(10);
table.AddHeaderCell(new Cell().AddStyle(hs).Add(new Paragraph("Billing Code")));
table.AddHeaderCell(new Cell().AddStyle(hs).Add(new Paragraph("Description")));
table.AddHeaderCell(new Cell().AddStyle(hs).Add(new Paragraph("Amount")));
table.AddHeaderCell(new Cell().AddStyle(hs).Add(new Paragraph("Match Profile")));
list.ForEach(line =>
{
table.AddCell(new Cell().AddStyle(cs).SetTextAlignment(TextAlignment.CENTER).Add(new Paragraph(line.BillingCode)));
table.AddCell(new Cell().AddStyle(cs).SetTextAlignment(TextAlignment.LEFT).Add(new Paragraph(line.Description)));
table.AddCell(new Cell().AddStyle(cs).SetTextAlignment(TextAlignment.RIGHT).Add(new Paragraph(line.Amount.ToString("C"))));
table.AddCell(new Cell().AddStyle(cs).SetTextAlignment(TextAlignment.CENTER).Add(new Paragraph(line.MatchProfile.HasValue ? line.MatchProfile.Value ? "Yes" : "No" : "-")));
});
return table;
}
What am I doing wrong with my code that results in this PDF being twice the size it should be - and why does Adobe prompt to save changes?
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.
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; }
}