【PDFBox】PDFBox操作PDF文档之读取指定页面文本内容、读取所有页面文本内容、根据模板文件生成PDF文档

这篇文章,主要介绍PDFBox操作PDF文档之读取指定页面文本内容、读取所有页面文本内容、根据模板文件生成PDF文档。

目录

一、PDFBox操作文本

1.1、读取所有页面文本内容

1.2、读取指定页面文本内容

1.3、写入文本内容

1.4、替换文本内容

(1)自定义PDTextStripper类

(2)创建KeyWordEntity实体类

(3)下载字体文件

(4)创建PDFUtil工具类

(5)运行效果

(6)不足之处


一、PDFBox操作文本

PDFBox操作文本内容,需要使用文本提取器PDTextStripper对象实现,这个PDTextStripper类提供了对文本内容操作的方法,例如:getText()获取文本,writeString()写入字符串等等,下面介绍PDFBox操作文本的几种情况。

1.1、读取所有页面文本内容

一个PDF文档是由多个页面组成的,某一个页面中都可能会包含文本内容,PDTextStripper类提供的【getText()】方法,可以获取到整个PDF文档的文本内容,案例代码如下所示:

package pdfbox.demo.text;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

import java.io.File;
import java.io.IOException;

/**
 * @version 1.0.0
 * @Date: 2023/7/18 9:03
 * @Author ZhuYouBin
 * @Description: 读取PDF文档中所有纯文本内容
 */
public class ReadAllText {
    public static void main(String[] args) throws IOException {
        // 1、加载指定PDF文档
        PDDocument document = PDDocument.load(new File("D:\\demo.pdf"));
        // 2、创建文本提取对象
        PDFTextStripper stripper = new PDFTextStripper();
        // 3、获取指定页面的文本内容
        String text = stripper.getText(document);
        System.out.println("获取文本内容: " + text);
        // 4、关闭
        document.close();
    }
}

1.2、读取指定页面文本内容

有些情况下,我们可能是需要获取某一个页面中的文本内容,这个时候可以通过PDTextStripper类设置页面边界,也就是设置提取哪些页面中的文本内容,只需要调用【setStartPage()】和【setEndPage()】方法即可,案例代码如下所示:

package pdfbox.demo.text;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

import java.io.File;
import java.io.IOException;

/**
 * @version 1.0.0
 * @Date: 2023/7/18 9:03
 * @Author ZhuYouBin
 * @Description: 读取PDF文档中所有纯文本内容
 */
public class ReadPageText {
    public static void main(String[] args) throws IOException {
        // 1、加载指定PDF文档
        PDDocument document = PDDocument.load(new File("D:\\demo.pdf"));
        // 2、创建文本提取对象
        PDFTextStripper stripper = new PDFTextStripper();
        // 指定页面读取内容
        stripper.setStartPage(0); // 设置起始页面,这里设置成0,就表示读取第一个页面
        stripper.setEndPage(0); // 设置结束页面,这里设置成0,就表示读取第一个页面
        // 3、获取指定页面的文本内容
        String text = stripper.getText(document);
        System.out.println("获取文本内容: " + text);
        // 4、关闭
        document.close();
    }
}

1.3、写入文本内容

前几篇文章已经介绍过了如何使用PDFBox写入纯文本内容到PDF文档里面,写入内容可以写入单行内容,也可以写入多行文本内容,可以参考文章:

【【PDFBox】PDFBox操作PDF文档之创建PDF文档、加载PDF文档、添加空白页面、删除页面、获取总页数、添加文本内容、PDFBox坐标系】。

1.4、替换文本内容

替换文本内容,PDFBox并没有提供替换文本内容的方法,这里我是采用了某种方式来实现替换文本内容的功能,大致思路:

  • 首先读取文本内容,获取到替换的文本在PDF文档中的页面坐标位置。
  • 获取到替换文本的坐标之后,将这块区域内容写入一个矩形框,矩形背景颜色采用白色,也就是覆盖替换的文本。
  • 在白色矩形区域里面,重新写入替换之后的文本内容。
  • 采用这种思路,就可以大致实现替换指定文本的功能啦。

