SpringBoot-iText-Pdf

x33g5p2x  于2021-09-22 转载在 Spring  
字(62.7k)|赞(0)|评价(0)|浏览(827)

SpringBoot-iText-Pdf

需要的全部Maven

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
    </parent>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- itextpdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.11</version>
        </dependency>

        <!-- itext水印-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.47</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcmail-jdk15on</artifactId>
            <version>1.47</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>core-renderer</artifactId>
            <version>R8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>
        <!-- 渲染 css 样式 -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.1.16</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.11.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>

    </dependencies>

生成一个PDF

需要5个步骤

创建文档

Document document = new Document();

生成PDF

PdfWriter.getInstance(doc, new FileOutputStream(DEST+ File.separator+"HelloWorld.pdf") );

打开PDF

document.open();

往PDF中写入内容

document.add(new Paragraph("Hello World"));

关闭PDF

document.close();

完整版

Document doc = new Document();
        PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR +"createSamplePDF.pdf"));
        doc.open();
        doc.add(new Paragraph("Hello World"));
        doc.close();

必须知道的内容

在操作演示PDF前我们需要知道如何获取 classes目录的路径 (项目打包后的根路径)

只有这样我们生成的PDF会在项目内部(当然如果你不需要PDF在项目的classes目录而且在其他地方,自行补充路径)

private static String FILE_DIR = null;

    static {
        try {
            FILE_DIR = Paths.get(ResourceUtils.getURL("classpath:").getPath().substring(1)).toString()+ File.separator;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

我们还需要知道一个问题就是itext默认不支持中文(默认隐藏中文字体只显示英文)

我们可以在添加中文时候的那个对象中(基本都有设置字体格式的方法) 添加下面方法,就能识别中文了
对象.setFont(PdfUtils.getChineseFont());

在块.短语,段落,的构造第二个参数是可以直接添加PdfUtils.getChineseFont()

中文字体工具类

package com.pdf.utils;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;

import java.io.IOException;

public class PdfFont {
    // color= BaseColor.BLUE(蓝色) BaseColor.BLACK(黑色) new BaseColor(105, 105, 105)(文本色)
    // style= Font.ITALIC细,Font.NORMA正常L,Font.BOLD粗
    // size=字体大小

    public static Font getChineseFont( int size,int style,BaseColor color) {

        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese, size, style, color);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }

    public static Font getChineseFont(int size, int style) {
        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese, size, style, new BaseColor(105, 105, 105));
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }
    public static Font getChineseFont( int size) {
        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese, size, Font.NORMAL, new BaseColor(105, 105, 105));
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }
    public static Font getChineseFont(int style,BaseColor color) {
        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese,style, Font.NORMAL, color);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }

    public static Font getChineseFont( long size, BaseColor color) {
        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese, size, Font.NORMAL, color);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }

    public static Font getChineseFont( BaseColor color) {
        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese, 15, Font.NORMAL, color);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }


    // 默认配置 黑色字体 大小15 不加粗
    public static Font getChineseFont( ) {
        BaseFont bfChinese;
        Font fontChinese = null;
        try {
            bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bfChinese, 15, Font.NORMAL, new BaseColor(105, 105, 105));
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;

    }


}

PDF基础配置

需要倒入的包

import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.List;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.draw.DottedLineSeparator;
import com.itextpdf.text.pdf.draw.LineSeparator;
import com.itextpdf.text.pdf.draw.VerticalPositionMark;
import lombok.SneakyThrows;
import org.junit.Test;
import org.springframework.util.ResourceUtils;

import java.awt.*;
import java.io.*;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

注意:看每个配置 哪行代码的后面 如果顺序错了那么久无效了

配置PDF

页面大小

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);

不止B4你进入PageSize类里有一大堆 常用B4

比如:

  1. B5 ,B6 B 7 这些都是比B4小
  2. B3, B2 都是比B4大

页面背景

不设置默认为白色

Rectangle rect = new Rectangle(PageSize.B4.rotate());
rect.setBackgroundColor(new BaseColor(199 , 237, 204) );  //设置颜色
Document doc = new Document(rect);

推荐几款护眼的颜色:

new BaseColor(199 , 237, 204) 豆沙绿(推荐)

new BaseColor(200, 200, 169) 淡青色

new BaseColor(254, 67, 101) 淡红色

new BaseColor(252, 167, 154) 淡粉色

new BaseColor(249, 205, 173) 淡黄色
1.
PDF版本设置(默认1.4)

PdfWriter writer = PdfWriter.getInstance(xxxxxx);
        writer.setPdfVersion(PdfWriter.PDF_VERSION_1_2); //设置版本号 一般不设置默认就可以

PDF文档属性(Title,Author,Subject,Keywords )

Document doc = new Document();
PdfWriter writer = PdfWriter.getInstance(xxxxxx);
//文档属性 注意: 如果设置了版本号那么需要在版本号后面写 
doc.addTitle("标题");  
doc.addAuthor("作者");  
doc.addSubject("主题");  
doc.addKeywords("关键字");  
doc.addCreator("创建者");

文档属性可以有可无 看你心情

页边空白

Document doc = new Document();
PdfWriter writer = PdfWriter.getInstance(xxxxxx);//必须在这行后面
doc.setMargins(10, 20, 30, 40);   //页边空白 左 右 上 下

配置后的效果

PDF密码

就相当于给文件加密需要使用密码验证后才能观看

Document doc = new Document();
PdfWriter writer = PdfWriter.getInstance(xxxxxx);//必须在这行后面 

// 设置密码 
//第一个参数是用户密码(允许复制,签名 不允许打印,编辑) 
//第二个参数是管理员密码(允许打印,编辑,复制,签名 加密级别)
writer.setEncryption("user".getBytes(), "admin".getBytes(),
PdfWriter.ALLOW_COPY,  // 允许复制,签名 不允许打印,编辑
PdfWriter.STANDARD_ENCRYPTION_128); //允许打印,编辑,复制,签名 加密级别

增删page(页)

添加PDF页 看代码你就懂了

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "createSamplePDF.pdf"));

        doc.open(); //打开PDF 默认为第一页
        doc.add(new Paragraph("First page")); // 给第一个添加内容
        doc.add(new Paragraph("Hello World"));

        doc.newPage();  // 创建第2页
        writer.setPageEmpty(false); //可以允许第2页为空 如果不设置那么PDF默认给没有内容的页 取消掉

        doc.newPage(); // 创建第3页
        doc.add(new Paragraph("New page"));
        doc.close();

//删除PDF页 // 看代码你就懂了

只选择指定类其他的删除

PdfReader reader = new PdfReader(FILE_DIR + "createSamplePDF.pdf");
        // 从原PDF中抽取指定页 生成
        reader.selectPages("1,3,5"); //只选择1和3页和第5页
        //生成新的PDF
        PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(FILE_DIR
                + "createSamplePDF_de.pdf"));
        stamp.close();
        reader.close();

只选择指定范围页其他页删除

/** * 截取pdfFile的第from页至第end页,组成一个新的文件名 * @param respdfFile 需要分割的PDF * @param savepath 新PDF * @param from 起始页 * @param end 结束页 */
    public static void splitPDFFile(String respdfFile, String savepath, int from, int end) {
        Document document = null;
        PdfCopy copy = null;
        try {
            PdfReader reader = new PdfReader(respdfFile);
            int n = reader.getNumberOfPages();
            if(end==0){
                end = n;
            }
            ArrayList<String> savepaths = new ArrayList<String>();
            savepaths.add(savepath);
            document = new Document(reader.getPageSize(1));
            copy = new PdfCopy(document, new FileOutputStream(savepaths.get(0)));
            document.open();
            for(int j=from; j<=end; j++) {
                document.newPage();
                PdfImportedPage page = copy.getImportedPage(reader, j);
                copy.addPage(page);
            }
            document.close();

        } catch (IOException e) {
            e.printStackTrace();
        } catch(DocumentException e) {
            e.printStackTrace();
        }
    }

PDF内容

如果你对BaseFont类自带的字体颜色不满足的话我们可以自己进行配色

这个网站是rgb颜色表 http://www.wahart.com.hk/rgb.htm new BaseColor(r, g, b);

基础

Chunk(块)

Chunk : 块,PDF文档中描述的最小原子元素 ,其他高级的文本对象都是基于Chunk的

在水平方向,Chunk的字符满一行,就会从头开始。请注意,这是从头开始,而不是另起一行。对于Chunk来说,行间距默认为0,那么当文档中只有Chunk时,这些字符永远只会出现再第一行。

注意:块一般不会单独使用而且和其他文本对象进行组合使用的

doc.open(); //打开PDF 默认为第一页

        //定义一个块
        Chunk chunk = new Chunk("Cat");
        //设置块的背景色 (可选)
// chunk.setBackground(BaseColor.WHITE); //白色背景(默认)
        //设置快内的字体为可识别中文字体 
		//字体样式,编码格式,插入方式,字体大小,字体样式(Font.ITALIC细,Font.NORMA正常L,Font.BOLD粗),字体颜色)
        Font font = FontFactory.getFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED,17, Font.NORMAL,  new BaseColor(105, 105, 105));
        //设置快内的字体颜色
        chunk.setFont(font);
        //增加块到文档
        doc.add(chunk);

        doc.close();

如果单独使用块的话-块重叠案例

