finish first pass of annotation api

This commit is contained in:
Eliot Jones
2018-12-22 15:54:32 +00:00
parent a5349dd77a
commit d572af8a52
9 changed files with 446 additions and 300 deletions

View File

@@ -21,7 +21,9 @@
{
for (var i = 0; i < document.NumberOfPages; i++)
{
document.GetPage(i + 1);
var page = document.GetPage(i + 1);
Assert.NotNull(page.GetAnnotations().ToList());
}
}
}

View File

@@ -28,6 +28,10 @@
"UglyToad.PdfPig.PdfDocument",
"UglyToad.PdfPig.ParsingOptions",
"UglyToad.PdfPig.Structure",
"UglyToad.PdfPig.Annotations.Annotation",
"UglyToad.PdfPig.Annotations.AnnotationType",
"UglyToad.PdfPig.Annotations.AnnotationBorder",
"UglyToad.PdfPig.Annotations.AnnotationFlags",
"UglyToad.PdfPig.Logging.ILog",
"UglyToad.PdfPig.Geometry.PdfPoint",
"UglyToad.PdfPig.Geometry.PdfRectangle",

View File

@@ -0,0 +1,79 @@
namespace UglyToad.PdfPig.Annotations
{
using System;
using Geometry;
using Tokens;
using Util.JetBrains.Annotations;
/// <summary>
/// An annotation on a page in a PDF document.
/// </summary>
public class Annotation
{
/// <summary>
/// The underlying PDF dictionary which this annotation was created from.
/// </summary>
[NotNull]
public DictionaryToken AnnotationDictionary { get; }
/// <summary>
/// The type of this annotation.
/// </summary>
public AnnotationType Type { get; }
/// <summary>
/// The rectangle in user space units specifying the location to place this annotation on the page.
/// </summary>
public PdfRectangle Rectangle { get; }
/// <summary>
/// The annotation text, or if the annotation does not display text, a description of the annotation's contents. Optional.
/// </summary>
[CanBeNull]
public string Content { get; }
/// <summary>
/// The name of this annotation which should be unique per page. Optional.
/// </summary>
[CanBeNull]
public string Name { get; }
/// <summary>
/// The date and time the annotation was last modified, can be in any format. Optional.
/// </summary>
[CanBeNull]
public string ModifiedDate { get; }
/// <summary>
/// Flags defining the appearance and behaviour of this annotation.
/// </summary>
public AnnotationFlags Flags { get; }
/// <summary>
/// Defines the annotation's border.
/// </summary>
public AnnotationBorder Border { get; }
/// <summary>
/// Create a new <see cref="Annotation"/>.
/// </summary>
public Annotation(DictionaryToken annotationDictionary, AnnotationType type, PdfRectangle rectangle, string content, string name, string modifiedDate,
AnnotationFlags flags, AnnotationBorder border)
{
AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary));
Type = type;
Rectangle = rectangle;
Content = content;
Name = name;
ModifiedDate = modifiedDate;
Flags = flags;
Border = border;
}
/// <inheritdoc />
public override string ToString()
{
return $"{Type} - {Content}";
}
}
}

View File

@@ -0,0 +1,54 @@
namespace UglyToad.PdfPig.Annotations
{
using System.Collections.Generic;
using Util.JetBrains.Annotations;
/// <summary>
/// A border for a PDF <see cref="Annotation"/> object.
/// </summary>
public class AnnotationBorder
{
/// <summary>
/// The default border style if not specified.
/// </summary>
public static AnnotationBorder Default { get; } = new AnnotationBorder(0, 0, 1, null);
/// <summary>
/// The horizontal corner radius in user space units.
/// </summary>
public decimal HorizontalCornerRadius { get; }
/// <summary>
/// The vertical corner radius in user space units.
/// </summary>
public decimal VerticalCornerRadius { get; }
/// <summary>
/// The width of the border in user space units.
/// </summary>
public decimal BorderWidth { get; }
/// <summary>
/// The dash pattern for the border lines if provided. Optional.
/// </summary>
[CanBeNull]
public IReadOnlyList<decimal> LineDashPattern { get; }
/// <summary>
/// Create a new <see cref="AnnotationBorder"/>.
/// </summary>
public AnnotationBorder(decimal horizontalCornerRadius, decimal verticalCornerRadius, decimal borderWidth, IReadOnlyList<decimal> lineDashPattern)
{
HorizontalCornerRadius = horizontalCornerRadius;
VerticalCornerRadius = verticalCornerRadius;
BorderWidth = borderWidth;
LineDashPattern = lineDashPattern;
}
/// <inheritdoc />
public override string ToString()
{
return $"{HorizontalCornerRadius} {VerticalCornerRadius} {BorderWidth}";
}
}
}

