Skip to content
Open
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
Binary file added PdfSharpCore.Test/Assets/Pdf20.pdf
Binary file not shown.
65 changes: 65 additions & 0 deletions PdfSharpCore.Test/IO/PdfVersionTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
44 changes: 21 additions & 23 deletions PdfSharpCore/Pdf.IO/PdfReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,37 @@ public static int TestPdfFile(byte[] data)
/// </summary>
///
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;
}

/// <summary>
/// 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.
/// </summary>
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');
}
}
Expand All @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion PdfSharpCore/Pdf/PdfDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ internal bool EarlyWrite

/// <summary>
/// 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.
/// </summary>
public int Version
{
Expand All @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion PdfSharpCore/root/PSSR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading