IronSoftwareDocotic.Pdf is a genuinely well-engineered library. The API is clean, the NuGet package is lean, and...
Docotic.Pdf is a genuinely well-engineered library. The API is clean, the NuGet package is lean, and it handles PDF parsing and programmatic construction reliably across platforms. The migration decision rarely comes from Docotic.Pdf breaking. It comes from how its HTML-to-PDF story is packaged.
The most common trigger: someone adds a feature that requires rendering a complex HTML template to PDF. Docotic.Pdf supports HTML rendering, but only via the separate BitMiracle.Docotic.Pdf.HtmlToPdf add-on, which exposes an async-only HtmlConverter that downloads its own Chromium on first use. Teams that started on Docotic for parsing or programmatic construction often end up dragging the add-on into the dependency tree just to render one invoice template, and once that's in, the licensing and packaging story starts looking like a candidate for consolidation onto a single renderer-first library.
This article covers the troubleshooting patterns and API mapping for migrating from BitMiracle Docotic.Pdf to IronPDF. The migration is relatively low-drama because both are managed .NET libraries with no COM dependencies. The complexity concentrates in two places: the different mental model (Docotic's document construction plus a separate add-on for HTML vs IronPDF's unified renderer + document model), and any text extraction or annotation code that relies on Docotic's parse-side API.
Neutral triggers teams run into:
BitMiracle.Docotic.Pdf.HtmlToPdf package. That add-on uses an async-only HtmlConverter and downloads its own Chromium on first use, so the dependency tree grows when you need HTML support.ChromePdfRenderer.RenderHtmlAsPdf(html) is a shorter onboarding path than document construction for HTML-heavy workloads.| Aspect | BitMiracle Docotic.Pdf | IronPDF |
|---|---|---|
| Focus | Parse, edit, construct, extract; HTML via add-on | HTML-to-PDF, edit, merge, security |
| Pricing | Free with watermark; commercial license | Per-developer or royalty-free |
| API Style | Document object model, explicit construction | High-level renderer + document model |
| Learning Curve | Gradual for parsing; steep for programmatic layout | Gradual for HTML; moderate for parse ops |
| HTML Rendering | Via BitMiracle.Docotic.Pdf.HtmlToPdf add-on (Chromium) |
Chromium-based, built-in |
| Page Indexing | 0-based | 0-based |
| Thread Safety | Reuse HtmlConverter; audit concurrent document access |
Renderer reusable; see IronPDF parallel examples |
| Namespace |
BitMiracle.Docotic.Pdf (HtmlConverter lives here too) |
IronPdf |
| Feature | Effort | Notes |
|---|---|---|
| HTML string to PDF | Low | Both support it; IronPDF is built-in, Docotic uses the HtmlToPdf add-on |
| HTML file to PDF | Low | Same |
| Merge PDFs | Low | Both support; Docotic uses Append(path), IronPDF uses PdfDocument.Merge
|
| Split/extract pages | Medium | Docotic copies pages page-by-page; IronPDF uses CopyPages
|
| Text watermark | Medium | Docotic drawing model vs IronPDF stamper |
| Image watermark | Medium | Coordinate model differs |
| Password protection | Low | Both support owner/user passwords |
| Text extraction | Medium-High | Docotic's text model is detailed (per-word bounds); audit IronPDF parity |
| Annotation handling | High | Docotic annotations API is rich; audit IronPDF coverage |
| Form field editing | Medium | Both have form APIs; method signatures differ |
| PDF/A output | Medium | Test against your validator |
| Business Scenario | Recommendation |
|---|---|
| Primarily programmatic PDF construction, no HTML | Evaluate whether Docotic's model serves you; migration cost may exceed benefit |
| HTML templates requiring browser-quality CSS | IronPDF's built-in Chromium renderer removes the add-on requirement |
| Heavy text extraction and parse workflows | Audit IronPDF's extraction API against your Docotic usage before committing |
| Mixed HTML + parse requirements | IronPDF can handle both; test extraction output quality |
Prerequisites:
Find all Docotic.Pdf references:
# Find all files with Docotic imports
rg "using BitMiracle" --type cs -l
# Find all PdfDocument usages (Docotic's main class)
rg "PdfDocument" --type cs
# Find text extraction calls
rg "GetText|GetWords|TextData" --type cs
# Find annotation references
rg "Annotations|PdfAnnotation" --type cs
# Find drawing / graphics operations
rg "PdfCanvas|DrawString|DrawLine|HtmlConverter" --type cs
Remove Docotic, add IronPDF:
dotnet remove package BitMiracle.Docotic.Pdf
dotnet remove package BitMiracle.Docotic.Pdf.HtmlToPdf
dotnet add package IronPdf
dotnet restore
PowerShell: Uninstall-Package BitMiracle.Docotic.Pdf (plus the HtmlToPdf add-on if installed) then Install-Package IronPdf.
Before (Docotic.Pdf):
using BitMiracle.Docotic.Pdf;
// Docotic uses a license string set before any API call
PdfInfo.SetLicenseKey("YOUR-LICENSE-KEY");
After (IronPDF):
using IronPdf;
// https://ironpdf.com/how-to/license-keys/
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
Before:
using BitMiracle.Docotic.Pdf;
// HtmlConverter (from the HtmlToPdf add-on) also lives in BitMiracle.Docotic.Pdf
After:
using IronPdf;
Before (Docotic, opening a PDF and extracting text):
using BitMiracle.Docotic.Pdf;
PdfInfo.SetLicenseKey("YOUR-LICENSE-KEY");
using var doc = new PdfDocument("input.pdf");
string text = doc.GetText();
Console.WriteLine(text);
After (IronPDF):
using IronPdf;
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
using var pdf = PdfDocument.FromFile("input.pdf");
// https://ironpdf.com/how-to/extract-text-and-images/
string text = pdf.ExtractAllText();
Console.WriteLine(text);
| Docotic.Pdf | IronPDF | Notes |
|---|---|---|
BitMiracle.Docotic.Pdf |
IronPdf |
Core namespace |
BitMiracle.Docotic.Pdf (HtmlConverter from HtmlToPdf add-on) |
IronPdf (ChromePdfRenderer) |
HTML rendering is built into IronPDF |
BitMiracle.Docotic.Pdf.Layout |
IronPdf |
Layout via HTML/CSS in IronPDF |
| Docotic.Pdf Class | IronPDF Class | Description |
|---|---|---|
PdfDocument |
PdfDocument |
Same name; different API |
PdfPage |
PdfPage |
Page object |
HtmlConverter |
ChromePdfRenderer |
HTML-to-PDF entry point |
PdfCanvas |
No direct equivalent | IronPDF uses HTML/CSS or stamper model for drawing |
PdfInfo.SetLicenseKey() |
IronPdf.License.LicenseKey |
License setup |
| Operation | Docotic.Pdf | IronPDF |
|---|---|---|
| Load from file | new PdfDocument("file.pdf") |
PdfDocument.FromFile("file.pdf") |
| Load from stream | PdfDocument.Load(stream) |
PdfDocument.FromStream(stream) |
| Create from HTML |
await converter.CreatePdfFromStringAsync(html) (add-on) |
renderer.RenderHtmlAsPdf(html) |
| Load from bytes | PdfDocument.Load(byteArray) |
PdfDocument.FromBinaryData(byteArray) |
| Operation | Docotic.Pdf | IronPDF |
|---|---|---|
| Page count | doc.PageCount |
pdf.PageCount |
| Get page |
doc.Pages[n] (0-based) |
pdf.Pages[n] (0-based) |
| Remove page | doc.RemovePage(n) |
pdf.RemovePages(n) |
| Page size |
page.Width, page.Height
|
pdf.Pages[n].Width, pdf.Pages[n].Height
|
| Operation | Docotic.Pdf | IronPDF |
|---|---|---|
| Merge PDFs |
pdf1.Append("file2.pdf") (path / Stream / byte[]) |
PdfDocument.Merge(pdf1, pdf2) |
| Extract pages | Copy pages page-by-page into a new PdfDocument
|
pdf.CopyPages(start, end) or pdf.CopyPages(indices)
|
Before (Docotic with the HtmlToPdf add-on):
using System;
using System.Threading.Tasks;
using BitMiracle.Docotic.Pdf;
class Program
{
static async Task Main()
{
PdfInfo.SetLicenseKey("YOUR-LICENSE-KEY");
// HtmlConverter is async-only and downloads Chromium on first use
using var converter = await HtmlConverter.CreateAsync();
var options = new HtmlConversionOptions();
options.Page.SetSize(PdfPaperSize.A4);
options.Page.MarginTop = 20;
options.Page.MarginBottom = 20;
string html = @"<html>
<body style='font-family:Arial; padding:40px'>
<h1 style='color:#2563EB'>Invoice #2071</h1>
<p>Amount: <strong>$1,200.00</strong></p>
<p>Due: 2025-12-01</p>
</body>
</html>";
using var pdf = await converter.CreatePdfFromStringAsync(html, options);
pdf.Save("invoice.pdf");
Console.WriteLine("Saved invoice.pdf");
}
}
After (IronPDF, built-in renderer, no add-on):
using System;
using IronPdf;
class Program
{
static void Main()
{
// https://ironpdf.com/how-to/html-string-to-pdf/
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
// HTML template — can be maintained separately from code
string html = @"<html>
<body style='font-family:Arial; padding:40px'>
<h1 style='color:#2563EB'>Invoice #2071</h1>
<p>Amount: <strong>$1,200.00</strong></p>
<p>Due: 2025-12-01</p>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
Console.WriteLine("Saved invoice.pdf");
}
}
Before (Docotic.Pdf):
using System;
using BitMiracle.Docotic.Pdf;
class Program
{
static void Main()
{
PdfInfo.SetLicenseKey("YOUR-LICENSE-KEY");
// PdfDocument.Append accepts a file path, Stream, or byte[] --
// not another PdfDocument instance.
using var pdf1 = new PdfDocument("part1.pdf");
pdf1.Append("part2.pdf");
pdf1.Append("part3.pdf");
pdf1.Save("merged.pdf");
Console.WriteLine("Merged to merged.pdf");
}
}
After (IronPDF):
using System;
using IronPdf;
class Program
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
// https://ironpdf.com/how-to/merge-or-split-pdfs/
var pdf1 = PdfDocument.FromFile("part1.pdf");
var pdf2 = PdfDocument.FromFile("part2.pdf");
var pdf3 = PdfDocument.FromFile("part3.pdf");
using var merged = PdfDocument.Merge(pdf1, pdf2, pdf3);
merged.SaveAs("merged.pdf");
Console.WriteLine("Merged to merged.pdf");
}
}
Before (Docotic.Pdf):
using System;
using BitMiracle.Docotic.Pdf;
class Program
{
static void Main()
{
PdfInfo.SetLicenseKey("YOUR-LICENSE-KEY");
using var doc = new PdfDocument("input.pdf");
foreach (var page in doc.Pages)
{
var canvas = page.Canvas;
// Save/restore graphics state around the watermark
canvas.SaveState();
canvas.SetTransparency(0.3); // 30% opacity
canvas.FontSize = 48;
canvas.FillColor = new PdfRgbColor(200, 0, 0);
// Rotate around the page center, then draw the watermark text
canvas.Rotate(45, page.Width / 2, page.Height / 2);
canvas.DrawString(page.Width / 4, page.Height / 2, "CONFIDENTIAL");
canvas.RestoreState();
}
doc.Save("watermarked.pdf");
Console.WriteLine("Watermarked PDF saved");
}
}
After (IronPDF):
using System;
using IronPdf;
using IronPdf.Editing;
class Program
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
using var pdf = PdfDocument.FromFile("input.pdf");
// https://ironpdf.com/how-to/stamp-text-image/
var stamper = new TextStamper
{
Text = "CONFIDENTIAL",
FontSize = 48,
Opacity = 40,
Rotation = 45,
VerticalAlignment = VerticalAlignment.Middle,
HorizontalAlignment = HorizontalAlignment.Center
};
// Applies to all pages by default
pdf.ApplyStamp(stamper);
pdf.SaveAs("watermarked.pdf");
Console.WriteLine("Watermarked PDF saved");
}
}
Before (Docotic.Pdf):
using System;
using BitMiracle.Docotic.Pdf;
class Program
{
static void Main()
{
PdfInfo.SetLicenseKey("YOUR-LICENSE-KEY");
using var doc = new PdfDocument("input.pdf");
// Docotic exposes encryption through a single Encrypt() call
var permissions = PdfPermissions.Printing |
PdfPermissions.ContentExtraction;
doc.Encrypt(
ownerPassword: "owner456",
userPassword: "user123",
permissions: permissions,
encryptionAlgorithm: PdfEncryptionAlgorithm.Aes256
);
doc.Save("protected.pdf");
Console.WriteLine("Password protected PDF saved");
}
}
After (IronPDF):
using System;
using IronPdf;
class Program
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
// https://ironpdf.com/how-to/pdf-permissions-passwords/
using var pdf = PdfDocument.FromFile("input.pdf");
pdf.SecuritySettings.UserPassword = "user123";
pdf.SecuritySettings.OwnerPassword = "owner456";
pdf.SecuritySettings.AllowUserPrinting =
IronPdf.Security.PdfPrintSecurity.FullPrintRights;
pdf.SecuritySettings.AllowUserCopyPasteContent = true;
pdf.SaveAs("protected.pdf");
Console.WriteLine("Password protected PDF saved");
}
}
Docotic.Pdf has a detailed text model. It exposes per-word bounds via page.GetWords(), with chunk.Bounds.Left and chunk.Bounds.Top for positional data. IronPDF's text extraction returns the text content with less detail on positional data. If your code depends on character- or word-level positioning (e.g., table detection, column parsing), test IronPDF's output carefully before committing.
// Docotic -- detailed word data with bounds
using var doc = new PdfDocument("input.pdf");
foreach (var page in doc.Pages)
{
foreach (var word in page.GetWords())
{
Console.WriteLine($"{word.Text} at ({word.Bounds.Left}, {word.Bounds.Top})");
}
}
// IronPDF -- text content
// https://ironpdf.com/how-to/extract-text-and-images/
using var pdf = PdfDocument.FromFile("input.pdf");
string allText = pdf.ExtractAllText();
for (int i = 0; i < pdf.PageCount; i++)
{
string pageText = pdf.ExtractTextFromPage(i);
Console.WriteLine($"--- Page {i + 1} ---\n{pageText}");
}
If you need positional data after migration, review the current IronPDF text extraction API at https://ironpdf.com/how-to/extract-text-and-images/.
Docotic.Pdf's annotation model is explicit. You create PdfAnnotation objects on page.Annotations with coordinates and properties. IronPDF has annotation support, but the API shape differs. Teams with heavy annotation workflows should test annotation round-trips (create, save, reload, verify) before migrating.
See IronPDF annotations docs for current API.
Both Docotic.Pdf and IronPDF use 0-based page indexing. This makes the page index migration simpler than moving from 1-based libraries, but still audit all page index references. Off-by-one errors are silent.
Docotic's PdfCanvas is a drawing context. You position everything with coordinates and explicit graphics state. IronPDF's stamper model is alignment-based with offsets. If you have precise coordinate requirements for stamps or overlays, check the stamper's IsStampedOnAllPages, HorizontalOffset, and VerticalOffset properties. For highly precise positioning, IronPDF's HTML-stamp approach (stamping a rendered HTML element) often gives finer control than the basic stamper API.
Docotic.Pdf core is fully managed and has no native interop requirements. Docotic's HtmlToPdf add-on downloads its own Chromium and brings the same browser-process model that IronPDF uses. IronPDF's Chromium renderer requires the standard Linux system libraries (libnss3, libatk-bridge2.0-0, libdrm2, etc.) on Linux deployments. If your existing Docotic workload is parse-only with no HtmlToPdf add-on, the Linux dependency footprint will grow after the migration.
If you're adding HTML-to-PDF as a new capability during this migration, reuse ChromePdfRenderer:
// Register in DI container as singleton in ASP.NET Core
services.AddSingleton<ChromePdfRenderer>();
// Or reuse at class level
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
Docotic.Pdf documents are IDisposable. IronPDF PdfDocument is also IDisposable. The migration shouldn't change disposal discipline if your existing code uses using consistently. Audit for any PdfDocument instances created without using or explicit .Dispose() calls.
If you generate PDFs in parallel, review IronPDF's concurrent rendering behavior at parallel examples. The Chromium renderer process is separate from the managed layer — concurrent requests share the renderer process differently than Docotic's in-process drawing API.
rg "using BitMiracle" --type cs -l to find all affected filesPdfCanvas drawing operations (will need refactoring to HTML or stamper)HtmlConverter) is in use. That code maps to ChromePdfRenderer
page.GetWords())BitMiracle.Docotic.Pdf (and .HtmlToPdf add-on) NuGet with IronPdf
PdfInfo.SetLicenseKey() with IronPdf.License.LicenseKey = "..."
using BitMiracle.Docotic.Pdf with using IronPdf
new PdfDocument("file.pdf") with PdfDocument.FromFile("file.pdf")
HtmlConverter.CreateAsync() + CreatePdfFromStringAsync with new ChromePdfRenderer().RenderHtmlAsPdf(...)
PdfCanvas drawing with TextStamper, ImageStamper, or HTMLpdf1.Append("file.pdf")) with PdfDocument.Merge(pdf1, pdf2, ...)
doc.Encrypt(...) with pdf.SecuritySettings propertiesdoc.GetText() / page.GetText() with pdf.ExtractAllText() / pdf.ExtractTextFromPage(i)
BitMiracle.Docotic.Pdf and BitMiracle.Docotic.Pdf.HtmlToPdf from all project filesThe Docotic.Pdf to IronPDF migration is generally straightforward for the common operations: loading, merging, and password protection. The complexity concentrates in annotation workflows and text extraction. If either is central to your use case, run a spike against the IronPDF API before committing to the migration path.
The real win for most teams isn't the API swap. It's collapsing the parse library plus the HtmlToPdf add-on plus the licensing for both into a single package that renders HTML like a browser. That removes the programmatic layout code no one wants to maintain, and it removes the add-on coordination that nobody enjoys auditing either.
Question for the comments: For teams that moved from a parse-focused PDF library (Docotic, iText, etc.) to a renderer-first library. How did you handle the text extraction use cases that didn't port cleanly? Did you keep the parse library alongside the renderer, or find a different approach?
The free trial is on NuGet if you want to test before committing.