ZingPDF logo

Docs

ZingPDF docs

Curated documentation for the main API surface, fluent authoring, package choices, and PDF behavior.

Quick Start

Load, create, edit, and save

Start by installing the package, importing the core namespaces, and deciding whether you are loading an existing PDF or creating a new one from scratch. The main entry points are Pdf.Load(...) for existing documents, Pdf.Create(...) for low-level blank documents, and Pdf.New() for the fluent authoring surface.

Install the package

dotnet add package ZingPDF

Import the namespaces used in the examples

using ZingPDF;
using ZingPDF.Syntax.CommonDataStructures;

Load an existing PDF

using var input = File.OpenRead("input.pdf");
using var pdf = Pdf.Load(input);

var pageCount = await pdf.GetPageCountAsync();
var firstPage = await pdf.GetPageAsync(1);

Create a blank PDF

using var pdf = Pdf.Create(options =>
{
    options.MediaBox = Rectangle.FromDimensions(595, 842);
});

using var output = File.Create("blank.pdf");
await pdf.SaveAsync(output);

Author a new PDF fluently

await Pdf.New()
    .Page(page => page
        .Size(595, 842)
        .Text(text => text
            .Value("Monthly delivery report")
            .HelveticaBold()
            .FontSize(24)
            .At(48, 780))
        .Rectangle(box => box
            .At(48, 720)
            .Size(220, 40)
            .Stroke(RGBColour.PrimaryBlue, 1)
            .Fill(new RGBColour(0.9, 0.97, 1)))
        .Text(text => text
            .Value("Generated with the fluent authoring API")
            .Helvetica()
            .FontSize(11)
            .InBox(60, 730, 196, 20)
            .AlignStart()
            .AlignMiddle()
            .Padding(0)))
    .SaveToFileAsync("authored.pdf");

Pdf.New() is a builder for authored pages. It covers page sizing, text, shapes, paths, images, watermarks, boxed text alignment, wrapping, and font registration through the same core document APIs. Use pdf.Pages(...) when you want the same page-level fluent operations on an existing document.

Use the standard API when you are loading an existing PDF, working directly with Pdf and Page, or need explicit control over each step. Use the fluent API when you are building a new document from authored content and want the page layout to read top to bottom.

Edit an existing PDF

Standard API:

using System.Text.RegularExpressions;

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));
var font = await pdf.RegisterStandardFontAsync(StandardPdfFonts.HelveticaBold);

var firstPage = await pdf.GetPageAsync(1);
await firstPage.AddTextAsync(
    "Approved",
    Rectangle.FromCoordinates(
        new Coordinate(48, 780),
        new Coordinate(168, 804)),
    font,
    18,
    RGBColour.Black);

var summaryPage = await pdf.AppendPageAsync(options =>
{
    options.MediaBox = Rectangle.FromDimensions(595, 842);
});

await summaryPage.AddTextAsync(
    "Summary",
    Rectangle.FromCoordinates(
        new Coordinate(48, 780),
        new Coordinate(220, 808)),
    font,
    24,
    RGBColour.Black);

await pdf.SaveAsync(File.Create("edited.pdf"));

Fluent API:

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));

await pdf.Pages(pages => pages
        .Page(1, page => page
            .Text(text => text
                .Value("Approved")
                .HelveticaBold()
                .FontSize(18)
                .At(48, 780)))
        .Append(page => page
            .Size(595, 842)
            .Text(text => text
                .Value("Summary")
                .HelveticaBold()
                .FontSize(24)
                .At(48, 780))))
    .SaveAsync(File.Create("edited.pdf"));

Remove(...) and Delete(...) are also available when you want to drop pages from an existing document.

Dedicated guide: How to create PDFs with the fluent authoring API in C#.

When saving, use an output stream that is writable and seekable. If you save to a different stream than the input stream, it must also be empty.

Append, insert, and delete pages

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));

await pdf.AppendPageAsync(options =>
{
    options.MediaBox = Rectangle.FromDimensions(595, 842);
});

await pdf.InsertPageAsync(1, options =>
{
    options.MediaBox = Rectangle.FromDimensions(300, 300);
});

await pdf.DeletePageAsync(2);
await pdf.SaveAsync(File.Create("pages-updated.pdf"));

Export selected pages or split a document

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));
using var selectedPages = await pdf.ExportPagesAsync([1, 3, 5]);
using var selectedOutput = File.Create("selected-pages.pdf");

await selectedPages.SaveAsync(selectedOutput);

var parts = await pdf.SplitAsync(10);

try
{
    for (var index = 0; index < parts.Count; index++)
    {
        using var output = File.Create($"part-{index + 1}.pdf");
        await parts[index].SaveAsync(output);
    }
}
finally
{
    foreach (var part in parts)
    {
        part.Dispose();
    }
}

