Add Header and Footer to PDF from Multiple HTML

The HiQPdf Chromium library offers extensive control over headers and footers. You can apply a header and a footer to a PDF document generated from multiple HTML documents. The header and footer HTML templates can contain any valid HTML markup, provided either as a string or via a URL. By using the template variables {page_number} and {total_pages}, you can include page numbering.

A key feature of the HTML to PDF Converter that enables adding an HTML header and footer to a PDF document generated from multiple HTML sources is delayed rendering. This can be enabled using the DelayContentRendering property. It allows the rendering of headers and footers to be postponed until after the individual PDF documents generated from multiple HTML sources are merged into a single document using an instance of the PdfMerge class.

The PdfMerge class provides methods to merge PDF documents from a memory buffer using the PdfMergeAddPdf(Byte, String) method, or from a file using the PdfMergeAddPdf(String, String) method. You can optionally specify a user or owner password if the added PDF document is password protected.

Code Sample - Add Header and Footer to PDF Merged from Multiple HTML

C#
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

using HiQPdf.Chromium;

namespace HiQPdf_Chromium_AspNetDemo.Controllers
{
    public class PdfHeadersAndFooters_MergeHtmlController : Controller
    {
        private readonly IWebHostEnvironment m_hostingEnvironment;
        public PdfHeadersAndFooters_MergeHtmlController(IWebHostEnvironment hostingEnvironment)
        {
            m_hostingEnvironment = hostingEnvironment;
        }

        public IActionResult Index()
        {
            SetCurrentViewData();

            return View();
        }

        [HttpPost]
        public ActionResult MergePdf(IFormCollection collection)
        {
            // Replace the demo serial number with the serial number received upon purchase
            // to run the converter in licensed mode
            Licensing.SerialNumber = "YCgJMTAE-BiwJAhIB-EhlWTlBA-UEBRQFBA-U1FOUVJO-WVlZWQ==";

            // Create the first HTML to PDF converter instance
            HtmlToPdf firstHtmlToPdfConverter = new HtmlToPdf();

            // Delay header and footer rendering until the merged PDF is saved
            firstHtmlToPdfConverter.Document.PdfHtmlHeader.DelayContentRendering = true;
            firstHtmlToPdfConverter.Document.PdfHtmlFooter.DelayContentRendering = true;

            // Set header and footer visibility for the first converter
            SetHeaderFooterVisibility(firstHtmlToPdfConverter, 0, collection);

            // Set common options for the first converter
            SetHtmlToPdfConverterOptions(firstHtmlToPdfConverter, collection);

            // Convert the first HTML to PDF
            byte[] firstPdfBytes = firstHtmlToPdfConverter.ConvertUrlToMemory(collection["textBoxUrl1"]);

            // Get header and footer size based on their rendered HTML content
            System.Drawing.Size headerSize = firstHtmlToPdfConverter.Document.PdfHtmlHeader.DestinationSize;
            System.Drawing.Size footerSize = firstHtmlToPdfConverter.Document.PdfHtmlFooter.DestinationSize;

            // Create the PDF merger
            PdfMerge pdfMerge = new PdfMerge();

            // Set merge options including header and footer dimensions
            SetPdfMergeOptions(pdfMerge, headerSize, footerSize, collection);

            // Add the first PDF to the merger
            int firstPdfPageCount = pdfMerge.AddPdf(firstPdfBytes);

            // Prepare the list of additional URLs to convert and merge
            List<string> urlsToConvert = new List<string>() {
                collection["textBoxUrl2"]
            };

            foreach (string url in urlsToConvert)
            {
                // Create a new converter for each HTML source
                HtmlToPdf htmlToPdfConverter = new HtmlToPdf();

                // Delay header and footer rendering until merge is saved
                htmlToPdfConverter.Document.PdfHtmlHeader.DelayContentRendering = true;
                htmlToPdfConverter.Document.PdfHtmlFooter.DelayContentRendering = true;

                // Calculate current page count to determine visibility logic
                int totalPdfPageCount = pdfMerge.PdfMergeInfo.TotalPagesProduced;

                // Set header and footer visibility based on current position
                SetHeaderFooterVisibility(htmlToPdfConverter, totalPdfPageCount, collection);

                // Apply general converter options
                SetHtmlToPdfConverterOptions(htmlToPdfConverter, collection);

                // Convert the HTML to PDF
                byte[] pdfBytes = htmlToPdfConverter.ConvertUrlToMemory(url);

                // Add the result to the merger
                int pdfPageCount = pdfMerge.AddPdf(pdfBytes);
            }

            // Merge all PDFs into a single document, applying the header and footer
            byte[] mergedPdf = pdfMerge.SaveToMemory();

            // Send the resulting PDF to the browser
            FileResult fileResult = new FileContentResult(mergedPdf, "application/pdf");
            fileResult.FileDownloadName = "Header_Footer_on_PDF_from_Multiple_HTML.pdf";

            return fileResult;
        }

