Auto Create Table of Contents

HiQPdf HTML to PDF Converter can be configured to automatically create a table of contents in a PDF document based on H1 to H6 heading tags found in an HTML document by enabling the PdfDocumentControlGenerateTableOfContents option. An object of type PdfDocumentControl is exposed by the HtmlToPdfDocument property. The code below can be used to enable table of contents generation in the PDF document.

C#
// create the HTML to PDF converter
HtmlToPdf htmlToPdfConverter = new HtmlToPdf();

// set if a table of contents is automatically created in the PDF document from HTML heading tags
htmlToPdfConverter.Document.GenerateTableOfContents = true;

In the custom mode, you can also mark arbitrary HTML elements as table of contents entries using the data-heading attribute, as described in the section below.

Table of Contents Structure

The table of contents container is equivalent to a block element with the ID 'html-to-pdf-toc'. Inside this block element there is another block element that contains the title, followed by an unordered list of table entries, similar to an HTML unordered list element. Each table of contents entry, corresponding to an HTML heading tag, has text, a level that determines the entry appearance in the table and a target PDF page number. The table of contents style can be controlled using a CSS stylesheet which reflects the internal structure described in this section.

Custom Table of Contents Entries Using the data-heading Attribute

Besides the standard H1 to H6 heading tags, any HTML element can be turned into a table of contents entry by setting the data-heading attribute to a level from 1 to 6, for example data-heading="2". The entry text is taken from the data-heading-text attribute when present, otherwise from the element text. A real heading can be excluded from the table of contents by setting data-heading="false". The custom data-heading attribute is honored only when the custom table of contents mode is used, that is when PdfTableOfContentsUseBrowserMode is false (the inline table of contents is always created in custom mode).

The HTML below shows how to mark ordinary elements as table of contents entries together with the standard heading tags.

XML
<!DOCTYPE html>
<html>
<head>
    <title>Auto Create Table of Contents</title>
    <link href="styles/webfonts.css" type="text/css" rel="stylesheet">
</head>
<body style="font-family: Verdana, sans-serif; font-size: 16px">
    <br />
    <br />
    <h1>Contents</h1>
    <a href="#Chapter1">Go To Chapter 1</a>
    <br />
    <a href="#Chapter2">Go To Chapter 2</a>
    <br />
    <a href="#Chapter3">Go To Chapter 3</a>
    <br />
    <a href="#CustomTocItems">Go To Custom TOC Items</a>
    <br />
    <!-- This DIV is the container for inline table of contents.
    If it is not explicitly defined, a default one is created
    before the HTML content of the converted page -->
    <div id="html-to-pdf-toc"></div>
    <h2 style="page-break-before: always" id="Chapter1">Chapter 1</h2>
    This is the chapter 1 content.
    <h2 style="page-break-before: always" id="Chapter2">Chapter 2</h2>
    This is the chapter 2 content.
    <h2 style="page-break-before: always" id="Chapter3">Chapter 3</h2>
    This is the chapter 3 content.

    <!-- Custom table of contents items created with the data-heading attribute.
     Any element (not only H1-H6) becomes a table of contents entry when it has
     a data-heading attribute with a valid level (1 to 6). -->
    <h2 style="page-break-before: always" id="CustomTocItems">Custom Table of Contents Example</h2>
    These table of contents entries are created from ordinary elements using the data-heading attribute.
    <div data-heading="3">Custom TOC Item - Level 3</div>
    This entry was added to the table of contents using a custom &lt;div&gt; element with data-heading="3".
    <div data-heading="3" data-heading-text="Custom TOC Item With Explicit Title">This visible text is ignored for the table of contents</div>
    This entry uses data-heading-text to set the table of contents title independently of the element text.
    <h3 data-heading="false">Excluded Heading (data-heading="false")</h3>
    This is a real H3 heading, but it is excluded from the table of contents because of data-heading="false".

    <p><i>Note: The custom data-heading attribute is enabled only when the custom table of contents mode is used.</i></p>
</body>
</html>

Table of Contents Options

The table of contents properties are controlled by the PdfDocumentControlTableOfContents property, which exposes an object of type PdfTableOfContents. By setting this object's properties, you can control the table of contents style and page numbering.

Table Title

