#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,
bool isLenientParsing);
void LoadResources(DictionaryToken dictionary, bool isLenientParsing);
}
}

View File

@@ -7,6 +7,12 @@
{
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);
StreamToken GetXObject(NameToken name);

View File

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

View File

@@ -50,7 +50,10 @@
{
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))
{

View File

@@ -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<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>();
@@ -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<DictionaryToken>(fontBase, scanner);
@@ -95,6 +98,11 @@
}
}
public void UnloadResourceDictionary()
{
currentResourceState.Pop();
}
private void LoadFontDictionary(DictionaryToken fontDictionary, bool isLenientParsing)
{
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.
*/
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);
}
@@ -368,6 +369,11 @@
// 5. Restore saved state.
PopState();
if (hasResources)
{
resourceStore.UnloadResourceDictionary();
}
}
public void BeginSubpath()

View File

@@ -58,9 +58,23 @@
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, isLenientParsing);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox, isLenientParsing);
UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary);
var stackDepth = 0;
LoadResources(dictionary, isLenientParsing);
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);
PageContent content = default(PageContent);
@@ -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<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);
}
}
}