OpenPDF使用教程及样例代码

使用OpenPDF生成pdf文档。

Java生成PDF文档的三方库

  • iText:仅限于仅限于个人用途或者开源项目,商业使用需要收费

  • OpenPDF:基于iText的一个分支发展而来,商业友好

  • Apache PDFBox:github上fork和star都比iText要高,不过笔者目前还未使用过,列出来只是告诉读者有这么一个生成PDF的库

OpenPDF可生成的内容

总的来说,OpenPDF可以支持的内容并不丰富,如无法生成条形图、折线图、饼图等,也无法在表格中插入图片,OpenPDF生成的PDF文档无法做到Word那么丰富。

以下是本次笔者自己摸索的OpenPDF支持的功能,如有遗漏,请在评论区指出:

设置PDF文件属性

包括标题、主题、作者、文档创建时间、PDF制作者、PDF制作程序、关键字、自定义属性。

设置页面属性

包括页面大小、上下左右页边距、页眉、页脚、页码.

插入文字段落

生成文本可以使用Chunk、Phrase、Paragraph三种类型,三者之间的关系为:Chunk implements Element,TextElementArray extends Element,Phrase implements TextElementArray,Paragraph extends Phrase

文本块Chunk是设置字体格式的最小单位,可以设置字体、字体颜色、背景颜色等,写满一行之后自动排列到下一行,Chunk可被包含于PhraseParagraph

短句Phrase可以包含多个Chunk,默认行间距是字体大小的1.5倍,写满一行之后自动排列到下一行,但Phrase之间并不会自动在末尾添加换行符。

段落ParagraphPhrase类似,但间距控制比Phrase好,不会出现两行文字重叠的情况,写满一行之后同样可以自动排列到下一行,Paragraph在自身内容前后都会添加换行符。一般情况下,个人建议使用Paragraph,省去一些排版的麻烦。

制表符\t在不同环境不同情况下的长短不一致,包括各类IDE也基本上推荐使用空格替代制表符,所以我们在PDF文档中也使用空格替代制表符。制表符也可以显示的,但经过实测,只有在仅有英文字符且未设置字体时才可以正常显示。测试结果请查看下文代码生成的PDF文档。

插入图片

可以加载外部图片,重设尺寸后插入到PDF文档中。也可以指定图片的绝对坐标,更多设置请自行查看Image相关方法。

插入表格

表格有两个基本的元素:Table和Cell,其中Table可以认为是一张画布,Cell是放置于其上的拼图。

Table和Cell均可设置其自身的边框样式、背景颜色、对齐方式,Table还可设置Cell之间的间隔等,具体设置请查阅下文代码、代码注释,并用生成的PDF文档对照理解。

其它

由于OpenPDF不支持中文,所以需要依赖第三方库或者本地字库,本文采用读取本地字库的方式,由MyFontUtil.java读取本地字库并预设常用字体。

Windows的字库文件位于C:\Windows\Fonts\

代码

pom.xml

<dependency>
    <groupId>com.github.librepdfgroupId>
    <artifactId>openpdfartifactId>
    <version>1.3.30version>
dependency>

MyFontUtil.java

package com.example.study.util;

import com.lowagie.text.Font;
import com.lowagie.text.pdf.BaseFont;

import java.io.IOException;

public class MyFontUtil {
    public static final Font SIMSUN_H1 = new Font(MyBaseFonts.SIMSUN, 32);
    public static final Font SIMSUN_H2 = new Font(MyBaseFonts.SIMSUN, 24);
    public static final Font SIMSUN_H3 = new Font(MyBaseFonts.SIMSUN, 18);
    public static final Font SIMSUN_TEXT = new Font(MyBaseFonts.SIMSUN, 10.5f);
    public static final Font SIMSUN_REMARK = new Font(MyBaseFonts.SIMSUN, 8);
    public static final Font MSYH_H1 = new Font(MyBaseFonts.MSYH, 32);
    public static final Font MSYH_H2 = new Font(MyBaseFonts.MSYH, 24);
    public static final Font MSYH_H3 = new Font(MyBaseFonts.MSYH, 18);
    public static final Font MSYH_TEXT = new Font(MyBaseFonts.MSYH, 10.5f);
    public static final Font MSYH_REMARK = new Font(MyBaseFonts.MSYH, 8);
    public static final Font MSYHBD_H1 = new Font(MyBaseFonts.MSYHBD, 32);
    public static final Font MSYHBD_H2 = new Font(MyBaseFonts.MSYHBD, 24);
    public static final Font MSYHBD_H3 = new Font(MyBaseFonts.MSYHBD, 18);
    public static final Font MSYHBD_TEXT = new Font(MyBaseFonts.MSYHBD, 10.5f);
    public static final Font MSYHBD_REMARK = new Font(MyBaseFonts.MSYHBD, 8);
    public static final Font MSYHL_H1 = new Font(MyBaseFonts.MSYHL, 32);
    public static final Font MSYHL_H2 = new Font(MyBaseFonts.MSYHL, 24);
    public static final Font MSYHL_H3 = new Font(MyBaseFonts.MSYHL, 18);
    public static final Font MSYHL_TEXT = new Font(MyBaseFonts.MSYHL, 10.5f);
    public static final Font MSYHL_REMARK = new Font(MyBaseFonts.MSYHL, 8);

