使用Apache Pdfbox生成pdf

前言

笔者翻遍了网上关于Java生成pdf文章,发现大多都是使用itext来生成pdf,但是由于协议问题,这个方案对于商用不太友好。另外还有一种方式是采用Apache基金会开源的pdfbox来生成pdf,这也是笔者这次使用的方案,但是目前网上的博客或多或少都存在一些问题,于是我决定将自己的方案分享出来供大家参考。

方案主要分为下面3步:

  1. 准备pdf表单
  2. 生成pdf
  3. 将pdf转为图片并写入pdf文件

Maven依赖:

xml 复制代码
            <dependency>
                <groupId>org.apache.pdfbox</groupId>
                <artifactId>pdfbox</artifactId>
                <version>2.0.26</version>
            </dependency>

1.准备pdf表单

本文使用Acrobat软件准备pdf表单

1.1 打开Acrobat

​编辑

1.2 打开已有的pdf模板

​编辑

1.3 选择添加文本域

一个文本域其实就是一个表单项。

​编辑

​编辑

1.4 编辑文本域

双击文本域即可进入编辑窗口,可以设置文本的样式等信息。这里的文本域的名称就是表单项名称,后续在代码中我们可以根据这个名称向指定的表单项插入值。

​编辑

1.5 保存模板

准备好表单后保存模板即可

​编辑

2.生成pdf

2.1 导入资源文件

将刚刚生成的pdf已经提前准备好的字体文件导入。

ini 复制代码
        String filePath = attachmentArchiveFolder + "/" + RandomUtils.randomInt(1,100) + ".pdf";
InputStream inputStream = new ClassPathResource("templates/pdfTemplate.pdf").getInputStream();
             // 按需要添加特殊的字体,但是这会导致生成的PDF变大
             InputStream microsoftYaHeiBold = new ClassPathResource("fonts/微软雅黑粗体.ttf").getInputStream();
             InputStream sourceHanSerifCN = new ClassPathResource("fonts/SourceHanSerifCN-Regular.ttf").getInputStream();
             PDDocument pdfDocument = PDDocument.load(inputStream);
             FileOutputStream fileOut = new FileOutputStream(filePath)

2.2 准备好表单以及字体

这里的titleApperance以及contentAppearance很重要,他们决定了pdf中文字的样式。

注意:

在导入字体时笔者使用

ini 复制代码
PDFont microsoftYaHeiBoldFont = PDType0Font.load(pdfDocument,microsoftYaHeiBold,false);

参数中的false意味不使用字体子集。这使得生成的pdf中会包含字体文件,导致pdf文件过大。

在笔者导入两个字体文件不作特殊处理的情况下文件大小将达到40MB左右,这取决于你的字体文件大小。

解决方案有三种:

1.如果你的pdf文件中不需要插入中文,那么可以不导入字体,导入字体的原因是pdf自带的字体不支持中文。

2.将false参数去掉或者改为true。这种情况下代码会读取字体文件的最小子集。但是这种方式在实际使用中很容易出现文件中缺失某个文字导致生成pdf失败的情况。

3.第三种方式是笔者采用的将生成的pdf转为图片再重新写入。

ini 复制代码
PDDocumentCatalog documentCatalog = pdfDocument.getDocumentCatalog();
            PDAcroForm acroForm = documentCatalog.getAcroForm();
            PDResources res = acroForm.getDefaultResources();
            PDFont microsoftYaHeiBoldFont = PDType0Font.load(pdfDocument, microsoftYaHeiBold,false);
            PDFont sourceHanSerifCNFont = PDType0Font.load(pdfDocument, sourceHanSerifCN,false);
            // 添加字体
            String microsoftYaHeiBoldFontName = res.add(microsoftYaHeiBoldFont).getName();
            String sourceHanSerifCNFontName = res.add(sourceHanSerifCNFont).getName();
            String titleAppearance = "/" + microsoftYaHeiBoldFontName + " 18 Tf 0 g";
            String contentAppearance = "/" + sourceHanSerifCNFontName + " 11 Tf 0 g";

2.3填充表单并写入输出流

这里的field即为制作表单时填写的表单项名称。至此pdf实际上已经制作完成,但是目前还有一个问题没有解决:pdf文件过大。

注意:

ini 复制代码
other.setDefaultAppearance(contentAppearance);

这行代码很重要,否则生成的表单将会十分难看!

ini 复制代码
PDVariableText title = (PDVariableText) acroForm.getField("title");
            title.setQ(PDVariableText.QUADDING_LEFT);
            title.setDefaultAppearance(titleAppearance);
            title.setValue(generatePDFDTO.getTitle());
pdfDocument.save(fileOut);

3.将pdf转为图片并写入pdf文件

这里的originalDocument就是上面的步骤中的PDDocument,我们将pdf文件的每一页都转换为图片再将图片写为一个新的pdf文件。经过测试,一个40MB的文件转换后仅300KB左右,提升了约99%的空间利用率。如果对于图片质量要求不高还可以进一步压缩。

ini 复制代码
PDDocument newDocument = new PDDocument();
        PDFRenderer pdfRenderer = new PDFRenderer(originalDocument);
        for (int page = 0; page < originalDocument.getNumberOfPages(); ++page) {
            BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
            PDImageXObject pdImage = PDImageXObject.createFromByteArray(newDocument, convertBufferedImageToByteArray(bim), "image-" + page);
            PDPage newPage = new PDPage(new PDRectangle(pdImage.getWidth(), pdImage.getHeight()));
            newDocument.addPage(newPage);
            PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
            contentStream.drawImage(pdImage, 0, 0);
            contentStream.close();
        }
        newDocument.save("final-output.pdf");
        newDocument.close();
arduino 复制代码
public byte[] convertBufferedImageToByteArray(BufferedImage image) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "PNG", baos); // 可以根据需要选择格式
        return baos.toByteArray();
    }

结尾

除了笔者本次所述生成pdf的方案外,还有很多方式可以生成pdf文档。例如可以让前端使用HTML生成pdf,或者在使用pdfbox时直接写入所有的pdf内容而不使用模板。

笔者这套方案的主要优势是方便生成有较为复杂的样式的pdf文件,同时可以较为精确的控制生成pdf文件。当然这里的精确是相对的,实际上pdfbox可以向某个坐标插入文字,但是这种方式个人认为开发时心智负担较大,不好调试,实际效果可能还不如使用模板的方式。

相关推荐
亚力山大抵8 分钟前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
超级小忍18 分钟前
Spring Boot 中常用的工具类库及其使用示例(完整版)
spring boot·后端
CHENWENFEIc1 小时前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试
重庆小透明2 小时前
力扣刷题记录【1】146.LRU缓存
java·后端·学习·算法·leetcode·缓存
博观而约取2 小时前
Django 数据迁移全解析:makemigrations & migrate 常见错误与解决方案
后端·python·django
寻月隐君3 小时前
Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式
后端·rust·github
GO兔3 小时前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go
Sincerelyplz3 小时前
【Temproal】快速了解Temproal的核心概念以及使用
笔记·后端·开源
爱上语文3 小时前
Redis基础(6):SpringDataRedis
数据库·redis·后端
Lemon程序馆3 小时前
速通 GO 垃圾回收机制
后端·go