Recursive AI

Migrating from iText 7 and iTextSharp to OnePDF

Move C# code from iText 7, 8 or 9 (and iTextSharp) to OnePDF: the API map, side-by-side samples run on both libraries, and what has no equivalent.

Moving from iText to OnePDF is mostly a change of entry points: PdfDocument.Open replaces PdfReader and PdfWriter, OnePdf.Layout replaces Document and its elements, and one-call methods such as Merge, Flatten, Sign, Redact and AddTextStamp replace helper classes and canvas code. The usual reason to move is the license. iText Core is AGPL-3.0 or commercial, and OnePDF is MIT OR Apache-2.0. This guide maps the iText 7 to 9 API to OnePDF, runs five common jobs on both libraries side by side, and lists what OnePDF doesn’t do, so you can tell early whether your code can move. The iText code is written from iText’s own tutorial, knowledge base and API reference. Every sample was compiled and run, iText 9.8.0 and OnePDF 1.11.0, on .NET 10, against the same input files.

Check these first

  • .NET version. OnePDF targets .NET 10 only. iText 9.8.0 targets .NET Framework 4.6.1 and .NET Standard 2.0 (NuGet). Code that must stay on .NET Framework can’t move.
  • HTML to PDF. OnePDF has no HTML engine, so there is nothing to replace pdfHTML with. Generate documents with OnePdf.Layout instead, or keep an HTML renderer for that part.
  • OCR, XFA forms, PAdES B-LT and B-LTA. No equivalent of pdfOCR or pdfXFA, and signatures stop at PAdES B-T. Details are below.
  • Page numbers start at 0. iText numbers pages from 1 (GetPage(1) is the first page). OnePDF indexes from 0 (Pages[0]). This is the most common bug in a port.

The API map

iText 7, 8 or 9OnePDF 1.11.0
new PdfDocument(new PdfReader(src))PdfDocument.Open(path | stream | bytes)
new PdfDocument(new PdfReader(src), new PdfWriter(dest)) (stamping mode)PdfDocument.Open(src, new PdfOpenOptions { AllowEditing = true }), then doc.Save(dest)
new PdfDocument(new PdfWriter(dest)) + Document, Paragraph, Table, CellLayoutDocument.Compose(...) with Text, Table, HeaderRow, Row (OnePdf.Layout)
pdf.Close() writes the filedoc.Save(...) writes it, Dispose releases it
pdf.GetNumberOfPages(), pdf.GetPage(i) (from 1)doc.PageCount, doc.Pages[i] (from 0)
PdfCanvas on a pagepage.BeginContent() → PdfPageContentBuilder, then Commit()
Watermark with PdfCanvas, PdfExtGState and ShowTextAligneddoc.AddTextStamp, doc.AddImageStamp
PdfTextExtractor.GetTextFromPagepage.ExtractText() (reading order, words and lines with positions)
PdfMerger, CopyPagesTo + PdfPageFormCopierPdfDocument.Merge, doc.ImportPages
PdfSplitterPdfDocument.Split
PdfFormCreator.GetAcroForm / PdfAcroForm.GetAcroForm, GetField(name).SetValue(v)doc.Form, form.SetValue(name, v)
form.FlattenFields()doc.Flatten()
PdfSigner, PdfPadesSigner + a BouncyCastle adapter packagedoc.Sign(...) with an X509Certificate2 or an external signer
Signature validationdoc.VerifySignatures(...)
PdfADocumentPdfSaveOptions.PdfA on save
PdfUADocumentPdfSaveOptions.PdfUA on save, tagged layout by default
WriterProperties.SetStandardEncryptionPdfSaveOptions.Encryption
pdfSweep add-on (redaction)doc.Redact(...) in the core package
pdfCalligraph add-on (complex scripts)OnePdf.Layout shapes Arabic, Indic and other scripts
pdfHTML, pdfOCR, pdfXFA add-onsNo equivalent

Before you start

TerminalNuGet
dotnet remove package itext
dotnet add package RecursiveAI.OnePdf --version 1.11.0
dotnet add package RecursiveAI.OnePdf.Layout --version 1.11.0

Add RecursiveAI.OnePdf.Layout only if you generate documents. Remove the itext.bouncy-castle-adapter package too if you signed with iText: OnePDF uses .NET’s own cryptography. The inputs below are a six-page report, the OPM SF 2823 form and a one-page contract.

Create a document with a table

iText builds a document by adding elements to a Document, as in its Jump-Start tutorial, chapter 1:

Program.csiText 9.8.0
using iText.IO.Font.Constants;
using iText.Kernel.Font;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
 
using PdfDocument pdf = new PdfDocument(new PdfWriter("summary-itext.pdf"));
using Document document = new Document(pdf, PageSize.LETTER);
document.SetMargins(54, 54, 54, 54);
 
PdfFont bold = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD);
document.Add(new Paragraph("Harbor trial summary").SetFont(bold).SetFontSize(20));
document.Add(new Paragraph("Six crews fished the harbor for six weeks."));
 
Table table = new Table(UnitValue.CreatePercentArray(new float[] { 3, 1 })).UseAllAvailableWidth();
table.AddHeaderCell(new Cell().Add(new Paragraph("Quay").SetFont(bold)));
table.AddHeaderCell(new Cell().Add(new Paragraph("Catch (kg)").SetFont(bold)));
table.AddCell(new Cell().Add(new Paragraph("North")));
table.AddCell(new Cell().Add(new Paragraph("1,240")));
table.AddCell(new Cell().Add(new Paragraph("South")));
table.AddCell(new Cell().Add(new Paragraph("980")));
document.Add(table);

OnePdf.Layout describes the same page with nested delegates:

Program.csOnePdf.Layout 1.11.0
using OnePdf.Layout;
 
LayoutDocument.Compose(doc =>
{
    doc.Title("Harbor trial summary").Language("en-US");
    doc.Page(page =>
    {
        page.Size(PageSize.Letter);
        page.Margin(54);
        page.Content().Column(col =>
        {
            col.Spacing(8);
            col.Item().Heading(1).Text("Harbor trial summary").FontSize(20).Bold();
            col.Item().Text("Six crews fished the harbor for six weeks.");
            col.Item().Table(table =>
            {
                table.Columns(c => { c.Relative(3); c.Relative(1); });
                table.CellPadding(4);
                table.CellBorder(0.5, Colors.Black);
                table.HeaderRow(r => { r.Cell().Text("Quay").Bold(); r.Cell().Text("Catch (kg)").Bold(); });
                table.Row(r => { r.Cell().Text("North"); r.Cell().Text("1,240"); });
                table.Row(r => { r.Cell().Text("South"); r.Cell().Text("980"); });
            });
        });
    });
}).Save("summary-onepdf.pdf");

Both files show the heading, the sentence and the two-row table. The differences come with the defaults. OnePdf.Layout tags the document for accessibility (the heading is an H1, the header row TH cells) and embeds a subset of Liberation Sans, so text is the same on every machine. Header rows repeat on every page when a table breaks across pages, and the footer can count pages (“Page 3 of 7”). More in create a PDF in C#.

Stamp a watermark on every page

In iText, an existing PDF is edited in stamping mode, a PdfReader and a PdfWriter on one document, and drawn on with a PdfCanvas. This is the watermark part of the tutorial’s chapter 5 “add content” example:

Program.csiText 9.8.0
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Kernel.Pdf.Extgstate;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
 
// Stamping mode: a reader and a writer on one PdfDocument.
using PdfDocument pdf = new PdfDocument(new PdfReader("report.pdf"), new PdfWriter("report-stamped-itext.pdf"));
using Document document = new Document(pdf);
for (int i = 1; i <= pdf.GetNumberOfPages(); i++)              // pages are numbered from 1
{
    PdfPage page = pdf.GetPage(i);
    Rectangle size = page.GetPageSize();
    PdfCanvas canvas = new PdfCanvas(page);
    canvas.SaveState();
    canvas.SetExtGState(new PdfExtGState().SetFillOpacity(0.2f));
    document.ShowTextAligned(new Paragraph("CONFIDENTIAL").SetFontSize(60),
        size.GetWidth() / 2, size.GetHeight() / 2, i, TextAlignment.CENTER, VerticalAlignment.MIDDLE, (float)(Math.PI / 4));
    canvas.RestoreState();
}

OnePDF opens the file for editing and has a stamp helper that draws once into a shared form, places it on every page and compensates for rotated pages:

Program.csOnePDF 1.11.0
using OnePdf;
 
using PdfDocument doc = PdfDocument.Open("report.pdf", new PdfOpenOptions { AllowEditing = true });
doc.AddTextStamp("CONFIDENTIAL", doc.AddStandardFont("Helvetica"), 60,
    new PdfStampOptions { Opacity = 0.2, RotationDegrees = 45 });   // every page, centered
