PDFBox - 直线/矩形提取

2023-12-04

我正在尝试从 PDF 中提取文本坐标和行(或矩形)坐标。

The TextPosition班级有getXDirAdj() and getYDirAdj()根据相应 TextPosition 对象表示的文本片段的方向转换坐标的方法(根据 @mkl 的评论进行更正) 无论页面旋转如何,最终输出都是一致的。

输出所需的坐标是 X0,Y0(页面的左上角)

这是对 @Tilman Hausherr 的解决方案的轻微修改。 y 坐标被反转(高度 - y)以使其与文本提取过程中的坐标保持一致,并且输出也写入 csv。

    public class LineCatcher extends PDFGraphicsStreamEngine
{
    private static final GeneralPath linePath = new GeneralPath();
    private static ArrayList<Rectangle2D> rectList= new ArrayList<Rectangle2D>();
    private int clipWindingRule = -1;
    private static String headerRecord = "Text|Page|x|y|width|height|space|font";

    public LineCatcher(PDPage page)
    {
        super(page);
    }

    public static void main(String[] args) throws IOException
    {
        if( args.length != 4 )
        {
            usage();
        }
        else
        {
            PDDocument document = null;
            FileOutputStream fop = null;
            File file;
            Writer osw = null;
            int numPages;
            double page_height;
            try
            {
                document = PDDocument.load( new File(args[0], args[1]) );
                numPages = document.getNumberOfPages();
                file = new File(args[2], args[3]);
                fop = new FileOutputStream(file);

                // if file doesnt exists, then create it
                if (!file.exists()) {
                    file.createNewFile();
                }

                osw = new OutputStreamWriter(fop, "UTF8");
                osw.write(headerRecord + System.lineSeparator());
                System.out.println("Line Processing numPages:" + numPages);
                for (int n = 0; n < numPages; n++) {
                    System.out.println("Line Processing page:" + n);
                    rectList = new ArrayList<Rectangle2D>();
                    PDPage page = document.getPage(n);
                    page_height = page.getCropBox().getUpperRightY();
                    LineCatcher lineCatcher = new LineCatcher(page);
                    lineCatcher.processPage(page);

                    try{
                        for(Rectangle2D rect:rectList) {

                            String pageNum = Integer.toString(n + 1);
                            String x = Double.toString(rect.getX());
                            String y = Double.toString(page_height - rect.getY()) ;
                            String w = Double.toString(rect.getWidth());
                            String h = Double.toString(rect.getHeight());
                            writeToFile(pageNum, x, y, w, h, osw);

                        }
                        rectList = null;
                        page = null;
                        lineCatcher = null;
                    }
                    catch(IOException io){
                        throw new IOException("Failed to Parse document for line processing. Incorrect document format. Page:" + n);
                    }
                };

            }
            catch(IOException io){
                throw new IOException("Failed to Parse document for line processing. Incorrect document format.");
            }
            finally
            {
                if ( osw != null ){
                    osw.close();
                }
                if( document != null )
                {
                    document.close();
                }
            }
        }
    }

    private static void writeToFile(String pageNum, String x, String y, String w, String h, Writer osw) throws IOException {
        String c = "^" + "|" +
                pageNum + "|" +
                x + "|" +
                y + "|" +
                w + "|" +
                h + "|" +
                "999" + "|" +
                "marker-only";
        osw.write(c + System.lineSeparator());
    }

    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
    {
        // to ensure that the path is created in the right direction, we have to create
        // it by combining single lines instead of creating a simple rectangle
        linePath.moveTo((float) p0.getX(), (float) p0.getY());
        linePath.lineTo((float) p1.getX(), (float) p1.getY());
        linePath.lineTo((float) p2.getX(), (float) p2.getY());
        linePath.lineTo((float) p3.getX(), (float) p3.getY());

        // close the subpath instead of adding the last line so that a possible set line
        // cap style isn't taken into account at the "beginning" of the rectangle
        linePath.closePath();
    }

    @Override
    public void drawImage(PDImage pdi) throws IOException
    {
    }

    @Override
    public void clip(int windingRule) throws IOException
    {
        // the clipping path will not be updated until the succeeding painting operator is called
        clipWindingRule = windingRule;

    }