        private void SetHeaderFooterVisibility(HtmlToPdf htmlToPdfConverter, int totalPagesBefore, IFormCollection collection)
        {
            // Set header visibility
            SetHeaderFooterVisibility(htmlToPdfConverter, totalPagesBefore, true, collection);

            // Set footer visibility
            SetHeaderFooterVisibility(htmlToPdfConverter, totalPagesBefore, false, collection);
        }

        private void SetHeaderFooterVisibility(HtmlToPdf htmlToPdfConverter, int totalPagesBefore, bool isHeader, IFormCollection collection)
        {
            PdfHtmlHeaderFooter pdfHtmlTemplate = null;

            bool showInFirstPage, showInEvenPages, showInOddPages;

            if (isHeader)
            {
                pdfHtmlTemplate = htmlToPdfConverter.Document.PdfHtmlHeader;

                showInFirstPage = collection["showHeaderInFirstPageCheckBox"].Count > 0;
                showInOddPages = collection["showHeaderInOddPagesCheckBox"].Count > 0;
                showInEvenPages = collection["showHeaderInEvenPagesCheckBox"].Count > 0;
            }
            else
            {
                pdfHtmlTemplate = htmlToPdfConverter.Document.PdfHtmlFooter;

                showInFirstPage = collection["showFooterInFirstPageCheckBox"].Count > 0;
                showInOddPages = collection["showFooterInOddPagesCheckBox"].Count > 0;
                showInEvenPages = collection["showFooterInEvenPagesCheckBox"].Count > 0;
            }

            pdfHtmlTemplate.ShowInFirstPage = showInFirstPage;
            pdfHtmlTemplate.ShowInEvenPages = showInEvenPages;
            pdfHtmlTemplate.ShowInOddPages = showInOddPages;

            if (totalPagesBefore > 0)
            {
                if (totalPagesBefore % 2 == 1)
                {
                    // First page is even in whole document
                    pdfHtmlTemplate.ShowInFirstPage = showInEvenPages;
                    pdfHtmlTemplate.ShowInOddPages = showInEvenPages;
                    pdfHtmlTemplate.ShowInEvenPages = showInOddPages;
                }
                else
                {
                    // First page is odd in whole document
                    pdfHtmlTemplate.ShowInFirstPage = showInOddPages;
                    pdfHtmlTemplate.ShowInOddPages = showInOddPages;
                    pdfHtmlTemplate.ShowInEvenPages = showInEvenPages;
                }
            }
        }