doc.Save("report-stamped-onepdf.pdf");

Both put a faint diagonal “CONFIDENTIAL” across each of the six pages. For anything other than a stamp, page.BeginContent() gives a builder with text, images, paths, colors and transparency, like PdfCanvas, and leaves the page’s own content untouched (add an image or watermark).

Extract text

iText extracts text through PdfTextExtractor with a strategy. Here both libraries read the two-column part of the report’s first page, and print three lines:

Program.csiText 9.8.0
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
 
using PdfDocument pdf = new PdfDocument(new PdfReader("report.pdf"));
string text = PdfTextExtractor.GetTextFromPage(pdf.GetPage(1), new LocationTextExtractionStrategy());
foreach (string line in text.Split('\n').Skip(8).Take(3))
    Console.WriteLine(line);
OutputConsole
Left column: crews on the north quay. Left column: Right column: crews on the south quay. Right column:
crews on the north quay. Left column: crews on the crews on the south quay. Right column: crews on the
north quay. Left column: crews on the north quay. Left south quay. Right column: crews on the south quay.
Program.csOnePDF 1.11.0
using OnePdf;
 
using PdfDocument doc = PdfDocument.Open("report.pdf");
string text = doc.Pages[0].ExtractText().Text;                    // pages are numbered from 0
foreach (string line in text.Split('\n').Skip(11).Take(3))
    Console.WriteLine(line);
OutputConsole
Left column: crews on the north quay. Left column:
crews on the north quay. Left column: crews on the
north quay. Left column: crews on the north quay. Left

LocationTextExtractionStrategy joins text at the same height into one line, so the two columns come out interleaved. OnePDF returns the left column, then the right, in reading order. It also gives every word and line with its position, a layout mode like pdftotext -layout, and right-to-left text in logical order. See extract text from a PDF in C#.

Fill and flatten a form

iText fills through the AcroForm object (chapter 5, PdfFormCreator) and flattens with FlattenFields:

Program.csiText 9.8.0
using iText.Forms;
using iText.Forms.Fields;
using iText.Kernel.Pdf;
 
using (PdfDocument pdf = new PdfDocument(new PdfReader("sf2823.pdf"), new PdfWriter("sf2823-flat-itext.pdf")))
{
    PdfAcroForm form = PdfFormCreator.GetAcroForm(pdf, true);
    form.GetField("Name").SetValue("Lovelace, Ada");
    form.GetField("DOB").SetValue("12/10/1985");
    form.GetField("Type of Insured").SetValue("Employee");
    form.FlattenFields();
}
 
using PdfDocument flat = new PdfDocument(new PdfReader("sf2823-flat-itext.pdf"));
Console.WriteLine($"form fields left: {PdfFormCreator.GetAcroForm(flat, false)?.GetAllFormFields().Count ?? 0}");
OutputConsole
form fields left: 0
Program.csOnePDF 1.11.0
using OnePdf;
 
