在 PDFBox 中,如何更改 PDRectangle 对象的原点 (0,0)?

In PDFBox, how to change the origin (0,0) point of a PDRectangle object?

情况:
在 PDFBox 中,PDRectangle 对象的默认原点 (0,0) 似乎是页面的左下角。

例如,下面的代码在页面的左下角给你一个正方形,每边长 100 个单位。

PDRectangle rectangle = new PDRectangle(0, 0, 100, 100);

问题:
是否可以将原点更改为左上角,例如,上面的代码将在页面的左上角为您提供相同的正方形?

我问的原因:
我正在使用 PDFTextStripper 获取文本的坐标(通过使用提取的 TextPosition 对象的 getX() 和 getY() 方法)。从 TextPosition 对象检索到的坐标似乎在左上角有一个原点 (0,0)。我希望我的 PDRectangle 对象的坐标与我的 TextPosition 对象的坐标具有相同的原点。

我试图通过 "page height minus Y-coordinate" 调整我的 PDRectangle 的 Y 坐标。这给了我想要的结果,但它并不优雅。我想要一个优雅的解决方案。

注意: 有人问过类似的问题。答案是我试过的,这不是最优雅的。 how to change the coordiantes of a text in a pdf page from lower left to upper left

您可以稍微更改坐标系,但很可能最终事情不会变得更优雅。

首先...

首先让我们澄清一些误解:

你假设

In PDFBox, PDRectangle objects' default origin (0,0) seems to be the lower-left corner of a page.

并非所有情况都如此,只是经常如此。

包含显示页面区域(在纸上或屏幕上)的区域通常由相关页面的 CropBox 条目定义:

CropBox rectangle (Optional; inheritable) A rectangle, expressed in default user space units, that shall define the visible region of default user space. When the page is displayed or printed, its contents shall be clipped (cropped) to this rectangle and then shall be imposed on the output medium in some implementation-defined manner.

... The positive x axis extends horizontally to the right and the positive y axis vertically upward, as in standard mathematical practice (subject to alteration by the Rotate entry in the page dictionary).

... In PostScript, the origin of default user space always corresponds to the lower-left corner of the output medium. While this convention is common in PDF documents as well, it is not required; the page dictionary’s CropBox entry can specify any rectangle of default user space to be made visible on the medium.

因此,原点(0,0)实际上可以在任何地方,它可能在左下方,在上方左侧、页面中间甚至远远超出显示的页面区域。

并且通过旋转条目,该区域甚至可以旋转( 90°、180° 或 270°)。

将原点(如您所观察到的那样)放在左下方只是惯例。

而且你好像认为坐标系是不变的。也不是这样,有一些操作可以让用户space坐标系发生巨大的变化,你可以平移,旋转,镜像,倾斜,and/or缩放!

因此,即使一开始坐标系是通常的坐标系,原点在左下角,x 轴向右,y 轴向上,它可能会以某种方式更改为页面内容中的奇怪内容描述。在那里绘制矩形 new PDRectangle(0, 0, 100, 100) 可能会在页面中心的右侧产生一些菱形。

你能做什么...

如您所见,PDF 用户 space 中的坐标是一个非常动态的问题。你能做些什么来驯服这种情况,取决于你在其中使用矩形的上下文。

不幸的是,您对自己所做工作的描述相当含糊。因此,这也会有些模糊。

页面内容中的坐标

如果你想在现有页面上绘制一些矩形,你首先需要一个页面内容流来写入,即一个 PDPageContentStream 实例,并且应该以保证原用户space坐标系未被扰乱。您可以通过使用带有三个布尔参数的构造函数将它们全部设置为 true:

来获得这样的实例
PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);

然后您可以对坐标系应用变换。您希望左上角为原点,y 值向下增加。如果页面裁剪框告诉你左上角有坐标(xtl,ytl),那么你申请

contentStream.concatenate2CTM(new AffineTransform(1, 0, 0, -1, xtl, ytl));

