HTML To PDF Solutions / HTML 转换 PDF 解决方案
Published: | Thu 10 January 2019 |
By: | Brian Shen |
Category: | Tools |
Tags: | phantomjs wkhtmltopdf PDF HTML |
Contents
1. Intro / 简介
There are many situations that we need to generate reports in PDF. And there are many solutions.
- Use libraries to manipulate pdf content. That suffers.
- Use html and then render it to PDF. That's wonderful.
I think everyone like the last solution since you can always have a preview.
And to render html to PDF, there are many solutions.
But the most popular ones are phantomjs
and wkhtmltopdf
.
I thought phantomjs
would be more popular if it's still under maintenance.
Unfortunately, it doesn't. So the lack of some necessary features makes wkhtmltopdf
an appearing alternative.
In my project, I used to generate PDF using phantomjs
. One day a customer asked for a menu.
And I found it unable to fulfill.
And then I began to use wkhtmltopdf
.
However, there are some problems both. And I'll explain.
很多情况下, 我们需要产生PDF的报告。 因此也有很多现存的解决方案:
- 可以使用 PDF 库 去操作PDF 的内容, 那会非常的痛苦
- 使用HTML 然后 渲染为PDF, 那会很好
我想每个人都改会喜欢后一种解决方案, 因为你永远可以有一个预览。
至于 将 HTML 转换为 PDF, 现在也有很多的解决方案。 最流行的就是 phantomjs
和 wkhtmltopdf
。
我想 phantomjs
会更加的流行 如果 它还在被维护的话。 可惜的是,他被停止维护了。 一些基础必要功能的缺少使得 wkhtmltopdf
称为很好的选择。
我曾使用 phantomjs
产生 PDF,但是后来客户需要 一个 目录,phantomjs
不具备这样的功能。因为我转向了 wkhtmltopdf
。
2. Sample Files / 示例文件
To compare the render results of the two Scriptable Headless Browser. We will use the following samples.
为了对比渲染结果,我们使用以下的示例。
./resources/
├── DailyReport.js
├── DailyReport1x.html
├── DailyReport3x.html
├── Report.css
├── echarts.common.min.js
├── jquery-1.12.4.min.js
└── jquery-3.3.1.min.js
DailyReport1x.html
(We render a link and a chart / 我们渲染一个 连接 和 图表):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Daily Report</title>
<link rel="stylesheet" href="Report.css">
<script src="jquery-1.12.4.min.js"></script>
<script src="echarts.common.min.js"></script>
<script src="DailyReport.js"></script>
</head>
<body>
<div class="page">
<div>
<h2>Menu</h2>
<div class="chartDiv">
<a href="#chartDiv">● Test Internal Link</a>
<br />
</div>
</div>
</div>
<div class="page">
<div style="height:100mm; width:180mm;" id="chartDiv">
</div>
</div>
</body>
</html>
DailyReport.js
:
function drawPie() {
var chartOptions = {
animation: false,
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
}]
};
var myChart = echarts.init(document.getElementById('chartDiv'));
myChart.setOption(chartOptions);
};
$(document).ready(function() {
drawPie();
});
Report.css
:
@page {
size: A4;
margin: 0;
}
@media print {
body {
background: white;
}
.page {
width: 210mm;
page-break-after: always;
}
}
html, body {
background: rgb(241,241,241);
}
.page {
page-break-after: always;
width: 210mm;
background: white;
}
And preview in Chrome / 在Chrome 中预览:
Print Preview / Chrome 打印预览:
3. phantomjs
3.1. Installation / 安装
Download from its Phantomjs Official Site and move it to /usr/bin/
.
The latest and end version is 2.1.1
.
从官网去下载,移动到 /usr/bin/
. 最新也是最后的版本是 2.1.1
.
3.2. Usage in JS / JS 调用
We use html-pdf
because it is simple.
使用 html-pdf
, 因为这样很简单。
npm install html-pdf
const fs = require('fs');
const phantomjs = require('html-pdf');
const SrcPath1x =
'/Users/BrianShen/Code/Asia/tset/Node/testPDF/resources/DailyReport1x.html';
const BasePath =
'file:///Users/BrianShen/Code/Asia/tset/Node/testPDF/resources/';
const OutPath = 'test.pdf';
const genPDFPahntomJS = function(srcPath, basePath, outPath) {
return new Promise( (resolve, reject) => {
fs.readFile(srcPath, 'UTF8', function(errfs, html) {
if (errfs) {
reject(errfs);
return;
}
const genOptions = {
height: '297mm',
width: '210mm',
base: basePath,
type: 'pdf',
border: {
top: '12mm', // default is 0, units: mm, cm, in, px
right: '0',
bottom: '0',
left: '0',
},
paginationOffset: 1, // Override the initial pagination number
footer: {
height: '20mm',
contents: {
first: ' ',
default:'{{page}}/{{pages}}',
},
},
};
phantomjs.create(html, genOptions).toFile(outPath, (err, res) => {
if (err) {
reject(err);
return;
}
resolve(res);
});
});
});
}
genPDFPahntomJS(SrcPath1x, BasePath, OutPath).then( () => {
console.log('Success');
}).catch( (err) => {
console.log(err);
})
let's see the result / 看看结果:
Very strange, isn't it?
phantomjs 2.1.1
has a very strange bug, that the size of printed page is only 75% as big as the standard page. For example, for A4 with 29.7*21. When we design our html, we can only use (29.7 * 0.75) * (21 * 0.75) .
And the fatal fault is that the internal hyperlink in the PDF cannot be clicked!
非常的奇怪。 phantomjs 2.1.1
有一个非常奇怪的bug,大的A4 是平常 A4 的 75% 。 这就意味着你在设计html 的时候要按照75%的比例进行设计.
4. wkhtmltopdf
4.1. Installation / 安装
OK, still download from wkhtmltopdf Official Site and install.
仍然从 官网下载并且安装 .
4.2. Usage in Command Line / 命令行使用
Quite simple. I just like it because it is simple and easy to begin and test.
非常简单,能让你快速的开始。
# wkhtmltopdf --debug-javascript --print-media-type --page-size A4 ./resources/DailyReport1x.html test.pdf
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
And let's see the result: / 看一看结果。
Everything works fine!
符合预期。
4.3. Usage in JS / JS 使用
I use wkhtmltopdf
as it is very popular.
我使用的是 wkhtmltopdf
。
npm install wkhtmltopdf
const wkhtmltopdf = require('wkhtmltopdf');
const SrcPath1x =
'file:///Users/BrianShen/Code/Asia/tset/Node/testPDF/resources/DailyReport1x.html';
const OutPath =
'./test1.pdf';
const genPDFWkHtmlToPdf = function(srcPath, outPath) {
return new Promise( (resolve, reject) => {
wkhtmltopdf(srcPath, {
output: outPath,
printMediaType: true,
footerLeft: '[page] / [toPage]',
}, (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
genPDFWkHtmlToPdf(SrcPath1x, OutPath).then( () => {
console.log('Success');
}).catch( (err) => {
console.log(err);
})
The result / 结果:
It's as same as we rendered with command line!
Besides, the internal hyperlink works!
与 命令行中 渲染的结果是一样的。此外,内部的连接是可以使用的。
5. Other Tips / 其他
5.1. Charts can be partial / 图表显示不全
ECharts will use animation by default, so disable it.
ECharts 会自动的开启 动画效果。会导致延时。关闭即可。
animation: false,
5.2. jQuery 3.3.1 is not fully compatible with wkhtmltopdf
jQuery 3.3.1 与 wkhtmltopdf 不兼容。
Let's see a sample:
看个例子。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Daily Report</title>
<link rel="stylesheet" href="Report.css">
<script src="jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="page">
<div>
<h2>Menu</h2>
<div class="chartDiv">
<a href="#chartDiv">● Test Internal Link</a>
<br />
</div>
</div>
<div>
<table id="tableDiv">
<tr><td>Test</td><td>Title</td></tr>
</table>
</div>
</div>
</body>
<script>
$(document).ready(function(){
$('#tableDiv').append('<tr><td>Row1</td><td>Row1</td></tr>');
})
</script>
</html>
When we open it in Chrome:
从Chrome 中打开。
And render it with wkhtmltopdf
:
使用 wkhtmltopdf
去渲染。
#wkhtmltopdf --debug-javascript --print-media-type --page-size A4 --footer-left "Page [page] of [toPage]" ./resources/DailyReport3x.html test.pdf
Loading pages (1/6)
Warning: file:///Users/BrianShen/Code/Asia/tset/Node/testPDF/resources/jquery-3.3.1.min.js:2 jQuery.Deferred exception: 'undefined' is not a function
Warning: undefined:0 TypeError: 'undefined' is not a function
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
It can't work.
But when we switch to 1.12.4 and render it, everything works fine.
发生了错误。但是我们切换到 1.12.4 的 jQuery, 一切工作正常。
Sad thing is that jQuery 1.12.4 is not under maintenance anymore.
遗憾的是,jQuery 1.12.4 不在维护了。
Comments !