使用Electron JS打印PDF文件

时间:2018-04-04 12:15:28

标签: javascript node.js pdf printing electron

我正在尝试创建一个Electron JS应用程序,其目的是打印字母大小的PDF。

这是我的打印代码片段:

print({silent:false})

我的问题是:如果我有build-wrapper-macosx-x86 --out-dir ./bw_output xcrun xcodebuild ,我的打印机会打印一个空白页面。如果我有dyld: warning: could not load inserted library 'somepath/libinterceptor.dylib' into library validated process because no suitable image found. Did find: somepath/libinterceptor.dylib: code signing blocked mmap() of 'somepath/build-wrapper-macosx-x86/libinterceptor.dylib' somepath/libinterceptor.dylib: stat() failed with errno=1 ,则打印机的打印方式与屏幕截图相同,包括标题,控件等。

enter image description here

我需要对PDF内容进行无声打印,而且我无法在数天内完成这项工作。有没有人和Electron一样经历过同样的事情?

6 个答案:

答案 0 :(得分:5)

最简单的方法是使用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>

答案 1 :(得分:4)

我正面临着同样的问题。尽管自2017年以来一直要求在打印机中将PDF打印到打印机上,但实际上并未在Electron中实现。这是SO上的另一个相关问题以及GitHub上的功能请求:

一种可能的解决方案可能是使用Google PDFium和包装NodeJS library ,这似乎允许从PDF转换为一组EMF,因此可以将EMF打印到本地/网络打印机,至少在Windows上。

作为另一个可行的选择,this answer为使用PdfiumViewer的PDF打印提供了一个简单的C#解决方案,这是.NET的PDFium包装库。

我还在寻找其他选择。对于我们来说,使用本地安装的Acrobat Reader实例进行打印不是可接受的解决方案。


已更新。目前,PDF.js解决了呈现/预览单个页面的问题,但对于打印本身,似乎Electron(在此发布时)缺少适当的打印API 。例如,您无法设置纸张尺寸/横向肖像模式等。此外,在打印时,由于HTML5画布的工作原理,PDF.js会生成光栅化的打印输出,这与Chrome PDF Viewer的工作方式不同。这是a discussion的其他一些PDF.js缺点。

因此,现在我认为我们可以继续使用PDF.js(用于Electron的Renderer流程中的UI)和PDFium(用于从Main流程进行实际打印)的组合。

基于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);
    }
}

答案 2 :(得分:4)

如果您已经有了pdf文件,或者在打印“我认为是”之前保存了pdf,则可以获取文件位置,然后可以使用外部过程使用child_process进行打印。 / p>

您可以在Windows上使用lp commandPDFtoPrinter

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用于Windows https://github.com/sumatrapdfreader/sumatrapdf

答案 3 :(得分:3)

由于您使用的是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)中。问题在于,每个事件“可以”在不同系统上的行为都不同。 我们必须忍受这一点,不能轻易改变这一点。但是知道这一点将有助于改善自定义应用程序的开发。

如果您加载url或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()可以轻松解决这个小问题。

有关电子打印的有用问答:

但是,由于大多数代码都是闭门造车的,没有世界范围的API可以使用,因此打印还有很多可能的问题。请记住,每台打印机的行为都可能有所不同,因此在更多计算机上进行测试将有所帮助。

答案 4 :(得分:2)

因此,似乎您正在尝试下载pdf文件,而不是打印print试图做的当前屏幕的pdf。因此,您有两种选择。

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渲染器的插件支持。

您可以根据需要在页面上修改此标签的大小样式。摆脱下载和打印选项以使用户无法按下的一种技巧是在PDF url的末尾附加#toolbar=0,以防止本机pdf查看器使用这些按钮显示顶部工具栏。

因此,通过这种方式,您可以进行预览,确保用户无法使用带有额外ui的pdf下载器中的内置下载或打印功能,并且可以添加另一个按钮进行下载,以便可以进行打印以后。

答案 5 :(得分:0)

这是 2021 年,这是有史以来最简单的方法。

开始吧

首先,安装pdftoprinter

npm install --save pdf-to-printer

将库导入您的文件

const ptp require('pdf-to-printer') // something like this

然后将该方法调用到您的函数中

ptp.print('specify your route/url');

它应该可以工作!