From 25314cc79dd07dcd72120c9d01fdfeea2b85d082 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sat, 18 Apr 2020 18:04:41 +0100 Subject: [PATCH] #161 change rotation to fix values and page size this doesn't account for images and pdf paths yet. --- ...Page180ClockwiseRotation - from PdfPig.jpg | Bin 0 -> 9491 bytes ...Page180ClockwiseRotation - from PdfPig.pdf | Bin 0 -> 7594 bytes ...Page270ClockwiseRotation - from PdfPig.jpg | Bin 0 -> 9382 bytes ...Page270ClockwiseRotation - from PdfPig.pdf | Bin 0 -> 7594 bytes ...ePage90ClockwiseRotation - from PdfPig.jpg | Bin 0 -> 9616 bytes ...ePage90ClockwiseRotation - from PdfPig.pdf | Bin 0 -> 7593 bytes .../GenerateLetterBoundingBoxImages.cs | 27 +++++++- .../Content/PageRotationDegrees.cs | 59 +++++++++++++++--- .../Graphics/ContentStreamProcessor.cs | 21 ++++--- src/UglyToad.PdfPig/Parser/PageFactory.cs | 28 ++++++--- 10 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage180ClockwiseRotation - from PdfPig.jpg create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage180ClockwiseRotation - from PdfPig.pdf create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage270ClockwiseRotation - from PdfPig.jpg create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage270ClockwiseRotation - from PdfPig.pdf create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage90ClockwiseRotation - from PdfPig.jpg create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage90ClockwiseRotation - from PdfPig.pdf diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage180ClockwiseRotation - from PdfPig.jpg b/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage180ClockwiseRotation - from PdfPig.jpg new file mode 100644 index 0000000000000000000000000000000000000000..364debc59ebd954ae5abd15bf93fbc6e81561d4d GIT binary patch literal 9491 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 $$9p7*JulyerFbBr(qWptr!RC@T> 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 IOI_jr0KY|Kj{pDw literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage270ClockwiseRotation - from PdfPig.jpg b/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage270ClockwiseRotation - from PdfPig.jpg new file mode 100644 index 0000000000000000000000000000000000000000..509d5a67fd390c9ed6451444ba7561576f342d0b GIT binary patch literal 9382 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 SXsw_cHRge7^~_HH9_{an6q@${ literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage270ClockwiseRotation - from PdfPig.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage270ClockwiseRotation - from PdfPig.pdf new file mode 100644 index 0000000000000000000000000000000000000000..621e173057702276d9475c77ee8448497853b6c0 GIT binary patch literal 7594 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 HyQ6;tr|=)5 literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage90ClockwiseRotation - from PdfPig.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/SinglePage90ClockwiseRotation - from PdfPig.pdf new file mode 100644 index 0000000000000000000000000000000000000000..945cd3324f92849fb86ffba620adc2af3b207260 GIT binary patch literal 7593 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 DX<}p? literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/GenerateLetterBoundingBoxImages.cs b/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/GenerateLetterBoundingBoxImages.cs index f05a4692..bf5f1e07 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/GenerateLetterBoundingBoxImages.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/GenerateLetterBoundingBoxImages.cs @@ -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) diff --git a/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs b/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs index 6e5b96b9..5a85e2dc 100644 --- a/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs +++ b/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs @@ -3,21 +3,23 @@ using System; using System.Diagnostics.Contracts; using Core; + using Geometry; /// /// Represents the rotation of a page in a PDF document defined by the page dictionary in degrees clockwise. /// public struct PageRotationDegrees : IEquatable { - 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); - /// /// The rotation of the page in degrees clockwise. /// public int Value { get; } + /// + /// Whether the rotation flips the x and y axes. + /// + public bool SwapsAxis => (Value == 90) || (Value == 270); + /// /// Get the rotation expressed in radians (anti-clockwise). /// @@ -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)); } /// diff --git a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs index fd7d6015..3eaafab0 100644 --- a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs @@ -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 graphicsStack = new Stack(); @@ -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. diff --git a/src/UglyToad.PdfPig/Parser/PageFactory.cs b/src/UglyToad.PdfPig/Parser/PageFactory.cs index 49f950aa..e6ad1b15 100644 --- a/src/UglyToad.PdfPig/Parser/PageFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PageFactory.cs @@ -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 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); }