diff --git a/PdfSharpCore.Test/Assets/Pdf20.pdf b/PdfSharpCore.Test/Assets/Pdf20.pdf
new file mode 100644
index 00000000..d26da331
Binary files /dev/null and b/PdfSharpCore.Test/Assets/Pdf20.pdf differ
diff --git a/PdfSharpCore.Test/IO/PdfVersionTests.cs b/PdfSharpCore.Test/IO/PdfVersionTests.cs
new file mode 100644
index 00000000..5d7ef28f
--- /dev/null
+++ b/PdfSharpCore.Test/IO/PdfVersionTests.cs
@@ -0,0 +1,65 @@
+using FluentAssertions;
+using PdfSharpCore.Pdf;
+using PdfSharpCore.Pdf.IO;
+using PdfSharpCore.Test.Helpers;
+using System.IO;
+using System.Text;
+using Xunit;
+
+namespace PdfSharpCore.Test.IO
+{
+ public class PdfVersionTests
+ {
+ [Theory]
+ [InlineData("%PDF-1.0\n", 10)]
+ [InlineData("%PDF-1.4\n", 14)]
+ [InlineData("%PDF-1.7\n", 17)]
+ [InlineData("%PDF-2.0\n", 20)]
+ // Acrobat also accepts a PostScript style header with the PDF version embedded.
+ [InlineData("%!PS-Adobe-3.0 PDF-2.0\n", 20)]
+ public void TestPdfFile_returnsTheVersionOfTheHeader(string header, int expected)
+ {
+ Pdf.IO.PdfReader.TestPdfFile(Encoding.ASCII.GetBytes(header)).Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("Definitely not a PDF")]
+ [InlineData("%PDF-0.9\n")]
+ [InlineData("%PDF-1.A\n")]
+ [InlineData("%PDF\n")]
+ public void TestPdfFile_returnsZeroForANonPdfHeader(string header)
+ {
+ Pdf.IO.PdfReader.TestPdfFile(Encoding.ASCII.GetBytes(header)).Should().Be(0);
+ }
+
+ [Fact]
+ public void Should_beAbleToReadAPdf20Document()
+ {
+ using var fs = File.OpenRead(PathHelper.GetInstance().GetAssetPath("Pdf20.pdf"));
+ var inputDocument = Pdf.IO.PdfReader.Open(fs, PdfDocumentOpenMode.Import);
+
+ inputDocument.Should().NotBeNull();
+ inputDocument.Version.Should().Be(20);
+ inputDocument.PageCount.Should().BeGreaterThan(0);
+ }
+
+ [Fact]
+ public void Should_preserveThePdf20HeaderWhenSavingADocument()
+ {
+ using var fs = File.OpenRead(PathHelper.GetInstance().GetAssetPath("Pdf20.pdf"));
+ var inputDocument = Pdf.IO.PdfReader.Open(fs, PdfDocumentOpenMode.Modify);
+
+ using var ms = new MemoryStream();
+ inputDocument.Save(ms, false);
+
+ Encoding.ASCII.GetString(ms.ToArray(), 0, 8).Should().Be("%PDF-2.0");
+ }
+
+ [Fact]
+ public void Version_acceptsPdf20()
+ {
+ var document = new PdfDocument { Version = 20 };
+ document.Version.Should().Be(20);
+ }
+ }
+}
diff --git a/PdfSharpCore/Pdf.IO/PdfReader.cs b/PdfSharpCore/Pdf.IO/PdfReader.cs
index 2cdb0b90..8e41f7cd 100644
--- a/PdfSharpCore/Pdf.IO/PdfReader.cs
+++ b/PdfSharpCore/Pdf.IO/PdfReader.cs
@@ -153,19 +153,37 @@ public static int TestPdfFile(byte[] data)
///
///
internal static int GetPdfFileVersion(byte[] bytes)
+ {
+ int version = ScanFileVersion(PdfEncoders.RawEncoding, bytes);
+
+ // If it doesn't work with the specified encoding the file might be incorrectly encoded as ASCII.
+ if (version == 0)
+ version = ScanFileVersion(System.Text.Encoding.ASCII, bytes);
+
+ return version;
+ }
+
+ ///
+ /// Scans the file header for «%PDF-x.y» using the specified encoding and returns the version
+ /// as an integer (e.g. 14 for PDF 1.4, 20 for PDF 2.0), or 0 if no version was found.
+ ///
+ static int ScanFileVersion(System.Text.Encoding encoding, byte[] bytes)
{
try
{
// Acrobat accepts headers like «%!PS-Adobe-N.n PDF-M.m»...
- string header = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length); // Encoding.ASCII.GetString(bytes);
+ string header = encoding.GetString(bytes, 0, bytes.Length);
+ if (header.Length == 0)
+ return 0;
if (header[0] == '%' || header.IndexOf("%PDF", StringComparison.Ordinal) >= 0)
{
int ich = header.IndexOf("PDF-", StringComparison.Ordinal);
- if (ich > 0 && header[ich + 5] == '.')
+ if (ich > 0 && ich + 6 < header.Length && header[ich + 5] == '.')
{
char major = header[ich + 4];
char minor = header[ich + 6];
- if (major >= '1' && major < '2' && minor >= '0' && minor <= '9')
+ // PDF 1.0 to 1.7 and PDF 2.0 are the versions defined so far.
+ if (major >= '1' && major <= '2' && minor >= '0' && minor <= '9')
return (major - '0') * 10 + (minor - '0');
}
}
@@ -174,26 +192,6 @@ internal static int GetPdfFileVersion(byte[] bytes)
catch
{ }
- // If it doesn't work with the specified encoding ...
- try
- {
- // The file might be incorrectly encoded as ASCII
- string header = System.Text.Encoding.ASCII.GetString(bytes);
- if (header[0] == '%' || header.IndexOf("%PDF", StringComparison.Ordinal) >= 0)
- {
- int ich = header.IndexOf("PDF-", StringComparison.Ordinal);
- if (ich > 0 && header[ich + 5] == '.')
- {
- char major = header[ich + 4];
- char minor = header[ich + 6];
- if (major >= '1' && major < '2' && minor >= '0' && minor <= '9')
- return (major - '0') * 10 + (minor - '0');
- }
- }
- }
- // ReSharper disable once EmptyGeneralCatchClause
- catch { }
-
return 0;
}
diff --git a/PdfSharpCore/Pdf/PdfDocument.cs b/PdfSharpCore/Pdf/PdfDocument.cs
index 674ef4b0..fb7e6f68 100644
--- a/PdfSharpCore/Pdf/PdfDocument.cs
+++ b/PdfSharpCore/Pdf/PdfDocument.cs
@@ -483,6 +483,7 @@ internal bool EarlyWrite
///
/// Gets or sets the PDF version number. Return value 14 e.g. means PDF 1.4 / Acrobat 5 etc.
+ /// Return value 20 means PDF 2.0.
///
public int Version
{
@@ -491,7 +492,7 @@ public int Version
{
if (!CanModify)
throw new InvalidOperationException(PSSR.CannotModify);
- if (value < 12 || value > 17) // TODO not really implemented
+ if ((value < 12 || value > 17) && value != 20) // TODO not really implemented
throw new ArgumentException(PSSR.InvalidVersionNumber, "value");
_version = value;
}
diff --git a/PdfSharpCore/root/PSSR.cs b/PdfSharpCore/root/PSSR.cs
index f047cbfd..62c7f376 100644
--- a/PdfSharpCore/root/PSSR.cs
+++ b/PdfSharpCore/root/PSSR.cs
@@ -217,7 +217,7 @@ public static string InvalidPdf
public static string InvalidVersionNumber
{
- get { return "Invalid version number. Valid values are 12, 13, and 14."; }
+ get { return "Invalid version number. Valid values are 12 to 17 and 20."; }
}
public static string CannotHandleXRefStreams