Compress, optimize, edit and create PDF files in .NET, and convert HTML and CSS to PDF. Pure C#, MIT licensed, no native binaries, no dependencies on .NET 8 and later.
PdfNetStack makes PDFs smaller without changing a single rendered pixel. It rewrites the file with compressed object streams and a compressed cross-reference stream, drops objects nothing points to, recompresses streams, adds PNG prediction to raw images, and merges duplicate fonts, images and resources. Then it reads its own output back and proves, object by object, that the document is the same as the one you gave it. If anything is off, you get your original bytes back.
using PdfNetStack;
var result = await PdfOptimizer.OptimizeFileAsync("manual.pdf", "manual.optimized.pdf");
Console.WriteLine($"{result.Disposition}: saved {result.SavingPercent:F1}%");PdfNetStack exists to give every .NET developer a PDF toolkit that is permissively licensed all the way down, safe to run on files from strangers, and honest about what it changes. It optimizes, edits, merges, splits and protects PDFs, and is growing toward full PdfSharp parity under MIT. It is created and maintained by Jamal Abdelkhalek at JADEV.
| Target | Runs on | Dependencies |
|---|---|---|
net8.0, net10.0 |
.NET 8, 9, 10 and later | None |
netstandard2.0 |
.NET Framework 4.6.2 to 4.8.1, Mono, Unity, Xamarin, .NET Core 2.0+ | System.Memory (Microsoft, MIT) |
CI runs the full test suite on .NET Framework 4.8, .NET 8 and .NET 10, on Windows, Linux and
macOS. The whole real world corpus also passes on .NET Framework 4.8. One difference: .NET
Standard's zlib has no level 9, so CompressionEffort.Maximum compresses a little less there.
Most PDF compression in the .NET world shells out to Ghostscript, depends on a copyleft or commercial library, or re-distills the document and quietly loses forms, tags or metadata. PdfNetStack takes a narrower job and does it with proof:
- Lossless by construction. Text, vectors, fonts, images, annotations, forms, bookmarks, structure tags, metadata and attachments are carried over. Nothing is rasterized.
- Verified, not assumed. Every rewrite is re-parsed and compared to the source, with stream content compared after decoding. A mismatch means the original is returned.
- Fail safe. Encrypted, signed and damaged files are returned byte for byte. The output is always a valid PDF: the verified rewrite or your input.
- No native code, no processes. One managed assembly for .NET 8 and .NET 10, trim and AOT compatible, same behaviour on Windows, Linux and macOS.
- MIT all the way down. On .NET 8 and later the core package has no dependencies; on .NET
Standard 2.0 it uses Microsoft's
System.Memory. CI blocks any runtime package that is not on a reviewed license allowlist.
Optimization is mostly parsing, hashing and zlib. Modern .NET does all three at close to native
speed (Span<T>, SIMD-backed hashing, zlib-ng under System.IO.Compression on .NET 9+), and the
work parallelizes across streams. A Rust or C++ core would mean shipping native binaries per
platform and a marshalling layer, for little measured gain. If profiling ever shows a hot spot
worth it, the pipeline stages are isolated enough to swap one out.
dotnet add package PdfNetStack # library
dotnet tool install -g PdfNetStack.Tool # command line: pdfnetstack
dotnet add package PdfNetStack.Azure # optional Blob Storage pipeline
dotnet add package PdfNetStack.Html # HTML and CSS to PDF// In memory
(byte[] output, PdfOptimizationResult result) = PdfOptimizer.Optimize(File.ReadAllBytes("in.pdf"));
// Streams
await using var input = File.OpenRead("in.pdf");
await using var output = File.Create("out.pdf");
PdfOptimizationResult r = await PdfOptimizer.OptimizeAsync(input, output);
// Options
var options = new PdfOptimizerOptions
{
Compression = CompressionEffort.Balanced, // faster, slightly larger
AllowIncrementalUpdates = true, // flatten edit history into one revision
MaxDegreeOfParallelism = 2, // cap CPU and memory per document
JpegQuality = 60, // opt-in, lossy: requantize JPEG images
};PdfOptimizationResult tells you what was written and why: Disposition, sizes, SHA-256 of
input and output, page count, a human readable Reason, and per pass statistics and timings.
using var input = File.OpenRead("contract.pdf");
PdfDocument doc = PdfDocument.Open(input);
doc.Pages.RemoveAt(0); // drop the cover
doc.Pages.Move(2, 0); // reorder
doc.Pages[0].Rotate(90); // rotate clockwise
doc.Pages.Add(PdfRectangle.A4); // blank page
PdfDocument annex = PdfDocument.Open("annex.pdf");
doc.Pages.Import(annex.Pages); // fonts and images shared between pages are copied once
doc.Info.Title = "Signed contract";
doc.Outlines.Add("Annex", doc.Pages[^1]); // bookmarks, read and written
doc.Save("contract-final.pdf"); // optimized and verified by default
PdfDocument merged = PdfDocument.Merge([PdfDocument.Open("a.pdf"), PdfDocument.Open("b.pdf")]);
foreach (PdfDocument single in merged.Split()) { /* one document per page */ }Inherited page attributes (resources, media box, rotation) are materialized on each page, so pages survive reordering and moving between documents. Annotations travel with their page and are copied fresh for every import; links to pages imported in the same call are remapped, links to pages left behind become empty. Editing a signed document invalidates its signatures.
using PdfNetStack.Drawing;
PdfDocument doc = PdfDocument.Create();
PdfPage page = doc.Pages.Add(PdfRectangle.A4);
using (PdfGraphics g = page.CreateGraphics()) // origin top left, y down, points
{
var header = new PdfLinearGradientBrush(new PdfPoint(0, 0), new PdfPoint(595, 0),
PdfColor.FromHex("#1E3A8A"), PdfColor.FromHex("#2563EB"));
g.DrawRectangle(null, header, 0, 0, 595, 140);
g.DrawRoundedRectangle(new PdfPen(PdfColor.FromHex("#CBD5E1")), PdfColor.White, 40, 170, 515, 220, 16);
g.DrawEllipse(null, PdfColor.FromHex("#14B8A6").WithAlpha(0.5), 60, 430, 180, 180);
PdfFont bold = PdfStandardFont.HelveticaBold; // using PdfNetStack.Fonts
g.DrawString("Invoice 2026-114", bold, 28, PdfColor.White, 40, 80); // x, baseline y
double width = bold.MeasureString("Total: €1,250.00", 14);
g.DrawString("Total: €1,250.00", bold, 14, PdfColor.Black, 555 - width, 360);
}
doc.Save("drawing.pdf");Paths with lines, Bezier curves, arcs, ellipses, polygons and rounded rectangles (elliptical
corners sized like CSS border-radius); pens with caps, joins and dashes; gray, RGB and CMYK
colors with opacity; linear and radial gradients; transforms, saved states and clipping.
CreateGraphics(PdfGraphicsLayer.Background) draws underneath a page's existing content, for
letterheads and watermarks. The API follows PdfSharp's XGraphics naming.
DrawInvisibleText(text, x, y, width, height) writes text that is not drawn but selects, copies
and searches, stretched to fill its box: put the words any OCR engine found over a scanned page
and the page becomes searchable. Any script works, without a font to embed beyond a tiny built in
one, and the result passes PDF/A-2u.
The 14 standard PDF fonts (Helvetica, Times, Courier with their bold and italic styles, Symbol, ZapfDingbats) need no embedding, so a page of text weighs about 2 KB. They cover Western and Central European languages (Windows-1252 plus Ł, ő, č and the like).
Any TrueType or OpenType font works too, embedded as a subset of the glyphs you draw:
using PdfNetStack.Fonts;
PdfFont inter = PdfOpenTypeFont.Load("fonts/Inter-Regular.ttf"); // .ttf .otf .ttc .woff
PdfFont? title = PdfFontCollection.System.Find("Segoe UI", weight: 600); // installed fonts
var brand = new PdfFontCollection();
brand.AddDirectory("fonts");
PdfFont heading = brand.Find("Inter", weight: 700) ?? PdfStandardFont.HelveticaBold;Embedded text is kerned from the font's GPOS or kern table, stays searchable and copyable in
any script the font covers (a ToUnicode map is written), and the subset keeps only the outlines
used, so a page in Inter costs a few KB. PdfFontCollection matches family, weight and italic
the way CSS does. Text can be filled with a gradient, outlined with a pen, or both;
MeasureString, Ascent, Descent and GetLineHeight give the metrics for layout. Fonts are
parsed defensively: a damaged file raises FontFormatException, and a font whose license
forbids embedding (OS/2 fsType) is refused. Not supported yet: WOFF2, CFF2 variable fonts.
Paragraphs wrap, align and justify, with rich text and right to left scripts:
var body = new PdfTextStyle(inter, 11) { Fallbacks = [notoArabic] };
var p = new PdfParagraph { Alignment = PdfTextAlignment.Justify, LineHeight = 1.5 }
.Add("Payment is due within ", body)
.Add("30 days", body with { Font = interBold })
.Add(". المبلغ المستحق: 1,250.00 €", body);
PdfTextLayout layout = g.DrawText(p, 50, 120, 300); // layout.Height is the room it tookText goes through the Unicode line breaking (UAX #14) and bidirectional (UAX #9) algorithms,
both passing the full Unicode conformance suites, and an OpenType shaper that matches HarfBuzz
glyph for glyph: ligatures, kerning, Arabic joining and vowel marks. Lines break greedily at
break opportunities, overlong words break between characters, soft hyphens show a hyphen when
used, and line height follows CSS. PdfTextLayout measures before drawing and can draw a range
of lines, for text that continues on the next page.
Images and reusable drawings:
PdfImage photo = PdfImage.Load("photo.jpg"); // JPEG copied as is, EXIF orientation honored
PdfImage logo = PdfImage.Load("logo.png"); // PNG with alpha becomes a soft mask
g.DrawImage(logo, 40, 30, 120, 40);
PdfSvg vector = PdfSvg.Load("logo.svg"); // SVG stays vector: sharp at any zoom
g.DrawSvg(vector, 40, 80, 120, 40);
PdfForm page = PdfForm.FromPage(doc, other.Pages[0]); // any page of any document
g.DrawForm(page, 0, 0, g.Width / 2, g.Height / 2); // n-up, stamps, templatesPNGs of every color type and bit depth are supported, interlaced or not; simple ones keep their compressed data, so embedding costs no recompression. ICC profiles and pixel density are kept. SVG drawings keep their paths, gradients, clipping and text as PDF vectors; filters, masks and patterns are not drawn. Each image, drawing and font is written once per document however often it is drawn.
Annotations use the same top left coordinates and carry their own appearance, so every viewer shows them alike:
page.Annotations.AddLink(72, 700, 120, 14, new Uri("https://example.com"));
page.Annotations.AddLink(72, 720, 120, 14, doc.Pages[3], top: 200);
page.Annotations.AddTextMarkup(PdfTextMarkup.Highlight, 72, 100, 300, 16, comment: "Check");
page.Annotations.AddNote(520, 40, "Please review", author: "Finance");
g.AddLink(0, 0, 100, 20, new Uri("https://example.com")); // follows the canvas transformFill and flatten forms:
PdfDocument doc = PdfDocument.Open("application.pdf");
((PdfTextField)doc.Form["name"]!).Text = "Jamal Abdelkhalek";
((PdfCheckBoxField)doc.Form["subscribe"]!).Checked = true;
((PdfRadioButtonField)doc.Form["plan"]!).Selected = "pro";
((PdfChoiceField)doc.Form["country"]!).Select("Morocco");
doc.Form.Flatten(); // optional: values become page content
doc.Save("filled.pdf");Every field kind is read (text, check box, radio group, combo and list box, push button, signature) with fully qualified names, inherited attributes and widget pages. Setting a value redraws the field's appearance from its border, background, font and color settings, with multiline wrapping, comb cells, password masking and automatic font size, so filled forms look the same in every viewer. A font the form embeds whole is reused; others map to the closest standard font.
Document settings: doc.Viewer (layout, panels, opening page, right to left reading),
doc.PageLabels (i, ii, iii then 1, 2, 3), doc.Attachments (embedded files, as e-invoices
use) and doc.Language.
PDF/A for archiving and e-invoices:
doc.Attachments.Add("factur-x.xml", xml, "text/xml");
doc.Save("invoice.pdf", new PdfSaveOptions { Conformance = PdfConformance.PdfA3B });Saving as PDF/A-2b, 2u, 3b or 3u first checks what cannot be repaired (fonts that are not embedded, DeviceCMYK, encryption, scripts, attachments in PDF/A-2) and lists every problem at once, then adds an sRGB output intent (a profile generated by the library, so nothing third party ships), XMP metadata matching the document information, and the associated file entries PDF/A-3 requires.
Digital signatures (PAdES baseline B-B):
using PdfNetStack.Signing;
X509Certificate2 cert = /* from a .pfx, the certificate store, or a smart card */;
byte[] signed = PdfSigner.Sign(File.ReadAllBytes("contract.pdf"), cert, new PdfSignatureOptions
{
Reason = "Approved",
Location = "Geneva",
Appearance = new PdfSignatureAppearance(PageIndex: 0, X: 340, Y: 700, Width: 210, Height: 60),
});
foreach (PdfSignatureInfo s in PdfSigner.Verify(signed))
Console.WriteLine($"{s.FieldName}: {s.SignerName}, intact {s.IsIntact}, whole file {s.CoversWholeDocument}");Signing appends an incremental update, so earlier signatures stay valid, and writes a CAdES
detached CMS signature (SHA-256, 384 or 512; RSA or ECDSA keys) built by the library, with no
extra package. OpenSSL verifies the result. Verify checks every signature's byte range, digest
and cryptographic validity and returns the certificate for your own trust decision.
PdfDocument doc = PdfDocument.Open("report.pdf");
doc.Security.Encrypt(userPassword: "read", ownerPassword: "admin",
permissions: PdfPermissions.Print | PdfPermissions.CopyContent); // AES-256 by default
doc.Save("report-protected.pdf");
PdfDocument opened = PdfDocument.Open("report-protected.pdf", password: "admin");
opened.Pages.RemoveAt(0);
opened.Save("trimmed.pdf"); // keeps the original passwords and permissions
opened.Security.RemoveEncryption(); // or save it unprotectedOpening supports every standard security handler revision: RC4 40 to 128 bit, AES-128 and AES-256 (revisions 2 to 6), user and owner passwords, crypt filters and unencrypted metadata. New protection is written as AES-256 (revision 6) or AES-128 (revision 4). A document opened with only the user password can be read but not saved, and pages can be copied out of it only when its permissions allow copying.
PdfDocument doc = PdfDocument.Open("scan.pdf");
foreach (PdfPageImage image in doc.Pages[0].GetImages())
{
Console.WriteLine($"{image.Width}x{image.Height} at {image.Bounds}, {image.HorizontalResolution:0} dpi");
if (image.Encoding == "DCTDecode") File.WriteAllBytes("photo.jpg", image.GetEncodedData()); // the JPEG as is
else image.Decode()?.SavePng("image.png"); // pixels
}GetImages walks the page content: image XObjects, inside forms too, and inline images, each
with the matrix that places it. Decode gives 8 bit gray or RGB pixels for raw samples of 1 to 16
bits, JPEGs (decoded as libjpeg does) and CCITT fax (Group 3 and 4, bit exact with libtiff), through DeviceGray, RGB and CMYK, Cal and ICC based
spaces, Indexed, Lab, Separation and DeviceN, with the Decode array and stencil masks applied.
using PdfNetStack.Html;
byte[] pdf = HtmlConverter.ConvertToPdf(html); // a string
PdfDocument doc = HtmlConverter.ConvertFileToDocument("invoice.html"); // a file and its assetsA layout engine in C#, no browser: the CSS cascade, block, inline, table, flex and grid layout,
floats and positioning, paged media with @page and named pages, running headers and footers
with page counters and named strings, repeated table headers, web fonts, right to left and Arabic text, links and bookmarks.
Without a resource loader nothing is fetched from the network. Details, options and limits in the
package readme.
pdfnetstack optimize in.pdf out.pdf
pdfnetstack batch ./incoming ./optimized --report report.csv
pdfnetstack batch ./corpus # measure only, writes nothing
pdfnetstack optimize in.pdf out.pdf --jpeg-quality 60 # lossy, JPEG images only
pdfnetstack inspect in.pdf # where the bytes go
pdfnetstack verify original.pdf other.pdf # check any tool's output for equivalence
pdfnetstack merge out.pdf a.pdf b.pdf c.pdf
pdfnetstack split in.pdf ./pages
pdfnetstack extract in.pdf out.pdf 1-3,7,9- # page ranges, any order, repeats allowed
pdfnetstack rotate in.pdf out.pdf 90 2-4
pdfnetstack meta in.pdf out.pdf --title "Annual report" --author "Finance"
pdfnetstack encrypt in.pdf out.pdf --user-password u --owner-password o --allow print,copy
pdfnetstack decrypt in.pdf out.pdf --password ovar pipeline = new BlobPdfOptimizer(new Uri("https://account.blob.core.windows.net"));
BlobOptimizationOutcome outcome = await pipeline.OptimizeAsync("incoming", "docs/manual.pdf", "optimized");Uses managed identity through DefaultAzureCredential, never account keys. The source blob is
read at a pinned ETag and never modified. Output goes to a different container under
pdf/<sha256 prefix>/<sha256>.pdf with a conditional write, so retries and concurrent workers
are idempotent. A JSON sidecar records source URI, ETag, both hashes, sizes, disposition, reason,
engine version and timings.
| Pass | Effect | Lossless |
|---|---|---|
| Garbage collection | Drops objects unreachable from the catalog, including old revisions | Yes |
| Object streams | Packs non-stream objects into Flate compressed object streams (PDF 1.5) | Yes |
| Xref stream | Replaces the text xref table with a predictor encoded, compressed stream | Yes |
| Stream recompression | Removes ASCIIHex/ASCII85 layers, turns LZW and RunLength into Flate, recompresses Flate at zlib level 9, keeps a result only when smaller | Yes |
| Image prediction | Tries PNG predictors on raw images of 8 bits or less per component | Yes |
| JPEG re-encoding | Rebuilds each JPEG's Huffman tables for that image, writes progressive and restart-marked JPEGs as plain baseline, drops segments PDF readers ignore (Exif, XMP, embedded ICC, comments). Works on the DCT coefficients, which stay identical | Yes |
| Deduplication | Merges byte identical fonts, images, ICC profiles, resource dictionaries and arrays, iterating until merges stop | Yes |
| Compact syntax | Minimal separators, canonical numbers, null entries dropped | Yes |
| Thumbnails, PieceInfo | Optional removal of page thumbnails and application private data | No, opt-in |
| JPEG requantization | JpegQuality: coarser quantization at a libjpeg quality, never finer than the image's own; soft masks stay lossless |
No, opt-in |
JPEG data is decoded to DCT coefficients and never to pixels, so components, chroma sampling and color model pass through untouched: a CMYK image stays CMYK. JPEG 2000, JBIG2 and CCITT data is never decoded or touched.
The optimizer refuses to rewrite, and returns the input unchanged, when the file is:
- encrypted,
- digitally signed or has usage rights (any
/ByteRange, signature dictionary,/Permsor/DSS), - incrementally updated, unless
AllowIncrementalUpdatesis set, - damaged: xref reconstruction, stream length repair, unreadable objects, or references whose
generation numbers disagree with the xref, unless
AllowRepairedFilesis set, - larger than
MaxInputBytes.
PDF/A-1 files are detected from their XMP metadata and written with a classic xref table and no object streams, because PDF/A-1 predates both.
Hostile input is bounded: nesting depth is capped, decoded stream size is capped
(MaxDecodedStreamBytes, default 256 MiB) as a zip bomb defence, /Prev loops terminate,
wrong stream lengths are recovered from endstream, and unreadable objects never crash the run.
The verifier walks the source and output graphs in lockstep from the trailer. Dictionaries
must have the same keys and values, arrays the same items, references must line up, and every
stream must decode to identical bytes with identical remaining filters. JPEG images are
compared by DCT coefficient, so a re-encoded image passes only if every coefficient is the
same. Encoding keys (/Length, /Filter, /DecodeParms) are the only other differences it
accepts. With JpegQuality set, a JPEG image may also differ by exactly the requantization the
optimizer applies, checked coefficient by coefficient, and by nothing else.
See docs/FIDELITY.md for the full method and its limits.
Measured on a mixed real world corpus of 1,296 files (569 MiB): invoices, reports, manuals, scanned documents, presentations and a 631 page spare parts catalogue, on an 8 core Windows machine with default options.
| Optimized | 1,003 files, total size 568.9 MiB to 469.0 MiB (17.6% saved) |
| Returned unchanged | 38 below the 1% threshold, 130 refused by the safety rules, 125 not PDFs (empty or resource fork files) |
| Verification failures | 0 |
| Throughput | about 65 s for the whole corpus, 8 to 9 MB/s, lossless JPEG re-encoding included |
With AllowIncrementalUpdates and AllowRepairedFiles |
1,118 files optimized, 20.1% saved |
Independent render check: every optimized file was rendered with PDFium at 150 DPI and compared with the original. 1,118 documents, 2,161 pages, zero differing pixels, identical extracted text. The script is in tools/render_compare.py.
The 631 page catalogue, 72.6 MiB of CAD drawings already Flate compressed: 6,075 duplicate objects merged, 3.1% saved, about 5 seconds with all cores, peak working set about 690 MB. Image heavy files gain the most, often 20 to 40%, mostly from PNG prediction on raw images.
Editing path: every document in the corpus was rebuilt by importing all of its pages into a new document and saving it. 1,152 documents, 2,233 pages, zero differing pixels against the originals at 150 DPI.
Encryption: AES-256 and AES-128 files written by PdfNetStack open in PDFium with either password and render identically to their sources; real AES-128 contracts from other producers decrypt to pixel identical output; RC4 files written for the legacy read path render identically in PDFium.
Your mileage depends on what produced the PDF. Files from well tuned producers that already use object streams have little left to gain losslessly.
| Option | Default | Notes |
|---|---|---|
MinSavingPercent |
1.0 | Keep the original unless the rewrite saves at least this much |
Compression |
Maximum |
Balanced cut corpus time by about a third for about 2% larger output |
UseObjectStreams |
true | Off writes a classic xref table |
ObjectsPerStream |
200 | Larger compresses better, smaller loads single pages faster |
DeduplicateObjects |
true | |
RecompressStreams |
true | |
UseImagePredictors |
true | |
OptimizeJpegImages |
true | Lossless JPEG re-encoding |
JpegQuality |
null | 1 to 100, libjpeg scale. Lossy, JPEG images only |
Verify |
true | Disable only for inputs you already trust |
AllowIncrementalUpdates |
false | Flattens edit history into one revision |
AllowRepairedFiles |
false | Rewrites damaged files into clean ones |
RemovePageThumbnails |
false | Not lossless |
RemovePieceInfo |
false | Not lossless for the authoring app, invisible to viewers |
MaxInputBytes |
1 GiB | |
MaxDecodedStreamBytes |
256 MiB | Per stream decode ceiling |
MaxDegreeOfParallelism |
CPU count | Lower it to cap memory on image heavy files |
PdfNetStack is growing into a full MIT alternative to PdfSharp, then an HTML and CSS to PDF engine in pure C#, each step with the same test and corpus discipline as the optimizer. Details in the roadmap.
| Step | Scope | Status |
|---|---|---|
| Editing | Open, create, merge, split, import, reorder, rotate and remove pages; document info; bookmarks | Done (1.0) |
| Encryption | Open RC4, AES-128 and AES-256 files; write AES-128/256 with permissions | Done (1.0) |
| Object model | Public PdfDictionary, PdfArray, PdfStream and friends for anything the API lacks |
Done (next release) |
| Drawing | Canvas on new and existing pages: paths, colors, transforms, clipping, gradients | Done (next release) |
| Fonts | Standard 14 fonts, TrueType/OpenType/WOFF embedding with subsetting, kerning, font collections | Done (next release) |
| Text layout | Line breaking, alignment, justification, rich text, Arabic and RTL | Done (next release) |
| Images and overlays | JPEG and PNG placement, pages of other PDFs as watermarks, letterheads, n-up | Done (next release) |
| Annotations | Web and page links, notes, highlight, underline, strikeout, free text, blend modes | Done (next release) |
| Forms (AcroForm) | Reading, filling with generated appearances, flattening | Done (next release) |
| Signatures and PDF/A | PAdES B-B signing and verification, PDF/A-2b, 2u, 3b and 3u output | Done (next release) |
| HTML and CSS | PdfNetStack.Html: CSS cascade, block, inline, table, flex and grid layout, floats, positioning, paged media, web fonts |
Done (next release) |
| Image extraction | Images per page with placement and resolution; JPEG and raw samples to pixels; PNG export | Done (next release) |
using PdfNetStack.Objects;
PdfDocument doc = PdfDocument.Open("report.pdf");
doc.Catalog.Set("PageMode", new PdfName("UseOutlines")); // open with bookmarks shown
var content = PdfStream.FromData("0 0 1 rg 72 72 144 144 re f"u8);
doc.Pages[0].Dictionary.Set("Contents", doc.AddObject(content)); // replace page content- Lossy compression covers JPEG images only and works on their coefficients: it does not downsample, does not convert raw images to JPEG, and has no perceptual quality gate yet. See the roadmap.
- Arithmetic coded, 12 bit, lossless and hierarchical JPEGs, JPEGs with more than 64 scans, and damaged JPEG data, are carried over untouched.
- Font subsetting, unused resource pruning and content stream rewriting are not done.
- Image extraction decodes raw samples, JPEGs and CCITT fax. JBIG2 and JPEG 2000 images come back
encoded (
GetEncodedData), soft masks are not applied, and Separation and DeviceN colors other than a Type 2 tint transform are approximated as gray. - Linearized (fast web view) output is not produced; linearization is dropped.
- The whole document is held in memory. Peak memory depends on decoded image sizes and on
parallelism: plan for about 10 times the file size, more for image heavy files, or lower
MaxDegreeOfParallelism.
MIT. See LICENSE. Runtime dependency licenses are listed in THIRD-PARTY-NOTICES.md.