使用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可以向某个坐标插入文字,但是这种方式个人认为开发时心智负担较大,不好调试,实际效果可能还不如使用模板的方式。

相关推荐
北风朝向10 小时前
Spring Boot参数校验8大坑与生产级避坑指南
java·spring boot·后端·spring
canonical_entropy11 小时前
一份关于“可逆计算”的认知解码:从技术细节到哲学思辨的完整指南
后端·低代码·deepseek
趙卋傑11 小时前
项目发布部署
linux·服务器·后端·web
数据知道12 小时前
Go基础:Go语言能用到的常用时间处理
开发语言·后端·golang·go语言
不爱编程的小九九13 小时前
小九源码-springboot048-基于spring boot心理健康服务系统
java·spring boot·后端
龙茶清欢13 小时前
Spring Boot 应用启动组件加载顺序与优先级详解
java·spring boot·后端·微服务
2351614 小时前
【LeetCode】3. 无重复字符的最长子串
java·后端·算法·leetcode·职场和发展
可观测性用观测云14 小时前
解锁DQL高级玩法——对日志关键信息提取和分析
后端
Chan1615 小时前
【 设计模式 | 结构型模式 代理模式 】
java·spring boot·后端·设计模式·intellij-idea
南囝coding15 小时前
Vercel 发布 AI Gateway 神器!可一键访问数百个模型,助力零门槛开发 AI 应用
前端·后端