使用 Electron JS 打印 PDF 文件
Posted
技术标签:
【中文标题】使用 Electron JS 打印 PDF 文件【英文标题】:Printing a PDF file with Electron JS 【发布时间】:2018-09-14 00:03:26 【问题描述】:我正在尝试创建一个用于打印字母大小 PDF 的 Electron JS 应用程序。
这是我的打印代码sn-p:
win = new BrowserWindow(
width: 378,
height: 566,
show: true,
webPreferences:
webSecurity: false,
plugins: true
);
// load PDF
win.loadURL('file://' + __dirname + '/header1_X_BTR.pdf');
// if pdf is loaded start printing
win.webContents.on('did-finish-load', () =>
win.webContents.print(silent: true, printBackground:true);
);
我的问题是:如果我有print(silent:true)
,我的打印机会打印一个空白页。如果我有print(silent:false)
,打印机的打印方式与屏幕截图相同,带有标题、控件等。
我需要 PDF 内容的静默打印,但我好几天都做不了。有人对 Electron 有过同样的经历吗?
【问题讨论】:
请将您的复制代码上传到git
存储库。通过复制,我可以更好地帮助您。
@Noseratio Heyho,因为 Electronjs 在 Nodejs 之上是非常错误的东西,我建议尝试"how to print PDF in Nodejs app" 的答案。您必须在后台生成新的子进程并尝试使用外部工具(当然,您必须检查平台,例如 darwin vs win)。
@boldnik,我想我在my answer 中介绍了这些选项。
【参考方案1】:
如果您已经有 pdf 文件,或者您在打印“我假设是”之前保存了 pdf,那么您可以获取文件位置,然后您可以使用外部进程使用 child_process
进行打印。
您可以在 windows 上使用 lp command
或 PDFtoPrinter
const ch = require('os');
switch (process.platform)
case 'darwin':
case 'linux':
ch.exec(
'lp ' + pdf.filename, (e) =>
if (e)
throw e;
);
break;
case 'win32':
ch.exec(
'ptp ' + pdf.filename,
windowsHide: true
, (e) =>
if (e)
throw e;
);
break;
default:
throw new Error(
'Platform not supported.'
);
希望对你有帮助。
编辑: 您也可以使用 SumatraPDF for windows https://github.com/sumatrapdfreader/sumatrapdf
【讨论】:
LPR
直接打印需要启用 PDF 的打印机,但大多数打印机都没有。 SumatraPDF 可能是一种选择,尽管PdfiumViewer
似乎更适合这项任务。
PDFtoPrinter 需要安装第 3 方应用程序,这似乎需要签署捆绑分发许可证,与 Adobe Acrobat Reader 相同。
@Noseratio 是的,但是 基线可以进行静默打印 lp
和 ptp
可以替换为您喜欢的其他命令/软件,以进行打印,只需生成其他进程。而且我认为现在在现代系统中,大多数人使用CUPS
而不是LPR
,如果我错了,请纠正我。【参考方案2】:
执行此操作的最简单方法是使用 PDF.js 将 PDF 页面呈现到页面上的各个画布元素,然后调用 print。
我修复了 this gist 以使用它设计的 PDF.js 版本 (v1),这可能是一个很好的起点。
这基本上是电子/铬 pdf 查看器正在做的,但现在您可以完全控制布局!
<html>
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/1.10.90/pdf.js"></script>
<script type="text/javascript">
function renderPDF(url, canvasContainer, options)
var options = options || scale: 1 ;
function renderPage(page)
var viewport = page.getViewport(options.scale);
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var renderContext =
canvasContext: ctx,
viewport: viewport
;
canvas.height = viewport.height;
canvas.width = viewport.width;
canvasContainer.appendChild(canvas);
page.render(renderContext);
function renderPages(pdfDoc)
for(var num = 1; num <= pdfDoc.numPages; num++)
pdfDoc.getPage(num).then(renderPage);
PDFJS.disableWorker = true;
PDFJS.getDocument(url).then(renderPages);
</script>
<div id="holder"></div>
<script type="text/javascript">
renderPDF('//cdn.mozilla.net/pdfjs/helloworld.pdf', document.getElementById('holder'));
</script>
</body>
</html>
【讨论】:
我们使用电子printToPdf
api,然后在保存到文件或输出到打印机之前使用它来显示与我们的 UI 内联的预览。它超级强大且灵活。
目前,PDF.js 解决了渲染/预览单个页面的问题,但就打印本身而言,Electron(在本文发布时)似乎缺少适当的打印 API。例如,您不能设置纸张大小/横向纵向模式等。此外,在打印时,PDF.js 会产生光栅化打印输出 - 这要归功于 HTML5 画布的工作方式 - 与 Chrome PDF 查看器不同。所以现在我认为它将是 PDF.js(用于 Electron 渲染器进程中的 UI)和 PDFium(用于从主进程实际打印)的组合。【参考方案3】:
我也面临同样的问题。尽管自 2017 年以来已要求将 PDF 打印到打印机,但似乎并未在 Electron 中实现。这是关于 SO 的另一个相关问题和 GitHub 上的功能请求:
Silent printing in electron Support printing in native PDF rendering一种可能的解决方案是使用Google PDFium 和包装NodeJS library,这似乎允许从PDF 转换为一组EMF,因此可以将EMF 打印到本地/网络打印机,至少在 Windows 上。
作为另一个可行的选择,this answer 使用PdfiumViewer 为 PDF 打印提供了一个简单的 C# 解决方案,PdfiumViewer 是一个用于 .NET 的 PDFium 包装库。
我还在寻找其他选择。使用本地安装的 Acrobat Reader 实例进行打印不是我们可接受的解决方案。
已更新。 目前,PDF.js 解决了渲染/预览单个页面的问题,但就打印本身而言,Electron(在本文发布时)似乎缺少适当的打印 API。例如,您不能设置纸张大小/横向纵向模式等。此外,在打印时,PDF.js 会生成光栅化打印输出 - 这要归功于 HTML5 画布的工作方式 - 与 Chrome PDF 查看器的工作方式不同。这是a discussion 的其他一些 PDF.js 缺点。
所以现在我认为我们可能会结合使用 PDF.js(用于 Electron 渲染器进程中的 UI)和 PDFium(用于从主进程实际打印)。
基于Tim's answer,这是使用ES8 async/await
的PDF.js 渲染器版本(从当前版本的Electron 开始支持):
async function renderPDF(url, canvasContainer, options)
options = options || scale: 1 ;
async function renderPage(page)
let viewport = page.getViewport(options.scale);
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let renderContext =
canvasContext: ctx,
viewport: viewport
;
canvas.height = viewport.height;
canvas.width = viewport.width;
canvasContainer.appendChild(canvas);
await page.render(renderContext);
let pdfDoc = await pdfjsLib.getDocument(url);
for (let num = 1; num <= pdfDoc.numPages; num++)
if (num > 1)
// page separator
canvasContainer.appendChild(document.createElement('hr'));
let page = await pdfDoc.getPage(num);
await renderPage(page);
【讨论】:
【参考方案4】:由于您使用的是contents.print([options], [callback])
,我假设您想在纸上而不是在磁盘上打印。
您的问题的答案很简单。这是您正在监听的事件导致错误。因此,如果您只是这样做:
winObject.webContents.on('did-frame-finish-load', () =>
setTimeout(() => winObject.webContents.print(silent: true, printBackground:true), 3000);
);
如果默认打印机是正确的,一切都会正常工作。我确实对此进行了测试,它或多或少会完成它的工作。您可以将我的事件更改为您喜欢的任何事件,重要的部分是 setTimeout 的等待。使用 silent:true
时,您尝试打印的 PDF 在框架中根本不可用。
但是,让我在这里稍微详细说明一下:
Electron 会将文件或 URL 加载到绑定到事件的已创建窗口 (BrowserWindow
) 中。问题是每个事件“可以”在不同的系统上表现不同。
我们必须忍受这一点,不能轻易改变这一点。但了解这一点将有助于改进自定义应用的开发。
如果您加载 urls 或 htmls,一切都将正常工作,而无需设置任何自定义选项。使用 PDF 作为源,我们必须使用它:
import electron, BrowserWindow from 'electron';
const win = new BrowserWindow(
// @NOTE I did keep the standard options out of this.
webPreferences: // You need this options to load pdfs
plugins: true // this will enable you to use pdfs as source and not just download it.
);
提示:如果没有webPreferences: plugins: true
,您的源 PDF 将被下载而不是加载到窗口中。
也就是说,您会将 PDF 加载到窗口的 webContents
中。所以我们必须监听与BrowserWindow
兼容的事件。你做的一切都是正确的,你唯一错过的部分是打印是另一个界面。
当您按“打印”时,打印将捕获您的webContents
。在使用打印机时了解这一点非常重要。因为如果某些内容在不同系统上的加载时间会稍长一些,例如 PDF 查看器将仍然是深灰色而没有字母,那么您的打印将打印深灰色背景甚至是按钮。
setTimeout()
可以轻松解决这个小问题。
使用电子打印的有用问答:
Silent printing in electron Print: How to stick footer on every page to the bottom?但是,打印存在更多可能的问题,因为大多数代码都是关起门来的,没有可供使用的全球 API。请记住,每台打印机的行为可能不同,因此在更多机器上进行测试会有所帮助。
【讨论】:
谢谢,请查看我的交叉评论here。 @Noseratio 你试过这个存储库吗? github.com/gerhardberger/electron-pdf-window 你是对的,只要你尝试正常打印你就不能设置选项。 谢谢,没遇到过,它确实是一个有用的资源,而且它还使用 PDF.JS,非常适合预览,但不太适合打印。所以我猜这将是 PDF.JS(UI)和 PDFium(后端)的组合。【参考方案5】:因此,您似乎是在尝试下载 pdf 文件,而不是打印当前屏幕的 pdf,这是 print
尝试做的。因此,您有几个选择。
1) 在电子中禁用本机 pdf 查看器:
如果您不关心显示 pdf 的电子窗口,则禁用电子中的本机 pdf 查看器应该会导致它将文件视为下载并尝试下载它。
new BrowserWindow(
webPreferences:
plugins: false
)
您可能还想签出electron's DownloadItem api 以对文件的保存位置进行一些操作。
2) 通过其他一些 api 下载 pdf
我不会给出这个的任何细节,因为你应该能够自己找到一些关于这个的信息,但基本上如果你想从某个地方下载文件,那么你可以使用其他一些下载 API,比如 AJAX库下载文件并将其保存在某处。这也可能允许您在电子窗口中呈现文档,因为一旦您开始下载,您可能会将窗口重定向到 pdf url 并让本机查看器处理它。
长话短说,在我看来,您并不是真的想从电子打印,您只想保存正在显示的 pdf 文件。从电子打印将呈现您在屏幕上看到的内容,而不是 pdf 文档本身,所以我认为您只是误解了打印的目标。希望这对你有帮助,祝你好运!
=== 编辑 ===
不幸的是,我不相信有办法直接从电子打印文件,因为电子打印是用于打印电子显示器的内容。但是您应该能够通过对文件的简单请求来下载文件(见上文)。
我对您的建议是创建一个用于预览文件的页面。这将是一个独立的页面,而不是内置的 pdf 查看器。然后,您可以在页面某处插入一个按钮以通过某种方式下载 pdf,并跳过任何保存位置提示(这应该很容易找到相关文档)。
然后,为了进行预览,您可以在同一页面上将webview
tag 添加到您的页面中,这将显示本机 pdf 查看器。为了让原生 pdf 查看器在 webview 标签中工作,您必须在标签中包含 plugins
属性。它是一个布尔标记,因此只需要它的存在即可,例如 <webview ... plugins>
这将打开对 pdf 查看器所需的 webview 渲染器的插件支持。
您可以根据需要修改页面上此标签的大小样式。摆脱下载和打印选项以使用户无法按下它们的技巧是将#toolbar=0
附加到 pdf url 的末尾,以防止本机 pdf 查看器显示带有这些按钮的顶部工具栏。
因此,通过这种方式,您可以进行预览,确保用户无法使用内置下载或从带有额外 ui 的 pdf 查看器打印,并且您可以添加另一个按钮来下载它以便打印稍后。
【讨论】:
实际上,我确实想直接从 Electron 打印,即我需要打印纸。如果您可以建议用于打印 PDF 的 API 或工具,那么在 Electron 之外下载和打印也是一种选择,但请确保现有答案/cmets 没有涵盖它。 @Noseratio 那么您是在尝试打印非 pdf 内容的 pdf,还是从某个来源打印 pdf 文件? 我想预览和打印一个 PDF 文件(有它的 URL)。在其当前版本中,Electon 执行预览作业,但无法打印。 嗯...这确实使事情变得复杂了一点。所以正如我所说,您可能必须在打印文件之前下载文件,因为我不熟悉的一些库可以直接从 url 打印 pdf。从电子打印就像从网络浏览器打印:它打印网页,您想要打印 pdf 文件。我会用我对你的建议更新我的答案。 我的意思是您现在不能使用电子打印来打印 PDF。除了预览之外,您还必须单独下载 PDF 文件。toolbar=0
只是为了防止用户通过作为 webview 预览的 PDF 查看器下载【参考方案6】:
这是 2021 年,这是有史以来最简单的方法。
开始吧
首先,安装pdftoprinter
npm install --save pdf-to-printer
将库导入到您的文件中
const ptp require('pdf-to-printer') // something like this
然后将方法调用到你的函数中
ptp.print('specify your route/url');
应该可以了!
【讨论】:
该方法尚未针对win32实现以上是关于使用 Electron JS 打印 PDF 文件的主要内容,如果未能解决你的问题,请参考以下文章
利用PDF.JS插件解决了本地pdf文件在线浏览问题(根据需要隐藏下载功能,只保留打印功能)