    @Override
    public void moveTo(float x, float y) throws IOException
    {
        linePath.moveTo(x, y);
    }

    @Override
    public void lineTo(float x, float y) throws IOException
    {
        linePath.lineTo(x, y);
    }

    @Override
    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
    {
        linePath.curveTo(x1, y1, x2, y2, x3, y3);
    }

    @Override
    public Point2D getCurrentPoint() throws IOException
    {
        return linePath.getCurrentPoint();
    }

    @Override
    public void closePath() throws IOException
    {
        linePath.closePath();
    }

    @Override
    public void endPath() throws IOException
    {
        if (clipWindingRule != -1)
        {
            linePath.setWindingRule(clipWindingRule);
            getGraphicsState().intersectClippingPath(linePath);
            clipWindingRule = -1;
        }
        linePath.reset();

    }

    @Override
    public void strokePath() throws IOException
    {
        rectList.add(linePath.getBounds2D());
        linePath.reset();
    }

    @Override
    public void fillPath(int windingRule) throws IOException
    {
        linePath.reset();
    }

    @Override
    public void fillAndStrokePath(int windingRule) throws IOException
    {
        linePath.reset();
    }

    @Override
    public void shadingFill(COSName cosn) throws IOException
    {
    }

    /**
     * This will print the usage for this document.
     */
    private static void usage()
    {
        System.err.println( "Usage: java " + LineCatcher.class.getName() + " <input-pdf>"  + " <output-file>");
    }
}

正在使用PDFGraphicsStreamEngine类来提取线和矩形坐标。线条和矩形的坐标与文本的坐标不对齐

PLOT of the text and line extract

绿色:文本 红色:按原样获取的线坐标 黑色:预期坐标(对输出应用变换后获得)

尝试过setRotation()在运行线提取之前校正旋转的方法。但结果并不一致。

使用 PDFBox 获得旋转并获得一致的线/矩形坐标输出有哪些可能的选项?


As far as I understand the requirements here, the OP works in a coordinate system with the origin in the upper left corner of the visible page (taking the page rotation into account), x coordinates increasing to the right, y coordinates increasing downwards, and the units being the PDF default user space units (usually 1/72 inch).

在这个坐标系中,他需要以以下形式提取(水平或垂直)线:

  • 左/上端点坐标和
  • 宽度/高度。

转型LineCatcher results

辅助类LineCatcher另一方面,他从蒂尔曼那里得到的not考虑页面旋转。此外,它还返回bottom垂直线的终点,而不是顶部终点。因此,必须对其应用坐标变换LineCatcher结果。

为此,只需替换

for(Rectangle2D rect:rectList) {
    String pageNum = Integer.toString(n + 1);
    String x = Double.toString(rect.getX());
    String y = Double.toString(page_height - rect.getY()) ;
    String w = Double.toString(rect.getWidth());
    String h = Double.toString(rect.getHeight());
    writeToFile(pageNum, x, y, w, h, osw);
}

by

int pageRotation = page.getRotation();
PDRectangle pageCropBox = page.getCropBox();

for(Rectangle2D rect:rectList) {
    String pageNum = Integer.toString(n + 1);
    String x, y, w, h;
    switch(pageRotation) {
    case 0:
        x = Double.toString(rect.getX() - pageCropBox.getLowerLeftX());
        y = Double.toString(pageCropBox.getUpperRightY() - rect.getY() + rect.getHeight());
        w = Double.toString(rect.getWidth());
        h = Double.toString(rect.getHeight());
        break;
    case 90:
        x = Double.toString(rect.getY() - pageCropBox.getLowerLeftY());
        y = Double.toString(rect.getX() - pageCropBox.getLowerLeftX());
        w = Double.toString(rect.getHeight());
        h = Double.toString(rect.getWidth());
        break;
    case 180:
        x = Double.toString(pageCropBox.getUpperRightX() - rect.getX() - rect.getWidth());
        y = Double.toString(rect.getY() - pageCropBox.getLowerLeftY());
        w = Double.toString(rect.getWidth());
        h = Double.toString(rect.getHeight());
        break;
    case 270:
        x = Double.toString(pageCropBox.getUpperRightY() - rect.getY() + rect.getHeight());
        y = Double.toString(pageCropBox.getUpperRightX() - rect.getX() - rect.getWidth());
        w = Double.toString(rect.getHeight());
        h = Double.toString(rect.getWidth());
        break;
    default:
        throw new IOException(String.format("Unsupported page rotation %d on page %d.", pageRotation, page));
    }
    writeToFile(pageNum, x, y, w, h, osw);
}

