SelectPdf for .NET 10 - Styled Table of Contents with Html to Pdf Converter - C# / .NET 10 Sample

This sample converts any web page into a pdf document with a table of contents styled the way you want, using SelectPdf Pdf Library for .NET. The headings the selectors match become the entries, each with the page number it ended up on, and optionally nested bookmarks too. The settings below start out reproducing the look of selectpdf.com.

- Generate table of contents pages: the table is drawn on pages of its own, styled with the fonts, colors, indents and spacing set in the "Generated pages" section. Fonts are looked up on the server by family name (Liberation Sans, Liberation Serif and Liberation Mono are always available).
- Write a table of contents into the page: the table is written into the web page as html (nav.selectpdf-toc) and styled with the css in the "Table written into the page" section.

Url:

Table of contents:


Heading selectors (comma separated, one level each):


Title (empty for none):


Levels shown (0 for all):


Create bookmarks for the same headings
Leader:


Leader color:
Leader gap: pt

Page number color:

Page margins: pt

Generated pages

Position:


Page background:
Number the entries (1., 1.1., ...)

Title font:
pt bold
Title color: align:
Space before / after the title: / pt
Page number font:
pt bold

Page numbers end this far from the right edge:
pt
LevelFontSize (pt)BoldColor Indent (pt)Space before (pt)
1
2
3

Table written into the page

Placement:


Placeholder selector (for the Placeholder placement):

On a page of its own
Include the default css

Css:

Sample code · C#
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using SelectPdf.Universal;

namespace SelectPdf.Samples.Pages
{
    public class StyledTableOfContentsModel : PageModel
    {
        // The defaults reproduce the look of selectpdf.com: cream page, dark ink, amber page numbers,
        // a serif display face for the title and the top level, a sans face below it.

        public class LevelRow
        {
            public string Font { get; set; }
            public float Size { get; set; }
            public bool Bold { get; set; }
            public string Color { get; set; }
            public float Indent { get; set; }
            public float SpacingBefore { get; set; }
        }

        [BindProperty] public string TxtUrl { get; set; } = "https://selectpdf.com/";
        [BindProperty] public string DdlMode { get; set; } = "GeneratePages";
        [BindProperty] public string TxtHeadings { get; set; } = "h1, h2, h3";
        [BindProperty] public bool ChkBookmarks { get; set; } = true;
        [BindProperty] public int TxtMargin { get; set; } = 0;

        // common to both modes
        [BindProperty] public string TxtTitle { get; set; } = "Contents";
        [BindProperty] public string DdlLeader { get; set; } = "Dots";
        [BindProperty] public string TxtLeaderColor { get; set; } = "#c7beb1";
        [BindProperty] public float TxtLeaderGap { get; set; } = 6;
        [BindProperty] public string TxtPageNumberColor { get; set; } = "#d97706";
        [BindProperty] public int TxtMaxLevel { get; set; } = 0;
        [BindProperty] public bool ChkNumberEntries { get; set; } = false;

        // the table written into the page
        [BindProperty] public string DdlPlacement { get; set; } = "Start";
        [BindProperty] public string TxtPlaceholder { get; set; } = "";
        [BindProperty] public bool ChkPageBreak { get; set; } = true;
        [BindProperty] public bool ChkDefaultCss { get; set; } = true;
        [BindProperty] public string TxtCss { get; set; } = DefaultCss;

        // the generated pages
        [BindProperty] public string DdlPosition { get; set; } = "Start";
        [BindProperty] public string TxtPageBackColor { get; set; } = "#fff8ec";
        [BindProperty] public string TxtTitleFont { get; set; } = "Liberation Serif";
        [BindProperty] public float TxtTitleSize { get; set; } = 34;
        [BindProperty] public bool ChkTitleBold { get; set; } = true;
        [BindProperty] public string TxtTitleColor { get; set; } = "#1f140a";
        [BindProperty] public string DdlTitleAlign { get; set; } = "Left";
        [BindProperty] public float TxtTitleSpacingBefore { get; set; } = 64;
        [BindProperty] public float TxtTitleSpacingAfter { get; set; } = 22;
        [BindProperty] public string TxtNumberFont { get; set; } = "Liberation Sans";
        [BindProperty] public float TxtNumberSize { get; set; } = 10;
        [BindProperty] public bool ChkNumberBold { get; set; } = true;
        [BindProperty] public float TxtNumberInset { get; set; } = 56;

        [BindProperty]
        public LevelRow[] Levels { get; set; } =
        {
            new LevelRow { Font = "Liberation Serif", Size = 16, Bold = true, Color = "#1f140a", Indent = 56, SpacingBefore = 14 },
            new LevelRow { Font = "Liberation Sans", Size = 11, Bold = true, Color = "#1f140a", Indent = 72, SpacingBefore = 6 },
            new LevelRow { Font = "Liberation Sans", Size = 10, Bold = false, Color = "#6b6257", Indent = 88, SpacingBefore = 3 },
        };

        public const string DefaultCss =
            "nav.selectpdf-toc { background: #fff8ec; color: #1f140a; font-family: Georgia, serif;\n" +
            "  max-width: 860px; margin: 0 auto; padding: 72px 32px 40px; box-sizing: border-box; }\n" +
            "nav.selectpdf-toc .selectpdf-toc-title { font-size: 52px; font-weight: 600;\n" +
            "  letter-spacing: -0.02em; line-height: 1.05; margin: 0 0 28px; padding-bottom: 20px;\n" +
            "  border-bottom: 1px solid #c7beb1; }\n" +
            "nav.selectpdf-toc li { margin: 12px 0; font-size: 17px; font-family: Arial, sans-serif; }\n" +
            "nav.selectpdf-toc ol ol { padding-left: 22px; }\n" +
            "nav.selectpdf-toc li.selectpdf-toc-level-1 > a { font-family: Georgia, serif;\n" +
            "  font-size: 24px; font-weight: 600; }\n" +
            "nav.selectpdf-toc li.selectpdf-toc-level-2 > a { font-weight: 600; }\n" +
            "nav.selectpdf-toc li.selectpdf-toc-level-3 > a { color: #6b6257; font-size: 15px; }";

