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

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

我正在编写一个桌面 WPF 应用程序,让人们可以打开扫描的 PDF 文件,根据需要旋转它们,然后根据需要编辑它们。该页面使用 PDFium 呈现到屏幕上,因此用户可以看到需要做什么。如果需要旋转,他们点击旋转按钮来旋转它。如果需要编辑,他们单击相应的按钮,然后使用鼠标在 Canvas 上绘制 System.Windows.Shapes.Rectangle。然后他们单击保存按钮将编辑(或多个编辑)保存到 pdf 文件。对 PDF 的实际更改是使用 PDFSharp v1.50.4000-beta3b 于 Visual Studio 2013 年通过 NuGet 下载的。

如果页面正面朝上,IE 旋转值为0,则一切正常。我可以毫无问题地在各处画框。当旋转值不是 0 时就会出现问题。如果我将页面沿任一方向旋转 90 度(旋转 = 90 或 -90),那么当我尝试在页面上绘制框时它会把事情搞砸。它似乎在不更改页面内容的情况下交换页面的高度和宽度(将其从横向变为纵向,反之亦然)。然后它会在页面再旋转 90 度时绘制矩形。

为了更好地展示我的意思,这里有一个例子: 我有一个标准尺寸的 pdf 页面(A4、Letter,无所谓)。它在文件的前三分之一处有一个大笑脸,其余部分有文本,旋转设置为 0,方向为纵向。我在我的程序中打开它并将其旋转 90 度。现在它是横向的,笑脸在页面右侧的三分之一处。我尝试在页面的右上角画一个框。当我点击保存按钮时,它改变了文件,现在它以纵向显示,但内容没有改变,所以现在笑脸在页面的右边缘是不可见的。我试图放置在右上角的框就好像它已经旋转一样,现在位于右下角。如果我把它做成一个漂亮的椭圆形矩形,我可以看到它确实看起来好像整个页面都旋转了但没有内容。如果我再做一次,右上角有另一个框,然后单击保存,它会再次交换高度和宽度,并将我的框旋转到与我放置它的位置相差 90 度的位置。现在我又能看到笑脸了,但盒子还是不在我想要的位置。

此外,如果页面旋转 180 度,那么当它保存框时,它会以某种方式将其应有的位置旋转 180 度。因此,如果我的笑脸倒置在页面底部并且我在他的眼睛上画了一个框(在页面底部),它会保存页面顶部的框。

最奇怪的是几周前它还运行良好,现在却不行了。从我的测试来看,似乎更改是在 PdfDocument.Save() 方法中以某种方式进行的,因为在那之前,矩形的坐标是页面当前 orientation/position 的坐标.

无论如何,现在我已经解释了问题,这是我的代码。

首先我们有处理旋转的代码。它在一个助手 class 中,它存储文件的路径和总页数。它接受要旋转的页码列表。此外,我必须将方向设置为纵向(无论是否应该),因为 PDFSharp 会自动在其他地方设置它,但如果我在这里手动设置它,它会正确旋转页面,如果我不在这里设置它,页面内容将在不更改页面本身的 size/orientation 的情况下旋转。

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;
    }

接下来是绘制密文框的代码。它接受 System.Windows.Rect 对象列表、颜色列表、要标记的页码和矩阵。矩阵是因为 pdf 被渲染为图像,而矩形是由用户绘制为 Canvas。图像可以放大或平移,矩阵存储这些变换,以便我可以将 Canvas 上的矩形位置与 image/pdf 上的适当点匹配。如果页面旋转为 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;
    }

在矩阵中(不,我没有将它用于矩阵数学,只是用它来传递数据而不是使用 6 ints/doubles,是的,我知道这是糟糕的编码实践,但正在修复它是一个相当低的优先级):

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

据我一步一步的了解,我的数学和一切看起来和工作都完全正常,直到 currDoc.Save(fullPath); 然后它神奇地得到了错误的值。如果我在该行之前的任何时间中断程序执行,实际文件不会得到一个框,但当它通过该行时它会搞砸。

我不知道这里发生了什么或如何解决它。它以前是工作的,我不记得我做了什么来改变它,所以它停止工作了。到目前为止,我一直在寻找解决方案一整天都没有运气。任何帮助将不胜感激。

所以我终于想通了。显然,PDFSharp 在处理页面旋转方面存在一些问题。要修复它,我首先必须调整 PDFSharp 的源代码。

我不得不注释掉当页面设置为横向时交换 height/width 值的代码。显然 PDFSharp 使用 "orientation" 变量来存储方向,尽管 PDF 没有这样的设置。通过注释掉这些行,我终于开始获得旋转页面的正确高度和宽度。这是对 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;
        }
    }

然后我不得不注释掉 WriteObject 方法中翻转媒体框的高度和宽度值的几行。这些是我注释掉的行。这阻止了 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

最后,在我自己的代码中,我不得不更改大部分编辑代码,以将框以正确的大小放在正确的位置。获得正确的数学需要很长时间,代码很混乱但它有效。任何关于如何清理它的建议将不胜感激。

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;
    }

这就是我找到的适合我的解决方案。

使用 Adob​​e Libraries 旋转页面时,此问题仍然存在,当您旋转页面并添加注释时,注释全部关闭。我找到的最简单的解决方案是旋转页面,提取页面,将单个页面重新保存为 PDF/A,然后重新插入到 pdf 中。通过 re-saving 作为 pdf/A 它将该页面上的所有基础文本坐标固定为新的旋转,以便在您添加注释时它们都在应有的位置。不幸的是,我还没有找到使用 pdfshap 将现有 pdf 保存到 pdf/A 的方法。