Text extraction

var textItems = await pdf.ExtractTextAsync();
foreach (var item in textItems)
{
    Console.WriteLine(item.Text);
}

Watermarks and compression

using var output = File.Create("watermarked.pdf");

var firstPage = await pdf.GetPageAsync(1);
await firstPage.AddWatermarkAsync("DRAFT");

await pdf.AddWatermarkAsync("CONFIDENTIAL");
pdf.Compress(dpi: 72, quality: 75);
await pdf.SaveAsync(output);

Page.AddWatermarkAsync(...) targets one page. Pdf.AddWatermarkAsync(...) applies the same text across the whole document.

Related guides: Create a blank PDF, Insert, append, and delete pages, Export selected pages or split a document, Extract text, Add watermarks, and Compress output.

Runnable examples: CreateBlankPdf and CreateProjectStatusReport.

Page Geometry

Map PDF points to displayed page points

Page.GetGeometryAsync(...) returns the resolved media box, crop box, visible box, and normalised page rotation. PDF page coordinates use a bottom-left origin. Display coordinates use a top-left origin after the visible box and clockwise page rotation are applied.

using ZingPDF;
using ZingPDF.Elements.Drawing;

using var input = File.OpenRead("input.pdf");
using var pdf = Pdf.Load(input);

var page = await pdf.GetPageAsync(1);
var geometry = await page.GetGeometryAsync();

var storedPdfPoint = new Coordinate(72, 144);
var previewPoint = geometry.PageToDisplay(storedPdfPoint);
var restoredPdfPoint = geometry.DisplayToPage(previewPoint);

Console.WriteLine($"{geometry.DisplayWidth} x {geometry.DisplayHeight}");
Console.WriteLine($"{previewPoint.X}, {previewPoint.Y}");
  • MediaBox, CropBox, and Rotate are resolved through the page tree when they are inherited.
  • VisibleBox is the intersection of the crop box and media box.
  • RotationDegrees is returned as 0, 90, 180, or 270.
  • Page.RenderAsync(...) and Pdf.RenderPageAsync(...) render PNG bytes using the same page geometry metadata.

Render a PNG preview

using ZingPDF;
using ZingPDF.Rendering;

using var input = File.OpenRead("input.pdf");
using var pdf = Pdf.Load(input);

var preview = await pdf.RenderPageAsync(1, new PdfPageRenderOptions
{
    Scale = 1.5
});

await File.WriteAllBytesAsync("page-1.png", preview.PngBytes.ToArray());
Console.WriteLine($"{preview.PixelWidth} x {preview.PixelHeight}");

Rendering uses the current in-memory PDF state, so page edits can be previewed before saving. The renderer is backed by PDFium through PDFtoImage and SkiaSharp native assets for Windows, Linux, and macOS.

Validate rendered output

For automated checks, render simple vector pages and compare sampled pixels or approved image signatures. Use PdfPageGeometry.PageToDisplay(...) to turn PDF coordinates into expected display pixels when testing overlays, crops, or rotation. The benchmark project includes page-rendering scenarios for thumbnail-scale previews, 1x previews, image-heavy input, and unsaved edits.

Packages

NuGet packages

ZingPDF ships as a small set of packages. Use the core package unless you specifically need HTML rendering, Liquid HTML templates, Google Fonts, or OCR support for scanned or image-based pages.

Which package should I install?

Start with ZingPDF. Add ZingPDF.FromHTML for HTML-to-PDF, ZingPDF.Templates.LiquidHtml for Liquid HTML templates, ZingPDF.GoogleFonts for Google Fonts, and ZingPDF.OCR for scanned or image-based pages.

ZingPDF

The main PDF library for loading, creating, editing, page rendering, merging, watermarking, compression, redaction, forms, metadata, and encryption.

  • Target framework: net8.0
  • Use for most applications
  • Runs on Windows, Linux, and macOS
  • Page rendering depends on PDFium through PDFtoImage and SkiaSharp native assets, so test on your target platform

ZingPDF.FromHTML

HTML-to-PDF package for Chromium-based rendering.

  • Target framework: net8.0
  • Use for HTML-to-PDF conversion
  • Cross-platform in principle, but heavier because it relies on browser automation
  • Keep its deployment requirements separate from the core package

ZingPDF.Templates

Shared source and diagnostic types for template renderer packages.

  • Target framework: net8.0
  • Use when building or referencing renderer packages
  • No dependency on HTML, Liquid, or browser automation
  • Contains template sources, diagnostics, and render exceptions

ZingPDF.Templates.LiquidHtml

