6

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.

Draken
  • 3,134
  • 13
  • 34
  • 54
Naomiss
  • 167
  • 4
  • 14
  • I would use iText 7, and use one of the TOC examples of [Chapter 6](http://developers.itextpdf.com/content/itext-7-building-blocks/chapter-6-creating-actions-destinations-and-bookmarks) in the [iText 7: building blocks](http://developers.itextpdf.com/content/itext-7-building-blocks) tutorial. If you can't upgrade to iText 7, please do not use `Chapter` and `Section`. Support for those objects has been dropped. Keep track of the TOC structure in a different way. Use `onGenericTag()` to get the page number and the destination that can be to link a line in the TOC to the corresponding content. – Bruno Lowagie Aug 19 '16 at 11:17
  • 1
    Note that "Maybe can somebody give me example?" is usually not allowed as a question on Stack Overflow. We often refer to such questions as "Can you do my home work?" questions. You are supposed to show what you've tried, and explain the technical problem you experienced. Asking someone else to do your job in your place isn't appreciated by most of the Stack Overflow contributors. When you say "I read few articles and didn't understand", you should at least link to those articles, otherwise you'll get answers such as: "read more articles." – Bruno Lowagie Aug 19 '16 at 11:20
  • I'm using iTextSharp 5.5.9. Can I use chapter and section? – Naomiss Aug 19 '16 at 12:09
  • Yes, you can, but I wouldn't. – Bruno Lowagie Aug 19 '16 at 12:53
  • You have updated your question by referring to an example about iText 7. You can't use that code in iText 5. The `Chapter` en `Section` class don't exist in iText 7. – Bruno Lowagie Aug 19 '16 at 12:54
  • Then how can I change "Chapter and Section" in my document? What should I use in itextsharp for create TOC? is onGenericTag()? With what? – Naomiss Aug 19 '16 at 13:22
  • It would lead us to far to answer all of these questions. You told me that you have read "iText in Action". The quick brown fox example on page 127-129 explains how to use `onGenericTag` to create an index. Words that needed to be referred to in an index (which is similar to a TOC), are marked with a generic tag. This triggers the `onGenericTag()` method in the `IndexEvents` class. In the `onGenericTag()` method, you can ask the `writer` for the current page number, and store that information in the index. You need to do something similar. – Bruno Lowagie Aug 19 '16 at 13:56
  • If I were you, I'd create a `TOCEvents` class in which I store a list of titles and page numbers. I would mark every title using a generic tag, and by doing so, populate the list of titles in the `TOCEvents` class. Once you've finished writing the document, I would pull the list of titles and page numbers from the `TOCEvents` class and use that list to write the TOC. It's that simple. Just do it! – Bruno Lowagie Aug 19 '16 at 13:59
  • I found this question that gives you an idea: http://stackoverflow.com/questions/29092738/itext-chapter-title-and-columntext But what you have to do is much, much easier. That example is too complex for what you're trying to achieve. – Bruno Lowagie Aug 19 '16 at 15:57
  • Thank you. I have found this, but I thought, that this isn't exactly what I needed. – Naomiss Aug 22 '16 at 11:45

3 Answers3

5

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:

enter image description here

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:

enter image description here

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.

Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165
  • I understand. It's from here http://developers.itextpdf.com/examples/columntext-examples-itext5/creating-table-contents-using-events#2777-createtoc.java – Naomiss Aug 22 '16 at 12:48
  • Yes, it's Java code. You have to port that example to C#. That shouldn't be difficult because iText for Java and iText for C# are kept in sync. They have identical functionality (but the method names can be slightly different: some Java methods are properties in C#; methods start with a lower-case character in Java; they start with an upper-case character in C#). Classes such as `List`, `SimpleEntry`,... are pure Java classes, but I'm sure that there are similar classes in C#. – Bruno Lowagie Aug 22 '16 at 13:02
  • Sorry, I have a question. If I have got class ITextEvents, where I have events(OnEndPage, OnOpenDocument, OnCloseDocument). Should I add event OnGenericTag in this class(ITextEvents) or better create new class(TOCEvents)? – Naomiss Aug 22 '16 at 14:54
  • You can choose, you can add it to an existing event that is declared to the writer, or you can declare more than one event to the writer. Whatever works best for you. – Bruno Lowagie Aug 22 '16 at 15:51
  • Thank you so much, I done it. I have got several questions. How can I do TOC at the beginning of document and with links to chapters? Should I use events for create links to chapters or not? For example how it done in your book. That is I'm clicking to chapter in TOC I go to chapter in the text. Or should I use generic tag for create links? Which is way should be used? Or may be should use ways from this article: http://developers.itextpdf.com/content/itext-7-building-blocks/chapter-6-creating-actions-destinations-and-bookmarks (GoTo actions, Named destinations). Thank you in advance. – Naomiss Aug 23 '16 at 12:21
  • Those are too many questions to answer in a comment. Post a new question for each of them, but be aware: many of those questions will be marked as duplicate, and I might not be willing to answer them because you didn't accept this answer, which was the complete answer to your original question. The answer is indeed to create named destinations, but you are once more referring to iText 7 documentation while you actually want an iText 5 answer. – Bruno Lowagie Aug 23 '16 at 12:26
  • Ok. Thank you. I understand. – Naomiss Aug 23 '16 at 12:31
  • Thanks for accepting the answer. As a reward for doing the right thing, I've created [an example that creates named destinations and links to them](http://developers.itextpdf.com/examples/columntext-examples-itext5/creating-table-contents-using-events#2783-createtoc2.java). Reordering pages can be done in a second go: http://developers.itextpdf.com/question/how-reorder-pages-pdf-file (There's also a way to do it while creating the document, but that's not very elegant.) – Bruno Lowagie Aug 23 '16 at 13:31
  • Thank you so very much! You are just saint! – Naomiss Aug 23 '16 at 14:33
3

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; }
}
}
  • To all iText savants who provided working code here: thanx for the great examples, BUT... what to do when the table of contents should be placed BEFORE the content ? The names and links can be set up beforehand.. but the page numbers of the content are not yet available when the TOC is to be reported, – Goodies Aug 09 '18 at 23:37
0

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

Alan
  • 101
  • 1
  • 6