使用PDFMAKE库将HTML导出为PDF文档
准备工作
字体文件选择尽量规避版权问题,选择思源黑体这一款开源字体。
开始使用
pdfmake
支持浏览器和node服务端生成,本文档主要是在浏览器端进行开发。在pdfmake库生成种,所有字号,边距这些单位都是使用的pt
进行计算,在html种常用的px
单位可以通过 px * 0.75
换算为 pt
单位值。原生不支持中文,所以使用之前还需要设置带中文的字体。
库的引入
在工程化开发项目种,可以使用 import pdfMake from 'pdfmake/build/pdfmake';
进行引入库。
在非工程化 单个HTML端,可以使用 <script src="pdfmake.js"></script>
进行引用。
调用的时候就可以通过 pdfMake方法进行使用。
浏览器端的常用方法
docDefinition
为一个配置参数对象
生成PDF实例
const instance = pdfMake.createPdf(docDefinition)
;
直接下载PDF文件
pdfMake.createPdf(docDefinition).download();
在新窗口打开生成的PDF文件
pdfMake.createPdf(docDefinition).open();
直接调用浏览器打印功能打印
pdfMake.createPdf(docDefinition).print();
转换PDF为 URLdata 展示到iframe中
pdfMake.createPdf(docDefinition).getDataUrl((dataUrl) => {
const targetElement = document.querySelector('#iframeContainer');
const iframe = document.createElement('iframe');
iframe.src = dataUrl;
targetElement.appendChild(iframe);
})
获取PDF的buffer对象或者blob对象(用于直接上传)
pdfMake.createPdf(docDefinition).getBuffer((buffer) => {
// ...
})
pdfMake.createPdf(docDefinition).getBlob((blob) => {
// ...
});
文档字体设置
字体导入可以使用 VIRTUAL FILE SYSTEM(VFS)或者 URL PROTOCOL 两种方式进行,VFS方式设置字体不这么方便,可以去官方文档进行查阅。 这里只展示了 通过URL方式的字体设置。
pdfMake.fonts = {
"SourceHanSans.ttf": {
normal: 'https://xxxx.com/pdffonts/SourceHanSansCN-Regular.otf',
bold: 'https://xxxx.com/pdffonts/SourceHanSansCN-Bold.otf',
italics: 'https://xxxx.com/pdffonts/SourceHanSansCN-Regular.otf',
bolditalics: 'https://xxxx.com/pdffonts/SourceHanSansCN-Bold.otf'
}
}
可以看到,需要提前将 普通,加粗,斜体,粗斜体等字体提前进行设置,同时也支持多种字体设置。
文档生成配置 docDefinition
下面展示一个基础配置 注意fontSize margin等设置使用单位为 pt
。
margin 的设置 [20, 30, 20, 30] 从左边开始,顺时针进行计算,即为 [左,上,右,下]
标准A4大小842×595,以pt计算
const docDefinition = {
pageBreakBefore: false,
content: [
{
text:"这是一个标题",
style:'head'
}
"helloworld",
{
text:"this is pdfmake",
fontSize: 20,
color: '#223399'
},
{
text: "center text",
alignment: 'center'
},
{
image: 'logo',
width: 100,
height: 100
}
],
// 默认字体,默认的样式,可以在这里设置全局样式
defaultStyle: {
font: 'SourceHanSans.ttf',
fontSize: 9
},
// 内容边距,不设置就会使用默认值
pageMargins: [20, 30, 20, 30],
footer: function (currentPage, pageCount) {
return [
{ text: currentPage.toString() + ' / ' + pageCount, alignment: 'center' }
]
},
// 图片引用
images:{
logo:'https://xxxxx.com/logo.png'
},
// styles 就像是css样式一样 可以在content内元素引用的
styles: {
head: {
margin: [0, 5, 0, 15],
fontSize: 24,
color:'#000'
}
}
}
关于样式设置的一些属性
font
字体名称的字符串,对应标签样式fontFamily
fontSize
字体大小,以pt为单位。对应标签样式fontSize
fontFeatures
字体特征,取决于字体支持 是一个数组类型,里面为特征的字符串
lineHeight
行高,以pt为单位。对应标签样式lineHeight
bold
字体加粗,对应标签样式fontWeight
,一般500往上为加粗
italics
斜体,可以读取行内样式fontStyle:italic
alignment
对应标签样式 textAlign
可选left
,center
,right
,justify
characterSpacing
对应标签样式letterSpacing
,以pt为单位
color
对应标签样式color
background
对应标签样式 backgroundColor
,需要设置成hex颜色
markerColor
列表标签的颜色
decoration
对应标签样式 textDecoration
,可选 underline
,lineThrough
,overline
decorationStyle
对应标签样式 textDecorationStyle
,可选 dashed
,dotted
,double
,wavy
decorationColor
对应标签样式 textDecorationColor
,需要设置成hex颜色
对于块元素,需要设置成stack
,table元素设置成table
等
docDefinition = {
content: [
{
stack:[
'hello',
{text: 'world',fontSize: 20}
]
},
{
color: '#444',
table: {
widths: [200, 'auto', 'auto'],
headerRows: 2,
// keepWithHeaderRows: 1,
body: [
[{text: 'Header with Colspan = 2', style: 'tableHeader', colSpan: 2, alignment: 'center'}, {}, {text: 'Header 3', style: 'tableHeader', alignment: 'center'}],
[{text: 'Header 1', style: 'tableHeader', alignment: 'center'}, {text: 'Header 2', style: 'tableHeader', alignment: 'center'}, {text: 'Header 3', style: 'tableHeader', alignment: 'center'}],
['Sample value 1', 'Sample value 2', 'Sample value 3'],
[{rowSpan: 3, text: 'rowSpan set to 3\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor'}, 'Sample value 2', 'Sample value 3'],
['', 'Sample value 2', 'Sample value 3'],
['Sample value 1', 'Sample value 2', 'Sample value 3'],
['Sample value 1', {colSpan: 2, rowSpan: 2, text: 'Both:\nrowSpan and colSpan\ncan be defined at the same time'}, ''],
['Sample value 1', '', ''],
]
}
},
]
}
如何遍历html节点
有个库叫 html-to-pdfmake 配合着用 非常省心github.com。
开发了一个方法 将HTML文档处理成pdfmake
认识的形式,直接调用possiblyAddNodeToResult
方法。
content 初始的内容,默认给个空数组
node 需要渲染的html节点
attr 默认样式
处理好以后 把content放到 docDefinition.content里面就可以开始生成了
var content = []
/**
* 用来生成PDF渲染的数组对象
* http://pdfmake.org/playground.html 链接里面的生成示例
*/
function possiblyAddNodeToResult(result = [], node, attr = {}) {
if (node.nodeType == 3) {
result.push({text:node.data, ...attr})
}
else if (node.nodeType == 1 && node.tagName == 'TABLE') {
let TableLine = {
style: 'table',
color: '#444',
}
let table = renderTableObject(result, node);
TableLine.table = table;
result.push(TableLine);
}
else if (node.nodeType == 1 && node.tagName == 'IMG') {
let image = renderImgObject(node);
result.push(image)
}
else if (node.nodeType == 1) {
const fontSizeMap = tag => {
let size = 10;
switch (tag) {
case 'H1':
size = 15
break;
case 'H2':
size = 14
break;
case 'H3':
size = 13
break;
case 'H4':
size = 12
break;
default:
size = 10
}
return size
}
let Attrs = {
fontSize: 10,
...attr
}
let Line = {};
let blockTags = ['DIV', 'SECTION', 'ARTICLE','P','H1','H2','H3','H4','H5']
if (blockTags.includes(node.tagName)) {
Line.stack = []
} else {
Line.text = []
}
if (node.tagName == 'H1' || node.tagName == 'H2' || node.tagName == 'H3' || node.tagName == 'H4' || node.tagName == 'H5') {
Attrs.bold = true;
Attrs.fontSize = fontSizeMap(node.tagName)
}
if (node.style.fontWeight == 'bold' || Number(node.style.fontWeight) > 500) {
Attrs.bold = true;
}
if (node.style.textAlign) {
Attrs.alignment = node.style.textAlign
}
if (node.style.color) {
Attrs.color = node.style.color;
}
if (node.style.fontSize) {
Attrs.fontSize = Number(node.style.fontSize.replace('px', '')) * 0.75;
}
for (var i = 0; i < node.childNodes.length; i++) {
possiblyAddNodeToResult(Line.stack || Line.text, node.childNodes[i], Attrs);
}
result.push({ ...Line, ...Attrs });
}
}
/**
* 处理Table 标签
*/
function renderTableObject(result, node) {
let caption = node.querySelectorAll('caption');
caption.forEach(cap => {
possiblyAddNodeToResult(result, cap, { alignment: "center" })
})
let headerRows = node.querySelectorAll('thead tr').length;
let ThCount = Array.from(node.querySelectorAll('thead tr')[0].querySelectorAll('th')).reduce((p, c) => {
p += (c.colSpan ? Number(c.colSpan) : 1);
return p
}, 0);
let widths = [...new Array(ThCount)].map((t, idx) => '*');
let body = [];
let rows = node.querySelectorAll('tr');
rows.forEach(row => {
let rowdata = [];
if (row.children) {
let count = row.children.length;
[...new Array(ThCount - count)].forEach((t) => rowdata.push(''));
Array.from(row.children).forEach((td, idx) => {
let delIdx = -1;
let appendLength = 0;
let data = {
text: td.innerText
}
/**
* 给cell加colSpan属性 并删除这一行数组 前面的空内容,在这个格子后面追加多出来的空
*/
if (td.colSpan > 1) {
data.colSpan = td.colSpan;
delIdx = td.colSpan - 2;
appendLength = td.colSpan - 1;
}
if (td.rowSpan > 1) {
data.rowSpan = td.rowSpan
}
rowdata.push(data);
rowdata = rowdata.filter((_, t) => t > delIdx);
[...new Array(appendLength)].forEach(() => {
rowdata.push('')
})
});
}
body.push(rowdata)
});
return {
widths,
headerRows,
body
}
}
/**
*
* 图片标签处理
*/
function renderImgObject(node) {
let name = Math.random().toString(36).substr(2,5);
images[name] = node.src;
let image = name;
let width = node.width|| 200;
let height = node.height || '';
return {
image,
width,
height
}
}
Demo下载地址cowtransfer.com/s/a8f08b42c…
转载自:https://juejin.cn/post/7226567602373050428