3

I'm trying to print a PDF file from my Windows 8 app to connected printer. I'm coding with WinJS, and know that I have to create a print task to initiate printing from a Windows 8 app. So, after reviewing the documentation, I have this code:

 onPrintTaskRequested: function (e) {
        var self = Application.navigator.pageControl,
            printTask = e.request.createPrintTask("Print Test Doc", function (args) {
                args.setSource(MSApp.getHtmlPrintDocumentSource(document));

                // Register the handler for print task completion event
                printTask.oncompleted = self.onPrintTaskCompleted;
            });
    }

According to the documentation, the MSApp.getHhtmlPrintDocumentSource method accepts a specific set of data types. As stated in the documentation:

This can be the root document, the document in an IFrame, a document fragment, or a SVG document. Be aware that htmlDoc must be a document, not an element.

Apparently I cannot simply set the argument for getHtmlPrintDocumentSource to a .PDF or .PNG binary. So, I'm curious: does the WinJS library offer a method for printing so that I can implement the printing of a PDF file to a connected printer? Can anybody offer some tips to implement?

Sushil
  • 5,265
  • 2
  • 17
  • 15
ariestav
  • 2,799
  • 4
  • 28
  • 56

2 Answers2

3

After trial and error, I was finally able implement the printing of a Base64 stream representing a PDF binary from a Windows 8 application.

I'm coding the app in HTML / CSS / WinJS. Essentially here is a brief explanation of how it was accomplished:

Create a new <canvas> element within the default.html file. Place it right after the open tag of the element. Like this:

<body role="application" class="app">
    <canvas id="pdf-render-output"></canvas>
    . 
    .
    .
</body>

Then inside the default.css file, setup a few rules as well as a print media query. Like this:

body > canvas {
    display: none;
}

.
. /* all your app's default css styles */
.

@media print {

    body > * {
       display:none;
       max-width: 100%;
    }

    html {
       max-width: 100%;
       border-top-color: none;
       border-top: 0;
    }

    body > canvas {
        display: block;
        border: none;
        max-width: 100%;
        width: 100%;
        height: 100%;
        position: relative;
    }
}

Of note is the order in which the rules are declared in CSS. It's important to place the print media query after declaring default CSS rules.

After this is setup, javascript handles the rest. The basic idea is to render the PDF.js output to the "hidden" canvas in the DOM. When the document object gets sent to print, the CSS print media declaration is queried so that all elements under <body> are hidden except for the canvas element. Here is the javascript to print only the first page in the PDF:

 //Define a container for the Base64 data we'll use with PDF.js
 var pdfPrintData = {};

 //Function to render PDF to canvas and begin printing contract with Windows 8 OS
 printPrescription: function () {

        var self = Application.navigator.pageControl,
            printManager = Windows.Graphics.Printing.PrintManager.getForCurrentView();

        self.getPDF().done(function () {
            var pdfStream = pdfPrintData.base64,
                pdfFile = convertDataURIToBinary(pdfStream);

            PDFJS.disableWorker = true;
            PDFJS.getDocument(pdfFile).then(function (pdf) {

                var numPages = pdf.numPages,
                    renderCanvas = $('#pdf-render-output')[0];

                //setup canvas
                renderCanvas.height = pdf.getPage(1).data.getViewport(1).height;
                renderCanvas.width = pdf.getPage(1).data.getViewport(1).width;

                //Setup a render context for pdf.js to out a pdf file to the canvas.
                var renderContext = {
                        canvasContext: renderCanvas.getContext('2d'),
                        viewport: pdf.getPage(1).data.getViewport(1)
                    };

                //Bring up Windows 8 OS print after PDF is rendered to render context.
                pdf.getPage(1).data.render(renderContext).then(function () {
                    printManager.onprinttaskrequested = self.onPrintTaskRequested;
                    Windows.Graphics.Printing.PrintManager.showPrintUIAsync();
                });

            })

        });

},
onPrintTaskRequested: function (e) {
        var self = Application.navigator.pageControl,
            printTask = e.request.createPrintTask("Print Prescription", function (args) {
                args.setSource(MSApp.getHtmlPrintDocumentSource(document));
                printTask.oncompleted = self.onPrintTaskCompleted;
            });

},
onPrintTaskCompleted: function (e) {
        if (e.completion === Windows.Graphics.Printing.PrintTaskCompletion.failed) {
            console.log("[ERX]  :   Failed to print!");
        }
    }

The self.getPDF method is just a function that retrieves the Base64 data stream, and that streams gets set on the .base64 property of the global pdfPrintData object. For some reason, I was not able to render the pdf using pdf.js to a dynamically create canvas in a dynamically created document. I had to render the output of the pdf.js render method to a canvas already present in the DOM.

ariestav
  • 2,799
  • 4
  • 28
  • 56
0

As far as I know, MSApp.getHtmlPrintDocumentSource(document) is meant to be used with HTML document objects, and nothing else.

If you can assume Windows 8.1, you can try to assemble a new HTML document from your PDF file by exporting each page into a raster image using PdfPage.RenderToStreamAsync. There is a sample project in MSDN for a PDF viewer that uses this new API where you can learn how to use this method.

If you cannot assume Windows 8.1 and you need to support plain Windows 8 or Windows RT (ARM), you might need to use a third party library to create the raster images or to do the printing all together.

Amyuni PDF Creator for WinRT for example can do the printing for you. Disclaimer: I currently work for the company that develops the library

yms
  • 10,361
  • 3
  • 38
  • 68
  • Thanks for the tips. I found Amyuni PDF Creator on my google hunt. Is there a free trial for using the software? Also, you mentioned "you might need to use a third party library to create the raster images". Do you mean something like PDF.js? – ariestav Jul 08 '13 at 19:43
  • @ariestav There is a 30 days free trial with free support. About "third party library" I meant that you will not be able to print using the WinRT API alone, so I actually meant **any** third party library, commercial or open source. I try my best to play fair when I answer questions in SO. – yms Jul 08 '13 at 19:54
  • About PDF.js, I know more or less what it does but I have never used it, so I do not know if it can be used for printing or rasterization. – yms Jul 08 '13 at 19:57
  • Amyuni has separate files for different architectures. My build is setup as "AnyCPU" in the VS2012's Configuration Manager. How would I include it given my build process? – ariestav Jul 08 '13 at 21:24
  • @yms pdf.js may not work as is. I have tried this earlier to render pdfs, and ran into issue where fonts were not getting loaded. it tries to insert ` – Sushil Jul 09 '13 at 06:31
  • @ariestav The library is a native library, not dotnet, that is why it has different dlls for each architecture. About your question, you have two options, you create two more copies of the project file that uses Amyuni PDF Creator, then add the new projects to the solution, and configure each project for one CPU architecture. All projects will be using the same source code files so only the project file will be duplicated. – yms Jul 09 '13 at 13:23
  • The other option I know about allows to keep just one project, but it involves editing the project file manually in a text editor. See this question for more details: [Conditionally use 32/64 bit reference when building in Visual Studio](http://stackoverflow.com/questions/3832552/conditionally-use-32-64-bit-reference-when-building-in-visual-studio) – yms Jul 09 '13 at 13:27
  • I've tried creating a blank HTML document via javascript, then rendering a PDF to a canvas container within that HTML document, then using that as the source parameter for getHtmlPrintDocumentSource. I'm using PDF.js's render method to do the rendering, but it's not working. Does anybody know if pdf.js needs a dom element that is displayed visually in the app? – ariestav Jul 10 '13 at 21:50