PdfPecker 1.25.0
dotnet add package PdfPecker --version 1.25.0
NuGet\Install-Package PdfPecker -Version 1.25.0
<PackageReference Include="PdfPecker" Version="1.25.0" />
<PackageVersion Include="PdfPecker" Version="1.25.0" />
<PackageReference Include="PdfPecker" />
paket add PdfPecker --version 1.25.0
#r "nuget: PdfPecker, 1.25.0"
#:package PdfPecker@1.25.0
#addin nuget:?package=PdfPecker&version=1.25.0
#tool nuget:?package=PdfPecker&version=1.25.0

PdfPecker
Fast HTML and Markdown to PDF conversion, PDF to PNG, JPEG, WebP and TIFF rendering, and PDF to HTML and Markdown, for .NET, with no dependencies.
PdfPecker has its own HTML parser, Markdown parser, CSS engine, layout engine and PDF writer, and its own PDF renderer. It does not start a browser, ship native binaries or pull in third-party packages; the NuGet package depends only on the .NET base class library.
using PdfPecker;
byte[] pdf = HtmlToPdf.Convert("<h1>Hello</h1><p>World</p>");
File.WriteAllBytes("hello.pdf", pdf);
Documentation: pdfpecker.net/docs · HTML to PDF in C#: a step-by-step guide
Install
dotnet add package PdfPecker
The package targets net8.0, net9.0, net10.0 and net11.0, so it works on .NET 8 and later, on Windows, Linux and macOS.
1.25.0 is the last release for .NET 8 and 9. Microsoft ended support for both on 10 November 2026. The next
release targets .NET 10 and later only; 1.25.0 and earlier releases keep working on .NET 8 and 9. This applies to
PdfPecker.Fluent, PdfPecker.Testing, PdfPecker.Razor and the pdfpecker tool too.
Quick start
// HTML string to PDF bytes, or file to file (relative images and stylesheets resolve against the HTML file's folder)
byte[] pdf = HtmlToPdf.Convert(html);
HtmlToPdf.ConvertFile("invoice.html", "invoice.pdf");
// ASP.NET Core
app.MapGet("/invoice", () => Results.File(HtmlToPdf.Convert(html), "application/pdf", "invoice.pdf"));
// Page size, margins, footer and fonts
var options = new PdfOptions
{
PageSize = PageSize.Letter,
Margins = PageMargins.FromMillimeters(20, 15, 20, 15),
FooterText = "Page {page} of {pages}",
};
options.Fonts.AddFile("fonts/OpenSans-Regular.ttf");
byte[] letter = HtmlToPdf.Convert(html, options);
// A copy of shared options with one setting changed, leaving the shared ones as they are
var draft = options.Clone();
draft.HeaderText = "Draft";
byte[] draftLetter = HtmlToPdf.Convert(html, draft);
// Markdown to PDF or HTML, merging, PDF pages to PNG, and a whole PDF to one black and white fax TIFF
byte[] guide = MarkdownToPdf.Convert(File.ReadAllText("README.md"));
string guideHtml = MarkdownToHtml.Convert(File.ReadAllText("README.md"));
byte[] merged = PdfMerger.Merge(invoicePdf, termsPdf);
byte[][] pngs = PdfToImage.Convert(pdf, new ImageOptions { Dpi = 150 });
byte[] tiff = PdfToImage.ConvertToTiff(pdf, new ImageOptions { Dpi = 200, TiffColorMode = TiffColorMode.BlackAndWhite });
// Make any existing PDF smaller: losslessly, or with images downsampled to 150 dpi and recompressed as JPEG
byte[] smaller = PdfCompactor.Compact(File.ReadAllBytes("scan.pdf"));
byte[] smallest = PdfCompactor.Compact(pdf, new PdfCompactOptions { MaxImageDpi = 150, ImageQuality = 75 });
// Strip JavaScript, launch and submit actions and other active content from a PDF someone sent you
byte[] safe = PdfSanitizer.RemoveActiveContent(File.ReadAllBytes("received.pdf"));
// Redact: take every match out of the file itself (text, image pixels, shapes, fields), not just cover it
byte[] redacted = PdfRedactor.RedactText(File.ReadAllBytes("contract.pdf"), new Regex(@"\b\d{6}-\d{4}\b"));
byte[] cut = PdfRedactor.Redact(pdf, new[] { new PdfRedactionArea(pageNumber: 1, x: 72, y: 100, width: 200, height: 40) });
// Turn any existing PDF into PDF/A-2b for archiving, with a list of what changed and of anything left to fix
var archived = PdfToPdfA.Convert(File.ReadAllBytes("received.pdf"));
if (!archived.IsConformant) Console.WriteLine(string.Join("\n", archived.Problems));
// PDF/X-4 for a print shop, for the press of the ICC profile it gives you, with a 3 mm bleed and crop marks from @page
byte[] print = HtmlToPdf.Convert(flyerHtml, new PdfOptions { PdfX = new PdfXOptions(File.ReadAllBytes("PSOcoated_v3.icc"), "FOGRA51") });
// Fast Web View: the first page shows while the rest of a large PDF is still downloading
byte[] web = HtmlToPdf.Convert(html, new PdfOptions { Linearize = true });
byte[] mergedForWeb = PdfLinearizer.Linearize(PdfMerger.Merge(invoicePdf, termsPdf));
byte[] protectedForWeb = PdfLinearizer.Linearize(mergedForWeb, new PdfSecurity { UserPassword = "secret" }, password: null);
// Title, author, PDF version, page sizes, encryption and PDF/A claim of any PDF
var properties = PdfDocumentProperties.Read(File.ReadAllBytes("received.pdf"));
Console.WriteLine($"{properties.Title} by {properties.Author}: PDF {properties.Version}, {properties.PageCount} pages, {properties.PdfAConformance}");
// Any PDF as one self-contained HTML page: page images with selectable, searchable text and working links
string html = PdfToHtml.Convert(File.ReadAllBytes("statement.pdf"));
// Or its text as headings, paragraphs, lists and real <table> elements in reading order, for search and language models
string structured = PdfToHtml.Convert(File.ReadAllBytes("statement.pdf"), new PdfHtmlOptions { Mode = PdfHtmlMode.Structured });
// The same structure with the PDF's fonts, colours, backgrounds, borders and pictures
string styled = PdfToHtml.Convert(File.ReadAllBytes("statement.pdf"),
new PdfHtmlOptions { Mode = PdfHtmlMode.Structured, IncludeStyles = true, IncludeImages = true });
// Or as Markdown: headings, lists, bold and italic, links and pipe tables, for language models and search indexes
string markdown = PdfToMarkdown.Convert(File.ReadAllBytes("statement.pdf"), new PdfMarkdownOptions { IncludePageComments = true });
// Open a PDF once and render its pages one at a time, or in parallel
var renderer = PdfRenderer.Open(pdf);
renderer.RenderPage(0, new ImageOptions { Dpi = 150 }).Save("page1.png");
The conversion methods are static and thread-safe, so you can convert many documents in parallel. The few instance types are FontCollection and PdfRenderer, which can be shared by parallel work, and StreamingPdfMerger, which is not thread-safe: it builds one output and is used from one thread at a time.
Fluent API
PdfPecker.Fluent builds the same PDFs from C# code instead of HTML,
in the style of QuestPDF, so most QuestPDF documents port by changing the using lines. It is a separate package from
this repository, released with every PdfPecker release under the same version, and its only dependency is PdfPecker.
dotnet add package PdfPecker.Fluent
using PdfPecker.Fluent;
Document.Create(container => container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Header().Text("Invoice 2026-104").FontSize(20).Bold();
page.Content().Table(table =>
{
table.ColumnsDefinition(columns => { columns.RelativeColumn(3); columns.RelativeColumn(); });
table.Cell().Text("Consulting");
table.Cell().AlignRight().Text("1,200.00");
});
page.Footer().AlignCenter().Text(x => { x.CurrentPageNumber(); x.Span(" / "); x.TotalPages(); });
}))
.GeneratePdf("invoice.pdf");
It writes compact HTML and CSS and renders it with PdfPecker, so every PdfOptions feature (PDF/A, PDF/UA,
signatures, e-invoices) works with it. Its DocumentOperation takes QuestPDF's calls for existing PDFs (choosing and
merging pages, overlays and underlays, attachments, encryption, linearization) and runs them on PdfPecker's merger,
stamper and protector. What it covers and how it differs from QuestPDF is in its
README.
Command line
PdfPecker.Cli is the same library as a pdfpecker command, for scripts,
build pipelines and designing a template. It is a dotnet tool released with every PdfPecker release under the same
version, and like the library it needs no browser, no JavaScript and no network.
dotnet tool install -g PdfPecker.Cli
pdfpecker convert invoice.html # writes invoice.pdf; .md files are Markdown
pdfpecker watch invoice.html # converts again on every save, for a live preview in your PDF viewer
pdfpecker merge cover.pdf report.pdf:2-9 -o all.pdf # also split, extract, remove, rotate, stamp, compact, linearize
pdfpecker sign all.pdf signed.pdf --cert me.pfx # also verify, encrypt, decrypt, sanitize
pdfpecker to-image all.pdf pages --format webp # also to-html, text, info
On a .NET 10 or 11 SDK the tool installs as a Native AOT executable for Windows, macOS and Linux (x64 and Arm64, glibc and musl), so a command starts in milliseconds instead of the third of a second the JIT needs: converting the sample invoice took 17 ms in all instead of 355 ms on a 4-core Linux VM. Other platforms and .NET 8 and 9 SDKs get the framework-dependent tool, as before.
A call without a command word is read like wkhtmltopdf's, so a script can switch by changing the program name:
pdfpecker -s A4 -T 15mm --footer-center "[page] / [topage]" in.html out.pdf. Page size, orientation, margins, headers and
footers with their tokens, zoom, style sheets and quiet mode work as they do there; options that exist because
wkhtmltopdf runs a browser (--javascript-delay, --cookie, --header-html, ...) are ignored with a warning. Web
addresses are refused. Its README is
here.
Testing PDFs
PdfPecker.Testing adds snapshot tests for the PDFs your code makes.
PdfAssert.MatchesSnapshot compares a PDF with a stored one, leaving out dates, file IDs and stream compression, and
when they differ it writes the new PDF and page images with the changed pixels marked. It works with any test
framework, because a failed check throws. It is released with every PdfPecker release under the same version, and its
only dependency is PdfPecker.
dotnet add package PdfPecker.Testing
using PdfPecker.Testing;
byte[] pdf = HtmlToPdf.Convert(invoiceHtml);
PdfAssert.MatchesSnapshot(pdf, "invoice"); // Snapshots/invoice.pdf next to the test file
PdfAssert.ContainsText(pdf, "Total due: 1,500.00");
PdfAssert.HasPageCount(pdf, 1);
How snapshots are created and accepted, and the appearance comparison for PDFs from other producers, are in its README.
Razor components
PdfPecker.Razor turns Razor components (.razor files) into PDFs,
so a template gets typed parameters, compile-time checks, child components, layouts and dependency injection. It renders
the component to HTML with Microsoft's HtmlRenderer, without an ASP.NET Core host, and converts the HTML with
PdfPecker. It is released with every PdfPecker release under the same version, and it is the one package here with a
dependency besides PdfPecker: Microsoft's Microsoft.AspNetCore.Components.Web, which holds the renderer.
dotnet add package PdfPecker.Razor
using PdfPecker.Razor;
// Invoice.razor has [Parameter] public InvoiceModel Data { get; set; }
byte[] pdf = await RazorToPdf.ConvertAsync<Invoice>(new Dictionary<string, object?> { ["Data"] = invoice });
Razor encodes caller data, every PdfOptions setting works, and the package is trimmable and runs with Native AOT
(Microsoft's components assembly reports trim warnings of its own, which its README explains). How to set up the
Razor project, inject services and stream to an ASP.NET Core response is in its
README.
Features
- HTML and CSS: flexbox, grid and subgrid, CSS tables (collapsed borders also from
colandcolgroup; rows and columns withvisibility: collapseleave), floats withshape-outside,clip-pathshapes (alsorect()andxywh()), sizes that fill the containing block (width: stretchand-webkit-fill-available, also for heights and their limits), percentage heights inside flex items resolved as in Chrome (stretched items, and items flexed from a length basis in a column whose container has only amin-height, as card and label layouts use),corner-shape(squircle, bevel, scoop, notch andsuperellipse()corners on borders, backgrounds, shadows, outlines and clips),overflow: clipwithoverflow-clip-margin,zoom(as Chrome computes it, for templates that shrink a page to fit), multi-column layout, gap decorations (column-ruleandrow-rulein grid and flex containers too, with lists andrepeat(),rule-break,rule-inset,rule-visibility-itemsandrule-overlap, cut where a grid or flex container breaks across pages), positioning (also the legacyclip: rect(), and absolute boxes inside a positioned inline element, such as tooltips and badges), transforms, gradients (alsoin oklchand other colour spaces, and as list markers; translucent stops blend premultiplied, as in Chrome),contrast-color()(black or white text, whichever reads better on a brand colour, as in Chrome), fixed background images (placed on each page's paper, as Chrome prints them), filters, masks, media and container queries,@scope(scope roots, limits and proximity, as in Chrome), logical properties,writing-mode(with floats, sized boxes and vertical-lr line order; Chinese and Japanese characters stand upright with the font's vertical forms, astext-orientationsays,text-combine-uprightsets dates and numbers across the line, and images stay upright, as in Chrome),text-wrapandwhite-space-collapse, hanging indents (text-indentwithhangingandeach-line), emphasis marks (text-emphasis), ruby annotations such as Japanese furigana (<ruby>,<rt>and<rp>: the annotation at half size over its base, spread around it and over the kana beside it, in the line's leading where it fits, as in Chrome), trimming lines to the cap height and baseline (text-box, for optically centred buttons and badges),line-clampwith your ownblock-ellipsis, underlines from the font or below the descenders (text-underline-position), solid underlines, overlines and line-throughs as thick and placed as Chrome draws them, drop caps,::first-linestyles with a background,::markerstyles (a larger marker font or a tall marker image makes its line taller,content: nonehides the marker,counter(list-item)numbers it and its owndirectionorders its text, as in Chrome), images and gradients in::beforeand::aftercontent, inline SVG (with percentage coordinates and sizes, as in Bootstrap's placeholder images,cx,cy,r,rx,ry,x,y,width,height,d: path()andstop-colorset from style sheets, HTML in<foreignObject>, and icons drawn with<use>from a sprite of symbols that is itself hidden, in thecurrentColorwhere they are used, as in Bootstrap's icon sprites; lengths in mm, cm, in, pt and em; nested<svg>and<symbol>viewports withpreserveAspectRatioand clipping; also CSStransformwithtransform-boxandtransform-originon SVG elements, such as a gauge needle turning about its own centre, andvector-effect: non-scaling-strokefor hairlines in a scaled chart, though markers still scale) and PNG, JPEG, GIF, WebP, BMP and TIFF images (also chosen bysrcset,<picture>andimage-set(), shown by<embed>and<object>, photos turned upright by their EXIF orientation, andimage-rendering: pixelatedorcrisp-edgeswriting enlarged images, such as QR codes and pixel art, with hard pixel edges in every PDF viewer, andobject-view-boxcropping a raster image in CSS, as Chrome does), and CSS-only charts such as Charts.css (column, bar, area, line and pie charts), laid out as Chrome lays them out. Typedattr()works in any property, as in Chrome:width: attr(data-pct %),color: attr(data-color type(<color>), gray)orcalc(attr(data-v number) * 1mm)draw bar charts and badges from data attributes, with fallbacks,::beforereading its element's attributes, and no attribute value ever reaching aurl().sibling-index()andsibling-count()work as in Chrome too, for staggered widths, colours that step through a list and tabs that share a row evenly.if()chooses a value fromstyle()of a custom property,media()orsupports(), such asborder-color: if(style(--status: overdue): red; else: green), as in Chrome (whosemedia()inif()still asks about the screen when it prints). Older CSS works too: the-webkit-property names Chrome still takes (-webkit-transform,-webkit-flex, ...), the legacydisplay: -webkit-boxand its-webkit-box-properties, and the-webkit-gradient()and prefixed gradient syntaxes. As in browsers, a length without a unit (width: 200) counts as pixels only in a page without a doctype; with<!DOCTYPE html>it is ignored. Without a doctype, lines also follow Chrome's quirks-mode line heights: a line of small<font>text, or an image alone in a table cell, is only as tall as its content. The old body attributestopmargin,leftmargin,marginheightandmarginwidthset the page margins as they do in Chrome. More - Paged media:
@pagesizes (also a page as tall as its content,size: 80mm auto, for receipts on a roll), borders, padding and background images (which cover the whole sheet) and margin boxes (also with running elements,position: running()andelement(), as in Prince and WeasyPrint), a bleed with crop marks and registration crosses for a print shop (bleed: 3mm; marks: crop cross, written as the PDF's trim and bleed boxes, as in WeasyPrint), CMYK colours for print withdevice-cmyk()of CSS Color 5 (color: device-cmyk(0 0 0 1)for text in black ink only, written as DeviceCMYK for text, backgrounds, borders, rules and SVG fills and strokes; gradients, shadows, filters and colour mixes use its plain sRGB conversion, as does PDF/A, whose output intent is sRGB),@page :nth(2n+1)and page groups,:nth(1 of chapter)(as in WeasyPrint; they style margin boxes and the page background, not the page size or margins), running headers and footers (also a first page of its own, even pages of their own for printing on both sides, and bands of their own for named pages),position: fixedboxes repeated on every page of their named page, also in the page margins with negative offsets, and over the running headers and footers unless their z-index is negative (for watermarks and page frames), footnotes, tables of contents with page numbers, the page count anywhere in the content (counter(pages), as in Prince), the first and last page of a section and page numbers counted within it (-pdfpecker-target-pages(), and{section-page:id}in running headers and footers), page numbers in running headers and footers in any counter style ({page:upper-roman},{total-pages:lower-alpha},{section-page:id:lower-roman}, or a zero-padded@counter-styleof your own), break control (also table row groups and group header rows kept together, as reports keep a group on one page, and forced breaks between table rows and between flex lines), table rows that break inside at a page's end as in Chrome, unless a cell would have nothing before the break (also rows with cells that span rows, whose spanning cells go on below the repeated header), flex rows whose items break at a page's end as in Chrome (the row moves whole when an item is kept whole), table headers and footers repeated on every page (also around a row split across pages), boxes split across pages or columns that repeat their border, padding, rounded corners, shadow and background on each page withbox-decoration-break: clone(also tables with separate borders), and bookmarks from the headings thatbookmark-level,bookmark-labelandbookmark-statecan extend, rename or close. More - Content scaled to fit a box:
-pdfpecker-scale-to-fit: shrinkfits text of any length into a fixed space, for labels, badges and certificates. More - Content kept to one page or box:
continue: discard(CSS Overflow 4) keeps the part of a box that fits on the page it starts on, or in its height ormax-height, and leaves the rest out instead of continuing on the next page. Lines, images and table rows that would cross the end are left out whole. - Content repeated on each page:
-pdfpecker-repeat: each-pagedraws a box again at the top of each later page its parent continues on, such as a label in a row beside a text several pages long. - Smaller images while converting:
-pdfpecker-image-quality: 75writes images as JPEGs at that quality, and-pdfpecker-image-max-resolution: 150dpidownsamples images drawn denser than that, judged by the size each is drawn at. Both are inherited, sobody { ... }sets them for the document andnoneturns them off for one image; transparency is kept losslessly, and an image is only replaced when the result is smaller. - Markdown: CommonMark 0.31 and GitHub Flavored Markdown to PDF or HTML, with footnotes, definition lists and YAML front matter. More
- Fonts: TrueType, OpenType, WOFF and WOFF2, subset and embedded, with kerning, ligatures, contextual alternates
(as Chrome applies them, such as Fira Code's ligatures), variable fonts and OpenType features, and bundled fallback fonts for letters and symbols a document's fonts lack. COLR colour fonts,
versions 0 and 1 (such as Noto Color Emoji), print in colour as Chrome prints them, in the default palette or the one
font-palettepicks:light,darkor an@font-palette-valuesrule withbase-paletteandoverride-colors.font-variant-alternatesturns on historical forms and the stylistic sets, character variants, stylistic, swash, ornament and annotation alternates that@font-feature-valuesnames for a family. Variation sequences pick the glyph the font gives them (cmap format 14), such as the forms of a CJK ideograph that ideographic variation selectors choose, and copy as written.@font-facetakessize-adjust,ascent-override,descent-overrideandline-gap-override, so a fallback face can be sized to match a web font.FontCollection.AddDeferredregisters a font that is read only when a document uses it, with aunicode-rangeso that fallback skips it for other characters, which suits large CJK fonts. More - Languages: Arabic and Hebrew with bidi and shaping, Indic scripts, Khmer, Myanmar, Sinhala, Tibetan, Thai and
Lao (copied and extracted as written, with
/ActualTextwhere shaping reorders or shares glyphs), CJK line breaking and justification,text-autospace(an eighth of an em between Chinese, Japanese or Korean text and Latin letters or digits, as in Chrome), and hyphenation in eleven languages (withhyphenate-limit-lines), plus TeX patterns of your own, whichHyphenationPatterns.Deferredreads only when a document uses their language. More - PDF/A, passwords and watermarks: PDF/A-2 and 3 (levels B, U and A, the accessible level, tagged from the HTML) and PDF/A-4, AES-256 encryption (passwords prepared with SASLprep, as PDF 2.0 requires), and text, image and SVG watermarks. More
- PDF/X-4 for print shops:
new PdfOptions { PdfX = new PdfXOptions(File.ReadAllBytes("PSOcoated_v3.icc"), "FOGRA51") }writes a PDF/X-4 file (ISO 15930-7) for the press whose ICC output profile you pass: PDF 1.6 with the output intent, a trim box on every page (the paper, inside the bleed box of@page { bleed: 3mm }), sRGB colours and images tagged with an sRGB profile for the print shop's colour conversion, CMYK JPEGs as DeviceCMYK, embedded fonts and the PDF/X metadata. PdfPecker bundles no press profile (the print shop, the ICC or the ECI provides one), does no trapping, and leaves links and fillable fields out, since PDF/X allows no annotations on the printed page. veraPDF has no PDF/X profile, so the files are checked by tests of each requirement and by rendering in Ghostscript and Poppler, not by a PDF/X validator. - Existing PDFs to PDF/A:
PdfToPdfAconverts PDFs from any tool to PDF/A-2b, 2u, 3b or 3u without rasterizing them. It embeds fonts that are not embedded (with the bundled glyphs, at the document's own widths), repairs embedded TrueType fonts whose glyphs, widths or character maps disagree with the PDF, adds the output intent and XMP metadata, removes scripts and other forbidden actions, gives annotations and form fields appearances, makes colours device independent, and keeps or converts attachments as the level allows. The result lists every change and every problem it could not fix, such as text in a font whose glyphs PdfPecker does not have (CJK fonts that are not embedded, for instance), and veraPDF checks the converted samples in CI. Encrypted and signed PDFs are refused. - Accessibility: tagged PDF, PDF/UA-1 and PDF/UA-2 built from your HTML. ARIA roles tag what HTML has no element
for:
role="math"(witharia-label) a formula,role="doc-toc"a table of contents whose list items are its entries, androle="doc-index"an index.data-pdf-list-labelmarks a list item's own label, such as a number in a flex row, when the item has no list marker. More - Forms: HTML inputs become fillable PDF fields; read, fill and flatten the fields of existing PDFs. Check boxes
and radio buttons take
accent-color, andappearance: noneleaves them to the page's own CSS (:checked,::beforeand::after), as for toggle switches; such a control is drawn rather than made a field. An element withdata-pdf-signature="Buyer"becomes an empty signature field over its box, whichPdfSignerfills later withFieldName = "Buyer". More - Digital signatures: PAdES signing, certification signatures, timestamps, external signers, verification (SHA-1 signatures count as not valid unless you allow them) and long-term validation. More
- E-invoices and attachments: Factur-X, ZUGFeRD, XRechnung and Peppol BIS 3.0 XML built from invoice data and checked against EN 16931, a ready-made invoice layout, and embedded files. More
- Barcodes: QR codes, EPC payment QR codes, Swiss QR-bill codes (with the Swiss cross and a checked payload), Data
Matrix and GS1 Data Matrix, Code 128 and EAN-13 as vector SVG, e.g.
Barcode.QrCode("https://example.com")orBarcode.Gs1DataMatrix("(01)09506000134352(10)AB-123"). More - Reading barcodes:
Barcode.Read(image)reads the QR, Data Matrix (including the rectangular DMRE sizes), Code 128 and EAN-13 codes in a PNG, JPEG, GIF, WebP, BMP or TIFF image, andPdfBarcodes.Find(pdf)those on PDF pages, whether drawn as vectors, embedded as images or scanned. Codes may be turned, tilted, mirrored or light on dark; each result has its text and bytes, its corners in pixels or page points, a GS1 flag, and the payment parsed from an EPC or Swiss QR-bill code. On the zxing-cpp test images it reads about 85 % of what zxing-cpp reads, with no wrong reads. - Existing PDFs: merge (also streamed), split, reorder, rotate, stamp, extract text with positions, extract images and read document properties. Text comes out in reading order: a tagged PDF's in the order of its tags, and columns side by side one after the other, also on pages turned sideways or upside down. More
- Compacting and Fast Web View:
PdfCompactormakes any PDF smaller without changing how it looks, andPdfOptions.LinearizeorPdfLinearizerwrites a linearized PDF whose first page shows before the rest arrives, also encrypted:PdfLinearizeropens a protected PDF with its password and encrypts the result in the same pass. More - Removing active content:
PdfSanitizertakes JavaScript, actions that launch programs, open other files or submit form data, XFA and media annotations out of PDFs from others, together with links to anything but web, e-mail and phone addresses (network paths,smb:and other protocol handlers), andHasActiveContenttells whether a PDF has any. Pages, text, web links and form fields stay. - Redaction:
PdfRedactorremoves what pages draw in given areas, or wherever a text or regular expression matches, from the file itself: the glyphs leave the content streams (so text extraction and copying no longer find them, while the text around them keeps its place), image pixels under an area are overwritten, and shapes, links and form fields in an area go, before a black box (or none) is drawn over it. Forms shared with other pages are redacted in copies, the originals of redacted images and forms leave the file unless a page still shows them elsewhere, replacement texts that would still name what was there are removed, and earlier revisions do not survive. - Images to PDF:
ImageToPdfputs scans and photos on pages of their own size or fitted to a page size, with photos turned upright by their EXIF orientation. More - Photo privacy:
PdfOptions.StripImageMetadataremoves EXIF (GPS location included), XMP, IPTC and comments from embedded JPEGs while keeping their pixels and colours. - PDF to image: renders any PDF page to PNG, JPEG or WebP, or a whole PDF to a multi-page TIFF, and
HtmlToImagegoes from HTML to images in one call.PdfRenderer.RenderPage(page, buffer, ImagePixelFormat.Gray)renders a page straight into a buffer of yours as grey, RGB or RGBA pixels, sized byGetPixelSize, for OCR and image processing. Comments that carry no appearance of their own (notes, highlights, underlines, strike-outs, squiggly lines, shapes, lines with their arrowheads, ink and free text) are drawn from their properties, as viewers draw them. More - Image decoding:
ImageToPixels.Convert(image)reads a PNG, JPEG, GIF, WebP, BMP or TIFF file (any page of a multi-page TIFF) straight to grey, RGB or RGBA pixels, with the same colour conversions, EXIF orientation and size limits asImageToPdfand without making a PDF of it: for OCR, image analysis and thumbnails. - PDF to HTML:
PdfToHtmlturns any PDF into one script-free HTML file that looks like the PDF, with text you can select, search and copy.PdfHtmlMode.Structuredwrites the text instead as flowing headings, paragraphs, lists and real<table>elements in reading order, with nothing placed at a fixed position, for search indexes and language models. A tagged PDF (PDF/UA, or any PDF with a structure tree) is read from its tags, which say exactly what each heading, list item and table cell is. Other PDFs are read from their layout: tables come from the rules and borders the PDF draws and from text that lines up in columns, with header rows, spanned cells and wrapped cells. Reading the layout can turn unusual layouts, floats and charts into plain paragraphs, and scanned pages have no text to give.IncludeStyleskeeps how the page looks in that structure: fonts, sizes, colours, backgrounds, rounded boxes, table borders, column widths and alignment, as CSS classes.IncludeImagesadds the page's pictures as<img>elements. More - PDF to Markdown:
PdfToMarkdownwrites the same structure as GitHub Flavored Markdown: headings, paragraphs, bulleted and numbered lists, bold and italic text, links, and pipe tables, with several header rows joined column by column and spanned cells in their first row and column. Line breaks the PDF made on purpose stay, and text that would read as Markdown is escaped.IncludePageCommentsmarks where each page starts, so a passage can be traced to its page, andIncludeImagesadds the pictures asdata:images. - Viewer settings and page labels: the page, zoom and panel a viewer opens with, print settings, and page numbers such as "i" to "iv" for front matter. More
- Reproducible output: a fixed
CreationDateorSOURCE_DATE_EPOCHgives byte-identical PDFs, for build pipelines and output caches. More - Safe failures: errors share the base class
PdfPeckerException, and the*Filemethods never leave a half-written output. More
PdfPecker is built for HTML templates you control: it runs no JavaScript, and conversion never fetches anything from
the network (remote images and stylesheets load only through a ResourceResolver you supply; those in data: URLs need
none). Only signing goes online, and only when you ask: a timestamp from the server you name in TimestampUrl, OCSP and CRL data for long-term
validation when you turn on FetchRevocationData (from public addresses only, unless your UrlFilter allows others), and online revocation checks when verification uses
X509RevocationMode.Online. See the limitations for what it does not do yet.
How it compares
The same three documents converted to A4 PDF by PdfPecker and six .NET alternatives, on one 4-core cloud VM (Intel Xeon @ 2.1 GHz, 260 MB L3, 16 GB, Linux, .NET 10.0.13). Times are medians after a 3-second warm-up, averaged over two runs; "first PDF" includes starting the browser where there is one. Parallel runs one conversion per core at once on the 5-page document. Peak memory is the proportional set size (PSS) of the whole process tree, so a browser's helper processes are included once and shared pages are not double-counted; .NET processes run with a 16 MB GC gen0 budget, as .NET would otherwise size it from the CPU cache.
PdfPecker is 50 to 200 times faster than the Chromium-based converters on the small documents and uses an eighth or less of their memory. How to read the table and how it was measured is in Performance; the benchmark itself, to run on your own machine, is in PdfPecker.Benchmarks.
| Converter | Engine | To deploy | First PDF | Invoice, 1 page | Feature tour, 5 pages | Report, 30 pages | Parallel | Peak memory |
|---|---|---|---|---|---|---|---|---|
| PdfPecker | own, managed | 6.3 MB DLL | 0.40 s | 0.92 ms | 2.9 ms | 33 ms | 1,132 docs/s | 92 MB |
| HtmlRenderer.PdfSharp 1.6.1 | own, managed (CSS 2) | 5.0 MB of DLLs | 0.54 s | 4.9 ms | 16 ms | 360 ms | 114 docs/s | 255 MB |
| iText pdfHTML 6.3.4 | own, managed | 11 MB of DLLs | 1.18 s | 24 ms | 132 ms | 2.2 s | 15 docs/s | 707 MB |
| wkhtmltopdf 0.12.6 | Qt WebKit, native | about 90 MB with Qt | 0.33 s | 282 ms | 309 ms | 600 ms | 12 docs/s | 165 MB |
| PuppeteerSharp 25.13 | Chromium 141 | about 600 MB browser | 1.15 s | 106 ms | 142 ms | 496 ms | 15 docs/s | 813 MB |
| Microsoft.Playwright 1.63 | Chromium 141 | browser + 135 MB driver | 0.98 s | 131 ms | 167 ms | 517 ms | 12 docs/s | 1,027 MB |
| Syncfusion HTML to PDF 35.1.40 | Chrome (Blink), bundled | about 545 MB with Chrome | 2.46 s | 183 ms | 236 ms | 968 ms | 3 docs/s | 2,239 MB |
Building
dotnet build
dotnet test
dotnet run --project samples/PdfPecker.Sample -c Release # converts samples/PdfPecker.Sample/html/*.html and *.md into ./output
dotnet run --project samples/PdfPecker.Fluent.Sample -c Release # writes the PdfPecker.Fluent examples into ./output
dotnet run --project src/PdfPecker.Cli -c Release -- convert in.html out.pdf # the pdfpecker command-line tool
dotnet run --project samples/PdfPecker.Razor.Sample -c Release # writes the PdfPecker.Razor invoice into ./output
dotnet run --project bench/PdfPecker.Benchmarks -c Release
dotnet run --project bench/PdfPecker.Benchmarks -c Release -- --requirements # memory and CPU per conversion
bench/compare-versions.sh v1.24.0 # this checkout against a release, in turns on this machine
dotnet pack src/PdfPecker -c Release -o ./nupkg # also checks the public API against the last release
dotnet pack src/PdfPecker.Fluent -c Release -o ./nupkg # the fluent package, with the same version
dotnet pack src/PdfPecker.Cli -c Release -o ./nupkg # the pdfpecker dotnet tool, with the same version
dotnet pack src/PdfPecker.Testing -c Release -o ./nupkg # the snapshot testing package, with the same version
dotnet pack src/PdfPecker.Razor -c Release -o ./nupkg # the Razor package, with the same version
Requirements and details are in BUILD.md and TESTING.md; how to fuzz the parsers is in fuzz/README.md.
To contribute, see CONTRIBUTING.md. Report security problems privately as described in SECURITY.md.
License
MIT. The third-party fonts, font data and code built into the library are listed, with their licenses, in THIRD-PARTY-NOTICES.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- No dependencies.
-
net11.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (10)
Showing the top 5 NuGet packages that depend on PdfPecker:
| Package | Downloads |
|---|---|
|
PdfPecker.Fluent
Fluent C# API for building PDFs with PdfPecker: pages, headers, footers, columns, rows, tables, text, images and barcodes in code. It writes HTML and CSS and renders them with PdfPecker, so it needs no browser and no native binaries. |
|
|
PdfPecker.Testing
Snapshot tests for PDFs with PdfPecker: compare a generated PDF with a stored one after leaving out dates, file IDs and compression, and get page images with the changed pixels marked when they differ. Also checks for text and page counts. Works with xUnit, NUnit, MSTest or any other test framework, because a failed check throws. |
|
|
PdfPecker.Razor
Razor components (.razor) to PDF with PdfPecker: typed templates with layouts, child components and dependency injection, rendered to HTML with Microsoft's HtmlRenderer and converted to PDF without a browser, an ASP.NET Core host or native binaries. |
|
|
PdfPecker.Email
Convert e-mail (.eml, MIME, Outlook .msg and winmail.dat) to PDF and PDF/A-3 with PdfPecker: headers, HTML or plain text bodies, inline images and the attachments embedded in the PDF. Dependency-free apart from PdfPecker; HTML bodies are sanitised and nothing is fetched from the network. |
|
|
PdfPecker.Ocr
Dependency-free OCR for .NET: reads text from scanned PDFs and images and makes scanned PDFs searchable. Its own neural network runtime in C#, no native binaries. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.25.0 | 162 | 11/12/2026 |
| 1.24.0 | 447 | 11/10/2026 |
| 1.23.0 | 696 | 11/4/2026 |
| 1.22.0 | 339 | 11/1/2026 |
| 1.21.0 | 230 | 10/28/2026 |
| 1.20.0 | 580 | 10/25/2026 |
| 1.19.0 | 206 | 10/22/2026 |
| 1.18.0 | 178 | 10/21/2026 |
| 1.17.0 | 131 | 10/21/2026 |
| 1.16.0 | 128 | 10/20/2026 |
| 1.15.0 | 120 | 10/19/2026 |
| 1.14.2 | 120 | 10/18/2026 |
| 1.14.1 | 103 | 10/18/2026 |
| 1.14.0 | 108 | 10/18/2026 |
| 1.13.0 | 2,549 | 10/18/2026 |
| 1.12.0 | 257 | 10/18/2026 |
| 1.11.0 | 491 | 10/18/2026 |
| 1.10.0 | 383 | 10/17/2026 |
| 1.9.0 | 693 | 10/16/2026 |
| 1.8.0 | 161 | 10/16/2026 |
What changed in each release: https://pdfpecker.net/docs/changelog/