]> source.dussan.org Git - nextcloud-server.git/commitdiff
PDF.JS updated, compatibility.js added for browser support, should fix: oc-395
authorThomas Mueller <thomas.mueller@tmit.eu>
Mon, 11 Jun 2012 22:49:40 +0000 (00:49 +0200)
committerThomas Mueller <thomas.mueller@tmit.eu>
Mon, 11 Jun 2012 22:50:34 +0000 (00:50 +0200)
apps/files_pdfviewer/appinfo/app.php
apps/files_pdfviewer/appinfo/info.xml
apps/files_pdfviewer/css/viewer.css
apps/files_pdfviewer/js/pdfjs/build/pdf.js
apps/files_pdfviewer/js/pdfjs/compatibility.js [new file with mode: 0644]
apps/files_pdfviewer/js/pdfjs/update.sh
apps/files_pdfviewer/js/pdfjs/viewer.js [new file with mode: 0644]
apps/files_pdfviewer/js/viewer.js

index 06b156706747489db5d8f0a9f158b9f8777c31b0..f700d004dca8c4c8e2c76ce072afdc863030ae9d 100644 (file)
@@ -3,5 +3,6 @@
 OCP\Util::addscript( 'files_pdfviewer', 'viewer');
 OCP\Util::addStyle( 'files_pdfviewer', 'viewer');
 OCP\Util::addscript( 'files_pdfviewer', 'pdfjs/build/pdf');
-OCP\Util::addscript( 'files_pdfviewer', 'pdfview');
+OCP\Util::addscript( 'files_pdfviewer', 'pdfjs/compatibility');
+OCP\Util::addscript( 'files_pdfviewer', 'pdfjs/viewer');
 ?>
index e3813be1001a3808619e5b00ec377aec78cc8265..074962f57fb73eec6354796f594cfea91ec2d3f3 100644 (file)
@@ -4,7 +4,7 @@
        <name>PDF Viewer</name>
        <description>Inline PDF viewer (pdfjs-based)</description>
        <licence>GPL</licence>
-       <author>Joan Creus</author>
+       <author>Joan Creus, Thomas Müller</author>
        <require>4</require>
        <shipped>true</shipped>
        <default_enable/>
index b735dbfedfc60da83ba26311e4404303b5eef76a..f9ce929d8b883fdc45265692be4829236a801700 100644 (file)
@@ -2,7 +2,6 @@
 /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
 
 #viewer {
-  background-color: #929292;
   font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
   /*margin: 0px;*/
   padding: 0px;
index a19a9b75fea2da1a8a5adc61c0b9b6a3f9d043e0..b1fc9d9747e179321f18db0dfdf95668e73f4044 100644 (file)
@@ -7,10 +7,9 @@ var PDFJS = {};
   // Use strict in our context only - users might not want it
   'use strict';
 
-  PDFJS.build = 'd823592';
+  PDFJS.build = '2aae4fd';
 
   // Files are inserted below - see Makefile
-  /* PDFJSSCRIPT_INCLUDE_ALL */
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -20,7 +19,7 @@ var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
-var ERRORS = 0, WARNINGS = 1, TODOS = 5;
+var ERRORS = 0, WARNINGS = 1, INFOS = 5;
 var verbosity = WARNINGS;
 
 // The global PDFJS object exposes the API
@@ -44,7 +43,19 @@ function getPdf(arg, callback) {
     params = { url: arg };
 
   var xhr = new XMLHttpRequest();
+
   xhr.open('GET', params.url);
+
+  var headers = params.headers;
+  if (headers) {
+    for (var property in headers) {
+      if (typeof headers[property] === 'undefined')
+        continue;
+
+      xhr.setRequestHeader(property, params.headers[property]);
+    }
+  }
+
   xhr.mozResponseType = xhr.responseType = 'arraybuffer';
   var protocol = params.url.indexOf(':') < 0 ? window.location.protocol :
     params.url.substring(0, params.url.indexOf(':') + 1);
@@ -76,8 +87,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;
 
@@ -113,18 +122,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
@@ -132,42 +133,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
@@ -183,43 +155,20 @@ 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;
+        var streams = [];
         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));
@@ -228,9 +177,31 @@ 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);
+    },
+    extractTextContent: function Page_extractTextContent() {
+      var handler = {
+        on: function nullHandlerOn() {},
+        send: function nullHandlerSend() {}
+      };
+
+      var xref = this.xref;
+      var content = xref.fetchIfRef(this.content);
+      var resources = xref.fetchIfRef(this.resources);
+      if (isArray(content)) {
+        // fetching items
+        var i, n = content.length;
+        var streams = [];
+        for (i = 0; i < n; ++i)
+          streams.push(xref.fetchIfRef(content[i]));
+        content = new StreamsSequenceStream(streams);
+      } else if (isStream(content)) {
+        content.reset();
+      }
+
+      var pe = new PartialEvaluator(
+                     xref, handler, 'p' + this.pageNumber + '_');
+      return pe.getTextContent(content, resources);
     },
 
     ensureFonts: function Page_ensureFonts(fonts, callback) {
@@ -250,60 +221,6 @@ var Page = (function PageClosure() {
         }.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();
@@ -337,6 +254,7 @@ var Page = (function PageClosure() {
           case 'http':
           case 'https':
           case 'ftp':
+          case 'mailto':
             return true;
           default:
             return false;
@@ -355,15 +273,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');
@@ -437,7 +350,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);
@@ -446,37 +360,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);
-        }
-      );
     }
   };
 
@@ -484,26 +367,26 @@ 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, password) {
     if (isStream(arg))
-      init.call(this, arg);
+      init.call(this, arg, password);
     else if (isArrayBuffer(arg))
-      init.call(this, new Stream(arg));
+      init.call(this, new Stream(arg), password);
     else
-      error('PDFDocModel: Unknown argument type');
+      error('PDFDocument: Unknown argument type');
   }
 
-  function init(stream) {
+  function init(stream, password) {
     assertWellFormed(stream.length > 0, 'stream must have data');
     this.stream = stream;
-    this.setup();
+    this.setup(password);
     this.acroForm = this.catalog.catDict.get('AcroForm');
   }
 
@@ -523,7 +406,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
     return true; /* found */
   }
 
-  PDFDocModel.prototype = {
+  PDFDocument.prototype = {
     get linearization() {
       var length = this.stream.length;
       var linearization = false;
@@ -584,7 +467,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)) {
@@ -594,11 +477,12 @@ var PDFDocModel = (function PDFDocModelClosure() {
       }
       // May not be a PDF file, continue anyway.
     },
-    setup: function PDFDocModel_setup(ownerPassword, userPassword) {
+    setup: function PDFDocument_setup(password) {
       this.checkHeader();
       var xref = new XRef(this.stream,
                           this.startXRef,
-                          this.mainXRefEntriesOffset);
+                          this.mainXRefEntriesOffset,
+                          password);
       this.xref = xref;
       this.catalog = new Catalog(xref);
     },
@@ -608,7 +492,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');
@@ -622,7 +506,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 = '';
@@ -643,321 +527,125 @@ 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;
+  return PDFDocument;
 })();
 
-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();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
-    this.pageCache = [];
-    this.fontsLoading = {};
-    this.workerReadyPromise = new Promise('workerReady');
+'use strict';
 
-    // 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');
-      }
+// Use only for debugging purposes. This should not be used in any code that is
+// in mozilla master.
+function log(msg) {
+  if (console && console.log)
+    console.log(msg);
+  else if (print)
+    print(msg);
+}
 
-      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);
-        }
+// A notice for devs that will not trigger the fallback UI.  These are good
+// for things that are helpful to devs, such as warning that Workers were
+// disabled, which is important to devs but not end users.
+function info(msg) {
+  if (verbosity >= INFOS) {
+    log('Info: ' + msg);
+    PDFJS.LogManager.notify('info', msg);
+  }
+}
 
-        var messageHandler = new MessageHandler('main', worker);
+// Non-fatal warnings that should trigger the fallback UI.
+function warn(msg) {
+  if (verbosity >= WARNINGS) {
+    log('Warning: ' + msg);
+    PDFJS.LogManager.notify('warn', msg);
+  }
+}
 
-        messageHandler.on('test', function pdfDocTest(supportTypedArray) {
-          if (supportTypedArray) {
-            this.worker = worker;
-            this.setupMessageHandler(messageHandler);
-          } else {
-            globalScope.PDFJS.disableWorker = true;
-            this.setupFakeWorker();
-          }
-        }.bind(this));
+// Fatal errors that should trigger the fallback UI and halt execution by
+// throwing an exception.
+function error(msg) {
+  log('Error: ' + msg);
+  log(backtrace());
+  PDFJS.LogManager.notify('error', msg);
+  throw new Error(msg);
+}
 
-        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();
+// Missing features that should trigger the fallback UI.
+function TODO(what) {
+  warn('TODO: ' + what);
+}
+
+function backtrace() {
+  try {
+    throw new Error();
+  } catch (e) {
+    return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
   }
+}
 
-  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() {}
-      };
+function assert(cond, msg) {
+  if (!cond)
+    error(msg);
+}
 
-      var messageHandler = new MessageHandler('main', fakeWorker);
-      this.setupMessageHandler(messageHandler);
+// In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
+// behavior is undefined.
+function assertWellFormed(cond, msg) {
+  if (!cond)
+    error(msg);
+}
 
-      // If the main thread is our worker, setup the handling for the messages
-      // the main thread sends to it self.
-      WorkerMessageHandler.setup(messageHandler);
+var LogManager = PDFJS.LogManager = (function LogManagerClosure() {
+  var loggers = [];
+  return {
+    addLogger: function logManager_addLogger(logger) {
+      loggers.push(logger);
     },
+    notify: function(type, message) {
+      for (var i = 0, ii = loggers.length; i < ii; i++) {
+        var logger = loggers[i];
+        if (logger[type])
+          logger[type](message);
+      }
+    }
+  };
+})();
 
+function shadow(obj, prop, value) {
+  Object.defineProperty(obj, prop, { value: value,
+                                     enumerable: true,
+                                     configurable: true,
+                                     writable: false });
+  return value;
+}
 
-    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;
+var PasswordException = (function PasswordExceptionClosure() {
+  function PasswordException(msg, code) {
+    this.name = 'PasswordException';
+    this.message = msg;
+    this.code = code;
+  }
 
-        page.stats.timeEnd('Page Request');
-        page.startRenderingFromOperatorList(data.operatorList, depFonts);
-      }, this);
+  PasswordException.prototype = new Error();
+  PasswordException.constructor = PasswordException;
 
-      messageHandler.on('obj', function pdfDocObj(data) {
-        var id = data[0];
-        var type = data[1];
+  return PasswordException;
+})();
 
