Text extraction
var textItems = await pdf.ExtractTextAsync();
foreach (var item in textItems)
{
Console.WriteLine(item.Text);
}
Docs
Curated documentation for the main API surface, fluent authoring, package choices, and PDF behavior.
Quick Start
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.
dotnet add package ZingPDF
using ZingPDF;
using ZingPDF.Syntax.CommonDataStructures;
using var input = File.OpenRead("input.pdf");
using var pdf = Pdf.Load(input);
var pageCount = await pdf.GetPageCountAsync();
var firstPage = await pdf.GetPageAsync(1);
using var pdf = Pdf.Create(options =>
{
options.MediaBox = Rectangle.FromDimensions(595, 842);
});
using var output = File.Create("blank.pdf");
await pdf.SaveAsync(output);
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.
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.
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"));
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();
}
}
var textItems = await pdf.ExtractTextAsync();
foreach (var item in textItems)
{
Console.WriteLine(item.Text);
}
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
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.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.
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
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.
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.
ZingPDFThe main PDF library for loading, creating, editing, page rendering, merging, watermarking, compression, redaction, forms, metadata, and encryption.
net8.0PDFtoImage and SkiaSharp native assets, so test on your target platform
ZingPDF.FromHTMLHTML-to-PDF package for Chromium-based rendering.
net8.0ZingPDF.TemplatesShared source and diagnostic types for template renderer packages.
net8.0ZingPDF.Templates.LiquidHtmlLiquid HTML template package for creating PDFs from data models.
net8.0ZingPDF.FromHTML for HTML-to-PDF conversionZingPDF.FromHTMLZingPDF.GoogleFontsGoogle Fonts integration package for registering Google Fonts in a PDF document.
net8.0ZingPDF.OCROCR companion package for scanned and image-based pages, with options for embedded-text-first or OCR-first workflows.
net8.0ZingPDF unless you specifically need HTML
conversion.ZingPDF.Templates.LiquidHtml for Liquid variables, loops, and conditionals in HTML
templates that render to PDF.ZingPDF.GoogleFonts if you need to register Google Fonts directly from the Google
Fonts catalog.ZingPDF.OCR when the page is primarily image-based or when OCR is part of your extraction workflow.ZingPDF.FromHTML as a separate deployment concern with its own runtime
requirements.
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
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.
dotnet add package ZingPDF.Templates.LiquidHtml
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);
var html = await LiquidHtmlPdfTemplate
.FromFile("invoice.liquid.html")
.RenderHtmlAsync(invoice);
ZingPDF.FromHTML after Liquid rendering.PdfTemplateRenderException.Diagnostics for template parse, load, and render failures.Dedicated guide: Create a PDF from a Liquid HTML template.
Fonts
ZingPDF registers standard PDF fonts and embedded TrueType fonts. ZingPDF.GoogleFonts
adds Google Fonts registration through the same document-level font path.
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);
await Pdf.New()
.Page(page => page
.Text(text => text
.Value("Hello, world")
.Helvetica()
.FontSize(18)
.At(48, 780)))
.SaveToFileAsync("hello.pdf");
StandardPdfFonts.HelveticaStandardPdfFonts.HelveticaBoldStandardPdfFonts.HelveticaObliqueStandardPdfFonts.HelveticaBoldObliqueStandardPdfFonts.TimesRomanStandardPdfFonts.TimesBoldStandardPdfFonts.TimesItalicStandardPdfFonts.TimesBoldItalicStandardPdfFonts.CourierStandardPdfFonts.CourierBoldStandardPdfFonts.CourierObliqueStandardPdfFonts.CourierBoldObliquevar font = await pdf.RegisterTrueTypeFontAsync(
"MyFont-Regular.ttf");
await page.AddTextAsync(
"Custom font text",
Rectangle.FromDimensions(240, 80),
font,
18,
RGBColour.Black);
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");
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);
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
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.
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)
]));
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)
]));
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");
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.
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.
Metadata
GetMetadataAsync() reads and updates the trailer Info dictionary through a
higher-level wrapper.
Title, Author, Subject, Keywords,
Creator, and CreationDate.
Producer is stamped to ZingPDF and
ModifiedDate is refreshed on save.
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
GetFormAsync() returns a Form wrapper for terminal field enumeration, typed
field lookup, and field-family-specific behavior.
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)));
Form.GetFieldsAsync() returns terminal fields as IFormField instances.Applicant.Address.Suburb.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");
}
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);
}
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"));
TextFormField is the simplest path when you need to fill names, dates, IDs, or other typed values.
GetValueAsync().SetValueAsync(...) and ClearAsync().Form.GetFieldAsync<TextFormField>(name).ChoiceFormField covers dropdowns and list boxes.
GetOptionsAsync().SelectOptionByTextAsync(...) and SelectOptionByValueAsync(...).Form.GetFieldAsync<ChoiceFormField>(name).CheckboxFormField and RadioButtonFormField expose selectable options through SelectableOption.
GetOptionsAsync().SelectAsync() and DeselectAsync().PushButtonFormField is read-only at the high level today.
SignatureFormField supports inspection, signing, and validation.
SignAsync(...) to sign an existing visible field.Pdf.SignInvisibleAsync(...) when you only need cryptographic validation and want to add a hidden field automatically.Pdf.GetSignaturesAsync() or ValidateSignatureAsync(...) to validate signed byte ranges and detached CMS payloads.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());
}
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.
SignAsync(...).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
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.
using var pdf = Pdf.Load(File.OpenRead("encrypted.pdf"));
await pdf.AuthenticateAsync("password");
var pageCount = await pdf.GetPageCountAsync();
using var pdf = Pdf.Create();
await pdf.EncryptAsync("user-password", "owner-password");
await pdf.SaveAsync(File.Create("protected.pdf"));
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"));
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.
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
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.
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"));
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.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
By default, SaveAsync(...) writes an incremental update. That preserves earlier revisions
in the file history.
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"));
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"));
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.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
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.Low-Level 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}");
}
AddAsync(...), Update(...), and Delete(...) only when the
integration owns the PDF syntax details being changed.Related guide: How to inspect the PDF object graph in C#. For exact member signatures, use the API docs.
Diagnostics
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
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.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.
Pdf instance across concurrent operations.Pdf instances on separate streams.ZingPDF tolerates some malformed content and common cross-reference defects, but it does not try to repair every damaged file structure.
startxref is missing or out of range and a classic xref table still exists near the end of the file.Evaluation FAQ
ZingPDF is the default package for loading, creating, editing, forms, encryption, and save
workflows. Add companion packages only for the extra capability they provide.
ZingPDF.FromHTML for Chromium-based HTML-to-PDF.ZingPDF.Templates.LiquidHtml for Liquid HTML templates that render to PDF.ZingPDF.GoogleFonts for Google Fonts registration.ZingPDF.OCR for OCR on scanned or image-based pages.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.
ZingPDF targets net8.0 and is intended for Windows, Linux, and macOS deployments that support
.NET 8.
ZingPDF.FromHTML has additional browser-runtime requirements.ZingPDF.Templates.LiquidHtml has the same browser-runtime requirements after Liquid rendering.Pdf.Load(...) must be seekable.ZingPDF covers the main document-editing path, but some PDF features are still outside the current high-level API.