wkhtmltopdf实现分栏
最近项目中遇到的一个需求,由wkhtmltopdf转换生成的PDF需要分栏显示。就像下面这样。
CSS columns
CSS中支持使用columns进行分栏显示。
但是wkhtmltopdf并不支持,详见官方issue#1872, issue#2266。
wkhtmltopdf + javascript
wkhtmltopdf支持在页面加载后执行javascript (–run-script),类似于body的onload。
实现思路:
先把所有内容装在一个半页的div中,假设叫org_div
根据需要创建与每页等高的div, 里面有两个子div
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
26function create_page(is_first) {
var page = document.createElement('div');
var height = 0;
if (is_first) {
height = _A4_HEIGHT - _QUESTION_START_Y - _WK_BOT_MARGIN;
} else {
height = _PAGE_HEIGHT;
}
page.style.height = height + 'px';
page.style.margin = '38px 46px 0px 48px';
var left_part = document.createElement('div');
left_part.style.width = '349px';
left_part.style.height = height + 'px';
left_part.style.borderRight = '1px dashed #808080';
left_part.style.display = 'inline-block';
left_part.style.verticalAlign = 'top';
page.appendChild(left_part);
var right_part = document.createElement('div');
right_part.style.width = '334px';
right_part.style.height = height + 'px';
right_part.style.display = 'inline-block';
right_part.style.verticalAlign = 'top';
right_part.style.marginLeft = '15px';
page.appendChild(right_part);
return page;
}如果是第一页,创建的高度可能有所不同,因为第一页会有标题等其他元素。
依次从org_div中取出子节点,将其放入创建好的新div中,若左边放不下了,就放右边;若右边放不下了,就再create_page(),直到org_div被取空
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
34function magic() {
var org_card = document.getElementById('org-analysis-card');
var page = create_page(true);
org_card.insertAdjacentElement('beforebegin', page);
var remain = page.offsetHeight;
var container = page.firstChild;
var next_container = function () {
if (container == page.firstChild) {
container = page.lastChild;
} else {
page = create_page(false);
org_card.insertAdjacentElement('beforebegin', page);
container = page.firstChild;
}
}
while (org_card.childNodes.length > 0) {
var block = org_card.firstChild;
if (block.offsetHeight <= (remain - 2)) {
org_card.removeChild(block);
container.appendChild(block);
remain = remain - block.offsetHeight - 2 - _QUESTION_SPACING;
} else {
var new_div = cut_element(block, remain - 2);
if (new_div) {
org_card.removeChild(block);
org_card.insertBefore(new_div, org_card.firstChild);
container.appendChild(block);
}
next_container();
remain = page.offsetHeight;
}
}
document.body.removeChild(org_card);
}若某子节点在左栏或右栏放不下事,对子节点进行切割,切割到高度满足为止
切割算法思路:
- 依次将最末尾的子节点移动到另外一个新创建的div中,直到offsetHeight满足条件为止。
- 若子节点还需要切割,递归此逻辑。
- 若子节点已没有子孙,若它是文本,则二分切割到满足为止。
其他替代方案
若使用的是Qt, 可使用QWebEnginePage的printToPdf,由于QWebEngine使用的是chromium,因此直接使用columns css就行。就像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16QWebEnginePage page;
QEventLoop loop;
loop.connect(&page, &QWebEnginePage::loadFinished, [&page, &loop, pdfFileName]() {
page.printToPdf([&loop, &pdfFileName] (QByteArray ba) {
QFile f(pdfFileName);
if (f.open(QIODevice::WriteOnly)) {
f.write(ba);
f.close();
} else {
L2LOG_ERROR(QString("Error opening file for writing %1. %2").arg(pdfFileName, f.errorString()));
}
loop.exit();
});
});
page.load(QUrl::fromLocalFile(htmlFileName));
loop.exec();使用其他第三方工具,例如ChromeHtmlToPdf, cef-pdf