The table of contents title can be set using the PdfTableOfContentsTitle property. The code below can be used to set the table of contents title.

C#
// set the table of contents title
htmlToPdfConverter.Document.TableOfContents.Title = "Table of Contents";

Table Style

The table of contents style can be controlled using a standard CSS stylesheet defined in the PdfTableOfContentsStyle property. You can control the global table of contents style, the title style, the style for each entry level and the page number style. There is a separate section dedicated to style customization.

Inline Table of Contents

The table of contents is normally created at the beginning of the PDF document, but it can also be generated inline at any position within the converted HTML document by setting the PdfTableOfContentsInline property. The code below can be used to create the table of contents inline.

C#
// set if the table of contents is rendered inline inside a DIV element with 'html-to-pdf-toc' ID
htmlToPdfConverter.Document.TableOfContents.Inline = true;

The table of contents position in the converted HTML document is given by a DIV element with the ID 'html-to-pdf-toc', defined as below.

XML
<div id="html-to-pdf-toc"></div>

If there is no such DIV element defined in the converted HTML document, the converter automatically creates it at the top of the HTML content being converted.

Page Numbers

The table of contents can display the target PDF page number on the right side of each table of contents entry.

  • Enable Page Numbers. Using the PdfTableOfContentsAddPageNumbers property, you can enable or disable page number display in the table of contents. When page numbers are disabled, you might also need to adjust the table style so it no longer shows the dotted line between the table entry and the page number.

  • Page Numbers Offset. Another option to control page numbering is to use the PdfTableOfContentsPageNumbersOffset property to adjust the page numbering by a constant positive or negative integer. This property can be useful when merging a PDF containing a table of contents with other PDF documents, similar to the 'Merge Multiple HTML to PDF' example from the demo application.

  • Count TOC and Start Pages. For a non-inline table of contents, you can also configure whether to include or exclude from the page numbering the PDF pages on which the table of contents is rendered using the PdfTableOfContentsCountTocAndExternalPages property. This property also includes the PDF pages coming from the PDF documents added before the main HTML content using the PdfDocumentControlAddStartPdf(Byte) method of the converter. If you exclude the table of contents and start PDF pages, the page numbers in the table of contents start at page 1, which refers to the next page after the table of contents. This property does not apply to an inline table of contents.

Table of Contents Mode

For a non-inline table of contents, it is also possible to configure the creation mode to be custom or browser using the PdfTableOfContentsUseBrowserMode property.

  • Custom Mode. This is the default mode and the converter uses a custom algorithm to generate the table of contents entries.

  • Browser Mode. In this mode, the table of contents is generated using the browser capabilities. To enable this mode, you can use the code below.

    C#
    // set if the browser capabilities are used when creating the table of contents
    // this option does not have effect if the table of contents is inline
    htmlToPdfConverter.Document.TableOfContents.UseBrowserMode = true;

Table of Contents Style

The table of contents style can be controlled using a standard CSS stylesheet set in the PdfTableOfContentsStyle property. The converter initializes this property with a default style that you can overwrite with your own CSS stylesheet to control the global table of contents style, the title style, styles for each entry level and the page number style. The code below sets the Style property with a style similar to the default style used by the converter.

