简体   繁体   English

旋转页面后,如何让 PDFSharp 在正确的位置绘制内容?

[英]How do I get PDFSharp to draw things in the right position after rotating the page?

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.我正在编写一个桌面 WPF 应用程序,以允许人们打开扫描的 PDF 文件,根据需要旋转它们,然后根据需要编辑它们。 The page is rendered to the screen using PDFium so the user can see what needs to be done.该页面使用 PDFium 呈现到屏幕上,因此用户可以看到需要做什么。 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.如果需要编辑,他们单击相应的按钮,然后使用鼠标在 Canvas 上绘制 System.Windows.Shapes.Rectangle。 Then they click the save button to save the redaction (or redactions) to the pdf file.然后他们单击保存按钮将编辑(或多个编辑)保存到 pdf 文件。 The actual changes to the PDF are made using PDFSharp v1.50.4000-beta3b downloaded through NuGet in Visual Studio 2013.对 PDF 的实际更改是使用通过 Visual Studio 2013 中的 NuGet 下载的 PDFSharp v1.50.4000-beta3b 进行的。

If the page is right side up, IE the rotate value is 0, then everything works fine.如果页面正面朝上,IE 旋转值为 0,则一切正常。 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.当旋转值不是 0 时就会出现问题。如果我将页面沿任一方向旋转 90 度(旋转 = 90 或 -90),那么当我尝试在页面上绘制框时它会把事情搞砸。 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.然后,如果页面再旋转 90 度,它将在该点绘制矩形。

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).为了更好地展示我的意思,这里有一个例子:我有一个标准尺寸的 pdf 页面(A4,Letter,无所谓)。 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.它在文件的前三分之一处有一个大笑脸,其余部分有文本,旋转设置为 0,方向为纵向。 I open it up in my program and rotate it 90 degrees.我在我的程序中打开它并将其旋转 90 度。 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.如果我再做一次,右上角有另一个框,然后单击保存,它会再次交换高度和宽度,并将我的框旋转到与我放置它的位置相差 90 度的位置。 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.此外,如果页面旋转 180 度,那么当它保存框时,它会以某种方式将其应有的位置旋转 180 度。 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.从我的测试来看,似乎在 PdfDocument.Save() 方法中以某种方式进行了更改,因为在那之前,矩形的坐标是页面当前方向/位置的坐标。

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.此外,我必须将方向设置为纵向(无论是否应该),因为 PDFSharp 会自动在其他地方设置它,但如果我在这里手动设置它,它会正确旋转页面,如果我不在这里设置它,页面内容将旋转而不改变页面本身的大小/方向。

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.它接受一个 System.Windows.Rect 对象列表、一个颜色列表、一个要标记的页码和一个矩阵。 The matrix is because the pdf is rendered to an image but the rectangles are drawn by a user to a Canvas.矩阵是因为 pdf 被渲染为图像,而矩形是由用户绘制到 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.图像可以放大或平移,矩阵存储这些变换,以便我可以将画布上矩形的位置与图像/pdf 上的适当点相匹配。 It works perfectly if the page rotation is 0.如果页面旋转为 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):在矩阵中(不,我没有将它用于矩阵数学,只是使用它来传递数据而不是使用 6 个整数/双精度数,是的,我知道这是糟糕的编码实践,但修复它的优先级相当低):

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);据我所知,一步一步走,我的数学和一切看起来和工作都完全按照它应该的方式进行,直到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.任何帮助将不胜感激。

So I finally figured it out.所以我终于想通了。 Apparently PDFSharp has some issues with how it handles page rotation.显然,PDFSharp 在处理页面旋转方面存在一些问题。 To fix it, I first had to tweak the source code for PDFSharp.要修复它,我首先必须调整 PDFSharp 的源代码。

I had to comment out the code that was swapping the height/width values when the page was set to landscape.当页面设置为横向时,我不得不注释掉交换高度/宽度值的代码。 Apparently PDFSharp uses a "orientation" variable to store the orientation despite the fact that PDFs don't have such a setting.显然 PDFSharp 使用“方向”变量来存储方向,尽管 PDF 没有这样的设置。 By commenting out those lines, I finally started getting the right height and width for rotated pages.通过注释掉这些行,我终于开始获得旋转页面的正确高度和宽度。 This is a change to PdfPage.cs.这是对 PdfPage.cs 的更改。

public XUnit Height
    {
        get
        {
            PdfRectangle rect = MediaBox;
            //return _orientation == PageOrientation.Portrait ? rect.Height : rect.Width;
            return rect.Height;
        }
        set
        {
            PdfRectangle rect = MediaBox;
            //if (_orientation == PageOrientation.Portrait)
                MediaBox = new PdfRectangle(rect.X1, 0, rect.X2, value);
            //else
            //    MediaBox = new PdfRectangle(0, rect.Y1, value, rect.Y2);
            _pageSize = PageSize.Undefined;
        }
    }