doc.open(); //打开PDF 默认为第一页

        //建块
         int i=0;
        for(i=1; i<=11; i++){
            doc.add(new Chunk("This is sentence "+i+". "));

        }

        doc.close();

可以发现重叠了 ,所以块一般是不能单独使用的,我们要配合下面的更高级文本对象进行使用,进行改变一条句子中的某些字的样式

Phrase(短句)

Phrase作用是添加一个短句。短语类知道如何添加行与行之间的间距。 但是没法和Chunk一样设置样式

doc.open(); //打开PDF 默认为第一页
        //添加短句
        int i=0;
        for(i=1; i<11; i++){
            doc.add(new Phrase("This is sentence "+i+". "));

        }
        doc.close();

可以看到了和Chunk不同的是,会自动换行了,既然Chunk可以 设置样式Phrase可以自动换行那么我们将他们组合不就可以了

Phrase组合Chunk使用

doc.open(); //打开PDF 默认为第一页

        //定义一个块
        Chunk chunk1 = new Chunk("哈哈哈哈哈哈1");
        //添加横线 比如下划线(0.2f, -2f) 删除线(0.2f, 4f) 第一个参数是浓度 ,第二个参数是位置 横线位置
        chunk1.setUnderline(0.2f, 4f);
        Font font1 = FontFactory.getFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED,17, Font.NORMAL,  new BaseColor(105, 105, 105));
        chunk1.setFont(font1);

        Chunk chunk2 = new Chunk("惺惺惜惺惺想寻2");
        chunk2.setBackground(new BaseColor(200, 200, 169) );//添加块背景颜色
        Font font2 = FontFactory.getFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED,22, Font.NORMAL,  new BaseColor(105, 105, 105));
        chunk2.setFont(font2);

        //将块添加到短句里
        Phrase elements = new Phrase(50); //设置行间距
        elements.add(chunk1);
        elements.add(chunk2);
        doc.add(elements);

        doc.close();

这样Phrase就能有Chunk的样式效果了

doc.open(); //打开PDF 默认为第一页

        //定义段落
        Paragraph paragraph = new Paragraph();
        //插入十条文本块到段落中
        int i=0;
        for(i=0; i<10; i++){
            Chunk chunk = new Chunk("This is a sentence which is long " + i + ". ");
            paragraph.add(chunk);
        }
        //添加段落
        doc.add(paragraph);

        doc.close();
Paragraph(段落)

Paragraph 是段落的处理。在一个段落中 每一个段落都会结尾自带换行,

你可以设置段落的对齐方式,缩进和间距。 简单来说就是对Phrase进行升级了

可以操控文本在PDF中的位置了

doc.open(); //打开PDF 默认为第一页
        
        //定义段落
        Paragraph paragraph = new Paragraph();
        //插入10条文本块到段落中
        int i=0;
        for(i=0; i<10; i++){
            Chunk chunk = new Chunk("This is a sentence which is long " + i + ". ");
            paragraph.add(chunk);
        }
        doc.add(paragraph);
        doc.close();

暂时看来和短句的运行效果差不多,每句都在自己的行。

您可以设置一个段落前后的间距。 也就是行的上间距和下间距

paragraph.setSpacingAfter(50);  
paragraph.setSpacingBefore(50);

您可以设置使用setAlignment()方法的段落的对齐方式。

paragraph.setAlignment(Element.ALIGN_LEFT);  //靠左
paragraph.setAlignment(Element.ALIGN_CENTER); //居中
paragraph.setAlignment(Element.ALIGN_RIGHT);  //靠右

您可以设置该段左,右缩进。

paragraph.setIndentationLeft(50);
paragraph.setIndentationRight(50);

案例演示

doc.open(); //打开PDF 默认为第一页

        //定义段落1
        Paragraph paragraph = new Paragraph("This is a sentence which is long . ");
        //设置段段落居中
        paragraph.setAlignment(Element.ALIGN_CENTER);
        //设置段落行缩进
        paragraph.setIndentationLeft(10); //左边缩进
        //添加段落
        doc.add(paragraph);

        //定义段落2
        Paragraph paragraph1 = new Paragraph("This is a sentence which is long . ");
        //设置段落行的上间距
        paragraph1.setSpacingAfter(20);
        //设置段段落居中
        paragraph1.setAlignment(Element.ALIGN_CENTER);
        //设置段落行缩进
        paragraph1.setIndentationLeft(150); //左边缩进
        //添加段落
        doc.add(paragraph1);


        doc.close();

拿什么时候使用Chunk 什么时候使用Phrase 什么时候使用Paragraph呢?

Chunk: 只能设置字体和背景样式 ,而且内容只能在一行中

Phrase: 行满自动换行,可以设置行高, 但是不能手动换行,不能设置字体样式

Paragraph: 除了不能设置字体样式和背景外 基本文本所能用于的特性都可以实现 ,每个Paragraph结尾自带换行

通俗易懂的话来讲就是 Paragraph用来设置内容排版 Phrase用来拼接语句的, Chunk用来设置字体和背景样式的

操作字的位置透明旋转…
PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "createSamplePDF.pdf"));

        doc.open(); //打开PDF 默认为第一页

        PdfGState gs = new PdfGState();
        gs.setFillOpacity(0.3f);//透明度

        PdfContentByte canvas = instance.getDirectContent();
        canvas.setGState(gs);
        Phrase phrase1 = new Phrase("This is a test!left");
        Phrase phrase2 = new Phrase("This is a test!right");
        Phrase phrase3 = new Phrase("This is a test!center");
        // 参数1 默认就行 ,参数2默认就行,参数3内容,参数4x轴,参数5y轴,参数6字体旋转
        ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase1, 100, 500, 10);
        ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase2, 150, 536, 40);
        ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase3, 200, 572, 60);

        doc.close();

注意 :起始位置是左上角 x 是正数=往右 y是正数=往上 否则反之

List(列表)

