---
title: "Getting started with OnePDF"
url: https://recursiveai.org/onepdf/docs/getting-started/
description: "Install OnePDF for .NET 10, read text with word positions, edit pages, fill a form, sign a PDF, and generate an invoice with OnePdf.Layout."
version: "1.11.0"
---

# Getting started with OnePDF

Install OnePDF for .NET 10, read text with word positions, edit pages, fill a form, sign a PDF, and generate an invoice with OnePdf.Layout.

OnePDF is a PDF library written in C# with no dependency beyond .NET itself. Every sample on this page was compiled and run against the 1.11.0 packages from NuGet.

## Install

OnePDF targets .NET 10 (`net10.0`), so you need the .NET 10 SDK.

```sh
dotnet new console -o PdfStart
cd PdfStart
dotnet add package RecursiveAI.OnePdf --version 1.11.0
dotnet add package RecursiveAI.OnePdf.Layout --version 1.11.0   # only for generating documents
```

The namespace is `OnePdf`. The core package has no dependencies: no native binaries and no other NuGet packages. It contains no `unsafe` code, and NativeAOT and trimming are supported. The license is MIT or Apache-2.0, at your choice.

The samples read and write files in the current directory. Put your own PDFs there under the names the code uses.

## Read the text

```csharp
using OnePdf;
using OnePdf.Text;

using PdfDocument doc = PdfDocument.Open("report.pdf");
Console.WriteLine($"{doc.PageCount} pages, PDF {doc.Version}, title: {doc.Info.Title}");

// The text of every page, in reading order.
IReadOnlyList<PdfPageText> pages = doc.ExtractText();
Console.WriteLine(pages[0].Text);

// Words with their boxes, in PDF points with the origin at the lower left of the page.
PdfPageText first = doc.Pages[0].ExtractText();
foreach (PdfTextWord word in first.Words.Take(5))
    Console.WriteLine($"{word.Text,-10} left={word.Bounds.Left:F1} bottom={word.Bounds.Bottom:F1} width={word.Bounds.Width:F1}");
```

The start of the output, for a six-page report with a two-column section:

```text
6 pages, PDF 1.7, title: Harbor trial report
Harbor trial report

Week 1

Prepared by Jane Doe, account 4471-2290. The harbor trial ran for six weeks. Each crew logged its
...
Left column: crews on the north quay. Left column:
crews on the north quay. Left column: crews on the
...
Right column: crews on the south quay. Right column:
...
Harbor     left=54.0 bottom=727.7 width=27.5
trial      left=84.0 bottom=727.7 width=14.5
```

