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
205 changes: 124 additions & 81 deletions src/UglyToad.PdfPig/PdfExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,87 +1,130 @@
namespace UglyToad.PdfPig
namespace UglyToad.PdfPig
{
using System;
using System;
using System.Diagnostics.CodeAnalysis;
using Core;
using Filters;
using Core;
using Filters;
using Parser.Parts;
using Tokenization.Scanner;
using Tokens;

/// <summary>
/// Extensions for PDF types.
/// </summary>
public static class PdfExtensions
{
/// <summary>
/// Try and get the entry with a given name and type or look-up the object if it's an indirect reference.
/// </summary>
public static bool TryGet<T>(this DictionaryToken dictionary, NameToken name, IPdfTokenScanner tokenScanner, [NotNullWhen(true)] out T? token)
where T : class, IToken
{
token = default;
if (!dictionary.TryGet(name, out var t) || !(t is T typedToken))
{
if (t is IndirectReferenceToken reference)
{
return DirectObjectFinder.TryGet(reference, tokenScanner, out token);
}

return false;
}

token = typedToken;
return true;
}

/// <summary>
/// Get the entry with a given name and type or look-up the object if it's an indirect reference.
/// </summary>
public static T Get<T>(this DictionaryToken dictionary, NameToken name, IPdfTokenScanner scanner) where T : class, IToken
{
if (!dictionary.TryGet(name, out var token) || !(token is T typedToken))
{
if (!(token is IndirectReferenceToken indirectReference))
{
throw new PdfDocumentFormatException($"Dictionary does not contain token with name {name} of type {typeof(T).Name}.");
}

typedToken = DirectObjectFinder.Get<T>(indirectReference, scanner);
}

return typedToken;
using Tokenization.Scanner;
using Tokens;

/// <summary>
/// Extensions for PDF types.
/// </summary>
public static class PdfExtensions
{
/// <summary>
/// Try and get the entry with a given name and type or look-up the object if it's an indirect reference.
/// </summary>
public static bool TryGet<T>(this DictionaryToken dictionary, NameToken name, IPdfTokenScanner tokenScanner, [NotNullWhen(true)] out T? token)
where T : class, IToken
{
token = default;
if (!dictionary.TryGet(name, out var t) || !(t is T typedToken))
{
if (t is IndirectReferenceToken reference)
{
return DirectObjectFinder.TryGet(reference, tokenScanner, out token);
}

return false;
}

token = typedToken;
return true;
}

/// <summary>
/// Get the entry with a given name and type or look-up the object if it's an indirect reference.
/// </summary>
public static T Get<T>(this DictionaryToken dictionary, NameToken name, IPdfTokenScanner scanner) where T : class, IToken
{
if (!dictionary.TryGet(name, out var token) || !(token is T typedToken))
{
if (!(token is IndirectReferenceToken indirectReference))
{
throw new PdfDocumentFormatException($"Dictionary does not contain token with name {name} of type {typeof(T).Name}.");
}

typedToken = DirectObjectFinder.Get<T>(indirectReference, scanner);
}

return typedToken;
}

/// <summary>
/// Get the decoded data from this stream.
/// </summary>
public static ReadOnlyMemory<byte> Decode(this StreamToken stream, IFilterProvider filterProvider)
{
var filters = filterProvider.GetFilters(stream.StreamDictionary);

var transform = stream.Data;
for (var i = 0; i < filters.Count; i++)
{
transform = filters[i].Decode(transform.Span, stream.StreamDictionary, i);
}

return transform;
}

/// <summary>
/// Get the decoded data from this stream.
/// </summary>
public static ReadOnlyMemory<byte> Decode(this StreamToken stream, IFilterProvider filterProvider)
{
var filters = filterProvider.GetFilters(stream.StreamDictionary);

var transform = stream.Data;
for (var i = 0; i < filters.Count; i++)
{
transform = filters[i].Decode(transform.Span, stream.StreamDictionary, i);
}

return transform;
/// <summary>
/// Get the decoded data from this stream.
/// </summary>
public static ReadOnlyMemory<byte> Decode(this StreamToken stream, ILookupFilterProvider filterProvider, IPdfTokenScanner scanner)
{
var filters = filterProvider.GetFilters(stream.StreamDictionary, scanner);

var transform = stream.Data;
for (var i = 0; i < filters.Count; i++)
{
transform = filters[i].Decode(transform.Span, stream.StreamDictionary, i);
}

return transform;
}

/// <summary>
/// Returns an equivalent token where any indirect references of child objects are
/// recursively traversed and resolved.
/// </summary>
internal static T Resolve<T>(this T token, IPdfTokenScanner scanner) where T : IToken
{
return (T)ResolveInternal(token, scanner);
}

private static IToken ResolveInternal(this IToken token, IPdfTokenScanner scanner)
{
if (token is StreamToken stream)
{
return new StreamToken(Resolve(stream.StreamDictionary, scanner), stream.Data);
}

if (token is DictionaryToken dict)
{
var resolvedItems = new Dictionary<NameToken, IToken>();
foreach (var kvp in dict.Data)
{
var value = kvp.Value is IndirectReferenceToken reference ? scanner.Get(reference.Data).Data : kvp.Value;
resolvedItems[NameToken.Create(kvp.Key)] = ResolveInternal(value, scanner);
}

return new DictionaryToken(resolvedItems);
}

if (token is ArrayToken arr)
{
var resolvedItems = new List<IToken>();
for (int i = 0; i < arr.Length; i++)
{
var value = arr.Data[i] is IndirectReferenceToken reference ? scanner.Get(reference.Data).Data : arr.Data[i];
resolvedItems.Add(ResolveInternal(value, scanner));
}
return new ArrayToken(resolvedItems);
}

var val = token is IndirectReferenceToken tokenReference ? scanner.Get(tokenReference.Data).Data : token;
return val;
}

/// <summary>
/// Get the decoded data from this stream.
/// </summary>
public static ReadOnlyMemory<byte> Decode(this StreamToken stream, ILookupFilterProvider filterProvider, IPdfTokenScanner scanner)
{
var filters = filterProvider.GetFilters(stream.StreamDictionary, scanner);

var transform = stream.Data;
for (var i = 0; i < filters.Count; i++)
{
transform = filters[i].Decode(transform.Span, stream.StreamDictionary, i);
}

return transform;
}
}
}
}
}
73 changes: 18 additions & 55 deletions src/UglyToad.PdfPig/XObjects/XObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,18 @@
if (xObject.Type != XObjectType.Image)
{
throw new InvalidOperationException($"Cannot create an image from an XObject with type: {xObject.Type}.");
}

var dictionary = xObject.Stream.StreamDictionary;
}
var dictionary = xObject.Stream.StreamDictionary.Resolve(pdfScanner);

