mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-21 04:17:57 +08:00
make external nodes different to document nodes and finish reimplementation
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
namespace UglyToad.PdfPig.Tests.Integration
|
namespace UglyToad.PdfPig.Tests.Integration
|
||||||
{
|
{
|
||||||
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class PigReproductionPowerpointTests
|
public class PigReproductionPowerpointTests
|
||||||
@@ -48,6 +49,8 @@
|
|||||||
{
|
{
|
||||||
var foundBookmarks = document.TryGetBookmarks(out var bookmarks);
|
var foundBookmarks = document.TryGetBookmarks(out var bookmarks);
|
||||||
Assert.True(foundBookmarks);
|
Assert.True(foundBookmarks);
|
||||||
|
Assert.Equal(35, bookmarks.Roots.Count);
|
||||||
|
Assert.Equal(35, bookmarks.GetNodes().Count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -202,6 +202,11 @@
|
|||||||
"UglyToad.PdfPig.Logging.ILog",
|
"UglyToad.PdfPig.Logging.ILog",
|
||||||
"UglyToad.PdfPig.Outline.Bookmarks",
|
"UglyToad.PdfPig.Outline.Bookmarks",
|
||||||
"UglyToad.PdfPig.Outline.BookmarkNode",
|
"UglyToad.PdfPig.Outline.BookmarkNode",
|
||||||
|
"UglyToad.PdfPig.Outline.DocumentBookmarkNode",
|
||||||
|
"UglyToad.PdfPig.Outline.ExternalBookmarkNode",
|
||||||
|
"UglyToad.PdfPig.Outline.Destinations.ExplicitDestination",
|
||||||
|
"UglyToad.PdfPig.Outline.Destinations.ExplicitDestinationCoordinates",
|
||||||
|
"UglyToad.PdfPig.Outline.Destinations.ExplicitDestinationType",
|
||||||
"UglyToad.PdfPig.ParsingOptions",
|
"UglyToad.PdfPig.ParsingOptions",
|
||||||
"UglyToad.PdfPig.PdfDocument",
|
"UglyToad.PdfPig.PdfDocument",
|
||||||
"UglyToad.PdfPig.Structure",
|
"UglyToad.PdfPig.Structure",
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
namespace UglyToad.PdfPig.Outline
|
||||||
using UglyToad.PdfPig.Geometry;
|
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.Outline
|
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A node in the <see cref="Bookmarks"/> of a PDF document.
|
/// A node in the <see cref="Bookmarks"/> (also known as outlines) of a PDF document.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BookmarkNode
|
public abstract class BookmarkNode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The text displayed for this node.
|
/// The text displayed for this node.
|
||||||
@@ -16,60 +14,29 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
public string Title { get; }
|
public string Title { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bookmark's coordinates in the pdf page.
|
/// The bookmark's sub-bookmarks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PdfPoint TopLeft { get; }
|
public IReadOnlyList<BookmarkNode> Children { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bookmark's bounding box in the pdf page.
|
/// Whether this node is a leaf node (has no children).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PdfRectangle BoundingBox { get; }
|
public bool IsLeaf { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The node's hierarchical level.
|
/// The node's level in the hierarchy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Level { get; }
|
public int Level { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The page number where the bookmark is located.
|
/// Create a new <see cref="BookmarkNode"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PageNumber { get; }
|
protected BookmarkNode(string title, int level, IReadOnlyList<BookmarkNode> children)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The link to an external source.
|
|
||||||
/// </summary>
|
|
||||||
public string ExternalLink { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if bookmark refers to an external source.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsExternal { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The bookmark's sub-bookmark.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<BookmarkNode> Children { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public BookmarkNode(string title, PdfPoint topLeft, PdfRectangle boundingBox, int level, int pageNumber,
|
|
||||||
string externalLink,
|
|
||||||
bool isExternal,
|
|
||||||
IReadOnlyList<BookmarkNode> children)
|
|
||||||
{
|
{
|
||||||
Title = title;
|
Title = title;
|
||||||
TopLeft = topLeft;
|
|
||||||
BoundingBox = boundingBox;
|
|
||||||
Level = level;
|
Level = level;
|
||||||
PageNumber = pageNumber;
|
|
||||||
ExternalLink = externalLink;
|
|
||||||
IsExternal = isExternal;
|
|
||||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||||
}
|
IsLeaf = children.Count == 0;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "page #" + PageNumber + ", " + Level + ", " + Title;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,34 +7,39 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Bookmarks
|
public class Bookmarks
|
||||||
{
|
{
|
||||||
internal Bookmarks(IReadOnlyList<BookmarkNode> tree)
|
|
||||||
{
|
|
||||||
Tree = tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bookmarks' node tree.
|
/// The roots of the bookmarks' node tree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<BookmarkNode> Tree { get; }
|
public IReadOnlyList<BookmarkNode> Roots { get; }
|
||||||
|
|
||||||
/// <summary>
|
internal Bookmarks(IReadOnlyList<BookmarkNode> roots)
|
||||||
/// Get all nodes as a list.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<BookmarkNode> GetNodes()
|
|
||||||
{
|
{
|
||||||
List<BookmarkNode> nodes = new List<BookmarkNode>();
|
Roots = roots;
|
||||||
GetNodes(Tree, nodes);
|
|
||||||
return nodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetNodes(IReadOnlyList<BookmarkNode> roots, List<BookmarkNode> nodes)
|
/// <summary>
|
||||||
|
/// Get all nodes.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<BookmarkNode> GetNodes()
|
||||||
{
|
{
|
||||||
foreach (var node in roots)
|
foreach (var root in Roots)
|
||||||
{
|
{
|
||||||
nodes.Add(node);
|
foreach (var child in GetNodes(root))
|
||||||
if (node.Children.Count > 0)
|
|
||||||
{
|
{
|
||||||
GetNodes(node.Children, nodes);
|
yield return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<BookmarkNode> GetNodes(BookmarkNode node)
|
||||||
|
{
|
||||||
|
yield return node;
|
||||||
|
|
||||||
|
foreach (var child in node.Children)
|
||||||
|
{
|
||||||
|
foreach (var childNode in GetNodes(child))
|
||||||
|
{
|
||||||
|
yield return childNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,122 +1,16 @@
|
|||||||
using System;
|
namespace UglyToad.PdfPig.Outline
|
||||||
using UglyToad.PdfPig.Geometry;
|
|
||||||
using UglyToad.PdfPig.Logging;
|
|
||||||
using UglyToad.PdfPig.Tokens;
|
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.Outline
|
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content;
|
using Content;
|
||||||
|
using Destinations;
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
|
using Logging;
|
||||||
using Parser.Parts;
|
using Parser.Parts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Tokenization.Scanner;
|
using Tokenization.Scanner;
|
||||||
|
using Tokens;
|
||||||
using Util;
|
using Util;
|
||||||
|
|
||||||
internal class ExplicitDestination
|
|
||||||
{
|
|
||||||
public int? PageNumber { get; }
|
|
||||||
|
|
||||||
public ExplicitDestinationType Type { get; }
|
|
||||||
|
|
||||||
public ExplicitDestinationCoordinates Coordinates { get; }
|
|
||||||
|
|
||||||
public ExplicitDestination(int? pageNumber,
|
|
||||||
ExplicitDestinationType type,
|
|
||||||
ExplicitDestinationCoordinates coordinates)
|
|
||||||
{
|
|
||||||
PageNumber = pageNumber;
|
|
||||||
Type = type;
|
|
||||||
Coordinates = coordinates;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The display type for opening an <see cref="ExplicitDestination"/>.
|
|
||||||
/// </summary>
|
|
||||||
internal enum ExplicitDestinationType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Display the page with the given top left coordinates and
|
|
||||||
/// zoom level.
|
|
||||||
/// </summary>
|
|
||||||
XyzCoordinates = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the entire page within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitPage = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the entire page width within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitHorizontally = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the entire page height within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitVertically = 3,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the rectangle specified by the <see cref="ExplicitDestinationCoordinates"/>
|
|
||||||
/// within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitRectangle = 4,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the page's bounding box within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitBoundingBox = 5,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the page's bounding box width within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitBoundingBoxHorizontally = 6,
|
|
||||||
/// <summary>
|
|
||||||
/// Fit the page's bounding box height within the window.
|
|
||||||
/// </summary>
|
|
||||||
FitBoundingBoxVertically = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The coordinates of the region to display for a <see cref="ExplicitDestination"/>.
|
|
||||||
/// </summary>
|
|
||||||
internal class ExplicitDestinationCoordinates
|
|
||||||
{
|
|
||||||
public static ExplicitDestinationCoordinates Empty { get; } = new ExplicitDestinationCoordinates(null, null, null, null);
|
|
||||||
/// <summary>
|
|
||||||
/// The left side of the region to display.
|
|
||||||
/// </summary>
|
|
||||||
public decimal? Left { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The top edge of the region to display.
|
|
||||||
/// </summary>
|
|
||||||
public decimal? Top { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The right side of the region to display
|
|
||||||
/// </summary>
|
|
||||||
public decimal? Right { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The bottom edge of the region to display.
|
|
||||||
/// </summary>
|
|
||||||
public decimal? Bottom { get; }
|
|
||||||
|
|
||||||
public ExplicitDestinationCoordinates(decimal? left)
|
|
||||||
{
|
|
||||||
Left = left;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExplicitDestinationCoordinates(decimal? left, decimal? top)
|
|
||||||
{
|
|
||||||
Left = left;
|
|
||||||
Top = top;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExplicitDestinationCoordinates(decimal? left, decimal? top, decimal? right, decimal? bottom)
|
|
||||||
{
|
|
||||||
Left = left;
|
|
||||||
Top = top;
|
|
||||||
Right = right;
|
|
||||||
Bottom = bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class BookmarksProvider
|
internal class BookmarksProvider
|
||||||
{
|
{
|
||||||
private readonly ILog log;
|
private readonly ILog log;
|
||||||
@@ -169,9 +63,113 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
next = DirectObjectFinder.Get<DictionaryToken>(nextReference, pdfScanner);
|
next = DirectObjectFinder.Get<DictionaryToken>(nextReference, pdfScanner);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return new Bookmarks(roots);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract bookmarks recursively.
|
||||||
|
/// </summary>
|
||||||
|
private void ReadBookmarksRecursively(DictionaryToken nodeDictionary, int level, bool readSiblings, HashSet<IndirectReference> seen,
|
||||||
|
IReadOnlyDictionary<string, ExplicitDestination> namedDestinations,
|
||||||
|
Catalog catalog,
|
||||||
|
List<BookmarkNode> list)
|
||||||
|
{
|
||||||
|
// 12.3 Document-Level Navigation
|
||||||
|
|
||||||
|
// 12.3.3 Document Outline - Title
|
||||||
|
// (Required) The text that shall be displayed on the screen for this item.
|
||||||
|
if (!nodeDictionary.TryGetOptionalStringDirect(NameToken.Title, pdfScanner, out var title))
|
||||||
|
{
|
||||||
|
throw new PdfDocumentFormatException($"Invalid title for outline (bookmark) node: {nodeDictionary}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = new List<BookmarkNode>();
|
||||||
|
if (nodeDictionary.TryGet(NameToken.First, pdfScanner, out DictionaryToken firstChild))
|
||||||
|
{
|
||||||
|
ReadBookmarksRecursively(firstChild, level + 1, true, seen, namedDestinations, catalog, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
BookmarkNode bookmark;
|
||||||
|
|
||||||
|
if (nodeDictionary.TryGet(NameToken.Dest, pdfScanner, out ArrayToken destArray))
|
||||||
|
{
|
||||||
|
var destination = GetExplicitDestination(destArray, catalog, isLenientParsing, log);
|
||||||
|
|
||||||
|
bookmark = new DocumentBookmarkNode(title, level, destination, children);
|
||||||
|
}
|
||||||
|
else if (nodeDictionary.TryGet(NameToken.Dest, pdfScanner, out IDataToken<string> destStringToken))
|
||||||
|
{
|
||||||
|
// 12.3.2.3 Named Destinations
|
||||||
|
if (namedDestinations.TryGetValue(destStringToken.Data, out var destination))
|
||||||
|
{
|
||||||
|
bookmark = new DocumentBookmarkNode(title, level, destination, children);
|
||||||
|
}
|
||||||
|
else if (!isLenientParsing)
|
||||||
|
{
|
||||||
|
throw new PdfDocumentFormatException($"Invalid destination name for bookmark node: {destStringToken.Data}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nodeDictionary.TryGet(NameToken.A, pdfScanner, out DictionaryToken actionDictionary)
|
||||||
|
&& TryGetAction(actionDictionary, catalog, pdfScanner, isLenientParsing, namedDestinations,
|
||||||
|
log, out var actionResult))
|
||||||
|
{
|
||||||
|
if (actionResult.isExternal)
|
||||||
|
{
|
||||||
|
bookmark = new ExternalBookmarkNode(title, level, actionResult.externalFileName, children);
|
||||||
|
}
|
||||||
|
else if (actionResult.destination != null)
|
||||||
|
{
|
||||||
|
bookmark = new DocumentBookmarkNode(title, level, actionResult.destination, children);
|
||||||
|
}
|
||||||
|
else if (!isLenientParsing)
|
||||||
|
{
|
||||||
|
throw new PdfDocumentFormatException($"Invalid action for bookmark node: {actionDictionary}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.Error($"No /Dest(ination) or /A(ction) entry found for bookmark node: {nodeDictionary}.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(bookmark);
|
||||||
|
|
||||||
|
if (!readSiblings)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk all siblings if this was the first child.
|
||||||
|
var current = nodeDictionary;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!current.TryGet(NameToken.Next, out IndirectReferenceToken nextReference)
|
||||||
|
|| !seen.Add(nextReference.Data))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = DirectObjectFinder.Get<DictionaryToken>(nextReference, pdfScanner);
|
||||||
|
|
||||||
|
if (current == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadBookmarksRecursively(current, level, false, seen, namedDestinations, catalog, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Named Destinations
|
||||||
private static IReadOnlyDictionary<string, ExplicitDestination> ReadNamedDestinations(Catalog catalog, IPdfTokenScanner pdfScanner,
|
private static IReadOnlyDictionary<string, ExplicitDestination> ReadNamedDestinations(Catalog catalog, IPdfTokenScanner pdfScanner,
|
||||||
bool isLenientParsing, ILog log)
|
bool isLenientParsing, ILog log)
|
||||||
{
|
{
|
||||||
@@ -222,9 +220,7 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
{
|
{
|
||||||
for (var i = 0; i < nodeNames.Length; i += 2)
|
for (var i = 0; i < nodeNames.Length; i += 2)
|
||||||
{
|
{
|
||||||
var key = nodeNames[i] as IDataToken<string>;
|
if (!(nodeNames[i] is IDataToken<string> key))
|
||||||
|
|
||||||
if (key == null)
|
|
||||||
{
|
{
|
||||||
if (isLenientParsing)
|
if (isLenientParsing)
|
||||||
{
|
{
|
||||||
@@ -283,106 +279,6 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extract bookmarks recursively.
|
|
||||||
/// </summary>
|
|
||||||
private void ReadBookmarksRecursively(DictionaryToken nodeDictionary, int level, bool readSiblings, HashSet<IndirectReference> seen,
|
|
||||||
IReadOnlyDictionary<string, ExplicitDestination> namedDestinations,
|
|
||||||
Catalog catalog,
|
|
||||||
List<BookmarkNode> list)
|
|
||||||
{
|
|
||||||
// 12.3 Document-Level Navigation
|
|
||||||
|
|
||||||
// 12.3.3 Document Outline - Title
|
|
||||||
// (Required) The text that shall be displayed on the screen for this item.
|
|
||||||
if (!nodeDictionary.TryGetOptionalStringDirect(NameToken.Title, pdfScanner, out var title))
|
|
||||||
{
|
|
||||||
throw new PdfDocumentFormatException($"Invalid title for outline (bookmark) node: {nodeDictionary}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeDictionary.TryGet(NameToken.Dest, pdfScanner, out ArrayToken destArray))
|
|
||||||
{
|
|
||||||
var desti = GetExplicitDestination(destArray, catalog, isLenientParsing, log);
|
|
||||||
}
|
|
||||||
else if (nodeDictionary.TryGet(NameToken.Dest, pdfScanner, out IDataToken<string> destStringToken))
|
|
||||||
{
|
|
||||||
// 12.3.2.3 Named Destinations
|
|
||||||
if (namedDestinations.TryGetValue(destStringToken.Data, out var destination))
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (!isLenientParsing)
|
|
||||||
{
|
|
||||||
throw new PdfDocumentFormatException($"Invalid destination name for bookmark node: {destStringToken.Data}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var children = new List<BookmarkNode>();
|
|
||||||
if (nodeDictionary.TryGet(NameToken.First, pdfScanner, out DictionaryToken firstChild))
|
|
||||||
{
|
|
||||||
ReadBookmarksRecursively(firstChild, level + 1, true, seen, namedDestinations, catalog, children);
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(new BookmarkNode(title, PdfPoint.Origin, new PdfRectangle(), level, 1, string.Empty, false, children));
|
|
||||||
|
|
||||||
if (!readSiblings)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk all siblings if this was the first child.
|
|
||||||
var current = nodeDictionary;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (!current.TryGet(NameToken.Next, out IndirectReferenceToken nextReference)
|
|
||||||
|| !seen.Add(nextReference.Data))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = DirectObjectFinder.Get<DictionaryToken>(nextReference, pdfScanner);
|
|
||||||
|
|
||||||
if (current == null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadBookmarksRecursively(current, level, false, seen, namedDestinations, catalog, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
//// 12.3.2 Destinations
|
|
||||||
//if (nodeDictionary.TryGet(NameToken.Dest, out ArrayToken destToken))
|
|
||||||
//{
|
|
||||||
// // 12.3.2.2 Explicit Destinations
|
|
||||||
// GetDestination(destToken, newNode);
|
|
||||||
//}
|
|
||||||
//else if (dictionary.TryGet(NameToken.Dest, out IDataToken<string> destStringToken))
|
|
||||||
//{
|
|
||||||
// // 12.3.2.3 Named Destinations
|
|
||||||
// GetNamedDestination(destStringToken, ref newNode);
|
|
||||||
//}
|
|
||||||
//else if (dictionary.TryGet(NameToken.A, out IToken actionToken))
|
|
||||||
//{
|
|
||||||
// // 12.6 Actions
|
|
||||||
// GetActions(actionToken, ref newNode);
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// log.Error("BookmarksProvider.RecursiveBookmark(): No 'Dest' or 'Action' token found.");
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static int ParsePageNumber(string goToStr)
|
|
||||||
{
|
|
||||||
if (int.TryParse(System.Text.RegularExpressions.Regex.Match(goToStr, "[0-9]+").Value, out int number))
|
|
||||||
{
|
|
||||||
return number + 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//#region Destinations
|
|
||||||
private static ExplicitDestination GetExplicitDestination(ArrayToken explicitDestinationArray, Catalog catalog,
|
private static ExplicitDestination GetExplicitDestination(ArrayToken explicitDestinationArray, Catalog catalog,
|
||||||
bool isLenientParsing,
|
bool isLenientParsing,
|
||||||
ILog log)
|
ILog log)
|
||||||
@@ -397,7 +293,7 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
throw new ArgumentException("Invalid (empty) array for an explicit destination.", nameof(explicitDestinationArray));
|
throw new ArgumentException("Invalid (empty) array for an explicit destination.", nameof(explicitDestinationArray));
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageNumber = default(int?);
|
var pageNumber = 1;
|
||||||
|
|
||||||
var pageToken = explicitDestinationArray[0];
|
var pageToken = explicitDestinationArray[0];
|
||||||
if (pageToken is IndirectReferenceToken pageIndirectReferenceToken)
|
if (pageToken is IndirectReferenceToken pageIndirectReferenceToken)
|
||||||
@@ -501,249 +397,52 @@ namespace UglyToad.PdfPig.Outline
|
|||||||
|
|
||||||
throw new PdfDocumentFormatException($"Unknown explicit destination type: {destTypeToken}.");
|
throw new PdfDocumentFormatException($"Unknown explicit destination type: {destTypeToken}.");
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
//private void GetNamedDestination(IDataToken<string> destStringToken, ref BookmarkNode currentNode)
|
private static bool TryGetAction(DictionaryToken actionDictionary, Catalog catalog, IPdfTokenScanner pdfScanner,
|
||||||
//{
|
bool isLenientParsing,
|
||||||
// if (destStringToken == null)
|
IReadOnlyDictionary<string, ExplicitDestination> namedDestinations,
|
||||||
// {
|
ILog log,
|
||||||
// throw new ArgumentNullException(nameof(destStringToken), "BookmarksProvider.GetNamedDestination()");
|
out (bool isExternal, string externalFileName, ExplicitDestination destination) result)
|
||||||
// }
|
{
|
||||||
|
result = (false, null, null);
|
||||||
|
|
||||||
// // 12.3.2.3 Named Destinations
|
if (!actionDictionary.TryGet(NameToken.S, pdfScanner, out NameToken actionType))
|
||||||
// if (structure.Catalog.CatalogDictionary.TryGet(NameToken.Dests, out IndirectReferenceToken destsToken11))
|
{
|
||||||
// {
|
throw new PdfDocumentFormatException($"No action type (/S) specified for action: {actionDictionary}.");
|
||||||
// // In PDF 1.1, the correspondence between name objects and destinations shall be defined by the
|
}
|
||||||
// // Dests entry in the document catalogue (see 7.7.2, “Document Catalog”). The value of this entry
|
|
||||||
// // shall be a dictionary in which each key is a destination name and the corresponding value is
|
|
||||||
// // either an array defining the destination, using the syntax shown in Table 151, or a dictionary
|
|
||||||
// // with a D entry whose value is such an array.
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.GetNamedDestination(): PDF 1.1.");
|
|
||||||
// }
|
|
||||||
// else if (structure.Catalog.CatalogDictionary.TryGet(NameToken.Names, out IndirectReferenceToken namesToken))
|
|
||||||
// {
|
|
||||||
// // In PDF 1.2 and later, the correspondence between strings and destinations may alternatively be
|
|
||||||
// // defined by the Dests entry in the document’s name dictionary (see 7.7.4, “Name Dictionary”).
|
|
||||||
// // The value of this entry shall be a name tree (7.9.6, “Name Trees”) 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, as described in the preceding paragraph.
|
|
||||||
// var namesDictionary = structure.GetObject(namesToken.Data).Data as DictionaryToken;
|
|
||||||
// if (namesDictionary == null)
|
|
||||||
// {
|
|
||||||
// throw new ArgumentNullException(nameof(namesDictionary), "BookmarksProvider.GetNamedDestination()");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (namesDictionary.TryGet(NameToken.Dests, out IndirectReferenceToken destsToken))
|
if (actionType.Equals(NameToken.GoTo))
|
||||||
// {
|
{
|
||||||
// var destsDictionary = structure.GetObject(destsToken.Data).Data as DictionaryToken;
|
if (actionDictionary.TryGet(NameToken.D, pdfScanner, out ArrayToken destinationArray))
|
||||||
// if (destsDictionary == null)
|
{
|
||||||
// {
|
var destination = GetExplicitDestination(destinationArray, catalog, isLenientParsing, log);
|
||||||
// throw new ArgumentNullException(nameof(destsDictionary), "BookmarksProvider.GetNamedDestination()");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// IToken found = FindInNameTree(destStringToken, destsDictionary);
|
result = (false, null, destination);
|
||||||
// if (found != null)
|
|
||||||
// {
|
|
||||||
// ArrayToken destToken = null;
|
|
||||||
// if (found is IndirectReferenceToken indirect)
|
|
||||||
// {
|
|
||||||
// var pageObject = structure.GetObject(indirect.Data);
|
|
||||||
// if (pageObject.Data is DictionaryToken dictionaryToken)
|
|
||||||
// {
|
|
||||||
// if (!dictionaryToken.TryGet(NameToken.D, out destToken))
|
|
||||||
// {
|
|
||||||
// throw new ArgumentException("BookmarksProvider.GetNamedDestination(): Cannot find token 'D'.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (pageObject.Data is ArrayToken arrayToken)
|
|
||||||
// {
|
|
||||||
// destToken = arrayToken;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.GetNamedDestination(): Token type '" + pageObject.Data + "'.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (found is ArrayToken arrayToken)
|
|
||||||
// {
|
|
||||||
// destToken = arrayToken;
|
|
||||||
// }
|
|
||||||
// else if (found is DictionaryToken)
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.GetNamedDestination(): Token type 'DictionaryToken'.");
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.GetNamedDestination(): Token type '" + found.GetType() + "'.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var pageNumber = structure.Catalog.GetPageByReference(((IndirectReferenceToken)destToken[0]).Data).PageNumber;
|
return true;
|
||||||
// if (pageNumber.HasValue)
|
}
|
||||||
// {
|
else if (actionDictionary.TryGet(NameToken.D, pdfScanner, out IDataToken<string> destinationName)
|
||||||
// currentNode.PageNumber = pageNumber.Value;
|
&& namedDestinations.TryGetValue(destinationName.Data, out var destination))
|
||||||
// }
|
{
|
||||||
// GetDestination(destToken, currentNode);
|
result = (false, null, destination);
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private IToken FindInNameTree<T>(T find, DictionaryToken dictionaryToken) where T : IDataToken<string>
|
return true;
|
||||||
//{
|
}
|
||||||
// // 7.9.6 Name Trees
|
}
|
||||||
// // Intermediate node
|
else if (actionType.Equals(NameToken.GoToR))
|
||||||
// if (dictionaryToken.TryGet(NameToken.Kids, out ArrayToken kidsToken))
|
{
|
||||||
// {
|
if (actionDictionary.TryGetOptionalStringDirect(NameToken.F, pdfScanner, out var filename))
|
||||||
// foreach (var kid in kidsToken.Data)
|
{
|
||||||
// {
|
result = (true, filename, null);
|
||||||
// var dictionary = structure.GetObject(((IndirectReferenceToken)kid).Data).Data as DictionaryToken;
|
return true;
|
||||||
// if (dictionary != null && dictionary.TryGet(NameToken.Limits, out ArrayToken limits))
|
}
|
||||||
// {
|
|
||||||
// // (Intermediate and leaf nodes only; required) Shall be an array of two strings,
|
|
||||||
// // that shall specify the (lexically) least and greatest keys included in the
|
|
||||||
// // Names array of a leaf node or in the Names arrays of any leaf nodes that are
|
|
||||||
// // descendants of an intermediate node.
|
|
||||||
// var least = limits[0] as IDataToken<string>;
|
|
||||||
// var greatest = limits[1] as IDataToken<string>;
|
|
||||||
|
|
||||||
// if (IsStringBetween(find.Data, least.Data, greatest.Data))
|
result = (true, string.Empty, null);
|
||||||
// {
|
return true;
|
||||||
// var indRef = FindInNameTree(find, dictionary);
|
}
|
||||||
// if (indRef != null)
|
|
||||||
// {
|
|
||||||
// return indRef;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new ArgumentException("BookmarksProvider.FindNamedDestination(): Did no find the key '" + find.Data + "' in Name Tree.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// // Leaf node
|
|
||||||
// if (dictionaryToken.TryGet(NameToken.Names, out ArrayToken names))
|
|
||||||
// {
|
|
||||||
// // Names
|
|
||||||
// // Shall be an array of the form [key_1, value_1, key_2, value_2, …, key_n, value_n]
|
|
||||||
// // where each key_i shall be a string and the corresponding value_i shall be the object
|
|
||||||
// // associated with that key. The keys shall be sorted in lexical order, as described below.
|
|
||||||
// for (int i = 0; i < names.Length; i += 2)
|
|
||||||
// {
|
|
||||||
// if (names[i] is IDataToken<string> n && n.Data.Equals(find.Data))
|
|
||||||
// {
|
|
||||||
// return names[i + 1];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new ArgumentNullException("BookmarksProvider.FindNamedDestination(): Could not find ArrayToken 'Names' in dictionary.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// throw new ArgumentException("BookmarksProvider.FindNamedDestination(): Did no find the key '" + find.Data + "' in Name Tree.");
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private bool IsStringBetween(string str, string least, string greatest)
|
return false;
|
||||||
//{
|
}
|
||||||
// return (string.Compare(str, least, StringComparison.Ordinal) >= 0 &&
|
|
||||||
// string.Compare(str, greatest, StringComparison.Ordinal) <= 0);
|
|
||||||
//}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Actions
|
|
||||||
//private void GetActions(IToken actionToken, ref BookmarkNode currentNode)
|
|
||||||
//{
|
|
||||||
// if (actionToken is DictionaryToken dictionaryToken)
|
|
||||||
// {
|
|
||||||
// if (dictionaryToken.TryGet(NameToken.S, out NameToken sToken))
|
|
||||||
// {
|
|
||||||
// if (sToken.Equals(NameToken.GoTo)) // 12.6.4.2, Go-To Actions
|
|
||||||
// {
|
|
||||||
// if (dictionaryToken.TryGet(NameToken.D, out IToken goToToken))
|
|
||||||
// {
|
|
||||||
// HandleGoToAction(goToToken, ref currentNode);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new ArgumentException("BookmarksProvider.GetActions(): Could not find token 'D' in 'GoTo'.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (sToken.Equals(NameToken.GoToR)) // 12.6.4.3, Remote Go-To Actions
|
|
||||||
// {
|
|
||||||
// if (dictionaryToken.TryGet(NameToken.D, out IToken goToRToken))
|
|
||||||
// {
|
|
||||||
// if (dictionaryToken.TryGet(NameToken.F, out IToken remoteFileToken))
|
|
||||||
// {
|
|
||||||
// currentNode.ExternalLink = GetString(NameToken.F, remoteFileToken);
|
|
||||||
// }
|
|
||||||
// HandleGoToRAction(goToRToken, ref currentNode);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new ArgumentException("BookmarksProvider.GetActions(): Could not find token 'D' in 'GoToR'.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// currentNode.IsExternal = true;
|
|
||||||
// log.Debug("BookmarksProvider.GetActions(): Ignoring unknown token '" + sToken.Data + "'.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new ArgumentException("BookmarksProvider.GetActions(): Could not find token 'S' in 'Action'.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (actionToken is IndirectReferenceToken indirectReferenceToken)
|
|
||||||
// {
|
|
||||||
// var tempToken = structure.GetObject(indirectReferenceToken.Data).Data;
|
|
||||||
// if (tempToken is DictionaryToken dictionaryAction)
|
|
||||||
// {
|
|
||||||
// GetActions(dictionaryAction, ref currentNode);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.GetActions(): " + nameof(tempToken) + " of type " + tempToken.GetType() + ".");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.GetActions(): " + nameof(actionToken) + " of type " + actionToken.GetType() + ".");
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private void HandleGoToRAction(IToken goToRToken, ref BookmarkNode currentNode)
|
|
||||||
//{
|
|
||||||
// currentNode.IsExternal = true;
|
|
||||||
// HandleGoToAction(goToRToken, ref currentNode);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private void HandleGoToAction(IToken goToToken, ref BookmarkNode currentNode)
|
|
||||||
//{
|
|
||||||
// if (goToToken is ArrayToken arrayToken)
|
|
||||||
// {
|
|
||||||
// GetDestination(arrayToken, currentNode);
|
|
||||||
// }
|
|
||||||
// else if (goToToken is IDataToken<string> stringToken)
|
|
||||||
// {
|
|
||||||
// GetNamedDestination(stringToken, ref currentNode);
|
|
||||||
// if (currentNode.PageNumber == 0)
|
|
||||||
// {
|
|
||||||
// currentNode.PageNumber = ParsePageNumber(stringToken.Data);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (goToToken is IndirectReferenceToken indirectReferenceToken)
|
|
||||||
// {
|
|
||||||
// HandleGoToAction(structure.GetObject(indirectReferenceToken.Data).Data, ref currentNode);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException("BookmarksProvider.HandleGoToAction(): " + nameof(goToToken) + " of type " + goToToken.GetType());
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
namespace UglyToad.PdfPig.Outline.Destinations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A destination location in the same file.
|
||||||
|
/// </summary>
|
||||||
|
public class ExplicitDestination
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The page number of the destination.
|
||||||
|
/// </summary>
|
||||||
|
public int PageNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The display type of the destination.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestinationType Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The display coordinates of the destination.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestinationCoordinates Coordinates { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ExplicitDestination"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestination(int pageNumber, ExplicitDestinationType type, ExplicitDestinationCoordinates coordinates)
|
||||||
|
{
|
||||||
|
PageNumber = pageNumber;
|
||||||
|
Type = type;
|
||||||
|
Coordinates = coordinates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
namespace UglyToad.PdfPig.Outline.Destinations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The coordinates of the region to display for a <see cref="ExplicitDestination"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ExplicitDestinationCoordinates
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty set of coordinates where no values have been set.
|
||||||
|
/// </summary>
|
||||||
|
public static ExplicitDestinationCoordinates Empty { get; } = new ExplicitDestinationCoordinates(null, null, null, null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The left side of the region to display.
|
||||||
|
/// </summary>
|
||||||
|
public decimal? Left { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The top edge of the region to display.
|
||||||
|
/// </summary>
|
||||||
|
public decimal? Top { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The right side of the region to display
|
||||||
|
/// </summary>
|
||||||
|
public decimal? Right { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom edge of the region to display.
|
||||||
|
/// </summary>
|
||||||
|
public decimal? Bottom { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ExplicitDestinationCoordinates"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestinationCoordinates(decimal? left)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ExplicitDestinationCoordinates"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestinationCoordinates(decimal? left, decimal? top)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Top = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ExplicitDestinationCoordinates"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestinationCoordinates(decimal? left, decimal? top, decimal? right, decimal? bottom)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Top = top;
|
||||||
|
Right = right;
|
||||||
|
Bottom = bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
namespace UglyToad.PdfPig.Outline.Destinations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The display type for opening an <see cref="ExplicitDestination"/>.
|
||||||
|
/// </summary>
|
||||||
|
public enum ExplicitDestinationType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Display the page with the given top left coordinates and
|
||||||
|
/// zoom level.
|
||||||
|
/// </summary>
|
||||||
|
XyzCoordinates = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the entire page within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitPage = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the entire page width within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitHorizontally = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the entire page height within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitVertically = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the rectangle specified by the <see cref="ExplicitDestinationCoordinates"/>
|
||||||
|
/// within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitRectangle = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the page's bounding box within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitBoundingBox = 5,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the page's bounding box width within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitBoundingBoxHorizontally = 6,
|
||||||
|
/// <summary>
|
||||||
|
/// Fit the page's bounding box height within the window.
|
||||||
|
/// </summary>
|
||||||
|
FitBoundingBoxVertically = 7
|
||||||
|
}
|
||||||
|
}
|
41
src/UglyToad.PdfPig/Outline/DocumentBookmarkNode.cs
Normal file
41
src/UglyToad.PdfPig/Outline/DocumentBookmarkNode.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
namespace UglyToad.PdfPig.Outline
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Destinations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// A node in the <see cref="Bookmarks" /> of a PDF document which corresponds
|
||||||
|
/// to a location in the current document.
|
||||||
|
/// </summary>
|
||||||
|
public class DocumentBookmarkNode : BookmarkNode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The page number where the bookmark is located.
|
||||||
|
/// </summary>
|
||||||
|
public int PageNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The destination of the bookmark in the current document.
|
||||||
|
/// </summary>
|
||||||
|
public ExplicitDestination Destination { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="DocumentBookmarkNode"/>.
|
||||||
|
/// </summary>
|
||||||
|
public DocumentBookmarkNode(string title, int level, ExplicitDestination destination, IReadOnlyList<BookmarkNode> children)
|
||||||
|
: base(title, level, children)
|
||||||
|
{
|
||||||
|
Destination = destination ?? throw new ArgumentNullException(nameof(destination));
|
||||||
|
PageNumber = destination.PageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"page #{PageNumber}, {Level}, {Title}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/UglyToad.PdfPig/Outline/ExternalBookmarkNode.cs
Normal file
33
src/UglyToad.PdfPig/Outline/ExternalBookmarkNode.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace UglyToad.PdfPig.Outline
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// A node in the <see cref="Bookmarks" /> of a PDF document which corresponds
|
||||||
|
/// to a location in an external file.
|
||||||
|
/// </summary>
|
||||||
|
public class ExternalBookmarkNode : BookmarkNode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the file containing this bookmark.
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ExternalBookmarkNode" />.
|
||||||
|
/// </summary>
|
||||||
|
public ExternalBookmarkNode(string title, int level, string fileName, IReadOnlyList<BookmarkNode> children) : base(title, level, children)
|
||||||
|
{
|
||||||
|
FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"file '{FileName}', {Level}, {Title}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user