mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-04-05 20:55:01 +08:00
Changes for annotation positions:
- Pass in the initial matrix to the annotation provider, so that it can return the correct rectangles / quad points. - Made a change / extensions to the Annotation class: - ModifiedDate is now a DateTimeOffset instead of unparsed string. If the string is invalid, ModifiedDate is set to the default value. - Added lookup for the "appearance streams"; all the annotations should have a "N" (normal) appearance, and optionally have a "R" (roll-over/hover) and "D" (down/click) appearance. Did not expose the actual stream objects, but added a flag indicating the existence of "R" / "D". At some point we can consider doing something with the appearances. - Changed signature of GetInitialMatrix / ContentStreamProcessor constructor from PdfRectangle back to what it was earlier, namely MediaBox and CropBox, to prevent accidentally mixing the two up in the caller.
This commit is contained in:
parent
a439b43246
commit
ea77156eb8
@ -4,6 +4,7 @@
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Geometry;
|
||||
using PdfPig.Graphics;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
public class ContentStreamProcessorTests
|
||||
@ -132,8 +133,8 @@
|
||||
}
|
||||
|
||||
private static void GetInitialTransformationMatrices(
|
||||
PdfRectangle mediaBox,
|
||||
PdfRectangle cropBox,
|
||||
MediaBox mediaBox,
|
||||
CropBox cropBox,
|
||||
PageRotationDegrees rotation,
|
||||
out TransformationMatrix initialMatrix,
|
||||
out TransformationMatrix inverseMatrix)
|
||||
@ -142,6 +143,16 @@
|
||||
inverseMatrix = initialMatrix.Inverse();
|
||||
}
|
||||
|
||||
private static void GetInitialTransformationMatrices(
|
||||
PdfRectangle mediaBox,
|
||||
PdfRectangle cropBox,
|
||||
PageRotationDegrees rotation,
|
||||
out TransformationMatrix initialMatrix,
|
||||
out TransformationMatrix inverseMatrix)
|
||||
{
|
||||
GetInitialTransformationMatrices(new MediaBox(mediaBox), new CropBox(cropBox), rotation, out initialMatrix, out inverseMatrix);
|
||||
}
|
||||
|
||||
private static void AssertAreEqual(PdfRectangle r1, PdfRectangle r2)
|
||||
{
|
||||
AssertAreEqual(r1.BottomLeft, r2.BottomLeft);
|
||||
|
@ -11,6 +11,10 @@
|
||||
/// </summary>
|
||||
public class Annotation
|
||||
{
|
||||
private readonly StreamToken normalAppearanceStream;
|
||||
private readonly StreamToken rollOverAppearanceStream;
|
||||
private readonly StreamToken downAppearanceStream;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying PDF dictionary which this annotation was created from.
|
||||
/// </summary>
|
||||
@ -43,7 +47,7 @@
|
||||
/// The date and time the annotation was last modified, can be in any format. Optional.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public string ModifiedDate { get; }
|
||||
public DateTimeOffset ModifiedDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags defining the appearance and behaviour of this annotation.
|
||||
@ -62,11 +66,22 @@
|
||||
/// </summary>
|
||||
public IReadOnlyList<QuadPointsQuadrilateral> QuadPoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a roll over appearance is present for this annotation (shown when you hover over this annotation)
|
||||
/// </summary>
|
||||
public bool HasRollOverAppearance => rollOverAppearanceStream != null;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a down appearance is present for this annotation (shown when you click on this annotation)
|
||||
/// </summary>
|
||||
public bool HasDownAppearance => downAppearanceStream != null;
|
||||
|
||||
/// <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, IReadOnlyList<QuadPointsQuadrilateral> quadPoints)
|
||||
public Annotation(DictionaryToken annotationDictionary, AnnotationType type, PdfRectangle rectangle, string content, string name, DateTimeOffset modifiedDate,
|
||||
AnnotationFlags flags, AnnotationBorder border, IReadOnlyList<QuadPointsQuadrilateral> quadPoints,
|
||||
StreamToken normalAppearanceStream, StreamToken rollOverAppearanceStream, StreamToken downAppearanceStream)
|
||||
{
|
||||
AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary));
|
||||
Type = type;
|
||||
@ -77,6 +92,9 @@
|
||||
Flags = flags;
|
||||
Border = border;
|
||||
QuadPoints = quadPoints ?? EmptyArray<QuadPointsQuadrilateral>.Instance;
|
||||
this.normalAppearanceStream = normalAppearanceStream;
|
||||
this.rollOverAppearanceStream = rollOverAppearanceStream;
|
||||
this.downAppearanceStream = downAppearanceStream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -13,9 +13,12 @@
|
||||
{
|
||||
private readonly IPdfTokenScanner tokenScanner;
|
||||
private readonly DictionaryToken pageDictionary;
|
||||
private readonly TransformationMatrix matrix;
|
||||
|
||||
public AnnotationProvider(IPdfTokenScanner tokenScanner, DictionaryToken pageDictionary)
|
||||
public AnnotationProvider(IPdfTokenScanner tokenScanner, DictionaryToken pageDictionary,
|
||||
TransformationMatrix matrix)
|
||||
{
|
||||
this.matrix = matrix;
|
||||
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
|
||||
this.pageDictionary = pageDictionary ?? throw new ArgumentNullException(nameof(pageDictionary));
|
||||
}
|
||||
@ -37,11 +40,12 @@
|
||||
var type = annotationDictionary.Get<NameToken>(NameToken.Subtype, tokenScanner);
|
||||
|
||||
var annotationType = type.ToAnnotationType();
|
||||
var rectangle = annotationDictionary.Get<ArrayToken>(NameToken.Rect, tokenScanner).ToRectangle(tokenScanner);
|
||||
var rectangle = matrix.Transform(annotationDictionary.Get<ArrayToken>(NameToken.Rect, tokenScanner).ToRectangle(tokenScanner));
|
||||
|
||||
var contents = GetNamedString(NameToken.Contents, annotationDictionary);
|
||||
var name = GetNamedString(NameToken.Nm, annotationDictionary);
|
||||
var modifiedDate = GetNamedString(NameToken.M, annotationDictionary);
|
||||
var modifiedDateAsString = GetNamedString(NameToken.M, annotationDictionary);
|
||||
if (!DateFormatHelper.TryParseDateTimeOffset(modifiedDateAsString, out var modifiedDate)) modifiedDate = default(DateTimeOffset);
|
||||
|
||||
var flags = (AnnotationFlags)0;
|
||||
if (annotationDictionary.TryGet(NameToken.F, out var flagsToken) && DirectObjectFinder.TryGet(flagsToken, tokenScanner, out NumericToken flagsNumericToken))
|
||||
@ -83,10 +87,10 @@
|
||||
{
|
||||
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])
|
||||
matrix.Transform(new PdfPoint(values[0], values[1])),
|
||||
matrix.Transform(new PdfPoint(values[2], values[3])),
|
||||
matrix.Transform(new PdfPoint(values[4], values[5])),
|
||||
matrix.Transform(new PdfPoint(values[6], values[7]))
|
||||
}));
|
||||
|
||||
values.Clear();
|
||||
@ -94,8 +98,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
yield return new Annotation(annotationDictionary, annotationType, rectangle, contents, name, modifiedDate, flags, border,
|
||||
quadPointRectangles);
|
||||
StreamToken normalAppearanceStream = null, downAppearanceStream = null, rollOverAppearanceStream = null;
|
||||
if (annotationDictionary.TryGet(NameToken.Ap, out DictionaryToken appearanceDictionary))
|
||||
{
|
||||
// The normal appearance of this annotation
|
||||
if (appearanceDictionary.TryGet(NameToken.N, out IndirectReferenceToken normalAppearanceRef))
|
||||
{
|
||||
normalAppearanceStream = tokenScanner.Get(normalAppearanceRef.Data)?.Data as StreamToken;
|
||||
}
|
||||
// If present, the 'roll over' appearance of this annotation (when hovering the mouse pointer over this annotation)
|
||||
if (appearanceDictionary.TryGet(NameToken.R, out IndirectReferenceToken rollOverAppearanceRef))
|
||||
{
|
||||
rollOverAppearanceStream = tokenScanner.Get(rollOverAppearanceRef.Data)?.Data as StreamToken;
|
||||
}
|
||||
// If present, the 'down' appearance of this annotation (when you click on it)
|
||||
if (appearanceDictionary.TryGet(NameToken.D, out IndirectReferenceToken downAppearanceRef))
|
||||
{
|
||||
downAppearanceStream = tokenScanner.Get(downAppearanceRef.Data)?.Data as StreamToken;
|
||||
}
|
||||
}
|
||||
|
||||
yield return new Annotation(annotationDictionary, annotationType, rectangle,
|
||||
contents, name, modifiedDate, flags, border, quadPointRectangles,
|
||||
normalAppearanceStream, rollOverAppearanceStream, downAppearanceStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,8 +87,8 @@
|
||||
|
||||
public ContentStreamProcessor(IResourceStore resourceStore,
|
||||
UserSpaceUnit userSpaceUnit,
|
||||
PdfRectangle mediaBox,
|
||||
PdfRectangle cropBox,
|
||||
MediaBox mediaBox,
|
||||
CropBox cropBox,
|
||||
PageRotationDegrees rotation,
|
||||
IPdfTokenScanner pdfScanner,
|
||||
IPageContentParser pageContentParser,
|
||||
@ -105,7 +105,7 @@
|
||||
|
||||
// initiate CurrentClippingPath to cropBox
|
||||
var clippingSubpath = new PdfSubpath();
|
||||
clippingSubpath.Rectangle(cropBox.BottomLeft.X, cropBox.BottomLeft.Y, cropBox.Width, cropBox.Height);
|
||||
clippingSubpath.Rectangle(cropBox.Bounds.BottomLeft.X, cropBox.Bounds.BottomLeft.Y, cropBox.Bounds.Width, cropBox.Bounds.Height);
|
||||
var clippingPath = new PdfPath() { clippingSubpath };
|
||||
clippingPath.SetClipping(FillingRule.EvenOdd);
|
||||
|
||||
@ -120,13 +120,13 @@
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
internal static TransformationMatrix GetInitialMatrix(UserSpaceUnit userSpaceUnit,
|
||||
PdfRectangle mediaBox,
|
||||
PdfRectangle cropBox,
|
||||
MediaBox mediaBox,
|
||||
CropBox cropBox,
|
||||
PageRotationDegrees rotation)
|
||||
{
|
||||
// Cater for scenario where the cropbox is larger than the mediabox.
|
||||
// If there is no intersection (method returns null), fall back to the cropbox.
|
||||
var viewBox = mediaBox.Intersect(cropBox) ?? cropBox;
|
||||
var viewBox = mediaBox.Bounds.Intersect(cropBox.Bounds) ?? cropBox.Bounds;
|
||||
|
||||
if (rotation.Value == 0
|
||||
&& viewBox.Left == 0
|
||||
|
@ -133,8 +133,10 @@
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
|
||||
}
|
||||
|
||||
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
|
||||
new AnnotationProvider(pdfScanner, dictionary),
|
||||
var initialMatrix = ContentStreamProcessor.GetInitialMatrix(userSpaceUnit, mediaBox, cropBox, rotation);
|
||||
|
||||
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
|
||||
new AnnotationProvider(pdfScanner, dictionary, initialMatrix),
|
||||
pdfScanner);
|
||||
|
||||
for (var i = 0; i < stackDepth; i++)
|
||||
@ -160,8 +162,8 @@
|
||||
var context = new ContentStreamProcessor(
|
||||
resourceStore,
|
||||
userSpaceUnit,
|
||||
mediaBox.Bounds,
|
||||
cropBox.Bounds,
|
||||
mediaBox,
|
||||
cropBox,
|
||||
rotation,
|
||||
pdfScanner,
|
||||
pageContentParser,
|
||||
|
Loading…
Reference in New Issue
Block a user