#161 change rotation to fix values and page size

this doesn't account for images and pdf paths yet.
This commit is contained in:
Eliot Jones 2020-04-18 18:04:41 +01:00
parent b122bf0ca6
commit 25314cc79d
10 changed files with 109 additions and 26 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -16,7 +16,10 @@
private const string SingleInkscapePage = "Single Page Simple - from inkscape";
private const string MotorInsuranceClaim = "Motor Insurance claim form";
private const string PigProduction = "Pig Production Handbook";
private const string SinglePage90ClockwiseRotation = "SinglePage90ClockwiseRotation - from PdfPig";
private const string SinglePage180ClockwiseRotation = "SinglePage180ClockwiseRotation - from PdfPig";
private const string SinglePage270ClockwiseRotation = "SinglePage270ClockwiseRotation - from PdfPig";
private static string GetFilename(string name)
{
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
@ -95,6 +98,24 @@
Run(ByzantineGenerals, 702);
}
[Fact]
public void SinglePage90ClockwiseRotationFromPdfPig()
{
Run(SinglePage90ClockwiseRotation, 595);
}
[Fact]
public void SinglePage180ClockwiseRotationFromPdfPig()
{
Run(SinglePage180ClockwiseRotation, 842);
}
[Fact]
public void SinglePage270ClockwiseRotationFromPdfPig()
{
Run(SinglePage270ClockwiseRotation, 595);
}
private static void Run(string file, int imageHeight = 792)
{
var pdfFileName = GetFilename(file);
@ -105,14 +126,14 @@
var page = document.GetPage(1);
var violetPen = new Pen(Color.BlueViolet, 1);
var greenPen = new Pen(Color.Crimson, 1);
var redPen = new Pen(Color.Crimson, 1);
using (var bitmap = new Bitmap(image))
using (var graphics = Graphics.FromImage(bitmap))
{
foreach (var word in page.GetWords())
{
DrawRectangle(word.BoundingBox, graphics, greenPen, imageHeight);
DrawRectangle(word.BoundingBox, graphics, redPen, imageHeight);
}
foreach (var letter in page.Letters)

View File

@ -3,21 +3,23 @@
using System;
using System.Diagnostics.Contracts;
using Core;
using Geometry;
/// <summary>
/// Represents the rotation of a page in a PDF document defined by the page dictionary in degrees clockwise.
/// </summary>
public struct PageRotationDegrees : IEquatable<PageRotationDegrees>
{
private static readonly TransformationMatrix Rotate90 = TransformationMatrix.FromValues(0, -1, 1, 0);
private static readonly TransformationMatrix Rotate180 = TransformationMatrix.FromValues(-1, 0, 0, -1);
private static readonly TransformationMatrix Rotate270 = TransformationMatrix.FromValues(0, 1, -1, 0);
/// <summary>
/// The rotation of the page in degrees clockwise.
/// </summary>
public int Value { get; }
/// <summary>
/// Whether the rotation flips the x and y axes.
/// </summary>
public bool SwapsAxis => (Value == 90) || (Value == 270);
/// <summary>
/// Get the rotation expressed in radians (anti-clockwise).
/// </summary>
@ -66,21 +68,60 @@
}
[Pure]
internal TransformationMatrix Rotate(TransformationMatrix matrix)
internal PdfRectangle Rotate(PdfRectangle rectangle, PdfVector pageSize)
{
// TODO: this is a bit of a hack because I don't understand matrices
/* There should be a single Affine Transform we can apply to any point resulting
* from a content stream operation which will rotate the point and translate it back to
* a point where the origin is in the page's lower left corner.
*
* For example this matrix represents a (clockwise) rotation and translation:
* [ cos sin tx ]
* [ -sin cos ty ]
* [ 0 0 1 ]
*
* The values of tx and ty are those required to move the origin back to the expected origin (lower-left).
* The corresponding values should be:
* Rotation: 0 90 180 270
* tx: 0 0 w w
* ty: 0 h h 0
*
* Where w and h are the page width and height after rotation.
*/
double cos, sin;
double dx = 0, dy = 0;
switch (Value)
{
case 0:
return matrix;
return rectangle;
case 90:
return Rotate90.Multiply(matrix);
cos = 0;
sin = 1;
dy = pageSize.Y;
break;
case 180:
return Rotate180.Multiply(matrix);
cos = -1;
sin = 0;
dx = pageSize.X;
dy = pageSize.Y;
break;
case 270:
return Rotate270.Multiply(matrix);
cos = 0;
sin = -1;
dx = pageSize.X;
break;
default:
throw new InvalidOperationException($"Invalid value for rotation: {Value}.");
}
PdfPoint Multiply(PdfPoint pt)
{
return new PdfPoint((pt.X * cos) + (pt.Y * sin) + dx,
(pt.X * -sin) + (pt.Y * cos) + dy);
}
return new PdfRectangle(Multiply(rectangle.TopLeft), Multiply(rectangle.TopRight),
Multiply(rectangle.BottomLeft), Multiply(rectangle.BottomRight));
}
/// <inheritdoc />

View File

@ -49,6 +49,7 @@
private readonly IFilterProvider filterProvider;
private readonly ILog log;
private readonly bool clipPaths;
private readonly PdfVector pageSize;
private readonly MarkedContentStack markedContentStack = new MarkedContentStack();
private Stack<CurrentGraphicsState> graphicsStack = new Stack<CurrentGraphicsState>();
@ -88,7 +89,8 @@
IPageContentParser pageContentParser,
IFilterProvider filterProvider,
ILog log,
bool clipPaths)
bool clipPaths,
PdfVector pageSize)
{
this.resourceStore = resourceStore;
this.userSpaceUnit = userSpaceUnit;
@ -98,6 +100,7 @@
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
this.log = log;
this.clipPaths = clipPaths;
this.pageSize = pageSize;
// initiate CurrentClippingPath to cropBox
var clippingSubpath = new PdfSubpath();
@ -175,7 +178,7 @@
// TODO: this does not seem correct, produces the correct result for now but we need to revisit.
// see: https://stackoverflow.com/questions/48010235/pdf-specification-get-font-size-in-points
var pointSize = Math.Round(rotation.Rotate(transformationMatrix).Multiply(TextMatrices.TextMatrix).Multiply(fontSize).A, 2);
var pointSize = Math.Round(transformationMatrix.Multiply(TextMatrices.TextMatrix).Multiply(fontSize).A, 2);
if (pointSize < 0)
{
@ -216,14 +219,18 @@
}
var boundingBox = font.GetBoundingBox(code);
var rotatedTransformationMatrix = rotation.Rotate(transformationMatrix);
var transformedGlyphBounds = PerformantRectangleTransformer
.Transform(renderingMatrix, textMatrix, rotatedTransformationMatrix, boundingBox.GlyphBounds);
.Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds);
var transformedPdfBounds = PerformantRectangleTransformer
.Transform(renderingMatrix, textMatrix, rotatedTransformationMatrix, new PdfRectangle(0, 0, boundingBox.Width, 0));
.Transform(renderingMatrix, textMatrix, transformationMatrix, new PdfRectangle(0, 0, boundingBox.Width, 0));
if (rotation.Value > 0)
{
transformedGlyphBounds = rotation.Rotate(transformedGlyphBounds, pageSize);
transformedPdfBounds = rotation.Rotate(transformedPdfBounds, pageSize);
}
// If the text rendering mode calls for filling, the current nonstroking color in the graphics state is used;
// if it calls for stroking, the current stroking color is used.

