namespace UglyToad.PdfPig.Annotations { using System; using System.Collections.Generic; using Exceptions; using Geometry; using Parser.Parts; using Tokenization.Scanner; using Tokens; using Util; using Util.JetBrains.Annotations; internal class AnnotationProvider { private readonly IPdfTokenScanner tokenScanner; private readonly DictionaryToken pageDictionary; private readonly bool isLenientParsing; public AnnotationProvider(IPdfTokenScanner tokenScanner, DictionaryToken pageDictionary, bool isLenientParsing) { this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner)); this.pageDictionary = pageDictionary ?? throw new ArgumentNullException(nameof(pageDictionary)); this.isLenientParsing = isLenientParsing; } public IEnumerable GetAnnotations() { if (!pageDictionary.TryGet(NameToken.Annots, out IToken annotationsToken) || !DirectObjectFinder.TryGet(annotationsToken, tokenScanner, out ArrayToken annotationsArray)) { yield break; } foreach (var token in annotationsArray.Data) { if (!DirectObjectFinder.TryGet(token, tokenScanner, out DictionaryToken annotationDictionary)) { if (isLenientParsing) { continue; } throw new PdfDocumentFormatException($"The annotations dictionary contained an annotation which wasn't a dictionary: {token}."); } if (!isLenientParsing && annotationDictionary.TryGet(NameToken.Type, out NameToken dictionaryType)) { if (dictionaryType != NameToken.Annot) { throw new PdfDocumentFormatException($"The annotations dictionary contained a non-annotation type dictionary: {annotationDictionary}."); } } var type = annotationDictionary.Get(NameToken.Subtype, tokenScanner); var annotationType = type.ToAnnotationType(); var rectangle = annotationDictionary.Get(NameToken.Rect, tokenScanner).ToRectangle(); string content = null; if (annotationDictionary.TryGet(NameToken.Contents, out var contentToken) && DirectObjectFinder.TryGet(contentToken, tokenScanner, out StringToken contentString)) { content = contentString.Data; } yield return new Annotation(annotationDictionary, rectangle, content, null, null); } } } internal class Annotation { /// /// The underlying PDF dictionary which this annotation was created from. /// [NotNull] public DictionaryToken AnnotationDictionary { get; } /// /// The rectangle in user space units specifying the location to place this annotation on the page. /// public PdfRectangle Rectangle { get; } /// /// The annotation text, or if the annotation does not display text, a description of the annotation's contents. Optional. /// [CanBeNull] public string Content { get; } /// /// The name of this annotation which should be unique per page. Optional. /// [CanBeNull] public string Name { get; } /// /// The date and time the annotation was last modified, can be in any format. Optional. /// [CanBeNull] public string ModifiedDate { get; } public AnnotationFlags Flags { get; } = (AnnotationFlags)0; /// /// Create a new . /// 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; } } /// /// The standard annotation types in PDF documents. /// internal enum AnnotationType { /// /// A 'sticky note' style annotation displaying some text with open/closed pop-up state. /// Text = 0, /// /// A link to elsewhere in the document or an external application/web link. /// Link = 1, /// /// Displays text on the page. Unlike there is no associated pop-up. /// FreeText = 2, /// /// Display a single straight line on the page with optional line ending styles. /// Line = 3, /// /// Display a rectangle on the page. /// Square = 4, /// /// Display an ellipse on the page. /// Circle = 5, /// /// Display a closed polygon on the page. /// Polygon = 6, /// /// Display a set of connected lines on the page which is not a closed polygon. /// PolyLine = 7, /// /// A highlight for text or content with associated annotation texyt. /// Highlight = 8, /// /// An underline under text with associated annotation text. /// Underline = 9, /// /// A jagged squiggly line under text with associated annotation text. /// Squiggly = 10, /// /// A strikeout through some text with associated annotation text. /// StrikeOut = 11, /// /// Text or graphics intended to display as if inserted by a rubber stamp. /// Stamp = 12, /// /// A visual symbol indicating the presence of text edits. /// Caret = 13, /// /// A freehand 'scribble' formed by one or more paths. /// Ink = 14, /// /// Displays text in a pop-up window for entry or editing. /// Popup = 15, /// /// A file. /// FileAttachment = 16, /// /// A sound to be played through speakers. /// Sound = 17, /// /// Embeds a movie from a file in a PDF document. /// Movie = 18, /// /// Used by interactive forms to represent field appearance and manage user interactions. /// Widget = 19, /// /// Specifies a page region for media clips to be played and actions to be triggered from. /// Screen = 20, /// /// Represents a symbol used during the physical printing process to maintain output quality, e.g. color bars or cut marks. /// PrinterMark = 21, /// /// Used during the physical printing process to prevent colors mixing. /// TrapNet = 22, /// /// Adds a watermark at a fixed size and position irrespective of page size. /// Watermark = 23, /// /// Represents a 3D model/artwork, for example from CAD, in a PDF document. /// Artwork3D = 24, /// /// A custom annotation type. /// 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; } } }