-        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;
-})();
-
-globalScope.PDFJS.PDFDoc = PDFDoc;
-
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-
-'use strict';
-
-function log(msg) {
-  if (console && console.log)
-    console.log(msg);
-  else if (print)
-    print(msg);
-}
-
-function warn(msg) {
-  if (verbosity >= WARNINGS)
-    log('Warning: ' + msg);
-}
-
-function backtrace() {
-  try {
-    throw new Error();
-  } catch (e) {
-    return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
-  }
-}
-
-function error(msg) {
-  log('Error: ' + msg);
-  log(backtrace());
-  throw new Error(msg);
-}
-
-function TODO(what) {
-  if (verbosity >= TODOS)
-    log('TODO: ' + what);
-}
-
-function malformed(msg) {
-  error('Malformed PDF: ' + msg);
-}
-
-function assert(cond, msg) {
-  if (!cond)
-    error(msg);
-}
-
-// In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
-// behavior is undefined.
-function assertWellFormed(cond, msg) {
-  if (!cond)
-    malformed(msg);
-}
-
-function shadow(obj, prop, value) {
-  Object.defineProperty(obj, prop, { value: value,
-                                     enumerable: true,
-                                     configurable: true,
-                                     writable: false });
-  return value;
-}
-
-function bytesToString(bytes) {
-  var str = '';
-  var length = bytes.length;
-  for (var n = 0; n < length; ++n)
-    str += String.fromCharCode(bytes[n]);
-  return str;
-}
+function bytesToString(bytes) {
+  var str = '';
+  var length = bytes.length;
+  for (var n = 0; n < length; ++n)
+    str += String.fromCharCode(bytes[n]);
+  return str;
+}
 
 function stringToBytes(str) {
   var length = str.length;
@@ -969,7 +657,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) {
@@ -990,6 +678,19 @@ var Util = (function UtilClosure() {
     return [xt, yt];
   };
 
+  Util.applyInverseTransform = function Util_applyInverseTransform(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 |
@@ -1058,7 +759,7 @@ var Util = (function UtilClosure() {
     }
 
     return result;
-  }
+  };
 
   Util.sign = function Util_sign(num) {
     return num < 0 ? -1 : 1;
@@ -1067,6 +768,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,
@@ -1095,6 +870,10 @@ function stringToPDFString(str) {
   return str2;
 }
 
+function stringToUTF8String(str) {
+  return decodeURIComponent(escape(str));
+}
+
 function isBool(v) {
   return typeof v == 'boolean';
 }
@@ -1168,7 +947,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 = {};
 
   /**
@@ -1190,6 +969,7 @@ var Promise = (function PromiseClosure() {
     }
     this.callbacks = [];
     this.errbacks = [];
+    this.progressbacks = [];
   };
   /**
    * Builds a promise that is resolved when all the passed in promises are
@@ -1205,7 +985,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) {
@@ -1261,7 +1041,7 @@ var Promise = (function PromiseClosure() {
       }
 
       this.isResolved = true;
-      this.data = data || null;
+      this.data = (typeof data !== 'undefined') ? data : null;
       var callbacks = this.callbacks;
 
       for (var i = 0, ii = callbacks.length; i < ii; i++) {
@@ -1269,7 +1049,14 @@ var Promise = (function PromiseClosure() {
       }
     },
 
-    reject: function Promise_reject(reason) {
+    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, exception) {
       if (this.isRejected) {
         error('A Promise can be rejected only once ' + this.name);
       }
@@ -1282,11 +1069,11 @@ var Promise = (function PromiseClosure() {
       var errbacks = this.errbacks;
 
       for (var i = 0, ii = errbacks.length; i < ii; i++) {
-        errbacks[i].call(null, reason);
+        errbacks[i].call(null, reason, exception);
       }
     },
 
-    then: function Promise_then(callback, errback) {
+    then: function Promise_then(callback, errback, progressback) {
       if (!callback) {
         error('Requiring callback' + this.name);
       }
@@ -1303,6 +1090,9 @@ var Promise = (function PromiseClosure() {
         if (errback)
           this.errbacks.push(errback);
       }
+
+      if (progressback)
+        this.progressbacks.push(progressback);
     }
   };
 
@@ -1359,8 +1149,661 @@ var StatTimer = (function StatTimerClosure() {
       return out;
     }
   };
-  return StatTimer;
+  return StatTimer;
+})();
+
+/* -*- 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|object} source Can be an url to where a PDF is
+ * located, a typed array (Uint8Array) already populated with data or
+ * and parameter object with the following possible fields:
+ *  - url   - The URL of the PDF.
+ *  - data  - A typed array with PDF data.
+ *  - httpHeaders - Basic authentication headers.
+ *  - password - For decrypting password-protected PDFs.
+ *
+ * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
+ */
+PDFJS.getDocument = function getDocument(source) {
+  var url, data, headers, password, parameters = {};
+  if (typeof source === 'string') {
+    url = source;
+  } else if (isArrayBuffer(source)) {
+    data = source;
+  } else if (typeof source === 'object') {
+    url = source.url;
+    data = source.data;
+    headers = source.httpHeaders;
+    password = source.password;
+    parameters.password = password || null;
+
+    if (!url && !data)
+      error('Invalid parameter array, need either .data or .url');
+  } else {
+    error('Invalid parameter in getDocument, need either Uint8Array, ' +
+          'string or a parameter object');
+  }
+
+  var promise = new PDFJS.Promise();
+  var transport = new WorkerTransport(promise);
+  if (data) {
+    // assuming the data is array, instantiating directly from it
+    transport.sendData(data, parameters);
+  } else if (url) {
+    // fetch url
+    PDFJS.getPdf(
+      {
+        url: url,
+        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 + '.');
+        },
+        headers: headers
+      },
+      function getPDFLoad(data) {
+        transport.sendData(data, parameters);
+      });
+  }
+
+  return promise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ */
+var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
+  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 PDFDocumentProxy_getPage(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 PDFDocumentProxy_getDestinations() {
+      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 PDFDocumentProxy_getOutline() {
+      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 PDFDocumentProxy_getMetadata() {
+      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;
+    },
+    isEncrypted: function PDFDocumentProxy_isEncrypted() {
+      var promise = new PDFJS.Promise();
+      promise.resolve(this.pdfInfo.encrypted);
+      return promise;
+    },
+    /**
+     * @return {Promise} A promise that is resolved with a TypedArray that has
+     * the raw data from the PDF.
+     */
+    getData: function PDFDocumentProxy_getData() {
+      var promise = new PDFJS.Promise();
+      this.transport.getData(promise);
+      return promise;
+    },
+    destroy: function PDFDocumentProxy_destroy() {
+      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 PDFPageProxy_getViewport(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 PDFPageProxy_getAnnotations() {
+      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 PDFPageProxy_render(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 PDFPageProxy_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 PDFPageProxy_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 PDFPageProxy_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();
+    },
+    /**
+     * @return {Promise} That is resolved with the a {string} that is the text
+     * content from the page.
+     */
+    getTextContent: function PDFPageProxy_getTextContent() {
+      var promise = new PDFJS.Promise();
+      this.transport.messageHandler.send('GetTextContent', {
+          pageIndex: this.pageNumber - 1
+        },
+        function textContentCallback(textContent) {
+          promise.resolve(textContent);
+        }
+      );
+      return promise;
+    },
+    /**
+     * Stub for future feature.
+     */
+    getOperationList: function PDFPageProxy_getOperationList() {
+      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 PDFPageProxy_destroy() {
+      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) {
+        info('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('NeedPassword', function transportPassword(data) {
+        this.workerReadyPromise.reject(data.exception.message, data.exception);
+      }, this);
+
+      messageHandler.on('IncorrectPassword', function transportBadPass(data) {
+        this.workerReadyPromise.reject(data.exception.message, data.exception);
+      }, 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, params) {
+      this.messageHandler.send('GetDocRequest', {data: data, params: params});
+    },
+
+    getData: function WorkerTransport_sendData(promise) {
+      this.messageHandler.send('GetData', null, function(data) {
+        promise.resolve(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;
+
 })();
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -1604,27 +2047,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();
@@ -1723,10 +2149,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
       this.ctx.webkitLineDashOffset = dashPhase;
     },
     setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
-      TODO('set rendering intent: ' + intent);
+      // Maybe if we one day fully support color spaces this will be important
+      // for now we can ignore.
+      // TODO set rendering intent?
     },
     setFlatness: function CanvasGraphics_setFlatness(flatness) {
-      TODO('set flatness: ' + flatness);
+      // There's no way to control this with canvas, but we can safely ignore.
+      // TODO set flatness?
     },
     setGState: function CanvasGraphics_setGState(states) {
       for (var i = 0, ii = states.length; i < ii; i++) {
@@ -2221,7 +2650,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
             text.length += shownText.length;
           }
         } else {
-          malformed('TJ array element ' + e + ' is not string or num');
+          error('TJ array element ' + e + ' is not string or num');
         }
       }
 
@@ -2474,6 +2903,40 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
           }
         }
       }
+      function rescaleImage(pixels, widthScale, heightScale) {
+        var scaledWidth = Math.ceil(width / widthScale);
+        var scaledHeight = Math.ceil(height / heightScale);
+
+        var itemsSum = new Uint32Array(scaledWidth * scaledHeight * 4);
+        var itemsCount = new Uint32Array(scaledWidth * scaledHeight);
+        for (var i = 0, position = 0; i < height; i++) {
+          var lineOffset = (0 | (i / heightScale)) * scaledWidth;
+          for (var j = 0; j < width; j++) {
+            var countOffset = lineOffset + (0 | (j / widthScale));
+            var sumOffset = countOffset << 2;
+            itemsSum[sumOffset] += pixels[position];
+            itemsSum[sumOffset + 1] += pixels[position + 1];
+            itemsSum[sumOffset + 2] += pixels[position + 2];
+            itemsSum[sumOffset + 3] += pixels[position + 3];
+            itemsCount[countOffset]++;
+            position += 4;
+          }
+        }
+        var tmpCanvas = createScratchCanvas(scaledWidth, scaledHeight);
+        var tmpCtx = tmpCanvas.getContext('2d');
+        var imgData = tmpCtx.getImageData(0, 0, scaledWidth, scaledHeight);
+        pixels = imgData.data;
+        for (var i = 0, j = 0, ii = scaledWidth * scaledHeight; i < ii; i++) {
+          var count = itemsCount[i];
+          pixels[j] = itemsSum[j] / count;
+          pixels[j + 1] = itemsSum[j + 1] / count;
+          pixels[j + 2] = itemsSum[j + 2] / count;
+          pixels[j + 3] = itemsSum[j + 3] / count;
+          j += 4;
+        }
+        tmpCtx.putImageData(imgData, 0, 0);
+        return tmpCanvas;
+      }
 
       this.save();
 
@@ -2496,8 +2959,19 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
 
       applyStencilMask(pixels, inverseDecode);
 
-      tmpCtx.putImageData(imgData, 0, 0);
-      ctx.drawImage(tmpCanvas, 0, -h);
+      var currentTransform = ctx.mozCurrentTransformInverse;
+      var widthScale = Math.max(Math.abs(currentTransform[0]), 1);
+      var heightScale = Math.max(Math.abs(currentTransform[3]), 1);
+      if (widthScale >= 2 || heightScale >= 2) {
+        // canvas does not resize well large images to small -- using simple
+        // algorithm to perform pre-scaling
+        tmpCanvas = rescaleImage(imgData.data, widthScale, heightScale);
+        ctx.scale(widthScale, heightScale);
+        ctx.drawImage(tmpCanvas, 0, -h / heightScale);
+      } else {
+        tmpCtx.putImageData(imgData, 0, 0);
+        ctx.drawImage(tmpCanvas, 0, -h);
+      }
       this.restore();
     },
 
@@ -2624,6 +3098,7 @@ if (!isWorker) {
       };
   }
 }
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -2663,51 +3138,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_assignXref(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;
@@ -2754,7 +3233,14 @@ var Catalog = (function CatalogClosure() {
 
   Catalog.prototype = {
     get metadata() {
-      var stream = this.catDict.get('Metadata');
+      var streamRef = this.catDict.getRaw('Metadata');
+      if (!isRef(streamRef))
+        return shadow(this, 'metadata', null);
+
+      var encryptMetadata = !this.xref.encrypt ? false :
+        this.xref.encrypt.encryptMetadata;
+
+      var stream = this.xref.fetch(streamRef, !encryptMetadata);
       var metadata;
       if (stream && isDict(stream.dict)) {
         var type = stream.dict.get('Type');
@@ -2762,7 +3248,16 @@ var Catalog = (function CatalogClosure() {
 
         if (isName(type) && isName(subtype) &&
             type.name === 'Metadata' && subtype.name === 'XML') {
-          metadata = stringToPDFString(bytesToString(stream.getBytes()));
+          // XXX: This should examine the charset the XML document defines,
+          // however since there are currently no real means to decode
+          // arbitrary charsets, let's just hope that the author of the PDF
+          // was reasonable enough to stick with the XML default charset,
+          // which is UTF-8.
+          try {
+            metadata = stringToUTF8String(bytesToString(stream.getBytes()));
+          } catch (e) {
+            info('Skipping invalid metadata.');
+          }
         }
       }
 
@@ -2920,12 +3415,12 @@ var Catalog = (function CatalogClosure() {
 })();
 
 var XRef = (function XRefClosure() {
-  function XRef(stream, startXRef, mainXRefEntriesOffset) {
+  function XRef(stream, startXRef, mainXRefEntriesOffset, password) {
     this.stream = stream;
     this.entries = [];
     this.xrefstms = {};
     var trailerDict = this.readXRef(startXRef);
-    trailerDict.xref = this;
+    trailerDict.assignXref(this);
     this.trailer = trailerDict;
     // prepare the XRef cache
     this.cache = [];
@@ -2933,8 +3428,7 @@ var XRef = (function XRefClosure() {
     var encrypt = trailerDict.get('Encrypt');
     if (encrypt) {
       var fileId = trailerDict.get('ID');
-      this.encrypt = new CipherTransformFactory(encrypt,
-                                                fileId[0] /*, password */);
+      this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password);
     }
 
     // get the root dictionary (catalog) object
@@ -2986,9 +3480,8 @@ var XRef = (function XRefClosure() {
         }
       }
 
-      // Sanity check: as per spec, first object must have these properties
-      if (this.entries[0] &&
-          !(this.entries[0].gen === 65535 && this.entries[0].free))
+      // Sanity check: as per spec, first object must be free
+      if (this.entries[0] && !this.entries[0].free)
         error('Invalid XRef table: unexpected first object');
 
       // Sanity check
@@ -3147,7 +3640,7 @@ var XRef = (function XRefClosure() {
       }
       // reading XRef streams
       for (var i = 0, ii = xrefStms.length; i < ii; ++i) {
-          this.readXRef(xrefStms[i]);
+          this.readXRef(xrefStms[i], true);
       }
       // finding main trailer
       var dict;
@@ -3170,7 +3663,7 @@ var XRef = (function XRefClosure() {
       // nothing helps
       error('Invalid PDF structure');
     },
-    readXRef: function XRef_readXRef(startXRef) {
+    readXRef: function XRef_readXRef(startXRef, recoveryMode) {
       var stream = this.stream;
       stream.pos = startXRef;
 
@@ -3203,16 +3696,18 @@ var XRef = (function XRefClosure() {
             error('Invalid XRef stream');
           }
           dict = this.readXRefStream(obj);
+          if (!dict)
+            error('Failed to read XRef stream');
         }
 
         // Recursively get previous dictionary, if any
         obj = dict.get('Prev');
         if (isInt(obj))
-          this.readXRef(obj);
+          this.readXRef(obj, recoveryMode);
         else if (isRef(obj)) {
           // The spec says Prev must not be a reference, i.e. "/Prev NNN"
           // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
-          this.readXRef(obj.num);
+          this.readXRef(obj.num, recoveryMode);
         }
 
         return dict;
@@ -3220,6 +3715,9 @@ var XRef = (function XRefClosure() {
         log('(while reading XRef): ' + e);
       }
 
+      if (recoveryMode)
+        return;
+
       warn('Indexing all PDF objects');
       return this.indexObjects();
     },
@@ -3432,6 +3930,7 @@ var PDFObjects = (function PDFObjectsClosure() {
   return PDFObjects;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -4270,7 +4769,10 @@ var PostScriptLexer = (function PostScriptLexerClosure() {
       // operator
       var str = ch.toLowerCase();
       while (true) {
-        ch = stream.lookChar().toLowerCase();
+        ch = stream.lookChar();
+        if (ch === null)
+          break;
+        ch = ch.toLowerCase();
         if (ch >= 'a' && ch <= 'z')
           str += ch;
         else
@@ -4306,6 +4808,7 @@ var PostScriptLexer = (function PostScriptLexerClosure() {
   return PostScriptLexer;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -4411,6 +4914,7 @@ var ExpertSubsetCharset = [
   'periodinferior', 'commainferior'
 ];
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -11344,6 +11848,7 @@ var CIDToUnicodeMaps = {
     {f: 7, c: 19887}]
 };
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -11797,12 +12302,12 @@ var LabCS = (function LabCSClosure() {
       error('Invalid WhitePoint components, no fallback available');
 
     if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
-      warn('Invalid BlackPoint, falling back to default');
+      info('Invalid BlackPoint, falling back to default');
       this.XB = this.YB = this.ZB = 0;
     }
 
     if (this.amin > this.amax || this.bmin > this.bmax) {
-      warn('Invalid Range, falling back to defaults');
+      info('Invalid Range, falling back to defaults');
       this.amin = -100;
       this.amax = 100;
       this.bmin = -100;
@@ -11876,6 +12381,7 @@ var LabCS = (function LabCSClosure() {
   };
   return LabCS;
 })();
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -12297,13 +12803,14 @@ var CipherTransform = (function CipherTransformClosure() {
 })();
 
 var CipherTransformFactory = (function CipherTransformFactoryClosure() {
+  var defaultPasswordBytes = new Uint8Array([
+    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
+    0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
+    0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
+
   function prepareKeyData(fileId, password, ownerPassword, userPassword,
                           flags, revision, keyLength, encryptMetadata) {
-    var defaultPasswordBytes = new Uint8Array([
-      0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
-      0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
-      0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
-      0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
     var hashData = new Uint8Array(100), i = 0, j, n;
     if (password) {
       n = Math.min(32, password.length);
@@ -12340,9 +12847,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
     var cipher, checkData;
 
     if (revision >= 3) {
-      // padded password in hashData, we can use this array for user
-      // password check
-      i = 32;
+      for (i = 0; i < 32; ++i)
+        hashData[i] = defaultPasswordBytes[i];
       for (j = 0, n = fileId.length; j < n; ++j)
         hashData[i++] = fileId[j];
       cipher = new ARCFourCipher(encryptionKey);
@@ -12355,16 +12861,53 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
         cipher = new ARCFourCipher(derivedKey);
         checkData = cipher.encryptBlock(checkData);
       }
+      for (j = 0, n = checkData.length; j < n; ++j) {
+        if (userPassword[j] != checkData[j])
+          return null;
+      }
     } else {
       cipher = new ARCFourCipher(encryptionKey);
-      checkData = cipher.encryptBlock(hashData.subarray(0, 32));
-    }
-    for (j = 0, n = checkData.length; j < n; ++j) {
-      if (userPassword[j] != checkData[j])
-        error('incorrect password');
+      checkData = cipher.encryptBlock(defaultPasswordBytes);
+      for (j = 0, n = checkData.length; j < n; ++j) {
+        if (userPassword[j] != checkData[j])
+          return null;
+      }
     }
     return encryptionKey;
   }
+  function decodeUserPassword(password, ownerPassword, revision, keyLength) {
+    var hashData = new Uint8Array(32), i = 0, j, n;
+    n = Math.min(32, password.length);
+    for (; i < n; ++i)
+      hashData[i] = password[i];
+    j = 0;
+    while (i < 32) {
+      hashData[i++] = defaultPasswordBytes[j++];
+    }
+    var hash = calculateMD5(hashData, 0, i);
+    var keyLengthInBytes = keyLength >> 3;
+    if (revision >= 3) {
+      for (j = 0; j < 50; ++j) {
+         hash = calculateMD5(hash, 0, hash.length);
+      }
+    }
+
+    var cipher, userPassword;
+    if (revision >= 3) {
+      userPassword = ownerPassword;
+      var derivedKey = new Uint8Array(keyLengthInBytes), k;
+      for (j = 19; j >= 0; j--) {
+        for (k = 0; k < keyLengthInBytes; ++k)
+          derivedKey[k] = hash[k] ^ j;
+        cipher = new ARCFourCipher(derivedKey);
+        userPassword = cipher.encryptBlock(userPassword);
+      }
+    } else {
+      cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
+      userPassword = cipher.encryptBlock(ownerPassword);
+    }
+    return userPassword;
+  }
 
   var identityName = new Name('Identity');
 
@@ -12387,17 +12930,34 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
     var userPassword = stringToBytes(dict.get('U'));
     var flags = dict.get('P');
     var revision = dict.get('R');
-    var encryptMetadata =
+    var encryptMetadata = algorithm == 4 &&  // meaningful when V is 4
       dict.get('EncryptMetadata') !== false; // makes true as default value
+    this.encryptMetadata = encryptMetadata;
+
     var fileIdBytes = stringToBytes(fileId);
     var passwordBytes;
     if (password)
       passwordBytes = stringToBytes(password);
 
-    this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
-                                        ownerPassword, userPassword,
-                                        flags, revision,
-                                        keyLength, encryptMetadata);
+    var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
+                                       ownerPassword, userPassword, flags,
+                                       revision, keyLength, encryptMetadata);
+    if (!encryptionKey && !password) {
+      throw new PasswordException('No password given', 'needpassword');
+    } else if (!encryptionKey && password) {
+      // Attempting use the password as an owner password
+      var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
+                                               revision, keyLength);
+      encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
+                                     ownerPassword, userPassword, flags,
+                                     revision, keyLength, encryptMetadata);
+    }
+
+    if (!encryptionKey)
+      throw new PasswordException('Incorrect Password', 'incorrectpassword');
+
+    this.encryptionKey = encryptionKey;
+
     if (algorithm == 4) {
       this.cf = dict.get('CF');
       this.stmf = dict.get('StmF') || identityName;
@@ -12472,6 +13032,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
   return CipherTransformFactory;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -12582,20 +13143,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
     // Compatibility
     BX: 'beginCompat',
-    EX: 'endCompat'
+    EX: 'endCompat',
+
+    // (reserved partial commands for the lexer)
+    BM: null,
+    BD: null,
+    'true': null,
+    fa: null,
+    fal: null,
+    fals: null,
+    'false': null,
+    nu: null,
+    nul: null,
+    'null': null
   };
 
-  function splitCombinedOperations(operations) {
-    // Two operations can be combined together, trying to find which two
-    // operations were concatenated.
-    for (var i = operations.length - 1; i > 0; i--) {
-      var op1 = operations.substring(0, i), op2 = operations.substring(i);
-      if (op1 in OP_MAP && op2 in OP_MAP)
-        return [op1, op2]; // operations found
-    }
-    return null;
-  }
-
   PartialEvaluator.prototype = {
     getOperatorList: function PartialEvaluator_getOperatorList(stream,
                                                                resources,
@@ -12627,13 +13189,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
         font = xref.fetchIfRef(font) || fontRes.get(fontName);
         assertWellFormed(isDict(font));
-        if (!font.translated) {
+
+        ++self.objIdCounter;
+        if (!font.loadedName) {
           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;
 
@@ -12738,36 +13302,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
       resources = resources || new Dict();
       var xobjs = resources.get('XObject') || new Dict();
       var patterns = resources.get('Pattern') || new Dict();
-      var parser = new Parser(new Lexer(stream), false, xref);
+      var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
       var res = resources;
-      var hasNextObj = false, nextObj;
       var args = [], obj;
       var TILING_PATTERN = 1, SHADING_PATTERN = 2;
 
       while (true) {
-        if (hasNextObj) {
-          obj = nextObj;
-          hasNextObj = false;
-        } else {
-          obj = parser.getObj();
-          if (isEOF(obj))
-            break;
-        }
+        obj = parser.getObj();
+        if (isEOF(obj))
+          break;
 
         if (isCmd(obj)) {
           var cmd = obj.cmd;
           var fn = OP_MAP[cmd];
-          if (!fn) {
-            // invalid content command, trying to recover
-            var cmds = splitCombinedOperations(cmd);
-            if (cmds) {
-              cmd = cmds[0];
-              fn = OP_MAP[cmd];
-              // feeding other command on the next interation
-              hasNextObj = true;
-              nextObj = Cmd.get(cmds[1]);
-            }
-          }
           assertWellFormed(fn, 'Unknown command "' + cmd + '"');
           // TODO figure out how to type-check vararg functions
 
@@ -12907,6 +13454,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
                         value[1]
                       ]);
                       break;
+                    case 'BM':
+                      // We support the default so don't trigger the TODO.
+                      if (!isName(value) || value.name != 'Normal')
+                        TODO('graphic state operator ' + key);
+                      break;
+                    case 'SMask':
+                      // We support the default so don't trigger the TODO.
+                      if (!isName(value) || value.name != 'None')
+                        TODO('graphic state operator ' + key);
+                      break;
+                    // Only generate info log messages for the following since
+                    // they are unlikey to have a big impact on the rendering.
                     case 'OP':
                     case 'op':
                     case 'OPM':
@@ -12919,14 +13478,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
                     case 'HT':
                     case 'SM':
                     case 'SA':
-                    case 'BM':
-                    case 'SMask':
                     case 'AIS':
                     case 'TK':
-                      TODO('graphic state operator ' + key);
+                      // TODO implement these operators.
+                      info('graphic state operator ' + key);
                       break;
                     default:
-                      warn('Unknown graphic state operator ' + key);
+                      info('Unknown graphic state operator ' + key);
                       break;
                   }
                 }
@@ -12940,13 +13498,88 @@ 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);
         }
       }
 
       return queue;
     },
 
+    getTextContent: function partialEvaluatorGetIRQueue(stream, resources) {
+
+      var self = this;
+      var xref = this.xref;
+
+      function handleSetFont(fontName, fontRef) {
+        var fontRes = resources.get('Font');
+
+        // TODO: TOASK: Is it possible to get here? If so, what does
+        // args[0].name should be like???
+        assert(fontRes, 'fontRes not available');
+
+        fontRes = xref.fetchIfRef(fontRes);
+        fontRef = fontRef || fontRes.get(fontName);
+        var font = xref.fetchIfRef(fontRef), tra;
+        assertWellFormed(isDict(font));
+        if (!font.translated) {
+          font.translated = self.translateFont(font, xref, resources);
+        }
+        return font;
+      }
+
+      resources = xref.fetchIfRef(resources) || new Dict();
+
+      var parser = new Parser(new Lexer(stream), false);
+      var res = resources;
+      var args = [], obj;
+
+      var text = '';
+      var chunk = '';
+      var font = null;
+      while (!isEOF(obj = parser.getObj())) {
+        if (isCmd(obj)) {
+          var cmd = obj.cmd;
+          switch (cmd) {
+            case 'Tf':
+              font = handleSetFont(args[0].name);
+              break;
+            case 'TJ':
+              var items = args[0];
+              for (var j = 0, jj = items.length; j < jj; j++) {
+                if (typeof items[j] === 'string') {
+                  chunk += items[j];
+                } else if (items[j] < 0) {
+                  // making all negative offsets a space - better to have
+                  // a space in incorrect place than not have them at all
+                  chunk += ' ';
+                }
+              }
+              break;
+            case 'Tj':
+              chunk += args[0];
+              break;
+            case "'":
+              chunk += args[0] + ' ';
+              break;
+            case '"':
+              chunk += args[2] + ' ';
+              break;
+          } // switch
+          if (chunk !== '') {
+            text += fontCharsToUnicode(chunk, font.translated.properties);
+            chunk = '';
+          }
+
+          args = [];
+        } else if (obj != null) {
+          assertWellFormed(args.length <= 33, 'Too many arguments');
+          args.push(obj);
+        }
+      }
+
+      return text;
+    },
+
     extractDataStructures: function
       partialEvaluatorExtractDataStructures(dict, baseDict,
                                             xref, properties) {
@@ -12954,7 +13587,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
       var toUnicode = dict.get('ToUnicode') ||
         baseDict.get('ToUnicode');
       if (toUnicode)
-        properties.toUnicode = this.readToUnicode(toUnicode, xref);
+        properties.toUnicode = this.readToUnicode(toUnicode, xref, properties);
 
       if (properties.composite) {
         // CIDSystemInfo helps to match CID to glyphs
@@ -13010,7 +13643,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
       properties.hasEncoding = hasEncoding;
     },
 
-    readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref) {
+    readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref,
+                                                           properties) {
       var cmapObj = toUnicode;
       var charToUnicode = [];
       if (isName(cmapObj)) {
@@ -13099,6 +13733,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
             }
           } else if (octet == 0x3E) {
             if (token.length) {
+              // Heuristic: guessing chars size by checking numbers sizes
+              // in the CMap entries.
+              if (token.length == 2 && properties.composite)
+                properties.wideChars = false;
+
               if (token.length <= 4) {
                 // parsing hex number
                 tokens.push(parseInt(token, 16));
@@ -13316,6 +13955,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         length1: length1,
         length2: length2,
         composite: composite,
+        wideChars: composite,
         fixedPitch: false,
         fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX,
         firstChar: firstChar || 0,
@@ -13336,7 +13976,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];
@@ -13380,6 +14019,7 @@ var EvalState = (function EvalStateClosure() {
   return EvalState;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -14135,6 +14775,736 @@ function isSpecialUnicode(unicode) {
     unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
 }
 
+// The normalization table is obtained by filtering the Unicode characters
+// database with <compat> entries.
+var NormalizedUnicodes = {
+  '\u00A8': '\u0020\u0308',
+  '\u00AF': '\u0020\u0304',
+  '\u00B4': '\u0020\u0301',
+  '\u00B5': '\u03BC',
+  '\u00B8': '\u0020\u0327',
+  '\u0132': '\u0049\u004A',
+  '\u0133': '\u0069\u006A',
+  '\u013F': '\u004C\u00B7',
+  '\u0140': '\u006C\u00B7',
+  '\u0149': '\u02BC\u006E',
+  '\u017F': '\u0073',
+  '\u01C4': '\u0044\u017D',
+  '\u01C5': '\u0044\u017E',
+  '\u01C6': '\u0064\u017E',
+  '\u01C7': '\u004C\u004A',
+  '\u01C8': '\u004C\u006A',
+  '\u01C9': '\u006C\u006A',
+  '\u01CA': '\u004E\u004A',
+  '\u01CB': '\u004E\u006A',
+  '\u01CC': '\u006E\u006A',
+  '\u01F1': '\u0044\u005A',
+  '\u01F2': '\u0044\u007A',
+  '\u01F3': '\u0064\u007A',
+  '\u02D8': '\u0020\u0306',
+  '\u02D9': '\u0020\u0307',
+  '\u02DA': '\u0020\u030A',
+  '\u02DB': '\u0020\u0328',
+  '\u02DC': '\u0020\u0303',
+  '\u02DD': '\u0020\u030B',
+  '\u037A': '\u0020\u0345',
+  '\u0384': '\u0020\u0301',
+  '\u03D0': '\u03B2',
+  '\u03D1': '\u03B8',
+  '\u03D2': '\u03A5',
+  '\u03D5': '\u03C6',
+  '\u03D6': '\u03C0',
+  '\u03F0': '\u03BA',
+  '\u03F1': '\u03C1',
+  '\u03F2': '\u03C2',
+  '\u03F4': '\u0398',
+  '\u03F5': '\u03B5',
+  '\u03F9': '\u03A3',
+  '\u0587': '\u0565\u0582',
+  '\u0675': '\u0627\u0674',
+  '\u0676': '\u0648\u0674',
+  '\u0677': '\u06C7\u0674',
+  '\u0678': '\u064A\u0674',
+  '\u0E33': '\u0E4D\u0E32',
+  '\u0EB3': '\u0ECD\u0EB2',
+  '\u0EDC': '\u0EAB\u0E99',
+  '\u0EDD': '\u0EAB\u0EA1',
+  '\u0F77': '\u0FB2\u0F81',
+  '\u0F79': '\u0FB3\u0F81',
+  '\u1E9A': '\u0061\u02BE',
+  '\u1FBD': '\u0020\u0313',
+  '\u1FBF': '\u0020\u0313',
+  '\u1FC0': '\u0020\u0342',
+  '\u1FFE': '\u0020\u0314',
+  '\u2002': '\u0020',
+  '\u2003': '\u0020',
+  '\u2004': '\u0020',
+  '\u2005': '\u0020',
+  '\u2006': '\u0020',
+  '\u2008': '\u0020',
+  '\u2009': '\u0020',
+  '\u200A': '\u0020',
+  '\u2017': '\u0020\u0333',
+  '\u2024': '\u002E',
+  '\u2025': '\u002E\u002E',
+  '\u2026': '\u002E\u002E\u002E',
+  '\u2033': '\u2032\u2032',
+  '\u2034': '\u2032\u2032\u2032',
+  '\u2036': '\u2035\u2035',
+  '\u2037': '\u2035\u2035\u2035',
+  '\u203C': '\u0021\u0021',
+  '\u203E': '\u0020\u0305',
+  '\u2047': '\u003F\u003F',
+  '\u2048': '\u003F\u0021',
+  '\u2049': '\u0021\u003F',
+  '\u2057': '\u2032\u2032\u2032\u2032',
+  '\u205F': '\u0020',
+  '\u20A8': '\u0052\u0073',
+  '\u2100': '\u0061\u002F\u0063',
+  '\u2101': '\u0061\u002F\u0073',
+  '\u2103': '\u00B0\u0043',
+  '\u2105': '\u0063\u002F\u006F',
+  '\u2106': '\u0063\u002F\u0075',
+  '\u2107': '\u0190',
+  '\u2109': '\u00B0\u0046',
+  '\u2116': '\u004E\u006F',
+  '\u2121': '\u0054\u0045\u004C',
+  '\u2135': '\u05D0',
+  '\u2136': '\u05D1',
+  '\u2137': '\u05D2',
+  '\u2138': '\u05D3',
+  '\u213B': '\u0046\u0041\u0058',
+  '\u2160': '\u0049',
+  '\u2161': '\u0049\u0049',
+  '\u2162': '\u0049\u0049\u0049',
+  '\u2163': '\u0049\u0056',
+  '\u2164': '\u0056',
+  '\u2165': '\u0056\u0049',
+  '\u2166': '\u0056\u0049\u0049',
+  '\u2167': '\u0056\u0049\u0049\u0049',
+  '\u2168': '\u0049\u0058',
+  '\u2169': '\u0058',
+  '\u216A': '\u0058\u0049',
+  '\u216B': '\u0058\u0049\u0049',
+  '\u216C': '\u004C',
+  '\u216D': '\u0043',
+  '\u216E': '\u0044',
+  '\u216F': '\u004D',
+  '\u2170': '\u0069',
+  '\u2171': '\u0069\u0069',
+  '\u2172': '\u0069\u0069\u0069',
+  '\u2173': '\u0069\u0076',
+  '\u2174': '\u0076',
+  '\u2175': '\u0076\u0069',
+  '\u2176': '\u0076\u0069\u0069',
+  '\u2177': '\u0076\u0069\u0069\u0069',
+  '\u2178': '\u0069\u0078',
+  '\u2179': '\u0078',
+  '\u217A': '\u0078\u0069',
+  '\u217B': '\u0078\u0069\u0069',
+  '\u217C': '\u006C',
+  '\u217D': '\u0063',
+  '\u217E': '\u0064',
+  '\u217F': '\u006D',
+  '\u222C': '\u222B\u222B',
+  '\u222D': '\u222B\u222B\u222B',
+  '\u222F': '\u222E\u222E',
+  '\u2230': '\u222E\u222E\u222E',
+  '\u2474': '\u0028\u0031\u0029',
+  '\u2475': '\u0028\u0032\u0029',
+  '\u2476': '\u0028\u0033\u0029',
+  '\u2477': '\u0028\u0034\u0029',
+  '\u2478': '\u0028\u0035\u0029',
+  '\u2479': '\u0028\u0036\u0029',
+  '\u247A': '\u0028\u0037\u0029',
+  '\u247B': '\u0028\u0038\u0029',
+  '\u247C': '\u0028\u0039\u0029',
+  '\u247D': '\u0028\u0031\u0030\u0029',
+  '\u247E': '\u0028\u0031\u0031\u0029',
+  '\u247F': '\u0028\u0031\u0032\u0029',
+  '\u2480': '\u0028\u0031\u0033\u0029',
+  '\u2481': '\u0028\u0031\u0034\u0029',
+  '\u2482': '\u0028\u0031\u0035\u0029',
+  '\u2483': '\u0028\u0031\u0036\u0029',
+  '\u2484': '\u0028\u0031\u0037\u0029',
+  '\u2485': '\u0028\u0031\u0038\u0029',
+  '\u2486': '\u0028\u0031\u0039\u0029',
+  '\u2487': '\u0028\u0032\u0030\u0029',
+  '\u2488': '\u0031\u002E',
+  '\u2489': '\u0032\u002E',
+  '\u248A': '\u0033\u002E',
+  '\u248B': '\u0034\u002E',
+  '\u248C': '\u0035\u002E',
+  '\u248D': '\u0036\u002E',
+  '\u248E': '\u0037\u002E',
+  '\u248F': '\u0038\u002E',
+  '\u2490': '\u0039\u002E',
+  '\u2491': '\u0031\u0030\u002E',
+  '\u2492': '\u0031\u0031\u002E',
+  '\u2493': '\u0031\u0032\u002E',
+  '\u2494': '\u0031\u0033\u002E',
+  '\u2495': '\u0031\u0034\u002E',
+  '\u2496': '\u0031\u0035\u002E',
+  '\u2497': '\u0031\u0036\u002E',
+  '\u2498': '\u0031\u0037\u002E',
+  '\u2499': '\u0031\u0038\u002E',
+  '\u249A': '\u0031\u0039\u002E',
+  '\u249B': '\u0032\u0030\u002E',
+  '\u249C': '\u0028\u0061\u0029',
+  '\u249D': '\u0028\u0062\u0029',
+  '\u249E': '\u0028\u0063\u0029',
+  '\u249F': '\u0028\u0064\u0029',
+  '\u24A0': '\u0028\u0065\u0029',
+  '\u24A1': '\u0028\u0066\u0029',
+  '\u24A2': '\u0028\u0067\u0029',
+  '\u24A3': '\u0028\u0068\u0029',
+  '\u24A4': '\u0028\u0069\u0029',
+  '\u24A5': '\u0028\u006A\u0029',
+  '\u24A6': '\u0028\u006B\u0029',
+  '\u24A7': '\u0028\u006C\u0029',
+  '\u24A8': '\u0028\u006D\u0029',
+  '\u24A9': '\u0028\u006E\u0029',
+  '\u24AA': '\u0028\u006F\u0029',
+  '\u24AB': '\u0028\u0070\u0029',
+  '\u24AC': '\u0028\u0071\u0029',
+  '\u24AD': '\u0028\u0072\u0029',
+  '\u24AE': '\u0028\u0073\u0029',
+  '\u24AF': '\u0028\u0074\u0029',
+  '\u24B0': '\u0028\u0075\u0029',
+  '\u24B1': '\u0028\u0076\u0029',
+  '\u24B2': '\u0028\u0077\u0029',
+  '\u24B3': '\u0028\u0078\u0029',
+  '\u24B4': '\u0028\u0079\u0029',
+  '\u24B5': '\u0028\u007A\u0029',
+  '\u2A0C': '\u222B\u222B\u222B\u222B',
+  '\u2A74': '\u003A\u003A\u003D',
+  '\u2A75': '\u003D\u003D',
+  '\u2A76': '\u003D\u003D\u003D',
+  '\u2E9F': '\u6BCD',
+  '\u2EF3': '\u9F9F',
+  '\u2F00': '\u4E00',
+  '\u2F01': '\u4E28',
+  '\u2F02': '\u4E36',
+  '\u2F03': '\u4E3F',
+  '\u2F04': '\u4E59',
+  '\u2F05': '\u4E85',
+  '\u2F06': '\u4E8C',
+  '\u2F07': '\u4EA0',
+  '\u2F08': '\u4EBA',
+  '\u2F09': '\u513F',
+  '\u2F0A': '\u5165',
+  '\u2F0B': '\u516B',
+  '\u2F0C': '\u5182',
+  '\u2F0D': '\u5196',
+  '\u2F0E': '\u51AB',
+  '\u2F0F': '\u51E0',
+  '\u2F10': '\u51F5',
+  '\u2F11': '\u5200',
+  '\u2F12': '\u529B',
+  '\u2F13': '\u52F9',
+  '\u2F14': '\u5315',
+  '\u2F15': '\u531A',
+  '\u2F16': '\u5338',
+  '\u2F17': '\u5341',
+  '\u2F18': '\u535C',
+  '\u2F19': '\u5369',
+  '\u2F1A': '\u5382',
+  '\u2F1B': '\u53B6',
+  '\u2F1C': '\u53C8',
+  '\u2F1D': '\u53E3',
+  '\u2F1E': '\u56D7',
+  '\u2F1F': '\u571F',
+  '\u2F20': '\u58EB',
+  '\u2F21': '\u5902',
+  '\u2F22': '\u590A',
+  '\u2F23': '\u5915',
+  '\u2F24': '\u5927',
+  '\u2F25': '\u5973',
+  '\u2F26': '\u5B50',
+  '\u2F27': '\u5B80',
+  '\u2F28': '\u5BF8',
+  '\u2F29': '\u5C0F',
+  '\u2F2A': '\u5C22',
+  '\u2F2B': '\u5C38',
+  '\u2F2C': '\u5C6E',
+  '\u2F2D': '\u5C71',
+  '\u2F2E': '\u5DDB',
+  '\u2F2F': '\u5DE5',
+  '\u2F30': '\u5DF1',
+  '\u2F31': '\u5DFE',
+  '\u2F32': '\u5E72',
+  '\u2F33': '\u5E7A',
+  '\u2F34': '\u5E7F',
+  '\u2F35': '\u5EF4',
+  '\u2F36': '\u5EFE',
+  '\u2F37': '\u5F0B',
+  '\u2F38': '\u5F13',
+  '\u2F39': '\u5F50',
+  '\u2F3A': '\u5F61',
+  '\u2F3B': '\u5F73',
+  '\u2F3C': '\u5FC3',
+  '\u2F3D': '\u6208',
+  '\u2F3E': '\u6236',
+  '\u2F3F': '\u624B',
+  '\u2F40': '\u652F',
+  '\u2F41': '\u6534',
+  '\u2F42': '\u6587',
+  '\u2F43': '\u6597',
+  '\u2F44': '\u65A4',
+  '\u2F45': '\u65B9',
+  '\u2F46': '\u65E0',
+  '\u2F47': '\u65E5',
+  '\u2F48': '\u66F0',
+  '\u2F49': '\u6708',
+  '\u2F4A': '\u6728',
+  '\u2F4B': '\u6B20',
+  '\u2F4C': '\u6B62',
+  '\u2F4D': '\u6B79',
+  '\u2F4E': '\u6BB3',
+  '\u2F4F': '\u6BCB',
+  '\u2F50': '\u6BD4',
+  '\u2F51': '\u6BDB',
+  '\u2F52': '\u6C0F',
+  '\u2F53': '\u6C14',
+  '\u2F54': '\u6C34',
+  '\u2F55': '\u706B',
+  '\u2F56': '\u722A',
+  '\u2F57': '\u7236',
+  '\u2F58': '\u723B',
+  '\u2F59': '\u723F',
+  '\u2F5A': '\u7247',
+  '\u2F5B': '\u7259',
+  '\u2F5C': '\u725B',
+  '\u2F5D': '\u72AC',
+  '\u2F5E': '\u7384',
+  '\u2F5F': '\u7389',
+  '\u2F60': '\u74DC',
+  '\u2F61': '\u74E6',
+  '\u2F62': '\u7518',
+  '\u2F63': '\u751F',
+  '\u2F64': '\u7528',
+  '\u2F65': '\u7530',
+  '\u2F66': '\u758B',
+  '\u2F67': '\u7592',
+  '\u2F68': '\u7676',
+  '\u2F69': '\u767D',
+  '\u2F6A': '\u76AE',
+  '\u2F6B': '\u76BF',
+  '\u2F6C': '\u76EE',
+  '\u2F6D': '\u77DB',
+  '\u2F6E': '\u77E2',
+  '\u2F6F': '\u77F3',
+  '\u2F70': '\u793A',
+  '\u2F71': '\u79B8',
+  '\u2F72': '\u79BE',
+  '\u2F73': '\u7A74',
+  '\u2F74': '\u7ACB',
+  '\u2F75': '\u7AF9',
+  '\u2F76': '\u7C73',
+  '\u2F77': '\u7CF8',
+  '\u2F78': '\u7F36',
+  '\u2F79': '\u7F51',
+  '\u2F7A': '\u7F8A',
+  '\u2F7B': '\u7FBD',
+  '\u2F7C': '\u8001',
+  '\u2F7D': '\u800C',
+  '\u2F7E': '\u8012',
+  '\u2F7F': '\u8033',
+  '\u2F80': '\u807F',
+  '\u2F81': '\u8089',
+  '\u2F82': '\u81E3',
+  '\u2F83': '\u81EA',
+  '\u2F84': '\u81F3',
+  '\u2F85': '\u81FC',
+  '\u2F86': '\u820C',
+  '\u2F87': '\u821B',
+  '\u2F88': '\u821F',
+  '\u2F89': '\u826E',
+  '\u2F8A': '\u8272',
+  '\u2F8B': '\u8278',
+  '\u2F8C': '\u864D',
+  '\u2F8D': '\u866B',
+  '\u2F8E': '\u8840',
+  '\u2F8F': '\u884C',
+  '\u2F90': '\u8863',
+  '\u2F91': '\u897E',
+  '\u2F92': '\u898B',
+  '\u2F93': '\u89D2',
+  '\u2F94': '\u8A00',
+  '\u2F95': '\u8C37',
+  '\u2F96': '\u8C46',
+  '\u2F97': '\u8C55',
+  '\u2F98': '\u8C78',
+  '\u2F99': '\u8C9D',
+  '\u2F9A': '\u8D64',
+  '\u2F9B': '\u8D70',
+  '\u2F9C': '\u8DB3',
+  '\u2F9D': '\u8EAB',
+  '\u2F9E': '\u8ECA',
+  '\u2F9F': '\u8F9B',
+  '\u2FA0': '\u8FB0',
+  '\u2FA1': '\u8FB5',
+  '\u2FA2': '\u9091',
+  '\u2FA3': '\u9149',
+  '\u2FA4': '\u91C6',
+  '\u2FA5': '\u91CC',
+  '\u2FA6': '\u91D1',
+  '\u2FA7': '\u9577',
+  '\u2FA8': '\u9580',
+  '\u2FA9': '\u961C',
+  '\u2FAA': '\u96B6',
+  '\u2FAB': '\u96B9',
+  '\u2FAC': '\u96E8',
+  '\u2FAD': '\u9751',
+  '\u2FAE': '\u975E',
+  '\u2FAF': '\u9762',
+  '\u2FB0': '\u9769',
+  '\u2FB1': '\u97CB',
+  '\u2FB2': '\u97ED',
+  '\u2FB3': '\u97F3',
+  '\u2FB4': '\u9801',
+  '\u2FB5': '\u98A8',
+  '\u2FB6': '\u98DB',
+  '\u2FB7': '\u98DF',
+  '\u2FB8': '\u9996',
+  '\u2FB9': '\u9999',
+  '\u2FBA': '\u99AC',
+  '\u2FBB': '\u9AA8',
+  '\u2FBC': '\u9AD8',
+  '\u2FBD': '\u9ADF',
+  '\u2FBE': '\u9B25',
+  '\u2FBF': '\u9B2F',
+  '\u2FC0': '\u9B32',
+  '\u2FC1': '\u9B3C',
+  '\u2FC2': '\u9B5A',
+  '\u2FC3': '\u9CE5',
+  '\u2FC4': '\u9E75',
+  '\u2FC5': '\u9E7F',
+  '\u2FC6': '\u9EA5',
+  '\u2FC7': '\u9EBB',
+  '\u2FC8': '\u9EC3',
+  '\u2FC9': '\u9ECD',
+  '\u2FCA': '\u9ED1',
+  '\u2FCB': '\u9EF9',
+  '\u2FCC': '\u9EFD',
+  '\u2FCD': '\u9F0E',
+  '\u2FCE': '\u9F13',
+  '\u2FCF': '\u9F20',
+  '\u2FD0': '\u9F3B',
+  '\u2FD1': '\u9F4A',
+  '\u2FD2': '\u9F52',
+  '\u2FD3': '\u9F8D',
+  '\u2FD4': '\u9F9C',
+  '\u2FD5': '\u9FA0',
+  '\u3036': '\u3012',
+  '\u3038': '\u5341',
+  '\u3039': '\u5344',
+  '\u303A': '\u5345',
+  '\u309B': '\u0020\u3099',
+  '\u309C': '\u0020\u309A',
+  '\u3131': '\u1100',
+  '\u3132': '\u1101',
+  '\u3133': '\u11AA',
+  '\u3134': '\u1102',
+  '\u3135': '\u11AC',
+  '\u3136': '\u11AD',
+  '\u3137': '\u1103',
+  '\u3138': '\u1104',
+  '\u3139': '\u1105',
+  '\u313A': '\u11B0',
+  '\u313B': '\u11B1',
+  '\u313C': '\u11B2',
+  '\u313D': '\u11B3',
+  '\u313E': '\u11B4',
+  '\u313F': '\u11B5',
+  '\u3140': '\u111A',
+  '\u3141': '\u1106',
+  '\u3142': '\u1107',
+  '\u3143': '\u1108',
+  '\u3144': '\u1121',
+  '\u3145': '\u1109',
+  '\u3146': '\u110A',
+  '\u3147': '\u110B',
+  '\u3148': '\u110C',
+  '\u3149': '\u110D',
+  '\u314A': '\u110E',
+  '\u314B': '\u110F',
+  '\u314C': '\u1110',
+  '\u314D': '\u1111',
+  '\u314E': '\u1112',
+  '\u314F': '\u1161',
+  '\u3150': '\u1162',
+  '\u3151': '\u1163',
+  '\u3152': '\u1164',
+  '\u3153': '\u1165',
+  '\u3154': '\u1166',
+  '\u3155': '\u1167',
+  '\u3156': '\u1168',
+  '\u3157': '\u1169',
+  '\u3158': '\u116A',
+  '\u3159': '\u116B',
+  '\u315A': '\u116C',
+  '\u315B': '\u116D',
+  '\u315C': '\u116E',
+  '\u315D': '\u116F',
+  '\u315E': '\u1170',
+  '\u315F': '\u1171',
+  '\u3160': '\u1172',
+  '\u3161': '\u1173',
+  '\u3162': '\u1174',
+  '\u3163': '\u1175',
+  '\u3164': '\u1160',
+  '\u3165': '\u1114',
+  '\u3166': '\u1115',
+  '\u3167': '\u11C7',
+  '\u3168': '\u11C8',
+  '\u3169': '\u11CC',
+  '\u316A': '\u11CE',
+  '\u316B': '\u11D3',
+  '\u316C': '\u11D7',
+  '\u316D': '\u11D9',
+  '\u316E': '\u111C',
+  '\u316F': '\u11DD',
+  '\u3170': '\u11DF',
+  '\u3171': '\u111D',
+  '\u3172': '\u111E',
+  '\u3173': '\u1120',
+  '\u3174': '\u1122',
+  '\u3175': '\u1123',
+  '\u3176': '\u1127',
+  '\u3177': '\u1129',
+  '\u3178': '\u112B',
+  '\u3179': '\u112C',
+  '\u317A': '\u112D',
+  '\u317B': '\u112E',
+  '\u317C': '\u112F',
+  '\u317D': '\u1132',
+  '\u317E': '\u1136',
+  '\u317F': '\u1140',
+  '\u3180': '\u1147',
+  '\u3181': '\u114C',
+  '\u3182': '\u11F1',
+  '\u3183': '\u11F2',
+  '\u3184': '\u1157',
+  '\u3185': '\u1158',
+  '\u3186': '\u1159',
+  '\u3187': '\u1184',
+  '\u3188': '\u1185',
+  '\u3189': '\u1188',
+  '\u318A': '\u1191',
+  '\u318B': '\u1192',
+  '\u318C': '\u1194',
+  '\u318D': '\u119E',
+  '\u318E': '\u11A1',
+  '\u3200': '\u0028\u1100\u0029',
+  '\u3201': '\u0028\u1102\u0029',
+  '\u3202': '\u0028\u1103\u0029',
+  '\u3203': '\u0028\u1105\u0029',
+  '\u3204': '\u0028\u1106\u0029',
+  '\u3205': '\u0028\u1107\u0029',
+  '\u3206': '\u0028\u1109\u0029',
+  '\u3207': '\u0028\u110B\u0029',
+  '\u3208': '\u0028\u110C\u0029',
+  '\u3209': '\u0028\u110E\u0029',
+  '\u320A': '\u0028\u110F\u0029',
+  '\u320B': '\u0028\u1110\u0029',
+  '\u320C': '\u0028\u1111\u0029',
+  '\u320D': '\u0028\u1112\u0029',
+  '\u320E': '\u0028\u1100\u1161\u0029',
+  '\u320F': '\u0028\u1102\u1161\u0029',
+  '\u3210': '\u0028\u1103\u1161\u0029',
+  '\u3211': '\u0028\u1105\u1161\u0029',
+  '\u3212': '\u0028\u1106\u1161\u0029',
+  '\u3213': '\u0028\u1107\u1161\u0029',
+  '\u3214': '\u0028\u1109\u1161\u0029',
+  '\u3215': '\u0028\u110B\u1161\u0029',
+  '\u3216': '\u0028\u110C\u1161\u0029',
+  '\u3217': '\u0028\u110E\u1161\u0029',
+  '\u3218': '\u0028\u110F\u1161\u0029',
+  '\u3219': '\u0028\u1110\u1161\u0029',
+  '\u321A': '\u0028\u1111\u1161\u0029',
+  '\u321B': '\u0028\u1112\u1161\u0029',
+  '\u321C': '\u0028\u110C\u116E\u0029',
+  '\u321D': '\u0028\u110B\u1169\u110C\u1165\u11AB\u0029',
+  '\u321E': '\u0028\u110B\u1169\u1112\u116E\u0029',
+  '\u3220': '\u0028\u4E00\u0029',
+  '\u3221': '\u0028\u4E8C\u0029',
+  '\u3222': '\u0028\u4E09\u0029',
+  '\u3223': '\u0028\u56DB\u0029',
+  '\u3224': '\u0028\u4E94\u0029',
+  '\u3225': '\u0028\u516D\u0029',
+  '\u3226': '\u0028\u4E03\u0029',
+  '\u3227': '\u0028\u516B\u0029',
+  '\u3228': '\u0028\u4E5D\u0029',
+  '\u3229': '\u0028\u5341\u0029',
+  '\u322A': '\u0028\u6708\u0029',
+  '\u322B': '\u0028\u706B\u0029',
+  '\u322C': '\u0028\u6C34\u0029',
+  '\u322D': '\u0028\u6728\u0029',
+  '\u322E': '\u0028\u91D1\u0029',
+  '\u322F': '\u0028\u571F\u0029',
+  '\u3230': '\u0028\u65E5\u0029',
+  '\u3231': '\u0028\u682A\u0029',
+  '\u3232': '\u0028\u6709\u0029',
+  '\u3233': '\u0028\u793E\u0029',
+  '\u3234': '\u0028\u540D\u0029',
+  '\u3235': '\u0028\u7279\u0029',
+  '\u3236': '\u0028\u8CA1\u0029',
+  '\u3237': '\u0028\u795D\u0029',
+  '\u3238': '\u0028\u52B4\u0029',
+  '\u3239': '\u0028\u4EE3\u0029',
+  '\u323A': '\u0028\u547C\u0029',
+  '\u323B': '\u0028\u5B66\u0029',
+  '\u323C': '\u0028\u76E3\u0029',
+  '\u323D': '\u0028\u4F01\u0029',
+  '\u323E': '\u0028\u8CC7\u0029',
+  '\u323F': '\u0028\u5354\u0029',
+  '\u3240': '\u0028\u796D\u0029',
+  '\u3241': '\u0028\u4F11\u0029',
+  '\u3242': '\u0028\u81EA\u0029',
+  '\u3243': '\u0028\u81F3\u0029',
+  '\u32C0': '\u0031\u6708',
+  '\u32C1': '\u0032\u6708',
+  '\u32C2': '\u0033\u6708',
+  '\u32C3': '\u0034\u6708',
+  '\u32C4': '\u0035\u6708',
+  '\u32C5': '\u0036\u6708',
+  '\u32C6': '\u0037\u6708',
+  '\u32C7': '\u0038\u6708',
+  '\u32C8': '\u0039\u6708',
+  '\u32C9': '\u0031\u0030\u6708',
+  '\u32CA': '\u0031\u0031\u6708',
+  '\u32CB': '\u0031\u0032\u6708',
+  '\u3358': '\u0030\u70B9',
+  '\u3359': '\u0031\u70B9',
+  '\u335A': '\u0032\u70B9',
+  '\u335B': '\u0033\u70B9',
+  '\u335C': '\u0034\u70B9',
+  '\u335D': '\u0035\u70B9',
+  '\u335E': '\u0036\u70B9',
+  '\u335F': '\u0037\u70B9',
+  '\u3360': '\u0038\u70B9',
+  '\u3361': '\u0039\u70B9',
+  '\u3362': '\u0031\u0030\u70B9',
+  '\u3363': '\u0031\u0031\u70B9',
+  '\u3364': '\u0031\u0032\u70B9',
+  '\u3365': '\u0031\u0033\u70B9',
+  '\u3366': '\u0031\u0034\u70B9',
+  '\u3367': '\u0031\u0035\u70B9',
+  '\u3368': '\u0031\u0036\u70B9',
+  '\u3369': '\u0031\u0037\u70B9',
+  '\u336A': '\u0031\u0038\u70B9',
+  '\u336B': '\u0031\u0039\u70B9',
+  '\u336C': '\u0032\u0030\u70B9',
+  '\u336D': '\u0032\u0031\u70B9',
+  '\u336E': '\u0032\u0032\u70B9',
+  '\u336F': '\u0032\u0033\u70B9',
+  '\u3370': '\u0032\u0034\u70B9',
+  '\u33E0': '\u0031\u65E5',
+  '\u33E1': '\u0032\u65E5',
+  '\u33E2': '\u0033\u65E5',
+  '\u33E3': '\u0034\u65E5',
+  '\u33E4': '\u0035\u65E5',
+  '\u33E5': '\u0036\u65E5',
+  '\u33E6': '\u0037\u65E5',
+  '\u33E7': '\u0038\u65E5',
+  '\u33E8': '\u0039\u65E5',
+  '\u33E9': '\u0031\u0030\u65E5',
+  '\u33EA': '\u0031\u0031\u65E5',
+  '\u33EB': '\u0031\u0032\u65E5',
+  '\u33EC': '\u0031\u0033\u65E5',
+  '\u33ED': '\u0031\u0034\u65E5',
+  '\u33EE': '\u0031\u0035\u65E5',
+  '\u33EF': '\u0031\u0036\u65E5',
+  '\u33F0': '\u0031\u0037\u65E5',
+  '\u33F1': '\u0031\u0038\u65E5',
+  '\u33F2': '\u0031\u0039\u65E5',
+  '\u33F3': '\u0032\u0030\u65E5',
+  '\u33F4': '\u0032\u0031\u65E5',
+  '\u33F5': '\u0032\u0032\u65E5',
+  '\u33F6': '\u0032\u0033\u65E5',
+  '\u33F7': '\u0032\u0034\u65E5',
+  '\u33F8': '\u0032\u0035\u65E5',
+  '\u33F9': '\u0032\u0036\u65E5',
+  '\u33FA': '\u0032\u0037\u65E5',
+  '\u33FB': '\u0032\u0038\u65E5',
+  '\u33FC': '\u0032\u0039\u65E5',
+  '\u33FD': '\u0033\u0030\u65E5',
+  '\u33FE': '\u0033\u0031\u65E5',
+  '\uFB00': '\u0066\u0066',
+  '\uFB01': '\u0066\u0069',
+  '\uFB02': '\u0066\u006C',
+  '\uFB03': '\u0066\u0066\u0069',
+  '\uFB04': '\u0066\u0066\u006C',
+  '\uFB05': '\u017F\u0074',
+  '\uFB06': '\u0073\u0074',
+  '\uFB13': '\u0574\u0576',
+  '\uFB14': '\u0574\u0565',
+  '\uFB15': '\u0574\u056B',
+  '\uFB16': '\u057E\u0576',
+  '\uFB17': '\u0574\u056D',
+  '\uFB4F': '\u05D0\u05DC',
+  '\uFE49': '\u203E',
+  '\uFE4A': '\u203E',
+  '\uFE4B': '\u203E',
+  '\uFE4C': '\u203E',
+  '\uFE4D': '\u005F',
+  '\uFE4E': '\u005F',
+  '\uFE4F': '\u005F'
+};
+
+function fontCharsToUnicode(charCodes, fontProperties) {
+  var toUnicode = fontProperties.toUnicode;
+  var composite = fontProperties.composite;
+  var encoding, differences, cidToUnicode;
+  var result = '';
+  if (composite) {
+    cidToUnicode = fontProperties.cidToUnicode;
+    for (var i = 0, ii = charCodes.length; i < ii; i += 2) {
+      var charCode = (charCodes.charCodeAt(i) << 8) |
+        charCodes.charCodeAt(i + 1);
+      if (toUnicode && charCode in toUnicode) {
+        var unicode = toUnicode[charCode];
+        result += typeof unicode !== 'number' ? unicode :
+          String.fromCharCode(unicode);
+        continue;
+      }
+      result += String.fromCharCode(!cidToUnicode ? charCode :
+        cidToUnicode[charCode] || charCode);
+    }
+  } else {
+    differences = fontProperties.differences;
+    encoding = fontProperties.baseEncoding;
+    for (var i = 0, ii = charCodes.length; i < ii; i++) {
+      var charCode = charCodes.charCodeAt(i);
+      var unicode;
+      if (toUnicode && charCode in toUnicode) {
+        var unicode = toUnicode[charCode];
+        result += typeof unicode !== 'number' ? unicode :
+          String.fromCharCode(unicode);
+        continue;
+      }
+
+      var glyphName = charCode in differences ? differences[charCode] :
+        encoding[charCode];
+      if (glyphName in GlyphsUnicode) {
+        result += String.fromCharCode(GlyphsUnicode[glyphName]);
+        continue;
+      }
+      result += String.fromCharCode(charCode);
+    }
+  }
+  // normalizing the unicode characters
+  for (var i = 0, ii = result.length; i < ii; i++) {
+    if (!(result[i] in NormalizedUnicodes))
+      continue;
+    result = result.substring(0, i) + NormalizedUnicodes[result[i]] +
+      result.substring(i + 1);
+    ii = result.length;
+  }
+  return result;
+}
+
 /**
  * 'Font' is the class the outside world should use, it encapsulate all the font
  * decoding logics whatever type it is (assuming the font type is supported).
@@ -14148,7 +15518,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('+');
@@ -14172,6 +15541,7 @@ var Font = (function FontClosure() {
     this.widths = properties.widths;
     this.defaultWidth = properties.defaultWidth;
     this.composite = properties.composite;
+    this.wideChars = properties.wideChars;
     this.hasEncoding = properties.hasEncoding;
 
     this.fontMatrix = properties.fontMatrix;
@@ -15109,6 +16479,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'];
@@ -15116,7 +16496,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);
@@ -15134,6 +16514,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 {
@@ -15149,7 +16531,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
@@ -15163,21 +16545,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
@@ -15357,9 +16728,9 @@ var Font = (function FontClosure() {
             this.isSymbolicFont = false;
         }
 
-        // heuristics: if removed more than 2 glyphs encoding WinAnsiEncoding
-        // does not set properly
-        if (glyphsRemoved > 2) {
+        // heuristics: if removed more than 10 glyphs encoding WinAnsiEncoding
+        // does not set properly (broken PDFs have about 100 removed glyphs)
+        if (glyphsRemoved > 10) {
           warn('Switching TrueType encoding to MacRomanEncoding for ' +
                this.name + ' font');
           encoding = Encodings.MacRomanEncoding;
@@ -15458,6 +16829,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({
@@ -15842,6 +17230,7 @@ var Font = (function FontClosure() {
           }
 
           // MacRoman encoding address by re-encoding the cmap table
+
           fontCharCode = glyphName in this.glyphNameMap ?
             this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName];
           break;
@@ -15885,7 +17274,7 @@ var Font = (function FontClosure() {
 
       glyphs = [];
 
-      if (this.composite) {
+      if (this.wideChars) {
         // composite fonts have multi-byte strings convert the string from
         // single-byte to multi-byte
         // XXX assuming CIDFonts are two-byte - later need to extract the
@@ -16841,7 +18230,7 @@ var CFFFont = (function CFFFontClosure() {
     this.properties = properties;
 
     var parser = new CFFParser(file, properties);
-    var cff = parser.parse();
+    var cff = parser.parse(true);
     var compiler = new CFFCompiler(cff);
     this.readExtra(cff);
     try {
@@ -16932,7 +18321,7 @@ var CFFParser = (function CFFParserClosure() {
     this.properties = properties;
   }
   CFFParser.prototype = {
-    parse: function CFFParser_parse() {
+    parse: function CFFParser_parse(normalizeCIDData) {
       var properties = this.properties;
       var cff = new CFF();
       this.cff = cff;
@@ -16987,6 +18376,21 @@ var CFFParser = (function CFFParserClosure() {
       cff.charset = charset;
       cff.encoding = encoding;
 
+      if (!cff.isCIDFont || !normalizeCIDData)
+        return cff;
+
+      // DirectWrite does not like CID fonts data. Trying to convert/flatten
+      // the font data and remove CID properties.
+      if (cff.fdArray.length !== 1)
+        error('Unable to normalize CID font in CFF data');
+
+      var fontDict = cff.fdArray[0];
+      fontDict.setByKey(17, topDict.getByName('CharStrings'));
+      cff.topDict = fontDict;
+      cff.isCIDFont = false;
+      delete cff.fdArray;
+      delete cff.fdSelect;
+
       return cff;
     },
     parseHeader: function CFFParser_parseHeader() {
@@ -16997,7 +18401,7 @@ var CFFParser = (function CFFParserClosure() {
         ++offset;
 
       if (offset != 0) {
-        warn('cff data is shifted');
+        info('cff data is shifted');
         bytes = bytes.subarray(offset);
         this.bytes = bytes;
       }
@@ -17585,9 +18989,9 @@ var CFFPrivateDict = (function CFFPrivateDictClosure() {
     [[12, 17], 'LanguageGroup', 'num', 0],
     [[12, 18], 'ExpansionFactor', 'num', 0.06],
     [[12, 19], 'initialRandomSeed', 'num', 0],
-    [19, 'Subrs', 'offset', null],
     [20, 'defaultWidthX', 'num', 0],
-    [21, 'nominalWidthX', 'num', 0]
+    [21, 'nominalWidthX', 'num', 0],
+    [19, 'Subrs', 'offset', null]
   ];
   var tables = null;
   function CFFPrivateDict(strings) {
@@ -18045,6 +19449,7 @@ var CFFCompiler = (function CFFCompilerClosure() {
   return CFFCompiler;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -22257,6 +23662,7 @@ var GlyphsUnicode = {
   '.notdef': 0x0000
 };
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -22274,7 +23680,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);
@@ -22632,6 +24038,7 @@ function loadJpegStream(id, imageData, objs) {
   img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
 }
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -25579,6 +26986,7 @@ var Metrics = {
   }
 };
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -25830,9 +27238,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;
     }
@@ -25842,8 +27253,16 @@ var Parser = (function ParserClosure() {
 })();
 
 var Lexer = (function LexerClosure() {
-  function Lexer(stream) {
+  function Lexer(stream, knownCommands) {
     this.stream = stream;
+    // The PDFs might have "glued" commands with other commands, operands or
+    // literals, e.g. "q1". The knownCommands is a dictionary of the valid
+    // commands and their prefixes. The prefixes are built the following way:
+    // if there a command that is a prefix of the other valid command or
+    // literal (e.g. 'f' and 'false') the following prefixes must be included,
+    // 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no
+    // other commands or literals as a prefix. The knowCommands is optional.
+    this.knownCommands = knownCommands;
   }
 
   Lexer.isSpace = function Lexer_isSpace(ch) {
@@ -26107,12 +27526,18 @@ var Lexer = (function LexerClosure() {
 
       // command
       var str = ch;
+      var knownCommands = this.knownCommands;
+      var knownCommandFound = knownCommands && (str in knownCommands);
       while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
+        // stop if known command is found and next character does not make
+        // the str a command
+        if (knownCommandFound && !((str + ch) in knownCommands))
+          break;
         stream.skip();
         if (str.length == 128)
           error('Command token too long: ' + str.length);
-
         str += ch;
+        knownCommandFound = knownCommands && (str in knownCommands);
       }
       if (str == 'true')
         return true;
@@ -26218,6 +27643,7 @@ var Linearization = (function LinearizationClosure() {
   return Linearization;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -26330,7 +27756,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
     var r1 = raw[6];
     return {
       type: 'Pattern',
-      getPattern: function(ctx) {
+      getPattern: function RadialAxial_getPattern(ctx) {
         var curMatrix = ctx.mozCurrentTransform;
         if (curMatrix) {
           var userMatrix = ctx.mozCurrentTransformInverse;
@@ -26521,6 +27947,7 @@ var TilingPattern = (function TilingPatternClosure() {
   return TilingPattern;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -28210,7 +29637,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
 
     if (a1 > codingLine[codingPos]) {
       if (a1 > this.columns) {
-        warn('row is wrong length');
+        info('row is wrong length');
         this.err = true;
         a1 = this.columns;
       }
@@ -28230,7 +29657,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
 
     if (a1 > codingLine[codingPos]) {
       if (a1 > this.columns) {
-        warn('row is wrong length');
+        info('row is wrong length');
         this.err = true;
         a1 = this.columns;
       }
@@ -28240,7 +29667,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
       codingLine[codingPos] = a1;
     } else if (a1 < codingLine[codingPos]) {
       if (a1 < 0) {
-        warn('invalid code');
+        info('invalid code');
         this.err = true;
         a1 = 0;
       }
@@ -28402,7 +29829,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
               this.eof = true;
               break;
             default:
-              warn('bad 2d code');
+              info('bad 2d code');
               this.addPixels(columns, 0);
               this.err = true;
           }
@@ -28465,7 +29892,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
             for (var i = 0; i < 4; ++i) {
               code1 = this.lookBits(12);
               if (code1 != 1)
-                warn('bad rtc code: ' + code1);
+                info('bad rtc code: ' + code1);
               this.eatBits(12);
               if (this.encoding > 0) {
                 this.lookBits(1);
@@ -28588,7 +30015,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
       if (result[0] && result[2])
         return result[1];
     }
-    warn('Bad two dim code');
+    info('Bad two dim code');
     return EOF;
   };
 
@@ -28621,7 +30048,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
       if (result[0])
         return result[1];
     }
-    warn('bad white code');
+    info('bad white code');
     this.eatBits(1);
     return 1;
   };
@@ -28658,7 +30085,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
       if (result[0])
         return result[1];
     }
-    warn('bad black code');
+    info('bad black code');
     this.eatBits(1);
     return 1;
   };
@@ -28815,6 +30242,7 @@ var LZWStream = (function LZWStreamClosure() {
   return LZWStream;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -28828,10 +30256,13 @@ function MessageHandler(name, comObj) {
   var ah = this.actionHandler = {};
 
   ah['console_log'] = [function ahConsoleLog(data) {
-      console.log.apply(console, data);
+    console.log.apply(console, data);
   }];
   ah['console_error'] = [function ahConsoleError(data) {
-      console.error.apply(console, data);
+    console.error.apply(console, data);
+  }];
+  ah['_warn'] = [function ah_Warn(data) {
+    warn(data);
   }];
 
   comObj.onmessage = function messageHandlerComObjOnMessage(event) {
@@ -28902,15 +30333,68 @@ 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));
+      var pdfData = data.data;
+      var pdfPassword = data.params.password;
+      try {
+        pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword);
+      } catch (e) {
+        if (e instanceof PasswordException) {
+          if (e.code === 'needpassword') {
+            handler.send('NeedPassword', {
+              exception: e
+            });
+          } else if (e.code === 'incorrectpassword') {
+            handler.send('IncorrectPassword', {
+              exception: e
+            });
+          }
+
+          return;
+        } else {
+          throw e;
+        }
+      }
+      var doc = {
+        numPages: pdfModel.numPages,
+        fingerprint: pdfModel.getFingerprint(),
+        destinations: pdfModel.catalog.destinations,
+        outline: pdfModel.catalog.documentOutline,
+        info: pdfModel.getDocumentInfo(),
+        metadata: pdfModel.catalog.metadata,
+        encrypted: !!pdfModel.xref.encrypt
+      };
+      handler.send('GetDoc', {pdfInfo: doc});
+    });
+
+    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('GetData', function wphSetupGetData(data, promise) {
+      promise.resolve(pdfModel.stream.bytes);
     });
 
-    handler.on('page_request', function wphSetupPageRequest(pageNum) {
-      pageNum = parseInt(pageNum);
+    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
       // Page.prototype.startRendering, but stops at one point and sends the
@@ -28947,7 +30431,7 @@ var WorkerMessageHandler = {
           };
         }
 
-        handler.send('page_error', {
+        handler.send('PageError', {
           pageNum: pageNum,
           error: e
         });
@@ -28965,13 +30449,30 @@ var WorkerMessageHandler = {
           fonts[dep] = true;
         }
       }
-
-      handler.send('page', {
-        pageNum: pageNum,
+      handler.send('RenderPage', {
+        pageIndex: data.pageIndex,
         operatorList: operatorList,
         depFonts: Object.keys(fonts)
       });
     }, this);
+
+    handler.on('GetTextContent', function wphExtractText(data, promise) {
+      var pageNum = data.pageIndex + 1;
+      var start = Date.now();
+
+      var textContent = '';
+      try {
+        var page = pdfModel.getPage(pageNum);
+        textContent = page.extractTextContent();
+        promise.resolve(textContent);
+      } catch (e) {
+        // Skip errored pages
+        promise.reject(e);
+      }
+
+      console.log('text indexing: page=%d - time=%dms',
+                      pageNum, Date.now() - start);
+    });
   }
 };
 
@@ -29012,10 +30513,22 @@ var workerConsole = {
 if (typeof window === 'undefined') {
   globalScope.console = workerConsole;
 
+  // Add a logger so we can pass warnings on to the main thread, errors will
+  // throw an exception which will be forwarded on automatically.
+  PDFJS.LogManager.addLogger({
+    warn: function(msg) {
+      postMessage({
+        action: '_warn',
+        data: msg
+      });
+    }
+  });
+
   var handler = new MessageHandler('worker_processor', this);
   WorkerMessageHandler.setup(handler);
 }
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -29655,7 +31168,10 @@ var JpegImage = (function jpegImage() {
                   tableData[z] = data[offset++];
                 }
               } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
-                  tableData[j] = readUint16();
+                for (j = 0; j < 64; j++) {
+                  var z = dctZigZag[j];
+                  tableData[z] = readUint16();
+                }
               } else
                 throw "DQT: invalid table spec";
               quantizationTables[quantizationTableSpec & 15] = tableData;
@@ -29670,7 +31186,8 @@ var JpegImage = (function jpegImage() {
             frame.precision = data[offset++];
             frame.scanLines = readUint16();
             frame.samplesPerLine = readUint16();
-            frame.components = [];
+            frame.components = {};
+            frame.componentsOrder = [];
             var componentsCount = data[offset++], componentId;
             var maxH = 0, maxV = 0;
             for (i = 0; i < componentsCount; i++) {
@@ -29678,6 +31195,7 @@ var JpegImage = (function jpegImage() {
               var h = data[offset + 1] >> 4;
               var v = data[offset + 1] & 15;
               var qId = data[offset + 2];
+              frame.componentsOrder.push(componentId);
               frame.components[componentId] = {
                 h: h,
                 v: v,
@@ -29746,14 +31264,13 @@ var JpegImage = (function jpegImage() {
       this.jfif = jfif;
       this.adobe = adobe;
       this.components = [];
-      for (var id in frame.components) {
-        if (frame.components.hasOwnProperty(id)) {
-          this.components.push({
-            lines: buildComponentData(frame, frame.components[id]),
-            scaleX: frame.components[id].h / frame.maxH,
-            scaleY: frame.components[id].v / frame.maxV
-          });
-        }
+      for (var i = 0; i < frame.componentsOrder.length; i++) {
+        var component = frame.components[frame.componentsOrder[i]];
+        this.components.push({
+          lines: buildComponentData(frame, component),
+          scaleX: component.h / frame.maxH,
+          scaleY: component.v / frame.maxV
+        });
       }
     },
     getData: function getData(width, height) {
@@ -29926,7 +31443,8 @@ var JpegImage = (function jpegImage() {
   };
 
   return constructor;
-})();/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+})();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 'use strict';
@@ -31788,6 +33306,7 @@ var JpxImage = (function JpxImageClosure() {
   return JpxImage;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
@@ -32220,14 +33739,35 @@ var bidi = PDFJS.bidi = (function bidiClosure() {
   return bidi;
 })();
 
+
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 'use strict';
 
 var Metadata = PDFJS.Metadata = (function MetadataClosure() {
+  function fixMetadata(meta) {
+    return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) {
+      var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g,
+                                function(code, d1, d2, d3) {
+        return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
+      });
+      var chars = '';
+      for (var i = 0; i < bytes.length; i += 2) {
+        var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
+        chars += code >= 32 && code < 127 && code != 60 && code != 62 &&
+          code != 38 && false ? String.fromCharCode(code) :
+          '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
+      }
+      return '>' + chars;
+    });
+  }
+
   function Metadata(meta) {
     if (typeof meta === 'string') {
+      // Ghostscript produces invalid metadata
+      meta = fixMetadata(meta);
+
       var parser = new DOMParser();
       meta = parser.parseFromString(meta, 'application/xml');
     } else if (!(meta instanceof Document)) {
diff --git a/apps/files_pdfviewer/js/pdfjs/compatibility.js b/apps/files_pdfviewer/js/pdfjs/compatibility.js
new file mode 100644 (file)
index 0000000..528841b
--- /dev/null
@@ -0,0 +1,340 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+// Checking if the typed arrays are supported
+(function checkTypedArrayCompatibility() {
+  if (typeof Uint8Array !== 'undefined') {
+    // some mobile version might not support Float64Array
+    if (typeof Float64Array === 'undefined')
+      window.Float64Array = Float32Array;
+
+    return;
+  }
+
+  function subarray(start, end) {
+    return new TypedArray(this.slice(start, end));
+  }
+
+  function setArrayOffset(array, offset) {
+    if (arguments.length < 2)
+      offset = 0;
+    for (var i = 0, n = array.length; i < n; ++i, ++offset)
+      this[offset] = array[i] & 0xFF;
+  }
+
+  function TypedArray(arg1) {
+    var result;
+    if (typeof arg1 === 'number') {
+      result = [];
+      for (var i = 0; i < arg1; ++i)
+        result[i] = 0;
+    } else
+      result = arg1.slice(0);
+
+    result.subarray = subarray;
+    result.buffer = result;
+    result.byteLength = result.length;
+    result.set = setArrayOffset;
+
+    if (typeof arg1 === 'object' && arg1.buffer)
+      result.buffer = arg1.buffer;
+
+    return result;
+  }
+
+  window.Uint8Array = TypedArray;
+
+  // we don't need support for set, byteLength for 32-bit array
+  // so we can use the TypedArray as well
+  window.Uint32Array = TypedArray;
+  window.Int32Array = TypedArray;
+  window.Uint16Array = TypedArray;
+  window.Float32Array = TypedArray;
+  window.Float64Array = TypedArray;
+})();
+
+// Object.create() ?
+(function checkObjectCreateCompatibility() {
+  if (typeof Object.create !== 'undefined')
+    return;
+
+  Object.create = function objectCreate(proto) {
+    var constructor = function objectCreateConstructor() {};
+    constructor.prototype = proto;
+    return new constructor();
+  };
+})();
+
+// Object.defineProperty() ?
+(function checkObjectDefinePropertyCompatibility() {
+  if (typeof Object.defineProperty !== 'undefined')
+    return;
+
+  Object.defineProperty = function objectDefineProperty(obj, name, def) {
+    delete obj[name];
+    if ('get' in def)
+      obj.__defineGetter__(name, def['get']);
+    if ('set' in def)
+      obj.__defineSetter__(name, def['set']);
+    if ('value' in def) {
+      obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
+        this.__defineGetter__(name, function objectDefinePropertyGetter() {
+          return value;
+        });
+        return value;
+      });
+      obj[name] = def.value;
+    }
+  };
+})();
+
+// Object.keys() ?
+(function checkObjectKeysCompatibility() {
+  if (typeof Object.keys !== 'undefined')
+    return;
+
+  Object.keys = function objectKeys(obj) {
+    var result = [];
+    for (var i in obj) {
+      if (obj.hasOwnProperty(i))
+        result.push(i);
+    }
+    return result;
+  };
+})();
+
+// No XMLHttpRequest.response ?
+(function checkXMLHttpRequestResponseCompatibility() {
+  var xhrPrototype = XMLHttpRequest.prototype;
+  if ('response' in xhrPrototype ||
+      'mozResponseArrayBuffer' in xhrPrototype ||
+      'mozResponse' in xhrPrototype ||
+      'responseArrayBuffer' in xhrPrototype)
+    return;
+  // IE ?
+  if (typeof VBArray !== 'undefined') {
+    Object.defineProperty(xhrPrototype, 'response', {
+      get: function xmlHttpRequestResponseGet() {
+        return new Uint8Array(new VBArray(this.responseBody).toArray());
+      }
+    });
+    Object.defineProperty(xhrPrototype, 'overrideMimeType', {
+      value: function xmlHttpRequestOverrideMimeType(mimeType) {}
+    });
+    return;
+  }
+
+  // other browsers
+  function responseTypeSetter() {
+    // will be only called to set "arraybuffer"
+    this.overrideMimeType('text/plain; charset=x-user-defined');
+  }
+  if (typeof xhrPrototype.overrideMimeType === 'function') {
+    Object.defineProperty(xhrPrototype, 'responseType',
+                          { set: responseTypeSetter });
+  }
+  function responseGetter() {
+    var text = this.responseText;
+    var i, n = text.length;
+    var result = new Uint8Array(n);
+    for (i = 0; i < n; ++i)
+      result[i] = text.charCodeAt(i) & 0xFF;
+    return result;
+  }
+  Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
+})();
+
+// window.btoa (base64 encode function) ?
+(function checkWindowBtoaCompatibility() {
+  if ('btoa' in window)
+    return;
+
+  var digits =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+  window.btoa = function windowBtoa(chars) {
+    var buffer = '';
+    var i, n;
+    for (i = 0, n = chars.length; i < n; i += 3) {
+      var b1 = chars.charCodeAt(i) & 0xFF;
+      var b2 = chars.charCodeAt(i + 1) & 0xFF;
+      var b3 = chars.charCodeAt(i + 2) & 0xFF;
+      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+      var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+      var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
+      buffer += (digits.charAt(d1) + digits.charAt(d2) +
+                 digits.charAt(d3) + digits.charAt(d4));
+    }
+    return buffer;
+  };
+})();
+
+// Function.prototype.bind ?
+(function checkFunctionPrototypeBindCompatibility() {
+  if (typeof Function.prototype.bind !== 'undefined')
+    return;
+
+  Function.prototype.bind = function functionPrototypeBind(obj) {
+    var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
+    var bound = function functionPrototypeBindBound() {
+      var args = Array.prototype.concat.apply(headArgs, arguments);
+      return fn.apply(obj, args);
+    };
+    return bound;
+  };
+})();
+
+// IE9 text/html data URI
+(function checkDocumentDocumentModeCompatibility() {
+  if (!('documentMode' in document) || document.documentMode !== 9)
+    return;
+  // overriding the src property
+  var originalSrcDescriptor = Object.getOwnPropertyDescriptor(
+    HTMLIFrameElement.prototype, 'src');
+  Object.defineProperty(HTMLIFrameElement.prototype, 'src', {
+    get: function htmlIFrameElementPrototypeSrcGet() { return this.$src; },
+    set: function htmlIFrameElementPrototypeSrcSet(src) {
+      this.$src = src;
+      if (src.substr(0, 14) != 'data:text/html') {
+        originalSrcDescriptor.set.call(this, src);
+        return;
+      }
+      // for text/html, using blank document and then
+      // document's open, write, and close operations
+      originalSrcDescriptor.set.call(this, 'about:blank');
+      setTimeout((function htmlIFrameElementPrototypeSrcOpenWriteClose() {
+        var doc = this.contentDocument;
+        doc.open('text/html');
+        doc.write(src.substr(src.indexOf(',') + 1));
+        doc.close();
+      }).bind(this), 0);
+    },
+    enumerable: true
+  });
+})();
+
+// HTMLElement dataset property
+(function checkDatasetProperty() {
+  var div = document.createElement('div');
+  if ('dataset' in div)
+    return; // dataset property exists
+
+  Object.defineProperty(HTMLElement.prototype, 'dataset', {
+    get: function() {
+      if (this._dataset)
+        return this._dataset;
+
+      var dataset = {};
+      for (var j = 0, jj = this.attributes.length; j < jj; j++) {
+        var attribute = this.attributes[j];
+        if (attribute.name.substring(0, 5) != 'data-')
+          continue;
+        var key = attribute.name.substring(5).replace(/\-([a-z])/g,
+          function(all, ch) { return ch.toUpperCase(); });
+        dataset[key] = attribute.value;
+      }
+
+      Object.defineProperty(this, '_dataset', {
+        value: dataset,
+        writable: false,
+        enumerable: false
+      });
+      return dataset;
+    },
+    enumerable: true
+  });
+})();
+
+// HTMLElement classList property
+(function checkClassListProperty() {
+  var div = document.createElement('div');
+  if ('classList' in div)
+    return; // classList property exists
+
+  function changeList(element, itemName, add, remove) {
+    var s = element.className || '';
+    var list = s.split(/\s+/g);
+    if (list[0] == '') list.shift();
+    var index = list.indexOf(itemName);
+    if (index < 0 && add)
+      list.push(itemName);
+    if (index >= 0 && remove)
+      list.splice(index, 1);
+    element.className = list.join(' ');
+  }
+
+  var classListPrototype = {
+    add: function(name) {
+      changeList(this.element, name, true, false);
+    },
+    remove: function(name) {
+      changeList(this.element, name, false, true);
+    },
+    toggle: function(name) {
+      changeList(this.element, name, true, true);
+    }
+  };
+
+  Object.defineProperty(HTMLElement.prototype, 'classList', {
+    get: function() {
+      if (this._classList)
+        return this._classList;
+
+      var classList = Object.create(classListPrototype, {
+        element: {
+          value: this,
+          writable: false,
+          enumerable: true
+        }
+      });
+      Object.defineProperty(this, '_classList', {
+        value: classList,
+        writable: false,
+        enumerable: false
+      });
+      return classList;
+    },
+    enumerable: true
+  });
+})();
+
+// Check console compatability
+(function checkConsoleCompatibility() {
+  if (typeof console == 'undefined') {
+    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);
+  }
+})();
+
+// Checks if navigator.language is supported
+(function checkNavigatorLanguage() {
+  if ('language' in navigator)
+    return;
+  Object.defineProperty(navigator, 'language', {
+    get: function navigatorLanguage() {
+      var language = navigator.userLanguage || 'en-US';
+      return language.substring(0, 2).toLowerCase() +
+        language.substring(2).toUpperCase();
+    },
+    enumerable: true
+  });
+})();
index 599f6338602fe1bd34ef95f5151355d4a5885db9..df100e780bac7a2c9ec9858a16d38f025b2851fb 100755 (executable)
@@ -1,3 +1,6 @@
 cd build
 rm pdf.js
 wget http://mozilla.github.com/pdf.js/build/pdf.js
+wget http://mozilla.github.com/pdf.js/web/compatibility.js
+wget http://mozilla.github.com/pdf.js/web/viewer.js
+
diff --git a/apps/files_pdfviewer/js/pdfjs/viewer.js b/apps/files_pdfviewer/js/pdfjs/viewer.js
new file mode 100644 (file)
index 0000000..ff4f245
--- /dev/null
@@ -0,0 +1,1936 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
+var kDefaultScale = 'auto';
+var kDefaultScaleDelta = 1.1;
+var kUnknownScale = 0;
+var kCacheSize = 20;
+var kCssUnits = 96.0 / 72.0;
+var kScrollbarPadding = 40;
+var kMinScale = 0.25;
+var kMaxScale = 4.0;
+var kImageDirectory = './images/';
+var kSettingsMemory = 20;
+
+var mozL10n = document.mozL10n || document.webL10n;
+
+function getFileName(url) {
+  var anchor = url.indexOf('#');
+  var query = url.indexOf('?');
+  var end = Math.min(
+    anchor > 0 ? anchor : url.length,
+    query > 0 ? query : url.length);
+  return url.substring(url.lastIndexOf('/', end) + 1, end);
+}
+
+var Cache = function cacheCache(size) {
+  var data = [];
+  this.push = function cachePush(view) {
+    var i = data.indexOf(view);
+    if (i >= 0)
+      data.splice(i);
+    data.push(view);
+    if (data.length > size)
+      data.shift().destroy();
+  };
+};
+
+var ProgressBar = (function ProgressBarClosure() {
+
+  function clamp(v, min, max) {
+    return Math.min(Math.max(v, min), max);
+  }
+
+  function ProgressBar(id, opts) {
+
+    // Fetch the sub-elements for later
+    this.div = document.querySelector(id + ' .progress');
+
+    // Get options, with sensible defaults
+    this.height = opts.height || 100;
+    this.width = opts.width || 100;
+    this.units = opts.units || '%';
+    this.percent = opts.percent || 0;
+
+    // Initialize heights
+    this.div.style.height = this.height + this.units;
+  }
+
+  ProgressBar.prototype = {
+
+    updateBar: function ProgressBar_updateBar() {
+      var progressSize = this.width * this._percent / 100;
+
+      if (this._percent > 95)
+        this.div.classList.add('full');
+
+      this.div.style.width = progressSize + this.units;
+    },
+
+    get percent() {
+      return this._percent;
+    },
+
+    set percent(val) {
+      this._percent = clamp(val, 0, 100);
+      this.updateBar();
+    }
+  };
+
+  return ProgressBar;
+})();
+
+var RenderingQueue = (function RenderingQueueClosure() {
+  function RenderingQueue() {
+    this.items = [];
+  }
+
+  RenderingQueue.prototype = {
+    enqueueDraw: function RenderingQueueEnqueueDraw(item) {
+      if (!item.drawingRequired())
+        return; // as no redraw required, no need for queueing.
+
+      this.items.push(item);
+      if (this.items.length > 1)
+        return; // not first item
+
+      item.draw(this.continueExecution.bind(this));
+    },
+    continueExecution: function RenderingQueueContinueExecution() {
+      var item = this.items.shift();
+
+      if (this.items.length == 0)
+        return; // queue is empty
+
+      item = this.items[0];
+      item.draw(this.continueExecution.bind(this));
+    }
+  };
+
+  return RenderingQueue;
+})();
+
+var FirefoxCom = (function FirefoxComClosure() {
+  return {
+    /**
+     * Creates an event that the extension is listening for and will
+     * synchronously respond to.
+     * NOTE: It is reccomended to use request() instead since one day we may not
+     * be able to synchronously reply.
+     * @param {String} action The action to trigger.
+     * @param {String} data Optional data to send.
+     * @return {*} The response.
+     */
+    requestSync: function(action, data) {
+      var request = document.createTextNode('');
+      request.setUserData('action', action, null);
+      request.setUserData('data', data, null);
+      request.setUserData('sync', true, null);
+      document.documentElement.appendChild(request);
+
+      var sender = document.createEvent('Events');
+      sender.initEvent('pdf.js.message', true, false);
+      request.dispatchEvent(sender);
+      var response = request.getUserData('response');
+      document.documentElement.removeChild(request);
+      return response;
+    },
+    /**
+     * Creates an event that the extension is listening for and will
+     * asynchronously respond by calling the callback.
+     * @param {String} action The action to trigger.
+     * @param {String} data Optional data to send.
+     * @param {Function} callback Optional response callback that will be called
+     * with one data argument.
+     */
+    request: function(action, data, callback) {
+      var request = document.createTextNode('');
+      request.setUserData('action', action, null);
+      request.setUserData('data', data, null);
+      request.setUserData('sync', false, null);
+      if (callback) {
+        request.setUserData('callback', callback, null);
+
+        document.addEventListener('pdf.js.response', function listener(event) {
+          var node = event.target,
+              callback = node.getUserData('callback'),
+              response = node.getUserData('response');
+
+          document.documentElement.removeChild(node);
+
+          document.removeEventListener('pdf.js.response', listener, false);
+          return callback(response);
+        }, false);
+      }
+      document.documentElement.appendChild(request);
+
+      var sender = document.createEvent('HTMLEvents');
+      sender.initEvent('pdf.js.message', true, false);
+      return request.dispatchEvent(sender);
+    }
+  };
+})();
+
+// Settings Manager - This is a utility for saving settings
+// First we see if localStorage is available
+// If not, we use FUEL in FF
+var Settings = (function SettingsClosure() {
+  var isLocalStorageEnabled = (function localStorageEnabledTest() {
+    // Feature test as per http://diveintohtml5.info/storage.html
+    // The additional localStorage call is to get around a FF quirk, see
+    // bug #495747 in bugzilla
+    try {
+      return 'localStorage' in window && window['localStorage'] !== null &&
+          localStorage;
+    } catch (e) {
+      return false;
+    }
+  })();
+
+  var isFirefoxExtension = PDFJS.isFirefoxExtension;
+
+  function Settings(fingerprint) {
+    var database = null;
+    var index;
+    if (isFirefoxExtension)
+      database = FirefoxCom.requestSync('getDatabase', null) || '{}';
+    else if (isLocalStorageEnabled)
+      database = localStorage.getItem('database') || '{}';
+    else
+      return false;
+
+    database = JSON.parse(database);
+    if (!('files' in database))
+      database.files = [];
+    if (database.files.length >= kSettingsMemory)
+      database.files.shift();
+    for (var i = 0, length = database.files.length; i < length; i++) {
+      var branch = database.files[i];
+      if (branch.fingerprint == fingerprint) {
+        index = i;
+        break;
+      }
+    }
+    if (typeof index != 'number')
+      index = database.files.push({fingerprint: fingerprint}) - 1;
+    this.file = database.files[index];
+    this.database = database;
+  }
+
+  Settings.prototype = {
+    set: function settingsSet(name, val) {
+      if (!('file' in this))
+        return false;
+
+      var file = this.file;
+      file[name] = val;
+      var database = JSON.stringify(this.database);
+      if (isFirefoxExtension)
+        FirefoxCom.requestSync('setDatabase', database);
+      else if (isLocalStorageEnabled)
+        localStorage.setItem('database', database);
+    },
+
+    get: function settingsGet(name, defaultValue) {
+      if (!('file' in this))
+        return defaultValue;
+
+      return this.file[name] || defaultValue;
+    }
+  };
+
+  return Settings;
+})();
+
+var cache = new Cache(kCacheSize);
+var renderingQueue = new RenderingQueue();
+var currentPageNumber = 1;
+
+var PDFView = {
+  pages: [],
+  thumbnails: [],
+  currentScale: kUnknownScale,
+  currentScaleValue: null,
+  initialBookmark: document.location.hash.substring(1),
+  startedTextExtraction: false,
+  pageText: [],
+  container: null,
+  initialized: false,
+  fellback: false,
+  pdfDocument: null,
+  // called once when the document is loaded
+  initialize: function pdfViewInitialize() {
+    this.container = document.getElementById('viewer');
+    this.initialized = true;
+  },
+
+  setScale: function pdfViewSetScale(val, resetAutoSettings) {
+    if (val == this.currentScale)
+      return;
+
+    var pages = this.pages;
+    for (var i = 0; i < pages.length; i++)
+      pages[i].update(val * kCssUnits);
+
+    if (this.currentScale != val)
+      this.pages[this.page - 1].scrollIntoView();
+    this.currentScale = val;
+
+    var event = document.createEvent('UIEvents');
+    event.initUIEvent('scalechange', false, false, window, 0);
+    event.scale = val;
+    event.resetAutoSettings = resetAutoSettings;
+    window.dispatchEvent(event);
+  },
+
+  parseScale: function pdfViewParseScale(value, resetAutoSettings) {
+    if ('custom' == value)
+      return;
+
+    var scale = parseFloat(value);
+    this.currentScaleValue = value;
+    if (scale) {
+      this.setScale(scale, true);
+      return;
+    }
+
+    var container = this.container;
+    var currentPage = this.pages[this.page - 1];
+    var pageWidthScale = (container.clientWidth - kScrollbarPadding) /
+                          currentPage.width * currentPage.scale / kCssUnits;
+    var pageHeightScale = (container.clientHeight - kScrollbarPadding) /
+                           currentPage.height * currentPage.scale / kCssUnits;
+    switch (value) {
+      case 'page-actual':
+        this.setScale(1, resetAutoSettings);
+        break;
+      case 'page-width':
+        this.setScale(pageWidthScale, resetAutoSettings);
+        break;
+      case 'page-height':
+        this.setScale(pageHeightScale, resetAutoSettings);
+        break;
+      case 'page-fit':
+        this.setScale(
+            Math.min(pageWidthScale, pageHeightScale), resetAutoSettings);
+        break;
+      case 'auto':
+        this.setScale(Math.min(1.0, pageWidthScale), resetAutoSettings);
+        break;
+    }
+
+    selectScaleOption(value);
+  },
+
+  zoomIn: function pdfViewZoomIn() {
+    var newScale = (this.currentScale * kDefaultScaleDelta).toFixed(2);
+    newScale = Math.min(kMaxScale, newScale);
+    this.parseScale(newScale, true);
+  },
+
+  zoomOut: function pdfViewZoomOut() {
+    var newScale = (this.currentScale / kDefaultScaleDelta).toFixed(2);
+    newScale = Math.max(kMinScale, newScale);
+    this.parseScale(newScale, true);
+  },
+
+  set page(val) {
+    var pages = this.pages;
+    var input = document.getElementById('pageNumber');
+    if (!(0 < val && val <= pages.length)) {
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('pagechange', false, false, window, 0);
+      event.pageNumber = this.page;
+      window.dispatchEvent(event);
+      return;
+    }
+
+    pages[val - 1].updateStats();
+    currentPageNumber = val;
+    var event = document.createEvent('UIEvents');
+    event.initUIEvent('pagechange', false, false, window, 0);
+    event.pageNumber = val;
+    window.dispatchEvent(event);
+
+    // checking if the this.page was called from the updateViewarea function:
+    // avoiding the creation of two "set page" method (internal and public)
+    if (updateViewarea.inProgress)
+      return;
+
+    // Avoid scrolling the first page during loading
+    if (this.loading && val == 1)
+      return;
+
+    pages[val - 1].scrollIntoView();
+  },
+
+  get page() {
+    return currentPageNumber;
+  },
+
+  open: function pdfViewOpen(url, scale, password) {
+    var parameters = {password: password};
+    if (typeof url === 'string') { // URL
+      this.url = url;
+      document.title = decodeURIComponent(getFileName(url)) || url;
+      parameters.url = url;
+    } else if (url && 'byteLength' in url) { // ArrayBuffer
+      parameters.data = url;
+    }
+
+//    if (!PDFView.loadingBar) {
+//      PDFView.loadingBar = new ProgressBar('#loadingBar', {});
+//    }
+
+    this.container = document.getElementById('viewer');
+
+    this.pdfDocument = null;
+    var self = this;
+    self.loading = true;
+    PDFJS.getDocument(parameters).then(
+      function getDocumentCallback(pdfDocument) {
+        self.load(pdfDocument, scale);
+        self.loading = false;
+      },
+      function getDocumentError(message, exception) {
+        if (exception.name === 'PasswordException') {
+          if (exception.code === 'needpassword') {
+            var promptString = mozL10n.get('request_password', null,
+                                      'PDF is protected by a password:');
+            password = prompt(promptString);
+            if (password && password.length > 0) {
+              return PDFView.open(url, scale, password);
+            }
+          }
+        }
+
+        var loadingIndicator = document.getElementById('loading');
+        loadingIndicator.textContent = mozL10n.get('loading_error_indicator',
+          null, 'Error');
+        var moreInfo = {
+          message: message
+        };
+        self.error(mozL10n.get('loading_error', null,
+          'An error occurred while loading the PDF.'), moreInfo);
+        self.loading = false;
+      },
+      function getDocumentProgress(progressData) {
+        self.progress(progressData.loaded / progressData.total);
+      }
+    );
+  },
+
+  download: function pdfViewDownload() {
+    function noData() {
+      FirefoxCom.request('download', { originalUrl: url });
+    }
+
+    var url = this.url.split('#')[0];
+    if (PDFJS.isFirefoxExtension) {
+      // Document isn't ready just try to download with the url.
+      if (!this.pdfDocument) {
+        noData();
+        return;
+      }
+      this.pdfDocument.getData().then(
+        function getDataSuccess(data) {
+          var bb = new MozBlobBuilder();
+          bb.append(data.buffer);
+          var blobUrl = window.URL.createObjectURL(
+                          bb.getBlob('application/pdf'));
+
+          FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url },
+            function response(err) {
+              if (err) {
+                // This error won't really be helpful because it's likely the
+                // fallback won't work either (or is already open).
+                PDFView.error('PDF failed to download.');
+              }
+              window.URL.revokeObjectURL(blobUrl);
+            }
+          );
+        },
+        noData // Error ocurred try downloading with just the url.
+      );
+    } else {
+      url += '#pdfjs.action=download', '_parent';
+      window.open(url, '_parent');
+    }
+  },
+
+  fallback: function pdfViewFallback() {
+    if (!PDFJS.isFirefoxExtension)
+      return;
+    // Only trigger the fallback once so we don't spam the user with messages
+    // for one PDF.
+    if (this.fellback)
+      return;
+    this.fellback = true;
+    var url = this.url.split('#')[0];
+    FirefoxCom.request('fallback', url, function response(download) {
+      if (!download)
+        return;
+      PDFView.download();
+    });
+  },
+
+  navigateTo: function pdfViewNavigateTo(dest) {
+    if (typeof dest === 'string')
+      dest = this.destinations[dest];
+    if (!(dest instanceof Array))
+      return; // invalid destination
+    // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+    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];
+      currentPage.scrollIntoView(dest);
+    }
+  },
+
+  getDestinationHash: function pdfViewGetDestinationHash(dest) {
+    if (typeof dest === 'string')
+      return PDFView.getAnchorUrl('#' + escape(dest));
+    if (dest instanceof Array) {
+      var destRef = dest[0]; // see navigateTo method for dest format
+      var pageNumber = destRef instanceof Object ?
+        this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+        (destRef + 1);
+      if (pageNumber) {
+        var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
+        var destKind = dest[1];
+        if (typeof destKind === 'object' && 'name' in destKind &&
+            destKind.name == 'XYZ') {
+          var scale = (dest[4] || this.currentScale);
+          pdfOpenParams += '&zoom=' + (scale * 100);
+          if (dest[2] || dest[3]) {
+            pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+          }
+        }
+        return pdfOpenParams;
+      }
+    }
+    return '';
+  },
+
+  /**
+   * For the firefox extension we prefix the full url on anchor links so they
+   * don't come up as resource:// urls and so open in new tab/window works.
+   * @param {String} anchor The anchor hash include the #.
+   */
+  getAnchorUrl: function getAnchorUrl(anchor) {
+    if (PDFJS.isFirefoxExtension)
+      return this.url.split('#')[0] + anchor;
+    return anchor;
+  },
+
+  /**
+   * Show the error box.
+   * @param {String} message A message that is human readable.
+   * @param {Object} moreInfo (optional) Further information about the error
+   *                            that is more technical.  Should have a 'message'
+   *                            and optionally a 'stack' property.
+   */
+  error: function pdfViewError(message, moreInfo) {
+    var moreInfoText = mozL10n.get('error_build', {build: PDFJS.build},
+      'PDF.JS Build: {{build}}') + '\n';
+    if (moreInfo) {
+      moreInfoText +=
+        mozL10n.get('error_message', {message: moreInfo.message},
+        'Message: {{message}}');
+      if (moreInfo.stack) {
+        moreInfoText += '\n' +
+          mozL10n.get('error_stack', {stack: moreInfo.stack},
+          'Stack: {{stack}}');
+      } else {
+        if (moreInfo.filename) {
+          moreInfoText += '\n' +
+            mozL10n.get('error_file', {file: moreInfo.filename},
+            'File: {{file}}');
+        }
+        if (moreInfo.lineNumber) {
+          moreInfoText += '\n' +
+            mozL10n.get('error_line', {line: moreInfo.lineNumber},
+            'Line: {{line}}');
+        }
+      }
+    }
+    if (PDFJS.isFirefoxExtension) {
+      console.error(message + '\n' + moreInfoText);
+      this.fallback();
+      return;
+    }
+    var errorWrapper = document.getElementById('errorWrapper');
+    errorWrapper.removeAttribute('hidden');
+
+    var errorMessage = document.getElementById('errorMessage');
+    errorMessage.textContent = message;
+
+    var closeButton = document.getElementById('errorClose');
+    closeButton.onclick = function() {
+      errorWrapper.setAttribute('hidden', 'true');
+    };
+
+    var errorMoreInfo = document.getElementById('errorMoreInfo');
+    var moreInfoButton = document.getElementById('errorShowMore');
+    var lessInfoButton = document.getElementById('errorShowLess');
+    moreInfoButton.onclick = function() {
+      errorMoreInfo.removeAttribute('hidden');
+      moreInfoButton.setAttribute('hidden', 'true');
+      lessInfoButton.removeAttribute('hidden');
+    };
+    lessInfoButton.onclick = function() {
+      errorMoreInfo.setAttribute('hidden', 'true');
+      moreInfoButton.removeAttribute('hidden');
+      lessInfoButton.setAttribute('hidden', 'true');
+    };
+    moreInfoButton.removeAttribute('hidden');
+    lessInfoButton.setAttribute('hidden', 'true');
+    errorMoreInfo.value = moreInfoText;
+
+    errorMoreInfo.rows = moreInfoText.split('\n').length - 1;
+  },
+
+  progress: function pdfViewProgress(level) {
+    var percent = Math.round(level * 100);
+    var loadingIndicator = document.getElementById('loading');
+    loadingIndicator.textContent = mozL10n.get('loading', {percent: percent},
+      'Loading... {{percent}}%');
+
+//    PDFView.loadingBar.percent = percent;
+  },
+
+  load: function pdfViewLoad(pdfDocument, scale) {
+    function bindOnAfterDraw(pageView, thumbnailView) {
+      // when page is painted, using the image as thumbnail base
+      pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+        thumbnailView.setImage(pageView.canvas);
+        preDraw();
+      };
+    }
+
+    this.pdfDocument = pdfDocument;
+
+//    var errorWrapper = document.getElementById('errorWrapper');
+//    errorWrapper.setAttribute('hidden', 'true');
+
+    var loadingBox = document.getElementById('loading');
+    loadingBox.setAttribute('hidden', 'true');
+
+//    var loadingBox = document.getElementById('loadingBox');
+//    loadingBox.setAttribute('hidden', 'true');
+
+//    var thumbsView = document.getElementById('thumbnailView');
+//    thumbsView.parentNode.scrollTop = 0;
+
+//    while (thumbsView.hasChildNodes())
+//      thumbsView.removeChild(thumbsView.lastChild);
+
+//    if ('_loadingInterval' in thumbsView)
+//      clearInterval(thumbsView._loadingInterval);
+
+    var container = document.getElementById('viewer');
+    while (container.hasChildNodes())
+      container.removeChild(container.lastChild);
+
+    var pagesCount = pdfDocument.numPages;
+    var id = pdfDocument.fingerprint;
+    var storedHash = null;
+//    document.getElementById('numPages').textContent =
+//      mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
+    document.getElementById('numPages').innerHTML = pagesCount;
+    document.getElementById('pageNumber').max = pagesCount;
+    PDFView.documentFingerprint = id;
+    var store = PDFView.store = new Settings(id);
+    if (store.get('exists', false)) {
+      var page = store.get('page', '1');
+      var zoom = store.get('zoom', PDFView.currentScale);
+      var left = store.get('scrollLeft', '0');
+      var top = store.get('scrollTop', '0');
+
+      storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
+    }
+
+    var pages = this.pages = [];
+    this.pageText = [];
+    this.startedTextExtraction = false;
+    var pagesRefMap = {};
+    var thumbnails = this.thumbnails = [];
+    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;
+      }
+
+      self.pagesRefMap = pagesRefMap;
+    });
+
+    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) {
+        self.outline = new DocumentOutlineView(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;
+    this.currentScaleValue = null;
+    if (this.initialBookmark) {
+      this.setHash(this.initialBookmark);
+      this.initialBookmark = null;
+    }
+    else if (storedHash)
+      this.setHash(storedHash);
+    else if (scale) {
+      this.parseScale(scale, true);
+      this.page = 1;
+    }
+
+    if (PDFView.currentScale === kUnknownScale) {
+      // Scale was not initialized: invalid bookmark or scale was not specified.
+      // Setting the default one.
+      this.parseScale(kDefaultScale, true);
+    }
+  },
+
+  search: function pdfViewStartSearch() {
+    // Limit this function to run every <SEARCH_TIMEOUT>ms.
+    var SEARCH_TIMEOUT = 250;
+    var lastSeach = this.lastSearch;
+    var now = Date.now();
+    if (lastSeach && (now - lastSeach) < SEARCH_TIMEOUT) {
+      if (!this.searchTimer) {
+        this.searchTimer = setTimeout(function resumeSearch() {
+            PDFView.search();
+          },
+          SEARCH_TIMEOUT - (now - lastSeach)
+        );
+      }
+      return;
+    }
+    this.searchTimer = null;
+    this.lastSearch = now;
+
+    function bindLink(link, pageNumber) {
+      link.href = '#' + pageNumber;
+      link.onclick = function searchBindLink() {
+        PDFView.page = pageNumber;
+        return false;
+      };
+    }
+
+    var searchResults = document.getElementById('searchResults');
+
+    var searchTermsInput = document.getElementById('searchTermsInput');
+    searchResults.removeAttribute('hidden');
+    searchResults.textContent = '';
+
+    var terms = searchTermsInput.value;
+
+    if (!terms)
+      return;
+
+    // simple search: removing spaces and hyphens, then scanning every
+    terms = terms.replace(/\s-/g, '').toLowerCase();
+    var index = PDFView.pageText;
+    var pageFound = false;
+    for (var i = 0, ii = index.length; i < ii; i++) {
+      var pageText = index[i].replace(/\s-/g, '').toLowerCase();
+      var j = pageText.indexOf(terms);
+      if (j < 0)
+        continue;
+
+      var pageNumber = i + 1;
+      var textSample = index[i].substr(j, 50);
+      var link = document.createElement('a');
+      bindLink(link, pageNumber);
+      link.textContent = 'Page ' + pageNumber + ': ' + textSample;
+      searchResults.appendChild(link);
+
+      pageFound = true;
+    }
+    if (!pageFound) {
+      searchResults.textContent = mozL10n.get('search_terms_not_found', null,
+                                              '(Not found)');
+    }
+  },
+
+  setHash: function pdfViewSetHash(hash) {
+    if (!hash)
+      return;
+
+    if (hash.indexOf('=') >= 0) {
+      var params = PDFView.parseQueryString(hash);
+      // borrowing syntax from "Parameters for Opening PDF Files"
+      if ('nameddest' in params) {
+        PDFView.navigateTo(params.nameddest);
+        return;
+      }
+      if ('page' in params) {
+        var pageNumber = (params.page | 0) || 1;
+        this.page = pageNumber;
+        if ('zoom' in params) {
+          var zoomArgs = params.zoom.split(','); // scale,left,top
+          // building destination array
+
+          // If the zoom value, it has to get divided by 100. If it is a string,
+          // it should stay as it is.
+          var zoomArg = zoomArgs[0];
+          var zoomArgNumber = parseFloat(zoomArg);
+          if (zoomArgNumber)
+            zoomArg = zoomArgNumber / 100;
+
+          var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0),
+            (zoomArgs[2] | 0), zoomArg];
+          var currentPage = this.pages[pageNumber - 1];
+          currentPage.scrollIntoView(dest);
+        } else
+          this.page = params.page; // simple page
+        return;
+      }
+    } else if (/^\d+$/.test(hash)) // page number
+      this.page = hash;
+    else // named destination
+      PDFView.navigateTo(unescape(hash));
+  },
+
+  switchSidebarView: function pdfViewSwitchSidebarView(view) {
+    var thumbsView = document.getElementById('thumbnailView');
+    var outlineView = document.getElementById('outlineView');
+    var searchView = document.getElementById('searchView');
+
+    var thumbsButton = document.getElementById('viewThumbnail');
+    var outlineButton = document.getElementById('viewOutline');
+    var searchButton = document.getElementById('viewSearch');
+
+    switch (view) {
+      case 'thumbs':
+        thumbsButton.classList.add('toggled');
+        outlineButton.classList.remove('toggled');
+        searchButton.classList.remove('toggled');
+        thumbsView.classList.remove('hidden');
+        outlineView.classList.add('hidden');
+        searchView.classList.add('hidden');
+
+        updateThumbViewArea();
+        break;
+
+      case 'outline':
+        thumbsButton.classList.remove('toggled');
+        outlineButton.classList.add('toggled');
+        searchButton.classList.remove('toggled');
+        thumbsView.classList.add('hidden');
+        outlineView.classList.remove('hidden');
+        searchView.classList.add('hidden');
+
+        if (outlineButton.getAttribute('disabled'))
+          return;
+        break;
+
+      case 'search':
+        thumbsButton.classList.remove('toggled');
+        outlineButton.classList.remove('toggled');
+        searchButton.classList.add('toggled');
+        thumbsView.classList.add('hidden');
+        outlineView.classList.add('hidden');
+        searchView.classList.remove('hidden');
+
+        var searchTermsInput = document.getElementById('searchTermsInput');
+        searchTermsInput.focus();
+        // Start text extraction as soon as the search gets displayed.
+        this.extractText();
+        break;
+    }
+  },
+
+  extractText: function() {
+    if (this.startedTextExtraction)
+      return;
+    this.startedTextExtraction = true;
+    var self = this;
+    function extractPageText(pageIndex) {
+      self.pages[pageIndex].pdfPage.getTextContent().then(
+        function textContentResolved(textContent) {
+          self.pageText[pageIndex] = textContent;
+          self.search();
+          if ((pageIndex + 1) < self.pages.length)
+            extractPageText(pageIndex + 1);
+        }
+      );
+    };
+    extractPageText(0);
+  },
+
+  getVisiblePages: function pdfViewGetVisiblePages() {
+    var pages = this.pages;
+    var kBottomMargin = 10;
+    var kTopPadding = 30;
+    var visiblePages = [];
+
+    var currentHeight = kTopPadding + kBottomMargin;
+    //var container = this.container;
+    var container = document.getElementById('viewer');
+
+    // Add 1px to the scrolltop to give a little wiggle room if the math is off,
+    // this won't be needed if we calc current page number based off the middle
+    // of the screen instead of the top.
+    var containerTop = container.scrollTop + 1;
+    for (var i = 1; i <= pages.length; ++i) {
+      var page = pages[i - 1];
+      var pageHeight = page.height + kBottomMargin;
+      if (currentHeight + pageHeight > containerTop)
+        break;
+
+      currentHeight += pageHeight;
+    }
+
+    var containerBottom = containerTop + container.clientHeight;
+    for (; i <= pages.length && currentHeight < containerBottom; ++i) {
+      var singlePage = pages[i - 1];
+      visiblePages.push({ id: singlePage.id, y: currentHeight,
+                          view: singlePage });
+      currentHeight += page.height + kBottomMargin;
+    }
+    return visiblePages;
+  },
+
+  getVisibleThumbs: function pdfViewGetVisibleThumbs() {
+    var thumbs = this.thumbnails;
+    var kBottomMargin = 15;
+    var visibleThumbs = [];
+
+    var view = document.getElementById('thumbnailView');
+    var currentHeight = kBottomMargin;
+
+    var top = view.scrollTop;
+    for (var i = 1; i <= thumbs.length; ++i) {
+      var thumb = thumbs[i - 1];
+      var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
+      if (currentHeight + thumbHeight > top)
+        break;
+
+      currentHeight += thumbHeight;
+    }
+
+    var bottom = top + view.clientHeight;
+    for (; i <= thumbs.length && currentHeight < bottom; ++i) {
+      var singleThumb = thumbs[i - 1];
+      visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
+                          view: singleThumb });
+      currentHeight += singleThumb.height * singleThumb.scaleY + kBottomMargin;
+    }
+
+    return visibleThumbs;
+  },
+
+  // Helper function to parse query string (e.g. ?param1=value&parm2=...).
+  parseQueryString: function pdfViewParseQueryString(query) {
+    var parts = query.split('&');
+    var params = {};
+    for (var i = 0, ii = parts.length; i < parts.length; ++i) {
+      var param = parts[i].split('=');
+      var key = param[0];
+      var value = param.length > 1 ? param[1] : null;
+      params[unescape(key)] = unescape(value);
+    }
+    return params;
+  }
+};
+
+var PageView = function pageView(container, pdfPage, id, scale,
+                                 stats, navigateTo) {
+  this.id = id;
+  this.pdfPage = pdfPage;
+
+  this.scale = scale || 1.0;
+  this.viewport = this.pdfPage.getViewport(this.scale);
+
+  var anchor = document.createElement('a');
+  anchor.name = '' + this.id;
+
+  var div = document.createElement('div');
+  div.id = 'pageContainer' + this.id;
+  div.className = 'page';
+
+  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;
+    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);
+    div.removeAttribute('data-loaded');
+
+    delete this.canvas;
+
+    this.loadingIconDiv = document.createElement('div');
+    this.loadingIconDiv.className = 'loadingIcon';
+    div.appendChild(this.loadingIconDiv);
+  };
+
+  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() {
+        if (dest)
+          PDFView.navigateTo(dest);
+        return false;
+      };
+    }
+    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(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) {
+      var container = document.createElement('section');
+      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 + 'annotation-' + type.toLowerCase() + '.svg';
+      image.alt = mozL10n.get('text_annotation_type', {type: type},
+        '[{{type}} Annotation]');
+      var content = document.createElement('div');
+      content.setAttribute('hidden', true);
+      var title = document.createElement('h1');
+      var text = document.createElement('p');
+      content.style.left = Math.floor(rect[2]) + 'px';
+      content.style.top = Math.floor(rect[1]) + 'px';
+      title.textContent = item.title;
+
+      if (!item.content && !item.title) {
+        content.setAttribute('hidden', true);
+      } else {
+        var e = document.createElement('span');
+        var lines = item.content.split('\n');
+        for (var i = 0, ii = lines.length; i < ii; ++i) {
+          var line = lines[i];
+          e.appendChild(document.createTextNode(line));
+          if (i < (ii - 1))
+            e.appendChild(document.createElement('br'));
+        }
+        text.appendChild(e);
+        image.addEventListener('mouseover', function annotationImageOver() {
+           content.removeAttribute('hidden');
+        }, false);
+
+        image.addEventListener('mouseout', function annotationImageOut() {
+           content.setAttribute('hidden', true);
+        }, false);
+      }
+
+      content.appendChild(title);
+      content.appendChild(text);
+      container.appendChild(image);
+      container.appendChild(content);
+
+      return container;
+    }
+
+    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;
+          case 'Widget':
+            // TODO: support forms
+            PDFView.fallback();
+            break;
+        }
+      }
+    });
+  }
+
+  this.getPagePoint = function pageViewGetPagePoint(x, y) {
+    return this.viewport.convertToPdfPoint(x, y);
+  };
+
+  this.scrollIntoView = function pageViewScrollIntoView(dest) {
+      if (!dest) {
+        div.scrollIntoView(true);
+        return;
+      }
+
+      var x = 0, y = 0;
+      var width = 0, height = 0, widthScale, heightScale;
+      var scale = 0;
+      switch (dest[1].name) {
+        case 'XYZ':
+          x = dest[2];
+          y = dest[3];
+          scale = dest[4];
+          break;
+        case 'Fit':
+        case 'FitB':
+          scale = 'page-fit';
+          break;
+        case 'FitH':
+        case 'FitBH':
+          y = dest[2];
+          scale = 'page-width';
+          break;
+        case 'FitV':
+        case 'FitBV':
+          x = dest[2];
+          scale = 'page-height';
+          break;
+        case 'FitR':
+          x = dest[2];
+          y = dest[3];
+          width = dest[4] - x;
+          height = dest[5] - y;
+          widthScale = (this.container.clientWidth - kScrollbarPadding) /
+            width / kCssUnits;
+          heightScale = (this.container.clientHeight - kScrollbarPadding) /
+            height / kCssUnits;
+          scale = Math.min(widthScale, heightScale);
+          break;
+        default:
+          return;
+      }
+
+      if (scale && scale !== PDFView.currentScale)
+        PDFView.parseScale(scale, true);
+      else if (PDFView.currentScale === kUnknownScale)
+        PDFView.parseScale(kDefaultScale, true);
+
+      var boundingRect = [
+        this.viewport.convertToViewportPoint(x, y),
+        this.viewport.convertToViewportPoint(x + width, y + height)
+      ];
+      setTimeout(function pageViewScrollIntoViewRelayout() {
+        // letting page to re-layout before scrolling
+        var scale = PDFView.currentScale;
+        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) + '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);
+      }, 0);
+  };
+
+  this.drawingRequired = function() {
+    return !div.querySelector('canvas');
+  };
+
+  this.draw = function pageviewDraw(callback) {
+    if (!this.drawingRequired()) {
+      this.updateStats();
+      callback();
+      return;
+    }
+
+    var canvas = document.createElement('canvas');
+    canvas.id = 'page' + this.id;
+    canvas.mozOpaque = true;
+    div.appendChild(canvas);
+    this.canvas = canvas;
+
+    var textLayerDiv = null;
+    if (!PDFJS.disableTextLayer) {
+      textLayerDiv = document.createElement('div');
+      textLayerDiv.className = 'textLayer';
+      div.appendChild(textLayerDiv);
+    }
+    var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
+
+    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();
+
+    // Rendering area
+
+    var self = this;
+    function pageViewDrawCallback(error) {
+      if (self.loadingIconDiv) {
+        div.removeChild(self.loadingIconDiv);
+        delete self.loadingIconDiv;
+      }
+
+      if (error) {
+        PDFView.error(mozL10n.get('rendering_error', null,
+          'An error occurred while rendering the page.'), error);
+      }
+
+      self.stats = pdfPage.stats;
+      self.updateStats();
+      if (self.onAfterDraw)
+        self.onAfterDraw();
+
+      cache.push(self);
+      callback();
+    }
+
+    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);
+  };
+
+  this.updateStats = function pageViewUpdateStats() {
+    if (PDFJS.pdfBug && Stats.enabled) {
+      var stats = this.stats;
+      Stats.add(this.id, stats);
+    }
+  };
+};
+
+var ThumbnailView = function thumbnailView(container, pdfPage, id) {
+  var anchor = document.createElement('a');
+  anchor.href = PDFView.getAnchorUrl('#page=' + id);
+  anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
+  anchor.onclick = function stopNavigation() {
+    PDFView.page = id;
+    return false;
+  };
+
+  var viewport = pdfPage.getViewport(1);
+  var pageWidth = this.width = viewport.width;
+  var pageHeight = this.height = viewport.height;
+  var pageRatio = pageWidth / pageHeight;
+  this.id = id;
+
+  var canvasWidth = 98;
+  var canvasHeight = canvasWidth / this.width * this.height;
+  var scaleX = this.scaleX = (canvasWidth / pageWidth);
+  var scaleY = this.scaleY = (canvasHeight / pageHeight);
+
+  var div = document.createElement('div');
+  div.id = 'thumbnailContainer' + id;
+  div.className = 'thumbnail';
+
+  anchor.appendChild(div);
+  container.appendChild(anchor);
+
+  this.hasImage = false;
+
+  function getPageDrawContext() {
+    var canvas = document.createElement('canvas');
+    canvas.id = 'thumbnail' + id;
+    canvas.mozOpaque = true;
+
+    canvas.width = canvasWidth;
+    canvas.height = canvasHeight;
+    canvas.className = 'thumbnailImage';
+    canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
+      {page: id}, 'Thumbnail of Page {{page}}'));
+
+    div.setAttribute('data-loaded', true);
+
+    var ring = document.createElement('div');
+    ring.className = 'thumbnailSelectionRing';
+    ring.appendChild(canvas);
+    div.appendChild(ring);
+
+    var ctx = canvas.getContext('2d');
+    ctx.save();
+    ctx.fillStyle = 'rgb(255, 255, 255)';
+    ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+    ctx.restore();
+    return ctx;
+  }
+
+  this.drawingRequired = function thumbnailViewDrawingRequired() {
+    return !this.hasImage;
+  };
+
+  this.draw = function thumbnailViewDraw(callback) {
+    if (this.hasImage) {
+      callback();
+      return;
+    }
+
+    var ctx = getPageDrawContext();
+    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;
+  };
+
+  this.setImage = function thumbnailViewSetImage(img) {
+    if (this.hasImage || !img)
+      return;
+
+    var ctx = getPageDrawContext();
+    ctx.drawImage(img, 0, 0, img.width, img.height,
+                  0, 0, ctx.canvas.width, ctx.canvas.height);
+
+    this.hasImage = true;
+  };
+};
+
+var DocumentOutlineView = function documentOutlineView(outline) {
+  var outlineView = document.getElementById('outlineView');
+//  while (outlineView.firstChild)
+//    outlineView.removeChild(outlineView.firstChild);
+
+  function bindItemLink(domObj, item) {
+    domObj.href = PDFView.getDestinationHash(item.dest);
+    domObj.onclick = function documentOutlineViewOnclick(e) {
+      PDFView.navigateTo(item.dest);
+      return false;
+    };
+  }
+
+  if (!outline) {
+    var noOutline = document.createElement('div');
+    noOutline.classList.add('noOutline');
+    //noOutline.textContent = mozL10n.get('no_outline', null,
+    //  'No Outline Available');
+    noOutline.textContent = 'No Outline Available';
+    //outlineView.appendChild(noOutline);
+    return;
+  }
+
+  var queue = [{parent: outlineView, items: outline}];
+  while (queue.length > 0) {
+    var levelData = queue.shift();
+    var i, n = levelData.items.length;
+    for (i = 0; i < n; i++) {
+      var item = levelData.items[i];
+      var div = document.createElement('div');
+      div.className = 'outlineItem';
+      var a = document.createElement('a');
+      bindItemLink(a, item);
+      a.textContent = item.title;
+      div.appendChild(a);
+
+      if (item.items.length > 0) {
+        var itemsDiv = document.createElement('div');
+        itemsDiv.className = 'outlineItems';
+        div.appendChild(itemsDiv);
+        queue.push({parent: itemsDiv, items: item.items});
+      }
+
+      levelData.parent.appendChild(div);
+    }
+  }
+};
+
+// optimised CSS custom property getter/setter
+var CustomStyle = (function CustomStyleClosure() {
+
+  // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
+  //              animate-css-transforms-firefox-webkit.html
+  // in some versions of IE9 it is critical that ms appear in this list
+  // before Moz
+  var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
+  var _cache = { };
+
+  function CustomStyle() {
+  }
+
+  CustomStyle.getProp = function get(propName, element) {
+    // check cache only when no element is given
+    if (arguments.length == 1 && typeof _cache[propName] == 'string') {
+      return _cache[propName];
+    }
+
+    element = element || document.documentElement;
+    var style = element.style, prefixed, uPropName;
+
+    // test standard property first
+    if (typeof style[propName] == 'string') {
+      return (_cache[propName] = propName);
+    }
+
+    // capitalize
+    uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
+
+    // test vendor specific properties
+    for (var i = 0, l = prefixes.length; i < l; i++) {
+      prefixed = prefixes[i] + uPropName;
+      if (typeof style[prefixed] == 'string') {
+        return (_cache[propName] = prefixed);
+      }
+    }
+
+    //if all fails then set to undefined
+    return (_cache[propName] = 'undefined');
+  }
+
+  CustomStyle.setProp = function set(propName, element, str) {
+    var prop = this.getProp(propName);
+    if (prop != 'undefined')
+      element.style[prop] = str;
+  }
+
+  return CustomStyle;
+})();
+
+var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
+  this.textLayerDiv = textLayerDiv;
+
+  this.beginLayout = function textLayerBuilderBeginLayout() {
+    this.textDivs = [];
+    this.textLayerQueue = [];
+  };
+
+  this.endLayout = function textLayerBuilderEndLayout() {
+    var self = this;
+    var textDivs = this.textDivs;
+    var textLayerDiv = this.textLayerDiv;
+    var renderTimer = null;
+    var renderingDone = false;
+    var renderInterval = 0;
+    var resumeInterval = 500; // in ms
+
+    // Render the text layer, one div at a time
+    function renderTextLayer() {
+      if (textDivs.length === 0) {
+        clearInterval(renderTimer);
+        renderingDone = true;
+        return;
+      }
+      var textDiv = textDivs.shift();
+      if (textDiv.dataset.textLength > 0) {
+        textLayerDiv.appendChild(textDiv);
+
+        if (textDiv.dataset.textLength > 1) { // avoid div by zero
+          // Adjust div width to match canvas text
+          // Due to the .offsetWidth calls, this is slow
+          // This needs to come after appending to the DOM
+          var textScale = textDiv.dataset.canvasWidth / textDiv.offsetWidth;
+          CustomStyle.setProp('transform' , textDiv,
+            'scale(' + textScale + ', 1)');
+          CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
+        }
+      } // textLength > 0
+    }
+    renderTimer = setInterval(renderTextLayer, renderInterval);
+
+    // Stop rendering when user scrolls. Resume after XXX milliseconds
+    // of no scroll events
+    var scrollTimer = null;
+    function textLayerOnScroll() {
+      if (renderingDone) {
+        window.removeEventListener('scroll', textLayerOnScroll, false);
+        return;
+      }
+
+      // Immediately pause rendering
+      clearInterval(renderTimer);
+
+      clearTimeout(scrollTimer);
+      scrollTimer = setTimeout(function textLayerScrollTimer() {
+        // Resume rendering
+        renderTimer = setInterval(renderTextLayer, renderInterval);
+      }, resumeInterval);
+    }; // textLayerOnScroll
+
+    window.addEventListener('scroll', textLayerOnScroll, false);
+  }; // endLayout
+
+  this.appendText = function textLayerBuilderAppendText(text,
+                                                        fontName, fontSize) {
+    var textDiv = document.createElement('div');
+
+    // vScale and hScale already contain the scaling to pixel units
+    var fontHeight = fontSize * text.geom.vScale;
+    textDiv.dataset.canvasWidth = text.canvasWidth * text.geom.hScale;
+    textDiv.dataset.fontName = fontName;
+
+    textDiv.style.fontSize = fontHeight + 'px';
+    textDiv.style.left = text.geom.x + 'px';
+    textDiv.style.top = (text.geom.y - fontHeight) + 'px';
+    textDiv.textContent = PDFJS.bidi(text, -1);
+    textDiv.dir = text.direction;
+    textDiv.dataset.textLength = text.length;
+    this.textDivs.push(textDiv);
+  };
+};
+
+window.addEventListener('load', function webViewerLoad(evt) {
+  PDFView.initialize();
+  var params = PDFView.parseQueryString(document.location.search.substring(1));
+
+  var file = PDFJS.isFirefoxExtension ?
+              window.location.toString() : params.file || kDefaultURL;
+
+/*  if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
+      !window.FileList || !window.Blob) {
+    document.getElementById('openFile').setAttribute('hidden', 'true');
+  } else {
+    document.getElementById('fileInput').value = null;
+  }
+*/
+  // Special debugging flags in the hash section of the URL.
+  var hash = document.location.hash.substring(1);
+  var hashParams = PDFView.parseQueryString(hash);
+
+  if ('disableWorker' in hashParams)
+    PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
+
+/*  if (!PDFJS.isFirefoxExtension) {
+    var locale = navigator.language;
+    if ('locale' in hashParams)
+      locale = hashParams['locale'];
+    mozL10n.language.code = locale;
+  }
+*/
+  if ('disableTextLayer' in hashParams)
+    PDFJS.disableTextLayer = (hashParams['disableTextLayer'] === 'true');
+
+  if ('pdfBug' in hashParams &&
+      (!PDFJS.isFirefoxExtension || FirefoxCom.requestSync('pdfBugEnabled'))) {
+    PDFJS.pdfBug = true;
+    var pdfBug = hashParams['pdfBug'];
+    var enabled = pdfBug.split(',');
+    PDFBug.enable(enabled);
+    PDFBug.init();
+  }
+
+/*  if (!PDFJS.isFirefoxExtension ||
+    (PDFJS.isFirefoxExtension && FirefoxCom.requestSync('searchEnabled'))) {
+    document.querySelector('#viewSearch').classList.remove('hidden');
+  }
+*/
+  // Listen for warnings to trigger the fallback UI.  Errors should be caught
+  // and call PDFView.error() so we don't need to listen for those.
+  PDFJS.LogManager.addLogger({
+    warn: function() {
+      PDFView.fallback();
+    }
+  });
+
+//  var thumbsView = document.getElementById('thumbnailView');
+//  thumbsView.addEventListener('scroll', updateThumbViewArea, true);
+/*
+  var mainContainer = document.getElementById('mainContainer');
+  var outerContainer = document.getElementById('outerContainer');
+  mainContainer.addEventListener('transitionend', function(e) {
+    if (e.target == mainContainer) {
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('resize', false, false, window, 0);
+      window.dispatchEvent(event);
+      outerContainer.classList.remove('sidebarMoving');
+    }
+  }, true);
+
+  document.getElementById('sidebarToggle').addEventListener('click',
+    function() {
+      this.classList.toggle('toggled');
+      outerContainer.classList.add('sidebarMoving');
+      outerContainer.classList.toggle('sidebarOpen');
+      updateThumbViewArea();
+    });
+
+  PDFView.open(file, 0);
+*/
+}, true);
+
+/**
+ * Render the next not yet visible page already such that it is
+ * hopefully ready once the user scrolls to it.
+ */
+function preDraw() {
+  var pages = PDFView.pages;
+  var visible = PDFView.getVisiblePages();
+  var last = visible[visible.length - 1];
+  // PageView.id is the actual page number, which is + 1 compared
+  // to the index in `pages`. That means, pages[last.id] is the next
+  // PageView instance.
+  if (pages[last.id] && pages[last.id].drawingRequired()) {
+    renderingQueue.enqueueDraw(pages[last.id]);
+    return;
+  }
+  // If there is nothing to draw on the next page, maybe the user
+  // is scrolling up, so, let's try to render the next page *before*
+  // the first visible page
+  if (pages[visible[0].id - 2]) {
+    renderingQueue.enqueueDraw(pages[visible[0].id - 2]);
+  }
+}
+
+function updateViewarea() {
+  if (!PDFView.initialized)
+    return;
+  var visiblePages = PDFView.getVisiblePages();
+  var pageToDraw;
+  for (var i = 0; i < visiblePages.length; i++) {
+    var page = visiblePages[i];
+    var pageObj = PDFView.pages[page.id - 1];
+
+    pageToDraw |= pageObj.drawingRequired();
+    renderingQueue.enqueueDraw(pageObj);
+  }
+
+  if (!visiblePages.length)
+    return;
+
+  // If there is no need to draw a page that is currenlty visible, preDraw the
+  // next page the user might scroll to.
+  if (!pageToDraw) {
+    preDraw();
+  }
+
+  updateViewarea.inProgress = true; // used in "set page"
+  var currentId = PDFView.page;
+  var firstPage = visiblePages[0];
+  PDFView.page = firstPage.id;
+  updateViewarea.inProgress = false;
+
+  var currentScale = PDFView.currentScale;
+  var currentScaleValue = PDFView.currentScaleValue;
+  var normalizedScaleValue = currentScaleValue == currentScale ?
+    currentScale * 100 : currentScaleValue;
+
+  var pageNumber = firstPage.id;
+  var pdfOpenParams = '#page=' + pageNumber;
+  pdfOpenParams += '&zoom=' + normalizedScaleValue;
+  var currentPage = PDFView.pages[pageNumber - 1];
+  var topLeft = currentPage.getPagePoint(PDFView.container.scrollLeft,
+    (PDFView.container.scrollTop - firstPage.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[0]));
+  store.set('scrollTop', Math.round(topLeft[1]));
+//  var href = PDFView.getAnchorUrl(pdfOpenParams);
+//  document.getElementById('viewBookmark').href = href;
+}
+
+window.addEventListener('scroll', function webViewerScroll(evt) {
+  updateViewarea();
+}, true);
+
+var thumbnailTimer;
+
+function updateThumbViewArea() {
+  // Only render thumbs after pausing scrolling for this amount of time
+  // (makes UI more responsive)
+  var delay = 50; // in ms
+
+  if (thumbnailTimer)
+    clearTimeout(thumbnailTimer);
+
+  thumbnailTimer = setTimeout(function() {
+    var visibleThumbs = PDFView.getVisibleThumbs();
+    for (var i = 0; i < visibleThumbs.length; i++) {
+      var thumb = visibleThumbs[i];
+      renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
+    }
+  }, delay);
+}
+
+window.addEventListener('resize', function webViewerResize(evt) {
+  if (PDFView.initialized &&
+      (document.getElementById('pageWidthOption').selected ||
+      document.getElementById('pageFitOption').selected ||
+      document.getElementById('pageAutoOption').selected))
+      PDFView.parseScale(document.getElementById('scaleSelect').value);
+  updateViewarea();
+});
+
+window.addEventListener('hashchange', function webViewerHashchange(evt) {
+  PDFView.setHash(document.location.hash.substring(1));
+});
+
+window.addEventListener('change', function webViewerChange(evt) {
+  var files = evt.target.files;
+  if (!files || files.length == 0)
+    return;
+
+  // Read the local file into a Uint8Array.
+  var fileReader = new FileReader();
+  fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
+    var data = evt.target.result;
+    var buffer = new ArrayBuffer(data.length);
+    var uint8Array = new Uint8Array(buffer);
+
+    for (var i = 0; i < data.length; i++)
+      uint8Array[i] = data.charCodeAt(i);
+
+    PDFView.open(uint8Array, 0);
+  };
+
+  // Read as a binary string since "readAsArrayBuffer" is not yet
+  // implemented in Firefox.
+  var file = files[0];
+  fileReader.readAsBinaryString(file);
+  document.title = file.name;
+
+  // URL does not reflect proper document location - hiding some icons.
+  document.getElementById('viewBookmark').setAttribute('hidden', 'true');
+  document.getElementById('download').setAttribute('hidden', 'true');
+}, true);
+
+function selectScaleOption(value) {
+  var options = document.getElementById('scaleSelect').options;
+  var predefinedValueFound = false;
+  for (var i = 0; i < options.length; i++) {
+    var option = options[i];
+    if (option.value != value) {
+      option.selected = false;
+      continue;
+    }
+    option.selected = true;
+    predefinedValueFound = true;
+  }
+  return predefinedValueFound;
+}
+
+window.addEventListener('localized', function localized(evt) {
+  document.getElementsByTagName('html')[0].dir = mozL10n.language.direction;
+}, true);
+
+window.addEventListener('scalechange', function scalechange(evt) {
+  var customScaleOption = document.getElementById('customScaleOption');
+  customScaleOption.selected = false;
+
+  if (!evt.resetAutoSettings &&
+       (document.getElementById('pageWidthOption').selected ||
+        document.getElementById('pageFitOption').selected ||
+        document.getElementById('pageAutoOption').selected)) {
+      updateViewarea();
+      return;
+  }
+
+  var predefinedValueFound = selectScaleOption('' + evt.scale);
+  if (!predefinedValueFound) {
+    customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
+    customScaleOption.selected = true;
+  }
+
+  updateViewarea();
+}, true);
+
+window.addEventListener('pagechange', function pagechange(evt) {
+  var page = evt.pageNumber;
+  if (document.getElementById('pageNumber').value != page) {
+    document.getElementById('pageNumber').value = page;
+    var selected = document.querySelector('.thumbnail.selected');
+    if (selected)
+      selected.classList.remove('selected');
+/*
+    var thumbnail = document.getElementById('thumbnailContainer' + page);
+    thumbnail.classList.add('selected');
+    var visibleThumbs = PDFView.getVisibleThumbs();
+    var numVisibleThumbs = visibleThumbs.length;
+    // If the thumbnail isn't currently visible scroll it into view.
+    if (numVisibleThumbs > 0) {
+      var first = visibleThumbs[0].id;
+      // Account for only one thumbnail being visible.
+      var last = numVisibleThumbs > 1 ?
+                  visibleThumbs[numVisibleThumbs - 1].id : first;
+      if (page <= first || page >= last)
+        thumbnail.scrollIntoView();
+    }
+*/
+  }
+  document.getElementById('previous').disabled = (page <= 1);
+  document.getElementById('next').disabled = (page >= PDFView.pages.length);
+}, true);
+
+// Firefox specific event, so that we can prevent browser from zooming
+window.addEventListener('DOMMouseScroll', function(evt) {
+  if (evt.ctrlKey) {
+    evt.preventDefault();
+
+    var ticks = evt.detail;
+    var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
+    for (var i = 0, length = Math.abs(ticks); i < length; i++)
+      PDFView[direction]();
+  }
+}, false);
+
+window.addEventListener('keydown', function keydown(evt) {
+  var handled = false;
+  var cmd = (evt.ctrlKey ? 1 : 0) |
+            (evt.altKey ? 2 : 0) |
+            (evt.shiftKey ? 4 : 0) |
+            (evt.metaKey ? 8 : 0);
+
+  // First, handle the key bindings that are independent whether an input
+  // control is selected or not.
+  if (cmd == 1 || cmd == 8) { // either CTRL or META key.
+    switch (evt.keyCode) {
+      case 61: // FF/Mac '='
+      case 107: // FF '+' and '='
+      case 187: // Chrome '+'
+        PDFView.zoomIn();
+        handled = true;
+        break;
+      case 109: // FF '-'
+      case 189: // Chrome '-'
+        PDFView.zoomOut();
+        handled = true;
+        break;
+      case 48: // '0'
+        PDFView.parseScale(kDefaultScale, true);
+        handled = true;
+        break;
+    }
+  }
+
+  if (handled) {
+    evt.preventDefault();
+    return;
+  }
+
+  // Some shortcuts should not get handled if a control/input element
+  // is selected.
+  var curElement = document.activeElement;
+  if (curElement && curElement.tagName == 'INPUT')
+    return;
+  var controlsElement = document.getElementById('controls');
+  while (curElement) {
+    if (curElement === controlsElement)
+      return; // ignoring if the 'controls' element is focused
+    curElement = curElement.parentNode;
+  }
+
+  if (cmd == 0) { // no control key pressed at all.
+    switch (evt.keyCode) {
+      case 37: // left arrow
+      case 75: // 'k'
+      case 80: // 'p'
+        PDFView.page--;
+        handled = true;
+        break;
+      case 39: // right arrow
+      case 74: // 'j'
+      case 78: // 'n'
+        PDFView.page++;
+        handled = true;
+        break;
+    }
+  }
+
+  if (handled) {
+    evt.preventDefault();
+  }
+});
index 85bbf09362e00dba0c07c7ee9643f758afb69141..d98bedc5c09281138b91a6c6aa8c97a33ed26919 100644 (file)
@@ -24,21 +24,9 @@ function showPDFviewer(dir,filename){
                var oldcontent = $("#content").html();
                $("#content").html(oldcontent+'<div id="loading">Loading... 0%</div><div id="viewer"></div>');
                showPDFviewer.lastTitle = document.title;
-               if(!showPDFviewer.loaded){
-                       OC.addScript( 'files_pdfviewer', 'pdfjs/build/pdf',function(){
-                               OC.addScript( 'files_pdfviewer', 'pdfview',function(){
-                                       showPDFviewer.loaded=true;
-                                       PDFJS.workerSrc = OC.filePath('files_pdfviewer','js','pdfjs/build/pdf.js');
-                                       PDFView.Ptitle = filename;
-                                       PDFView.open(url,1.00);
-                                       PDFView.active=true;
-                               });
-                       });
-               }else{
-                       PDFView.Ptitle = filename;
-                       PDFView.open(url,1.00);
-                       PDFView.active=true;
-               }
+               PDFView.Ptitle = filename;
+               PDFView.open(url,1.00);
+               PDFView.active=true;
                $("#pageWidthOption").attr("selected","selected");
                showPDFviewer.shown = true;
        }
@@ -46,7 +34,6 @@ function showPDFviewer(dir,filename){
 showPDFviewer.shown=false;
 showPDFviewer.oldCode='';
 showPDFviewer.lastTitle='';
-showPDFviewer.loaded=false;
 
 $(document).ready(function(){
        if(!$.browser.msie){//doesnt work on IE