add support for quadpoints to annotations

highlight, link, strikeout, squiggly and underline annotation types may define a set of quadrilaterals using the quadpoints entry. this defines the regions to show/activate the annotation. the order of points in the quadpoints array does not match the specification so we provide a convenience class to access the point data rather than interpreting it as a rectangle: https://stackoverflow.com/questions/9855814/pdf-spec-vs-acrobat-creation-quadpoints.
This commit is contained in:
Eliot Jones 2020-01-05 16:23:07 +00:00
parent e064d39671
commit 00bd285262
5 changed files with 96 additions and 4 deletions

View File

@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System.Linq;
using Annotations;
using Xunit;
public class CatGeneticsTests
@ -31,6 +32,13 @@
var annotations = page.ExperimentalAccess.GetAnnotations().ToList();
Assert.NotEmpty(annotations);
var highlights = annotations.Where(x => x.Type == AnnotationType.Highlight);
foreach (var highlight in highlights)
{
Assert.NotEmpty(highlight.QuadPoints);
}
}
}
}

View File

@ -63,6 +63,7 @@
"UglyToad.PdfPig.Annotations.AnnotationBorder",
"UglyToad.PdfPig.Annotations.AnnotationFlags",
"UglyToad.PdfPig.Annotations.AnnotationType",
"UglyToad.PdfPig.Annotations.QuadPointsQuadrilateral",
"UglyToad.PdfPig.Content.Catalog",
"UglyToad.PdfPig.Content.CropBox",
"UglyToad.PdfPig.Content.DocumentInformation",

View File

@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Annotations
{
using System;
using System.Collections.Generic;
using Core;
using Tokens;
using Util.JetBrains.Annotations;
@ -54,11 +55,18 @@
/// </summary>
public AnnotationBorder Border { get; }
/// <summary>
/// Rectangles defined using QuadPoints, for <see cref="AnnotationType.Link"/> these are the regions used to activate the link,
/// for text markup annotations these are the text regions to apply the markup to.
/// See <see cref="QuadPointsQuadrilateral.Points"/> for more information regarding the order of the points.
/// </summary>
public IReadOnlyList<QuadPointsQuadrilateral> QuadPoints { 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)
AnnotationFlags flags, AnnotationBorder border, IReadOnlyList<QuadPointsQuadrilateral> quadPoints)
{
AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary));
Type = type;
@ -68,6 +76,7 @@
ModifiedDate = modifiedDate;
Flags = flags;
Border = border;
QuadPoints = quadPoints ?? EmptyArray<QuadPointsQuadrilateral>.Instance;
}
/// <inheritdoc />

View File

@ -59,10 +59,10 @@
var name = GetNamedString(NameToken.Nm, annotationDictionary);
var modifiedDate = GetNamedString(NameToken.M, annotationDictionary);
var flags = (AnnotationFlags) 0;
var flags = (AnnotationFlags)0;
if (annotationDictionary.TryGet(NameToken.F, out var flagsToken) && DirectObjectFinder.TryGet(flagsToken, tokenScanner, out NumericToken flagsNumericToken))
{
flags = (AnnotationFlags) flagsNumericToken.Int;
flags = (AnnotationFlags)flagsNumericToken.Int;
}
var border = AnnotationBorder.Default;
@ -82,7 +82,36 @@
border = new AnnotationBorder(horizontal, vertical, width, dashes);
}
yield return new Annotation(annotationDictionary, annotationType, rectangle, contents, name, modifiedDate, flags, border);
var quadPointRectangles = new List<QuadPointsQuadrilateral>();
if (annotationDictionary.TryGet(NameToken.Quadpoints, tokenScanner, out ArrayToken quadPointsArray))
{
var values = new List<decimal>();
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[]
{
new PdfPoint(values[0], values[1]),
new PdfPoint(values[2], values[3]),
new PdfPoint(values[4], values[5]),
new PdfPoint(values[6], values[7])
}));
values.Clear();
}
}
}
yield return new Annotation(annotationDictionary, annotationType, rectangle, contents, name, modifiedDate, flags, border,
quadPointRectangles);
}
}

View File

@ -0,0 +1,45 @@
namespace UglyToad.PdfPig.Annotations
{
using System;
using System.Collections.Generic;
using Core;
/// <summary>
/// A QuadPoints quadrilateral is four points defining the region for an annotation to use.
/// An annotation may cover multiple quadrilaterals.
/// </summary>
public class QuadPointsQuadrilateral
{
/// <summary>
/// The 4 points defining this quadrilateral.
/// The PDF specification defines these as being in anti-clockwise order starting from the lower-left corner, however
/// Adobe's implementation doesn't obey the specification and points seem to go in the order: top-left, top-right,
/// bottom-left, bottom-right. See: https://stackoverflow.com/questions/9855814/pdf-spec-vs-acrobat-creation-quadpoints.
/// </summary>
public IReadOnlyList<PdfPoint> Points { get; }
/// <summary>
/// Create a new <see cref="QuadPointsQuadrilateral"/>.
/// </summary>
public QuadPointsQuadrilateral(IReadOnlyList<PdfPoint> points)
{
if (points == null)
{
throw new ArgumentNullException(nameof(points));
}
if (points.Count != 4)
{
throw new ArgumentException($"Quadpoints quadrilateral should only contain 4 points, instead got {points.Count} points.");
}
Points = points;
}
/// <inheritdoc />
public override string ToString()
{
return $"[ {Points[0]}, {Points[1]}, {Points[2]}, {Points[3]} ]";
}
}
}