mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-19 19:07:56 +08:00
#161 change rotation to fix values and page size
this doesn't account for images and pdf paths yet.
This commit is contained in:
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
@@ -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)
|
||||||
|
@@ -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 />
|
||||||
|
@@ -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.
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user