make external nodes different to document nodes and finish reimplementation

This commit is contained in:
Eliot Jones
2019-12-05 13:21:19 +00:00
parent ecf0b8743b
commit 2e5c995322
10 changed files with 408 additions and 518 deletions

View File

@@ -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());
} }
} }
} }

View File

@@ -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",

View File

@@ -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;
} }
} }
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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 documents 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
} }
} }

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View 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}";
}
}
}

View 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}";
}
}
}