有序列表 无需列表 字母列表
注意导包 import com.itextpdf.text./*; 而不是java.util

有序列表

doc.open(); //打开PDF 默认为第一页
        
        List orderedList = new List(List.ORDERED);

        for (int i = 0; i < 10; i++) {
            orderedList.add(new ListItem("Item "));
        }

        doc.add(orderedList);
        
        doc.close();

无序列表

doc.open(); //打开PDF 默认为第一页

        List unorderedList = new List(List.UNORDERED);

        for (int i = 0; i < 10; i++) {
            unorderedList.add(new ListItem("Item "));
        }

        doc.add(unorderedList);

        doc.close();

手指列表

doc.open(); //打开PDF 默认为第一页

        ZapfDingbatsList zapfDingbatsList = new ZapfDingbatsList(43, 30);

        for (int i = 0; i < 10; i++) {
            zapfDingbatsList.add(new ListItem("Item "));
        }

        doc.add(zapfDingbatsList);

列表嵌套

doc.open(); //打开PDF 默认为第一页

// 父列表
        List orderedList = new List(List.ORDERED);
        orderedList.add(new ListItem("Item "));

// 子列表
        List unorderedList = new List(false, false, 30);
        for (int i = 0; i < 5; i++) {
            //取消列表符合
            unorderedList.setListSymbol(new Chunk("", FontFactory.getFont(FontFactory.HELVETICA, 6)));
            unorderedList.add(new ListItem("Item"));
        }
        orderedList.add(unorderedList);
        doc.add(orderedList);

        doc.close();

高级

Anchor(链接)

Anchor,相当于html中的超链接,主要实现2个功能:

1.跳转到外部站点。

2.跳转到文档的特定位置。

跳转到特定网站,只需要设置Anchor对象的Reference属性

调转到文档的特定位置(在HTML中是描点),先要创建一个Anchor对象到特定位置,并为其命名,如“China”,然后,在创建一个Anchor至于需要点击的位置,设置其Reference属性为“/#China”

演示一个锚链接

doc.open(); //打开PDF 默认为第一页

        // Anchor超链接和锚点对象: internal and external links
       
        Anchor dest = new Anchor("连接到设置的UN锚点", PdfFont.getChineseFont(BaseColor.BLUE));
        dest.setName("CN"); // 设置锚点
        dest.setReference("#UN");// 跳转到UN锚点
        doc.add(dest);

        doc.newPage();  // 创建第2页
        instance.setPageEmpty(false); //可以允许第2页为空 如果不设置那么PDF默认给没有内容的页 取消掉

        doc.newPage(); // 创建第3页
        Anchor toUS = new Anchor("连接到设置的CN锚点。", PdfFont.getChineseFont(BaseColor.BLUE));
        toUS.setName("UN");
        toUS.setReference("#CN");// 跳转到CN锚点
        doc.add(toUS);

	    doc.close();

锚链可以制作目录和导航

演示创建超链接跳转百度

doc.open(); //打开PDF 默认为第一页

        Anchor dest = new Anchor("超链接", PdfFont.getChineseFont(BaseColor.BLUE));
        dest.setReference("http://www.baidu.com");// 如果不是描点,那么我们可以使用超链接连接
        doc.add(dest);

        doc.close();
Chapter(章) Section(节)
doc.open(); //打开PDF 默认为第一页

        //定义段落
        Paragraph paragraph = new Paragraph();
        //添加段落内容
        paragraph.add(new Phrase("This is a chapter."));
        //定义章
        Chapter chapter = new Chapter(paragraph, 1);
        //添加章-节内容
         chapter.addSection("This is section 1", 2);
         chapter.addSection("This is section 2", 2);
        //添加章节
        doc.add(chapter);

        doc.close();

代码中chapter.addSection(“This is section 1”, 2)中的2是设置深度,如果设置成1跟章得头是一个级别了。

Image(图片)

如果同一个图像创建多次并都添加到文档中,文档的大小就增长很快,如果一个图像要在多个地方使用,只要生成一个Image对象,在添加到文档前,设置其属性就可以了,没有必要创建多份。

创建图像

Image img = Image.getInstance(FILE_DIR+"baidu.jpg");
        doc.add(img);

图片的配置

img.setBorder(Image.BOX) //边框,

img.setBorderWidth(10)//边框宽度
    
img.setBorderColor(BaseColor.WHITE) //边框颜色, 
    
img.setRotationDegrees(-30) //旋转 正向左旋转 负向右旋转

img.setAbsolutePosition(x,y) //绝对位置 0,0 是最左下角 x变大向右移动 y变大向上移动

缩放图片

img.scaleAbsolute(150, 150);//绝对宽和高
img.scaleAbsoluteWidth(150);//绝对宽
img.scaleAbsoluteHeight(150);//绝对高
img.scalePercent(50); //百分比
img.scaleToFit(400,300);//像素大小

对其方式

img.setAlignment(Image.LEFT) 
// Image.LEFT(最左边) Image.RIGHT(最右边) Image.TOP(中)

图片自适应大小

Rectangle rect = new Rectangle(PageSize.B4.rotate());

        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页
        Image img = Image.getInstance(FILE_DIR+"37.jpg");
        img.scaleToFit(rect.getWidth(), rect.getHeight()); //关键代码
        doc.add(img);

        doc.close();

简单小演示

doc.open(); //打开PDF 默认为第一页

        Paragraph paragraph = new Paragraph("你妹的", PdfFont.getChineseFont(BaseColor.BLACK));
        paragraph.setAlignment(Element.ALIGN_CENTER);
        doc.add(paragraph );
        //插入图片
        Image img = Image.getInstance(FILE_DIR+"baidu.jpg");
        img.setAlignment( Image.RIGHT); //图片靠右
        img.scaleToFit(200, 100);//大小
        img.setRotationDegrees(-30);//旋转
        doc.add(img);
        //定义段落
        Paragraph paragraph1 = new Paragraph("作者:xxxxxxxxx",PdfFont.getChineseFont(BaseColor.BLACK));
        paragraph1.setAlignment(Element.ALIGN_RIGHT);
        doc.add(paragraph1);

        doc.close();

分割线线条

箭头

doc.open(); //打开PDF 默认为第一页

        doc.add(new VerticalPositionMark() {
            public void draw(PdfContentByte canvas, float llx, float lly,
                             float urx, float ury, float y) {
                canvas.beginText();
                BaseFont bf = null;
                try {
                    bf = BaseFont.createFont(BaseFont.ZAPFDINGBATS, "", BaseFont.EMBEDDED);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                canvas.setFontAndSize(bf, 12);
                // LEFT
                canvas.showTextAligned(Element.ALIGN_CENTER, String.valueOf((char) 220), llx - 10, y, 0);
                // RIGHT
                canvas.showTextAligned(Element.ALIGN_CENTER, String.valueOf((char) 220), urx + 10, y + 8, 180);
                canvas.endText();
            }
        });

        doc.close();

直线

doc.open(); //打开PDF 默认为第一页

        Paragraph p1 = new Paragraph("LEFT");
        p1.add(new Chunk(new LineSeparator()));
        p1.add("RIGHT");
        doc.add(p1);

        doc.close();

点线

Paragraph p1 = new Paragraph("LEFT");
        p1.add(new Chunk(new DottedLineSeparator()));
        p1.add("RIGHT");
        doc.add(p1);

下划线

doc.open(); //打开PDF 默认为第一页

        LineSeparator UNDERLINE = new LineSeparator(1, 100, null, Element.ALIGN_CENTER, -2);
        Paragraph p3 = new Paragraph();
        p3.setFont(PdfFont.getChineseFont());//设置中文支撑
        p3.add("三生三世惺惺惜惺惺想寻寻寻寻寻寻寻寻寻寻寻寻寻");
        p3.add(UNDERLINE);//下划线
        p3.add("NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN");
        p3.add(UNDERLINE);//下划线
        doc.add(p3);

        doc.close();

https://www.cnblogs.com/liaojie970/p/7132475.html 设置段落位置

PDF转换

给PDF添加水印

分为图片水印和字体水印 因为有点复杂的缘故我直接封装好了工具类直接可以使用 ,但是也不是一成不变的,

需要你根据情况进行调整大小,一般默认配置就够用了

注意:设置水印和设置图片不是一个概念,如果PDF有背景颜色的话会将水印盖住这个需要注意 ,

因为水印是PDF文件生成好后,后期添加上去的,在堆层的最底层

PdfWatermark水印工具类

package com.pdf.utils;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;

import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class PdfWatermark {

    /** * 文字水印 * <p> * 中间或者两边水印 * * @param bos 添加完水印的输出 * @param input 原PDF文件输入 * @param word 水印内容 * @param model 水印添加位置1中间,2两边 */
    public static void setWatermark(String bos, String input, String word, int model)
            throws DocumentException, IOException {

        PdfReader reader = new PdfReader(new FileInputStream(input));
        PdfStamper stamper = new PdfStamper(reader,new BufferedOutputStream(new FileOutputStream(bos) ));
        PdfContentByte content;
        // 创建字体,第一个参数是字体路径,itext有一些默认的字体比如说:
        BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        PdfGState gs = new PdfGState();
        gs.setFillOpacity(0.3f);//水印透明度
        // 获取PDF页数
        int total = reader.getNumberOfPages();
        // 遍历每一页
        for (int i = 0; i < total; i++) {
            float width = reader.getPageSize(i + 1).getWidth(); // 页宽度
            float height = reader.getPageSize(i + 1).getHeight(); // 页高度
            content = stamper.getOverContent(i + 1);// 内容
            content.beginText();//开始写入文本
            content.setGState(gs);
            content.setColorFill(BaseColor.LIGHT_GRAY);
            content.setTextMatrix(70, 200);//设置字体的输出位置

            if (model == 1) { //平行居中的3条水印
                content.setFontAndSize(base, 40); //字体大小
                //showTextAligned 方法的参数分别是(文字对齐方式,位置内容,输出水印X轴位置,Y轴位置,旋转角度)
                content.showTextAligned(Element.ALIGN_CENTER, word, width / 2, height/3, 30);
                content.showTextAligned(Element.ALIGN_CENTER, word, width / 2, height/2, 30);
                content.showTextAligned(Element.ALIGN_CENTER, word, width / 2, height/4-50, 30);
            } else { // 左右两边个从上到下4条水印
                float rotation = 30;// 水印旋转度数

                content.setFontAndSize(base, 20);
                content.showTextAligned(Element.ALIGN_LEFT, word, 20, height - 50, rotation);
                content.showTextAligned(Element.ALIGN_LEFT, word, 20, height / 4 * 3 - 50, rotation);
                content.showTextAligned(Element.ALIGN_LEFT, word, 20, height / 2 - 50, rotation);
                content.showTextAligned(Element.ALIGN_LEFT, word, 20, height / 4 - 50, rotation);

                content.setFontAndSize(base, 22);
                content.showTextAligned(Element.ALIGN_RIGHT, word, width - 20, height - 50, rotation);
                content.showTextAligned(Element.ALIGN_RIGHT, word, width - 20, height / 4 * 3 - 50, rotation);
                content.showTextAligned(Element.ALIGN_RIGHT, word, width - 20, height / 2 - 50, rotation);
                content.showTextAligned(Element.ALIGN_RIGHT, word, width - 20, height / 4 - 50, rotation);
            }
            content.endText();//结束写入文本
        }
        stamper.close();
        reader.close();
    }

    /** * 给pdf文件添加水印 (注意如果有添加背景颜色的话那么 图片水印将失效被盖住了) * * @param outPdfFile 加了水印后要输出的路径 * @param InPdfFile 要加水印的原pdf文件路径 * @param markImagePath 水印图片路径 * @throws Exception */
    public static void addPdfMark( String outPdfFile,String InPdfFile, String markImagePath) throws Exception {

        PdfReader reader = new PdfReader(InPdfFile, "PDF".getBytes());

        PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(outPdfFile));
        PdfGState gs = new PdfGState();

        Image img = Image.getInstance(markImagePath);// 插入水印
        img.setRotationDegrees(30);//旋转 角度
// img.scaleAbsolute(200,100);//自定义图片大小
        img.scalePercent(50);//图片依照比例缩放 百分之50
        gs.setFillOpacity(0.3f);//水印透明度
        int total = reader.getNumberOfPages(); //全部页
        PdfContentByte content;
        for (int i = 0; i < total; i++) {
            content = stamp.getUnderContent(+ 1); // 内容
            content.setGState(gs);
            float width = reader.getPageSize(i + 1).getWidth(); // 页宽度
            float height = reader.getPageSize(i + 1).getHeight(); // 页高度
            img.setAbsolutePosition(width / 2, height/3); //图片位置 第1张图
            content.addImage(img);
            img.setAbsolutePosition(width / 2, height/2); //图片位置 第2张图
            content.addImage(img);
            img.setAbsolutePosition(width / 2, height/4-50); //图片位置 第3张图
            content.addImage(img);
        }

        stamp.close();// 关闭
        reader.close();
    }

}

