From 677d2b5e8f4fd7a8d15526b0fe2377240e6a651a Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Mon, 25 Nov 2019 14:34:02 +0000 Subject: [PATCH] #82 make resource store state local to the page and operation being processed resources such as fonts are linked to page content operations using name labels, e.g. "/F1", these resource labels can be reassigned on different pages or inside form xobjects. we now clear the entire resource state for each page which is parsed and after form xobject operations which use resource dictionaries. --- src/UglyToad.PdfPig/Content/IPageFactory.cs | 2 - src/UglyToad.PdfPig/Content/IResourceStore.cs | 6 ++ .../Content/PageTreeMembers.cs | 5 ++ src/UglyToad.PdfPig/Content/Pages.cs | 7 +- src/UglyToad.PdfPig/Content/ResourceStore.cs | 10 ++- .../Graphics/ContentStreamProcessor.cs | 8 ++- src/UglyToad.PdfPig/Parser/PageFactory.cs | 35 ++++++---- src/UglyToad.PdfPig/Util/StackDictionary.cs | 68 +++++++++++++++++++ 8 files changed, 121 insertions(+), 20 deletions(-) create mode 100644 src/UglyToad.PdfPig/Util/StackDictionary.cs 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); + } + } +}