Repository navigation
PdfNetStack 1.1.0
-
QuantizeImages(--quantize) reduces raw 8 bit RGB images to a palette of at most 256 colors in
their own color space, for the drawings and screenshots where JPEG rings around fine labels and
barely saves anything. Each image gets the smallest palette, of 256, 64, 16 or 4 colors, that
keepsMinImageSsim, or 0.97 without a gate, and no more than 8 sample steps of mean change with
at most 1% of pixels moved by over 32, which catches a hue drift luminance cannot see. A color
holding most of its cluster stays exact, so white stays white. WithConvertImagesToJpegthe
smaller candidate wins image by image. The proof expands the palette and checks the same bounds,
andPdfEquivalence.FindDifferenceaccepts such images when given a minimum SSIM. On a technical
manual full of screenshots and diagrams,JpegQuality = 50alone saved 18.0% and adding
QuantizeImagessaved 47.9%.QuantizeImagesalone saved 43.7%, with pages rendered by PDFium
closer to the source than withJpegQuality = 50alone (mean SSIM 0.9999 against 0.9996). The
pass costs CPU: about 2.7 times as long on that file, single threaded. On the public corpus it
took the lossy profile from 29.5% to 32.1% with no failure: 15 of the 65 rewritten documents got
smaller, up to 45%, and none got larger. -
The corpus benchmark measures a third profile,
balanced, the preset of that name: 23.4% on the
public corpus, at full resolution, with no page below its PSNR warning.--profilespicks others. -
PdfOptimizerOptions.BalancedandPdfOptimizerOptions.Smallest(--preset balanced,
--preset smallest) are ready sets of lossy options.Defaultstays lossless. -
The corpus benchmark also runs for every version tag and attaches its report to the GitHub
release, with a comparison against the report of the previous release made by
tools/corpus_compare.py: savings, time and peak memory per profile, and per document any
rewrite that grew by more than 1% of the original, took 1.5 times as long, needed 1.3 times the
memory, stopped being rewritten or now fails a check. Reports record each document's SHA-256,
so only identical files are compared. -
Opening documents with control over memory, for serverless hosts and large files.
PdfLoadOptionsgathers the password,MaxDecodedStreamBytes, a newMaxImagePixels, the
Mode(PdfLoadMode.InMemoryorOnDemand),SpoolThresholdandTempDirectory, taken by
new overloadsPdfDocument.Open(string, PdfLoadOptions),Open(Stream, PdfLoadOptions)and
Open(ReadOnlyMemory<byte>, PdfLoadOptions); the existing overloads keep working and call them.- The input is read once.
Open(Stream)copied a stream into a growing buffer and then into an
array; now aMemoryStreamwhose buffer is exposed is used without copying, a seekable stream
is read into an array of its length (4.0 MB allocated for a 4.0 MB file, was 8.0 MB from a
MemoryStream), and any other stream is read in pieces joined once. - On demand from a path, the document opens the file itself and keeps it until it is disposed.
PdfDocumentis nowIDisposable: disposing a document read on demand closes what it opened
and makes later use throwObjectDisposedException; the caller's stream is never disposed,
and disposing a document in memory does nothing. - A stream that cannot seek (an HTTP request body, a storage download) can be read on demand:
OpenOnDemandandOpencopy it to memory up toSpoolThreshold(16 MiB) and past it to a
temporary file inTempDirectory(the system's temporary folder by default; /tmp on AWS
Lambda), opened withDeleteOnCloseon Windows and unlinked once open elsewhere, and deleted
when the document is disposed or opening fails. PdfDocument.OpenAsync(Stream, PdfLoadOptions?, CancellationToken)reads the stream with
asynchronous reads only, for hosts that forbid synchronous I/O, and deletes any partial copy
when cancelled.MaxImagePixels(2^28 by default, the previous fixed cap) refuses an image with more pixels
before its pixels are allocated, withPdfFormatException, for raw samples, JPEG, JPEG 2000,
JBIG2 and CCITT images, by the dictionary's size and by the size the data declares.
PdfImage.Load(byte[], int, long maxPixels)does the same for JPEG, PNG, GIF, BMP, TIFF and
WebP files, withFormatException.PdfDocument.ReleaseUnusedObjects()frees the objects a document read on demand has parsed
and nothing uses any more; they are parsed again when needed. Objects that differ from the
file, and objects still referenced anywhere (found by a full garbage collection), are kept,
so no edit is lost. Reading 300 pages that each have their own font kept 1,503 parsed objects
and 7.8 MB; releasing every 50 pages kept 301 objects and 1.0 MB.
- The input is read once.
-
PdfDocument.Save(Stream)writes progressively whenPdfSaveOptions.Optimizeis off: each
object, or batch of objects for an object stream, goes to the stream as soon as it is
serialized, offsets are counted as bytes go out, and the cross-reference data comes last.
Memory beyond the document follows the largest object, not the file; a document opened with
OpenOnDemandhas its stream data copied from the source as it is written. The bytes are the
onesToArrayreturns for the same options. On a generated 278 MB file opened on demand, an
unoptimized save peaked at 3 MB of live memory (51 MB working set) instead of 835 MB, and took
0.9 to 1.2 s instead of 1.4 to 5.6 s on a busy machine. With the optimizer on, the default,
the file is still built in memory, as the proof needs all of it; the XML docs say so. -
Without the optimizer, encryption is applied in the same single pass; the optimized path still
encrypts the proven file as a last step. -
PdfDocument.SaveAsync(Stream, PdfSaveOptions?, CancellationToken)and
SaveAsync(string, ...): the same output, written with asynchronous calls only and flushed,
for response bodies in ASP.NET Core and Azure Functions isolated, which refuse synchronous IO.
Cancellation is observed before each object and by each write; a cancelled or failed save
throws, andSaveAsync(path)then leaves the file as it was. -
HtmlConverter.ConvertToPdfAsync(html, stream, options, cancellationToken): layout on its own
thread with no caller thread waiting, thenSaveAsync. -
Streaming optimizer:
PdfOptimizerOptions.Streaming. The source is read on demand (as
OpenOnDemandreads it), streams are recompressed a batch at a time (at most 32 MiB of source
data) and written straight away, and the rewrite goes to a temporary file: the destination's
own temporary file forOptimizeFileAsync, otherwise one in the system temporary folder. The
proof is the in-memory proof, run on a reader over that file: every reachable object compared,
every stream by decoded content. Duplicates are found from what streams decode to before
anything is written, so the output can differ from the in-memory rewrite (JPEG files differing
only in metadata stay two); on the test documents and the benchmark file it is the same.
OptimizeAsyncreads aFileStreamorMemoryStreamin place and copies any other input to a
temporary file first, asynchronously; the caller's output is only written asynchronously.
Linearization cannot be combined with it.OptimizationStatistics.Streamedreports the mode,
andpdfnetstack optimize --streamingturns it on, without an input size limit. On a generated
278 MB file: 50 MB of live memory (300 to 460 MB working set) against 810 MB (1.4 GB), and no
slower (8 to 11 s against 10 to 15 s on a busy machine), measured by
tools/PdfNetStack.MemoryBench. -
Serverless and containers. The core, HTML and OCR libraries publish with Native AOT and
trimming with no warnings (PdfNetStack.Ocris now marked AOT compatible too), checked in CI
on linux-x64 and linux-arm64 for .NET 8 and .NET 10 bytests/PdfNetStack.AotSmoke, which roots
all three libraries and then creates, opens, optimizes, extracts text and images from, reads
predefined CJK CMaps in and converts HTML to PDF. CI also runs the test suite with invariant
globalization and under tr-TR (PDFNETSTACK_TEST_CULTUREsets the culture of a test run). -
PdfFontCollection.AddDirectoryandPdfFontCollection.Systemskip a directory that cannot be
listed instead of giving up on the whole tree, search at most 16 levels deep without following
links to directories (a link cycle used to recurse until the path was too long), and visit
files in ordinal order, so the same fonts win on every machine. With no home directory (HOME
unset, as in some serverless hosts) the per user font directories are skipped; they used to
resolve against the current directory. -
Variable font instances are held weakly by their font. A font shared by a long running process,
such as one in aPdfFontCollectionused for every HTML conversion, no longer keeps every
location it was ever asked for (each a full font program); an instance stays shared while a
document or the application holds it. -
Name constraints fail closed without ICU. Under invariant globalization .NET performs no NFKC
normalization or IDNA mapping, so a directory string or host name with characters outside
ASCII cannot be put in canonical form; it now counts as undecided (excluded, and not
permitted) instead of being compared as written, which let full width letters pass an
excluded subtree. -
Cold start: the syllable grammars of the Khmer, Myanmar and Universal Shaping Engine shapers
are built when such text is first shaped, not with the first shaper. The first line of text in
an embedded font allocates 360 KB less and, under the JIT, takes about 10% less time.
tools/measure_cold_start.pymeasures the first open and save, optimization, text extraction,
HTML conversion and embedded font text in a fresh process. -
Samples under
samples/serverless: a Native AOT AWS Lambda function (provided.al2023, arm64)
that optimizes PDFs uploaded to S3 and converts HTML to PDF behind a function URL, with a SAM
template, and an Azure Functions isolated worker app with an HTTP trigger for HTML and an Event
Grid blob trigger for optimization. Both are built in CI. -
OCR finds pages scanned sideways or upside down. When most of the lines read on a page do not
run left to right (the detector found them tall, or the line orientation model turned them), a
copy reduced to 1600 pixels on its long side is read in each of the four orientations and the
one with the most confident left to right text wins; the page is then read turned back and the
words map back to page space through the turn.PdfOcrPage.Orientationsays how far the
content stood turned;PdfOcrOptions.DetectOrientation(on by default) turns it off. Upright
pages cost nothing more. Sideways pages now read in line order. -
OCR rasterizer hook:
PdfOcrOptions.Rasterizertakes anIPdfPageRasterizerthe application
supplies, for pages with no scan to decode (text as vector outlines, vector art, encodings not
decoded). It renders the page as shown atRasterizerDpi(300, at mostMaxOcrDpi, lowered to
fitMaxImagePixels); its result is checked for size and shape, and its failures are reported
per page. PdfNetStack still does not render; a PDFium based renderer can be plugged in. -
pdfnetstack-ocr, a command line tool in its own package,PdfNetStack.Ocr.Tool:
pdfnetstack-ocr in.pdf out.pdf --languages latin,arabic [--models folder], with--text,
--json,--no-orientation,--all-pages,--min-confidence,--threadsand--password.
It carries the models and ONNX Runtime's native libraries for Windows, Linux and macOS (a 70 MB
package), soPdfNetStack.Toolstays small and free of ONNX.pdfnetstack ocrruns it when it
is installed and says how to install it when it is not. -
eng/check-licenses.ps1allows a package underonlyInto ship with several named projects. -
Signature validation confirms qualified status against EU trusted lists the application
supplies; the library still downloads nothing.PdfTrustedList.Loadreads ETSI TS 119 612
lists (the list of lists and national lists) from bytes or a stream, with DTDs prohibited, at
most 32 MiB and 64 levels: scheme information, pointers to other lists with their signing
certificates, providers, services with type, status, history, digital identities
(certificates, subject key identifiers, subject names), qualifications with their criteria and
additional service information. Each list's enveloped XMLDSig signature is verified against the
certificates supplied for it, or for a national list against the pointers of a verified list of
lists (Load(xml, listOfLists)): every reference must digest correctly and one must cover the
whole list, identifiers must be unique, and Canonical XML 1.0 and 1.1 and Exclusive XML
Canonicalization are implemented in the library (no new dependency), checked against
Microsoft's implementation in the tests. RSA PKCS #1 v1.5, RSA-PSS with any salt length (also
for id-RSASSA-PSS keys) and ECDSA, with SHA-256, 384 and 512; SHA-1 is refused. The signer must
be valid at the signed XAdES signing time, or else at the issue date. -
PdfValidationOptions.TrustedListsandTrustedListsAsTrustAnchors; every signer gets
PdfCertificateValidation.TrustedListQualification(Qualificationat the signing time,
AtIssuance, the decidingService,Reasons) as ETSI TS 119 615 decides it: the CA/QC
service whose identity is the public key of a CA above the certificate, its status from the
history at issuance and at the signing time (granted, or supervised or accredited before
eIDAS), the qualifiers its criteria select (key usage, policies, extended key usage, subject
attributes, nested lists) and the certificate's QCStatements. Conflicts, criteria or critical
extensions the library cannot evaluate, and missing or expired lists give the new
PdfQualification.Indeterminate. So does a certificate whose own checks did not pass: the
lists are then not consulted. Services are matched by public key only: a listed certificate
with the same key, or a listed X509SKI equal to the SHA-1 of the certificate's subjectPublicKey
(RFC 5280 method 1). The subject key identifier extension a certificate carries is never used. -
Trusted list anchors (
TrustedListsAsTrustAnchors, default true) are restricted. Only the
identities of CA/QC and CA/PKC services anchor a chain, and only for a validation time at which
the service's entry in force has an approved status (granted, recognised at national level,
or before eIDAS under supervision, in cessation or accredited) and no critical extension the
library does not interpret. TSA/QTST and TSA services anchor time stamp authority chains under
the same rules, never a signer's. OCSP, CRL and other services, and withdrawn, deprecated or
never approved ones, anchor nothing. The default stays true: lists are only used when their
signature verified against certificates the application supplied, and without anchors the
qualification could never be confirmed. A chain that reaches none of these anchors fails, and
the problems say what the list names its top certificate as. -
Timestamps get
PdfTimestampInfo.QualifiedTimestamp: Passed only when the authority's
certificate checks passed, the token is intact, and a TSA/QTST service granted at the
validation time has as its identity the authority's own key, or the key of a CA of its chain
that the list names as that TSA/QTST service. Otherwise Failed when no such service was
granted, or Indeterminate when the authority's checks did not pass.
tools/PdfNetStack.TrustedListCheckreads and verifies the live EU lists for a manual check;
the tests generate every list and PKI. -
JPEG 2000 images decode tile by tile, a band of rows at a time, straight into 8 bit pixels.
Only a window of rows per resolution and a few rows of code-blocks per subband are held in
32 bits, so the decode limit has to cover the pixels and those rows instead of 32 bit planes
of the whole image: a 600 dpi color page (4812 by 7541, one tile) now decodes within the
default 256 MiB, peaking near 190 MB where it needed about 1 GB. Tiled images reuse one tile's
buffers. The samples are unchanged, bit for bit. -
JPEG 2000 decoding is faster with unchanged output: tier-1 keeps each stripe column's state in
one word and skips columns with nothing to code, the MQ decoder renormalizes a byte at a time,
rows are lifted with their low and high-pass halves apart and columns several at a time, and
the color transform and level shift run in one pass. A 6 megapixel lossy RGB image decodes in
about 60% of the time it took; lossless images gain less, being bound by the arithmetic decoder. -
JBIG2 regions combine with the page a byte at a time instead of a pixel at a time, and an
immediate text region that draws onto the page the same way symbol by symbol (OR onto a white
region, AND onto a black one, or REPLACE) is drawn straight onto it with no bitmap of its own.
MMR runs fill bytes, and generic regions read their context rows by offset. A synthetic 600 dpi
text page decodes about four times faster. -
PdfPageImage.Decodemaps one component images of 8 bits or less (bilevel scans, gray,
indexed) through a table of each sample value's pixel instead of converting every pixel, and
returns a JPEG 2000 image's 8 bit gray or RGB pixels without copying them. With the JBIG2
changes, a synthetic 600 dpi text page comes out ofDecodefour to five times faster. -
tools/make_jpx_fixtures.py --largewrites a large synthetic page for measuring memory and
time; it is not committed. -
Optimizer: bilevel images are coded as CCITT Group 4 when that is smaller
(PdfOptimizerOptions.OptimizeBilevelImages, on by default;--no-group4turns it off). It
takes 1 bit images stored as samples under any general filters, and CCITT Group 3 data that
decodes without a flaw and whose parameters leave nothing to a reader's defaults. The encoder
follows ITU-T T.6 and writes the same bytes as libtiff. The proof decodes both versions and
requires every pixel to match, andPdfEquivalenceaccepts such a recoding from any tool on
the same terms.OptimizationStatistics.ImagesRecodedAsGroup4counts them. -
Lossy optimizer options reach scanned documents.
MaxImageDpireduces bilevel images (raw,
CCITT or JBIG2) and keeps them bilevel: each new pixel takes the share of the source that covers
it, the less common value wins past half, and a stroke thinner than the new pixels keeps its
middle, so text stays legible; the result is Group 4 or Flate. It also reduces gray and RGB
JPEG 2000 images, which become JPEG atJpegQuality(or lossless samples without it), and
ConvertImagesToJpegturns photographic JPEG 2000 images into JPEG. The proof decodes JPEG 2000,
JBIG2 and CCITT sources, compares a bilevel output with the same reduction of its source, and
allows a JPEG 2000 image without /BitsPerComponent to gain /BitsPerComponent 8. A JBIG2Globals
stream no image uses any more is dropped. -
Optimizer: every JPEG the lossy options make is checked against the proof's color and coarse
scale bounds before it is chosen, so a small image a JPEG would shift too far stays as it was
instead of failing the proof of the whole file. -
The lossy image pass shares a memory budget of twice
MaxDecodedStreamBytesbetween its
workers, so a document of large JPEG 2000 scans no longer decodes one per thread at once: peak
memory on two scanned books of the benchmark corpus fell from 5.7 and 7.0 GiB to 2.2 and 2.7. -
CCITT decoding: a row whose every pixel changes color (a 1 or 2 pixel wide image, for one)
no longer ends decoding there. -
tools/scan_bench.pywrites synthetic scans of each kind a scanner produces (1 bit under Flate
and LZW, Group 3 fax, Group 4, JBIG2, gray and color JPEG 2000) and measures both optimizer
profiles on them. -
HTML conversion is 1.5 to 1.9 times faster with the same PDF, byte for byte. On the benchmark
samples (fastest of 125 conversions each, on a busy 8 core machine): a one page invoice went
from 46 to 31 ms, a one page report from 30 to 20 ms, a 21 page invoice with a 640 row table
from 465 to 244 ms, a 15 page report with lists, images, SVG charts and a grid from 110 to 68
ms, and a 52 page text from 316 to 193 ms. The first conversion in a new process is 1.1 to 1.8
times faster. What changed:ConvertToPdfsaves without the optimizer's equivalence proof, which read both files back
and took about half the optimizer's time: the converter draws all the content itself. New
PdfSaveOptions.VerifyOptimization(default true) andHtmlConverterOptions.SaveOptions
(default null: asPdfSaveOptions.Defaultwithout the proof); setSaveOptionsto
PdfSaveOptions.Defaultto keep the proof.- Each page paints the parts of the document that reach it instead of walking all of it.
- Table cells are measured for the column widths and laid out from the same text, fonts,
shaping and break opportunities, and collapsed borders read each style once per table. - Shaped runs are kept for the rest of the conversion (up to 256 characters a run and 256K
characters in all), so repeated labels and numbers shape once. - OpenType lookups pass over glyphs their first coverage does not list, as HarfBuzz's set
digests do; each stage's lookups are worked out once per font. A hostile font's step budget
is spent more slowly as a result; output is otherwise the same. - Installed fallback fonts are looked up only when a character needs them, so a document in
the generic families never scans the installed fonts: its first conversion in a process went
from 352 to 258 ms. - Font subsets are made and compressed side by side when a document is saved.
- Less work per character in inline layout: the bidi algorithm is skipped for text below
U+0590 in a paragraph that is not right to left (every level is 0 there), fonts are chosen
once per run of one style, break opportunities need no sorting, and CSS keywords and colors
are read once per value. Attribute values without character references, such as data: URIs,
are copied whole. - The optimizer's content analysis reads operators without a string each, and font program
merging reads each program once. - Fixed:
ConvertFileToDocumentdroppedMaxPageswhen it set the base address.
-
tools/PdfNetStack.Benchmarkshas three larger HTML samples (a 21 page invoice, a 15 page
report with lists, images, SVG and a grid, a 52 page text);quick,coldandsamplestake
a sample name,samplesalso writes the PDFs and their page counts, andquickcounts the
allocations of every thread. -
HTML: absurd sizes no longer exhaust memory. A font size such as 1e300px laid out one line
across an unbounded number of pages. Computed lengths are now clamped to a million px either
way (percentages to a million percent), font sizes to 10000px, unitless line heights to 1000
and page sizes to 14400 points a side, andHtmlConverterOptions.MaxPages(default 10000)
ends a conversion that needs more pages with the newHtmlConversionException. -
Variable fonts in font matching.
PdfFontCollectioncounts a variable font for every weight,
width and slant its axes cover, and registers its named instances as faces; faces are also
found by full or PostScript name.Find(family, PdfFontQuery)matches width, then style, then
weight with ranges as CSS Fonts 4 does (5.2), and returns the instance at the values found,
withOpticalSizeandVariationsapplied over them (7.2): ital for italic, slnt for oblique,
never both.PdfFontStylegives normal, italic or oblique at any angle. Static families match
as before, now also by OS/2 width class. -
HTML:
@font-facetakes ranges infont-weight,font-stretch(andfont-width) and
font-style(oblique 0deg 15deg), and thefont-variation-settingsand
font-named-instancedescriptors. A missing descriptor isauto: the face matches as normal
and the variation follows the style unclamped.font-style: oblique <angle>,
font-optical-sizing(opsz set to the font size in CSS px) andfont-variation-settingsare
supported; thefontshorthand resets the last two. Rules with identical descriptors: the
last one wins. -
Variable font instances are made once per location and the default location is the font
itself, so a document embeds one program per location drawn, however many sizes use it.
@font-facerules that share a file share its font. A damaged variation table makes matching
fall back to the font's default instance. -
HTML bounds the variable font instances a document can ask for: axis values snap to a grid
(whole units for wght, wdth and opsz, half degrees for slnt, tenths for ital, a hundredth of
the range for other axes) and each font makes at most 32 instances per conversion, the nearest
one standing in beyond that. Font values that are not finite are dropped and the rest clamped,
and a style value that font matching still rejects falls back to the family's default instead
of failing the conversion.font-stretchaccepts only finite percentages. -
PdfFontQuerychecks each value when it is set and copiesVariations, so an invalid query
throws where it is built. -
tools/make_variable_font_fixtures.pyalso builds Test-Axes.ttf, a font with wght, wdth,
opsz, ital and slnt axes for the matching tests. -
PDF/A-1b output (
PdfConformance.PdfA1B): PDF 1.4 with a classic cross-reference table and no
object streams, also when the optimizer runs. Transparency (opacity, soft masks, alpha images,
blend modes, transparency groups), JPEG 2000, optional content, embedded files, file attachment
and other post PDF 1.4 annotations, OpenType font programs and ICC profiles newer than version 2
are refused with the rest of the problems; subset CID fonts get a CIDSet stream read from the
embedded program. -
PDF/A-4, 4e and 4f output (
PdfA4,PdfA4E,PdfA4F): PDF 2.0 withpdfaid:rev2020. The
information dictionary written holds only the modification date; the document keeps its values
and the XMP carries them. Every embedded file is an associated file with its relationship; 4f
needs at least one and takes any type, plain 4 and 4e take PDF/A-1, 2 and 4 files only, and 4e
allows 3D and RichMedia annotations with their GoTo3DView and RichMediaExecute actions. -
PDF/A checks for every part: actions are walked (open action, links, widgets, bookmarks and
Nextchains) and Launch, JavaScript, ResetForm, ImportData, Hide, media and other forbidden
types, and named actions other than page navigation, are refused, as are additional actions on
pages, widgets and fields, XFA, transfer functions, external stream data, PostScript and
reference XObjects and OPI. LZW streams are re-encoded with Flate, image interpolation is
switched off and NeedAppearances is removed. Saving an invoice as anything but PDF/A-3 is
refused. -
Signature validation, the X.509 checks of ETSI EN 319 102-1: certificate policy processing as in
RFC 5280, 6.1 with policy mappings, policy constraints and inhibit anyPolicy, on the policy graph
of RFC 9618 (PdfValidationOptions.AcceptablePolicies,RequireExplicitPolicy,
InhibitPolicyMapping,InhibitAnyPolicy;PdfCertificateValidation.Policyand
ValidPolicies); name constraints on DNS names, email addresses, URI hosts, directory names
and IP addresses, the trust anchor's included (PdfCertificateCheck.NameConstraints), with
hosts compared in one canonical form (one trailing dot dropped, IDN as ASCII, lower case, URIs
read bySystem.Uri), directory strings decoded in every string type and prepared as RFC 4518
says, and anything that cannot be read or judged failing closed, including constraints that
cannot be read, which fail every certificate below them; and the
QCStatements of ETSI EN 319 412-5 (PdfCertificateCheck.QcStatements,PdfQcStatements),
with the qualification they claim (PdfCertificateValidation.Qualification: QESig, QESeal,
AdESig-QC and the rest). No trusted list is downloaded. Critical policy, name constraint and
QC statement extensions no longer fail a certificate as unknown. -
Text extraction is about 3.8 times faster with identical output:
ExtractTexton a 60 page,
27,000 word report in an embedded TrueType font went from 255 ms to 67 ms and from 300 MB to
69 MB allocated. Reading order sorts runs of touching letters instead of single letters and
decides each line once per size and baseline; the content interpreter no longer copies the text
state for every glyph, shares operator names and reads CID widths without a copy per glyph. -
HTML conversion does less repeated work per line of text: a tab width is measured only for runs
that contain a tab, the paragraph text is copied once for all its runs, and a font's OpenType
features are read once per script. Output is unchanged. -
The command line tool is published as native executables, compiled ahead of time, for
linux-x64, linux-arm64, win-x64, osx-x64 and osx-arm64, attached to each GitHub release with a
SHA256SUMS.txt. No .NET install is needed to run them. -
tools/PdfNetStack.Benchmarksmeasures text extraction and HTML conversion with
BenchmarkDotNet; it is not part of the solution or the packages. -
Timing tests take the fastest of several runs, so a busy machine cannot fail them.
-
Shaping for more scripts, matching HarfBuzz glyph for glyph on the fonts we compare:
- The Universal Shaping Engine, with its categories generated from the Unicode Character
Database and Microsoft's overrides bytools/make_shaping_data.py: Balinese, Javanese,
Sinhala, Tibetan, Sundanese, Batak, Buginese, Cham, Tai Tham, Lepcha, Meetei Mayek, Chakma,
Grantha, Newa, Sharada, Tirhuta and the other Brahmic scripts HarfBuzz gives it, Indic fonts
made for it (dev3 and the other version 3 tags), and Mongolian, N'Ko, Adlam and the other
scripts that join like Arabic (a Mongolian free variation selector takes its letter's form),
and Egyptian hieroglyph clusters. - Thai and Lao: sara am splits into nikhahit and sara aa, and fonts without Thai substitutions
get their marks placed through the Windows or Macintosh private use glyphs. - Khmer (coeng ro and pre-base vowels reordered, split vowels decomposed, cfar), Myanmar
(kinzi, medials and pre-base vowels reordered) and Hangul (syllables composed or decomposed
for the font, ljmo, vjmo and tjmo, tone marks). - All shapers: invalid vowel sequences of the Indic and USE scripts get a dotted circle, cmap
format 14 variation sequences pick their glyphs, a combining grapheme joiner that blocks no
reordering is skipped by lookups, a ZWJ keeps a mark from attaching to the base before it
(mark and mkmk see joiners), and contextual lookups whose nested lookups add glyphs index the
sequence as HarfBuzz does.
- The Universal Shaping Engine, with its categories generated from the Unicode Character
-
Text extraction reads right to left lines in logical order when they start or end with a Latin
word: the paragraph direction comes from both ends of the line, and punctuation next to the
word stays in it ("To." rather than "To ."). Numbers in right to left lines follow the
weak rules of UAX #9: after Arabic, "2026-114" (drawn 114-2026) reads in logical order, while
dates, amounts and percentages stay whole. -
Public benchmark corpus:
eng/corpus/manifest.jsonlists 77 public domain and openly licensed
PDFs (U.S. government publications, arXiv papers under CC BY 4.0, public domain scans and
Chinese, Japanese and Korean documents from Wikimedia Commons, and test files of the veraPDF,
qpdf, PDFium, pypdf and PDF Association projects) by URL, SHA-256, size, license, where the
license is stated and what each file exercises. No file is committed.tools/fetch_corpus.py
downloads and verifies them;tools/corpus_bench.pyrunstools/PdfNetStack.CorpusRunneron
each one, one process per measurement, for lossless and lossy optimization (size, time, peak
memory), checks every rewrite again withPdfEquivalence, compares the text and decoded images
the library reads before and after, compares PDFium renders, and writes a Markdown and a JSON
report. TheCorpus benchmarkworkflow runs it weekly and on demand. -
Fixed, found by the corpus:
- A cross-reference
/Prevchain that loops is treated as damage and the file is rebuilt from
its objects. A newest section naming itself as/Prevused to hide every older section, so
the file opened without its page tree, and the optimizer (withAllowRepairedFilesor not)
could rewrite it as a document without pages. - Content stream compaction judges whether a stream ends inside a comment from its tokens. A
%inside a string on the last line, common in the two byte codes of composite fonts, was
taken for a comment once the stream was compacted onto one line, so the proof rejected the
rewrite and the whole document was returned unchanged. - JBIG2: the work budget is eight times the page instead of four. Scans that code the page as
one symbol placed by a text region spend five, and failed to decode.
- A cross-reference
-
Optimizer, lossless structural passes, each proven by the equivalence check against its own
analysis of the source (docs/FIDELITY.md gives the rules):PruneUnusedResourcesremoves fonts, XObjects, graphics states, patterns, shadings, color
spaces and property lists that no content using the resource dictionary names, following
forms, tiling patterns, Type 3 glyph procedures, soft masks and annotation appearances, and
inherited and shared resource dictionaries. Anything it cannot read for certain is kept.CompactContentStreamsrewrites content streams with minimal white space, canonical numbers,
shortest strings and no comments; inline image data is copied exactly.MergeFontSubsetsmerges subsets of one TrueType font embedded separately, as merged
documents carry them, into one program when no glyph any page shows changes.- Deduplication compares streams by their decoded content, so differently encoded copies of a
font or image merge. Linearize(opt-in) writes a linearized file (Annex F) with page offset, shared object and
outline hint tables, laid out the way qpdf checks it. Inherited page attributes move onto the
pages.OptimizationStatisticsreportsResourcesPruned,ContentStreamsCompacted,
FontProgramsMergedandLinearized; the command line tool takes--linearize,
--no-prune,--no-compactand--no-font-merge.
-
PdfDocument.OpenOnDemand(Stream)opens a document on a seekable stream without reading it into
memory: objects are parsed when first used and stream data is read when asked for, with 64 bit
offsets.PdfDocument.SaveIncremental(Stream)saves the changes since opening as an incremental
update, appending in place when given the source stream; existing signatures stay valid. -
PdfEquivalence.FindDifferenceaccepts the structural passes above and inherited page
attributes moved onto pages. -
tools/make_optimizer_samples.pyandtools/measure_optimizer.pymeasure each pass on
synthetic files and check linearized output with qpdf when it is installed. -
Annotations:
PdfAnnotationCollection.AddStamp(the fourteen standard stamps, or a custom one
from aPdfFormor a drawing callback),AddInk,AddSquare,AddCircle,AddLine(with
PdfLineEndingat either end),AddPolygon,AddPolylineandAddFileAttachment(the file
embedded behind an icon;PdfAnnotation.Attachmentreads it back).PdfAnnotationOptions
sets the color, interior color, border width, dashes, opacity, comment, author and subject.
Each comes with a generated appearance; below full opacity the drawing is a transparency group
faded as a whole. PDF/A-3 output declares the files of file attachment annotations as
associated files, and PDF/A-2 refuses them. -
Annotation flattening:
PdfAnnotationCollection.Flatten(annotation),Flatten(options)for a
page andPdfDocument.FlattenAnnotations(options)draw each annotation's normal appearance (or
the one its state selects) into the page content, fitted to its rectangle by bounding box and
matrix as ISO 32000-1, 12.5.5 describes, upright on rotated pages for NoRotate annotations and
inside its optional content group, then remove it with its pop up. Hidden and NoView
annotations stay; links and widgets stay unlessPdfFlattenOptionsasks for them, and a
flattened widget leaves the form.PdfAcroForm.Flattenuses the same placement. -
Form fields travel with their pages:
Pages.Import,MergeandSplitcopy each widget's
field and ancestors into the target form, with the source form's default appearance,
alignment, resources and calculation order. A top level field whose name the target already
uses is renamed with the first free suffix_2,_3and so on. Before, widgets were copied
without their fields. -
Format actions: a field whose format action calls
AFNumber_Format,AFPercent_Format,
AFDate_FormatEx,AFDate_Format,AFTime_Format,AFTime_FormatExorAFSpecial_Format
shows its value formatted in the generated appearance (red for negative numbers when asked),
andPdfTextField.FormattedTextreturns it. The call is parsed and the formatting done in C#;
no JavaScript runs, and any other script leaves the value as it is. -
Fixed: form flattening threw on a widget whose appearance entry was not a stream.
-
HTML grid layout follows CSS Grid 1 and 2 further:
grid-auto-flowwithcolumnanddense
packing; line names in track lists andrepeat(), placement by name and byspan name, with
thename-startandname-endlines of areas and the implicit lines past the explicit grid;
implicit tracks before the explicit grid for negative lines;subgridon rows, columns or
both, with the parent's line names, the subgrid's own name list, its own gaps, and its items
sizing the parent's tracks through its margins, borders and padding.repeat(auto-fill)
counts the other tracks of the list when working out how many repetitions fit, and the empty
tracks ofrepeat(auto-fit)collapse with their gaps. Thegrid-templateandgrid
shorthands are read, area strings andauto-flowincluded. -
HTML named pages: the
pageproperty works on any block in the block flow, not only children
ofbody(CSS Paged Media 3, 8). A change of page name forces a break, and the content after
it is laid out at its own page type's width. A box that spans the break is sliced: its top
edge shows before the break and its bottom edge after, and the margin after the break is kept. -
HTML pagination: a forced break before the first content or after the last no longer adds an
empty page, sobreak-before: pageon the first element starts the document on page one. -
Fixed: HTML boxes with a
z-indexnow paint in stacking order (CSS 2.1, Appendix E). A
negativez-indexpaints over the stacking context's background and borders and under its
blocks, floats and text; a positive one paints over all of it, in z-index order. A
position: fixedbox with a negativez-indexpaints under the content of every page, where
before it painted over it. Positioned boxes, flex items and grid items with az-index, and
boxes with opacity below one or a transform, start a stacking context. -
Fixed: HTML
text-decoration-style: wavydraws a wave instead of a straight line, for
underline, overline and line-through. Its amplitude and wavelength follow the line's
thickness, and it is clipped to the decorated text. -
PAdES B-T, B-LT and B-LTA.
PdfTimestampClientbuilds RFC 3161 requests (hash, random nonce,
optional policy) and checks the reply (status, hash, nonce, signature, a critical time stamping
extended key usage); the application supplies the transport.PdfSignatureOptions.Timestamp
adds a signature timestamp,PdfSigner.AddDocumentTimestampa /DocTimeStamp, and
PdfSigner.AddValidationDatathe document security store: certificate chains, OCSP responses
and CRLs from anIPdfRevocationSource, and a VRI entry per signature, in an incremental update.
Only revocation data that matches the certificate and is signed by its issuer or delegated
responder is kept; delta and partitioned CRLs are not used. The library makes no network calls. -
Signature validation:
PdfSigner.Verify(pdf, PdfValidationOptions)builds chains to caller
supplied trust anchors and checks validity at the timestamp's time when its authority validates
(otherwise now), key usage (signer,
CA with path length, time stamp authority) and revocation from the document security store or
supplied data, with OCSP freshness and delegated responders.PdfCertificateValidationreports
each check per certificate as passed, failed, indeterminate or not checked, with the reasons.
PdfSignatureInfonow also reports document timestamps and signature timestamps. -
Factur-X and ZUGFeRD:
PdfInvoice.Attachembeds the invoice XML (checked for well formedness,
without DTDs) as factur-x.xml, or xrechnung.xml for XRECHNUNG, with the profile's relationship;
saving as PDF/A-3 writes the fx: XMP properties (MINIMUM to EXTENDED and XRECHNUNG) with their
extension schema, checks the attachment is still there, and keeps the metadata when an opened
invoice is saved again. -
PDF/A-3: the catalog's /AF array now references the attachment's file specification itself
instead of a copy of it. -
CI: a veraPDF job validates PDF/A-2b, 2u, 3b and 3u files made by the library, including a
Factur-X invoice signed to PAdES B-LTA. -
Lossy image options for the optimizer, all off by default:
MaxImageDpi(--max-dpi) downsamples images drawn above 1.5 times the target resolution,
measured from the largest placement found on pages, in forms, tiling patterns, soft mask
groups and annotation appearances. The filter is Lanczos with three lobes. Masks, images with
a color key mask or a /Matte soft mask, bilevel, indexed, 16 bit, Lab, Separation and DeviceN
images, and images reachable from anything the measurement does not walk (Type 3 glyphs,
thumbnails, alternates) are left alone. A downsampled JPEG stays a JPEG, never finer than it was.ConvertImagesToJpeg(--to-jpeg) re-encodes raw 8 bit gray and RGB photographs as baseline
JPEG atJpegQuality(forward DCT, libjpeg quality tables, 4:2:0 below quality 90, Huffman
tables optimized per image). A classifier leaves line art, screenshots, charts, palette and
bilevel images lossless. A lossy result is kept only when it is at least 10% smaller than the
best lossless one.MinImageSsim(--min-ssim) is a perceptual gate on luminance SSIM: any image whose lossy
change, requantization included, falls below it stays as it was, and
ImagesKeptByQualityGatecounts them. The proof checks resampled and converted images
against the source resampled to their new size (components, aspect, mean color, a coarse
comparison and SSIM), and with a gate also requantized ones. An image whose data is carried
over unchanged must keep its size.PdfEquivalence.FindDifferencetakes the same gate and
the optimizer's exclusions: soft masks, masks, stencil and color keyed images must match
exactly, and images with a /Mask or a /Matte soft mask keep their size.- A JPEG with a color key mask is no longer requantized, since the key matches exact sample
values. ProgressiveJpeg(--progressive) also writes every JPEG the optimizer produces as
progressive, with libjpeg's scan script and Huffman tables per scan, and keeps it when
smaller. Coefficients are unchanged, so this one is lossless.OptimizationStatisticsreportsImagesDownsampled,ImagesConvertedToJpegand
ImagesKeptByQualityGate, and the result's reason names every lossy change.
-
CompressionEffort.Exhaustive(--exhaustive): an original Deflate encoder in the manner of
Zopfli, written from RFC 1951 and published descriptions: optimal parsing over every match
length with costs refined over several passes, block splitting, and code lengths limited by
package-merge. zlib's output is kept whenever it is smaller. On the synthetic files of
tools/make_lossy_corpus.pyit saves 3 to 5% more than level 9 on text, scans and photographs
(nothing on line art and screenshots), for 2 to 150 times the CPU time, the most on long
repetitive streams. -
PdfNetStack.Azureis discontinued so the project can focus on PDF itself. Reading from and
writing to storage belongs to the application, around the stream APIs. Version 1.0.0 stays on
NuGet and keeps working. -
Variable fonts:
PdfOpenTypeFont.GetInstancegives a named instance (NamedInstances) or any
axis values (VariationAxes,IsVariable), embedded as a static font. TrueType outlines move
by their gvar deltas, with untouched points interpolated and composite offsets varied; advances
come from HVAR or the phantom points, font metrics from MVAR (with hhea kept in step), control
values from cvar, weight and width classes and the italic angle from the axes, through avar.
CFF2 fonts load (at their default instance) and convert to CID-keyed CFF: blends resolve in
charstrings and Private DICTs, subroutines are inlined, and argument runs longer than CFF's 48
operands are split. The shaper applies GDEF variation deltas to GPOS values and anchors, and
GSUB and GPOS feature variations. Outlines, advances and metrics match fontTools' instancer and
shaping matches HarfBuzz on the synthetic fixtures and on the variable fonts we compare.
Damaged variation data is aFontFormatException. -
Unicode normalization in the shaper, as HarfBuzz's normalizer does it: characters the font
lacks decompose into ones it has, marks sort into canonical order (with the shaping tailorings
for Hebrew, Arabic and others), a base and mark compose when the font has the composite, and
spaces and the non-breaking hyphen without glyphs draw with the space and hyphen at their own
widths. Font fallback,CanRenderand HTML text use the same coverage. Data generated from the
Unicode Character Database 16.0 bytools/make_shaping_data.py. -
Indic shaping for Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada and
Malayalam, per Microsoft's script development specifications: syllables, base consonant,
reph, pre-base matras and consonants, the basic forms one feature at a time, final reordering,
presentation forms, old (deva) and new (dev2) font specifications, and dotted circles for
broken clusters. It matches HarfBuzz on a synthetic Devanagari and Bengali fixture and on the
three faces of a font covering all nine scripts. -
Kashida justification (
PdfParagraph.Kashida, off by default): justified Arabic lines elongate
one join per word with tatweel, at the point Arabic typography prefers, up to three tatweel
widths, before spaces widen. Tatweels carry no text, so copying gives the original words. -
Fixed: a mark drawn with a vertical offset (Arabic dots and vowels in many fonts) moved every
glyph after it by its horizontal offset, so letters failed to join and lines came out wider
than measured. Spaces at the edge of a right to left run are now written outside its
ActualText, so viewers no longer read them on the wrong side of the neighboring run. -
Tagged PDF from HTML (
HtmlConverterOptions.Tagged, on by default): headings, paragraphs,
lists, tables with header and data cells, figures with their alt text, links tied to their
annotations, sections and quotes form a structure tree in reading order (ISO 32000-1, 14.8);
backgrounds, borders, text shadows, repeated table headers, fixed boxes and page margins are
artifacts. The catalog is marked and viewers show the document title. -
JPEG 2000 images decode (
PdfPageImage.Decode, JPXDecode): JP2 files and raw codestreams,
ITU-T T.800 part 1. Every progression order and progression change, precincts, layers, tiles
and tile-parts, PPM, PPT, SOP and EPH, all code-block styles, ROI, the 5/3 and 9/7 wavelets,
RCT and ICT, subsampled, signed and 1 to 16 bit components, JP2 palettes and channel
definitions. The image dictionary's color space wins, otherwise the JP2 header's (ISO 32000-1,
7.4.9). Lossless images decode bit exact and lossy ones within one step of OpenJPEG. Sizes,
counts and work are bounded. SMaskInData is not applied; HTJ2K (part 15) and part 2 extensions
are not supported. -
JBIG2 images decode (
PdfPageImage.Decode, JBIG2Decode with JBIG2Globals): generic,
refinement, text and halftone regions with their symbol and pattern dictionaries and code
tables, arithmetic, Huffman and MMR coded, per ITU-T T.88. Output is bounded by the image's
size, and damaged data is aPdfFormatException. Tested bit for bit against PDFium on the T.88
Annex H test datastream and synthetic streams. -
Named destinations:
PdfDocument.NamedDestinationslists the names of the /Dests name tree
and the catalog's /Dests, resolves them to pages, and renames them with every bookmark, link
and GoTo action that refers to them, in their own string or name form, rewriting the name tree
as one sorted leaf; colliding renames are refused.PdfOutline.DestinationNamegives the name
a bookmark points at. -
GIF images:
PdfImage.Loadreads GIF 87a and 89a (the first frame, interlaced or not, with its
transparent color as a soft mask), soDrawImageand<img>in HTML take them too. -
BMP, TIFF and WebP images:
PdfImage.Loadreads BMP (1 to 32 bits, RLE4 and RLE8, bit fields,
top down rows, alpha, an embedded JPEG or PNG), TIFF (strips and tiles, chunky and planar,
uncompressed, PackBits, LZW and Deflate with the predictor, CCITT modified Huffman, Group 3 and
Group 4, JPEG; bilevel, gray, palette, RGB, CMYK and YCbCr samples of 1 to 16 bits, associated
and unassociated alpha, BigTIFF) and WebP (lossy and lossless, with alpha, the first frame of an
animation). Alpha becomes a soft mask. A TIFF page stored as one strip of fax or JPEG data is
embedded as is (CCITTFaxDecode, DCTDecode).PdfImage.GetPageCountandPdfImage.Load(path, page)give every page of a multi-page TIFF. Lossy WebP decodes to the pixels libwebp gives,
bit for bit. Decoding is bounded, and damaged files fail with aFormatException.<img>in
HTML takes all three formats. -
PdfGraphics.DrawImagefits an image into a box (PdfImageFit: fill, contain, cover, none,
scale down), aligns it there (PdfImageAlignment, clipping what spills over) and fades it
with an opacity. -
Form creation:
PdfAcroForm.AddTextField,AddCheckBox,AddRadioGroup(buttons may span
pages),AddComboBox,AddListBox,AddSignatureFieldandAddPushButton(optionally opening a
web address), styled withPdfFieldOptions(standard font, size, colors, border, tooltip, read
only, required, alignment, multiline, password, length limit, comb, editable, multiple
selection). Every new field comes with its appearance and the print flag. -
New package
PdfNetStack.Ocr:PdfOcr.MakeSearchablefinds each page's
scan, turns it upright, reads it and lays the words over it as invisible text;Recognize
reports without changing the document.OnnxOcrEngineruns the PP-OCRv5 mobile models
(PaddleOCR, Apache-2.0) on ONNX Runtime for Latin and Arabic script, with DB box detection, line
orientation, CTC decoding, word boxes and right to left ordering in managed code, and model files
checked against pinned SHA-256 hashes (tools/fetch_ocr_models.py). ONNX Runtime's telemetry is
turned off before the first model loads.IOcrEngineplugs in any other engine. -
New package
PdfNetStack.Ocr.Models(Apache-2.0 models, versioned on its own from 1.0.0): the
four PP-OCRv5 model files with their license and notices, copied next to the application on build
and publish;OnnxOcrEngine.Load()without a folder loads them from there. The files are never
committed: CI and the release workflow fetch them, and packing fails when one is missing or does
not match its pin. The license check now also covers bundled files and keeps ONNX Runtime out of
every package butPdfNetStack.Ocr. -
OCR reads scans stored as several images: strips or tiles side by side, or a text mask over a
background. The pieces are painted in drawing order into one gray canvas in page space, at the
finest piece resolution up toMaxOcrDpi, decoded one at a time, with image count, combined
pixels, canvas size and overlap capped before anything is decoded; word boxes map back to the
page through the canvas. -
Text extraction:
PdfPage.ExtractText()returns a page's text in reading order,
GetLetters()each glyph with its text, bounds, baseline, font name, visible size and render
mode, andGetWords()the words with their bounds. Glyphs are placed as the specification
positions them (character, word and horizontal scaling, rise, TJ adjustments, forms, Type 3
font matrices, vertical writing). Text comes from ToUnicode maps (bfchar and bfrange, ligatures,
surrogate pairs, codes of 1 to 4 bytes), from the simple font encodings and /Differences with
the Adobe Glyph List, from embedded CMaps with usecmap, the Identity and Unicode predefined
CMaps, and embedded TrueType cmaps; spans marked with ActualText are replaced by their text, so
right to left text the library writes reads back in logical order. Lines follow each run's own
baseline, so rotated and vertical text read in order; columns of prose are found by XY cuts
and read one after the other, right to left for Arabic and Hebrew pages, while tables keep
reading row by row. CMaps, fonts and content are bounded and
never throw. -
CJK text extraction: Adobe's predefined CMaps for the Adobe-Japan1, GB1, CNS1, Korea1 and KR
collections (all 180 of cmap-resources, among them every CJK CMap of ISO 32000-1 Table 118, such
as 90ms-RKSJ-H, GBK-EUC-H, B5pc-H, KSCms-UHC-H and the Uni...-UCS2, UTF16, UTF8 and UTF32 forms)
split codes and give CIDs, so widths and vertical metrics follow the CMap, and the CID to
Unicode tables of mapping-resources-pdf give text to composite fonts without a ToUnicode map,
by the CMap's collection or the CIDFont's /CIDSystemInfo (ISO 32000-1, 9.10.2). The data is
packed into one Brotli stream per collection (196 KB in all) and decompressed on first use.
Embedded CMaps that use a predefined one inherit its collection, and keep their own /WMode. -
Simple fonts without /Encoding read their codes through the built-in encoding of an embedded
CFF program (FontFile3, Type1C or OpenType): the Standard and Expert encodings and custom
encodings of both formats with supplements, glyphs named through predefined or custom charsets. -
PdfGraphics.DrawInvisibleText: invisible, searchable text stretched to a box, optionally
rotated, for OCR text layers. A built in glyphless font (Type0, Identity-H, one empty glyph,
identity ToUnicode) takes any script; right to left text and characters outside the BMP also
carry ActualText. -
Image extraction:
PdfPage.GetImages()lists the images a page draws (XObjects, inside forms
too, and inline images) with their placement, bounds and resolution.PdfPageImage.Decode()
returns aPdfBitmapof 8 bit gray or RGB pixels from raw samples of 1 to 16 bits, from
JPEGs (inverse DCT, fancy upsampling and color conversion as libjpeg does them, within two
steps per sample) and from CCITT fax data (Group 3 one and two dimensional, Group 4, bit
exact with libtiff), through the device, Cal, ICC based, Indexed and Lab spaces, and Separation
and DeviceN (exact for a Type 2 tint transform, gray otherwise);PdfBitmap.ToPng()saves it.GetEncodedData()returns a JPEG or JPEG 2000 image as stored. -
New package
PdfNetStack.Html: HTML and CSS to PDF in pure C#, without a browser.
HtmlConverter.ConvertToPdf,ConvertToDocumentandConvertFileToDocument, configured with
HtmlConverterOptions(base address, resource loader, fonts, page style, extra style sheet,
bookmarks).- HTML parsed the way browsers repair it, with the legacy presentational attributes.
- CSS: cascade, inheritance,
var(), Selectors Level 4, media queries for print,@import,
@font-face,@pagewith margin boxes,calc()and friends, CSS Color 4. - Layout: blocks with margin collapsing, inline text with Unicode line breaking, bidi and
shaping, floats, relative, absolute and fixed positioning, tables in both border models,
flexbox, grid with fr tracks,minmax(),repeat()and named areas, inline blocks,
lists, counters and generated content. - Paged media: page sizes and margins,
:first,:leftand:right, named pages laid out
at their own size, running headers and footers with page counters and named strings
(string-set,string()), running elements (position: running()shown with
content: element(), any content: logos, tables, styled text, changing with the page where
they stand, with page counters inside), margin boxes that widen when
their neighbours are empty, forced and avoided breaks, orphans and widows, table headers
repeated on every page. - Painting: backgrounds with images and gradients, rounded borders, shadows, outlines,
transforms,object-fit, decorations; links and bookmarks; document metadata from the head. - Safe by default for untrusted HTML: no network access without a loader, files only from
the base folder, and limits on nesting, selectors,var()expansion, spans and tiling. - It depends on the exact
PdfNetStackversion it ships with.
-
The
PdfNetStackandPdfNetStack.Azurepackages are checked against 1.0.0 on every pack, so
a change that would break code built against the published version fails the build. -
SVG:
PdfSvg.LoadandParse, andPdfGraphics.DrawSvg, draw SVG as PDF vectors: shapes,
paths with arcs, groups,useandsymbol, nested viewports withviewBoxand
preserveAspectRatio, transforms, solid colors, linear and radial gradients, clip paths,
opacity, dashes, text in the standard fonts, embedded images, and<style>rules with type,
class and id selectors. Each drawing is written once per document. Doctypes are skipped, so
entity expansion cannot be abused, and size, depth andusenesting are capped. In
PdfNetStack.Html, SVG works in<img>, CSS backgrounds and inline<svg>. -
Public object model:
PdfObject,PdfNull,PdfBoolean,PdfNumber,PdfName,
PdfString,PdfArray,PdfDictionary,PdfStreamandPdfReferencein
PdfNetStack.Objects, withPdfDocument.Catalog,Resolve,AddObjectand
GetStreamData, andPdfPage.Dictionary, for features the high level API does not cover. -
Drawing:
PdfPage.CreateGraphics()returns aPdfGraphicscanvas (top left origin, points)
for new and existing pages, in front of or behind the existing content.PdfPathwith lines,
Bezier curves, arcs, ellipses, polygons and rounded rectangles;PdfPen,PdfColor(gray, RGB,
CMYK, opacity), solid, linear and radial gradient brushes; transforms, saved states, clipping.
Only changed graphics state is written, and shared page resources are never modified. -
Text with the 14 standard fonts:
PdfStandardFont,PdfGraphics.DrawString(fill with any
brush including gradients, stroke with a pen, or both) andPdfFont.MeasureString, with
ascent, descent, line height, cap height, x-height and underline metrics. Latin text uses
WinAnsiEncoding; up to 37 further characters per font and document (Central European letters,
the minus sign) get codes through /Differences. Symbol and ZapfDingbats take their Unicode
characters. Control and format characters are skipped; a character the font lacks throws. -
Embedded fonts:
PdfOpenTypeFont.Loadreads TrueType and OpenType (CFF) fonts from .ttf,
.otf, .ttc/.otc collections, WOFF and WOFF2 files. Drawn text embeds a subset of the glyphs
used as a Type 0 font (CIDFontType2 or CIDFontType0C), with a W array and a ToUnicode map, so
text is searchable and copyable in any script. Pair kerning from GPOS (formats 1 and 2, extension
lookups) or the kern table. Line metrics follow browsers (typo metrics when the font sets
USE_TYPO_METRICS, else hhea). Fonts whose fsType forbids embedding are refused; damaged fonts
raiseFontFormatException. -
PdfFontCollection: add fonts, files and directories, or usePdfFontCollection.Systemfor
installed fonts;Find(family, weight, italic)matches as CSS does. Scanning reads only the
name, OS/2 and head tables; fonts load on first use. -
WOFF2 fonts:
PdfOpenTypeFont.Load,PdfFontCollection(.woff2 files) and@font-facein
PdfNetStack.Htmlread WOFF 2.0, including transformed glyf, loca and hmtx tables and font
collections. Table checksums and head.checkSumAdjustment are recomputed. .NET 8 and later
decompress with the built-in Brotli decoder; .NET Standard 2.0 uses a managed Brotli decoder
(RFC 7932) with the static dictionary embedded. Lengths, offsets and counts are all checked, the
decompressed size is capped at 128 MB as for WOFF, and damaged files raise
FontFormatException. -
PdfFontgainsStrikeoutPositionandStrikeoutThickness. -
Complex text:
DrawStringandMeasureStringrun the Unicode bidirectional algorithm and an
OpenType shaper. Arabic and Persian join and take their contextual forms, ligatures form
(lam-alef, fi), vowel marks sit on their letters, mixed right to left and left to right text is
ordered correctly with mirrored brackets, and kerning follows GPOS or the kern table as
HarfBuzz applies them. Shaped and right to left text carries ActualText, so it copies and
searches in logical order. -
Paragraph layout:
PdfParagraph(rich text spans ofPdfTextStyle),PdfTextLayoutand
PdfGraphics.DrawText. Wrapping at Unicode line break opportunities, start, end, left, right,
center and justified alignment, right to left paragraphs, fallback fonts per style, letter and
word spacing, underline and strikethrough, first line indent, CSS line height, soft hyphens,
tabs, and drawing a range of lines for text split across pages. -
Images:
PdfImage.Loadfor JPEG (embedded without recompression, EXIF orientation, Adobe
CMYK, ICC profiles, JFIF density) and PNG (all color types and bit depths, Adam7 interlacing,
tRNS as color key or soft mask, alpha channels as soft masks, iCCP, pHYs; simple PNGs keep
their compressed data with PNG predictors).PdfGraphics.DrawImageat natural or given size.
Each image is written once per document. -
Form XObjects:
PdfForm.Createfor reusable drawings,PdfForm.FromPageto place any page of
any document (rotation honored, resources imported once),PdfGraphics.DrawForm, and
PdfGraphics.Formfor drawing into a form. -
Annotations:
PdfPage.Annotationsreads every annotation (kind, rectangle, contents, link
address, destination page including named destinations) and adds web and page links, notes,
text markup (highlight, underline, strikeout, squiggly) and free text, each with an appearance
stream.PdfGraphics.AddLinkmaps a canvas rectangle through the current transform. -
PdfGraphics.BlendMode: the 16 PDF blend modes, saved and restored with the graphics state. -
Interactive forms:
PdfDocument.Formlists every field (PdfTextField,PdfCheckBoxField,
PdfRadioButtonField,PdfChoiceField,PdfPushButtonField,PdfSignatureField) with fully
qualified names, flags, options and widgets. Setting values regenerates appearances (borders,
backgrounds, DA font and color, auto size, multiline, comb, password) and drops XFA.
PdfAcroForm.Flattenstamps the appearances into the pages and removes the form. -
Document features:
PdfDocument.Viewer(page layout, page mode, window options, right to left
reading, opening page and zoom),PdfDocument.PageLabels(roman, decimal and letter ranges
with prefixes, label lookup),PdfDocument.Attachments(embed, read, replace, remove files)
andPdfDocument.Language. -
PDF/A:
PdfSaveOptions.Conformancewrites PDF/A-2b, 2u, 3b or 3u. Problems that cannot be
repaired are listed together in one exception; the output gets an sRGB output intent with a
profile the library generates, XMP metadata synchronized with the document information,
printable annotations, and PDF/A-3 associated file entries for attachments. -
Digital signatures:
PdfSigner.Signadds a PAdES B-B signature (ETSI.CAdES.detached, SHA-256,
384 or 512, RSA or ECDSA, ESS signing certificate v2) in an incremental update, matching the
file's cross-reference style, optionally with a visible appearance.PdfSigner.Verifychecks
each signature's byte range, digest and signature and reports the signer certificate. The CMS
structure is encoded by the library; no new dependency. -
Packages now include
THIRD-PARTY-NOTICES.md, which carries Adobe's notice for the standard
font metrics.