使用教程

文字水印 (原PDF可以有背景颜色) 使用的最多

参数1 是加完水印后的PDF存储位置 参数2是原PDF 参数3是 添加的文字水印 参数4是位置 (1中间,2两边)

@Test
    public void test2() throws Exception {  //文字水印
      PdfWatermark.setWatermark(FILE_DIR + "createSamplePDF_sui.pdf", FILE_DIR + "createSamplePDF.pdf", "测试打印", 1);

    }

参数4:是1的情况

参数4:是2的情况

图片水印 (原PDF不能有背景颜色 否则图片水印无效)

参数1 是加完水印后的PDF存储位置 参数2是原PDF 参数3是水印图片位置

@Test
    public void test2_1() throws Exception { //图片水印
        PdfWatermark.addPdfMark(FILE_DIR + "createSamplePDF_sui.pdf", FILE_DIR + "createSamplePDF.pdf", FILE_DIR + "baidu.jpg");

    }

删除PDF内的空页

/** * * * @param pdfSourceFile 原文件 * @param pdfDestinationFile 处理后新生成的文件 */

    public static void removeBlankPdfPages(String pdfSourceFile, String pdfDestinationFile)
    {
        try
        {
            // step 1: create new reader
            PdfReader r = new PdfReader(pdfSourceFile);
            RandomAccessFileOrArray raf = new RandomAccessFileOrArray(pdfSourceFile);
            Document document = new Document(r.getPageSizeWithRotation(1));
            // step 2: create a writer that listens to the document
            PdfCopy writer = new PdfCopy(document, new FileOutputStream(pdfDestinationFile));
            // step 3: we open the document
            document.open();
            // step 4: we add content
            PdfImportedPage page = null;

            //loop through each page and if the bs is larger than 20 than we know it is not blank.
            //if it is less than 20 than we don't include that blank page.
            for (int i=1;i<=r.getNumberOfPages();i++)
            {
                //get the page content
                byte bContent [] = r.getPageContent(i,raf);
                ByteArrayOutputStream bs = new ByteArrayOutputStream();
                //write the content to an output stream
                bs.write(bContent);
                //add the page to the new pdf
                if (bs.size() > 20)
                {
                    page = writer.getImportedPage(r, i);
                    writer.addPage(page);
                }
                bs.close();
            }
            //close everything
            document.close();
            writer.close();
            raf.close();
            r.close();
        }
        catch(Exception e)
        {
            //do what you need here
        }
    }

压缩PDF到Zip

这种压缩是在生成PDF的时候直接写入到zip里

//创建一个zip
             ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(FILE_DIR + "zipPDF.zip"));
            //创建PDF文件
            ZipEntry entry = new ZipEntry("hello_" + 1 + ".pdf");
            //将PDF添加到ZIP
            zip.putNextEntry(entry);
            Document document = new Document();
            PdfWriter writer = PdfWriter.getInstance(document, zip);
            // 后面内容

            writer.setCloseStream(false);
            document.open();
            document.add(new Paragraph("Hello " + 1));
            document.close();
            zip.closeEntry();

            zip.close();

合并PDF

读取所有文档然后追加到新的文档

/** * * @param fileList 文档地址 * @param savepath 合并后地址 */

    public static void mergepdf(ArrayList<String> fileList, String savepath) {

        Document document = null;
        try {
            document = new Document(new PdfReader(fileList.get(0)).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, new FileOutputStream(savepath));

            document.open();
            for (int i = 0; i < fileList.size(); i++) {
                PdfReader reader = new PdfReader(fileList.get(i));

                copy.addDocument(reader);
                reader.close();
            }
        } catch (IOException | DocumentException e) {
            e.printStackTrace();
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }
@Test
    public void he() throws IOException, DocumentException {

        ArrayList<String> arrayList=new ArrayList();
        arrayList.add(FILE_DIR + "splitPDF1.pdf");
        arrayList.add(FILE_DIR + "splitPDF2.pdf");
        mergepdf(arrayList, FILE_DIR + "mergePDF.pdf");
    }

读取PDF文档的页,然后一页一页追加到新的文档。 (也就是可以控制合并的页)

/** * * @param fileList 文档地址 * @param savepath 合并后地址 */
    public static void mergepdf(ArrayList<String> fileList, String savepath) {
        Document document = null;
        try {
            document = new Document(new PdfReader(fileList.get(0)).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, new FileOutputStream(savepath));

            document.open();
            for (int i = 0; i < fileList.size(); i++) {
                PdfReader reader = new PdfReader(fileList.get(i));

                copy.addDocument(reader);
                reader.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }
@Test
    public void he() throws IOException, DocumentException {

        ArrayList<String> arrayList=new ArrayList();
        arrayList.add(FILE_DIR + "splitPDF1.pdf");
        arrayList.add(FILE_DIR + "splitPDF2.pdf");
        mergepdf(arrayList, FILE_DIR + "mergePDF.pdf");

    }

添加批注和附件

PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));
        instance.setLinearPageMode();
        doc.open(); //打开PDF 默认为第一页

        doc.add(new Paragraph("1 page"));
        //给第一页添加批注
        doc.add(new Annotation("Title", "This is a annotation!"));

        doc.newPage();
        doc.add(new Paragraph("2 page"));
        //给第二页添加批注
        doc.add(new Annotation("Title", "This is a annotation!"));

        doc.newPage();
        doc.add(new Paragraph("3 page"));
        //给第三页添加附件
        Chunk chunk2 = new Chunk("\u00a0\u00a0"); // 必须有
        PdfAnnotation annotation = PdfAnnotation.createFileAttachment(
                instance, null, "Title", null,
                FILE_DIR+"baidu.jpg",
                "img.jpg");
        annotation.put(PdfName.NAME,
                new PdfString("Paperclip"));
        chunk2.setAnnotation(annotation);
        doc.add(chunk2);
        doc.close();

PDF表格

在PDF中整个表格没有控制位置的方法 但是会始终在PDF中间,而且还是自适应,并且独占一行,

插入表格

doc.open(); //打开PDF 默认为第一页

        PdfPTable table = new PdfPTable(3);//设置3列
        PdfPCell cell;

        cell = new PdfPCell(new Phrase("Cell with colspan 3"));//设置列的内容
        cell.setColspan(3);//合并3列
        table.addCell(cell);//将内容添加到表格里第一行

        cell = new PdfPCell(new Phrase("Cell with rowspan 2")); //设置列的内容
        cell.setRowspan(2); //合并2行
        table.addCell(cell); //将内容添加到表格里第二行

        // 然后开始填空行
        table.addCell("row 1; cell 1");
        table.addCell("row 1; cell 2");

        table.addCell("row 2; cell 1");
        table.addCell("row 2; cell 2");
        doc.add(table);

        doc.close();

设置表格宽度位置

在上面代码的后面添加

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页

        PdfPTable table = new PdfPTable(3);//设置3列
        PdfPCell cell;

        cell = new PdfPCell(new Phrase("Cell with colspan 3"));//设置列的内容
        cell.setColspan(3);//合并3列
        table.addCell(cell);//将内容添加到表格里第一行

        cell = new PdfPCell(new Phrase("Cell with rowspan 2")); //设置列的内容
        cell.setRowspan(2); //合并2行
        table.addCell(cell); //将内容添加到表格里第二行

        // 然后开始填空行
        table.addCell("row 1; cell 1");
        table.addCell("row 1; cell 2");

        table.addCell("row 2; cell 1");
        table.addCell("row 2; cell 2");
        doc.add(table);

//宽度100%
        table.setWidthPercentage(100);
        doc.add(table);
        doc.add(new Paragraph("\n\n"));

//宽度50% 居左
        table.setWidthPercentage(50);
        table.setHorizontalAlignment(Element.ALIGN_LEFT);
        doc.add(table);
        doc.add(new Paragraph("\n\n"));

//宽度50% 居中
        table.setWidthPercentage(50);
        table.setHorizontalAlignment(Element.ALIGN_CENTER);
        doc.add(table);
        doc.add(new Paragraph("\n\n"));

//宽度50% 居右
        table.setWidthPercentage(50);
        table.setHorizontalAlignment(Element.ALIGN_RIGHT);
        doc.add(table);
        doc.add(new Paragraph("\n\n"));

//固定宽度 居中
        table.setTotalWidth(300);
        table.setLockedWidth(true);
        table.setHorizontalAlignment(Element.ALIGN_CENTER);
        doc.add(table);

        doc.close();

设置表格边框

//没有边框 
PdfPTable table1 = new PdfPTable(3);    
table1.getDefaultCell().setBorder(PdfPCell.NO_BORDER);    
table1.addCell(new Paragraph("Cell 1"));   
table1.addCell(new Paragraph("Cell 2"));   
table1.addCell(new Paragraph("Cell 3"));   
document.add(table1);

设置边框粗细颜色

doc.open(); //打开PDF 默认为第一页

        //边框粗细颜色
        doc.newPage();
        Rectangle b1 = new Rectangle(0f, 0f);
        b1.setBorderWidthLeft(6f);//左边框大小
        b1.setBorderWidthBottom(5f);
        b1.setBorderWidthRight(4f);
        b1.setBorderWidthTop(2f);
        b1.setBorderColorLeft(BaseColor.RED);//左边框
        b1.setBorderColorBottom(BaseColor.ORANGE);//下边框
        b1.setBorderColorRight(BaseColor.YELLOW);
        b1.setBorderColorTop(BaseColor.GREEN);
        PdfPTable table2 = new PdfPTable(1);
        PdfPCell cell =  new PdfPCell(new Paragraph("Cell 1"));
        cell.cloneNonPositionParameters(b1);
        table2.addCell(cell);
        doc.add(table2);

        doc.close();

设置表格的边框和背景颜色

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页

        PdfPTable table = new PdfPTable(3);
        PdfPCell cell = new PdfPCell(new Paragraph("合并3列单元格",PdfFont.getChineseFont()));
        cell.setColspan(3);//合并3个列单元格
        table.addCell(cell);
        table.addCell("1.1");
        table.addCell("2.1");
        table.addCell("3.1");
        table.addCell("1.2");
        table.addCell("2.2");
        table.addCell("3.2");

        cell = new PdfPCell(new Paragraph("红色边框",PdfFont.getChineseFont()));
        //边框颜色
        cell.setBorderColor(new BaseColor(255, 0, 0));
        table.addCell(cell);

        cell = new PdfPCell(new Paragraph("合并2列元格,背景灰色",PdfFont.getChineseFont()));
        cell.setColspan(2);
        //背景色
        cell.setBackgroundColor(new BaseColor(0xC0, 0xC0, 0xC0));
        table.addCell(cell);

        table.setWidthPercentage(50); //设置表格的宽度
        doc.add(new Paragraph("表格1",PdfFont.getChineseFont()));
        doc.add(table);

        doc.close();

设置表格前后间隔

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页

        PdfPTable table = new PdfPTable(3);
        PdfPCell cell = new PdfPCell(new Paragraph("合并3列单元格",PdfFont.getChineseFont()));
        cell.setColspan(3);//合并3个列单元格
        table.addCell(cell);
        table.addCell("1.1");
        table.addCell("2.1");
        table.addCell("3.1");
        table.addCell("1.2");
        table.addCell("2.2");
        table.addCell("3.2");

        doc.newPage();
        doc.add(new Paragraph("表格前的间距",PdfFont.getChineseFont()));
        table.setSpacingBefore(150f); //设置表格前的间距
        table.setSpacingAfter(150f);  //设置表格后的间距
        doc.add(table);
        doc.add(new Paragraph("表格后的间距",PdfFont.getChineseFont()));

        doc.close();

设置单个的单元格宽度

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页
        
        doc.add(new Paragraph("按百分比设置单元格宽度\n\n",PdfFont.getChineseFont()));
        float[] widths = {0.1f, 0.1f, 0.05f, 0.75f};
        PdfPTable table = new PdfPTable(widths);
        table.addCell("10%");
        table.addCell("10%");
        table.addCell("5%");
        table.addCell("75%");

        table.addCell("aa");
        table.addCell("aa");
        table.addCell("a");
        table.addCell("aaaaaaaaaaaaaaa");

        table.addCell("bb");
        table.addCell("bb");
        table.addCell("b");
        table.addCell("bbbbbbbbbbbbbbb");

        table.addCell("cc");
        table.addCell("cc");
        table.addCell("c");
        table.addCell("ccccccccccccccc");
        doc.add(table);
        doc.add(new Paragraph("\n\n"));

        doc.close();

设置单个的单元格高度

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页
        PdfPTable table = new PdfPTable(2);
        PdfPCell cell;
        //设置高度
        table.addCell(new PdfPCell(new Paragraph("任意高度",PdfFont.getChineseFont())));
        cell = new PdfPCell(new Paragraph("1. blah blah\n2. blah blah blah\n3. blah blah\n4. blah blah blah\n5. blah blah\n6. blah blah blah\n7. blah blah\n8. blah blah blah"));
        table.addCell(cell);

//固定高度
        table.addCell(new PdfPCell(new Paragraph("固定高度",PdfFont.getChineseFont())));
        cell.setFixedHeight(50f);
        table.addCell(cell);

//最小高度
        table.addCell(new PdfPCell(new Paragraph("最小高度",PdfFont.getChineseFont())));
        cell = new PdfPCell(new Paragraph("最小高度:50",PdfFont.getChineseFont()));
        cell.setMinimumHeight(50f);
        table.addCell(cell);

//最后一行拉长到page底部
        table.setExtendLastRow(true);
        table.addCell(new PdfPCell(new Paragraph("拉长最后一行",PdfFont.getChineseFont())));
        cell = new PdfPCell(new Paragraph("最后一行拉长到page底部",PdfFont.getChineseFont()));
        table.addCell(cell);

        doc.add(table);

        doc.close();

PDF之二维码和条形码

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));

        doc.open(); //打开PDF 默认为第一页

        //条形码

        PdfContentByte cd = instance.getDirectContent();
        Barcode128 code128 = new Barcode128();
        code128.setCode("http://www.baidu.com".trim());
        code128.setCodeType(Barcode128.CODE128);
        Image code128Image = code128.createImageWithBarcode(cd, null, null);
        code128Image.setAbsolutePosition(400,300);//条形码的位置
        code128Image.scalePercent(125);
        doc.add(code128Image);

        //二维码
        BarcodeQRCode qrcode = new BarcodeQRCode("http://www.baidu.com".trim(), 1, 1, null);
        Image qrcodeImage = qrcode.getImage();
        qrcodeImage.setAbsolutePosition(200,400);//二维码的位置
        qrcodeImage.scalePercent(200);
        doc.add(qrcodeImage);

        
        doc.close();

