Convert Markdown to PDF

The Markdown to PDF Converter component allows you to convert Markdown documents to PDF.

This component is bundled and distributed as two separate NuGet packages for Windows and Linux, each including the same .NET Standard 2.0 library and different native runtimes. Targeting .NET Standard 2.0 makes the packages compatible with a wide range of .NET Core and .NET Framework versions.

You can reference the HiQPdf.Next.MarkdownToPdf.Windows NuGet package in applications running on Windows or the HiQPdf.Next.MarkdownToPdf.Linux NuGet package in applications running on Linux to enable Markdown to PDF conversion in your application. The package for Windows is referenced by the HiQPdf.Next.Windows metapackage for all components, and the package for Linux is referenced by the HiQPdf.Next.Linux metapackage for all components.

There are also multiplatform metapackages that reference both the Windows and Linux Markdown to PDF packages: HiQPdf.Next.MarkdownToPdf for the Markdown to PDF functionality and HiQPdf.Next for the entire HiQPdf Next library.

Overview

The HiQPdf.NextMarkdownToPdfConverter class allows you to load a Markdown file and generate a PDF document, with optional control over page formatting, layout and visual elements such as headers and footers and table of contents creation.

Create the Markdown to PDF Converter

The HiQPdf.NextMarkdownToPdfConverter class is used to convert Markdown documents to PDF. You can create an instance using the default constructor, which initializes the converter with standard settings. These settings can later be customized through the MarkdownToPdfConverterPdfDocumentOptions property which exposes an object of MarkdownToPdfDocumentOptions type controlling various aspects of the generated PDF document.

Create a Markdown to PDF Converter Instance
// Create a new Markdown to PDF converter instance
MarkdownToPdfConverter markdownToPdfConverter = new MarkdownToPdfConverter();

Note that MarkdownToPdfConverter instances are not reusable. You must create a new instance for each conversion. Reusing an instance after a completed conversion will result in an exception.

Configure the PDF Page Settings

The format of the generated PDF document pages is controlled by the PDF page settings defined by the HiQPdf.NextMarkdownToPdfDocumentOptions class properties. An object of this type is exposed through the MarkdownToPdfConverterPdfDocumentOptions property.

The following example shows how to configure page size, orientation and margins.

Use Custom PDF Page Settings
markdownToPdfConverter.PdfDocumentOptions.PdfPageSize = PdfPageSize.A4;
markdownToPdfConverter.PdfDocumentOptions.PdfPageOrientation = PdfPageOrientation.Landscape;
markdownToPdfConverter.PdfDocumentOptions.LeftMargin = 20;
markdownToPdfConverter.PdfDocumentOptions.RightMargin = 20;
markdownToPdfConverter.PdfDocumentOptions.TopMargin = 30;
markdownToPdfConverter.PdfDocumentOptions.BottomMargin = 30;

Add HTML Headers and Footers

You can add a header or footer from a URL or from an HTML string. The header and footer can include variables such as {page_number} or {total_pages} and support automatic resizing.

The creation of the HTML header and footer is controlled by the MarkdownToPdfDocumentOptionsPdfHtmlHeader and MarkdownToPdfDocumentOptionsPdfHtmlFooter properties. These properties expose objects of type PdfHtmlHeaderFooter, which derives from PdfHtmlTemplate.

The MarkdownToPdfDocumentOptions object is exposed through the MarkdownToPdfConverterPdfDocumentOptions property.

The header and footer options are similar to those available in the HTML to PDF Converter and are described in detail in the HTML Header and Footer with Page Numbers documentation section.

Table of Contents

If MarkdownToPdfDocumentOptionsGenerateTableOfContents is set to true, the converter will automatically create a table of contents based on the titles and subtitles defined in the Markdown document using heading styles. The creation and appearance of the table of contents are controlled by the properties of a PdfTableOfContents object exposed through the MarkdownToPdfDocumentOptionsTableOfContents property.

The MarkdownToPdfDocumentOptions object is exposed through the MarkdownToPdfConverterPdfDocumentOptions property.

The following example shows how enable the automatic generation of a table of contents.

Generate Table of Contents
markdownToPdfConverter.PdfDocumentOptions.GenerateTableOfContents = true;

Apply a Custom Style Sheet

The Markdown to PDF converter allows you to apply custom CSS styling rules to the generated PDF document using the MarkdownToPdfDocumentOptionsStyleSheet property.

The MarkdownToPdfDocumentOptions object is exposed through the MarkdownToPdfConverterPdfDocumentOptions property.

The following example shows how to set the style that specifies the font size and color of H1 heading text.

Apply Custom Style Sheet
markdownToPdfConverter.PdfDocumentOptions.StyleSheet = @"
h1 { 
    font-size: 24pt;
    color: #0b57d0;
}
";

Convert Markdown to PDF

