Markdown to PDF

Convert CommonMark + GFM Markdown to styled PDF documents with a single method call. Built-in parser with zero managed dependencies — no Markdig, no external packages.

Quick Start

Add Markdown content to any page using the .Markdown() extension method:

Document.Create(doc =>
{
    doc.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(50);
        page.Content().Markdown("# Hello World\n\nThis is **bold** and *italic*.");
    });
}).GeneratePdf("hello.pdf");

Standalone Document

Create a complete PDF from a Markdown string without manually setting up pages:

MarkdownExtensions.CreateFromMarkdown(markdownText)
    .WithMetadata(new DocumentMetadata
    {
        Title = "Quarterly Report",
        Author = "Finance Team"
    })
    .GeneratePdf("report.pdf");

This creates an A4 document with default margins and applies the standard MarkdownStyle.

Supported Syntax

FolioPDF's Markdown parser implements CommonMark 0.31.2 (90% spec compliance) plus GFM extensions:

FeatureSyntaxPDF Rendering
Headings (1–6)# H1 through ###### H6, setextBold text with scaled font sizes
ParagraphsBlank-line separated blocksSpaced paragraphs with configurable line height
Bold / Italic**bold**, *italic*, ***both***Font weight and style changes
Strikethrough~~deleted~~Strikethrough text decoration
Links[text](url), reference linksClickable hyperlinks with configurable color
Images![alt](path), reference imagesEmbedded images (local files via resolver)
Code spans`inline code`Monospace font with background highlight
Fenced code blocks```lang ... ```Monospace block with background panel
Block quotes> quoteLeft border with padding and italic text
Unordered lists- itemBullet characters with configurable indent
Ordered lists1. itemNumbered items with configurable indent
Task lists (GFM)- [x] doneChecked/unchecked characters
Tables (GFM)Pipe-delimited with alignmentFull table layout with header styling
Thematic breaks---, ***, ___Horizontal rule
HTML entities&, ©Decoded to Unicode characters
Backslash escapes\*not italic\*Literal punctuation characters
Autolinks<https://example.com>Clickable hyperlinks
Hard line breaksTwo trailing spaces or \Forced line break within paragraph

Link Reference Definitions

Define link targets once and reference them throughout the document:

var md =
    "Read the [getting started guide][gs] first, then check the [API reference][api].\n" +
    "\n" +
    "[gs]: https://example.com/docs/getting-started \"Getting Started Guide\"\n" +
    "[api]: https://example.com/docs/api\n";
page.Content().Markdown(md);

Supports full references ([text][label]), collapsed references ([text][]), and shortcut references ([text]). Labels are case-insensitive.

Customizing Styles

Pass a MarkdownStyle to control every visual aspect of the rendered output:

var style = new MarkdownStyle
{
    // Headings
    Heading1FontSize = 36,
    Heading2FontSize = 28,
    Heading3FontSize = 22,
    HeadingColor = "#1a1a2e",

    // Body text
    BodyFontSize = 12,
    BodyFontFamily = "Lato",
    LineHeight = 1.5f,
    ParagraphSpacing = 10,

    // Code
    CodeFontFamily = "Courier",
    CodeFontSize = 10,
    CodeBlockBackground = "#282c34",
    CodeBlockTextColor = "#abb2bf",
    CodeBlockCornerRadius = 4,
    CodeBlockPadding = 12,
    InlineCodeBackground = "#f0f0f0",

    // Links
    LinkColor = "#0366d6",

    // Block quotes
    BlockQuoteBorderColor = "#dfe2e5",
    BlockQuoteBorderWidth = 3,
    BlockQuoteTextColor = "#6a737d",

    // Lists
    ListIndent = 20,
    BulletCharacter = "\u2022",
    TaskListCheckedCharacter = "\u2611",
    TaskListUncheckedCharacter = "\u2610",

    // Tables
    TableHeaderBackground = "#f6f8fa",
    TableBorderColor = "#dfe2e5",
    TableCellPadding = 6,

    // Thematic breaks
    ThematicBreakColor = "#eaecef",
    ThematicBreakThickness = 1,

    // Semantic tags (for PDF/UA accessibility)
    EnableSemanticTags = true,
};

page.Content().Markdown(markdownText, style);

MarkdownStyle Properties

CategoryPropertyTypeDefault
HeadingsHeading1FontSizeHeading6FontSizefloat28, 24, 20, 16, 14, 12
HeadingColorstring"#000000"
BodyBodyFontSizefloat12
BodyFontFamilystring"Lato"
LineHeightfloat1.4
ParagraphSpacingfloat8
CodeCodeFontFamilystring"Courier"
CodeBlockBackgroundstring"#f6f8fa"
CodeBlockPaddingfloat10
InlineCodeBackgroundstring"#f0f0f0"
LinksLinkColorstring"#0366d6"
Block quotesBlockQuoteBorderColorstring"#dfe2e5"
BlockQuoteBorderWidthfloat3
ListsListIndentfloat20
BulletCharacterstring"\u2022" (bullet)
TablesTableHeaderBackgroundstring"#f6f8fa"
TableBorderColorstring"#dfe2e5"
Thematic breaksThematicBreakColorstring"#eaecef"
AccessibilityEnableSemanticTagsbooltrue