固定模板生成PDF方式

手动生成PDF发票(使用较少)

公司提供具体模板格式你使用itext画出来…:

我这里随便从网上找来了一个发票

链接:https://pan.baidu.com/s/11iy8M0_Oo0E1tzEITMSSLw
提取码:1234

下面就来演示将上面这张图完整的还原出来,其实很简单将这张图为背景就行了,自己在需要添加值得地方设置变量就可以了

Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        PdfWriter instance = PdfWriter.getInstance(doc, new FileOutputStream(FILE_DIR + "splitPDF2.pdf"));
        doc.open(); //打开PDF 默认为第一页
        Image img = Image.getInstance(FILE_DIR+"37.jpg");
        img.scaleToFit(rect.getWidth(), rect.getHeight());
        doc.add(img);

        Phrase phrase1 = new Phrase("胡123",PdfFont.getChineseFont(12));
        Phrase phrase2 = new Phrase("212346512312311",PdfFont.getChineseFont(12));
        Phrase phrase3 = new Phrase("河南北京",PdfFont.getChineseFont(12));
        PdfContentByte canvas = instance.getDirectContent();
        // 参数1 默认就行 ,参数2默认就行,参数3内容,参数4x轴,参数5y轴,参数6字体旋转
        ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase1, 230, 550, 0);
        ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase2, 255, 530, 0);
        ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase3, 240, 510, 0);
        doc.close();

这种方式的好处就是灵活,只需要提供画好的图片就行,

但是就是太麻烦了,需要自己控制文本的位置

常用于特别复杂的场景,使用word画不出来的场景,需要的样式花样特别多的场景,将样式效果脱落出去,我们只管文字的填充就行

利用word生成PDF(使用较多)

需要的工具

本地word(windows10自带 或者 wps 都行)
1.
在线word转PDF

免费word转PDF https://smallpdf.com/cn/word-to-pdf 一天2次(足够用了)

免费word转PDF http://www.pdfdo.com/doc-to-pdf.aspx 没有次数限制

免费word转PDF http://wordtopdf.55.la/ 限制每次转换大小最多100m 没有次数限制

… 网上一大堆都是免费的如果上面都过期了那么自己找
1.
给PDF添加表单控件的工具 Adobe Acrobat DC

链接:https://pan.baidu.com/s/1dah5Gxh-YZRGFbZiCEVXFg
提取码:1234
解压后记得断网,不然就不能使用,然后找到目录里的AcroPro.msi 双击安装就行 之后就可以正常使用了

第一步先创建好word模板

利用上面提供的网站进行转换为PDF,转换完成后双击打开PDF(使用Adobe Acrobat DC打开的)

如果你右下角没有的话在更多工具里是有的,然后进到下面这个页面

之后会自动在空白的地方补充,文本框 如果没有自动补充那么在最上面有工具栏里自行添加

然后我们需要调整 文框的大小和位置自行调整最合适的位置,否则字默认显示位置是top,我们需要将文本框变小刚好够一个字的大小

字体的大小默认是跟随着文本框的高度进行变换的…

我们还可以点击某个文本右键属性设置文本格式(字体颜色 字体大小 ,自动计算…太多了基本HTML表单能实现的功能这里都能实现)

如果你要设置多个的话可以 shift+鼠标左键或者ctrl+a全部选中进行统一设置都行

调整后的效果

第二步使用java代码给文本框中对应的属性赋值

需要使用到3个关键的对象

PdfReader reader = new PdfReader(templateFile); // 模版文件目录
        PdfStamper ps = new PdfStamper(reader, new FileOutputStream(outFile)); // 生成的输出流
        AcroFields s = ps.getAcroFields(); //获取全部表单

案例:

