分类: java | 标签: java pdf

java生成pdf并加水印(通过wkhtmltopdf实现)

发表于: 2021-08-03 23:47:28 | 字数统计: 2.5k | 阅读时长预计: 12分钟

公司项目有个需求就是根据模板动态的生成pdf文件,在网上看过很多方式生成的效果都不是很好。要么样式不支持,要么字体不支持。我这边项目的需求是这样:

1.根据模板生成纸张方向为横向的pdf
2.给pdf的每一页中间的位置都加上文字水印

研究了很久终于实现了效果,效果如下图所示:

image-20210801204853727

总体实现方案

  • 准备好html模板 —>就是写html,注意:样式少用css3的,并且不要使用外部样式.可以把样式写在style标签内部,也就是保证样式和html标签在同一个html文件中。

  • 通过模板引擎将html模板生成含数据的html —> 使用beetl模板引擎实现,当然你也可以用freemarker等模板引擎实现

  • 把生成的html转成pdf—>通过wkhtmltopdf这个程序实现

  • 最后把生成的pdf文件加上水印—>通过itext实现

1 编写模板

模板如下,其实就是写html文件,可以看到${xxx}这种占位符,这些数据都是动态的,用来给模板引擎识别的。

image-20210801205236360

2 模板引擎生成带数据的html文件

因为公司项目用的是beetl模板引擎所以我这里也就用beetl生成html文件.你也可以用其它的模板引擎生成,比如freemarker。如果这两个都不会,那就先去学习一下吧,O(∩_∩)O哈哈~,其实不难,这两个模板引擎的语法和jsp的语法很像。

beetl官网:https://www.kancloud.cn/xiandafu/beetl3_guide/2138944

freemarker官网:http://freemarker.foofun.cn/

beetl的maven依赖如下:

<dependency>
    <groupId>com.ibeetl</groupId>
    <artifactId>beetl</artifactId>
    <version>3.1.8.RELEASE</version>
</dependency>

大致代码如下:这里需要根据你自己的模板进行修改

public static void fileResourceLoaderTest() throws Exception{
        String root = System.getProperty("user.dir")+File.separator+"template";
        //System.out.println(root);
        FileResourceLoader resourceLoader = new FileResourceLoader(root,"utf-8");
        Configuration cfg = Configuration.defaultConfiguration();
        GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
        //准备模板需要的动态数据
        Map map = new HashMap();//给模板的数据
        map.put("address", "清远市清城区横荷清远大道55号");
        //通过模板位置构建模板对象
        Template t = gt.getTemplate("/s01/order.html");
        //将数据和模板绑定,
        t.binding(map);
        String str = t.render();
        System.out.println(str);
        //生成html文件
        FileWriter writer = new FileWriter("C:\\Users\\Administrator\\Desktop\\a\\1.html");
        writer.write(str);
    }

3 通过wkhtmltopdf将生成的html文件转成pdf文件

wkhtmltopdf是一个程序,具体来说就是一个软件,也是生成pdf的关键技术。可以在java程序中调用这个软件来把html文件转成pdf文件。我对比了很多方案,我认为wkhtmltopdf是目前来说比较好的一种方案。

3.1 下载wkhtmltopdf

官网下载地址:https://wkhtmltopdf.org/downloads.html

根据自己的需要下载相应的版本即可

image-20210802230511892

3.2 安装wkhtmltopdf

下载下来的安装包如下:

image-20210802230435552

3.2.1 windows安装

windows安装比较简单,一直点击下一步即可

装好之后可以使用cmd命令行来运行

在装好的路径下右键—>打开命令窗口:

image-20210802232012602

命令格式:

wkhtmltopdf.exe https://wkhtmltopdf.org/downloads.html c:\1.pdf

image-20210802232308095

可以看到其实就是把https://wkhtmltopdf.org/downloads.html这个网页在c盘下生成1.pdf

image-20210802232335699

当然也可以把本地的html文件转成pdf,比如:

wkhtmltopdf.exe c:\1.html c:\1.pdf

这样生成的pdf默认是竖向的,但是公司项目需求是横向的所以要加上参数 -O landscape

wkhtmltopdf -O landscape c:\1.html c:\1.pdf

其它参数配置可以看看这个博客:wkhtmltopdf 参数 详解