View File

@@ -0,0 +1,137 @@
namespace UglyToad.PdfPig.Annotations
{
using Tokens;
internal static class AnnotationExtensions
{
public static AnnotationType ToAnnotationType(this NameToken name)
{
if (name.Data == NameToken.Text.Data)
{
return AnnotationType.Text;
}
if (name.Data == NameToken.Link.Data)
{
return AnnotationType.Link;
}
if (name.Data == NameToken.FreeText.Data)
{
return AnnotationType.FreeText;
}
if (name.Data == NameToken.Line.Data)
{
return AnnotationType.Line;
}
if (name.Data == NameToken.Square.Data)
{
return AnnotationType.Square;
}
if (name.Data == NameToken.Circle.Data)
{
return AnnotationType.Circle;
}
if (name.Data == NameToken.Polygon.Data)
{
return AnnotationType.Polygon;
}
if (name.Data == NameToken.PolyLine.Data)
{
return AnnotationType.PolyLine;
}
if (name.Data == NameToken.Highlight.Data)
{
return AnnotationType.Highlight;
}
if (name.Data == NameToken.Underline.Data)
{
return AnnotationType.Underline;
}
if (name.Data == NameToken.Squiggly.Data)
{
return AnnotationType.Squiggly;
}
if (name.Data == NameToken.StrikeOut.Data)
{
return AnnotationType.StrikeOut;
}
if (name.Data == NameToken.Stamp.Data)
{
return AnnotationType.Stamp;
}
if (name.Data == NameToken.Caret.Data)
{
return AnnotationType.Caret;
}
if (name.Data == NameToken.Ink.Data)
{
return AnnotationType.Ink;
}
if (name.Data == NameToken.Popup.Data)
{
return AnnotationType.Popup;
}
if (name.Data == NameToken.FileAttachment.Data)
{
return AnnotationType.FileAttachment;
}
if (name.Data == NameToken.Sound.Data)
{
return AnnotationType.Sound;
}
if (name.Data == NameToken.Movie.Data)
{
return AnnotationType.Movie;
}
if (name.Data == NameToken.Widget.Data)
{
return AnnotationType.Widget;
}
if (name.Data == NameToken.Screen.Data)
{
return AnnotationType.Screen;
}
if (name.Data == NameToken.PrinterMark.Data)
{
return AnnotationType.PrinterMark;
}
if (name.Data == NameToken.TrapNet.Data)
{
return AnnotationType.TrapNet;
}
if (name.Data == NameToken.Watermark.Data)
{
return AnnotationType.Watermark;
}
if (name.Data == NameToken.Annotation3D.Data)
{
return AnnotationType.Artwork3D;
}
return AnnotationType.Other;
}
}
}

View File

