PdfBox品尝(I) 常用方法的简单封装

项目最近需要动态的生成pdf文件。一开始采用的是牛逼好用的Itext,但是后来由于Itext的license协议是GPL的。所以最终决定用pdfbox来替换掉它;

在研究pdfbox的过程中,发现pdfbox跟Itext差距不是一般的大,pdfbox提供的都是一些基础的“画pdf”的方法。对此,为了项目和日常使用的方便,简单封装了一个utils工具类。

public class PdfBoxUtils {

    /**
     * 插入一张图片
     * @param document
     * @param contentStream 输出流
     * @param imgFile 图片文件
     * @param xStart x主标
     * @param yStart y主标
     * @param width 图片宽
     * @param hight 图片高
     * @throws IOException
     */
    public static void drawImage(PDDocument document, PDPageContentStream contentStream, File imgFile, float xStart, float yStart, float width, float hight) throws IOException {
        PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, document);
        contentStream.drawImage(pdImage, xStart, yStart, width, hight );
    }


    /**
     * 画一条线
     * @param contentStream
     * @param xStart
     * @param yStart
     * @param xEnd
     * @param yEnd
     * @throws IOException
     */
    public static void drawLine(PDPageContentStream contentStream, float xStart, float yStart, float xEnd, float yEnd) throws IOException {
        contentStream.moveTo(xStart, yStart);
        contentStream.lineTo(xEnd, yEnd);
        contentStream.stroke();
    }

    /**
     * 定义文本输出流开始
     * @param contentStream
     * @param leading 行距
     * @param offSetX 第一行的x坐标间距
     * @param offSetY 第一行的y主表间距
     * @throws IOException
     */
    public static void beginTextSteam(PDPageContentStream contentStream, Float leading, Float offSetX, Float offSetY) throws IOException {
        // 输出流开始
        contentStream.beginText();
        // 行间距
        contentStream.setLeading(leading);
        // 书写行定位
        contentStream.newLineAtOffset(offSetX, offSetY);
    }

    /**
     * 定义文本输出流结束
     * @param contentStream
     * @throws IOException
     */
    public static void endTextSteam(PDPageContentStream contentStream) throws IOException {
        contentStream.endText();
    }

    /**
     * 创建一定数量的空行
     * @param contentStream
     * @param emptyNum
     * @throws IOException
     */
    public static void createEmptyParagraph(PDPageContentStream contentStream, Integer emptyNum) throws IOException {
        for (int i = 0; i < emptyNum; i++) {
            contentStream.newLine();
        }
    }


    /**
     * 写一个段落
     * @param contentStream
     * @param text
     * @throws IOException
     */
    public static void drawParagraph(PDPageContentStream contentStream, String text) throws IOException {
        contentStream.showText(text);
        contentStream.newLine();
    }

    /**
     * 写一个段落并设定字体
     * @param contentStream
     * @param text
     * @throws IOException
     */
    public static void drawParagraph(PDPageContentStream contentStream, String text, PDFont font, Integer fontSize) throws IOException {
        contentStream.setFont(font, fontSize);
        contentStream.showText(text);
        contentStream.newLine();
    }


    /**
     * 将html转为pdf文件(中文)
     * <p>采用的是openhtmltopdf插件</p>
     * <p>注意html中每一个标签必须要有结尾标签</p>
     * <p>如果要解决中文乱码问题,请在<head></head>标签中加上css样式
     * <style>
     *  *{
     *      font-family: arialuni(这里填上对应的中文字体名)
     *        }
     * </style></p>
     * @param outputStream pdf文件输出流
     * @param htmlFile html文件
     * @param fontInputStream 字体文件输出流(自定义字体文件,解决中文乱码问题)如:simsun.tff(宋体)
     * @param fontFamily 字体名,如:simsun(宋体)
     * @throws IOException
     */
    public static void convertHtmlToPdf(OutputStream outputStream, File htmlFile, InputStream fontInputStream, String fontFamily) throws IOException {
        PdfRendererBuilder builder = new PdfRendererBuilder();
        builder.useFastMode();
        builder.useFont(setFSFont(fontInputStream), fontFamily);
        builder.withFile(htmlFile);
        builder.toStream(outputStream);
        builder.run();
    }

    /**
     * 将html转为pdf文件(中文)
     * <p>采用的是openhtmltopdf插件</p>
     * <p>注意html中每一个标签必须要有结尾标签</p>
     * <p>如果要解决中文乱码问题,请在<head></head>标签中加上css样式
     * <style>
     *  *{
     *      font-family: arialuni(这里填上对应的中文字体名)
     *        }
     * </style></p>
     * @param outputStream pdf文件输出流
     * @param htmlFileStr Provides a string containing XHTML/XML to convert to PDF.
     * @param fontInputStream 字体文件输出流(自定义字体文件,解决中文乱码问题)如:simsun.tff(宋体)
     * @param fontFamily 字体名,如:simsun(宋体)
     * @throws IOException
     */
    public static void convertHtmlToPdf(OutputStream outputStream, String htmlFileStr, InputStream fontInputStream, String fontFamily) throws IOException {
        PdfRendererBuilder builder = new PdfRendererBuilder();
        builder.useFastMode();
        builder.useFont(setFSFont(fontInputStream), fontFamily);
        builder.withHtmlContent(htmlFileStr, null);
        builder.toStream(outputStream);
        builder.run();
    }

    /**
     * 将html转为pdf文件
     * <p>采用的是openhtmltopdf插件</p>
     * <p>注意html中每一个标签必须要有结尾标签</p>
     * @param outputStream pdf文件输出流
     * @param htmlFile html文件
     * @throws IOException
     */
    public static void convertHtmlToPdf(OutputStream outputStream, File htmlFile) throws IOException {
        PdfRendererBuilder builder = new PdfRendererBuilder();
        builder.useFastMode();
        builder.withFile(htmlFile);
        builder.toStream(outputStream);
        builder.run();
    }

    /**
     * 返回自定义字体
     * @param fontInputStream
     * @return
     */
    private static FSSupplier<InputStream> setFSFont(InputStream fontInputStream) {
        return () -> fontInputStream;
    }

