diff --git a/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs b/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs index 74a906e4..62901ade 100644 --- a/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs +++ b/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs @@ -1,244 +1,275 @@ -namespace UglyToad.PdfPig.Tests.Writer -{ - using Integration; - using PdfPig.Writer; - using System; - using System.Collections.Generic; - using System.IO; - using Xunit; - - public class PdfMergerTests - { - [Fact] - public void CanMerge2SimpleDocuments() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - - var result = PdfMerger.Merge(one, two); - CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf."); - } - - [Fact] - public void CanMerge2SimpleDocumentsIntoStream() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - - using (var outputStream = GetSelfDestructingNewFileStream("merge2")) - { - if (outputStream is null) - { - return;//we can't create a file in this test session - } - - PdfMerger.Merge(one, two, outputStream); - CanMerge2SimpleDocumentsAssertions(outputStream, "Write something inInkscape", "I am a simple pdf."); - } - } - - [Fact] - public void CanMerge2SimpleDocumentsReversed() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - - var result = PdfMerger.Merge(one, two); - CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape"); - } - - internal static void CanMerge2SimpleDocumentsAssertions(Stream stream, string page1Text, string page2Text, bool checkVersion=true) - { - stream.Position = 0; - using (var document = PdfDocument.Open(stream, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(2, document.NumberOfPages); - if (checkVersion) - { - Assert.Equal(1.5m, document.Version); - } - - var page1 = document.GetPage(1); - Assert.Equal(page1Text, page1.Text); - - var page2 = document.GetPage(2); - Assert.Equal(page2Text, page2.Text); - } - } - - [Fact] - public void RootNodePageCount() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - - var result = PdfMerger.Merge(one, two); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(2, document.NumberOfPages); - } - - var oneBytes = File.ReadAllBytes(one); - - var result2 = PdfMerger.Merge(new[] { result, oneBytes }); - - using (var document = PdfDocument.Open(result2, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(3, document.NumberOfPages); - } - } - - [Fact] - public void ObjectCountLower() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - - var result = PdfMerger.Merge(one, two); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(2, document.NumberOfPages); - Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 24, - "Expected object count to be lower than 24"); - } - } - - [Fact] - public void DedupsObjectsFromSameDoc() - { - var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); - - var result = PdfMerger.Merge(new List { File.ReadAllBytes(one) }, new List> { new List { 1, 2} }); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(2, document.NumberOfPages); - Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29, - "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use - } - } - - [Fact] - public void CanMergeWithObjectStream() - { - var first = IntegrationHelpers.GetDocumentPath("Single Page Simple - from google drive.pdf"); - var second = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); - - var result = PdfMerger.Merge(first, second); - - WriteFile(nameof(CanMergeWithObjectStream), result); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(7, document.NumberOfPages); - - foreach (var page in document.GetPages()) - { - Assert.NotNull(page.Text); - } - } - } - - [Fact] - public void CanMergeWithSelection() - { - var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); - var contents = File.ReadAllBytes(first); - - var toCopy = new[] {2, 1, 4, 3, 6, 5}; - var result = PdfMerger.Merge(new [] { contents }, new [] { toCopy }); - - WriteFile(nameof(CanMergeWithSelection), result); - - using (var existing = PdfDocument.Open(contents, ParsingOptions.LenientParsingOff)) - using (var merged = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(6, merged.NumberOfPages); +namespace UglyToad.PdfPig.Tests.Writer +{ + using Integration; + using PdfPig.Writer; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Xunit; + + public class PdfMergerTests + { + [Fact] + public void CanMerge2SimpleDocuments() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + + var result = PdfMerger.Merge(one, two); + CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf."); + } + + [Fact] + public void CanMerge2SimpleDocumentsIntoStream() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + + using (var outputStream = GetSelfDestructingNewFileStream("merge2")) + { + if (outputStream is null) + { + return;//we can't create a file in this test session + } + + PdfMerger.Merge(one, two, outputStream); + CanMerge2SimpleDocumentsAssertions(outputStream, "Write something inInkscape", "I am a simple pdf."); + } + } + + [Fact] + public void CanMerge2SimpleDocumentsReversed() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + + var result = PdfMerger.Merge(one, two); + CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape"); + } + + internal static void CanMerge2SimpleDocumentsAssertions(Stream stream, string page1Text, string page2Text, bool checkVersion=true) + { + stream.Position = 0; + using (var document = PdfDocument.Open(stream, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(2, document.NumberOfPages); + if (checkVersion) + { + Assert.Equal(1.5m, document.Version); + } + + var page1 = document.GetPage(1); + Assert.Equal(page1Text, page1.Text); + + var page2 = document.GetPage(2); + Assert.Equal(page2Text, page2.Text); + } + } + + [Fact] + public void RootNodePageCount() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + + var result = PdfMerger.Merge(one, two); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(2, document.NumberOfPages); + } + + var oneBytes = File.ReadAllBytes(one); + + var result2 = PdfMerger.Merge(new[] { result, oneBytes }); + + using (var document = PdfDocument.Open(result2, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(3, document.NumberOfPages); + } + } + + [Fact] + public void ObjectCountLower() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + + var result = PdfMerger.Merge(one, two); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(2, document.NumberOfPages); + Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 24, + "Expected object count to be lower than 24"); + } + } + + [Fact] + public void DedupsObjectsFromSameDoc() + { + var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); + + var result = PdfMerger.Merge(new List { File.ReadAllBytes(one) }, new List> { new List { 1, 2} }); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(2, document.NumberOfPages); + Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29, + "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use + } + } + + [Fact] + public void CanMergeWithObjectStream() + { + var first = IntegrationHelpers.GetDocumentPath("Single Page Simple - from google drive.pdf"); + var second = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); + + var result = PdfMerger.Merge(first, second); + + WriteFile(nameof(CanMergeWithObjectStream), result); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(7, document.NumberOfPages); + + foreach (var page in document.GetPages()) + { + Assert.NotNull(page.Text); + } + } + } + + [Fact] + public void CanMergeWithSelection() + { + var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); + var contents = File.ReadAllBytes(first); + + var toCopy = new[] {2, 1, 4, 3, 6, 5}; + var result = PdfMerger.Merge(new [] { contents }, new [] { toCopy }); + + WriteFile(nameof(CanMergeWithSelection), result); + + using (var existing = PdfDocument.Open(contents, ParsingOptions.LenientParsingOff)) + using (var merged = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(6, merged.NumberOfPages); for (var i =1;i page.ExperimentalAccess.GetAnnotations().Count(x => x.Type == Annotations.AnnotationType.Link))); + } + } + + [Fact] + public void CanMergeWithLinksWithSelection() + { + var test = IntegrationHelpers.GetDocumentPath("outline.pdf"); + var result = PdfMerger.Merge(new[] { File.ReadAllBytes(test), File.ReadAllBytes(test) }, new[] { new[] { 2, 1 }, new[] { 3, 1 } }); + + WriteFile(nameof(CanMergeWithLinksWithSelection), result); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, document.GetPages().Sum( + page => page.ExperimentalAccess.GetAnnotations().Count(x => x.Type == Annotations.AnnotationType.Link))); + } + } + + private static void WriteFile(string name, byte[] bytes) + { + try + { + if (!Directory.Exists("Merger")) + { + Directory.CreateDirectory("Merger"); + } + + var output = Path.Combine("Merger", $"{name}.pdf"); + + File.WriteAllBytes(output, bytes); + } + catch + { + // ignored. + } + } + + private static FileStream GetSelfDestructingNewFileStream(string name) + { + try + { + if (!Directory.Exists("Merger")) + { + Directory.CreateDirectory("Merger"); + } + + var output = Path.Combine("Merger", $"{name}.pdf"); + return File.Create(output, 4096, FileOptions.DeleteOnClose); + } + catch + { + return null; + } + } + + [Fact] + public void NoStackoverflow() + { + try + { + var bytes = PdfMerger.Merge(IntegrationHelpers.GetDocumentPath("68-1990-01_A.pdf")); + using (var document = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(45, document.NumberOfPages); + } + } + catch (StackOverflowException) + { + Assert.True(false); + } + } + } +} diff --git a/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs b/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs index 2c7fdc73..96693e98 100644 --- a/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs +++ b/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs @@ -142,7 +142,7 @@ } } - private PdfAction GetAction(DictionaryToken annotationDictionary) + internal PdfAction GetAction(DictionaryToken annotationDictionary) { // If this annotation returns a direct destination, turn it into a GoTo action. if (DestinationProvider.TryGetDestination(annotationDictionary, diff --git a/src/UglyToad.PdfPig/Content/Page.cs b/src/UglyToad.PdfPig/Content/Page.cs index 9b9eefea..aab7bd2d 100644 --- a/src/UglyToad.PdfPig/Content/Page.cs +++ b/src/UglyToad.PdfPig/Content/Page.cs @@ -18,7 +18,7 @@ /// public class Page { - private readonly AnnotationProvider annotationProvider; + internal readonly AnnotationProvider annotationProvider; internal readonly IPdfTokenScanner pdfScanner; private readonly Lazy textLazy; diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index ccb1f792..e9efa47f 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -5,15 +5,16 @@ namespace UglyToad.PdfPig.Writer using System.Collections.Generic; using System.IO; using System.Linq; + using System.Runtime.CompilerServices; using Content; using Core; using Fonts; + using PdfPig.Actions; using PdfPig.Fonts.TrueType; using PdfPig.Fonts.Standard14Fonts; using PdfPig.Fonts.TrueType.Parser; using PdfPig.Outline; using PdfPig.Outline.Destinations; - using System.Runtime.CompilerServices; using Tokenization.Scanner; using Tokens; @@ -306,6 +307,18 @@ namespace UglyToad.PdfPig.Writer /// Page to copy. /// A builder for editing the page. public PdfPageBuilder AddPage(PdfDocument document, int pageNumber) + { + return AddPage(document, pageNumber, null); + } + + /// + /// Add a new page with the specified size, this page will be included in the output when is called. + /// + /// Source document. + /// Page to copy. + /// If set, links are copied based on the result of the delegate. + /// A builder for editing the page. + public PdfPageBuilder AddPage(PdfDocument document, int pageNumber, Func copyLink) { if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var refs)) { @@ -335,6 +348,8 @@ namespace UglyToad.PdfPig.Writer throw new KeyNotFoundException($"Page {pageNumber} was not found in the source document."); } + var page = document.GetPage(pageNumber); + // copy content streams var streams = new List(); if (pageInfo.Page.TryGet(NameToken.Contents, out IToken contentsToken)) @@ -365,6 +380,7 @@ namespace UglyToad.PdfPig.Writer // manually copy page dict / resources as we need to modify some var copiedPageDict = new Dictionary(); + var links = new List<(DictionaryToken token, PdfAction action)>(); Dictionary resources = new Dictionary(); // just put all parent resources into new page @@ -424,9 +440,7 @@ namespace UglyToad.PdfPig.Writer continue; } - // -> ignore links to resolve issues with refencing non-existing pages - // at some point should add support for copying the links if the - // pages are copied as well but for now just fix corruption + // if copyLink is unset, ignore links to resolve issues with refencing non-existing pages var toAdd = new List(); foreach (var annot in arr.Data) { @@ -436,10 +450,38 @@ namespace UglyToad.PdfPig.Writer // malformed continue; } + if (tk.TryGet(NameToken.Subtype, out var st) && st is NameToken nm && nm == NameToken.Link) { - // link -> ignore - continue; + if (copyLink == null) + { + // ingore link if don't know how to copy + continue; + } + + var link = page.annotationProvider.GetAction(tk); + if (link == null) + { + // ignore unknown link actions + continue; + } + + var copiedLink = copyLink(link); + if (copiedLink == null) + { + // ignore if caller wants to skip the link + continue; + } + + if (copiedLink != link) + { + // defer to write links when all pages are added + var copiedToken = (DictionaryToken)WriterUtil.CopyToken(context, tk, document.Structure.TokenScanner, refs); + links.Add((copiedToken, copiedLink)); + continue; + } + + // copy as is if caller returns the same link } toAdd.Add(WriterUtil.CopyToken(context, tk, document.Structure.TokenScanner, refs)); } @@ -454,7 +496,7 @@ namespace UglyToad.PdfPig.Writer copiedPageDict[NameToken.Resources] = new DictionaryToken(resources); - var builder = new PdfPageBuilder(pages.Count + 1, this, streams, copiedPageDict); + var builder = new PdfPageBuilder(pages.Count + 1, this, streams, copiedPageDict, links); pages[builder.PageNumber] = builder; return builder; @@ -556,7 +598,7 @@ namespace UglyToad.PdfPig.Writer } int leafNum = 0; - var pageReferences = new Dictionary(pages.Count); + var pageReferences = pages.ToDictionary(p => p.Key, p => context.ReserveObjectNumber()); foreach (var page in pages) { @@ -601,8 +643,25 @@ namespace UglyToad.PdfPig.Writer } context.AttemptDeduplication = prev; - pageReferences[page.Key] = context.WriteToken(new DictionaryToken(pageDictionary)); - leafChildren[leafNum].Add(pageReferences[page.Key]); + // write links + if (page.Value.links != null && page.Value.links.Count > 0) + { + var annots = new List(); + + if (pageDictionary.TryGetValue(NameToken.Annots, out var existingAnnots)) + { + annots.AddRange(((ArrayToken)existingAnnots).Data); + } + + foreach (var (token, action) in page.Value.links) + { + annots.Add(CreateLinkAnnotationToken(token, action, pageReferences)); + } + + pageDictionary[NameToken.Annots] = new ArrayToken(annots); + } + + leafChildren[leafNum].Add(context.WriteToken(new DictionaryToken(pageDictionary), pageReferences[page.Key])); if (leafChildren[leafNum].Count >= desiredLeafSize) { @@ -816,12 +875,7 @@ namespace UglyToad.PdfPig.Writer switch (node) { case DocumentBookmarkNode documentBookmarkNode: - if (!pageReferences.TryGetValue(documentBookmarkNode.PageNumber, out var pageReference)) - { - throw new KeyNotFoundException($"Page {documentBookmarkNode.PageNumber} was not found in the source document."); - } - - data[NameToken.Dest] = CreateExplicitDestinationToken(documentBookmarkNode.Destination, pageReference); + data[NameToken.Dest] = CreateExplicitDestinationToken(documentBookmarkNode.Destination, pageReferences); break; case UriBookmarkNode uriBookmarkNode: @@ -842,8 +896,13 @@ namespace UglyToad.PdfPig.Writer return childObjectNumbers; } - private static ArrayToken CreateExplicitDestinationToken(ExplicitDestination destination, IndirectReferenceToken page) + private static ArrayToken CreateExplicitDestinationToken(ExplicitDestination destination, Dictionary pageReferences) { + if (!pageReferences.TryGetValue(destination.PageNumber, out var page)) + { + throw new KeyNotFoundException($"Page {destination.PageNumber} was not found in the source document."); + } + switch (destination.Type) { case ExplicitDestinationType.XyzCoordinates: @@ -917,6 +976,65 @@ namespace UglyToad.PdfPig.Writer } } + private static DictionaryToken CreateLinkAnnotationToken(DictionaryToken token, PdfAction action, Dictionary pageReferences) + { + var data = new Dictionary(); + + foreach (var item in token.Data) + { + var nameToken = NameToken.Create(item.Key); + if (nameToken == NameToken.A || nameToken == NameToken.Dest) + { + // ignore /A and /Dest + continue; + } + + data[nameToken] = item.Value; + } + + data[NameToken.A] = CreateActionToken(action, pageReferences); + return new DictionaryToken(data); + } + + private static DictionaryToken CreateActionToken(PdfAction action, Dictionary pageReferences) + { + switch (action) + { + case UriAction uriAction: + return new DictionaryToken(new Dictionary() + { + [NameToken.S] = NameToken.Uri, + [NameToken.Uri] = new StringToken(uriAction.Uri), + }); + + case GoToAction goToAction: + return new DictionaryToken(new Dictionary() + { + [NameToken.S] = NameToken.GoTo, + [NameToken.D] = CreateExplicitDestinationToken(goToAction.Destination, pageReferences), + }); + + case GoToEAction goToEAction: + return new DictionaryToken(new Dictionary() + { + [NameToken.S] = NameToken.GoToE, + [NameToken.F] = new StringToken(goToEAction.FileSpecification), + [NameToken.D] = CreateExplicitDestinationToken(goToEAction.Destination, pageReferences), + }); + + case GoToRAction goToRAction: + return new DictionaryToken(new Dictionary() + { + [NameToken.S] = NameToken.GoToR, + [NameToken.F] = new StringToken(goToRAction.Filename), + [NameToken.D] = CreateExplicitDestinationToken(goToRAction.Destination, pageReferences), + }); + + default: + throw new NotSupportedException($"{action.GetType().Name} is not a supported PDF action type."); + } + } + internal class FontStored { [NotNull] diff --git a/src/UglyToad.PdfPig/Writer/PdfMerger.cs b/src/UglyToad.PdfPig/Writer/PdfMerger.cs index 21e47a32..318a4583 100644 --- a/src/UglyToad.PdfPig/Writer/PdfMerger.cs +++ b/src/UglyToad.PdfPig/Writer/PdfMerger.cs @@ -6,6 +6,8 @@ using Filters; using Logging; using System.Linq; + using UglyToad.PdfPig.Actions; + using UglyToad.PdfPig.Outline.Destinations; /// /// Merges PDF documents into each other. @@ -151,19 +153,63 @@ pages = pagesBundle[fileIndex]; } + var basePageNumber = document.Pages.Count; + if (pages == null) { for (var i = 1; i <= existing.NumberOfPages; i++) { - document.AddPage(existing, i); - } - } else - { - foreach (var i in pages) - { - document.AddPage(existing, i); + document.AddPage(existing, i, link => CopyLink(link, n => basePageNumber + n)); } } + else + { + var pageNumbers = new Dictionary(); + for (var i = 0; i < pages.Count; i++) + { + pageNumbers[pages[i]] = basePageNumber + i + 1; + } + + foreach (var i in pages) + { + document.AddPage(existing, i, link => CopyLink( + link, n => pageNumbers.TryGetValue(n, out var pageNumber) ? pageNumber : null)); + } + } + } + } + + PdfAction CopyLink(PdfAction action, Func getPageNumber) + { + var link = action as AbstractGoToAction; + if (link == null) + { + // copy the link if it is not a link to PDF documents + return action; + } + + var newPageNumber = getPageNumber(link.Destination.PageNumber); + if (newPageNumber == null) + { + // ignore the link if the target page does not exist in the PDF document + return null; + } + + var newDestination = new ExplicitDestination(newPageNumber.Value, link.Destination.Type, link.Destination.Coordinates); + + switch (action) + { + case GoToAction goToAction: + return new GoToAction(newDestination); + + case GoToEAction goToEAction: + return new GoToEAction(newDestination, goToEAction.FileSpecification); + + case GoToRAction goToRAction: + return new GoToRAction(newDestination, goToRAction.Filename); + + default: + return action; } } } diff --git a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs index 4a82e597..c4e3a86d 100644 --- a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs @@ -21,6 +21,7 @@ using Tokens; using Graphics.Operations.PathPainting; using Images.Png; + using UglyToad.PdfPig.Actions; /// /// A builder used to add construct a page in a PDF document. @@ -36,6 +37,9 @@ // streams internal readonly List contentStreams; private IPageContentStream currentStream; + + // links to be resolved when all page references are available + internal readonly List<(DictionaryToken token, PdfAction action)> links; // maps fonts added using PdfDocumentBuilder to page font names private readonly Dictionary documentFonts = new Dictionary(); @@ -80,16 +84,17 @@ } internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder, IEnumerable copied, - Dictionary pageDict) + Dictionary pageDict, List<(DictionaryToken token, PdfAction action)> links) { this.documentBuilder = documentBuilder ?? throw new ArgumentNullException(nameof(documentBuilder)); + this.links = links; PageNumber = number; pageDictionary = pageDict; contentStreams = new List(); contentStreams.AddRange(copied); currentStream = new DefaultContentStream(); contentStreams.Add(currentStream); - } + } /// /// Allow to append a new content stream before the current one and select it