diff --git a/Makefile b/Makefile
index 3cc423350..d4833a561 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ DEFAULT_TESTS := test_manifest.json
 DEFAULT_PYTHON := python2.7
 
 EXTENSION_SRC := ./extensions/
-EXTENSION_BASE_VERSION := 4bb289ec499013de66eb421737a4dbb4a9273eda
+EXTENSION_BASE_VERSION := f0f0418a9c6637981fe1182b9212c2d592774c7d
 FIREFOX_EXTENSION_NAME := pdf.js.xpi
 FIREFOX_AMO_EXTENSION_NAME := pdf.js.amo.xpi
 CHROME_EXTENSION_NAME := pdf.js.crx
@@ -20,6 +20,7 @@ all: bundle
 PDF_JS_FILES = \
   core.js \
   util.js \
+  api.js \
   canvas.js \
   obj.js \
   function.js \
@@ -74,7 +75,8 @@ bundle: | $(BUILD_DIR)
 	@cd src; \
 	cat $(PDF_JS_FILES) > all_files.tmp; \
 	sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \
-	sed -i.bak "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%h" -n 1`/" ../$(BUILD_TARGET); \
+	cp ../$(BUILD_TARGET) ../$(BUILD_TARGET).bak; \
+	sed "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%h" -n 1`/" ../$(BUILD_TARGET).bak > ../$(BUILD_TARGET); \
 	rm -f ../$(BUILD_TARGET).bak; \
 	rm -f *.tmp; \
 	cd ..
@@ -184,7 +186,7 @@ web: | production extension compiler pages-repo
 # and deletions.
 pages-repo: | $(BUILD_DIR)
 	@if [ ! -d "$(GH_PAGES)" ]; then \
