javascript 使用 Electron JS 打印 PDF 文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/49650784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 08:42:03  来源:igfitidea点击:

Printing a PDF file with Electron JS

javascriptnode.jspdfprintingelectron

提问by Grig Dodon

I am trying to create an Electron JS app that has the purpose to print letter size PDFs.

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

This is my snippet of code for printing:

这是我的打印代码片段:

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

My issues are: if I have print({silent:true})my printer prints an empty page. If I have print({silent:false}), the printer prints in the same way as the screenshot, with headers, controls, etc.

我的问题是:如果print({silent:true})我的打印机打印空白页。如果有print({silent:false}),打印机会以与屏幕截图相同的方式打印,包括标题、控件等。

enter image description here

在此处输入图片说明

I need a silent print of the PDF content, and I can't manage to do it for days. Did anyone experience the same thing with Electron?

我需要 PDF 内容的无声打印,但我好几天都做不到。有没有人在 Electron 上遇到过同样的事情?

采纳答案by zer09

If you have already have the pdf file or you save the pdf before printing "I assuming it is", then you can grab the file location then you can use externals process to do the printing using child_process.

如果您已经有了 pdf 文件,或者您在打印“我假设是”之前保存了 pdf,那么您可以获取文件位置,然后您可以使用外部进程使用child_process.

You can use lp commandor PDFtoPrinterfor windows

您可以使用lp commandPDFtoPrinterfor windows

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.'
        );
}

I hope it helps.

我希望它有帮助。

Edit:You can also use SumatraPDF for windows https://github.com/sumatrapdfreader/sumatrapdf

编辑:您也可以将 SumatraPDF 用于 windows https://github.com/sumatrapdfreader/sumatrapdf

回答by Tim

The easiest way to do this is to render the PDF pages to individual canvas elements on a page using PDF.js and then call print.

最简单的方法是使用 PDF.js 将 PDF 页面渲染到页面上的单个画布元素,然后调用打印。

I fixed this gistto use the PDF.js version (v1) it was designed for and its probably a good starting point.

我修复了这个要点以使用它设计的 PDF.js 版本 (v1),这可能是一个很好的起点。

This is essentially what the electron/chrome pdf viewer is doing but now you have full control over the layout!

这基本上是电子/铬 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>

回答by noseratio

I'm facing the same issue. It appears the PDF printing to a printer is just not implemented in Electron, despite it's been requested since 2017. Here is another related question on SO and the feature request on GitHub:

我面临同样的问题。尽管自 2017 年以来就有人要求将 PDF 打印到打印机,但它似乎并未在 Electron 中实现。这是关于 SO 的另一个相关问题和 GitHub 上的功能请求:

One possible solution might be to use Google PDFiumand a wrapping NodeJS librarywhich appears to allow conversion from PDF to a set of EMFs, so the EMFs can be printed to a local/network printer, at least on Windows.

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

As another viable option, this answerprovides a simple C# solution for PDF printing using PdfiumViewer, which is a PDFium wrapper library for .NET.

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

I'm sill looking at any other options. Utilizing a locally installed instance of Acrobat Reader for printing is not an acceptable solution for us.

我还在考虑任何其他选择。使用本地安装的 Acrobat Reader 实例进行打印对我们来说不是一个可接受的解决方案。



UPDATED.更新。目前,PDF.jsPDF.js解决了渲染/预览单个页面的问题,但至于打印本身,似乎 Electron(在发布时)只是缺乏正确的打印 API。例如,您无法设置纸张大小/横向纵向模式等。此外,在打印时,PDF.js 会生成光栅化打印输出——这要归功于 HTML5 画布的工作方式——与 Chrome PDF 查看器的工作方式不同。这是a discussion一些其他 PDF.js 缺点的讨论

