Using PDF.JS with Ionic 3.x

/ August 21, 2017/ Ionic

(Last Updated On: April 30, 2018)

A little while ago I was assigned to develop an Hybrid App using Ionic 3 for read and comment PDF files and was requested to use PDF.js. It was kind of difficult at the beginning because almost all the (scarce) documentation does not show directly how to work with Webpack based projects. Let’s see how I did it.

I will use a blank Ionic start project as base.

The first thing that we need to do is to install PDF.js on our project (the dist version) and the correspond Typings

npm install --save [email protected]
npm install --save-dev @types/pdfjs-dist

If you find find an unmet dependency warning UNMET PEER DEPENDENCY webpack@>=0.9 <2 || ^2.1.0-beta || ^2.2.0 just ignore it, it is not important now.

Now copy a PDF file to your assets folder src/assets/pdf1.pdf.

Change your project files to reflect this:

Ad:


home.ts

import { Component, ElementRef, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import * as PDFJS from "pdfjs-dist/webpack.js";
import { PDFPageProxy, PDFPageViewport, PDFRenderTask } from 'pdfjs-dist';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})
export class HomePage {
    pdfDocument: PDFJS.PDFDocumentProxy;
    PDFJSViewer = PDFJS;
    pageContainerUnique = {
        width: 0 as number,
        height: 0 as number,
        element: null as HTMLElement,
        canvas: null as HTMLCanvasElement,
        textContainer: null as HTMLElement,
        canvasWrapper: null as HTMLElement
    }
    @ViewChild('pageContainer') pageContainerRef: ElementRef;
    @ViewChild('viewer') viewerRef: ElementRef;
    @ViewChild('canvas') canvasRef: ElementRef;
    @ViewChild('canvasWrapper') canvasWrapperRef: ElementRef;
    @ViewChild('textContainer') textContainerRef: ElementRef;

    constructor(public navCtrl: NavController) {
        console.log(this.PDFJSViewer);
    }

    ionViewDidLoad() {
        this.pageContainerUnique.element = this.pageContainerRef.nativeElement as HTMLElement;
        this.pageContainerUnique.canvasWrapper = this.canvasWrapperRef.nativeElement as HTMLCanvasElement;
        this.pageContainerUnique.canvas = this.canvasRef.nativeElement as HTMLCanvasElement;
        this.pageContainerUnique.textContainer = this.textContainerRef.nativeElement as HTMLCanvasElement;
        this.loadPdf('assets/pdf1.pdf');
    }
    
    loadPdf(pdfPath: string): Promise<boolean> {

        return this.PDFJSViewer.getDocument(pdfPath)
            .then(pdf => {
                this.pdfDocument = pdf;
                console.log("pdf loaded:"); console.dir(this.pdfDocument);
                return this.loadPage(1);
            }).then((pdfPage) => {
                console.dir(pdfPage);
            }).catch(e => {
                console.error(e);
                return false;
            });
    }

    loadPage(pageNum: number = 1) {
        let pdfPage: PDFPageProxy;

        return this.pdfDocument.getPage(pageNum).then(thisPage => {
            pdfPage = thisPage;
            return this.renderOnePage(pdfPage);
        }).then(() => {
            return pdfPage;
        });

    } // loadpage()