C#
            htmlToPdfConverter.Document.TableOfContents.Style = """
#html-to-pdf-toc {
    font-family: Arial, sans-serif;
    margin: 0px;
    padding: 10px;
    box-sizing: border-box;
    background-color: #FFFFFF;
    width: 100%;
    position: relative;
    display : block;
    z-index: 100;
    opacity: 1;

    /* uncomment the lines below to force PDF page breaks before and after inline TOC */
    /*page-break-after : always;
    page-break-before : always;*/

    /* uncomment the line below for RTL table of contents*/
    /*direction : rtl;*/
}

/* TOC title style */
#html-to-pdf-toc .toc-title {
    font-size: 22px;
    font-weight: bold;
    margin-bottom: 15px;
    color: #444444;
    text-align: center;
}

/* Common style for all TOC entries text */
#html-to-pdf-toc li .toc-link {
    order: 1;
    text-decoration: none;
    color: inherit;
    padding: 0px;
    margin: 0px;
}

/* Level specific style for TOC entries text */

#html-to-pdf-toc li .toc-link-level-1 {
    font-weight: bold;
    font-size: 18px;
    color: #111111;
}

#html-to-pdf-toc li .toc-link-level-2 {
    font-weight: bold;
    font-size: 16px;
    color: #222222;
}

#html-to-pdf-toc li .toc-link-level-3 {
    font-weight: bold;
    font-size: 14px;
    color: #333333;
}

#html-to-pdf-toc li .toc-link-level-4 {
    font-weight: normal;
    font-size: 12px;
    color: #333333;
}

#html-to-pdf-toc li .toc-link-level-5 {
    font-weight: normal;
    font-size: 11px;
    color: #333333;
    font-style: italic;
}

#html-to-pdf-toc li .toc-link-level-6 {
    font-weight: normal;
    font-size: 10px;
    color: #333333;
    font-style: italic;
}

/* Common style for all page numbers */
#html-to-pdf-toc li .page-link {
    order: 3;
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: bold;
    font-style: normal;
    text-decoration: none;
    padding: 0px;
    margin: 0px;
    color: #111111;
}

/* Style for space between entry text and page number */ 
#html-to-pdf-toc li::after {
    flex-grow: 1;
    order: 2;
    content: "";
    height: 1em;
    /* comment the line below to remove the dotted line from TOC */
    border-bottom: 2px dotted lightgray;
}

#html-to-pdf-toc ul {
    list-style-type: none; 
    padding: 0px;
}

/* Common style for all TOC entries*/
#html-to-pdf-toc li {
    font-size: 14px;
    display: flex;
    padding: 0px;
    margin: 0px;
}

/* Level specific style for TOC entries */

#html-to-pdf-toc ul li.level-1 {    
    margin-bottom: 10px;
    margin-inline-start: 0px;    
}

#html-to-pdf-toc ul li.level-2 {
    margin-bottom: 8px;
    margin-inline-start: 20px;
}

#html-to-pdf-toc ul li.level-3 {
    margin-bottom: 6px;
    margin-inline-start: 40px;
}

#html-to-pdf-toc ul li.level-4 {
    margin-bottom: 4px;
    margin-inline-start: 60px;
}

#html-to-pdf-toc ul li.level-5 {
    margin-bottom: 2px;
    margin-inline-start: 80px;
}

#html-to-pdf-toc ul li.level-6 {
    margin-bottom: 2px;
    margin-inline-start: 100px;
}
""";

Global Table Style

The global table style is defined by the '#html-to-pdf-toc' selector. You can configure the padding, the background color, the font family, text direction and other properties. For an inline table of contents, you can force PDF page breaks before and after the table of contents by uncommenting the 'page-break-after: always' and 'page-break-before: always' styles.

Title Style

The title style is controlled by the '#html-to-pdf-toc .toc-title' selector.

Table Entries Style

The '#html-to-pdf-toc li' selector controls the common style of all entries in the table of contents. The style of the entries for various levels can be controlled using the '#html-to-pdf-toc ul li.level-n' selectors, where n can be an integer from 1 to 6.

The common styles for all entry text are controlled by the '#html-to-pdf-toc li .toc-link' selector. The text style of the entries for various levels can be controlled using the '#html-to-pdf-toc li .toc-link-level-n' selectors, where n can be an integer from 1 to 6.

The common style for all page numbers is controlled by the '#html-to-pdf-toc li .page-link' selector. To remove the dotted line between the table entry text and the page number, comment out the 'border-bottom: 2px dotted lightgray' style in the '#html-to-pdf-toc li::after' selector.

Demo Source Code

C#
using HiQPdf.Next;
using HiQPdf_Next_AspNetDemo.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;

namespace HiQPdf_Next_AspNetDemo.Controllers
{
    public class AutoCreateTableOfContentsController : Controller
    {
        IWebHostEnvironment m_hostingEnvironment;

        public AutoCreateTableOfContentsController(IWebHostEnvironment hostingEnvironment)
        {
            m_hostingEnvironment = hostingEnvironment;
        }

        public IActionResult Index()
        {
            var model = SetViewModel();

            return View(model);
        }

