使用itext生成pdf

itext是用来处理pdf内容的开源工具(商业使用需要收费),可以用来根据模板生成pdf。

简介


  • Github参见这里,官网参见这里,帮助文档上有一些例子,另外如果有疑问可以在知识库的搜索框里输入,有可能会找到对应的答案(知识库中的一些问题来自stackoverflow上的解答)。
  • itext分为若干个组件,有的用于ocr,有的用于绘制pdf,有的用于html转换pdf,等等
  • 这次要做的是利用itextpdf,利用调整好的html,转换成指定格式的pdf

上手代码


  • 首先需要在pom中引入itextpdf的依赖如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>html2pdf</artifactId>
    <version>${com.itextpdf.html2pdf.version}</version>
    </dependency>

    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>forms</artifactId>
    <version>${com.itextpdf.version}</version>
    </dependency>

    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>svg</artifactId>
    <version>${com.itextpdf.version}</version>
    </dependency>

    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>styled-xml-parser</artifactId>
    <version>${com.itextpdf.version}</version>
    </dependency>

    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>layout</artifactId>
    <version>${com.itextpdf.version}</version>
    </dependency>

    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>kernel</artifactId>
    <version>${com.itextpdf.version}</version>
    </dependency>

    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>io</artifactId>
    <version>${com.itextpdf.version}</version>
    </dependency>
  • itext对于中文的支持,需要引入itext-asian包或者引入中文字体;这里通过引入微软雅黑字体解决:

    1
    2
    3
    4
    5
    6
    7
    ConverterProperties props = new ConverterProperties();
    FontProvider fp = new FontProvider();
    fp.addStandardPdfFonts();
    // .ttf 字体所在目录
    String resources = Html2PdfUtil.class.getResource(FONT_RESOURCE_DIR).getPath();
    fp.addDirectory(resources);
    props.setFontProvider(fp);
  • 由于需要使用html转换为pdf,同时对于部分章节需要另起一页,所以使用了如下方法,其中PAGE_SIZE为定义好的页面大小,可以设置为PAGE_SIZE.A4等等;

    1
    2
    3
    4
     //html转成元素
    List<IElement> elements = HtmlConverter.convertToElements(htmlContent, props);
    PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
    Document document = new Document(pdf, PAGE_SIZE, false);
  • 由于需要为每一页添加页眉页脚,参考这个问题,增加对应的handler,用于处理页眉页脚:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
     /**
    * 添加页眉页脚文字与分割线
    * pdf绘制时坐标可以参考平面直角坐标系的第一象限
    */
    private static class HeaderFooterHandler implements IEventHandler {
    protected String info;
    public void setInfo(String info) {
    this.info = info;
    }
    public String getInfo() {
    return info;
    }
    @Override
    public void handleEvent(Event event) {
    PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
    PdfPage page = docEvent.getPage();
    Rectangle pageSize = page.getPageSize();
    PdfDocument pdfDoc = ((PdfDocumentEvent) event).getDocument();
    PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamBefore(),page.getResources(), pdfDoc);
    // 画页脚分割线
    // MARGIN_LEFT为左侧留白,FOOTER_LINE_BOTTOM_DISTANCE为页脚线到底部距离,MARGIN_RIGHT为右侧留白
    pdfCanvas.setStrokeColor(ColorConstants.GRAY) .moveTo(MARGIN_LEFT, FOOTER_LINE_BOTTOM_DISTANCE).lineTo(PAGE_SIZE.getWidth()- MARGIN_RIGHT, FOOTER_LINE_BOTTOM_DISTANCE).closePathStroke();
    // 画页眉分割线
    // HEADER_LINE_TOP_DISTANCE为页眉线到顶部距离
    pdfCanvas.setStrokeColor(ColorConstants.GRAY).moveTo(MARGIN_LEFT, pageSize.getHeight() - HEADER_LINE_TOP_DISTANCE).lineTo(PAGE_SIZE.getWidth()- MARGIN_RIGHT, pageSize.getHeight() - HEADER_LINE_TOP_DISTANCE).closePathStroke();
    try {
    // 加载字体
    PdfFont YaHeiFont = PdfFontFactory.createFont("/font/WeiRuanYaHei-1.ttf", PdfEncodings.IDENTITY_H,true);
    //header
    float headerFontSize = 14;
    String headerTextContent = "页眉文字";
    int headerTextLength = headerTextContent.length();
    // 这个Rectangle用于定位,定位点在矩形左下角
    // x坐标:长方形左侧边线
    // y坐标:长方形底部边线
    // MARGIN_RIGHT为右侧留白
    // HEADER_PADDING_BOTTOM为页眉文字到页眉线留白
    Rectangle headerRightPosition = new Rectangle((float) (PAGE_SIZE.getWidth() - MARGIN_RIGHT - Math.ceil(headerFontSize * headerTextLength)),
    pageSize.getHeight() - (HEADER_LINE_TOP_DISTANCE- (0.5f * headerFontSize)) + HEADER_PADDING_BOTTOM , headerFontSize * headerTextLength, headerFontSize);
    Canvas headerRightCanvas = new Canvas(pdfCanvas, pdfDoc, headerRightPosition);
    // 设置下字体
    Text headerText = new Text(headerTextContent).setFont(YaHeiFont).setFontSize(headerFontSize).setFontColor(ColorConstants.GRAY);
    Paragraph headerParagraph = new Paragraph().add(headerText).setMarginRight(0);
    headerRightCanvas.add(headerParagraph);
    //footer
    // 页码
    float footerFontSize = 12;
    String pageNumTextContent = "P" + pdfDoc.getPageNumber(page);
    int pageNumTextLength = pageNumTextContent.length();
    // x坐标:左侧margin
    // y坐标:页脚线到页面底部-字体占位高度
    Rectangle pageNumPosition =
    new Rectangle(MARGIN_LEFT, FOOTER_LINE_BOTTOM_DISTANCE - footerFontSize,footerFontSize * pageNumTextLength, footerFontSize);
    Canvas pageNumCanvas = new Canvas(pdfCanvas, pdfDoc, pageNumPosition);
    Text pageNumText = new Text(pageNumTextContent).setFont(YaHeiFont).setFontSize(footerFontSize).setFontColor(ColorConstants.GRAY);
    Paragraph pageNumParagraph = new Paragraph().add(pageNumText).setMarginLeft(0);
    pageNumCanvas.add(pageNumParagraph);
    // 页脚文字
    int footerTextLength = info.length();
    // x坐标:页面宽度-右侧margin-字体占位宽度
    // y坐标:页脚线到页面底部-字体占位高度
    Rectangle footerRightPosition = new Rectangle(PAGE_SIZE.getWidth() - MARGIN_RIGHT - footerFontSize*footerTextLength,
    FOOTER_LINE_BOTTOM_DISTANCE - footerFontSize, footerTextLength * footerFontSize, footerFontSize);
    Canvas footerRightCanvas = new Canvas(pdfCanvas, pdfDoc, footerRightPosition);
    footerRightCanvas.setFont(YaHeiFont);
    Text footerText = new Text(info).setFont(YaHeiFont).setFontColor(ColorConstants.GRAY).setFontSize(footerFontSize);
    Paragraph footerTextParagraph = new Paragraph().add(footerText).setMarginRight(0).setTextAlignment(TextAlignment.RIGHT);
    footerRightCanvas.add(footerTextParagraph);
    }
    catch (Exception e){
    e.printStackTrace();
    }
    }
    }
  • 同理,可以为每一页添加背景图片:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     /**
    * 添加背景图片
    */
    private static class BackgroundEventHandler implements IEventHandler {
    public static final String BACKGROUND_IMAGE = Html2PdfUtil.class.getResource("/pdfTemplate").getPath() + "/background.png";
    @Override
    public void handleEvent(Event event) {
    PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
    PdfDocument pdfDoc = docEvent.getDocument();
    PdfPage page = docEvent.getPage();
    try {
    // 添加背景图片
    Image backgroundImage = new Image(ImageDataFactory.create(BACKGROUND_IMAGE));
    PdfCanvas background = new PdfCanvas(page.newContentStreamBefore(),
    page.getResources(), pdfDoc);
    Rectangle areaBackground = page.getPageSize();
    new Canvas(background, pdfDoc, areaBackground)
    .add(backgroundImage);
    } catch (Exception e){
    e.printStackTrace();
    }

    }
    }
  • 完整的生成pdf方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    /**
    * 保存风险评述,生成pdf方法
    * @param htmlContent html文本
    * @param dest 目的文件路径,如 /xxx/xxx.pdf
    * @throws IOException IO异常
    */
    public static void createPdf(String htmlContent, String dest) throws IOException {
    ConverterProperties props = new ConverterProperties();
    FontProvider fp = new FontProvider();
    fp.addStandardPdfFonts();
    // .ttf 字体所在目录
    String resources = Html2PdfUtil.class.getResource(FONT_RESOURCE_DIR).getPath();
    fp.addDirectory(resources);
    props.setFontProvider(fp);
    //html转成元素
    List<IElement> elements = HtmlConverter.convertToElements(htmlContent, props);
    PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
    Document document = new Document(pdf, PAGE_SIZE, false);
    //设置下留白
    document.setMargins(MARGIN_TOP, MARGIN_LEFT, MARGIN_BOTTOM, MARGIN_RIGHT);
    //背景图片处理器
    BackgroundEventHandler backgroundEventHandler = new BackgroundEventHandler();
    pdf.addEventHandler(PdfDocumentEvent.END_PAGE, backgroundEventHandler);
    //页眉页脚分割线与文字处理器
    HeaderFooterHandler handler = new HeaderFooterHandler();
    handler.setInfo("页脚文字");
    pdf.addEventHandler(PdfDocumentEvent.START_PAGE, handler);

    //添加分页符,此处用于在指定的元素位置添加分页符,非必须
    for (IElement element : elements) {
    if (element instanceof Div) {
    Div div = (Div) element;
    List<IElement> children = div.getChildren();
    children.add(2,new AreaBreak());
    }
    }

    for (IElement element : elements) {
    if (element instanceof HtmlPageBreak) {
    // 分页符
    document.add((HtmlPageBreak) element);
    } else if (element instanceof Paragraph) {
    //段落,这里可以统一设置文字对齐
    Paragraph p = (Paragraph) element;
    p.setTextAlignment(TextAlignment.JUSTIFIED);
    document.add(p);
    } else if (element instanceof Div) {
    //块
    Div div = (Div) element;
    document.add(div);
    } else if (element instanceof com.itextpdf.layout.element.List) {
    //列表
    com.itextpdf.layout.element.List list = (com.itextpdf.layout.element.List) element;
    document.add(list);
    } else {
    document.add((IBlockElement) element);
    }
    }
    document.close();
    }

总结


调试下来,发现最有用的就是官网知识库还有源码,官网知识库能够解决怎么做的问题,对于一些细节的调整,可以查看源码并调试,就能得到想要的结果。