(1)自定义PDTextStripper类

要想获取到文本的坐标信息,必须自定义一个类,继承自PDTextStripper类,然后重写【writeString()】方法,这个方法有两个参数:

  • 第一个参数是text:表示当前读取到的文本内容。
  • 第二个参数是List<TextPosition>:表示当前文本内容中某一个字符的坐标信息。
package pdfbox.demo.text.keyword;

import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.apache.pdfbox.util.Matrix;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @version 1.0.0
 * @Date: 2023/7/18 10:18
 * @Author ZhuYouBin
 * @Description: 自定义文本提取器,获取查找文本的坐标位置
 */
public class KeyWordPositionStripper extends PDFTextStripper {

    /**
     * 查找的关键字集合
     */
    private final List<String> keyWordList;
    /**
     * 查找成功的关键字实体对象集合
     */
    private final List<KeyWordEntity> keyWordEntityList = new ArrayList<>();

    public KeyWordPositionStripper(List<String> keyWordList) throws IOException {
        this.keyWordList = keyWordList;
    }

    @Override
    protected void writeString(String text, List<TextPosition> positions) {
        int size = positions.size();
        for (String keyWord : keyWordList) {
            char[] chars = keyWord.toCharArray();
            for (int i = 0; i < size; i++) {
                // 获取当前读取的字符
                String currentChar = positions.get(i).getUnicode();
                // 当前字符 和 keyWord 关键字进行匹配
                if (!Objects.equals(currentChar, String.valueOf(chars[0]))) {
                    continue;
                }
                int count = 1;
                int j;
                for (j = 1; j < chars.length && i + j < size; j++) {
                    currentChar = positions.get(i + j).getUnicode();
                    if (!Objects.equals(currentChar, String.valueOf(chars[j]))) {
                        break;
                    }
                    count++;
                }
                // 匹配成功,记录文本的坐标位置
                if (count == chars.length) {
                    TextPosition startPosition = positions.get(i);
                    TextPosition endPosition = positions.get(i + j < size ? i + j : i + j - 1);
                    // 创建实体对象
                    KeyWordEntity entity = new KeyWordEntity();
                    entity.setKeyWord(keyWord);
                    // 获取起始字符坐标
                    Matrix matrix = startPosition.getTextMatrix();
                    float x = matrix.getTranslateX();
                    float y = matrix.getTranslateY();
                    // 获取结束字符坐标
                    Matrix endMatrix = endPosition.getTextMatrix();
                    float x2 = endMatrix.getTranslateX();
                    // 获取字体大小
                    float fontSizeInPt = startPosition.getFontSizeInPt();
                    entity.setX(x);
                    entity.setY(y - fontSizeInPt / 5);
                    float width = i + j < size ? x2 - x : x2 - x + fontSizeInPt;
                    entity.setWidth(width);
                    entity.setHeight(fontSizeInPt);
                    keyWordEntityList.add(entity);
                }
            }
        }
    }

    public List<KeyWordEntity> getKeyWordEntityList() {
        return keyWordEntityList;
    }
}

(2)创建KeyWordEntity实体类

创建一个KeyWordEntity实体类,用于表示需要查找的关键字文本,关键字也就是我们需要替换的文本内容,一般在实际开发中,就相当于是模板占位符内容。实体类需要设置关键字名称、文本的坐标信息。

package pdfbox.demo.text.keyword;

import java.io.Serializable;

/**
 * @version 1.0.0
 * @Date: 2023/7/18 11:22
 * @Author ZhuYouBin
 * @Description: 查找的关键字
 */
public class KeyWordEntity implements Serializable {
    private String keyWord;

    private float x;
    private float y;
    private float width;
    private float height;

    public String getKeyWord() {
        return keyWord;
    }

