在Spring Boot中使用iTextPDF创建动态PDF文档

简介: iTextPDF 是一个用于创建和操作 PDF(Portable Document Format)文档的流行的 Java 库。它提供了一套全面的功能,用于处理 PDF 文件,包括创建新文档、修改现有文档以及提取信息。

最近,我们的系统新增了一个客服模块,其中一个重要功能是能够以PDF格式导出客服与用户之间的聊天记录。这些聊天记录包含文字、图片和文件等多种内容。为了实现这一功能,我们首先使用了itextpdf 5.x版本制作了一个Demo。今天,我将与家人们分享一下这项进展。

itextpdf.jpg

iTextPDF 介绍

iTextPDF 是一个用于创建和操作 PDF(Portable Document Format)文档的流行的 Java 库。它提供了一套全面的功能,用于处理 PDF 文件,包括创建新文档、修改现有文档以及提取信息。以下是 iTextPDF 的一些关键方面的简要概述:

  • 文档创建:

iTextPDF 允许您从头开始创建新的 PDF 文档。

您可以向文档添加段落、表格、图像和其他元素。

  • 文本操作:

该库提供了格式化和处理文本的方法。

  • 页面布局:

您可以定义页面的布局,包括页面尺寸、边距等。

  • 字体和颜色:

iTextPDF 允许您选择字体和颜色,以定制文档的外观。

  • 表格:

通过 iTextPDF,您可以创建包含表格的文档,设置表格的列数、行数和单元格内容。

  • 图像处理:

您可以将图像插入到文档中,并设置图像的大小和位置。

  • 文档安全性:

iTextPDF 提供了对文档进行加密和数字签名的功能,以增强文档的安全性。

  • 文档解析:

除了创建文档,iTextPDF 还允许您解析现有的 PDF 文档,提取文本、图像等信息。

代码示例

我们此处使用的 iTextPDF 5.x的版本实现的

添加依赖

在pom文件中添加如下依赖

<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>itextpdf</artifactId>
  <version>5.5.13.2</version> 
</dependency>

代码编写

service代码

