Using PDF.JS with Ionic 3.x
(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:
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;
});
}
}
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.
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!
Hi, i’m getting “PDFDocumentProxy not found”, I think i need to add the pdf.js worker to ionic, any tips?
I only had to change:
pdfDocument: PDFDocumentProxy;
to
pdfDocument: PDFJSViewer.PDFDocumentProxy;
and everything worked.
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!
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 »
Hi, did you get this error with others PDFs? Like the one PDFJS uses as example.
No, just with this livro.pdf, it’s a book with 35mb.
I need to load it in the PDF viewer
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.
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 »
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.
Thanks
It is giving error that
”
File URL not supported
”
when running on device. PDF does not render
That sounds like a problem with the way you get the path to your file. What are you using as Path?
and how to make it responsive?
PDF.JS has scale function. Or you could just redraw the complete page on orientation/size change.
how to do this exactly?
var scale = 1.5;
var viewport = page.getViewport(scale);
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
https://mozilla.github.io/pdf.js/examples/
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 »
Sorry I do not know that error but I have found this:
Source: Github
Hi there, do you know if it’s possible create annotate. I’ve been looking for, but without success.
Sadly no. The documentation about it was so difficult to follow than we ended up developing our own Annotation system.
can i get that annotation layer source code through any blog url or through email?
[email protected]
Sadly no. It is proprietary code.
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
What is the problem exactly?
i have a pdf file located at
cordova.file.externalDataDirectory+'pdf_name.pdf
‘ , meaning instead ofthis.loadPdf('assets/pdf1.pdf')
i havethis.loadPdf(cordova.file.externalDataDirectory+'pdf_name.pdf')
.but it can’t load. it is giving blank page
Is this on Android or iOS? Don’t you get any console error?
am on Android , no console error
Please, is my path correct?
Coz i have tried the same path with fileOpener and it worked
i have printed the path and got this :
file:///storage/emulated/0/Android/data/io.ionic.starter/files/pdf_name.pdf
I recommend you to use the Ionic Native wrapper for this plugin. You can find it here. See it that helps. That is what I used.
ok thanks. will check
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!
Hi. I have updated the tutorial. Give it a try.
BTW, sadly the lib is not really Angular friendly and I think the typings are not up-to-date.
If you get some warnings that are false positives you can disable them writing
//@ts-ignore
in the line before the warning.thank for the tuto. l got cannot resolve symbol PDFJS. Please help
I Have updated the tutorial. Check if it works for you now.
If the content of pdf is big, I cannot scroll horizontally. How do i enable scroll and zoom?
You could try this answer from stackoverflow.com
Thanks! Great starting point.
Bro, it is displaying only first page of the PDF, how to fix this?
You have to load each page with the
loadPage
function. Be aware! Each page will eat a little bit more of your RAM.Thanks I got it
Hi, i create my project ionic, load only frist page pdf, why?
Tnx
You have to load each page with the
loadPage
function. Be aware! Each page will eat a little bit more of your RAMHi, 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
That is usually an specific PDF error. I can not help you with this because it is a broad error. Take a look here: https://github.com/mozilla/pdf.js/issues
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 !
Sorry I can not help you, I didn’t use the multipage option because their memory Fingerprint was too high for low end devices.
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!
This video from Josh Morony will help you : https://www.youtube.com/watch?v=NL1rDMeXsuc
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?
PDFJS renders the document as a canvas image, that means there is no text there it is all an image.
The
textContainer
is filled with transparentdivs
containing the text of the pdf. There the user can select it to copy and so on.Thank you so much for the reply!
🙂
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.
Hi, sorry that is too specific and I am not currently working with PDFJS.
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
Hi Salvation. There is an Annotation layer for PDFJS here : https://www.npmjs.com/package/pdf-annotate (although we had problems with it and ended up developing and implementing our own annotation layer).
hi, i want to ask
how if file from FileChooser in native ionic ? how to show it in pdf?
Sorry I didn’t implement this part of the app in this project, I suppose you pass the uri that you obtains with
FileChooser
tothis.loadPdf()
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 »
Are you using the
File Chooser
plugin to load your files?No I’m not using any plugin to load files
That error says that the web view is not able to access the file (this is for security reasons) maybe try to load the file with an
XMLRequest
I’m just using getfile() method of file plugin,and passing path of file into loadpdf() function.
Have you tried with
resolveLocalFilesystemUrl
?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
Yes. You need to get the correct path to your file. using file//:... does not work becase if it worked almost every app could read any file from your android device. You need to resolve the correct path to your file. Use the File Path Plugin BTW look at this:… Read more »
Both are not working
1-filepath giving me same path as file:///……..
2-webview.convertfilesrc() method giving the error object(..) is not function error
There is plugin that converts that to a device path. I do not remember the name though.
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
node_modules
doesn’t exist when you compiles your app. Put it in theassets
folder.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”
That usually means that the app could not read the PDF file. Probably it is because some CORS issue. I would try it to download the file first and then open it.
Hi Saninn,
Do you have any screenshots of how documents are displayed in the viewer?
Hi! Sadly no at the moment. I did this in my last job and the app was the client internal use. But as I remember it was how you see PDF using Google drive. But with your customized tools bar.
Pretty cool!
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 »
obau.ru
Hi Saninn, thank you very much for the tuto! I’m having an issue, do you know how to fix it?
Hi, sorry for the late reply, I am getting a lot of spam on this post.
That doesn’t look like a problem that comes from this package. Are you sure this problem just appears when PDF.Js is installed?
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 »