上面这些代码引用的是pdfbox的jar和openHtmlToPdf的jar。其实只引用openHtmlToPdf就完全够了,因为openHtmlToPdf是一个基于pdfbox封装的处理html==>pdf的jar,里头提供了pdfbox。我实际是直接引用了openHtmlToPdf,如果项目中没有涉及到html转pdf,直接引用pdfbox的jar就行了。

需要html转pdf
        <!-- https://mvnrepository.com/artifact/com.openhtmltopdf/openhtmltopdf-core -->
        <dependency>
            <groupId>com.openhtmltopdf</groupId>
            <artifactId>openhtmltopdf-core</artifactId>
            <version>1.0.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.openhtmltopdf/openhtmltopdf-pdfbox -->
        <dependency>
            <groupId>com.openhtmltopdf</groupId>
            <artifactId>openhtmltopdf-pdfbox</artifactId>
            <version>1.0.4</version>
        </dependency>
只需要pdfbox
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>fontbox</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>xmpbox</artifactId>
            <version>2.0.0</version>
        </dependency>

pdfbox写pdf中文乱码的问题

pdfbox如果写入中文,会出现不显示/乱码的问题,解决的办法最直接的是引入自己下载好的字体;

PDDocument document = new PDDocument();
PDType0Font font = PDType0Font.load(document, new FileInputStream(new File("d:\\tmp\\arialuni.ttf")));

实例demo

    private PDRectangle pageSize = PDRectangle.A4;

    private Integer marginX = 50;
    private Integer marginY = 50;

    @Test
    public void test1() throws IOException {
        PDDocument document = new PDDocument();
        PDType0Font font = PDType0Font.load(document, new FileInputStream(new File("d:\\tmp\\arialuni.ttf")));
        drawFirstPage(document, font);
        // drawSecondPage(document, font);
        document.save(new FileOutputStream(new File("d:\\tmp\\test2.pdf")));
        document.close();
    }

    private void drawFirstPage(PDDocument document, PDType0Font font) throws IOException {
        PDPage pdPage = new PDPage(pageSize);
        document.addPage(pdPage);
        PDPageContentStream contentStream = new PDPageContentStream(document, pdPage);

        PdfBoxUtils.beginTextSteam(contentStream, 20f, marginX.floatValue(), pageSize.getHeight()-(2*marginY));
        // 书写信息
        PdfBoxUtils.drawParagraph(contentStream, "物流单摘要", font, 18);
        PdfBoxUtils.createEmptyParagraph(contentStream, 2);

        contentStream.setFont(font, 13);
        PdfBoxUtils.drawParagraph(contentStream, "物流单号:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a02022099");
        PdfBoxUtils.drawParagraph(contentStream, "结算时间段:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0从20200909到20200807");
        PdfBoxUtils.drawParagraph(contentStream, "商品总数量(件):\u00a0100000");
        PdfBoxUtils.drawParagraph(contentStream, "商品总价格(元):\u00a0100000000000");
        PdfBoxUtils.drawParagraph(contentStream, "买卖人名称:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0李白");
        PdfBoxUtils.createEmptyParagraph(contentStream, 4);

        PdfBoxUtils.drawParagraph(contentStream, "公司(盖章):");
        PdfBoxUtils.createEmptyParagraph(contentStream, 2);
        contentStream.showText("日期:");

        PdfBoxUtils.createEmptyParagraph(contentStream, 16);
        contentStream.newLineAtOffset(195, 0);
        PdfBoxUtils.drawParagraph(contentStream, "小熊猫超级防伪码", font, 12);

        PdfBoxUtils.endTextSteam(contentStream);

        // 划线
        PdfBoxUtils.drawLine(contentStream, marginX, 545, PDRectangle.A4.getWidth() - marginX, 545);
        PdfBoxUtils.drawLine(contentStream, marginX, 410, PDRectangle.A4.getWidth() - marginX, 410);

        // 贴图
        PdfBoxUtils.drawImage(document, contentStream, new File("d:\\tmp\\条形码测试.png"),
                (pageSize.getWidth()/2)-80, 150, 160, 160);

        contentStream.close();
    }

pdf效果


image.png

如何用pdfbox画table表格

与Itext不同,pdfbox并没有直接提供画table的方法;要想用pdfbox生成table,需要我们自己用它的grid来画;这里头我也封装了一个可用的table,在这片文章篇幅问题我就不引入,我会接下来在我另一篇文章专门介绍。(ps:第一次在简书发表文章,如有不足欢迎指出~笑脸)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。