Is there a recommended pipeline for "printable" (PDF-able) HTML? #371

Open
opened 2026-01-29 14:35:13 +00:00 by claunia · 1 comment
Owner

Originally created by @daiplusplus on GitHub (Jun 10, 2020).

I'm using MarkDig in an application that will render the Markdown as HTML for a page viewed in a browser, but also in a page which is converted into a printable PDF (using wkhtmltopdf and/or Puppeteer), so things like embedded videos using UseMediaLinks() won't work.

One of the main things I'd like to change is rendering Markdown hyperlinks as plaintext but keeping the link target in parentheses after the link-text (but still keeping it clickable as users may open the PDF on their computer).

If there isn't a built-in way to do this, I'll be happy to contribute a PR.

Originally created by @daiplusplus on GitHub (Jun 10, 2020). I'm using MarkDig in an application that will render the Markdown as HTML for a page viewed in a browser, but also in a page which is converted into a printable PDF (using `wkhtmltopdf` and/or Puppeteer), so things like embedded videos using `UseMediaLinks()` won't work. One of the main things I'd like to change is rendering Markdown hyperlinks as plaintext but keeping the link target in parentheses after the link-text (but still keeping it clickable as users may open the PDF on their computer). If there isn't a built-in way to do this, I'll be happy to contribute a PR.
claunia added the question label 2026-01-29 14:35:13 +00:00
Author
Owner

@markjerz commented on GitHub (Jan 9, 2023):

I've just had to do something similar. Taking inspiration from MediaLinkExtensions you can add your own extension which makes use of the TryWriters on the LinkInlineRenderer.

These functions are executed before rendering links. You can render the link yourself there and prevent the default rendering.

Set up your pipeline with your extension:

var plainTextPipeline = new MarkdownPipelineBuilder().Use<PlainTextLinkExtension>().Build();

class PlainTextLinkExtension : IMarkdownExtension {
    public void Setup(MarkdownPipelineBuilder pipeline) { }

    public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) {
        if (renderer is HtmlRenderer htmlRenderer) {
            var inlineRenderer = htmlRenderer.ObjectRenderers.FindExact<LinkInlineRenderer>();
            if (inlineRenderer != null) {
                inlineRenderer.TryWriters.Remove(TryLinkInlineRenderer);
                inlineRenderer.TryWriters.Add(TryLinkInlineRenderer);
            }
        }
    }

    private bool TryLinkInlineRenderer(HtmlRenderer renderer, LinkInline linkInline) {
        if (linkInline.IsImage || linkInline.Url is null) {
            return false;
        }

        renderer.Write(linkInline.Url); <-- output what you want here
        return true;
    }
}

This code is what Markdown.ToPlainText() does (using your pipeline instead of the default):

await using var ptStringWriter = new StringWriter();
var plainTextRenderer = new HtmlRenderer(ptStringWriter) { EnableHtmlForBlock = false, EnableHtmlForInline = false, EnableHtmlEscape = false };
plainTextPipeline.Setup(plainTextRenderer);
plainTextRenderer.Render(Markdown.Parse(email.EmailContent, plainTextPipeline));
plainTextRenderer.Writer.Flush();

var plainText = ptStringWriter.ToString();
@markjerz commented on GitHub (Jan 9, 2023): I've just had to do something similar. Taking inspiration from `MediaLinkExtensions` you can add your own extension which makes use of the `TryWriters` on the `LinkInlineRenderer`. These functions are executed before rendering links. You can render the link yourself there and prevent the default rendering. Set up your pipeline with your extension: ``` var plainTextPipeline = new MarkdownPipelineBuilder().Use<PlainTextLinkExtension>().Build(); class PlainTextLinkExtension : IMarkdownExtension { public void Setup(MarkdownPipelineBuilder pipeline) { } public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { if (renderer is HtmlRenderer htmlRenderer) { var inlineRenderer = htmlRenderer.ObjectRenderers.FindExact<LinkInlineRenderer>(); if (inlineRenderer != null) { inlineRenderer.TryWriters.Remove(TryLinkInlineRenderer); inlineRenderer.TryWriters.Add(TryLinkInlineRenderer); } } } private bool TryLinkInlineRenderer(HtmlRenderer renderer, LinkInline linkInline) { if (linkInline.IsImage || linkInline.Url is null) { return false; } renderer.Write(linkInline.Url); <-- output what you want here return true; } } ``` This code is what `Markdown.ToPlainText()` does (using your pipeline instead of the default): ``` await using var ptStringWriter = new StringWriter(); var plainTextRenderer = new HtmlRenderer(ptStringWriter) { EnableHtmlForBlock = false, EnableHtmlForInline = false, EnableHtmlEscape = false }; plainTextPipeline.Setup(plainTextRenderer); plainTextRenderer.Render(Markdown.Parse(email.EmailContent, plainTextPipeline)); plainTextRenderer.Writer.Flush(); var plainText = ptStringWriter.ToString(); ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/markdig#371