import cn.xj.xjdoc.system.entity.Message;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfAction;
import com.itextpdf.text.pdf.PdfWriter;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class PdfService {
   
   

    public void export(HttpServletResponse response) throws IOException, DocumentException {
   
   

        List<Message> messageList = getMsgList();
        // 创建 PDF document
        Document document = new Document();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfWriter pdfWriter = PdfWriter.getInstance(document, baos);

        //获取系统字体,如果是中文,则需注意linux中不存在windows字体,中文乱码或者写不进去
        FontFactory.registerDirectories();

        Font chineseFont = FontFactory.getFont("SimSun", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        document.open();

        // 发送人字体
        Font senderFont = new Font(chineseFont.getBaseFont(), 16, Font.BOLD, BaseColor.ORANGE);
        // 发送时间字体
        Font senderTimeFont = new Font(chineseFont.getBaseFont(), 12, Font.UNDEFINED, BaseColor.BLUE);
        // 内容字体
        Font defaultFont = new Font(chineseFont.getBaseFont(), 12);

        // 将信息写入pdf中
        for (Message msg : messageList) {
   
   

            String senderText = String.format("%s  ", msg.getSendUser());
            String timeText = msg.getSendTime();
            String messageText = msg.getContent();
            Integer type = msg.getType();

            Paragraph paragraph = new Paragraph();

            ColumnText columnText = new ColumnText(pdfWriter.getDirectContent());
            columnText.addElement(paragraph);
            columnText.setSimpleColumn(20, document.bottom(), document.right() - 20, document.top(), 0, Element.ALIGN_LEFT);
            paragraph.add(new Chunk(senderText, senderFont));
            paragraph.add(new Chunk(timeText + "\n", senderTimeFont));

            float proportion = 1f;

            if(type == 1){
   
   
                //文字
                paragraph.add(new Chunk(messageText + "\n", defaultFont));
            }else if(type == 2){
   
   
                //图片
                // 创建Image对象
                Image image = Image.getInstance(new URL(msg.getContent()));

                //等比缩小图片
                if(image.getWidth() > 150){
   
   
                    proportion = image.getWidth()/150;
                    image.scaleToFit(image.getWidth()/proportion, image.getHeight()/proportion);
                }
                //加一空行
                paragraph.add(new Chunk(Chunk.NEWLINE));
                // 判断当前页内容是否已满
                if ((pdfWriter.getVerticalPosition(true) - image.getHeight()/proportion) < document.bottom()) {
   
   
                    document.newPage(); // Start a new page
                    columnText.setYLine(document.top());
                }
                paragraph.add(image);
            }else{
   
   
                //文件
                // 创建Chunk,设置文件连接,点击下载
                Chunk chunk = new Chunk(msg.getFileName(),defaultFont);
                chunk.setAction(new PdfAction(new URL(msg.getContent()).toExternalForm()));
                paragraph.add(chunk);
            }

            paragraph.setIndentationLeft(20f);
            paragraph.setIndentationRight(20f);
            paragraph.setSpacingAfter(10f);

            // 判断当前页内容是否已满
            if (ColumnText.hasMoreText(columnText.go())) {
   
   
                document.newPage();
            }
            document.add(paragraph);

        }
        document.close();

        //返回pdf
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "inline; filename=chat_export.pdf");
        response.getOutputStream().write(baos.toByteArray());
        response.getOutputStream().flush();
    }


    /**
     * 获取数据的方法
     * @return
     */
    public List<Message>  getMsgList(){
   
   
        String  jsonArrayString ="[{\"type\": 1, \"content\": \"嘿,听说最近有点八卦,你有什么料吗?\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:46:12\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \"当然啦!你知道吗,最近有个超级智能机器人在公司里开始工作了,听说能做我们的工作,让人有点担心啊。\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:47:17\", \"sendUser\": \"修己\"}, {\"type\": 1, \"content\": \" 哇,真的吗?那它是怎么工作的?会不会抢我们的饭碗?\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:48:22\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \" 别慌,听说它只是一个AI助手,能够处理一些重复性的任务,让我们有更多时间专注于创造性的工作。\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:49:12\", \"sendUser\": \"修己\"}, {\"type\": 1, \"content\": \" 哦,原来如此。不过,你觉得这个机器人有没有潜在的危险性啊?\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:50:18\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \"哈哈,我也有点担心,但听说它的设计是为了帮助我们,而不是取代我们。还有,它可不会参与任何八卦。\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:51:42\", \"sendUser\": \"修己\"}, {\"type\": 1, \"content\": \" 哈哈,说到八卦,你有听说最近公司里有什么有趣的事情吗?\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:52:12\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \"对呀,听说某某老板最近秘密约会了某某同事,整个公司都在传。你觉得是真的吗?照片都爆出来了,给你发下\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:53:12\", \"sendUser\": \"修己\"}, {\"type\": 2, \"content\": \"http://chevereto.xiuji.mynatapp.cc/images/2023/06/27/z_3b2fa0f1285d8071229631addebf3087.png\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:54:12\", \"sendUser\": \"修己\"}, {\"type\": 2, \"content\": \"http://chevereto.xiuji.mynatapp.cc/images/2023/06/23/_20230623212425.png\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:55:12\", \"sendUser\": \"修己\"}, {\"type\": 2, \"content\": \"http://chevereto.xiuji.mynatapp.cc/images/2023/06/27/z_73bc1ab1a92d68b15e9e1babc3d15afb.png\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:56:12\", \"sendUser\": \"修己\"}, {\"type\": 2, \"content\": \"http://chevereto.xiuji.mynatapp.cc/images/2023/06/23/_20230623212445.png\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:57:12\", \"sendUser\": \"修己\"}, {\"type\": 1, \"content\": \"哇,这可真是八卦大爆炸啊!我倒是没听说,但如果是真的,那可真是太有趣了。\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:58:12\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \"是啊,公司里的八卦总是让人忍不住想知道更多。有时候我觉得,我们也是一群活在八卦世界里的AI助手。\", \"fileName\": null, \"sendTime\": \"2024-01-25 21:59:12\", \"sendUser\": \"修己\"}, {\"type\": 1, \"content\": \"哈哈,说得对!我们也需要一些八卦来调剂一下生活。不过,我还是觉得那个超级智能机器人有点神秘呢。\", \"fileName\": null, \"sendTime\": \"2024-01-25 22:02:36\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \"是啊,或许我们也能通过它得知更多关于公司内幕的事情。要不要试试向它搭讪?\", \"fileName\": null, \"sendTime\": \"2024-01-25 22:05:36\", \"sendUser\": \"修己\"}, {\"type\": 1, \"content\": \" 哈哈,说不定它能告诉我们更多关于那对神秘约会的内幕。不过,我们还是小心点为好,免得被当成八卦机器人。\", \"fileName\": null, \"sendTime\": \"2024-01-25 22:08:36\", \"sendUser\": \"大雄\"}, {\"type\": 1, \"content\": \"顺便给你在发个我最近吃的瓜的pdf\", \"fileName\": null, \"sendTime\": \"2024-01-25 22:12:36\", \"sendUser\": \"修己\"}, {\"type\": 4, \"content\": \"http://chevereto.xiuji.mynatapp.cc/images/2023/06/23/西安外国语大学会计ACCA2001班丁玉婕出轨情况说明.pdf\", \"fileName\": \"西安外国语大学会计ACCA2001班丁玉婕出轨情况说明.pdf\", \"sendTime\": \"2024-01-25 22:18:36\", \"sendUser\": \"修己\"}]";
        // 使用Fastjson将JsonArray字符串解析为JSONArray对象
        JSONArray jsonArray = JSONArray.parseArray(jsonArrayString);
        List<Message> messageList = new ArrayList<>();
        // 遍历JsonArray,将每个JSON对象转换为Message对象并添加到List中
        for (Object jsonMessage : jsonArray) {
   
   
            JSONObject jsonObject = (JSONObject) jsonMessage;
            Message message = jsonObject.toJavaObject(Message.class);
            messageList.add(message);
        }
        return messageList;
    }
}