        private void SetHtmlToPdfConverterOptions(HtmlToPdf htmlToPdfConverter, IFormCollection collection)
        {
            bool headerEnabled = collection["headerEnabledCheckBox"].Count > 0;
            if (headerEnabled)
            {
                // Set the header HTML from a URL or from an HTML string
                bool headerHtmlFromUrl = collection["HeaderHtmlSource"] == "headerUrlRadioButton";
                if (headerHtmlFromUrl)
                {
                    string headerUrl = collection["headerUrlTextBox"];

                    htmlToPdfConverter.Document.PdfHtmlHeader.HtmlSourceUrl = headerUrl;
                }
                else
                {
                    string headerHtml = collection["headerHtmlTextBox"];
                    string headerHtmlBaseUrl = collection["headerHtmlBaseUrlTextBox"];

                    htmlToPdfConverter.Document.PdfHtmlHeader.Html = headerHtml;
                    htmlToPdfConverter.Document.PdfHtmlHeader.HtmlBaseUrl = headerHtmlBaseUrl;
                }

                // Enable automatic height adjustment based on header HTML content
                bool autoSizeHeaderContentHeight = collection["autoSizeHeaderContentHeightCheckBox"].Count > 0;
                htmlToPdfConverter.Document.PdfHtmlHeader.AutoSizeContentHeight = autoSizeHeaderContentHeight;

                // Set minimum and maximum content height when AutoSizeContentHeight is enabled
                htmlToPdfConverter.Document.PdfHtmlHeader.MinContentHeight = int.Parse(collection["headerMinContentHeightTextBox"]);
                htmlToPdfConverter.Document.PdfHtmlHeader.MaxContentHeight = int.Parse(collection["headerMaxContentHeightTextBox"]);

                // Set a fixed header height when AutoResizeHeight is disabled
                string headerHeightValueString = collection["headerHeightTextBox"];
                if (!string.IsNullOrEmpty(headerHeightValueString))
                {
                    int headerHeight = int.Parse(headerHeightValueString);
                    htmlToPdfConverter.Document.PdfHtmlHeader.Height = headerHeight;
                }

                // If AutoResizeHeight and FitHeight are enabled, the content may be scaled down to fit the specified height
                bool fitHeaderHeight = collection["fitHeaderHeightCheckBox"].Count > 0;
                htmlToPdfConverter.Document.PdfHtmlHeader.FitHeight = fitHeaderHeight;

                // Automatically adjust the top page margin based on the header height
                bool autoResizeTopMargin = collection["autoResizeTopMarginCheckBox"].Count > 0;
                htmlToPdfConverter.Document.PdfHtmlHeader.AutoResizePdfMargins = autoResizeTopMargin;

                // Reserve space for the header on all pages, regardless of visibility
                // If false, print styles are used instead of screen styles
                htmlToPdfConverter.Document.PdfHtmlHeader.ReserveSpaceAlways = collection["reserveHeaderSpaceCheckBox"].Count > 0;

                // Optimize the header rendering time by providing a hint if the HTML template contains variables such as { page_number} or { total_pages}
                htmlToPdfConverter.Document.PdfHtmlHeader.SkipVariablesParsing = collection["skipHeaderVariablesParsingCheckBox"].Count > 0;

                // Optionally set additional time to wait for the asynchronous header HTML content before rendering
                if (collection["headerWaitBeforeConvertTextBox"][0].Length > 0)
                    htmlToPdfConverter.Document.PdfHtmlHeader.WaitBeforeConvert = int.Parse(collection["headerWaitBeforeConvertTextBox"]);
            }

            bool footerEnabled = collection["footerEnabledCheckBox"].Count > 0;
            if (footerEnabled)
            {
                // Set the footer HTML from a URL or from an HTML string
                bool footerHtmlFromUrl = collection["FooterHtmlSource"] == "footerUrlRadioButton";
                if (footerHtmlFromUrl)
                {
                    string footerUrl = collection["footerUrlTextBox"];

                    htmlToPdfConverter.Document.PdfHtmlFooter.HtmlSourceUrl = footerUrl;
                }
                else
                {
                    string footerHtml = collection["footerHtmlTextBox"];
                    string footerHtmlBaseUrl = collection["footerHtmlBaseUrlTextBox"];

                    htmlToPdfConverter.Document.PdfHtmlFooter.Html = footerHtml;
                    htmlToPdfConverter.Document.PdfHtmlFooter.HtmlBaseUrl = footerHtmlBaseUrl;
                }

                // Enable automatic height adjustment based on footer HTML content
                bool autoSizeFooterContentHeight = collection["autoSizeHeaderContentHeightCheckBox"].Count > 0;
                htmlToPdfConverter.Document.PdfHtmlFooter.AutoSizeContentHeight = autoSizeFooterContentHeight;

                // Set minimum and maximum content height when AutoSizeContentHeight is enabled
                htmlToPdfConverter.Document.PdfHtmlFooter.MinContentHeight = int.Parse(collection["footerMinContentHeightTextBox"]);
                htmlToPdfConverter.Document.PdfHtmlFooter.MaxContentHeight = int.Parse(collection["footerMaxContentHeightTextBox"]);

                // Set a fixed footer height when AutoResizeHeight is disabled
                string footerHeightValueString = collection["footerHeightTextBox"];
                if (!string.IsNullOrEmpty(footerHeightValueString))
                {
                    int footerHeight = int.Parse(footerHeightValueString);
                    htmlToPdfConverter.Document.PdfHtmlFooter.Height = footerHeight;
                }

                // If AutoResizeHeight and FitHeight are enabled, the content may be scaled down to fit the specified height
                bool fitFooterHeight = collection["fitFooterHeightCheckBox"].Count > 0;
                htmlToPdfConverter.Document.PdfHtmlFooter.FitHeight = fitFooterHeight;

                // Automatically adjust the bottom page margin based on the footer height
                bool autoResizeBottomMargin = collection["autoResizeBottomMarginCheckBox"].Count > 0;
                htmlToPdfConverter.Document.PdfHtmlFooter.AutoResizePdfMargins = autoResizeBottomMargin;

                // Reserve space for the footer on all pages, regardless of visibility
                // If false, print styles are used instead of screen styles
                htmlToPdfConverter.Document.PdfHtmlFooter.ReserveSpaceAlways = collection["reserveFooterSpaceCheckBox"].Count > 0;

                // Optimize the footer rendering time by providing a hint if the HTML template contains variables such as { page_number} or { total_pages}
                htmlToPdfConverter.Document.PdfHtmlFooter.SkipVariablesParsing = collection["skipFooterVariablesParsingCheckBox"].Count > 0;

                // Optionally set additional time to wait for the asynchronous footer HTML content before rendering
                if (collection["footerWaitBeforeConvertTextBox"][0].Length > 0)
                    htmlToPdfConverter.Document.PdfHtmlFooter.WaitBeforeConvert = int.Parse(collection["footerWaitBeforeConvertTextBox"]);
            }
        }

