diff --git a/src/UglyToad.PdfPig/PdfExtensions.cs b/src/UglyToad.PdfPig/PdfExtensions.cs
index 68d9012e6..fe7d1f91d 100644
--- a/src/UglyToad.PdfPig/PdfExtensions.cs
+++ b/src/UglyToad.PdfPig/PdfExtensions.cs
@@ -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;
-
- ///
- /// Extensions for PDF types.
- ///
- public static class PdfExtensions
- {
- ///
- /// Try and get the entry with a given name and type or look-up the object if it's an indirect reference.
- ///
- public static bool TryGet(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;
- }
-
- ///
- /// Get the entry with a given name and type or look-up the object if it's an indirect reference.
- ///
- public static T Get(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(indirectReference, scanner);
- }
-
- return typedToken;
+ using Tokenization.Scanner;
+ using Tokens;
+
+ ///
+ /// Extensions for PDF types.
+ ///
+ public static class PdfExtensions
+ {
+ ///
+ /// Try and get the entry with a given name and type or look-up the object if it's an indirect reference.
+ ///
+ public static bool TryGet(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;
+ }
+
+ ///
+ /// Get the entry with a given name and type or look-up the object if it's an indirect reference.
+ ///
+ public static T Get(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(indirectReference, scanner);
+ }
+
+ return typedToken;
+ }
+
+ ///
+ /// Get the decoded data from this stream.
+ ///
+ public static ReadOnlyMemory 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;
}
- ///
- /// Get the decoded data from this stream.
- ///
- public static ReadOnlyMemory 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;
+ ///
+ /// Get the decoded data from this stream.
+ ///
+ public static ReadOnlyMemory 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;
+ }
+
+ ///
+ /// Returns an equivalent token where any indirect references of child objects are
+ /// recursively traversed and resolved.
+ ///
+ internal static T Resolve(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();
+ 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();
+ 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;
}
-
- ///
- /// Get the decoded data from this stream.
- ///
- public static ReadOnlyMemory 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;
- }
- }
-}
+ }
+}
diff --git a/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs b/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs
index 16d699f6d..a6f4b47cb 100644
--- a/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs
+++ b/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs
@@ -34,24 +34,18 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
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(NameToken.Width, pdfScanner).Int;
- var height = dictionary.Get(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)
@@ -63,7 +57,7 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
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;
System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span));
@@ -76,7 +70,7 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
}
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}.");
}
@@ -86,26 +80,14 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
}
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;
+
var supportsFilters = true;
var filters = filterProvider.GetFilters(dictionary, pdfScanner);
foreach (var filter in filters)
@@ -115,34 +97,15 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
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>(() => streamToken.Decode(filterProvider, pdfScanner))
: null;
var decode = Array.Empty();
-
- if (dictionary.TryGet(NameToken.Decode, pdfScanner, out ArrayToken? decodeArrayToken))
+ if (dictionary.TryGet(NameToken.Decode, out ArrayToken decodeArrayToken))
{
decode = decodeArrayToken.Data.OfType()
.Select(x => x.Double)
@@ -152,11 +115,11 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
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)
{
details = resourceStore.GetColorSpaceDetails(firstColorSpaceName, dictionary);
@@ -164,7 +127,7 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
else if (!isJpxDecode)
{
details = xObject.DefaultColorSpace;
- }
+ }
}
else
{
@@ -184,7 +147,7 @@ public static XObjectImage ReadImage(XObjectContentRecord xObject,
dictionary,
xObject.Stream.Data,
decodedBytes,
- details);
+ details);
}
}
}