public  void editPdfTemplate(String templateFile, String outFile) throws IOException, DocumentException {
        PdfReader reader = new PdfReader(templateFile); // 模版文件目录
        PdfStamper ps = new PdfStamper(reader, new FileOutputStream(outFile)); // 生成的输出流
        AcroFields s = ps.getAcroFields();

        //编辑文本域表单的内容
        s.setField("fill_1", "558");
        s.setField("fill_2", "99966");

        ps.setFormFlattening(true);  // 模板中的变量赋值之后不能编辑
        ps.close();
        reader.close();

    }
@Test
    public  void mob() throws DocumentException, IOException {
        					// 模板pdf 生成的pdf
        editPdfTemplate(FILE_DIR+"moban.pdf",FILE_DIR+"wordPDF.pdf");
    }

动态 freemarket模板生成PDF文档

我们使用 freemarket+itext+springboot 方式

一般pdf是用来生成发票或者简单的报告用的, 而某些领导蛋疼非要使用PDF导出数据统计报表你说气不气,没办法都是打工的不解决不行啊…

网上有很多种方式但是我一 一都进行了实验最后得出了结论就是 别乱搞没用,基本都失真,HTML转PDF基本都没有不失真的,

而且html的定位…很多css都不支持…浪费了我2天的我时间把网上各种生成PDF都试了个遍,最后还是放弃了,发现这种方式不能实现特别复杂的html页面 只能实现一些需要大量数据报表的显示,而且对样式要求不高,一般遇到这种情况都是采用导出Excel了而不是导出PDF了.

而且这种方式没法手动添加页只能自动适配页,也就是利用模板语法</#list xxx >,进行循环.

每页大具体大小是可以控制的多大都行但是必须要保证每次循环的内容必须都是在一页中. 之后每循环一次就是一页

我们HTML模板使用的是 Freemarker 也就是需要一些模板语法懂得都懂不会上百度…

简单来说就只需要3种语法就足够了:

获取值

${name}

if

<#if var ??> 
不是空
<#else> 
为空 
</#if>

循环

<#list goodsList as goods>
姓名: ${goods.name}  年龄: ${goods.age} 性别${goods.sex} <br/>  
</#list>

我们还需要知道这种方式的弊端

css 各种定位 和 浮动 弹性布局 … 都不能使用 那么如何控制属性的位置呢?? 可以通过表格的方式 表格内地元素都能使用的.

比如 text-align 控制文本位置 valign 垂直对齐 align 水平对齐 , 行高 … 都可以进行控制 自己慢慢试试就行了
1.
图片不能加载本地图片 只能加载服务器图片, 也就是http请求的图片 如果想加载本地图片需要使用nginx做本地文件代理

location /images/ {
             alias D:/nginx/nginx-1.19.10/html/images/;  # 代理的路径
             autoindex on; #是否开启目录浏览
        }

当访问http://xxxx:80/images 就相当于在访问 D:/nginx/nginx-1.19.10/html/images/

使用的话我们只需要在后面在追加一个图片名称就行了 http://xxxx:80/images/1.jpg
1.
对于空值的校验,Freemarker 在渲染模板时不允许取值为空,如果某一项值可能为空,则必须添加校验,推荐取值方式

<#if demoValue??>
    ${demoValue}
</#if>

模板样式必须添加的

/*-------------- 必须有的内容-------------------------*/
        /* 调整生成PDF页面宽和高 */
        @page {
            size: 340mm 350mm;
        }
        body{
            margin: 0;
            padding: 0;
            font-family: SimSun;
        }

        table {
            page-break-inside: auto;
            -fs-table-paginate: paginate;
            border-spacing: 0;
            cellspacing: 0;
            cellpadding: 0;
            border: solid 1px #ccc;
            padding: 2px 2px;
        }

        tr {
            page-break-inside: avoid;
            page-break-after: auto;
        }

 /*---------------必须有的内容------------------------*/

上代码就是防止生成的PDF时候表格内容超过当前页的时候不能自动适配

下面就详细演示所有代码 (web版下载)

项目结构

宋体字体

链接:https://pan.baidu.com/s/15uLOVmh2NZHG3g2zoPoaKw
提取码:1234

html模板

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
    <title>Title</title>
    <style>
 /*-------------- 必须有的内容-------------------------*/
        /* 调整页面宽和高 */
        @page {
            size: 340mm 350mm;
        }
        body{
            margin: 0;
            padding: 0;
            font-family: SimSun;
        }

        table {
            page-break-inside: auto;
            -fs-table-paginate: paginate;
            border-spacing: 0;
            cellspacing: 0;
            cellpadding: 0;
            border: solid 1px #ccc;
            padding: 2px 2px;
        }

        tr {
            page-break-inside: avoid;
            page-break-after: auto;
        }

 /*---------------必须有的内容------------------------*/

        table{

            border: 1px solid #333;
            border-bottom: none;
            border-left: none;

        }
        td{
            height: 30px;
            border: 1px solid #333;
            border-top: none;
            text-align: center;
        }
        tr.title{
            font-weight: bold;
        }
        td.title{
            height: 50px;
            font-weight: bold;
        }
        td.value{
            color: blue;
        }
        td.content{
            font-size: 12px;
            text-align: left;
        }
        td.sign{
            text-align: left;
            height: 40px;
        }



    </style>

</head>
<body>

<#list 1..max as i>

