Using PDF.JS with Ionic 3.x

/ August 21, 2017/ Ionic

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 pdfjs-dist
npm install --save-dev @types/pdfjs-dist

You will probably find an unmet dependency warning UNMET PEER DEPENDENCY webpack@>=0.9 <2 || ^2.1.0-beta || ^2.2.0 but it is not important now.

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

Now the source Code

Ad:


home.ts

import { Component, ElementRef, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import { PDFJS as PDFJSViewer } from 'pdfjs-dist/webpack';
@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})
export class HomePage {
    pdfDocument: PDFDocumentProxy;
    PDFJSViewer: PDFJSViewer = PDFJSViewer;
    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) {

    }

    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 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: PDFJSViewer.PDFPageProxy;

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

    } // loadpage()


    async renderOnePage(pdfPage: PDFJSViewer.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 PDFJSViewer.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: PDFJSViewer.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:


Spread the love

Leave a Reply

26 Comments on "Using PDF.JS with Ionic 3.x"

avatar
  Subscribe  
newest oldest most voted
Notify of
Eduardo
Guest

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

Eduardo
Guest

I only had to change:

pdfDocument: PDFDocumentProxy;

to

pdfDocument: PDFJSViewer.PDFDocumentProxy;

and everything worked.

Pedro Henrique
Guest
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

It is giving error that

File URL not supported

when running on device. PDF does not render

Syed
Guest

and how to make it responsive?

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

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

Frederick
Guest

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