close

DEV Community

Michael Jordan
Michael Jordan

Posted on

Render a PDF to an image in .NET — with a pure-C# PDF engine

Most PDF libraries in .NET are wrappers. PDFium, MuPDF, Ghostscript — solid engines, but they ship native binaries you P/Invoke into. That buys you per-RID packages, Native AOT friction, the "works on my box, throws DllNotFoundException in the Alpine container" dance, and a deployment story that's never quite boring.

PdfLibrary takes the other road: the PDF engine and every image codec are pure managed C#. No vendored native PDF library, no native image decoders. I'll be precise about the one native piece (SkiaSharp, for rasterization) further down — because a "no native dependencies" claim that quietly ignores SkiaSharp would be a lie, and you'd find out the first time you deployed.

Here's the part you came for — rendering the first page of a PDF to a PNG:

using PdfLibrary.Structure;
using PdfLibrary.Rendering.SkiaSharp;

using var document = PdfDocument.Load("input.pdf");

document.GetPage(0)                 // 0-based page index
    .Render(document)
    .WithDpi(150)
    .ToFile("page1.png");
Enter fullscreen mode Exit fullscreen mode

That's it. No engine init, no global handle to dispose, no SetDllDirectory.

Install

dotnet add package Lxman.PdfLibrary
dotnet add package Lxman.PdfLibrary.Rendering.SkiaSharp
Enter fullscreen mode Exit fullscreen mode

The core package parses, creates, edits, and optimizes PDFs. The .Rendering.SkiaSharp package is the rasterization backend — add it only when you actually need pixels.

A bit more control

using var document = PdfDocument.Load("input.pdf");
var page = document.GetPage(0);

// High-DPI render with a custom background, straight to an SKImage
using var image = page.Render(document)
    .WithDpi(300)
    .WithBackgroundColor(new SKColor(255, 250, 240))   // antique white
    .ToImage();

// Or render just a region of the page
using var crop = page.Render(document)
    .WithScale(2.0)
    .WithCropBox(100, 100, 400, 600)                   // x, y, width, height
    .ToImage();
Enter fullscreen mode Exit fullscreen mode

.WithScale(1.0) is 72 DPI (1 PDF point = 1 pixel); .WithDpi(n) is the same knob expressed the way you usually think about it.

The honest "no native dependencies" section

This matters, so here's the exact breakdown:

  • Lxman.PdfLibrary (core) — parsing, content streams, fonts, and all image decoding are 100% managed C#. That includes the codecs people usually reach for a native lib to handle: baseline and progressive JPEG, JPEG 2000, JBIG2, CCITT Group 3/4 fax, LZW, and Flate. Zero native dependencies.
  • Lxman.PdfLibrary.Rendering.SkiaSharp (rasterizer) — uses SkiaSharp as the 2D canvas to turn the parsed page into pixels. SkiaSharp carries a native component.

So the rule of thumb:

  • Parsing, text extraction, editing, optimizing → fully managed, ship no native code.
  • Rasterizing a page to an image → add SkiaSharp.

SkiaSharp is a well-behaved, broadly-supported cross-platform dependency, so this is a very different proposition from bundling and P/Invoking a PDF engine yourself — but it's not nothing, and you should know exactly where the line is.

It's not just a renderer

Rendering is the flashy demo, but the managed core is the actual point. A few things that need only the core package (no SkiaSharp):

Extract text:

using var doc = PdfDocument.Load("input.pdf");
string text = doc.GetPage(0).ExtractText(doc);
Enter fullscreen mode Exit fullscreen mode

Edit an existing document:

using PdfLibrary.Editing;

using var doc = PdfDocument.Load("input.pdf");
var edit = doc.Edit();
edit.Pages.RemoveAt(2);     // delete the 3rd page
edit.Pages.Rotate(0, 90);   // rotate the 1st page 90°
edit.Save("edited.pdf");
Enter fullscreen mode Exit fullscreen mode

Optimize / shrink:

using PdfLibrary.Optimization;

using var doc = PdfDocument.Load("input.pdf");
using var output = File.Create("optimized.pdf");
PdfOptimizer.Optimize(doc, output);   // lossless by default
Enter fullscreen mode Exit fullscreen mode

Threading

PdfLibrary is built for the one-document-per-request model — the standard pattern for ASP.NET Core. Load a PdfDocument per request, render it on its own target, dispose both. Under that model it's thread-safe, and the process-wide caches (glyph paths, font resolution, ICC profiles) are synchronized. The one thing you must not do is share a single PdfDocument (or a SkiaSharpRenderTarget) across threads.

Links

If you try it and something doesn't render the way you expect, open an issue with the PDF attached — edge cases in the wild are how this kind of library gets better.

Top comments (0)