Patch PDF Viewer Change Orientation

Introduction
The PDF viewer on the Palm Pre is capable of displaying documents in landscape mode as well as in portrait mode, however it seems that only portrait mode is enabled by default. I took this as an opportunity to figure out how the Pre handles orientation changes, and also how it interacts with native-code objects embedded in Mojo applications.

While the official SDK docs have not been released yet, we can make some inferences about the behavior of screen rotation and embedded objects by examining code in the existing applications found in the recovery image. Since the PDF viewer does not have rotation support by default, I examined a program that does: the web browser. I started by grepping for "rotation" and "orientation" within the code, and found that there is a function called "orientationChanged" in the page-assistant controller for the application. However, I found nowhere in the code where this function was defined.

This leads one to believe that orientationChanged is a function defined as part of the base controller object of Mojo. This is easily confirmed by digging around in /usr/palm/frameworks/mojo and doing a few greps.

Edit By jacmorel: This patch has been made obsolete by the 1.2 update. I have placed new instructions in the discussion section of this page. The original author can use it to update this page.

Step 1: Allow the PDF viewer to respond to orientation changes.
When we're viewing the document, the scene is being controlled by document-assistant.js, which is by default located in:

/usr/palm/applications/com.palm.app.pdfviewer/app/controllers/document-assistant.js.

Looking at this file, we see that a DocumentAssistant object is created to handle this. Following the model of the Browser example, I simply override the orientationChanged function of the DocumentAssistant object that's created. It doesn't seem to explicitly derive from mojo controllers, but JavaScript uses non-conventional models for extension and inheritance, and mojo also does some magic in the background, so I figure I'll give it a try to see what happens.

Since the dosetup function creates the embedded PDF viewer object and inserts it, I figure I can simply call that again. However, the dosetup function depends on an element called "install_object_here", which it replaces with the PDF viewer object. Since we'll be subsequently replacing the PDF viewer object on new rotations, we need to make sure that the object still has an ID so it can be located and replaced. The current setup creates the PDF element with no HTML element ID.

Add a new object attribute line to the dosetup function. You can put it right after the new_obj height attribute (line 718).

new_obj.setAttribute('id',"install_object_here");

Step 2: Allow the PDF viewer to respond to orientation changes.
We now have to create the orientation change handler.

Add this code to the very end of the file after the "});" (approx. line 1248).

The comments explain what is happening. DocumentAssistant.prototype.orientationChanged = function(orientation) { //Prevent infinite loops if(this._orientation === orientation) { return; }  this._orientation = orientation;

//Replace the PDF viewer with an empty div to prevent glitching during the rotate. var el = this.controller.get('install_object_here'); var em = this.controller.document.createElement('div'); em.id = 'install_object_here'; //Make sure to preserve the element ID  el.parentNode.replaceChild(em, el); //Replace the PDF viewer with an empty div

//Signal to the windowing system to do the rotate this.controller.window.PalmSystem.setWindowOrientation(orientation);

//Re-render the PDF object this.dosetup; }

Before doing the rotation, we replace the PDF rendering object with an empty div. This is because rotating the screen while a PDF rendering object is visible causes weird glitchy appearances. It still works, but it's ugly. Remove the object just before rotating looks much cleaner.

Step 3: Preserve the page state.
Now, we just have one other important problem to clean up. When you rotate, the view jumps back to page one, instead of staying on the page you were viewing. To do this, we use a hook that the object establishes. From what I can see, it looks like native code objects can define controller function names that it can call back to. Two that are used by the PDF Viewer are "DocumentOpened" and "FirstDrawComplete". We'll use DocumentOpened here: we don't want to jump through pages until the Document has been parsed and is available. So, in the DocumentOpened function

We then add this code below the show page count function (line 433).

Edit By Silvertoken: The following code has been updated for 1.1.0 compatibility. The executeJump function requires a page number to jump to.

this.executeJump(this.currentpage);

Edit By Matt Clark: I think you want to becareful with the two prior lines of code. I followed this mod and then when I used a hyperlink inside a pdf to go to a page it just got stuck in a infinite loop and I had to take the battery out to get the palm to restart.

jumppages is a controller-available variable that's used to pass the number of pages to jump to the execute command. It looks like this was done because executeJump is often called within a timer context. I'm not sure, but I'm guessing that they're using this method to avoid creating unnecessary closures.

The reasoning for the math is: executeJump calls a PDF event that jumps a relative number of pages. Since you're already on page 1, you want to jump forward 1 page to get to page 2, 2 pages to get to page 3, etc. Or, in general, currentpage - 1.

Once you've added these two lines, the view will jump immediately to the page you were last viewing, when you rotate.

Now you've got rotatable PDFs!

In Closing
In doing this mod, you can get a feel for how Mojo apps handle screen orientation changes. There's also a short example of getting events from native code objects embedded in your Mojo app.

There's one remaining TODO: it would be nice to correctly preserve the zoom level through rotates. To do this, I'll need to understand how the gestureStart, gestureChange, and gestureEnd events work. This will come soon!

Acknowledgements
Thanks to jblebrun for the mod and writeup.

Patch
(Added by ansky26.) Here is a patch encompassing all the above for post v1.1.0. I took the diff after verifying that everything still works, and it does. v1.1.0 just offset the changes by a few lines.

(Corrected by Silvertoken) Corrected the executeJump function to be compatible with v1.1.0

--- a/usr/palm/applications/com.palm.app.pdfviewer/app/controllers/document-assistant.js.orig	Fri Jul 31 22:07:25 2009 +++ b/usr/palm/applications/com.palm.app.pdfviewer/app/controllers/document-assistant.js	Fri Jul 31 22:06:53 2009 @@ -444,6 +444,9 @@ 		this.controller.setMenuVisible(Mojo.Menu.commandMenu, true); this.showPageCnt; + +       this.executeJump(this.currentpage); },    doRender: function  { @@ -739,6 +742,7 @@ 		var height = this.controller.window.innerHeight - headerHeight; new_obj.setAttribute('width', width); new_obj.setAttribute('height', height); +       new_obj.setAttribute('id',"install_object_here"); new_obj.setAttribute('file', this.filename); @@ -1269,3 +1273,24 @@    }, }); + +DocumentAssistant.prototype.orientationChanged = function(orientation) { +   //Prevent infinite loops +   if(this._orientation === orientation) { +       return; +   } +   this._orientation = orientation; + +   //Replace the PDF viewer with an empty div to prevent glitching during the rotate. +   var el = this.controller.get('install_object_here'); +   var em = this.controller.document.createElement('div'); +   em.id = 'install_object_here'; //Make sure to preserve the element ID +   el.parentNode.replaceChild(em, el); //Replace the PDF viewer with an empty div + +   //Signal to the windowing system to do the rotate +   this.controller.window.PalmSystem.setWindowOrientation(orientation); + +   //Re-render the PDF object +   this.dosetup; +} +