2018-12-21 02:18:32 +08:00
|
|
|
|
namespace UglyToad.PdfPig.Annotations
|
|
|
|
|
{
|
2023-04-11 00:14:14 +08:00
|
|
|
|
using Actions;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2018-12-22 23:54:32 +08:00
|
|
|
|
using System.Linq;
|
2020-01-05 06:39:13 +08:00
|
|
|
|
using Core;
|
2023-04-11 00:14:14 +08:00
|
|
|
|
using Logging;
|
|
|
|
|
using Outline.Destinations;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
using Parser.Parts;
|
|
|
|
|
using Tokenization.Scanner;
|
|
|
|
|
using Tokens;
|
|
|
|
|
using Util;
|
|
|
|
|
|
|
|
|
|
internal class AnnotationProvider
|
|
|
|
|
{
|
|
|
|
|
private readonly IPdfTokenScanner tokenScanner;
|
|
|
|
|
private readonly DictionaryToken pageDictionary;
|
2023-04-11 00:14:14 +08:00
|
|
|
|
private readonly NamedDestinations namedDestinations;
|
|
|
|
|
private readonly ILog log;
|
2023-03-14 01:15:24 +08:00
|
|
|
|
private readonly TransformationMatrix matrix;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
|
2023-03-14 01:15:24 +08:00
|
|
|
|
public AnnotationProvider(IPdfTokenScanner tokenScanner, DictionaryToken pageDictionary,
|
2023-04-11 00:14:14 +08:00
|
|
|
|
TransformationMatrix matrix, NamedDestinations namedDestinations, ILog log)
|
2018-12-21 02:18:32 +08:00
|
|
|
|
{
|
2023-03-14 01:15:24 +08:00
|
|
|
|
this.matrix = matrix;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
|
|
|
|
|
this.pageDictionary = pageDictionary ?? throw new ArgumentNullException(nameof(pageDictionary));
|
2023-04-11 00:14:14 +08:00
|
|
|
|
this.namedDestinations = namedDestinations;
|
|
|
|
|
this.log = log;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<Annotation> GetAnnotations()
|
|
|
|
|
{
|
2023-05-27 20:40:01 +08:00
|
|
|
|
var lookupAnnotations = new Dictionary<IndirectReference, Annotation>();
|
|
|
|
|
|
2020-02-28 19:39:56 +08:00
|
|
|
|
if (!pageDictionary.TryGet(NameToken.Annots, tokenScanner, out ArrayToken annotationsArray))
|
2018-12-21 02:18:32 +08:00
|
|
|
|
{
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var token in annotationsArray.Data)
|
|
|
|
|
{
|
|
|
|
|
if (!DirectObjectFinder.TryGet(token, tokenScanner, out DictionaryToken annotationDictionary))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-27 20:40:01 +08:00
|
|
|
|
Annotation replyTo = null;
|
|
|
|
|
if (annotationDictionary.TryGet(NameToken.Irt, out IndirectReferenceToken referencedAnnotation)
|
|
|
|
|
&& lookupAnnotations.TryGetValue(referencedAnnotation.Data, out var linkedAnnotation))
|
|
|
|
|
{
|
|
|
|
|
replyTo = linkedAnnotation;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-21 02:18:32 +08:00
|
|
|
|
var type = annotationDictionary.Get<NameToken>(NameToken.Subtype, tokenScanner);
|
|
|
|
|
var annotationType = type.ToAnnotationType();
|
2023-04-11 00:14:14 +08:00
|
|
|
|
var action = GetAction(annotationDictionary);
|
2023-03-14 01:15:24 +08:00
|
|
|
|
var rectangle = matrix.Transform(annotationDictionary.Get<ArrayToken>(NameToken.Rect, tokenScanner).ToRectangle(tokenScanner));
|
2018-12-22 23:54:32 +08:00
|
|
|
|
var contents = GetNamedString(NameToken.Contents, annotationDictionary);
|
|
|
|
|
var name = GetNamedString(NameToken.Nm, annotationDictionary);
|
2023-03-14 19:01:24 +08:00
|
|
|
|
// As indicated in PDF reference 8.4.1, the modified date can be anything, but is usually a date formatted according to sec. 3.8.3
|
|
|
|
|
var modifiedDate = GetNamedString(NameToken.M, annotationDictionary);
|
2018-12-22 23:54:32 +08:00
|
|
|
|
|
2020-01-06 00:23:07 +08:00
|
|
|
|
var flags = (AnnotationFlags)0;
|
2018-12-22 23:54:32 +08:00
|
|
|
|
if (annotationDictionary.TryGet(NameToken.F, out var flagsToken) && DirectObjectFinder.TryGet(flagsToken, tokenScanner, out NumericToken flagsNumericToken))
|
2018-12-21 02:18:32 +08:00
|
|
|
|
{
|
2020-01-06 00:23:07 +08:00
|
|
|
|
flags = (AnnotationFlags)flagsNumericToken.Int;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-22 23:54:32 +08:00
|
|
|
|
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;
|
2024-01-30 23:11:12 +08:00
|
|
|
|
var dashes = default(IReadOnlyList<double>);
|
2018-12-21 02:18:32 +08:00
|
|
|
|
|
2018-12-22 23:54:32 +08:00
|
|
|
|
if (borderArray.Length == 4 && borderArray.Data[4] is ArrayToken dashArray)
|
|
|
|
|
{
|
|
|
|
|
dashes = dashArray.Data.OfType<NumericToken>().Select(x => x.Data).ToList();
|
|
|
|
|
}
|
2018-12-21 02:18:32 +08:00
|
|
|
|
|
2018-12-22 23:54:32 +08:00
|
|
|
|
border = new AnnotationBorder(horizontal, vertical, width, dashes);
|
|
|
|
|
}
|
2018-12-21 02:18:32 +08:00
|
|
|
|
|
2020-01-06 00:23:07 +08:00
|
|
|
|
var quadPointRectangles = new List<QuadPointsQuadrilateral>();
|
|
|
|
|
if (annotationDictionary.TryGet(NameToken.Quadpoints, tokenScanner, out ArrayToken quadPointsArray))
|
|
|
|
|
{
|
2024-01-30 23:11:12 +08:00
|
|
|
|
var values = new List<double>();
|
2020-01-06 00:23:07 +08:00
|
|
|
|
for (var i = 0; i < quadPointsArray.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!(quadPointsArray[i] is NumericToken value))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
values.Add(value.Data);
|
|
|
|
|
|
|
|
|
|
if (values.Count == 8)
|
|
|
|
|
{
|
|
|
|
|
quadPointRectangles.Add(new QuadPointsQuadrilateral(new[]
|
|
|
|
|
{
|
2024-01-30 23:11:12 +08:00
|
|
|
|
matrix.Transform(new PdfPoint(values[0], values[1])),
|
|
|
|
|
matrix.Transform(new PdfPoint(values[2], values[3])),
|
|
|
|
|
matrix.Transform(new PdfPoint(values[4], values[5])),
|
2023-03-14 01:15:24 +08:00
|
|
|
|
matrix.Transform(new PdfPoint(values[6], values[7]))
|
2020-01-06 00:23:07 +08:00
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
values.Clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 00:14:14 +08:00
|
|
|
|
AppearanceStream normalAppearanceStream = null;
|
|
|
|
|
AppearanceStream downAppearanceStream = null;
|
|
|
|
|
AppearanceStream rollOverAppearanceStream = null;
|
|
|
|
|
|
2023-03-14 01:15:24 +08:00
|
|
|
|
if (annotationDictionary.TryGet(NameToken.Ap, out DictionaryToken appearanceDictionary))
|
|
|
|
|
{
|
|
|
|
|
// The normal appearance of this annotation
|
2023-04-11 00:14:14 +08:00
|
|
|
|
if (AppearanceStreamFactory.TryCreate(appearanceDictionary, NameToken.N, tokenScanner, out AppearanceStream stream))
|
2023-03-14 01:15:24 +08:00
|
|
|
|
{
|
2023-04-11 00:14:14 +08:00
|
|
|
|
normalAppearanceStream = stream;
|
2023-03-14 01:15:24 +08:00
|
|
|
|
}
|
2023-04-11 00:14:14 +08:00
|
|
|
|
|
2023-03-14 01:15:24 +08:00
|
|
|
|
// If present, the 'roll over' appearance of this annotation (when hovering the mouse pointer over this annotation)
|
2023-04-11 00:14:14 +08:00
|
|
|
|
if (AppearanceStreamFactory.TryCreate(appearanceDictionary, NameToken.R, tokenScanner, out stream))
|
2023-03-14 01:15:24 +08:00
|
|
|
|
{
|
2023-04-11 00:14:14 +08:00
|
|
|
|
rollOverAppearanceStream = stream;
|
2023-03-14 01:15:24 +08:00
|
|
|
|
}
|
2023-04-11 00:14:14 +08:00
|
|
|
|
|
2023-03-14 01:15:24 +08:00
|
|
|
|
// If present, the 'down' appearance of this annotation (when you click on it)
|
2023-04-11 00:14:14 +08:00
|
|
|
|
if (AppearanceStreamFactory.TryCreate(appearanceDictionary, NameToken.D, tokenScanner, out stream))
|
2023-03-14 01:15:24 +08:00
|
|
|
|
{
|
2023-04-11 00:14:14 +08:00
|
|
|
|
downAppearanceStream = stream;
|
2023-03-14 01:15:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 00:14:14 +08:00
|
|
|
|
string appearanceState = null;
|
|
|
|
|
if (annotationDictionary.TryGet(NameToken.As, out NameToken appearanceStateToken))
|
|
|
|
|
{
|
|
|
|
|
appearanceState = appearanceStateToken.Data;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-27 20:40:01 +08:00
|
|
|
|
var annotation = new Annotation(
|
|
|
|
|
annotationDictionary,
|
|
|
|
|
annotationType,
|
|
|
|
|
rectangle,
|
|
|
|
|
contents,
|
|
|
|
|
name,
|
|
|
|
|
modifiedDate,
|
|
|
|
|
flags,
|
|
|
|
|
border,
|
|
|
|
|
quadPointRectangles,
|
|
|
|
|
action,
|
|
|
|
|
normalAppearanceStream,
|
|
|
|
|
rollOverAppearanceStream,
|
|
|
|
|
downAppearanceStream,
|
|
|
|
|
appearanceState,
|
|
|
|
|
replyTo);
|
|
|
|
|
|
|
|
|
|
if (token is IndirectReferenceToken indirectReference)
|
|
|
|
|
{
|
|
|
|
|
lookupAnnotations[indirectReference.Data] = annotation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield return annotation;
|
2018-12-22 23:54:32 +08:00
|
|
|
|
}
|
2018-12-21 02:18:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 11:23:16 +08:00
|
|
|
|
internal PdfAction GetAction(DictionaryToken annotationDictionary)
|
2023-04-11 00:14:14 +08:00
|
|
|
|
{
|
|
|
|
|
// If this annotation returns a direct destination, turn it into a GoTo action.
|
|
|
|
|
if (DestinationProvider.TryGetDestination(annotationDictionary,
|
|
|
|
|
NameToken.Dest,
|
|
|
|
|
namedDestinations,
|
|
|
|
|
tokenScanner,
|
|
|
|
|
log,
|
|
|
|
|
false,
|
|
|
|
|
out var destination))
|
|
|
|
|
{
|
|
|
|
|
return new GoToAction(destination);
|
|
|
|
|
}
|
2024-01-30 23:11:12 +08:00
|
|
|
|
|
2023-04-11 00:14:14 +08:00
|
|
|
|
// Try get action from the dictionary.
|
|
|
|
|
if (ActionProvider.TryGetAction(annotationDictionary, namedDestinations, tokenScanner, log, out var action))
|
|
|
|
|
{
|
|
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No action or destination found, return null
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-22 23:54:32 +08:00
|
|
|
|
private string GetNamedString(NameToken name, DictionaryToken dictionary)
|
2018-12-21 02:18:32 +08:00
|
|
|
|
{
|
2018-12-22 23:54:32 +08:00
|
|
|
|
string content = null;
|
|
|
|
|
if (dictionary.TryGet(name, out var contentToken))
|
2018-12-21 02:18:32 +08:00
|
|
|
|
{
|
2018-12-22 23:54:32 +08:00
|
|
|
|
if (contentToken is StringToken contentString)
|
|
|
|
|
{
|
|
|
|
|
content = contentString.Data;
|
|
|
|
|
}
|
|
|
|
|
else if (contentToken is HexToken contentHex)
|
|
|
|
|
{
|
|
|
|
|
content = contentHex.Data;
|
|
|
|
|
}
|
|
|
|
|
else if (DirectObjectFinder.TryGet(contentToken, tokenScanner, out StringToken indirectContentString))
|
|
|
|
|
{
|
|
|
|
|
content = indirectContentString.Data;
|
|
|
|
|
}
|
2018-12-21 02:18:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-22 23:54:32 +08:00
|
|
|
|
return content;
|
2018-12-21 02:18:32 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-22 23:54:32 +08:00
|
|
|
|
}
|