(带目录的提取行 test testExtractLineRotationTestWithDir)

与的关系TextPosition.get?DirAdj()坐标

OP 通过参考描述坐标TextPosition类方法getXDirAdj() and getYDirAdj()。事实上,这些方法返回坐标系中的坐标,原点位于页面左上角,并且y坐标向下增加旋转页面以使文本垂直绘制后.

在示例文档的情况下,所有文本都被绘制,以便在应用页面旋转后它是直立的。由此我对上面写的要求的理解就得到了。

使用的问题TextPosition.get?DirAdj()然而,作为全局坐标的值的问题是,在具有沿不同方向绘制文本的页面的文档中,收集的文本坐标突然相对于不同的坐标系。因此,一般的解决方案不应该像那样疯狂地收集坐标。相反,它应该首先确定页面方向(例如,页面旋转给出的方向或大多数文本共享的方向),并使用由该方向给出的固定坐标系中的坐标加上文本书写方向的指示有问题的一块。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PDFBox - 直线/矩形提取 的相关文章

  • 按键时关闭 ModalWindow

    我希望能够在用户按下某个键 在我的例子中是 ESC 时关闭 ModalWindow 我有一个用于按键的 Javascript 侦听器 它调用取消按钮 ID 的单击事件 jQuery modalWindowInfo closeButtonId
  • 日期语句之间的 JPQL SELECT [关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我想将此 SQL 语句转换为等效的 JPQL SELECT FROM events WHERE events date BETWE
  • Spring应用中Eureka健康检查的问题

    我正在开发一个基于 Spring 的应用程序 其中包含多个微服务 我的一个微服务充当尤里卡服务器 到目前为止一切正常 在我所有其他微服务中 用 EnableEurekaClient 我想启用这样的健康检查 应用程序 yml eureka c
  • 为什么 JTables 使 TableModel 在呈现时不可序列化?

    所以最近我正在开发一个工具 供我们配置某些应用程序 它不需要是什么真正令人敬畏的东西 只是一个具有一些 SQL 脚本生成功能并创建几个 XML 文件的基本工具 在此期间 我使用自己的 AbstractTableModel 实现创建了一系列
  • 为 java 游戏创建交互式 GUI

    大家好 我正在创建一个类似于 java 中的 farmville 的游戏 我只是想知道如何实现用户通常单击以与游戏客户端交互的交互式对象 按钮 我不想使用 swing 库 通用 Windows 看起来像对象 我想为我的按钮导入自定义图像 并
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • 谷歌应用程序引擎会话

    什么是java应用程序引擎 默认会话超时 如果我们将会话超时设置为非常非常长的时间 会不会产生不良影响 因为谷歌应用程序引擎会话默认情况下仅存储在数据存储中 就像facebook一样 每次访问该页面时 会话仍然永远存在 默认会话超时设置为
  • 来自 dll 的 Java 调用函数

    我有这个 python 脚本导入zkemkeeperdll 并连接到考勤设备 ZKTeco 这是我正在使用的脚本 from win32com client import Dispatch zk Dispatch zkemkeeper ZKE
  • 无法创建请求的服务[org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]-MySQL

    我是 Hibernate 的新手 我目前正在使用 Spring boot 框架并尝试通过 hibernate 创建数据库表 我知道以前也问过同样的问题 但我似乎无法根据我的环境找出如何修复错误 休眠配置文件
  • 将 MOXy 设置为 JAXB 提供程序,而在同一包中没有属性文件

    我正在尝试使用 MOXy 作为我的 JAXB 提供程序 以便将内容编组 解组到 XML JSON 中 我创建了 jaxb properties 文件 内容如下 javax xml bind context factory org eclip
  • 帮助将图像从 Servlet 获取到 JSP 页面 [重复]

    这个问题在这里已经有答案了 我目前必须生成一个显示字符串文本的图像 我需要在 Servlet 上制作此图像 然后以某种方式将图像传递到 JSP 页面 以便它可以显示它 我试图避免保存图像 而是以某种方式将图像流式传输到 JSP 自从我开始寻
  • tomcat 中受密码保护的应用程序

    我正在使用 JSP Servlet 开发一个Web应用程序 并且我使用了Tomcat 7 0 33 as a web container 所以我的要求是tomcat中的每个应用程序都会password像受保护的manager applica
  • 获取文件的总大小(以字节为单位)[重复]

    这个问题在这里已经有答案了 可能的重复 java 高效获取文件大小 https stackoverflow com questions 116574 java get file size efficiently 我有一个名为 filenam
  • 为什么 Java 8 不允许非公共默认方法?

    让我们举个例子 public interface Testerface default public String example return Hello public class Tester implements Testerface
  • Eclipse 选项卡宽度不变

    我浏览了一些与此相关的帖子 但它们似乎并不能帮助我解决我的问题 我有一个项目 其中 java 文件以 2 个空格的宽度缩进 我想将所有内容更改为 4 空格宽度 我尝试了 正确的缩进 选项 但当我将几行修改为 4 空格缩进时 它只是将所有内容
  • Cucumber 0.4.3 (cuke4duke) 与 java + maven gem 问题

    我最近开始为 Cucumber 安装一个示例项目 并尝试使用 maven java 运行它 我遵循了这个指南 http www goodercode com wp using cucumber tests with maven and ja
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn
  • 使用反射覆盖最终静态字段是否有限制?

    在我的一些单元测试中 我在最终静态字段上的反射中遇到了奇怪的行为 下面是说明我的问题的示例 我有一个基本的 Singleton 类 其中包含一个 Integer public class BasicHolder private static
  • 使用 svn 1.8.x、subclise 1.10 的 m2e-subclipse 连接器在哪里?

    我读到 m2e 的生产商已经停止生产 svn 1 7 以外的任何版本的 m2e 连接器 Tigris 显然已经填补了维护 m2e subclipse 连接器的空缺 Q1 我的问题是 使用 svn 1 8 x 的 eclipse 更新 url
  • 如何防止在Spring Boot单元测试中执行import.sql

    我的类路径中有一个 import sql 文件 其中包含一些 INSERT 语句 当使用 profile devel 运行我的应用程序时 它的数据被加载到 postgres 数据库中 到目前为止一切正常 当使用测试配置文件执行测试时 imp

随机推荐

  • JSoup HTTP 获取 URL 时出错。状态=405

    我想连接到https www notebooksbilliger de 但使用以下代码则不起作用 try Response response Jsoup connect url userAgent Mozilla ignoreContent
  • PHP 7.2 中未定义的函数 odbc_connect()

    我收到错误 致命错误 未捕获错误 调用未定义的函数 odbc connect Ive added the extension in php ini and phpinfo is confirming that the odbc driver
  • 取消排序:记住排列并撤消它

    假设我有一个函数 f 它接受向量 v 并返回一个新向量 其中元素以某种方式转换 它通过调用假设向量已排序的函数 g 来实现这一点 所以我希望 f 的定义如下 f v Module s r s Sort v remember the perm
  • 覆盖主页中最近添加的列表

    我想知道是否可以覆盖最近添加的列表在主页中 默认行为是任何新提交的项目都会显示在列表中 无论其发布日期如何 有没有办法覆盖它 以便仅发布最新提交的出版物 例如两年内 或有条件的出版物 if dc date issued gt 2014 显示
  • 计算 PySpark 中列的中位数

    我有一个数据框 如下所示 parsed date count 2017 12 16 2 2017 12 16 2 2017 12 17 2 2017 12 17 2 2017 12 18 1 2017 12 19 4 2017 12 19
  • 如何在 Android 中录音电话

    我需要在我的 Android 应用程序中录制电话 我尝试过使用 MediaRecorder 将 AudioSource 设置为 MIC VOICE COMMUNICATION VOICE CALL 和其他选项 但他们都没有记录通话 任何人都
  • Python - 使用正则表达式查找多个匹配项并将其打印出来[重复]

    这个问题在这里已经有答案了 我需要从 HTML 源文件中查找表单内容 我做了一些搜索并找到了很好的方法来做到这一点 但问题是它只打印第一个找到的内容 我如何循环遍历它并输出所有表单内容 而不是只是第一个 line bla bla bla
  • Firestore - 监听特定字段的变化?

    如何使用 firestore js sdk 监听特定字段的变化 在文档中 他们似乎只展示了如何监听整个文档 如果任何 SF 字段发生变化 就会触发回调 db collection cities doc SF onSnapshot funct
  • 使用反向引用和哈希时的 Ruby gsub 问题

    以下代码定义带有正则表达式 键 和替换 值 的哈希 然后它迭代哈希并相应地替换字符串 简单的字符串替换效果很好 但是当我需要在替换之前计算结果时 几年到几天的情况发生变化 它就不行了 预先定义哈希值是关键 我缺少什么 任何帮助将不胜感激 a
  • 通过 OLE 从 Ruby 或 VBS 调用时,Word Document.SaveAs 会忽略编码

    我有一个脚本 VBS 或 Ruby 可以将 Word 文档保存为 过滤后的 HTML 但编码参数被忽略 HTML 文件始终以 Windows 1252 进行编码 我在 Windows 7 SP1 上使用 Word 2007 SP3 红宝石示
  • 如何使用 jQuery 悬停、更改、切换图片

    我正在做我的项目 我正在努力做到这一点 我想让每个图像的点击都能正常工作 这样每个图像都有自己的纸张 需要它悬停 我想使用切换百叶窗效果 我读到 在这方面使用 CSS 背景是明智的 这样我可以交换图片 但我无法理解它 我已经采取了一些代码来
  • Java 8 Firebase Tasks.await()

    我在 Spring Boot 应用程序中使用 Firebase JAVA sdk 尝试验证 firebase 令牌 映射 resp new HashMap Task
  • 使用 dplyr 计算组平均值,同时排除当前观察值

    Using dplyr 最好 我试图计算每个观察值的组平均值 同时从组中排除该观察值 看来这应该可以通过组合来实现rowwise and group by 但这两个功能不能同时使用 给定这个数据框 df lt data frame grou
  • Rust 正则表达式模式 - 无法识别的转义模式

    我确实有以下字符串 lengthSeconds 2664 我想与这个正则表达式匹配 Regex new lengthSeconds d 我什至尝试过这个 Regex new r lengthSeconds d 但我得到这个 regex pa
  • SWT 全局 KeyListener 按钮焦点问题

    对于我的应用程序 我需要空格键来调用独立于焦点小部件的函数 在应用程序中的任何位置 但前提是打开相应的选项卡 我发现可以在显示中添加一个过滤器 如下所示 getShell getDisplay addFilter SWT KeyDown n
  • 如何将带有空格的路径存储到bash中的变量中

    我想存储 c users me dir name到一个变量中将其传递给cd系统调用 键入时有效 cd c users me dir name or cd c users me dir name 但如果我存储它则不起作用 dirname c
  • 延迟加载 FXProperties

    这是来自的后续here 我正在实现一个表 它将数据异步加载到表单元格中 问题是 表格单元格有时无法正确更新 有时它会以某种方式 挂起 并且永远显示 正在加载 仅当我在表格中滚动一点时 实际值才会更新 重现 运行应用程序并在表中快速向下滚动
  • Javascript:显示大数字时如何避免科学记数法

    基于 为了可读性 JavaScript使用科学记数法来显示非常大的数字 大于或等于1021的绝对值 例如 写入语句 document write 1000000000000000000000000 会产生输出 1e24 我可以避免显示科学记
  • 如何使用text strip()函数?

    我可以删除数字 但不能删除字母字符 gt gt gt text 132abcd13232111 gt gt gt text strip 123 abcd 为什么以下不起作用 gt gt gt text strip abcd 132abcd1
  • PDFBox - 直线/矩形提取

    我正在尝试从 PDF 中提取文本坐标和行 或矩形 坐标 The TextPosition班级有getXDirAdj and getYDirAdj 根据相应 TextPosition 对象表示的文本片段的方向转换坐标的方法 根据 mkl 的评