var bounds = xObject.AppliedTransformation.Transform(new PdfRectangle(new PdfPoint(0, 0), new PdfPoint(1, 1)));

var width = dictionary.Get<NumericToken>(NameToken.Width, pdfScanner).Int;
var height = dictionary.Get<NumericToken>(NameToken.Height, pdfScanner).Int;
var width = dictionary.GetInt(NameToken.Width);
var height = dictionary.GetInt(NameToken.Height);

bool isImageMask = false;
if (dictionary.TryGet(NameToken.ImageMask, pdfScanner, out BooleanToken? isMaskToken))
{
dictionary = dictionary.With(NameToken.ImageMask, isMaskToken);
isImageMask = isMaskToken.Data;
}
var isImageMask = dictionary.TryGet(NameToken.ImageMask, out BooleanToken isMaskToken) && isMaskToken.Data;

var isJpxDecode = dictionary.TryGet(NameToken.Filter, pdfScanner, out NameToken filterName)
&& filterName.Equals(NameToken.JpxDecode);
var isJpxDecode = dictionary.TryGet(NameToken.Filter, out NameToken filterName) && filterName.Equals(NameToken.JpxDecode);

int bitsPerComponent;
if (isImageMask)
Expand All @@ -63,9 +57,9 @@
if (isJpxDecode)
{
// Optional for JPX
if (dictionary.TryGet(NameToken.BitsPerComponent, pdfScanner, out NumericToken? bitsPerComponentToken))
if (dictionary.TryGet(NameToken.BitsPerComponent, out NumericToken? bitsPerComponentToken))
{
bitsPerComponent = bitsPerComponentToken.Int;

Check warning on line 62 in src/UglyToad.PdfPig/XObjects/XObjectFactory.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span));
}
else
Expand All @@ -76,36 +70,24 @@
}
else
{
if (!dictionary.TryGet(NameToken.BitsPerComponent, pdfScanner, out NumericToken? bitsPerComponentToken))
if (!dictionary.TryGet(NameToken.BitsPerComponent, out NumericToken? bitsPerComponentToken))
{
throw new PdfDocumentFormatException($"No bits per component defined for image: {dictionary}.");
}

bitsPerComponent = bitsPerComponentToken.Int;

Check warning on line 78 in src/UglyToad.PdfPig/XObjects/XObjectFactory.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
}
}