controller代码

import cn.xj.xjdoc.system.service.PdfService;
import com.itextpdf.text.DocumentException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@Slf4j
public class PdfController {
   
   

    @Resource
    private PdfService pdfService;

    @GetMapping("/export")
    public void export(HttpServletResponse response) throws DocumentException, IOException {
   
   
        pdfService.export(response);
    }
}

代码到这就完了,我们可以启动服务,查看下展示的效果:

_20240126070304.jpg

_20240126070314.jpg

_20240126070325.jpg

到这儿,如果不出意外的话肯定要出意外了,请继续阅读文章。

Linux上解决中文没写入或者乱码问题

如果我们将服务部署到Linux服务器上,可能会遇到中文未正确写入或乱码的问题。这是由于Linux系统上的字体库与Windows系统不同。为了解决这个问题,我们可以在代码中直接将所需字体的ttf文件复制到项目目录下,并使用itextpdf加载这些字体。此前,我们成功为服务器添加了Windows字体库,因此我们可以直接从系统中获取字体。接下来,我们将介绍在Linux中添加Windows字体的操作步骤。

windows字体库的位置:C:\Users\Administrator\AppData\Local\Microsoft\Windows\Fonts

Linux 中添加windows字体库

  • ubuntu

将windows的字体库Fonts 复制到目录 /usr/share/fonts 下,执行如下权限命令:

sudo chmod -R 777 Fonts

然后执行以下命令使字体生效

sudo fc-cache -fv
  • centos

将windows的字体库Fonts下的文件 复制到目录 /usr/share/fonts 下,依次执行如下命令:

yum install -y mkfontscale

yum install -y fontconfig

cd /usr/share/fonts/

mkfontscale

mkfontdir

fc-cache

fc-list

构建具有windows字体库的docker镜像

  • ubuntu

Dockerfile

# 基于哪个镜像
FROM ubuntu:20.10

# 维护者
MAINTAINER xj