To convert a Markdown string to a PDF document in a memory buffer, use the MarkdownToPdfConverterConvertStringToPdf(String, String) method. The first parameter is the Markdown string to convert while the second parameter is the base URL used to resolve relative links and image paths in the Markdown content.

Convert to PDF in Memory
byte[] outPdfBuffer = markdownToPdfConverter.ConvertStringToPdf(markdownString, baseUrl);

After conversion, the resulting PDF document can be returned as a byte array for in-memory processing such as streaming to a web client or saving to a database or a file.

For example, you can write the byte array to disk to store the PDF as a file.

Write PDF Data to File
File.WriteAllBytes("output.pdf", outPdfBuffer);

Alternatively, you can use the MarkdownToPdfConverterConvertStringToPdfFile(String, String, String) method to convert the Markdown string along with the base URL to a PDF file whose full path is given by the third parameter.

Convert to PDF File
markdownToPdfConverter.ConvertStringToPdfFile(markdownString, baseUrl, outputPdfFilePath);

If you need to convert a Markdown file or stream to PDF, you first have to read the content of the file or stream into a string using the appropriate text encoding such as UTF-8. The example below shows how to read a Markdown file into a string.

Read a Markdown String from File
byte[] inputMarkdownBytes = System.IO.File.ReadAllBytes(markdownFilePath);
string markdownString = Encoding.UTF8.GetString(inputMarkdownBytes);
string baseUrl = "file://" + markdownFilePath;

Alternatively, you can read the Markdown file directly into a string using File.ReadAllText when the file is encoded as UTF-8.

Read a Markdown String Using ReadAllText
string markdownString = System.IO.File.ReadAllText(markdownFilePath, Encoding.UTF8);
string baseUrl = "file://" + markdownFilePath;

Asynchronous Markdown to PDF Methods

There are also asynchronous variants of these methods that follow the Task-based Asynchronous Pattern (TAP) in .NET, allowing Markdown to PDF conversion to run in parallel using async and await. These methods share the same names as their synchronous counterparts and include the "Async" suffix. They also accept an optional System.ThreadingCancellationToken parameter that can be used to cancel the conversion operation where applicable.

To convert a Markdown string to a PDF document in a memory buffer, use the MarkdownToPdfConverterConvertStringToPdfAsync(String, String, CancellationToken) method. The first parameter is the Markdown string to convert while the second parameter is the base URL used to resolve relative links and image paths in the Markdown content.

Asynchronously Convert to PDF in Memory
byte[] outPdfBuffer = await markdownToPdfConverter.ConvertStringToPdfAsync(markdownString, baseUrl);

Alternatively, you can use the MarkdownToPdfConverterConvertStringToPdfFileAsync(String, String, String, CancellationToken) method to convert the Markdown string along with the base URL to a PDF file whose full path is given by the third parameter.

Asynchronously Convert to PDF File
await markdownToPdfConverter.ConvertStringToPdfFileAsync(markdownString, baseUrl, outputPdfFilePath);

Code Sample - Convert Markdown to PDF

Convert Markdown to PDF in ASP.NET Core
using System;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using HiQPdf_Next_AspNetDemo.Models;

using HiQPdf.Next;

namespace HiQPdf_Next_AspNetDemo.Controllers
{
    public class MarkdownToPdfController : Controller
    {
        private readonly IWebHostEnvironment m_hostingEnvironment;

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

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

            return View(model);
        }

