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
Subscribe
Notify of
guest
79 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Eduardo
Eduardo
6 years ago

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

Eduardo
Eduardo
Reply to  Eduardo
6 years ago

I only had to change:

pdfDocument: PDFDocumentProxy;

to

pdfDocument: PDFJSViewer.PDFDocumentProxy;

and everything worked.

Pedro Henrique
Pedro Henrique
6 years ago

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 »

Pedro Henrique
Pedro Henrique
Reply to  Saninn Salas Diaz
6 years ago

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

Pedro Henrique
Pedro Henrique
Reply to  Saninn Salas Diaz
6 years ago

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… Read more »

Pedro Henrique
Pedro Henrique
Reply to  Saninn Salas Diaz
6 years ago

Thanks

Syed
Syed
6 years ago

It is giving error that

File URL not supported

when running on device. PDF does not render

Syed
Syed
6 years ago

and how to make it responsive?

kishan
kishan
Reply to  Saninn Salas Diaz
5 years ago

how to do this exactly?

Manoj
Manoj
6 years ago

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
Ricardo
6 years ago

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

DSKM
DSKM
Reply to  Saninn Salas Diaz
5 years ago

can i get that annotation layer source code through any blog url or through email?

[email protected]

Frederick
Frederick
6 years ago

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

Frederick
Frederick
Reply to  Saninn Salas Diaz
6 years ago

i have a pdf file located at cordova.file.externalDataDirectory+'pdf_name.pdf‘ , meaning instead of this.loadPdf('assets/pdf1.pdf') i have this.loadPdf(cordova.file.externalDataDirectory+'pdf_name.pdf').
but it can’t load. it is giving blank page

Frederick
Frederick
Reply to  Saninn Salas Diaz
6 years ago

am on Android , no console error
Please, is my path correct?
Coz i have tried the same path with fileOpener and it worked

Frederick
Frederick
Reply to  Frederick
6 years ago

i have printed the path and got this :
file:///storage/emulated/0/Android/data/io.ionic.starter/files/pdf_name.pdf

Frederick
Frederick
Reply to  Saninn Salas Diaz
6 years ago

ok thanks. will check

Rob
Rob
6 years ago

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
amenkey
5 years ago

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

Jo Cool
Jo Cool
5 years ago

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

Kees Schouten
Kees Schouten
5 years ago

Thanks! Great starting point.

kishan
kishan
5 years ago

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

kishan
kishan
Reply to  Saninn Salas Diaz
5 years ago

Thanks I got it

Luca
Luca
5 years ago

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

khaidaloo
khaidaloo
5 years ago