public XUnit Width
    {
        get
        {
            PdfRectangle rect = MediaBox;
            //return _orientation == PageOrientation.Portrait ? rect.Width : rect.Height;
            return rect.Width;
        }
        set
        {
            PdfRectangle rect = MediaBox;
            //if (_orientation == PageOrientation.Portrait)
                MediaBox = new PdfRectangle(0, rect.Y1, value, rect.Y2);
            //else
            //    MediaBox = new PdfRectangle(rect.X1, 0, rect.X2, value);
            _pageSize = PageSize.Undefined;
        }
    }

Then I had to comment out a few lines in the WriteObject method that were flipping the height and width values of the mediabox.然后我不得不在 WriteObject 方法中注释掉几行,这些行翻转了媒体框的高度和宽度值。 These are the lines I commented out.这些是我注释掉的行。 This stopped PDFSharp from flipping my rotated pages size every time I saved it.这阻止了 PDFSharp 在我每次保存时翻转我旋转的页面大小。

        //// HACK: temporarily flip media box if Landscape
        //PdfRectangle mediaBox = MediaBox;
        //// TODO: Take /Rotate into account
        //if (_orientation == PageOrientation.Landscape)
        //    MediaBox = new PdfRectangle(mediaBox.X1, mediaBox.Y1, mediaBox.Y2, mediaBox.X2);

...

//if (_orientation == PageOrientation.Landscape)
        //    MediaBox

Lastly, in my own code I had to change most of the redaction code to put the boxes in the right spot at the right size.最后,在我自己的代码中,我不得不更改大部分编辑代码,以将框以正确的大小放在正确的位置。 Getting the math right took forever and the code is messy but it works.获得正确的数学需要很长时间,代码很混乱但它有效。 Any suggestions on how to clean it up would be appreciated.任何有关如何清理它的建议将不胜感激。

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);
            int angle = currDoc.Pages[pageNum].Rotate;
            PdfPage oldPage = currDoc.Pages[pageNum];
            XBrush brush = null;
            XGraphics gfx = XGraphics.FromPdfPage(oldPage);
            XPoint pagePoint = new XPoint(0, 0);

            if (angle == 180)
            {
                pagePoint.X = oldPage.Width / 2;
                pagePoint.Y = oldPage.Height / 2;
                gfx.RotateAtTransform(180, pagePoint);
            }

            for (int i = 0; i < redactions.Count; i++)
            {
                Rect redaction = redactions[i];
                System.Windows.Media.Color redactionColor = redactionColors[i];
                double scaleValue = oldPage.Height / matrix.OffsetX;

                if (angle == 180 || angle == 0)
                {
                    redaction.X = redaction.X / (matrix.OffsetX / oldPage.Width);
                    redaction.Y = redaction.Y / (matrix.OffsetY / oldPage.Height);
                    redaction.Width = redaction.Width / (matrix.OffsetX / oldPage.Width);
                    redaction.Height = redaction.Height / (matrix.OffsetY / oldPage.Height);

                    redaction.Width = redaction.Width / matrix.M11;
                    redaction.Height = redaction.Height / matrix.M12;
                }
                else if (angle == 90 || angle == 270)
                {
                    Rect tempRect = redaction;

                    tempRect.X = redaction.X * scaleValue;
                    tempRect.Y = redaction.Y * scaleValue;

                    tempRect.Height = redaction.Height * scaleValue;
                    tempRect.Width = redaction.Width * scaleValue;

                    redaction.Width = tempRect.Height;
                    redaction.Height = tempRect.Width;

                    tempRect.Width = tempRect.Width / matrix.M11;
                    tempRect.Height = tempRect.Height / matrix.M12;

                    redaction.X = oldPage.Width - tempRect.Y - tempRect.Height;
                    redaction.Y = tempRect.X;

                    if (angle == 90)
                        gfx.RotateAtTransform(180, new XPoint(oldPage.Width / 2, oldPage.Height / 2));

                    redaction.Width = tempRect.Height;
                    redaction.Height = tempRect.Width;
                }

                brush = new XSolidBrush(XColor.FromArgb(redactionColor.A, redactionColor));
                gfx.DrawRectangle(brush, redaction);
            }

            gfx.Save();
            currDoc.Save(fullPath);

            return true;
        }
        else
            return false;
    }

So that's the solution I found that works for me.这就是我发现对我有用的解决方案。

This problem also persists when rotating page using Adobe Libraries, when you rotate a page and add annotations the annotations are all off.使用 Adobe Libraries 旋转页面时,此问题也仍然存在,当您旋转页面并添加注释时,注释全部关闭。 The easiest solution I have found is to rotate the page, extract the page, resave the individual page as a PDF/A and then then reinsert back into the pdf.我找到的最简单的解决方案是旋转页面,提取页面,将单个页面重新保存为 PDF/A,然后重新插入到 pdf 中。 By re-saving as a pdf/A it fixes all the underlying text coordinates on that page to the new rotation so that when you add annotations they are all where the should be.通过重新保存为 pdf/A,它将该页面上的所有基础文本坐标固定为新的旋转,以便在您添加注释时它们都在应有的位置。 Unfortunately I have not found a way of saving an existing pdf to pdf/A using pdfshap.不幸的是,我还没有找到使用 pdfshap 将现有 pdf 保存为 pdf/A 的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM