如何导出PDF

101 阅读4分钟

1. 背景

开发过程中,有时候会希望将页面的内容导出为pdf,第一反应是使用jsPDFhtml2canvas结合,然后浏览器直接生成PDF,但通过这种方式生成的pdf会有很多问题。比如

  1. 字体缺失
  2. pdf页眉页脚只能实现非常简单的配置
  3. 内容被裁减,需要做大量的js处理才能实现
  4. 跨域资源加载失败
  5. pdf清晰度极差,样式与页面中的样式差别较大

那么有没有更好用的方式能够规避这些问题,那就是pdfmake

2. 介绍

Pdfmake 是一个方便的 JavaScript 库,既免费又开源,是简化在 Web 应用程序中创建 PDF 文档过程的绝佳工具。您可以通过定义文本、图像、表格等来声明 PDF 文档结构,并应用样式,pdfmake 将管理其余部分以创建具有所需视觉样式的 PDF

以下是 pdfmake 的一些主要功能:

  • 创建 PDF: Pdfmake 使您能够从结构化数据创建 PDF 文档,从而轻松生成报告、发票、表格和其他类型的文档。
  • 添加表格:Pdfmake 使您能够轻松地设计和将表格插入到您的 PDF 中,从而显示表格数据并保持结构化布局简单。
  • 添加图像: Pdfmake 允许在您的 PDF 文档中包含图像,从而可以合并徽标、图形或照片以增强视觉吸引力。
  • 添加密码: Pdfmake 支持为您的 PDF 添加密码保护,通过要求输入文档访问密码来帮助保护敏感信息。

3. 使用

1. 安装

yarn add pdfmake html-to-pdfmake # html-to-pdfmake用于将未知的html转化为pdf,使用场景为富文本编辑器
yarn add @types/html-to-pdfmake @types/pdfmake -D

2. 引用

import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
import htmlToPdfmake from 'html-to-pdfmake';

3. 定义pdf文档内容

const documentDefinition = {
    pageSize: "LETTER", // 页面大小为 Letter 
    pageOrientation: "landscape", // 横向页面 
    pageMargins: [20, 35, 20, 15], // [left, top, right, bottom] 边距 
    header: { 
        columns: [ 
            "模拟左页眉", 
            { 
                text: "页眉", 
                alignment: "center", // 文本位置 
                fontSize: 10, // 文本大小 
                color: "orange", // 文本颜色 
                margin: [0, 15, 0, 0] 
            }, 
            { 
                text: "右页眉", 
                alignment: "right", 
                bold: true, // 加粗 
            }
        ], 
    }, 
    footer: { 
        columns: [ 
            "模拟左页脚", 
            { 
                text: "页脚", 
                alignment: "center", // 文本位置 fontSize: 10, // 文本大小 
                color: "orange", // 文本颜色 
                margin: [0, 15, 0, 0] }, // 边距 
                { 
                    text: "右页脚", 
                    alignment: "right", 
                    bold: true, // 加粗 
                }, 
            ], 
    }, 
    content: [ // pdf内容 
        { table: // table使用 
            { 
                widths: [553, 180], 
                style: "titleCellStyle", 
                body: [ 
                    { 
                        columns: [ 
                            { svg: "svg", // 设置icon,页面中如果有全选的选中/未选中状态,也可使用svg实现 }, 
                            { 
                                margin: [15, 10, 5, 10], 
                                stack: [ // 将text转化为多行,同时可对每一行单独设置样式 
                                    {
                                        text: "主标题", 
                                        style: "titleCellStyle"
                                    }, 
                                    { 
                                        text: "子标题",
                                        style: "titleCellStyle", // 与配置中styles关联 
                                    }, 
                                ], 
                            } 
                        ] 
                    } 
                ] 
            } 
        } 
    ], 
    styles: { // 设置统一的样式,content处通过style引用 
        titleCellStyle: { 
            lineHeight: 1.5, 
            bold: true, 
        } 
    }, 
    background: [ // 设置水印,如果需要设置多水印,数组里可添加多个,改变坐标即可 
        { 
            work: { 
                text: "水印", 
                fontSize: 30, 
                bold: true, 
                color: "blue", 
                angle: -45, // 倾斜度 
                opacity: 0.3, // 透明度 
                italics: true, // 斜体 
            }, 
            absolutePosition: { // 水印坐标
                x: 100, 
                y: 100 
            }  
        } 
    ] 
};

4. 富文本处理

以上的文档只能实现已知的内容,对于富文本中的内容则需要通过html-to-pdfmake实现

const html = htmlToPdfmake(HTMLElement, {
   imagesByReference: true, // 图片
   tableAutoSize: true, // 表格缩紧
   ignoreStyles: ['font-family'], // 忽略样式
   defaultStyles: {
     h1: { // 设置h1标题对应的字体,其他标签对应的字体也可以在这设置
         fontSize: 18, 
         bold: true,
         marginBottom: 5 
     } 
  }
});

html中包括contentimages两部分内容,其中item.content可通过stack渲染,内容追加到content

{
    stack: item.content
} 

注意:pdf中的图片需要在image声明之后才可以引用, 意味着此时富文本中的images是无法直接显示的,需要在文档中声明之后才可以显示

// 将图片数组转为对象
for (const key in item.images) {
    imagesObj[key] = item.images[key];
}
// 将图片映射到页面文档中       
images: imagesObj,

注意:富文本图片显示成功之后,富文本中本来图片在一行的,存在换行,是因为图片会被每个stack包裹。

5. 完整的文档结构

{

    pageSize: '',

    pageOrientation: '',

    pageMargins: [],

    header: {
        ...
    },
    
    footer: {
        ...
    },
    
    content: [...],
    
    styles: {
        ...
    },
    
    background: [...]
}
## 6. 导出pdf

```javascript
pdfMake.vfs = (pdfFonts as any).pdfMake.vfs; // 设置字体

const pdfDocGenerator = pdfMake.createPdf(documentDefinition); //创建 PDF 文档 

pdfDocGenerator.download('filename.pdf'); // 下载 PDF 文件

pdfMake.createPdf(docDefinition).open(); // 在新窗口打开生成的PDF文件

pdfMake.createPdf(docDefinition).print(); // 直接调用浏览器打印功能打印