-	git clone -b gh-pages $(REPO) $(GH_PAGES); \
+	git clone --depth 1 -b gh-pages $(REPO) $(GH_PAGES); \
 	rm -rf $(GH_PAGES)/*; \
 	fi;
 	@mkdir -p $(GH_PAGES)/web;
@@ -211,7 +213,7 @@ pages-repo: | $(BUILD_DIR)
 # copy of the pdf.js source.
 CONTENT_DIR := content
 BUILD_NUMBER := `git log --format=oneline $(EXTENSION_BASE_VERSION).. | wc -l | awk '{print $$1}'`
-PDFJSSCRIPT_VERSION := 0.2.$(BUILD_NUMBER)
+PDFJSSCRIPT_VERSION := 0.3.$(BUILD_NUMBER)
 EXTENSION_WEB_FILES = \
 	web/images \
 	web/viewer.css \
@@ -273,25 +275,34 @@ extension: | production
 	@cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/
 	# Modify the viewer so it does all the extension only stuff.
 	@cd $(FIREFOX_BUILD_CONTENT)/web; \
-	sed -i.bak '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html; \
-	sed -i.bak '/PDFJSSCRIPT_REMOVE_CORE/d' viewer.html; \
-	sed -i.bak '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html; \
-	sed -i.bak '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html; \
+	cp viewer-snippet-firefox-extension.html viewer-snippet-firefox-extension.html.bak; \
+	sed '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html.bak > viewer-snippet-firefox-extension.html; \
+	cp viewer.html viewer.html.bak; \
+	sed '/PDFJSSCRIPT_REMOVE_CORE/d' viewer.html.bak > viewer.html; \
+	cp viewer.html viewer.html.bak; \
+	sed '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html.bak > viewer.html; \
+	cp viewer.html viewer.html.bak; \
+	sed '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html.bak > viewer.html; \
 	rm -f *.bak;
 	# We don't need pdf.js anymore since its inlined
 	@rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/;
 	# Update the build version number
-	@sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf
-	@sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf.in
-	@sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/update.rdf
-	@sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/README.mozilla
+	cp $(FIREFOX_BUILD_DIR)/install.rdf $(FIREFOX_BUILD_DIR)/install.rdf.bak
+	@sed "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf.bak > $(FIREFOX_BUILD_DIR)/install.rdf
+	cp $(FIREFOX_BUILD_DIR)/install.rdf.in $(FIREFOX_BUILD_DIR)/install.rdf.in.bak
+	@sed "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf.in.bak > $(FIREFOX_BUILD_DIR)/install.rdf.in
+	cp $(FIREFOX_BUILD_DIR)/update.rdf $(FIREFOX_BUILD_DIR)/update.rdf.bak
+	@sed "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/update.rdf.bak > $(FIREFOX_BUILD_DIR)/update.rdf
+	cp $(FIREFOX_BUILD_DIR)/README.mozilla $(FIREFOX_BUILD_DIR)/README.mozilla.bak
+	@sed "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/README.mozilla.bak > $(FIREFOX_BUILD_DIR)/README.mozilla
 	@rm -f $(FIREFOX_BUILD_DIR)/*.bak
 	@find $(FIREFOX_BUILD_DIR) -name ".*" -delete
 	# Create the xpi
 	@cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES)
 	@echo "extension created: " $(FIREFOX_EXTENSION_NAME)
 	# Build the amo extension too (remove the updateUrl)
-	@sed -i.bak "/updateURL/d" $(FIREFOX_BUILD_DIR)/install.rdf
+	cp $(FIREFOX_BUILD_DIR)/install.rdf $(FIREFOX_BUILD_DIR)/install.rdf.bak
+	@sed "/updateURL/d" $(FIREFOX_BUILD_DIR)/install.rdf.bak > $(FIREFOX_BUILD_DIR)/install.rdf
 	@rm -f $(FIREFOX_BUILD_DIR)/*.bak
 	@cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_AMO_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES)
 	@echo "AMO extension created: " $(FIREFOX_AMO_EXTENSION_NAME)
diff --git a/examples/acroforms/forms.js b/examples/acroforms/forms.js
index 6ec92766d..868825fc7 100644
--- a/examples/acroforms/forms.js
+++ b/examples/acroforms/forms.js
@@ -9,7 +9,7 @@
 
 var formFields = {};
 
-function setupForm(div, content, scale) {
+function setupForm(div, content, viewport) {
   function bindInputItem(input, item) {
     if (input.name in formFields) {
       var value = formFields[input.name];
@@ -27,16 +27,20 @@ function setupForm(div, content, scale) {
   }
   function createElementWithStyle(tagName, item) {
     var element = document.createElement(tagName);
-    element.style.left = (item.x * scale) + 'px';
-    element.style.top = (item.y * scale) + 'px';
-    element.style.width = Math.ceil(item.width * scale) + 'px';
-    element.style.height = Math.ceil(item.height * scale) + 'px';
+    var rect = Util.normalizeRect(
+      viewport.convertToViewportRectangle(item.rect));
+    element.style.left = Math.floor(rect[0]) + 'px';
+    element.style.top = Math.floor(rect[1]) + 'px';
+    element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+    element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
     return element;
   }
   function assignFontStyle(element, item) {
     var fontStyles = '';
-    if ('fontSize' in item)
-      fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px;';
+    if ('fontSize' in item) {
+      fontStyles += 'font-size: ' + Math.round(item.fontSize *
+        viewport.fontScale) + 'px;';
+    }
     switch (item.textAlignment) {
       case 0:
         fontStyles += 'text-align: left;';
@@ -51,83 +55,88 @@ function setupForm(div, content, scale) {
     element.setAttribute('style', element.getAttribute('style') + fontStyles);
   }
 
-  var items = content.getAnnotations();
-  for (var i = 0; i < items.length; i++) {
-    var item = items[i];
-    switch (item.type) {
-      case 'Widget':
-        if (item.fieldType != 'Tx' && item.fieldType != 'Btn' &&
-            item.fieldType != 'Ch')
-          break;
-        var inputDiv = createElementWithStyle('div', item);
-        inputDiv.className = 'inputHint';
-        div.appendChild(inputDiv);
-        var input;
-        if (item.fieldType == 'Tx') {
-          input = createElementWithStyle('input', item);
-        }
-        if (item.fieldType == 'Btn') {
-          input = createElementWithStyle('input', item);
-          if (item.flags & 32768) {
-            input.type = 'radio';
-             // radio button is not supported
-          } else if (item.flags & 65536) {
-            input.type = 'button';
-            // pushbutton is not supported
-          } else {
-            input.type = 'checkbox';
+  content.getAnnotations().then(function(items) {
+    for (var i = 0; i < items.length; i++) {
+      var item = items[i];
+      switch (item.type) {
+        case 'Widget':
+          if (item.fieldType != 'Tx' && item.fieldType != 'Btn' &&
+              item.fieldType != 'Ch')
+            break;
+          var inputDiv = createElementWithStyle('div', item);
+          inputDiv.className = 'inputHint';
+          div.appendChild(inputDiv);
+          var input;
+          if (item.fieldType == 'Tx') {
+            input = createElementWithStyle('input', item);
           }
-        }
-        if (item.fieldType == 'Ch') {
-          input = createElementWithStyle('select', item);
-          // select box is not supported
-        }
-        input.className = 'inputControl';
-        input.name = item.fullName;
-        input.title = item.alternativeText;
-        assignFontStyle(input, item);
-        bindInputItem(input, item);
-        div.appendChild(input);
-        break;
+          if (item.fieldType == 'Btn') {
+            input = createElementWithStyle('input', item);
+            if (item.flags & 32768) {
+              input.type = 'radio';
+               // radio button is not supported
+            } else if (item.flags & 65536) {
+              input.type = 'button';
+              // pushbutton is not supported
+            } else {
+              input.type = 'checkbox';
+            }
+          }
+          if (item.fieldType == 'Ch') {
+            input = createElementWithStyle('select', item);
+            // select box is not supported
+          }
+          input.className = 'inputControl';
+          input.name = item.fullName;
+          input.title = item.alternativeText;
+          assignFontStyle(input, item);
+          bindInputItem(input, item);
+          div.appendChild(input);
+          break;
+      }
     }
-  }
+  });
 }
 
 function renderPage(div, pdf, pageNumber, callback) {
-  var page = pdf.getPage(pageNumber);
-  var scale = 1.5;
+  pdf.getPage(pageNumber).then(function(page) {
+    var scale = 1.5;
+    var viewport = page.getViewport(scale);
 
-  var pageDisplayWidth = page.width * scale;
-  var pageDisplayHeight = page.height * scale;
+    var pageDisplayWidth = viewport.width;
+    var pageDisplayHeight = viewport.height;
 
-  var pageDivHolder = document.createElement('div');
-  pageDivHolder.className = 'pdfpage';
-  pageDivHolder.style.width = pageDisplayWidth + 'px';
-  pageDivHolder.style.height = pageDisplayHeight + 'px';
-  div.appendChild(pageDivHolder);
+    var pageDivHolder = document.createElement('div');
+    pageDivHolder.className = 'pdfpage';
+    pageDivHolder.style.width = pageDisplayWidth + 'px';
+    pageDivHolder.style.height = pageDisplayHeight + 'px';
+    div.appendChild(pageDivHolder);
 
-  // Prepare canvas using PDF page dimensions
-  var canvas = document.createElement('canvas');
-  var context = canvas.getContext('2d');
-  canvas.width = pageDisplayWidth;
-  canvas.height = pageDisplayHeight;
-  pageDivHolder.appendChild(canvas);
+    // Prepare canvas using PDF page dimensions
+    var canvas = document.createElement('canvas');
+    var context = canvas.getContext('2d');
+    canvas.width = pageDisplayWidth;
+    canvas.height = pageDisplayHeight;
+    pageDivHolder.appendChild(canvas);
 
 
-  // Render PDF page into canvas context
-  page.startRendering(context, callback);
+    // Render PDF page into canvas context
+    var renderContext = {
+      canvasContext: context,
+      viewport: viewport
+    };
+    page.render(renderContext).then(callback);
 
-  // Prepare and populate form elements layer
-  var formDiv = document.createElement('div');
-  pageDivHolder.appendChild(formDiv);
+    // Prepare and populate form elements layer
+    var formDiv = document.createElement('div');
+    pageDivHolder.appendChild(formDiv);
 
-  setupForm(formDiv, page, scale);
+    setupForm(formDiv, page, viewport);
+  });
 }
 
-PDFJS.getPdf(pdfWithFormsPath, function getPdfForm(data) {
-  // Instantiate PDFDoc with PDF data
-  var pdf = new PDFJS.PDFDoc(data);
-  
+// Fetch the PDF document from the URL using promices
+PDFJS.getDocument(pdfWithFormsPath).then(function getPdfForm(pdf) {
   // Rendering all pages starting from first
   var viewer = document.getElementById('viewer');
   var pageNumber = 1;
diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html
index 8a9053f78..858ad649f 100644
--- a/examples/acroforms/index.html
+++ b/examples/acroforms/index.html
@@ -6,6 +6,7 @@
   <!-- In production, change the content of PDFJS.workerSrc below -->
   <script type="text/javascript" src="../../src/core.js"></script>
   <script type="text/javascript" src="../../src/util.js"></script>
+  <script type="text/javascript" src="../../src/api.js"></script>
   <script type="text/javascript" src="../../src/canvas.js"></script>
   <script type="text/javascript" src="../../src/obj.js"></script>
   <script type="text/javascript" src="../../src/function.js"></script>
diff --git a/examples/helloworld/hello.js b/examples/helloworld/hello.js
index 45e61eb6f..7bf18d65b 100644
--- a/examples/helloworld/hello.js
+++ b/examples/helloworld/hello.js
@@ -7,25 +7,31 @@
 
 'use strict';
 
-PDFJS.getPdf('helloworld.pdf', function getPdfHelloWorld(data) {
-  //
-  // Instantiate PDFDoc with PDF data
-  //
-  var pdf = new PDFJS.PDFDoc(data);
-  var page = pdf.getPage(1);
-  var scale = 1.5;
+//
+// Fetch the PDF document from the URL using promices
+//
+PDFJS.getDocument('helloworld.pdf').then(function(pdf) {
+  // Using promise to fetch the page
+  pdf.getPage(1).then(function(page) {
+    var scale = 1.5;
+    var viewport = page.getViewport(scale);
 
-  //
-  // Prepare canvas using PDF page dimensions
-  //
-  var canvas = document.getElementById('the-canvas');
-  var context = canvas.getContext('2d');
-  canvas.height = page.height * scale;
-  canvas.width = page.width * scale;
+    //
+    // Prepare canvas using PDF page dimensions
+    //
+    var canvas = document.getElementById('the-canvas');
+    var context = canvas.getContext('2d');
+    canvas.height = viewport.height;
+    canvas.width = viewport.width;
 
-  //
-  // Render PDF page into canvas context
-  //
-  page.startRendering(context);
+    //
+    // Render PDF page into canvas context
+    //
+    var renderContext = {
+      canvasContext: context,
+      viewport: viewport
+    };
+    page.render(renderContext);
+  });
 });
 
diff --git a/examples/helloworld/index.html b/examples/helloworld/index.html
index c6af616e6..c9df98809 100644
--- a/examples/helloworld/index.html
+++ b/examples/helloworld/index.html
@@ -6,6 +6,7 @@
   <!-- In production, change the content of PDFJS.workerSrc below -->
   <script type="text/javascript" src="../../src/core.js"></script>
   <script type="text/javascript" src="../../src/util.js"></script>
+  <script type="text/javascript" src="../../src/api.js"></script>
   <script type="text/javascript" src="../../src/canvas.js"></script>
   <script type="text/javascript" src="../../src/obj.js"></script>
   <script type="text/javascript" src="../../src/function.js"></script>
diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js
index 4467abc6b..f4b5b7712 100644
--- a/extensions/firefox/components/PdfStreamConverter.js
+++ b/extensions/firefox/components/PdfStreamConverter.js
@@ -153,7 +153,7 @@ PdfStreamConverter.prototype = {
                     'resource://pdf.js/web/viewer.html', null, null);
 
     var listener = this.listener;
-    // Proxy all the requst observer calls, when it gets to onStopRequst
+    // Proxy all the request observer calls, when it gets to onStopRequest
     // we can get the dom window.
     var proxy = {
       onStartRequest: function() {
diff --git a/make.js b/make.js
index e0975fec8..bcd7f433d 100755
--- a/make.js
+++ b/make.js
@@ -79,6 +79,7 @@ target.bundle = function() {
   var SRC_FILES =
        ['core.js',
         'util.js',
+        'api.js',
         'canvas.js',
         'obj.js',
         'function.js',
@@ -175,8 +176,8 @@ var EXTENSION_WEB_FILES =
        'web/viewer.js',
        'web/viewer.html',
        'web/viewer-production.html'],
-    EXTENSION_BASE_VERSION = '4bb289ec499013de66eb421737a4dbb4a9273eda',
-    EXTENSION_VERSION_PREFIX = '0.2.',
+    EXTENSION_BASE_VERSION = 'f0f0418a9c6637981fe1182b9212c2d592774c7d',
+    EXTENSION_VERSION_PREFIX = '0.3.',
     EXTENSION_BUILD_NUMBER,
     EXTENSION_VERSION;
 
diff --git a/src/api.js b/src/api.js
new file mode 100644
index 000000000..3d97dacd2
--- /dev/null
+++ b/src/api.js
@@ -0,0 +1,590 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+/**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * is used, which means it must follow the same origin rules that any XHR does
+ * e.g. No cross domain requests without CORS.
+ *
+ * @param {string|TypedAray} source Either a url to a PDF is located or a
+ * typed array already populated with data.
+ * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
+ */
+PDFJS.getDocument = function getDocument(source) {
+  var promise = new PDFJS.Promise();
+  var transport = new WorkerTransport(promise);
+  if (typeof source === 'string') {
+    // fetch url
+    PDFJS.getPdf(
+      {
+        url: source,
+        progress: function getPDFProgress(evt) {
+          if (evt.lengthComputable)
+            promise.progress({
+              loaded: evt.loaded,
+              total: evt.total
+            });
+        },
+        error: function getPDFError(e) {
+          promise.reject('Unexpected server response of ' +
+            e.target.status + '.');
+        }
+      },
+      function getPDFLoad(data) {
+        transport.sendData(data);
+      });
+  } else {
+    // assuming the source is array, instantiating directly from it
+    transport.sendData(source);
+  }
+  return promise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ */
+var PDFDocumentProxy = (function() {
+  function PDFDocumentProxy(pdfInfo, transport) {
+    this.pdfInfo = pdfInfo;
+    this.transport = transport;
+  }
+  PDFDocumentProxy.prototype = {
+    /**
+     * @return {number} Total number of pages the PDF contains.
+     */
+    get numPages() {
+      return this.pdfInfo.numPages;
+    },
+    /**
+     * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+     * unique.
+     */
+    get fingerprint() {
+      return this.pdfInfo.fingerprint;
+    },
+    /**
+     * @param {number} The page number to get. The first page is 1.
+     * @return {Promise} A promise that is resolved with a {PDFPageProxy}
+     * object.
+     */
+    getPage: function(number) {
+      return this.transport.getPage(number);
+    },
+    /**
+     * @return {Promise} A promise that is resolved with a lookup table for
+     * mapping named destinations to reference numbers.
+     */
+    getDestinations: function() {
+      var promise = new PDFJS.Promise();
+      var destinations = this.pdfInfo.destinations;
+      promise.resolve(destinations);
+      return promise;
+    },
+    /**
+     * @return {Promise} A promise that is resolved with an {array} that is a
+     * tree outline (if it has one) of the PDF. The tree is in the format of:
+     * [
+     *  {
+     *   title: string,
+     *   bold: boolean,
+     *   italic: boolean,
+     *   color: rgb array,
+     *   dest: dest obj,
+     *   items: array of more items like this
+     *  },
+     *  ...
+     * ].
+     */
+    getOutline: function() {
+      var promise = new PDFJS.Promise();
+      var outline = this.pdfInfo.outline;
+      promise.resolve(outline);
+      return promise;
+    },
+    /**
+     * @return {Promise} A promise that is resolved with an {object} that has
+     * info and metadata properties.  Info is an {object} filled with anything
+     * available in the information dictionary and similarly metadata is a
+     * {Metadata} object with information from the metadata section of the PDF.
+     */
+    getMetadata: function() {
+      var promise = new PDFJS.Promise();
+      var info = this.pdfInfo.info;
+      var metadata = this.pdfInfo.metadata;
+      promise.resolve({
+        info: info,
+        metadata: metadata ? new PDFJS.Metadata(metadata) : null
+      });
+      return promise;
+    },
+    destroy: function() {
+      this.transport.destroy();
+    }
+  };
+  return PDFDocumentProxy;
+})();
+
+var PDFPageProxy = (function PDFPageProxyClosure() {
+  function PDFPageProxy(pageInfo, transport) {
+    this.pageInfo = pageInfo;
+    this.transport = transport;
+    this.stats = new StatTimer();
+    this.stats.enabled = !!globalScope.PDFJS.enableStats;
+    this.objs = transport.objs;
+    this.renderInProgress = false;
+  }
+  PDFPageProxy.prototype = {
+    /**
+     * @return {number} Page number of the page. First page is 1.
+     */
+    get pageNumber() {
+      return this.pageInfo.pageIndex + 1;
+    },
+    /**
+     * @return {number} The number of degrees the page is rotated clockwise.
+     */
+    get rotate() {
+      return this.pageInfo.rotate;
+    },
+    /**
+     * @return {object} The reference that points to this page. It has 'num' and
+     * 'gen' properties.
+     */
+    get ref() {
+      return this.pageInfo.ref;
+    },
+    /**
+     * @return {array} An array of the visible portion of the PDF page in the
+     * user space units - [x1, y1, x2, y2].
+     */
+    get view() {
+      return this.pageInfo.view;
+    },
+    /**
+     * @param {number} scale The desired scale of the viewport.
+     * @param {number} rotate Degrees to rotate the viewport. If omitted this
+     * defaults to the page rotation.
+     * @return {PageViewport} Contains 'width' and 'height' properties along
+     * with transforms required for rendering.
+     */
+    getViewport: function(scale, rotate) {
+      if (arguments.length < 2)
+        rotate = this.rotate;
+      return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
+    },
+    /**
+     * @return {Promise} A promise that is resolved with an {array} of the
+     * annotation objects.
+     */
+    getAnnotations: function() {
+      if (this.annotationsPromise)
+        return this.annotationsPromise;
+
+      var promise = new PDFJS.Promise();
+      this.annotationsPromise = promise;
+      this.transport.getAnnotations(this.pageInfo.pageIndex);
+      return promise;
+    },
+    /**
+     * Begins the process of rendering a page to the desired context.
+     * @param {object} params A parameter object that supports:
+     * {
+     *   canvasContext(required): A 2D context of a DOM Canvas object.,
+     *   textLayer(optional): An object that has beginLayout, endLayout, and
+     *                        appendText functions.
+     * }.
+     * @return {Promise} A promise that is resolved when the page finishes
+     * rendering.
+     */
+    render: function(params) {
+      this.renderInProgress = true;
+
+      var promise = new Promise();
+      var stats = this.stats;
+      stats.time('Overall');
+      // If there is no displayReadyPromise yet, then the operatorList was never
+      // requested before. Make the request and create the promise.
+      if (!this.displayReadyPromise) {
+        this.displayReadyPromise = new Promise();
+        this.destroyed = false;
+
+        this.stats.time('Page Request');
+        this.transport.messageHandler.send('RenderPageRequest', {
+          pageIndex: this.pageNumber - 1
+        });
+      }
+
+      var self = this;
+      function complete(error) {
+        self.renderInProgress = false;
+        if (self.destroyed) {
+          delete self.operatorList;
+          delete self.displayReadyPromise;
+        }
+
+        if (error)
+          promise.reject(error);
+        else
+          promise.resolve();
+      };
+
+      // Once the operatorList and fonts are loaded, do the actual rendering.
+      this.displayReadyPromise.then(
+        function pageDisplayReadyPromise() {
+          if (self.destroyed) {
+            complete();
+            return;
+          }
+
+          var gfx = new CanvasGraphics(params.canvasContext,
+            this.objs, params.textLayer);
+          try {
+            this.display(gfx, params.viewport, complete);
+          } catch (e) {
+            complete(e);
+          }
+        }.bind(this),
+        function pageDisplayReadPromiseError(reason) {
+          complete(reason);
+        }
+      );
+
+      return promise;
+    },
+    /**
+     * For internal use only.
+     */
+    startRenderingFromOperatorList:
+      function PDFPageWrapper_startRenderingFromOperatorList(operatorList,
+                                                             fonts) {
+      var self = this;
+      this.operatorList = operatorList;
+
+      var displayContinuation = function pageDisplayContinuation() {
+        // Always defer call to display() to work around bug in
+        // Firefox error reporting from XHR callbacks.
+        setTimeout(function pageSetTimeout() {
+          self.displayReadyPromise.resolve();
+        });
+      };
+
+      this.ensureFonts(fonts,
+        function pageStartRenderingFromOperatorListEnsureFonts() {
+          displayContinuation();
+        }
+      );
+    },
+    /**
+     * For internal use only.
+     */
+    ensureFonts: function PDFPageWrapper_ensureFonts(fonts, callback) {
+      this.stats.time('Font Loading');
+      // Convert the font names to the corresponding font obj.
+      for (var i = 0, ii = fonts.length; i < ii; i++) {
+        fonts[i] = this.objs.objs[fonts[i]].data;
+      }
+
+      // Load all the fonts
+      FontLoader.bind(
+        fonts,
+        function pageEnsureFontsFontObjs(fontObjs) {
+          this.stats.timeEnd('Font Loading');
+
+          callback.call(this);
+        }.bind(this)
+      );
+    },
+    /**
+     * For internal use only.
+     */
+    display: function PDFPageWrapper_display(gfx, viewport, callback) {
+      var stats = this.stats;
+      stats.time('Rendering');
+
+      gfx.beginDrawing(viewport);
+
+      var startIdx = 0;
+      var length = this.operatorList.fnArray.length;
+      var operatorList = this.operatorList;
+      var stepper = null;
+      if (PDFJS.pdfBug && StepperManager.enabled) {
+        stepper = StepperManager.create(this.pageNumber - 1);
+        stepper.init(operatorList);
+        stepper.nextBreakPoint = stepper.getNextBreakPoint();
+      }
+
+      var self = this;
+      function next() {
+        startIdx =
+          gfx.executeOperatorList(operatorList, startIdx, next, stepper);
+        if (startIdx == length) {
+          gfx.endDrawing();
+          stats.timeEnd('Rendering');
+          stats.timeEnd('Overall');
+          if (callback) callback();
+        }
+      }
+      next();
+    },
+    /**
+     * Stub for future feature.
+     */
+    getTextContent: function() {
+      var promise = new PDFJS.Promise();
+      var textContent = 'page text'; // not implemented
+      promise.resolve(textContent);
+      return promise;
+    },
+    /**
+     * Stub for future feature.
+     */
+    getOperationList: function() {
+      var promise = new PDFJS.Promise();
+      var operationList = { // not implemented
+        dependencyFontsID: null,
+        operatorList: null
+      };
+      promise.resolve(operationList);
+      return promise;
+    },
+    /**
+     * Destroys resources allocated by the page.
+     */
+    destroy: function() {
+      this.destroyed = true;
+
+      if (!this.renderInProgress) {
+        delete this.operatorList;
+        delete this.displayReadyPromise;
+      }
+    }
+  };
+  return PDFPageProxy;
+})();
+/**
+ * For internal use only.
+ */
+var WorkerTransport = (function WorkerTransportClosure() {
+  function WorkerTransport(promise) {
+    this.workerReadyPromise = promise;
+    this.objs = new PDFObjects();
+
+    this.pageCache = [];
+    this.pagePromises = [];
+    this.fontsLoading = {};
+
+    // If worker support isn't disabled explicit and the browser has worker
+    // support, create a new web worker and test if it/the browser fullfills
+    // all requirements to run parts of pdf.js in a web worker.
+    // Right now, the requirement is, that an Uint8Array is still an Uint8Array
+    // as it arrives on the worker. Chrome added this with version 15.
+    if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
+      var workerSrc = PDFJS.workerSrc;
+      if (typeof workerSrc === 'undefined') {
+        error('No PDFJS.workerSrc specified');
+      }
+
+      try {
+        var worker;
+        if (PDFJS.isFirefoxExtension) {
+          // The firefox extension can't load the worker from the resource://
+          // url so we have to inline the script and then use the blob loader.
+          var bb = new MozBlobBuilder();
+          bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
+          var blobUrl = window.URL.createObjectURL(bb.getBlob());
+          worker = new Worker(blobUrl);
+        } else {
+          // Some versions of FF can't create a worker on localhost, see:
+          // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+          worker = new Worker(workerSrc);
+        }
+
+        var messageHandler = new MessageHandler('main', worker);
+        this.messageHandler = messageHandler;
+
+        messageHandler.on('test', function transportTest(supportTypedArray) {
+          if (supportTypedArray) {
+            this.worker = worker;
+            this.setupMessageHandler(messageHandler);
+          } else {
+            globalScope.PDFJS.disableWorker = true;
+            this.setupFakeWorker();
+          }
+        }.bind(this));
+
+        var testObj = new Uint8Array(1);
+        // Some versions of Opera throw a DATA_CLONE_ERR on
+        // serializing the typed array.
+        messageHandler.send('test', testObj);
+        return;
+      } catch (e) {
+        warn('The worker has been disabled.');
+      }
+    }
+    // Either workers are disabled, not supported or have thrown an exception.
+    // Thus, we fallback to a faked worker.
+    globalScope.PDFJS.disableWorker = true;
+    this.setupFakeWorker();
+  }
+  WorkerTransport.prototype = {
+    destroy: function WorkerTransport_destroy() {
+      if (this.worker)
+        this.worker.terminate();
+
+      this.pageCache = [];
+      this.pagePromises = [];
+    },
+    setupFakeWorker: function WorkerTransport_setupFakeWorker() {
+      // If we don't use a worker, just post/sendMessage to the main thread.
+      var fakeWorker = {
+        postMessage: function WorkerTransport_postMessage(obj) {
+          fakeWorker.onmessage({data: obj});
+        },
+        terminate: function WorkerTransport_terminate() {}
+      };
+
+      var messageHandler = new MessageHandler('main', fakeWorker);
+      this.setupMessageHandler(messageHandler);
+
+      // If the main thread is our worker, setup the handling for the messages
+      // the main thread sends to it self.
+      WorkerMessageHandler.setup(messageHandler);
+    },
+
+    setupMessageHandler:
+      function WorkerTransport_setupMessageHandler(messageHandler) {
+      this.messageHandler = messageHandler;
+
+      messageHandler.on('GetDoc', function transportDoc(data) {
+        var pdfInfo = data.pdfInfo;
+        var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+        this.pdfDocument = pdfDocument;
+        this.workerReadyPromise.resolve(pdfDocument);
+      }, this);
+
+      messageHandler.on('GetPage', function transportPage(data) {
+        var pageInfo = data.pageInfo;
+        var page = new PDFPageProxy(pageInfo, this);
+        this.pageCache[pageInfo.pageIndex] = page;
+        var promise = this.pagePromises[pageInfo.pageIndex];
+        promise.resolve(page);
+      }, this);
+
+      messageHandler.on('GetAnnotations', function transportAnnotations(data) {
+        var annotations = data.annotations;
+        var promise = this.pageCache[data.pageIndex].annotationsPromise;
+        promise.resolve(annotations);
+      }, this);
+
+      messageHandler.on('RenderPage', function transportRender(data) {
+        var page = this.pageCache[data.pageIndex];
+        var depFonts = data.depFonts;
+
+        page.stats.timeEnd('Page Request');
+        page.startRenderingFromOperatorList(data.operatorList, depFonts);
+      }, this);
+
+      messageHandler.on('obj', function transportObj(data) {
+        var id = data[0];
+        var type = data[1];
+        if (this.objs.hasData(id))
+          return;
+
+        switch (type) {
+          case 'JpegStream':
+            var imageData = data[2];
+            loadJpegStream(id, imageData, this.objs);
+            break;
+          case 'Image':
+            var imageData = data[2];
+            this.objs.resolve(id, imageData);
+            break;
+          case 'Font':
+            var name = data[2];
+            var file = data[3];
+            var properties = data[4];
+
+            if (file) {
+              // Rewrap the ArrayBuffer in a stream.
+              var fontFileDict = new Dict();
+              file = new Stream(file, 0, file.length, fontFileDict);
+            }
+
+            // At this point, only the font object is created but the font is
+            // not yet attached to the DOM. This is done in `FontLoader.bind`.
+            var font = new Font(name, file, properties);
+            this.objs.resolve(id, font);
+            break;
+          default:
+            error('Got unkown object type ' + type);
+        }
+      }, this);
+
+      messageHandler.on('PageError', function transportError(data) {
+        var page = this.pageCache[data.pageNum - 1];
+        if (page.displayReadyPromise)
+          page.displayReadyPromise.reject(data.error);
+        else
+          error(data.error);
+      }, this);
+
+      messageHandler.on('JpegDecode', function(data, promise) {
+        var imageData = data[0];
+        var components = data[1];
+        if (components != 3 && components != 1)
+          error('Only 3 component or 1 component can be returned');
+
+        var img = new Image();
+        img.onload = (function messageHandler_onloadClosure() {
+          var width = img.width;
+          var height = img.height;
+          var size = width * height;
+          var rgbaLength = size * 4;
+          var buf = new Uint8Array(size * components);
+          var tmpCanvas = createScratchCanvas(width, height);
+          var tmpCtx = tmpCanvas.getContext('2d');
+          tmpCtx.drawImage(img, 0, 0);
+          var data = tmpCtx.getImageData(0, 0, width, height).data;
+
+          if (components == 3) {
+            for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+              buf[j] = data[i];
+              buf[j + 1] = data[i + 1];
+              buf[j + 2] = data[i + 2];
+            }
+          } else if (components == 1) {
+            for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+              buf[j] = data[i];
+            }
+          }
+          promise.resolve({ data: buf, width: width, height: height});
+        }).bind(this);
+        var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+        img.src = src;
+      });
+    },
+
+    sendData: function WorkerTransport_sendData(data) {
+      this.messageHandler.send('GetDocRequest', data);
+    },
+
+    getPage: function WorkerTransport_getPage(pageNumber, promise) {
+      var pageIndex = pageNumber - 1;
+      if (pageIndex in this.pagePromises)
+        return this.pagePromises[pageIndex];
+      var promise = new PDFJS.Promise('Page ' + pageNumber);
+      this.pagePromises[pageIndex] = promise;
+      this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
+      return promise;
+    },
+
+    getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
+      this.messageHandler.send('GetAnnotationsRequest',
+        { pageIndex: pageIndex });
+    }
+  };
+  return WorkerTransport;
+
+})();
diff --git a/src/canvas.js b/src/canvas.js
index 8f29051fd..9d470fbec 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -241,27 +241,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
       'shadingFill': true
     },
 
