使用 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 方应用程序,这似乎需要签署捆绑分发许可证,与 Adob​​e Acrobat Reader 相同。 @Noseratio 是的,但是 基线可以进行静默打印 lpptp 可以替换为您喜欢的其他命令/软件,以进行打印,只需生成其他进程。而且我认为现在在现代系统中,大多数人使用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 属性。它是一个布尔标记,因此只需要它的存在即可,例如 &lt;webview ... plugins&gt; 这将打开对 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 文件的主要内容,如果未能解决你的问题,请参考以下文章

electron 选择打印机打印

利用PDF.JS插件解决了本地pdf文件在线浏览问题(根据需要隐藏下载功能,只保留打印功能)

使用电子 js 创建和保存 pdf 文件

js直接打印pdf文件内容

如何在 Electron 中使用 fs 保存 PDF 文件?

在 electron 中打开本地文件并在 wavesurfer.js 中渲染