    public static class MyBaseFonts {
        public static BaseFont SIMSUN;
        public static BaseFont MSYH;
        public static BaseFont MSYHBD;
        public static BaseFont MSYHL;

        static {
            try {
                SIMSUN = BaseFont.createFont("C:\\Windows\\Fonts\\simsun.ttc,0", BaseFont.IDENTITY_H, true);
                // microsoft yahei=msyh
                MSYH = BaseFont.createFont("C:\\Windows\\Fonts\\msyh.ttc,0", BaseFont.IDENTITY_H, true);
                MSYHBD = BaseFont.createFont("C:\\Windows\\Fonts\\msyhbd.ttc,0", BaseFont.IDENTITY_H, true);
                MSYHL = BaseFont.createFont("C:\\Windows\\Fonts\\msyhl.ttc,0", BaseFont.IDENTITY_H, true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

PdfUtil.java

package com.example.study.util;


import com.lowagie.text.Cell;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.HeaderFooter;
import com.lowagie.text.Image;
import com.lowagie.text.List;
import com.lowagie.text.ListItem;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Table;
import com.lowagie.text.alignment.HorizontalAlignment;
import com.lowagie.text.alignment.VerticalAlignment;
import com.lowagie.text.pdf.PdfWriter;

import java.awt.Color;
import java.io.FileOutputStream;
import java.io.IOException;

public class PdfUtil {
    public static void main(String[] args) throws Exception {
        String pdfFile = "F:\\pdf\\demo.pdf";
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFile));
        document.open();
        setMetaInfo(document);
        setPageInfo(document);

        setCover(document);
        addText(document);
        addImage(document);
        addTable(document);

        document.close();
        writer.close();
    }

    /**
     * 添加表格
     * 表格有两个基本的元素:Table和Cell,其中Table可以认为是一张画布,Cell是放置于其上的拼图。这就是OpenPDF中表格的基本结构
     *
     * @param document pdf文档对象
     */
    private static void addTable(Document document) {
        document.add(new Paragraph("三、生成表格示例", MyFontUtil.SIMSUN_H2));
        // 列数是必需的,行数不那么重要,即便超过3行也可以添加到表格中
        Table table = new Table(5, 3);
        setTableStyle(table);
        table.addCell(createTitleCell("序号"));
        table.addCell(createTitleCell("姓名"));
        table.addCell(createTitleCell("性别"));
        table.addCell(createTitleCell("籍贯"));
        table.addCell(createTitleCell("备注"));
        table.addCell(createDataCell("1"));
        table.addCell(createDataCell("王冰冰"));
        table.addCell(createDataCell("女"));
        table.addCell(createDataCell("吉林省长春市"));
        table.addCell(createDataCell("央视记者"));
        table.addCell(createDataCell("2"));
        table.addCell(createDataCell("庄晓莹"));
        table.addCell(createDataCell("女"));
        table.addCell(createDataCell("福建省三明市"));
        table.addCell(createDataCell("中央电视广播总台国防军事频道编导、记者"));
        table.addCell(createDataCell("3"));
        table.addCell(createDataCell("张三"));
        table.addCell(createDataCell("男"));
        table.addCell(createDataCell("厚大法考"));
        table.addCell(createDataCell("法外狂徒"));
        table.addCell(createDataCell("4"));
        table.addCell(createDataCell("后面是默认填充→"));
        document.add(table);
        Paragraph tableRemark = new Paragraph();
        tableRemark.setFont(MyFontUtil.SIMSUN_REMARK);
        tableRemark.setAlignment(Element.ALIGN_CENTER);
        tableRemark.add("使用openpdf生成表格示例");
        document.add(tableRemark);
    }

    /**
     * 设置表格的数据单元格样式
     *
     * @param text 单元格内容
     */
    private static Cell createDataCell(String text) {
        Cell cell = new Cell(new Paragraph(text, MyFontUtil.SIMSUN_TEXT));
        // 设置水平对齐方式
        cell.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 设置垂直对齐方式
        cell.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置单元格边框颜色,同样用四个bit位分别表示上(0001)、下(0010)、左(0100)、右(1000)四个边
        cell.setBorder(Rectangle.BOX);
        // 设置单元格边框颜色。注:经过实测,setBorderColor有效,但是setBorderColorTop/Bottom/Left/Right是无效的,也就是Cell的边框只能设置一个颜色
        cell.setBorderColor(Color.RED);
        // 设置背景色
        cell.setBackgroundColor(Color.LIGHT_GRAY);
        return cell;
    }

    /**
     * 设置表格的标题栏样式
     *
     * @param text 标题
     */
    private static Cell createTitleCell(String text) {
        Cell cell = new Cell(new Paragraph(text, MyFontUtil.SIMSUN_TEXT));
        // 设置水平对齐方式
        cell.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 设置垂直对齐方式
        cell.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置单元格边框颜色,同样用四个bit位分别表示上(0001)、下(0010)、左(0100)、右(1000)四个边
        cell.setBorder(Rectangle.BOX);
        // 设置单元格边框颜色。注:经过实测,setBorderColor有效,但是setBorderColorTop/Bottom/Left/Right是无效的,也就是Cell的边框只能设置一个颜色
        cell.setBorderColorLeft(Color.RED);
        // 设置背景色
        cell.setBackgroundColor(Color.GRAY);
        return cell;
    }

    /**
     * 设置表格样式
     *
     * @param table 表格对象
     */
    private static void setTableStyle(Table table) {
        // 表格在页面水平方向的对齐方式
        table.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 设置表格边框(仅最外层边框为Table的边框,内部边框属于Cell),同样用四个bit位分别表示上(0001)、下(0010)、左(0100)、右(1000)四个边
        table.setBorder(Rectangle.BOX);
        // 表格边框颜色,只有设置了边框(上一步的setBorder)才有效
        table.setBorderColor(Color.BLACK);
        // 设置背景色(可以把Table想象成一个画布,Cell为放置于画布上的拼图,这里设置的是画布的背景色,与Cell的背景色不同)
        table.setBackgroundColor(Color.ORANGE);
        // 设置列宽
        table.setWidths(new int[]{10, 20, 10, 20, 20});
        // Cell之间的间隔
        table.setSpacing(0.5f);
        // 设置该值可以让文字与底部表格线留出一定空间
        table.setPadding(2.5f);
        // 以下两行y一般同时使用:设置默认Cell,然后使用默认Cell填充空的Cell
        table.setDefaultCell(createDataCell("unknow"));
        table.setAutoFillEmptyCells(true);
        // 表格跨页设置,但是好像没什么效果
        table.setTableFitsPage(false);
    }

    /**
     * 添加图片
     *
     * @param document pdf文档对象
     */
    private static void addImage(Document document) throws IOException {
        document.add(new Paragraph("二、生成图片示例", MyFontUtil.SIMSUN_H2));
        Image image = Image.getInstance("F:\\pdf\\addImage.jpg");
        // 重设图片大小
        image.scaleAbsolute(500, 300);
        document.add(image);
    }

    /**
     * 添加文字
     *
     * @param document pdf文档对象
     */
    private static void addText(Document document) {
        /* 可以使用Chapter、ChapterAutoNumber生成段落,但是数字样式单一,建议自定义,直接用程序的方式生成章节号
        ChapterAutoNumber chapter = new ChapterAutoNumber(new Paragraph("一、生成文本示例", MyFontUtil.SIMSUN_H2));
        // 章节编号格式:带点号
        chapter.setNumberStyle(Section.NUMBERSTYLE_DOTTED);
        // 设置章节深度(一级标题)
        chapter.setNumberDepth(1);
        document.add(chapter);*/
        document.add(new Paragraph("一、生成文本示例", MyFontUtil.SIMSUN_H2));
        document.add(new Paragraph("1、整段文字为同一种字体(宋体)", MyFontUtil.SIMSUN_H3));
        Paragraph p1 = new Paragraph();
        p1.setFont(MyFontUtil.SIMSUN_TEXT);
        p1.add("    生成文本可以使用Chunk、Phrase、Paragraph三种类型,三者之间的关系为:Chunk implements Element,TextElementArray extends Element,Phrase implements TextElementArray,Paragraph extends Phrase。");
        Paragraph p2 = new Paragraph();
        p2.setFont(MyFontUtil.SIMSUN_TEXT);
        p2.add("    文本块Chunk是设置字体格式的最小单位,可以设置字体、字体颜色、背景颜色等,写满一行之后自动排列到下一行,Chunk可被包含于Phrase和Paragraph。");
        Paragraph p3 = new Paragraph();
        p3.setFont(MyFontUtil.SIMSUN_TEXT);
        p3.add("    短句Phrase可以包含多个Chunk,默认行间距是字体大小的1.5倍,写满一行之后自动排列到下一行,但Phrase之间并不会自动在末尾添加换行符。");
        Paragraph p4 = new Paragraph();
        p4.setFont(MyFontUtil.SIMSUN_TEXT);
        p4.add("    段落Paragraph与Phrase类似,但间距控制比Phrase好,不会出现两行文字重叠的情况,写满一行之后同样可以自动排列到下一行,Paragraph在自身内容前后都会添加换行符。一般情况下,个人建议使用Paragraph,省去一些排版的麻烦。");
        Paragraph p5 = new Paragraph();
        p5.setFont(MyFontUtil.SIMSUN_TEXT);
        p5.add("    制表符\\t在不同环境不同情况下的长短不一致,包括各类IDE也基本上推荐使用空格替代制表符,所以我们在PDF文档中也使用空格替代制表符。制表符也可以显示的,但经过实测,只有在仅有英文字符且未设置字体时才可以正常显示。测试中也可以看到不同字体中的空格宽度也不一样,测试结果如下:");
        Paragraph p6 = new Paragraph("    [\t\t]->2 tabs!");
        Paragraph p7 = new Paragraph();
        p7.add("    [\t\t\t\t]->4 tabs!");
        Paragraph p8 = new Paragraph("    [\t\t\t\t]->set Font!", MyFontUtil.SIMSUN_TEXT);
        Paragraph p9 = new Paragraph("    [\t\t\t\t]->contains chinese char!");
        Paragraph p10 = new Paragraph("    下面展示的是插入无序列表:", MyFontUtil.SIMSUN_TEXT);
        List underOrderedlist = new List();
        underOrderedlist.add(new ListItem(new Chunk("小兔几", MyFontUtil.SIMSUN_TEXT)));
        underOrderedlist.add(new ListItem(new Phrase("小凶许", MyFontUtil.SIMSUN_TEXT)));
        underOrderedlist.add(new ListItem(new Paragraph("小脑斧", MyFontUtil.SIMSUN_TEXT)));
        underOrderedlist.add(new ListItem(new Paragraph("小西几", MyFontUtil.SIMSUN_TEXT)));
        p10.add(underOrderedlist);
        Paragraph p11 = new Paragraph("    下面展示的是插入有序列表:", MyFontUtil.SIMSUN_TEXT);
        List orderedlist = new List();
        orderedlist.setNumbered(true);
        orderedlist.add(new ListItem(new Chunk("小兔几", MyFontUtil.SIMSUN_TEXT)));
        orderedlist.add(new ListItem(new Phrase("小凶许", MyFontUtil.SIMSUN_TEXT)));
        orderedlist.add(new ListItem(new Paragraph("小脑斧", MyFontUtil.SIMSUN_TEXT)));
        orderedlist.add(new ListItem(new Paragraph("小西几", MyFontUtil.SIMSUN_TEXT)));
        p11.add(orderedlist);

        document.add(p1);
        document.add(p2);
        document.add(p3);
        document.add(p4);
        document.add(p5);
        document.add(p6);
        document.add(p7);
        document.add(p8);
        document.add(p9);
        document.add(p10);
        document.add(p11);

        document.add(new Paragraph("2、一段文字包含多种字体格式", MyFontUtil.SIMSUN_H3));
        Paragraph p12 = new Paragraph("    ");
        p12.add(new Chunk("这部分是微软雅黑-黑体,18号字体。", MyFontUtil.MSYHBD_H3));
        p12.add(new Chunk("这部分是宋体,10.5号字体。后面开始测试字体:", MyFontUtil.SIMSUN_TEXT));
        // 测试字体
        String[] styles = {"无格式", "加黑", "变斜", "下划线", "删除线"};
        for (int i = 0; i < styles.length; i++) {
            // 要使用不同字体,必须新建一个字体对象,不允许直接修改公共变量
            Font font = new Font(MyFontUtil.MyBaseFonts.SIMSUN, 10.5f);
            font.setColor(Color.RED);
            // 使用四个bit位设置字体格式,类似Linux的文件权限(chmod 147)设置,bit位与字体的对应关系可以查看Font.getStyleValue()方法
            int style = i == 0 ? 0 : 1 << (i - 1);
            font.setStyle(style);
            Chunk chunk = new Chunk();
            chunk.setFont(font);
            chunk.append(style + "-" + styles[i] + ";");
            p12.add(chunk);
        }
        p12.add(new Chunk("后面开始测试背景色和划线:", MyFontUtil.SIMSUN_TEXT));
        Font font = new Font(MyFontUtil.MyBaseFonts.SIMSUN, 10.5f);
        font.setColor(Color.RED);
        font.setStyle(3);
        Chunk chunk = new Chunk();
        chunk.setFont(font);
        chunk.setBackground(Color.YELLOW);
        chunk.setUnderline(0.4f, 6);
        chunk.append("这一段文字为红色、加黑、变斜、黄底,有一条宽度为0.4、垂直坐标(行底部为y轴0坐标)为6的线段。");
        p12.add(chunk);
        Chunk markerPen = new Chunk();
        markerPen.setFont(MyFontUtil.SIMSUN_TEXT);
        // 第一个参数表示线条颜色,第二个参数是线宽,第四个参数是线的y轴坐标,同setUnderline(float, float),其它参数不知道怎么计算的,实际使用时慢慢试吧
        markerPen.setUnderline(Color.GREEN, 1, 1, 4, 0, 1);
        markerPen.append("这一段文字用荧光笔(绿色圆角宽线)标记。");
        p12.add(markerPen);
        document.add(p12);
    }

    /**
     * 设置封面
     *
     * @param document pdf文档对象
     */
    private static void setCover(Document document) {
        document.add(new Chunk("编号:0123456789", MyFontUtil.MSYH_H3));
        Paragraph fileName = new Paragraph();
        fileName.setFont(MyFontUtil.MSYHBD_H1);
        fileName.setAlignment(Element.ALIGN_CENTER);
        int lineCnt = 19;
        while (lineCnt-- > 0)
            fileName.add(Chunk.NEWLINE);
        fileName.add("使用OpenPDF生成PDF文档\n\n\n");
        Chunk chunk = new Chunk("2023年3月26日", MyFontUtil.MSYH_H3);
        fileName.add(chunk);
        document.add(fileName);
        lineCnt = 19;
        while (lineCnt-- > 0)
            document.add(Chunk.NEWLINE);
        Paragraph subscript = new Paragraph();
        subscript.setFont(MyFontUtil.MSYH_H3);
        subscript.setAlignment(Element.ALIGN_RIGHT);
        subscript.add("本文档使用openpdf生成");
        document.add(subscript);
        document.newPage();
    }

    /**
     * 设置页面大小、边距、页眉、页脚
     *
     * @param document pdf文档对象
     */
    private static void setPageInfo(Document document) {
        // 页面大小
        document.setPageSize(PageSize.A4);

        // 左、右、上、下边距
        document.setMargins(36, 64, 36, 72);

        // 设置页眉,可以使用Phrase、Paragraph
        Paragraph headerParagraph = new Paragraph();
        headerParagraph.setFont(MyFontUtil.SIMSUN_TEXT);
        headerParagraph.add("PDF输出样例");
        // 第二个参数表示是否显示页码
        HeaderFooter header = new HeaderFooter(headerParagraph, false);
        header.setAlignment(Element.ALIGN_CENTER);
        document.setHeader(header);

        // 设置页脚,页脚设置页码,所以在页码数字前后多了前缀后缀
        Phrase pre = new Phrase();
        pre.setFont(MyFontUtil.SIMSUN_TEXT);
        pre.add("第");
        Phrase suffix = new Phrase();
        suffix.setFont(MyFontUtil.SIMSUN_TEXT);
        suffix.add("页");
        // 两个参数都是Phrase类型,则表示分别设置pageCount的前缀和后缀
        HeaderFooter footer = new HeaderFooter(pre, suffix);
        footer.setAlignment(Element.ALIGN_RIGHT);
        document.setFooter(footer);
        // 重设页码
        document.resetPageCount();
    }

    /**
     * 设置文件属性
     *
     * @param document pdf文档对象
     */
    private static void setMetaInfo(Document document) {
        document.addTitle("这是标题");
        document.addSubject("这是主题");
        document.addAuthor("java_t_t");
        document.addCreationDate();
        document.addCreator("应用程序");
        // 无参方法document.addProducer()的值为OpenPDF的版本,如OpenPDF 1.3.9
        document.addProducer("PDF制作程序");
        // 关键字,只能有一个
        document.addKeywords("keyword");
        // 自定义属性,可以有多个
        document.addHeader("custom_key_1", "custome_value_1");
        document.addHeader("custom_key_2", "custome_value_2");
        // document.addDocListener(new Document());
    }
}

你可能感兴趣的:(实用功能,java)