View File

@ -47,15 +47,15 @@
log?.Error($"Page {number} had its type specified as {type} rather than 'Page'.");
}
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
var rotation = new PageRotationDegrees(pageTreeMembers.Rotation);
if (dictionary.TryGet(NameToken.Rotate, pdfScanner, out NumericToken rotateToken))
{
rotation = new PageRotationDegrees(rotateToken.Int);
}
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
var stackDepth = 0;
while (pageTreeMembers.ParentResources.Count > 0)
@ -71,6 +71,19 @@
resourceStore.LoadResourceDictionary(resources);
stackDepth++;
}
// Apply rotation.
if (rotation.SwapsAxis)
{
mediaBox = new MediaBox(new PdfRectangle(mediaBox.Bounds.Bottom,
mediaBox.Bounds.Left,
mediaBox.Bounds.Top,
mediaBox.Bounds.Right));
cropBox = new CropBox(new PdfRectangle(cropBox.Bounds.Bottom,
cropBox.Bounds.Left,
cropBox.Bounds.Top,
cropBox.Bounds.Right));
}
UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary);
@ -108,7 +121,7 @@
}
}
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths);
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
}
else
{
@ -121,7 +134,7 @@
var bytes = contentStream.Decode(filterProvider);
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths);
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
}
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
@ -137,7 +150,7 @@
}
private PageContent GetContent(int pageNumber, IReadOnlyList<byte> contentBytes, CropBox cropBox, UserSpaceUnit userSpaceUnit,
PageRotationDegrees rotation, bool clipPaths)
PageRotationDegrees rotation, bool clipPaths, MediaBox mediaBox)
{
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentBytes),
log);
@ -146,7 +159,8 @@
pageContentParser,
filterProvider,
log,
clipPaths);
clipPaths,
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height));
return context.Process(pageNumber, operations);
}