using (PdfDocument doc = PdfDocument.Open("sf2823.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    PdfAcroForm form = doc.Form!;
    form.SetValue("Name", "Lovelace, Ada");
    form.SetValue("DOB", "12/10/1985");
    form.SetValue("Type of Insured", "Employee");
    doc.Flatten();
    doc.Save("sf2823-flat-onepdf.pdf");
}
 
using PdfDocument flat = PdfDocument.Open("sf2823-flat-onepdf.pdf");
Console.WriteLine($"form fields left: {flat.Form?.TerminalFields.Count() ?? 0}");
OutputConsole
form fields left: 0

In both files, page 1 shows the name and date as plain page content (pdftotext finds both) and no fields are left. OnePDF’s SetValue checks the value against the field: an option that isn’t in a choice list or a check box state the box doesn’t have throws, naming the field. It generates the appearance of each field it fills, so the value shows in every viewer. See fill PDF form fields and flatten a PDF form.

Sign a PDF (PAdES)

iText 9 signs PAdES baseline signatures with PdfPadesSigner. Keys and certificates go through iText’s BouncyCastle wrapper types, so one of its two adapter packages must be installed (Bouncy Castle changes). This sample follows iText’s PAdES high-level API article, including its way of reading a PEM file. In 9.8.0, DigestAlgorithms is in iText.Kernel.Crypto (API reference):

Program.csiText 9.8.0 · itext.bouncy-castle-adapter 9.8.0
using iText.Bouncycastleconnector;
using iText.Commons.Bouncycastle;
using iText.Commons.Bouncycastle.Cert;
using iText.Commons.Bouncycastle.Crypto;
using iText.Commons.Bouncycastle.Openssl;
using iText.Kernel.Crypto;
using iText.Kernel.Pdf;
using iText.Signatures;
 
// signer.pem: the signer's certificate and its encrypted private key.
IBouncyCastleFactory factory = BouncyCastleFactoryCreator.GetFactory();
var chain = new List<IX509Certificate>();
IPrivateKey? key = null;
using (TextReader file = new StreamReader("signer.pem"))
{
    IPemReader parser = factory.CreatePEMParser(file, "test".ToCharArray());
    for (object item = parser.ReadObject(); item != null; item = parser.ReadObject())
    {
        if (item is IX509Certificate certificate) chain.Add(certificate);
        if (item is IPrivateKey privateKey) key = privateKey;
    }
}
 
var signer = new PdfPadesSigner(new PdfReader("contract.pdf"), new FileStream("contract-signed-itext.pdf", FileMode.Create));
SignerProperties properties = new SignerProperties().SetFieldName("Approval").SetReason("I approve this contract");
signer.SignWithBaselineBProfile(properties, chain.ToArray(), new PrivateKeySignature(key!, DigestAlgorithms.SHA256));
Console.WriteLine("signed contract-signed-itext.pdf");
OutputConsole
signed contract-signed-itext.pdf

OnePDF takes the X509Certificate2 that .NET loads from a .pfx:

Program.csOnePDF 1.11.0
using System.Security.Cryptography.X509Certificates;
using OnePdf;
 
using X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile("signer.pfx", "test");
using PdfDocument doc = PdfDocument.Open("contract.pdf", new PdfOpenOptions { AllowEditing = true });
doc.Sign("contract-signed-onepdf.pdf", PdfSigningCredential.FromCertificate(certificate),
    new PdfSigningOptions { FieldName = "Approval", Reason = "I approve this contract" });   // PAdES B-B by default
Console.WriteLine("signed contract-signed-onepdf.pdf");
OutputConsole
signed contract-signed-onepdf.pdf

pdfsig reports both files the same way: ETSI.CAdES.detached, “Total document signed”, “Signature is Valid.” For a key in an HSM or a cloud key service, pass a signing function to PdfSigningCredential.FromExternalSigner. That covers what iText’s IExternalSignature does. Time-stamps (B-T), visible and certification signatures, and verification are in sign a PDF in C#.

Merge, split, PDF/A and PDF/UA

  • Merge. A plain PdfMerger.Merge loop drops form fields, as iText’s knowledge base notes. PdfDocument.Merge keeps fields, bookmarks and links in one call. Both versions side by side: merge PDF files in C#.
  • Split. PdfDocument.Split(doc, pagesPerPart) or with page ranges (split a PDF in C#).
  • PDF/A. iText uses a PdfADocument subclass from the start. OnePDF makes it a save option, which writes the output intent and XMP itself and refuses with the violations listed (create PDF/A in C#).
  • PDF/UA. Likewise a save option, PDF/UA-1 or PDF/UA-2, with layout documents tagged by default (PDF/UA in C#).

What has no equivalent in OnePDF

  • HTML to PDF (pdfHTML). There is no HTML or CSS engine.
  • OCR (pdfOCR). Text is read only where the PDF has a text layer.
  • XFA forms (pdfXFA). OnePDF fills and flattens AcroForm fields. In a hybrid form it works on the AcroForm side and drops the XFA description when it merges or fully flattens the form, and an XFA-only form can’t be filled.
  • PAdES B-LT and B-LTA, that is long-term validation data (/DSS). OnePDF writes B-B and B-T and document time-stamps.
  • Public-key (certificate) encryption. Password encryption is supported: OnePDF writes AES-128 and AES-256, and reads the older RC4 handlers too.
  • Rendering to images (iText’s pdfRender add-on). OnePDF doesn’t rasterize pages. For page images in .NET, see PDF to image in C# on oneimage.dev.
  • .NET Framework and .NET Standard targets, as above.

iTextSharp 5

iTextSharp is iText 5 for .NET, with a different API from iText 7 onward. iText describes its status and license on its iTextSharp page, and our iText license article covers the versions. Porting from iTextSharp works by task rather than by class name: find the job in the map above (open, stamp, fill, merge, sign, extract) and use the OnePDF call for it.

Where next