<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: IronSoftware</title>
    <description>The latest articles on DEV Community by IronSoftware (@ironsoftware).</description>
    <link>https://dev.to/ironsoftware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F714737%2F30e70529-1628-476f-9a14-3b157c0f5c82.png</url>
      <title>DEV Community: IronSoftware</title>
      <link>https://dev.to/ironsoftware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ironsoftware"/>
    <language>en</language>
    <item>
      <title>ZXing Decoder Online vs IronBarcode in .NET</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Tue, 30 Jun 2026 00:06:49 +0000</pubDate>
      <link>https://dev.to/ironsoftware/zxing-decoder-online-vs-ironbarcode-in-net-59oo</link>
      <guid>https://dev.to/ironsoftware/zxing-decoder-online-vs-ironbarcode-in-net-59oo</guid>
      <description>&lt;h1&gt;
  
  
  ZXing Decoder Online vs IronBarcode: When Do You Actually Need a .NET Library?
&lt;/h1&gt;

&lt;p&gt;If you have a single QR code image on your desktop and you want to know what it says, the answer is almost never "write code." Open &lt;a href="https://zxing.org" rel="noopener noreferrer"&gt;ZXing Decoder Online&lt;/a&gt;, upload the file, read the text. Done in ten seconds, nothing installed, nothing to license. So why does a .NET barcode library exist at all?&lt;/p&gt;

&lt;p&gt;Full disclosure: we build IronBarcode at Iron Software, so we're not neutral. We'll be straight about when a free online decoder is all you need, and the honest answer is that it covers more cases than vendors like us tend to admit. The real split here isn't quality. It's use case: a manual web tool versus a programmatic library you embed in an app to read codes automatically, in bulk, from messy sources.&lt;/p&gt;

&lt;p&gt;Here's what we found. The ZXing project gives you two distinct things that often get lumped together, and keeping them separate is half the battle. There's the website at zxing.org for checking a single image by hand, and there's the open-source &lt;a href="https://github.com/zxing/zxing" rel="noopener noreferrer"&gt;ZXing library&lt;/a&gt; (and its .NET port, ZXing.NET) that you call from code. Both are free. Here's the ZXing.NET decode in C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ZXing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ZXing.QrCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;BarcodeReader&lt;/span&gt; &lt;span class="n"&gt;barcodeReader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BarcodeReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="n"&gt;decodeResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;barcodeReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceBitmap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;decodedText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decodeResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole pitch for the library side, and for a lot of projects it's genuinely enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ZXing Decoder Online tool
&lt;/h2&gt;

&lt;p&gt;The website is the part people reach for first, and rightly so. You visit zxing.org, choose a file or paste an image URL, submit, and it shows the decoded contents along with the format and raw bytes. It reads 1D and 2D formats, it's instant, and there is nothing to install or pay for.&lt;/p&gt;

&lt;p&gt;✅ For manual, occasional work we'd point you straight at this tool, and we won't pretend otherwise. Debugging a single QR code a teammate sent you, checking what a printed label encodes, sanity-checking one image during development. Reaching for a NuGet package and a build step there would be more work, not less, and we've watched plenty of developers over-engineer exactly this.&lt;/p&gt;

&lt;p&gt;The limits show up the moment the task stops being manual. The website can't run inside your application. You can't point it at a folder of 5,000 scans, hand it a 40-page PDF, or call it from a nightly job. It's a person clicking a button, one image at a time. That's a feature for its purpose, not a flaw, but it's also exactly where a library starts to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ZXing library in code
&lt;/h2&gt;

&lt;p&gt;If you want decoding inside your own app, ZXing.NET is the free, open-source option, and it's a solid one. It's been around for years, has a large community, and ships under a permissive license. You generate and read codes directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ZXing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ZXing.QrCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Drawing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;BarcodeWriter&lt;/span&gt; &lt;span class="n"&gt;barcodeWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BarcodeWriter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BarcodeFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QR_CODE&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;qrBitmap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;barcodeWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, ZXing!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tradeoff here is what you assemble around it. ZXing.NET works on the bitmap you give it, so you handle image loading, rasterizing PDF pages yourself, and any cleanup when a scan is skewed, low-contrast, or noisy. On clean images it decodes reliably. On the imperfect inputs that real-world scanning produces, you tend to write preprocessing code before the bitmap ever reaches the decoder. For many teams that's an acceptable trade, especially when the budget is zero and the inputs are tidy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IronBarcode fits
&lt;/h2&gt;

&lt;p&gt;IronBarcode is a commercial .NET library. That's the honest headline difference from ZXing.NET, and we'll lead with it: it costs money where ZXing is free. So the question we'd ask is what you get for that, and whether it matches your problem.&lt;/p&gt;

&lt;p&gt;Installation is one command, and the snippets below run in about five minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Install-Package BarCode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generating a code is one line, and reading one back is two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronBarCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;BarcodeWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBarcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, IronBarcode!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BarcodeEncoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QRCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SaveAsPng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"qrcode.png"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronBarCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;qrResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BarcodeReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"qrcode.png"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qrResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there's no separate image-loading step and no format argument required. You point it at a file and read the value. What you're paying for is the layer around the decoder. IronBarcode reads barcodes directly from PDFs, multi-frame TIFFs, and common image formats, and it applies its own correction for rotation, low resolution, and noise so you don't hand-write that cleanup. It handles batch input across many files and supports the 1D and 2D formats you'd expect, including QR, Code 128, Code 39, EAN, UPC, PDF417, and Data Matrix.&lt;/p&gt;

&lt;p&gt;That's the use case we see it built for: programmatic reading inside a .NET app, at volume, from sources that aren't pristine. We'd be the first to say it's not a replacement for the zxing.org website, and it's not trying to be a cheaper ZXing.NET. It's a paid alternative aimed squarely at the automated-pipeline problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the three compare
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;ZXing Decoder Online&lt;/th&gt;
&lt;th&gt;ZXing.NET&lt;/th&gt;
&lt;th&gt;IronBarcode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read / decode&lt;/td&gt;
&lt;td&gt;✅ Manual, one image&lt;/td&gt;
&lt;td&gt;✅ In code&lt;/td&gt;
&lt;td&gt;✅ In code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write / generate&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbologies&lt;/td&gt;
&lt;td&gt;1D and 2D&lt;/td&gt;
&lt;td&gt;1D and 2D&lt;/td&gt;
&lt;td&gt;QR, Code 128, Code 39, EAN, UPC, PDF417, Data Matrix, more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image preprocessing&lt;/td&gt;
&lt;td&gt;None needed (human-driven)&lt;/td&gt;
&lt;td&gt;Manual, you write it&lt;/td&gt;
&lt;td&gt;Built-in rotation and noise correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read from PDF / batch&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Manual rasterization&lt;/td&gt;
&lt;td&gt;✅ Direct, batched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.NET targets&lt;/td&gt;
&lt;td&gt;N/A (web tool)&lt;/td&gt;
&lt;td&gt;.NET Standard / Core / Framework (+ binding)&lt;/td&gt;
&lt;td&gt;.NET Standard / Core 2.0+, Windows, Linux, macOS, Azure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Free, hosted&lt;/td&gt;
&lt;td&gt;Apache 2.0, free, open source&lt;/td&gt;
&lt;td&gt;Commercial, paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Hosted by ZXing project&lt;/td&gt;
&lt;td&gt;Community cadence&lt;/td&gt;
&lt;td&gt;Commercial release cadence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So which should you use?
&lt;/h2&gt;

&lt;p&gt;There's no single winner, and we'd distrust any comparison that pretended there was. It comes down to what you're actually doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Reach for &lt;strong&gt;ZXing Decoder Online&lt;/strong&gt; for one-off, manual decoding. It's free, instant, install-free, and the right call when a human is checking a single image.&lt;/li&gt;
&lt;li&gt;✅ Reach for the &lt;strong&gt;ZXing.NET library&lt;/strong&gt; when you need decoding in code, your inputs are reasonably clean, and you want a free, open-source dependency you can read and modify.&lt;/li&gt;
&lt;li&gt;✅ Reach for &lt;strong&gt;IronBarcode&lt;/strong&gt; when you're reading codes programmatically in .NET in high volume, from PDFs and imperfect scans, and a single-package install with built-in image correction is worth a license fee. The &lt;a href="https://ironsoftware.com/csharp/barcode/tutorials/reading-barcodes/" rel="noopener noreferrer"&gt;IronBarcode barcode reading tutorial&lt;/a&gt; shows the PDF and batch paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cutoff that matters is manual versus automated. If a person is decoding one image, the website wins and nothing we sell changes that. If your application is reading many codes from unpredictable sources without anyone clicking a button, that's where a library, free or paid, starts to pull its weight.&lt;/p&gt;

&lt;p&gt;The fairest test is your own data. Take a representative batch of the images you actually deal with, run them through ZXing.NET and IronBarcode, and compare what decodes and how much glue code each one needed.&lt;/p&gt;

&lt;p&gt;Do you reach for an online decoder or a library when you need to read codes, and where's your cutoff? We'd like to hear where the line falls for you in the comments.&lt;/p&gt;

&lt;p&gt;If your cutoff lands on the library side, you can test IronBarcode on your own files with the &lt;a href="https://ironsoftware.com/csharp/barcode/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;ZXing is a trademark of its respective owner; this comparison reflects publicly available information at the time of writing.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>barcode</category>
      <category>programming</category>
    </item>
    <item>
      <title>Tesseract OCR in C#: Setup Pain and an Alternative</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Sat, 20 Jun 2026 01:46:53 +0000</pubDate>
      <link>https://dev.to/ironsoftware/tesseract-ocr-in-c-setup-pain-and-an-alternative-2e57</link>
      <guid>https://dev.to/ironsoftware/tesseract-ocr-in-c-setup-pain-and-an-alternative-2e57</guid>
      <description>&lt;h1&gt;
  
  
  Tesseract OCR in C#: Setup Pain and an Alternative
&lt;/h1&gt;

&lt;p&gt;If you've tried wiring Tesseract into a .NET app, you already know the first hour rarely goes to actual OCR. It goes to native binaries, C++ runtimes, and figuring out why the build that worked on your machine breaks in CI. Tesseract is a genuinely good engine, but the path from "free download" to "running in production" is bumpier than most tutorials admit.&lt;/p&gt;

&lt;p&gt;Quick disclosure: we work on IronOCR at Iron Software, so we have a horse in this race. We'll be straight about where vanilla Tesseract is the right call and where it turns into a maintenance tax. If this reads like a sales pitch, tell us in the comments and we'll tighten it up.&lt;/p&gt;

&lt;p&gt;Here's the one-liner version of what we're comparing against, so you can see the shape of the API before we get into the weeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronOcr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IronTesseract&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OcrInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"image.png"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs on Windows, Linux, and macOS from the same NuGet package, with no native install step. The rest of this post walks through three places where that difference actually matters: setup, accuracy on messy scans, and cross-platform deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup and installation
&lt;/h2&gt;

&lt;p&gt;This is where most teams lose the most time, so we'll start here.&lt;/p&gt;

&lt;p&gt;Raw Tesseract in C# means dealing with the C++ side of the engine. You're matching platform-specific binaries, making sure the Visual C++ runtime is present, and juggling 32-bit versus 64-bit compatibility. If you want a current Tesseract 5 build on Windows, you're often looking at cross-compiling with MinGW, which frequently doesn't produce a working binary on the first try. The free C# wrappers on GitHub help, but several of them lag behind &lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer"&gt;the official Tesseract engine&lt;/a&gt;, so you can end up stuck on an older 3.x or 4.x build without meaning to.&lt;/p&gt;

&lt;p&gt;IronOCR takes a different route: one managed package, installed the way you install anything else in .NET.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IronOcr&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No native DLLs to copy, no C++ runtime to chase down, no per-platform configuration. It targets .NET Framework 4.6.2+, .NET Standard 2.0+ (covering .NET 5 through 10), and .NET Core 2.0+, and the dependency resolution is handled by NuGet. The trade-off is honest: it's a commercial library rather than a free one. If your budget line is the only constraint and you have time to fight the toolchain, vanilla Tesseract is a reasonable choice. If your constraint is shipping date, the managed package usually wins back its cost in saved setup hours.&lt;/p&gt;

&lt;p&gt;💡 You can pull IronOcr from NuGet and have the three-line example above running in a few minutes, with no native install step in the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy on real-world scans
&lt;/h2&gt;

&lt;p&gt;Tesseract reads clean, high-resolution, well-aligned text really well. The problems show up the moment your input looks like something a human actually scanned: a slightly rotated page, a phone photo, a low-DPI fax, background speckle from a cheap scanner.&lt;/p&gt;

&lt;p&gt;On those inputs, raw Tesseract output degrades quickly, and the usual fix is to build a preprocessing pipeline in front of it: deskew, denoise, threshold, often with a separate tool like ImageMagick. That's real work, and it tends to be different work for each document type you support.&lt;/p&gt;

&lt;p&gt;IronOCR bundles common preprocessing filters into the input pipeline so you can apply them inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronOcr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IronTesseract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OcrInput&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pageIndices&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadImageFrames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"img\example.tiff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pageIndices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeNoise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// removes digital speckle so it isn't read as characters&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Deskew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// corrects rotation before the engine tries to line-segment&lt;/span&gt;
&lt;span class="n"&gt;OcrResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DeNoise()&lt;/code&gt; strips scanning artifacts and &lt;code&gt;Deskew()&lt;/code&gt; straightens rotated pages, the two corrections that most often rescue a bad scan. Iron Software claims 99.8 to 100% accuracy on typical business documents with this approach; that's their published figure, not a guarantee for your specific inputs, so benchmark it against your own worst-case pages before you commit.&lt;/p&gt;

&lt;p&gt;The result object also carries per-word confidence scores and layout blocks, which is handy when you want to flag low-confidence fields for human review instead of trusting the whole page blindly. In our experience, that confidence data is what makes OCR safe to put into an automated workflow: you route the clean pages straight through and kick the doubtful ones to a person, rather than discovering a misread invoice total three steps downstream.&lt;/p&gt;

&lt;p&gt;⚠️ One thing we'd push back on, gently: preprocessing is not magic, and neither library reads text that genuinely isn't there. If your source is a 72-DPI screenshot of a screenshot, no amount of &lt;code&gt;DeNoise()&lt;/code&gt; will recover characters that were never captured. The honest framing is that good preprocessing widens the band of inputs that work; it doesn't remove the need for reasonable scan quality.&lt;/p&gt;

&lt;p&gt;If you need a point of comparison for accuracy on hard inputs, &lt;a href="https://cloud.google.com/use-cases/ocr" rel="noopener noreferrer"&gt;Google Cloud Vision OCR&lt;/a&gt; is the usual cloud benchmark. Strong results, but it sends your documents off-machine and bills per request, which rules it out for offline or privacy-sensitive work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-platform deployment
&lt;/h2&gt;

&lt;p&gt;This is the other place native dependencies bite, and it usually bites later, in a deploy pipeline rather than on your laptop.&lt;/p&gt;

&lt;p&gt;With raw Tesseract, every target environment wants its own build. Docker needs a base image with the right libraries baked in. Azure deployments fail when the Visual C++ runtime isn't present. Linux behavior shifts between distributions depending on which packages are available. None of these are unsolvable, but each one is a separate thing to test and maintain.&lt;/p&gt;

&lt;p&gt;Because IronOCR is managed code, the same package runs across the environments teams usually target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop: WPF, WinForms, Console&lt;/li&gt;
&lt;li&gt;Web: ASP.NET Core, Blazor&lt;/li&gt;
&lt;li&gt;Cloud and serverless: Azure Functions, AWS Lambda&lt;/li&gt;
&lt;li&gt;Containers: Docker, Kubernetes&lt;/li&gt;
&lt;li&gt;OS coverage: Windows, macOS (Intel and Apple Silicon), and common Linux distros including Alpine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The library handles the platform differences internally, so you're testing your code rather than your runtime's binary layout. That matters most in serverless setups, where you don't fully control the host: an AWS Lambda or Azure Functions cold start that can't find a native dependency is a frustrating thing to debug from a log stream, and avoiding native dependencies sidesteps the whole category of failure.&lt;/p&gt;

&lt;p&gt;If you do stay on raw Tesseract for deployment, our advice is to pin everything: the engine version, the wrapper version, and the base image, and treat any one of them changing as a thing to retest. Most of the "it worked yesterday" reports we've seen with native OCR trace back to one of those three drifting underneath the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick note on languages
&lt;/h2&gt;

&lt;p&gt;One more practical difference. Managing languages in raw Tesseract means downloading and placing &lt;a href="https://github.com/tesseract-ocr/tessdata" rel="noopener noreferrer"&gt;the tessdata language files&lt;/a&gt; by hand (the full set is around 4GB) with the folder structure and environment paths set exactly right at runtime. IronOCR handles languages as NuGet packages instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronOcr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// PM&amp;gt; Install-Package IronOcr.Languages.ChineseSimplified&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IronTesseract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OcrLanguage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChineseSimplified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSecondaryLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OcrLanguage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;English&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// mixed-language pages read in one pass&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OcrInput&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"multi-language.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsTextFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"results.txt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You add a language pack the same way you add any dependency, and version compatibility comes along with it. If you want the full setup, the &lt;a href="https://ironsoftware.com/csharp/ocr/docs/" rel="noopener noreferrer"&gt;IronOCR documentation&lt;/a&gt; walks through the language packs and filter options in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tesseract vs IronOCR at a glance
&lt;/h2&gt;