3.2.2 centos7-linux安装

  • 首先需要安装以下依赖:
yum install libX11
yum install libXext
yum install libXrender
yum install libjpeg
yum install xorg-x11-fonts-75dpi
yum install xorg-x11-fonts-Type1
  • 然后执行安装命令
rpm -ivh wkhtmltox-0.12.6-1.centos7.x86_64.rpm

至此linux上就安装好了,linux的运行wkhtmltopdf和windows类似

比如将当前目录下的1.html转成1.pdf

/opt/wkhtmltopdf/bin/wkhtmltopdf -O landscape .\1.html .\1.pdf

/opt/wkhtmltopdf/bin/wkhtmltopdf其实就是装好之后的路径,可以通过find命令查出来.每台linux的服务器可能不一样,所以要查一下。

find / -name 'wkhtmltopdf'

比如我查出来的结果如下:

image-20210802234806791

3.3 封装一个html转pdf的工具类

public class WKHtmlToPdfUtil {
    
    /**
     *       测试
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception{
        htmlToPdf("https://www.baidu.com","d:\\1.pdf");
    }

    /**
     * 获取windows和linux调用wkhtmltopdf程序的命令
     * @param sourceFilePath
     * @param targetFilePath
     * @return
     */
    public static String getCommand(String sourceFilePath, String targetFilePath) {
        String system = System.getProperty("os.name");
        if(system.contains("Windows")) {
            System.out.println("windows");
            //-O landscape 表示纸张方向为横向  默认为纵向  如果要生成横向那么去掉-O landscape即可
            return "C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe -O landscape " + sourceFilePath + " " + targetFilePath;
        }else if(system.contains("Linux")) {
            System.out.println("linux");
            return "/usr/local/bin/wkhtmltopdf -O landscape " + sourceFilePath + " " + targetFilePath;
        }
        return "";
    }
    
    /**
     * html转pdf
     * @param sourceFilePath
     * @param targetFilePath
     * @return
     */
    public static boolean htmlToPdf(String sourceFilePath, String targetFilePath) {
        try {
            System.out.println("转化开始");
            String command = WKHtmlToPdfUtil.getCommand(sourceFilePath, targetFilePath);
            System.out.println("command:"+command);
            Process process = Runtime.getRuntime().exec(command);
            process.waitFor();  //这个调用比较关键,就是等当前命令执行完成后再往下执行
            System.out.println("html-->pdf:success");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("html-->pdf:error");
            return false;
        }
    }
}

3.4 wkhtmltopdf的一些问题总结

3.4.1 强制分页问题

添加样式,使用样式的容器将会独占一页,如果分页最后一页也会独占一页,给div容器加上以下样式即可。

page-break-after: always !important;

3.4.2 每页显示表头

thead {
     display:table-header-group;/* 给thead加上这行保证每页都会带上表头 */
}

给thead加上这行保证每页都会带上表头,同时也会解决表头与内容重叠问题.

如果仍然存在表头和内容重叠问题,一般是因为表格的一行的内容超过一页。我就遇到过这种问题,我的解决方案是用js缩放页面。

document.getElementsByTagName('body')[0].style.zoom=0.6;//0.6为缩放的比例

3.4.3 表格分页时行内容被截断问题

给表格tbody的tr标签加上这个样式

tbody tr{
    page-break-inside: avoid !important;
}

4 给pdf的每一页都加上文字水印和页码

如果你的项目没有这个需求可以不看这部分

maven坐标如下:

<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itextasian</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>2.1.7</version>
</dependency>

其中itextasian这个maven仓库默认是没有的,需要手动添加到自己的maven仓库

我上传到百度云了:

链接:https://pan.baidu.com/s/1bN3MTRjzlQaqzF5FpfpySg
提取码:ijgf

下载下来后将lowagie.rar 中的 lowagie 文件夹 直接拷贝到本地仓库的com文件夹下面即可

image-20210803232811879

demo代码如下

import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.junit.Test;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Image;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;

public class PDFAddWaterMart{
    