So for now I think we might go on with a combination of PDF.js (for UI in the Electron's Renderer process) and PDFium (for actual printing from the Main process).

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

Based on Tim's answer, here's a version of the PDF.js renderer using ES8 async/await(supported as of the current version of Electron):

根据Tim 的回答,这里有一个使用 ES8 的 PDF.js 渲染器版本async/await(从当前版本的 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);
    }
}

回答by Megajin

Since your are using contents.print([options], [callback])I will assume that you want to print on paper and not on your Disk.

由于您正在使用,contents.print([options], [callback])我将假设您想在纸上而不是在磁盘上打印。



The answer to your issue is simple. It is the event you are listening on which is causing the error. So if you simply do this:

你的问题的答案很简单。正是您正在侦听的事件导致了错误。因此,如果您只是这样做:

  winObject.webContents.on('did-frame-finish-load', () => {
    setTimeout(() => {winObject.webContents.print({silent: true, printBackground:true})}, 3000);
  });

everything will work fine if the default printer is the right one. I did test this and it will do its job more or less. You can change my event to whatever event you like, the important part is the waiting with setTimeout. The PDF you are trying to print is simply not available in the frame when using silent:true.

如果默认打印机是正确的,一切都会正常工作。我确实对此进行了测试,它或多或少会完成它的工作。您可以将我的事件更改为您喜欢的任何事件,重要的部分是 setTimeout 的等待。使用 .pdf 时,您尝试打印的 PDF 在框架中根本不可用silent:true

However let me get into detail here a little bit to make things clear:

但是,让我在这里稍微详细说明一下:

Electron will load Files or URLs into a created window (BrowserWindow) which is bound to events. The problem is that every event "can" behave differently on different systems. We have to live with that and cannot change this easily. But knowing this will help improve the development of custom Apps.

Electron 会将文件或 URL 加载到BrowserWindow与事件绑定的创建窗口 ( ) 中。问题是每个事件“可以”在不同的系统上表现不同。我们必须接受这一点,不能轻易改变这一点。但了解这一点将有助于改进自定义应用程序的开发。

If you load urls or htmls everything will work without setting any custom options. Using PDFs as source we have to use this:

如果您加载 url 或 html,一切都将正常工作,而无需设置任何自定义选项。使用 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.
  }
});

hint: without webPreferences: { plugins: true }your source PDF will be downloaded instead of loaded into the window.

提示:没有webPreferences: { plugins: true }您的源 PDF 将被下载而不是加载到窗口中。

That said you will load your PDF into the webContentsof your window. So we have to listen on events compatible with BrowserWindow. You did everything right, the only part you missed was that printing is another interface.

也就是说,您会将您的 PDF 加载到webContents您的窗口中。所以我们必须监听与BrowserWindow. 你做的一切都对,你唯一错过的部分是打印是另一个界面。

Printing will capture your webContentsas it is when you press "print". This is very inportant to know when working with printers. Because if something will load slightly longer on a different system, for example the PDFs viewer will be still dark grey without the letters, then your printing will print the dark grey background or even the buttons.

webContents当您按“打印”时,打印将捕获您的原样。在使用打印机时,了解这一点非常重要。因为如果某些内容在不同的系统上加载时间稍长,例如 PDF 查看器在没有字母的情况下仍然是深灰色,那么您的打印将打印深灰色背景甚至按钮。

That little issue is easily fixed with setTimeout().

这个小问题很容易用setTimeout().

Useful Q&A for printing with electron:

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

However there are alot more possible issues with printing, since most of the code is behind closed doors without worldwide APIs to use. Just keep in mind that every printer can behave differently so testing on more machines will help.

然而,打印还有更多可能的问题,因为大部分代码都是闭门造车的,没有全球 API 可供使用。请记住,每台打印机的行为都可能不同,因此在更多机器上进行测试会有所帮助。

回答by obermillerk

So it seems like you're trying to download the pdf file rather than print a pdf of the current screen which is what printtries to do. As such, you have a couple of options.

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

1) Disable the native pdf viewer in electron:

1)禁用电子中的原生pdf查看器:

If you don't care about the electron window displaying the pdf, disabling the native pdf viewer in electron should instead cause it to treat the file as a download and attempt to download it.

如果您不关心显示 pdf 的电子窗口,则在电子中禁用本机 pdf 查看器应该会导致它将文件视为下载文件并尝试下载它。

new BrowserWindow({
  webPreferences: {
    plugins: false
  }
})

You may also want to checkout electron's DownloadItem apito do some manipulation on where the file will be saved.

您可能还想检查电子的 DownloadItem api以对文件的保存位置进行一些操作。

2) Download the pdf through some other api

2)通过其他一些api下载pdf

I'm not gonna give any specifics for this one because you should be able to find some information on this yourself, but basically if you want to download the file from somewhere, then you can use some other download API like an AJAX library to download the file and save it somewhere. This would potentially allow you to render the document in an electron window as well, since once you initiate the download you can probably redirect the window to the pdf url and have the native viewer handle it.

我不会为此提供任何细节,因为您应该能够自己找到有关此的一些信息,但基本上如果您想从某个地方下载文件,那么您可以使用其他一些下载 API,例如 AJAX 库来下载文件并将其保存在某处。这也可能允许您在电子窗口中呈现文档,因为一旦您开始下载,您就可以将窗口重定向到 pdf url 并让本机查看器处理它。

Long story short, it sounds to me like you don't really want to print from electron, you just want to save the pdf file that you're displaying. Printing from electron will render what you see on the screen, not the pdf document itself so I think you just misunderstood what the goal of print was. Hopefully this helps you, good luck!

长话短说,在我看来,您并不是真的想从电子打印,您只想保存正在显示的 pdf 文件。从电子打印将呈现您在屏幕上看到的内容,而不是 pdf 文档本身,所以我认为您只是误解了打印的目标是什么。希望这对你有帮助,祝你好运!

=== EDIT ===

=== 编辑 ===

Unfortunately, I don't believe that there is a way to print the file directly from electron since electron printing is for printing the contents of electrons display. But you should be able to download the file via a simple request for the file (see above).

不幸的是,我不相信有一种方法可以直接从电子打印文件,因为电子打印用于打印电子显示的内容。但是您应该能够通过对文件的简单请求来下载文件(见上文)。

My recommendation for you would be to create a page for previewing the file. This would be an independent page, not the built in pdf viewer. You can then insert a button somewhere on the page to download the pdf via some means and skip any save location prompts (this should be easy enough to find documentation for).

我对您的建议是创建一个页面来预览文件。这将是一个独立的页面,而不是内置的 pdf 查看器。然后,您可以在页面上的某处插入一个按钮,通过某种方式下载 pdf 并跳过任何保存位置提示(这应该很容易找到文档)。

Then, in order to have your preview, on the same page you can have a webviewtaginto your page, which will display the native pdf viewer. In order for the native pdf viewer to work in the webview tag, you mustinclude the pluginsattribute in the tag. It's a boolean tag, so it's mere presence is all that is needed such as <webview ... plugins>This turns on plugin support for that webview's renderer which is required for the pdf viewer.

然后,为了预览,在同一页面上,您可以在页面中webview添加一个标签,该标签将显示本机 pdf 查看器。为了让原生 pdf 查看器在 webview 标签中工作,您必须plugins在标签中包含该属性。它是一个布尔标签,所以它只是存在是所需要的,例如<webview ... plugins>这打开了对 pdf 查看器所需的 webview 渲染器的插件支持。

You can modify the size styling of this tag on the page as you wish to suit your needs. A trick to get rid of the download and print options so that a user cannot press them is to append #toolbar=0to the end of the pdf url to prevent the native pdf viewer from displaying the top toolbar with these buttons.

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

So, this way you can have your preview, ensure that the user can't use the built in download or print from the pdf viewer with the extra ui, and you can add another button to download it so it can be printed later.

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