@@ -6,7 +6,7 @@
/// Specifies characteristics of an annotation in a PDF or FDF document.
/// </summary>
[Flags]
internal enum AnnotationFlags
public enum AnnotationFlags
{
/// <summary>
/// Do not display the annotation if it is not one of the standard annotation types.

View File

@@ -2,13 +2,12 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using Exceptions;
using Geometry;
using Parser.Parts;
using Tokenization.Scanner;
using Tokens;
using Util;
using Util.JetBrains.Annotations;
internal class AnnotationProvider
{
@@ -56,305 +55,57 @@
var annotationType = type.ToAnnotationType();
var rectangle = annotationDictionary.Get<ArrayToken>(NameToken.Rect, tokenScanner).ToRectangle();
string content = null;
if (annotationDictionary.TryGet(NameToken.Contents, out var contentToken) && DirectObjectFinder.TryGet(contentToken, tokenScanner, out StringToken contentString))
var contents = GetNamedString(NameToken.Contents, annotationDictionary);
var name = GetNamedString(NameToken.Nm, annotationDictionary);
var modifiedDate = GetNamedString(NameToken.M, annotationDictionary);
var flags = (AnnotationFlags) 0;
if (annotationDictionary.TryGet(NameToken.F, out var flagsToken) && DirectObjectFinder.TryGet(flagsToken, tokenScanner, out NumericToken flagsNumericToken))
{
flags = (AnnotationFlags) flagsNumericToken.Int;
}
var border = AnnotationBorder.Default;
if (annotationDictionary.TryGet(NameToken.Border, out var borderToken) && DirectObjectFinder.TryGet(borderToken, tokenScanner, out ArrayToken borderArray)
&& borderArray.Length >= 3)
{
var horizontal = borderArray.GetNumeric(0).Data;
var vertical = borderArray.GetNumeric(1).Data;
var width = borderArray.GetNumeric(2).Data;
var dashes = default(IReadOnlyList<decimal>);
if (borderArray.Length == 4 && borderArray.Data[4] is ArrayToken dashArray)
{
dashes = dashArray.Data.OfType<NumericToken>().Select(x => x.Data).ToList();
}
border = new AnnotationBorder(horizontal, vertical, width, dashes);
}
yield return new Annotation(annotationDictionary, annotationType, rectangle, contents, name, modifiedDate, flags, border);
}
}
private string GetNamedString(NameToken name, DictionaryToken dictionary)
{
string content = null;
if (dictionary.TryGet(name, out var contentToken))
{
if (contentToken is StringToken contentString)
{
content = contentString.Data;
}
yield return new Annotation(annotationDictionary, rectangle, content, null, null);
else if (contentToken is HexToken contentHex)
{
content = contentHex.Data;
}
else if (DirectObjectFinder.TryGet(contentToken, tokenScanner, out StringToken indirectContentString))
{
content = indirectContentString.Data;
}
}
return content;
}
}
internal class Annotation
{
/// <summary>
/// The underlying PDF dictionary which this annotation was created from.
/// </summary>
[NotNull]
public DictionaryToken AnnotationDictionary { get; }
/// <summary>
/// The rectangle in user space units specifying the location to place this annotation on the page.
/// </summary>
public PdfRectangle Rectangle { get; }
/// <summary>
/// The annotation text, or if the annotation does not display text, a description of the annotation's contents. Optional.
/// </summary>
[CanBeNull]
public string Content { get; }
/// <summary>
/// The name of this annotation which should be unique per page. Optional.
/// </summary>
[CanBeNull]
public string Name { get; }
/// <summary>
/// The date and time the annotation was last modified, can be in any format. Optional.
/// </summary>
[CanBeNull]
public string ModifiedDate { get; }
public AnnotationFlags Flags { get; } = (AnnotationFlags)0;
/// <summary>
/// Create a new <see cref="Annotation"/>.
/// </summary>
public Annotation(DictionaryToken annotationDictionary, PdfRectangle rectangle, string content, string name, string modifiedDate)
{
AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary));
Rectangle = rectangle;
Content = content;
Name = name;
ModifiedDate = modifiedDate;
}
}
/// <summary>
/// The standard annotation types in PDF documents.
/// </summary>
internal enum AnnotationType
{
/// <summary>
/// A 'sticky note' style annotation displaying some text with open/closed pop-up state.
/// </summary>
Text = 0,
/// <summary>
/// A link to elsewhere in the document or an external application/web link.
/// </summary>
Link = 1,
/// <summary>
/// Displays text on the page. Unlike <see cref="Text"/> there is no associated pop-up.
/// </summary>
FreeText = 2,
/// <summary>
/// Display a single straight line on the page with optional line ending styles.
/// </summary>
Line = 3,
/// <summary>
/// Display a rectangle on the page.
/// </summary>
Square = 4,
/// <summary>
/// Display an ellipse on the page.
/// </summary>
Circle = 5,
/// <summary>
/// Display a closed polygon on the page.
/// </summary>
Polygon = 6,
/// <summary>
/// Display a set of connected lines on the page which is not a closed polygon.
/// </summary>
PolyLine = 7,
/// <summary>
/// A highlight for text or content with associated annotation texyt.
/// </summary>
Highlight = 8,
/// <summary>
/// An underline under text with associated annotation text.
/// </summary>
Underline = 9,
/// <summary>
/// A jagged squiggly line under text with associated annotation text.
/// </summary>
Squiggly = 10,
/// <summary>
/// A strikeout through some text with associated annotation text.
/// </summary>
StrikeOut = 11,
/// <summary>
/// Text or graphics intended to display as if inserted by a rubber stamp.
/// </summary>
Stamp = 12,
/// <summary>
/// A visual symbol indicating the presence of text edits.
/// </summary>
Caret = 13,
/// <summary>
/// A freehand 'scribble' formed by one or more paths.
/// </summary>
Ink = 14,
/// <summary>
/// Displays text in a pop-up window for entry or editing.
/// </summary>
Popup = 15,
/// <summary>
/// A file.
/// </summary>
FileAttachment = 16,
/// <summary>
/// A sound to be played through speakers.
/// </summary>
Sound = 17,
/// <summary>
/// Embeds a movie from a file in a PDF document.
/// </summary>
Movie = 18,
/// <summary>
/// Used by interactive forms to represent field appearance and manage user interactions.
/// </summary>
Widget = 19,
/// <summary>
/// Specifies a page region for media clips to be played and actions to be triggered from.
/// </summary>
Screen = 20,
/// <summary>
/// Represents a symbol used during the physical printing process to maintain output quality, e.g. color bars or cut marks.
/// </summary>
PrinterMark = 21,
/// <summary>
/// Used during the physical printing process to prevent colors mixing.
/// </summary>
TrapNet = 22,
/// <summary>
/// Adds a watermark at a fixed size and position irrespective of page size.
/// </summary>
Watermark = 23,
/// <summary>
/// Represents a 3D model/artwork, for example from CAD, in a PDF document.
/// </summary>
Artwork3D = 24,
/// <summary>
/// A custom annotation type.
/// </summary>
Other = 25
}
internal static class AnnotationExtensions
{
public static AnnotationType ToAnnotationType(this NameToken name)
{
if (name.Data == NameToken.Text.Data)
{
return AnnotationType.Text;
}
if (name.Data == NameToken.Link.Data)
{
return AnnotationType.Link;
}
if (name.Data == NameToken.FreeText.Data)
{
return AnnotationType.FreeText;
}
if (name.Data == NameToken.Line.Data)
{
return AnnotationType.Line;
}
if (name.Data == NameToken.Square.Data)
{
return AnnotationType.Square;
}
if (name.Data == NameToken.Circle.Data)
{
return AnnotationType.Circle;
}
if (name.Data == NameToken.Polygon.Data)
{
return AnnotationType.Polygon;
}
if (name.Data == NameToken.PolyLine.Data)
{
return AnnotationType.PolyLine;
}
if (name.Data == NameToken.Highlight.Data)
{
return AnnotationType.Highlight;
}
if (name.Data == NameToken.Underline.Data)
{
return AnnotationType.Underline;
}
if (name.Data == NameToken.Squiggly.Data)
{
return AnnotationType.Squiggly;
}
if (name.Data == NameToken.StrikeOut.Data)
{
return AnnotationType.StrikeOut;
}
if (name.Data == NameToken.Stamp.Data)
{
return AnnotationType.Stamp;
}
if (name.Data == NameToken.Caret.Data)
{
return AnnotationType.Caret;
}
if (name.Data == NameToken.Ink.Data)
{
return AnnotationType.Ink;
}
if (name.Data == NameToken.Popup.Data)
{
return AnnotationType.Popup;
}
if (name.Data == NameToken.FileAttachment.Data)
{
return AnnotationType.FileAttachment;
}
if (name.Data == NameToken.Sound.Data)
{
return AnnotationType.Sound;
}
if (name.Data == NameToken.Movie.Data)
{
return AnnotationType.Movie;
}
if (name.Data == NameToken.Widget.Data)
{
return AnnotationType.Widget;
}
if (name.Data == NameToken.Screen.Data)
{
return AnnotationType.Screen;
}
if (name.Data == NameToken.PrinterMark.Data)
{
return AnnotationType.PrinterMark;
}
if (name.Data == NameToken.TrapNet.Data)
{
return AnnotationType.TrapNet;
}
if (name.Data == NameToken.Watermark.Data)
{
return AnnotationType.Watermark;
}
if (name.Data == NameToken.Annotation3D.Data)
{
return AnnotationType.Artwork3D;
}
return AnnotationType.Other;
}
}
}
}