Liquid HTML template package for creating PDFs from data models.

  • Target framework: net8.0
  • Uses Fluid for Liquid rendering
  • Uses ZingPDF.FromHTML for HTML-to-PDF conversion
  • Has the same browser automation requirements as ZingPDF.FromHTML

ZingPDF.GoogleFonts

Google Fonts integration package for registering Google Fonts in a PDF document.

  • Target framework: net8.0
  • Use for fetching Google Fonts at document build time
  • Cross-platform with outbound HTTPS access
  • Requires a Google Fonts Developer API key at registration time

ZingPDF.OCR

OCR companion package for scanned and image-based pages, with options for embedded-text-first or OCR-first workflows.

  • Target framework: net8.0
  • Use for OCR-driven text extraction on scanned or image-based pages
  • Best fit for image-based pages and supported image XObjects
  • Ships with a Tesseract-backed engine option

Package selection notes

  • For application integrations, prefer ZingPDF unless you specifically need HTML conversion.
  • Use ZingPDF.Templates.LiquidHtml for Liquid variables, loops, and conditionals in HTML templates that render to PDF.
  • The core package already covers standard PDF fonts and embedded TrueType font registration.
  • Use ZingPDF.GoogleFonts if you need to register Google Fonts directly from the Google Fonts catalog.
  • Use ZingPDF.OCR when the page is primarily image-based or when OCR is part of your extraction workflow.
  • Treat ZingPDF.FromHTML as a separate deployment concern with its own runtime requirements.
  • Validate your exact deployment platform if you depend on image handling, font metrics, or browser automation.

Direct HTML-to-PDF conversion

ZingPDF.FromHTML exposes Converter.ToPdfAsync(...) for direct conversion from an HTML string or a URL.

using ZingPDF.FromHTML;

await using var pdfStream = await Converter.ToPdfAsync(
    new Uri("https://example.com/report"),
    new NavigationOptions
    {
        TimeoutExpiration = 60_000,
        WaitUntilFlags = WaitUntil.Networkidle0
    });

await using var output = File.Create("report.pdf");
await pdfStream.CopyToAsync(output);

Dedicated guide: How to convert HTML to PDF in C#.

Templates

Liquid HTML templates

ZingPDF.Templates.LiquidHtml renders a Liquid HTML template with a .NET model, converts the rendered HTML to PDF through ZingPDF.FromHTML, and writes the PDF stream you provide.

Install the renderer package

dotnet add package ZingPDF.Templates.LiquidHtml

Render a template file

using ZingPDF.Templates.LiquidHtml;

var invoice = new
{
    Number = "INV-1001",
    CustomerName = "Ada Lovelace",
    Items = new[]
    {
        new { Description = "Consulting", Total = 240m },
        new { Description = "Support", Total = 80m }
    }
};

await using var output = File.Create("invoice.pdf");

await LiquidHtmlPdfTemplate
    .FromFile("invoice.liquid.html")
    .RenderAsync(invoice, output);

Inspect rendered HTML

var html = await LiquidHtmlPdfTemplate
    .FromFile("invoice.liquid.html")
    .RenderHtmlAsync(invoice);

Template behavior

  • Liquid variables, loops, conditionals, and includes are handled by Fluid.
  • File-backed templates resolve Liquid includes from the template directory by default.
  • The package uses browser automation through ZingPDF.FromHTML after Liquid rendering.
  • Use PdfTemplateRenderException.Diagnostics for template parse, load, and render failures.

Dedicated guide: Create a PDF from a Liquid HTML template.

Fonts

Font registration

ZingPDF registers standard PDF fonts and embedded TrueType fonts. ZingPDF.GoogleFonts adds Google Fonts registration through the same document-level font path.

Register a standard PDF font

using ZingPDF.Fonts;
using ZingPDF.Graphics;

using var pdf = Pdf.Create();
var page = await pdf.GetPageAsync(1);
var font = await pdf.RegisterStandardFontAsync(StandardPdfFonts.Helvetica);

await page.AddTextAsync(
    "Hello, world",
    Rectangle.FromDimensions(240, 80),
    font,
    18,
    RGBColour.Black);

Use a standard font in the fluent API

await Pdf.New()
    .Page(page => page
        .Text(text => text
            .Value("Hello, world")
            .Helvetica()
            .FontSize(18)
            .At(48, 780)))
    .SaveToFileAsync("hello.pdf");

Usable standard PDF fonts

  • StandardPdfFonts.Helvetica
  • StandardPdfFonts.HelveticaBold
  • StandardPdfFonts.HelveticaOblique
  • StandardPdfFonts.HelveticaBoldOblique
  • StandardPdfFonts.TimesRoman
  • StandardPdfFonts.TimesBold
  • StandardPdfFonts.TimesItalic
  • StandardPdfFonts.TimesBoldItalic
  • StandardPdfFonts.Courier
  • StandardPdfFonts.CourierBold
  • StandardPdfFonts.CourierOblique
  • StandardPdfFonts.CourierBoldOblique