从这里开始你就有了一个你想要的坐标系,原点在左上角,y 坐标是镜像的。

不过请注意一件事:如果您也打算绘制文本,那么不仅文本插入点 y 坐标会被镜像,而且文本本身也会被镜像,除非您通过添加同样镜像的文本矩阵来抵消它!因此,如果你想添加很多文本,这可能没有你想要的那么优雅。

注释坐标

如果您不想在内容流中使用矩形而是为了添加注释,则您不受上述转换的约束,但您也不能使用它。

因此,在这种情况下,您必须按原样使用裁剪框并相应地变换矩形。

为什么PDFBox文本提取坐标是原样

本质上,为了以正确的顺序将文本行放在一起并正确地对行进行排序,您不想要这种奇怪的情况,而是想要一个简单的稳定坐标系。一些 PDFBox 开发人员为此选择了左上角原点、y 轴向下增加的变体,因此 TextPosition 坐标已标准化为该方案。

在我看来,更好的选择是使用默认用户 space 坐标,以便更轻松地重复使用坐标。因此,您可能想尝试使用 textPosition.getTextMatrix().getTranslateX()textPosition.getTextMatrix().getTranslateY() 以获得 TextPosition textPosition

以下似乎是 "adjust" TextPosition 坐标的最佳方式:

x_adjusted =  x_original + page.findCropBox().getLowerLeftX();
y_adjusted = -y_original + page.findCropBox().getUpperRightY();

其中 pageTextPosition 对象所在的 PDPage

添加 PDF 的高度(最简单的解决方案)

接受的答案给我带来了一些问题。此外,镜像文本并为此进行调整对我来说似乎不是正确的解决方案。所以这就是我想出的,到目前为止,它工作得非常顺利。

解决方案(示例如下):

  • x=0y=0 是左上角的纸上绘图时,使用原始点调用 getAdjustedPoints(...) 方法。
  • 此方法将return浮动数组(长度为4),可用于绘制矩形
  • 数组顺序为x、y、宽度和高度。只需通过 addRect(...) 方法

private float[] getAdjustedPoints(PDPage page, float x, float y, float width, float height) {
    float resizedWidth = getSizeFromInches(width);
    float resizedHeight = getSizeFromInches(height);
    return new float[] {
            getAdjustedX(page, getSizeFromInches(x)),
            getAdjustedY(page, getSizeFromInches(y)) - resizedHeight,
            resizedWidth, resizedHeight
    };
}

private float getSizeFromInches(float inches) {
    // 72 is POINTS_PER_INCH - it's defined in the PDRectangle class
    return inches * 72f;
}

private float getAdjustedX(PDPage page, float x) {
    return x + page.getCropBox().getLowerLeftX();
}

private float getAdjustedY(PDPage page, float y) {
    return -y + page.getCropBox().getUpperRightY();
}

示例:

private PDPage drawPage1(PDDocument document) {
    PDPage page = new PDPage(PDRectangle.LETTER);

    try {
        // Gray Color Box
        PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false, false);
        contentStream.setNonStrokingColor(Color.decode(MyColors.Gallery));
        float [] p1 = getAdjustedPoints(page, 0f, 0f, 8.5f, 1f);
        contentStream.addRect(p1[0], p1[1], p1[2], p1[3]);
        contentStream.fill();

        // Disco Color Box
        contentStream.setNonStrokingColor(Color.decode(MyColors.Disco));
        p1 = getAdjustedPoints(page, 4.5f, 1f, 4, 0.25f);
        contentStream.addRect(p1[0], p1[1], p1[2], p1[3]);
        contentStream.fill();

        contentStream.close();
    } catch (Exception e) { }

    return page;
}

如您所见,我画了 2 个矩形框。
为了绘制它,我使用了以下坐标,假设 x=0y=0 位于左上角。

灰色彩盒: x=0, y=0, w=8.5, h=1
迪斯科彩盒: x=4.5 y=1, w=4, h=0.25

这是我的结果图片。