zoukankan      html  css  js  c++  java
  • java生成pdf并加水印,通过wkhtmltopdf实现

    公司项目有个需求就是根据模板动态的生成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 导出时遇到的问题

  • 相关阅读:
    程序模块化 与 单元测试
    【集美大学1411_助教博客】2017软件工程开跑啦。。。。
    沟通很重要
    参赛感言
    助教日志_期末总结
    助教日志_【沈阳航空航天大学软件工程 1,2班】期末排行
    [数据库事务与锁]详解一: 彻底理解数据库事务
    [数据库事务与锁]详解二: 数据库的读现象浅析
    [数据库事务与锁]详解三: 深入分析事务的隔离级别
    [数据库事务与锁]详解四: 数据库的锁机制
  • 原文地址:https://www.cnblogs.com/helf/p/15096971.html
Copyright © 2011-2022 走看看