news 2026/9/7 4:19:09

Electron PrintToPDFOptions 完全指南:用 webContents.printToPDF() 将网页打印为 PDF

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Electron PrintToPDFOptions 完全指南:用 webContents.printToPDF() 将网页打印为 PDF

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,默认值与类型校验均以源码为准:

属性类型默认值说明
landscapebooleanfalse纸张方向,true横向,false纵向
displayHeaderFooterbooleanfalse是否显示页眉和页脚
printBackgroundbooleanfalse是否打印背景图形
scalenumber1网页渲染缩放比例
pageSizestring | Size'Letter'指定生成 PDF 的纸张大小,取A0A6LegalLetterTabloidLedger之一,或包含width/height(单位:英寸)的对象
marginsPrintToPDFMargins{}(各边 0.4 英寸)页边距,单位英寸
pageRangesstring''(打印全部页面)要打印的页码范围,如'1-5, 8, 11-13'
headerTemplatestring''页眉 HTML 模板
footerTemplatestring''页脚 HTML 模板,格式同headerTemplate
preferCSSPageSizebooleanfalse是否优先采用 CSS 定义的页面大小;为false时内容会被缩放以适配纸张
generateTaggedPDFboolean(实验性)false是否生成带标记(可无障碍访问)的 PDF
generateDocumentOutlineboolean(实验性)false是否根据内容标题生成 PDF 文档大纲

默认值并非凭空而来,lib/browser/print-to-pdf.ts 中的printSettings构造段逐项写明了回退链:landscape ?? falsedisplayHeaderFooter ?? falseheaderTemplate ?? ''scale ?? 1.0pageRanges ?? ''、四个边距统一?? 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 定义了三种情况:

  1. 字符串形式:按小写匹配内置纸张表paperFormats(L6-L18),命中则展开为paperWidth/paperHeight(英寸),未命中抛Invalid pageSize ${pageSize}错误:

    格式宽(英寸)高(英寸)
    Letter8.511
    Legal8.514
    Tabloid1117
    Ledger1711
    A033.146.8
    A123.433.1
    A216.5423.4
    A311.716.54
    A48.2711.7
    A55.838.27
    A64.135.83
  2. 对象形式{ width, height }两个字段都必须是 number,否则抛TypeError: width and height properties are required for pageSize

  3. 其他类型:抛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方向上要求topbottom各自不超过纸张高度,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() 的完整执行链如下:

  1. 类型断言checkType逐个校验各选项,margins缺省按空对象处理;
  2. 纸张解析parsePageSize将字符串或对象统一展开为paperWidth/paperHeight
  3. 边距越界检查:四边分别不得超过对应方向的纸张尺寸;
  4. 构造 printSettings:按默认值回填后附加自增的requestIDnextRequestId),交给底层绑定target._printToPDF。若该绑定不存在,抛Printing feature is disabled——测试套件正是用features.isPrintingEnabled()守卫整个 printToPDF 测试块(spec/api-web-contents-spec.ts#L4422),说明该能力在构建中可被整体裁剪;
  5. 按帧树排队:同一帧树(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,返回页数、页面viewmarkInfo与文本内容等 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 4:18:51

YOLOv8结构拆解与改进实战:从数据诊断到消融实验

做毕业设计选 YOLOv8&#xff0c;是目前很多同学的目标检测标配。但一个常见的现象是&#xff1a;代码下载很顺利&#xff0c;训练完一看 mAP&#xff0c;效果并不理想。于是很多人开始在网上搜索各种改进模块&#xff0c;注意力机制、小目标检测头、BiFPN、新损失函数……一样…

作者头像 李华
网站建设 2026/9/7 4:16:38

精读Mask2Former:掩码注意力如何统一语义、实例与全景分割

分割这个方向&#xff0c;论文多到什么程度呢&#xff1f;光是用关键词去搜&#xff0c;语义分割、实例分割、全景分割、点云分割、遥感分割、医疗分割&#xff0c;每一类都能拉出几十上百篇&#xff0c;更不用说这两年Transformer和Mask类方法爆发之后&#xff0c;几乎每周都有…

作者头像 李华
网站建设 2026/9/7 4:14:38

旋转机械臂PID控制与前馈补偿:Java实现与参数整定指南

旋转机械臂运动控制里&#xff0c;PID 是最常被先拿来用的闭环算法&#xff0c;但真正想让关节停得稳、偏得小、跟得上轨迹&#xff0c;通常还要在 PID 之外加前馈补偿。这个话题在 Java 机器人编程里经常被提&#xff0c;尤其是写上位机控制逻辑时&#xff0c;很多人卡住的点不…

作者头像 李华
网站建设 2026/9/7 4:11:51

相位梯度超表面:从广义斯涅耳定律到10GHz波束偏转设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华