使用带有VB.NET的PDFBox检测粗体、斜体和贯穿文本

使用带有VB.NET的PDFBox检测粗体、斜体和贯穿文本,vb.net,pdfbox,Vb.net,Pdfbox,使用PDFBox提取PDF时,是否有办法保留文本格式 我有一个程序可以解析PDF文档以获取信息。当新版本的PDF发布时,作者使用粗体或斜体文本来表示新信息,并在指示的省略文本上划线或加下划线。在PDFbox中使用基本剥离器类会返回所有文本,但格式会被删除,因此我无法判断文本是新的还是省略的。我目前正在使用下面的项目示例代码: Dim doc As PDDocument = Nothing Try doc = PDDocument.load(RFPFilePath

使用PDFBox提取PDF时,是否有办法保留文本格式

我有一个程序可以解析PDF文档以获取信息。当新版本的PDF发布时,作者使用粗体或斜体文本来表示新信息,并在指示的省略文本上划线或加下划线。在PDFbox中使用基本剥离器类会返回所有文本,但格式会被删除,因此我无法判断文本是新的还是省略的。我目前正在使用下面的项目示例代码:

    Dim doc As PDDocument = Nothing

    Try
        doc = PDDocument.load(RFPFilePath)
        Dim stripper As New PDFTextStripper()

        stripper.setAddMoreFormatting(True)
        stripper.setSortByPosition(True)
        rtxt_DocumentViewer.Text = stripper.getText(doc)

    Finally
        If doc IsNot Nothing Then
            doc.close()
        End If
    End Try
如果我简单地将PDF文本复制并粘贴到保留格式的richtextbox中,我的解析代码就可以正常工作。我想通过编程的方式打开PDF,选择全部,复制,关闭文档,然后将其粘贴到我的richtextbox中,但这看起来很笨拙

正如OP在一篇评论中提到的,Java示例就可以了,而我只在Java中使用了PDFBox,这个答案以Java示例为特色。此外,本示例仅使用PDFBox版本1.8.11进行了开发和测试

自定义文本剥离器 正如在评论中已经提到的那样


OP示例文档中的粗体和斜体效果是通过使用不同的字体(包含字母的粗体或斜体版本)绘制文本生成的。示例文档中的下划线和横线效果是通过在文本行下方/下方绘制一个矩形生成的,该矩形具有文本行的宽度和非常小的高度。因此,要提取这些信息,必须扩展
PDFTextStripper
,以某种方式响应字体更改和文本附近的矩形

这是一个扩展
PDFTextStripper
的示例类,如下所示:

public class PDFStyledTextStripper extends PDFTextStripper
{
    public PDFStyledTextStripper() throws IOException
    {
        super();
        registerOperatorProcessor("re", new AppendRectangleToPath());
    }

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException
    {
        for (TextPosition textPosition : textPositions)
        {
            Set<String> style = determineStyle(textPosition);
            if (!style.equals(currentStyle))
            {
                output.write(style.toString());
                currentStyle = style;
            }
            output.write(textPosition.getCharacter());
        }
    }

    Set<String> determineStyle(TextPosition textPosition)
    {
        Set<String> result = new HashSet<>();

        if (textPosition.getFont().getBaseFont().toLowerCase().contains("bold"))
            result.add("Bold");

        if (textPosition.getFont().getBaseFont().toLowerCase().contains("italic"))
            result.add("Italic");

        if (rectangles.stream().anyMatch(r -> r.underlines(textPosition)))
            result.add("Underline");

        if (rectangles.stream().anyMatch(r -> r.strikesThrough(textPosition)))
            result.add("StrikeThrough");

        return result;
    }

    class AppendRectangleToPath extends OperatorProcessor
    {
        public void process(PDFOperator operator, List<COSBase> arguments)
        {
            COSNumber x = (COSNumber) arguments.get(0);
            COSNumber y = (COSNumber) arguments.get(1);
            COSNumber w = (COSNumber) arguments.get(2);
            COSNumber h = (COSNumber) arguments.get(3);

            double x1 = x.doubleValue();
            double y1 = y.doubleValue();

            // create a pair of coordinates for the transformation
            double x2 = w.doubleValue() + x1;
            double y2 = h.doubleValue() + y1;

            Point2D p0 = transformedPoint(x1, y1);
            Point2D p1 = transformedPoint(x2, y1);
            Point2D p2 = transformedPoint(x2, y2);
            Point2D p3 = transformedPoint(x1, y2);

            rectangles.add(new TransformedRectangle(p0, p1, p2, p3));
        }

        Point2D.Double transformedPoint(double x, double y)
        {
            double[] position = {x,y}; 
            getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
                    position, 0, position, 0, 1);
            return new Point2D.Double(position[0],position[1]);
        }
    }

    static class TransformedRectangle
    {
        public TransformedRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3)
        {
            this.p0 = p0;
            this.p1 = p1;
            this.p2 = p2;
            this.p3 = p3;
        }

        boolean strikesThrough(TextPosition textPosition)
        {
            Matrix matrix = textPosition.getTextPos();
            // TODO: This is a very simplistic implementation only working for horizontal text without page rotation
            // and horizontal rectangular strikeThroughs with p0 at the left bottom and p2 at the right top

            // Check if rectangle horizontally matches (at least) the text
            if (p0.getX() > matrix.getXPosition() || p2.getX() < matrix.getXPosition() + textPosition.getWidth() - textPosition.getFontSizeInPt() / 10.0)
                return false;
            // Check whether rectangle vertically is at the right height to underline
            double vertDiff = p0.getY() - matrix.getYPosition();
            if (vertDiff < 0 || vertDiff > textPosition.getFont().getFontDescriptor().getAscent() * textPosition.getFontSizeInPt() / 1000.0)
                return false;
            // Check whether rectangle is small enough to be a line
            return Math.abs(p2.getY() - p0.getY()) < 2;
        }

        boolean underlines(TextPosition textPosition)
        {
            Matrix matrix = textPosition.getTextPos();
            // TODO: This is a very simplistic implementation only working for horizontal text without page rotation
            // and horizontal rectangular underlines with p0 at the left bottom and p2 at the right top

            // Check if rectangle horizontally matches (at least) the text
            if (p0.getX() > matrix.getXPosition() || p2.getX() < matrix.getXPosition() + textPosition.getWidth() - textPosition.getFontSizeInPt() / 10.0)
                return false;
            // Check whether rectangle vertically is at the right height to underline
            double vertDiff = p0.getY() - matrix.getYPosition();
            if (vertDiff > 0 || vertDiff < textPosition.getFont().getFontDescriptor().getDescent() * textPosition.getFontSizeInPt() / 500.0)
                return false;
            // Check whether rectangle is small enough to be a line
            return Math.abs(p2.getY() - p0.getY()) < 2;
        }

        final Point2D p0, p1, p2, p3;
    }

    final List<TransformedRectangle> rectangles = new ArrayList<>();
    Set<String> currentStyle = Collections.singleton("Undefined");
}
(从,从测试方法调用
testExtractStyledFromExampleDocument

一个人得到结果

[]This is an example of plain text 
 
[Bold]This is an example of bold text 
[] 
[Underline]This is an example of underlined text[] 
 
[Italic]This is an example of italic text  
[] 
[StrikeThrough]This is an example of strike through text[]  
 
[Italic, Bold]This is an example of bold, italic text 
对于OP的示例文档


PS与此同时,
PDFStyledTextStripper
的代码已稍作更改,以用于github发行版中共享的示例文档,特别是其内部类的代码
TransformedRectangle
,cf..

正如OP在一篇评论中提到的,Java示例就可以了,我还只是将PDFBox与Java一起使用,这个答案以Java示例为特色。此外,本示例仅使用PDFBox版本1.8.11进行了开发和测试

自定义文本剥离器 正如在评论中已经提到的那样


OP示例文档中的粗体和斜体效果是通过使用不同的字体(包含字母的粗体或斜体版本)绘制文本生成的。示例文档中的下划线和横线效果是通过在文本行下方/下方绘制一个矩形生成的,该矩形具有文本行的宽度和非常小的高度。因此,要提取这些信息,必须扩展
PDFTextStripper
,以某种方式响应字体更改和文本附近的矩形

这是一个扩展
PDFTextStripper
的示例类,如下所示:

public class PDFStyledTextStripper extends PDFTextStripper
{
    public PDFStyledTextStripper() throws IOException
    {
        super();
        registerOperatorProcessor("re", new AppendRectangleToPath());
    }

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException
    {
        for (TextPosition textPosition : textPositions)
        {
            Set<String> style = determineStyle(textPosition);
            if (!style.equals(currentStyle))
            {
                output.write(style.toString());
                currentStyle = style;
            }
            output.write(textPosition.getCharacter());
        }
    }

    Set<String> determineStyle(TextPosition textPosition)
    {
        Set<String> result = new HashSet<>();

        if (textPosition.getFont().getBaseFont().toLowerCase().contains("bold"))
            result.add("Bold");

        if (textPosition.getFont().getBaseFont().toLowerCase().contains("italic"))
            result.add("Italic");

        if (rectangles.stream().anyMatch(r -> r.underlines(textPosition)))
            result.add("Underline");

        if (rectangles.stream().anyMatch(r -> r.strikesThrough(textPosition)))
            result.add("StrikeThrough");

        return result;
    }

    class AppendRectangleToPath extends OperatorProcessor
    {
        public void process(PDFOperator operator, List<COSBase> arguments)
        {
            COSNumber x = (COSNumber) arguments.get(0);
            COSNumber y = (COSNumber) arguments.get(1);
            COSNumber w = (COSNumber) arguments.get(2);
            COSNumber h = (COSNumber) arguments.get(3);

            double x1 = x.doubleValue();
            double y1 = y.doubleValue();

            // create a pair of coordinates for the transformation
            double x2 = w.doubleValue() + x1;
            double y2 = h.doubleValue() + y1;

            Point2D p0 = transformedPoint(x1, y1);
            Point2D p1 = transformedPoint(x2, y1);
            Point2D p2 = transformedPoint(x2, y2);
            Point2D p3 = transformedPoint(x1, y2);

            rectangles.add(new TransformedRectangle(p0, p1, p2, p3));
        }

        Point2D.Double transformedPoint(double x, double y)
        {
            double[] position = {x,y}; 
            getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
                    position, 0, position, 0, 1);
            return new Point2D.Double(position[0],position[1]);
        }
    }

    static class TransformedRectangle
    {
        public TransformedRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3)
        {
            this.p0 = p0;
            this.p1 = p1;
            this.p2 = p2;
            this.p3 = p3;
        }

        boolean strikesThrough(TextPosition textPosition)
        {
            Matrix matrix = textPosition.getTextPos();
            // TODO: This is a very simplistic implementation only working for horizontal text without page rotation
            // and horizontal rectangular strikeThroughs with p0 at the left bottom and p2 at the right top

            // Check if rectangle horizontally matches (at least) the text
            if (p0.getX() > matrix.getXPosition() || p2.getX() < matrix.getXPosition() + textPosition.getWidth() - textPosition.getFontSizeInPt() / 10.0)
                return false;
            // Check whether rectangle vertically is at the right height to underline
            double vertDiff = p0.getY() - matrix.getYPosition();
            if (vertDiff < 0 || vertDiff > textPosition.getFont().getFontDescriptor().getAscent() * textPosition.getFontSizeInPt() / 1000.0)
                return false;
            // Check whether rectangle is small enough to be a line
            return Math.abs(p2.getY() - p0.getY()) < 2;
        }

        boolean underlines(TextPosition textPosition)
        {
            Matrix matrix = textPosition.getTextPos();
            // TODO: This is a very simplistic implementation only working for horizontal text without page rotation
            // and horizontal rectangular underlines with p0 at the left bottom and p2 at the right top

            // Check if rectangle horizontally matches (at least) the text
            if (p0.getX() > matrix.getXPosition() || p2.getX() < matrix.getXPosition() + textPosition.getWidth() - textPosition.getFontSizeInPt() / 10.0)
                return false;
            // Check whether rectangle vertically is at the right height to underline
            double vertDiff = p0.getY() - matrix.getYPosition();
            if (vertDiff > 0 || vertDiff < textPosition.getFont().getFontDescriptor().getDescent() * textPosition.getFontSizeInPt() / 500.0)
                return false;
            // Check whether rectangle is small enough to be a line
            return Math.abs(p2.getY() - p0.getY()) < 2;
        }

        final Point2D p0, p1, p2, p3;
    }

    final List<TransformedRectangle> rectangles = new ArrayList<>();
    Set<String> currentStyle = Collections.singleton("Undefined");
}
(从,从测试方法调用
testExtractStyledFromExampleDocument

一个人得到结果

[]This is an example of plain text 
 
[Bold]This is an example of bold text 
[] 
[Underline]This is an example of underlined text[] 
 
[Italic]This is an example of italic text  
[] 
[StrikeThrough]This is an example of strike through text[]  
 
[Italic, Bold]This is an example of bold, italic text 
对于OP的示例文档



PS同时,对
PDFStyledTextStripper
的代码进行了轻微更改,使其也适用于github发行版中共享的示例文档,特别是其内部类的代码
transformedlectangle
,参见

“作者使用粗体或斜体文本表示新信息,并在指示的省略文本上划线或加下划线“-他们使用不同的字体吗?或者他们使用穷人的粗体等模拟?我相信这些开始作为msword文档,然后被转换为PDF。如果要将文本复制/粘贴到word文档中,字体将保持不变,并启用粗体/斜体或删除线属性。这并不能回答我的问题。如果您不知道,请共享文档进行演示。谢谢您的帮助,我想我不理解您的问题。该文档没有什么特别之处,只是一个word文档转换为PDF。我创建了一个与这里遇到的格式相同的示例文档:示例文档中的粗体和斜体效果是通过使用不同的字体(包含字母的粗体或斜体版本)绘制文本生成的。示例文档中的下划线和横线效果是通过在文本行下/穿过文本行绘制一个矩形生成的,该矩形具有文本行的宽度和非常小的高度。因此,要提取这些信息,您必须扩展
PDFTextStripper
,以某种方式对字体更改和文本附近的矩形作出反应。“作者使用粗体或斜体文本来表示新信息,并在指示的省略文本上划线或加下划线”-他们使用不同的字体吗?或者他们使用穷人的粗体等模拟?我相信这些开始作为msword文档,然后被转换为PDF。如果要将文本复制/粘贴到word文档中,字体将保持不变,并启用粗体/斜体或删除线属性。这并不能回答我的问题。如果您不知道,请共享文档进行演示。谢谢您的帮助,我想我不理解您的问题。该文档没有什么特别之处,只是一个word文档转换为PDF。我创建了一个与这里遇到的格式相同的示例文档:示例文档中的粗体和斜体效果是通过使用不同的字体(包含字母的粗体或斜体版本)绘制文本生成的。示例文档中的下划线和横线效果是通过在文本行下/穿过文本行绘制一个矩形生成的,该矩形具有文本行的宽度和非常小的高度。因此,要提取这些信息,您必须将
PDFTextStripper
扩展到