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/UglyToad.PdfPig.Tests/Filters/BitStreamTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace UglyToad.PdfPig.Tests.Filters
{
using PdfPig.Filters;
using PdfPig.Filters.Lzw;

public class BitStreamTests
{
Expand Down
126 changes: 126 additions & 0 deletions src/UglyToad.PdfPig.Tests/Integration/FilterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using PdfPig.Filters;
using PdfPig.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;

public class FilterTests
{
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
private static readonly HashSet<string> _documentsToIgnore =
[
"issue_671.pdf",
"GHOSTSCRIPT-698363-0.pdf",
"ErcotFacts.pdf"
];

[Theory]
[MemberData(nameof(GetAllDocuments))]
public void NoImageDecoding(string documentName)
{
// Add the full path back on, we removed it so we could see it in the test explorer.
documentName = Path.Combine(DocumentFolder.Value, documentName);

var parsingOptions = new ParsingOptions
{
UseLenientParsing = true,
FilterProvider = MyFilterProvider.Instance
};

using (var document = PdfDocument.Open(documentName, parsingOptions))
{
for (var i = 0; i < document.NumberOfPages; i++)
{
var page = document.GetPage(i + 1);

foreach (var pdfImage in page.GetImages())
{
if (pdfImage.ImageDictionary.TryGet(NameToken.Filter, out NameToken filter))
{
if (filter.Data.Equals(NameToken.FlateDecode.Data) ||
filter.Data.Equals(NameToken.FlateDecodeAbbreviation.Data) ||
filter.Data.Equals(NameToken.LzwDecode.Data) ||
filter.Data.Equals(NameToken.LzwDecodeAbbreviation.Data))
{
continue;
}
}
else
{
continue;
}

Assert.False(pdfImage.TryGetPng(out _));
}
}
}
}

public sealed class NoFilter : IFilter
{
public bool IsSupported => false;

public ReadOnlyMemory<byte> Decode(ReadOnlySpan<byte> input, DictionaryToken streamDictionary, int filterIndex)
{
throw new NotImplementedException();
}
}

public class MyFilterProvider : BaseFilterProvider
{
/// <summary>
/// The single instance of this provider.
/// </summary>
public static readonly IFilterProvider Instance = new MyFilterProvider();

/// <inheritdoc/>
protected MyFilterProvider() : base(GetDictionary())
{
}

private static Dictionary<string, IFilter> GetDictionary()
{
var ascii85 = new Ascii85Filter();
var asciiHex = new AsciiHexDecodeFilter();
var flate = new FlateFilter();
var runLength = new RunLengthFilter();
var lzw = new LzwFilter();

var noFilter = new NoFilter();

return new Dictionary<string, IFilter>
{
{ NameToken.Ascii85Decode.Data, ascii85 },
{ NameToken.Ascii85DecodeAbbreviation.Data, ascii85 },
{ NameToken.AsciiHexDecode.Data, asciiHex },
{ NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex },
{ NameToken.CcittfaxDecode.Data, noFilter },
{ NameToken.CcittfaxDecodeAbbreviation.Data, noFilter },
{ NameToken.DctDecode.Data, noFilter },
{ NameToken.DctDecodeAbbreviation.Data, noFilter },
{ NameToken.FlateDecode.Data, flate },
{ NameToken.FlateDecodeAbbreviation.Data, flate },
{ NameToken.Jbig2Decode.Data, noFilter },
{ NameToken.JpxDecode.Data, noFilter },
{ NameToken.RunLengthDecode.Data, runLength },
{ NameToken.RunLengthDecodeAbbreviation.Data, runLength },
{NameToken.LzwDecode, lzw },
{NameToken.LzwDecodeAbbreviation, lzw }
};
}
}

public static IEnumerable<object[]> GetAllDocuments
{
get
{
var files = Directory.GetFiles(DocumentFolder.Value, "*.pdf");

// Return the shortname so we can see it in the test explorer.
return files.Where(x => !_documentsToIgnore.Any(i => x.EndsWith(i))).Select(x => new object[] { Path.GetFileName(x) });
}
}
}
}
10 changes: 10 additions & 0 deletions src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,20 @@ public void OnlyExposedApiIsPublic()
"UglyToad.PdfPig.CrossReference.CrossReferenceType",
"UglyToad.PdfPig.CrossReference.TrailerDictionary",
"UglyToad.PdfPig.Exceptions.PdfDocumentEncryptedException",
"UglyToad.PdfPig.Filters.BaseFilterProvider",
"UglyToad.PdfPig.Filters.DefaultFilterProvider",
"UglyToad.PdfPig.Filters.IFilter",
"UglyToad.PdfPig.Filters.IFilterProvider",
"UglyToad.PdfPig.Filters.ILookupFilterProvider",
"UglyToad.PdfPig.Filters.Ascii85Filter",
"UglyToad.PdfPig.Filters.AsciiHexDecodeFilter",
"UglyToad.PdfPig.Filters.CcittFaxDecodeFilter",
"UglyToad.PdfPig.Filters.DctDecodeFilter",
"UglyToad.PdfPig.Filters.FlateFilter",
"UglyToad.PdfPig.Filters.Jbig2DecodeFilter",
"UglyToad.PdfPig.Filters.JpxDecodeFilter",
"UglyToad.PdfPig.Filters.LzwFilter",
"UglyToad.PdfPig.Filters.RunLengthFilter",
"UglyToad.PdfPig.Functions.FunctionTypes",
"UglyToad.PdfPig.Functions.PdfFunction",
"UglyToad.PdfPig.PdfFonts.CharacterBoundingBox",
Expand Down
5 changes: 4 additions & 1 deletion src/UglyToad.PdfPig.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XY/@EntryIndexedValue">XY</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
3 changes: 1 addition & 2 deletions src/UglyToad.PdfPig/Filters/Ascii85Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
using Core;
using Tokens;

/// <inheritdoc />
/// <summary>
/// ASCII 85 (Base85) is a binary to text encoding using 5 ASCII characters per 4 bytes of data.
/// </summary>
internal sealed class Ascii85Filter : IFilter
public sealed class Ascii85Filter : IFilter
{
private const byte EmptyBlock = (byte)'z';
private const byte Offset = (byte)'!';
Expand Down
3 changes: 1 addition & 2 deletions src/UglyToad.PdfPig/Filters/AsciiHexDecodeFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
using Core;
using Tokens;

/// <inheritdoc />
/// <summary>
/// Encodes/decodes data using the ASCII hexadecimal encoding where each byte is represented by two ASCII characters.
/// </summary>
internal sealed class AsciiHexDecodeFilter : IFilter
public sealed class AsciiHexDecodeFilter : IFilter
{
private static readonly short[] ReverseHex =
[
Expand Down
96 changes: 96 additions & 0 deletions src/UglyToad.PdfPig/Filters/BaseFilterProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
namespace UglyToad.PdfPig.Filters
{
using Core;
using System;
using System.Collections.Generic;
using System.Linq;
using Tokens;
using Util;

/// <summary>
/// Base abstract class for FilterProvider.
/// </summary>
public abstract class BaseFilterProvider : IFilterProvider
{
/// <summary>
/// Dictionary of filters.
/// </summary>
protected readonly IReadOnlyDictionary<string, IFilter> FilterInstances;

/// <summary>
/// Create a new <see cref="BaseFilterProvider"/> with the given filters.
/// </summary>
/// <param name="filterInstances"></param>
protected BaseFilterProvider(IReadOnlyDictionary<string, IFilter> filterInstances)
{
FilterInstances = filterInstances;
}

/// <inheritdoc />
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
{
if (dictionary is null)
{
throw new ArgumentNullException(nameof(dictionary));
}

var token = dictionary.GetObjectOrDefault(NameToken.Filter, NameToken.F);
if (token is null)
{
return Array.Empty<IFilter>();
}

switch (token)
{
case ArrayToken filters:
var result = new IFilter[filters.Data.Count];
for (var i = 0; i < filters.Data.Count; i++)
{
var filterToken = filters.Data[i];
var filterName = ((NameToken)filterToken).Data;
result[i] = GetFilterStrict(filterName);
}

return result;
case NameToken name:
return new[] { GetFilterStrict(name.Data) };
default:
throw new PdfDocumentFormatException($"The filter for the stream was not a valid object. Expected name or array, instead got: {token}.");
}
}

/// <inheritdoc />
public IReadOnlyList<IFilter> GetNamedFilters(IReadOnlyList<NameToken> names)
{
if (names is null)
{
throw new ArgumentNullException(nameof(names));
}

var result = new List<IFilter>();

foreach (var name in names)
{
result.Add(GetFilterStrict(name));
}

return result;
}

private IFilter GetFilterStrict(string name)
{
if (!FilterInstances.TryGetValue(name, out var factory))
{
throw new NotSupportedException($"The filter with the name {name} is not supported yet. Please raise an issue.");
}

return factory;
}

/// <inheritdoc />
public IReadOnlyList<IFilter> GetAllFilters()
{
return FilterInstances.Values.Distinct().ToList();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
namespace UglyToad.PdfPig.Filters
{
/// <summary>
/// Specifies the compression type to use with <see cref="T:UglyToad.PdfPig.Filters.CcittFaxDecoderStream" />.
/// </summary>
internal enum CcittFaxCompressionType
{
/// <summary>
/// Modified Huffman (MH) - Group 3 variation (T2)
/// </summary>
ModifiedHuffman,
/// <summary>
/// Modified Huffman (MH) - Group 3 (T4)
/// </summary>
Group3_1D,
/// <summary>
/// Modified Read (MR) - Group 3 (T4)
/// </summary>
Group3_2D,
/// <summary>
/// Modified Modified Read (MMR) - Group 4 (T6)
/// </summary>
Group4_2D
}
}
namespace UglyToad.PdfPig.Filters.CcittFax
{
/// <summary>
/// Specifies the compression type to use with <see cref="T:UglyToad.PdfPig.Filters.CcittFaxDecoderStream" />.
/// </summary>
internal enum CcittFaxCompressionType : byte
{
/// <summary>
/// Modified Huffman (MH) - Group 3 variation (T2)
/// </summary>
ModifiedHuffman,
/// <summary>
/// Modified Huffman (MH) - Group 3 (T4)
/// </summary>
Group3_1D,
/// <summary>
/// Modified Read (MR) - Group 3 (T4)
/// </summary>
Group3_2D,
/// <summary>
/// Modified Modified Read (MMR) - Group 4 (T6)
/// </summary>
Group4_2D
}
}
Loading