Electron 的 printToPDF 在鸿蒙 PC 上翻车了,我换了个纯前端方案绕过去
Electron 的 printToPDF 在鸿蒙 PC 上翻车了我换了个纯前端方案绕过去先说结论如果你在鸿蒙 PC 上用 Electron 做应用需要导出 PDF 的功能别碰webContents.printToPDF()。不是它不好是它在鸿蒙上的 Chromium 环境里跟中文字体和页面布局有仇。我绕了整整两圈结果在前端用两个纯 JS 库搞定了效果反而更好。上周三斌哥丢过来一个需求日报模块加一个导出 PDF 的功能用户想打印或者发邮件用。我扫了一眼脑子里已经冒出方案了Electron 不是有webContents.printToPDF()吗一行代码的事。const{ipcMain,BrowserWindow,app}require(electron);constfsrequire(fs).promises;constpathrequire(path);ipcMain.handle(export-pdf,async(_event,htmlContent){constwinnewBrowserWindow({show:false});awaitwin.loadURL(data:text/html,${encodeURIComponent(htmlContent)});constpdfBufferawaitwin.webContents.printToPDF({marginsType:1,printBackground:true,pageSize:A4});constoutputPathpath.join(app.getPath(downloads),report.pdf);awaitfs.writeFile(outputPath,pdfBuffer);win.close();returnoutputPath;});这段代码在 Windows 上跑得顺顺当当。我在开发机上试了一次PDF 出来了排版精美中文清晰。我把代码提交顺手在群里回了一句“搞定了明天联调。”第二天测试在鸿蒙 PC 上跑了一遍把 PDF 发给我。我打开一看整个人都傻了。中文字全变成了空白方块。我第一反应是字体问题。鸿蒙 PC 的 Chromium 内核在渲染 PDF 时可能没有正确嵌入中文字体。我检查了系统字体目录/usr/share/fonts下面明明有 Noto Sans CJK。webContents.printToPDF()理论上应该能访问到系统字体但实际生成的 PDF 里英文和数字正常中文全部消失。我试了在 HTML 里强制指定font-family: Noto Sans CJK SC, sans-serif;也试了把 CSS 的media print规则写得更详细。没有用。printToPDF在鸿蒙上似乎有自己的字体解析逻辑跟页面的 CSS 是两条平行线。等一下这里我漏说一个前提。我们项目用的 Electron 版本是 28.x对应的 Chromium 版本是 120。鸿蒙 PC 的桌面环境基于 OpenHarmony它的图形栈和字体渲染路径跟标准 Linux 桌面并不完全一致。Electron 的printToPDF底层调用的是 Chromium 的 Headless 打印管线这个管线在某些非标准 Linux 发行版上确实有已知问题——尤其是在字体回退font fallback的逻辑上。也就是说这个问题不是我用错 API 了是平台兼容性层面的坑。我换了个思路。既然printToPDF不靠谱那能不能直接调用系统打印对话框让用户自己选择打印到 PDF// 渲染进程constprintBtndocument.getElementById(print-btn);printBtn.addEventListener(click,(){window.print();});或者在主进程里用webContents.print()win.webContents.print({silent:false,printBackground:true});代码写完了鸿蒙 PC 上一点按钮界面没反应。控制台没有报错主进程也没日志。我愣了十秒钟又点了一次还是没反应。后来翻了一圈 OpenHarmony 的文档才找到一行不起眼的话当前桌面版本暂不支持系统打印对话框。不是 Electron 的问题是整个鸿蒙 PC 的打印子系统还没完全对接 Chromium 的打印 UI。两条路全堵死了。我坐在那儿盯着屏幕脑子里在复盘。需求其实很简单把一页 HTML 格式的日报转换成 PDF 文件。没有复杂排版没有多页表格就是一些文字、几个图表、一个标题。printToPDF不行window.print()也不行那我为什么一定要依赖系统层的打印能力这个念头冒出来的时候我其实有点抗拒。前端生成 PDF那不就是说要在浏览器里用 JS 画 PDF性能能行吗清晰度够吗我以前用过jsPDF印象里是画简单表格还可以复杂页面很吃力。但我还是决定试一下。这次不找原生方案了找能用的方案。我先在渲染进程里装了html2canvas和jspdfnpminstallhtml2canvas jspdf然后写了一个导出函数importhtml2canvasfromhtml2canvas;import{jsPDF}fromjspdf;asyncfunctionexportDomToPdf(domElement,filenamereport.pdf){// 先把 DOM 转成 canvasconstcanvasawaithtml2canvas(domElement,{scale:2,// 2倍分辨率保证清晰度useCORS:true,logging:false,backgroundColor:#ffffff});constimgDatacanvas.toDataURL(image/png);// A4 尺寸210mm x 297mm换算成 pt1mm 2.83465ptconstpdfnewjsPDF(p,pt,a4);constpageWidthpdf.internal.pageSize.getWidth();constpageHeightpdf.internal.pageSize.getHeight();constimgWidthpageWidth;constimgHeight(canvas.height*imgWidth)/canvas.width;letheightLeftimgHeight;letposition0;// 如果内容超出一页分页处理pdf.addImage(imgData,PNG,0,position,imgWidth,imgHeight);heightLeft-pageHeight;while(heightLeft0){positionheightLeft-imgHeight;pdf.addPage();pdf.addImage(imgData,PNG,0,position,imgWidth,imgHeight);heightLeft-pageHeight;}pdf.save(filename);}渲染进程里调用constexportBtndocument.getElementById(export-btn);exportBtn.addEventListener(click,async(){constreportEldocument.getElementById(daily-report);constdateStrnewDate().toISOString().slice(0,10);awaitexportDomToPdf(reportEl,日报-${dateStr}.pdf);});我在鸿蒙 PC 上跑了一次。日报模块的 DOM 节点大概包含三百来个元素有文字有几个 ECharts 图表。html2canvas的转换耗时 400 毫秒左右PDF 生成耗时 200 毫秒总耗时不到一秒。打开生成的 PDF中文清晰图表完整排版跟页面上看到的几乎一致。因为scale: 2的设置图表边缘没有锯齿文字也没有模糊。我特意对比了一下 Windows 上printToPDF生成的 PDF 和这个前端方案生成的 PDF。文件体积上前端方案的大一点因为是图片嵌入但差距在 200KB 以内对于日报这种场景完全可以接受。而在可控性上前端方案完胜——我想在哪分页就在哪分页想加页眉页脚直接画不用跟 Chromium 的打印 CSS 规则较劲。更关键的是这个方案在 Windows 和鸿蒙 PC 上表现完全一致。html2canvas和jspdf是纯前端库不依赖任何平台原生能力。我甚至在预加载脚本里把exportDomToPdf暴露给了主进程方便其他模块复用// preload.jsconst{contextBridge,ipcRenderer}require(electron);contextBridge.exposeInMainWorld(pdfAPI,{exportDomToPdf:(selector,filename)ipcRenderer.invoke(export-pdf,selector,filename)});主进程里转发一下// main.jsipcMain.handle(export-pdf,async(_event,selector,filename){constwinBrowserWindow.getFocusedWindow();returnwin.webContents.executeJavaScript(window.exportDomToPdf(document.querySelector(${selector}), ${filename}));});等一下这段其实有点绕。更简单的做法是让导出逻辑完全留在渲染进程主进程只负责打开保存对话框。不过那是后话了反正能跑通。回头来看这件事我其实掉进了优先使用原生 API的思维惯性。printToPDF是 Electron 官方 API听起来就应该是正统方案。但正统方案在不完整的平台支持面前反而成了最大的不确定性来源。有时候退一步用最朴素的工具组合反而能得到最稳定的结果。等鸿蒙 PC 的打印子系统成熟之后printToPDF应该能正常工作。但在那之前html2canvas jsPDF 这个组合我会继续用下去。你在跨平台开发里遇到过类似的情况吗某个官方 API 在特定平台上彻底失灵到头来靠一个土办法解决欢迎评论区聊聊。关于我我叫老三一个写了十年代码的前端 鸿蒙 ArkTS 水手。目前主业做 Taro 多端项目业余时间全泡在 AI 自动化和独立开发上——不是因为多热爱加班而是打心底觉得程序开发这件事正在被 AI 重构我不跟上就会被甩下。这个账号记录的就是我在这条路上的真实经历踩过的坑、推翻过的方案、以及偶尔值得高兴的小进展。不写教科书不讲大道理只分享我自己试过、做过、确认过的东西。如果你也在写代码或者也在思考 AI 时代开发者该往哪走——欢迎留言聊聊一起摸索。本文遵循 MIT 协议转载请注明出处。