[Java]使用Apache Pdfbox生成pdf

前言        

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

        方案主要分为下面3步:

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

Maven依赖:

            <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已经提前准备好的字体文件导入。

        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中文字的样式。

注意:

在导入字体时笔者使用

PDFont microsoftYaHeiBoldFont = PDType0Font.load(pdfDocument,microsoftYaHeiBold,false);

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

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

解决方案有三种:

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

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

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

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文件过大。

注意:

other.setDefaultAppearance(contentAppearance);

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

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%的空间利用率。如果对于图片质量要求不高还可以进一步压缩。

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

Apache PDFBox是一个强大的Java库,用于处理PDF文档,包括读取、修改和生成PDF文件。以下是使用PDFBox生成PDF的基本步骤: 1. **添加依赖**:如果你的项目使用Maven,可以在pom.xml中添加PDFBox依赖: ```xml <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.24</version> <!-- 最新版本 --> </dependency> ``` 2. **创建PDF工具类**: ```java import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDFType1Font; public class PdfBoxExample { public static void createPdf() { try { // 创建一个新的PDDocument对象 PDDocument document = new PDDocument(); // 创建一个新的PDPage,并将其添加到文档中 PDPage page = new PDPage(); document.addPage(page); // 获取页面的content stream PDPageContentStream contentStream = new PDPageContentStream(document, page); // 设置字体 contentStream.setFont(PDFType1Font.HELVETICA, 14); // 写入文本 contentStream.beginText(); contentStream.newLineAtOffset(72, 600); // 72pt是1英寸的点数,(x,y)坐标位置 contentStream.showText("Hello, Apache PDFBox!"); contentStream.endText(); // 保存并关闭文档 document.save("output.pdf"); document.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 3. **运行示例**: 调用`PdfBoxExample.createPdf();`方法即可生成PDF。 注意,这只是一个基础的示例,实际应用中可能还需要处理更复杂的内容,如图片、表格、页眉页脚等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值