mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-07-15 23:24:37 +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.Core;
|
||||||
using PdfPig.Geometry;
|
using PdfPig.Geometry;
|
||||||
using PdfPig.Graphics;
|
using PdfPig.Graphics;
|
||||||
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class ContentStreamProcessorTests
|
public class ContentStreamProcessorTests
|
||||||
@ -132,8 +133,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void GetInitialTransformationMatrices(
|
private static void GetInitialTransformationMatrices(
|
||||||
PdfRectangle mediaBox,
|
MediaBox mediaBox,
|
||||||
PdfRectangle cropBox,
|
CropBox cropBox,
|
||||||
PageRotationDegrees rotation,
|
PageRotationDegrees rotation,
|
||||||
out TransformationMatrix initialMatrix,
|
out TransformationMatrix initialMatrix,
|
||||||
out TransformationMatrix inverseMatrix)
|
out TransformationMatrix inverseMatrix)
|
||||||
@ -142,6 +143,16 @@
|
|||||||
inverseMatrix = initialMatrix.Inverse();
|
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)
|
private static void AssertAreEqual(PdfRectangle r1, PdfRectangle r2)
|
||||||
{
|
{
|
||||||
AssertAreEqual(r1.BottomLeft, r2.BottomLeft);
|
AssertAreEqual(r1.BottomLeft, r2.BottomLeft);
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Annotation
|
public class Annotation
|
||||||
{
|
{
|
||||||
|
private readonly StreamToken normalAppearanceStream;
|
||||||
|
private readonly StreamToken rollOverAppearanceStream;
|
||||||
|
private readonly StreamToken downAppearanceStream;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The underlying PDF dictionary which this annotation was created from.
|
/// The underlying PDF dictionary which this annotation was created from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,7 +47,7 @@
|
|||||||
/// The date and time the annotation was last modified, can be in any format. Optional.
|
/// The date and time the annotation was last modified, can be in any format. Optional.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public string ModifiedDate { get; }
|
public DateTimeOffset ModifiedDate { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flags defining the appearance and behaviour of this annotation.
|
/// Flags defining the appearance and behaviour of this annotation.
|
||||||
@ -62,11 +66,22 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<QuadPointsQuadrilateral> QuadPoints { get; }
|
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>
|
/// <summary>
|
||||||
/// Create a new <see cref="Annotation"/>.
|
/// Create a new <see cref="Annotation"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Annotation(DictionaryToken annotationDictionary, AnnotationType type, PdfRectangle rectangle, string content, string name, string modifiedDate,
|
public Annotation(DictionaryToken annotationDictionary, AnnotationType type, PdfRectangle rectangle, string content, string name, DateTimeOffset modifiedDate,
|
||||||
AnnotationFlags flags, AnnotationBorder border, IReadOnlyList<QuadPointsQuadrilateral> quadPoints)
|
AnnotationFlags flags, AnnotationBorder border, IReadOnlyList<QuadPointsQuadrilateral> quadPoints,
|
||||||
|
StreamToken normalAppearanceStream, StreamToken rollOverAppearanceStream, StreamToken downAppearanceStream)
|
||||||
{
|
{
|
||||||
AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary));
|
AnnotationDictionary = annotationDictionary ?? throw new ArgumentNullException(nameof(annotationDictionary));
|
||||||
Type = type;
|
Type = type;
|
||||||
@ -77,6 +92,9 @@
|
|||||||
Flags = flags;
|
Flags = flags;
|
||||||
Border = border;
|
Border = border;
|
||||||
QuadPoints = quadPoints ?? EmptyArray<QuadPointsQuadrilateral>.Instance;
|
QuadPoints = quadPoints ?? EmptyArray<QuadPointsQuadrilateral>.Instance;
|
||||||
|
this.normalAppearanceStream = normalAppearanceStream;
|
||||||
|
this.rollOverAppearanceStream = rollOverAppearanceStream;
|
||||||
|
this.downAppearanceStream = downAppearanceStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -13,9 +13,12 @@
|
|||||||
{
|
{
|
||||||
private readonly IPdfTokenScanner tokenScanner;
|
private readonly IPdfTokenScanner tokenScanner;
|
||||||
private readonly DictionaryToken pageDictionary;
|
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.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
|
||||||
this.pageDictionary = pageDictionary ?? throw new ArgumentNullException(nameof(pageDictionary));
|
this.pageDictionary = pageDictionary ?? throw new ArgumentNullException(nameof(pageDictionary));
|
||||||
}
|
}
|
||||||
@ -37,11 +40,12 @@
|
|||||||
var type = annotationDictionary.Get<NameToken>(NameToken.Subtype, tokenScanner);
|
var type = annotationDictionary.Get<NameToken>(NameToken.Subtype, tokenScanner);
|
||||||
|
|
||||||
var annotationType = type.ToAnnotationType();
|
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 contents = GetNamedString(NameToken.Contents, annotationDictionary);
|
||||||
var name = GetNamedString(NameToken.Nm, 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;
|
var flags = (AnnotationFlags)0;
|
||||||
if (annotationDictionary.TryGet(NameToken.F, out var flagsToken) && DirectObjectFinder.TryGet(flagsToken, tokenScanner, out NumericToken flagsNumericToken))
|
if (annotationDictionary.TryGet(NameToken.F, out var flagsToken) && DirectObjectFinder.TryGet(flagsToken, tokenScanner, out NumericToken flagsNumericToken))
|
||||||
@ -83,10 +87,10 @@
|
|||||||
{
|
{
|
||||||
quadPointRectangles.Add(new QuadPointsQuadrilateral(new[]
|
quadPointRectangles.Add(new QuadPointsQuadrilateral(new[]
|
||||||
{
|
{
|
||||||
new PdfPoint(values[0], values[1]),
|
matrix.Transform(new PdfPoint(values[0], values[1])),
|
||||||
new PdfPoint(values[2], values[3]),
|
matrix.Transform(new PdfPoint(values[2], values[3])),
|
||||||
new PdfPoint(values[4], values[5]),
|
matrix.Transform(new PdfPoint(values[4], values[5])),
|
||||||
new PdfPoint(values[6], values[7])
|
matrix.Transform(new PdfPoint(values[6], values[7]))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
values.Clear();
|
values.Clear();
|
||||||
@ -94,8 +98,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new Annotation(annotationDictionary, annotationType, rectangle, contents, name, modifiedDate, flags, border,
|
StreamToken normalAppearanceStream = null, downAppearanceStream = null, rollOverAppearanceStream = null;
|
||||||
quadPointRectangles);
|
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,
|
public ContentStreamProcessor(IResourceStore resourceStore,
|
||||||
UserSpaceUnit userSpaceUnit,
|
UserSpaceUnit userSpaceUnit,
|
||||||
PdfRectangle mediaBox,
|
MediaBox mediaBox,
|
||||||
PdfRectangle cropBox,
|
CropBox cropBox,
|
||||||
PageRotationDegrees rotation,
|
PageRotationDegrees rotation,
|
||||||
IPdfTokenScanner pdfScanner,
|
IPdfTokenScanner pdfScanner,
|
||||||
IPageContentParser pageContentParser,
|
IPageContentParser pageContentParser,
|
||||||
@ -105,7 +105,7 @@
|
|||||||
|
|
||||||
// initiate CurrentClippingPath to cropBox
|
// initiate CurrentClippingPath to cropBox
|
||||||
var clippingSubpath = new PdfSubpath();
|
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 };
|
var clippingPath = new PdfPath() { clippingSubpath };
|
||||||
clippingPath.SetClipping(FillingRule.EvenOdd);
|
clippingPath.SetClipping(FillingRule.EvenOdd);
|
||||||
|
|
||||||
@ -120,13 +120,13 @@
|
|||||||
|
|
||||||
[System.Diagnostics.Contracts.Pure]
|
[System.Diagnostics.Contracts.Pure]
|
||||||
internal static TransformationMatrix GetInitialMatrix(UserSpaceUnit userSpaceUnit,
|
internal static TransformationMatrix GetInitialMatrix(UserSpaceUnit userSpaceUnit,
|
||||||
PdfRectangle mediaBox,
|
MediaBox mediaBox,
|
||||||
PdfRectangle cropBox,
|
CropBox cropBox,
|
||||||
PageRotationDegrees rotation)
|
PageRotationDegrees rotation)
|
||||||
{
|
{
|
||||||
// Cater for scenario where the cropbox is larger than the mediabox.
|
// Cater for scenario where the cropbox is larger than the mediabox.
|
||||||
// If there is no intersection (method returns null), fall back to the cropbox.
|
// 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
|
if (rotation.Value == 0
|
||||||
&& viewBox.Left == 0
|
&& viewBox.Left == 0
|
||||||
|
@ -133,8 +133,10 @@
|
|||||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
|
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
|
var initialMatrix = ContentStreamProcessor.GetInitialMatrix(userSpaceUnit, mediaBox, cropBox, rotation);
|
||||||
new AnnotationProvider(pdfScanner, dictionary),
|
|
||||||
|
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
|
||||||
|
new AnnotationProvider(pdfScanner, dictionary, initialMatrix),
|
||||||
pdfScanner);
|
pdfScanner);
|
||||||
|
|
||||||
for (var i = 0; i < stackDepth; i++)
|
for (var i = 0; i < stackDepth; i++)
|
||||||
@ -160,8 +162,8 @@
|
|||||||
var context = new ContentStreamProcessor(
|
var context = new ContentStreamProcessor(
|
||||||
resourceStore,
|
resourceStore,
|
||||||
userSpaceUnit,
|
userSpaceUnit,
|
||||||
mediaBox.Bounds,
|
mediaBox,
|
||||||
cropBox.Bounds,
|
cropBox,
|
||||||
rotation,
|
rotation,
|
||||||
pdfScanner,
|
pdfScanner,
|
||||||
pageContentParser,
|
pageContentParser,
|
||||||
|
Loading…
Reference in New Issue
Block a user