Register an embedded TrueType font from disk

var font = await pdf.RegisterTrueTypeFontAsync(
    "MyFont-Regular.ttf");

await page.AddTextAsync(
    "Custom font text",
    Rectangle.FromDimensions(240, 80),
    font,
    18,
    RGBColour.Black);

Use an embedded TrueType font in the fluent API

await Pdf.New()
    .Page(page => page
        .Text(text => text
            .Value("Custom font text")
            .WithTrueTypeFont("MyFont-Regular.ttf", fontName: "MyFont-Regular")
            .FontSize(18)
            .At(48, 780)))
    .SaveToFileAsync("custom-font.pdf");

Register a Google Font

using ZingPDF.GoogleFonts;

var client = new GoogleFontsClient("<google-fonts-api-key>");
var font = await pdf.RegisterGoogleFontAsync(
    client,
    new GoogleFontRequest
    {
        Family = "Inter",
        Variant = "regular"
    });

await page.AddTextAsync(
    "Google Fonts text",
    Rectangle.FromDimensions(240, 80),
    font,
    18,
    RGBColour.Black);

Font notes

  • The current high-level registration path targets WinAnsi / Windows-1252 text.
  • Symbol and ZapfDingbats are not yet exposed through the high-level registration API.
  • ZingPDF.GoogleFonts requires a Google Fonts Developer API key and network access.

Related guides: Use standard PDF fonts, Use Google Fonts in a PDF, Embed a TrueType font, and Add text to a page.

Drawing

Draw vector paths and authored page content

The page API supports drawing vector paths with stroke and fill options. The fluent authoring API uses the same drawing primitives for rectangles, lines, paths, images, watermarks, and positioned or boxed text.

Draw a stroked path

using ZingPDF.Elements.Drawing;
using ZingPDF.Graphics;

await page.AddPathAsync(new Path(
    new StrokeOptions(RGBColour.PrimaryRed, 2),
    null,
    PathType.Linear,
    [
        new Coordinate(10, 10),
        new Coordinate(50, 60),
        new Coordinate(80, 20)
    ]));

Fill a bezier shape

await page.AddPathAsync(new Path(
    null,
    new FillOptions(RGBColour.PrimaryBlue),
    PathType.Bezier,
    [
        new Coordinate(20, 20),
        new Coordinate(40, 80),
        new Coordinate(90, 80),
        new Coordinate(110, 20)
    ]));

Draw the same kind of content fluently

await Pdf.New()
    .Page(page => page
        .Line(line => line
            .From(48, 720)
            .To(220, 720)
            .Stroke(RGBColour.PrimaryRed, 2))
        .Rectangle(box => box
            .At(48, 660)
            .Size(120, 40)
            .Stroke(RGBColour.PrimaryBlue, 1)
            .Fill(new RGBColour(0.9, 0.97, 1)))
        .Path(path => path
            .Bezier()
            .Point(220, 660)
            .Point(260, 720)
            .Point(320, 720)
            .Point(360, 660)
            .Fill(RGBColour.PrimaryBlue)))
    .SaveToFileAsync("drawing.pdf");

Boxed text, alignment, and wrapping

await Pdf.New()
    .Page(page => page
        .Text(text => text
            .Value("A longer note that should wrap inside the box.")
            .Helvetica()
            .FontSize(11)
            .InBox(48, 640, 180, 42)
            .AlignStart()
            .AlignTop()
            .Padding(0)
            .Wrap()
            .ClipOverflow()))
    .SaveToFileAsync("boxed-text.pdf");

Related guides: Add text to a page, Use standard PDF fonts, and Embed a TrueType font.

API reference

The symbol-by-symbol reference is generated from the source and XML documentation comments, and is best used when you need exact member signatures or type-level detail.

Open the API reference

Metadata

Document metadata

GetMetadataAsync() reads and updates the trailer Info dictionary through a higher-level wrapper.

  • Editable fields: Title, Author, Subject, Keywords, Creator, and CreationDate.
  • Save-managed fields: Producer is stamped to ZingPDF and ModifiedDate is refreshed on save.
  • If no info dictionary exists, ZingPDF creates one during save.
  • Date values may be read from either PDF date objects or strings.
using var output = File.Create("metadata-updated.pdf");