        [HttpPost]
        public ActionResult ConvertToPdf(AutoCreateTableOfContentsViewModel model)
        {
            if (!ModelState.IsValid)
            {
                var errorMessage = ModelStateHelper.GetModelErrors(ModelState);
                throw new ValidationException(errorMessage);
            }

            // Set the serial number received after purchase to use the library in licensed mode; leave it commented for demo mode
            // Licensing.SerialNumber = "your-serial-number";

            // Create an HTML to PDF converter object with default settings
            HtmlToPdf htmlToPdfConverter = new HtmlToPdf();

            // set if a table of contents is automatically created in the PDF document from HTML heading tags
            htmlToPdfConverter.Document.GenerateTableOfContents = model.GenerateToc;

            // set if the table of contents is rendered inline inside a DIV element with 'html-to-pdf-toc' ID
            htmlToPdfConverter.Document.TableOfContents.Inline = model.InlineToc;

            // set if the browser capabilities are used when creating the table of contents
            // this option does not have effect if the table of contents is inline
            htmlToPdfConverter.Document.TableOfContents.UseBrowserMode = model.UseBrowserMode;

            // set if the page numbers from table of contents are displyed
            htmlToPdfConverter.Document.TableOfContents.AddPageNumbers = model.AddPageNumbers;

            // set if the TOC pages are included in the page numbers displayed in the TOC
            // this option is not applicable to the inline table of contents
            htmlToPdfConverter.Document.TableOfContents.CountTocAndExternalPages = model.CountTocPages;

            // set an offset to be applied to all page numbers in the table of contents
            // this option does not have effect if the table of contents is inline
            htmlToPdfConverter.Document.TableOfContents.PageNumbersOffset = model.PageNumbersOffset;

            // set the table of contents title
            htmlToPdfConverter.Document.TableOfContents.Title = model.TocTitle;

            // set a custom CSS style for the table of contents
            // a default style is applied if this property is not set
            htmlToPdfConverter.Document.TableOfContents.Style = model.TocStyle;

            // set browser height if specified, otherwise use the default
            if (model.BrowserHeight.HasValue)
                htmlToPdfConverter.BrowserHeight = model.BrowserHeight.Value;

            // Set an additional delay, in seconds, to wait for asynchronous content after the initial load
            // The default value is 0
            htmlToPdfConverter.WaitBeforeConvert = model.WaitTime;

            // Set the maximum time, in seconds, to wait for the HTML page to load
            // The default value is 120 seconds
            htmlToPdfConverter.HtmlLoadedTimeout = model.LoadHtmlTimeout;

            // Set the PDF page margins in points. The default is 0
            htmlToPdfConverter.Document.Margins = new PdfMargins(
                        model.LeftMargin, model.RightMargin,
                        model.TopMargin, model.BottomMargin);

            // Set the page layout: how the width at which the HTML is laid out relates to the PDF page width
            PdfPageSize pageSize = GetSelectedPageSize(model.PageSize);
            PdfPageOrientation pageOrientation = GetSelectedPageOrientation(model.PageOrientation);

            switch (model.PageLayout)
            {
                case "FitBrowserWindowToPage":
                    // Fixed page size: the HTML is laid out as in a browser window of the given width and the result
                    // is scaled to the content width of the page, so a responsive site keeps its desktop layout.
                    // This is the default layout of the converter, with an A4 page and a 1024 pixel window
                    htmlToPdfConverter.FitBrowserWindowToPage(pageSize, pageOrientation, windowWidth: model.BrowserWidth);
                    break;

                case "LayoutAtPageWidth":
                    // Fixed page size: the HTML is laid out at the content width of the page, one CSS pixel
                    // being 0.75 points. For HTML templates designed for the paper size
                    htmlToPdfConverter.LayoutAtPageWidth(pageSize, pageOrientation);
                    break;

                default:
                    // The PDF page width follows the browser window width and the HTML is drawn 1:1;
                    // the page height comes from the page size and the orientation
                    htmlToPdfConverter.PageWidthFromBrowserWindow(model.BrowserWidth, singlePage: false, zoom: model.BrowserZoom);
                    htmlToPdfConverter.Document.PageSize = pageSize;
                    htmlToPdfConverter.Document.PageOrientation = pageOrientation;
                    break;
            }

            // convert HTML to PDF
            byte[] pdfBuffer = null;

            if (model.UrlOrHtmlCode == "ConvertUrl")
            {
                // convert URL to a PDF memory buffer
                string url = model.Url;

                pdfBuffer = htmlToPdfConverter.ConvertUrlToMemory(url);
            }
            else
            {
                // convert HTML code
                string htmlCode = model.HtmlCode;
                string baseUrl = model.BaseUrl;

                // convert HTML code to a PDF memory buffer
                pdfBuffer = htmlToPdfConverter.ConvertHtmlToMemory(htmlCode, baseUrl);
            }

            FileResult fileResult = new FileContentResult(pdfBuffer, "application/pdf");
            fileResult.FileDownloadName = "AutoCreateTableOfContents.pdf";

            return fileResult;
        }

