#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.
This commit is contained in:
Eliot Jones
2019-11-25 14:34:02 +00:00
parent 9028f932b2
commit 677d2b5e8f
8 changed files with 121 additions and 20 deletions

View File

@@ -6,7 +6,5 @@
{ {
Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers,
bool isLenientParsing); bool isLenientParsing);
void LoadResources(DictionaryToken dictionary, bool isLenientParsing);
} }
} }

View File

@@ -7,6 +7,12 @@
{ {
void LoadResourceDictionary(DictionaryToken resourceDictionary, bool isLenientParsing); void LoadResourceDictionary(DictionaryToken resourceDictionary, bool isLenientParsing);
/// <summary>
/// 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.
/// </summary>
void UnloadResourceDictionary();
IFont GetFont(NameToken name); IFont GetFont(NameToken name);
StreamToken GetXObject(NameToken name); StreamToken GetXObject(NameToken name);

View File

@@ -1,5 +1,8 @@
namespace UglyToad.PdfPig.Content namespace UglyToad.PdfPig.Content
{ {
using System.Collections.Generic;
using Tokens;
/// <summary> /// <summary>
/// Contains the values inherited from the Page Tree for this page. /// Contains the values inherited from the Page Tree for this page.
/// </summary> /// </summary>
@@ -13,5 +16,7 @@
public MediaBox MediaBox { get; set; } public MediaBox MediaBox { get; set; }
public int Rotation { get; set; } public int Rotation { get; set; }
public Queue<DictionaryToken> ParentResources { get; } = new Queue<DictionaryToken>();
} }
} }

View File

@@ -45,12 +45,15 @@
} }
var pageTreeMembers = new PageTreeMembers(); var pageTreeMembers = new PageTreeMembers();
while (pageStack.Count > 0) while (pageStack.Count > 0)
{ {
currentNode = pageStack.Pop(); 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)) if (currentNode.NodeDictionary.TryGet(NameToken.MediaBox, pdfScanner, out ArrayToken mediaBox))
{ {

View File

@@ -7,6 +7,7 @@
using Parser.Parts; using Parser.Parts;
using Tokenization.Scanner; using Tokenization.Scanner;
using Tokens; using Tokens;
using Util;
internal class ResourceStore : IResourceStore internal class ResourceStore : IResourceStore
{ {
@@ -14,7 +15,7 @@
private readonly IFontFactory fontFactory; private readonly IFontFactory fontFactory;
private readonly Dictionary<IndirectReference, IFont> loadedFonts = new Dictionary<IndirectReference, IFont>(); private readonly Dictionary<IndirectReference, IFont> loadedFonts = new Dictionary<IndirectReference, IFont>();
private readonly Dictionary<NameToken, IndirectReference> currentResourceState = new Dictionary<NameToken, IndirectReference>(); private readonly StackDictionary<NameToken, IndirectReference> currentResourceState = new StackDictionary<NameToken, IndirectReference>();
private readonly Dictionary<NameToken, DictionaryToken> extendedGraphicsStates = new Dictionary<NameToken, DictionaryToken>(); private readonly Dictionary<NameToken, DictionaryToken> extendedGraphicsStates = new Dictionary<NameToken, DictionaryToken>();
@@ -28,6 +29,8 @@
public void LoadResourceDictionary(DictionaryToken resourceDictionary, bool isLenientParsing) public void LoadResourceDictionary(DictionaryToken resourceDictionary, bool isLenientParsing)
{ {
currentResourceState.Push();
if (resourceDictionary.TryGet(NameToken.Font, out var fontBase)) if (resourceDictionary.TryGet(NameToken.Font, out var fontBase))
{ {
var fontDictionary = DirectObjectFinder.Get<DictionaryToken>(fontBase, scanner); var fontDictionary = DirectObjectFinder.Get<DictionaryToken>(fontBase, scanner);
@@ -95,6 +98,11 @@
} }
} }
public void UnloadResourceDictionary()
{
currentResourceState.Pop();
}
private void LoadFontDictionary(DictionaryToken fontDictionary, bool isLenientParsing) private void LoadFontDictionary(DictionaryToken fontDictionary, bool isLenientParsing)
{ {
foreach (var pair in fontDictionary.Data) foreach (var pair in fontDictionary.Data)

View File

@@ -336,7 +336,8 @@
* 5. Restore the saved graphics state, as if by invoking the Q operator. * 5. Restore the saved graphics state, as if by invoking the Q operator.
*/ */
if (formStream.StreamDictionary.TryGet<DictionaryToken>(NameToken.Resources, pdfScanner, out var formResources)) var hasResources = formStream.StreamDictionary.TryGet<DictionaryToken>(NameToken.Resources, pdfScanner, out var formResources);
if (hasResources)
{ {
resourceStore.LoadResourceDictionary(formResources, isLenientParsing); resourceStore.LoadResourceDictionary(formResources, isLenientParsing);
} }
@@ -368,6 +369,11 @@
// 5. Restore saved state. // 5. Restore saved state.
PopState(); PopState();
if (hasResources)
{
resourceStore.UnloadResourceDictionary();
}
} }
public void BeginSubpath() public void BeginSubpath()

View File

@@ -57,11 +57,25 @@
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, isLenientParsing); MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, isLenientParsing);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox, 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); UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary);
LoadResources(dictionary, isLenientParsing);
PageContent content = default(PageContent); PageContent content = default(PageContent);
if (!dictionary.TryGet(NameToken.Contents, out var contents)) 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)); 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; return page;
} }
@@ -201,17 +220,5 @@
return mediaBox; return mediaBox;
} }
public void LoadResources(DictionaryToken dictionary, bool isLenientParsing)
{
if (!dictionary.TryGet(NameToken.Resources, out var token))
{
return;
}
var resources = DirectObjectFinder.Get<DictionaryToken>(token, pdfScanner);
resourceStore.LoadResourceDictionary(resources, isLenientParsing);
}
} }
} }

View File

@@ -0,0 +1,68 @@
// ReSharper disable InconsistentNaming
namespace UglyToad.PdfPig.Util
{
using System;
using System.Collections.Generic;
internal class StackDictionary<K, V>
{
private readonly List<Dictionary<K, V>> values = new List<Dictionary<K, V>>();
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<K, V>());
}
public void Pop()
{
if (values.Count == 0)
{
throw new InvalidOperationException("Cannot pop empty stacked dictionary.");
}
values.RemoveAt(values.Count - 1);
}
}
}