var metadata = await pdf.GetMetadataAsync();
metadata.Title = "Quarterly Report";
metadata.Author = "Taylor Smith";
metadata.Subject = "Q1 FY2026";
metadata.Keywords = "finance,quarterly";
metadata.Creator = "Back Office Importer";
metadata.CreationDate = new DateTimeOffset(2026, 3, 1, 9, 0, 0, TimeSpan.Zero);

await pdf.SaveAsync(output);

Related guide: How to update PDF metadata in C#.

Forms

AcroForm fields

GetFormAsync() returns a Form wrapper for terminal field enumeration, typed field lookup, and field-family-specific behavior.

Create fields

GetOrCreateFormAsync() returns a form wrapper that can add new AcroForm fields to 1-based pages. ZingPDF creates the field dictionary, widget annotation, page annotation reference, and default appearance resources for supported field types.

using ZingPDF.Elements.Drawing;
using ZingPDF.Elements.Forms.FieldTypes.Button;
using ZingPDF.Elements.Forms.FieldTypes.Choice;
using ZingPDF.Syntax.CommonDataStructures;

var form = await pdf.GetOrCreateFormAsync();

await form.AddTextFieldAsync(
    1,
    "Applicant.Name",
    Rectangle.FromCoordinates(new Coordinate(48, 720), new Coordinate(260, 748)),
    options => options.DefaultValue = "Ada Lovelace");

await form.AddCheckboxFieldAsync(
    1,
    "Applicant.AcceptedTerms",
    Rectangle.FromCoordinates(new Coordinate(48, 680), new Coordinate(64, 696)),
    options => options.Checked = true);

await form.AddRadioButtonFieldAsync(
    1,
    "Applicant.ContactMethod",
    [
        new RadioButtonFieldOption("Email", Rectangle.FromCoordinates(new Coordinate(48, 640), new Coordinate(64, 656))),
        new RadioButtonFieldOption("Phone", Rectangle.FromCoordinates(new Coordinate(48, 612), new Coordinate(64, 628)))
    ],
    options => options.SelectedValue = "Email");

await form.AddComboBoxFieldAsync(
    1,
    "Applicant.State",
    Rectangle.FromCoordinates(new Coordinate(48, 560), new Coordinate(220, 586)),
    [
        new ChoiceFieldOption("NSW", "New South Wales"),
        new ChoiceFieldOption("VIC", "Victoria")
    ]);

await form.AddSignatureFieldAsync(
    1,
    "Approval.Signature",
    Rectangle.FromCoordinates(new Coordinate(48, 400), new Coordinate(260, 450)));

Field discovery

  • Form.GetFieldsAsync() returns terminal fields as IFormField instances.
  • Nested names are flattened into dot-separated paths like Applicant.Address.Suburb.
  • Shared field metadata includes name, description, field flags, field bounds, and field dimensions.

Known field names

If you already know the fully qualified field name, fetch the field directly instead of enumerating the whole form.

var form = await pdf.GetFormAsync();
if (form is null)
{
    return;
}

var nameField = await form.GetFieldAsync("Applicant.Name");
if (nameField is not null)
{
    await nameField.SetValueAsync("Taylor Smith");
}

var stateField = await form.GetFieldAsync("Applicant.State");
if (stateField is not null)
{
    await stateField.SelectOptionByTextAsync("NSW");
}

Field enumeration

GetFieldsAsync() returns terminal fields in document order so you can inspect names and map them into your own data model.

var form = await pdf.GetFormAsync();
if (form is null)
{
    return;
}

foreach (var field in await form.GetFieldsAsync())
{
    Console.WriteLine(field.Name);
}

Form flattening

FlattenAsync() writes the current field appearances into page content and removes the interactive form structure.

var form = await pdf.GetFormAsync();
if (form is null)
{
    return;
}

await form.FlattenAsync();
await pdf.SaveAsync(File.Create("form-flattened.pdf"));

Text fields

TextFormField is the simplest path when you need to fill names, dates, IDs, or other typed values.

  • Read the current value with GetValueAsync().
  • Write or clear values with SetValueAsync(...) and ClearAsync().
  • Look fields up directly with Form.GetFieldAsync<TextFormField>(name).

Choice fields

ChoiceFormField covers dropdowns and list boxes.

  • Inspect available options with GetOptionsAsync().
  • Select by label or export value with SelectOptionByTextAsync(...) and SelectOptionByValueAsync(...).
  • Look fields up directly with Form.GetFieldAsync<ChoiceFormField>(name).

Checkboxes and radio buttons

CheckboxFormField and RadioButtonFormField expose selectable options through SelectableOption.

  • Inspect choices with GetOptionsAsync().
  • Toggle state with SelectAsync() and DeselectAsync().
  • Use this path when the PDF stores export values rather than plain text.

Push buttons

