likes
comments
collection
share

使用PDFMAKE库将HTML导出为PDF文档

作者站长头像
站长
· 阅读数 49

准备工作

pdfmake@0.2.7 文档地址 思源黑体字体文件 文档地址

字体文件选择尽量规避版权问题,选择思源黑体这一款开源字体。

开始使用

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 配合着用 非常省心使用PDFMAKE库将HTML导出为PDF文档 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
评论
请登录