View File

@@ -0,0 +1,113 @@
namespace UglyToad.PdfPig.Annotations
{
/// <summary>
/// The standard annotation types in PDF documents.
/// </summary>
public enum AnnotationType
{
/// <summary>
/// A 'sticky note' style annotation displaying some text with open/closed pop-up state.
/// </summary>
Text = 0,
/// <summary>
/// A link to elsewhere in the document or an external application/web link.
/// </summary>
Link = 1,
/// <summary>
/// Displays text on the page. Unlike <see cref="Text"/> there is no associated pop-up.
/// </summary>
FreeText = 2,
/// <summary>
/// Display a single straight line on the page with optional line ending styles.
/// </summary>
Line = 3,
/// <summary>
/// Display a rectangle on the page.
/// </summary>
Square = 4,
/// <summary>
/// Display an ellipse on the page.
/// </summary>
Circle = 5,
/// <summary>
/// Display a closed polygon on the page.
/// </summary>
Polygon = 6,
/// <summary>
/// Display a set of connected lines on the page which is not a closed polygon.
/// </summary>
PolyLine = 7,
/// <summary>
/// A highlight for text or content with associated annotation texyt.
/// </summary>
Highlight = 8,
/// <summary>
/// An underline under text with associated annotation text.
/// </summary>
Underline = 9,
/// <summary>
/// A jagged squiggly line under text with associated annotation text.
/// </summary>
Squiggly = 10,
/// <summary>
/// A strikeout through some text with associated annotation text.
/// </summary>
StrikeOut = 11,
/// <summary>
/// Text or graphics intended to display as if inserted by a rubber stamp.
/// </summary>
Stamp = 12,
/// <summary>
/// A visual symbol indicating the presence of text edits.
/// </summary>
Caret = 13,
/// <summary>
/// A freehand 'scribble' formed by one or more paths.
/// </summary>
Ink = 14,
/// <summary>
/// Displays text in a pop-up window for entry or editing.
/// </summary>
Popup = 15,
/// <summary>
/// A file.
/// </summary>
FileAttachment = 16,
/// <summary>
/// A sound to be played through speakers.
/// </summary>
Sound = 17,
/// <summary>
/// Embeds a movie from a file in a PDF document.
/// </summary>
Movie = 18,
/// <summary>
/// Used by interactive forms to represent field appearance and manage user interactions.
/// </summary>
Widget = 19,
/// <summary>
/// Specifies a page region for media clips to be played and actions to be triggered from.
/// </summary>
Screen = 20,
/// <summary>
/// Represents a symbol used during the physical printing process to maintain output quality, e.g. color bars or cut marks.
/// </summary>
PrinterMark = 21,
/// <summary>
/// Used during the physical printing process to prevent colors mixing.
/// </summary>
TrapNet = 22,
/// <summary>
/// Adds a watermark at a fixed size and position irrespective of page size.
/// </summary>
Watermark = 23,
/// <summary>
/// Represents a 3D model/artwork, for example from CAD, in a PDF document.
/// </summary>
Artwork3D = 24,
/// <summary>
/// A custom annotation type.
/// </summary>
Other = 25
}
}

View File

@@ -18,6 +18,11 @@
[NotNull]
public IReadOnlyList<IToken> Data { get; }
/// <summary>
/// The number of tokens in this array.
/// </summary>
public int Length { get; }
/// <summary>
/// Create a new <see cref="ArrayToken"/>.
/// </summary>
@@ -53,6 +58,7 @@
}
Data = result;
Length = Data.Count;
}
/// <inheritdoc />