-    beginDrawing: function CanvasGraphics_beginDrawing(mediaBox) {
-      var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
+    beginDrawing: function CanvasGraphics_beginDrawing(viewport) {
+      var transform = viewport.transform;
       this.ctx.save();
-      switch (mediaBox.rotate) {
-        case 0:
-          this.ctx.transform(1, 0, 0, -1, 0, ch);
-          break;
-        case 90:
-          this.ctx.transform(0, 1, 1, 0, 0, 0);
-          break;
-        case 180:
-          this.ctx.transform(-1, 0, 0, 1, cw, 0);
-          break;
-        case 270:
-          this.ctx.transform(0, -1, -1, 0, cw, ch);
-          break;
-      }
-      // Scale so that canvas units are the same as PDF user space units
-      this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
-      // Move the media left-top corner to the (0,0) canvas position
-      this.ctx.translate(-mediaBox.x, -mediaBox.y);
+      this.ctx.transform.apply(this.ctx, transform);
 
       if (this.textLayer)
         this.textLayer.beginLayout();
diff --git a/src/core.js b/src/core.js
index 15cd147e2..90a2c9733 100644
--- a/src/core.js
+++ b/src/core.js
@@ -63,8 +63,6 @@ var Page = (function PageClosure() {
   function Page(xref, pageNumber, pageDict, ref) {
     this.pageNumber = pageNumber;
     this.pageDict = pageDict;
-    this.stats = new StatTimer();
-    this.stats.enabled = !!globalScope.PDFJS.enableStats;
     this.xref = xref;
     this.ref = ref;
 
@@ -100,18 +98,10 @@ var Page = (function PageClosure() {
       return shadow(this, 'mediaBox', obj);
     },
     get view() {
+      var mediaBox = this.mediaBox;
       var cropBox = this.inheritPageProp('CropBox');
-      var view = {
-        x: 0,
-        y: 0,
-        width: this.width,
-        height: this.height
-      };
       if (!isArray(cropBox) || cropBox.length !== 4)
-        return shadow(this, 'view', view);
-
-      var mediaBox = this.mediaBox;
-      var offsetX = mediaBox[0], offsetY = mediaBox[1];
+        return shadow(this, 'view', mediaBox);
 
       // From the spec, 6th ed., p.963:
       // "The crop, bleed, trim, and art boxes should not ordinarily
@@ -119,42 +109,13 @@ var Page = (function PageClosure() {
       // effectively reduced to their intersection with the media box."
       cropBox = Util.intersect(cropBox, mediaBox);
       if (!cropBox)
-        return shadow(this, 'view', view);
-
-      var tl = this.rotatePoint(cropBox[0] - offsetX, cropBox[1] - offsetY);
-      var br = this.rotatePoint(cropBox[2] - offsetX, cropBox[3] - offsetY);
-      view.x = Math.min(tl.x, br.x);
-      view.y = Math.min(tl.y, br.y);
-      view.width = Math.abs(tl.x - br.x);
-      view.height = Math.abs(tl.y - br.y);
+        return shadow(this, 'view', mediaBox);
 
-      return shadow(this, 'view', view);
+      return shadow(this, 'view', cropBox);
     },
     get annotations() {
       return shadow(this, 'annotations', this.inheritPageProp('Annots'));
     },
-    get width() {
-      var mediaBox = this.mediaBox;
-      var rotate = this.rotate;
-      var width;
-      if (rotate == 0 || rotate == 180) {
-        width = (mediaBox[2] - mediaBox[0]);
-      } else {
-        width = (mediaBox[3] - mediaBox[1]);
-      }
-      return shadow(this, 'width', width);
-    },
-    get height() {
-      var mediaBox = this.mediaBox;
-      var rotate = this.rotate;
-      var height;
-      if (rotate == 0 || rotate == 180) {
-        height = (mediaBox[3] - mediaBox[1]);
-      } else {
-        height = (mediaBox[2] - mediaBox[0]);
-      }
-      return shadow(this, 'height', height);
-    },
     get rotate() {
       var rotate = this.inheritPageProp('Rotate') || 0;
       // Normalize rotation so it's a multiple of 90 and between 0 and 270
@@ -170,43 +131,19 @@ var Page = (function PageClosure() {
       return shadow(this, 'rotate', rotate);
     },
 
-    startRenderingFromOperatorList:
-      function Page_startRenderingFromOperatorList(operatorList, fonts) {
-      var self = this;
-      this.operatorList = operatorList;
-
-      var displayContinuation = function pageDisplayContinuation() {
-        // Always defer call to display() to work around bug in
-        // Firefox error reporting from XHR callbacks.
-        setTimeout(function pageSetTimeout() {
-          self.displayReadyPromise.resolve();
-        });
-      };
-
-      this.ensureFonts(fonts,
-        function pageStartRenderingFromOperatorListEnsureFonts() {
-          displayContinuation();
-        }
-      );
-    },
-
     getOperatorList: function Page_getOperatorList(handler, dependency) {
-      if (this.operatorList) {
-        // content was compiled
-        return this.operatorList;
-      }
-
-      this.stats.time('Build IR Queue');
-
       var xref = this.xref;
       var content = this.content;
       var resources = this.resources;
       if (isArray(content)) {
         // fetching items
+        var streams = [];
         var i, n = content.length;
         for (i = 0; i < n; ++i)
-          content[i] = xref.fetchIfRef(content[i]);
-        content = new StreamsSequenceStream(content);
+          streams.push(xref.fetchIfRef(content[i]));
+        content = new StreamsSequenceStream(streams);
+      } else if (isStream(content)) {
+        content.reset();
       } else if (!content) {
         // replacing non-existent page content with empty one
         content = new Stream(new Uint8Array(0));
@@ -215,82 +152,9 @@ var Page = (function PageClosure() {
       var pe = this.pe = new PartialEvaluator(
                                 xref, handler, 'p' + this.pageNumber + '_');
 
-      this.operatorList = pe.getOperatorList(content, resources, dependency);
-      this.stats.timeEnd('Build IR Queue');
-      return this.operatorList;
+      return pe.getOperatorList(content, resources, dependency);
     },
 
-    ensureFonts: function Page_ensureFonts(fonts, callback) {
-      this.stats.time('Font Loading');
-      // Convert the font names to the corresponding font obj.
-      for (var i = 0, ii = fonts.length; i < ii; i++) {
-        fonts[i] = this.objs.objs[fonts[i]].data;
-      }
-
-      // Load all the fonts
-      FontLoader.bind(
-        fonts,
-        function pageEnsureFontsFontObjs(fontObjs) {
-          this.stats.timeEnd('Font Loading');
-
-          callback.call(this);
-        }.bind(this)
-      );
-    },
-
-    display: function Page_display(gfx, callback) {
-      var stats = this.stats;
-      stats.time('Rendering');
-      var xref = this.xref;
-      var resources = this.resources;
-      var mediaBox = this.mediaBox;
-      assertWellFormed(isDict(resources), 'invalid page resources');
-
-      gfx.xref = xref;
-      gfx.res = resources;
-      gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
-            width: this.width,
-            height: this.height,
-            rotate: this.rotate });
-
-      var startIdx = 0;
-      var length = this.operatorList.fnArray.length;
-      var operatorList = this.operatorList;
-      var stepper = null;
-      if (PDFJS.pdfBug && StepperManager.enabled) {
-        stepper = StepperManager.create(this.pageNumber);
-        stepper.init(operatorList);
-        stepper.nextBreakPoint = stepper.getNextBreakPoint();
-      }
-
-      var self = this;
-      function next() {
-        startIdx =
-          gfx.executeOperatorList(operatorList, startIdx, next, stepper);
-        if (startIdx == length) {
-          gfx.endDrawing();
-          stats.timeEnd('Rendering');
-          stats.timeEnd('Overall');
-          if (callback) callback();
-        }
-      }
-      next();
-    },
-    rotatePoint: function Page_rotatePoint(x, y, reverse) {
-      var rotate = reverse ? (360 - this.rotate) : this.rotate;
-      switch (rotate) {
-        case 180:
-          return {x: this.width - x, y: y};
-        case 90:
-          return {x: this.width - y, y: this.height - x};
-        case 270:
-          return {x: y, y: x};
-        case 360:
-        case 0:
-        default:
-          return {x: x, y: this.height - y};
-      }
-    },
     getLinks: function Page_getLinks() {
       var links = [];
       var annotations = pageGetAnnotations();
@@ -342,15 +206,10 @@ var Page = (function PageClosure() {
         if (!isName(subtype))
           continue;
         var rect = annotation.get('Rect');
-        var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
-        var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
 
         var item = {};
         item.type = subtype.name;
-        item.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
-        item.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
-        item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
-        item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
+        item.rect = rect;
         switch (subtype.name) {
           case 'Link':
             var a = annotation.get('A');
@@ -424,7 +283,8 @@ var Page = (function PageClosure() {
             var title = annotation.get('T');
             item.content = stringToPDFString(content || '');
             item.title = stringToPDFString(title || '');
-            item.name = annotation.get('Name').name;
+            item.name = !annotation.has('Name') ? 'Note' :
+              annotation.get('Name').name;
             break;
           default:
             TODO('unimplemented annotation type: ' + subtype.name);
@@ -433,37 +293,6 @@ var Page = (function PageClosure() {
         items.push(item);
       }
       return items;
-    },
-    startRendering: function Page_startRendering(ctx, callback, textLayer)  {
-      var stats = this.stats;
-      stats.time('Overall');
-      // If there is no displayReadyPromise yet, then the operatorList was never
-      // requested before. Make the request and create the promise.
-      if (!this.displayReadyPromise) {
-        this.pdf.startRendering(this);
-        this.displayReadyPromise = new Promise();
-      }
-
-      // Once the operatorList and fonts are loaded, do the actual rendering.
-      this.displayReadyPromise.then(
-        function pageDisplayReadyPromise() {
-          var gfx = new CanvasGraphics(ctx, this.objs, textLayer);
-          try {
-            this.display(gfx, callback);
-          } catch (e) {
-            if (callback)
-              callback(e);
-            else
-              error(e);
-          }
-        }.bind(this),
-        function pageDisplayReadPromiseError(reason) {
-          if (callback)
-            callback(reason);
-          else
-            error(reason);
-        }
-      );
     }
   };
 
@@ -471,20 +300,20 @@ var Page = (function PageClosure() {
 })();
 
 /**
- * The `PDFDocModel` holds all the data of the PDF file. Compared to the
+ * The `PDFDocument` holds all the data of the PDF file. Compared to the
  * `PDFDoc`, this one doesn't have any job management code.
- * Right now there exists one PDFDocModel on the main thread + one object
+ * Right now there exists one PDFDocument on the main thread + one object
  * for each worker. If there is no worker support enabled, there are two
- * `PDFDocModel` objects on the main thread created.
+ * `PDFDocument` objects on the main thread created.
  */
-var PDFDocModel = (function PDFDocModelClosure() {
-  function PDFDocModel(arg, callback) {
+var PDFDocument = (function PDFDocumentClosure() {
+  function PDFDocument(arg, callback) {
     if (isStream(arg))
       init.call(this, arg);
     else if (isArrayBuffer(arg))
       init.call(this, new Stream(arg));
     else
-      error('PDFDocModel: Unknown argument type');
+      error('PDFDocument: Unknown argument type');
   }
 
   function init(stream) {
@@ -510,7 +339,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
     return true; /* found */
   }
 
-  PDFDocModel.prototype = {
+  PDFDocument.prototype = {
     get linearization() {
       var length = this.stream.length;
       var linearization = false;
@@ -571,7 +400,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
     },
     // Find the header, remove leading garbage and setup the stream
     // starting from the header.
-    checkHeader: function PDFDocModel_checkHeader() {
+    checkHeader: function PDFDocument_checkHeader() {
       var stream = this.stream;
       stream.reset();
       if (find(stream, '%PDF-', 1024)) {
@@ -581,7 +410,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
       }
       // May not be a PDF file, continue anyway.
     },
-    setup: function PDFDocModel_setup(ownerPassword, userPassword) {
+    setup: function PDFDocument_setup(ownerPassword, userPassword) {
       this.checkHeader();
       var xref = new XRef(this.stream,
                           this.startXRef,
@@ -595,7 +424,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
       // shadow the prototype getter
       return shadow(this, 'numPages', num);
     },
-    getDocumentInfo: function PDFDocModel_getDocumentInfo() {
+    getDocumentInfo: function PDFDocument_getDocumentInfo() {
       var info;
       if (this.xref.trailer.has('Info')) {
         var infoDict = this.xref.trailer.get('Info');
@@ -609,7 +438,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
 
       return shadow(this, 'getDocumentInfo', info);
     },
-    getFingerprint: function PDFDocModel_getFingerprint() {
+    getFingerprint: function PDFDocument_getFingerprint() {
       var xref = this.xref, fileID;
       if (xref.trailer.has('ID')) {
         fileID = '';
@@ -630,251 +459,10 @@ var PDFDocModel = (function PDFDocModelClosure() {
 
       return shadow(this, 'getFingerprint', fileID);
     },
-    getPage: function PDFDocModel_getPage(n) {
+    getPage: function PDFDocument_getPage(n) {
       return this.catalog.getPage(n);
     }
   };
 
-  return PDFDocModel;
-})();
-
-var PDFDoc = (function PDFDocClosure() {
-  function PDFDoc(arg, callback) {
-    var stream = null;
-    var data = null;
-
-    if (isStream(arg)) {
-      stream = arg;
-      data = arg.bytes;
-    } else if (isArrayBuffer(arg)) {
-      stream = new Stream(arg);
-      data = arg;
-    } else {
-      error('PDFDoc: Unknown argument type');
-    }
-
-    this.data = data;
-    this.stream = stream;
-    this.pdfModel = new PDFDocModel(stream);
-    this.fingerprint = this.pdfModel.getFingerprint();
-    this.info = this.pdfModel.getDocumentInfo();
-    this.catalog = this.pdfModel.catalog;
-    this.objs = new PDFObjects();
-
-    this.pageCache = [];
-    this.fontsLoading = {};
-    this.workerReadyPromise = new Promise('workerReady');
-
-    // If worker support isn't disabled explicit and the browser has worker
-    // support, create a new web worker and test if it/the browser fullfills
-    // all requirements to run parts of pdf.js in a web worker.
-    // Right now, the requirement is, that an Uint8Array is still an Uint8Array
-    // as it arrives on the worker. Chrome added this with version 15.
-    if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
-      var workerSrc = PDFJS.workerSrc;
-      if (typeof workerSrc === 'undefined') {
-        error('No PDFJS.workerSrc specified');
-      }
-
-      try {
-        var worker;
-        if (PDFJS.isFirefoxExtension) {
-          // The firefox extension can't load the worker from the resource://
-          // url so we have to inline the script and then use the blob loader.
-          var bb = new MozBlobBuilder();
-          bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
-          var blobUrl = window.URL.createObjectURL(bb.getBlob());
-          worker = new Worker(blobUrl);
-        } else {
-          // Some versions of FF can't create a worker on localhost, see:
-          // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
-          worker = new Worker(workerSrc);
-        }
-
-        var messageHandler = new MessageHandler('main', worker);
-
-        messageHandler.on('test', function pdfDocTest(supportTypedArray) {
-          if (supportTypedArray) {
-            this.worker = worker;
-            this.setupMessageHandler(messageHandler);
-          } else {
-            globalScope.PDFJS.disableWorker = true;
-            this.setupFakeWorker();
-          }
-        }.bind(this));
-
-        var testObj = new Uint8Array(1);
-        // Some versions of Opera throw a DATA_CLONE_ERR on
-        // serializing the typed array.
-        messageHandler.send('test', testObj);
-        return;
-      } catch (e) {
-        warn('The worker has been disabled.');
-      }
-    }
-    // Either workers are disabled, not supported or have thrown an exception.
-    // Thus, we fallback to a faked worker.
-    globalScope.PDFJS.disableWorker = true;
-    this.setupFakeWorker();
-  }
-
-  PDFDoc.prototype = {
-    setupFakeWorker: function PDFDoc_setupFakeWorker() {
-      // If we don't use a worker, just post/sendMessage to the main thread.
-      var fakeWorker = {
-        postMessage: function PDFDoc_postMessage(obj) {
-          fakeWorker.onmessage({data: obj});
-        },
-        terminate: function PDFDoc_terminate() {}
-      };
-
-      var messageHandler = new MessageHandler('main', fakeWorker);
-      this.setupMessageHandler(messageHandler);
-
-      // If the main thread is our worker, setup the handling for the messages
-      // the main thread sends to it self.
-      WorkerMessageHandler.setup(messageHandler);
-    },
-
-
-    setupMessageHandler: function PDFDoc_setupMessageHandler(messageHandler) {
-      this.messageHandler = messageHandler;
-
-      messageHandler.on('page', function pdfDocPage(data) {
-        var pageNum = data.pageNum;
-        var page = this.pageCache[pageNum];
-        var depFonts = data.depFonts;
-
-        page.stats.timeEnd('Page Request');
-        page.startRenderingFromOperatorList(data.operatorList, depFonts);
-      }, this);
-
-      messageHandler.on('obj', function pdfDocObj(data) {
-        var id = data[0];
-        var type = data[1];
-
-        switch (type) {
-          case 'JpegStream':
-            var imageData = data[2];
-            loadJpegStream(id, imageData, this.objs);
-            break;
-          case 'Image':
-            var imageData = data[2];
-            this.objs.resolve(id, imageData);
-            break;
-          case 'Font':
-            var name = data[2];
-            var file = data[3];
-            var properties = data[4];
-
-            if (file) {
-              // Rewrap the ArrayBuffer in a stream.
-              var fontFileDict = new Dict();
-              file = new Stream(file, 0, file.length, fontFileDict);
-            }
-
-            // At this point, only the font object is created but the font is
-            // not yet attached to the DOM. This is done in `FontLoader.bind`.
-            var font = new Font(name, file, properties);
-            this.objs.resolve(id, font);
-            break;
-          default:
-            error('Got unkown object type ' + type);
-        }
-      }, this);
-
-      messageHandler.on('page_error', function pdfDocError(data) {
-        var page = this.pageCache[data.pageNum];
-        if (page.displayReadyPromise)
-          page.displayReadyPromise.reject(data.error);
-        else
-          error(data.error);
-      }, this);
-
-      messageHandler.on('jpeg_decode', function(data, promise) {
-        var imageData = data[0];
-        var components = data[1];
-        if (components != 3 && components != 1)
-          error('Only 3 component or 1 component can be returned');
-
-        var img = new Image();
-        img.onload = (function messageHandler_onloadClosure() {
-          var width = img.width;
-          var height = img.height;
-          var size = width * height;
-          var rgbaLength = size * 4;
-          var buf = new Uint8Array(size * components);
-          var tmpCanvas = createScratchCanvas(width, height);
-          var tmpCtx = tmpCanvas.getContext('2d');
-          tmpCtx.drawImage(img, 0, 0);
-          var data = tmpCtx.getImageData(0, 0, width, height).data;
-
-          if (components == 3) {
-            for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
-              buf[j] = data[i];
-              buf[j + 1] = data[i + 1];
-              buf[j + 2] = data[i + 2];
-            }
-          } else if (components == 1) {
-            for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
-              buf[j] = data[i];
-            }
-          }
-          promise.resolve({ data: buf, width: width, height: height});
-        }).bind(this);
-        var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
-        img.src = src;
-      });
-
-      setTimeout(function pdfDocFontReadySetTimeout() {
-        messageHandler.send('doc', this.data);
-        this.workerReadyPromise.resolve(true);
-      }.bind(this));
-    },
-
-    get numPages() {
-      return this.pdfModel.numPages;
-    },
-
-    startRendering: function PDFDoc_startRendering(page) {
-      // The worker might not be ready to receive the page request yet.
-      this.workerReadyPromise.then(function pdfDocStartRenderingThen() {
-        page.stats.time('Page Request');
-        this.messageHandler.send('page_request', page.pageNumber + 1);
-      }.bind(this));
-    },
-
-    getPage: function PDFDoc_getPage(n) {
-      if (this.pageCache[n])
-        return this.pageCache[n];
-
-      var page = this.pdfModel.getPage(n);
-      // Add a reference to the objects such that Page can forward the reference
-      // to the CanvasGraphics and so on.
-      page.objs = this.objs;
-      page.pdf = this;
-      return (this.pageCache[n] = page);
-    },
-
-    destroy: function PDFDoc_destroy() {
-      if (this.worker)
-        this.worker.terminate();
-
-      if (this.fontWorker)
-        this.fontWorker.terminate();
-
-      for (var n in this.pageCache)
-        delete this.pageCache[n];
-
-      delete this.data;
-      delete this.stream;
-      delete this.pdf;
-      delete this.catalog;
-    }
-  };
-
-  return PDFDoc;
+  return PDFDocument;
 })();
-
-globalScope.PDFJS.PDFDoc = PDFDoc;
-
diff --git a/src/evaluator.js b/src/evaluator.js
index 350ab20b2..e07394201 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -153,13 +153,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
         font = xref.fetchIfRef(font) || fontRes.get(fontName);
         assertWellFormed(isDict(font));
+        ++self.objIdCounter;
         if (!font.translated) {
           font.translated = self.translateFont(font, xref, resources,
                                                dependency);
           if (font.translated) {
             // keep track of each font we translated so the caller can
             // load them asynchronously before calling display on a page
-            loadedName = 'font_' + uniquePrefix + (++self.objIdCounter);
+            loadedName = 'font_' + uniquePrefix + self.objIdCounter;
             font.translated.properties.loadedName = loadedName;
             font.loadedName = loadedName;
 
@@ -466,7 +467,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
           args = [];
         } else if (obj != null) {
           assertWellFormed(args.length <= 33, 'Too many arguments');
-          args.push(obj);
+          args.push(obj instanceof Dict ? obj.getAll() : obj);
         }
       }
 
@@ -862,7 +863,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         properties.coded = true;
         var charProcs = dict.get('CharProcs').getAll();
         var fontResources = dict.get('Resources') || resources;
-        properties.resources = fontResources;
         properties.charProcOperatorList = {};
         for (var key in charProcs) {
           var glyphStream = charProcs[key];
diff --git a/src/fonts.js b/src/fonts.js
index 7fdab8fbb..7bd3ddd06 100644
--- a/src/fonts.js
+++ b/src/fonts.js
@@ -766,7 +766,6 @@ var Font = (function FontClosure() {
     this.name = name;
     this.coded = properties.coded;
     this.charProcOperatorList = properties.charProcOperatorList;
-    this.resources = properties.resources;
     this.sizes = [];
 
     var names = name.split('+');
@@ -1727,6 +1726,16 @@ var Font = (function FontClosure() {
         properties.glyphNames = glyphNames;
       }
 
+      function isOS2Valid(os2Table) {
+        var data = os2Table.data;
+        // usWinAscent == 0 makes font unreadable by windows
+        var usWinAscent = (data[74] << 8) | data[75];
+        if (usWinAscent == 0)
+          return false;
+
+        return true;
+      }
+
       // Check that required tables are present
       var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
                              'hmtx', 'maxp', 'name', 'post'];
@@ -1734,7 +1743,7 @@ var Font = (function FontClosure() {
       var header = readOpenTypeHeader(font);
       var numTables = header.numTables;
 
-      var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf;
+      var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf, os2;
       var tables = [];
       for (var i = 0; i < numTables; i++) {
         var table = readTableEntry(font);
@@ -1752,6 +1761,8 @@ var Font = (function FontClosure() {
             hmtx = table;
           else if (table.tag == 'head')
             head = table;
+          else if (table.tag == 'OS/2')
+            os2 = table;
 
           requiredTables.splice(index, 1);
         } else {
@@ -1767,7 +1778,7 @@ var Font = (function FontClosure() {
         tables.push(table);
       }
 
-      var numTables = header.numTables + requiredTables.length;
+      var numTables = tables.length + requiredTables.length;
 
       // header and new offsets. Table entry information is appended to the
       // end of file. The virtualOffset represents where to put the actual
@@ -1781,21 +1792,10 @@ var Font = (function FontClosure() {
       // of missing tables
       createOpenTypeHeader(header.version, ttf, numTables);
 
-      if (requiredTables.indexOf('OS/2') != -1) {
-        // extract some more font properties from the OpenType head and
-        // hhea tables; yMin and descent value are always negative
-        var override = {
-          unitsPerEm: int16([head.data[18], head.data[19]]),
-          yMax: int16([head.data[42], head.data[43]]),
-          yMin: int16([head.data[38], head.data[39]]) - 0x10000,
-          ascent: int16([hhea.data[4], hhea.data[5]]),
-          descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
-        };
-
-        tables.push({
-          tag: 'OS/2',
-          data: stringToArray(createOS2Table(properties, null, override))
-        });
+      // Invalid OS/2 can break the font for the Windows
+      if (os2 && !isOS2Valid(os2)) {
+        tables.splice(tables.indexOf(os2), 1);
+        os2 = null;
       }
 
       // Ensure the [h/v]mtx tables contains the advance width and
@@ -2076,6 +2076,23 @@ var Font = (function FontClosure() {
       }
       this.unicodeIsEnabled = unicodeIsEnabled;
 
+      if (!os2) {
+        // extract some more font properties from the OpenType head and
+        // hhea tables; yMin and descent value are always negative
+        var override = {
+          unitsPerEm: int16([head.data[18], head.data[19]]),
+          yMax: int16([head.data[42], head.data[43]]),
+          yMin: int16([head.data[38], head.data[39]]) - 0x10000,
+          ascent: int16([hhea.data[4], hhea.data[5]]),
+          descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
+        };
+
+        tables.push({
+          tag: 'OS/2',
+          data: stringToArray(createOS2Table(properties, glyphs, override))
+        });
+      }
+
       // Rewrite the 'post' table if needed
       if (requiredTables.indexOf('post') != -1) {
         tables.push({
diff --git a/src/image.js b/src/image.js
index 035e2f754..c8c19f9e5 100644
--- a/src/image.js
+++ b/src/image.js
@@ -15,7 +15,7 @@ var PDFImage = (function PDFImageClosure() {
       var colorSpace = dict.get('ColorSpace', 'CS');
       colorSpace = ColorSpace.parse(colorSpace, xref, res);
       var numComps = colorSpace.numComps;
-      handler.send('jpeg_decode', [image.getIR(), numComps], function(message) {
+      handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
         var data = message.data;
         var stream = new Stream(data, 0, data.length, image.dict);
         promise.resolve(stream);
diff --git a/src/obj.js b/src/obj.js
index 200b40a7f..c905a7dc5 100644
--- a/src/obj.js
+++ b/src/obj.js
@@ -37,51 +37,55 @@ var Dict = (function DictClosure() {
   // xref is optional
   function Dict(xref) {
     // Map should only be used internally, use functions below to access.
-    this.map = Object.create(null);
-    this.xref = xref;
-  }
+    var map = Object.create(null);
+
+    this.assignXref = function Dict_assingXref(newXref) {
+      xref = newXref;
+    };
 
-  Dict.prototype = {
     // automatically dereferences Ref objects
-    get: function Dict_get(key1, key2, key3) {
+    this.get = function Dict_get(key1, key2, key3) {
       var value;
-      var xref = this.xref;
-      if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map ||
+      if (typeof (value = map[key1]) != 'undefined' || key1 in map ||
           typeof key2 == 'undefined') {
-        return xref ? this.xref.fetchIfRef(value) : value;
+        return xref ? xref.fetchIfRef(value) : value;
       }
-      if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map ||
+      if (typeof (value = map[key2]) != 'undefined' || key2 in map ||
           typeof key3 == 'undefined') {
-        return xref ? this.xref.fetchIfRef(value) : value;
+        return xref ? xref.fetchIfRef(value) : value;
       }
-      value = this.map[key3] || null;
-      return xref ? this.xref.fetchIfRef(value) : value;
-    },
+      value = map[key3] || null;
+      return xref ? xref.fetchIfRef(value) : value;
+    };
+
     // no dereferencing
-    getRaw: function Dict_getRaw(key) {
-      return this.map[key];
-    },
+    this.getRaw = function Dict_getRaw(key) {
+      return map[key];
+    };
+
     // creates new map and dereferences all Refs
-    getAll: function Dict_getAll() {
+    this.getAll = function Dict_getAll() {
       var all = {};
-      for (var key in this.map)
-        all[key] = this.get(key);
+      for (var key in map) {
+        var obj = this.get(key);
+        all[key] = obj instanceof Dict ? obj.getAll() : obj;
+      }
       return all;
-    },
+    };
 
-    set: function Dict_set(key, value) {
-      this.map[key] = value;
-    },
+    this.set = function Dict_set(key, value) {
+      map[key] = value;
+    };
 
-    has: function Dict_has(key) {
-      return key in this.map;
-    },
+    this.has = function Dict_has(key) {
+      return key in map;
+    };
 
-    forEach: function Dict_forEach(callback) {
-      for (var key in this.map) {
+    this.forEach = function Dict_forEach(callback) {
+      for (var key in map) {
         callback(key, this.get(key));
       }
-    }
+    };
   };
 
   return Dict;
@@ -299,7 +303,7 @@ var XRef = (function XRefClosure() {
     this.entries = [];
     this.xrefstms = {};
     var trailerDict = this.readXRef(startXRef);
-    trailerDict.xref = this;
+    trailerDict.assignXref(this);
     this.trailer = trailerDict;
     // prepare the XRef cache
     this.cache = [];
diff --git a/src/parser.js b/src/parser.js
index 1c50d0f5f..2855018a6 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -249,9 +249,12 @@ var Parser = (function ParserClosure() {
       if (name == 'CCITTFaxDecode' || name == 'CCF') {
         return new CCITTFaxStream(stream, params);
       }
-      if (name == 'RunLengthDecode') {
+      if (name == 'RunLengthDecode' || name == 'RL') {
         return new RunLengthStream(stream);
       }
+      if (name == 'JBIG2Decode') {
+        error('JBIG2 image format is not currently supprted.');
+      }
       warn('filter "' + name + '" not supported yet');
       return stream;
     }
diff --git a/src/util.js b/src/util.js
index de7f3c1d5..63f6115a7 100644
--- a/src/util.js
+++ b/src/util.js
@@ -76,7 +76,7 @@ function stringToBytes(str) {
 
 var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
 
-var Util = (function UtilClosure() {
+var Util = PDFJS.Util = (function UtilClosure() {
   function Util() {}
 
   Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
@@ -97,6 +97,19 @@ var Util = (function UtilClosure() {
     return [xt, yt];
   };
 
+  Util.applyInverseTransform = function Util_applyTransform(p, m) {
+    var d = m[0] * m[3] - m[1] * m[2];
+    var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+    var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+    return [xt, yt];
+  };
+
+  Util.inverseTransform = function Util_inverseTransform(m) {
+    var d = m[0] * m[3] - m[1] * m[2];
+    return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
+      (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
+  };
+
   // Apply a generic 3d matrix M on a 3-vector v:
   //   | a b c |   | X |
   //   | d e f | x | Y |
@@ -165,7 +178,7 @@ var Util = (function UtilClosure() {
     }
 
     return result;
-  }
+  };
 
   Util.sign = function Util_sign(num) {
     return num < 0 ? -1 : 1;
@@ -174,6 +187,80 @@ var Util = (function UtilClosure() {
   return Util;
 })();
 
+var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
+  function PageViewport(viewBox, scale, rotate, offsetX, offsetY) {
+    // creating transform to convert pdf coordinate system to the normal
+    // canvas like coordinates taking in account scale and rotation
+    var centerX = (viewBox[2] + viewBox[0]) / 2;
+    var centerY = (viewBox[3] + viewBox[1]) / 2;
+    var rotateA, rotateB, rotateC, rotateD;
+    switch (rotate) {
+      case -180:
+      case 180:
+        rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
+        break;
+      case -270:
+      case 90:
+        rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
+        break;
+      case -90:
+      case 270:
+        rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
+        break;
+      case 360:
+      case 0:
+      default:
+        rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
+        break;
+    }
+    var offsetCanvasX, offsetCanvasY;
+    var width, height;
+    if (rotateA == 0) {
+      offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+      offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+      width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+      height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+    } else {
+      offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+      offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+      width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+      height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+    }
+    // creating transform for the following operations:
+    // translate(-centerX, -centerY), rotate and flip vertically,
+    // scale, and translate(offsetCanvasX, offsetCanvasY)
+    this.transform = [
+      rotateA * scale,
+      rotateB * scale,
+      rotateC * scale,
+      rotateD * scale,
+      offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+      offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+    ];
+
+    this.offsetX = offsetX;
+    this.offsetY = offsetY;
+    this.width = width;
+    this.height = height;
+    this.fontScale = scale;
+  }
+  PageViewport.prototype = {
+    convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+      return Util.applyTransform([x, y], this.transform);
+    },
+    convertToViewportRectangle:
+      function PageViewport_convertToViewportRectangle(rect) {
+      var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
+      var br = Util.applyTransform([rect[2], rect[3]], this.transform);
+      return [tl[0], tl[1], br[0], br[1]];
+    },
+    convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+      return Util.applyInverseTransform([x, y], this.transform);
+    }
+  };
+  return PageViewport;
+})();
+
 var PDFStringTranslateTable = [
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
   0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
@@ -275,7 +362,7 @@ function isPDFFunction(v) {
  * can be set. If any of these happens twice or the data is required before
  * it was set, an exception is throw.
  */
-var Promise = (function PromiseClosure() {
+var Promise = PDFJS.Promise = (function PromiseClosure() {
   var EMPTY_PROMISE = {};
 
   /**
@@ -297,6 +384,7 @@ var Promise = (function PromiseClosure() {
     }
     this.callbacks = [];
     this.errbacks = [];
+    this.progressbacks = [];
   };
   /**
    * Builds a promise that is resolved when all the passed in promises are
@@ -312,7 +400,7 @@ var Promise = (function PromiseClosure() {
       deferred.resolve(results);
       return deferred;
     }
-    for (var i = 0; i < unresolved; ++i) {
+    for (var i = 0, ii = promises.length; i < ii; ++i) {
       var promise = promises[i];
       promise.then((function(i) {
         return function(value) {
@@ -376,6 +464,13 @@ var Promise = (function PromiseClosure() {
       }
     },
 
+    progress: function Promise_progress(data) {
+      var callbacks = this.progressbacks;
+      for (var i = 0, ii = callbacks.length; i < ii; i++) {
+        callbacks[i].call(null, data);
+      }
+    },
+
     reject: function Promise_reject(reason) {
       if (this.isRejected) {
         error('A Promise can be rejected only once ' + this.name);
@@ -393,7 +488,7 @@ var Promise = (function PromiseClosure() {
       }
     },
 
-    then: function Promise_then(callback, errback) {
+    then: function Promise_then(callback, errback, progressback) {
       if (!callback) {
         error('Requiring callback' + this.name);
       }
@@ -410,6 +505,9 @@ var Promise = (function PromiseClosure() {
         if (errback)
           this.errbacks.push(errback);
       }
+
+      if (progressback)
+        this.progressbacks.push(progressback);
     }
   };
 
diff --git a/src/worker.js b/src/worker.js
index 42bd61050..25f3f52cd 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -85,14 +85,43 @@ var WorkerMessageHandler = {
       handler.send('test', data instanceof Uint8Array);
     });
 
-    handler.on('doc', function wphSetupDoc(data) {
+    handler.on('GetDocRequest', function wphSetupDoc(data) {
       // Create only the model of the PDFDoc, which is enough for
       // processing the content of the pdf.
-      pdfModel = new PDFDocModel(new Stream(data));
+      pdfModel = new PDFDocument(new Stream(data));
+      var doc = {
+        numPages: pdfModel.numPages,
+        fingerprint: pdfModel.getFingerprint(),
+        destinations: pdfModel.catalog.destinations,
+        outline: pdfModel.catalog.documentOutline,
+        info: pdfModel.getDocumentInfo(),
+        metadata: pdfModel.catalog.metadata
+      };
+      handler.send('GetDoc', {pdfInfo: doc});
     });
 
-    handler.on('page_request', function wphSetupPageRequest(pageNum) {
-      pageNum = parseInt(pageNum);
+    handler.on('GetPageRequest', function wphSetupGetPage(data) {
+      var pageNumber = data.pageIndex + 1;
+      var pdfPage = pdfModel.getPage(pageNumber);
+      var page = {
+        pageIndex: data.pageIndex,
+        rotate: pdfPage.rotate,
+        ref: pdfPage.ref,
+        view: pdfPage.view
+      };
+      handler.send('GetPage', {pageInfo: page});
+    });
+
+    handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
+      var pdfPage = pdfModel.getPage(data.pageIndex + 1);
+      handler.send('GetAnnotations', {
+        pageIndex: data.pageIndex,
+        annotations: pdfPage.getAnnotations()
+      });
+    });
+
+    handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
+      var pageNum = data.pageIndex + 1;
 
 
       // The following code does quite the same as
@@ -130,7 +159,7 @@ var WorkerMessageHandler = {
           };
         }
 
-        handler.send('page_error', {
+        handler.send('PageError', {
           pageNum: pageNum,
           error: e
         });
@@ -148,9 +177,8 @@ var WorkerMessageHandler = {
           fonts[dep] = true;
         }
       }
-
-      handler.send('page', {
-        pageNum: pageNum,
+      handler.send('RenderPage', {
+        pageIndex: data.pageIndex,
         operatorList: operatorList,
         depFonts: Object.keys(fonts)
       });
diff --git a/test/driver.js b/test/driver.js
index a1dc4b242..cd5ea49e7 100644
--- a/test/driver.js
+++ b/test/driver.js
@@ -10,7 +10,7 @@
 // Disable worker support for running test as
 //   https://github.com/mozilla/pdf.js/pull/764#issuecomment-2638944
 //   "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1."
-PDFJS.disableWorker = true;
+// PDFJS.disableWorker = true;
 
 var appPath, browser, canvas, currentTaskIdx, manifest, stdout;
 var inFlightRequests = 0;
@@ -100,13 +100,24 @@ function nextTask() {
 
   getPdf(task.file, function nextTaskGetPdf(data) {
     var failure;
+    function continuation() {
+      task.pageNum = task.firstPage || 1;
+      nextPage(task, failure);
+    }
     try {
-      task.pdfDoc = new PDFJS.PDFDoc(data);
+      var promise = PDFJS.getDocument(data);
+      promise.then(function(doc) {
+        task.pdfDoc = doc;
+        continuation();
+      }, function(e) {
+        failure = 'load PDF doc : ' + e;
+        continuation();
+      });
+      return;
     } catch (e) {
       failure = 'load PDF doc : ' + exceptionToString(e);
     }
-    task.pageNum = task.firstPage || 1;
-    nextPage(task, failure);
+    continuation();
   });
 }
 
@@ -163,45 +174,45 @@ function nextPage(task, loadError) {
       log(' loading page ' + task.pageNum + '/' + task.pdfDoc.numPages +
           '... ');
       var ctx = canvas.getContext('2d');
-      page = task.pdfDoc.getPage(task.pageNum);
-
-      var pdfToCssUnitsCoef = 96.0 / 72.0;
-      // using mediaBox for the canvas size
-      var pageWidth = page.width;
-      var pageHeight = page.height;
-      canvas.width = pageWidth * pdfToCssUnitsCoef;
-      canvas.height = pageHeight * pdfToCssUnitsCoef;
-      clear(ctx);
-
-      // using the text layer builder that does nothing to test
-      // text layer creation operations
-      var textLayerBuilder = {
-        beginLayout: function nullTextLayerBuilderBeginLayout() {},
-        endLayout: function nullTextLayerBuilderEndLayout() {},
-        appendText: function nullTextLayerBuilderAppendText(text, fontName,
-                                                            fontSize) {}
-      };
-
-      page.startRendering(
-        ctx,
-        function nextPageStartRendering(error) {
-          var failureMessage = false;
-          if (error)
-            failureMessage = 'render : ' + error.message;
-          snapshotCurrentPage(task, failureMessage);
+      task.pdfDoc.getPage(task.pageNum).then(function(page) {
+        var pdfToCssUnitsCoef = 96.0 / 72.0;
+        var viewport = page.getViewport(pdfToCssUnitsCoef);
+        canvas.width = viewport.width;
+        canvas.height = viewport.height;
+        clear(ctx);
+
+        // using the text layer builder that does nothing to test
+        // text layer creation operations
+        var textLayerBuilder = {
+          beginLayout: function nullTextLayerBuilderBeginLayout() {},
+          endLayout: function nullTextLayerBuilderEndLayout() {},
+          appendText: function nullTextLayerBuilderAppendText(text, fontName,
+                                                              fontSize) {}
+        };
+        var renderContext = {
+          canvasContext: ctx,
+          textLayer: textLayerBuilder,
+          viewport: viewport
+        };
+        var completeRender = (function(error) {
+          page.destroy();
+          snapshotCurrentPage(task, error);
+        });
+        page.render(renderContext).then(function() {
+          completeRender(false);
         },
-        textLayerBuilder
-      );
+        function(error) {
+          completeRender('render : ' + error);
+        });
+      },
+      function(error) {
+        snapshotCurrentPage(task, 'render : ' + error);
+      });
     } catch (e) {
       failure = 'page setup : ' + exceptionToString(e);
+      snapshotCurrentPage(task, failure);
     }
   }
-
-  if (failure) {
-    // Skip right to snapshotting if there was a failure, since the
-    // fonts might be in an inconsistent state.
-    snapshotCurrentPage(task, failure);
-  }
 }
 
 function snapshotCurrentPage(task, failure) {
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index 2a7c27875..d93dae609 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -31,4 +31,5 @@
 !issue1002.pdf
 !issue925.pdf
 !gradientfill.pdf
-
+!basicapi.pdf
+!mixedfonts.pdf
diff --git a/test/pdfs/basicapi.pdf b/test/pdfs/basicapi.pdf
new file mode 100644
index 000000000..31ffcfe9f
Binary files /dev/null and b/test/pdfs/basicapi.pdf differ
diff --git a/test/pdfs/mixedfonts.pdf b/test/pdfs/mixedfonts.pdf
new file mode 100644
index 000000000..82bdbec34
Binary files /dev/null and b/test/pdfs/mixedfonts.pdf differ
diff --git a/test/test.py b/test/test.py
index 4dc6ca8a9..368069aff 100644
--- a/test/test.py
+++ b/test/test.py
@@ -16,6 +16,7 @@ BROWSERLOG_FILE = 'browser.log'
 REFDIR = 'ref'
 TMPDIR = 'tmp'
 VERBOSE = False
+BROWSER_TIMEOUT = 60
 
 SERVER_HOST = "localhost"
 
@@ -74,7 +75,7 @@ class State:
     browsers = [ ]
     manifest = { }
     taskResults = { }
-    remaining = 0
+    remaining = { }
     results = { }
     done = False
     numErrors = 0
@@ -83,6 +84,7 @@ class State:
     numFBFFailures = 0
     numLoadFailures = 0
     eqLog = None
+    lastPost = { }
 
 class Result:
     def __init__(self, snapshot, failure, page):
@@ -180,6 +182,7 @@ class PDFTestHandler(BaseHTTPRequestHandler):
 
         result = json.loads(self.rfile.read(numBytes))
         browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot']
+        State.lastPost[browser] = int(time.time())
         taskResults = State.taskResults[browser][id]
         taskResults[round].append(Result(snapshot, failure, page))
 
@@ -199,9 +202,16 @@ class PDFTestHandler(BaseHTTPRequestHandler):
                   self.server.masterMode)
             # Please oh please GC this ...
             del State.taskResults[browser][id]
-            State.remaining -= 1
+            State.remaining[browser] -= 1
 
-        State.done = (0 == State.remaining)
+            checkIfDone()
+
+def checkIfDone():
+    State.done = True
+    for key in State.remaining:
+        if State.remaining[key] != 0:
+            State.done = False
+            return
 
 # Applescript hack to quit Chrome on Mac
 def tellAppToQuit(path, query):
@@ -376,6 +386,8 @@ def setUp(options):
 
     for b in testBrowsers:
         State.taskResults[b.name] = { }
+        State.remaining[b.name] = len(manifestList)
+        State.lastPost[b.name] = int(time.time())
         for item in manifestList:
             id, rounds = item['id'], int(item['rounds'])
             State.manifest[id] = item
@@ -384,8 +396,6 @@ def setUp(options):
                 taskResults.append([ ])
             State.taskResults[b.name][id] = taskResults
 
-    State.remaining = len(testBrowsers) * len(manifestList)
-
     return testBrowsers
 
 def startBrowsers(browsers, options):
@@ -568,6 +578,12 @@ def runTests(options, browsers):
     try:
         startBrowsers(browsers, options)
         while not State.done:
+            for b in State.lastPost:
+                if State.remaining[b] > 0 and int(time.time()) - State.lastPost[b] > BROWSER_TIMEOUT:
+                    print 'TEST-UNEXPECTED-FAIL | test failed', b, "has not responded in", BROWSER_TIMEOUT, "s"
+                    State.numErrors += State.remaining[b]
+                    State.remaining[b] = 0
+                    checkIfDone()
             time.sleep(1)
         processResults()
     finally:
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 6a083bdf7..b6879e1ec 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -19,7 +19,7 @@
     },
     {  "id": "intelisa-eq",
        "file": "pdfs/intelisa.pdf",
-       "md5": "83032ccbfdc5a66269b1403971110abe",
+       "md5": "c1444b7ccd935c0577d094297e1a6448",
        "link": true,
        "pageLimit": 100,
        "rounds": 1,
@@ -29,12 +29,13 @@
        "file": "pdfs/pdf.pdf",
        "md5": "dbdb23c939d2be09b43126c3c56060c7",
        "link": true,
+       "pageLimit": 500,
        "rounds": 1,
        "type": "load"
     },
     {  "id": "shavian-load",
        "file": "pdfs/shavian.pdf",
-       "md5": "4fabf0a03e82693007435020bc446f9b",
+       "md5": "79253352f48b55b7fa28a2586875d8b7",
        "link": true,
        "rounds": 1,
        "type": "load"
@@ -241,10 +242,10 @@
        "skipPages": [ 16 ],
        "type": "load"
     },
-    {  "id": "tcpdf_033",
-       "file": "pdfs/tcpdf_033.pdf",
-       "md5": "861294df58d185aae80919173f2732ff",
-       "link": true,
+    {  "id": "mixedfonts",
+       "file": "pdfs/mixedfonts.pdf",
+       "md5": "a582b83fa1b3a25a6f13803a367c71ec",
+       "link": false,
        "rounds": 1,
        "type": "eq"
     },
diff --git a/test/test_slave.html b/test/test_slave.html
index 50bb067e1..7c2097110 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -5,6 +5,7 @@
     <style type="text/css"></style>
     <script type="text/javascript" src="/src/core.js"></script>
     <script type="text/javascript" src="/src/util.js"></script>
+    <script type="text/javascript" src="/src/api.js"></script>
     <script type="text/javascript" src="/src/canvas.js"></script>
     <script type="text/javascript" src="/src/obj.js"></script>
     <script type="text/javascript" src="/src/function.js"></script>
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
new file mode 100644
index 000000000..318dbb42a
--- /dev/null
+++ b/test/unit/api_spec.js
@@ -0,0 +1,109 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+describe('api', function() {
+  // TODO run with worker enabled
+  PDFJS.disableWorker = true;
+  var basicApiUrl = '/basicapi.pdf';
+  function waitsForPromise(promise) {
+    waitsFor(function() {
+      return promise.isResolved || promise.isRejected;
+    }, 250);
+  }
+  function expectAfterPromise(promise, successCallback) {
+    waitsForPromise(promise);
+    runs(function() {
+      promise.then(successCallback,
+      function(error, e) {
+        // Shouldn't get here.
+        expect(false).toEqual(true);
+      });
+    });
+  }
+  describe('PDFJS', function() {
+    describe('getDocument', function() {
+      it('creates pdf doc from URL', function() {
+        console.log('what is');
+        debugger;
+        var promise = PDFJS.getDocument(basicApiUrl);
+        expectAfterPromise(promise, function(data) {
+          expect(true).toEqual(true);
+        });
+      });
+      /*
+      it('creates pdf doc from typed array', function() {
+        // TODO
+      });
+      */
+    });
+  });
+  describe('PDFDocument', function() {
+    var promise = PDFJS.getDocument(basicApiUrl);
+    waitsForPromise(promise);
+    var doc;
+    runs(function() {
+      promise.then(function(data) { doc = data; });
+    });
+    it('gets number of pages', function() {
+      expect(doc.numPages).toEqual(3);
+    });
+    it('gets fingerprint', function() {
+      expect(typeof doc.fingerprint).toEqual('string');
+    });
+    it('gets page', function() {
+      var promise = doc.getPage(1);
+      expectAfterPromise(promise, function(data) {
+        expect(true).toEqual(true);
+      });
+    });
+    it('gets destinations', function() {
+      var promise = doc.getDestinations();
+      expectAfterPromise(promise, function(data) {
+        // TODO this seems to be broken for the test pdf
+      });
+    });
+    it('gets outline', function() {
+      var promise = doc.getOutline();
+      expectAfterPromise(promise, function(outline) {
+        // Two top level entries.
+        expect(outline.length).toEqual(2);
+        // Make sure some basic attributes are set.
+        expect(outline[1].title).toEqual('Chapter 1');
+        expect(outline[1].items.length).toEqual(1);
+        expect(outline[1].items[0].title).toEqual('Paragraph 1.1');
+      });
+    });
+    it('gets metadata', function() {
+      var promise = doc.getMetadata();
+      expectAfterPromise(promise, function(metadata) {
+        expect(metadata.info['Title']).toEqual('Basic API Test');
+        expect(metadata.metadata.get('dc:title')).toEqual('Basic API Test');
+      });
+    });
+  });
+  describe('Page', function() {
+    var promise = new Promise();
+    PDFJS.getDocument(basicApiUrl).then(function(doc) {
+      doc.getPage(1).then(function(data) {
+        promise.resolve(data);
+      });
+    });
+    waitsForPromise(promise);
+    var page;
+    runs(function() {
+      promise.then(function(data) {
+        page = data;
+      });
+    });
+    it('gets ref', function() {
+      expect(page.ref).toEqual({num: 15, gen: 0});
+    });
+    // TODO rotate
+    // TODO viewport
+    // TODO annotaions
+    // TOOD text content
+    // TODO operation list
+  });
+});
diff --git a/test/unit/jsTestDriver.conf b/test/unit/jsTestDriver.conf
index 9a26df6a4..b0f917b66 100644
--- a/test/unit/jsTestDriver.conf
+++ b/test/unit/jsTestDriver.conf
@@ -1,32 +1,39 @@
 server: http://localhost:4224
 
+basepath: ..
+
 load:
-  - ../../external/jasmine/jasmine.js
-  - ../../external/jasmineAdapter/JasmineAdapter.js
-  - ../../src/obj.js
-  - ../../src/core.js
-  - ../../src/util.js
-  - ../../src/canvas.js
-  - ../../src/obj.js
-  - ../../src/function.js
-  - ../../src/charsets.js
-  - ../../src/cidmaps.js
-  - ../../src/colorspace.js
-  - ../../src/crypto.js
-  - ../../src/evaluator.js
-  - ../../src/fonts.js
-  - ../../src/glyphlist.js
-  - ../../src/image.js
-  - ../../src/metrics.js
-  - ../../src/parser.js
-  - ../../src/pattern.js
-  - ../../src/stream.js
-  - ../../src/worker.js
-  - ../../src/bidi.js
-  - ../../external/jpgjs/jpg.js
-  - ../unit/obj_spec.js
-  - ../unit/font_spec.js
-  - ../unit/function_spec.js
-  - ../unit/crypto_spec.js
-  - ../unit/stream_spec.js
+  - ../external/jasmine/jasmine.js
+  - ../external/jasmineAdapter/JasmineAdapter.js
+  - ../src/obj.js
+  - ../src/core.js
+  - ../src/util.js
+  - ../src/api.js
+  - ../src/canvas.js
+  - ../src/obj.js
+  - ../src/function.js
+  - ../src/charsets.js
+  - ../src/cidmaps.js
+  - ../src/colorspace.js
+  - ../src/crypto.js
+  - ../src/evaluator.js
+  - ../src/fonts.js
+  - ../src/glyphlist.js
+  - ../src/image.js
+  - ../src/metrics.js
+  - ../src/parser.js
+  - ../src/pattern.js
+  - ../src/stream.js
+  - ../src/worker.js
+  - ../src/bidi.js
+  - ../src/metadata.js
+  - ../external/jpgjs/jpg.js
+  - unit/obj_spec.js
+  - unit/font_spec.js
+  - unit/function_spec.js
+  - unit/crypto_spec.js
+  - unit/stream_spec.js
+  - unit/api_spec.js
 
+gateway:
+ - {matcher: "*.pdf", server: "http://localhost:8888/test/pdfs/"}
diff --git a/web/compatibility.js b/web/compatibility.js
index 9f139b7dd..5c192c9a9 100644
--- a/web/compatibility.js
+++ b/web/compatibility.js
@@ -234,3 +234,21 @@
     console = {log: function() {}};
   }
 })();
+
+// Check onclick compatibility in Opera
+(function checkOnClickCompatibility() {
+  // workaround for reported Opera bug DSK-354448:
+  // onclick fires on disabled buttons with opaque content
+  function ignoreIfTargetDisabled(event) {
+    if (isDisabled(event.target)) {
+      event.stopPropagation();
+    }
+  }
+  function isDisabled(node) {
+    return node.disabled || (node.parentNode && isDisabled(node.parentNode));
+  }
+  if (navigator.userAgent.indexOf('Opera') != -1) {
+    // use browser detection since we cannot feature-check this bug
+    document.addEventListener('click', ignoreIfTargetDisabled, true);
+  }
+})();
diff --git a/web/debugger.js b/web/debugger.js
index 00f5f6fd4..610a63854 100644
--- a/web/debugger.js
+++ b/web/debugger.js
@@ -163,29 +163,29 @@ var StepperManager = (function StepperManagerClosure() {
     enabled: false,
     active: false,
     // Stepper specific functions.
-    create: function create(pageNumber) {
+    create: function create(pageIndex) {
       var debug = document.createElement('div');
-      debug.id = 'stepper' + pageNumber;
+      debug.id = 'stepper' + pageIndex;
       debug.setAttribute('hidden', true);
       debug.className = 'stepper';
       stepperDiv.appendChild(debug);
       var b = document.createElement('option');
-      b.textContent = 'Page ' + (pageNumber + 1);
-      b.value = pageNumber;
+      b.textContent = 'Page ' + (pageIndex + 1);
+      b.value = pageIndex;
       stepperChooser.appendChild(b);
-      var initBreakPoints = breakPoints[pageNumber] || [];
-      var stepper = new Stepper(debug, pageNumber, initBreakPoints);
+      var initBreakPoints = breakPoints[pageIndex] || [];
+      var stepper = new Stepper(debug, pageIndex, initBreakPoints);
       steppers.push(stepper);
       if (steppers.length === 1)
-        this.selectStepper(pageNumber, false);
+        this.selectStepper(pageIndex, false);
       return stepper;
     },
-    selectStepper: function selectStepper(pageNumber, selectPanel) {
+    selectStepper: function selectStepper(pageIndex, selectPanel) {
       if (selectPanel)
         this.manager.selectPanel(1);
       for (var i = 0; i < steppers.length; ++i) {
         var stepper = steppers[i];
-        if (stepper.pageNumber == pageNumber)
+        if (stepper.pageIndex == pageIndex)
           stepper.panel.removeAttribute('hidden');
         else
           stepper.panel.setAttribute('hidden', true);
@@ -193,11 +193,11 @@ var StepperManager = (function StepperManagerClosure() {
       var options = stepperChooser.options;
       for (var i = 0; i < options.length; ++i) {
         var option = options[i];
-        option.selected = option.value == pageNumber;
+        option.selected = option.value == pageIndex;
       }
     },
-    saveBreakPoints: function saveBreakPoints(pageNumber, bps) {
-      breakPoints[pageNumber] = bps;
+    saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
+      breakPoints[pageIndex] = bps;
       sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints));
     }
   };
@@ -205,12 +205,12 @@ var StepperManager = (function StepperManagerClosure() {
 
 // The stepper for each page's IRQueue.
 var Stepper = (function StepperClosure() {
-  function Stepper(panel, pageNumber, initialBreakPoints) {
+  function Stepper(panel, pageIndex, initialBreakPoints) {
     this.panel = panel;
     this.len;
     this.breakPoint = 0;
     this.nextBreakPoint = null;
-    this.pageNumber = pageNumber;
+    this.pageIndex = pageIndex;
     this.breakPoints = initialBreakPoints;
     this.currentIdx = -1;
   }
@@ -256,7 +256,7 @@ var Stepper = (function StepperClosure() {
               self.breakPoints.push(x);
             else
               self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
-            StepperManager.saveBreakPoints(self.pageNumber, self.breakPoints);
+            StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
           }
         })(i);
 
@@ -278,7 +278,7 @@ var Stepper = (function StepperClosure() {
       return null;
     },
     breakIt: function breakIt(idx, callback) {
-      StepperManager.selectStepper(this.pageNumber, true);
+      StepperManager.selectStepper(this.pageIndex, true);
       var self = this;
       var dom = document;
       self.currentIdx = idx;
diff --git a/web/images/text.svg b/web/images/text.svg
new file mode 100644
index 000000000..25df8f466
--- /dev/null
+++ b/web/images/text.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<svg
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   height="40"
+   width="40"
+   id="svg2995"
+   version="1.1">
+  
+  <rect
+     style="fill:#f1e47b;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-opacity:1"
+     id="rect3009"
+     width="30.169491"
+     height="24.576269"
+     x="4.237288"
+     y="6.7796612" />
+  <rect
+     style="fill:#000000;fill-opacity:1;stroke:none"
+     id="rect3781"
+     width="23.38983"
+     height="1.1864407"
+     x="7.6271186"
+     y="11.389831" />
+  <rect
+     style="fill:#000000;fill-opacity:1;stroke:none"
+     id="rect3781-1"
+     width="23.38983"
+     height="0.67796612"
+     x="7.6271191"
+     y="21.61017" />
+  <rect
+     style="fill:#000000;fill-opacity:1;stroke:none"
+     id="rect3781-7"
+     width="23.38983"
+     height="0.67796612"
+     x="7.4576273"
+     y="26.152542" />
+  <rect
+     style="fill:#000000;fill-opacity:1;stroke:none"
+     id="rect3781-1-4"
+     width="23.38983"
+     height="0.67796612"
+     x="7.6271186"
+     y="17.033899" />
+</svg>
diff --git a/web/viewer.html b/web/viewer.html
index ff9c01ae2..4fd0bded4 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -1,6 +1,6 @@
 <html>
   <head>
-    <title>PDF viewer</title>
+    <title>PDF.js viewer</title>
     <!-- PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION -->
 
     <link rel="stylesheet" href="viewer.css"/>
@@ -10,6 +10,7 @@
     <!-- PDFJSSCRIPT_INCLUDE_BUILD -->
     <script type="text/javascript" src="../src/core.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE -->
     <script type="text/javascript" src="../src/util.js"></script>  <!-- PDFJSSCRIPT_REMOVE_CORE -->
+    <script type="text/javascript" src="../src/api.js"></script>  <!-- PDFJSSCRIPT_REMOVE_CORE -->
     <script type="text/javascript" src="../src/metadata.js"></script>  <!-- PDFJSSCRIPT_REMOVE_CORE -->
     <script type="text/javascript" src="../src/canvas.js"></script>  <!-- PDFJSSCRIPT_REMOVE_CORE -->
     <script type="text/javascript" src="../src/obj.js"></script>  <!-- PDFJSSCRIPT_REMOVE_CORE -->
@@ -34,9 +35,8 @@
     <script type="text/javascript" src="debugger.js"></script>
     <script type="text/javascript" src="viewer.js"></script>
   </head>
-  
+
   <body>
-    
     <div id="outerContainer">
 
       <div class="toolbar">
diff --git a/web/viewer.js b/web/viewer.js
index b58c3ecc2..fdf8f9fd4 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -32,7 +32,7 @@ var Cache = function cacheCache(size) {
       data.splice(i);
     data.push(view);
     if (data.length > size)
-      data.shift().update();
+      data.shift().destroy();
   };
 };
 
@@ -247,9 +247,9 @@ var PDFView = {
 
     var currentPage = this.pages[this.page - 1];
     var pageWidthScale = (window.innerWidth - kScrollbarPadding) /
-                          currentPage.width / kCssUnits;
+                          currentPage.width * currentPage.scale / kCssUnits;
     var pageHeightScale = (window.innerHeight - kScrollbarPadding) /
-                           currentPage.height / kCssUnits;
+                           currentPage.height * currentPage.scale / kCssUnits;
     if ('page-width' == value)
       this.setScale(pageWidthScale, resetAutoSettings);
     if ('page-height' == value)
@@ -318,27 +318,25 @@ var PDFView = {
     }
 
     var self = this;
-    PDFJS.getPdf(
-      {
-        url: url,
-        progress: function getPdfProgress(evt) {
-          if (evt.lengthComputable)
-            self.progress(evt.loaded / evt.total);
-        },
-        error: function getPdfError(e) {
-          var loadingIndicator = document.getElementById('loading');
-          loadingIndicator.textContent = 'Error';
-          var moreInfo = {
-            message: 'Unexpected server response of ' + e.target.status + '.'
-          };
-          self.error('An error occurred while loading the PDF.', moreInfo);
-        }
+    self.loading = true;
+    PDFJS.getDocument(url).then(
+      function getDocumentCallback(pdfDocument) {
+        self.load(pdfDocument, scale);
+        self.loading = false;
       },
-      function getPdfLoad(data) {
-        self.loading = true;
-        self.load(data, scale);
+      function getDocumentError(message, exception) {
+        var loadingIndicator = document.getElementById('loading');
+        loadingIndicator.textContent = 'Error';
+        var moreInfo = {
+          message: message
+        };
+        self.error('An error occurred while loading the PDF.', moreInfo);
         self.loading = false;
-      });
+      },
+      function getDocumentProgress(progressData) {
+        self.progress(progressData.loaded / progressData.total);
+      }
+    );
   },
 
   download: function pdfViewDownload() {
@@ -360,6 +358,8 @@ var PDFView = {
     var destRef = dest[0];
     var pageNumber = destRef instanceof Object ?
       this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1);
+    if (pageNumber > this.pages.length)
+      pageNumber = this.pages.length;
     if (pageNumber) {
       this.page = pageNumber;
       var currentPage = this.pages[pageNumber - 1];
@@ -461,7 +461,7 @@ var PDFView = {
     PDFView.loadingBar.percent = percent;
   },
 
-  load: function pdfViewLoad(data, scale) {
+  load: function pdfViewLoad(pdfDocument, scale) {
     function bindOnAfterDraw(pageView, thumbnailView) {
       // when page is painted, using the image as thumbnail base
       pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
@@ -489,14 +489,8 @@ var PDFView = {
     while (container.hasChildNodes())
       container.removeChild(container.lastChild);
 
-    var pdf;
-    try {
-      pdf = new PDFJS.PDFDoc(data);
-    } catch (e) {
-      this.error('An error occurred while reading the PDF.', e);
-    }
-    var pagesCount = pdf.numPages;
-    var id = pdf.fingerprint;
+    var pagesCount = pdfDocument.numPages;
+    var id = pdfDocument.fingerprint;
     var storedHash = null;
     document.getElementById('numPages').textContent = '/ ' + pagesCount;
     document.getElementById('pageNumber').max = pagesCount;
@@ -514,30 +508,68 @@ var PDFView = {
     var pages = this.pages = [];
     var pagesRefMap = {};
     var thumbnails = this.thumbnails = [];
-    for (var i = 1; i <= pagesCount; i++) {
-      var page = pdf.getPage(i);
-      var pageView = new PageView(container, page, i, page.width, page.height,
-                                  page.stats, this.navigateTo.bind(this));
-      var thumbnailView = new ThumbnailView(thumbsView, page, i,
-                                            page.width / page.height);
-      bindOnAfterDraw(pageView, thumbnailView);
-
-      pages.push(pageView);
-      thumbnails.push(thumbnailView);
-      var pageRef = page.ref;
-      pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
-    }
+    var pagePromises = [];
+    for (var i = 1; i <= pagesCount; i++)
+      pagePromises.push(pdfDocument.getPage(i));
+    var self = this;
+    var pagesPromise = PDFJS.Promise.all(pagePromises);
+    pagesPromise.then(function(promisedPages) {
+      for (var i = 1; i <= pagesCount; i++) {
+        var page = promisedPages[i - 1];
+        var pageView = new PageView(container, page, i, scale,
+                                    page.stats, self.navigateTo.bind(self));
+        var thumbnailView = new ThumbnailView(thumbsView, page, i);
+        bindOnAfterDraw(pageView, thumbnailView);
+
+        pages.push(pageView);
+        thumbnails.push(thumbnailView);
+        var pageRef = page.ref;
+        pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
+      }
 
-    this.pagesRefMap = pagesRefMap;
-    this.destinations = pdf.catalog.destinations;
+      self.pagesRefMap = pagesRefMap;
+    });
 
-    if (pdf.catalog.documentOutline) {
-      this.outline = new DocumentOutlineView(pdf.catalog.documentOutline);
-      var outlineSwitchButton = document.getElementById('viewOutline');
-      outlineSwitchButton.removeAttribute('disabled');
-      this.switchSidebarView('outline');
-    }
+    var destinationsPromise = pdfDocument.getDestinations();
+    destinationsPromise.then(function(destinations) {
+      self.destinations = destinations;
+    });
+
+    // outline and initial view depends on destinations and pagesRefMap
+    PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() {
+      pdfDocument.getOutline().then(function(outline) {
+        if (!outline)
+          return;
 
+        self.outline = new DocumentOutlineView(outline);
+        var outlineSwitchButton = document.getElementById('viewOutline');
+        outlineSwitchButton.removeAttribute('disabled');
+        self.switchSidebarView('outline');
+      });
+
+      self.setInitialView(storedHash, scale);
+    });
+
+    pdfDocument.getMetadata().then(function(data) {
+      var info = data.info, metadata = data.metadata;
+      self.documentInfo = info;
+      self.metadata = metadata;
+
+      var pdfTitle;
+      if (metadata) {
+        if (metadata.has('dc:title'))
+          pdfTitle = metadata.get('dc:title');
+      }
+
+      if (!pdfTitle && info && info['Title'])
+        pdfTitle = info['Title'];
+
+      if (pdfTitle)
+        document.title = pdfTitle + ' - ' + document.title;
+    });
+  },
+
+  setInitialView: function pdfViewSetInitialView(storedHash, scale) {
     // Reset the current scale, as otherwise the page's scale might not get
     // updated if the zoom level stayed the same.
     this.currentScale = 0;
@@ -558,24 +590,6 @@ var PDFView = {
       // Setting the default one.
       this.parseScale(kDefaultScale, true);
     }
-
-    this.metadata = null;
-    var metadata = pdf.catalog.metadata;
-    var info = this.documentInfo = pdf.info;
-    var pdfTitle;
-
-    if (metadata) {
-      this.metadata = metadata = new PDFJS.Metadata(metadata);
-
-      if (metadata.has('dc:title'))
-        pdfTitle = metadata.get('dc:title');
-    }
-
-    if (!pdfTitle && info && info['Title'])
-      pdfTitle = info['Title'];
-
-    if (pdfTitle)
-      document.title = pdfTitle + ' - ' + document.title;
   },
 
   setHash: function pdfViewSetHash(hash) {
@@ -649,7 +663,7 @@ var PDFView = {
     var windowTop = window.pageYOffset;
     for (var i = 1; i <= pages.length; ++i) {
       var page = pages[i - 1];
-      var pageHeight = page.height * page.scale + kBottomMargin;
+      var pageHeight = page.height + kBottomMargin;
       if (currentHeight + pageHeight > windowTop)
         break;
 
@@ -673,8 +687,8 @@ var PDFView = {
 
     var view = document.getElementById('thumbnailView');
     var currentHeight = kBottomMargin;
-    var top = view.scrollTop;
-
+    
+    var top = view.scrollTop;    
     for (var i = 1; i <= thumbs.length; ++i) {
       var thumb = thumbs[i - 1];
       var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
@@ -685,7 +699,6 @@ var PDFView = {
     }
 
     var bottom = top + view.clientHeight;
-
     for (; i <= thumbs.length && currentHeight < bottom; ++i) {
       var singleThumb = thumbs[i - 1];
       visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
@@ -710,16 +723,13 @@ var PDFView = {
   }
 };
 
-var PageView = function pageView(container, content, id, pageWidth, pageHeight,
+var PageView = function pageView(container, pdfPage, id, scale,
                                  stats, navigateTo) {
   this.id = id;
-  this.content = content;
+  this.pdfPage = pdfPage;
 
-  var view = this.content.view;
-  this.x = view.x;
-  this.y = view.y;
-  this.width = view.width;
-  this.height = view.height;
+  this.scale = scale || 1.0;
+  this.viewport = this.pdfPage.getViewport(this.scale);
 
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
@@ -731,10 +741,18 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
   container.appendChild(anchor);
   container.appendChild(div);
 
+  this.destroy = function pageViewDestroy() {
+    this.update();
+    this.pdfPage.destroy();
+  };
+
   this.update = function pageViewUpdate(scale) {
     this.scale = scale || this.scale;
-    div.style.width = (this.width * this.scale) + 'px';
-    div.style.height = (this.height * this.scale) + 'px';
+    var viewport = this.pdfPage.getViewport(this.scale);
+
+    this.viewport = viewport;
+    div.style.width = viewport.width + 'px';
+    div.style.height = viewport.height + 'px';
 
     while (div.hasChildNodes())
       div.removeChild(div.lastChild);
@@ -747,7 +765,21 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
     div.appendChild(this.loadingIconDiv);
   };
 
-  function setupAnnotations(content, scale) {
+  Object.defineProperty(this, 'width', {
+    get: function PageView_getWidth() {
+      return this.viewport.width;
+    },
+    enumerable: true
+  });
+
+  Object.defineProperty(this, 'height', {
+    get: function PageView_getHeight() {
+      return this.viewport.height;
+    },
+    enumerable: true
+  });
+
+  function setupAnnotations(pdfPage, viewport) {
     function bindLink(link, dest) {
       link.href = PDFView.getDestinationHash(dest);
       link.onclick = function pageViewSetupLinksOnclick() {
@@ -757,11 +789,13 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       };
     }
     function createElementWithStyle(tagName, item) {
+      var rect = viewport.convertToViewportRectangle(item.rect);
+      rect = PDFJS.Util.normalizeRect(rect);
       var element = document.createElement(tagName);
-      element.style.left = (Math.floor(item.x - view.x) * scale) + 'px';
-      element.style.top = (Math.floor(item.y - view.y) * scale) + 'px';
-      element.style.width = Math.ceil(item.width * scale) + 'px';
-      element.style.height = Math.ceil(item.height * scale) + 'px';
+      element.style.left = Math.floor(rect[0]) + 'px';
+      element.style.top = Math.floor(rect[1]) + 'px';
+      element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+      element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
       return element;
     }
     function createCommentAnnotation(type, item) {
@@ -769,17 +803,20 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       container.className = 'annotComment';
 
       var image = createElementWithStyle('img', item);
+      var type = item.type;
+      var rect = viewport.convertToViewportRectangle(item.rect);
+      rect = PDFJS.Util.normalizeRect(rect);
       image.src = kImageDirectory + type.toLowerCase() + '.svg';
+      image.alt = '[' + type + ' Annotation]';
       var content = document.createElement('div');
       content.setAttribute('hidden', true);
       var title = document.createElement('h1');
       var text = document.createElement('p');
-      var offsetPos = Math.floor(item.x - view.x + item.width);
-      content.style.left = (offsetPos * scale) + 'px';
-      content.style.top = (Math.floor(item.y - view.y) * scale) + 'px';
+      content.style.left = Math.floor(rect[2]) + 'px';
+      content.style.top = Math.floor(rect[1]) + 'px';
       title.textContent = item.title;
 
-      if (!item.content) {
+      if (!item.content && !item.title) {
         content.setAttribute('hidden', true);
       } else {
         var e = document.createElement('span');
@@ -792,11 +829,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
         }
         text.appendChild(e);
         image.addEventListener('mouseover', function annotationImageOver() {
-           this.nextSibling.removeAttribute('hidden');
+           content.removeAttribute('hidden');
         }, false);
 
         image.addEventListener('mouseout', function annotationImageOut() {
-           this.nextSibling.setAttribute('hidden', true);
+           content.setAttribute('hidden', true);
         }, false);
       }
 
@@ -808,29 +845,29 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       return container;
     }
 
-    var items = content.getAnnotations();
-    for (var i = 0; i < items.length; i++) {
-      var item = items[i];
-      switch (item.type) {
-        case 'Link':
-          var link = createElementWithStyle('a', item);
-          link.href = item.url || '';
-          if (!item.url)
-            bindLink(link, ('dest' in item) ? item.dest : null);
-          div.appendChild(link);
-          break;
-        case 'Text':
-          var comment = createCommentAnnotation(item.name, item);
-          if (comment)
-            div.appendChild(comment);
-          break;
+    pdfPage.getAnnotations().then(function(items) {
+      for (var i = 0; i < items.length; i++) {
+        var item = items[i];
+        switch (item.type) {
+          case 'Link':
+            var link = createElementWithStyle('a', item);
+            link.href = item.url || '';
+            if (!item.url)
+              bindLink(link, ('dest' in item) ? item.dest : null);
+            div.appendChild(link);
+            break;
+          case 'Text':
+            var comment = createCommentAnnotation(item.name, item);
+            if (comment)
+              div.appendChild(comment);
+            break;
+        }
       }
-    }
+    });
   }
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
-    var scale = PDFView.currentScale;
-    return this.content.rotatePoint(x / scale, y / scale);
+    return this.viewport.convertToPdfPoint(x, y);
   };
 
   this.scrollIntoView = function pageViewScrollIntoView(dest) {
@@ -878,8 +915,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       }
 
       var boundingRect = [
-        this.content.rotatePoint(x, y),
-        this.content.rotatePoint(x + width, y + height)
+        this.viewport.convertToViewportPoint(x, y),
+        this.viewport.convertToViewportPoint(x + width, y + height)
       ];
 
       if (scale && scale !== PDFView.currentScale)
@@ -890,18 +927,18 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       setTimeout(function pageViewScrollIntoViewRelayout() {
         // letting page to re-layout before scrolling
         var scale = PDFView.currentScale;
-        var x = Math.min(boundingRect[0].x, boundingRect[1].x);
-        var y = Math.min(boundingRect[0].y, boundingRect[1].y);
-        var width = Math.abs(boundingRect[0].x - boundingRect[1].x);
-        var height = Math.abs(boundingRect[0].y - boundingRect[1].y);
+        var x = Math.min(boundingRect[0][0], boundingRect[1][0]);
+        var y = Math.min(boundingRect[0][1], boundingRect[1][1]);
+        var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]);
+        var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]);
 
         // using temporary div to scroll it into view
         var tempDiv = document.createElement('div');
         tempDiv.style.position = 'absolute';
-        tempDiv.style.left = Math.floor(x * scale) + 'px';
-        tempDiv.style.top = Math.floor(y * scale) + 'px';
-        tempDiv.style.width = Math.ceil(width * scale) + 'px';
-        tempDiv.style.height = Math.ceil(height * scale) + 'px';
+        tempDiv.style.left = Math.floor(x) + 'px';
+        tempDiv.style.top = Math.floor(y) + 'px';
+        tempDiv.style.width = Math.ceil(width) + 'px';
+        tempDiv.style.height = Math.ceil(height) + 'px';
         div.appendChild(tempDiv);
         tempDiv.scrollIntoView(true);
         div.removeChild(tempDiv);
@@ -933,21 +970,20 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
     }
     var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
 
-    var scale = this.scale;
-    canvas.width = pageWidth * scale;
-    canvas.height = pageHeight * scale;
+    var scale = this.scale, viewport = this.viewport;
+    canvas.width = viewport.width;
+    canvas.height = viewport.height;
 
     var ctx = canvas.getContext('2d');
     ctx.save();
     ctx.fillStyle = 'rgb(255, 255, 255)';
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     ctx.restore();
-    ctx.translate(-this.x * scale, -this.y * scale);
 
     // Rendering area
 
     var self = this;
-    this.content.startRendering(ctx, function pageViewDrawCallback(error) {
+    function pageViewDrawCallback(error) {
       if (self.loadingIconDiv) {
         div.removeChild(self.loadingIconDiv);
         delete self.loadingIconDiv;
@@ -956,16 +992,30 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       if (error)
         PDFView.error('An error occurred while rendering the page.', error);
 
-      self.stats = content.stats;
+      self.stats = pdfPage.stats;
       self.updateStats();
       if (self.onAfterDraw)
         self.onAfterDraw();
 
       cache.push(self);
       callback();
-    }, textLayer);
+    }
 
-    setupAnnotations(this.content, this.scale);
+    var renderContext = {
+      canvasContext: ctx,
+      viewport: this.viewport,
+      textLayer: textLayer
+    };
+    this.pdfPage.render(renderContext).then(
+      function pdfPageRenderCallback() {
+        pageViewDrawCallback(null);
+      },
+      function pdfPageRenderError(error) {
+        pageViewDrawCallback(error);
+      }
+    );
+
+    setupAnnotations(this.pdfPage, this.viewport);
     div.setAttribute('data-loaded', true);
   };
 
@@ -977,7 +1027,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
   };
 };
 
-var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
+var ThumbnailView = function thumbnailView(container, pdfPage, id) {
   var anchor = document.createElement('a');
   anchor.href = PDFView.getAnchorUrl('#page=' + id);
   anchor.onclick = function stopNivigation() {
@@ -985,15 +1035,16 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
     return false;
   };
 
-  var view = page.view;
-  this.width = view.width;
-  this.height = view.height;
+  var viewport = pdfPage.getViewport(1);
+  var pageWidth = viewport.width;
+  var pageHeight = viewport.height;
+  var pageRatio = pageWidth / pageHeight;
   this.id = id;
 
   var canvasWidth = 98;
   var canvasHeight = canvasWidth / this.width * this.height;
-  var scaleX = this.scaleX = (canvasWidth / this.width);
-  var scaleY = this.scaleY = (canvasHeight / this.height);
+  var scaleX = this.scaleX = (canvasWidth / pageWidth);
+  var scaleY = this.scaleY = (canvasHeight / pageHeight);
 
   var div = document.createElement('div');
   div.id = 'thumbnailContainer' + id;
@@ -1014,7 +1065,7 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
     canvas.className = 'thumbnailImage';
 
     div.setAttribute('data-loaded', true);
-
+    
     var ring = document.createElement('div');
     ring.className = 'thumbnailSelectionRing';
     ring.appendChild(canvas);
@@ -1023,11 +1074,8 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
     var ctx = canvas.getContext('2d');
     ctx.save();
     ctx.fillStyle = 'rgb(255, 255, 255)';
-    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    ctx.fillRect(0, 0, canvasWidth, canvasHeight);
     ctx.restore();
-
-    var view = page.view;
-    ctx.translate(-view.x * scaleX, -view.y * scaleY);
     return ctx;
   }
 
@@ -1042,10 +1090,19 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
     }
 
     var ctx = getPageDrawContext();
-    page.startRendering(ctx, function thumbnailViewDrawStartRendering() {
-      callback();
-    });
-
+    var drawViewport = pdfPage.getViewport(scaleX);
+    var renderContext = {
+      canvasContext: ctx,
+      viewport: drawViewport
+    };
+    pdfPage.render(renderContext).then(
+      function pdfPageRenderCallback() {
+        callback();
+      },
+      function pdfPageRenderError(error) {
+        callback();
+      }
+    );
     this.hasImage = true;
   };
 
@@ -1235,7 +1292,6 @@ window.addEventListener('load', function webViewerLoad(evt) {
 
   var file = PDFJS.isFirefoxExtension ?
               window.location.toString() : params.file || kDefaultURL;
-  PDFView.open(file, 0);
 
   if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
       !window.FileList || !window.Blob) {
@@ -1273,6 +1329,8 @@ window.addEventListener('load', function webViewerLoad(evt) {
       document.getElementById('sidebarContainer').classList.toggle('hidden');
       updateThumbViewArea();
     });
+
+  PDFView.open(file, 0);
 }, true);
 
 /**
@@ -1336,14 +1394,14 @@ function updateViewarea() {
   var currentPage = PDFView.pages[pageNumber - 1];
   var topLeft = currentPage.getPagePoint(window.pageXOffset,
     window.pageYOffset - firstPage.y - kViewerTopMargin);
-  pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y);
+  pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]);
 
   var store = PDFView.store;
   store.set('exists', true);
   store.set('page', pageNumber);
   store.set('zoom', normalizedScaleValue);
-  store.set('scrollLeft', Math.round(topLeft.x));
-  store.set('scrollTop', Math.round(topLeft.y));
+  store.set('scrollLeft', Math.round(topLeft[0]));
+  store.set('scrollTop', Math.round(topLeft[1]));
   var href = PDFView.getAnchorUrl(pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
 }
@@ -1397,7 +1455,11 @@ window.addEventListener('change', function webViewerChange(evt) {
 
     for (var i = 0; i < data.length; i++)
       uint8Array[i] = data.charCodeAt(i);
-    PDFView.load(uint8Array);
+
+    // TODO using blob instead?
+    PDFJS.getDocument(uint8Array).then(function(pdfDocument) {
+      PDFView.load(pdfDocument);
+    });
   };
 
   // Read as a binary string since "readAsArrayBuffer" is not yet