    async renderOnePage(pdfPage: PDFPageProxy) {

        let textContainer: HTMLElement;
        let canvas: HTMLCanvasElement;
        let wrapper: HTMLElement;

        let canvasContext: CanvasRenderingContext2D;
        let page: HTMLElement

        page = this.pageContainerUnique.element;
        textContainer = this.pageContainerUnique.textContainer;
        canvas = this.pageContainerUnique.canvas;
        wrapper = this.pageContainerUnique.canvasWrapper;

        canvasContext = canvas.getContext('2d') as CanvasRenderingContext2D;
        canvasContext.imageSmoothingEnabled = false;
        canvasContext.webkitImageSmoothingEnabled = false;
        canvasContext.mozImageSmoothingEnabled = false;
        canvasContext.oImageSmoothingEnabled = false;

        let viewport = pdfPage.getViewport(1) as PDFPageViewport;

        canvas.width = viewport.width;
        canvas.height = viewport.height;
        page.style.width = `${viewport.width}px`;
        page.style.height = `${viewport.height}px`;
        wrapper.style.width = `${viewport.width}px`;
        wrapper.style.height = `${viewport.height}px`;
        textContainer.style.width = `${viewport.width}px`;
        textContainer.style.height = `${viewport.height}px`;

        //fix for 4K


        if (window.devicePixelRatio > 1) {
            let canvasWidth = canvas.width;
            let canvasHeight = canvas.height;

            canvas.width = canvasWidth * window.devicePixelRatio;
            canvas.height = canvasHeight * window.devicePixelRatio;
            canvas.style.width = canvasWidth + "px";
            canvas.style.height = canvasHeight + "px";

            canvasContext.scale(window.devicePixelRatio, window.devicePixelRatio);
        }

        // THIS RENDERS THE PAGE !!!!!!


        let renderTask: PDFRenderTask = pdfPage.render({
            canvasContext,
            viewport
        });

        let container = textContainer;

        return renderTask.then(() => {
            //console.error("I WORK JUST UNTIL HERE");


            return pdfPage.getTextContent();

        }).then((textContent) => {

            let textLayer: HTMLElement;


            textLayer = this.pageContainerUnique.textContainer


            while (textLayer.lastChild) {
                textLayer.removeChild(textLayer.lastChild);
            }

            this.PDFJSViewer.renderTextLayer({
                textContent,
                container,
                viewport,
                textDivs: []
            });

            return true;
        });
    }
}
Ad:


home.html

<ion-header>
    <ion-navbar>
        <ion-title>
            PDFJS on IONIC
        </ion-title>
    </ion-navbar>
</ion-header>

<ion-content padding>
    <div #viewerContainer
         class="viewer-container">
        <div #viewer
             class="viewer">
            <ng-container #pagesContainer>
                <div #pageContainer
                     class="page"
                     [style.width.px]="pageContainerUnique.width"
                     [style.height.px]="pageContainerUnique.height">

                    <div class="canvas-wrapper"
                         #canvasWrapper>
                        <canvas class="page-canvas"
                                #canvas></canvas>

                    </div>
                    <div #textContainer
                         class="text-layer selectable">
                    </div>
                </div>
            </ng-container>
        </div>
    </div>
</ion-content>

Ad:


home.scss

page-home {
    .viewer-container {
        width: 100%;
        overflow: hidden;
        padding: 0 16px;
        .viewer {
            position: relative;
            width: 100%;
            .page {
                direction: ltr; 
                width: 100%;
                position: relative;
                overflow: visible;
                background-clip: content-box;
                background-color: white;
                margin: 0 auto 35px;
                .canvas-wrapper {
                    overflow: hidden;
                    position: absolute;
                } 
                &[data-loaded='true'] {
                    .textLayer {
                        margin: 0 auto;
                        position: relative;
                        border: 1px solid #b7b7b7;
                        box-shadow: 0px 0px 20px 1px rgba(0, 0, 0, 0.43);
                    }
                }
                .text-layer {
                     ::selection {
                        background: map-get( $colors, dark); //color: #fff;
                    }
                     ::-moz-selection {
                        background: map-get( $colors, dark); //color: #fff;
                    }
                    &.selectable {
                        -webkit-user-select: text;
                        -moz-user-select: text;
                        -ms-user-select: text;
                        user-select: text;
                    }
                    position: absolute;
                    left: 0;
                    top: 0;
                    right: 0;
                    bottom: 0;
                    overflow: hidden;
                    opacity: 0.2;
                    line-height: 1.0;
                    & > div {
                        color: transparent;
                        position: absolute;
                        white-space: pre;
                        cursor: cell;
                        -webkit-transform-origin: 0% 0%;
                        -moz-transform-origin: 0% 0%;
                        -o-transform-origin: 0% 0%;
                        -ms-transform-origin: 0% 0%;
                        transform-origin: 0% 0%;
                    }
                    cursor: cell;
                    &.selectable {
                        cursor: default;
                        & > div {
                            cursor: text;
                        }
                    }
                }
            }
        }
    }
}

The complete implementation will depend on what exactly do you need, this is just a little part of the code I needed but i think it is a good start point.

Ad:


Edit: 11.Feb.2018 I have to say as much as I respect this lib it is really hard to use inside an Angular project!

Spread the love

44
Leave a Reply

avatar
14 Comment threads
30 Thread replies
2 Followers
 
Most reacted comment
Hottest comment thread
15 Comment authors
Saninn Salas DiazDSKMkishankhaidalooLuca Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Eduardo
Guest
Eduardo

Hi, i’m getting “PDFDocumentProxy not found”, I think i need to add the pdf.js worker to ionic, any tips?

Eduardo
Guest
Eduardo

I only had to change:

pdfDocument: PDFDocumentProxy;

to

pdfDocument: PDFJSViewer.PDFDocumentProxy;

and everything worked.

Pedro Henrique
Guest
Pedro Henrique

Hi Saninn, thanks a lot for show how you did the pdf viewer! I’m having an issue, ERROR Error: Uncaught (in promise): UnexpectedResponseException: Unexpected server response (416) while retrieving PDF “http://localhost:8100/assets/livro.pdf”. Do you know how i can solve that problem? I can click on close and then the pdf viewer… Read more »

Syed
Guest
Syed

It is giving error that

File URL not supported

when running on device. PDF does not render

Syed
Guest
Syed

and how to make it responsive?

Manoj
Guest
Manoj

Not getting what actually causes this.ERROR Error: Uncaught (in promise): DataCloneError: Failed to execute 'postMessage' on 'Worker': function (delegate, current, target, task, applyThis, applyArgs) { try { onEnter(zo...... } could not be cloned. Error: Failed to execute 'postMessage' on 'Worker': function (delegate, current, target, task, applyThis, applyArgs) { try {… Read more »

Ricardo
Guest
Ricardo

Hi there, do you know if it’s possible create annotate. I’ve been looking for, but without success.

Frederick
Guest
Frederick

Hello, thank you for this tutorial. Please am facing a challenge when trying to load pdf file from the device directory(cordova.file.externalDataDirectory) , please can you help me ,
thanks in advance

Rob
Guest
Rob

There seems to be a problem with import { PDFJS as PDFJSViewer } from 'pdfjs-dist/webpack';, VsCode warns me could not find a declaration file for module.

when running I get Cannot read property ‘getDocument’ of undefined. Is the webpack reference changed?

Any help would be appreciated.

Thanks!

amenkey
Guest
amenkey

thank for the tuto. l got cannot resolve symbol PDFJS. Please help

Jo Cool
Guest
Jo Cool

If the content of pdf is big, I cannot scroll horizontally. How do i enable scroll and zoom?

Kees Schouten
Guest
Kees Schouten

Thanks! Great starting point.

kishan
Guest
kishan

Bro, it is displaying only first page of the PDF, how to fix this?

Luca
Guest
Luca

Hi, i create my project ionic, load only frist page pdf, why?
Tnx

khaidaloo
Guest
khaidaloo

Hi, I’m having an issue
=={Uncaught (in promise): DataCloneError: The object could not be cloned. [email protected]://localhost:8100/build/vendor.js:131007:7 [email protected]://localhost:8100/build/vendor.js:130824:7 [email protected]://localhost:8100/build/vendor.js:130867:9 WorkerTransport_setupMessageHandler/</sink.onPull/<@http://localhost:8100/build/vendor.js:138918:13 F</l</[email protected]://localhost:8100/build/polyfills.js:3:14974 [email protected]://localhost:8100/build/vendor.js:5134:24 F</l</[email protected]://localhost:8100/build/polyfills.js:3:14901 F</c</[email protected]://localhost:8100/build/polyfills.js:3:10124 f/<@http://localhost:8100/build/polyfills.js:3:20240 F</l</[email protected]://localhost:8100/build/polyfills.js:3:15649 [email protected]://localhost:8100/build/vendor.js:5125:24 F</l</[email protected]://localhost:8100/build/polyfills.js:3:15562 F</c</[email protected]://localhost:8100/build/polyfills.js:3:10815 [email protected]://localhost:8100/build/polyfills.js:3:7887 F</h</[email protected]://localhost:8100/build/polyfills.js:3:16823 [email protected]://localhost:8100/build/polyfills.js:2:27646 [email protected]://localhost:8100/build/polyfills.js:2:27967}

please help me