公司项目有个需求就是根据模板动态的生成pdf文件,在网上看过很多方式生成的效果都不是很好。要么样式不支持,要么字体不支持。我这边项目的需求是这样:
1.根据模板生成纸张方向为横向的pdf
2.给pdf的每一页中间的位置都加上文字水印
研究了很久终于实现了效果,效果如下图所示:
总体实现方案
准备好html模板 —>就是写html,注意:样式少用css3的,并且不要使用外部样式.可以把样式写在style标签内部,也就是保证样式和html标签在同一个html文件中。
通过模板引擎将html模板生成含数据的html —> 使用
beetl
模板引擎实现,当然你也可以用freemarker
等模板引擎实现把生成的html转成pdf—>通过
wkhtmltopdf
这个程序实现最后把生成的pdf文件加上水印—>通过
itex
t实现
1 编写模板
模板如下,其实就是写html文件,可以看到${xxx}
这种占位符,这些数据都是动态的,用来给模板引擎识别的。
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
根据自己的需要下载相应的版本即可
3.2 安装wkhtmltopdf
下载下来的安装包如下:
3.2.1 windows安装
windows安装比较简单,一直点击下一步即可
装好之后可以使用cmd命令行来运行
在装好的路径下右键—>打开命令窗口:
命令格式:
wkhtmltopdf.exe https://wkhtmltopdf.org/downloads.html c:\1.pdf
可以看到其实就是把https://wkhtmltopdf.org/downloads.html这个网页在c盘下生成1.pdf
当然也可以把本地的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'
比如我查出来的结果如下:
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文件夹下面即可
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();
}
}