From 0e39bc0b76f92a6f25268ef25cd327b745e0188b Mon Sep 17 00:00:00 2001 From: mvantzet Date: Mon, 10 Apr 2023 18:14:14 +0200 Subject: [PATCH] Annotations named destinations (#579) * Add Named Destinations to Catalog so that bookmarks and links can access them. The named destinations require access to page nodes, so created Pages object that is made using PagesFactory (which contains the page-related code from Catalog). * Further implementation of destinations: - Implement NamedDestinations in AnnotationProvider, so that we can look up named destinations for annotations and turn them into explicit destinations. Reused existing code inside BookmarksProvider to get destinations/actions. - Added GoToE action - According to the PDF reference, destinations are also required for external destinations and hence for ExternalBookmarkNode. This allows us to push up DocumentBookmarkNode.Destination to BookmarkNode. * Implemented stateful appearance streams and integration test * Added AppearanceStream to public API because it is used in the (public) Annotation constructor * After #552, must push down ExplicitDestination do DocumentBookmarkNode since it does not apply to UriBookmarkNode. * Added actions, which fits the PDF model better and works well with the new bookmarks code (after PR #552) * Rename Action to PdfAction + removed unused using in ActionProvider.cs --------- Co-authored-by: mvantzet --- .../AdvancedPdfDocumentAccessTests.cs | 4 +- .../Integration/AnnotationsTest.cs | 51 +++ .../Integration/Documents/toc.pdf | Bin 0 -> 24066 bytes .../SpecificTestDocuments/appearances.pdf | Bin 0 -> 92078 bytes .../PublicApiScannerTests.cs | 9 + .../NameToken.Constants.cs | 1 + .../AcroForms/AcroFormFactory.cs | 2 +- .../Actions/AbstractGoToAction.cs | 24 ++ src/UglyToad.PdfPig/Actions/ActionProvider.cs | 107 +++++++ src/UglyToad.PdfPig/Actions/ActionType.cs | 80 +++++ src/UglyToad.PdfPig/Actions/GoToAction.cs | 17 + src/UglyToad.PdfPig/Actions/GoToEAction.cs | 24 ++ src/UglyToad.PdfPig/Actions/GoToRAction.cs | 24 ++ src/UglyToad.PdfPig/Actions/PdfAction.cs | 22 ++ src/UglyToad.PdfPig/Actions/UriAction.cs | 21 ++ src/UglyToad.PdfPig/Annotations/Annotation.cs | 27 +- .../Annotations/AnnotationProvider.cs | 66 +++- .../Annotations/AppearanceStream.cs | 66 ++++ .../Annotations/AppearanceStreamFactory.cs | 42 +++ src/UglyToad.PdfPig/Content/Catalog.cs | 79 +---- src/UglyToad.PdfPig/Content/IPageFactory.cs | 5 +- src/UglyToad.PdfPig/Content/Pages.cs | 58 ++-- src/UglyToad.PdfPig/Content/PagesFactory.cs | 241 ++++++++++++++ src/UglyToad.PdfPig/Outline/BookmarkNode.cs | 1 + .../Outline/BookmarksProvider.cs | 295 +----------------- .../Destinations/DestinationProvider.cs | 36 +++ .../Destinations/ExplicitDestination.cs | 2 +- .../Outline/Destinations/NamedDestinations.cs | 44 +++ .../Destinations/NamedDestinationsProvider.cs | 220 +++++++++++++ .../Outline/DocumentBookmarkNode.cs | 6 +- .../Outline/EmbeddedBookmarkNode.cs | 33 ++ .../Outline/ExternalBookmarkNode.cs | 5 +- src/UglyToad.PdfPig/Parser/CatalogFactory.cs | 208 +----------- src/UglyToad.PdfPig/Parser/PageFactory.cs | 37 +-- .../Parser/PdfDocumentFactory.cs | 8 +- src/UglyToad.PdfPig/PdfDocument.cs | 15 +- .../Writer/PdfDocumentBuilder.cs | 8 +- 37 files changed, 1263 insertions(+), 625 deletions(-) create mode 100644 src/UglyToad.PdfPig.Tests/Integration/AnnotationsTest.cs create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/toc.pdf create mode 100644 src/UglyToad.PdfPig.Tests/Integration/SpecificTestDocuments/appearances.pdf create mode 100644 src/UglyToad.PdfPig/Actions/AbstractGoToAction.cs create mode 100644 src/UglyToad.PdfPig/Actions/ActionProvider.cs create mode 100644 src/UglyToad.PdfPig/Actions/ActionType.cs create mode 100644 src/UglyToad.PdfPig/Actions/GoToAction.cs create mode 100644 src/UglyToad.PdfPig/Actions/GoToEAction.cs create mode 100644 src/UglyToad.PdfPig/Actions/GoToRAction.cs create mode 100644 src/UglyToad.PdfPig/Actions/PdfAction.cs create mode 100644 src/UglyToad.PdfPig/Actions/UriAction.cs create mode 100644 src/UglyToad.PdfPig/Annotations/AppearanceStream.cs create mode 100644 src/UglyToad.PdfPig/Annotations/AppearanceStreamFactory.cs create mode 100644 src/UglyToad.PdfPig/Content/PagesFactory.cs create mode 100644 src/UglyToad.PdfPig/Outline/Destinations/DestinationProvider.cs create mode 100644 src/UglyToad.PdfPig/Outline/Destinations/NamedDestinations.cs create mode 100644 src/UglyToad.PdfPig/Outline/Destinations/NamedDestinationsProvider.cs create mode 100644 src/UglyToad.PdfPig/Outline/EmbeddedBookmarkNode.cs diff --git a/src/UglyToad.PdfPig.Tests/Integration/AdvancedPdfDocumentAccessTests.cs b/src/UglyToad.PdfPig.Tests/Integration/AdvancedPdfDocumentAccessTests.cs index 064cc33f..fd4ce14c 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/AdvancedPdfDocumentAccessTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/AdvancedPdfDocumentAccessTests.cs @@ -13,7 +13,7 @@ using (var document = PdfDocument.Open(path)) { - var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary; + var pg = document.Structure.Catalog.Pages.GetPageNode(1).NodeDictionary; var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken; document.Advanced.ReplaceIndirectObject(contents.Data, tk => { @@ -39,7 +39,7 @@ dict[NameToken.Length] = new NumericToken(0); var replacement = new StreamToken(new DictionaryToken(dict), new List()); - var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary; + var pg = document.Structure.Catalog.Pages.GetPageNode(1).NodeDictionary; var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken; document.Advanced.ReplaceIndirectObject(contents.Data, replacement); diff --git a/src/UglyToad.PdfPig.Tests/Integration/AnnotationsTest.cs b/src/UglyToad.PdfPig.Tests/Integration/AnnotationsTest.cs new file mode 100644 index 00000000..5353b1de --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Integration/AnnotationsTest.cs @@ -0,0 +1,51 @@ +namespace UglyToad.PdfPig.Tests.Integration +{ + using Actions; + using System.Linq; + using Xunit; + + public class AnnotationsTest + { + [Fact] + public void AnnotationsHaveActions() + { + var pdf = IntegrationHelpers.GetDocumentPath("toc"); + + using (var doc = PdfDocument.Open(pdf)) + { + var annots = doc.GetPage(1).ExperimentalAccess.GetAnnotations().ToArray(); + Assert.Equal(5, annots.Length); + Assert.All(annots, a => Assert.NotNull(a.Action)); + Assert.All(annots, a => Assert.IsType(a.Action)); + Assert.All(annots, a => Assert.True((a.Action as GoToAction).Destination.PageNumber > 0)); + } + } + + [Fact] + public void CheckAnnotationAppearanceStreams() + { + var pdf = IntegrationHelpers.GetSpecificTestDocumentPath("appearances"); + using (var doc = PdfDocument.Open(pdf)) + { + var annotations = doc.GetPage(1).ExperimentalAccess.GetAnnotations().ToArray(); + Assert.Equal(1, annotations.Length); + var annotation = annotations[0]; + + Assert.True(annotation.HasDownAppearance); + Assert.True(annotation.HasNormalAppearance); + Assert.False(annotation.HasRollOverAppearance); + + Assert.False(annotation.downAppearanceStream.IsStateless); + Assert.False(annotation.normalAppearanceStream.IsStateless); + + Assert.Contains("Off", annotation.downAppearanceStream.GetStates); + Assert.Contains("Yes", annotation.downAppearanceStream.GetStates); + + Assert.Contains("Off", annotation.normalAppearanceStream.GetStates); + Assert.Contains("Yes", annotation.normalAppearanceStream.GetStates); + + Assert.Equal("Off", annotation.appearanceState); + } + } + } +} diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/toc.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/toc.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e280636916d94db2ae7ed6ebb91d686fd658a8bc GIT binary patch literal 24066 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 +/// Abstract class for GoTo-type actions (GoTo, GoToE, GoToR) that have a destination +/// +public abstract class AbstractGoToAction : PdfAction +{ + ///

+ /// Destination for the GoTo-type action + /// + public ExplicitDestination Destination { get; } + + /// + /// Constructor + /// + /// + /// + protected AbstractGoToAction(ActionType type, ExplicitDestination destination) : base(type) + { + Destination = destination; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Actions/ActionProvider.cs b/src/UglyToad.PdfPig/Actions/ActionProvider.cs new file mode 100644 index 00000000..b22f23da --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/ActionProvider.cs @@ -0,0 +1,107 @@ +namespace UglyToad.PdfPig.Actions +{ + using Core; + using Logging; + using Outline; + using Tokenization.Scanner; + using Tokens; + using Outline.Destinations; + using Util; + + internal static class ActionProvider + { + /// + /// Get an action (A) from dictionary. If GoTo, GoToR or GoToE, also fetches the action destination. + /// + /// + /// + /// + /// + /// + /// + /// + internal static bool TryGetAction(DictionaryToken dictionary, + NamedDestinations namedDestinations, + IPdfTokenScanner pdfScanner, + ILog log, + out PdfAction result) + { + result = null; + + if (!dictionary.TryGet(NameToken.A, pdfScanner, out DictionaryToken actionDictionary)) + { + return false; + } + + if (!actionDictionary.TryGet(NameToken.S, pdfScanner, out NameToken actionType)) + { + throw new PdfDocumentFormatException($"No action type (/S) specified for action: {actionDictionary}."); + } + + if (actionType.Equals(NameToken.GoTo)) + { + // For GoTo, D(estination) is required + if (DestinationProvider.TryGetDestination(actionDictionary, + NameToken.D, + namedDestinations, + pdfScanner, + log, + false, + out var destination)) + { + result = new GoToAction(destination); + return true; + } + } + else if (actionType.Equals(NameToken.GoToR)) + { + // For GoToR, F(ile) and D(estination) are required + if (actionDictionary.TryGetOptionalStringDirect(NameToken.F, pdfScanner, out var filename) + && DestinationProvider.TryGetDestination(actionDictionary, + NameToken.D, + namedDestinations, + pdfScanner, + log, + true, + out var destination)) + { + result = new GoToRAction(destination, filename); + return true; + } + } + else if (actionType.Equals(NameToken.GoToE)) + { + // For GoToE, D(estination) is required + if (DestinationProvider.TryGetDestination(actionDictionary, + NameToken.D, + namedDestinations, + pdfScanner, + log, + true, + out var destination)) + { + // F(ile specification) is optional + if (!actionDictionary.TryGetOptionalStringDirect(NameToken.F, + pdfScanner, + out var fileSpecification)) + { + fileSpecification = null; + } + + result = new GoToEAction(destination, fileSpecification); + return true; + } + } + else if (actionType.Equals(NameToken.Uri)) + { + if (!actionDictionary.TryGetOptionalStringDirect(NameToken.Uri, pdfScanner, out var uri)) + { + uri = null; + } + result = new UriAction(uri); + return true; + } + return false; + } + } +} diff --git a/src/UglyToad.PdfPig/Actions/ActionType.cs b/src/UglyToad.PdfPig/Actions/ActionType.cs new file mode 100644 index 00000000..9a76841d --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/ActionType.cs @@ -0,0 +1,80 @@ +namespace UglyToad.PdfPig.Actions; + +/// +/// Action types (PDF reference 8.5.3) +/// +public enum ActionType +{ + /// + /// Go to a destination in the current document. + /// + GoTo, + /// + /// (“Go-to remote”) Go to a destination in another document. + /// + GoToR, + /// + /// (“Go-to embedded”; PDF 1.6) Go to a destination in an embedded file. + /// + GoToE, + /// + /// Launch an application, usually to open a file. + /// + Launch, + /// + /// Begin reading an article thread. + /// + Thread, + /// + /// Resolve a uniform resource identifier. + /// + URI, + /// + /// (PDF 1.2) Play a sound. + /// + Sound, + /// + /// (PDF 1.2) Play a movie. + /// + Movie, + /// + /// (PDF 1.2) Set an annotation’s Hidden flag. + /// + Hide, + /// + /// (PDF 1.2) Execute an action predefined by the viewer application. + /// + Named, + /// + /// (PDF 1.2) Send data to a uniform resource locator. + /// + SubmitForm, + /// + /// (PDF 1.2) Set fields to their default values. + /// + ResetForm, + /// + /// (PDF 1.2) Import field values from a file. + /// + ImportData, + /// + /// (PDF 1.3) Execute a JavaScript script. + /// + JavaScript, + /// + /// (PDF 1.5) Set the states of optional content groups. + /// + SetOCGState, + /// + /// (PDF 1.5) Controls the playing of multimedia content. + /// + Rendition, + /// + /// (PDF 1.5) Updates the display of a document, using a transition dictionary. + /// + Trans, + /// + /// (PDF 1.6) Set the current view of a 3D annotation + /// + GoTo3DView +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Actions/GoToAction.cs b/src/UglyToad.PdfPig/Actions/GoToAction.cs new file mode 100644 index 00000000..aa94e00d --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/GoToAction.cs @@ -0,0 +1,17 @@ +namespace UglyToad.PdfPig.Actions; + +using Outline.Destinations; + +/// +/// GoTo action (with a destination inside the current document) +/// +public class GoToAction : AbstractGoToAction +{ + /// + /// Constructor + /// + /// + public GoToAction(ExplicitDestination destination) : base(ActionType.GoTo, destination) + { + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Actions/GoToEAction.cs b/src/UglyToad.PdfPig/Actions/GoToEAction.cs new file mode 100644 index 00000000..c57d2d3a --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/GoToEAction.cs @@ -0,0 +1,24 @@ +namespace UglyToad.PdfPig.Actions; + +using Outline.Destinations; + +/// +/// GoToE action (to go to a destination inside a file embedded within the PDF) +/// +public class GoToEAction : AbstractGoToAction +{ + /// + /// File specification of the embedded file + /// + public string FileSpecification { get; } + + /// + /// Constructor + /// + /// Destination within the embedded file + /// Specification of the embedded file + public GoToEAction(ExplicitDestination destination, string fileSpecification) : base(ActionType.GoToE, destination) + { + FileSpecification = fileSpecification; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Actions/GoToRAction.cs b/src/UglyToad.PdfPig/Actions/GoToRAction.cs new file mode 100644 index 00000000..8aa65849 --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/GoToRAction.cs @@ -0,0 +1,24 @@ +namespace UglyToad.PdfPig.Actions; + +using Outline.Destinations; + +/// +/// GoToR action, to go to a destination in a remote PDF +/// +public class GoToRAction : AbstractGoToAction +{ + /// + /// Filename of the remote PDF + /// + public string Filename { get; } + + /// + /// Constructor + /// + /// Destination within the remote PDF + /// Filename of the remote PDF + public GoToRAction(ExplicitDestination destination, string filename) : base(ActionType.GoToR, destination) + { + Filename = filename; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Actions/PdfAction.cs b/src/UglyToad.PdfPig/Actions/PdfAction.cs new file mode 100644 index 00000000..8c46e01e --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/PdfAction.cs @@ -0,0 +1,22 @@ +namespace UglyToad.PdfPig.Actions +{ + /// + /// Actions (PDF reference 8.5) + /// + public class PdfAction + { + /// + /// Type of action + /// + public ActionType Type { get; } + + /// + /// Constructor + /// + /// + protected PdfAction(ActionType type) + { + Type = type; + } + } +} diff --git a/src/UglyToad.PdfPig/Actions/UriAction.cs b/src/UglyToad.PdfPig/Actions/UriAction.cs new file mode 100644 index 00000000..ead37582 --- /dev/null +++ b/src/UglyToad.PdfPig/Actions/UriAction.cs @@ -0,0 +1,21 @@ +namespace UglyToad.PdfPig.Actions; + +/// +/// Action to open a URI +/// +public class UriAction : PdfAction +{ + /// + /// URI to open + /// + public string Uri { get; } + + /// + /// Constructor + /// + /// URI to open + public UriAction(string uri) : base(ActionType.URI) + { + Uri = uri; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Annotations/Annotation.cs b/src/UglyToad.PdfPig/Annotations/Annotation.cs index aa19dd4d..e2cecca9 100644 --- a/src/UglyToad.PdfPig/Annotations/Annotation.cs +++ b/src/UglyToad.PdfPig/Annotations/Annotation.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Core; + using Actions; using Tokens; using Util.JetBrains.Annotations; @@ -11,9 +12,10 @@ /// public class Annotation { - private readonly StreamToken normalAppearanceStream; - private readonly StreamToken rollOverAppearanceStream; - private readonly StreamToken downAppearanceStream; + internal readonly AppearanceStream normalAppearanceStream; + internal readonly AppearanceStream rollOverAppearanceStream; + internal readonly AppearanceStream downAppearanceStream; + internal readonly string appearanceState; /// /// The underlying PDF dictionary which this annotation was created from. @@ -66,6 +68,16 @@ /// public IReadOnlyList QuadPoints { get; } + /// + /// Action for this annotation, if any (can be null) + /// + public PdfAction Action { get; } + + /// + /// Indicates if a normal appearance is present for this annotation + /// + public bool HasNormalAppearance => normalAppearanceStream != null; + /// /// Indicates if a roll over appearance is present for this annotation (shown when you hover over this annotation) /// @@ -79,9 +91,12 @@ /// /// Create a new . /// - public Annotation(DictionaryToken annotationDictionary, AnnotationType type, PdfRectangle rectangle, string content, string name, string modifiedDate, + public Annotation(DictionaryToken annotationDictionary, AnnotationType type, PdfRectangle rectangle, + string content, string name, string modifiedDate, AnnotationFlags flags, AnnotationBorder border, IReadOnlyList quadPoints, - StreamToken normalAppearanceStream, StreamToken rollOverAppearanceStream, StreamToken downAppearanceStream) + PdfAction action, + AppearanceStream normalAppearanceStream, AppearanceStream rollOverAppearanceStream, + AppearanceStream downAppearanceStream, string appearanceState) { AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary)); Type = type; @@ -92,9 +107,11 @@ Flags = flags; Border = border; QuadPoints = quadPoints ?? EmptyArray.Instance; + Action = action; this.normalAppearanceStream = normalAppearanceStream; this.rollOverAppearanceStream = rollOverAppearanceStream; this.downAppearanceStream = downAppearanceStream; + this.appearanceState = appearanceState; } /// diff --git a/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs b/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs index 1e452846..2c7fdc73 100644 --- a/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs +++ b/src/UglyToad.PdfPig/Annotations/AnnotationProvider.cs @@ -1,9 +1,13 @@ namespace UglyToad.PdfPig.Annotations { + using Actions; using System; using System.Collections.Generic; using System.Linq; using Core; + using Logging; + using Outline; + using Outline.Destinations; using Parser.Parts; using Tokenization.Scanner; using Tokens; @@ -13,14 +17,18 @@ { private readonly IPdfTokenScanner tokenScanner; private readonly DictionaryToken pageDictionary; + private readonly NamedDestinations namedDestinations; + private readonly ILog log; private readonly TransformationMatrix matrix; public AnnotationProvider(IPdfTokenScanner tokenScanner, DictionaryToken pageDictionary, - TransformationMatrix matrix) + TransformationMatrix matrix, NamedDestinations namedDestinations, ILog log) { this.matrix = matrix; this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner)); this.pageDictionary = pageDictionary ?? throw new ArgumentNullException(nameof(pageDictionary)); + this.namedDestinations = namedDestinations; + this.log = log; } public IEnumerable GetAnnotations() @@ -38,10 +46,9 @@ } var type = annotationDictionary.Get(NameToken.Subtype, tokenScanner); - var annotationType = type.ToAnnotationType(); + var action = GetAction(annotationDictionary); var rectangle = matrix.Transform(annotationDictionary.Get(NameToken.Rect, tokenScanner).ToRectangle(tokenScanner)); - var contents = GetNamedString(NameToken.Contents, annotationDictionary); var name = GetNamedString(NameToken.Nm, annotationDictionary); // As indicated in PDF reference 8.4.1, the modified date can be anything, but is usually a date formatted according to sec. 3.8.3 @@ -98,32 +105,67 @@ } } - StreamToken normalAppearanceStream = null, downAppearanceStream = null, rollOverAppearanceStream = null; + AppearanceStream normalAppearanceStream = null; + AppearanceStream downAppearanceStream = null; + AppearanceStream rollOverAppearanceStream = null; + if (annotationDictionary.TryGet(NameToken.Ap, out DictionaryToken appearanceDictionary)) { // The normal appearance of this annotation - if (appearanceDictionary.TryGet(NameToken.N, out IndirectReferenceToken normalAppearanceRef)) + if (AppearanceStreamFactory.TryCreate(appearanceDictionary, NameToken.N, tokenScanner, out AppearanceStream stream)) { - normalAppearanceStream = tokenScanner.Get(normalAppearanceRef.Data)?.Data as StreamToken; + normalAppearanceStream = stream; } + // If present, the 'roll over' appearance of this annotation (when hovering the mouse pointer over this annotation) - if (appearanceDictionary.TryGet(NameToken.R, out IndirectReferenceToken rollOverAppearanceRef)) + if (AppearanceStreamFactory.TryCreate(appearanceDictionary, NameToken.R, tokenScanner, out stream)) { - rollOverAppearanceStream = tokenScanner.Get(rollOverAppearanceRef.Data)?.Data as StreamToken; + rollOverAppearanceStream = stream; } + // If present, the 'down' appearance of this annotation (when you click on it) - if (appearanceDictionary.TryGet(NameToken.D, out IndirectReferenceToken downAppearanceRef)) + if (AppearanceStreamFactory.TryCreate(appearanceDictionary, NameToken.D, tokenScanner, out stream)) { - downAppearanceStream = tokenScanner.Get(downAppearanceRef.Data)?.Data as StreamToken; + downAppearanceStream = stream; } } + string appearanceState = null; + if (annotationDictionary.TryGet(NameToken.As, out NameToken appearanceStateToken)) + { + appearanceState = appearanceStateToken.Data; + } + yield return new Annotation(annotationDictionary, annotationType, rectangle, - contents, name, modifiedDate, flags, border, quadPointRectangles, - normalAppearanceStream, rollOverAppearanceStream, downAppearanceStream); + contents, name, modifiedDate, flags, border, quadPointRectangles, action, + normalAppearanceStream, rollOverAppearanceStream, downAppearanceStream, appearanceState); } } + private PdfAction GetAction(DictionaryToken annotationDictionary) + { + // If this annotation returns a direct destination, turn it into a GoTo action. + if (DestinationProvider.TryGetDestination(annotationDictionary, + NameToken.Dest, + namedDestinations, + tokenScanner, + log, + false, + out var destination)) + { + return new GoToAction(destination); + } + + // Try get action from the dictionary. + if (ActionProvider.TryGetAction(annotationDictionary, namedDestinations, tokenScanner, log, out var action)) + { + return action; + } + + // No action or destination found, return null + return null; + } + private string GetNamedString(NameToken name, DictionaryToken dictionary) { string content = null; diff --git a/src/UglyToad.PdfPig/Annotations/AppearanceStream.cs b/src/UglyToad.PdfPig/Annotations/AppearanceStream.cs new file mode 100644 index 00000000..98350e3f --- /dev/null +++ b/src/UglyToad.PdfPig/Annotations/AppearanceStream.cs @@ -0,0 +1,66 @@ +namespace UglyToad.PdfPig.Annotations; + +using System; +using System.Collections.Generic; +using Tokens; + +/// +/// Appearance stream (PDF Reference 8.4.4) that describes what an annotation looks like. Each stream is a Form XObject. +/// The appearance stream is either stateless (in which case is true) +/// or stateful, in which case is false and the states can be retrieved via . +/// The states can then be used to retrieve the state-specific appearances using . +/// +public class AppearanceStream +{ + private readonly IDictionary appearanceStreamsByState; + + private readonly StreamToken statelessAppearanceStream; + + /// + /// Indicates if this appearance stream is stateless, or whether you can get appearances by state. + /// + public bool IsStateless => statelessAppearanceStream != null; + + /// + /// Get list of states. If this is a stateless appearance stream, an empty collection is returned. + /// + public ICollection GetStates => appearanceStreamsByState != null ? appearanceStreamsByState.Keys : new string[0]; + + /// + /// Constructor for stateless appearance stream + /// + /// + internal AppearanceStream(StreamToken streamToken) + { + statelessAppearanceStream = streamToken; + } + + /// + /// Constructor for stateful appearance stream + /// + /// + internal AppearanceStream(IDictionary appearanceStreamsByState) + { + this.appearanceStreamsByState = appearanceStreamsByState; + } + + /// + /// Get appearance stream for particular state + /// + /// + /// + /// + /// + public StreamToken Get(string state) + { + if (appearanceStreamsByState == null) + { + throw new Exception("Cannot get appearance by state when this is a stateless appearance stream"); + } + if (!appearanceStreamsByState.ContainsKey(state)) + { + throw new ArgumentOutOfRangeException(nameof(state), $"Appearance stream does not have state '{state}' (available states: {string.Join(",", appearanceStreamsByState.Keys)})"); + } + return appearanceStreamsByState[state]; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Annotations/AppearanceStreamFactory.cs b/src/UglyToad.PdfPig/Annotations/AppearanceStreamFactory.cs new file mode 100644 index 00000000..f0196415 --- /dev/null +++ b/src/UglyToad.PdfPig/Annotations/AppearanceStreamFactory.cs @@ -0,0 +1,42 @@ +namespace UglyToad.PdfPig.Annotations; + +using System.Collections.Generic; +using Tokenization.Scanner; +using Tokens; + +internal static class AppearanceStreamFactory +{ + public static bool TryCreate(DictionaryToken appearanceDictionary, NameToken name, IPdfTokenScanner tokenScanner, out AppearanceStream appearanceStream) + { + if (appearanceDictionary.TryGet(name, out IndirectReferenceToken appearanceReference)) + { + var streamToken = tokenScanner.Get(appearanceReference.Data)?.Data as StreamToken; + appearanceStream = new AppearanceStream(streamToken); + return true; + } + + if (appearanceDictionary.TryGet(name, out DictionaryToken stateDictionary)) + { + var dict = new Dictionary(); + foreach (var state in stateDictionary.Data.Keys) + { + if (stateDictionary.Data.TryGetValue(state, out var stateRef) && + stateRef is IndirectReferenceToken appearanceRef) + { + var streamToken = tokenScanner.Get(appearanceRef.Data)?.Data as StreamToken; + dict[state] = streamToken; + } + + } + + if (dict.Count > 0) + { + appearanceStream = new AppearanceStream(dict); + return true; + } + } + + appearanceStream = null; + return false; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Content/Catalog.cs b/src/UglyToad.PdfPig/Content/Catalog.cs index 9bbdd71f..4153f259 100644 --- a/src/UglyToad.PdfPig/Content/Catalog.cs +++ b/src/UglyToad.PdfPig/Content/Catalog.cs @@ -1,8 +1,7 @@ namespace UglyToad.PdfPig.Content { using System; - using System.Collections.Generic; - using Core; + using Outline; using Tokens; using Util.JetBrains.Annotations; @@ -12,90 +11,24 @@ /// public class Catalog { - private readonly IReadOnlyDictionary pagesByNumber; - /// /// The catalog dictionary containing assorted information. /// [NotNull] public DictionaryToken CatalogDictionary { get; } - /// - /// Defines the page tree node which is the root of the pages tree for the document. - /// - [NotNull] - public DictionaryToken PagesDictionary { get; } - - /// - /// The page tree for this document containing all pages, page numbers and their dictionaries. - /// - public PageTreeNode PageTree { get; } + internal NamedDestinations NamedDestinations { get; } - /// - /// Number of discovered pages. - /// - public int? NumberOfDiscoveredPages => pagesByNumber?.Count; + internal Pages Pages { get; } /// /// Create a new . /// - internal Catalog(DictionaryToken catalogDictionary, DictionaryToken pagesDictionary, - PageTreeNode pageTree) + internal Catalog(DictionaryToken catalogDictionary, Pages pages, NamedDestinations namedDestinations) { CatalogDictionary = catalogDictionary ?? throw new ArgumentNullException(nameof(catalogDictionary)); - PagesDictionary = pagesDictionary ?? throw new ArgumentNullException(nameof(pagesDictionary)); - PageTree = pageTree ?? throw new ArgumentNullException(nameof(pageTree)); - - if (!pageTree.IsRoot) - { - throw new ArgumentException("Page tree must be the root page tree node.", nameof(pageTree)); - } - - var byNumber = new Dictionary(); - PopulatePageByNumberDictionary(pageTree, byNumber); - pagesByNumber = byNumber; - } - - private static void PopulatePageByNumberDictionary(PageTreeNode node, Dictionary result) - { - if (node.IsPage) - { - if (!node.PageNumber.HasValue) - { - throw new InvalidOperationException($"Node was page but did not have page number: {node}."); - } - - result[node.PageNumber.Value] = node; - return; - } - - foreach (var child in node.Children) - { - PopulatePageByNumberDictionary(child, result); - } - } - - internal PageTreeNode GetPageNode(int pageNumber) - { - if (!pagesByNumber.TryGetValue(pageNumber, out var node)) - { - throw new InvalidOperationException($"Could not find page node by number for: {pageNumber}."); - } - - return node; - } - - internal PageTreeNode GetPageByReference(IndirectReference reference) - { - foreach (var page in pagesByNumber) - { - if (page.Value.Reference.Equals(reference)) - { - return page.Value; - } - } - - return null; + Pages = pages ?? throw new ArgumentNullException(nameof(pages)); + NamedDestinations = namedDestinations; } } } diff --git a/src/UglyToad.PdfPig/Content/IPageFactory.cs b/src/UglyToad.PdfPig/Content/IPageFactory.cs index 172a3e51..c0380cad 100644 --- a/src/UglyToad.PdfPig/Content/IPageFactory.cs +++ b/src/UglyToad.PdfPig/Content/IPageFactory.cs @@ -1,13 +1,14 @@ namespace UglyToad.PdfPig.Content { + using Outline; using Tokens; internal interface IPageFactory { - Page Create( - int number, + Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, + NamedDestinations annotationProvider, InternalParsingOptions parsingOptions); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Content/Pages.cs b/src/UglyToad.PdfPig/Content/Pages.cs index 3bd55c09..1066a991 100644 --- a/src/UglyToad.PdfPig/Content/Pages.cs +++ b/src/UglyToad.PdfPig/Content/Pages.cs @@ -1,5 +1,8 @@ namespace UglyToad.PdfPig.Content { + using Annotations; + using Core; + using Outline; using System; using System.Collections.Generic; using Tokenization.Scanner; @@ -8,29 +11,25 @@ internal class Pages { - private readonly Catalog catalog; private readonly IPageFactory pageFactory; private readonly IPdfTokenScanner pdfScanner; + private readonly Dictionary pagesByNumber; + public int Count => pagesByNumber.Count; + + /// + /// The page tree for this document containing all pages, page numbers and their dictionaries. + /// + public PageTreeNode PageTree { get; } - public int Count { get; } - - internal Pages(Catalog catalog, IPageFactory pageFactory, IPdfTokenScanner pdfScanner) + internal Pages(IPageFactory pageFactory, IPdfTokenScanner pdfScanner, PageTreeNode pageTree, Dictionary pagesByNumber) { - this.catalog = catalog ?? throw new ArgumentNullException(nameof(catalog)); this.pageFactory = pageFactory ?? throw new ArgumentNullException(nameof(pageFactory)); this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner)); - - Count = catalog.PagesDictionary.GetIntOrDefault(NameToken.Count); - var CountOfPagesByPagesTree = catalog.PageTree.Children.Count; - var numberOfDiscoveredPages = catalog.NumberOfDiscoveredPages; - if (numberOfDiscoveredPages is null == false && Count != numberOfDiscoveredPages) - { - //log.Warning($"Dictionary Page Count {Count} different to discovered pages {numberOfDiscoveredPages}. Using {numberOfDiscoveredPages}."); - Count = numberOfDiscoveredPages.Value; - } + this.pagesByNumber = pagesByNumber; + PageTree = pageTree; } - - public Page GetPage(int pageNumber, InternalParsingOptions parsingOptions) + + internal Page GetPage(int pageNumber, NamedDestinations namedDestinations, InternalParsingOptions parsingOptions) { if (pageNumber <= 0 || pageNumber > Count) { @@ -40,7 +39,7 @@ $"Page number {pageNumber} invalid, must be between 1 and {Count}."); } - var pageNode = catalog.GetPageNode(pageNumber); + var pageNode = GetPageNode(pageNumber); var pageStack = new Stack(); var currentNode = pageNode; @@ -71,14 +70,37 @@ pageTreeMembers.Rotation = rotateToken.Int; } } - var page = pageFactory.Create( pageNumber, pageNode.NodeDictionary, pageTreeMembers, + namedDestinations, parsingOptions); return page; } + + internal PageTreeNode GetPageNode(int pageNumber) + { + if (!pagesByNumber.TryGetValue(pageNumber, out var node)) + { + throw new InvalidOperationException($"Could not find page node by number for: {pageNumber}."); + } + + return node; + } + + internal PageTreeNode GetPageByReference(IndirectReference reference) + { + foreach (var page in pagesByNumber) + { + if (page.Value.Reference.Equals(reference)) + { + return page.Value; + } + } + + return null; + } } } diff --git a/src/UglyToad.PdfPig/Content/PagesFactory.cs b/src/UglyToad.PdfPig/Content/PagesFactory.cs new file mode 100644 index 00000000..2f2c244c --- /dev/null +++ b/src/UglyToad.PdfPig/Content/PagesFactory.cs @@ -0,0 +1,241 @@ +namespace UglyToad.PdfPig.Content; + +using Core; +using Logging; +using Parser.Parts; +using System; +using System.Collections.Generic; +using System.Linq; +using Tokenization.Scanner; +using Tokens; +using Util; + +internal class PagesFactory +{ + private class PageCounter + { + public int PageCount { get; private set; } + public void Increment() + { + PageCount++; + } + } + + public static Pages Create(IndirectReference pagesReference, DictionaryToken pagesDictionary, IPdfTokenScanner scanner, IPageFactory pageFactory, ILog log, bool isLenientParsing) + { + var pageNumber = new PageCounter(); + + var pageTree = ProcessPagesNode(pagesReference, pagesDictionary, new IndirectReference(1, 0), true, + scanner, isLenientParsing, pageNumber); + + if (!pageTree.IsRoot) + { + throw new ArgumentException("Page tree must be the root page tree node.", nameof(pageTree)); + } + + var pagesByNumber = new Dictionary(); + PopulatePageByNumberDictionary(pageTree, pagesByNumber); + + var dictionaryPageCount = pagesDictionary.GetIntOrDefault(NameToken.Count); + if (dictionaryPageCount != pagesByNumber.Count) + { + log.Warn($"Dictionary Page Count {dictionaryPageCount} different to discovered pages {pagesByNumber.Count}. Using {pagesByNumber.Count}."); + } + + return new Pages(pageFactory, scanner, pageTree, pagesByNumber); + } + + + private static PageTreeNode ProcessPagesNode(IndirectReference referenceInput, + DictionaryToken nodeDictionaryInput, + IndirectReference parentReferenceInput, + bool isRoot, + IPdfTokenScanner pdfTokenScanner, + bool isLenientParsing, + PageCounter pageNumber) + { + bool isPage = CheckIfIsPage(nodeDictionaryInput, parentReferenceInput, isRoot, pdfTokenScanner, isLenientParsing); + + if (isPage) + { + pageNumber.Increment(); + + return new PageTreeNode(nodeDictionaryInput, referenceInput, true, pageNumber.PageCount).WithChildren(EmptyArray.Instance); + } + + + + //If we got here, we have to iterate till we manage to exit + + // Attempt to detect (and break) any infinite loop (IL) by recording the ids of the last 1000 (by default) tokens processed. + const int InfiniteLoopWorkingWindow = 1000; + var visitedTokens = new Dictionary>(); // Quick lookup containing ids (object number, generation) of tokens already processed (trimmed as we go to last 1000 (by default)) + var visitedTokensWorkingWindow = new Queue<(long ObjectNumber, int Generation)>(InfiniteLoopWorkingWindow); + + var toProcess = + new Queue<(PageTreeNode thisPage, IndirectReference reference, DictionaryToken nodeDictionary, IndirectReference parentReference, + List nodeChildren)>(); + var firstPage = new PageTreeNode(nodeDictionaryInput, referenceInput, false, null); + var setChildren = new List(); + var firstPageChildren = new List(); + + setChildren.Add(() => firstPage.WithChildren(firstPageChildren)); + + toProcess.Enqueue( + (thisPage: firstPage, reference: referenceInput, nodeDictionary: nodeDictionaryInput, parentReference: parentReferenceInput, + nodeChildren: firstPageChildren)); + + do + { + var current = toProcess.Dequeue(); + + #region Break any potential infinite loop + // Remember the last 1000 (by default) tokens and if we attempt to process again break out of loop + var currentReferenceObjectNumber = current.reference.ObjectNumber; + var currentReferenceGeneration = current.reference.Generation; + if (visitedTokens.ContainsKey(currentReferenceObjectNumber)) + { + var generations = visitedTokens[currentReferenceObjectNumber]; + + if (generations.Contains(currentReferenceGeneration)) + { + var listOfLastVisitedToken = visitedTokensWorkingWindow.ToList(); + var indexOfCurrentTokenInListOfLastVisitedToken = listOfLastVisitedToken.IndexOf((currentReferenceObjectNumber, currentReferenceGeneration)); + var howManyTokensBack = Math.Abs(indexOfCurrentTokenInListOfLastVisitedToken - listOfLastVisitedToken.Count); //eg initate loop is taking us back to last token or five token back + System.Diagnostics.Debug.WriteLine($"Break infinite loop while processing page {pageNumber.PageCount+1} tokens. Token with object number {currentReferenceObjectNumber} and generation {currentReferenceGeneration} processed {howManyTokensBack} token(s) back. "); + continue; // don't reprocess token already processed. break infinite loop. Issue #519 + } + else + { + generations.Add(currentReferenceGeneration); + visitedTokens[currentReferenceObjectNumber] = generations; + } + } + else + { + visitedTokens.Add(currentReferenceObjectNumber, new HashSet() { currentReferenceGeneration }); + + visitedTokensWorkingWindow.Enqueue((currentReferenceObjectNumber, currentReferenceGeneration)); + if (visitedTokensWorkingWindow.Count >= InfiniteLoopWorkingWindow) + { + var toBeRemovedFromWorkingHashset = visitedTokensWorkingWindow.Dequeue(); + var toBeRemovedObjectNumber = toBeRemovedFromWorkingHashset.ObjectNumber; + var toBeRemovedGeneration = toBeRemovedFromWorkingHashset.Generation; + var generations = visitedTokens[toBeRemovedObjectNumber]; + generations.Remove(toBeRemovedGeneration); + if (generations.Count == 0) + { + visitedTokens.Remove(toBeRemovedObjectNumber); + } + else + { + visitedTokens[toBeRemovedObjectNumber] = generations; + } + } + } + #endregion + if (!current.nodeDictionary.TryGet(NameToken.Kids, pdfTokenScanner, out ArrayToken kids)) + { + if (!isLenientParsing) + { + throw new PdfDocumentFormatException($"Pages node in the document pages tree did not define a kids array: {current.nodeDictionary}."); + } + + kids = new ArrayToken(EmptyArray.Instance); + } + + foreach (var kid in kids.Data) + { + if (!(kid is IndirectReferenceToken kidRef)) + { + throw new PdfDocumentFormatException($"Kids array contained invalid entry (must be indirect reference): {kid}."); + } + + if (!DirectObjectFinder.TryGet(kidRef, pdfTokenScanner, out DictionaryToken kidDictionaryToken)) + { + throw new PdfDocumentFormatException($"Could not find dictionary associated with reference in pages kids array: {kidRef}."); + } + + bool isChildPage = CheckIfIsPage(kidDictionaryToken, current.reference, false, pdfTokenScanner, isLenientParsing); + + if (isChildPage) + { + var kidPageNode = + new PageTreeNode(kidDictionaryToken, kidRef.Data, true, pageNumber.PageCount).WithChildren(EmptyArray.Instance); + current.nodeChildren.Add(kidPageNode); + } + else + { + var kidChildNode = new PageTreeNode(kidDictionaryToken, kidRef.Data, false, null); + var kidChildren = new List(); + toProcess.Enqueue( + (thisPage: kidChildNode, reference: kidRef.Data, nodeDictionary: kidDictionaryToken, parentReference: current.reference, + nodeChildren: kidChildren)); + + setChildren.Add(() => kidChildNode.WithChildren(kidChildren)); + + current.nodeChildren.Add(kidChildNode); + } + } + } while (toProcess.Count > 0); + + foreach (var action in setChildren) + { + action(); + } + + foreach (var child in firstPage.Children.ToRecursiveOrderList(x=>x.Children).Where(child => child.IsPage)) + { + pageNumber.Increment(); + child.PageNumber = pageNumber.PageCount; + } + + return firstPage; + } + + private static bool CheckIfIsPage(DictionaryToken nodeDictionary, IndirectReference parentReference, bool isRoot, IPdfTokenScanner pdfTokenScanner, bool isLenientParsing) + { + var isPage = false; + + if (!nodeDictionary.TryGet(NameToken.Type, pdfTokenScanner, out NameToken type)) + { + if (!isLenientParsing) { throw new PdfDocumentFormatException($"Node in the document pages tree did not define a type: {nodeDictionary}."); } + + if (!nodeDictionary.TryGet(NameToken.Kids, pdfTokenScanner, out ArrayToken _)) { isPage = true; } + } + else + { + isPage = type.Equals(NameToken.Page); + + if (!isPage && !type.Equals(NameToken.Pages) && !isLenientParsing) { throw new PdfDocumentFormatException($"Node in the document pages tree defined invalid type: {nodeDictionary}."); } + } + + if (!isLenientParsing && !isRoot) + { + if (!nodeDictionary.TryGet(NameToken.Parent, pdfTokenScanner, out IndirectReferenceToken parentReferenceToken)) { throw new PdfDocumentFormatException($"Could not find parent indirect reference token on pages tree node: {nodeDictionary}."); } + + if (!parentReferenceToken.Data.Equals(parentReference)) { throw new PdfDocumentFormatException($"Pages tree node parent reference {parentReferenceToken.Data} did not match actual parent {parentReference}."); } + } + + return isPage; + } + + private static void PopulatePageByNumberDictionary(PageTreeNode node, Dictionary result) + { + if (node.IsPage) + { + if (!node.PageNumber.HasValue) + { + throw new InvalidOperationException($"Node was page but did not have page number: {node}."); + } + + result[node.PageNumber.Value] = node; + return; + } + + foreach (var child in node.Children) + { + PopulatePageByNumberDictionary(child, result); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Outline/BookmarkNode.cs b/src/UglyToad.PdfPig/Outline/BookmarkNode.cs index 53753b82..8e507fef 100644 --- a/src/UglyToad.PdfPig/Outline/BookmarkNode.cs +++ b/src/UglyToad.PdfPig/Outline/BookmarkNode.cs @@ -1,5 +1,6 @@ namespace UglyToad.PdfPig.Outline { + using Destinations; using System; using System.Collections.Generic; diff --git a/src/UglyToad.PdfPig/Outline/BookmarksProvider.cs b/src/UglyToad.PdfPig/Outline/BookmarksProvider.cs index a1ede513..fc0d611e 100644 --- a/src/UglyToad.PdfPig/Outline/BookmarksProvider.cs +++ b/src/UglyToad.PdfPig/Outline/BookmarksProvider.cs @@ -1,5 +1,6 @@ namespace UglyToad.PdfPig.Outline { + using Actions; using Content; using Destinations; using Logging; @@ -41,14 +42,12 @@ return null; } - var namedDestinations = ReadNamedDestinations(catalog, pdfScanner, log); - var roots = new List(); var seen = new HashSet(); while (next != null) { - ReadBookmarksRecursively(next, 0, false, seen, namedDestinations, catalog, roots); + ReadBookmarksRecursively(next, 0, false, seen, catalog.NamedDestinations, roots); if (!next.TryGet(NameToken.Next, out IndirectReferenceToken nextReference) || !seen.Add(nextReference.Data)) @@ -66,8 +65,7 @@ /// Extract bookmarks recursively. /// private void ReadBookmarksRecursively(DictionaryToken nodeDictionary, int level, bool readSiblings, HashSet seen, - IReadOnlyDictionary namedDestinations, - Catalog catalog, + NamedDestinations namedDestinations, List list) { // 12.3 Document-Level Navigation @@ -82,37 +80,37 @@ var children = new List(); if (nodeDictionary.TryGet(NameToken.First, pdfScanner, out DictionaryToken firstChild)) { - ReadBookmarksRecursively(firstChild, level + 1, true, seen, namedDestinations, catalog, children); + ReadBookmarksRecursively(firstChild, level + 1, true, seen, namedDestinations, children); } BookmarkNode bookmark; - if (nodeDictionary.TryGet(NameToken.Dest, pdfScanner, out ArrayToken destArray) - && TryGetExplicitDestination(destArray, catalog, log, out var destination)) + if (DestinationProvider.TryGetDestination(nodeDictionary, NameToken.Dest, namedDestinations, pdfScanner, log, false, out var destination)) { bookmark = new DocumentBookmarkNode(title, level, destination, children); } - else if (nodeDictionary.TryGet(NameToken.Dest, pdfScanner, out IDataToken destStringToken)) + else if (ActionProvider.TryGetAction(nodeDictionary, namedDestinations, pdfScanner, log, out var actionResult)) { - // 12.3.2.3 Named Destinations - if (namedDestinations.TryGetValue(destStringToken.Data, out destination)) + if (actionResult is GoToRAction goToRAction) { - bookmark = new DocumentBookmarkNode(title, level, destination, children); + bookmark = new ExternalBookmarkNode(title, level, goToRAction.Destination, children, goToRAction.Filename); + } + else if (actionResult is GoToAction goToAction) + { + bookmark = new DocumentBookmarkNode(title, level, goToAction.Destination, children); + } + else if (actionResult is UriAction uriAction) + { + bookmark = new UriBookmarkNode(title, level, uriAction.Uri, children); } else { return; } } - else if (nodeDictionary.TryGet(NameToken.A, pdfScanner, out DictionaryToken actionDictionary) - && TryGetAction(actionDictionary, catalog, pdfScanner, namedDestinations, log, title, level, children, out var actionResult)) - { - bookmark = actionResult; - } else { log.Error($"No /Dest(ination) or /A(ction) entry found for bookmark node: {nodeDictionary}."); - return; } @@ -140,267 +138,8 @@ break; } - ReadBookmarksRecursively(current, level, false, seen, namedDestinations, catalog, list); + ReadBookmarksRecursively(current, level, false, seen, namedDestinations, list); } } - - #region Named Destinations - private static IReadOnlyDictionary ReadNamedDestinations(Catalog catalog, IPdfTokenScanner pdfScanner, - ILog log) - { - var result = new Dictionary(); - - if (catalog.CatalogDictionary.TryGet(NameToken.Dests, pdfScanner, out DictionaryToken dests)) - { - /* - * In PDF 1.1, the correspondence between name objects and destinations is defined by the /Dests entry in the document catalog. - * The value of this entry is a dictionary in which each key is a destination name and the corresponding value is either an array - * defining the destination, using the explicit destination syntax, or a dictionary with a /D entry whose value is such an array. - */ - foreach (var kvp in dests.Data) - { - var value = kvp.Value; - - if (TryReadExplicitDestination(value, catalog, pdfScanner, log, out var destination)) - { - result[kvp.Key] = destination; - } - } - } - else if (catalog.CatalogDictionary.TryGet(NameToken.Names, pdfScanner, out DictionaryToken names) - && names.TryGet(NameToken.Dests, pdfScanner, out dests)) - { - /* - * In PDF 1.2, the correspondence between strings and destinations is defined by the /Dests entry in the document's name dictionary. - * The value of the /Dests entry is a name tree mapping name strings to destinations. - * The keys in the name tree may be treated as text strings for display purposes. - * The destination value associated with a key in the name tree may be either an array or a dictionary. - */ - NameTreeParser.FlattenNameTree(dests, pdfScanner, value => - { - if (TryReadExplicitDestination(value, catalog, pdfScanner, log, out var destination)) - { - return destination; - } - - return null; - }, result); - } - - return result; - } - - private static bool TryReadExplicitDestination(IToken value, Catalog catalog, IPdfTokenScanner pdfScanner, - ILog log, out ExplicitDestination destination) - { - destination = null; - - if (DirectObjectFinder.TryGet(value, pdfScanner, out ArrayToken valueArray) - && TryGetExplicitDestination(valueArray, catalog, log, out destination)) - { - return true; - } - - if (DirectObjectFinder.TryGet(value, pdfScanner, out DictionaryToken valueDictionary) - && valueDictionary.TryGet(NameToken.D, pdfScanner, out valueArray) - && TryGetExplicitDestination(valueArray, catalog, log, out destination)) - { - return true; - } - - return false; - } - - private static bool TryGetExplicitDestination(ArrayToken explicitDestinationArray, Catalog catalog, - ILog log, - out ExplicitDestination destination) - { - destination = null; - - if (explicitDestinationArray == null || explicitDestinationArray.Length == 0) - { - return false; - } - - int pageNumber; - - var pageToken = explicitDestinationArray[0]; - - if (pageToken is IndirectReferenceToken pageIndirectReferenceToken) - { - var page = catalog.GetPageByReference(pageIndirectReferenceToken.Data); - - if (page?.PageNumber == null) - { - return false; - } - - pageNumber = page.PageNumber.Value; - } - else if (pageToken is NumericToken pageNumericToken) - { - pageNumber = pageNumericToken.Int + 1; - } - else - { - var errorMessage = $"{nameof(TryGetExplicitDestination)} No page number given in 'Dest': '{explicitDestinationArray}'."; - - log.Error(errorMessage); - - return false; - } - - var destTypeToken = explicitDestinationArray[1] as NameToken; - if (destTypeToken == null) - { - var errorMessage = $"Missing name token as second argument to explicit destination: {explicitDestinationArray}."; - - log.Error(errorMessage); - - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitPage, ExplicitDestinationCoordinates.Empty); - - return true; - } - - if (destTypeToken.Equals(NameToken.XYZ)) - { - // [page /XYZ left top zoom] - var left = explicitDestinationArray[2] as NumericToken; - var top = explicitDestinationArray[3] as NumericToken; - - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.XyzCoordinates, - new ExplicitDestinationCoordinates(left?.Data, top?.Data)); - - return true; - } - - if (destTypeToken.Equals(NameToken.Fit)) - { - // [page /Fit] - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitPage, - ExplicitDestinationCoordinates.Empty); - - return true; - } - - if (destTypeToken.Equals(NameToken.FitH)) - { - // [page /FitH top] - var top = explicitDestinationArray[2] as NumericToken; - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitHorizontally, - new ExplicitDestinationCoordinates(null, top?.Data)); - - return true; - } - - if (destTypeToken.Equals(NameToken.FitV)) - { - // [page /FitV left] - var left = explicitDestinationArray[2] as NumericToken; - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitVertically, - new ExplicitDestinationCoordinates(left?.Data)); - - return true; - } - - if (destTypeToken.Equals(NameToken.FitR)) - { - // [page /FitR left bottom right top] - var left = explicitDestinationArray[2] as NumericToken; - var bottom = explicitDestinationArray[3] as NumericToken; - var right = explicitDestinationArray[4] as NumericToken; - var top = explicitDestinationArray[5] as NumericToken; - - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitRectangle, - new ExplicitDestinationCoordinates(left?.Data, top?.Data, right?.Data, bottom?.Data)); - - return true; - } - - if (destTypeToken.Equals(NameToken.FitB)) - { - // [page /FitB] - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitBoundingBox, - ExplicitDestinationCoordinates.Empty); - - return true; - } - - if (destTypeToken.Equals(NameToken.FitBH)) - { - // [page /FitBH top] - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitBoundingBoxHorizontally, - new ExplicitDestinationCoordinates(null, (explicitDestinationArray[2] as NumericToken)?.Data)); - - return true; - } - - if (destTypeToken.Equals(NameToken.FitBV)) - { - // [page /FitBV left] - destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitBoundingBoxVertically, - new ExplicitDestinationCoordinates((explicitDestinationArray[2] as NumericToken)?.Data)); - - return true; - } - - return false; - } - #endregion - - private static bool TryGetAction(DictionaryToken actionDictionary, Catalog catalog, IPdfTokenScanner pdfScanner, - IReadOnlyDictionary namedDestinations, - ILog log, string title, int level, List children, out BookmarkNode result) - { - result = null; - - if (!actionDictionary.TryGet(NameToken.S, pdfScanner, out NameToken actionType)) - { - throw new PdfDocumentFormatException($"No action type (/S) specified for action: {actionDictionary}."); - } - - if (actionType.Equals(NameToken.GoTo)) - { - if (actionDictionary.TryGet(NameToken.D, pdfScanner, out ArrayToken destinationArray) - && TryGetExplicitDestination(destinationArray, catalog, log, out var destination)) - { - result = new DocumentBookmarkNode(title, level, destination, children); - - return true; - } - - if (actionDictionary.TryGet(NameToken.D, pdfScanner, out IDataToken destinationName) - && namedDestinations.TryGetValue(destinationName.Data, out destination)) - { - result = new DocumentBookmarkNode(title, level, destination, children); - - return true; - } - } - else if (actionType.Equals(NameToken.GoToR)) - { - if (actionDictionary.TryGetOptionalStringDirect(NameToken.F, pdfScanner, out var filename)) - { - result = new ExternalBookmarkNode(title, level, filename, children); - return true; - } - - result = new ExternalBookmarkNode(title, level, string.Empty, children); - return true; - } - else if (actionType.Equals(NameToken.Uri)) - { - if (actionDictionary.TryGetOptionalStringDirect(NameToken.Uri, pdfScanner, out var uri)) - { - result = new UriBookmarkNode(title, level, uri, children); - return true; - } - - result = new UriBookmarkNode(title, level, string.Empty, children); - return true; - } - - return false; - } } } diff --git a/src/UglyToad.PdfPig/Outline/Destinations/DestinationProvider.cs b/src/UglyToad.PdfPig/Outline/Destinations/DestinationProvider.cs new file mode 100644 index 00000000..52f5d97c --- /dev/null +++ b/src/UglyToad.PdfPig/Outline/Destinations/DestinationProvider.cs @@ -0,0 +1,36 @@ +namespace UglyToad.PdfPig.Outline.Destinations +{ + using Logging; + using Tokenization.Scanner; + using Tokens; + + internal static class DestinationProvider + { + /// + /// Get explicit destination or a named destination (Ref 12.3.2.3) from dictionary + /// + /// + /// Token name, can be D or Dest + /// + /// + /// + /// in case we are looking up a destination for a GoToR (Go To Remote) action: pass in true + /// to enforce a check for indirect page references (which is not allowed for GoToR) + /// + /// + internal static bool TryGetDestination(DictionaryToken dictionary, NameToken destinationToken, NamedDestinations namedDestinations, IPdfTokenScanner pdfScanner, ILog log, bool isRemoteDestination, out ExplicitDestination destination) + { + if (dictionary.TryGet(destinationToken, pdfScanner, out ArrayToken destArray)) + { + return namedDestinations.TryGetExplicitDestination(destArray, log, isRemoteDestination, out destination); + } + if (dictionary.TryGet(destinationToken, pdfScanner, out IDataToken destStringToken)) + { + return namedDestinations.TryGet(destStringToken.Data, out destination); + } + destination = null; + return false; + } + + } +} diff --git a/src/UglyToad.PdfPig/Outline/Destinations/ExplicitDestination.cs b/src/UglyToad.PdfPig/Outline/Destinations/ExplicitDestination.cs index 854af717..d93ac31b 100644 --- a/src/UglyToad.PdfPig/Outline/Destinations/ExplicitDestination.cs +++ b/src/UglyToad.PdfPig/Outline/Destinations/ExplicitDestination.cs @@ -6,7 +6,7 @@ public class ExplicitDestination { /// - /// The page number of the destination. + /// The page number (1-based) of the destination. /// public int PageNumber { get; } diff --git a/src/UglyToad.PdfPig/Outline/Destinations/NamedDestinations.cs b/src/UglyToad.PdfPig/Outline/Destinations/NamedDestinations.cs new file mode 100644 index 00000000..45a8dc5e --- /dev/null +++ b/src/UglyToad.PdfPig/Outline/Destinations/NamedDestinations.cs @@ -0,0 +1,44 @@ +namespace UglyToad.PdfPig.Outline; + +using Content; +using Destinations; +using Logging; +using System.Collections.Generic; +using Tokens; + +/// +/// Named destinations in a PDF document +/// +internal class NamedDestinations +{ + /// + /// Dictionary containing explicit destinations, keyed by name + /// + private readonly IReadOnlyDictionary namedDestinations; + + /// + /// Pages are required for getting explicit destinations + /// + private readonly Pages pages; + + /// + /// Constructor + /// + /// + /// + internal NamedDestinations(IReadOnlyDictionary namedDestinations, Pages pages) + { + this.namedDestinations = namedDestinations; + this.pages = pages; + } + + internal bool TryGet(string name, out ExplicitDestination destination) + { + return namedDestinations.TryGetValue(name, out destination); + } + + internal bool TryGetExplicitDestination(ArrayToken explicitDestinationArray, ILog log, bool isRemoteDestination, out ExplicitDestination destination) + { + return NamedDestinationsProvider.TryGetExplicitDestination(explicitDestinationArray, pages, log, isRemoteDestination, out destination); + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Outline/Destinations/NamedDestinationsProvider.cs b/src/UglyToad.PdfPig/Outline/Destinations/NamedDestinationsProvider.cs new file mode 100644 index 00000000..bfef9dae --- /dev/null +++ b/src/UglyToad.PdfPig/Outline/Destinations/NamedDestinationsProvider.cs @@ -0,0 +1,220 @@ +namespace UglyToad.PdfPig.Outline; + +using Content; +using Destinations; +using Logging; +using Parser.Parts; +using System.Collections.Generic; +using Tokenization.Scanner; +using Tokens; + +internal static class NamedDestinationsProvider +{ + internal static NamedDestinations Read(DictionaryToken catalogDictionary, IPdfTokenScanner pdfScanner, Pages pages, ILog log) + { + var destinationsByName = new Dictionary(); + + if (catalogDictionary.TryGet(NameToken.Dests, pdfScanner, out DictionaryToken destinations)) + { + /* + * In PDF 1.1, the correspondence between name objects and destinations is defined by the /Dests entry in the document catalog. + * The value of this entry is a dictionary in which each key is a destination name and the corresponding value is either an array + * defining the destination, using the explicit destination syntax, or a dictionary with a /D entry whose value is such an array. + */ + foreach (var kvp in destinations.Data) + { + var value = kvp.Value; + + if (TryReadExplicitDestination(value, pdfScanner, pages, log, false, out var destination)) + { + destinationsByName[kvp.Key] = destination; + } + } + } + else if (catalogDictionary.TryGet(NameToken.Names, pdfScanner, out DictionaryToken names) + && names.TryGet(NameToken.Dests, pdfScanner, out destinations)) + { + /* + * In PDF 1.2, the correspondence between strings and destinations is defined by the /Dests entry in the document's name dictionary. + * The value of the /Dests entry is a name tree mapping name strings to destinations. + * The keys in the name tree may be treated as text strings for display purposes. + * The destination value associated with a key in the name tree may be either an array or a dictionary. + */ + NameTreeParser.FlattenNameTree(destinations, pdfScanner, value => + { + if (TryReadExplicitDestination(value, pdfScanner, pages, log, false, out var destination)) + { + return destination; + } + + return null; + }, destinationsByName); + } + + return new NamedDestinations(destinationsByName, pages); + } + + private static bool TryReadExplicitDestination(IToken value, IPdfTokenScanner pdfScanner, Pages pages, ILog log, bool isRemoteDestination, out ExplicitDestination destination) + { + destination = null; + + if (DirectObjectFinder.TryGet(value, pdfScanner, out ArrayToken valueArray) + && TryGetExplicitDestination(valueArray, pages, log, isRemoteDestination, out destination)) + { + return true; + } + + if (DirectObjectFinder.TryGet(value, pdfScanner, out DictionaryToken valueDictionary) + && valueDictionary.TryGet(NameToken.D, pdfScanner, out valueArray) + && TryGetExplicitDestination(valueArray, pages, log, isRemoteDestination, out destination)) + { + return true; + } + + return false; + } + + internal static bool TryGetExplicitDestination(ArrayToken explicitDestinationArray, Pages pages, ILog log, bool isRemoteDestination, out ExplicitDestination destination) + { + destination = null; + + if (explicitDestinationArray == null || explicitDestinationArray.Length == 0) + { + return false; + } + + int pageNumber; + + var pageToken = explicitDestinationArray[0]; + + if (pageToken is IndirectReferenceToken pageIndirectReferenceToken) + { + if (isRemoteDestination) + { + // Table 8.50 Remote Go-To Actions + var errorMessage = $"{nameof(TryGetExplicitDestination)} Cannot use indirect reference for remote destination."; + log?.Error(errorMessage); + return false; + } + var page = pages.GetPageByReference(pageIndirectReferenceToken.Data); + if (page?.PageNumber == null) + { + return false; + } + + pageNumber = page.PageNumber.Value; + } + else if (pageToken is NumericToken pageNumericToken) + { + pageNumber = pageNumericToken.Int + 1; + } + else + { + var errorMessage = $"{nameof(TryGetExplicitDestination)} No page number given in 'Dest': '{explicitDestinationArray}'."; + + log?.Error(errorMessage); + + return false; + } + + NameToken destTypeToken = null; + if (explicitDestinationArray.Length > 1) + { + destTypeToken = explicitDestinationArray[1] as NameToken; + } + if (destTypeToken == null) + { + var errorMessage = $"Missing name token as second argument to explicit destination: {explicitDestinationArray}."; + + log?.Error(errorMessage); + + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitPage, ExplicitDestinationCoordinates.Empty); + + return true; + } + + if (destTypeToken.Equals(NameToken.XYZ)) + { + // [page /XYZ left top zoom] + var left = explicitDestinationArray[2] as NumericToken; + var top = explicitDestinationArray[3] as NumericToken; + + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.XyzCoordinates, + new ExplicitDestinationCoordinates(left?.Data, top?.Data)); + + return true; + } + + if (destTypeToken.Equals(NameToken.Fit)) + { + // [page /Fit] + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitPage, + ExplicitDestinationCoordinates.Empty); + + return true; + } + + if (destTypeToken.Equals(NameToken.FitH)) + { + // [page /FitH top] + var top = explicitDestinationArray[2] as NumericToken; + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitHorizontally, + new ExplicitDestinationCoordinates(null, top?.Data)); + + return true; + } + + if (destTypeToken.Equals(NameToken.FitV)) + { + // [page /FitV left] + var left = explicitDestinationArray[2] as NumericToken; + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitVertically, + new ExplicitDestinationCoordinates(left?.Data)); + + return true; + } + + if (destTypeToken.Equals(NameToken.FitR)) + { + // [page /FitR left bottom right top] + var left = explicitDestinationArray[2] as NumericToken; + var bottom = explicitDestinationArray[3] as NumericToken; + var right = explicitDestinationArray[4] as NumericToken; + var top = explicitDestinationArray[5] as NumericToken; + + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitRectangle, + new ExplicitDestinationCoordinates(left?.Data, top?.Data, right?.Data, bottom?.Data)); + + return true; + } + + if (destTypeToken.Equals(NameToken.FitB)) + { + // [page /FitB] + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitBoundingBox, + ExplicitDestinationCoordinates.Empty); + + return true; + } + + if (destTypeToken.Equals(NameToken.FitBH)) + { + // [page /FitBH top] + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitBoundingBoxHorizontally, + new ExplicitDestinationCoordinates(null, (explicitDestinationArray[2] as NumericToken)?.Data)); + + return true; + } + + if (destTypeToken.Equals(NameToken.FitBV)) + { + // [page /FitBV left] + destination = new ExplicitDestination(pageNumber, ExplicitDestinationType.FitBoundingBoxVertically, + new ExplicitDestinationCoordinates((explicitDestinationArray[2] as NumericToken)?.Data)); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Outline/DocumentBookmarkNode.cs b/src/UglyToad.PdfPig/Outline/DocumentBookmarkNode.cs index bb57db6f..7cb9e6a2 100644 --- a/src/UglyToad.PdfPig/Outline/DocumentBookmarkNode.cs +++ b/src/UglyToad.PdfPig/Outline/DocumentBookmarkNode.cs @@ -1,6 +1,5 @@ namespace UglyToad.PdfPig.Outline { - using System; using System.Collections.Generic; using Destinations; @@ -14,7 +13,7 @@ /// /// The page number where the bookmark is located. /// - public int PageNumber { get; } + public int PageNumber => Destination.PageNumber; /// /// The destination of the bookmark in the current document. @@ -28,8 +27,7 @@ public DocumentBookmarkNode(string title, int level, ExplicitDestination destination, IReadOnlyList children) : base(title, level, children) { - Destination = destination ?? throw new ArgumentNullException(nameof(destination)); - PageNumber = destination.PageNumber; + Destination = destination; } /// diff --git a/src/UglyToad.PdfPig/Outline/EmbeddedBookmarkNode.cs b/src/UglyToad.PdfPig/Outline/EmbeddedBookmarkNode.cs new file mode 100644 index 00000000..06ee2d49 --- /dev/null +++ b/src/UglyToad.PdfPig/Outline/EmbeddedBookmarkNode.cs @@ -0,0 +1,33 @@ +namespace UglyToad.PdfPig.Outline; + +using Destinations; +using System; +using System.Collections.Generic; + +/// +/// +/// A node in the of a PDF document which corresponds +/// to a location in an embedded file. +/// +public class EmbeddedBookmarkNode : DocumentBookmarkNode +{ + /// + /// The file specification for the embedded file + /// + public string FileSpecification { get; } + + /// + /// + /// Create a new . + /// + public EmbeddedBookmarkNode(string title, int level, ExplicitDestination destination, IReadOnlyList children, string fileSpecification) : base(title, level, destination, children) + { + FileSpecification = fileSpecification ?? throw new ArgumentNullException(nameof(fileSpecification)); + } + + /// + public override string ToString() + { + return $"Embedded file '{FileSpecification}', {Level}, {Title}"; + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Outline/ExternalBookmarkNode.cs b/src/UglyToad.PdfPig/Outline/ExternalBookmarkNode.cs index e7c1b38e..3e98c2d5 100644 --- a/src/UglyToad.PdfPig/Outline/ExternalBookmarkNode.cs +++ b/src/UglyToad.PdfPig/Outline/ExternalBookmarkNode.cs @@ -1,5 +1,6 @@ namespace UglyToad.PdfPig.Outline { + using Destinations; using System; using System.Collections.Generic; @@ -8,7 +9,7 @@ /// A node in the of a PDF document which corresponds /// to a location in an external file. /// - public class ExternalBookmarkNode : BookmarkNode + public class ExternalBookmarkNode : DocumentBookmarkNode { /// /// The name of the file containing this bookmark. @@ -19,7 +20,7 @@ /// /// Create a new . /// - public ExternalBookmarkNode(string title, int level, string fileName, IReadOnlyList children) : base(title, level, children) + public ExternalBookmarkNode(string title, int level, ExplicitDestination destination, IReadOnlyList children, string fileName) : base(title, level, destination, children) { FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); } diff --git a/src/UglyToad.PdfPig/Parser/CatalogFactory.cs b/src/UglyToad.PdfPig/Parser/CatalogFactory.cs index 88e69586..83c88449 100644 --- a/src/UglyToad.PdfPig/Parser/CatalogFactory.cs +++ b/src/UglyToad.PdfPig/Parser/CatalogFactory.cs @@ -1,30 +1,18 @@ namespace UglyToad.PdfPig.Parser { using System; - using System.Collections.Generic; using Content; using Core; + using Logging; + using Outline; using Parts; - using System.Linq; using Tokenization.Scanner; using Tokens; - using Util; internal static class CatalogFactory { - - private class PageCounter - { - public int PageCount { get; private set; } - public void Increment() - { - PageCount++; - } - } - public static Catalog Create(IndirectReference rootReference, DictionaryToken dictionary, - IPdfTokenScanner scanner, - bool isLenientParsing) + IPdfTokenScanner scanner, PageFactory pageFactory, ILog log, bool isLenientParsing) { if (dictionary == null) { @@ -41,203 +29,27 @@ throw new PdfDocumentFormatException($"No pages entry was found in the catalog dictionary: {dictionary}."); } - DictionaryToken pages; + DictionaryToken pagesDictionary; var pagesReference = rootReference; if (value is IndirectReferenceToken pagesRef) { pagesReference = pagesRef.Data; - pages = DirectObjectFinder.Get(pagesRef, scanner); + pagesDictionary = DirectObjectFinder.Get(pagesRef, scanner); } else if (value is DictionaryToken pagesDict) { - pages = pagesDict; + pagesDictionary = pagesDict; } else { - pages = DirectObjectFinder.Get(value, scanner); + pagesDictionary = DirectObjectFinder.Get(value, scanner); } - var pageNumber = new PageCounter(); + var pages = PagesFactory.Create(pagesReference, pagesDictionary, scanner, pageFactory, log, isLenientParsing); + var namedDestinations = NamedDestinationsProvider.Read(dictionary, scanner, pages, null); - var pageTree = ProcessPagesNode(pagesReference, pages, new IndirectReference(1, 0), true, - scanner, isLenientParsing, pageNumber); - - return new Catalog(dictionary, pages, pageTree); - } - - private static PageTreeNode ProcessPagesNode(IndirectReference referenceInput, - DictionaryToken nodeDictionaryInput, - IndirectReference parentReferenceInput, - bool isRoot, - IPdfTokenScanner pdfTokenScanner, - bool isLenientParsing, - PageCounter pageNumber) - { - bool isPage = CheckIfIsPage(nodeDictionaryInput, parentReferenceInput, isRoot, pdfTokenScanner, isLenientParsing); - - if (isPage) - { - pageNumber.Increment(); - - return new PageTreeNode(nodeDictionaryInput, referenceInput, true, pageNumber.PageCount).WithChildren(EmptyArray.Instance); - } - - - - //If we got here, we have to iterate till we manage to exit - - // Attempt to detect (and break) any infitine loop (IL) by recording the ids of the last 1000 (by default) tokens processed. - const int InfiniteLoopWorkingWindow = 1000; - var visitedTokens = new Dictionary>(); // Quick lookup containing ids (object number, generation) of tokens already processed (trimmed as we go to last 1000 (by default)) - var visitedTokensWorkingWindow = new Queue<(long ObjectNumber, int Generation)>(InfiniteLoopWorkingWindow); - - var toProcess = - new Queue<(PageTreeNode thisPage, IndirectReference reference, DictionaryToken nodeDictionary, IndirectReference parentReference, - List nodeChildren)>(); - var firstPage = new PageTreeNode(nodeDictionaryInput, referenceInput, false, null); - var setChildren = new List(); - var firstPageChildren = new List(); - - setChildren.Add(() => firstPage.WithChildren(firstPageChildren)); - - toProcess.Enqueue( - (thisPage: firstPage, reference: referenceInput, nodeDictionary: nodeDictionaryInput, parentReference: parentReferenceInput, - nodeChildren: firstPageChildren)); - - do - { - var current = toProcess.Dequeue(); - - #region Break any potential infinite loop - // Remember the last 1000 (by default) tokens and if we attempt to process again break out of loop - var currentReferenceObjectNumber = current.reference.ObjectNumber; - var currentReferenceGeneration = current.reference.Generation; - if (visitedTokens.ContainsKey(currentReferenceObjectNumber)) - { - var generations = visitedTokens[currentReferenceObjectNumber]; - - if (generations.Contains(currentReferenceGeneration)) - { - var listOfLastVisitedToken = visitedTokensWorkingWindow.ToList(); - var indexOfCurrentTokenInListOfLastVisitedToken = listOfLastVisitedToken.IndexOf((currentReferenceObjectNumber, currentReferenceGeneration)); - var howManyTokensBack = Math.Abs(indexOfCurrentTokenInListOfLastVisitedToken - listOfLastVisitedToken.Count); //eg initate loop is taking us back to last token or five token back - System.Diagnostics.Debug.WriteLine($"Break infinite loop while processing page {pageNumber.PageCount+1} tokens. Token with object number {currentReferenceObjectNumber} and generation {currentReferenceGeneration} processed {howManyTokensBack} token(s) back. "); - continue; // don't reprocess token already processed. break infinite loop. Issue #519 - } - else - { - generations.Add(currentReferenceGeneration); - visitedTokens[currentReferenceObjectNumber] = generations; - } - } - else - { - visitedTokens.Add(currentReferenceObjectNumber, new HashSet() { currentReferenceGeneration }); - - visitedTokensWorkingWindow.Enqueue((currentReferenceObjectNumber, currentReferenceGeneration)); - if (visitedTokensWorkingWindow.Count >= InfiniteLoopWorkingWindow) - { - var toBeRemovedFromWorkingHashset = visitedTokensWorkingWindow.Dequeue(); - var toBeRemovedObjectNumber = toBeRemovedFromWorkingHashset.ObjectNumber; - var toBeRemovedGeneration = toBeRemovedFromWorkingHashset.Generation; - var generations = visitedTokens[toBeRemovedObjectNumber]; - generations.Remove(toBeRemovedGeneration); - if (generations.Count == 0) - { - visitedTokens.Remove(toBeRemovedObjectNumber); - } - else - { - visitedTokens[toBeRemovedObjectNumber] = generations; - } - } - } - #endregion - if (!current.nodeDictionary.TryGet(NameToken.Kids, pdfTokenScanner, out ArrayToken kids)) - { - if (!isLenientParsing) - { - throw new PdfDocumentFormatException($"Pages node in the document pages tree did not define a kids array: {current.nodeDictionary}."); - } - - kids = new ArrayToken(EmptyArray.Instance); - } - - foreach (var kid in kids.Data) - { - if (!(kid is IndirectReferenceToken kidRef)) - { - throw new PdfDocumentFormatException($"Kids array contained invalid entry (must be indirect reference): {kid}."); - } - - if (!DirectObjectFinder.TryGet(kidRef, pdfTokenScanner, out DictionaryToken kidDictionaryToken)) - { - throw new PdfDocumentFormatException($"Could not find dictionary associated with reference in pages kids array: {kidRef}."); - } - - bool isChildPage = CheckIfIsPage(kidDictionaryToken, current.reference, false, pdfTokenScanner, isLenientParsing); - - if (isChildPage) - { - var kidPageNode = - new PageTreeNode(kidDictionaryToken, kidRef.Data, true, pageNumber.PageCount).WithChildren(EmptyArray.Instance); - current.nodeChildren.Add(kidPageNode); - } - else - { - var kidChildNode = new PageTreeNode(kidDictionaryToken, kidRef.Data, false, null); - var kidChildren = new List(); - toProcess.Enqueue( - (thisPage: kidChildNode, reference: kidRef.Data, nodeDictionary: kidDictionaryToken, parentReference: current.reference, - nodeChildren: kidChildren)); - - setChildren.Add(() => kidChildNode.WithChildren(kidChildren)); - - current.nodeChildren.Add(kidChildNode); - } - } - } while (toProcess.Count > 0); - - foreach (var action in setChildren) - { - action(); - } - - foreach (var child in firstPage.Children.ToRecursiveOrderList(x=>x.Children).Where(child => child.IsPage)) - { - pageNumber.Increment(); - child.PageNumber = pageNumber.PageCount; - } - - return firstPage; - } - - private static bool CheckIfIsPage(DictionaryToken nodeDictionary, IndirectReference parentReference, bool isRoot, IPdfTokenScanner pdfTokenScanner, bool isLenientParsing) - { - var isPage = false; - - if (!nodeDictionary.TryGet(NameToken.Type, pdfTokenScanner, out NameToken type)) - { - if (!isLenientParsing) { throw new PdfDocumentFormatException($"Node in the document pages tree did not define a type: {nodeDictionary}."); } - - if (!nodeDictionary.TryGet(NameToken.Kids, pdfTokenScanner, out ArrayToken _)) { isPage = true; } - } - else - { - isPage = type.Equals(NameToken.Page); - - if (!isPage && !type.Equals(NameToken.Pages) && !isLenientParsing) { throw new PdfDocumentFormatException($"Node in the document pages tree defined invalid type: {nodeDictionary}."); } - } - - if (!isLenientParsing && !isRoot) - { - if (!nodeDictionary.TryGet(NameToken.Parent, pdfTokenScanner, out IndirectReferenceToken parentReferenceToken)) { throw new PdfDocumentFormatException($"Could not find parent indirect reference token on pages tree node: {nodeDictionary}."); } - - if (!parentReferenceToken.Data.Equals(parentReference)) { throw new PdfDocumentFormatException($"Pages tree node parent reference {parentReferenceToken.Data} did not match actual parent {parentReference}."); } - } - - return isPage; + return new Catalog(dictionary, pages, namedDestinations); } } } diff --git a/src/UglyToad.PdfPig/Parser/PageFactory.cs b/src/UglyToad.PdfPig/Parser/PageFactory.cs index 03467e70..b8ce5337 100644 --- a/src/UglyToad.PdfPig/Parser/PageFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PageFactory.cs @@ -10,6 +10,7 @@ using Graphics; using Graphics.Operations; using Logging; + using Outline; using Parts; using Tokenization.Scanner; using Tokens; @@ -21,20 +22,24 @@ private readonly IResourceStore resourceStore; private readonly ILookupFilterProvider filterProvider; private readonly IPageContentParser pageContentParser; + private readonly ILog log; public PageFactory( IPdfTokenScanner pdfScanner, IResourceStore resourceStore, ILookupFilterProvider filterProvider, - IPageContentParser pageContentParser) + IPageContentParser pageContentParser, + ILog log) { this.resourceStore = resourceStore; this.filterProvider = filterProvider; this.pageContentParser = pageContentParser; this.pdfScanner = pdfScanner; + this.log = log; } - public Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, InternalParsingOptions parsingOptions) + public Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, + NamedDestinations namedDestinations, InternalParsingOptions parsingOptions) { if (dictionary == null) { @@ -48,8 +53,8 @@ parsingOptions.Logger.Error($"Page {number} had its type specified as {type} rather than 'Page'."); } - MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, parsingOptions.Logger); - CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox, parsingOptions.Logger); + MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers); + CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox); var rotation = new PageRotationDegrees(pageTreeMembers.Rotation); if (dictionary.TryGet(NameToken.Rotate, pdfScanner, out NumericToken rotateToken)) @@ -133,11 +138,9 @@ content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions); } - var initialMatrix = ContentStreamProcessor.GetInitialMatrix(userSpaceUnit, mediaBox, cropBox, rotation, parsingOptions.Logger); - - var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content, - new AnnotationProvider(pdfScanner, dictionary, initialMatrix), - pdfScanner); + var initialMatrix = ContentStreamProcessor.GetInitialMatrix(userSpaceUnit, mediaBox, cropBox, rotation, log); + var annotationProvider = new AnnotationProvider(pdfScanner, dictionary, initialMatrix, namedDestinations, log); + var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content, annotationProvider, pdfScanner); for (var i = 0; i < stackDepth; i++) { @@ -187,8 +190,7 @@ private CropBox GetCropBox( DictionaryToken dictionary, PageTreeMembers pageTreeMembers, - MediaBox mediaBox, - ILog log) + MediaBox mediaBox) { CropBox cropBox; if (dictionary.TryGet(NameToken.CropBox, out var cropBoxObject) && @@ -216,23 +218,22 @@ private MediaBox GetMediaBox( int number, DictionaryToken dictionary, - PageTreeMembers pageTreeMembers, - ILog log) + PageTreeMembers pageTreeMembers) { MediaBox mediaBox; - if (dictionary.TryGet(NameToken.MediaBox, out var mediaboxObject) - && DirectObjectFinder.TryGet(mediaboxObject, pdfScanner, out ArrayToken mediaboxArray)) + if (dictionary.TryGet(NameToken.MediaBox, out var mediaBoxObject) + && DirectObjectFinder.TryGet(mediaBoxObject, pdfScanner, out ArrayToken mediaBoxArray)) { - if (mediaboxArray.Length != 4) + if (mediaBoxArray.Length != 4) { - log.Error($"The MediaBox was the wrong length in the dictionary: {dictionary}. Array was: {mediaboxArray}. Defaulting to US Letter."); + log.Error($"The MediaBox was the wrong length in the dictionary: {dictionary}. Array was: {mediaBoxArray}. Defaulting to US Letter."); mediaBox = MediaBox.Letter; return mediaBox; } - mediaBox = new MediaBox(mediaboxArray.ToRectangle(pdfScanner)); + mediaBox = new MediaBox(mediaBoxArray.ToRectangle(pdfScanner)); } else { diff --git a/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs b/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs index 0edc962e..2b962177 100644 --- a/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs @@ -176,14 +176,18 @@ crossReferenceTable.Trailer, parsingOptions.UseLenientParsing); + var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider, + new PageContentParser(new ReflectionGraphicsStateOperationFactory()), parsingOptions.Logger); + var catalog = CatalogFactory.Create( rootReference, rootDictionary, pdfScanner, + pageFactory, + parsingOptions.Logger, parsingOptions.UseLenientParsing); - var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider, - new PageContentParser(new ReflectionGraphicsStateOperationFactory())); + var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider, crossReferenceTable); var bookmarksProvider = new BookmarksProvider(parsingOptions.Logger, pdfScanner); diff --git a/src/UglyToad.PdfPig/PdfDocument.cs b/src/UglyToad.PdfPig/PdfDocument.cs index 2cc59fe9..cf6610f6 100644 --- a/src/UglyToad.PdfPig/PdfDocument.cs +++ b/src/UglyToad.PdfPig/PdfDocument.cs @@ -14,6 +14,8 @@ using Tokenization.Scanner; using Tokens; using Outline; + using Outline.Destinations; + using System.Linq; using Util.JetBrains.Annotations; /// @@ -42,6 +44,7 @@ [NotNull] private readonly Pages pages; + private readonly NamedDestinations namedDestinations; /// /// The metadata associated with this document. @@ -75,13 +78,12 @@ /// public bool IsEncrypted => encryptionDictionary != null; - internal PdfDocument( - IInputBytes inputBytes, - HeaderVersion version, + internal PdfDocument(IInputBytes inputBytes, + HeaderVersion version, CrossReferenceTable crossReferenceTable, IPageFactory pageFactory, Catalog catalog, - DocumentInformation information, + DocumentInformation information, EncryptionDictionary encryptionDictionary, IPdfTokenScanner pdfScanner, ILookupFilterProvider filterProvider, @@ -98,7 +100,8 @@ this.parsingOptions = parsingOptions; Information = information ?? throw new ArgumentNullException(nameof(information)); - pages = new Pages(catalog, pageFactory, pdfScanner); + pages = catalog.Pages; + namedDestinations = catalog.NamedDestinations; Structure = new Structure(catalog, crossReferenceTable, pdfScanner); Advanced = new AdvancedPdfDocumentAccess(pdfScanner, filterProvider, catalog); documentForm = new Lazy(() => acroFormFactory.GetAcroForm(catalog)); @@ -148,7 +151,7 @@ try { - return pages.GetPage(pageNumber, parsingOptions); + return pages.GetPage(pageNumber, namedDestinations, parsingOptions); } catch (Exception ex) { diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index 2472145c..ccb1f792 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -276,7 +276,6 @@ namespace UglyToad.PdfPig.Writer return AddPage(rectangle.Width, rectangle.Height); } - internal IToken CopyToken(IPdfTokenScanner source, IToken token) { if (!existingCopies.TryGetValue(source, out var refs)) @@ -288,15 +287,18 @@ namespace UglyToad.PdfPig.Writer return WriterUtil.CopyToken(context, token, source, refs); } - internal class PageInfo + private class PageInfo { public DictionaryToken Page { get; set; } public IReadOnlyList Parents { get; set; } } + private readonly ConditionalWeakTable> existingCopies = new ConditionalWeakTable>(); + private readonly ConditionalWeakTable> existingTrees = new ConditionalWeakTable>(); + /// /// Add a new page with the specified size, this page will be included in the output when is called. /// @@ -315,7 +317,7 @@ namespace UglyToad.PdfPig.Writer { pagesInfos = new Dictionary(); int i = 1; - foreach (var (pageDict, parents) in WriterUtil.WalkTree(document.Structure.Catalog.PageTree)) + foreach (var (pageDict, parents) in WriterUtil.WalkTree(document.Structure.Catalog.Pages.PageTree)) { pagesInfos[i] = new PageInfo {