我试图使用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) {
}
}
有什么建议吗?我做错了什么?
答案 0 :(得分:4)
这只是过度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;
(来自showGlyph(...)
中的LegacyPDFStreamEngine
,PdfTextStripper
的父类
虽然字体边界框确实通常太大,但其中一半通常是不够的。
答案 1 :(得分:0)
以下代码对我有用:
// 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()));