From 1d31b50260e8c51a89906557b967292ff4fa0174 Mon Sep 17 00:00:00 2001 From: EliotJones Date: Sat, 5 Jul 2025 16:55:04 -0500 Subject: [PATCH] writer util did not follow reference links #1032 when copying various dictionaries from a source document to the builder any indirect references in the source document would throw because the code expected the dictionary token directly. now we follow the list of indirect references until we find a non-indirect leaf token. also changes the exception type. --- src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs | 8 ++-- src/UglyToad.PdfPig/Writer/WriterUtil.cs | 45 ++++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs index b6e17e47e..79d64c23c 100644 --- a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs @@ -831,7 +831,7 @@ public PdfPageBuilder CopyFrom(Page srcPage) // We need to relocate the resources, and we have to make sure that none of the resources collide with // the already written operation's resources - var resources = pageDictionary.GetOrCreateDict(NameToken.Resources); + var resources = pageDictionary.GetOrCreateDict(NameToken.Resources, srcPage.pdfScanner); foreach (var set in srcResourceDictionary.Data) { @@ -858,7 +858,7 @@ public PdfPageBuilder CopyFrom(Page srcPage) // Since we don't directly add font's to the pages resources, we have to go look at the document's font if (srcResourceDictionary.TryGet(NameToken.Font, srcPage.pdfScanner, out DictionaryToken? fontsDictionary)) { - var pageFontsDictionary = resources.GetOrCreateDict(NameToken.Font); + var pageFontsDictionary = resources.GetOrCreateDict(NameToken.Font, srcPage.pdfScanner); foreach (var fontSet in fontsDictionary.Data) { @@ -903,7 +903,7 @@ public PdfPageBuilder CopyFrom(Page srcPage) // Since we don't directly add xobjects's to the pages resources, we have to go look at the document's xobjects if (srcResourceDictionary.TryGet(NameToken.Xobject, srcPage.pdfScanner, out DictionaryToken? xobjectsDictionary)) { - var pageXobjectsDictionary = resources.GetOrCreateDict(NameToken.Xobject); + var pageXobjectsDictionary = resources.GetOrCreateDict(NameToken.Xobject, srcPage.pdfScanner); foreach (var xobjectSet in xobjectsDictionary.Data) { @@ -945,7 +945,7 @@ public PdfPageBuilder CopyFrom(Page srcPage) // Since we don't directly add xobjects's to the pages resources, we have to go look at the document's xobjects if (srcResourceDictionary.TryGet(NameToken.ExtGState, srcPage.pdfScanner, out DictionaryToken? gsDictionary)) { - var pageGstateDictionary = resources.GetOrCreateDict(NameToken.ExtGState); + var pageGstateDictionary = resources.GetOrCreateDict(NameToken.ExtGState, srcPage.pdfScanner); foreach (var gstate in gsDictionary.Data) { diff --git a/src/UglyToad.PdfPig/Writer/WriterUtil.cs b/src/UglyToad.PdfPig/Writer/WriterUtil.cs index 52cbe4b4d..d2798ff76 100644 --- a/src/UglyToad.PdfPig/Writer/WriterUtil.cs +++ b/src/UglyToad.PdfPig/Writer/WriterUtil.cs @@ -12,38 +12,36 @@ internal static class WriterUtil { - public static Dictionary GetOrCreateDict(this Dictionary dict, NameToken key) + public static Dictionary GetOrCreateDict( + this Dictionary dict, + T key, + IPdfTokenScanner? sourceScanner = null) where T : notnull { if (dict.TryGetValue(key, out var item)) { - if (!(item is DictionaryToken dt)) + var chainCount = 0; + var itemChain = item; + while (itemChain is IndirectReferenceToken ir && chainCount < 100) { - throw new ApplicationException("Expected dictionary token, got " + item.GetType()); - } + if (sourceScanner == null) + { + break; + } - if (dt.Data is Dictionary mutable) - { - return mutable; - } + itemChain = sourceScanner.Get(ir.Data); - mutable = dt.Data. - ToDictionary(x => x.Key, x => x.Value); - dict[key] = DictionaryToken.With(mutable); - return mutable; - } + chainCount++; - var created = new Dictionary(); - dict[key] = DictionaryToken.With(created); - return created; - } + if (itemChain is ObjectToken ot) + { + itemChain = ot.Data; + } + } - public static Dictionary GetOrCreateDict(this Dictionary dict, string key) - { - if (dict.TryGetValue(key, out var item)) - { - if (!(item is DictionaryToken dt)) + if (itemChain is not DictionaryToken dt) { - throw new ApplicationException("Expected dictionary token, got " + item.GetType()); + throw new InvalidOperationException( + $"While trying to copy token called {key} which should have been a dictionary token we found a token of type {item.GetType()}"); } if (dt.Data is Dictionary mutable) @@ -61,6 +59,7 @@ public static Dictionary GetOrCreateDict(this Dictionary /// The purpose of this method is to resolve indirect reference. That mean copy the reference's content to the new document's stream /// and replace the indirect reference with the correct/new one