Search code examples
javaopenpdf

How to add a table of contents using OpenPDF


I'm working on a tool for generating PDF documents representing the results of assessments. The structure and some of the texts and images of these documents are defined by non-technical users (which is one of the reasons why for example Apache FOP and XSL is not an option).

OpenPDF seems like a promising library for that (other than Apache PDFBox, which is too low-level). However, it is necessary that generated documents contain a table of contents.
The intended document structure is this:

 1. Cover
 2. Abstract
 3. Table of Contents
 4. Chapter 1 .. n

Since I cannot know how many pages the document will finally have or on which pages the different chapters will start, I cannot define the table of contents before adding each chapter to the document.
As OpenPDF directly writes elements to the document, it doesn't seem possible to keep a reference of an exemplary table of contents element and add its content after adding all of the chapters.


Solution

  • I have found a solution that works regarding the intended structure by using the reorderPages(int[]) method of com.lowagie.text.pdf.PdfWriter:

    First, I keep the intended first page of the table of contents (the first page after the abstract):

    int intendedTocFirstPage = pdfWriter.getCurrentPageNumber() - 1; // - 1 because of a necessary `document.newPage();` before that
    

    After adding all chapters to the document, I am adding the table of contents last and keep the first and last page of it (because more than one page could be needed, depending on the number of chapters and sub chapters):

    int tocFirstPage = pdfWriter.getCurrentPageNumber();
    document.add(new Paragraph("TBA: Actual Table of Contents")); // TODO replace with the table of contents based on the existing chapters and sections
    document.newPage();
    int tocLastpage = pdfWriter.getCurrentPageNumber();
    

    Then I am creating an array that represents the new order of the pages based on the three int variables:

    private int[] getReorderedPagesForTableOfContents(int intendedTocFirstPage, int tocFirstPage, int tocLastpage) {
        int[] pages = IntStream
                .range(1, tocLastpage)
                .toArray();
    
        /*
         * Reorder the pages array by placing the toc page numbers at
         * the indexes starting from targetedTocFirstPage (should be
         * the page directly after the summary)
         */
        int numberOfTocPages = tocLastpage - tocFirstPage;
        if (numberOfTocPages >= 0) {
            System.arraycopy(pages, tocFirstPage - 1, pages, intendedTocFirstPage, numberOfTocPages);
        }
    
        /* Shift the page numbers of all pages after the last toc page */
        for (int i = intendedTocFirstPage + numberOfTocPages; i < pages.length; i++) {
            pages[i] = i - numberOfTocPages + 1; // `+ 1` because page numbers start with 1 not 0
        }
    
        return pages;
    }
    

    And finally, I am reordering the pages of the document:

    int[] reorderedPages = getReorderedPagesForTableOfContents(targetedTocFirstPage, tocFirstPage, tocLastpage);
    pdfWriter.reorderPages(reorderedPages);
    

    This works but it creates another problem:
    Using a footer for displaying page numbers won't work properly anymore, as the numbers from before the reordering will be kept.
    A possible solution for this is to first create the full document including the reordering of the pages and then using a PdfReader for adding the page numbers afterwards as described in this answer: https://stackoverflow.com/a/759972/10551549

    If anyone has a better solution, I will be happy to hear it (because this one is a bit messy, in my opinion). :)