PushButtonFormField is read-only at the high level today.

  • Inspect captions and primary action type.
  • Read URI actions, named actions, and additional-action triggers.
  • Action dictionaries are exposed as metadata because click behavior belongs to PDF viewers and editing software.

Signature fields

SignatureFormField supports inspection, signing, and validation.

  • Read signed state and signature metadata such as signer name, reason, filter, and signing time.
  • Use SignAsync(...) to sign an existing visible field.
  • Use Pdf.SignInvisibleAsync(...) when you only need cryptographic validation and want to add a hidden field automatically.
  • Use Pdf.GetSignaturesAsync() or ValidateSignatureAsync(...) to validate signed byte ranges and detached CMS payloads.
  • Signing saves rewritten output so the signed byte ranges are stable and do not depend on incremental history.

Inspect push-button action metadata

Push buttons expose action dictionaries as metadata for viewer or editor integrations. ZingPDF does not execute those actions.

using ZingPDF.Elements.Forms.FieldTypes.Button;

var form = await pdf.GetFormAsync();
if (form is null)
{
    return;
}

foreach (var button in (await form.GetFieldsAsync()).OfType())
{
    Console.WriteLine(await button.GetCaptionAsync());
    Console.WriteLine(await button.GetActionTypeAsync());
    Console.WriteLine(await button.GetActionUriAsync());
    Console.WriteLine(await button.GetNamedActionAsync());
}

Validate a signed PDF

ValidateIntegrityAsync() checks the PDF /ByteRange, extracts the detached CMS payload from /Contents, and verifies that the signed bytes match the CMS signature.

using var pdf = Pdf.Load(File.OpenRead("signed.pdf"));

var signatures = await pdf.GetSignaturesAsync();
var result = await signatures[0].ValidateIntegrityAsync();

if (result.Status == PdfSignatureValidationStatus.Valid)
{
    Console.WriteLine("The signed byte ranges match the CMS signature.");
}

Use ValidateAsync(...) with PdfSignatureValidationOptions to build a signer certificate chain with extra certificates or custom trusted roots. The result separates integrity, coverage, certificate, revocation, timestamp, and permission checks.

Current gaps

  • A form reset API for restoring fields to their default values is planned.
  • High-level signing supports existing visible signature fields and hidden validation-only fields on unencrypted PDFs. For visible signing, create the signature field before calling SignAsync(...).
  • Signing password-protected input PDFs is planned as a separate workflow. Encrypting an already signed PDF is not exposed as a high-level workflow because rewriting signed bytes invalidates existing signatures.
  • Signature validation does not yet validate trusted timestamp tokens, DSS/VRI long-term validation data, or DocMDP certification permissions.

Related guides: Create PDF form fields and Fill PDF form fields and Flatten a PDF form and Inspect push-button actions and Sign a PDF form field and Validate a signed PDF.

Encryption

Passwords and encryption

ZingPDF supports authenticating encrypted PDFs, applying password protection to plain PDFs, controlling user permissions, and saving decrypted output from encrypted input. New encrypted output can use legacy RC4-128 for compatibility or AES-128 and AES-256 through the Standard security handler.

Open an encrypted PDF

using var pdf = Pdf.Load(File.OpenRead("encrypted.pdf"));

await pdf.AuthenticateAsync("password");
var pageCount = await pdf.GetPageCountAsync();

Encrypt a previously unencrypted PDF

using var pdf = Pdf.Create();

await pdf.EncryptAsync("user-password", "owner-password");
await pdf.SaveAsync(File.Create("protected.pdf"));

Choose AES-128 or AES-256 when saving

If you do not pass an algorithm, ZingPDF writes RC4-128 for compatibility. Pass PdfEncryptionAlgorithm.Aes128 or PdfEncryptionAlgorithm.Aes256 when you want AES output.

// AES-128
using var aes128Pdf = Pdf.Create();

await aes128Pdf.EncryptAsync(
    "user-password",
    "owner-password",
    PdfEncryptionAlgorithm.Aes128);

await aes128Pdf.SaveAsync(File.Create("protected-aes128.pdf"));

// AES-256
using var aes256Pdf = Pdf.Create();

await aes256Pdf.EncryptAsync(
    "user-password",
    "owner-password",
    PdfEncryptionAlgorithm.Aes256);

await aes256Pdf.SaveAsync(File.Create("protected-aes256.pdf"));

Restrict copy, edit, or print access

Pass PdfEncryptionPermissions when the file should open with a password but still limit what the user can do afterward.

using var restrictedPdf = Pdf.Create();

await restrictedPdf.EncryptAsync(
    "user-password",
    "owner-password",
    PdfEncryptionAlgorithm.Aes256,
    PdfEncryptionPermissions.Print | PdfEncryptionPermissions.FillForms);

