Search code examples
c#itext7

itext 7 table of contents issues


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:

enter image description here

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


Solution

  • 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();