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

10 Comments

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

    1. I only had to change:

      pdfDocument: PDFDocumentProxy;

      to

      pdfDocument: PDFJSViewer.PDFDocumentProxy;

      and everything worked.

      1. Hi! I just came back from work.

        A couple of weeks ago there was a new release from PDFJS-Dist. Maybe it changed the definition. I am glad that you managed to get it to work!

  2. 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 load fine, but on devices i don’t have the option to close the error, just the program don’t run.
    Sorry for the bad english !

    1. Hi, did you get this error with others PDFs? Like the one PDFJS uses as example.

      1. No, just with this livro.pdf, it’s a book with 35mb.
        I need to load it in the PDF viewer

        1. Then maybe is as I thought. The PDF or is Metadata file is damaged. You could try to fix it with external tools I suppose.

          Other thing you can try it is to catch the error wrapping your function in a promise.

          1. Thanks a lot for the help.
            I tried to run now on the phone and it worked, it was just taking a while to appear believe it because of the size.
            I uploaded the book through a server, can I reduce this loading time if I load through assets or in some other way?

            1. You can save it in your local file system or in assets. This will be faster than download it but your App will be bigger.

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
*
*