使用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