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:第一次在简书发表文章,如有不足欢迎指出~笑脸)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355