mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-04-05 20:55:01 +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:
parent
b122bf0ca6
commit
25314cc79d
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,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)
|
||||
|
@ -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 />
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user