<table class="table" cellspacing="0">

    <tr >
        <td class="title" colspan="10">
            项目成员绩效考核表
        </td>
    </tr>

    <tr >
        <td style="width: 10%;" align="">
            被考核者
        </td>
        <td class="value" style="width: 10%;">
            <#if employee??>
                <#if employee.name??>
                    ${employee.name}
                </#if>
            </#if>
        </td>
        <td style="width: 10%;">
            部门
        </td>
        <td colspan="2" class="value" style="width: 20%;">
            <#if employeeDepart??>
                <#if employeeDepart.name??>
                    ${employeeDepart.name}
                </#if>
            </#if>
        </td>
        <td style="width: 10%;">
            考核者
        </td>
        <td class="value" style="width: 10%;">
            <#if acceptUser??>
                <#if acceptUser.name??>
                    ${acceptUser.name}
                </#if>
            </#if>
        </td>
        <td style="width: 10%;">
            考核时间
        </td>
        <td colspan="2" class="value" style="width: 20%;">
            <#if statisticalTime??>
                ${statisticalTime}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="10">
            第一部分工作目标(权重80%)
        </td>
    </tr>

    <tr class="title">
        <td colspan="2">
            指标名称
        </td>
        <td >
            权重
        </td>
        <td colspan="3">
            指标定义与评分标准
        </td>
        <td >
            完成值
        </td>
        <td >
            数据提供部门/人
        </td>
        <td >
            自评得分
        </td>
        <td >
            上级评分
        </td>
    </tr>

    <tr >
        <td colspan="2">
            工作计划完成率
        </td>
        <td >
            30%
        </td>
        <td colspan="3" class="content">
            实际完成量/计划完成量*100%<br/>
            1.完成比≥100%,本项为满分;<br/>
            2.完成比在90%(含)-100%(不含),扣10分;<br/>
            3.完成比在80%(含)-90%(不含),扣20分;<br/>
            4.完成比在80%(不含)以下的,本项为0分
        </td>
        <td class="value">
            <#if jobCompletionRate??>
                ${jobCompletionRate*100}%
            </#if>
        </td>
        <td >
            项目经理
        </td>
        <td class="value">
            <#if jobCompletionRateScore??>
                ${jobCompletionRateScore}
            </#if>
        </td>
        <td class="value">
            <#if jobCompletionRateSuperiorScore??>
                ${jobCompletionRateSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            工作计划完成及时率
        </td>
        <td >
            25%
        </td>
        <td colspan="3" class="content">
            实际完成天数/计划完成天数*100%<br/>
            1.完成比≦100%,本项为满分;<br/>
            2.完成比在100%-110%(不含),扣5分;<br/>
            3.完成比在110%(含)-130%(不含)扣10分;<br/>
            4.完成比在130%(含)以上的,本项为0分
        </td>
        <td class="value">
            <#if finishRate??>
                ${finishRate*100}%
            </#if>
        </td>
        <td >
            项目经理
        </td>
        <td class="value">
            <#if finishRateScore??>
                ${finishRateScore}
            </#if>
        </td>
        <td class="value">
            <#if finishRateSuperiorScore??>
                ${finishRateSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            返工率
        </td>
        <td >
            20%
        </td>
        <td colspan="3" class="content">
            实际返工次数/计划返工次数*100%<br/>
            1.完成比≤100%,本项为满分;<br/>
            2.完成比在100%(不含)-110%(不含),扣10分;<br/>
            3.完成比在110%(不含)-120%(含),扣15分;<br/>
            4.完成比在120%(不含)以上的,本项为0分
        </td>
        <td class="value">
            <#if returnRate??>
                ${returnRate*100}%
            </#if>
        </td>
        <td >
            项目经理<br/>
            外型供应商
        </td>
        <td class="value">
            <#if returnRateScore??>
                ${returnRateScore}
            </#if>
        </td>
        <td class="value">
            <#if returnRateSuperiorScore??>
                ${returnRateSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            技术文档资料保存完整性
        </td>
        <td >
            15%
        </td>
        <td colspan="3" class="content">
            实际上传资料数/要求上传资料数*100%<br/>
            1.完成比100%,本项得满分;<br/>
            2.完成比<100%,本项为0分
        </td>
        <td class="value">
            <#if uploadRate??>
                ${uploadRate*100}%
            </#if>
        </td>
        <td >
            项目经理
        </td>
        <td class="value">
            <#if uploadRateScore??>
                ${uploadRateScore}
            </#if>
        </td>
        <td class="value">
            <#if uploadRateSuperiorScore??>
                ${uploadRateSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            满意度
        </td>
        <td >
            10%
        </td>
        <td colspan="3" class="content">
            及时参加项目例会、积极汇报工作<br/>
            总分10分<br/>
            每遗漏一次,扣4分;扣完为止
        </td>
        <td >
            --
        </td>
        <td >
            项目经理
        </td>
        <td class="value">
            <#if satisfactionScore??>
                ${satisfactionScore}
            </#if>
        </td>
        <td class="value">
            <#if satisfactionSuperiorScore??>
                ${satisfactionSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="10">
            第二部分工作态度(权重20%)
        </td>
    </tr>

    <tr class="title">
        <td colspan="2">
            工作态度指标
        </td>
        <td>
            衡量方法
        </td>
        <td colspan="4">
            衡量标准
        </td>
        <td >
            权重
        </td>
        <td >
            自评得分
        </td>
        <td >
            上级评分
        </td>
    </tr>

    <tr >
        <td colspan="2">
            责任心
        </td>
        <td>
            上级评价
        </td>
        <td colspan="4" class="content">
            1.仅仅能按上级要求完成本职工作(5分); <br/>
            2.能够严格按照工作标准完成工作目标对本职工作负责到底,<br/>
            工作中不推卸责任、不上交矛盾,失误较少(10分); <br/>
            3.对待工作不怕繁琐、有耐心,考虑问题与做事细致、周到(15分); <br/>
            4.对待工作精益求精,力求一次性做到完美(20分); <br/>
            5.对团队成员拥有强烈的责任感,努力帮助团队成员提升工作质量(25分)
        </td>
        <td >
            25%
        </td>
        <td class="value">
            <#if responsibilityScore??>
                ${responsibilityScore}
            </#if>
        </td>
        <td class="value">
            <#if responsibilitySuperiorScore??>
                ${responsibilitySuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            主动性
        </td>
        <td>
            上级评价
        </td>
        <td colspan="4" class="content">
            1.按上级安排/ 指示做事,安排什么做什么(5分)<br/>
            2.按自己的职位职责做事,工作任务大多能完成;<br/>
            同时对工作中出现的问题,也能被动反应,予以处理(10分)<br/>
            3.对自己的工作大致有思考,上级安排的任务能有效配合确定工作计划,<br/>
            并按计划完成工作任务;同时能积极处理工作中出现的各种问题,<br/>
            需要请示或上级支持时也能按程序办理(15分)<br/>
            4.提前思考、主动安排自己的工作计划,并将之主动与上级沟通、协商、确定,<br/>
            按计划推进、完成工作任务;对工作问题提前预防,并妥善处理各类问题;<br/>
            能积极主动地协助同事完成职责范围外的其他工作(20分)<br/>
            5.上级只给出一个方向或任务,既能独立地制定计划、组织资源、推进实施、保证完成,<br/>
            支持、鼓励团队成员与周围同事积极主动开展工作,<br/>
            能营造积极、主动的文化氛围(25)
        </td>
        <td >
            25%
        </td>
        <td class="value">
            <#if initiativeScore??>
                ${initiativeScore}
            </#if>
        </td>
        <td class="value">
            <#if initiativeSuperiorScore??>
                ${initiativeSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            团队合作
        </td>
        <td>
            上级评价
        </td>
        <td colspan="4" class="content">
            1.积极融入团队并乐于接受同事帮助,配合团队完成工作(5分)<br/>
            2.主动给予同事必要的帮助;碰到困难时,善于利用团队的力量解决问题(10分)<br/>
            3.决策前积极发表个人意见,充分参与团队讨论;决策后,个人无论是否有异议,<br/>
            必须从行动上完全予以支持(15分);<br/>
            4.能够客观认识同事的优缺点,并在工作中充分体现“对事不对人”的原则(20分)<br/>
            5.能够以积极正面的心态去影响团队,并改善团队表现和氛围(25分)<br/>
        </td>
        <td >
            25%
        </td>
        <td class="value">
            <#if teamCooperationScore??>
                ${teamCooperationScore}
            </#if>
        </td>
        <td class="value">
            <#if teamCooperationSuperiorScore??>
                ${teamCooperationSuperiorScore}
            </#if>
        </td>
    </tr>
    <tr >
        <td colspan="2">
            保密意识
        </td>
        <td>
            上级评价
        </td>
        <td colspan="4" class="content">
            1.对岗位的保密责任有一定的认识(5分);<br/>
            2.熟悉公司保密协议,明确职责范围内的保密事项,并采取相应的维护措施(10分)<br/>
            3.以身作则,自觉、严格遵守保密协议,对保密协议未明确界定的问题能够很好的处理(15分)<br/>
            4.影响身边的同事,宣传保密意识,随时提醒同事;发现保密协议的缺陷和漏洞<br/>
            能及时向有关部门报告,并提出完善建议(20分);<br/>
            5.获悉他人违反和破坏保密协议时,积极抵制,能够及时向公司有关部门报告,<br/>
            并分情况采取积极措施以最大限度减少恶性后果,处理得当(25分)
        </td>
        <td >
            25%
        </td>
        <td class="value">
            <#if secrecyScore??>
                ${secrecyScore}
            </#if>
        </td>
        <td class="value">
            <#if secrecySuperiorScore??>
                ${secrecySuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="8">
            合计
        </td>
        <td class="value">
            <#if totalScore??>
                ${totalScore}
            </#if>
        </td>
        <td class="value">
            <#if totalSuperiorScore??>
                ${totalSuperiorScore}
            </#if>
        </td>
    </tr>

    <tr >
        <td colspan="2">
            等级评定规则
        </td>
        <td class="content" colspan="5">
            A:优秀(100分以上)<br/>
            B:良好(90-100分)<br/>
            C:合格(80-90分)<br/>
            D:基本合格(70-80分)<br/>
            E:需改进(60-70分)<br/>
            F:不合格(60分以下)
        </td>
        <td >
            等级评定
        </td>
        <td class="value" colspan="2">
            <#if grade??>
                ${grade}
            </#if>
        </td>
    </tr>

    <tr style="height: 100px;" >
        <td colspan="2">
            自我总结
        </td>
        <td colspan="8" class="content value">
            <#if selfSummary??>
                ${selfSummary}
            </#if>
        </td>
    </tr>

    <tr >
        <td rowspan="2" colspan="2">
            考核结果确认
        </td>
        <td class="sign" colspan="4">
            考核者签名:
        </td>
        <td class="sign" colspan="4">
            日期:
        </td>
    </tr>
    <tr>
        <td class="sign" colspan="4">
            被考核者签名:
        </td>
        <td class="sign" colspan="4">
            日期:
        </td>
    </tr>

</table>

</#list>

</body>
</html>

获取资源路径工具类

package com.pdf.utils;

import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileNotFoundException;

/** * @Description: 项目静态资源文件工具类 * 仅可用于包含在web项目中的资源文件路径,资源文件必须放置于 web 模块下 */
public class ResourceFileUtil {

    /** * 获取资源文件 * * @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名) * eg: "templates/pdf_export_demo.ftl" * @return * @throws FileNotFoundException */
    public static File getFile(String relativePath) throws FileNotFoundException {
        if (relativePath == null || relativePath.length() == 0) {
            return null;
        }
        if (relativePath.startsWith("/")) {
            relativePath = relativePath.substring(1);
        }
        File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX
                + relativePath);

        return file;
    }

    /** * 获取资源绝对路径 * * @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名) * eg: "templates/pdf_export_demo.ftl" * @return * @throws FileNotFoundException */
    public static String getAbsolutePath(String relativePath) throws FileNotFoundException {
        return getFile(relativePath).getAbsolutePath();
    }

    /** * 获取资源父级目录 * * @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名) * eg: "templates/pdf_export_demo.ftl" * @return * @throws FileNotFoundException */
    public static String getParent(String relativePath) throws FileNotFoundException {
        return getFile(relativePath).getParent();
    }

    /** * 获取资源文件名 * * @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名) * eg: "templates/pdf_export_demo.ftl" * @return * @throws FileNotFoundException */
    public static String getFileName(String relativePath) throws FileNotFoundException {
        return getFile(relativePath).getName();
    }

}

生成PDF工具类

package com.pdf.utils;

import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Entities;
import org.jsoup.select.Elements;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.*;
import java.util.Map;

public class PDFUtil {

    // 必须在resoures下面
    private  static String font="templates/font/simsun.ttf";  //字体
    private  static String templ="templates/pdf_export_employee_kpi.html"; //模板

    private PDFUtil(){}

    private volatile static Configuration configuration;

    static {
        if (configuration == null) {
            synchronized (PDFUtil.class) {
                if (configuration == null) {
                    configuration = new Configuration(Configuration.VERSION_2_3_28);
                }
            }
        }
    }

    /** * freemarker 引擎渲染 html * * @param dataMap 传入 html 模板的 Map 数据 * @param ftlFilePath html 模板文件相对路径(相对于 resources路径,路径 + 文件名) * eg: "templates/pdf_export_demo.ftl" * @return */
    public static String freemarkerRender(Map<String, Object> dataMap, String ftlFilePath) {
        Writer out = new StringWriter();
        configuration.setDefaultEncoding("UTF-8");
        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        try {
            configuration.setDirectoryForTemplateLoading(new File(ResourceFileUtil.getParent(ftlFilePath)));
            configuration.setLogTemplateExceptions(false);
            configuration.setWrapUncheckedExceptions(true);
            Template template = configuration.getTemplate(ResourceFileUtil.getFileName(ftlFilePath));
            template.process(dataMap, out);
            out.flush();
            return out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /** * 使用 iText 生成 PDF 文档 * * @param htmlTmpStr html 模板文件字符串 * @param fontFile 所需字体文件(相对路径+文件名) * */
    public static byte[] createPDF(String htmlTmpStr, String fontFile) {
        ByteArrayOutputStream outputStream = null;
        byte[] result = null;
        try {
            outputStream = new ByteArrayOutputStream();
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(htmlTmpStr);
            ITextFontResolver fontResolver = renderer.getFontResolver();
            // 解决中文支持问题,需要所需字体(ttc)文件
            fontResolver.addFont(ResourceFileUtil.getAbsolutePath(fontFile),BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            renderer.layout();
            renderer.createPDF(outputStream);
            result=outputStream.toByteArray();
            if(outputStream != null) {
                outputStream.flush();
                outputStream.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }





    /** * PDF 文件导出 * * @return */

    public static ResponseEntity<?> export(Map<String, Object> dataMap, String pdfName) {
        HttpHeaders headers = new HttpHeaders();
        /** * 数据导出(PDF 格式) */

        String htmlStr = PDFUtil.freemarkerRender(dataMap, templ);

        String s = formatHtml(htmlStr);
        System.out.println(s);
        byte[] pdfBytes = PDFUtil.createPDF(s, font);
        if (pdfBytes != null && pdfBytes.length > 0) {
            String fileName=null;
            if (pdfName!=null){
                fileName= pdfName;
            }else {
                fileName = System.currentTimeMillis() + (int) (Math.random() * 90000 + 10000) + ".pdf";
            }
            headers.setContentDispositionFormData("attachment", fileName);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(pdfBytes, headers, HttpStatus.OK);

        }

        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }

    public static ResponseEntity<?> export(Map<String, Object> dataMap) {
        return  export(   dataMap ,null);
    }

    /** * 使用jsoup规范化html * * @param html html内容 * @return 规范化后的html */
    private static String formatHtml(String html) {
        Document doc = Jsoup.parse(html);
        // 去除过大的宽度
        String style = doc.attr("style");
        if (StringUtils.isNotEmpty(style) && style.contains("width")) {
            doc.attr("style", "");
        }
        Elements divs = doc.select("div");
        for (Element div : divs) {
            String divStyle = div.attr("style");
            if (StringUtils.isNotEmpty(divStyle) && divStyle.contains("width")) {
                div.attr("style", "");
            }
        }
        // jsoup生成闭合标签
        doc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml);
        doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);
        return doc.html();
    }
}

Controller

package com.pdf.controller;

import com.pdf.utils.PDFUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@RestController
public class PdfController {

    /** * PDF 文件导出 */
    @GetMapping(value = "/pdf")
    public ResponseEntity<?> export(){
        try {
            Map<String, Object> dataMap = new HashMap<>(16);
            dataMap.put("statisticalTime",new Date().toString());
            dataMap.put("max",5);
            ResponseEntity<?> responseEntity = PDFUtil.export(dataMap);
            return responseEntity;
        } catch (Exception e) {
            e.printStackTrace();
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }
}

启动类

package com.pdf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com")
public class ApplictioBoot {
    public static void main(String[] args) {
        SpringApplication.run(ApplictioBoot.class,args);
    }
}

然后进行测试 访问 http://localhost:8080/pdf

注意:如果上面 word 模板方式或者 html+freemaret模板方式都不满足你的业务需求的话那么,你就需要手写Itext的方式进行生成PDF了

web页面下载方式

有两种方式

  1. 先生成PDF文件到本地 然后在读取本地pdf文件将二进制数据发送到前端之后在删除本地生成的PDF
  2. 直接在缓存中生成pdf 生成后直接想二进制发给前端

我们常用于第二种方式 ,而第一种方式只需要会io流就行了这里就不多讲了,直接上第二种方式的代码就行了

package com.pdf.utils;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

public class PdfDownLoadUtils {

    public static ByteArrayOutputStream ini() throws DocumentException {
        Rectangle rect = new Rectangle(PageSize.B4.rotate());
        Document doc = new Document(rect);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfWriter instance = PdfWriter.getInstance(doc, baos);
        doc(doc);
        return baos;
    }

    private static void doc(Document doc) throws DocumentException {
        doc.open(); //打开PDF 默认为第一页

        PdfPTable table = new PdfPTable(3);//设置3列
        PdfPCell cell;
        cell = new PdfPCell(new Phrase("Cell with colspan 3"));//设置列的内容
        cell.setColspan(3);//合并3列
        table.addCell(cell);//将内容添加到表格里第一行

        cell = new PdfPCell(new Phrase("Cell with rowspan 2")); //设置列的内容
        cell.setRowspan(2); //合并2行
        table.addCell(cell); //将内容添加到表格里第二行
        // 然后开始填空行
        table.addCell("row 1; cell 1");
        table.addCell("row 1; cell 2");

        table.addCell("row 2; cell 1");
        table.addCell("row 2; cell 2");
        doc.add(table);
        doc.close();
    }

}

核心代码

/** * PDF 文件导出 */
    @GetMapping(value = "/pdfDow")
    public ResponseEntity<?> export1(HttpServletRequest request, HttpServletResponse response)  {
        HttpHeaders headers = new HttpHeaders();

        String pdName="student.pdf"; //文件名称

        //设置页面编码格式
        try {
            ByteArrayOutputStream ini = PdfDownLoadUtils.ini();
// 页面打开PDF 不下载
// response.setContentLength(ini.size());
// OutputStream out = response.getOutputStream();
// ini.writeTo(out);
// return new ResponseEntity<byte[]>( headers, HttpStatus.OK);
            // 下载PDF
            headers.setContentDispositionFormData("attachment", pdName);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(ini.toByteArray(), headers, HttpStatus.OK);
        } catch (DocumentException e) {
            e.printStackTrace();
        }

        // 返回错误信息
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }

如果非要使用第一种方式的话只需要下面这一个方法就行了

public static void downloadFile(String path, HttpServletResponse response) {
        try {
            // path是指欲下载的文件的路径。
            File file = new File(path);
            // 取得文件名。
            String filename = file.getName();
            // 以流的形式下载文件。
            InputStream fis = new BufferedInputStream(new FileInputStream(path));
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            // 清空response
            response.reset();
            // 设置response的Header
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
            response.addHeader("Content-Length", "" + file.length());
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("application/octet-stream");
            toClient.write(buffer);
            toClient.flush();
            toClient.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }
/** * PDF 文件导出 */
    @GetMapping(value = "/pdfDow")
    public ResponseEntity<?> export1(HttpServletRequest request, HttpServletResponse response){

        try {
            int a=1/0;
            PdfDownLoadUtils.downloadFile(ResourceFileUtil.getAbsolutePath("PDF/student.pdf"),response);

        } catch (Exception e) {
            e.printStackTrace();
        }

        HttpHeaders headers = new HttpHeaders();
        // 失败返回错误信息
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }
    table.addCell("row 2; cell 1");
        table.addCell("row 2; cell 2");
        doc.add(table);
        doc.close();
    }

}

核心代码

/** * PDF 文件导出 */
    @GetMapping(value = "/pdfDow")
    public ResponseEntity<?> export1(HttpServletRequest request, HttpServletResponse response)  {
        HttpHeaders headers = new HttpHeaders();

        String pdName="student.pdf"; //文件名称

        //设置页面编码格式
        try {
            ByteArrayOutputStream ini = PdfDownLoadUtils.ini();
// 页面打开PDF 不下载
// response.setContentLength(ini.size());
// OutputStream out = response.getOutputStream();
// ini.writeTo(out);
// return new ResponseEntity<byte[]>( headers, HttpStatus.OK);
            // 下载PDF
            headers.setContentDispositionFormData("attachment", pdName);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(ini.toByteArray(), headers, HttpStatus.OK);
        } catch (DocumentException e) {
            e.printStackTrace();
        }

        // 返回错误信息
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }

如果非要使用第一种方式的话只需要下面这一个方法就行了

public static void downloadFile(String path, HttpServletResponse response) {
        try {
            // path是指欲下载的文件的路径。
            File file = new File(path);
            // 取得文件名。
            String filename = file.getName();
            // 以流的形式下载文件。
            InputStream fis = new BufferedInputStream(new FileInputStream(path));
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            // 清空response
            response.reset();
            // 设置response的Header
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
            response.addHeader("Content-Length", "" + file.length());
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("application/octet-stream");
            toClient.write(buffer);
            toClient.flush();
            toClient.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }
/** * PDF 文件导出 */
    @GetMapping(value = "/pdfDow")
    public ResponseEntity<?> export1(HttpServletRequest request, HttpServletResponse response){

        try {
            int a=1/0;
            PdfDownLoadUtils.downloadFile(ResourceFileUtil.getAbsolutePath("PDF/student.pdf"),response);

        } catch (Exception e) {
            e.printStackTrace();
        }

        HttpHeaders headers = new HttpHeaders();
        // 失败返回错误信息
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }

相关文章