        private PdfPageSize GetSelectedPageSize(string pageSize)
        {
            switch (pageSize)
            {
                case "A0":
                    return PdfPageSize.A0;
                case "A1":
                    return PdfPageSize.A1;
                case "A10":
                    return PdfPageSize.A10;
                case "A2":
                    return PdfPageSize.A2;
                case "A3":
                    return PdfPageSize.A3;
                case "A4":
                    return PdfPageSize.A4;
                case "A5":
                    return PdfPageSize.A5;
                case "A6":
                    return PdfPageSize.A6;
                case "A7":
                    return PdfPageSize.A7;
                case "A8":
                    return PdfPageSize.A8;
                case "A9":
                    return PdfPageSize.A9;
                case "ArchA":
                    return PdfPageSize.ArchA;
                case "ArchB":
                    return PdfPageSize.ArchB;
                case "ArchC":
                    return PdfPageSize.ArchC;
                case "ArchD":
                    return PdfPageSize.ArchD;
                case "ArchE":
                    return PdfPageSize.ArchE;
                case "B0":
                    return PdfPageSize.B0;
                case "B1":
                    return PdfPageSize.B1;
                case "B2":
                    return PdfPageSize.B2;
                case "B3":
                    return PdfPageSize.B3;
                case "B4":
                    return PdfPageSize.B4;
                case "B5":
                    return PdfPageSize.B5;
                case "Flsa":
                    return PdfPageSize.Flsa;
                case "HalfLetter":
                    return PdfPageSize.HalfLetter;
                case "Ledger":
                    return PdfPageSize.Ledger;
                case "Legal":
                    return PdfPageSize.Legal;
                case "Letter":
                    return PdfPageSize.Letter;
                case "Letter11x17":
                    return PdfPageSize.Letter11x17;
                case "Note":
                    return PdfPageSize.Note;
                default:
                    return PdfPageSize.A4;
            }
        }

        private PdfPageOrientation GetSelectedPageOrientation(string pageOrientation)
        {
            return pageOrientation == "Portrait" ?
                PdfPageOrientation.Portrait : PdfPageOrientation.Landscape;
        }

        private AutoCreateTableOfContentsViewModel SetViewModel()
        {
            var model = new AutoCreateTableOfContentsViewModel();

            var contentRootPath = Path.Combine(m_hostingEnvironment.ContentRootPath, "wwwroot");

            HttpRequest request = ControllerContext.HttpContext.Request;
            UriBuilder uriBuilder = new UriBuilder();
            uriBuilder.Scheme = request.Scheme;
            uriBuilder.Host = request.Host.Host;
            if (request.Host.Port != null)
                uriBuilder.Port = (int)request.Host.Port;
            uriBuilder.Path = request.PathBase.ToString() + request.Path.ToString();
            uriBuilder.Query = request.QueryString.ToString();

            string currentPageUrl = uriBuilder.Uri.AbsoluteUri;
            string rootUrl = currentPageUrl.Substring(0, currentPageUrl.Length - "AutoCreateTableOfContents".Length);

            model.HtmlCode = System.IO.File.ReadAllText(Path.Combine(contentRootPath, "DemoFiles/Html/Table_of_Contents.html"));
            model.BaseUrl = rootUrl + "DemoFiles/Html/";
            model.TocStyle = System.IO.File.ReadAllText(Path.Combine(contentRootPath, "DemoFiles/Html/TOC_Style.css"));

            return model;
        }
    }
}

See Also