        private void SetPdfMergeOptions(PdfMerge pdfMerge, System.Drawing.Size headerSize, System.Drawing.Size footerSize, IFormCollection collection)
        {
            bool headerEnabled = collection["headerEnabledCheckBox"].Count > 0;
            if (headerEnabled)
            {
                PdfHtmlTemplate pdfHeaderTemplate = null;

                // Set the header HTML from a URL or from an HTML string
                bool headerHtmlFromUrl = collection["HeaderHtmlSource"] == "headerUrlRadioButton";
                if (headerHtmlFromUrl)
                {
                    string headerUrl = collection["headerUrlTextBox"];

                    pdfHeaderTemplate = pdfMerge.AddHtmlTemplate(0, 0, headerSize.Width, headerSize.Height,
                            PdfTemplateHorizontalAlign.Left, PdfTemplateVerticalAlign.Top, headerUrl);
                }
                else
                {
                    string headerHtml = collection["headerHtmlTextBox"];
                    string headerHtmlBaseUrl = collection["headerHtmlBaseUrlTextBox"];

                    pdfHeaderTemplate = pdfMerge.AddHtmlTemplate(0, 0, headerSize.Width, headerSize.Height,
                            PdfTemplateHorizontalAlign.Left, PdfTemplateVerticalAlign.Top, headerHtml, headerHtmlBaseUrl);
                }

                // Enable automatic height adjustment based on header HTML content
                bool autoSizeHeaderContentHeight = collection["autoSizeHeaderContentHeightCheckBox"].Count > 0;
                pdfHeaderTemplate.AutoSizeContentHeight = autoSizeHeaderContentHeight;

                // Set minimum and maximum content height when AutoSizeContentHeight is enabled
                pdfHeaderTemplate.MinContentHeight = int.Parse(collection["headerMinContentHeightTextBox"]);
                pdfHeaderTemplate.MaxContentHeight = int.Parse(collection["headerMaxContentHeightTextBox"]);

                // Set a fixed header height when AutoResizeHeight is disabled
                string headerHeightValueString = collection["headerHeightTextBox"];
                if (!string.IsNullOrEmpty(headerHeightValueString))
                {
                    int headerHeight = int.Parse(headerHeightValueString);
                    pdfHeaderTemplate.Height = headerHeight;
                }

                // If AutoResizeHeight and FitHeight are enabled, the content may be scaled down to fit the specified height
                bool fitHeaderHeight = collection["fitHeaderHeightCheckBox"].Count > 0;
                pdfHeaderTemplate.FitHeight = fitHeaderHeight;

                // Set header visibility on specific PDF pages: first page, odd-numbered pages and even-numbered pages
                pdfHeaderTemplate.ShowInFirstPage = collection["showHeaderInFirstPageCheckBox"].Count > 0;
                pdfHeaderTemplate.ShowInOddPages = collection["showHeaderInOddPagesCheckBox"].Count > 0;
                pdfHeaderTemplate.ShowInEvenPages = collection["showHeaderInEvenPagesCheckBox"].Count > 0;

                // Optimize the header rendering time by providing a hint if the HTML template contains variables such as { page_number} or { total_pages}
                pdfHeaderTemplate.SkipVariablesParsing = collection["skipHeaderVariablesParsingCheckBox"].Count > 0;

                // Optionally set additional time to wait for the asynchronous header HTML content before rendering
                if (collection["headerWaitBeforeConvertTextBox"][0].Length > 0)
                    pdfHeaderTemplate.WaitBeforeConvert = int.Parse(collection["headerWaitBeforeConvertTextBox"]);
            }

            bool footerEnabled = collection["footerEnabledCheckBox"].Count > 0;
            if (footerEnabled)
            {
                PdfHtmlTemplate pdfFooterTemplate = null;

                // Set the footer HTML from a URL or from an HTML string
                bool footerHtmlFromUrl = collection["FooterHtmlSource"] == "footerUrlRadioButton";
                if (footerHtmlFromUrl)
                {
                    string footerUrl = collection["footerUrlTextBox"];
                    pdfFooterTemplate = pdfMerge.AddHtmlTemplate(0, 0, footerSize.Width, footerSize.Height,
                            PdfTemplateHorizontalAlign.Left, PdfTemplateVerticalAlign.Bottom, footerUrl);
                }
                else
                {
                    string footerHtml = collection["footerHtmlTextBox"];
                    string footerHtmlBaseUrl = collection["footerHtmlBaseUrlTextBox"];

                    pdfFooterTemplate = pdfMerge.AddHtmlTemplate(0, 0, footerSize.Width, footerSize.Height,
                            PdfTemplateHorizontalAlign.Left, PdfTemplateVerticalAlign.Bottom, footerHtml, footerHtmlBaseUrl);
                }

                // Enable automatic height adjustment based on footer HTML content
                bool autoSizeFooterContentHeight = collection["autoSizeFooterContentHeightCheckBox"].Count > 0;
                pdfFooterTemplate.AutoSizeContentHeight = autoSizeFooterContentHeight;

                // Set minimum and maximum content height when AutoSizeContentHeight is enabled
                pdfFooterTemplate.MinContentHeight = int.Parse(collection["footerMinContentHeightTextBox"]);
                pdfFooterTemplate.MaxContentHeight = int.Parse(collection["footerMaxContentHeightTextBox"]);

                // Set a fixed footer height when AutoResizeHeight is disabled
                string footerHeightValueString = collection["footerHeightTextBox"];
                if (!string.IsNullOrEmpty(footerHeightValueString))
                {
                    int footerHeight = int.Parse(footerHeightValueString);
                    pdfFooterTemplate.Height = footerHeight;
                }

                // If AutoResizeHeight and FitHeight are enabled, the content may be scaled down to fit the specified height
                bool fitFooterHeight = collection["fitFooterHeightCheckBox"].Count > 0;
                pdfFooterTemplate.FitHeight = fitFooterHeight;

                // Set footer visibility on specific PDF pages: first page, odd-numbered pages and even-numbered pages
                pdfFooterTemplate.ShowInFirstPage = collection["showFooterInFirstPageCheckBox"].Count > 0;
                pdfFooterTemplate.ShowInOddPages = collection["showFooterInOddPagesCheckBox"].Count > 0;
                pdfFooterTemplate.ShowInEvenPages = collection["showFooterInEvenPagesCheckBox"].Count > 0;

                // Optimize the footer rendering time by providing a hint if the HTML template contains variables such as { page_number} or { total_pages}
                pdfFooterTemplate.SkipVariablesParsing = collection["skipFooterVariablesParsingCheckBox"].Count > 0;

                // Optionally set additional time to wait for the asynchronous footer HTML content before rendering
                if (collection["footerWaitBeforeConvertTextBox"][0].Length > 0)
                    pdfFooterTemplate.WaitBeforeConvert = int.Parse(collection["footerWaitBeforeConvertTextBox"]);
            }
        }

