I am writing a desktop WPF app to allow people to open up scanned PDF files, rotate them if need be, and then redact them if need be. The page is rendered to the screen using PDFium so the user can see what needs to be done. If it needs to be rotated, they click the rotate button to rotate it. If it needs to be redacted, they click on the appropriate button and then use the mouse to draw a System.Windows.Shapes.Rectangle on a Canvas. Then they click the save button to save the redaction (or redactions) to the pdf file. The actual changes to the PDF are made using PDFSharp v1.50.4000-beta3b downloaded through NuGet in Visual Studio 2013.
If the page is right side up, IE the rotate value is 0, then everything works fine. I can draw boxes all over the place with no problems. The issue arises when the rotate value is anything other than 0. If I rotate the page 90 degrees in either direction (rotate = 90 or -90), then when I try to draw the box on the page it messes things up. It seems to be swapping the height and width (turning it from landscape to portrait or vice versa) of the page without changing the content of the page. Then it draws the rectangle at the point it would be at if the page was rotated another 90 degrees.
To hopefully better demonstrate what I mean, here's an example: I've got a pdf page that is the standard size (A4, Letter, doesn't matter). It has a big smiley face on the top third of the file and text on the remainder and the rotate setting is 0 and the orientation is Portrait. I open it up in my program and rotate it 90 degrees. Now it is landscape and the smiley face is sideways on the right third of the page. I try and draw a box in the upper right corner of the page. When I click the save button, it changes the file and now it displays in portrait orientation however the content didn't change so now the Smiley face is invisible off of the right edge of the page. The box I had tried to place in the upper right corner acts as if it has been rotated and it is now in the lower right corner. If I make it a nice oblong rectangle, I can see that it really does look as though it's been rotated with the whole page but without the content. If I do it again, with another box in the upper right corner and then click save, it will swap the height and width again and rotate my box into a position 90 degrees off from where I placed it. Now I can see the smiley face again but the box still isn't where I want it.
Also, if the page rotation is 180, then when it saves the box, it some how rotates the position it's supposed to be in 180 degrees. So if my smiley face is upside down on the bottom of the page and I draw a box over his eyes (at the bottom of the page), it saves the box at the top of the page.
The weirdest part is that it was working perfectly a few weeks ago and now it isn't. From my testing, it appears as though the change is somehow being made in the PdfDocument.Save() method because before that point, the coordinates of the rectangle are what they should be for the current orientation/position of the page.
Anyways, now that I've explained the problem, here's my code.
First we have the code that handles the rotation. It's in a helper class and it stores the path to the file and a total page count. It takes in a list of page numbers to rotate. Also, I have to set the orientation to portrait (whether it should be or not) because PDFSharp is automatically setting that elsewhere but if I set it manually here, it rotates the pages properly and if I don't set it here, the page content will rotate without changing the size/orientation of the page itself.
public bool RotatePages(List<int> pageNums)
{
if (pageNums.Count > 0)
{
PdfDocument currDoc = PdfReader.Open(fullPath, PdfDocumentOpenMode.Modify);
for (int i = 0; i < totalPageCount; i++)
{
PdfPage newPage = currDoc.Pages[i]; //newDoc.AddPage();
if (pageNums.Contains(i))
{
newPage.Orientation = PdfSharp.PageOrientation.Portrait;
newPage.Rotate = (newPage.Rotate + 90) % 360;
}
}
currDoc.Save(fullPath);
return true;
}
else
return false;
}
Next is the code to draw the redaction boxes. It takes in a list of System.Windows.Rect objects, a list of colors, a page number to mark, and a matrix. The matrix is because the pdf is rendered to an image but the rectangles are drawn by a user to a Canvas. The image can be zoomed in on or panned around and the matrix stores those transformations so that I can match the position of the rectangle on the Canvas to the appropriate point on the image/pdf. It works perfectly if the page rotation is 0.
public bool Redact(List<Rect> redactions, List<System.Windows.Media.Color> redactionColors, System.Windows.Media.Matrix matrix, int pageNum)
{
if (pageNum >= 0 && pageNum < totalPageCount && redactions.Count > 0 && redactions.Count == redactionColors.Count)
{
PdfDocument currDoc = PdfReader.Open(fullPath, PdfDocumentOpenMode.Modify);
PdfPage newPage = currDoc.Pages[pageNum];
XGraphics gfx = XGraphics.FromPdfPage(newPage);
XBrush brush = null;
for (int i = 0; i < redactions.Count; i++)
{
Rect redaction = redactions[i];
System.Windows.Media.Color redactionColor = redactionColors[i];
redaction.X = redaction.X / (matrix.OffsetX / newPage.Width);
redaction.Y = redaction.Y / (matrix.OffsetY / newPage.Height);
redaction.Width = redaction.Width / (matrix.OffsetX / newPage.Width);
redaction.Height = redaction.Height / (matrix.OffsetY / newPage.Height);
redaction.Width = redaction.Width / matrix.M11;
redaction.Height = redaction.Height / matrix.M12;
brush = new XSolidBrush(XColor.FromArgb(redactionColor.A, redactionColor));
gfx.DrawRectangle(brush, redaction);
}
gfx.Save();
currDoc.Save(fullPath);
return true;
}
else
return false;
}
In the matrix (and no I'm not using it for matrix math, just using it to pass data around rather than using 6 ints/doubles, yes I am aware that it's lousy coding practice but fixing it is a rather low priority):
M11 = the x scale transform
M12 = the y scale transform
M21 = the x translate transform
M22 = the y translate transform
OffsetX = the actual width of the image control
OffsetY = the actual height of the image control
As near as I can tell by walking through step by step, my math and everything looks and works exactly as it should until currDoc.Save(fullPath);
and then it magically gets the wrong values. If I break program execution at any time before that line, the actual file doesn't get a box but the moment it passes that line it messes up.
I have no idea what's going on here or how to fix it. It was previously working and I don't remember what I did to change it so it stopped working. I've been searching for a solution all day with no luck so far. Any help would be greatly appreciated.