#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,6 +16,9 @@
private const string SingleInkscapePage = "Single Page Simple - from inkscape"; private const string SingleInkscapePage = "Single Page Simple - from inkscape";
private const string MotorInsuranceClaim = "Motor Insurance claim form"; private const string MotorInsuranceClaim = "Motor Insurance claim form";
private const string PigProduction = "Pig Production Handbook"; 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) private static string GetFilename(string name)
{ {
@@ -95,6 +98,24 @@
Run(ByzantineGenerals, 702); 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) private static void Run(string file, int imageHeight = 792)
{ {
var pdfFileName = GetFilename(file); var pdfFileName = GetFilename(file);
@@ -105,14 +126,14 @@
var page = document.GetPage(1); var page = document.GetPage(1);
var violetPen = new Pen(Color.BlueViolet, 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 bitmap = new Bitmap(image))
using (var graphics = Graphics.FromImage(bitmap)) using (var graphics = Graphics.FromImage(bitmap))
{ {
foreach (var word in page.GetWords()) foreach (var word in page.GetWords())
{ {
DrawRectangle(word.BoundingBox, graphics, greenPen, imageHeight); DrawRectangle(word.BoundingBox, graphics, redPen, imageHeight);
} }
foreach (var letter in page.Letters) foreach (var letter in page.Letters)

View File

@@ -3,21 +3,23 @@
using System; using System;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using Core; using Core;
using Geometry;
/// <summary> /// <summary>
/// Represents the rotation of a page in a PDF document defined by the page dictionary in degrees clockwise. /// Represents the rotation of a page in a PDF document defined by the page dictionary in degrees clockwise.
/// </summary> /// </summary>
public struct PageRotationDegrees : IEquatable<PageRotationDegrees> 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> /// <summary>
/// The rotation of the page in degrees clockwise. /// The rotation of the page in degrees clockwise.
/// </summary> /// </summary>
public int Value { get; } public int Value { get; }
/// <summary>
/// Whether the rotation flips the x and y axes.
/// </summary>
public bool SwapsAxis => (Value == 90) || (Value == 270);
/// <summary> /// <summary>
/// Get the rotation expressed in radians (anti-clockwise). /// Get the rotation expressed in radians (anti-clockwise).
/// </summary> /// </summary>
@@ -66,21 +68,60 @@
} }
[Pure] [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) switch (Value)
{ {
case 0: case 0:
return matrix; return rectangle;
case 90: case 90:
return Rotate90.Multiply(matrix); cos = 0;
sin = 1;
dy = pageSize.Y;
break;
case 180: case 180:
return Rotate180.Multiply(matrix); cos = -1;
sin = 0;
dx = pageSize.X;
dy = pageSize.Y;
break;
case 270: case 270:
return Rotate270.Multiply(matrix); cos = 0;
sin = -1;
dx = pageSize.X;
break;
default: default:
throw new InvalidOperationException($"Invalid value for rotation: {Value}."); 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 /> /// <inheritdoc />

View File

@@ -49,6 +49,7 @@
private readonly IFilterProvider filterProvider; private readonly IFilterProvider filterProvider;
private readonly ILog log; private readonly ILog log;
private readonly bool clipPaths; private readonly bool clipPaths;
private readonly PdfVector pageSize;
private readonly MarkedContentStack markedContentStack = new MarkedContentStack(); private readonly MarkedContentStack markedContentStack = new MarkedContentStack();
private Stack<CurrentGraphicsState> graphicsStack = new Stack<CurrentGraphicsState>(); private Stack<CurrentGraphicsState> graphicsStack = new Stack<CurrentGraphicsState>();
@@ -88,7 +89,8 @@
IPageContentParser pageContentParser, IPageContentParser pageContentParser,
IFilterProvider filterProvider, IFilterProvider filterProvider,
ILog log, ILog log,
bool clipPaths) bool clipPaths,
PdfVector pageSize)
{ {
this.resourceStore = resourceStore; this.resourceStore = resourceStore;
this.userSpaceUnit = userSpaceUnit; this.userSpaceUnit = userSpaceUnit;
@@ -98,6 +100,7 @@
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider)); this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
this.log = log; this.log = log;
this.clipPaths = clipPaths; this.clipPaths = clipPaths;
this.pageSize = pageSize;
// initiate CurrentClippingPath to cropBox // initiate CurrentClippingPath to cropBox
var clippingSubpath = new PdfSubpath(); 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. // 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 // 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) if (pointSize < 0)
{ {
@@ -217,13 +220,17 @@
var boundingBox = font.GetBoundingBox(code); var boundingBox = font.GetBoundingBox(code);
var rotatedTransformationMatrix = rotation.Rotate(transformationMatrix);
var transformedGlyphBounds = PerformantRectangleTransformer var transformedGlyphBounds = PerformantRectangleTransformer
.Transform(renderingMatrix, textMatrix, rotatedTransformationMatrix, boundingBox.GlyphBounds); .Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds);
var transformedPdfBounds = PerformantRectangleTransformer 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 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. // 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'."); 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); var rotation = new PageRotationDegrees(pageTreeMembers.Rotation);
if (dictionary.TryGet(NameToken.Rotate, pdfScanner, out NumericToken rotateToken)) if (dictionary.TryGet(NameToken.Rotate, pdfScanner, out NumericToken rotateToken))
{ {
rotation = new PageRotationDegrees(rotateToken.Int); rotation = new PageRotationDegrees(rotateToken.Int);
} }
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
var stackDepth = 0; var stackDepth = 0;
while (pageTreeMembers.ParentResources.Count > 0) while (pageTreeMembers.ParentResources.Count > 0)
@@ -72,6 +72,19 @@
stackDepth++; 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); UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary);
PageContent content = default(PageContent); PageContent content = default(PageContent);
@@ -108,7 +121,7 @@
} }
} }
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths); content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
} }
else else
{ {
@@ -121,7 +134,7 @@
var bytes = contentStream.Decode(filterProvider); 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, 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, 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), var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentBytes),
log); log);
@@ -146,7 +159,8 @@
pageContentParser, pageContentParser,
filterProvider, filterProvider,
log, log,
clipPaths); clipPaths,
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height));
return context.Process(pageNumber, operations); return context.Process(pageNumber, operations);
} }