Electron PrintToPDFOptions 完全指南:用 webContents.printToPDF() 将网页打印为 PDF
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
Electron 的printToPDF()是主进程中把渲染进程当前网页转换为 PDF 二进制数据的核心能力,其全部行为由PrintToPDFOptions对象驱动,从纸张大小、页边距到页眉页脚模板均可精细控制。本文以 PrintToPDFOptions 结构文档 为主体,逐条讲解每个选项的类型、默认值与语义,并结合 lib/browser/print-to-pdf.ts 的源码实现与 spec/api-web-contents-spec.ts 中的测试用例,说明选项如何被校验、转换并下发给 Chromium 打印管线,以及并发调用时的队列保护机制。
API 入口:printToPDF(options) 的调用位置
PrintToPDFOptions是以下两个 API 的入参结构,两者共享同一份选项翻译与任务排队逻辑(见 lib/browser/print-to-pdf.ts 顶部注释):
contents.printToPDF(options):定义在 WebContents 文档 的 printToPDF 小节,通过 lib/browser/api/web-contents.ts 挂载到原型上;frame.printToPDF(options):定义在 WebFrameMain 文档,实现见 lib/browser/api/web-frame-main.ts。
返回值均为Promise<Buffer>,resolve 的值即完整的 PDF 文件数据,可直接fs.writeFile落盘。官方文档中的最小示例:
const { app, BrowserWindow } = require('electron') const fs = require('node:fs') const os = require('node:os') const path = require('node:path') app.whenReady().then(() => { const win = new BrowserWindow() win.loadURL('https://example.com') win.webContents.on('did-finish-load', () => { // 使用默认打印选项 const pdfPath = path.join(os.homedir(), 'Desktop', 'temp.pdf') win.webContents.printToPDF({}).then(data => { fs.writeFile(pdfPath, data, (error) => { if (error) throw error console.log(`Wrote PDF successfully to ${pdfPath}`) }) }).catch(error => { console.log(`Failed to write PDF to ${pdfPath}: `, error) }) }) })一个需要注意的文档声明:如果网页使用了@pageCSS at-rule,landscape选项会被忽略。也就是说 CSS 的页面方向优先级高于该布尔选项,这在给已有打印样式的设计稿生成 PDF 时要特别留意。
PrintToPDFOptions 属性总览
以下表格完整继承自 print-to-pdf-options.md,默认值与类型校验均以源码为准:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
landscape | boolean | false | 纸张方向,true横向,false纵向 |
displayHeaderFooter | boolean | false | 是否显示页眉和页脚 |
printBackground | boolean | false | 是否打印背景图形 |
scale | number | 1 | 网页渲染缩放比例 |
pageSize | string | Size | 'Letter' | 指定生成 PDF 的纸张大小,取A0–A6、Legal、Letter、Tabloid、Ledger之一,或包含width/height(单位:英寸)的对象 |
margins | PrintToPDFMargins | {}(各边 0.4 英寸) | 页边距,单位英寸 |
pageRanges | string | ''(打印全部页面) | 要打印的页码范围,如'1-5, 8, 11-13' |
headerTemplate | string | '' | 页眉 HTML 模板 |
footerTemplate | string | '' | 页脚 HTML 模板,格式同headerTemplate |
preferCSSPageSize | boolean | false | 是否优先采用 CSS 定义的页面大小;为false时内容会被缩放以适配纸张 |
generateTaggedPDF | boolean(实验性) | false | 是否生成带标记(可无障碍访问)的 PDF |
generateDocumentOutline | boolean(实验性) | false | 是否根据内容标题生成 PDF 文档大纲 |
默认值并非凭空而来,lib/browser/print-to-pdf.ts 中的printSettings构造段逐项写明了回退链:landscape ?? false、displayHeaderFooter ?? false、headerTemplate ?? ''、scale ?? 1.0、pageRanges ?? ''、四个边距统一?? 0.4,再叠加parsePageSize(options.pageSize ?? 'letter')的结果。
源码还有一层文档未强调的防御:每个选项都会经过checkType(L20-L27)做运行时类型断言,类型不符会抛出TypeError,例如margins must be an object。测试文件 spec/api-web-contents-spec.ts 对此有专门用例,逐项传入landscape: []、scale: 'not-a-number'、pageSize: 'IAmAPageSize'、margins: 'terrible'等错误类型并断言全部被 reject——用例注释解释了动机:“These will hard crash in Chromium unless we type-check”,即不校验会让 Chromium 直接崩溃,Electron 层的前置类型检查把崩溃转化为可捕获的 Promise rejection。
pageSize:纸张大小的解析与全部取值
pageSize是约束最多的选项。parsePageSize 定义了三种情况:
字符串形式:按小写匹配内置纸张表
paperFormats(L6-L18),命中则展开为paperWidth/paperHeight(英寸),未命中抛Invalid pageSize ${pageSize}错误:格式 宽(英寸) 高(英寸) Letter8.5 11 Legal8.5 14 Tabloid11 17 Ledger17 11 A033.1 46.8 A123.4 33.1 A216.54 23.4 A311.7 16.54 A48.27 11.7 A55.83 8.27 A64.13 5.83 对象形式:
{ width, height }两个字段都必须是 number,否则抛TypeError: width and height properties are required for pageSize;其他类型:抛
TypeError: pageSize must be a string or an object。
测试 spec/api-web-contents-spec.ts 的with custom page sizes用例对上述全部 11 种格式逐一生成 PDF,并用 pdf.js 解析页面view数组([top, left, width, height],单位 PDF point)除以 72 换算回英寸,断言与上表数值在 0.01 英寸误差内一致——这是“选项表”与“实际输出”之间的端到端核对。
margins:PrintToPDFMargins 与越界校验
margins 结构 包含四个可选字段,单位均为英寸:
topnumber(可选)- 顶部边距,默认 1cm(约 0.4 英寸)bottomnumber(可选)- 底部边距,默认同上leftnumber(可选)- 左侧边距,默认同上rightnumber(可选)- 右侧边距,默认同上
除了类型检查,lib/browser/print-to-pdf.ts 还做物理合理性校验:top + bottom方向上要求top和bottom各自不超过纸张高度,left/right各自不超过纸张宽度,任一违反即抛margins must be less than or equal to pageSize。测试 rejects when margins exceed physical page size 用pageSize: 'Letter'配top: 100, bottom: 100精确复现了这条错误信息。
// A4 纵向 + 自定义边距 + 页眉页脚 + 背景打印 const data = await win.webContents.printToPDF({ pageSize: 'A4', landscape: false, printBackground: true, scale: 1, margins: { top: 0.5, bottom: 0.5, left: 0.6, right: 0.6 }, displayHeaderFooter: true, headerTemplate: '<span class="title"></span>', footerTemplate: '第 <span class="pageNumber"></span> 页 / 共 <span class="totalPages"></span> 页' })pageRanges:页码范围筛选
pageRanges为字符串,格式形如'1-5, 8, 11-13',默认空字符串表示打印全部页面。两点行为值得注意,均有测试佐证:
- 无效范围会 reject:传入
pageRanges: '999'(超出实际页数)的调用会失败(L4599); - 失败后可恢复:recovers after a prior call fails with an invalid page range 用例先触发失败,再立即发起一次
printToPDF({}),断言后续调用正常返回单页 PDF——说明一次失败不会污染底层打印状态; - 范围数量正确:对多页 fixture(
spec/fixtures/api/print-to-pdf-large.html)传入pageRanges: '1-3'后,解析出的numPages恰为 3(L4582-L4594)。
headerTemplate / footerTemplate:页眉页脚模板
模板生效的前置条件是displayHeaderFooter: true。两者均为合法 HTML 片段,通过特定 class 名注入打印时的动态值:
| class | 注入内容 |
|---|---|
date | 格式化后的打印日期 |
title | 文档标题 |
url | 文档位置(URL) |
pageNumber | 当前页码 |
totalPages | 文档总页数 |
例如<span class=title></span>会被替换为包含文档标题的 span。测试 with custom header and footer 传入"<div>I'm a PDF header</div>"与"<div>I'm a PDF footer</div>",再用 pdf.js 提取全文,断言两段文本都出现在 PDF 内容流中。
方向、背景、缩放与 CSS 页面大小
landscape: true使输出宽高反转。in landscape mode 用例直接断言width > height。再次提醒:若页面 CSS 定义了@page规则,此选项会被忽略。printBackground: true才输出背景色/背景图,这与浏览器打印对话框的“背景图形”复选框语义一致。scale是网页渲染缩放比例,默认 1,必须是 number(scale: 'not-a-number'会被 reject)。preferCSSPageSize默认false,此时内容会被缩放以适配纸张;设为true时优先采用 CSS 定义的页面大小。该选项主要服务@page已定义尺寸/边距的打印友好页面。
实验性选项:generateTaggedPDF 与 generateDocumentOutline
文档将两者标记为Experimental:
generateTaggedPDF生成带标记(tagged,即可无障碍访问)的 PDF;文档明确提示该属性处于实验阶段,生成的 PDF 可能不完全符合 PDF/UA 与 WCAG 标准;generateDocumentOutline根据内容标题生成 PDF 文档大纲。
测试 can generate tag data for PDFs 验证了启用后的实际产物:pdf.js 读出的markInfo深度等于{ Marked: true, UserProperties: false, Suspects: false };而 does not tag PDFs by default 用例确认默认输出markInfo为 null,即默认 PDF 不带标记结构。
源码级执行流程:从选项校验到打印任务排队
printToPDF() 的完整执行链如下:
- 类型断言:
checkType逐个校验各选项,margins缺省按空对象处理; - 纸张解析:
parsePageSize将字符串或对象统一展开为paperWidth/paperHeight; - 边距越界检查:四边分别不得超过对应方向的纸张尺寸;
- 构造 printSettings:按默认值回填后附加自增的
requestID(nextRequestId),交给底层绑定target._printToPDF。若该绑定不存在,抛Printing feature is disabled——测试套件正是用features.isPrintingEnabled()守卫整个 printToPDF 测试块(spec/api-web-contents-spec.ts#L4422),说明该能力在构建中可被整体裁剪; - 按帧树排队:同一帧树(frame tree)内的并发 PDF 任务在渲染进程会互相冲突,源码用一个以
frameTreeNodeId为键的Map做串行化(L60-L112):新任务catch(() => {})掉前一个任务的错误后串联执行,完成后清理队列项。选择frameTreeNodeId而非对象身份做键,是因为它跨导航稳定;mainFrame/top在窗口销毁过程中可能为 null,此时统一落入-1桶。不同webContents之间的打印可以并行。
对应测试有两条稳定性用例:does not crash when called multiple times in parallel(同一 webContents 同时发起 3 次)与 in sequence,均断言三次返回非空Buffer。
此外,测试覆盖了若干边缘场景,可作为能力边界的可靠依据:
- iframe 打印:同源 iframe 内容可以被打印(L4614-L4620);跨域 iframe 在 Linux 上因 OOPIF 崩溃问题暂被禁用,该用例带平台条件
process.platform !== 'linux'(L4622-L4638); - 直接打印现有 PDF 文件:加载
file://的 PDF 后需先等待-pdf-ready-to-print事件再调用printToPDF({})(L4652-L4661),webview 场景同理。
输出结果的验证方式
测试基础设施 spec/lib/pdf-helpers.ts 展示了官方如何验证printToPDF的产物:把 Buffer 写入临时目录,再 fork 一个 Node 子进程运行spec/fixtures/api/pdf-reader.mjs(基于 pdf.js)解析 PDF,返回页数、页面view、markInfo与文本内容等 JSON 信息。这个“Buffer → pdf.js 解析 → 断言元数据”的模式可以直接搬到自己的集成测试里:
const data = await w.webContents.printToPDF({ pageSize: 'A4' }) // data 是 Buffer,先落盘再用任意 PDF 解析器核对 // 页面尺寸 = view[2] / 72 英寸,标记状态看 markInfo小结
PrintToPDFOptions的十二个属性在 print-to-pdf-options.md 中给出了完整契约,而 lib/browser/print-to-pdf.ts 保证了这份契约在运行期被严格执行:类型错误被转化为可捕获的异常、纸张大小被解析为英寸尺寸、页边距被限制在纸张物理范围内、并发任务被按帧树串行化。开发者只需按“纸张 → 边距 → 页眉页脚 → 范围筛选”的顺序配置选项,即可获得一份可复现、可测试的 PDF 输出;对结果质量有疑问时,参照 spec/lib/pdf-helpers.ts 的 pdf.js 解析方式即可对输出做量化断言。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考