Hi, I’m having an issue
=={Uncaught (in promise): DataCloneError: The object could not be cloned. postMessage@http://localhost:8100/build/vendor.js:131007:7 sendStreamRequest@http://localhost:8100/build/vendor.js:130824:7 error@http://localhost:8100/build/vendor.js:130867:9 WorkerTransport_setupMessageHandler/</sink.onPull/<@http://localhost:8100/build/vendor.js:138918:13 F</l</t.prototype.invoke@http://localhost:8100/build/polyfills.js:3:14974 onInvoke@http://localhost:8100/build/vendor.js:5134:24 F</l</t.prototype.invoke@http://localhost:8100/build/polyfills.js:3:14901 F</c</r.prototype.run@http://localhost:8100/build/polyfills.js:3:10124 f/<@http://localhost:8100/build/polyfills.js:3:20240 F</l</t.prototype.invokeTask@http://localhost:8100/build/polyfills.js:3:15649 onInvokeTask@http://localhost:8100/build/vendor.js:5125:24 F</l</t.prototype.invokeTask@http://localhost:8100/build/polyfills.js:3:15562 F</c</r.prototype.runTask@http://localhost:8100/build/polyfills.js:3:10815 o@http://localhost:8100/build/polyfills.js:3:7887 F</h</e.invokeTask@http://localhost:8100/build/polyfills.js:3:16823 p@http://localhost:8100/build/polyfills.js:2:27646 v@http://localhost:8100/build/polyfills.js:2:27967}

please help me

Naruto
Naruto
5 years ago

I am working show and zoom pinch pdf in ionic by libary PDFJS.
How to zoom multi-page canvas by pdfjs in IONIC ???
Help me! Thanks.
Sorry my English !

Naruto
Naruto
5 years ago

Hi Saninn, thanks a lot for show how you did the pdf viewer! I’m having an issue, How to pinch zoom at a cursor point with gesture in IONIC. Tks!

DAMINI VYAS
DAMINI VYAS
5 years ago

Hey, what is the role of the text-container, Like I may omit it if I don’t want to display any text after the pdf? Or is it something related to the pdf?

DAMINI VYAS
DAMINI VYAS
Reply to  Saninn Salas Diaz
5 years ago

Thank you so much for the reply!
🙂

Mustajab Jafry
Mustajab Jafry
5 years ago

Thanks for the great tutorial.
Can you give a demo to show a toolbar when pdf document is taped. I want to implement search, page up and page down and horizontal scrolling functionality.

Salvation
Salvation
5 years ago

hello diaz,
thanks for an excellent tutorials i need some help in adding and removing text selection with varying colors from pdf. i will appreciate if you can give me any pointer thanks

Enrico Didan
Enrico Didan
5 years ago

hi, i want to ask
how if file from FileChooser in native ionic ? how to show it in pdf?

ajay verma
ajay verma
5 years ago

Able to view pdf on browser using ionic serve ,but not able to view pdf on device(Testing on android right now). Please see below for error msg.Sorry for preview incomplete comment polyfills.js:3 Fetch API cannot load file:///android_asset/www/assets/imgs/11.pdf. URL scheme “file” is not supported. (anonymous) @ polyfills.js:3 t @ 0.js:1 value… Read more »

Ajay verma
Ajay verma
Reply to  Saninn Salas Diaz
5 years ago

No I’m not using any plugin to load files

Ajay verma
Ajay verma
Reply to  Saninn Salas Diaz
5 years ago

I’m just using getfile() method of file plugin,and passing path of file into loadpdf() function.

Ajay verma
Ajay verma
Reply to  Saninn Salas Diaz
5 years ago

Yes,and if I’m using readAsDataUrl() (which gives base 64 string of pdf) and passing that into getdocument() I’m able to view pdf.But its taking to much time to generate base64 string and then view that.Does there any other way to view pdf

ajay verma
ajay verma
Reply to  Saninn Salas Diaz
5 years ago

Both are not working
1-filepath giving me same path as file:///……..
2-webview.convertfilesrc() method giving the error object(..) is not function error

Joe
Joe
4 years ago

I can not import * as PDFJS from “pdfjs-dist/webpack.js”. I checked and saw file webpack.js is existed in node_module, but i can not import it in ts file

rizalrm
rizalrm
4 years ago

Hi Sanin,
thank you so much, you have share your knowledge
i have problem when i load file from URL, example i want view pdf document from http://www.africau.edu/images/default/sample.pdf. The error is “Error: PDFDocument: stream must have data”

David
David
4 years ago

Hi Saninn,

Do you have any screenshots of how documents are displayed in the viewer?

AlinaaRein
AlinaaRein
4 years ago

Pretty cool!

Last edited 3 years ago by Saninn Salas Diaz
TerenceGravy
TerenceGravy
4 years ago

7 Places You Can Park Overnight & Sleep On A Road Trip — ROAD TRIP USA Campervan Hire Auckland However there’s another way of visiting the united states that blows others out of your water, and that’s by using a tour in the campervan. FYI, Mighway supplies a compulsory insurance… Read more »

Brucesor
Brucesor
4 years ago
Raphael Pinheiro
Raphael Pinheiro
4 years ago

Hi Saninn, thank you very much for the tuto! I’m having an issue, do you know how to fix it?

Error

Arash
Arash
1 year ago

Hello @Saninn, first of all, thanks for your great post I am using ionic 6, and I got this Error:  ./node_modules/pdfjs-dist/build/pdf.js:15177:21-48 – Warning: Module not found: Error: Can’t resolve ‘zlib’ in ‘C:\MyData\IOT\lastproject\node_modules\pdfjs-dist\build’ BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default. This is no… Read more »