Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>
<PropertyGroup>
<NoWarn>CS1591</NoWarn>
<Version>3.8.8</Version>
<Version>3.9.0</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="MarkdownSnippets.MsBuild" Version="28.4.1" />
<PackageVersion Include="DeterministicPdf" Version="1.0.1" />
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="14.15.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="NUnit" Version="4.6.1" />
Expand Down
17 changes: 15 additions & 2 deletions src/Tests/PasswordSamples.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
[TestFixture]
public class PasswordSamples
{
// A password protected pdf cannot produce a deterministic pdf target, so the pdf target is
// excluded and only the rendered pages are verified.
[Test]
public Task PasswordSample() =>
VerifyFile("password.pdf")
.ImageMagickPdfPassword("password");
}
.ImageMagickPdfPassword("password")
.ExcludeTargets("pdf");

[Test]
public void PasswordWithPdfTargetThrows()
{
var exception = Assert.ThrowsAsync<Exception>(
() => VerifyFile("password.pdf")
.ImageMagickPdfPassword("password"))!;

Assert.That(exception.Message, Does.Contain("""ExcludeTargets("pdf")"""));
}
}
Binary file added src/Tests/Tests.VerifyPdf.verified.pdf
Binary file not shown.
Binary file not shown.
3 changes: 2 additions & 1 deletion src/Verify.ImageMagick/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
global using System.Diagnostics.CodeAnalysis;
global using DeterministicPdf;
global using System.Diagnostics.CodeAnalysis;
global using EmptyFiles;
global using ImageMagick;
global using ImageMagick.Formats;
Expand Down
1 change: 1 addition & 0 deletions src/Verify.ImageMagick/Verify.ImageMagick.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<TargetFrameworks>net48;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DeterministicPdf" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="Polyfill" PrivateAssets="all" />
<PackageReference Include="Verify" />
Expand Down
37 changes: 36 additions & 1 deletion src/Verify.ImageMagick/VerifyImageMagick_Pdf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,34 @@ internal static ConversionResult Convert(string? name, Stream stream, IReadOnlyD
var magickSettings = context.MagickReadSettings();
magickSettings.Format = magickFormat;
var password = context.PdfPassword();
var includePdf = !context.IsTargetExcluded("pdf");
if (password != null)
{
// Checked before rendering, which is the expensive part, so the failure is immediate.
// An encrypted document cannot have a deterministic pdf snapshot: the trailer /ID seeds
// the encryption key, so neutralizing it would leave the document undecryptable. Rather
// than silently omitting the target, which would make the snapshot set differ from an
// unencrypted document for no visible reason, this is explicit.
if (includePdf)
{
throw new(
"""
A password protected pdf cannot produce a deterministic pdf target, since the trailer /ID seeds the encryption key and neutralizing it would leave the document undecryptable.
Exclude the pdf target to verify only the rendered pages: ExcludeTargets("pdf")
""");
}

magickSettings.SetDefines(
new PdfReadDefines
{
Password = password
});
}

// Made seekable so the source document can be re-read for the pdf target after
// MagickImageCollection has consumed it.
stream = WrapStream(stream);

using var images = new MagickImageCollection();
images.Read(stream, magickSettings);
var count = images.Count;
Expand All @@ -47,6 +66,22 @@ internal static ConversionResult Convert(string? name, Stream stream, IReadOnlyD
streams.Add(memoryStream);
}

return new(null, streams.Select(_ => new Target("png", _, name)));
List<Target> targets = [];

// The pdf snapshot is always the full document, regardless of PagesToInclude, which trims
// only the rendered pages above. Mirrors the svg target in ConvertSvg, which likewise emits
// the source document alongside the render.
if (includePdf)
{
stream.Position = 0;
targets.Add(
new("pdf", PdfNormalizer.Normalize(stream), name, performConversion: false)
{
BypassComparersForSubsequentOnDifference = true
});
}

targets.AddRange(streams.Select(_ => new Target("png", _, name)));
return new(null, targets);
}
}
Loading