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); } } }