【问题标题】:Text coordinates when stripping from PDFBox从 PDFBox 中剥离时的文本坐标
【发布时间】:2018-02-15 05:37:56
【问题描述】:

我正在尝试使用 PDFBox 从 pdf 文件中提取带有坐标的文本。

我混合了一些在互联网上找到的方法/信息(stackoverflow 也是),但我的坐标问题似乎不正确。例如,当我尝试使用坐标在 tex 上绘制矩形时,矩形会在其他地方绘制。

这是我的代码(请不要评判风格,写得很快就是为了测试)

TextLine.java

    import java.util.List;
    import org.apache.pdfbox.text.TextPosition;

    /**
     *
     * @author samue
     */
    public class TextLine {
        public List<TextPosition> textPositions = null;
        public String text = "";
    }

myStripper.java

    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.pdmodel.PDPage;
    import org.apache.pdfbox.text.PDFTextStripper;
    import org.apache.pdfbox.text.TextPosition;

    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */

    /**
     *
     * @author samue
     */
    public class myStripper extends PDFTextStripper {
        public myStripper() throws IOException
        {
        }

        @Override
        protected void startPage(PDPage page) throws IOException
        {
            startOfLine = true;
            super.startPage(page);
        }

        @Override
        protected void writeLineSeparator() throws IOException
        {
            startOfLine = true;
            super.writeLineSeparator();
        }

        @Override
        public String getText(PDDocument doc) throws IOException
        {
            lines = new ArrayList<TextLine>();
            return super.getText(doc);
        }

        @Override
        protected void writeWordSeparator() throws IOException
        {
            TextLine tmpline = null;

            tmpline = lines.get(lines.size() - 1);
            tmpline.text += getWordSeparator();

            super.writeWordSeparator();
        }


        @Override
        protected void writeString(String text, List<TextPosition> textPositions) throws IOException
        {
            TextLine tmpline = null;

            if (startOfLine) {
                tmpline = new TextLine();
                tmpline.text = text;
                tmpline.textPositions = textPositions;
                lines.add(tmpline);
            } else {
                tmpline = lines.get(lines.size() - 1);
                tmpline.text += text;
                tmpline.textPositions.addAll(textPositions);
            }

            if (startOfLine)
            {
                startOfLine = false;
            }
            super.writeString(text, textPositions);
        }

        boolean startOfLine = true;
        public ArrayList<TextLine> lines = null;

    }

AWT 按钮上的点击事件

 private void jButton1MouseClicked(java.awt.event.MouseEvent evt) {                                      
    // TODO add your handling code here:
    try {
        File file = new File("C:\\Users\\samue\\Desktop\\mwb_I_201711.pdf");
        PDDocument doc = PDDocument.load(file);

        myStripper stripper = new myStripper();

        stripper.setStartPage(1); // fix it to first page just to test it
        stripper.setEndPage(1);
        stripper.getText(doc);

        TextLine line = stripper.lines.get(1); // the line i want to paint on

        float minx = -1;
        float maxx = -1;

        for (TextPosition pos: line.textPositions)
        {
            if (pos == null)
                continue;

            if (minx == -1 || pos.getTextMatrix().getTranslateX() < minx) {
                minx = pos.getTextMatrix().getTranslateX();
            }
            if (maxx == -1 || pos.getTextMatrix().getTranslateX() > maxx) {
                maxx = pos.getTextMatrix().getTranslateX();
            }
        }

        TextPosition firstPosition = line.textPositions.get(0);
        TextPosition lastPosition = line.textPositions.get(line.textPositions.size() - 1);

        float x = minx;
        float y = firstPosition.getTextMatrix().getTranslateY();
        float w = (maxx - minx) + lastPosition.getWidth();
        float h = lastPosition.getHeightDir();

        PDPageContentStream contentStream = new PDPageContentStream(doc, doc.getPage(0), PDPageContentStream.AppendMode.APPEND, false);

        contentStream.setNonStrokingColor(Color.RED);
        contentStream.addRect(x, y, w, h);
        contentStream.fill();
        contentStream.close();

        File fileout = new File("C:\\Users\\samue\\Desktop\\pdfbox.pdf");
        doc.save(fileout);
        doc.close();
    } catch (Exception ex) {

    }
}                                     

有什么建议吗?我做错了什么?

【问题讨论】:

  • 我还没看懂你的代码(现在必须去睡觉了)。请注意,在 PDF 中,y = 0 是底部,而不是顶部。这是一个示例,可能有助于理解如何使用文本提取坐标:svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/…
  • 您是否尝试过将PDPageContentStream 构造函数与另一个布尔参数resetContext 一起使用并将其设置为true
  • 是的,我知道 0 是底部,这就是我使用 .getTextMatrix().getTranslateY() 而不是 getY() 或 getYDirAdj() 的原因。我尝试使用 resetContext 但没有帮助。现在我去看看那个源代码,我会更新的,谢谢
  • 我刚刚测试了您的代码,它在示例 PDF 上确实可以正常工作(嗯,它只涵盖了从基线向上的文本,但这是意料之中的)。因此,矩形在在别处绘制的 PDF 中存在一些不同。如果您使用我在之前的评论中指出的PDPageContentStream,我认为这将得到解决。但是,如果没有您观察到该问题的示例 PDF,我无法确定。因此,请分享一个示例 PDF。
  • 我尝试了您链接中的代码。它正在工作,但是在 PNG 上绘制形状,而不是在流上绘制矩形。我正在尝试将形状转换为矩形,但我遇到了一些困难 ^^'' 无论如何 PDF 就是这个:download-a.akamaihd.net/files/media_mwb/b7/mwb_I_201711.pdf 我想是关于字体或字体转换的东西