        public List<SelectListItem> Modes { get; } = new List<SelectListItem>
        {
            new SelectListItem { Value = "GeneratePages", Text = "Generate table of contents pages" },
            new SelectListItem { Value = "InsertHtml", Text = "Write a table of contents into the page" },
        };

        public List<SelectListItem> Leaders { get; } = Items("Dots", "Dashes", "Line", "None");
        public List<SelectListItem> Placements { get; } = Items("Start", "End", "Placeholder");
        public List<SelectListItem> Positions { get; } = Items("Start", "AfterFirstPage", "End");
        public List<SelectListItem> Aligns { get; } = Items("Left", "Center", "Right");

        public void OnGet()
        {
        }

        public IActionResult OnPost()
        {
            PdfTableOfContentsMode mode =
                SampleForm.ParseEnum(DdlMode, PdfTableOfContentsMode.GeneratePages);
            string[] headings = (TxtHeadings ?? "")
                .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim()).Where(s => s.Length > 0).ToArray();

            // instantiate a html to pdf converter object
            HtmlToPdf converter = new HtmlToPdf();
            converter.Options.NavigationTimeout = Startup.NavigationTimeout;

            converter.Options.MarginTop = TxtMargin;
            converter.Options.MarginRight = TxtMargin;
            converter.Options.MarginBottom = TxtMargin;
            converter.Options.MarginLeft = TxtMargin;

            // bookmarks for the same headings, nested by level, shown when the document is opened
            if (ChkBookmarks)
            {
                converter.Options.PdfBookmarkOptions.CssSelectors = headings;
                converter.Options.PdfBookmarkOptions.HierarchicalBookmarks = true;
                converter.Options.ViewerPreferences.PageMode = PdfViewerPageMode.UseOutlines;
            }

            // the table of contents: one entry per heading the selectors match
            PdfTableOfContentsOptions toc = converter.Options.TableOfContents;
            toc.Mode = mode;
            toc.HeadingSelectors = headings;
            toc.Title = string.IsNullOrWhiteSpace(TxtTitle) ? null : TxtTitle;
            toc.Leader = SampleForm.ParseEnum(DdlLeader, PdfTableOfContentsLeader.Dots);
            toc.LeaderColor = Color(TxtLeaderColor);
            toc.LeaderGap = TxtLeaderGap;
            toc.PageNumberColor = Color(TxtPageNumberColor);
            toc.MaxLevel = TxtMaxLevel;
            toc.NumberEntries = ChkNumberEntries;

            if (mode == PdfTableOfContentsMode.InsertHtml)
            {
                // the table is written into the page as html and styled with css
                toc.HtmlPlacement = SampleForm.ParseEnum(DdlPlacement, PdfTableOfContentsHtmlPlacement.Start);
                toc.PlaceholderSelector = TxtPlaceholder;
                toc.PageBreak = ChkPageBreak;
                toc.IncludeDefaultCss = ChkDefaultCss;
                converter.Options.CustomCSS = TxtCss;
            }
            else
            {
                // the table is drawn on pages of its own
                toc.PagePosition = SampleForm.ParseEnum(DdlPosition, PdfTableOfContentsPagePosition.Start);
                toc.PageBackColor = Color(TxtPageBackColor);
                toc.TitleFont = Font(TxtTitleFont, TxtTitleSize, ChkTitleBold);
                toc.TitleColor = Color(TxtTitleColor);
                toc.TitleAlign = SampleForm.ParseEnum(DdlTitleAlign, PdfTextHorizontalAlign.Left);
                toc.TitleSpacingBefore = TxtTitleSpacingBefore;
                toc.TitleSpacingAfter = TxtTitleSpacingAfter;
                toc.PageNumberFont = Font(TxtNumberFont, TxtNumberSize, ChkNumberBold);
                toc.PageNumberRightInset = TxtNumberInset;
                toc.LevelStyles = Levels.Select(level => new PdfTableOfContentsLevelStyle
                {
                    Font = Font(level.Font, level.Size, level.Bold),
                    Color = Color(level.Color),
                    Indent = level.Indent,
                    SpacingBefore = level.SpacingBefore,
                }).ToArray();
            }

            // create a new pdf document converting an url
            PdfDocument doc = converter.ConvertUrl(TxtUrl);

            // save pdf document
            byte[] pdf = doc.Save();

            // close pdf document
            doc.Close();

            // return resulted pdf document
            FileResult fileResult = new FileContentResult(pdf, "application/pdf");
            fileResult.FileDownloadName = "Document.pdf";
            return fileResult;
        }

        // a font installed on the server (Liberation Sans, Serif and Mono are always available)
        private static PdfFont Font(string family, float size, bool bold)
        {
            return PdfFont.CreateFromSystemFont(family, size, bold ? PdfFontStyle.Bold : PdfFontStyle.Regular);
        }

        // "#rrggbb" from a color input
        private static PdfColor Color(string hex)
        {
            if (string.IsNullOrWhiteSpace(hex)) return PdfColor.Empty;
            hex = hex.Trim().TrimStart('#');
            int rgb = int.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
            return new PdfColor((byte)(rgb >> 16), (byte)(rgb >> 8), (byte)rgb);
        }

        private static List<SelectListItem> Items(params string[] values)
        {
            return values.Select(v => new SelectListItem { Value = v, Text = v }).ToList();
        }
    }
}