    @Test
    public void addTextMart() throws Exception{
        // 要输出的pdf文件
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("C:\\result1.pdf")));
        // 将pdf文件先加水印然后输出
        setWatermarkText(bos, "C:\\input.pdf","壹新设计报价云平台");
    }
    
    //@Test
    public void addImageMart() throws Exception{
        // 要输出的pdf文件
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("C:\\result2.pdf")));
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        // 将pdf文件先加水印然后输出
        setWatermarkImage(bos, "C:\\input.pdf", format.format(cal.getTime()) + "  下载使用人:" + "测试user", 16);
    }
    
    
    /**
     * 设置文字水印
     * @param bos
     * @param input
     * @param text
     * @throws DocumentException
     * @throws IOException
     */
    public static void setWatermarkText(BufferedOutputStream bos, String input,String text)
            throws DocumentException, IOException {
        PdfReader reader = new PdfReader(input);
        PdfStamper stamper = new PdfStamper(reader, bos);
        int total = reader.getNumberOfPages()+1;
        PdfContentByte content;
        BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        PdfGState gs1 = new PdfGState();
        gs1.setFillOpacity(0.2f);//设置透明度
        PdfGState gs2 = new PdfGState();
        gs2.setFillOpacity(1f);
        for (int i = 1; i < total; i++) {
            content = stamper.getOverContent(i);// 在内容上方加水印
            //content = stamper.getUnderContent(i);//在内容下方加水印
            //水印内容
            content.setGState(gs1);
            content.beginText();
            content.setColorFill(Color.GRAY);
            content.setFontAndSize(base, 50);
            content.setTextMatrix(70, 200);
            //350为x坐标 350y坐标  45为旋转45度
            content.showTextAligned(Element.ALIGN_CENTER, text, 350, 350, 45);
            content.endText();//结束文字
            //页脚内容
            content.setGState(gs2);
            content.beginText();
            content.setColorFill(Color.BLACK);
            content.setFontAndSize(base, 8);
            content.setTextMatrix(70, 200);
            content.showTextAligned(Element.ALIGN_CENTER, "壹新设计报价云平台 www.newbeall.com 第"+i+ "页,共"+(total-1)+"页", 370, 10, 0);
            content.endText();
        }
        stamper.close();
    }
    
    /**
     * 设置图片水印
     * @param bos输出文件的位置
     * @param input
     *            原PDF位置
     * @param waterMarkName
     *            页脚添加水印
     * @param permission
     *            权限码
     * @throws DocumentException
     * @throws IOException
     */
    public static void setWatermarkImage(BufferedOutputStream bos, String input, String waterMarkName, int permission)
            throws DocumentException, IOException {
        
        
        PdfReader reader = new PdfReader(input);
        PdfStamper stamper = new PdfStamper(reader, bos);
        
        int total = reader.getNumberOfPages() + 1;
        PdfContentByte content;
        BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        PdfGState gs = new PdfGState();
        for (int i = 1; i < total; i++) {
            content = stamper.getOverContent(i);// 在内容上方加水印
            // content = stamper.getUnderContent(i);//在内容下方加水印
            gs.setFillOpacity(0.2f);
            // content.setGState(gs);
            content.beginText();
            content.setColorFill(Color.LIGHT_GRAY);
            content.setFontAndSize(base, 50);
            content.setTextMatrix(70, 200);
            //这里的水印图片换成你自己的
            Image image = Image.getInstance("C:\\Users\\Administrator\\Desktop\\quotation12.png");
            /*
              img.setAlignment(Image.LEFT | Image.TEXTWRAP);
              img.setBorder(Image.BOX); img.setBorderWidth(10);
              img.setBorderColor(BaseColor.WHITE); img.scaleToFit(100072);//大小
              img.setRotationDegrees(-30);//旋转
             */
            image.setAbsolutePosition(200, 206); // set the first background
            image.scaleToFit(200, 200);// image of the absolute
            image.setRotationDegrees(45);//旋转45度
            content.addImage(image);
            content.setColorFill(Color.BLACK);
            content.setFontAndSize(base, 8);
            content.showTextAligned(Element.ALIGN_CENTER, "下载时间:" + waterMarkName + "", 300, 10, 0);
            content.endText();

        }
        stamper.close();
    }
}

参考

java生成pdf

wkhtmltopdf 参数 详解

Java给图片和PDF文件添加水印(图片水印和文字水印)

maven项目中使用itextasian

使用 wkhtmltopdf 导出时遇到的问题

------ 本文结束,感谢您的阅读 ------
本文作者: 贺刘芳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。