        [HttpPost]
        public async Task<IActionResult> ConvertMarkdownToPdf(MarkdownToPdfViewModel model)
        {
            if (!ModelState.IsValid)
            {
                var errorMessage = ModelStateHelper.GetModelErrors(ModelState);
                throw new ValidationException(errorMessage);
            }

            // 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 a Markdown to PDF converter object with default settings
            MarkdownToPdfConverter markdownToPdfConverter = new MarkdownToPdfConverter();

            // Set custom styling rules using CSS syntax
            markdownToPdfConverter.PdfDocumentOptions.StyleSheet = model.StyleSheet;

            // Set whether a table of contents is automatically generated from headings
            markdownToPdfConverter.PdfDocumentOptions.GenerateTableOfContents = model.GenerateToc;

            // Set PDF page size which can be a predefined size like A4 or a custom size in points 
            // Leave it not set to have a default A4 PDF page
            markdownToPdfConverter.PdfDocumentOptions.PageSize = SelectedPdfPageSize(model.PdfPageSize);

            // Set PDF page orientation to Portrait or Landscape
            // Leave it not set to have a default Portrait orientation for PDF page
            markdownToPdfConverter.PdfDocumentOptions.PageOrientation = SelectedPdfPageOrientation(model.PdfPageOrientation);

            // Set PDF page margins in points or leave them not set to have a PDF page without margins
            markdownToPdfConverter.PdfDocumentOptions.Margins.Left = model.LeftMargin;
            markdownToPdfConverter.PdfDocumentOptions.Margins.Right = model.RightMargin;
            markdownToPdfConverter.PdfDocumentOptions.Margins.Top = model.TopMargin;
            markdownToPdfConverter.PdfDocumentOptions.Margins.Bottom = model.BottomMargin;

            // Set the Markdown viewer zoom percentage
            markdownToPdfConverter.PdfDocumentOptions.Zoom = model.MarkdownViewerZoom;

            // Set PDF header and footer
            SetHeader(markdownToPdfConverter, model);
            SetFooter(markdownToPdfConverter, model);

            string markdownString = null;
            string baseUrl = null;

            if (model.MarkdownSource == "Url")
            {
                // Obtain the Markdown string and base URL from a Markdown file using UTF-8 encoding
                string markdownUrl = model.MarkdownFileUrl?.Trim();
                if (string.IsNullOrWhiteSpace(markdownUrl))
                    throw new Exception("No Markdown file provided: upload a file or specify a URL");

                byte[] inputMarkdownBytes = null;

                try
                {
                    if (markdownUrl.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
                    {
                        string localPath = new Uri(markdownUrl).LocalPath;
                        inputMarkdownBytes = await System.IO.File.ReadAllBytesAsync(localPath);
                    }
                    else
                    {
                        using var httpClient = new System.Net.Http.HttpClient();
                        inputMarkdownBytes = await httpClient.GetByteArrayAsync(markdownUrl);
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Could not download the Markdown file from URL", ex);
                }

                markdownString = Encoding.UTF8.GetString(inputMarkdownBytes);
                baseUrl = markdownUrl;
            }
            else
            {
                // The Markdown string and base URL are provided directly
                markdownString = model.MarkdownString;
                baseUrl = model.BaseUrl;
            }

            // Convert the Markdown string to a PDF document in a memory buffer
            byte[] outPdfBuffer = markdownToPdfConverter.ConvertStringToPdf(markdownString, baseUrl);

            // Send the PDF file to browser
            FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
            // send as attachment
            fileResult.FileDownloadName = "Markdown_to_Pdf.pdf";

            return fileResult;
        }

        private void SetHeader(MarkdownToPdfConverter markdownToPdfConverter, MarkdownToPdfViewModel model)
        {
            bool headerEnabled = model.HeaderEnabled;
            if (!headerEnabled)
                return;

            // Set the header HTML from a URL or from an HTML string
            bool headerHtmlFromUrl = model.HeaderHtmlSource == "Url";
            if (headerHtmlFromUrl)
            {
                string headerUrl = model.HeaderUrl;

                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.HtmlSourceUrl = headerUrl;
            }
            else
            {
                string headerHtml = model.HeaderHtml;
                string headerHtmlBaseUrl = model.HeaderHtmlBaseUrl;

                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.Html = headerHtml;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.HtmlBaseUrl = headerHtmlBaseUrl;
            }

            // Enable automatic height adjustment based on header HTML content
            bool autoSizeHeaderContentHeight = model.AutoSizeHeaderContentHeight;
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.AutoSizeContentHeight = autoSizeHeaderContentHeight;

            // Set the minimum and maximum content height used when AutoSizeContentHeight is enabled
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.MinContentHeight = model.HeaderMinContentHeight;
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.MaxContentHeight = model.HeaderMaxContentHeight;

            // Set a fixed height for the header if AutoResizeHeight is disabled
            if (model.HeaderHeight.HasValue)
            {
                int headerHeight = model.HeaderHeight.Value;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.Height = headerHeight;
            }

            // If AutoResizeHeight is enabled and both Height and FitHeight are set,
            // the content may be scaled down to fit the specified height
            bool fitHeaderHeight = model.FitHeaderHeight;
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.FitHeight = fitHeaderHeight;

            // Enable automatic top margin adjustment in the PDF based on the header
            bool autoResizeTopMargin = model.AutoResizeTopMargin;
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.AutoResizePdfMargins = autoResizeTopMargin;

            // Set header visibility on specific PDF pages: first page, odd-numbered pages and even-numbered pages
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.ShowInFirstPage = model.ShowHeaderInFirstPage;
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.ShowInOddPages = model.ShowHeaderInOddPages;
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.ShowInEvenPages = model.ShowHeaderInEvenPages;

            // Reserve space for the header on all pages, regardless of visibility
            // If false, the document will be rendered using print styles instead of screen styles
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.ReserveSpaceAlways = model.ReserveHeaderSpace;

            // Optimize the header rendering time by providing a hint if the HTML template contains variables such as { page_number} or { total_pages}
            markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.SkipVariablesParsing = model.SkipHeaderVariablesParsing;

            // Optionally set additional time to wait for the asynchronous header HTML content before rendering
            if (model.HeaderWaitBeforeConvert.HasValue && model.HeaderWaitBeforeConvert.Value > 0)
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlHeader.WaitBeforeConvert = model.HeaderWaitBeforeConvert.Value;
        }

        private void SetFooter(MarkdownToPdfConverter markdownToPdfConverter, MarkdownToPdfViewModel model)
        {
            bool footerEnabled = model.FooterEnabled;
            if (footerEnabled)
            {
                // Set the footer HTML from a URL or from an HTML string
                bool footerHtmlFromUrl = model.FooterHtmlSource == "Url";
                if (footerHtmlFromUrl)
                {
                    string footerUrl = model.FooterUrl;

                    markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.HtmlSourceUrl = footerUrl;
                }
                else
                {
                    string footerHtml = model.FooterHtml;
                    string footerHtmlBaseUrl = model.FooterHtmlBaseUrl;

                    markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.Html = footerHtml;
                    markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.HtmlBaseUrl = footerHtmlBaseUrl;
                }

                // Enable automatic height adjustment based on footer HTML content
                bool autoSizeFooterContentHeight = model.AutoSizeFooterContentHeight;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.AutoSizeContentHeight = autoSizeFooterContentHeight;

                // Set the minimum and maximum content height used when AutoSizeContentHeight is enabled
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.MinContentHeight = model.FooterMinContentHeight;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.MaxContentHeight = model.FooterMaxContentHeight;

                // Set a fixed height for the footer if AutoResizeHeight is disabled
                if (model.FooterHeight.HasValue)
                {
                    int footerHeight = model.FooterHeight.Value;
                    markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.Height = footerHeight;
                }

                // If AutoResizeHeight is enabled and both Height and FitHeight are set,
                // the content may be scaled down to fit the specified height
                bool fitFooterHeight = model.FitFooterHeight;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.FitHeight = fitFooterHeight;

                // Enable automatic bottom margin adjustment in the PDF based on the footer
                bool autoResizeBottomMargin = model.AutoResizeBottomMargin;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.AutoResizePdfMargins = autoResizeBottomMargin;

                // Set footer visibility on specific PDF pages: first page, odd-numbered pages and even-numbered pages
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.ShowInFirstPage = model.ShowFooterInFirstPage;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.ShowInOddPages = model.ShowFooterInOddPages;
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.ShowInEvenPages = model.ShowFooterInEvenPages;

                // Reserve space for the footer on all pages, regardless of visibility
                // If false, the document will be rendered using print styles instead of screen styles
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.ReserveSpaceAlways = model.ReserveFooterSpace;

                // Optimize the footer rendering time by providing a hint if the HTML template contains variables such as { page_number} or { total_pages}
                markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.SkipVariablesParsing = model.SkipFooterVariablesParsing;

                // Optionally set additional time to wait for the asynchronous footer HTML content before rendering
                if (model.FooterWaitBeforeConvert.HasValue && model.FooterWaitBeforeConvert.Value > 0)
                    markdownToPdfConverter.PdfDocumentOptions.PdfHtmlFooter.WaitBeforeConvert = model.FooterWaitBeforeConvert.Value;
            }
        }

        private PdfPageSize SelectedPdfPageSize(string selectedValue)
        {
            switch (selectedValue)
            {
                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 SelectedPdfPageOrientation(string selectedValue)
        {
            return selectedValue == "Portrait" ? PdfPageOrientation.Portrait : PdfPageOrientation.Landscape;
        }

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

            var contentRootPath = System.IO.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 - "MarkdownToPdf".Length);

            model.MarkdownFileUrl = rootUrl + "/DemoFiles/Markdown/Markdown_Document.md";
            model.MarkdownString = System.IO.File.ReadAllText(System.IO.Path.Combine(contentRootPath, "DemoFiles/Markdown/Markdown_Document.md"));
            model.BaseUrl = rootUrl + "/DemoFiles/Markdown/";
            model.StyleSheet = System.IO.File.ReadAllText(System.IO.Path.Combine(contentRootPath, "DemoFiles/Markdown/Markdown_Style.css"));
            model.HeaderHtml = System.IO.File.ReadAllText(System.IO.Path.Combine(contentRootPath, "DemoFiles/Html/Header_HTML.html"));
            model.FooterHtml = System.IO.File.ReadAllText(System.IO.Path.Combine(contentRootPath, "DemoFiles/Html/Footer_HTML.html"));
            model.HeaderHtmlBaseUrl = rootUrl + "DemoFiles/Html/";
            model.HeaderUrl = rootUrl + "DemoFiles/Html/Header_HTML.html";
            model.FooterHtmlBaseUrl = rootUrl + "DemoFiles/Html/";
            model.FooterUrl = rootUrl + "DemoFiles/Html/Footer_HTML.html";

            return model;
        }
    }
}

See Also