PDF.JS is a wonderful script. It handles most, if not all the PDF loading and viewing functionalities leaving you to just integrate it and voila! Loaded PDF.

Now, a common feature that some users may want is the capability to create annotations. The thing is, with PDF.JS you cannot. The only way for this is to make your own.

I am not going to post my code here for doing that but this post will guide you on what resources you need in order to accomplish this and where in certain parts of the PDF.JS code you need to modify and/or add code to suit your requirements.

Here is a screenshot of the one I made with some shape annotations and icon annotations.

pdfjs_annotation1

1) ADD ANOTHER CANVAS ON TOP OF THE PDF.JS PAGE CANVAS TO DRAW ANNOTATIONS.

It is worth noting that every page in the PDF.JS PDF document has its own canvas element where drawing takes place if the PDF page contains images. You cannot do your annotation drawing here as that would mean erasing the PDF content. To avoid the hassle of going deep within PDF.JS code, the quickest alternative would be to create another canvas and place it on top.

The div and canvas hierarchy goes like this: pageContainer[page#] > .canvasWrapper > [canvas_element].

In order to get the same attributes when the pages are rotated or scaled, use JQuery’s clone() method to clone the canvas element within .canvasWrapper and then append clones to every page that starts with pageContainer[page#] > .canvasWrapper.

As for annotations, a good place to start is to search in Google on articles guiding users how to create shapes in an HTML5 Canvas element. There are many pre-made script samples that already lets you create, move and resize shapes.

There is one thing worth mentioning that once you add this canvas to the page, you will also have to set the z-index attribute so that it will not be placed behind the PDF pages.

I used a z-index value 1000.

2) ADD A CUSTOM FUNCTION IN VIEWER.JS UNDER EVENT PAGERENDERED

Look for the keyword pagerendered and call your custom function within the document.addEventListener(‘pagerendered’) block.

This is crucial because whenever a page gets rendered because of scaling, rotation or on first loading the PDF, the pagerendered event will always get triggered.

Within your custom function, you can then do the cloning of the canvas for your annotation and load existing annotations and drawing them to the canvas.

3) CREATE A CUSTOM CLASS TO STORE ANNOTATION DETAILS AND A CLASS THAT CONTAINS A CANVAS OBJECT THAT LISTENS TO MOUSE EVENTS.

For example, let us call the custom class CanvasHolder. This will reference the canvas object that you cloned from every page’s canvasWrapper. It is best to create a new CanvasHolder object for every PDF page.

The canvas object must listen to mouse events like onmouseup, onmousemove and onmousedown as this is where all your annotation features will take place.

4) STORE AN ORIGINAL BOUND FOR EVERY ANNOTATION AND A DIFFERENT BOUND FOR DISPLAY PURPOSES

I am no Math wizard so in my case, I decided to store 2 rectangle bounds for every annotation. One is the original that is the equivalent of scale value 1.0. The other is for display purposes so whatever scale value is chosen by the user, the display rectangle bound will use the original rectangle bound’s values and multiply it with the current scale value of PDF.JS.

If you create a new annotation and the scale value is not 1.0, you also have to recompute such that the annotation data saved must be the original rectangle bound’s value.

5) HOW TO SELECT TEXT

Since another canvas layer is placed on top for annotation drawing, chances are you might want to select some text but when you click on it, there is no way to do so because the annotation canvas layer is blocking the PDF page.

The trick here is to use a CSS attribute called pointer-event. In Javascript, you can call it like this:

The values can be auto or none. When setting it to none, the canvas layer is still visible but any mouse events will not be captured by the canvas object. Instead, it will pass through and PDF page will then capture those events.

Pretty slick, right?

6) HOW TO SCALE ANNOTATIONS

Scaling occurs when the zoom in and zoom out buttons are clicked or if a PDF is first loaded. Within the custom function that you make that gets called in the pagerendered event, the best way to add existing annotations of the PDF is to add the annotations according to the last scale value chosen by the user then have some sort of rotate function and rotate it based on the rotation value (90, 180, 240, 360|0 degrees).

For scaling annotations based on the current scale value, you only have to multiply the annotation’s width and height against the current scale value using

Here is a screenshot of the PDF page when rotated and scaled at 130%.

pdfjs_annotation2

7) HOW TO ROTATE ANNOTATIONS

As mentioned before, the original rectangle bound values is very useful as this can be your starting point to use the values and do some math rotating them in 90, 180, 240 and 360 degrees.

You only need to provide 2 functions for rotating right and left. Regardless if the rotation was done clockwise or counter clockwise, when you compute for the degree value, use the function Math.abs() in Javascript as it will convert any negative value to positive.

That should lessen your code in computing for the new coordinates during rotation as you only have to about 3 degrees namely, 90, 180 and 240. Remember, 360 is considered 0 (the normal orientation).

You also need to take into account the current scale value of the PDF in order for annotations to be placed correctly in the x,y space when rotated.

I think that these are the main points that need to be tackled in order for you to be able to create clean code on how to create annotations in PDF.JS.

While it may be not be easy to visualize understand since there is not even one block of code here, once you inspect the DOM tree and observe the changes when you scale, rotate and view the PDF, you will be able to understand how it works behind the scenes.

The rest should be easy …

Here is a video of what I did in action.

Here is a video of more annotations as well as a right sidebar list syncing the actions of annotations done in the canvas with the list.

I wanted to use PNG for my buttons but considering that I suck when it comes to graphics, and I do not have the time to surf and learn how to make one with normal and pressed button states, I was lucky to chance upon the Android Button Maker website.

Now, if you only are into those simple and minimalistic styled buttons, this website will help you easily create buttons without using images. Yes, only XML code is needed.

What’s more, when you want to set something like choosing a color, the results are shown in real time. Even the source code too!

The developer did a very good job with this. There are detailed instructions on how to use the XML files in the layout which can also help developers new to Android.

So I would like to say kudos to his work. The developer who made this definitely helped save me quite a lot of time. If there is some easier and quicker way to make a simple button, why make an image, right? It will only add up to memory consumption too ;).

Visit http://angrytools.com/android/button/

I have to admit, you need to dig in a little Math here. Drawing an equilateral triangle in a Canvas is possible using a Path object or use vertices to set up the 3 points.

However, to do automatic calculation, the best possible way to do this would be to either input 2 points and have the method calculate the 3rd point. In my case, I was able to create a method using only 1 point and specifying the distance.

Remember, an equilateral triangle has the same length on all 3 sides so my method’s other parameter entails having to declare the size per side.

And I was able to make a method where the middle point will be calculated depending on the desired location of the developer.

This is the result of the triangle that the method generates. It returns a Path object where, depending on the direction that you choose will point to that direction.

You may notice there is a 3rd parameter of type Direction. It is just an enum class that I created. You can change it if you want. But if you want to use the same class, here is the code for the Direction enum class.

If you want to create a triangle that faces downward like in the 3rd image above with the number 9, you can do it like this.

Related Posts Plugin for WordPress, Blogger...