Printing a PDF file with Electron JS

28,752

Solution 1

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.

You can use lp command or PDFtoPrinter for 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

Solution 2

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.

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

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

<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>

Solution 3

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:

One possible solution might be to use Google PDFium and a wrapping NodeJS library which 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.

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

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.


UPDATED. For now, PDF.js solves the problem with rendering/previewing individual pages, but as to printing itself, it appears Electron (at the time of this posting) just lacks the proper printing APIs. E.g., you can't set paper size/landscape portrait mode etc. Moreover, when printing, PDF.js produces rasterized printouts - thanks to how HTML5 canvas work - unlike how Chrome PDF Viewer does it. Here is a discussion of some other PDF.js shortcomings.

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).

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):

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

Solution 4

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


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.

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.

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

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.

That said you will load your PDF into the webContents of 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.

Printing will capture your webContents as 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.

That little issue is easily fixed with 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.

Solution 5

This is 2021 and here is the simplest way ever.

Let's begin

First of all, install the pdftoprinter

npm install --save pdf-to-printer

import the library into your file

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

Then call the method into your function

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

It should work!

Share:
28,752

Related videos on Youtube

Grig Dodon
Author by

Grig Dodon

Updated on August 12, 2021

Comments

  • Grig Dodon
    Grig Dodon almost 3 years

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

    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.

    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?

    • AmerllicA
      AmerllicA over 5 years
      Please upload your reproduction code on a git repository. With a reproduction, I can help you better.
    • boldnik
      boldnik over 5 years
      @Noseratio Heyho, since Electronjs is very buggy thing above Nodejs, I would suggest to try the answer for "how to print PDF in Nodejs app". You have to spawn the new child process on the background and try to use external tool (for what you have to check the platform of course like darwin vs win).
    • noseratio
      noseratio over 5 years
      @boldnik, I think I covered those options in my answer.
  • noseratio
    noseratio almost 6 years
    LPR direct printing requires a PDF-enabled printer, most printers are not. SumatraPDF might be an option, although PdfiumViewer seems to be better positioned towards this task.
  • noseratio
    noseratio almost 6 years
    PDFtoPrinter requires installation of a 3rd party app which seems to require signing a bundle distribution license, same as Adobe Acrobat Reader.
  • zer09
    zer09 almost 6 years
    @Noseratio yeah, but the baseline is there to do a silent printing the lp and ptp can be replaced by other command/software that you like, to do the printing, just spawn other process. And also I think on modern system now most of them use CUPS instead of LPR, correct me if I'm wrong.
  • Tim
    Tim almost 6 years
    We use the electron printToPdf api and then use this to display a preview inline with our UI before saving to file or outputting to printer. It's super powerful and flexible.
  • noseratio
    noseratio over 5 years
    Actually, I do want to print directly from Electron, i.e. having a paper print-out is what I need. Downloading and printing it outside Electron is also an option if you can suggest an API or tool for printing a PDF, but please make sure it hasn't been covered by existing answers/comments.
  • obermillerk
    obermillerk over 5 years
    @Noseratio So are you trying to print a pdf of non-pdf content, or print a pdf file from some source?
  • noseratio
    noseratio over 5 years
    I want to preview and print a PDF file (having its URL). In its current version, Electon does the preview job, but fails to print it.
  • obermillerk
    obermillerk over 5 years
    Hmm... that does complicate things a bit. So as I said, you're probably going to have to download the file before printing it bar some library that I'm not familiar with that can print the pdf directly from a url. Printing from electron is like printing from a web browser: it prints the web page, where as you want to print the pdf file. I'll update my answer with my suggestions for you.
  • noseratio
    noseratio over 5 years
    #toolbar=0 etc.. that works for HTML but doesn't work for PDF, it only prints the current view (a single page) even if you hide the UI. Or maybe we're missing something. In which case, could you post a version that works correctly, taking the OP's code as base? Thanks.
  • obermillerk
    obermillerk over 5 years
    What I'm saying is you can't use electrons print to print the PDF right now. You will have to download the PDF file separately aside from the previewing. The toolbar=0 is just to prevent the user from downloading through the PDF viewer that serves as a preview in the webview
  • noseratio
    noseratio over 5 years
    Thanks, please check my cross-comment here.
  • Megajin
    Megajin over 5 years
    @Noseratio have you tried this repository? github.com/gerhardberger/electron-pdf-window You are right as long as you try to print normally you can't set the options.
  • noseratio
    noseratio over 5 years
    Thanks, haven't come across it, it's a useful resource indeed and it also uses PDF.JS so good for previewing but not so much for printing. So I guess it will be a combo of PDF.JS (UI) and PDFium (backend).
  • noseratio
    noseratio over 5 years
    For now, PDF.js solves the problem with rendering/previewing individual pages, but as to printing itself, it appears Electron (at the time of this posting) just lacks the proper printing APIs. E.g., you can't set paper size/landscape portrait mode etc. Moreover, when printing, PDF.js produces rasterized printouts - thanks to how HTML5 canvas work - unlike Chrome PDF Viewer. So for now I think it'll be a combination of PDF.js (for UI in the Electron's Renderer process) and PDFium (for actual printing from the Main process).
  • Habibul Hasan
    Habibul Hasan over 2 years
    The method is not yet implemented for win32
  • Dinesh Gurjar
    Dinesh Gurjar about 2 years
    yes this one resolve my problem. my code for silent print is "ptp.print(filepath2, {silent: true}).then(console.log);"