# 拷贝文件到容器
ADD Fonts /usr/share/fonts/chinese_font/
RUN chmod -R 755 /usr/share/fonts/chinese_font
RUN fc-cache -fv
  • centos

Dockerfile

# 基于哪个镜像
FROM centos:centos7.1.1503
# 维护者
MAINTAINER xj
ADD Fonts/* /usr/share/fonts/
RUN yum install -y mkfontscale
RUN yum install -y fontconfig
RUN cd /usr/share/fonts/
RUN mkfontscale
RUN mkfontdir
RUN fc-cache
RUN fc-list

总结

这个例子演示了如何使用Spring Boot和iTextPDF创建动态的、个性化的PDF文档。你可以根据实际需求扩展生成的PDF内容,包括图表、表格等,以满足项目的特定要求。希望这篇文章对你有所帮助!如果有任何问题或建议,请随时提出。

目录
相关文章
|
16天前
|
存储 运维 安全
《VERICUT 9.X电脑配置要求与安装说明(官方)》—— PDF文档
本文档详细介绍了 CGTech Vericut 9.X 的安装指南,涵盖安装前准备、安装流程、系统要求、故障排除、许可证配置及多种接口配置等内容,为用户提供全面的软件安装与配置指导。
39 2
|
2月前
|
API C#
在.NET中使用QuestPDF高效地生成PDF文档
在.NET中使用QuestPDF高效地生成PDF文档
|
3月前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
703 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
5月前
|
C# 开发者 Windows
WPF与PDF文档:解锁创建和编辑PDF文件的新技能——从环境配置到代码实践,手把手教你如何在WPF应用中高效处理PDF,提升文档管理效率
【8月更文挑战第31天】随着数字文档的普及,PDF因跨平台兼容性和高保真度成为重要格式。WPF虽不直接支持PDF处理,但借助第三方库(如iTextSharp)可在WPF应用中实现PDF的创建与编辑。本文通过具体案例和示例代码,详细介绍了如何在WPF中集成PDF库,并展示了从设计用户界面到实现PDF创建与编辑的完整流程。不仅包括创建新文档的基本步骤,还涉及在现有PDF中添加页眉页脚等高级功能。通过这些示例,WPF开发者可以更好地掌握PDF处理技术,提升应用程序的功能性和实用性。
195 0
|
5月前
|
开发框架 前端开发 JavaScript
在Winform分页控件中集成导出PDF文档的功能
在Winform分页控件中集成导出PDF文档的功能
|
5月前
[PDF提取重命名]提取识别文字并对PDF文件批量重命名,提取PDF指定可复制的内容并批量重命名PDF,批量PDF文档指定识别提取区域
本文介绍一款实用工具,能快速从可复制内容的PDF中提取指定区域信息并据此重命名文件。设置提取坐标及导入PDF文档、设定新文件名后启动提取流程,即可高效批量处理。保存坐标设置以便重复使用,适用于需频繁修改大量PDF文件名的场景。
494 0
[PDF提取重命名]提取识别文字并对PDF文件批量重命名,提取PDF指定可复制的内容并批量重命名PDF,批量PDF文档指定识别提取区域
|
6月前
|
JavaScript Java
Java 将Markdown文件转换为Word和PDF文档
【7月更文挑战第5天】Java中使用`Spire.Doc for Java`库可方便地将Markdown转换为Word或PDF。基本步骤包括导入模块,创建`Document`对象,加载Markdown文件,然后保存为目标格式(`.docx`或`.pdf`)。若遇到`Invalid UTF-8 stream`错误,需确保Markdown文件是UTF-8无BOM编码。页面设置可通过`PageSetup`类调整。注意,实际应用会依据具体需求和环境有所调整。
348 6
|
6月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的旅游攻略系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的旅游攻略系统的详细设计和实现(源码+lw+部署文档+讲解等)
134 7
|
6月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的在线招聘平台的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的在线招聘平台的详细设计和实现(源码+lw+部署文档+讲解等)
|
6月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的公交系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的公交系统的详细设计和实现(源码+lw+部署文档+讲解等)