Image Handling

Markdown images (![alt](path)) are resolved through an IMarkdownImageResolver. The default resolver loads images from the local file system:

// Default: FileSystemImageResolver resolves relative paths from the working directory
page.Content().Markdown("![Photo](images/photo.jpg)");

// Custom resolver for different base paths
var resolver = new FileSystemImageResolver("C:\\Assets\\Images");
page.Content().Markdown(markdownText, new MarkdownStyle(), resolver);

// Null resolver: skip all images (useful for text-only rendering)
page.Content().Markdown(markdownText, new MarkdownStyle(), new NullImageResolver());

IMarkdownImageResolver

public interface IMarkdownImageResolver
{
    byte[]? ResolveImage(string url);
}

Return the image bytes for a given URL/path, or null to skip the image. Implement this interface for custom image sources (databases, HTTP, embedded resources, etc.).

Architecture

The Markdown pipeline is a three-stage process:

StageClassDescription
Parse MarkdownParser Converts Markdown text into an AST (MarkdownDocument). Two-phase: block parsing with link reference definition extraction, then delimiter-stack inline parsing.
Render to PDF MarkdownPdfRenderer Walks the AST and emits FolioPDF layout elements (Column, Row, Table, Text, etc.) using the fluent API. Each AST node maps to one or more layout elements.
Render to HTML MarkdownHtmlRenderer Walks the AST and emits HTML. Used exclusively for CommonMark spec compliance testing — not part of the PDF pipeline.

AST Node Types

Block nodes: MarkdownDocument, MarkdownHeading, MarkdownParagraph, MarkdownCodeBlock, MarkdownBlockQuote, MarkdownList, MarkdownListItem, MarkdownThematicBreak, MarkdownTable, MarkdownTableCell

Inline nodes: MarkdownText, MarkdownEmphasis, MarkdownCodeSpan, MarkdownLink, MarkdownImage, MarkdownAutolink, MarkdownStrikethrough, MarkdownLineBreak, MarkdownSoftBreak

CommonMark Compliance

The parser is tested against the official CommonMark 0.31.2 specification (652 test examples). Each example runs as an individual enforced test case.

SectionPass Rate
ATX headings100%
Emphasis and strong emphasis97%
Setext headings96%
Link reference definitions96%
Images96%
Autolinks95%
Fenced code blocks93%
Backslash escapes92%
Code spans91%
Block quotes88%
Links87%
Hard line breaks87%
Entity references82%
List items77%
Lists69%
Overall90%

HTML blocks and Raw HTML sections are architecturally skipped (raw HTML passthrough has no meaning in PDF output).

Complete Example

var md =
    "# Project Status Report\n" +
    "\n" +
    "**Project:** Widget Redesign\n" +
    "**Date:** April 2026\n" +
    "**Status:** On Track\n" +
    "\n" +
    "## Summary\n" +
    "\n" +
    "The widget redesign project has completed Phase 2. Key metrics:\n" +
    "\n" +
    "| Metric          | Target | Actual |\n" +
    "|-----------------|--------|--------|\n" +
    "| Performance     | 50ms   | 42ms   |\n" +
    "| Test Coverage   | 90%    | 94%    |\n" +
    "| Bug Count       | < 10   | 7      |\n" +
    "\n" +
    "## Next Steps\n" +
    "\n" +
    "1. Begin Phase 3 integration testing\n" +
    "2. Schedule stakeholder review for April 18\n" +
    "3. Finalize documentation\n" +
    "\n" +
    "> **Note:** All deadlines assume no scope changes from the April 4 review.\n" +
    "\n" +
    "---\n" +
    "\n" +
    "For details, see the [full project plan](https://internal.example.com/project/widget-v2).\n";

var style = new MarkdownStyle
{
    Heading1FontSize = 32,
    BodyFontSize = 11,
    LinkColor = "#1a73e8",
    TableHeaderBackground = "#e8f0fe",
};

Document.Create(doc =>
{
    doc.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(50);

        page.Header().Text("Widget Redesign").FontSize(10).FontColor("#999999");

        page.Content().Markdown(md, style);

        page.Footer().AlignCenter().Text(t =>
        {
            t.Span("Page ");
            t.CurrentPageNumber();
            t.Span(" of ");
            t.TotalPages();
        });
    });
}).GeneratePdf("status-report.pdf");

Next Steps