希望达到脱离浏览器将页面导成 pdf
的效果, 于是利用 wkhtmltopdf
的将 url
转 pdf
的能力实现
利用 java
代码触发 wkhtmltopdf
有几点东西要注意一下
linux
系统是默认不带中文字体的, 需要自己手动安装一下wkhtmltopdf [OPTIONS]... [More input files]
--allow
: 允许加载从指定的文件夹中的文件或文件(可重复)--book
: 设置一会打印一本书的时候,通常设置的选项--collate
: 打印多份副本时整理--cookie
: 设置一个额外的cookie(可重复)--cookie-jar
: 读取和写入的Cookie,并在提供的cookie jar文件--copies
: 复印打印成pdf文件数(默认为1)--cover*
: 使用HTML文件作为封面。它会带页眉和页脚的TOC之前插入--custom-header
: 设置一个附加的HTTP头(可重复)--debug-javascript
: 显示的javascript调试输出--default-header
: 添加一个缺省的头部,与页面的左边的名称,页面数到右边,例如:–header-left '[webpage]' --header-right '[page]/[toPage]' --header-line
--disable-external-links
: 禁止生成链接到远程网页--disable-internal-links
: 禁止使用本地链接--disable-pdf-compression
: 禁止在PDF对象使用无损压缩--disable-smart-shrinking
: 禁止使用WebKit的智能战略收缩,使像素/ DPI比没有不变--disallow-local-file-access
: 禁止允许转换的本地文件读取其他本地文件,除非explecitily允许用 --allow
--dpi
: 显式更改DPI(这对基于X11的系统没有任何影响)--enable-plugins
: 启用已安装的插件(如Flash--encoding
: 设置默认的文字编码--extended-help
: 显示更广泛的帮助,详细介绍了不常见的命令开关--forms
: 打开HTML表单字段转换为PDF表单域--grayscale
: PDF格式将在灰阶产生--help
: Display help--htmldoc
: 输出程序HTML帮助--ignore-load-errors
: 忽略claimes加载过程中已经遇到了一个错误页面--lowquality
: 产生低品质的PDF/ PS。有用缩小结果文档的空间--manpage
: 输出程序手册页--margin-bottom
: 设置页面下边距 (default 10mm)--margin-left
: 将左边页边距 (default 10mm)--margin-right
: 设置页面右边距 (default 10mm)--margin-top
: 设置页面上边距 (default 10mm)--minimum-font-size
: 最小字体大小 (default 5)--no-background
: 不打印背景--orientation
: 设置方向为横向或纵向--page-height
: 页面高度 (default unit millimeter)--page-offset*
: 设置起始页码 (default 1)--page-size
: 设置纸张大小: A4, Letter, etc.--page-width
: 页面宽度 (default unit millimeter)--password
: HTTP验证密码--post
: Add an additional post field (repeatable)--post-file
: Post an aditional file (repeatable)--print-media-type
: 使用的打印介质类型,而不是屏幕--proxy
: 使用代理--quiet
: Be less verbose--read-args-from-stdin
: 读取标准输入的命令行参数--readme
: 输出程序自述--redirect-delay
: 等待几毫秒为JS-重定向(default 200)--replace*
: 替换名称,值的页眉和页脚(可重复)--stop-slow-scripts
: 停止运行缓慢的JavaScripts--title
: 生成的PDF文件的标题(第一个文档的标题使用,如果没有指定)--toc
: 插入的内容的表中的文件的开头--use-xserver
: 使用X服务器(一些插件和其他的东西没有X11可能无法正常工作)--user-style-sheet
: 指定用户的样式表,加载在每一页中--username
: HTTP认证的用户名--version
: 输出版本信息退出--zoom
: 使用这个缩放因子 (default 1)--header-center**
: (设置在中心位置的页眉内容)--header-font-name*
: (default Arial) (设置页眉的字体名称)--header-font-size*
: (设置页眉的字体大小)--header-html
: (添加一个HTML页眉,后面是网址)--header-left
: (左对齐的页眉文本)--header-line
: (显示一条线在页眉下)--header-right*
: (右对齐页眉文本)--header-spacing:
: (设置页眉和内容的距离,默认0)--footer-center:
: (设置在中心位置的页脚内容)--footer-font-name*
: (设置页脚的字体名称)--footer-font-size*
: (设置页脚的字体大小default 11)--footer-html
: (添加一个HTML页脚,后面是网址)--footer-left
: (左对齐的页脚文本)--footer-line
: 显示一条线在页脚内容上)--footer-right
: (右对齐页脚文本)--footer-spacing
: (设置页脚和内容的距离)###表内容选项中
--toc-depth
: Set the depth of the toc (default 3)--toc-disable-back-links
: Do not link from section header to toc--toc-disable-links
: Do not link from toc to sections--toc-font-name*
: Set the font used for the toc (default Arial)--toc-header-font-name*
: The font of the toc header (if unset use --toc-font-name)--toc-header-font-size*
: The font size of the toc header (default 15)--toc-header-text*
: The header text of the toc (default Table Of Contents)--toc-l1-font-size*
: Set the font size on level 1 of the toc (default 12)--toc-l1-indentation*
: Set indentation on level 1 of the toc (default 0)--toc-l2-font-size*
: Set the font size on level 2 of the toc (default 10)--toc-l2-indentation*
: Set indentation on level 2 of the toc (default 20)--toc-l3-font-size*
: Set the font size on level 3 of the toc (default 8)--toc-l3-indentation*
: Set indentation on level 3 of the toc (default 40)--toc-l4-font-size*
: Set the font size on level 4 of the toc (default 6)--toc-l4-indentation*
: Set indentation on level 4 of the toc (default 60)--toc-l5-font-size*
: Set the font size on level 5 of the toc (default 4)--toc-l5-indentation*
: Set indentation on level 5 of the toc (default 80)--toc-l6-font-size*
: Set the font size on level 6 of the toc (default 2)--toc-l6-indentation*
: Set indentation on level 6 of the toc (default 100)--toc-l7-font-size*
: Set the font size on level 7 of the toc (default 0)--toc-l7-indentation*
: Set indentation on level 7 of the toc (default 120)--toc-no-dots
: Do not use dots, in the toc-dump-outline
: 转储目录到一个文件--outline
: 显示目录(文章中h1,h2来定)--outline-depth
: 设置目录的深度(默认为4)[page]
: 由当前正在打印的页的数目代替[frompage]
: 由要打印的第一页的数量取代[topage]
: 由最后一页要打印的数量取代[webpage]
: 通过正在打印的页面的URL替换[section]
: 由当前节的名称替换[subsection]
: 由当前小节的名称替换[date]
: 由当前日期系统的本地格式取代[time]
: 由当前时间,系统的本地格式取代[page]
: 由当前正在打印的页的数目代替[frompage]
: 由要打印的第一页的数量取代[topage]
: 由最后一页要打印的数量取代[webpage]
: 通过正在打印的页面的URL替换[section]
: 由当前节的名称替换[subsection]
: 由当前小节的名称替换[date]
: 由当前日期系统的本地格式取代[time]
: 由当前时间,系统的本地格式取代/**
* @Author: WanG
* @Date: 2019-08-22 13:54
* @version: v1.0
* @description: 使用wkhtmltopdf, 来将页面转成pdf
*/
@Slf4j
@Service
public class PDFService {
private Random random = new Random();
/**
* 将传入的页面转pdf, 返回字节数组
* 默认自动随机生成文件名字
* @author wangq 2019-08-22 16:27
* @param url 页面url
* @return
*/
public byte[] html2pdf(String url) {
String tmp = System.getProperty("java.io.tmpdir");
String filename = System.currentTimeMillis() + random.nextInt(1000) + "";
return doHtml2pdf(url, tmp + "/" + filename);
}
private byte[] doHtml2pdf(String src, String dest) {
String space = " ";
String wkhtmltopdf = findExecutable();
if (StringUtils.isEmpty(wkhtmltopdf)) {
log.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();
log.info("create dir for new file,{}", dirsCreation);
}
/*
一些特殊符号
[page] : 页码
[data] : 日期(例如:8/22/19)
*/
StringBuilder cmd = new StringBuilder();
cmd.append(findExecutable()).append(space)
.append(src).append(space)
.append("--footer-center").append(space).append("[page]").append(space)
.append("--footer-font-size").append(space).append("14").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-right").append(space).append("WanG提供技术支持").append(space)
.append(dest);
InputStream is = null;
try {
String finalCmd = cmd.toString();
log.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) {
log.error("html转换pdf出错", e);
throw new RuntimeException("html转换pdf出错了");
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 根据当前系统返回 wkhtmltopdf 执行命令
* @author wangq 2019-08-22 16:26
* @return
*/
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) {
log.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) {
log.debug(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}