diff --git a/src/UglyToad.PdfPig/Content/IPageFactory.cs b/src/UglyToad.PdfPig/Content/IPageFactory.cs index 8ca300ed..9076cce1 100644 --- a/src/UglyToad.PdfPig/Content/IPageFactory.cs +++ b/src/UglyToad.PdfPig/Content/IPageFactory.cs @@ -6,7 +6,5 @@ { Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, bool isLenientParsing); - - void LoadResources(DictionaryToken dictionary, bool isLenientParsing); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Content/IResourceStore.cs b/src/UglyToad.PdfPig/Content/IResourceStore.cs index 66b03752..a5f33767 100644 --- a/src/UglyToad.PdfPig/Content/IResourceStore.cs +++ b/src/UglyToad.PdfPig/Content/IResourceStore.cs @@ -7,6 +7,12 @@ { void LoadResourceDictionary(DictionaryToken resourceDictionary, bool isLenientParsing); + /// + /// Remove any named resources and associated state for the last resource dictionary loaded. + /// Does not affect the cached resources, just the labels associated with them. + /// + void UnloadResourceDictionary(); + IFont GetFont(NameToken name); StreamToken GetXObject(NameToken name); diff --git a/src/UglyToad.PdfPig/Content/PageTreeMembers.cs b/src/UglyToad.PdfPig/Content/PageTreeMembers.cs index 9f14c999..20ae0744 100644 --- a/src/UglyToad.PdfPig/Content/PageTreeMembers.cs +++ b/src/UglyToad.PdfPig/Content/PageTreeMembers.cs @@ -1,5 +1,8 @@ namespace UglyToad.PdfPig.Content { + using System.Collections.Generic; + using Tokens; + /// /// Contains the values inherited from the Page Tree for this page. /// @@ -13,5 +16,7 @@ public MediaBox MediaBox { get; set; } public int Rotation { get; set; } + + public Queue ParentResources { get; } = new Queue(); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Content/Pages.cs b/src/UglyToad.PdfPig/Content/Pages.cs index 7304802e..bf9cdac3 100644 --- a/src/UglyToad.PdfPig/Content/Pages.cs +++ b/src/UglyToad.PdfPig/Content/Pages.cs @@ -45,12 +45,15 @@ } var pageTreeMembers = new PageTreeMembers(); - + while (pageStack.Count > 0) { currentNode = pageStack.Pop(); - pageFactory.LoadResources(currentNode.NodeDictionary, isLenientParsing); + if (currentNode.NodeDictionary.TryGet(NameToken.Resources, pdfScanner, out DictionaryToken resourcesDictionary)) + { + pageTreeMembers.ParentResources.Enqueue(resourcesDictionary); + } if (currentNode.NodeDictionary.TryGet(NameToken.MediaBox, pdfScanner, out ArrayToken mediaBox)) { diff --git a/src/UglyToad.PdfPig/Content/ResourceStore.cs b/src/UglyToad.PdfPig/Content/ResourceStore.cs index e8a70ce9..92ca2fe8 100644 --- a/src/UglyToad.PdfPig/Content/ResourceStore.cs +++ b/src/UglyToad.PdfPig/Content/ResourceStore.cs @@ -7,6 +7,7 @@ using Parser.Parts; using Tokenization.Scanner; using Tokens; + using Util; internal class ResourceStore : IResourceStore { @@ -14,7 +15,7 @@ private readonly IFontFactory fontFactory; private readonly Dictionary loadedFonts = new Dictionary(); - private readonly Dictionary currentResourceState = new Dictionary(); + private readonly StackDictionary currentResourceState = new StackDictionary(); private readonly Dictionary extendedGraphicsStates = new Dictionary(); @@ -28,6 +29,8 @@ public void LoadResourceDictionary(DictionaryToken resourceDictionary, bool isLenientParsing) { + currentResourceState.Push(); + if (resourceDictionary.TryGet(NameToken.Font, out var fontBase)) { var fontDictionary = DirectObjectFinder.Get(fontBase, scanner); @@ -95,6 +98,11 @@ } } + public void UnloadResourceDictionary() + { + currentResourceState.Pop(); + } + private void LoadFontDictionary(DictionaryToken fontDictionary, bool isLenientParsing) { foreach (var pair in fontDictionary.Data) diff --git a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs index 430498cb..4346e371 100644 --- a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs @@ -336,7 +336,8 @@ * 5. Restore the saved graphics state, as if by invoking the Q operator. */ - if (formStream.StreamDictionary.TryGet(NameToken.Resources, pdfScanner, out var formResources)) + var hasResources = formStream.StreamDictionary.TryGet(NameToken.Resources, pdfScanner, out var formResources); + if (hasResources) { resourceStore.LoadResourceDictionary(formResources, isLenientParsing); } @@ -368,6 +369,11 @@ // 5. Restore saved state. PopState(); + + if (hasResources) + { + resourceStore.UnloadResourceDictionary(); + } } public void BeginSubpath() diff --git a/src/UglyToad.PdfPig/Parser/PageFactory.cs b/src/UglyToad.PdfPig/Parser/PageFactory.cs index b182b530..e3dc71c5 100644 --- a/src/UglyToad.PdfPig/Parser/PageFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PageFactory.cs @@ -57,11 +57,25 @@ MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, isLenientParsing); CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox, isLenientParsing); + + var stackDepth = 0; + + while (pageTreeMembers.ParentResources.Count > 0) + { + var resource = pageTreeMembers.ParentResources.Dequeue(); + + resourceStore.LoadResourceDictionary(resource, isLenientParsing); + stackDepth++; + } + + if (dictionary.TryGet(NameToken.Resources, pdfScanner, out DictionaryToken resources)) + { + resourceStore.LoadResourceDictionary(resources, isLenientParsing); + stackDepth++; + } UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary); - LoadResources(dictionary, isLenientParsing); - PageContent content = default(PageContent); if (!dictionary.TryGet(NameToken.Contents, out var contents)) @@ -114,6 +128,11 @@ var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content, new AnnotationProvider(pdfScanner, dictionary, isLenientParsing)); + for (var i = 0; i < stackDepth; i++) + { + resourceStore.UnloadResourceDictionary(); + } + return page; } @@ -201,17 +220,5 @@ return mediaBox; } - - public void LoadResources(DictionaryToken dictionary, bool isLenientParsing) - { - if (!dictionary.TryGet(NameToken.Resources, out var token)) - { - return; - } - - var resources = DirectObjectFinder.Get(token, pdfScanner); - - resourceStore.LoadResourceDictionary(resources, isLenientParsing); - } } } diff --git a/src/UglyToad.PdfPig/Util/StackDictionary.cs b/src/UglyToad.PdfPig/Util/StackDictionary.cs new file mode 100644 index 00000000..c3a3408b --- /dev/null +++ b/src/UglyToad.PdfPig/Util/StackDictionary.cs @@ -0,0 +1,68 @@ +// ReSharper disable InconsistentNaming +namespace UglyToad.PdfPig.Util +{ + using System; + using System.Collections.Generic; + + internal class StackDictionary + { + private readonly List> values = new List>(); + + public V this[K key] + { + get + { + if (TryGetValue(key, out var result)) + { + return result; + } + + throw new KeyNotFoundException($"No item with key {key} in stack."); + } + set + { + if (values.Count == 0) + { + throw new InvalidOperationException($"Cannot set item in empty stack, call {nameof(Push)} before use."); + } + + values[values.Count - 1][key] = value; + } + } + + public bool TryGetValue(K key, out V result) + { + if (values.Count == 0) + { + throw new InvalidOperationException($"Cannot get item from empty stack, call {nameof(Push)} before use."); + } + + for (var i = values.Count - 1; i >= 0; i--) + { + if (values[i].TryGetValue(key, out result)) + { + return true; + } + } + + result = default(V); + + return false; + } + + public void Push() + { + values.Add(new Dictionary()); + } + + public void Pop() + { + if (values.Count == 0) + { + throw new InvalidOperationException("Cannot pop empty stacked dictionary."); + } + + values.RemoveAt(values.Count - 1); + } + } +}