await restrictedPdf.SaveAsync(File.Create("restricted.pdf"));

EncryptAsync(...) supports Standard security handler RC4-128, AES-128, and AES-256 output. If you do not pass an algorithm, ZingPDF writes RC4-128 for compatibility.

Decrypt an encrypted PDF

using var pdf = Pdf.Load(File.OpenRead("encrypted.pdf"));

await pdf.DecryptAsync("password");
await pdf.SaveAsync(File.Create("decrypted.pdf"));

DecryptAsync(...) removes encryption from the latest saved revision. If you also need to remove older encrypted history from the file bytes, call RemoveHistoryAsync() before saving.

Related guides: Encrypt a PDF with AES and Remove a password from a PDF.

Redaction

Redaction marks and rewritten output

ZingPDF can mark exact extracted-text matches or explicit page regions, remove matched text from supported page content, redact intersecting vector and XObject content, draw visible redaction overlays, and rewrite the document so earlier file history is not preserved.

Mark exact text and explicit regions

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));

var plan = await pdf.RedactionAsync();

await plan.MarkTextAsync("12345");
await plan.MarkTextAsync(new Regex(@"\b\d{3}-\d{2}-\d{4}\b"));
plan.MarkRegion(
    1,
    Rectangle.FromCoordinates(
        new Coordinate(72, 520),
        new Coordinate(180, 548)));

await plan.ApplyAsync(new PdfRedactionOptions
{
    OverlayText = "REDACTED"
});

await pdf.SaveAsync(File.Create("redacted.pdf"));

What v1 redaction does

  • MarkTextAsync(...) uses extracted glyph runs to find exact string or regex matches and maps those matches back to supported text-showing operators.
  • MarkRegion(...) adds an explicit rectangle on a known page and removes intersecting supported text, vector, image XObject, and form XObject content.
  • ApplyAsync(...) rewrites matched content, draws the redaction overlay, and switches the document to rewritten-file save behavior.
  • PdfRedactionOptions controls fill colour and overlay text. Incremental-save redaction is not supported.

Current limits

This version removes matched text from supported page content and structurally redacts intersecting vector paths, image XObjects, and form XObjects. It still refuses inline images and shading content, and it does not yet derive OCR bounds for scanned pages.

Redaction currently uses the standard Pdf API because it is a document-level workflow over an existing file, rather than an authored page layout.

Related guide: Redact PDF content in C#.

Save Behavior

How saving works

By default, SaveAsync(...) writes an incremental update. That preserves earlier revisions in the file history.

Incremental save

SaveAsync(...) appends a new revision to the file by default, leaving earlier revisions in the document history.

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));

await pdf.AddWatermarkAsync("INTERNAL");
await pdf.SaveAsync(File.Create("output.pdf"));

Removing file history

Call RemoveHistoryAsync() before saving if you want the file rewritten with only the latest live objects. This removes earlier incremental revisions, helping with file size and document security.

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));

await pdf.RemoveHistoryAsync();
await pdf.SaveAsync(File.Create("sanitized.pdf"));

Stream requirements

  • Pdf.Load(...) requires a seekable input stream. That is a parsing requirement and does not change if you later call RemoveHistoryAsync().
  • SaveAsync(...) currently requires a writable, seekable output stream because the save pipeline checks stream length and, in some paths, resets and truncates the stream before writing.
  • If you save to a different stream, that stream must be empty.

Form updates and metadata stamping are applied as part of the save pipeline.

Compress(...) works on eligible unfiltered streams and can recompress JPEG image streams. DecompressAsync() skips JPEG image streams.

Related guides: Rewrite a PDF without incremental history, Redact PDF content, Add watermarks, and Compress output.

OCR

OCR for scanned and image-based pages

The core package extracts embedded PDF text. Add ZingPDF.OCR when you need OCR-based text extraction for scanned or image-based pages.

using ZingPDF.OCR;

using var pdf = Pdf.Load(File.OpenRead("scanned.pdf"));
var engine = new TesseractOcrEngine("./tessdata", "eng");

var text = await pdf.ExtractPlainTextWithOcrAsync(engine);
  • ExtractTextWithOcrAsync(...) prefers embedded text first by default, and that behavior can be changed with PdfOcrOptions.
  • ExtractPlainTextWithOcrAsync(...) returns a joined plain-text view across pages.
  • The current OCR path works best on image-based pages and supported image XObjects.

Low-Level Access

Low-level object access

IPdf.Objects exposes the underlying PDF object collection and page tree for integrations that need direct syntax-level access.

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));

var catalog = await pdf.Objects.GetDocumentCatalogAsync();
var trailer = await pdf.Objects.GetLatestTrailerDictionaryAsync();
var pageCount = await pdf.Objects.PageTree.GetPageCountAsync();