        private void SetCurrentViewData()
        {
            ViewData["ContentRootPath"] = 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();

            ViewData["CurrentPageUrl"] = uriBuilder.Uri.AbsoluteUri;
        }
    }
}

Header HTML Template

XML
<!DOCTYPE html>
<html>
<head>
    <title>Header HTML with Page Numbers</title>
</head>
<body style="font-family: 'Times New Roman'; font-size: 16px">
    <table style="width: 100%">
        <tr>
            <td style=" font-size:18px; font-weight: bold; color: navy">Header HTML with Page Numbers</td>
            <td rowspan="2">
                <a href="http://www.hiqpdf.com">
                    <img style="float: right; height: 50px" src="Images/HiQPdfLogo.jpg" />
                </a>
            </td>
            <td rowspan="2" style="width: 5px"></td>
        </tr>
        <tr>
            <td>
                <span style="font-size: 16px">Page <span style="color:blue">{page_number}</span> of <span style="color:green">{total_pages}</span> pages</span>
            </td>
        </tr>
    </table>
</body>
</html>

Footer HTML Template

XML
<!DOCTYPE html>
<html>
<head>
    <title>Footer HTML with Page Numbers</title>
</head>
<body style="font-family: 'Times New Roman'; font-size: 16px">
    <table style="width: 100%">
        <tr>
            <td style=" font-size:18px; font-weight: bold; color: navy">Footer HTML with Page Numbers</td>
            <td rowspan="2">
                <a href="http://www.hiqpdf.com">
                    <img style="float: right; height: 50px" src="Images/HiQPdfLogo.jpg" />
                </a>
            </td>
            <td rowspan="2" style="width: 5px"></td>
        </tr>
        <tr>
            <td>
                <span style="font-size: 16px">Page <span style="color:blue">{page_number}</span> of <span style="color:green">{total_pages}</span> pages</span>
            </td>
        </tr>
    </table>
</body>
</html>

See Also