标签: java pdfbox


【解决方案1】:

以下代码对我有用:

    // Definition of font baseline, ascent, descent: https://en.wikipedia.org/wiki/Ascender_(typography)
    //
    // The origin of the text coordinate system is the top-left corner where Y increases downward.
    // TextPosition.getX(), getY() return the baseline.
    TextPosition firstLetter = textPositions.get(0);
    TextPosition lastLetter = textPositions.get(textPositions.size() - 1);

    // Looking at LegacyPDFStreamEngine.showGlyph(), ascender and descender heights are calculated like
    // CapHeight: https://stackoverflow.com/a/42021225/14731
    float ascent = firstLetter.getFont().getFontDescriptor().getAscent() / 1000 * lastLetter.getFontSize();
    Point topLeft = new Point(firstLetter.getX(), firstLetter.getY() - ascent);

    float descent = lastLetter.getFont().getFontDescriptor().getDescent() / 1000 * lastLetter.getFontSize();
    // Descent is negative, so we need to negate it to move downward.
    Point bottomRight = new Point(lastLetter.getX() + lastLetter.getWidth(),
        lastLetter.getY() - descent);

    float descender = lastLetter.getFont().getFontDescriptor().getDescent() / 1000 * lastLetter.getFontSize();
    // Descender height is negative, so we need to negate it to move downward
    Point bottomRight = new Point(lastLetter.getX() + lastLetter.getWidth(),
        lastLetter.getY() - descender);

换句话说,我们正在创建一个从字体的上升到下降的边界框。

如果您想以左下角的原点呈现这些坐标,请参阅https://stackoverflow.com/a/28114320/14731 了解更多详细信息。您需要应用这样的转换:

contents.transform(new Matrix(1, 0, 0, -1, 0, page.getHeight()));

【讨论】:

  • 遗憾的是,上升/下降等并不总是可靠的。如果您需要精确的边界,请参阅 DrawPrintTextLocations.java 示例中的青色矩形。
  • @TilmanHausherr 这是很棒的代码。为什么这个功能没有直接嵌入到 PDFBox 的 API 中?意思是,为什么它位于示例代码中而不是(比如说)TextPosition 上的方法?
  • 我正在考虑将其添加到 LegacyPDFStreamEngine 代码中。然而,很多文本提取结果正在发生变化。而且它也不完美,它不适用于 type3 字体。最后:还有很多事情要做……比如回答用户问题、修复错误等。
【解决方案2】:

这只是过度PdfTextStripper 坐标归一化的另一种情况。就像您一样,我曾认为通过使用TextPosition.getTextMatrix()(而不是getX()getY)可以获得实际坐标,但是不,即使这些矩阵值也必须更正(至少在PDFBox 2.0.x 中,我没有检查 1.8.x),因为矩阵乘以平移,使裁剪框的左下角成为原点。

因此,在您的情况下(裁剪框的左下角不是原点),您必须更正这些值,例如通过替换

        float x = minx;
        float y = firstPosition.getTextMatrix().getTranslateY();

通过

        PDRectangle cropBox = doc.getPage(0).getCropBox();

        float x = minx + cropBox.getLowerLeftX();
        float y = firstPosition.getTextMatrix().getTranslateY() + cropBox.getLowerLeftY();

而不是

你现在得到

但显然,您还必须稍微修正高度。这是由于PdfTextStripper 确定文本高度的方式:

    // 1/2 the bbox is used as the height todo: why?
    float glyphHeight = bbox.getHeight() / 2;

(来自LegacyPDFStreamEngine中的showGlyph(...)PdfTextStripper的父类)

虽然字体边界框确实通常太大,但通常一半是不够的。

【讨论】:

  • 谢谢!这是解决问题的方法,非常感谢!无论如何,我正在尝试做更多基于单个字形的事情,当我完成时我会发布它。再次感谢!
  • 如果有任何答案“是问题的解决方案”,请接受(点击左上角的勾号)。
  • 对不起,我没注意到:P
  • @SamueleDiella 找到解决方案了吗?我也面临同样的问题。如果您有任何需要,请帮助我
  • @MukeshMethaniya 正如您在 Samuele 的评论中看到的那样,解决方案是使用我的答案中提出的改编。
猜你喜欢
  • 2018-04-03
  • 2011-08-12
  • 2019-02-14
  • 1970-01-01
  • 2022-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多