From 28ff7f19ee8cd438ed8c096f704f568eadd1acda Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 23 Jul 2026 15:39:53 +1000 Subject: [PATCH] Add option to skip PDF byte normalization Introduces `SkipPdfNormalization` as a `SettingsTask` extension backed by a new context key, allowing callers to snapshot PDF bytes exactly as produced. `Convert` now normalizes bytes only when this flag is not set, while preserving existing behavior by default. The added XML docs clarify when to use this setting and the snapshot impact of toggling it (notably XMP canonicalization changes). --- claude.md | 17 +++++-- readme.md | 22 ++++++++- src/Directory.Build.props | 2 +- ...kipPdfNormalization#page_0001.verified.png | Bin 0 -> 8634 bytes .../Samples.SkipPdfNormalization.verified.pdf | Bin 0 -> 10946 bytes .../Samples.SkipPdfNormalization.verified.txt | 16 +++++++ src/Tests/Samples.cs | 9 ++++ ...TheNeutralizedBytes#page_0001.verified.png | Bin 0 -> 8634 bytes ...pshotHoldsTheNeutralizedBytes.verified.pdf | Bin 0 -> 10768 bytes ...pshotHoldsTheNeutralizedBytes.verified.txt | 16 +++++++ ...ldsTheProducerBytes#page_0001.verified.png | Bin 0 -> 8634 bytes ...SnapshotHoldsTheProducerBytes.verified.pdf | Bin 0 -> 10946 bytes ...SnapshotHoldsTheProducerBytes.verified.txt | 16 +++++++ src/Tests/SkipPdfNormalizationTests.cs | 25 +++++++++++ src/Verify.PDFium/VerifyPDFium.cs | 42 ++++++++++++++++-- 15 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 src/Tests/Samples.SkipPdfNormalization#page_0001.verified.png create mode 100644 src/Tests/Samples.SkipPdfNormalization.verified.pdf create mode 100644 src/Tests/Samples.SkipPdfNormalization.verified.txt create mode 100644 src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes#page_0001.verified.png create mode 100644 src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.pdf create mode 100644 src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.txt create mode 100644 src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes#page_0001.verified.png create mode 100644 src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes.verified.pdf create mode 100644 src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes.verified.txt create mode 100644 src/Tests/SkipPdfNormalizationTests.cs diff --git a/claude.md b/claude.md index 788e2df..4f6ca88 100644 --- a/claude.md +++ b/claude.md @@ -35,21 +35,29 @@ Entry point is `VerifyPDFium.Initialize(dpi = 96)` which registers a stream conv 3. One `png` target per page, named `page_0001` style To keep snapshots stable for PDFs freshly generated at test time, the non-deterministic fields are neutralized two ways: -- **In the `pdf` bytes** (`PdfNormalizer.Normalize`): the trailer `/ID`, the info-dictionary `/CreationDate`/`/ModDate`, and the XMP metadata dates plus `xmpMM:DocumentID`/`InstanceID` are overwritten by a length-preserving, in-place byte scan (no string round-trip, no regex) — only the volatile characters change, so cross-reference offsets stay valid. `Normalize` mutates in place and runs *after* the `PdfiumDocument` (which reads lazily from the same buffer) is disposed, so no defensive copy is needed. Values inside a compressed object/metadata stream (`/ObjStm`, flate-compressed XMP) are not reachable by the plaintext scan. -- **In the info file** (`PdfNormalizer.NormalizeProperties`): `Properties` is a `Dictionary` whose `CreationDate`/`ModDate` values are parsed (`PdfDate`) into `DateTimeOffset`, so Verify's built-in date scrubbing renders them deterministically (`DateTimeOffset_1` etc.). Properties are read from the original bytes, before `Normalize` zeroes them. +- **In the `pdf` bytes** (`DeterministicPdf.PdfNormalizer.Normalize`): the trailer `/ID`, the info-dictionary `/CreationDate`/`/ModDate`, and the XMP dates plus `xmpMM:DocumentID`/`InstanceID`/`OriginalDocumentID` and `dc:date` are overwritten by an in-place byte scan (no string round-trip, no regex). That value-zeroing is length-preserving, so cross-reference offsets survive it. A final `CanonicalizeXmp` pass then collapses the XMP packet's whitespace — Apache FOP serializes the packet through the platform's XML writer, so indentation varies by JRE — and that pass is **not** length-preserving: it rebuilds the buffer and repairs both the metadata stream length and the classic cross-reference table. A document it cannot safely rewrite (cross-reference stream, incremental update, unlocatable stream length) comes back unchanged. `Normalize` copies its input, so a caller keeps ownership of the array passed in; the call is still made only after the `PdfiumDocument` (which reads lazily from the same buffer) is disposed. Values compressed away inside an `/ObjStm` or a flate-compressed XMP packet are not reachable by the plaintext scan and are left alone. +- **In the info file** (`PdfProperties.Normalize`): `Properties` is a `Dictionary` whose `CreationDate`/`ModDate` values are parsed (`PdfDate`) into `DateTimeOffset`, so Verify's built-in date scrubbing renders them deterministically (`DateTimeOffset_1` etc.). Properties are read from the original bytes, before the byte-level pass zeroes them. + +Both can be opted out of per verification, via a context key set by a `SettingsTask` extension and read in `Convert`: +- `ExcludePdfDocument` — drops the `.verified.pdf` entirely, for producers whose bytes can never be made deterministic (Aspose.Cells embeds the machine's system fonts). +- `SkipPdfNormalization` — keeps the `.verified.pdf` but snapshots the producer's own bytes, for producers that are already byte-deterministic. Worth knowing when changing this: `CanonicalizeXmp` is the pass that alters bytes even for such a producer, so toggling the setting on an existing suite shifts every stored `.verified.pdf` once. Key files: -- **VerifyPDFium.cs** — initialization and the converter -- **PdfNormalizer.cs** — neutralizes the `pdf` bytes and projects the info-file properties to the scrubbable object map +- **VerifyPDFium.cs** — initialization, the converter, and the two opt-out extensions +- **PdfProperties.cs** — projects the pdfium-reported property map to the scrubbable object map - **PdfDate.cs** — parses PDF date strings (`D:YYYYMMDD…`) to `DateTimeOffset` - **PdfInfo.cs** / **PageInfo.cs** — info shape for the snapshot (per-page width/height in points and text via `PdfPage.GetText()`) +The byte-level normalizer is **not** a type in this repo — it is `PdfNormalizer` from the [DeterministicPdf](https://github.com/SimonCropp/DeterministicPdf) package, which owns and tests the algorithm. + Style note: only public types get a namespace declaration (`VerifyTests`); internal types live in the global namespace. ### Tests (`src/Tests/`) - **ModuleInitializer.cs** — calls `VerifyPDFium.Initialize()` via `[ModuleInitializer]` (also the readme `enable` snippet); PNGs compare via SSIM (`VerifierSettings.UseSsimForPng()`) - **Samples.cs** — snapshot tests, also the readme usage snippets +- **PdfNormalizerTests.cs** — asserts the wiring rather than the algorithm: that a normalized document still loads in pdfium, that volatile values are neutralized, and that the pass is idempotent +- **SkipPdfNormalizationTests.cs** — asserts the opposite pairing, that the skipped snapshot holds the producer's own bytes while the default one holds the neutralized bytes. `SampleIsNotAlreadyNormalized` pins the premise, so the pair cannot both pass vacuously if `sample.pdf` ever becomes normalization-invariant - Test assets `sample.pdf` (1 page) and `multi-page.pdf` (4 pages), both Letter size, were produced by [Morph](https://github.com/Papyrine/Morph)'s PDF exporter with embedded font subsets, so rendering is machine-independent. Engine-level unit tests (load/render/page sizes/metadata/threading) live in the Morph.PDFium repo, not here. @@ -57,6 +65,7 @@ Engine-level unit tests (load/render/page sizes/metadata/threading) live in the ## Dependency notes - Morph.PDFium pins the PDFium native version; bumping it may shift rendered pixels — expect to regenerate `*.verified.png` files in the same commit. +- DeterministicPdf owns the byte-level normalization, so bumping it may shift `.verified.pdf` files across every consuming suite without any document having changed. The 1.3.0 release did exactly that by adding `CanonicalizeXmp`: downstream snapshots moved by the size of the collapsed XMP whitespace while the rendered pages and info files stayed identical. When a bump changes the `.verified.pdf` but leaves the pages and info alone, that is the expected shape of it — not a rendering regression. - Morph.PDFium is a Papyrine package: `Papyrine_SponsorshipLicenseIgnored` is set in `src/Directory.Build.props` and `SC023` is in `NoWarn` (same arrangement as Verify.OpenXml's Morph dependency). ## Build Configuration diff --git a/readme.md b/readme.md index 3a9d191..60ad0b7 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Verifying a `pdf` produces: * The pdf itself as `.verified.pdf`. This can be omitted with [`ExcludePdfDocument`](#exclude-the-pdf-document). * A PNG render of every page as `#page_0001.verified.png`, `#page_0002.verified.png`, etc. -The non-deterministic fields of the pdf (the trailer `/ID`, the `/CreationDate` and `/ModDate`, and the equivalent XMP metadata) are neutralized so the same source document produces a byte-identical `.verified.pdf` across runs. +The non-deterministic fields of the pdf (the trailer `/ID`, the `/CreationDate` and `/ModDate`, and the equivalent XMP metadata) are neutralized so the same source document produces a byte-identical `.verified.pdf` across runs. A producer that already emits deterministic bytes can skip that work with [`SkipPdfNormalization`](#skip-pdf-normalization). Rendering is provided by [Morph.PDFium](https://github.com/Papyrine/Morph.PDFium), which wraps the prebuilt PDFium binaries from [pdfium-binaries](https://github.com/bblanchon/pdfium-binaries) (Windows, Linux, and macOS). Rendering is deterministic for a given Morph.PDFium version: the same input produces byte-identical PNGs on every machine and OS, and no image library dependency is added. @@ -100,6 +100,26 @@ public Task ExcludePdfDocument() => +### Skip pdf normalization + +By default the pdf bytes are normalized before being snapshotted: the trailer `/ID`, the `/CreationDate` and `/ModDate`, and the XMP dates and identifiers are neutralized, and the XMP packet is canonicalized. A producer that already emits byte-deterministic documents gains nothing from that, and pays for a full buffer copy, a rescan, and — when the XMP is canonicalized — a rebuild plus a cross-reference repair. `SkipPdfNormalization` snapshots the bytes exactly as produced: + + + +```cs +[Test] +public Task SkipPdfNormalization() => + VerifyFile("sample.pdf") + .SkipPdfNormalization(); +``` +snippet source | anchor + + +Only skip it when the producer is genuinely deterministic. Without normalization a freshly generated pdf carries a wall-clock `/CreationDate` and a fresh `/ID`, so the snapshot differs on every run. + +The XMP canonicalization is worth calling out, because it is the pass that changes bytes even for an already-deterministic producer: it collapses the packet's whitespace. Turning this setting on for an existing suite therefore shifts every stored `.verified.pdf` once, without anything about the documents having changed. + + ## Icon [PDF](https://thenounproject.com/icon/pdf-7564953//) designed by [Meilia](https://thenounproject.com/creator/meilia1/) from [The Noun Project](https://thenounproject.com). diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4c6b67c..ffe92d1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CS0649;CA1416;NU1608;NU1109;SC023 - 1.3.0 + 1.4.0 14.0 1.0.0 PDF, PDFium, Verify diff --git a/src/Tests/Samples.SkipPdfNormalization#page_0001.verified.png b/src/Tests/Samples.SkipPdfNormalization#page_0001.verified.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0cadfae757637a9f77d5c580f104a8c931ed8d GIT binary patch literal 8634 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 literal 0 HcmV?d00001 diff --git a/src/Tests/Samples.SkipPdfNormalization.verified.pdf b/src/Tests/Samples.SkipPdfNormalization.verified.pdf new file mode 100644 index 0000000000000000000000000000000000000000..acb06ceb3c38991f060cf84d78802f0d6131807c GIT binary patch literal 10946 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 rG_~dVK#n+o00k1%kk .ExcludePdfDocument(); #endregion + + #region SkipPdfNormalization + + [Test] + public Task SkipPdfNormalization() => + VerifyFile("sample.pdf") + .SkipPdfNormalization(); + + #endregion } diff --git a/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes#page_0001.verified.png b/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes#page_0001.verified.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0cadfae757637a9f77d5c580f104a8c931ed8d GIT binary patch literal 8634 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 literal 0 HcmV?d00001 diff --git a/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.pdf b/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e8f6d780fcf11bd5385639dd805a8f4de1ac54ab GIT binary patch literal 10768 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 literal 0 HcmV?d00001 diff --git a/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.txt b/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.txt new file mode 100644 index 0000000..9540e7d --- /dev/null +++ b/src/Tests/SkipPdfNormalizationTests.NormalizedSnapshotHoldsTheNeutralizedBytes.verified.txt @@ -0,0 +1,16 @@ +{ + PageCount: 1, + Properties: { + CreationDate: DateTimeOffset_1, + Creator: Morph, + ModDate: DateTimeOffset_1, + Producer: PDFsharp 6.2.4 + }, + Pages: [ + { + Width: 612.0, + Height: 792.0, + Text: Hello, World! This is a simple paragraph. + } + ] +} \ No newline at end of file diff --git a/src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes#page_0001.verified.png b/src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes#page_0001.verified.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0cadfae757637a9f77d5c580f104a8c931ed8d GIT binary patch literal 8634 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 literal 0 HcmV?d00001 diff --git a/src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes.verified.pdf b/src/Tests/SkipPdfNormalizationTests.SkippedSnapshotHoldsTheProducerBytes.verified.pdf new file mode 100644 index 0000000000000000000000000000000000000000..acb06ceb3c38991f060cf84d78802f0d6131807c GIT binary patch literal 10946 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 rG_~dVK#n+o00k1%kk + Verify(new MemoryStream(File.ReadAllBytes("sample.pdf")), "pdf") + .SkipPdfNormalization(); + + [Test] + public Task NormalizedSnapshotHoldsTheNeutralizedBytes() => + Verify(new MemoryStream(File.ReadAllBytes("sample.pdf")), "pdf"); +} diff --git a/src/Verify.PDFium/VerifyPDFium.cs b/src/Verify.PDFium/VerifyPDFium.cs index 42c9392..90fd4de 100644 --- a/src/Verify.PDFium/VerifyPDFium.cs +++ b/src/Verify.PDFium/VerifyPDFium.cs @@ -9,6 +9,10 @@ public static class VerifyPDFium // pdf can never be made byte-deterministic. The rendered pages and info file still verify. const string excludeDocumentKey = "VerifyPDFium.ExcludeDocument"; + // Context key set by SkipPdfNormalization. When present the pdf bytes are snapshotted as + // produced, for producers that already emit byte-deterministic documents. + const string skipNormalizationKey = "VerifyPDFium.SkipNormalization"; + public static bool Initialized { get; private set; } /// @@ -43,6 +47,30 @@ public static SettingsTask ExcludePdfDocument(this SettingsTask settings) return settings; } + /// + /// Snapshots the pdf bytes exactly as produced, skipping the normalization that neutralizes the + /// trailer /ID, the /CreationDate and /ModDate, and the XMP dates and + /// identifiers. Use it when the producer already emits byte-deterministic documents, since + /// normalizing them again copies the whole buffer, rescans it, and — when the XMP packet is + /// canonicalized — rebuilds it and repairs the cross-reference table, all to change nothing. + /// + /// + /// Only skip this when the producer is genuinely deterministic. Without it a freshly generated + /// pdf carries a wall-clock /CreationDate and a fresh /ID, so the snapshot differs + /// on every run. + /// + /// The XMP canonicalization is worth calling out because it is the pass that changes bytes for + /// an already-deterministic producer: it collapses the packet's whitespace, so enabling or + /// disabling this setting on an existing suite shifts the stored .verified.pdf even + /// though nothing about the document changed. Expect to re-accept those snapshots once. + /// + /// + public static SettingsTask SkipPdfNormalization(this SettingsTask settings) + { + settings.CurrentSettings.Context[skipNormalizationKey] = true; + return settings; + } + static ConversionResult Convert(Stream stream, IReadOnlyDictionary context) { using var buffer = new MemoryStream(); @@ -81,9 +109,13 @@ static ConversionResult Convert(Stream stream, IReadOnlyDictionary context) => !context.TryGetValue(excludeDocumentKey, out var value) || value is not true; + + static bool Normalize(IReadOnlyDictionary context) => + !context.TryGetValue(skipNormalizationKey, out var value) || + value is not true; }