如何使用JsPDF在导出的PDF中嵌入任意二进制附件?
问题描述
我正在使用jsPDF将HTML内容导出为可下载的PDF,以下是基础功能的示例代码:
import React from "react"; import { render } from "react-dom"; import { renderToString } from "react-dom/server"; import Hello from "./Hello"; import jsPDF from "jspdf"; const styles = { fontFamily: "sans-serif", textAlign: "center" }; const colstyle = { width: "30%" }; const tableStyle = { width: "100%" }; const Prints = () => ( <div> <h3>Time & Materials Statement of Work (SOW)</h3> <h4>General Information</h4> <table id="tab_customers" class="table table-striped" style={tableStyle}> <colgroup> <col span="1" style={colstyle} /> <col span="1" style={colstyle} /> </colgroup> <thead> <tr class="warning"> <th>SOW Creation Date</th> <th>SOW Start Date</th> <th>Project</th> <th>Last Updated</th> <th>SOW End Date</th> </tr> </thead> <tbody> <tr> <td>Dec 13, 2017</td> <td>Jan 1, 2018</td> <td>NM Connect - NMETNMCM</td> <td>Dec 13, 2017</td> <td>Dec 31, 2018</td> </tr> </tbody> </table> <p> This is a Time and Materials Statement of Work between Northwestern Mutual Life Insurance Company and Infosys with all general terms and conditions as described in the current Master Agreement and its related documents </p> </div> ); const print = () => { const string = renderToString(<Prints />); const pdf = new jsPDF("p", "mm", "a4"); pdf.fromHTML(string); pdf.save("pdf"); }; const App = () => ( <div style={styles}> <Hello name="CodeSandbox" /> <h2>Start editing to see some magic happen {"\u2728"}</h2> <button onClick={print}>print</button> </div> ); render(<App />, document.getElementById("root"));
我的实际场景比示例复杂,用户上传的附件存在MySQL数据库,可通过API获取二进制数据。现在想在导出PDF时,把这些附件像Word里那样嵌入进去,双击就能打开对应文件。请问能不能实现?如果可以,具体怎么操作?我没找到相关示例。另外,我知道怎么给PDF加图片,能不能通过类似的方法支持任意文件类型?
解决方案
一、能否实现嵌入可双击打开的附件?
可以实现,但jsPDF核心库本身不直接支持该功能,需要借助插件或直接操作PDF底层结构(PDF规范原生支持嵌入文件附件)。
二、具体实现步骤
方法1:使用jspdf-addons插件快速嵌入
- 安装插件:
npm install jspdf-addons
- 核心实现代码:
import jsPDF from 'jspdf'; import 'jspdf-addons'; // 封装嵌入附件的函数 const embedAttachment = async (pdf, fileData, fileName) => { // 将API获取的二进制数据转为Uint8Array格式 const uint8Array = new Uint8Array(fileData); // 调用插件方法嵌入附件 pdf.addFileAttachment(fileName, uint8Array); }; // 在导出PDF的函数中调用 const print = async () => { const string = renderToString(<Prints />); const pdf = new jsPDF("p", "mm", "a4"); pdf.fromHTML(string); // 从API获取附件二进制数据 const attachmentRes = await fetch('/api/get-attachment'); const fileBlob = await attachmentRes.blob(); const fileBuffer = await fileBlob.arrayBuffer(); const fileName = 'project-attachment.docx'; // 替换为实际文件名 // 嵌入附件 await embedAttachment(pdf, fileBuffer, fileName); pdf.save("sow-with-attachments.pdf"); };
方法2:手动操作PDF结构(无需插件)
如果插件适配有问题,可以直接基于PDF规范手动添加文件附件注释:
const embedAttachmentManual = (pdf, fileData, fileName, mimeType) => { const doc = pdf.internal; // 创建嵌入式文件对象 const fileRef = doc.newObject(); doc._body.push(` ${fileRef} 0 obj << /Type /EmbeddedFile /Subtype /${mimeType.split('/')[1]} /Length ${fileData.length} >> stream ${String.fromCharCode(...new Uint8Array(fileData))} endstream endobj `); // 创建文件附件注释(显示为页面上的图标) const annotRef = doc.newObject(); const pageNum = 1; // 指定附件图标显示在第一页 doc._body.push(` ${annotRef} 0 obj << /Type /Annot /Subtype /FileAttachment /Rect [100 100 120 120] // 图标位置:x1,y1,x2,y2 /Contents (${fileName}) /FS << /Type /Filespec /F (${fileName}) /EF << /F ${fileRef} 0 R >> >> /Name /Paperclip // 图标样式:Paperclip/Tag/GraphPushPin等 >> endobj `); // 将注释关联到目标页面 const page = doc.pages[pageNum - 1]; doc._out(`/Annots [${annotRef} 0 R]`, page); };
调用方式和方法1一致,需传入二进制数据、文件名和文件的MIME类型(如application/pdf、application/vnd.openxmlformats-officedocument.wordprocessingml.document)。
三、能否扩展图片方法支持任意文件?
不行。图片是PDF原生支持的页面渲染内容,直接绘制在PDF页面上;而任意文件是作为嵌入式附件存储,不属于页面渲染层,两者的PDF结构完全不同。图片方法只能处理图像类数据,无法扩展为嵌入任意文件,必须使用上述的附件嵌入方案。
注意事项
- 大部分主流PDF阅读器(如Adobe Acrobat、Chrome浏览器)都支持双击打开附件,但少数轻量阅读器可能不兼容;
- 嵌入大文件会导致PDF体积急剧增大,建议限制附件大小;
- 确保从API获取的二进制数据格式正确(需转为Uint8Array或ArrayBuffer)。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

