What for?

PRD: 打印条款以供用户下载。

After googling around,there are two possiable options:

  • iText. 以前一般都用这个来做PDF的生成,但试用了一下感觉很是难用!

  • wkhtmltopdf. 一个单独的程序,命令行可直接使用用,相对(也有一些坑)来说简单易用。官方介绍:

    wkhtmltopdf and wkhtmltoimage are open source (LGPLv3) command line tools to render HTML into PDF and various image formats using the Qt WebKit rendering engine. These run entirely “headless” and do not require a display or display service.

开始捣鼓

本地环境

本地mac,官网下了一个pkg包,安装后终端直接:

1
wkhtmltopdf http://baidu.com baidu.pdf

yeah,it works! 不错,以为就行了,结果各种问题,字体太小,页面也感觉缩小了很多,起初以为CSS的问题,调了一下没什么卵用。于是再次Google半天,发现了这么一篇文章:wkhtmltopdf-font-and-sizing-issues, 使用了该文章中所说的--disable-smart-shrinking 页面缩小的问题得到解决。 但还有一个问题,页面未分页,整个诺达的HTML页面完全打成了一页,SO上看到有人说以下方法可以解决:

1
2
3
4
5
6
7
8
div {
page-break-before: always;
}
body {
line-height: 1em;
text-rendering: geometricPrecision;
}

加入style之后效果得以缓解。以为至此就算折腾完了,no way, 继续。

服务器环境

服务器centos, yum:

1
yum install wkhtmltopdf

兴高采烈地试一把:

1
wkhtmltopdf http://baidu.com baidu.pdf

oops, error: can not connect to X server.

尝试Google大法,SO有个回答:http://stackoverflow.com/a/9685072/1388881, 照着试了一下,结果还是无果。 又Google了大半天发现很多直接下载官网的安装包就没有问题,于是:

1
2
3
4
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
tar xf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
cd wkhtmltox/bin
cp wkhtmltopdf /bin

再次尝试wkhtmltopdf http://baidu.com baidu.pdf, and it works! 只想感叹一句,能活到今天真是💗大啊!

字体安装

由于服务器缺少部分字体,所以还需安装中文字体,从windows下拷来字体放至服务器. 百度网盘放了份字体.

1
2
3
4
5
cp fonts to /usr/share/fonts/chinese_fonts
# 为了字体起效果,而且不重启机器
fc-cache /usr/share/fonts/chinese_fonts
# 查看安装的字体
fc-list

Code

一开始使用的是Java WkHtmlToPdf Wrapper, 但在使用过程中偶尔会出现HostNotFoundError. 所以放弃了。看了下源码不是很多代码,自己写了个调用wkhtmltopdf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public class PDFService {
private final Logger logger = LoggerFactory.getLogger(PDFService.class);
private Random random = new Random();
public byte[] html2pdf(String srcUrl) {
String tmp = System.getProperty("java.io.tmpdir");
String filename = System.currentTimeMillis() + random.nextInt(1000) + "";
return html2pdf(srcUrl, tmp + "/" + filename);
}
private byte[] html2pdf(String src, String dest) {
String space = " ";
String wkhtmltopdf = findExecutable();
if (StringUtils.isEmpty(wkhtmltopdf)) {
logger.error("no wkhtmltopdf found!");
throw new RuntimeException("html转换pdf出错了");
}
File file = new File(dest);
File parent = file.getParentFile();
if (!parent.exists()) {
boolean dirsCreation = parent.mkdirs();
logger.info("create dir for new file,{}", dirsCreation);
}
StringBuilder cmd = new StringBuilder();
cmd.append(findExecutable()).append(space)
.append(src).append(space)
.append("--footer-right").append(space).append("页码:[page]").append(space)
.append("--footer-font-size").append(space).append("5").append(space)
.append("--disable-smart-shrinking").append(space)
.append("--load-media-error-handling")
.append(space).append("ignore").append(space)
.append("--load-error-handling").append(space).append("ignore").append(space)
.append("--footer-left").append(space).append("电子档打印时间:[date]").append(space)
.append(dest);
InputStream is = null;
try {
String finalCmd = cmd.toString();
logger.info("final cmd:{}", finalCmd);
Process proc = Runtime.getRuntime().exec(finalCmd);
new Thread(new ProcessStreamHandler(proc.getInputStream())).start();
new Thread(new ProcessStreamHandler(proc.getErrorStream())).start();
proc.waitFor();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
is = new FileInputStream(file);
byte[] buf = new byte[1024];
while (is.read(buf, 0, buf.length) != -1) {
baos.write(buf, 0, buf.length);
}
return baos.toByteArray();
} catch (Exception e) {
logger.error("html转换pdf出错", e);
throw new RuntimeException("html转换pdf出错了");
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Attempts to find the `wkhtmltopdf` executable in the system path.
*
* @return the wkhtmltopdf command according to the OS
*/
public String findExecutable() {
Process p;
try {
String osname = System.getProperty("os.name").toLowerCase();
String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
p = Runtime.getRuntime().exec(cmd);
new Thread(new ProcessStreamHandler(p.getErrorStream())).start();
p.waitFor();
return IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
} catch (Exception e) {
logger.error("no wkhtmltopdf found!", e);
}
return "";
}
private static class ProcessStreamHandler implements Runnable {
private InputStream is;
public ProcessStreamHandler(InputStream is) {
this.is = is;
}
@Override
public void run() {
BufferedReader reader = null;
try {
InputStreamReader isr = new InputStreamReader(is, "utf-8");
reader = new BufferedReader(isr);
String line;
while ((line = reader.readLine()) != null) {
if (ConfigAdapter.getIsDebug())
System.out.println(line); //输出内容
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}