- `PdfDocument` holds the file open and reads it as needed, so dispose it. It opens a path, a `Stream` or a `ReadOnlyMemory<byte>`. Page indexes are zero-based.
- `doc.ExtractText()` reads the pages in parallel. Reading order comes from the structure tree when the file is tagged, and from the geometry of the page otherwise, so the two columns above come out one after the other instead of line by line. Right-to-left text comes out in logical order.
- `Text` is one of three plain-text modes: `ReadingOrder` (the default), `ContentOrder`, and `Layout`, which keeps the arrangement of the page like `pdftotext -layout`. See [Extract text from a PDF](https://recursiveai.org/onepdf/cookbook/extract-text/).
- A word’s `Bounds` is in points (1/72 inch), with the origin at the lower left of the page and y going up, after the page’s rotation is applied. `Blocks` hold `Lines`, which hold `Words`.
- OnePDF does no OCR. A scanned page without a text layer gives no text.

## Merge, split, rotate and delete pages

```csharp
using OnePdf;

// Merge two files into a new document.
using (PdfDocument a = PdfDocument.Open("chapter-1.pdf"))
using (PdfDocument b = PdfDocument.Open("chapter-2.pdf"))
using (PdfDocument merged = PdfDocument.Merge([a, b]))
{
    merged.SetInfo("Title", "Chapters 1 and 2");
    merged.Save("chapters.pdf");
}

// Rotate and delete pages of an existing file. Page indexes are zero-based.
using (PdfDocument doc = PdfDocument.Open("report.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    doc.RotatePage(0, 90);       // clockwise, added to the current rotation
    doc.RemovePages([4, 5]);     // the last two pages
    doc.Save("report-edited.pdf");
}

// Split into files of two pages each.
using (PdfDocument doc = PdfDocument.Open("report.pdf"))
{
    int n = 1;
    foreach (PdfDocument part in PdfDocument.Split(doc, pagesPerPart: 2))
    {
        part.Save($"report-part-{n++}.pdf");
        part.Dispose();
    }
}
```

- Documents open read-only. Pass `AllowEditing = true` to change one.
- `Merge` and `Split` return new documents held in memory. They don’t refer to their sources, so the sources can be disposed straight away. Bookmarks, links, form fields and the tagged structure of the copied pages come along. `Merge` copies no title or other metadata, which is why the sample sets one.
- After a page operation, read `doc.Pages` again. `PdfPage` objects taken before it are out of date.
- `Save` writes a clean new file. `SaveIncremental` appends only the changes to the original bytes.

In this run `chapters.pdf` has 4 pages and all four bookmarks of its sources, `report-edited.pdf` has 4 pages with the first turned 90 degrees, and the report became three files of two pages.

## Fill a form

```csharp
using OnePdf;

using PdfDocument doc = PdfDocument.Open("of345.pdf", new PdfOpenOptions { AllowEditing = true });
PdfAcroForm form = doc.Form ?? throw new InvalidOperationException("The file has no form.");

foreach (PdfFormField field in form.TerminalFields.Take(6))
    Console.WriteLine($"{field.FullName} ({field.Kind})");

form.SetValue("name", "Ada Lovelace");
form.SetValue("title", "Analyst");
form.SetChecked("xyes1", true);
doc.Save("of345-filled.pdf");
```

```text
name (Text)
dob (Text)
title (Text)
homeaddr (Text)
agcy (Text)
xyes1 (CheckBox)
```

The form here is OF 345, a US government form. Fields are addressed by their fully qualified names, which `TerminalFields` lists. Every value is checked before it is stored: an unknown field, a value that isn’t one of a list’s options, or text longer than the field allows throws `PdfFormValueException` and changes nothing. OnePDF also draws each filled field’s appearance, so the value shows in every viewer, not only in viewers that redraw fields themselves.

Field scripts never run, so calculated fields aren’t recomputed. XFA form data is read as raw XML and never filled. To fill a form and then flatten it into the page, see [Fill and flatten form fields](https://recursiveai.org/onepdf/cookbook/fill-flatten-forms/).

## Sign and verify

```csharp
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using OnePdf;

// A self-signed certificate for trying this out. In production, load your own certificate with its private key.
using RSA key = RSA.Create(3072);
var request = new CertificateRequest("CN=Example Signer", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, critical: true));
using X509Certificate2 certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(1));

using (PdfDocument doc = PdfDocument.Open("report.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    doc.Sign("report-signed.pdf", PdfSigningCredential.FromCertificate(certificate), new PdfSigningOptions
    {
        FieldName = "Approval",
        Reason = "Approved for release",
        PageIndex = 5,
        Rectangle = new PdfRectangle(54, 400, 254, 460),   // omit for an invisible signature
    });
}

using (PdfDocument signed = PdfDocument.Open("report-signed.pdf"))
{
    PdfSignatureReport report = signed.VerifySignatures(new PdfSignatureVerifyOptions { TrustAnchors = [certificate] });
    foreach (PdfSignatureVerification v in report.Signatures)
        Console.WriteLine($"{v.Signature.FieldName}: valid={v.IsValid}, chain={v.Chain}, coverage={v.Coverage}");
}
```

```text
Approval: valid=True, chain=Trusted, coverage=WholeDocument
```

- Signing is always an incremental update, so the original bytes stay an exact prefix of the output and earlier signatures stay valid.
- The default format is PAdES baseline B-B (`ETSI.CAdES.detached`). Give `PdfSigningOptions.Timestamp` a callback that posts the request to your time-stamp authority and the signature becomes B-T. OnePDF doesn’t write long-term validation data (PAdES B-LT and B-LTA).
- Verification never touches the network. A chain counts as trusted only when it leads to a certificate in `TrustAnchors` (or to the system store, with `UseSystemTrustStore`). The report also lists every change made after each signature.

In production, load your certificate with `X509CertificateLoader.LoadPkcs12FromFile`, or keep the key in a hardware module and sign through `PdfSigningCredential.FromExternalSigner`. More in [Sign a PDF and verify its signatures](https://recursiveai.org/onepdf/cookbook/sign-and-verify/).

## Generate a document

`RecursiveAI.OnePdf.Layout` builds new documents from code. This one is a one-page invoice:

```csharp
using System.Globalization;
using OnePdf.Layout;

var us = CultureInfo.GetCultureInfo("en-US");
var lines = new (string Item, int Quantity, decimal Price)[]
{
    ("Design review", 2, 450.00m),
    ("Implementation, hours", 12, 120.00m),
    ("Hosting, October", 1, 80.00m),
};
decimal total = lines.Sum(l => l.Quantity * l.Price);

LayoutDocument.Compose(doc =>
{
    doc.Title("Invoice 2026-104").Language("en-US");
    doc.Page(page =>
    {
        page.Size(PageSize.Letter);
        page.Margin(54);
        page.Content().Column(col =>
        {
            col.Spacing(14);
            col.Item().Row(row =>
            {
                row.RelativeItem().Heading(1).Text("Invoice").FontSize(26).Bold();
                row.RelativeItem().AlignEnd().Text("No. 2026-104\nOctober 26, 2026");
            });
            col.Item().Text("Bill to: Example Co., 100 Main Street, Springfield");
            col.Item().Table(table =>
            {
                table.Columns(c => { c.Relative(3); c.Constant(60); c.Constant(90); c.Constant(90); });
                table.CellPadding(5);
                table.CellBorder(0.5, Colors.LightGrey);
                table.HeaderRow(r =>
                {
                    r.Cell().Text("Item").Bold();
                    r.Cell().AlignEnd().Text("Qty").Bold();
                    r.Cell().AlignEnd().Text("Price").Bold();
                    r.Cell().AlignEnd().Text("Amount").Bold();
                });
                foreach (var l in lines)
                {
                    table.Row(r =>
                    {
                        r.Cell().Text(l.Item);
                        r.Cell().AlignEnd().Text(l.Quantity.ToString(us));
                        r.Cell().AlignEnd().Text(l.Price.ToString("C", us));
                        r.Cell().AlignEnd().Text((l.Quantity * l.Price).ToString("C", us));
                    });
                }
                table.FooterRow(r =>
                {
                    r.Cell().Span(3).AlignEnd().Text("Total").Bold();
                    r.Cell().AlignEnd().Text(total.ToString("C", us)).Bold();
                });
            });
        });
        page.Footer().AlignCenter().Text("Payment due within 30 days.").FontSize(9);
    });
}).Save("invoice.pdf");
```

`pdftotext -layout` reads the result back as:

```text
                                                                                       No. 2026-104
Invoice                                                                             October 26, 2026

Bill to: Example Co., 100 Main Street, Springfield

 Item                                                           Qty         Price          Amount

 Design review                                                        2   $450.00          $900.00

 Implementation, hours                                            12      $120.00        $1,440.00

 Hosting, October                                                     1    $80.00           $80.00

                                                                            Total        $2,420.00
```

- Pages are described with nested delegates. Nothing is laid out until `Save`, which writes the file page by page.
- Text is shaped (kerning, ligatures, Arabic, Indic and other complex scripts) and set in Liberation Sans by default, embedded as a subset. System fonts are never used unless you add them.
- Output is tagged by default: the heading is an `H1` and the table’s header cells are `TH`. That is what makes a [PDF/UA save](https://recursiveai.org/onepdf/cookbook/create-pdf-ua/) possible.
- Header and footer rows repeat on every page a table reaches. See [Generate a PDF with tables](https://recursiveai.org/onepdf/cookbook/generate-tables/).

## The packages

All four packages share one version number.

| Package | What it is | License |
|---|---|---|
| `RecursiveAI.OnePdf` | The library: open and repair, decrypt, extract text, edit pages, fill forms, draw on pages, redact, sign and verify, and save as PDF/A or PDF/UA. Namespace `OnePdf`. | MIT or Apache-2.0 |
| `RecursiveAI.OnePdf.Layout` | Generates documents from code: shaped text, tables, lists, images, SVG, headers and footers, page numbers, tagged output. Namespace `OnePdf.Layout`. Depends on `RecursiveAI.OnePdf`, `RecursiveAI.OnePdf.Fonts` and `RecursiveAI.OneFont`. | MIT or Apache-2.0 |
| `RecursiveAI.OnePdf.Fonts` | Fonts for documents that use the standard 14 fonts without embedding them: Liberation Sans, Serif and Mono, Noto Sans Math and Noto Sans Symbols. Layout uses them as its default fonts, and PDF/A and PDF/UA saves embed them for standard fonts OnePDF added. Namespace `OnePdf.Fonts`. | MIT or Apache-2.0, fonts under the SIL Open Font License |
| `RecursiveAI.OnePdf.Hyphenation` | Hyphenation patterns for Layout in 26 language variants, loaded on first use. Opt in with `Hyphenator(Hyphenators.Auto())`. Namespace `OnePdf.Hyphenation`. | MIT or Apache-2.0, with the patterns' own MIT, BSD-3-Clause and Unlicense terms |

```sh
dotnet add package RecursiveAI.OnePdf.Fonts --version 1.11.0
dotnet add package RecursiveAI.OnePdf.Hyphenation --version 1.11.0
```

Layout brings in the Fonts package itself. Files you open that use the standard fonts without embedding them get their outlines from the package once you call `OnePdf.Fonts.FontsPackage.Register()` at startup, before opening documents.

## What OnePDF doesn’t do

- **HTML to PDF.** There is no HTML engine. Build documents with OnePdf.Layout instead.
- **Rendering pages to images.** OnePDF turns a page into drawing calls for a device you supply. To get PNG or JPEG pages, use OneImage.Pdf: see [PDF pages as images](https://recursiveai.org/oneimage/docs/pdf/).
- **OCR.** Scanned pages without a text layer give no text.

## Where next

Cookbook recipes, each with a complete program:

- [Extract text from a PDF](https://recursiveai.org/onepdf/cookbook/extract-text/) and [get words with their positions](https://recursiveai.org/onepdf/cookbook/word-positions/)
- [Merge PDFs](https://recursiveai.org/onepdf/cookbook/merge-pdfs/), [split a PDF](https://recursiveai.org/onepdf/cookbook/split-pdf/), [rotate, delete and reorder pages](https://recursiveai.org/onepdf/cookbook/rotate-delete-pages/)
- [Fill and flatten form fields](https://recursiveai.org/onepdf/cookbook/fill-flatten-forms/), [add an image or a watermark](https://recursiveai.org/onepdf/cookbook/add-image-watermark/), [redact text](https://recursiveai.org/onepdf/cookbook/redact/)
- [Sign a PDF and verify its signatures](https://recursiveai.org/onepdf/cookbook/sign-and-verify/), [decrypt or encrypt a PDF](https://recursiveai.org/onepdf/cookbook/decrypt-pdf/), [repair a damaged PDF](https://recursiveai.org/onepdf/cookbook/repair-damaged-pdf/)
- [Create PDF/A](https://recursiveai.org/onepdf/cookbook/create-pdf-a/), [create PDF/UA](https://recursiveai.org/onepdf/cookbook/create-pdf-ua/), [generate a PDF with tables](https://recursiveai.org/onepdf/cookbook/generate-tables/), [Arabic and Hindi text](https://recursiveai.org/onepdf/cookbook/arabic-hindi-text/)

Articles:

- [Choosing a C# PDF library](https://recursiveai.org/onepdf/articles/csharp-pdf-library/): license, price and native code compared
- [Extract text from PDF in C#](https://recursiveai.org/onepdf/articles/extract-text-from-pdf-csharp/) and [create PDF invoices and reports in C#](https://recursiveai.org/onepdf/articles/create-pdf-csharp/)
- [Moving from iText](https://recursiveai.org/onepdf/articles/migrate-from-itext/): the same five jobs side by side
- Full migration guides from [PDFsharp and MigraDoc](https://recursiveai.org/onepdf/articles/migrate-from-pdfsharp/) and [PdfPig](https://recursiveai.org/onepdf/articles/migrate-from-pdfpig/), and [extracting images from a PDF](https://recursiveai.org/onepdf/articles/extract-images-from-pdf-csharp/)

The reference docs:

- [Text layout and reading order](https://recursiveai.org/onepdf/docs/text-layout/) and the [JSON form of extracted text](https://recursiveai.org/onepdf/docs/extraction-json/)
- [Writing](https://recursiveai.org/onepdf/docs/writing/), [page operations](https://recursiveai.org/onepdf/docs/page-operations/), [forms](https://recursiveai.org/onepdf/docs/forms/), [content creation](https://recursiveai.org/onepdf/docs/content-creation/), [redaction](https://recursiveai.org/onepdf/docs/redaction/)
- [Signatures](https://recursiveai.org/onepdf/docs/signatures/), [repair rules](https://recursiveai.org/onepdf/docs/repair/), [PDF/A](https://recursiveai.org/onepdf/docs/pdfa/), [PDF/UA](https://recursiveai.org/onepdf/docs/pdfua/), [tagging existing files](https://recursiveai.org/onepdf/docs/tagging/)
- [Layout](https://recursiveai.org/onepdf/docs/layout/), [layout text](https://recursiveai.org/onepdf/docs/layout-text/), [SVG in layout](https://recursiveai.org/onepdf/docs/layout-svg/)
- Moving from another library: [PdfPig](https://recursiveai.org/onepdf/docs/migrating-from-pdfpig/), [PDFsharp](https://recursiveai.org/onepdf/docs/migrating-from-pdfsharp/), [QuestPDF](https://recursiveai.org/onepdf/docs/migrating-from-questpdf/)
