From 33f92cd11c0fa965fb76f6304ab4c865933e5f65 Mon Sep 17 00:00:00 2001 From: BobLd Date: Tue, 2 Jun 2020 16:12:24 +0100 Subject: [PATCH] handle page rotation by updating initial TransformationMatrix --- .../Content/PageRotationDegrees.cs | 57 -------------- .../Graphics/ContentStreamProcessor.cs | 77 ++++++++++++++++--- 2 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs b/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs index 5a85e2dc..571cc408 100644 --- a/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs +++ b/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs @@ -66,63 +66,6 @@ Value = rotation; } - - [Pure] - 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 rectangle; - case 90: - cos = 0; - sin = 1; - dy = pageSize.Y; - break; - case 180: - cos = -1; - sin = 0; - dx = pageSize.X; - dy = pageSize.Y; - break; - case 270: - 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)); - } /// public override int GetHashCode() diff --git a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs index 98e0967a..362cade9 100644 --- a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs @@ -88,7 +88,7 @@ IPdfTokenScanner pdfScanner, IPageContentParser pageContentParser, IFilterProvider filterProvider, - ILog log, + ILog log, bool clipPaths, PdfVector pageSize) { @@ -108,10 +108,75 @@ var clippingPath = new PdfPath() { clippingSubpath }; clippingPath.SetClipping(FillingRule.NonZeroWinding); - graphicsStack.Push(new CurrentGraphicsState() { CurrentClippingPath = clippingPath }); + graphicsStack.Push(new CurrentGraphicsState() + { + CurrentTransformationMatrix = GetInitialMatrix(), + CurrentClippingPath = clippingPath + }); + ColorSpaceContext = new ColorSpaceContext(GetCurrentState, resourceStore); } + [System.Diagnostics.Contracts.Pure] + private TransformationMatrix GetInitialMatrix() + { + // TODO: this is a bit of a hack because I don't understand matrices + // TODO: use MediaBox (i.e. pageSize) or CropBox? + + /* + * 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 ] + * Warning: rotation is counter-clockwise here + * + * 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 (rotation.Value) + { + case 0: + cos = 1; + sin = 0; + break; + case 90: + cos = 0; + sin = 1; + dy = pageSize.Y; + break; + case 180: + cos = -1; + sin = 0; + dx = pageSize.X; + dy = pageSize.Y; + break; + case 270: + cos = 0; + sin = -1; + dx = pageSize.X; + break; + default: + throw new InvalidOperationException($"Invalid value for page rotation: {rotation.Value}."); + } + + return new TransformationMatrix( + cos, -sin, 0, + sin, cos, 0, + dx, dy, 1); + } + public PageContent Process(int pageNumberCurrent, IReadOnlyList operations) { pageNumber = pageNumberCurrent; @@ -225,19 +290,13 @@ } var boundingBox = font.GetBoundingBox(code); - + var transformedGlyphBounds = PerformantRectangleTransformer .Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds); var transformedPdfBounds = PerformantRectangleTransformer .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. // In modes that perform both filling and stroking, the effect is as if each glyph outline were filled and then stroked in separate operations.