Console.WriteLine($"Root reference: {trailer.Root}");
Console.WriteLine($"Pages: {pageCount}");

await foreach (var indirectObject in pdf.Objects)
{
    Console.WriteLine($"{indirectObject.Id}: {indirectObject.Object.GetType().Name}");
}
  • Use high-level wrappers for ordinary page, form, metadata, redaction, signing, and save workflows.
  • Use AddAsync(...), Update(...), and Delete(...) only when the integration owns the PDF syntax details being changed.
  • Deleting an object marks it free for the next update. It does not scrub old file bytes from previous revisions.

Related guide: How to inspect the PDF object graph in C#. For exact member signatures, use the API docs.

Diagnostics

Performance tracing

PerformanceTrace records process-wide timing entries for instrumented ZingPDF operations and caller-defined scopes.

using ZingPDF.Diagnostics;

PerformanceTrace.Reset();
PerformanceTrace.SetEnabled(true);

using var pdf = Pdf.Load(File.OpenRead("input.pdf"));
_ = await pdf.GetPageCountAsync();
_ = await pdf.ExtractTextAsync();

PerformanceTrace.SetEnabled(false);
Console.WriteLine(PerformanceTrace.GetSummary());

Set ZINGPDF_PERF_TRACE=1 before process startup to enable tracing without code changes. Tracing is diagnostic instrumentation, not an API-level timing guarantee.

Related guide: How to trace PDF performance in C#.

Technical FAQ

Technical FAQ

Memory model

ZingPDF keeps the input stream open and resolves PDF objects on demand. It does not materialize the whole file into a full in-memory document model at load time.

  • Pdf.Load(...) holds a seekable input stream for the lifetime of the Pdf instance.
  • Document versions, trailers, the catalog, and indirect objects are parsed lazily as code touches them.
  • Parsed objects are cached after resolution.
  • Compressed object streams use a bounded cache rather than staying permanently expanded in memory.
  • Some save paths still stage full output in memory, including signing and rewriting back to the source stream.

Thread safety

Pdf instances should be treated as not thread-safe. The document model is mutable, the object collection keeps mutable caches and update state, and parsing operates against a shared underlying stream. That model keeps single-document workflows fast and predictable.

  • Do not share a single Pdf instance across concurrent operations.
  • Use one document instance per logical request or workflow.
  • If you need parallel work, open separate Pdf instances on separate streams.

Malformed PDFs

ZingPDF tolerates some malformed content and common cross-reference defects, but it does not try to repair every damaged file structure.

  • Content stream parsing returns the successfully parsed prefix when unsupported or malformed sections appear later in the stream.
  • Cross-reference stream parsing repairs the common case where the xref stream does not include a reference to itself.
  • The loader can recover when startxref is missing or out of range and a classic xref table still exists near the end of the file.
  • ZingPDF does not yet rebuild a full xref graph by scanning every indirect object in the file.

Evaluation FAQ

Evaluation FAQ

Package selection

ZingPDF is the default package for loading, creating, editing, forms, encryption, and save workflows. Add companion packages only for the extra capability they provide.

  • Add ZingPDF.FromHTML for Chromium-based HTML-to-PDF.
  • Add ZingPDF.Templates.LiquidHtml for Liquid HTML templates that render to PDF.
  • Add ZingPDF.GoogleFonts for Google Fonts registration.
  • Add ZingPDF.OCR for OCR on scanned or image-based pages.

Licensing

Evaluation and other non-commercial use stay free. Paid seats are required when the library is used for commercial work or internal business operations outside genuine evaluation.

  • Seats are licensed per developer.
  • Contractors can use seats licensed to the same customer legal entity.
  • See the pricing section and legal terms for the exact commercial contract.

Deployment environments

ZingPDF targets net8.0 and is intended for Windows, Linux, and macOS deployments that support .NET 8.

  • The core library fits desktop apps, background jobs, services, workers, and CLI tools.
  • ZingPDF.FromHTML has additional browser-runtime requirements.
  • ZingPDF.Templates.LiquidHtml has the same browser-runtime requirements after Liquid rendering.
  • Input streams passed to Pdf.Load(...) must be seekable.

Current limits

ZingPDF covers the main document-editing path, but some PDF features are still outside the current high-level API.

  • Encrypted PDFs are outside the current signing path, and visible signing uses an existing signature field.
  • OCR works best on image-based pages and supported image XObjects rather than arbitrary rendered page content.
  • Push-button action dictionaries are exposed as metadata for viewers and editing software.
  • Malformed file repair is limited to a few common recovery paths rather than full document reconstruction.