    public void setKeyWord(String keyWord) {
        this.keyWord = keyWord;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getWidth() {
        return width;
    }

    public void setWidth(float width) {
        this.width = width;
    }

    public float getHeight() {
        return height;
    }

    public void setHeight(float height) {
        this.height = height;
    }
}

(3)下载字体文件

如果你不想使用PDFBox提供的字体,那么你可以使用外部字体文件,字体文件可以去【经典宋体简|经典|字体下载】网站下载。

(4)创建PDFUtil工具类

package pdfbox.demo.text.keyword;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.springframework.core.io.ClassPathResource;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;

/**
 * @version 1.0.0
 * @Date: 2023/7/18 16:01
 * @Author ZhuYouBin
 * @Description: 基于PDFBox的工具类
 */
public class PDFUtil {

    /**
     * 读取PDF模板文件,替换指定关键字的数据
     * @param keyWordMap 需要替换的关键字数据,key表示占位符,value表示替换后的内容
     * @param pdfPath PDF模板文件的路径
     * @param destPdf 生成的目标PDF文件
     */
    public static void replaceText(Map<String, String> keyWordMap, String pdfPath, String destPdf) throws IOException {
        if (keyWordMap == null || keyWordMap.keySet().size() <= 0) {
            return;
        }
        Set<String> keyWordSet = keyWordMap.keySet();
        // 1、读取PDF模板文件
        PDDocument document = PDDocument.load(new File(pdfPath));
        // 2、创建自定义文本提取器
        KeyWordPositionStripper stripper = new KeyWordPositionStripper(new ArrayList<>(keyWordSet));
        stripper.setSortByPosition(true);
        // 注意: writeString() 方法必须执行 getText() 方法之后才会执行
        stripper.getText(document);
        // 3、获取关键字实体对象
        List<KeyWordEntity> keyWordEntityList = stripper.getKeyWordEntityList();
        // 4、替换指定关键字文本内容
        PDPageContentStream stream = new PDPageContentStream(document, document.getPage(0), PDPageContentStream.AppendMode.APPEND, true);
        // 5、加载外部字体文件,这里是直接通过File加载,如果你是SpringBoot项目,则可以通过流加载
        PDType0Font font = PDType0Font.load(document, new File("D:\\simsun.ttf"));
        // 6、循环替换文本内容
        for (KeyWordEntity keyWord : keyWordEntityList) {
            stream.setNonStrokingColor(Color.WHITE);
            stream.addRect(keyWord.getX(), keyWord.getY(), keyWord.getWidth(), keyWord.getHeight());
            stream.fill();
            // 设置画笔颜色
            stream.setNonStrokingColor(Color.BLACK);
            // 替换关键字文本内容
            stream.beginText();
            stream.setFont(font, 14);
            stream.newLineAtOffset(keyWord.getX(), keyWord.getY());
            stream.showText(keyWordMap.get(keyWord.getKeyWord()));
            stream.endText();
        }
        // 关闭内容流
        stream.close();
        // 保存替换之后的文档
        document.save(destPdf);
        // 关闭文档
        document.close();
    }

    public static void main(String[] args) throws IOException {
        Map<String, String> keyWordMap = new HashMap<>();
        keyWordMap.put("{{name}}", "张三");
        keyWordMap.put("{{age}}", "25");
        keyWordMap.put("{{sex}}", "男");
        keyWordMap.put("{{address}}", "福建省厦门市");
        // 模拟测试
        PDFUtil.replaceText(keyWordMap, "D:\\pdfbox-template.pdf", "D:\\new-document.pdf");
    }
}

(5)运行效果

这里的PDF模板文件如下图所示:

使用PDFBox替换模板文件的内容之后,运行结果如下所示:

(6)不足之处

虽然这里可以实现替换文本内容,但是这个代码仍然存在一些不足之处,有以下几点:

  • 1、替换的文本位置无法保证和原文本内容对齐,需要自己根据实际模板,调整相应坐标位置。
  • 2、当替换的文本内容太多,会覆盖后面的文本内容。
  • 3、目前只能够替换指定页面的文本内容。
  • 4、其他不足。。。

到此,PDFBox操作文本就介绍完啦。

综上,这篇文章结束了,主要介绍PDFBox操作PDF文档之读取指定页面文本内容、读取所有页面文本内容、根据模板文件生成PDF文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/39519.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在 Amazon 上以高可用性模式实现 Microsoft SQL 数据库服务现代化的注意事项

许多企业都有需要 Microsoft SQL Server 来运行关系数据库工作负载的应用程序&#xff1a;一些应用程序可能是专有软件&#xff0c;供应商可使用它强制 Microsoft SQL Server 运行数据库服务&#xff1b;其他应用程序可能是长期存在的、自主开发的应用程序&#xff0c;它们在最…

XUbuntu22.04之vim无法复制内容到系统(一百八十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

将大模型集成到语音识别系统中的例子

概述 本文旨在探索将大型语言模型&#xff08;LLMs&#xff09;集成到自动语音识别&#xff08;ASR&#xff09;系统中以提高转录准确性的潜力。 文章介绍了目前的ASR方法及其存在的问题&#xff0c;并对使用LLMs的上下文学习能力来改进ASR系统的性能进行了合理的动机论证。 本…

VIM文本如何复制到系统剪切板?

今天从vim上用鼠标复制代码&#xff0c;发现把VIM当中的行号也复制进去了&#xff0c;就很麻烦&#xff0c;于是简单研究了下&#xff0c;如果vim支持clipboard的话就比较好办&#xff0c;具体支持与否&#xff0c;使用命令查看&#xff1a; vim --version | grep "clipb…

Android系统启动流程分析

当按下Android系统的开机电源按键时候&#xff0c;硬件会触发引导芯片&#xff0c;执行预定义的代码&#xff0c;然后加载引导程序(BootLoader)到RAM&#xff0c;Bootloader是Android系统起来前第一个程序&#xff0c;主要用来拉起Android系统程序&#xff0c;Android系统被拉起…

基于Java+Swingl实现拼图游戏

基于JavaSwingl实现拼图游戏 一、系统介绍二、效果展示三、其他系统实现四、获取源码 一、系统介绍 拼图游戏是一个简单的小程序&#xff0c;游戏规则如下&#xff1a;将一张大图分成9张小图&#xff0c;然后任意挑8张图&#xff0c;随意放在3行3列的矩阵中。 通过点击鼠标移动…

Maven 项目构建生命周期

Maven 项目构建生命周期 一句话: Maven 构建生命周期描述的是一次构建过程经历了多少个事件 生命周期的3 大阶段 clean 清理工作 default 核心工作&#xff0c;例如编译&#xff0c;测试&#xff0c;打包&#xff0c;部署等 site 产生报告&#xff0c;发布站点等 生命周期…

react和vue2/3父子组件的双向绑定(sync、emit、v-model)

目录 Vue .sync&#xff08;2.3.0&#xff09; $emit &#xff08;2.3后&#xff09; 自定义组件的 v-model 2.2.0 v-modelemits(3.0取消了.sync) React 父组件回调函数 相关基础 框架 MVC &#xff08;Model View Controller&#xff09;/MVP&#xff08;Model View…

RHCSA——Linux网络、磁盘及软件包管理

ZY目录 Linux操作系统讲解&#xff1a;一、网络管理1、NetworkManager1.1、nmtui界面&#xff1a;1.2、nmcli使用方法&#xff1a; 2、配置网络2.1、网络接口以及网络连接2.2、配置方法&#xff1a;2.3、ping命令&#xff1a;2.4、wget命令 二、磁盘管理2.1、分区得两种格式2.1…

日撸java三百行day77-79

文章目录 说明GUI1. GUI 总体布局2. GUI 代码理解2.1 对话框相关控件2.1.1 ApplicationShowdown.java&#xff08;关闭应用程序&#xff09;2.1.2 DialogCloser.java&#xff08;关闭对话框&#xff09;2.1.3 ErrorDialog.java&#xff08;显示错误信息&#xff09;2.1.4 HelpD…

day35-Postman/ajax

0目录 1.postman 2.ajax 1.Postman 1.1 定义&#xff1a;postman用于测试http协议接口&#xff0c;无论是开发还是测试人员 1.2 Servlet中的doGet&#xff08;&#xff09;/doPost…

idea 常用快捷键总结

IDEA常用快捷键总结 很多新手小白在使用IDEA进行代码编写的时候 对快捷键很感兴趣 这里泡泡给大家总结了一些常用的快捷键 希望能帮助到你 记得要收藏下来时常观看并且练习&#xff0c;才能熟练哦~ 1. 根据psvm或者main快速生成主函数 我们可以在类中输入psvm 或者main 然后I…

C# Winfrom将DataGridView数据导入Excel

1.项目添加Word和Excel的COM类型库引用 2.创建Excel工作表 //定义Excel操作对象Microsoft.Office.Interop.Excel.Application excelApp new Microsoft.Office.Interop.Excel.Application();//定义Excel工作表Microsoft.Office.Interop.Excel.Worksheet worksheet excelApp.Wo…

TCP的窗口控制和重发控制【TCP原理(笔记三)】

文章目录 利用窗口控制提高速度窗口控制与重发控制确认应答未能返回的情况某个报文段丢失的情况 控制流 利用窗口控制提高速度 TCP以1个段为单位&#xff0c;每发一个段进行一次确认应答的处理&#xff0c;如图。这样的传输方式有一个缺点。那就是&#xff0c;包的往返时间越长…

Centos使用docker部署nacos

Centos使用docker部署nacos 对于使用Docker部署Nacos&#xff0c;您可以按照以下步骤进行操作&#xff1a; 在您的服务器上安装Docker和Docker Compose。创建一个用于存储Nacos数据的目录&#xff0c;例如/path/to/nacos/data。创建一个docker-compose.yml文件&#xff0c;并…

心电前置放大电路制作与原理详细分析(附电路板实物图)

心电前置放大电路制作与原理详细分析(附电路板实物图) 实验目的实验结果实验电路图原理解释与计算实验测试过程实验参数测量实验洞洞板焊接实验目的 心电信号具有微弱、低频、和高阻抗等特性,极其容易受到干扰。为了实现心电信号的放大,前置放大器需要满足高输入阻抗、高共…

前端开发如何更好的避免样式冲突?级联层(CSS@layer)

目录 前言 一、什么是级联层 (Cascade Layers)&#xff1f; 1.1 级联层的官方定义 1.2 级联层为了解决什么问题&#xff1f; 二、理解级联层的前提 —— 级联 (cascade) 2.1 什么是级联&#xff1f; 2.2 当前级联的排序标准 2.3 级联起源&#xff08;Cascading Origins…

Spring Boot进阶(54):Windows 平台安装 MongoDB数据库 | 超级详细,建议收藏

1. 前言&#x1f525; Windows如何安装MongoDB数据库及使用呢&#xff1f;这将又会是干货满满的一期&#xff0c;全程无尿点不废话只抓重点教&#xff0c;具有非常好的学习效果&#xff0c;拿好小板凳准备就坐&#xff01;希望学习的过程中大家认真听好好学&#xff0c;学习的途…

第二章:在html中使用javascript

1、在html页面中插入js的主要方法就是使用<script>元素 2、html4.01为<script>定义了以下6个属性&#xff1a;【language已经废弃&#xff0c;其他5个属性都是可选的】 async 表示应该立即下载脚本&#xff0c;但不应该妨碍页面中的其他操作&#xff0c;比如下载…

中金:龙湖基本面稳健,股价超跌具备配置价值

恒大2.4万亿元的天量债务爆出后&#xff0c;让本就信心不足的房地产行业&#xff0c;越发雪上加霜&#xff0c;房企股价遭遇集体下挫&#xff0c;业内公认的万科、龙湖、保利、中海等“优等生”也不免被波及。多家证券机构提醒&#xff0c;行业预期降至冰点的情况下&#xff0c…
最新文章