&lt;p&gt;Same engine family underneath, different packaging and tooling around it. Read the rows against your own constraints rather than looking for an overall winner.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Vanilla Tesseract (C# wrapper)&lt;/th&gt;
&lt;th&gt;IronOCR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install&lt;/td&gt;
&lt;td&gt;Native binaries, C++ runtime, per-platform setup&lt;/td&gt;
&lt;td&gt;Single NuGet package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engine version&lt;/td&gt;
&lt;td&gt;Depends on wrapper; several lag behind upstream&lt;/td&gt;
&lt;td&gt;Current Tesseract 5 build bundled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preprocessing&lt;/td&gt;
&lt;td&gt;Build your own, often with ImageMagick&lt;/td&gt;
&lt;td&gt;DeNoise, Deskew, and filters built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;Manual tessdata files (~4GB full set)&lt;/td&gt;
&lt;td&gt;NuGet language packs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Separate build per OS / container&lt;/td&gt;
&lt;td&gt;Same package on Windows, macOS, Linux, Alpine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache 2.0, free&lt;/td&gt;
&lt;td&gt;Commercial, paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;You pin and retest engine + wrapper + image&lt;/td&gt;
&lt;td&gt;Handled inside the package&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So which one should you reach for?
&lt;/h2&gt;

&lt;p&gt;Vanilla Tesseract earns its place on research projects, proofs of concept, and pipelines where you control the input quality and have time to tune the toolchain. It's free, the license is permissive, and the engine is solid. If cost is the hard constraint and setup time is cheap for you, it's the right call.&lt;/p&gt;

&lt;p&gt;IronOCR makes more sense when you're shipping to production against real-world document quality, deploying across several platforms, or working to a deadline where setup time is a cost you can't absorb.&lt;/p&gt;

&lt;p&gt;What's your experience been? If you've got a Tesseract setup that runs cleanly in Docker or CI, drop your approach in the comments; the configs people share for native OCR are usually more useful than any official doc. And if you've hit the cross-platform wall, tell us where it broke; we'd like to hear it.&lt;/p&gt;

&lt;p&gt;If you want to test against your own documents first, &lt;a href="https://ironsoftware.com/csharp/ocr/" rel="noopener noreferrer"&gt;IronOCR has a free trial&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ocr</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Android OCR Libraries (and Where .NET Developers Fit In)</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Fri, 19 Jun 2026 00:17:40 +0000</pubDate>
      <link>https://dev.to/ironsoftware/android-ocr-libraries-and-where-net-developers-fit-in-2chp</link>
      <guid>https://dev.to/ironsoftware/android-ocr-libraries-and-where-net-developers-fit-in-2chp</guid>
      <description>&lt;h1&gt;
  
  
  Android OCR Libraries (and Where .NET Developers Fit In)
&lt;/h1&gt;

&lt;p&gt;If you're adding text recognition to an Android app, you have a handful of OCR libraries to pick from, and they don't all solve the same problem. Some run on-device, some call a cloud API, and one of them is technically a .NET library that reaches Android through .NET MAUI. We'll walk through the main options, show some code, and be honest about the trade-offs.&lt;/p&gt;

&lt;p&gt;Upfront: we work on IronOCR at Iron Software. It's a .NET library, not a native Android SDK. We'll be clear about where it fits (cross-platform code through .NET MAUI, which can target Android) versus the Android-native options below, so you can tell which tool matches your stack.&lt;/p&gt;

&lt;p&gt;OCR libraries take an image, find the text inside it, and hand it back as a string you can search, store, or translate. On Android that powers document scanning, live translation, receipt capture, and form data entry. The libraries differ mostly in where the work happens (on the phone or in the cloud), how many languages they cover, and how much setup they ask of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Android OCR options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tesseract OCR
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer"&gt;Tesseract&lt;/a&gt; is the open-source OCR engine most people start with. It supports over 100 languages and runs offline. The engine itself is C++, so on Android you reach it through a wrapper rather than calling it directly. For years that wrapper was &lt;code&gt;tess-two&lt;/code&gt;, though it's no longer maintained. It's a solid choice when you want local processing and no per-request cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Mobile Vision API
&lt;/h3&gt;

&lt;p&gt;The Mobile Vision API used to be the quick on-device option bundled with Google Play services. It's &lt;strong&gt;deprecated now&lt;/strong&gt; and you should not start new projects with it. Google's replacement is ML Kit (covered below), which has the current text-recognition models and ongoing support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microsoft Azure Computer Vision
&lt;/h3&gt;

&lt;p&gt;Azure's vision service does OCR in the cloud alongside image tagging, object detection, and other analysis. It handles many languages and reads tricky images well, but it needs a network connection and you pay per call. It's a good fit when your app is already online and you want accuracy without shipping models to the device.&lt;/p&gt;

&lt;h3&gt;
  
  
  ABBYY Mobile Web Capture
&lt;/h3&gt;

&lt;p&gt;ABBYY's Mobile Web Capture is a JavaScript SDK aimed at document capture inside web-based flows, like onboarding screens. A user points the camera at a document and the SDK captures a clean image for processing. It's commercial and built more for capture-and-submit pipelines than for general in-app OCR.&lt;/p&gt;

&lt;h3&gt;
  
  
  ML Kit
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/ml-kit" rel="noopener noreferrer"&gt;ML Kit&lt;/a&gt; is Google's current on-device option and the migration path away from Mobile Vision. Text recognition runs locally, so it's fast and keeps image data on the phone. You get a clean API without needing to train or host models yourself. For most new Android-native apps that want offline OCR, this is the default we'd reach for first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tesseract4Android
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/adaptech-cz/Tesseract4Android" rel="noopener noreferrer"&gt;Tesseract4Android&lt;/a&gt; is a fork of &lt;code&gt;tess-two&lt;/code&gt;, rewritten to work with CMake and recent Android Studio versions. It wraps the Tesseract OCR engine with Java and JNI, so you get Tesseract's accuracy and language coverage with an interface that fits a modern Android project. Since &lt;code&gt;tess-two&lt;/code&gt; is abandoned, this is the maintained way to run Tesseract on Android today.&lt;/p&gt;

&lt;p&gt;It bundles Tesseract 5.3.4, Leptonica 1.83.1 for image processing, and libjpeg/libpng for image handling. Setup is two Gradle edits and then a few lines of code.&lt;/p&gt;

&lt;p&gt;First, add the JitPack repository to your root &lt;code&gt;build.gradle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;allprojects&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
        &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s1"&gt;'https://jitpack.io'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the dependency in your app module's &lt;code&gt;build.gradle&lt;/code&gt;. Pick the standard or OpenMP variant depending on whether you want the extra threading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Standard variant&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'cz.adaptech.tesseract4android:tesseract4android:4.7.0'&lt;/span&gt;
    &lt;span class="c1"&gt;// OpenMP variant&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'cz.adaptech.tesseract4android:tesseract4android-openmp:4.7.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, you talk to &lt;code&gt;TessBaseAPI&lt;/code&gt;: point it at your language data and an image, then read the text back. Here's a small manager class that wraps the lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.googlecode.tesseract.android.TessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.graphics.Bitmap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OCRManager&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;TessBaseAPI&lt;/span&gt; &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OCRManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;dataPath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataPath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;recognizeText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUTF8Text&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onDestroy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tessBaseAPI&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where IronOCR fits for .NET teams
&lt;/h2&gt;

&lt;p&gt;Everything above is for native Android development in Java or Kotlin. If your team writes C# instead, you're in a different lane. &lt;a href="https://ironsoftware.com/csharp/ocr/" rel="noopener noreferrer"&gt;IronOCR&lt;/a&gt; is a .NET OCR library, not an Android SDK, so it won't drop into a Kotlin project. Where it can reach Android is through .NET MAUI: you write one C# codebase and MAUI builds it for Android (along with iOS, Windows, and macOS).&lt;/p&gt;

&lt;p&gt;So the honest framing is this. If you're building a native Android app, one of the libraries above is the right call, and ML Kit or Tesseract4Android are the two we'd shortlist. If you're already a .NET shop and want OCR in cross-platform C# that happens to run on Android, IronOCR is worth a look. It reads text from images and scanned documents, supports many languages, and works across .NET targets including .NET Core, .NET Framework, and MAUI.&lt;/p&gt;

&lt;p&gt;To add it to a .NET project, install the NuGet package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Install-Package IronOcr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the API is a couple of lines. Point &lt;code&gt;IronTesseract&lt;/code&gt; at an image and read the &lt;code&gt;.Text&lt;/code&gt; off the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronOcr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;imageText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IronTesseract&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"images\image.png"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Recognized Text:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same call works whether you run it on a desktop service or inside a MAUI app targeting Android, since you're using the same C# across platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking one
&lt;/h2&gt;

&lt;p&gt;Here's the same set of options side by side, so you can match a tool to your constraints at a glance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Processing&lt;/th&gt;
&lt;th&gt;Native Android?&lt;/th&gt;
&lt;th&gt;Languages&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ML Kit&lt;/td&gt;
&lt;td&gt;On-device&lt;/td&gt;
&lt;td&gt;Yes (Java/Kotlin)&lt;/td&gt;
&lt;td&gt;~100+ (Latin, plus Chinese, Japanese, Korean, Devanagari models)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Offline OCR in new native apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tesseract4Android&lt;/td&gt;
&lt;td&gt;On-device&lt;/td&gt;
&lt;td&gt;Yes (Java/JNI)&lt;/td&gt;
&lt;td&gt;100+ via Tesseract traineddata&lt;/td&gt;
&lt;td&gt;Free (Apache 2.0)&lt;/td&gt;
&lt;td&gt;Tesseract's language coverage on native Android&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Computer Vision&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Yes (via REST)&lt;/td&gt;
&lt;td&gt;160+&lt;/td&gt;
&lt;td&gt;Pay per call&lt;/td&gt;
&lt;td&gt;Online apps wanting accuracy without shipping models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ABBYY Mobile Web Capture&lt;/td&gt;
&lt;td&gt;Cloud/SDK&lt;/td&gt;
&lt;td&gt;No (JavaScript SDK)&lt;/td&gt;
&lt;td&gt;Many (commercial)&lt;/td&gt;
&lt;td&gt;Commercial license&lt;/td&gt;
&lt;td&gt;Document capture inside web-based flows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronOCR&lt;/td&gt;
&lt;td&gt;On-device (.NET)&lt;/td&gt;
&lt;td&gt;Via .NET MAUI only&lt;/td&gt;
&lt;td&gt;125+&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;OCR in cross-platform C# that also targets Android&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's no single best answer here, only the one that matches your codebase and where the work needs to happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native Android, offline:&lt;/strong&gt; ML Kit for the current Google path, or Tesseract4Android if you want Tesseract's language coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Android, cloud-backed accuracy:&lt;/strong&gt; Azure Computer Vision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture inside web flows:&lt;/strong&gt; ABBYY Mobile Web Capture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C# / cross-platform via MAUI:&lt;/strong&gt; IronOCR. There's a free trial if you want to test it against your own images before committing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And skip Mobile Vision for anything new, since it's deprecated.&lt;/p&gt;

&lt;p&gt;What are you building OCR into, and which way did you lean? If you've shipped one of these on Android, we'd like to hear how accuracy and setup held up in production. Drop a comment with your experience.&lt;/p&gt;

</description>
      <category>android</category>
      <category>ocr</category>
      <category>dotnet</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Android OCR Libraries: A Field Guide (and Where .NET Fits)</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Wed, 17 Jun 2026 17:23:23 +0000</pubDate>
      <link>https://dev.to/ironsoftware/android-ocr-libraries-a-field-guide-and-where-net-fits-1b3h</link>
      <guid>https://dev.to/ironsoftware/android-ocr-libraries-a-field-guide-and-where-net-fits-1b3h</guid>
      <description>&lt;h1&gt;
  
  
  Android OCR Libraries: A Field Guide (and Where .NET Fits)
&lt;/h1&gt;

&lt;p&gt;If you need text out of an image on Android, the short version is this: for most apps, ML Kit or Tesseract4Android will get you there. I've spent a lot of the past few years helping teams pick OCR tooling, and the mistake I see most often is choosing a library before knowing whether the work runs on-device, in the cloud, or in a cross-platform .NET project that happens to ship to Android. This guide walks the real options, shows code early, and is upfront about which ones are native Android and which are not.&lt;/p&gt;

&lt;p&gt;Full transparency: I'm a Developer Advocate at Iron Software. IronOCR is a .NET library, not a native Android SDK — I'll be clear about where it fits (cross-platform via .NET MAUI) versus the Android-native options below. So when I get to IronOCR, read it as the .NET path, not a drop-in replacement for a Java/Kotlin Android SDK.&lt;/p&gt;

&lt;p&gt;Before we get into the options, here's the shape of native Android OCR so you know what we're comparing. With Tesseract4Android — the most common native path — it's essentially three calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TessBaseAPI&lt;/span&gt; &lt;span class="n"&gt;tess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;tess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataPath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"eng"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// point at your tessdata + language&lt;/span&gt;
&lt;span class="n"&gt;tess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// hand it a Bitmap&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUTF8Text&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// read the recognized text back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load a model, hand it an image, read the text. Almost every option below is a variation on those three lines — the libraries differ mostly in how much work they do around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Android OCR library actually does
&lt;/h2&gt;

&lt;p&gt;OCR (Optical Character Recognition) reads pixels and gives you back characters. On Android, that usually means taking a photo or a scanned page, running it through a text-recognition engine, and getting editable, searchable strings out the other side. The practical uses are familiar: scanning documents, reading receipts, translating signs from a live camera feed, and pulling fields off ID cards.&lt;/p&gt;

&lt;p&gt;The traits worth comparing across libraries are accuracy and language coverage, whether processing happens on-device or in the cloud, how hard the integration is, and how much you can tune preprocessing for messy real-world images. Those four points decide most choices, so I'll touch on them as I go.&lt;/p&gt;

&lt;p&gt;A note on accuracy, because it trips people up: OCR quality depends as much on the input image as on the engine. A clean, high-contrast scan reads well in almost any library; a crumpled receipt shot at an angle in poor light will frustrate the best of them. Whatever you pick, budget time for preprocessing — deskewing, thresholding, and cropping to the text region usually move the needle more than swapping engines. The libraries below differ mostly in how much of that work they do for you versus how much they leave in your hands.&lt;/p&gt;

&lt;p&gt;The other early decision is on-device versus cloud. On-device OCR keeps images on the phone, works offline, and has no per-request cost, but it's bounded by the device's CPU. Cloud OCR can lean on bigger models and often reads harder images, at the price of latency, a network dependency, and sending user images to a third party. For anything privacy-sensitive — IDs, medical forms, financial documents — that distinction often makes the choice for you before accuracy even enters the conversation.&lt;/p&gt;

&lt;p&gt;To set the tone, here's the kind of code you'll actually write — this is Tesseract4Android, the most common native path for getting Tesseract running on Android:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.googlecode.tesseract.android.TessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.graphics.Bitmap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OCRManager&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;TessBaseAPI&lt;/span&gt; &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OCRManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;dataPath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataPath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;recognizeText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUTF8Text&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onDestroy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tessBaseAPI&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tessBaseAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole shape of it: initialize with a data path and language, set a bitmap, read the text, clean up. Now let's look at the field.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Android OCR options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tesseract OCR
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer"&gt;Tesseract&lt;/a&gt; is the long-running open-source OCR engine, with support for over 100 languages. It's the core that most other tools wrap. On Android you don't talk to it directly — you go through a wrapper such as the older &lt;code&gt;tess-two&lt;/code&gt; or the maintained Tesseract4Android (more on that below). It runs offline, which is its biggest practical advantage for mobile, and it's free, which is the other. The cost shows up as setup: you manage language data files yourself and you'll do your own image cleanup, since Tesseract does little of that automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Google Mobile Vision API
&lt;/h3&gt;

&lt;p&gt;Part of Google Play services, Mobile Vision offered on-device text detection with a simple API. It's deprecated now — Google asks developers to migrate to ML Kit for current features, performance, and ongoing support. If you find a tutorial pointing you here, treat it as historical and use ML Kit instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Microsoft Azure Computer Vision
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/products/ai-services/ai-vision" rel="noopener noreferrer"&gt;Azure AI Vision&lt;/a&gt; is a cloud OCR service with strong accuracy and broad language support, plus extras like object detection and image tagging. The trade-off is obvious: it needs an internet connection, and you're sending images to a server. That rules it out for offline or privacy-sensitive apps but suits backend-heavy workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. ABBYY Mobile Web Capture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.abbyy.com/mobile-web-capture-sdk/" rel="noopener noreferrer"&gt;ABBYY Mobile Web Capture&lt;/a&gt; is a JavaScript SDK aimed at capturing document images inside web-based flows — think customer onboarding where someone points their phone camera at an ID or form. It handles framing and image quality automatically. It's a commercial product, and it's a web SDK rather than a native Android library, so it fits a specific use case rather than general in-app OCR.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. ML Kit
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/ml-kit" rel="noopener noreferrer"&gt;ML Kit&lt;/a&gt;, from Google, is the modern on-device path and the natural successor to Mobile Vision. Text recognition runs locally, which keeps it fast and keeps images off the network — good for both responsiveness and privacy. The API is approachable without machine-learning background, the base text-recognition model ships with the app, and it handles real-time camera input well, which is why I reach for it on translation and live-scan features. For most new native Android apps it's where I'd start, and only move to Tesseract4Android if I need a language ML Kit doesn't cover or want full control over the engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tesseract4Android, in practice
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/adaptech-cz/Tesseract4Android" rel="noopener noreferrer"&gt;Tesseract4Android&lt;/a&gt; is a rewrite of the old &lt;code&gt;tess-two&lt;/code&gt; library, rebuilt to work with CMake and current Android Studio. It wraps the Tesseract OCR engine through Java/JNI, so you get Tesseract's accuracy and language support with an interface that fits a normal Android project. If you want offline Tesseract on Android and you want a maintained dependency, this is the one.&lt;/p&gt;

&lt;p&gt;It bundles Tesseract OCR 5.x, Leptonica for image processing, and libjpeg/libpng for image handling — you don't wire those up yourself.&lt;/p&gt;

&lt;p&gt;Getting it into a project takes two Gradle edits. First, add the JitPack repository to your root &lt;code&gt;build.gradle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;allprojects&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
        &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s1"&gt;'https://jitpack.io'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then declare the dependency in your app module's &lt;code&gt;build.gradle&lt;/code&gt;, picking the Standard or OpenMP variant depending on whether you want multi-threaded throughput:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Standard variant&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'cz.adaptech.tesseract4android:tesseract4android:4.7.0'&lt;/span&gt;
    &lt;span class="c1"&gt;// OpenMP variant&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'cz.adaptech.tesseract4android:tesseract4android-openmp:4.7.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there you use &lt;code&gt;TessBaseAPI&lt;/code&gt; exactly as shown in the snippet near the top: initialize with your language data, set the image, read the text. That's the full native Tesseract path on Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where .NET fits: IronOCR
&lt;/h2&gt;

&lt;p&gt;Here's where I'm careful, because it's easy to oversell. &lt;a href="https://ironsoftware.com/csharp/ocr/" rel="noopener noreferrer"&gt;IronOCR&lt;/a&gt; is a .NET OCR library. It is not a native Android SDK, and I won't pretend you can drop it into a Kotlin project. What it does is run OCR inside the .NET ecosystem — and because .NET MAUI can target Android, a cross-platform C# app can ship OCR to Android (and iOS, Windows, and macOS) from one codebase.&lt;/p&gt;

&lt;p&gt;So the decision is really about your stack. If you're writing native Java/Kotlin, use ML Kit or Tesseract4Android. If your team builds in C# and you're already shipping to Android through .NET MAUI, IronOCR lets you keep one language across platforms instead of maintaining a separate native OCR layer. That's the honest framing.&lt;/p&gt;

&lt;p&gt;On the .NET side, IronOCR wraps Tesseract with handling for things like skew, low resolution, and multi-language documents, and it works across desktop, web, and cloud .NET projects. The reason I bring it up in an Android article is that "Android" increasingly includes cross-platform apps, and people writing C# deserve to know it's an option.&lt;/p&gt;

&lt;p&gt;Installation is a single package. From the Package Manager Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IronOcr&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then import the namespace and read an image. Here's the minimal version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronOcr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IronTesseract&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OcrInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"image.png"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines: construct the engine, read an image into an &lt;code&gt;OcrInput&lt;/code&gt;, pull the &lt;code&gt;.Text&lt;/code&gt;. In a .NET MAUI project the same call runs on the Android target, which is the whole point — you write the OCR logic once.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'd choose
&lt;/h2&gt;

&lt;p&gt;Here's what I tell developers when they ask which one to pick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native Android, offline, open source:&lt;/strong&gt; Tesseract4Android. It's maintained, it bundles its dependencies, and Tesseract's language coverage is hard to beat for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Android, modern and easy:&lt;/strong&gt; ML Kit. On-device, privacy-friendly, low friction, and the path Google itself points you to from the deprecated Mobile Vision API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud, backend-heavy, accuracy first:&lt;/strong&gt; Azure Computer Vision — as long as you're fine sending images over the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web-based document capture:&lt;/strong&gt; ABBYY Mobile Web Capture, for onboarding-style flows rather than general in-app OCR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform C# / .NET MAUI:&lt;/strong&gt; IronOCR, so your Android, iOS, and desktop builds share one OCR codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no single winner here — the right call depends on whether you're offline or online, native or cross-platform, and how much you care about keeping images on the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Android OCR isn't one decision; it's a couple of small ones. Pick on-device versus cloud first, then native versus cross-platform, and the shortlist narrows fast. For native apps, ML Kit and Tesseract4Android cover most of what teams need. For C# teams shipping through .NET MAUI, OCR can live in the same codebase as the rest of your app.&lt;/p&gt;

&lt;p&gt;If that last case is you, IronOCR offers a free trial so you can read a few real images before committing — the three-line example above is genuinely the whole API surface for basic recognition. Either way, choose the library that matches your stack, not the one with the loudest feature list.&lt;/p&gt;

</description>
      <category>android</category>
      <category>ocr</category>
      <category>mobile</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>EasyOCR vs Tesseract vs IronOCR: A Practical OCR Comparison</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Tue, 16 Jun 2026 21:32:59 +0000</pubDate>
      <link>https://dev.to/ironsoftware/easyocr-vs-tesseract-vs-ironocr-a-practical-ocr-comparison-4k2f</link>
      <guid>https://dev.to/ironsoftware/easyocr-vs-tesseract-vs-ironocr-a-practical-ocr-comparison-4k2f</guid>
      <description>&lt;h1&gt;
  
  
  EasyOCR vs Tesseract vs IronOCR: A Practical OCR Comparison
&lt;/h1&gt;

&lt;p&gt;Optical Character Recognition turns scanned pages, PDFs, and photographed text into data your code can search and process. It powers everything from book digitization and receipt scanning to form processing and accessibility tooling. Picking an engine, though, is less about which one is "best" and more about which one fits your stack and your inputs. We pulled together three of the common choices and tried each one on the same kind of work: EasyOCR and Tesseract on the Python side, and IronOCR on the .NET side.&lt;/p&gt;

&lt;p&gt;Full disclosure: we build IronOCR at Iron Software, one of the three tools compared here. We've tried to keep this fair and call out where EasyOCR or Tesseract is the better fit, especially if you're already working in Python. Worth saying up front: two of these are Python libraries and one is a .NET library, so part of this decision is really an ecosystem choice rather than a pure feature contest.&lt;/p&gt;

&lt;p&gt;Here's what we found.&lt;/p&gt;

&lt;h2&gt;
  
  
  EasyOCR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/JaidedAI/EasyOCR" rel="noopener noreferrer"&gt;EasyOCR&lt;/a&gt; is an open-source Python library built on deep learning, and it supports over 80 languages with no extra setup, including Latin scripts, Chinese, Japanese, Korean, Arabic, and many more. Its big strength is recognition quality on messy inputs. When we ran it on noisy, low-resolution, skewed, or handwriting-adjacent text, its neural models tended to hold up better than older rule-based engines. It's free, and the API is about as simple as OCR gets: one reader object, one call, and you get back the text along with bounding boxes and a confidence score per detection. That confidence value is genuinely useful when you need to flag low-certainty reads for human review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;easyocr&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the reader with the languages you need
&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;easyocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Run OCR and get text + bounding boxes + confidence
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readtext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sample_image.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Detected: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (Confidence: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole core loop. The tradeoff here is setup weight: EasyOCR runs on PyTorch, so your first install pulls in a sizable dependency chain, and it's noticeably faster with a GPU. On CPU-only modern hardware it still works, just slower on large batches. If you're already doing machine-learning work in Python, none of that will surprise you, and EasyOCR slots in cleanly. If you just want a quick text dump and don't otherwise touch PyTorch, the footprint can feel heavy for what you're getting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tesseract
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;Tesseract&lt;/a&gt; is the veteran of open-source OCR, and we'll happily give it that respect. Started at Hewlett-Packard, later picked up by Google, it's been refined for decades and supports more than 100 languages. You can train it on new fonts, and the community around it is enormous, which means plenty of answered questions when you hit a snag. In Python you reach it through the &lt;code&gt;pytesseract&lt;/code&gt; wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytesseract&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;

&lt;span class="c1"&gt;# Point pytesseract at your installed Tesseract binary if needed
# pytesseract.pytesseract.tesseract_cmd = r'&amp;lt;path_to_tesseract&amp;gt;'
&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sample_image.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where Tesseract shines is clean, machine-printed text: digitized books, receipts, multi-column documents, structured forms. It's fast, mature, and free. It handles layout analysis and can output searchable PDFs. We'd also call out the depth of its configuration options: you can pass page-segmentation modes and engine settings to fine-tune behavior for a given document type. The tradeoff is that it's more sensitive to input quality than EasyOCR. On noisy or distorted images you'll often need to bolt on OpenCV or PIL for preprocessing to get good results, and finding the right configuration takes some trial and error. For clean scans, though, it's hard to argue with a free engine that's been hardened over this many years.&lt;/p&gt;

&lt;h2&gt;
  
  
  IronOCR
&lt;/h2&gt;

&lt;p&gt;IronOCR is the &lt;a href="https://ironsoftware.com/csharp/ocr/" rel="noopener noreferrer"&gt;.NET OCR library&lt;/a&gt; we build, aimed at C# and other .NET languages. It wraps a tuned Tesseract engine and adds image preprocessing, multi-format input (JPEG, PNG, GIF, TIFF, and PDF), and direct searchable-PDF output. You install it from NuGet rather than pip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronOcr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IronTesseract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OcrLanguage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;English&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load the image into an OcrInput, then read it&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OcrInput&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"path\to\your\image.png"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;OcrResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a .NET team, the appeal is staying in one runtime. You're not standing up a Python service or shelling out to an external process just to read text from an image. The preprocessing (noise reduction, contrast, deskew, resizing) is built in, so you often skip the OpenCV step that Tesseract would otherwise need. Searchable-PDF conversion is built in, it accepts JPEG, PNG, GIF, TIFF, and PDF input, and it handles batch processing for larger volumes. Being commercial, it also comes with support and regular updates rather than community-only help.&lt;/p&gt;

&lt;p&gt;The honest tradeoff: IronOCR isn't free, and if you're already in Python, adopting it means crossing an ecosystem boundary you probably don't want to cross. Its value is specifically for .NET shops that want native integration and on-prem processing without gluing a Python toolchain into their build.&lt;/p&gt;

&lt;h2&gt;
  
  
  How they compare
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;EasyOCR&lt;/th&gt;
&lt;th&gt;Tesseract&lt;/th&gt;
&lt;th&gt;IronOCR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Python (pytesseract)&lt;/td&gt;
&lt;td&gt;.NET / C#&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;80+&lt;/td&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best at&lt;/td&gt;
&lt;td&gt;Noisy / handwritten-ish text&lt;/td&gt;
&lt;td&gt;Clean printed text&lt;/td&gt;
&lt;td&gt;Native .NET + searchable PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preprocessing&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;External (OpenCV/PIL)&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Searchable PDF&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So which one?
&lt;/h2&gt;

&lt;p&gt;We don't think there's a single winner here, and the choice mostly tracks your context. If you're in Python and your inputs are noisy or varied, EasyOCR's deep-learning recognition is hard to beat for the price (free). If you're in Python and working mostly with clean printed documents, Tesseract is fast, battle-tested, and backed by a huge community. We'd reach for either one happily, and for most Python projects they're the right default.&lt;/p&gt;

&lt;p&gt;IronOCR earns its place when you're building on .NET and want OCR that feels native to the platform, with preprocessing and searchable-PDF export handled for you and commercial support behind it. That convenience is the thing you're paying for, and whether it's worth it depends on your team and your stack. If that's your situation, IronOCR offers a &lt;a href="https://ironsoftware.com/csharp/ocr/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; so you can test it on your own documents before committing.&lt;/p&gt;

&lt;p&gt;What are you running OCR on, and which engine has held up best for your inputs? We'd like to hear what's worked (and what hasn't) in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>dotnet</category>
      <category>ocr</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>QuestPDF C# Tutorial: Generate PDFs in .NET (2026)</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Fri, 12 Jun 2026 19:48:44 +0000</pubDate>
      <link>https://dev.to/ironsoftware/questpdf-c-tutorial-generate-pdfs-in-net-2026-95h</link>
      <guid>https://dev.to/ironsoftware/questpdf-c-tutorial-generate-pdfs-in-net-2026-95h</guid>
      <description>&lt;p&gt;QuestPDF is a code-first PDF library for .NET: you describe a document with a fluent C# API and it renders a PDF, with no HTML pipeline and no headless browser. This guide goes from install to a real, data-driven report, covers the layout model the whole API is built on, and is honest about where QuestPDF stops. Everything targets QuestPDF 2026.5.0 and runs on current .NET (it ships for &lt;code&gt;net6.0&lt;/code&gt; and &lt;code&gt;netstandard2.0&lt;/code&gt;, so it runs on &lt;a href="https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core" rel="noopener noreferrer"&gt;.NET 8 and .NET 10&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I install and license QuestPDF?
&lt;/h2&gt;

&lt;p&gt;One NuGet package, no native dependencies to provision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package QuestPDF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then set the license tier once at startup, before any document is generated. Community is free for individuals and for companies under $1M USD annual gross revenue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Infrastructure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;QuestPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LicenseType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Community&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://www.questpdf.com/quick-start.html" rel="noopener noreferrer"&gt;quick-start guide&lt;/a&gt; is the canonical reference if you want the official walkthrough alongside this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write your first PDF end to end
&lt;/h2&gt;

&lt;p&gt;A complete, runnable program. Note the shape: a document holds a page, and the page has a header, content, and footer, each a chained call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Fluent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Helpers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Infrastructure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;QuestPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LicenseType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Community&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PageSizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Centimetre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultTextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Monthly Report"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;PaddingVertical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Spacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generated from C#, no HTML involved."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Date: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;yyyy&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;MM&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AlignCenter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Page "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CurrentPageNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" of "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GeneratePdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes &lt;code&gt;report.pdf&lt;/code&gt; to disk. The output paginates automatically and the footer page numbers update across pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the container model works
&lt;/h2&gt;

&lt;p&gt;Everything in QuestPDF is a container you configure by chaining methods, and containers nest. &lt;code&gt;page.Content()&lt;/code&gt; returns a container; calling &lt;code&gt;.Padding(10).Background(Colors.Grey.Lighten4)&lt;/code&gt; wraps it, and &lt;code&gt;.Column(...)&lt;/code&gt; fills it with child containers. Once that clicks, the entire API reads the same way: configure a container, then place children inside it. That single idea is what the &lt;a href="https://www.questpdf.com/api-reference/column.html" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; is built around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing layout with Column, Row, and Text
&lt;/h2&gt;

&lt;p&gt;Three primitives cover most documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Column&lt;/code&gt;&lt;/strong&gt; stacks items vertically, with &lt;code&gt;Spacing&lt;/code&gt; between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Row&lt;/code&gt;&lt;/strong&gt; places items horizontally; size them with &lt;code&gt;RelativeItem&lt;/code&gt; (proportional) and &lt;code&gt;ConstantItem(points)&lt;/code&gt; (fixed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Text&lt;/code&gt;&lt;/strong&gt; renders styled runs, including dynamic spans like page numbers.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Spacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RelativeItem&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invoice #1042"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConstantItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;140&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AlignRight&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2026-06-04"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Thank you for your business."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a two-column header row with the label left-aligned and the date right-aligned, followed by a full-width text line. For tables specifically (line items, statements), QuestPDF has a dedicated table API worth its own read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reusing layout with components
&lt;/h2&gt;

&lt;p&gt;For layout you repeat across documents (a letterhead, a signature block), QuestPDF supports components: a class implementing &lt;code&gt;IComponent&lt;/code&gt; that drops into any container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Letterhead&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComponent&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IContainer&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Acme Corp"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123 Example Street, Springfield"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use it anywhere a container is expected:&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Letterhead&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Letterhead&lt;/code&gt; component renders its two-line block wherever you call &lt;code&gt;.Component(new Letterhead())&lt;/code&gt;. Components keep shared layout in one place instead of copying the same fluent calls into every document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling text, fonts, and color
&lt;/h2&gt;

&lt;p&gt;Styling is chained onto any text run, and a page-wide default keeps things consistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultTextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;FontFamily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Lato"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Heading"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;FontColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Darken2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Status: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PAID"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;FontColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Green&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Darken1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux containers, make sure the font you name is actually installed (or bundle it), or QuestPDF falls back to a default face.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting page size, margins, and orientation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PageSizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Landscape&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MarginHorizontal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Centimetre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MarginVertical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.5f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Centimetre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PageSizes&lt;/code&gt; includes the common presets (A4, Letter, and so on), and &lt;code&gt;.Landscape()&lt;/code&gt; flips any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate a report from your data
&lt;/h2&gt;

&lt;p&gt;Real documents come from a model. Here a list of rows becomes a styled report section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;BuildReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;QuestPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LicenseType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Community&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PageSizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Centimetre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Quarterly Summary"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;PaddingTop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Spacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RelativeItem&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConstantItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AlignRight&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AlignCenter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CurrentPageNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" / "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;GeneratePdf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GeneratePdf()&lt;/code&gt; with no argument returns a &lt;code&gt;byte[]&lt;/code&gt;, which is exactly what an API endpoint returns to the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where QuestPDF stops: HTML, reading, and converting
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because it decides whether QuestPDF fits: it is generation-only. It does not render HTML, CSS, or Razor, and it cannot open, read, extract text from, edit, merge, or convert an existing PDF. Those are deliberate scope choices, not bugs, and for code-first document generation they do not matter. They matter when your input is HTML or an existing PDF.&lt;/p&gt;

&lt;h2&gt;
  
  
  When your source of truth is HTML, not C
&lt;/h2&gt;

&lt;p&gt;If the document already lives as an HTML or Razor template (a designer-owned invoice, an existing web view), reproducing it in fluent code is real work, and QuestPDF cannot consume the markup directly.&lt;/p&gt;

&lt;p&gt;IronPDF is a commercial library that renders HTML or Razor markup to PDF through a Chromium engine, so the template you already maintain stays the source of truth. It &lt;a href="https://ironpdf.com/how-to/razor-to-pdf-blazor-server/" rel="noopener noreferrer"&gt;renders a Razor view straight to PDF&lt;/a&gt; and takes a raw HTML string just as readily. A workflow using it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Render an HTML string; a rendered Razor view is passed the same way&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;h1&amp;gt;Invoice #1042&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Total: $1,200.00&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a PDF from the HTML string using Chromium's rendering engine. The trade-offs compared to QuestPDF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing HTML and Razor templates work as inputs, which QuestPDF does not support.&lt;/li&gt;
&lt;li&gt;It requires a paid commercial license; there is no free tier for production use.&lt;/li&gt;
&lt;li&gt;The Chromium engine adds over 100 MB to the deployment footprint.&lt;/li&gt;
&lt;li&gt;On Linux, native dependencies (&lt;code&gt;libgdiplus&lt;/code&gt;, system fonts) must be installed on the host or container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For documents you define in code, QuestPDF carries none of those costs: pure managed .NET, no native binary, no commercial license required under the community revenue threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running QuestPDF on Linux, Docker, and in ASP.NET
&lt;/h2&gt;

&lt;p&gt;QuestPDF is managed code with no native binary to ship, so deployment is the same as the rest of your app. In ASP.NET Core, return the bytes directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;GetRows&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No headless browser to install, no Chromium layer, no extra container configuration. It deploys cleanly to Linux, Docker, and serverless hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;License set too late.&lt;/strong&gt; Set &lt;code&gt;QuestPDF.Settings.License&lt;/code&gt; at startup, before the first &lt;code&gt;Document.Create&lt;/code&gt;, or generation throws.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naming a font that is not installed.&lt;/strong&gt; On slim Linux images, reference a font that exists on the host or bundle it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expecting HTML.&lt;/strong&gt; Passing an HTML string to QuestPDF does nothing useful; it is not an HTML renderer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-nesting cells and items.&lt;/strong&gt; Deep per-item layouts slow large documents; keep item content shallow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does QuestPDF need a purchased license key in code?&lt;/strong&gt;&lt;br&gt;
You set a &lt;code&gt;LicenseType&lt;/code&gt; (Community, Professional, or Enterprise). Community needs no purchased key under the $1M revenue threshold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can QuestPDF render HTML or a Razor view?&lt;/strong&gt;&lt;br&gt;
No. It is code-first by design. For HTML input, rebuild it with the fluent API or use an HTML-rendering library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it run on Linux and in containers?&lt;/strong&gt;&lt;br&gt;
Yes. Managed .NET, no native binary, so it deploys like any other dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which .NET versions are supported?&lt;/strong&gt;&lt;br&gt;
It targets &lt;code&gt;net6.0&lt;/code&gt; and &lt;code&gt;netstandard2.0&lt;/code&gt;, so it runs on .NET 8 and .NET 10.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I preview a document while developing?&lt;/strong&gt;&lt;br&gt;
Use the QuestPDF Companion app for live preview and hot reload, covered in a separate guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kind of documents are you building?
&lt;/h2&gt;

&lt;p&gt;Are you generating reports from structured data, converting existing HTML invoices, or something else? The answer changes which library fits, and I'm curious what real-world use cases people are hitting with .NET PDF generation in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;QuestPDF is the strongest free option for code-first PDF generation in .NET: one package, no native runtime, no commercial license under the community threshold. Learn the container model and the three primitives (Column, Row, Text) and you can build most business documents, with tables and images layered on top. Reach for an HTML renderer when your content genuinely starts life as HTML, or when you need to read or edit existing PDFs, neither of which QuestPDF handles.&lt;/p&gt;

&lt;p&gt;If you're in the HTML-to-PDF camp and want to test against your own templates, &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;IronPDF offers a 30-day trial&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>QuestPDF Docs: Where to Start with the C# PDF Library</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Fri, 12 Jun 2026 19:48:09 +0000</pubDate>
      <link>https://dev.to/ironsoftware/questpdf-docs-where-to-start-with-the-c-pdf-library-2pb9</link>
      <guid>https://dev.to/ironsoftware/questpdf-docs-where-to-start-with-the-c-pdf-library-2pb9</guid>
      <description>&lt;p&gt;QuestPDF's official documentation is thorough, which is great once you know the shape of the library and a little overwhelming on day one. This guide is a concept map: the order to learn things in, the few ideas everything else builds on, and the fastest path from install to a document you understand. It points at the official pages so you always have the authoritative reference alongside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where should I start in the QuestPDF docs?
&lt;/h2&gt;

&lt;p&gt;Start with the mental model, not the API list. QuestPDF documents are a tree of nested containers, and almost everything you read later is a variation on that one idea. The &lt;a href="https://www.questpdf.com/quick-start.html" rel="noopener noreferrer"&gt;quick-start guide&lt;/a&gt; is the right first page; read it for the shape, then come back here for the order to learn the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model everything builds on
&lt;/h2&gt;

&lt;p&gt;Four concepts unlock the rest of the documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document and Page:&lt;/strong&gt; a &lt;code&gt;Document&lt;/code&gt; holds one or more pages; each page has a header, content, and footer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers:&lt;/strong&gt; every element is a container you configure by chaining (&lt;code&gt;.Padding()&lt;/code&gt;, &lt;code&gt;.Background()&lt;/code&gt;, &lt;code&gt;.AlignRight()&lt;/code&gt;), and containers nest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout primitives:&lt;/strong&gt; &lt;a href="https://www.questpdf.com/api-reference/column.html" rel="noopener noreferrer"&gt;&lt;code&gt;Column&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;Row&lt;/code&gt;, and &lt;code&gt;Table&lt;/code&gt; arrange child containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text:&lt;/strong&gt; styled runs, including dynamic spans like page numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the container tree clicks, the API reference stops being a wall of methods and becomes a lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read these three sections first
&lt;/h2&gt;

&lt;p&gt;In order, the minimum to be productive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quick start:&lt;/strong&gt; install the package and set the license.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic concepts:&lt;/strong&gt; the container model and the &lt;code&gt;Column&lt;/code&gt; / &lt;code&gt;Row&lt;/code&gt; / &lt;code&gt;Table&lt;/code&gt; primitives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One worked example:&lt;/strong&gt; a document that uses a header, a table, and a footer together.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What to read after the basics
&lt;/h2&gt;

&lt;p&gt;Once the basics land, the high-value pages are the &lt;a href="https://www.questpdf.com/api-reference/table/basics.html" rel="noopener noreferrer"&gt;table API&lt;/a&gt; (most reports need it), text styling and fonts, images, and page settings (size, margins, orientation). Bookmark the API reference rather than reading it cover to cover; you will return to it per feature, not in one sitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: quick-start to a real document
&lt;/h2&gt;

&lt;p&gt;Run this, then read each method in the docs as a question you already have an answer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Fluent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Helpers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Infrastructure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;QuestPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LicenseType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Community&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Centimetre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;FontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;PaddingTop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Start here, then add a table."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AlignCenter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CurrentPageNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" / "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GeneratePdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"start.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates &lt;code&gt;start.pdf&lt;/code&gt; in the working directory. Every method here has a docs page; learning them in the context of a running example is faster than reading them cold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The example repository worth cloning
&lt;/h2&gt;

&lt;p&gt;Beyond the prose docs, an official repository teaches by example: the &lt;a href="https://github.com/QuestPDF/QuestPDF-ExampleInvoice" rel="noopener noreferrer"&gt;invoice sample&lt;/a&gt; is a full, realistic document (header, line-item table, totals, footer) you can run and read end to end. For learning how the pieces fit, stepping through that sample is often faster than the reference, and it pairs well with the quick-start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the API reference
&lt;/h2&gt;

&lt;p&gt;The API reference is organized by element, mirroring the mental model: a page for &lt;code&gt;Column&lt;/code&gt;, pages for the &lt;a href="https://www.questpdf.com/api-reference/table/basics.html" rel="noopener noreferrer"&gt;table&lt;/a&gt; (basics, header and footer, cell styling, overlapping cells), and pages for text, images, and page settings. When you hit a specific need ("repeat a table header," "span two columns"), go straight to that element's page rather than searching the whole site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs for an HTML-first workflow
&lt;/h2&gt;

&lt;p&gt;QuestPDF's docs assume a code-first model: you describe layout in C#. If your team thinks in HTML and CSS instead (designers own the templates, content comes from a web view), the most useful documentation is for a library built around HTML rendering, because QuestPDF's docs never cover an HTML input path.&lt;/p&gt;

&lt;p&gt;IronPDF is a commercial library that documents the HTML and ASP.NET view path directly, so the markup your team already writes becomes the PDF. Its guides cover &lt;a href="https://ironpdf.com/blog/using-ironpdf/convert-html-to-pdf-aspdotnet-csharp/" rel="noopener noreferrer"&gt;converting HTML to PDF in ASP.NET and C#&lt;/a&gt;, a different starting point than fluent layout. A workflow using it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// viewHtml is the rendered output of an ASP.NET view, or any HTML string&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewHtml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"from-view.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a PDF from the HTML string. The trade-offs compared to QuestPDF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML and ASP.NET view rendering are fully documented, which QuestPDF does not cover.&lt;/li&gt;
&lt;li&gt;It requires a paid commercial license; there is no free tier for production use.&lt;/li&gt;
&lt;li&gt;The Chromium rendering engine adds over 100 MB to the deployment footprint.&lt;/li&gt;
&lt;li&gt;On Linux, native dependencies (&lt;code&gt;libgdiplus&lt;/code&gt;, system fonts) must be installed separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For code-first generation, QuestPDF needs none of those things: pure managed .NET, no native binary, no license cost under the community threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common documentation gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diving into the API reference first.&lt;/strong&gt; Read the container model before the method list, or the reference reads as noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the license note.&lt;/strong&gt; The quick-start sets &lt;code&gt;LicenseType&lt;/code&gt;; miss it and your first run throws.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looking for an HTML page.&lt;/strong&gt; There is none; QuestPDF is code-first by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading the whole reference up front.&lt;/strong&gt; Use it per feature; the worked example teaches more than a cover-to-cover read.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is the QuestPDF documentation free to access?&lt;/strong&gt;&lt;br&gt;
Yes, it is public on the project's site, independent of which license tier governs your use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to read all of it before building?&lt;/strong&gt;&lt;br&gt;
No. Quick start, basic concepts, and one worked example are enough to be productive; treat the rest as reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where are the code examples?&lt;/strong&gt;&lt;br&gt;
Throughout the docs and in the worked examples; the fluent API is example-driven, so most pages show runnable snippets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the table documentation?&lt;/strong&gt;&lt;br&gt;
Under the API reference: basics, header and footer, cell style pattern, and overlapping cells each have their own page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my documents are really HTML?&lt;/strong&gt;&lt;br&gt;
HTML-rendering documentation will serve you better than a code-first layout guide; see the section above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does your document source look like?
&lt;/h2&gt;

&lt;p&gt;Does your content start life as C# models and data, or does it live in HTML templates that designers maintain? That distinction drives the library choice more than any feature list. I'm curious how your team's documents actually get generated today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;For code-first generation, QuestPDF is the better fit: no commercial license below the revenue threshold, no Chromium, no native dependencies. Learn the container tree first, read quick-start plus basic concepts plus one worked example, then use the rest as reference organized by element. If your pipeline starts with HTML templates instead of C# data, an HTML-rendering library's docs are the ones to follow.&lt;/p&gt;

&lt;p&gt;If you're building an HTML-to-PDF workflow on top of ASP.NET, &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;try IronPDF free for 30 days&lt;/a&gt; to test it against your own templates.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>QuestPDF Companion: Live PDF Preview and Hot Reload</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Fri, 12 Jun 2026 19:47:36 +0000</pubDate>
      <link>https://dev.to/ironsoftware/questpdf-companion-live-pdf-preview-and-hot-reload-2d71</link>
      <guid>https://dev.to/ironsoftware/questpdf-companion-live-pdf-preview-and-hot-reload-2d71</guid>
      <description>&lt;p&gt;Iterating on a PDF layout by regenerating the file, opening it in a viewer, and squinting at the diff is slow. QuestPDF's &lt;a href="https://www.questpdf.com/companion/usage.html" rel="noopener noreferrer"&gt;Companion app&lt;/a&gt; removes that loop: it renders your document live and refreshes as you change the code. This guide covers what the Companion is, how to wire up hot reload, what it shows that a flat PDF cannot, and where it fits in a real workflow. It requires QuestPDF 2024.10 or newer; everything here targets 2026.5.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the QuestPDF Companion app?
&lt;/h2&gt;

&lt;p&gt;The Companion is a desktop previewer that pairs with your .NET project. Instead of writing a PDF to disk on every change, you point your document at the Companion and it displays the rendered result, refreshing when you re-run. It also surfaces layout information a flat PDF does not, which is what makes it more than a viewer. The app is a separate &lt;a href="https://www.questpdf.com/companion/download.html" rel="noopener noreferrer"&gt;download&lt;/a&gt; that runs alongside your IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I preview a document live?
&lt;/h2&gt;

&lt;p&gt;Build the document as usual, then call the Companion method instead of (or alongside) &lt;code&gt;GeneratePdf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Fluent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Helpers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;QuestPDF.Infrastructure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;QuestPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LicenseType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Community&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PageSizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Centimetre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Edit me and re-run to see the preview update."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Streams the document to the Companion app (default HTTP port 12500)&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShowInCompanion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the Companion app running, each execution updates the preview. You adjust margins, spacing, or table widths and see the effect immediately without writing a file to disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring up hot reload
&lt;/h2&gt;

&lt;p&gt;Live preview becomes a tight loop with hot reload. Start your application in DEBUG mode with "Hot Reload on Save" enabled, and on every file save the document refreshes in the Companion without a full restart. The communication runs over a local HTTP port (12500 by default), which you can override by passing a port to &lt;code&gt;ShowInCompanion(port)&lt;/code&gt; if something else is using it. The &lt;a href="https://www.questpdf.com/companion/features.html" rel="noopener noreferrer"&gt;features page&lt;/a&gt; documents the supported workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Companion shows you
&lt;/h2&gt;

&lt;p&gt;This is where it earns its place over a PDF viewer. The Companion can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highlight the visible layout structure, so you see how containers are sized and nested.&lt;/li&gt;
&lt;li&gt;Show the attributes of a selected element (padding, size, alignment).&lt;/li&gt;
&lt;li&gt;Present the document as a tree, so you can navigate the hierarchy instead of scrolling pages.&lt;/li&gt;
&lt;li&gt;Render in light or dark mode to match your environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For debugging an overflow or a spacing bug, seeing the layout tree is far faster than inferring the problem from a generated page.&lt;/p&gt;

&lt;h2&gt;
  
  
  ShowInCompanion and the older previewer
&lt;/h2&gt;

&lt;p&gt;If you are on an older codebase you may see &lt;code&gt;ShowInPreviewer()&lt;/code&gt; instead. The Companion is the current tool, and &lt;code&gt;ShowInCompanion()&lt;/code&gt; is its method, available from QuestPDF 2024.10 onward. If the method is missing, your package version predates the Companion; upgrade before wiring it up. The repository for the app lives at &lt;a href="https://github.com/QuestPDF/QuestPDF.Companion" rel="noopener noreferrer"&gt;github.com/QuestPDF/QuestPDF.Companion&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it speeds up the inner loop
&lt;/h2&gt;

&lt;p&gt;Three concrete wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No file churn.&lt;/strong&gt; You are not generating and opening &lt;code&gt;output.pdf&lt;/code&gt; dozens of times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster layout debugging.&lt;/strong&gt; Overflow and spacing problems are visible directly, not inferred from a static page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tighter feedback.&lt;/strong&gt; Change code, save, look. The cycle drops from many seconds to near-instant for small edits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a development-time tool. Production code still calls &lt;code&gt;GeneratePdf&lt;/code&gt; (or generates to a stream); the Companion is for the authoring phase only.&lt;/p&gt;

&lt;h2&gt;
  
  
  From preview to production output
&lt;/h2&gt;

&lt;p&gt;The Companion is a development aid; production code generates the file or bytes directly from the same document definition you previewed. The switch is one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Development: live preview in the Companion&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShowInCompanion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Production: generate bytes, or GeneratePdf("file.pdf") for a file&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GeneratePdf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the &lt;code&gt;ShowInCompanion()&lt;/code&gt; call behind a debug check or remove it before shipping; the production path never touches the Companion.&lt;/p&gt;

&lt;h2&gt;
  
  
  An HTML-first preview loop
&lt;/h2&gt;

&lt;p&gt;The Companion is the right tool for code-first layouts. If your document is built from HTML and CSS, the Companion does not apply because QuestPDF does not accept HTML input.&lt;/p&gt;

&lt;p&gt;IronPDF is a commercial library that renders HTML files through a Chromium engine, which means your browser stays the live preview tool. It &lt;a href="https://ironpdf.com/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;converts an HTML file to PDF&lt;/a&gt; using Chromium, so the file you tweak in the browser is the same one that becomes the PDF. A workflow using it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Render the very file you have open in the browser&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlFileAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"preview.html"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"preview.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a PDF from the HTML file on disk. The trade-offs compared to QuestPDF plus the Companion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your browser remains the live preview tool with no separate previewer to install.&lt;/li&gt;
&lt;li&gt;It requires a paid commercial license.&lt;/li&gt;
&lt;li&gt;The Chromium engine adds over 100 MB to the deployment footprint.&lt;/li&gt;
&lt;li&gt;On Linux, native dependencies (&lt;code&gt;libgdiplus&lt;/code&gt;, system fonts) must be present.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For fluent C# documents, QuestPDF and the Companion carry none of those costs: pure managed .NET, free under the community threshold, no Chromium layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Companion pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App not running.&lt;/strong&gt; &lt;code&gt;ShowInCompanion()&lt;/code&gt; streams to the desktop app; start the app first or nothing appears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not in DEBUG with hot reload.&lt;/strong&gt; The live refresh depends on "Hot Reload on Save" in a DEBUG run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package too old.&lt;/strong&gt; &lt;code&gt;ShowInCompanion()&lt;/code&gt; needs QuestPDF 2024.10+; older versions only have &lt;code&gt;ShowInPreviewer()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port in use.&lt;/strong&gt; The default 12500 can clash; pass an explicit port to &lt;code&gt;ShowInCompanion(port)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping it.&lt;/strong&gt; Do not leave &lt;code&gt;ShowInCompanion()&lt;/code&gt; in production code paths; it is a development aid.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is the Companion app required to use QuestPDF?&lt;/strong&gt;&lt;br&gt;
No. It is an optional development aid; QuestPDF generates PDFs without it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the Companion affect production?&lt;/strong&gt;&lt;br&gt;
No. You call &lt;code&gt;ShowInCompanion()&lt;/code&gt; only while developing; production code generates the PDF directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it hot reload on every keystroke?&lt;/strong&gt;&lt;br&gt;
It refreshes when the document is rendered again, which with "Hot Reload on Save" means on each save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which version do I need?&lt;/strong&gt;&lt;br&gt;
QuestPDF 2024.10 or newer for &lt;code&gt;ShowInCompanion()&lt;/code&gt;; the app itself is a separate download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the Companion covered by the Community license?&lt;/strong&gt;&lt;br&gt;
Yes, the Community tier (free under the $1M revenue threshold) includes the development tooling; confirm specifics on the project's site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does your current PDF iteration loop look like?
&lt;/h2&gt;

&lt;p&gt;Are you regenerating a file and opening it in a viewer on every change, or have you found a faster setup? I'd like to know what friction points people hit before finding the Companion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;For code-first .NET PDF work, the QuestPDF Companion is the most practical development tool available: no license cost, no extra runtime dependency, and a layout tree that makes spacing bugs obvious. Call &lt;code&gt;ShowInCompanion()&lt;/code&gt;, run in DEBUG with hot reload, and iterate without regenerating files by hand. The only time it is not your fastest loop is when the document is HTML-first, where a browser plus an HTML renderer is the natural fit instead.&lt;/p&gt;

&lt;p&gt;If your pipeline is HTML-first, &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;try IronPDF free for 30 days&lt;/a&gt; to see if it fits your templates.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>productivity</category>
    </item>
    <item>
      <title>PdfPig: Excellent at Extraction, Honest About the Rest</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Thu, 11 Jun 2026 17:23:46 +0000</pubDate>
      <link>https://dev.to/ironsoftware/pdfpig-excellent-at-extraction-honest-about-the-rest-2ddm</link>
      <guid>https://dev.to/ironsoftware/pdfpig-excellent-at-extraction-honest-about-the-rest-2ddm</guid>
      <description>&lt;p&gt;The conclusion first, because it is the most useful thing here: &lt;a href="https://github.com/UglyToad/PdfPig" rel="noopener noreferrer"&gt;PdfPig&lt;/a&gt; is good. That is meant without qualification, and most of this article shows why.&lt;/p&gt;

&lt;p&gt;Here's the thesis in one sentence: PdfPig is honest about its scope, and that honesty is the most underrated feature in any open-source library. The catch (and there is one) is that the scope is narrower than it looks at first glance, so the real question is whether its scope matches what you actually need to do.&lt;/p&gt;

&lt;p&gt;To make that concrete, here is the entire setup ceremony for reading a PDF with PdfPig:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Pages: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfPages&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lines of using-directives, one to open, one to query. No factories, no DI registration, no async setup, no native binary to ship alongside. That minimalism is a refreshing contrast to a lot of what is in the .NET PDF space.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PdfPig Is, In Plain Terms
&lt;/h2&gt;

&lt;p&gt;PdfPig is a .NET port of &lt;a href="https://pdfbox.apache.org/" rel="noopener noreferrer"&gt;Apache PDFBox&lt;/a&gt;, the long-standing Java PDF library. It's licensed &lt;a href="https://www.apache.org/licenses/LICENSE-2.0" rel="noopener noreferrer"&gt;Apache 2.0&lt;/a&gt;: permissive, no copyleft, safe for commercial products without legal ambiguity (which is the bar a lot of "free" .NET PDF libraries quietly fail to clear). The package on NuGet has &lt;a href="https://www.nuget.org/packages/PdfPig/" rel="noopener noreferrer"&gt;over 21 million downloads&lt;/a&gt; and a healthy community of contributors. Stars on the &lt;a href="https://github.com/UglyToad/PdfPig" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; sit around 2,400, with active issue triage and a maintainer who actually merges PRs.&lt;/p&gt;

&lt;p&gt;That last point matters more than star counts. Plenty of OSS libraries have 10x the stars and zero recent commits. PdfPig's most recent push at the time of this writing was late April 2026, meaning the maintainer was active on the repo within the last two weeks. Releases over the last 18 months: &lt;a href="https://github.com/UglyToad/PdfPig/releases/tag/v0.1.11" rel="noopener noreferrer"&gt;v0.1.11&lt;/a&gt; (July 2025), v0.1.12 (November 2025), v0.1.13 (December 2025), and the current stable &lt;a href="https://github.com/UglyToad/PdfPig/releases" rel="noopener noreferrer"&gt;v0.1.14&lt;/a&gt; (March 2026). That's a roughly quarterly cadence, which is plenty for a library focused on a stable problem domain.&lt;/p&gt;

&lt;p&gt;One real caveat on versioning before we go further: PdfPig is pre-1.0. The &lt;a href="https://github.com/UglyToad/PdfPig/blob/master/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; is upfront about this: minor versions can change the public API without &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;SemVer&lt;/a&gt; guarantees until 1.0 is reached. If you're pinning a dependency in production, pin the patch version, not just the minor. The README's API-stability warning is worth taking literally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core API: Extracting Text Like It's 2026
&lt;/h2&gt;

&lt;p&gt;To the code. The simplest thing PdfPig does well is pulling text out of a PDF in reading order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPages&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContentOrderTextExtractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Page &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; chars"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Open a document, iterate pages, get text. No service registration, no factory pattern, no fluent builder you have to learn. The class names are honest about what they do: &lt;code&gt;ContentOrderTextExtractor&lt;/code&gt; extracts text in content order, &lt;code&gt;NearestNeighbourWordExtractor&lt;/code&gt; extracts words by spatial proximity. There's a small set of layout-analysis tools and they each do one thing.&lt;/p&gt;

&lt;p&gt;A gotcha worth knowing: &lt;code&gt;page.Text&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; the property you want. It returns the raw content stream order, which is rarely the human reading order. The README is explicit about this: "you should not use &lt;code&gt;page.Text&lt;/code&gt; directly, unless you know what you're doing." Developers use it anyway because it is the obvious property name, then spend an afternoon wondering why their extracted invoice totals show up before the line items. Use the layout-analysis extractors instead. They exist for exactly this reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Deeper: Words, Letters, Bounding Boxes
&lt;/h2&gt;

&lt;p&gt;This is where PdfPig earns its place. Most "extract text" libraries give you a string and call it a day. PdfPig gives you a tree: pages, then letters with positions, then words assembled by spatial algorithms, then text blocks via page segmenters, then optional reading-order detection.&lt;/p&gt;

&lt;p&gt;Here's what that looks like for a richer extraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig.DocumentLayoutAnalysis.ReadingOrderDetector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"statement.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Page&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Letter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Letters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wordExtractor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NearestNeighbourWordExtractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wordExtractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;letters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pageSegmenter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DocstrumBoundingBoxes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageSegmenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;readingOrder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UnsupervisedReadingOrderDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;readingOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BoundingBox&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever tried to do invoice parsing or statement parsing, you already know that "give us all the text" is the easy half of the problem. The hard half is "give us the text grouped by visual region, in the order a human would read it." PdfPig hands you the building blocks for that, including bounding box coordinates you can reason about geometrically. The &lt;a href="https://en.wikipedia.org/wiki/Document_layout_analysis" rel="noopener noreferrer"&gt;Docstrum algorithm&lt;/a&gt; and the unsupervised reading-order detector aren't perfect, but they're real implementations of real research, not heuristics duct-taped together. These tools are well-suited to line-item extraction on supplier invoices and tabular data from multi-column PDF reports, exactly the kinds of workflows where regex-over-flat-string approaches collapse.&lt;/p&gt;

&lt;p&gt;One more thing worth flagging in this PdfPig review: encrypted PDFs work fine if you have the password: pass it as the second argument to &lt;code&gt;PdfDocument.Open&lt;/code&gt;. Some teams switch libraries assuming encrypted reading is a paid feature. With PdfPig, it is included.&lt;/p&gt;

&lt;p&gt;Worth knowing on the runtime side: the current stable PdfPig package targets .NET Standard 2.0 and .NET Framework 4.6.2, which means it runs across current &lt;a href="https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core" rel="noopener noreferrer"&gt;.NET LTS releases&lt;/a&gt; (.NET 8 and .NET 10), older Framework apps, and anything in between. The same API surfaces in mixed environments without target-framework friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Creation Footnote, and Why It's a Footnote
&lt;/h2&gt;

&lt;p&gt;PdfPig does support PDF creation through &lt;code&gt;PdfDocumentBuilder&lt;/code&gt;. The README has an example: register a Standard 14 font, add a page, drop some text, write bytes. It works for what it's documented to do.&lt;/p&gt;

&lt;p&gt;But the documentation itself is upfront about what it can't do, and these matter for production workflows. From the README:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Document creation supports very limited changes to existing PDF documents. However it does not support any of the following: Editing forms, Copying or changing annotations, metadata or document structure data, Adding or removing text with existing fonts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Far from a backhanded criticism, that is the maintainer being honest about what the creation API is for. It's there so you can produce simple debug PDFs (the wiki shows an example of overlaying bounding boxes on an extracted page for layout-analysis debugging, which is a great use case for this API). It's not there to be your invoice generator, your report builder, or your form-filling pipeline.&lt;/p&gt;

&lt;p&gt;If your workflow is "read PDFs and extract data," PdfPig is excellent. If your workflow is "read PDFs, modify them, fill forms, sign them, and emit new PDFs," you're going to hit the wall on the creation side fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where PdfPig Stops
&lt;/h2&gt;

&lt;p&gt;Care is warranted here: the goal is to map the boundary rather than to list everything PdfPig doesn't do, which would be an unfair frame for any library. The point is to know whether your workflow lives inside that boundary or outside it.&lt;/p&gt;

&lt;p&gt;What PdfPig genuinely does not do, per its &lt;a href="https://github.com/UglyToad/PdfPig/wiki" rel="noopener noreferrer"&gt;own documentation&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML-to-PDF conversion.&lt;/strong&gt; Not its job. The wiki recommends other libraries for this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF rendering to images.&lt;/strong&gt; Not its job either. The wiki points at &lt;a href="https://github.com/GowenGit/docnet" rel="noopener noreferrer"&gt;docnet&lt;/a&gt; or PDFtoImage. There's a separate &lt;a href="https://www.nuget.org/packages/PdfPig.Rendering.Skia" rel="noopener noreferrer"&gt;&lt;code&gt;PdfPig.Rendering.Skia&lt;/code&gt;&lt;/a&gt; package that adds rendering, but it's a separate concern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form population.&lt;/strong&gt; Forms are readable but read-only: you cannot change values or add new form fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digital signatures.&lt;/strong&gt; No signing, no signature validation in the way a compliance-driven workflow would need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF/A or PDF/UA compliance.&lt;/strong&gt; Out of scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editing existing PDFs.&lt;/strong&gt; You can read them; the creation API can produce simple new pages but isn't intended for round-trip editing of arbitrary input PDFs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility tagging.&lt;/strong&gt; Out of scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are flaws. They're scope boundaries the maintainer drew on purpose, and the documentation tells you exactly where they are. That's the rare, valuable thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  When PdfPig Is the Right Tool
&lt;/h2&gt;

&lt;p&gt;Concretely, here are workflows where PdfPig is the first choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data extraction from PDFs.&lt;/strong&gt; Invoices, statements, regulatory filings, scientific papers, anything where the PDF is the input and structured data is the output. PdfPig's letter-level positioning and word/block extractors are specifically designed for this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search indexing.&lt;/strong&gt; Pulling text out of a PDF corpus to feed into a search engine. The text-extraction APIs are fast and stable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout analysis and document understanding.&lt;/strong&gt; Research workflows where you need bounding boxes, reading order, and the geometric structure of the page. PdfPig is one of the few .NET options that exposes this layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight inspection tooling.&lt;/strong&gt; Debug viewers, validators, content audits. The basic creation API is good enough for overlays and annotations on extracted content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your job description includes the phrase "we need to read a PDF and pull data out of it," PdfPig should be on your shortlist before you look at anything paid.&lt;/p&gt;

&lt;h2&gt;
  
  
  When PdfPig Leaves You Short
&lt;/h2&gt;

&lt;p&gt;The flip side, also concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to render HTML or web content to PDF: PdfPig isn't your tool. You need a browser-engine-backed library or a layout engine.&lt;/li&gt;
&lt;li&gt;You need to fill in PDF forms and save them back: PdfPig can read the form fields, but it can't write them.&lt;/li&gt;
&lt;li&gt;You need to digitally sign PDFs for compliance, contracts, or audit trail purposes: out of scope.&lt;/li&gt;
&lt;li&gt;You need PDF/A archival format compliance for regulated industries: out of scope.&lt;/li&gt;
&lt;li&gt;You need to generate complex multi-page reports with tables, charts, and styled typography from a template: the basic creation API isn't built for this.&lt;/li&gt;
&lt;li&gt;You need to round-trip-edit existing PDFs (read in, modify, write out). The creation API explicitly doesn't support this for non-trivial documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For workflows that span both reading and writing (extract data from a PDF, generate a new PDF based on what you extracted, sign it, archive it), you'll need either a second library to handle the write half or a single library that covers both halves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow Tax: One Library Won't Cover Both Halves
&lt;/h2&gt;

&lt;p&gt;The honest scope is exactly what PdfPig's maintainer has documented, and it is the property that makes PdfPig genuinely good at what it does. The implication for production planning is the one most readers leave the documentation without quite naming: any workflow that spans both reading and writing (extract from input PDFs, produce or modify output PDFs) commits the team to a second library, with its own license, its own dependency footprint, its own deployment surface, and its own maintenance trajectory.&lt;/p&gt;

&lt;p&gt;This is the workflow tax. PdfPig is free in the literal sense: Apache 2.0, no commercial gating, no revenue threshold, no native binary to ship. PdfPig is not free in the architectural sense for any workflow that needs to write back. The bill comes due the moment the team's roadmap includes form filling, digital signatures, PDF/A archival output, template-driven PDF generation, round-trip editing, HTML-to-PDF rendering, or any of the categories the README is explicit about not covering.&lt;/p&gt;

&lt;p&gt;The bill takes one of two forms. Either the team adopts a second PDF library (commercial or open-source) to cover the write half, and now pays a license fee, accepts another dependency's maintenance trajectory, and absorbs the integration cost of coordinating two libraries' object models. Or the team builds the missing functionality in-house, which means writing what is effectively a second PDF library at internal cost, engineer-quarters rather than engineer-weeks for any non-trivial scope.&lt;/p&gt;

&lt;p&gt;For teams whose workflow is genuinely one-directional (search indexing, document understanding, invoice parsing, regulatory-filing analysis), PdfPig stands alone and the workflow tax is zero. For teams whose workflow is intake-then-emit (read an invoice, generate an audit document; parse a regulatory submission, produce a redacted version; extract data, sign and archive), the architectural picture is two libraries, and the question becomes not "is PdfPig free?" but "is the combined cost of PdfPig plus the second library lower than the cost of a single-library solution?"&lt;/p&gt;

&lt;p&gt;The answer is workload-dependent. The cleaner the read/write split (different teams, different services, different infrastructure), the better the two-library composition. The tighter the integration (same service, shared models, frequent round-trips), the more attractive a single library that covers both halves becomes. Treating this as a workflow architecture decision rather than a free-vs-paid library decision is the framing the rest of this article exists to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Complementary Approach
&lt;/h2&gt;

&lt;p&gt;This is where the honest framing matters most. PdfPig and a generation-capable library aren't strictly competitors; they're often complementary. Teams commonly pair them: PdfPig for the extraction half of a workflow, a generation library for the rendering, signing, and form-filling half.&lt;/p&gt;

&lt;p&gt;A toy version of that pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Stage 1: PdfPig extracts data from the source document&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sourceText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"incoming-invoice.pdf"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;sourceText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContentOrderTextExtractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;InvoiceData&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ParseInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Stage 2: A generation-capable library produces the audit PDF&lt;/span&gt;
&lt;span class="c1"&gt;// (a generation library would handle this stage)&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;auditHtml&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&amp;lt;h1&amp;gt;Audit for &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/h1&amp;gt;..."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... HTML-to-PDF conversion, signing, etc., happens here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A range of generation libraries can cover the second stage. The point is that "PdfPig vs anything else" is often the wrong frame. PdfPig is the right tool for its half of the workflow. The architecture question is what tool covers the other half, and whether you want one library that does both halves or two libraries each doing what they do best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Honest Take
&lt;/h2&gt;

&lt;p&gt;If you're building data-extraction tooling and PdfPig fits your workflow, use PdfPig. The library is well-maintained, fairly licensed, technically solid, and refreshingly honest about its scope.&lt;/p&gt;

&lt;p&gt;If your workflow needs both reading and writing capabilities and you want to evaluate unified options, read the licensing carefully on whatever you pick. The honest answer to "which library should we use?" almost always depends on your specific workflow, your team's constraints, and your budget.&lt;/p&gt;

&lt;p&gt;The .NET PDF space has too many libraries claiming to do everything and quietly failing at half of it, and PdfPig is the opposite: it claims to do a focused set of things, and it does them. That's worth saying out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you also need to create or edit PDFs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PdfPig is read-only by design.&lt;/strong&gt; It extracts text, positions, and structure, and does that well, and it cannot create, modify, or sign a document. A pipeline that only ingests PDFs never hits that limit, but a pipeline that reads and also has to produce, fill, or sign one needs a second tool for the write half.&lt;/p&gt;

&lt;p&gt;IronPDF, a commercial library, reads and writes in one package, so the same library that extracts text can also create, edit, and sign the output.&lt;/p&gt;

&lt;p&gt;It can &lt;a href="https://ironpdf.com/tutorials/csharp-edit-pdf-complete-tutorial/" rel="noopener noreferrer"&gt;create and edit PDFs&lt;/a&gt;, covering the write half PdfPig leaves open. A commercial-library workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Install: dotnet add package IronPdf&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// One library for both halves: render new output, then edit or sign it&lt;/span&gt;
&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-TRIAL-OR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;h1&amp;gt;Created, not just read&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro: read and write in one library, where PdfPig is read-only and needs a second tool.&lt;/li&gt;
&lt;li&gt;Con: a commercial license, where PdfPig is Apache 2.0 and free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your workflow has to both read and produce, the &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;free trial, no credit card required&lt;/a&gt; lets you prototype the round trip first. For extraction-only work, PdfPig stays the lighter, better-scoped choice.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What are you extracting with PdfPig, and how do you cover the write half when you need it: a second library, or build it in-house?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>productivity</category>
    </item>
    <item>
      <title>PdfSharp 2026: Great at the Basics, Narrow Everywhere Else</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Wed, 10 Jun 2026 16:14:06 +0000</pubDate>
      <link>https://dev.to/ironsoftware/pdfsharp-2026-great-at-the-basics-narrow-everywhere-else-4oep</link>
      <guid>https://dev.to/ironsoftware/pdfsharp-2026-great-at-the-basics-narrow-everywhere-else-4oep</guid>
      <description>&lt;p&gt;&lt;a href="https://www.pdfsharp.com/" rel="noopener noreferrer"&gt;PDFSharp&lt;/a&gt; is the kind of library that has aged well. It is open source, it is permissively licensed, it is still being released, and its documented feature set has actually grown over the past few years. Reviewing it in 2026 is a different exercise from reviewing it in 2020: several of the gaps that defined PDFSharp's reputation (no PDF/A, no digital signatures, no PDF/UA accessibility) have been closed in the &lt;a href="https://www.nuget.org/packages/PDFSharp" rel="noopener noreferrer"&gt;6.2.x release line&lt;/a&gt;, which puts the library in a meaningfully different position than the older comparison articles suggest.&lt;/p&gt;

&lt;p&gt;The .NET PDF landscape is full of articles that either oversell free libraries or write them off, and PDFSharp deserves neither treatment. The library is genuinely useful within its scope; the architectural question is whether your scope and PDFSharp's scope are the same shape.&lt;/p&gt;

&lt;p&gt;To make the argument concrete before going further, here is what generating a basic styled invoice looks like in PDFSharp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfSharp.Drawing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfSharp.Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gfx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XGraphics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromPdfPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;titleFont&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Arial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XFontStyleEx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bodyFont&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Arial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XFontStyleEx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Regular&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;gfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invoice 2026-0428"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;titleFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XBrushes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;XStringFormats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TopLeft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;gfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Total due: $1,240.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bodyFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XBrushes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;XStringFormats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TopLeft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a complete, runnable program. No external binary, no headless browser, no system dependency: a managed-code .NET library that produces a valid PDF on every supported runtime. If your job is to draw text and lines onto a page in a predictable, scriptable way, PDFSharp does that job and has done it reliably for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PDFSharp Genuinely Does Well
&lt;/h2&gt;

&lt;p&gt;Before the section on where the library's scope ends, the strengths deserve real space. This is not grading on a curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The license is genuinely permissive.&lt;/strong&gt; PDFSharp is published under the &lt;a href="https://docs.pdfsharp.net/General/License/License.html" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt; by empira Software GmbH, with copyright spanning 2005 through 2026. There is no dual-licensing trap, no commercial-tier threshold, no "free for non-commercial use" asterisk. You can use PDFSharp in proprietary software, in SaaS deployments, in shipped products, and the licensing question is fully answered by reading one short license file. Among free .NET PDF libraries, that level of clarity is the exception, not the rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The project is still maintained.&lt;/strong&gt; The current package is &lt;a href="https://www.nuget.org/packages/PDFSharp" rel="noopener noreferrer"&gt;PDFsharp 6.2.4 on NuGet&lt;/a&gt;, with target frameworks of &lt;code&gt;net8.0&lt;/code&gt;, &lt;code&gt;net9.0&lt;/code&gt;, &lt;code&gt;net10.0&lt;/code&gt;, and &lt;code&gt;netstandard2.0&lt;/code&gt;. The package was last updated 2026-01-06. The &lt;a href="https://github.com/empira/PDFsharp" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; shows ongoing commits and releases, and the maintainer ships regularly. This is not the maintenance picture you get with several other "free" .NET PDF libraries where the last meaningful release was years ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core PDF object model is solid.&lt;/strong&gt; PDFSharp gives you direct access to the PDF document structure: pages, fonts, graphics state, content streams, the document catalog. If you want to merge documents, split documents, extract pages, set metadata, or manipulate the page tree, the API maps cleanly onto the &lt;a href="https://www.iso.org/standard/75839.html" rel="noopener noreferrer"&gt;PDF specification&lt;/a&gt;. This is harder to appreciate in a feature checklist than in code, but it matters: you can get unusual things done in PDFSharp because the abstraction does not stand between you and the format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption is current and standards-aligned.&lt;/strong&gt; Per the &lt;a href="https://docs.pdfsharp.net/PDFsharp/Topics/PDF-Features/Encryption.html" rel="noopener noreferrer"&gt;encryption documentation&lt;/a&gt;, PDFSharp supports AES-128 (encryption v4, supported by PDF 1.5 and later) and AES-256 (encryption v5, only supported in PDF 2.0). Version 6.2.0 added read support for the proprietary revision-5 encryption scheme as well. For the common case (encrypting a generated document with a user password and an owner password), the API is short and the cipher choices are not stuck in 2010.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Digital signatures landed in 6.2.0.&lt;/strong&gt; This is the biggest change to PDFSharp's surface area in the recent release line. The &lt;a href="https://docs.pdfsharp.net/PDFsharp/Topics/PDF-Features/Signatures.html" rel="noopener noreferrer"&gt;signatures topic&lt;/a&gt; documents &lt;a href="https://datatracker.ietf.org/doc/html/rfc5652" rel="noopener noreferrer"&gt;CMS-based signing&lt;/a&gt; with a built-in &lt;code&gt;PdfSharpDefaultSigner&lt;/code&gt; and an &lt;code&gt;IDigitalSigner&lt;/code&gt; interface for custom signers. Timestamp-server URIs are supported on .NET, with the documented caveat that timestamps are not supported on the .NET Framework build. For straightforward signing workflows where you have a certificate and want to apply a CMS signature, the surface area is reasonable and the implementation is in a separate &lt;code&gt;PdfSharp.Cryptography&lt;/code&gt; assembly so you do not pay the dependency cost unless you need the feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF/A and PDF/UA are listed as supported.&lt;/strong&gt; Per the &lt;a href="https://docs.pdfsharp.net/PDFsharp/Topics/PDF-Features/About.html" rel="noopener noreferrer"&gt;PDF features overview&lt;/a&gt;, PDFSharp supports creating PDF/A-conforming documents for archival and adding PDF/UA accessibility tagging. This is a meaningful expansion from older versions, and it closes one of the most common objections to PDFSharp in regulated-industry contexts. The gaps section returns to this because the breadth of conformance levels matters, but the fact that the categories are addressed at all is the news.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MigraDoc gives you a higher-level composition layer.&lt;/strong&gt; PDFSharp on its own is a drawing library: you position graphics on a page in coordinate space. For document workflows where you actually want paragraphs, tables, headers, and pagination, the &lt;a href="https://www.nuget.org/packages/PDFsharp-MigraDoc" rel="noopener noreferrer"&gt;PDFsharp-MigraDoc package&lt;/a&gt; layers a document model on top. MigraDoc depends on PDFSharp and ships from the same maintainer at matched versions, so the integration is real, not an afterthought. The trade-off, named explicitly in the next section, is that you are now adopting two libraries with two mental models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The deployment story is clean.&lt;/strong&gt; PDFSharp is managed code. There is no native binary to ship, no Chromium engine to package, no system-wide tool to install on the host. A NuGet reference, a &lt;code&gt;dotnet publish&lt;/code&gt;, and you are done. On Linux containers, on Windows servers, on serverless functions, the deployment shape is the same one you already use for the rest of your .NET application. The package targets &lt;code&gt;netstandard2.0&lt;/code&gt; alongside the current runtimes, which means it slots into legacy and modern codebases without the target-framework friction that has bitten teams adopting newer libraries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance is rarely the issue.&lt;/strong&gt; For the workloads PDFSharp is designed for (programmatic generation of structured documents in the dozens-to-thousands per minute range), throughput is not the architectural concern. The library is fast enough that you can usually move on to harder questions.&lt;/p&gt;

&lt;p&gt;That is the case for PDFSharp, and it is a stronger case in 2026 than it was three years ago. None of those points are qualified-with-an-asterisk: the library is well-maintained, well-licensed, structurally clean, and substantially more feature-complete than its older reputation suggests. If your PDF needs sit comfortably inside that envelope, you should use PDFSharp and stop reading comparison articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Scope Ends
&lt;/h2&gt;

&lt;p&gt;The piece of the article you actually came for: PDFSharp's documented feature set still has gaps, and those gaps are predictable enough to lay out in a table. The feature matrix below was compiled from PDFSharp's own documentation and verified at write-time on 2026-05-09.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;PDFSharp 6.2.x&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Programmatic PDF generation&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Native, well-documented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge / split / page manipulation&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Direct PDF object model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption (AES-128, AES-256)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Including PDF 2.0 crypt filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital signatures (CMS)&lt;/td&gt;
&lt;td&gt;Yes (6.2.0+)&lt;/td&gt;
&lt;td&gt;Timestamp support not on .NET Framework build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF/A archival&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Conformance level breadth not enumerated in docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF/UA accessibility tagging&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Tagging primitives present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML to PDF&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Requires &lt;code&gt;HtmlRenderer.PdfSharp&lt;/code&gt; (HTML 4.01 / CSS 2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript rendering&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No browser engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS3 / modern web layout&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Out of scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF to image rasterization&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;Page → bitmap&lt;/code&gt; API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-level text extraction&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Low-level content-stream access only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex table layouts&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Use &lt;a href="https://www.nuget.org/packages/PDFsharp-MigraDoc" rel="noopener noreferrer"&gt;MigraDoc&lt;/a&gt; for document composition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form filling&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Object-model access; no high-level form API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row in that matrix is a real architectural decision waiting to happen. Read this section as the "what does your project actually need" worksheet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML to PDF is the big one.&lt;/strong&gt; If your PDF generation pipeline starts from an HTML template (and many do, because designers can produce HTML and cannot produce PDFSharp drawing code), PDFSharp does not solve the problem. The community answer is &lt;a href="https://www.nuget.org/packages/HtmlRenderer.PdfSharp/" rel="noopener noreferrer"&gt;&lt;code&gt;HtmlRenderer.PdfSharp&lt;/code&gt;&lt;/a&gt; at version 1.5.2 on NuGet. Two things to know about that package: it supports HTML 4.01 and CSS Level 2, which is fine for invoices with tables and predictable layouts but cannot render modern web pages with flexbox, grid, custom properties, web fonts loaded over the network, or anything that depends on JavaScript. Second, the &lt;a href="https://github.com/ArthurHub/HTML-Renderer" rel="noopener noreferrer"&gt;HtmlRenderer repository&lt;/a&gt; has an open community discussion (&lt;a href="https://github.com/ArthurHub/HTML-Renderer/issues/151" rel="noopener noreferrer"&gt;Issue #151&lt;/a&gt;) where a user has offered to take over maintenance, indicating the long-term steward of the project is an open question. The package still works; the question of how long it will keep up with the .NET runtime cadence is one the reader should evaluate against their own timeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript-rendered content is out of scope by construction.&lt;/strong&gt; PDFSharp is not a browser engine. If your source document is a React app, a Vue dashboard, or any HTML page where the visible content is assembled by client-side JavaScript, PDFSharp cannot turn it into a PDF. This is a property of the library's design (managed C# all the way down), not a defect, but it is a hard architectural ceiling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF-to-image rasterization is missing.&lt;/strong&gt; If your workflow needs to produce thumbnails, page previews, or rendered images from a PDF, PDFSharp does not have a &lt;code&gt;Page → PNG&lt;/code&gt; or &lt;code&gt;Page → JPEG&lt;/code&gt; API. You would integrate a separate rendering library. For applications that surface PDFs in a web UI with thumbnail navigation, this is a real gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text extraction is low-level only.&lt;/strong&gt; PDFSharp gives you content-stream access; it does not give you a high-level "extract paragraphs" or "extract words with bounding boxes" API. For document-analysis or invoice-parsing workflows, that means writing your own text reconstruction layer, or using a different library for that step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF/A and PDF/UA support are present, but conformance breadth is not enumerated.&lt;/strong&gt; The docs confirm that PDFSharp supports creating PDF/A-conforming documents and adding PDF/UA accessibility, which is exactly what the categories require. What the docs do not enumerate is which conformance levels (PDF/A-1a vs A-1b vs A-2a vs A-2b vs A-2u vs A-3a/b/u) are produced, nor whether the output passes third-party validators like veraPDF without remediation. If you are in a regulated context where the conformance level is a contractual requirement rather than a category checkbox, validate the output against your specific level before depending on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complex tables push you to MigraDoc.&lt;/strong&gt; PDFSharp draws; MigraDoc composes. The split is reasonable, but it is two libraries, two mental models, and two version surfaces to keep aligned. Teams that adopt PDFSharp for "simple PDF generation" and then need real tables often discover they have effectively adopted MigraDoc as well, which is fine but should be a conscious decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PDFSharp Cannot Be a Modern HTML-Rendering Solution
&lt;/h2&gt;

&lt;p&gt;The feature-matrix gap above is most often summarized as "PDFSharp doesn't do HTML, so you bolt on HtmlRenderer.PdfSharp." The summary is accurate, and it is the half of the picture that justifies a closer look in 2026.&lt;/p&gt;

&lt;p&gt;HtmlRenderer.PdfSharp is the community-maintained bridge that lets PDFSharp consume HTML input. It is real, it is functional, and for the workloads it covers (HTML 4.01 with CSS Level 2: invoices, statements, predictable layout templates) it remains a viable choice. The maintenance picture, however, is not as healthy as PDFSharp's. The &lt;a href="https://github.com/ArthurHub/HTML-Renderer/issues/151" rel="noopener noreferrer"&gt;HtmlRenderer GitHub repository's Issue #151&lt;/a&gt; is an open conversation in which a community member has offered to take over maintenance, an indication that the project's long-term steward is genuinely an open question. The package on NuGet remains at version 1.5.2 with no recent updates, and the rendering engine itself is locked to a 2010-era subset of HTML and CSS that pre-dates flexbox, grid, custom properties, modern web fonts, and anything that depends on JavaScript.&lt;/p&gt;

&lt;p&gt;The architectural implication is consequential. PDFSharp the library is well-maintained, MIT-licensed, and structurally clean. The moment a team's PDF pipeline starts from anything other than coordinate-space drawing (Razor views, design-tool exports, customer-facing dashboard captures, anything a designer can produce in HTML), the team is no longer choosing PDFSharp alone. They are choosing PDFSharp paired with HtmlRenderer.PdfSharp, and inheriting both the CSS2-era rendering ceiling and the open question of who will maintain the bridge over the .NET 10 LTS support window.&lt;/p&gt;

&lt;p&gt;This is the case where "free" and "fit for modern commercial use" diverge for PDFSharp. The library itself is fine. The HTML-rendering pipeline a modern team actually needs is not built into the library and is not maintained by the same hands. For workloads whose PDF inputs originate as HTML, the practical recommendation is to either commit to a layout-DSL approach (which PDFSharp and MigraDoc together can serve cleanly) or to choose a library whose HTML rendering is in-scope and version-aligned with the rest of the package, a different architectural choice from "free + bolt-on HTML."&lt;/p&gt;

&lt;p&gt;There is no inheritance argument against PDFSharp itself. The dependency-decay risk lives one package over, in the HTML bridge most adoptions implicitly take alongside the main library. Teams whose pipeline never needs that bridge can stop reading here and ship PDFSharp; teams whose pipeline does need it should treat HtmlRenderer.PdfSharp's maintenance status as a real input to the decision, not a footnote.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Off: Drawing vs. Rendering
&lt;/h2&gt;

&lt;p&gt;The alternative to a coordinate-space drawing library is a Chromium-based rendering engine wrapped in a managed .NET library, which means the HTML row in the matrix above flips: HTML5, CSS3, JavaScript, web fonts, SVG, and modern layout primitives all render through a bundled Chrome engine without an external binary install. Page rasterization, high-level text extraction, form filling, and PDF/A / PDF/UA conformance can be bundled into a single NuGet package. The trade-off is a larger deployment footprint (the Chromium engine ships with the package) and a commercial license.&lt;/p&gt;

&lt;p&gt;That trade-off is the actual decision. PDFSharp is free, MIT-licensed, and architecturally minimal; it solves the programmatic-generation problem without expanding your dependency surface. A Chromium-based commercial library costs money, ships a heavier engine, and solves the HTML-rendering and modern-feature problem without you assembling a stack of helper libraries. Neither answer is universally right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conditional Verdict
&lt;/h2&gt;

&lt;p&gt;PDFSharp in 2026 is a better library than its older reviews suggest. The 6.2.x release line meaningfully closed the gaps that used to define the comparison: encryption is current, digital signatures exist, PDF/A and PDF/UA are addressed, the project is being maintained, the license is genuinely permissive. For programmatic PDF generation that lives entirely inside .NET, with no HTML pipeline and no JavaScript-rendered source content, PDFSharp is a reasonable default, with no argument against it here.&lt;/p&gt;

&lt;p&gt;The conditional half of the verdict is that the architectural envelope is narrower than it looks on a feature checklist. The moment your pipeline needs HTML-to-PDF beyond HTML 4.01, or JavaScript rendering, or PDF-to-image, or high-level text extraction, or strict conformance-level PDF/A validation, you are either composing PDFSharp with helper libraries (some of which are looking for new maintainers) or you are reaching for a different tool. That is not PDFSharp's fault. It is the scope the maintainer chose, and the scope they have stayed honest about.&lt;/p&gt;

&lt;p&gt;So: read the feature matrix above against your actual pipeline. If every row you need is a green cell, ship PDFSharp and go build the rest of your product. If two or three of the rows you need are not green, that is the decision moment, and it is worth making it deliberately rather than discovering it three sprints in.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you need HTML/CSS rendering
&lt;/h2&gt;

&lt;p&gt;PDFSharp draws in coordinate space: you place text and graphics by position in C#. That fits when you think in points and margins, and fits poorly when the document already exists as HTML and CSS, such as a designer-owned invoice template, a marketing page, or a styled report, because &lt;strong&gt;PDFSharp has no modern HTML rendering path&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;IronPDF, a commercial library, renders HTML and CSS through a Chromium engine, so the markup the web team already owns becomes the PDF without hand-placed coordinates.&lt;/p&gt;

&lt;p&gt;It renders &lt;a href="https://ironpdf.com/how-to/html-file-to-pdf/" rel="noopener noreferrer"&gt;an HTML file to PDF&lt;/a&gt; with HTML5 and CSS3 rendering via a bundled Chromium engine, no coordinate math involved. A commercial-library workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Install: dotnet add package IronPdf&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// HTML5 and CSS3 rendering via a bundled Chromium engine&lt;/span&gt;
&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-TRIAL-OR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;h1 style='color:#2c3e50'&amp;gt;Styled by CSS, not coordinates&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro: renders modern HTML and CSS, which PDFSharp does not do without a CSS2-era bolt-on.&lt;/li&gt;
&lt;li&gt;Con: a commercial license and a heavier engine, where PDFSharp is MIT and minimal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your layout lives in HTML, the &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;30-day trial that needs no card&lt;/a&gt; lets you render a real template before deciding. For coordinate-space, code-first generation with a minimal dependency, PDFSharp stays the right tool.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Where did PdfSharp's scope stop for you? Did you bolt on HtmlRenderer.PdfSharp, move to MigraDoc, or switch libraries entirely?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>programming</category>
    </item>
    <item>
      <title>PuppeteerSharp PDF in Production: The Real Costs</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Wed, 10 Jun 2026 16:13:21 +0000</pubDate>
      <link>https://dev.to/ironsoftware/puppeteersharp-pdf-in-production-the-real-costs-456n</link>
      <guid>https://dev.to/ironsoftware/puppeteersharp-pdf-in-production-the-real-costs-456n</guid>
      <description>&lt;p&gt;The architectural conversation that emerges when a logistics team evaluates &lt;a href="https://www.puppeteersharp.com/" rel="noopener noreferrer"&gt;PuppeteerSharp&lt;/a&gt; for high-volume PDF generation has a familiar shape. Someone on the team gets a working prototype in an afternoon. The HTML rendering quality is excellent. The API is faithful to upstream Puppeteer. The team's first inclination is "this works, let's ship it." The architect's second question, posed as a forward-looking hypothetical the review is designed to surface early rather than as a recounted incident, is: "what does this look like at 2,000 PDFs per minute, in containers, behind an autoscaler, when a year-three security audit asks which version of Chromium our PDF service is currently shipping?"&lt;/p&gt;

&lt;p&gt;The answer to the second question is the subject of this article. It is written from the position of an architect doing the production-readiness review for a team that has already prototyped, not from the position of someone who has shipped PuppeteerSharp at logistics-scale volume. The recommendations here are based on documented operational characteristics of headless Chromium and on PuppeteerSharp's own posture about what it ships.&lt;/p&gt;

&lt;p&gt;PuppeteerSharp is a capable library. The point is that choosing between headless-browser PDF generation and library-based PDF generation is an architectural decision rather than a library-feature one, and the architectural costs compound at production volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PuppeteerSharp does well
&lt;/h2&gt;

&lt;p&gt;The technical merits of PuppeteerSharp deserve airtime before the operational analysis. The library is a serious piece of engineering, and the production-readiness conversation only becomes interesting because PuppeteerSharp is genuinely capable enough to be a viable option in the first place.&lt;/p&gt;

&lt;p&gt;PuppeteerSharp is the .NET port of Google's &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; Node.js library, maintained primarily by Darío Kondratiuk. The current stable release is &lt;a href="https://www.nuget.org/packages/PuppeteerSharp" rel="noopener noreferrer"&gt;PuppeteerSharp 24.42.0&lt;/a&gt;, published in May 2026, and the project has shipped consistently across nearly every Chromium update cycle since 2017. That maintenance posture matters: a wrapper around a moving Chromium target only works if the wrapper moves with Chromium, and PuppeteerSharp's release cadence shows the maintainer takes that responsibility seriously.&lt;/p&gt;

&lt;p&gt;Rendering fidelity is the headline strength. Because PuppeteerSharp drives a real Chromium instance, HTML, CSS, JavaScript, web fonts, SVG, modern CSS layout primitives, and even WebGL all render the way they would in a browser. For PDF outputs that have to faithfully reflect a complex web-rendered document, this is the highest-fidelity option in the .NET ecosystem. If the input is "the customer-facing HTML page exactly as the customer sees it, exported to PDF," PuppeteerSharp's output will be closer to ground truth than any layout-DSL-based PDF library can achieve.&lt;/p&gt;

&lt;p&gt;The API surface is comfortable. The library exposes Puppeteer's familiar primitives (&lt;code&gt;Browser&lt;/code&gt;, &lt;code&gt;Page&lt;/code&gt;, &lt;code&gt;Frame&lt;/code&gt;, &lt;code&gt;BrowserContext&lt;/code&gt;) with idiomatic .NET async patterns. Developers coming from Node Puppeteer find PuppeteerSharp instantly readable. The same is true for browser-automation testing scenarios, where PuppeteerSharp earns its place in test suites that need real Chromium behavior. As a browser-automation tool with a side capability of PDF output, PuppeteerSharp is a defensible default.&lt;/p&gt;

&lt;p&gt;The license is friendly. PuppeteerSharp is MIT-licensed, with no commercial gating, no revenue threshold, no AGPL viral exposure. For teams burned by license-shaped surprises elsewhere in the .NET PDF ecosystem, this is meaningful on its own.&lt;/p&gt;

&lt;p&gt;These strengths are real. They are also the reason teams reach for PuppeteerSharp when they need an HTML-to-PDF pipeline. The production-readiness conversation is not about whether PuppeteerSharp can do the job; it is about what the job costs to keep doing reliably as volume scales.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the documentation surfaces, and what it implies
&lt;/h2&gt;

&lt;p&gt;PuppeteerSharp's documentation is honest about its model: the library downloads and manages a Chromium binary, then drives that binary via the DevTools Protocol. This is the same model as upstream Puppeteer, and the implications are well understood by anyone who has run headless Chromium in production at scale. The documentation surfaces &lt;em&gt;that&lt;/em&gt; there is a Chromium binary. What it leaves to the reader's experience is what living with that Chromium binary means at logistics-scale volume.&lt;/p&gt;

&lt;p&gt;Three operational characteristics drive that cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The runtime memory baseline is set by Chromium, not by application code.&lt;/strong&gt; Two figures matter, and they describe different things. First, the &lt;em&gt;baseline&lt;/em&gt; of a typical .NET API service before adding any browser-based dependency: roughly 200–400 MB resident memory for a service in steady state, which is the size class teams are sizing their nodes for. Second, the &lt;em&gt;per-Chromium-instance overhead&lt;/em&gt; added on top of that baseline once PuppeteerSharp is in the dependency tree.&lt;/p&gt;

&lt;p&gt;The cleanest available inference for the per-instance figure comes from production write-ups that report concurrency limits per host. &lt;a href="https://medium.com/@TheTechDude/puppeteer-memory-leaks-crashes-and-zombie-processes-6-months-of-screenshots-in-production-b2ae7e65df3f" rel="noopener noreferrer"&gt;One frequently-cited write-up reports that a 2 GB VPS comfortably hosts ten to twenty concurrent headless instances under typical conditions&lt;/a&gt;. Working backward from that envelope (2,048 MB ÷ 10–20 instances, after subtracting an OS and runtime baseline) implies roughly 100–200 MB per Chromium instance as a working estimate at moderate workload, with the explicit caveat that this is a derived figure, not a measured one, and that peak workloads are documented well higher. The upstream Puppeteer issue tracker documents &lt;a href="https://github.com/puppeteer/puppeteer/issues/5416" rel="noopener noreferrer"&gt;tabular reports of 50,000+ rows consuming all available memory and crashing the renderer&lt;/a&gt;, which is the upper end of the variance the per-instance estimate cannot capture.&lt;/p&gt;

&lt;p&gt;For a service running a pool of, say, four browser instances behind an autoscaler, the steady-state memory bill is the .NET service baseline plus four times the per-instance overhead, easily a 600–1,200 MB envelope at moderate workload, with peak excursions into the multi-gigabyte range under the kind of large-document workloads logistics generates. A logistics workflow generating large bills-of-lading or multi-page shipment manifests sits in exactly the size class where Chromium's memory profile becomes the dominant operational variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The container image carries the browser.&lt;/strong&gt; PuppeteerSharp ships a Chromium binary at roughly 170MB per platform target, and a typical &lt;a href="https://hub.docker.com/r/yukinying/chrome-headless-browser-stable" rel="noopener noreferrer"&gt;Debian-based Chromium container image lands in the 380MB range before any application code&lt;/a&gt;. For a microservice whose own assembly is a few megabytes, the deployment artifact is functionally a Chromium image with a thin .NET veneer on top. Not a deal-breaker on its own, but it changes the character of the service: registry storage, image-pull latency, autoscaling cold-start time, and CI build time all become Chromium-bound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chromium's release cadence becomes part of the security backlog.&lt;/strong&gt; Google ships Chromium stable updates on a roughly four-week cycle, with critical CVEs typically patched within one to three days. For a service running PuppeteerSharp in production, every Chromium release is a security event the team owns. The audit question "which version of Chromium is the PDF service running, and when was the renderer last patched" is now part of the service's compliance surface. PuppeteerSharp's &lt;code&gt;BrowserFetcher&lt;/code&gt; will pull a newer Chromium when asked; deciding when to ask, regression-testing the new version against PDF outputs, and rolling it through the container pipeline is operational work the documentation does not estimate.&lt;/p&gt;

&lt;p&gt;None of these costs is concealed. Each is implicit in the architectural choice rather than enumerated in the API reference. That is the difference the production-readiness review is built to surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scaling math
&lt;/h2&gt;

&lt;p&gt;Throughput per instance is dominated by Chromium startup time and memory pressure. &lt;a href="https://www.codepasta.com/2024/04/19/optimizing-puppeteer-pdf-generation" rel="noopener noreferrer"&gt;Public discussion of Puppeteer in production&lt;/a&gt; consistently lands on the same pattern: do not launch a fresh browser per request. The dominant production design pattern in the .NET community is &lt;a href="https://dev.to/imzihad21/the-secret-to-scalability-architecting-a-high-performance-net-puppeteer-page-pool-343e"&gt;a single long-lived browser instance with a pool of pages reused across requests&lt;/a&gt;, with concurrency tuned to roughly &lt;code&gt;cores - 1&lt;/code&gt; per &lt;a href="https://www.codepasta.com/2024/04/19/optimizing-puppeteer-pdf-generation" rel="noopener noreferrer"&gt;the codepasta optimization writeup&lt;/a&gt;, and browsers recycled periodically to bound memory growth. With a warm pool, per-PDF generation latency for moderate documents lands in the hundreds of milliseconds; the same codepasta writeup cites p95 around 365ms for typical documents. Two cold-start measurements are worth distinguishing: AWS Lambda cold-start with the browser already on disk runs around 5 seconds for the first request after a code update, while a fresh &lt;code&gt;BrowserFetcher&lt;/code&gt; Chromium download (e.g., on first deployment) is in the 10-second-plus range, and together they explain why every serious deployment runs a warm pool and pre-fetches the binary at image build time, not at runtime.&lt;/p&gt;

&lt;p&gt;For a logistics workload with bursty volume (end-of-day manifest generation clearing tens of thousands of documents in a defined window), the architecture that emerges is a constellation of small services each running a browser pool, fronted by an autoscaler, with a job queue feeding work in. That is a workable architecture. It is also not a library decision. It is a small distributed system whose capacity, reliability, and cost the team now owns.&lt;/p&gt;

&lt;p&gt;The transitive cost surface includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A pool manager with health checks and page recycling, &lt;a href="https://dev.to/imzihad21/the-secret-to-scalability-architecting-a-high-performance-net-puppeteer-page-pool-343e"&gt;because unbounded browser/page creation leaks memory in well-documented ways&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Container orchestration tuned for the Chromium memory profile, including &lt;code&gt;--disable-dev-shm-usage&lt;/code&gt; and shared-memory routing.&lt;/li&gt;
&lt;li&gt;Monitoring for zombie Chromium processes, which the upstream community documents as a recurring failure mode.&lt;/li&gt;
&lt;li&gt;A patch-and-test pipeline for Chromium versions, integrated with the organization's security cadence.&lt;/li&gt;
&lt;li&gt;A regression-test suite for PDF output, because Chromium updates can subtly shift rendering and the diff is the team's problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For some teams this is the right tradeoff: HTML-fidelity is non-negotiable, the operational maturity is there, and the volume justifies the investment. For other teams, this is a microservice pattern they did not realize they were signing up for when they ran &lt;code&gt;dotnet add package PuppeteerSharp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MIT-licensed doesn't mean free at production scale
&lt;/h2&gt;

&lt;p&gt;PuppeteerSharp's MIT license is the cleanest in the .NET PDF space, with no copyleft viral exposure, no revenue threshold, and no per-developer fee. For teams burned by license-shaped surprises elsewhere, the licensing question genuinely is one fewer thing to track. The architectural question is whether "MIT-licensed" is the same property as "free," and at logistics-scale production volume, those two properties diverge by enough to matter.&lt;/p&gt;

&lt;p&gt;The total cost of operating PuppeteerSharp in production resolves to four line items, none of which appear in a procurement review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory pressure as a managed-code concern.&lt;/strong&gt; The dominant production design pattern, a long-lived browser instance with a recycled page pool, is partly a workaround for documented bugs rather than a stylistic preference. &lt;a href="https://github.com/hardkoded/puppeteer-sharp/issues/640" rel="noopener noreferrer"&gt;Issue #640 on the PuppeteerSharp repository&lt;/a&gt; documents a managed memory leak in &lt;code&gt;Connection.cs&lt;/code&gt; where entries in the internal &lt;code&gt;_callbacks&lt;/code&gt; dictionary are never removed across calls, producing OOM under sustained load and most pronounced with large callback payloads. The mitigation in production is to recycle browsers periodically, which means the team is now operating a small fleet of browsers with health checks, page-count caps, and rolling restart logic. That fleet is real engineering work the dependency tree does not bill for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment ceiling on serverless.&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html" rel="noopener noreferrer"&gt;AWS Lambda's documented limits&lt;/a&gt; are 50 MB zipped for direct upload and 250 MB unzipped for the function plus all layers combined. PuppeteerSharp's Chromium binary, in its default form, sits in the 150-300 MB range depending on platform. Lambda-targeted deployments of PuppeteerSharp exist, but they are a focused engineering effort (stripped Chromium builds, container-image deployment mode, layer architecture), not a &lt;code&gt;dotnet publish&lt;/code&gt; that just works. For teams whose deployment target is serverless, "MIT-licensed and works in Lambda" is two separate problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chromium patch ownership.&lt;/strong&gt; Every Chromium stable release on the roughly four-week cycle becomes a security event the team owns, with PDF-output regression-testing implied. The patch cadence does not slow because PuppeteerSharp is MIT-licensed; it slows when the team builds the pipeline to handle it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The four-headed operational tax.&lt;/strong&gt; A team adopting PuppeteerSharp for HTML-to-PDF in production is implicitly committing to a pool manager, a container orchestration tune-up for Chromium memory, monitoring for zombie processes, and a Chromium patch-and-regression pipeline. None of those items have an upfront line item, and a typical estimate of accumulated engineering investment to build them properly runs into the engineer-month range for the first deployment alone.&lt;/p&gt;

&lt;p&gt;The MIT license remains a real benefit. It is also, at production scale, a small fraction of the actual cost surface. For workloads where the operational tax is the right tradeoff (fleets that already exist for browser automation, fidelity-critical document rendering), the license is the cleanest answer in the ecosystem. For workloads where the only reason a Chromium fleet exists is PDF generation, MIT does not mean free; it means the bill is paid in engineering time rather than in a license invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the headless-browser model is the right choice
&lt;/h2&gt;

&lt;p&gt;There are workloads where the headless-browser model is unambiguously the right architecture. If the team is &lt;em&gt;already&lt;/em&gt; operating a headless Chromium fleet for browser-automation testing, web scraping, or web-render snapshotting, adding PDF generation to that fleet is a marginal cost. The pool exists, the patch pipeline exists, the monitoring exists. PuppeteerSharp is the natural choice in that environment, because the team is not paying the operational tax for the first time. They are amortizing it across multiple workloads.&lt;/p&gt;

&lt;p&gt;Similarly, if the workload requires real browser behavior beyond rendering (JavaScript execution, dynamic content waiting, interactive form filling before capture), a headless browser is doing work no library-based renderer can replicate. PuppeteerSharp's strength as a browser-automation tool is the same strength here, and the PDF output is a side effect of work the browser was going to do anyway.&lt;/p&gt;

&lt;p&gt;The review surfaces a problem only when PDF generation is the &lt;em&gt;only&lt;/em&gt; reason the headless-browser fleet exists. In that case, the team has implicitly chosen to run a small Chromium service in exchange for a rendering engine they could have had as a library dependency. That is the framing teams who have not done the review are usually missing when they ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision the review actually asks for
&lt;/h2&gt;

&lt;p&gt;A logistics team evaluating PuppeteerSharp for a high-volume document-generation workload should be able to answer four questions before adopting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is HTML-fidelity rendering of customer-shaped documents a hard requirement, or is a layout-DSL output acceptable?&lt;/li&gt;
&lt;li&gt;Does the team already operate a headless-browser fleet for other reasons, or will PDF generation be the first one?&lt;/li&gt;
&lt;li&gt;Is the team prepared to take ownership of Chromium's patch cadence as part of the service's compliance surface?&lt;/li&gt;
&lt;li&gt;As a reasoned hypothetical: what would the year-three security audit conversation look like when the auditor asks which version of Chromium the PDF service is running, and is the team prepared to answer it deliberately?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Answers to one and two leaning toward "fidelity is required, the fleet already exists" point at PuppeteerSharp. Answers leaning toward "library call, not a service" point at a managed library, commercial or otherwise. The honest reading of the .NET PDF ecosystem is that there are good answers in both directions, and the question is which architecture the team is buying.&lt;/p&gt;

&lt;p&gt;PuppeteerSharp on &lt;a href="https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core" rel="noopener noreferrer"&gt;.NET 10 LTS&lt;/a&gt;, on a managed Chromium pool, with a competent ops team behind it, will generate PDFs in a logistics pipeline reliably. The operational tax is not catastrophic; it is just real, and it does not appear on the API reference page. The work of the production-readiness review is to put it on the page where the architectural decision is being made, before the team is six months into operating a Chromium service they did not realize they had built.&lt;/p&gt;

&lt;p&gt;If the four questions above are the first time the team is encountering them, the review has done its job. The answers, and the architecture they imply, are the team's call.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you want a library call, not a service
&lt;/h2&gt;

&lt;p&gt;PuppeteerSharp's power is a real browser, and &lt;strong&gt;its cost is operating one&lt;/strong&gt;: a 150 to 300 MB Chromium download, memory pressure under load, and a patch-and-monitoring pipeline that turns PDF generation into a service you run. Teams without that operational bandwidth, and serverless shops bumping into Lambda's 250 MB ceiling, often want the rendering engine as an in-process library rather than a managed Chromium fleet.&lt;/p&gt;

&lt;p&gt;IronPDF, a commercial library, bundles its engine in the package with no &lt;code&gt;BrowserFetcher&lt;/code&gt; step, so HTML-to-PDF stays a library call instead of a Chromium fleet you keep alive.&lt;/p&gt;

&lt;p&gt;It documents first-class &lt;a href="https://ironpdf.com/get-started/aws/" rel="noopener noreferrer"&gt;AWS Lambda deployment&lt;/a&gt;, which is exactly where PuppeteerSharp's footprint hurts most. A commercial-library workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Install: dotnet add package IronPdf&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the renderer&lt;/span&gt;
&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-TRIAL-OR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;h1&amp;gt;A library call, not a service&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro: an in-process engine with no pool, watchdog, or BrowserFetcher step to operate.&lt;/li&gt;
&lt;li&gt;Con: a commercial license, where PuppeteerSharp is MIT and free.&lt;/li&gt;
&lt;li&gt;Con: IronPDF also bundles a Chromium engine -- the difference vs PuppeteerSharp is operational management overhead, not binary size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the Chromium operational tax is the problem, the &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;no-card 30-day trial key&lt;/a&gt; lets you test a serverless function before committing. If your team already runs a headless-browser fleet, PuppeteerSharp is the natural fit and the tax is already paid.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you run PuppeteerSharp at volume, how are you handling memory, zombie processes, and Chromium patching? What does your pool look like?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>webdev</category>
    </item>
    <item>
      <title>DinkToPdf in 2026: Archived Dependency in Your .NET App</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Tue, 09 Jun 2026 16:55:31 +0000</pubDate>
      <link>https://dev.to/ironsoftware/dinktopdf-in-2026-archived-dependency-in-your-net-app-2c97</link>
      <guid>https://dev.to/ironsoftware/dinktopdf-in-2026-archived-dependency-in-your-net-app-2c97</guid>
      <description>&lt;p&gt;If you've shipped a .NET service that generates PDFs in the last seven or eight years, there's a reasonable chance you reached for &lt;a href="https://github.com/rdvojmoc/DinkToPdf" rel="noopener noreferrer"&gt;DinkToPdf&lt;/a&gt;. It was the obvious answer in the early .NET Core era: a NuGet package, a tidy &lt;code&gt;IConverter&lt;/code&gt; interface, and an HTML-to-PDF API that worked the first time you called it. The install-day experience really is friction-free. That's the part that's true.&lt;/p&gt;

&lt;p&gt;The part that doesn't show up in the install-day experience is the dependency you took underneath it. DinkToPdf is a P/Invoke wrapper. The thing it wraps is wkhtmltopdf, which is software whose upstream GitHub repository was &lt;a href="https://github.com/wkhtmltopdf/wkhtmltopdf" rel="noopener noreferrer"&gt;archived on January 2, 2023&lt;/a&gt;, with the &lt;a href="https://github.com/wkhtmltopdf" rel="noopener noreferrer"&gt;whole organization archived on July 10, 2024&lt;/a&gt;. What you installed when you ran &lt;code&gt;dotnet add package DinkToPdf&lt;/code&gt; is a thin .NET surface over a native library that has not received an upstream release since &lt;a href="https://github.com/wkhtmltopdf/wkhtmltopdf/releases" rel="noopener noreferrer"&gt;version 0.12.6 in June 2020&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the install-day code that makes the experience feel so good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DinkToPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DinkToPdf.Contracts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;IConverter&lt;/span&gt; &lt;span class="n"&gt;converter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SynchronizedConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfTools&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;converter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HtmlToPdfDocument&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Objects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ObjectSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;HtmlContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;h1&amp;gt;Hello, PDF&amp;lt;/h1&amp;gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two using-statements, one converter, one call. That is genuinely a clean .NET API. The reason DinkToPdf got popular is right there in those eight lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DinkToPdf Actually Does Well
&lt;/h2&gt;

&lt;p&gt;Before the dependency chain, the library deserves its due, because it earned the adoption it got. There are real reasons this was the default choice in 2017–2020 .NET Core projects, and several of those reasons still hold up today on their own terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The .NET API is well-designed.&lt;/strong&gt; The &lt;code&gt;IConverter&lt;/code&gt; interface, the &lt;code&gt;SynchronizedConverter&lt;/code&gt; for thread-safety, the &lt;code&gt;HtmlToPdfDocument&lt;/code&gt; model, the &lt;code&gt;ObjectSettings&lt;/code&gt; and &lt;code&gt;GlobalSettings&lt;/code&gt; separation: these are idiomatic .NET. Whoever wrote this knew the platform. The DI-friendly registration pattern (&lt;code&gt;services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()))&lt;/code&gt;) drops cleanly into ASP.NET Core's container. For teams writing greenfield .NET Core code in 2018, the API shape was a real upgrade over the older Process.Start-the-CLI patterns that wkhtmltopdf wrappers had used in the .NET Framework era.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first PDF lands fast.&lt;/strong&gt; The eight-line example above is not pedagogical simplification. That really is the entire setup ceremony, once you've copied the &lt;a href="https://github.com/rdvojmoc/DinkToPdf#nuget" rel="noopener noreferrer"&gt;native binary into your output directory&lt;/a&gt;. For a developer with a deadline, the speed-to-first-result is meaningful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The HTML-rendering capability covers a lot of ground.&lt;/strong&gt; Because wkhtmltopdf wraps QtWebKit, you get a real (if dated) browser engine doing the layout. That covers most static HTML reports, invoice templates, statement formats, the whole "render this Razor view as a PDF" workflow that probably accounts for the majority of HTML-to-PDF use cases in line-of-business .NET applications. CSS works. Images work. Reasonable typography works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The community knowledge base is large.&lt;/strong&gt; Because DinkToPdf was popular for several years, there are answers on Stack Overflow for most of the gotchas. Threading patterns, container deployment recipes, the &lt;code&gt;useCmaps&lt;/code&gt; flag, font-loading on Linux, the various &lt;code&gt;--page-size&lt;/code&gt; and &lt;code&gt;--margin-*&lt;/code&gt; flag mappings: all of it has been written down by someone. For a team taking the dependency in 2026, that institutional memory is part of what they're inheriting along with the library itself.&lt;/p&gt;

&lt;p&gt;So the product is real. The API is good. The friction is low. None of what follows is intended to argue otherwise. The question worth walking through is what is on the other side of the P/Invoke boundary, because that is the half of the picture the install-day experience never shows you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dependency Chain You Actually Took
&lt;/h2&gt;

&lt;p&gt;Here is the literal dependency graph for a service using DinkToPdf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your ASP.NET Core service
        │
        ▼
DinkToPdf 1.0.8  ← .NET wrapper, last NuGet release April 2017
        │
        │ P/Invoke (DllImport on libwkhtmltox.{dll,so,dylib})
        ▼
wkhtmltox native library v0.12.4
   (shipped in the DinkToPdf repo under /v0.12.4/)
        │
        ▼
wkhtmltopdf 0.12.x
   (upstream repo archived 2023-01-02; last release 0.12.6, June 2020)
        │
        ▼
QtWebKit (Qt 4 fork)
   (QtWebKit deprecated by Qt in 2015; Qt 4 unsupported since 2015)
        │
        ▼
WebKit (the rendering engine)
   (the wkhtmltopdf-bundled WebKit fork has not been updated since ~2012,
    per the wkhtmltopdf project's own status page)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five layers. The .NET package you reference in your &lt;code&gt;.csproj&lt;/code&gt; is the top one. The bottom four came along for free, and the bottom three of them are software that the open-source world stopped maintaining at various points between 2012 and 2024. Two specific things are worth pointing out about this graph.&lt;/p&gt;

&lt;p&gt;First: the &lt;a href="https://github.com/rdvojmoc/DinkToPdf/tree/master/v0.12.4/64%20bit" rel="noopener noreferrer"&gt;native binary the DinkToPdf repo distributes&lt;/a&gt; is wkhtmltox v0.12.4. That is one minor version behind the final wkhtmltopdf release of 0.12.6. The DinkToPdf README still points users to the v0.12.4 folder for the binary, so the most-installed configuration of DinkToPdf in the wild is running a wkhtmltopdf binary that is slightly older than the last upstream release of an already-archived project.&lt;/p&gt;

&lt;p&gt;Second: the &lt;a href="https://wkhtmltopdf.org/status.html" rel="noopener noreferrer"&gt;wkhtmltopdf project's own status page&lt;/a&gt;, written by the maintainer before the repo was archived, was already explicit that "Qt 4 (which wkhtmltopdf uses) hasn't been supported since 2015, the WebKit in it hasn't been updated since 2012." That status page also recommends, in its own words, that users not pass untrusted HTML to wkhtmltopdf without sanitization, and points at WeasyPrint, Prince, or Puppeteer as alternatives for new projects. That recommendation was written by the people who knew the codebase best.&lt;/p&gt;

&lt;p&gt;The dependency chain is not a secret. The DinkToPdf README mentions wkhtmltopdf in its first sentence. The wkhtmltopdf README links to the status page. Nothing here is being concealed. The documentation stops short of the implication: when you take DinkToPdf, the code you are actually relying on for the rendering work is upstream of the .NET wrapper, and that upstream has an archive flag on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "DinkToPdf vs wkhtmltopdf" Is a Distinction Without a Difference
&lt;/h2&gt;

&lt;p&gt;A common framing in 2026 production-readiness discussions is "we're not using wkhtmltopdf, we're using DinkToPdf, which is a .NET library." That framing makes the question feel like a .NET ecosystem question rather than an upstream-archive question. It isn't.&lt;/p&gt;

&lt;p&gt;DinkToPdf is the wrapper. wkhtmltopdf is the engine. The PDF that gets written to disk is rendered by the engine, not the wrapper. Any rendering bug, any CSS support gap, any font-handling quirk, any security-relevant issue in QtWebKit's WebKit-from-2012 origin: all of those live downstream of the wrapper, in code the wrapper has no ability to fix because the upstream is not accepting changes.&lt;/p&gt;

&lt;p&gt;Consider what "still maintained" would have to mean for this stack to behave like a maintained dependency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new CSS feature would need wkhtmltopdf to ship a release. wkhtmltopdf's last release was &lt;a href="https://github.com/wkhtmltopdf/wkhtmltopdf/releases" rel="noopener noreferrer"&gt;June 10, 2020&lt;/a&gt;. The repo archive flag means there will not be another one without a fork takeover.&lt;/li&gt;
&lt;li&gt;A WebKit-derived security advisory would need a patched wkhtmltopdf binary. The WebKit version inside QtWebKit-inside-wkhtmltopdf hasn't been touched in the upstream sense since approximately 2012.&lt;/li&gt;
&lt;li&gt;A .NET wrapper bug fix would require DinkToPdf to ship a release. DinkToPdf's &lt;a href="https://www.nuget.org/packages/DinkToPdf" rel="noopener noreferrer"&gt;last NuGet release was 1.0.8 in April 2017&lt;/a&gt;. The GitHub repository still receives issue traffic in 2026, but no published package update has shipped in nine years.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The forks that exist (Haukcode.DinkToPdf, the various WkHtmlToPdf-DotNet community packages) modernize the .NET wrapper meaningfully, and credit to those maintainers for the work. But none of them can fix the rendering engine. The thing the wrapper wraps is the thing that does the work, and that thing is archived. For a production decision in 2026, "DinkToPdf" and "wkhtmltopdf" are operationally the same dependency wearing two different labels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inherited abandonment: free until it costs you
&lt;/h2&gt;

&lt;p&gt;There is a gap between "free to install" and "free to operate," and DinkToPdf sits squarely inside it. The DinkToPdf NuGet package is MIT-licensed, costs nothing, and clears procurement review without a conversation. What the procurement review does not surface is the cost of operating a dependency whose .NET wrapper has not shipped a release since April 2017 and whose rendering engine has been archived by its maintainer since 2023.&lt;/p&gt;

&lt;p&gt;The cost shows up in three places, and none of them are theoretical.&lt;/p&gt;

&lt;p&gt;First, deployment friction. The DinkToPdf GitHub issue tracker carries a long-running pattern of native-binary-loading failures in container environments. &lt;a href="https://github.com/rdvojmoc/DinkToPdf/issues/116" rel="noopener noreferrer"&gt;Issue #116&lt;/a&gt; documents &lt;code&gt;Unable to load shared library 'libwkhtmltox'&lt;/code&gt; on Alpine Linux Docker images on .NET Core 3.1, the musl-versus-glibc mismatch that Alpine-based deployments routinely hit. &lt;a href="https://github.com/rdvojmoc/DinkToPdf/issues/138" rel="noopener noreferrer"&gt;Issue #138&lt;/a&gt; reports the same &lt;code&gt;DllNotFoundException&lt;/code&gt; from container deployments more generally. Both issues are open or closed-as-stale; neither has been resolved in the published NuGet package because the published NuGet package has not changed since 2017. Teams running modern .NET on slim Linux containers, which is most teams in 2026, pay for this in deployment-day debugging and in CI flakiness that the dependency's install-day experience never previewed.&lt;/p&gt;

&lt;p&gt;Second, compliance exposure. A SOC 2 auditor, a SOC 2 customer questionnaire, or an enterprise security review will note the nine-year gap in DinkToPdf's release history. The wrapper carries the archived rendering engine into the dependency tree. CVE-2022-35583 (CVSS 9.8) and CVE-2020-21365 sit downstream of every DinkToPdf install with no patch path. For commercial workloads subject to vulnerability-management standards, "we use the wrapper, not the engine" is not a defense that survives the auditor's enumeration of the transitively-loaded native libraries.&lt;/p&gt;

&lt;p&gt;Third, opportunity cost. Every quarter a team spends maintaining DinkToPdf in production (porting around the Alpine issue, isolating the network egress, documenting the compensating controls, justifying the dependency to the next reviewer) is a quarter not spent on the product. The wrapper was free in 2017. It is not free in 2026; it has simply moved the bill from a license line item to an engineering-time line item.&lt;/p&gt;

&lt;p&gt;The honest framing for a 2026 commercial adoption is that DinkToPdf is no longer recommended for new projects, and the active community forks (Haukcode.DinkToPdf, the various WkHtmlToPdf-DotNet packages) do not change this conclusion because none of them can fix the engine. For teams carrying an existing DinkToPdf dependency, the migration is genuinely defensible work even when the renders themselves still produce acceptable output.&lt;/p&gt;

&lt;h2&gt;
  
  
  When DinkToPdf Is Still Fine
&lt;/h2&gt;

&lt;p&gt;DinkToPdf is still a reasonable choice in specific cases in 2026. The dependency-chain story does not turn it into a bad library. It turns it into a library with a clearly bounded fitness window.&lt;/p&gt;

&lt;p&gt;DinkToPdf is fine for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal tools that render trusted HTML you control end-to-end&lt;/li&gt;
&lt;li&gt;Batch jobs running on infrastructure you patch yourself&lt;/li&gt;
&lt;li&gt;Prototypes where the proof-of-concept matters more than the production posture&lt;/li&gt;
&lt;li&gt;Existing services where the renders work and the migration cost would be larger than the value of moving.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those use cases is invalidated by anything in this article.&lt;/p&gt;

&lt;p&gt;DinkToPdf becomes a harder sell for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Services that render HTML originating from outside the trust boundary (the &lt;a href="https://wkhtmltopdf.org/status.html" rel="noopener noreferrer"&gt;wkhtmltopdf status page's own recommendation&lt;/a&gt; is to not do this without sanitization)&lt;/li&gt;
&lt;li&gt;Services subject to vulnerability-management standards where the dependency tree gets enumerated at audit time&lt;/li&gt;
&lt;li&gt;Services running on .NET 10 LTS where the multi-year support window outlives the practical viability of an archived rendering engine&lt;/li&gt;
&lt;li&gt;Any greenfield project where the dependency choice is being made today rather than carried forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The line is whether your project sits on the side of that boundary where the upstream-archive question matters, rather than a blanket "use this" or "don't."&lt;/p&gt;

&lt;h2&gt;
  
  
  So What Do You Do With This?
&lt;/h2&gt;

&lt;p&gt;Here is the question worth taking away. Open your &lt;code&gt;.csproj&lt;/code&gt; and look at your DinkToPdf reference. Ask the question the documentation didn't ask for you: do you know what's downstream of that line? Do you know what version of the native binary is in your output directory? Do you know when that binary was last built? Do you know whether your security team, the next time they enumerate your container image's dependencies, is going to ask why an archived project is doing the rendering work in your service?&lt;/p&gt;

&lt;p&gt;If the answer to all of those is yes and the trade-off is acceptable, that is a real decision. The aim here is to replace a vibe with a graph you can check yourself, dates and links included, rather than to talk anyone out of DinkToPdf.&lt;/p&gt;

&lt;p&gt;The library is fine. The wrapper is clean. The dependency chain is the part worth looking at. What does yours look like, and when did you last actually look at it?&lt;/p&gt;

&lt;h2&gt;
  
  
  A maintained, deployment-friendly alternative
&lt;/h2&gt;

&lt;p&gt;DinkToPdf's cost shows up at deployment. &lt;strong&gt;It P/Invokes a native wkhtmltopdf binary that is archived upstream&lt;/strong&gt;, and the "works on Windows, breaks on Alpine" native-loading failures are a recurring tax on teams shipping to Linux containers.&lt;/p&gt;

&lt;p&gt;IronPDF, a commercial library, runs as a managed .NET package. IronPDF handles the native binary extraction automatically, so teams don't need to manually manage per-platform binary placement the way DinkToPdf requires.&lt;/p&gt;

&lt;p&gt;It &lt;a href="https://ironpdf.com/how-to/docker-linux/" rel="noopener noreferrer"&gt;auto-configures its Linux and Docker dependencies&lt;/a&gt;, turning deployment into a NuGet reference rather than a P/Invoke binary you copy per target. A commercial-library workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Install: dotnet add package IronPdf&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Managed package; the engine and its Linux deps come with it&lt;/span&gt;
&lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;License&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LicenseKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR-TRIAL-OR-LICENSE-KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;h1&amp;gt;Managed package; dependencies auto-configured&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro: automatic dependency management and a maintained engine, where DinkToPdf wraps an archived one.&lt;/li&gt;
&lt;li&gt;Con: a commercial license, where DinkToPdf is MIT and free.&lt;/li&gt;
&lt;li&gt;Con: the bundled Chromium engine substantially increases image size compared to wkhtmltopdf/DinkToPdf.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If "works on Windows, breaks on Alpine" is your recurring tax, the &lt;a href="https://ironpdf.com/start-free/trial/" rel="noopener noreferrer"&gt;30-day trial, no credit card&lt;/a&gt; lets you validate a Linux container build first. For an internal tool on trusted HTML where the renders already work, DinkToPdf can stay until the migration is worth it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you hit the Alpine/musl native-loading failure with DinkToPdf, or are your renders still fine? What did you decide to do about the archived engine?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