var intent = xObject.DefaultRenderingIntent;
if (dictionary.TryGet(NameToken.Intent, pdfScanner, out NameToken renderingIntentToken))
if (dictionary.TryGet(NameToken.Intent, out NameToken renderingIntentToken))
{
intent = renderingIntentToken.Data.ToRenderingIntent();
}

var interpolate = dictionary.TryGet(NameToken.Interpolate, pdfScanner, out BooleanToken? interpolateToken)
&& interpolateToken.Data;

if (dictionary.TryGet(NameToken.Filter, out var filterToken) && filterToken is IndirectReferenceToken)
{
if (dictionary.TryGet(NameToken.Filter, pdfScanner, out ArrayToken? filterArray))
{
dictionary = dictionary.With(NameToken.Filter, filterArray);
}
else if (dictionary.TryGet(NameToken.Filter, pdfScanner, out NameToken? filterNameToken))
{
dictionary = dictionary.With(NameToken.Filter, filterNameToken);
}
}

var interpolate = dictionary.TryGet(NameToken.Interpolate, out BooleanToken? interpolateToken)
&& interpolateToken.Data;

Check warning on line 89 in src/UglyToad.PdfPig/XObjects/XObjectFactory.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

var supportsFilters = true;
var filters = filterProvider.GetFilters(dictionary, pdfScanner);
foreach (var filter in filters)
Expand All @@ -115,34 +97,15 @@
supportsFilters = false;
break;
}
}

var decodeParams = dictionary.GetObjectOrDefault(NameToken.DecodeParms, NameToken.Dp);
if (decodeParams is IndirectReferenceToken refToken)
{
dictionary = dictionary.With(NameToken.DecodeParms, pdfScanner.Get(refToken.Data).Data);
}

var jbig2GlobalsParams = dictionary.GetObjectOrDefault(NameToken.Jbig2Globals);
if (jbig2GlobalsParams is IndirectReferenceToken jbig2RefToken)
{
dictionary = dictionary.With(NameToken.Jbig2Globals, pdfScanner.Get(jbig2RefToken.Data).Data);
}

var imParams = dictionary.GetObjectOrDefault(NameToken.Im);
if (imParams is IndirectReferenceToken imRefToken)
{
dictionary = dictionary.With(NameToken.Im, pdfScanner.Get(imRefToken.Data).Data);
}

var streamToken = new StreamToken(dictionary, xObject.Stream.Data);

var decodedBytes = supportsFilters ? new Lazy<ReadOnlyMemory<byte>>(() => streamToken.Decode(filterProvider, pdfScanner))
: null;

var decode = Array.Empty<double>();

if (dictionary.TryGet(NameToken.Decode, pdfScanner, out ArrayToken? decodeArrayToken))
if (dictionary.TryGet(NameToken.Decode, out ArrayToken decodeArrayToken))
{
decode = decodeArrayToken.Data.OfType<NumericToken>()
.Select(x => x.Double)
Expand All @@ -152,19 +115,19 @@
ColorSpaceDetails? details = null;
if (!isImageMask)
{
if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out NameToken? colorSpaceNameToken))
if (dictionary.TryGet(NameToken.ColorSpace, out NameToken? colorSpaceNameToken))
{
details = resourceStore.GetColorSpaceDetails(colorSpaceNameToken, dictionary);
}
else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken? colorSpaceArrayToken)
else if (dictionary.TryGet(NameToken.ColorSpace, out ArrayToken? colorSpaceArrayToken)
&& colorSpaceArrayToken.Length > 0 && colorSpaceArrayToken.Data[0] is NameToken firstColorSpaceName)

Check warning on line 123 in src/UglyToad.PdfPig/XObjects/XObjectFactory.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
details = resourceStore.GetColorSpaceDetails(firstColorSpaceName, dictionary);
}
else if (!isJpxDecode)
{
details = xObject.DefaultColorSpace;
}
}
}
else
{
Expand All @@ -184,7 +147,7 @@
dictionary,
xObject.Stream.Data,
decodedBytes,
details);
details);
}
}
}
Loading