diff --git a/src/annotation.js b/src/annotation.js
index b24ec5489..6f5ae28c7 100644
--- a/src/annotation.js
+++ b/src/annotation.js
@@ -16,7 +16,7 @@
  */
 /* globals Util, isDict, isName, stringToPDFString, TODO, Dict, Stream,
            stringToBytes, PDFJS, isWorker, assert, NotImplementedException,
-           Promise, isArray, ObjectLoader, isValidUrl */
+           Promise, isArray, ObjectLoader, isValidUrl, OperatorList */
 
 'use strict';
 
@@ -162,13 +162,7 @@ var Annotation = (function AnnotationClosure() {
       var promise = new Promise();
 
       if (!this.appearance) {
-        promise.resolve({
-          queue: {
-            fnArray: [],
-            argsArray: []
-          },
-          dependency: {}
-        });
+        promise.resolve(new OperatorList());
         return promise;
       }
 
@@ -192,19 +186,11 @@ var Annotation = (function AnnotationClosure() {
       var border = data.border;
 
       resourcesPromise.then(function(resources) {
-        var listPromise = evaluator.getOperatorList(this.appearance, resources);
-        listPromise.then(function(appearanceStreamData) {
-          var fnArray = appearanceStreamData.queue.fnArray;
-          var argsArray = appearanceStreamData.queue.argsArray;
-
-          fnArray.unshift('beginAnnotation');
-          argsArray.unshift([data.rect, transform, matrix]);
-
-          fnArray.push('endAnnotation');
-          argsArray.push([]);
-
-          promise.resolve(appearanceStreamData);
-        });
+        var opList = new OperatorList();
+        opList.addOp('beginAnnotation', [data.rect, transform, matrix]);
+        evaluator.getOperatorList(this.appearance, resources, opList);
+        opList.addOp('endAnnotation', []);
+        promise.resolve(opList);
       }.bind(this));
 
       return promise;
@@ -284,7 +270,7 @@ var Annotation = (function AnnotationClosure() {
   };
 
   Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
-      annotations, pageQueue, pdfManager, dependencies, partialEvaluator) {
+      annotations, opList, pdfManager, partialEvaluator) {
 
     function reject(e) {
       annotationsReadyPromise.reject(e);
@@ -296,22 +282,13 @@ var Annotation = (function AnnotationClosure() {
     for (var i = 0, n = annotations.length; i < n; ++i) {
       annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
     }
-
     Promise.all(annotationPromises).then(function(datas) {
-      var fnArray = pageQueue.fnArray;
-      var argsArray = pageQueue.argsArray;
-      fnArray.push('beginAnnotations');
-      argsArray.push([]);
+      opList.addOp('beginAnnotations', []);
       for (var i = 0, n = datas.length; i < n; ++i) {
-        var annotationData = datas[i];
-        var annotationQueue = annotationData.queue;
-        Util.concatenateToArray(fnArray, annotationQueue.fnArray);
-        Util.concatenateToArray(argsArray, annotationQueue.argsArray);
-        Util.extendObj(dependencies, annotationData.dependencies);
+        var annotOpList = datas[i];
+        opList.addOpList(annotOpList);
       }
-      fnArray.push('endAnnotations');
-      argsArray.push([]);
-
+      opList.addOp('endAnnotations', []);
       annotationsReadyPromise.resolve();
     }, reject);
 
@@ -458,8 +435,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
 
     getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
 
-
       var promise = new Promise();
+      var opList = new OperatorList();
       var data = this.data;
 
       // Even if there is an appearance stream, ignore it. This is the
@@ -467,65 +444,44 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
 
       var defaultAppearance = data.defaultAppearance;
       if (!defaultAppearance) {
-        promise.resolve({
-          queue: {
-            fnArray: [],
-            argsArray: []
-          },
-          dependency: {}
-        });
+        promise.resolve(opList);
         return promise;
       }
 
       // Include any font resources found in the default appearance
 
       var stream = new Stream(stringToBytes(defaultAppearance));
-      var listPromise = evaluator.getOperatorList(stream, this.fieldResources);
-      listPromise.then(function(appearanceStreamData) {
-        var appearanceFnArray = appearanceStreamData.queue.fnArray;
-        var appearanceArgsArray = appearanceStreamData.queue.argsArray;
-        var fnArray = [];
-        var argsArray = [];
-
-        // TODO(mack): Add support for stroke color
-        data.rgb = [0, 0, 0];
-        for (var i = 0, n = fnArray.length; i < n; ++i) {
-          var fnName = appearanceFnArray[i];
-          var args = appearanceArgsArray[i];
-          if (fnName === 'dependency') {
-            var dependency = args[i];
-            if (dependency.indexOf('g_font_') === 0) {
-              data.fontRefName = dependency;
-            }
-            fnArray.push(fnName);
-            argsArray.push(args);
-          } else if (fnName === 'setFont') {
-            data.fontRefName = args[0];
-            var size = args[1];
-            if (size < 0) {
-              data.fontDirection = -1;
-              data.fontSize = -size;
-            } else {
-              data.fontDirection = 1;
-              data.fontSize = size;
-            }
-          } else if (fnName === 'setFillRGBColor') {
-            data.rgb = args;
-          } else if (fnName === 'setFillGray') {
-            var rgbValue = args[0] * 255;
-            data.rgb = [rgbValue, rgbValue, rgbValue];
+      evaluator.getOperatorList(stream, this.fieldResources, opList);
+      var appearanceFnArray = opList.fnArray;
+      var appearanceArgsArray = opList.argsArray;
+      var fnArray = [];
+      var argsArray = [];
+
+      // TODO(mack): Add support for stroke color
+      data.rgb = [0, 0, 0];
+      // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
+      for (var i = 0, n = fnArray.length; i < n; ++i) {
+        var fnName = appearanceFnArray[i];
+        var args = appearanceArgsArray[i];
+
+        if (fnName === 'setFont') {
+          data.fontRefName = args[0];
+          var size = args[1];
+          if (size < 0) {
+            data.fontDirection = -1;
+            data.fontSize = -size;
+          } else {
+            data.fontDirection = 1;
+            data.fontSize = size;
           }
+        } else if (fnName === 'setFillRGBColor') {
+          data.rgb = args;
+        } else if (fnName === 'setFillGray') {
+          var rgbValue = args[0] * 255;
+          data.rgb = [rgbValue, rgbValue, rgbValue];
         }
-        promise.resolve({
-          queue: {
-            fnArray: fnArray,
-            argsArray: argsArray
-          },
-          dependency: {}
-        });
-
-      });
-
+      }
+      promise.resolve(opList);
       return promise;
     }
   });
@@ -557,13 +513,7 @@ var TextAnnotation = (function TextAnnotationClosure() {
 
     getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
       var promise = new Promise();
-      promise.resolve({
-        queue: {
-          fnArray: [],
-          argsArray: []
-        },
-        dependency: {}
-      });
+      promise.resolve(new OperatorList());
       return promise;
     },
 
diff --git a/src/api.js b/src/api.js
index c1ab09d95..0f571a0f1 100644
--- a/src/api.js
+++ b/src/api.js
@@ -17,7 +17,7 @@
 /* globals CanvasGraphics, combineUrl, createScratchCanvas, error, ErrorFont,
            Font, FontLoader, globalScope, info, isArrayBuffer, loadJpegStream,
            MessageHandler, PDFJS, PDFObjects, Promise, StatTimer, warn,
-           WorkerMessageHandler, PasswordResponses */
+           WorkerMessageHandler, PasswordResponses, Util */
 
  'use strict';
 
@@ -222,6 +222,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
     this.objs = new PDFObjects();
     this.renderInProgress = false;
     this.cleanupAfterRender = false;
+    this.renderTasks = [];
   }
   PDFPageProxy.prototype = {
     /**
@@ -289,20 +290,24 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
      *                               rendering call the function that is the
      *                               first argument to the callback.
      * }.
-     * @return {Promise} A promise that is resolved when the page finishes
-     * rendering.
+     * @return {RenderTask} An extended promise that is resolved when the page
+     * finishes rendering (see RenderTask).
      */
     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.operatorList = {
+          fnArray: [],
+          argsArray: [],
+          lastChunk: false
+        };
 
         this.stats.time('Page Request');
         this.transport.messageHandler.send('RenderPageRequest', {
@@ -310,128 +315,48 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
         });
       }
 
-      var self = this;
-      function complete(error) {
-        self.renderInProgress = false;
-        if (self.destroyed || self.cleanupAfterRender) {
-          delete self.displayReadyPromise;
-          delete self.operatorList;
-          self.objs.clear();
-        }
-
-        if (error)
-          promise.reject(error);
-        else
-          promise.resolve();
-      }
-      var continueCallback = params.continueCallback;
+      var internalRenderTask = new InternalRenderTask(complete, params,
+                                       this.objs, this.commonObjs,
+                                       this.operatorList, this.pageNumber);
+      this.renderTasks.push(internalRenderTask);
+      var renderTask = new RenderTask(internalRenderTask);
 
-      // Once the operatorList and fonts are loaded, do the actual rendering.
+      var self = this;
       this.displayReadyPromise.then(
-        function pageDisplayReadyPromise() {
+        function pageDisplayReadyPromise(transparency) {
           if (self.destroyed) {
             complete();
             return;
           }
-
-          var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
-            this.objs, params.textLayer, params.imageLayer);
-          try {
-            this.display(gfx, params.viewport, complete, continueCallback);
-          } catch (e) {
-            complete(e);
-          }
-        }.bind(this),
+          stats.time('Rendering');
+          internalRenderTask.initalizeGraphics(transparency);
+          internalRenderTask.operatorListChanged();
+        },
         function pageDisplayReadPromiseError(reason) {
           complete(reason);
         }
       );
 
-      return promise;
-    },
-    /**
-     * For internal use only.
-     */
-    startRenderingFromOperatorList:
-      function PDFPageProxy_startRenderingFromOperatorList(operatorList,
-                                                           fonts) {
-      var self = this;
-      this.operatorList = operatorList;
-
-      this.ensureFonts(fonts,
-        function pageStartRenderingFromOperatorListEnsureFonts() {
-          self.displayReadyPromise.resolve();
+      function complete(error) {
+        var i = self.renderTasks.indexOf(internalRenderTask);
+        if (i >= 0) {
+            self.renderTasks.splice(i, 1);
         }
-      );
-    },
-    /**
-     * For internal use only.
-     */
-    ensureFonts: function PDFPageProxy_ensureFonts(fonts, callback) {
-      this.stats.time('Font Loading');
-      // Convert the font names to the corresponding font obj.
-      var fontObjs = [];
-      for (var i = 0, ii = fonts.length; i < ii; i++) {
-        var obj = this.commonObjs.getData(fonts[i]);
-        if (obj.error) {
-          warn('Error during font loading: ' + obj.error);
-          continue;
+
+        if (self.renderTasks.length === 0 &&
+            (self.destroyed || self.cleanupAfterRender)) {
+          self._destroy();
         }
-        if (!obj.coded) {
-          this.transport.embeddedFontsUsed = true;
+        if (error) {
+          renderTask.reject(error);
+        } else {
+          renderTask.resolve();
         }
-        fontObjs.push(obj);
+        stats.timeEnd('Rendering');
+        stats.timeEnd('Overall');
       }
 
-      // Load all the fonts
-      FontLoader.bind(
-        fontObjs,
-        function pageEnsureFontsFontObjs(fontObjs) {
-          this.stats.timeEnd('Font Loading');
-
-          callback.call(this);
-        }.bind(this)
-      );
-    },
-    /**
-     * For internal use only.
-     */
-    display: function PDFPageProxy_display(gfx, viewport, callback,
-                                           continueCallback) {
-      var stats = this.stats;
-      stats.time('Rendering');
-
-      var operatorList = this.operatorList;
-      gfx.beginDrawing(viewport, operatorList.transparency);
-
-      var startIdx = 0;
-      var length = operatorList.fnArray.length;
-      var stepper = null;
-      if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
-          globalScope['StepperManager'].enabled) {
-        stepper = globalScope['StepperManager'].create(this.pageNumber - 1);
-        stepper.init(operatorList);
-        stepper.nextBreakPoint = stepper.getNextBreakPoint();
-      }
-
-      var continueWrapper;
-      if (continueCallback)
-        continueWrapper = function() { continueCallback(next); };
-      else
-        continueWrapper = next;
-
-      var self = this;
-      function next() {
-        startIdx = gfx.executeOperatorList(operatorList, startIdx,
-                                           continueWrapper, stepper);
-        if (startIdx == length) {
-          gfx.endDrawing();
-          stats.timeEnd('Rendering');
-          stats.timeEnd('Overall');
-          if (callback) callback();
-        }
-      }
-      continueWrapper();
+      return renderTask;
     },
     /**
      * @return {Promise} That is resolved with the a {string} that is the text
@@ -466,10 +391,38 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
     destroy: function PDFPageProxy_destroy() {
       this.destroyed = true;
 
-      if (!this.renderInProgress) {
-        delete this.operatorList;
-        delete this.displayReadyPromise;
-        this.objs.clear();
+      if (this.renderTasks.length === 0) {
+        this._destroy();
+      }
+    },
+    /**
+     * For internal use only. Does the actual cleanup.
+     */
+    _destroy: function PDFPageProxy__destroy() {
+      delete this.operatorList;
+      delete this.displayReadyPromise;
+      this.objs.clear();
+    },
+    /**
+     * For internal use only.
+     */
+    _startRenderPage: function PDFPageProxy_startRenderPage(transparency) {
+      this.displayReadyPromise.resolve(transparency);
+    },
+    /**
+     * For internal use only.
+     */
+    _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
+      // Add the new chunk to the current operator list.
+      Util.concatenateToArray(this.operatorList.fnArray,
+                              operatorListChunk.fnArray);
+      Util.concatenateToArray(this.operatorList.argsArray,
+                              operatorListChunk.argsArray);
+      this.operatorList.lastChunk = operatorListChunk.lastChunk;
+
+      // Notify all the rendering tasks there are more operators to be consumed.
+      for (var i = 0; i < this.renderTasks.length; i++) {
+        this.renderTasks[i].operatorListChanged();
       }
     }
   };
@@ -644,12 +597,17 @@ var WorkerTransport = (function WorkerTransportClosure() {
         promise.resolve(annotations);
       }, this);
 
-      messageHandler.on('RenderPage', function transportRender(data) {
+      messageHandler.on('StartRenderPage', function transportRender(data) {
         var page = this.pageCache[data.pageIndex];
-        var depFonts = data.depFonts;
 
         page.stats.timeEnd('Page Request');
-        page.startRenderingFromOperatorList(data.operatorList, depFonts);
+        page._startRenderPage(data.transparency);
+      }, this);
+
+      messageHandler.on('RenderPageChunk', function transportRender(data) {
+        var page = this.pageCache[data.pageIndex];
+
+        page._renderPageChunk(data.operatorList);
       }, this);
 
       messageHandler.on('commonobj', function transportObj(data) {
@@ -662,14 +620,22 @@ var WorkerTransport = (function WorkerTransportClosure() {
           case 'Font':
             var exportedData = data[2];
 
-            // 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;
-            if ('error' in exportedData)
+            if ('error' in exportedData) {
               font = new ErrorFont(exportedData.error);
-            else
+              warn('Error during font loading: ' + font.error);
+              this.commonObjs.resolve(id, font);
+              break;
+            } else {
               font = new Font(exportedData);
-            this.commonObjs.resolve(id, font);
+            }
+
+            FontLoader.bind(
+              [font],
+              function fontReady(fontObjs) {
+                this.commonObjs.resolve(id, font);
+              }.bind(this)
+            );
             break;
           default:
             error('Got unknown common object type ' + type);
@@ -814,3 +780,129 @@ var WorkerTransport = (function WorkerTransportClosure() {
   return WorkerTransport;
 
 })();
+
+/**
+ * RenderTask is basically a promise but adds a cancel function to terminate it.
+ */
+var RenderTask = (function RenderTaskClosure() {
+  function RenderTask(internalRenderTask) {
+    this.internalRenderTask = internalRenderTask;
+    Promise.call(this);
+  }
+
+  RenderTask.prototype = Object.create(Promise.prototype);
+
+  /**
+   * Cancel the rendering task. If the task is curently rendering it will not be
+   * cancelled until graphics pauses with a timeout. The promise that this
+   * object extends will resolved when cancelled.
+   */
+  RenderTask.prototype.cancel = function RenderTask_cancel() {
+    this.internalRenderTask.cancel();
+  };
+
+  return RenderTask;
+})();
+
+var InternalRenderTask = (function InternalRenderTaskClosure() {
+
+  function InternalRenderTask(callback, params, objs, commonObjs, operatorList,
+                              pageNumber) {
+    this.callback = callback;
+    this.params = params;
+    this.objs = objs;
+    this.commonObjs = commonObjs;
+    this.operatorListIdx = null;
+    this.operatorList = operatorList;
+    this.pageNumber = pageNumber;
+    this.running = false;
+    this.graphicsReadyCallback = null;
+    this.graphicsReady = false;
+    this.cancelled = false;
+  }
+
+  InternalRenderTask.prototype = {
+
+    initalizeGraphics:
+        function InternalRenderTask_initalizeGraphics(transparency) {
+
+      if (this.cancelled) {
+        return;
+      }
+      if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
+          globalScope.StepperManager.enabled) {
+        this.stepper = globalScope.StepperManager.create(this.pageNumber - 1);
+        this.stepper.init(this.operatorList);
+        this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
+      }
+
+      var params = this.params;
+      this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
+                                    this.objs, params.textLayer,
+                                    params.imageLayer);
+
+      this.gfx.beginDrawing(params.viewport, transparency);
+      this.operatorListIdx = 0;
+      this.graphicsReady = true;
+      if (this.graphicsReadyCallback) {
+        this.graphicsReadyCallback();
+      }
+    },
+
+    cancel: function InternalRenderTask_cancel() {
+      this.running = false;
+      this.cancelled = true;
+      this.callback();
+    },
+
+    operatorListChanged: function InternalRenderTask_operatorListChanged() {
+      if (!this.graphicsReady) {
+        if (!this.graphicsReadyCallback) {
+          this.graphicsReadyCallback = this._continue.bind(this);
+        }
+        return;
+      }
+
+      if (this.stepper) {
+        this.stepper.updateOperatorList(this.operatorList);
+      }
+
+      if (this.running) {
+        return;
+      }
+      this._continue();
+    },
+
+    _continue: function InternalRenderTask__continue() {
+      this.running = true;
+      if (this.cancelled) {
+        return;
+      }
+      if (this.params.continueCallback) {
+        this.params.continueCallback(this._next.bind(this));
+      } else {
+        this._next();
+      }
+    },
+
+    _next: function InternalRenderTask__next() {
+      if (this.cancelled) {
+        return;
+      }
+      this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList,
+                                        this.operatorListIdx,
+                                        this._continue.bind(this),
+                                        this.stepper);
+      if (this.operatorListIdx === this.operatorList.fnArray.length) {
+        this.running = false;
+        if (this.operatorList.lastChunk) {
+          this.gfx.endDrawing();
+          this.callback();
+        }
+      }
+    }
+
+  };
+
+  return InternalRenderTask;
+})();
diff --git a/src/canvas.js b/src/canvas.js
index 0b1bed490..d51ed0308 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -670,7 +670,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
             this.setFlatness(value);
             break;
           case 'Font':
-            this.setFont(state[1], state[2]);
+            this.setFont(value[0], value[1]);
             break;
           case 'CA':
             this.current.strokeAlpha = state[1];
diff --git a/src/core.js b/src/core.js
index 4b005361c..3e1ea9339 100644
--- a/src/core.js
+++ b/src/core.js
@@ -18,7 +18,8 @@
            isArrayBuffer, isDict, isName, isStream, isString, Lexer,
            Linearization, NullStream, PartialEvaluator, shadow, Stream,
            StreamsSequenceStream, stringToPDFString, TODO, Util, warn, XRef,
-           MissingDataException, Promise, Annotation, ObjectLoader */
+           MissingDataException, Promise, Annotation, ObjectLoader, OperatorList
+           */
 
 'use strict';
 
@@ -183,33 +184,35 @@ var Page = (function PageClosure() {
       dataPromises.then(function(data) {
         var contentStream = data[0];
 
-        partialEvaluator.getOperatorList(contentStream, self.resources).then(
-          function(data) {
-            pageListPromise.resolve(data);
-          },
-          reject
-        );
+
+        var opList = new OperatorList(handler, self.pageIndex);
+
+        handler.send('StartRenderPage', {
+          transparency: partialEvaluator.hasBlendModes(self.resources),
+          pageIndex: self.pageIndex
+        });
+        partialEvaluator.getOperatorList(contentStream, self.resources, opList);
+        pageListPromise.resolve(opList);
       });
 
       var annotationsPromise = pdfManager.ensure(this, 'annotations');
       Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
-        var pageData = datas[0];
-        var pageQueue = pageData.queue;
+        var pageOpList = datas[0];
         var annotations = datas[1];
 
         if (annotations.length === 0) {
-          PartialEvaluator.optimizeQueue(pageQueue);
-          promise.resolve(pageData);
+          PartialEvaluator.optimizeQueue(pageOpList);
+          pageOpList.flush(true);
+          promise.resolve(pageOpList);
           return;
         }
 
-        var dependencies = pageData.dependencies;
         var annotationsReadyPromise = Annotation.appendToOperatorList(
-          annotations, pageQueue, pdfManager, dependencies, partialEvaluator);
+          annotations, pageOpList, pdfManager, partialEvaluator);
         annotationsReadyPromise.then(function () {
-          PartialEvaluator.optimizeQueue(pageQueue);
-
-          promise.resolve(pageData);
+          PartialEvaluator.optimizeQueue(pageOpList);
+          pageOpList.flush(true);
+          promise.resolve(pageOpList);
         }, reject);
       }, reject);
 
@@ -244,12 +247,9 @@ var Page = (function PageClosure() {
               self.pageIndex, 'p' + self.pageIndex + '_',
               self.idCounters);
 
-        partialEvaluator.getTextContent(
-            contentStream, self.resources).then(function(bidiTexts) {
-          textContentPromise.resolve({
-            bidiTexts: bidiTexts
-          });
-        });
+        var bidiTexts = partialEvaluator.getTextContent(contentStream,
+                                                        self.resources);
+        textContentPromise.resolve(bidiTexts);
       });
 
       return textContentPromise;
diff --git a/src/evaluator.js b/src/evaluator.js
index b3de3b102..7d141eac5 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -153,24 +153,51 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
   var TILING_PATTERN = 1, SHADING_PATTERN = 2;
 
-  function createOperatorList(fnArray, argsArray, dependencies) {
-    return {
-      queue: {
-        fnArray: fnArray || [],
-        argsArray: argsArray || []
-      },
-      dependencies: dependencies || {}
-    };
-  }
-
   PartialEvaluator.prototype = {
+    hasBlendModes: function PartialEvaluator_hasBlendModes(resources) {
+      if (!isDict(resources)) {
+        return false;
+      }
+
+      var nodes = [resources];
+      while (nodes.length) {
+        var node = nodes.shift();
+        // First check the current resources for blend modes.
+        var graphicStates = node.get('ExtGState');
+        if (isDict(graphicStates)) {
+          graphicStates = graphicStates.getAll();
+          for (var key in graphicStates) {
+            var graphicState = graphicStates[key];
+            var bm = graphicState['BM'];
+            if (isName(bm) && bm.name !== 'Normal') {
+              return true;
+            }
+          }
+        }
+        // Descend into the XObjects to look for more resources and blend modes.
+        var xObjects = node.get('XObject');
+        if (!isDict(xObjects)) {
+          continue;
+        }
+        xObjects = xObjects.getAll();
+        for (var key in xObjects) {
+          var xObject = xObjects[key];
+          if (!isStream(xObject)) {
+            continue;
+          }
+          var xResources = xObject.dict.get('Resources');
+          if (isDict(xResources)) {
+            nodes.push(xResources);
+          }
+        }
+      }
+      return false;
+    },
 
     buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
-                                                                 xobj, smask) {
+                                                                 xobj, smask,
+                                                                 operatorList) {
       var self = this;
-      var promise = new Promise();
-      var fnArray = [];
-      var argsArray = [];
 
       var matrix = xobj.dict.get('Matrix');
       var bbox = xobj.dict.get('BBox');
@@ -191,44 +218,22 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
           // There is also a group colorspace, but since we put everything in
           // RGB I'm not sure we need it.
         }
-        fnArray.push('beginGroup');
-        argsArray.push([groupOptions]);
+        operatorList.addOp('beginGroup', [groupOptions]);
       }
 
-      fnArray.push('paintFormXObjectBegin');
-      argsArray.push([matrix, bbox]);
-
-      // Pass in the current `queue` object. That means the `fnArray`
-      // and the `argsArray` in this scope is reused and new commands
-      // are added to them.
-      var opListPromise = this.getOperatorList(xobj,
-          xobj.dict.get('Resources') || resources);
-      opListPromise.then(function(data) {
-        var queue = data.queue;
-        var dependencies = data.dependencies;
-        Util.prependToArray(queue.fnArray, fnArray);
-        Util.prependToArray(queue.argsArray, argsArray);
-        self.insertDependencies(queue, dependencies);
-
-        queue.fnArray.push('paintFormXObjectEnd');
-        queue.argsArray.push([]);
-
-        if (group) {
-          queue.fnArray.push('endGroup');
-          queue.argsArray.push([groupOptions]);
-        }
+      operatorList.addOp('paintFormXObjectBegin', [matrix, bbox]);
 
-        promise.resolve({
-          queue: queue,
-          dependencies: dependencies
-        });
-      });
+      this.getOperatorList(xobj, xobj.dict.get('Resources') || resources,
+                           operatorList);
+      operatorList.addOp('paintFormXObjectEnd', []);
 
-      return promise;
+      if (group) {
+        operatorList.addOp('endGroup', [groupOptions]);
+      }
     },
 
     buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(
-                                resources, image, inline) {
+                                resources, image, inline, operatorList) {
       var self = this;
       var dict = image.dict;
       var w = dict.get('Width', 'W');
@@ -236,14 +241,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
       if (PDFJS.maxImageSize !== -1 && w * h > PDFJS.maxImageSize) {
         warn('Image exceeded maximum allowed size and was removed.');
-        return null;
+        return;
       }
 
-      var dependencies = {};
-      var retData = {
-        dependencies: dependencies
-      };
-
       var imageMask = dict.get('ImageMask', 'IM') || false;
       if (imageMask) {
         // This depends on a tmpCanvas beeing filled with the
@@ -259,10 +259,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         var decode = dict.get('Decode', 'D');
         var inverseDecode = !!decode && decode[0] > 0;
 
-        retData.fn = 'paintImageMaskXObject';
-        retData.args = [PDFImage.createMask(imgArray, width, height,
-                                            inverseDecode)];
-        return retData;
+        operatorList.addOp('paintImageMaskXObject',
+          [PDFImage.createMask(imgArray, width, height,
+                                            inverseDecode)]
+        );
+        return;
       }
 
       var softMask = dict.get('SMask', 'SM') || false;
@@ -276,64 +277,53 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         var imageObj = new PDFImage(this.xref, resources, image,
                                     inline, null, null);
         var imgData = imageObj.getImageData();
-        retData.fn = 'paintInlineImageXObject';
-        retData.args = [imgData];
-        return retData;
+        operatorList.addOp('paintInlineImageXObject', [imgData]);
+        return;
       }
 
       // If there is no imageMask, create the PDFImage and a lot
       // of image processing can be done here.
       var uniquePrefix = this.uniquePrefix || '';
       var objId = 'img_' + uniquePrefix + (++this.idCounters.obj);
-      dependencies[objId] = true;
-      retData.args = [objId, w, h];
+      operatorList.addDependency(objId);
+      var args = [objId, w, h];
 
       if (!softMask && !mask && image instanceof JpegStream &&
           image.isNativelySupported(this.xref, resources)) {
         // These JPEGs don't need any more processing so we can just send it.
-        retData.fn = 'paintJpegXObject';
+        operatorList.addOp('paintJpegXObject', args);
         this.handler.send(
             'obj', [objId, this.pageIndex, 'JpegStream', image.getIR()]);
-        return retData;
+        return;
       }
 
-      retData.fn = 'paintImageXObject';
 
       PDFImage.buildImage(function(imageObj) {
           var imgData = imageObj.getImageData();
           self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]);
         }, self.handler, self.xref, resources, image, inline);
 
-      return retData;
+      operatorList.addOp('paintImageXObject', args);
     },
 
     handleTilingType: function PartialEvaluator_handleTilingType(
-                          fn, args, resources, pattern, patternDict) {
-      var self = this;
+                          fn, args, resources, pattern, patternDict,
+                          operatorList) {
       // Create an IR of the pattern code.
-      var promise = new Promise();
-      var opListPromise = this.getOperatorList(pattern,
-          patternDict.get('Resources') || resources);
-      opListPromise.then(function(data) {
-        var opListData = createOperatorList([], [], data.dependencies);
-        var queue = opListData.queue;
-
-        // Add the dependencies that are required to execute the
-        // operatorList.
-        self.insertDependencies(queue, data.dependencies);
-        queue.fnArray.push(fn);
-        queue.argsArray.push(
-          TilingPattern.getIR(data.queue, patternDict, args));
-        promise.resolve(opListData);
-      });
-
-      return promise;
+      var tilingOpList = this.getOperatorList(pattern,
+                                  patternDict.get('Resources') || resources);
+      // Add the dependencies to the parent operator list so they are resolved
+      // before sub operator list is executed synchronously.
+      operatorList.addDependencies(tilingOpList.dependencies);
+      operatorList.addOp(fn, TilingPattern.getIR({
+                               fnArray: tilingOpList.fnArray,
+                               argsArray: tilingOpList.argsArray
+                              }, patternDict, args));
     },
 
     handleSetFont: function PartialEvaluator_handleSetFont(
-                      resources, fontArgs, font) {
+                      resources, fontArgs, fontRef, operatorList) {
 
-      var promise = new Promise();
       // TODO(mack): Not needed?
       var fontName;
       if (fontArgs) {
@@ -341,69 +331,27 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         fontName = fontArgs[0].name;
       }
       var self = this;
-      var fontPromise = this.loadFont(fontName, font, this.xref, resources);
-      fontPromise.then(function(data) {
-        var font = data.font;
-        var loadedName = font.loadedName;
-        if (!font.sent) {
-          var fontData = font.translated.exportData();
-
-          self.handler.send('commonobj', [
-            loadedName,
-            'Font',
-            fontData
-          ]);
-          font.sent = true;
-        }
-
-        // Ensure the font is ready before the font is set
-        // and later on used for drawing.
-        // OPTIMIZE: This should get insert to the operatorList only once per
-        // page.
-        var fnArray = [];
-        var argsArray = [];
-        var queue = {
-          fnArray: fnArray,
-          argsArray: argsArray
-        };
-        var dependencies = data.dependencies;
-        dependencies[loadedName] = true;
-        self.insertDependencies(queue, dependencies);
-        if (fontArgs) {
-          fontArgs[0] = loadedName;
-          fnArray.push('setFont');
-          argsArray.push(fontArgs);
-        }
-        promise.resolve({
-          loadedName: loadedName,
-          queue: queue,
-          dependencies: dependencies
-        });
-      });
-      return promise;
-    },
-
-    insertDependencies: function PartialEvaluator_insertDependencies(
-                            queue, dependencies) {
-
-      var fnArray = queue.fnArray;
-      var argsArray = queue.argsArray;
-      var depList = Object.keys(dependencies);
-      if (depList.length) {
-        fnArray.push('dependency');
-        argsArray.push(depList);
+      var font = this.loadFont(fontName, fontRef, this.xref, resources,
+                               operatorList);
+      var loadedName = font.loadedName;
+      if (!font.sent) {
+        var fontData = font.translated.exportData();
+
+        self.handler.send('commonobj', [
+          loadedName,
+          'Font',
+          fontData
+        ]);
+        font.sent = true;
       }
+
+      return loadedName;
     },
 
-    setGState: function PartialEvaluator_setGState(resources, gState) {
+    setGState: function PartialEvaluator_setGState(resources, gState,
+                                                   operatorList) {
 
       var self = this;
-      var opListData = createOperatorList();
-      var queue = opListData.queue;
-      var fnArray = queue.fnArray;
-      var argsArray = queue.argsArray;
-      var dependencies = opListData.dependencies;
-
       // TODO(mack): This should be rewritten so that this function returns
       // what should be added to the queue during each iteration
       function setGStateForKey(gStateObj, key, value) {
@@ -422,21 +370,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
             gStateObj.push([key, value]);
             break;
           case 'Font':
-            var promise = new Promise();
-            self.handleSetFont(resources, null, value[0]).then(function(data) {
-              var gState = ['Font', data.loadedName, value[1]];
-              promise.resolve({
-                gState: gState,
-                queue: data.queue,
-                dependencies: data.dependencies
-              });
-            });
-            gStateObj.push(['promise', promise]);
+            var loadedName = self.handleSetFont(resources, null, value[0],
+                                                operatorList);
+            operatorList.addDependency(loadedName);
+            gStateObj.push([key, [loadedName, value[1]]]);
             break;
           case 'BM':
-            if (!isName(value) || value.name !== 'Normal') {
-              queue.transparency = true;
-            }
             gStateObj.push([key, value]);
             break;
           case 'SMask':
@@ -477,47 +416,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         setGStateForKey(gStateObj, key, value);
       }
 
-      var promises = [];
-      var indices = [];
-      for (var i = 0, n = gStateObj.length; i < n; ++i) {
-        var value = gStateObj[i];
-        if (value[0] === 'promise') {
-          promises.push(value[1]);
-          indices.push(i);
-        }
-      }
-
-      var promise = new Promise();
-      Promise.all(promises).then(function(datas) {
-        for (var i = 0, n = datas.length; i < n; ++i) {
-          var data = datas[i];
-          var index = indices[i];
-          gStateObj[index] = data.gState;
-          var subQueue = data.queue;
-          Util.concatenateToArray(fnArray, subQueue.fnArray);
-          Util.concatenateToArray(argsArray, subQueue.argsArray);
-          queue.transparency = subQueue.transparency || queue.transparency;
-          Util.extendObj(dependencies, data.dependencies);
-        }
-        fnArray.push('setGState');
-        argsArray.push([gStateObj]);
-        promise.resolve(opListData);
-      });
-
-      return promise;
+      operatorList.addOp('setGState', [gStateObj]);
     },
 
     loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
-                                                 resources) {
-      function errorFont(promise) {
-        promise.resolve({
-          font: {
-            translated: new ErrorFont('Font ' + fontName + ' is not available'),
-            loadedName: 'g_font_error'
-          },
-          dependencies: {}
-        });
-        return promise;
+                                                 resources,
+                                                 parentOperatorList) {
+
+      function errorFont() {
+        return {
+          translated: new ErrorFont('Font ' + fontName + ' is not available'),
+          loadedName: 'g_font_error'
+        };
       }
 
       var fontRef;
@@ -530,20 +440,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
           fontRef = fontRes.getRaw(fontName);
         } else {
           warn('fontRes not available');
-          return errorFont(new Promise());
+          return errorFont();
         }
       }
       if (this.fontCache.has(fontRef)) {
         return this.fontCache.get(fontRef);
       }
 
-      var promise = new Promise();
-      this.fontCache.put(fontRef, promise);
 
       font = xref.fetchIfRef(fontRef);
       if (!isDict(font)) {
-        return errorFont(promise);
+        return errorFont();
       }
+      this.fontCache.put(fontRef, font);
 
       // keep track of each font we translated so the caller can
       // load them asynchronously before calling display on a page
@@ -562,55 +471,37 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
       if (font.translated.loadCharProcs) {
         var charProcs = font.get('CharProcs').getAll();
         var fontResources = font.get('Resources') || resources;
-        var opListPromises = [];
         var charProcKeys = Object.keys(charProcs);
+        var charProcOperatorList = {};
         for (var i = 0, n = charProcKeys.length; i < n; ++i) {
           var key = charProcKeys[i];
           var glyphStream = charProcs[key];
-          opListPromises.push(
-            this.getOperatorList(glyphStream, fontResources));
-        }
-        Promise.all(opListPromises).then(function(datas) {
-          var charProcOperatorList = {};
-          var dependencies = {};
-          for (var i = 0, n = charProcKeys.length; i < n; ++i) {
-            var key = charProcKeys[i];
-            var data = datas[i];
-            charProcOperatorList[key] = data.queue;
-            Util.extendObj(dependencies, data.dependencies);
+          var operatorList = this.getOperatorList(glyphStream, fontResources);
+          charProcOperatorList[key] = operatorList.getIR();
+          if (!parentOperatorList) {
+            continue;
           }
-          font.translated.charProcOperatorList = charProcOperatorList;
-          font.loaded = true;
-          promise.resolve({
-            font: font,
-            dependencies: dependencies
-          });
-        }.bind(this));
+          // Add the dependencies to the parent operator list so they are
+          // resolved before sub operator list is executed synchronously.
+          parentOperatorList.addDependencies(charProcOperatorList.dependencies);
+        }
+        font.translated.charProcOperatorList = charProcOperatorList;
+        font.loaded = true;
       } else {
         font.loaded = true;
-        promise.resolve({
-          font: font,
-          dependencies: {}
-        });
       }
-      return promise;
+      return font;
     },
 
     getOperatorList: function PartialEvaluator_getOperatorList(stream,
-                                                               resources) {
+                                                               resources,
+                                                               operatorList) {
 
       var self = this;
       var xref = this.xref;
       var handler = this.handler;
 
-      var fnArray = [];
-      var argsArray = [];
-      var queue = {
-        transparency: false,
-        fnArray: fnArray,
-        argsArray: argsArray
-      };
-      var dependencies = {};
+      operatorList = operatorList || new OperatorList();
 
       resources = resources || new Dict();
       var xobjs = resources.get('XObject') || new Dict();
@@ -621,6 +512,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
       var promise = new Promise();
       var args = [];
+      nextOp:
       while (true) {
 
         var obj = parser.getObj();
@@ -677,10 +569,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
               var typeNum = dict.get('PatternType');
 
               if (typeNum == TILING_PATTERN) {
-                var patternPromise = self.handleTilingType(
-                    fn, args, resources, pattern, dict);
-                fn = 'promise';
-                args = [patternPromise];
+                self.handleTilingType(fn, args, resources, pattern, dict,
+                                      operatorList);
+                args = [];
+                continue;
               } else if (typeNum == SHADING_PATTERN) {
                 var shading = dict.get('Shading');
                 var matrix = dict.get('Matrix');
@@ -706,37 +598,28 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
               );
 
               if ('Form' == type.name) {
-                fn = 'promise';
-                args = [self.buildFormXObject(resources, xobj)];
+                self.buildFormXObject(resources, xobj, null, operatorList);
+                args = [];
+                continue;
               } else if ('Image' == type.name) {
-                var data = self.buildPaintImageXObject(
-                    resources, xobj, false);
-                if (!data) {
-                  args = [];
-                  continue;
-                }
-                Util.extendObj(dependencies, data.dependencies);
-                self.insertDependencies(queue, data.dependencies);
-                fn = data.fn;
-                args = data.args;
+                self.buildPaintImageXObject(resources, xobj, false,
+                                            operatorList);
+                args = [];
+                continue;
               } else {
                 error('Unhandled XObject subtype ' + type.name);
               }
             }
           } else if (cmd == 'Tf') { // eagerly collect all fonts
-            fn = 'promise';
-            args = [self.handleSetFont(resources, args)];
+            var loadedName = self.handleSetFont(resources, args, null,
+                                                operatorList);
+            operatorList.addDependency(loadedName);
+            fn = 'setFont';
+            args[0] = loadedName;
           } else if (cmd == 'EI') {
-            var data = self.buildPaintImageXObject(
-                resources, args[0], true);
-            if (!data) {
-              args = [];
-              continue;
-            }
-            Util.extendObj(dependencies, data.dependencies);
-            self.insertDependencies(queue, data.dependencies);
-            fn = data.fn;
-            args = data.args;
+            self.buildPaintImageXObject(resources, args[0], true, operatorList);
+            args = [];
+            continue;
           }
 
           switch (fn) {
@@ -768,12 +651,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
                 break;
 
               var gState = extGState.get(dictName.name);
-              fn = 'promise';
-              args = [self.setGState(resources, gState)];
+              self.setGState(resources, gState, operatorList);
+              args = [];
+              continue nextOp;
           } // switch
 
-          fnArray.push(fn);
-          argsArray.push(args);
+          operatorList.addOp(fn, args);
           args = [];
           parser.saveState();
         } else if (obj !== null && obj !== undefined) {
@@ -782,163 +665,86 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
         }
       }
 
-      var subQueuePromises = [];
-      for (var i = 0; i < fnArray.length; ++i) {
-        if (fnArray[i] === 'promise') {
-          subQueuePromises.push(argsArray[i][0]);
-        }
-      }
-      Promise.all(subQueuePromises).then(function(datas) {
-        // TODO(mack): Optimize by using repositioning elements
-        // in original queue rather than creating new queue
-
-        for (var i = 0, n = datas.length; i < n; ++i) {
-          var data = datas[i];
-          var subQueue = data.queue;
-          queue.transparency = subQueue.transparency || queue.transparency;
-          Util.extendObj(dependencies, data.dependencies);
-        }
-
-        var newFnArray = [];
-        var newArgsArray = [];
-        var currOffset = 0;
-        var subQueueIdx = 0;
-        for (var i = 0, n = fnArray.length; i < n; ++i) {
-          var offset = i + currOffset;
-          if (fnArray[i] === 'promise') {
-            var data = datas[subQueueIdx++];
-            var subQueue = data.queue;
-            var subQueueFnArray = subQueue.fnArray;
-            var subQueueArgsArray = subQueue.argsArray;
-            for (var j = 0, nn = subQueueFnArray.length; j < nn; ++j) {
-              newFnArray[offset + j] = subQueueFnArray[j];
-              newArgsArray[offset + j] = subQueueArgsArray[j];
-            }
-            currOffset += subQueueFnArray.length - 1;
-          } else {
-            newFnArray[offset] = fnArray[i];
-            newArgsArray[offset] = argsArray[i];
-          }
-        }
-
-        promise.resolve({
-          queue: {
-            fnArray: newFnArray,
-            argsArray: newArgsArray,
-            transparency: queue.transparency
-          },
-          dependencies: dependencies
-        });
-      });
-
-      return promise;
+      return operatorList;
     },
 
     getTextContent: function PartialEvaluator_getTextContent(
-                                                    stream, resources) {
+                                                    stream, resources, state) {
 
+      var bidiTexts;
       var SPACE_FACTOR = 0.35;
       var MULTI_SPACE_FACTOR = 1.5;
-      var self = this;
 
-      var statePromise = new Promise();
-
-      function handleSetFont(fontName, fontRef, resources) {
-        var promise = new Promise();
-        self.loadFont(fontName, fontRef, self.xref, resources).then(
-          function(data) {
-            promise.resolve(data.font.translated);
-          }
-        );
-        return promise;
+      if (!state) {
+        bidiTexts = [];
+        state = {
+          bidiTexts: bidiTexts
+        };
+      } else {
+        bidiTexts = state.bidiTexts;
       }
 
-      function getBidiText(str, startLevel, vertical) {
-        if (str) {
-          return PDFJS.bidi(str, -1, vertical);
-        }
+      var self = this;
+      var xref = this.xref;
+
+      function handleSetFont(fontName, fontRef) {
+        return self.loadFont(fontName, fontRef, xref, resources, null);
       }
 
-      resources = this.xref.fetchIfRef(resources) || new Dict();
+      resources = xref.fetchIfRef(resources) || new Dict();
       // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
       var xobjs = null;
 
       var parser = new Parser(new Lexer(stream), false);
+      var res = resources;
+      var args = [], obj;
 
-      var chunkPromises = [];
-      var fontPromise;
-      var args = [];
-
-      while (true) {
-        var obj = parser.getObj();
-        if (isEOF(obj)) {
-          break;
-        }
-
+      var chunk = '';
+      var font = null;
+      while (!isEOF(obj = parser.getObj())) {
         if (isCmd(obj)) {
           var cmd = obj.cmd;
           switch (cmd) {
             // TODO: Add support for SAVE/RESTORE and XFORM here.
             case 'Tf':
-              fontPromise = handleSetFont(args[0].name, null, resources);
-              //.translated;
+              font = handleSetFont(args[0].name).translated;
               break;
             case 'TJ':
-              var chunkPromise = new Promise();
-              chunkPromises.push(chunkPromise);
-              fontPromise.then(function(items, chunkPromise, font) {
-                var chunk = '';
-                for (var j = 0, jj = items.length; j < jj; j++) {
-                  if (typeof items[j] === 'string') {
-                    chunk += fontCharsToUnicode(items[j], font);
-                  } else if (items[j] < 0 && font.spaceWidth > 0) {
-                    var fakeSpaces = -items[j] / font.spaceWidth;
-                    if (fakeSpaces > MULTI_SPACE_FACTOR) {
-                      fakeSpaces = Math.round(fakeSpaces);
-                      while (fakeSpaces--) {
-                        chunk += ' ';
-                      }
-                    } else if (fakeSpaces > SPACE_FACTOR) {
+              var items = args[0];
+              for (var j = 0, jj = items.length; j < jj; j++) {
+                if (typeof items[j] === 'string') {
+                  chunk += fontCharsToUnicode(items[j], font);
+                } else if (items[j] < 0 && font.spaceWidth > 0) {
+                  var fakeSpaces = -items[j] / font.spaceWidth;
+                  if (fakeSpaces > MULTI_SPACE_FACTOR) {
+                    fakeSpaces = Math.round(fakeSpaces);
+                    while (fakeSpaces--) {
                       chunk += ' ';
                     }
+                  } else if (fakeSpaces > SPACE_FACTOR) {
+                    chunk += ' ';
                   }
                 }
-                chunkPromise.resolve(
-                    getBidiText(chunk, -1, font.vertical));
-              }.bind(null, args[0], chunkPromise));
+              }
               break;
             case 'Tj':
-              var chunkPromise = new Promise();
-              chunkPromises.push(chunkPromise);
-              fontPromise.then(function(charCodes, chunkPromise, font) {
-                var chunk = fontCharsToUnicode(charCodes, font);
-                chunkPromise.resolve(
-                    getBidiText(chunk, -1, font.vertical));
-              }.bind(null, args[0], chunkPromise));
+              chunk += fontCharsToUnicode(args[0], font);
               break;
             case '\'':
-              // For search, adding a extra white space for line breaks
-              // would be better here, but that causes too much spaces in
-              // the text-selection divs.
-              var chunkPromise = new Promise();
-              chunkPromises.push(chunkPromise);
-              fontPromise.then(function(charCodes, chunkPromise, font) {
-                var chunk = fontCharsToUnicode(charCodes, font);
-                chunkPromise.resolve(
-                    getBidiText(chunk, -1, font.vertical));
-              }.bind(null, args[0], chunkPromise));
+              // For search, adding a extra white space for line breaks would be
+              // better here, but that causes too much spaces in the
+              // text-selection divs.
+              chunk += fontCharsToUnicode(args[0], font);
               break;
             case '"':
               // Note comment in "'"
-              var chunkPromise = new Promise();
-              chunkPromises.push(chunkPromise);
-              fontPromise.then(function(charCodes, chunkPromise, font) {
-                var chunk = fontCharsToUnicode(charCodes, font);
-                chunkPromise.resolve(
-                    getBidiText(chunk, -1, font.vertical));
-              }.bind(null, args[2], chunkPromise));
+              chunk += fontCharsToUnicode(args[2], font);
               break;
             case 'Do':
+              // Set the chunk such that the following if won't add something
+              // to the state.
+              chunk = '';
+
               if (args[0].code) {
                 break;
               }
@@ -951,8 +757,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
               var xobj = xobjs.get(name);
               if (!xobj)
                 break;
-              assertWellFormed(isStream(xobj),
-                               'XObject should be a stream');
+              assertWellFormed(isStream(xobj), 'XObject should be a stream');
 
               var type = xobj.dict.get('Subtype');
               assertWellFormed(
@@ -963,11 +768,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
               if ('Form' !== type.name)
                 break;
 
-              var chunkPromise = self.getTextContent(
+              state = this.getTextContent(
                 xobj,
-                xobj.dict.get('Resources') || resources
+                xobj.dict.get('Resources') || resources,
+                state
               );
-              chunkPromises.push(chunkPromise);
               break;
             case 'gs':
               var dictName = args[0];
@@ -980,37 +785,27 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
 
               for (var i = 0; i < gsState.length; i++) {
                 if (gsState[i] === 'Font') {
-                  fontPromise = handleSetFont(
-                      args[0].name, null, resources);
+                  font = handleSetFont(args[0].name).translated;
                 }
               }
               break;
           } // switch
 
+          if (chunk !== '') {
+            var bidiText = PDFJS.bidi(chunk, -1, font.vertical);
+            bidiTexts.push(bidiText);
+
+            chunk = '';
+          }
+
           args = [];
-          parser.saveState();
         } else if (obj !== null && obj !== undefined) {
           assertWellFormed(args.length <= 33, 'Too many arguments');
           args.push(obj);
         }
       } // while
 
-      Promise.all(chunkPromises).then(function(datas) {
-        var bidiTexts = [];
-        for (var i = 0, n = datas.length; i < n; ++i) {
-          var bidiText = datas[i];
-          if (!bidiText) {
-            continue;
-          } else if (isArray(bidiText)) {
-            Util.concatenateToArray(bidiTexts, bidiText);
-          } else {
-            bidiTexts.push(bidiText);
-          }
-        }
-        statePromise.resolve(bidiTexts);
-      });
-
-      return statePromise;
+      return state;
     },
 
     extractDataStructures: function
@@ -1639,6 +1434,74 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
   return PartialEvaluator;
 })();
 
+
+var OperatorList = (function OperatorListClosure() {
+  var CHUNK_SIZE = 100;
+
+  function OperatorList(messageHandler, pageIndex) {
+    this.messageHandler = messageHandler;
+    this.fnArray = [];
+    this.argsArray = [];
+    this.dependencies = {},
+    this.pageIndex = pageIndex;
+  }
+
+  OperatorList.prototype = {
+
+    addOp: function(fn, args) {
+      this.fnArray.push(fn);
+      this.argsArray.push(args);
+      if (this.messageHandler && this.fnArray.length >= CHUNK_SIZE) {
+        this.flush();
+      }
+    },
+
+    addDependency: function(dependency) {
+      if (dependency in this.dependencies) {
+        return;
+      }
+      this.dependencies[dependency] = true;
+      this.addOp('dependency', [dependency]);
+    },
+
+    addDependencies: function(dependencies) {
+      for (var key in dependencies) {
+        this.addDependency(key);
+      }
+    },
+
+    addOpList: function(opList) {
+      Util.concatenateToArray(this.fnArray, opList.fnArray);
+      Util.concatenateToArray(this.argsArray, opList.argsArray);
+      Util.extendObj(this.dependencies, opList.dependencies);
+    },
+
+    getIR: function() {
+      return {
+        fnArray: this.fnArray,
+        argsArray: this.argsArray
+      };
+    },
+
+    flush: function(lastChunk) {
+      PartialEvaluator.optimizeQueue(this);
+      this.messageHandler.send('RenderPageChunk', {
+        operatorList: {
+          fnArray: this.fnArray,
+          argsArray: this.argsArray,
+          lastChunk: lastChunk
+        },
+        pageIndex: this.pageIndex
+      });
+      this.dependencies = [];
+      this.fnArray = [];
+      this.argsArray = [];
+    }
+  };
+
+  return OperatorList;
+})();
+
 var EvalState = (function EvalStateClosure() {
   function EvalState() {
     // Are soft masks and alpha values shapes or opacities?
diff --git a/src/util.js b/src/util.js
index 8cee4a1eb..a1d7e2b98 100644
--- a/src/util.js
+++ b/src/util.js
@@ -903,14 +903,14 @@ var StatTimer = (function StatTimerClosure() {
       if (!this.enabled)
         return;
       if (name in this.started)
-        throw 'Timer is already running for ' + name;
+        warn('Timer is already running for ' + name);
       this.started[name] = Date.now();
     },
     timeEnd: function StatTimer_timeEnd(name) {
       if (!this.enabled)
         return;
       if (!(name in this.started))
-        throw 'Timer has not been started for ' + name;
+        warn('Timer has not been started for ' + name);
       this.times.push({
         'name': name,
         'start': this.started[name],
diff --git a/src/worker.js b/src/worker.js
index 9b12a2ff1..72839d740 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -398,31 +398,11 @@ var WorkerMessageHandler = {
         var pageNum = data.pageIndex + 1;
         var start = Date.now();
         // Pre compile the pdf page and fetch the fonts/images.
-        page.getOperatorList(handler).then(function(opListData) {
-
-          var operatorList = opListData.queue;
-          var dependency = Object.keys(opListData.dependencies);
-
-          // The following code does quite the same as
-          // Page.prototype.startRendering, but stops at one point and sends the
-          // result back to the main thread.
+        page.getOperatorList(handler).then(function(operatorList) {
 
           log('page=%d - getOperatorList: time=%dms, len=%d', pageNum,
               Date.now() - start, operatorList.fnArray.length);
 
-          // Filter the dependecies for fonts.
-          var fonts = {};
-          for (var i = 0, ii = dependency.length; i < ii; i++) {
-            var dep = dependency[i];
-            if (dep.indexOf('g_font_') === 0) {
-              fonts[dep] = true;
-            }
-          }
-          handler.send('RenderPage', {
-            pageIndex: data.pageIndex,
-            operatorList: operatorList,
-            depFonts: Object.keys(fonts)
-          });
         }, function(e) {
 
           var minimumStackMessage =
diff --git a/test/unit/evaluator_spec.js b/test/unit/evaluator_spec.js
index 5d558fca4..fd023fc4d 100644
--- a/test/unit/evaluator_spec.js
+++ b/test/unit/evaluator_spec.js
@@ -36,14 +36,11 @@ describe('evaluator', function() {
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('qTT');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(1);
-        expect(result.fnArray[0]).toEqual('save');
-        expect(result.argsArray[0].length).toEqual(0);
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(1);
+      expect(result.fnArray[0]).toEqual('save');
+      expect(result.argsArray[0].length).toEqual(0);
     });
 
     it('should handle one operations', function() {
@@ -51,13 +48,10 @@ describe('evaluator', function() {
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('Q');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(1);
-        expect(result.fnArray[0]).toEqual('restore');
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(1);
+      expect(result.fnArray[0]).toEqual('restore');
     });
 
     it('should handle two glued operations', function() {
@@ -67,14 +61,11 @@ describe('evaluator', function() {
       var resources = new ResourcesMock();
       resources.Res1 = {};
       var stream = new StringStream('/Res1 DoQ');
-      var promise = evaluator.getOperatorList(stream, resources);
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(2);
-        expect(result.fnArray[0]).toEqual('paintXObject');
-        expect(result.fnArray[1]).toEqual('restore');
-      });
+      var result = evaluator.getOperatorList(stream, resources);
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(2);
+      expect(result.fnArray[0]).toEqual('paintXObject');
+      expect(result.fnArray[1]).toEqual('restore');
     });
 
     it('should handle tree glued operations', function() {
@@ -82,15 +73,12 @@ describe('evaluator', function() {
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('qqq');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(3);
-        expect(result.fnArray[0]).toEqual('save');
-        expect(result.fnArray[1]).toEqual('save');
-        expect(result.fnArray[2]).toEqual('save');
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(3);
+      expect(result.fnArray[0]).toEqual('save');
+      expect(result.fnArray[1]).toEqual('save');
+      expect(result.fnArray[2]).toEqual('save');
     });
 
     it('should handle three glued operations #2', function() {
@@ -100,15 +88,12 @@ describe('evaluator', function() {
       var resources = new ResourcesMock();
       resources.Res1 = {};
       var stream = new StringStream('B*Bf*');
-      var promise = evaluator.getOperatorList(stream, resources);
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(3);
-        expect(result.fnArray[0]).toEqual('eoFillStroke');
-        expect(result.fnArray[1]).toEqual('fillStroke');
-        expect(result.fnArray[2]).toEqual('eoFill');
-      });
+      var result = evaluator.getOperatorList(stream, resources);
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(3);
+      expect(result.fnArray[0]).toEqual('eoFillStroke');
+      expect(result.fnArray[1]).toEqual('fillStroke');
+      expect(result.fnArray[2]).toEqual('eoFill');
     });
 
     it('should handle glued operations and operands', function() {
@@ -116,17 +101,14 @@ describe('evaluator', function() {
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('q5 Ts');
-      var promise  = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(2);
-        expect(result.fnArray[0]).toEqual('save');
-        expect(result.fnArray[1]).toEqual('setTextRise');
-        expect(result.argsArray.length).toEqual(2);
-        expect(result.argsArray[1].length).toEqual(1);
-        expect(result.argsArray[1][0]).toEqual(5);
-      });
+      var result  = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(2);
+      expect(result.fnArray[0]).toEqual('save');
+      expect(result.fnArray[1]).toEqual('setTextRise');
+      expect(result.argsArray.length).toEqual(2);
+      expect(result.argsArray[1].length).toEqual(1);
+      expect(result.argsArray[1][0]).toEqual(5);
     });
 
     it('should handle glued operations and literals', function() {
@@ -134,21 +116,18 @@ describe('evaluator', function() {
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('trueifalserinullq');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(!!result.fnArray && !!result.argsArray).toEqual(true);
-        expect(result.fnArray.length).toEqual(3);
-        expect(result.fnArray[0]).toEqual('setFlatness');
-        expect(result.fnArray[1]).toEqual('setRenderingIntent');
-        expect(result.fnArray[2]).toEqual('save');
-        expect(result.argsArray.length).toEqual(3);
-        expect(result.argsArray[0].length).toEqual(1);
-        expect(result.argsArray[0][0]).toEqual(true);
-        expect(result.argsArray[1].length).toEqual(1);
-        expect(result.argsArray[1][0]).toEqual(false);
-        expect(result.argsArray[2].length).toEqual(0);
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+      expect(result.fnArray.length).toEqual(3);
+      expect(result.fnArray[0]).toEqual('setFlatness');
+      expect(result.fnArray[1]).toEqual('setRenderingIntent');
+      expect(result.fnArray[2]).toEqual('save');
+      expect(result.argsArray.length).toEqual(3);
+      expect(result.argsArray[0].length).toEqual(1);
+      expect(result.argsArray[0][0]).toEqual(true);
+      expect(result.argsArray[1].length).toEqual(1);
+      expect(result.argsArray[1][0]).toEqual(false);
+      expect(result.argsArray[2].length).toEqual(0);
     });
   });
 
@@ -159,39 +138,30 @@ describe('evaluator', function() {
                                            'prefix');
       var stream = new StringStream('5 1 d0');
       console.log('here!');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(result.argsArray[0][0]).toEqual(5);
-        expect(result.argsArray[0][1]).toEqual(1);
-        expect(result.fnArray[0]).toEqual('setCharWidth');
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(result.argsArray[0][0]).toEqual(5);
+      expect(result.argsArray[0][1]).toEqual(1);
+      expect(result.fnArray[0]).toEqual('setCharWidth');
     });
     it('should execute if too many arguments', function() {
       var evaluator = new PartialEvaluator(new PdfManagerMock(),
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('5 1 4 d0');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(result.argsArray[0][0]).toEqual(5);
-        expect(result.argsArray[0][1]).toEqual(1);
-        expect(result.argsArray[0][2]).toEqual(4);
-        expect(result.fnArray[0]).toEqual('setCharWidth');
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(result.argsArray[0][0]).toEqual(5);
+      expect(result.argsArray[0][1]).toEqual(1);
+      expect(result.argsArray[0][2]).toEqual(4);
+      expect(result.fnArray[0]).toEqual('setCharWidth');
     });
     it('should skip if too few arguments', function() {
       var evaluator = new PartialEvaluator(new PdfManagerMock(),
                                            new XrefMock(), new HandlerMock(),
                                            'prefix');
       var stream = new StringStream('5 d0');
-      var promise = evaluator.getOperatorList(stream, new ResourcesMock());
-      promise.then(function(data) {
-        var result = data.queue;
-        expect(result.argsArray).toEqual([]);
-        expect(result.fnArray).toEqual([]);
-      });
+      var result = evaluator.getOperatorList(stream, new ResourcesMock());
+      expect(result.argsArray).toEqual([]);
+      expect(result.fnArray).toEqual([]);
     });
   });
 });
diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html
index 26ffbeba5..1d22f517b 100644
--- a/test/unit/unit_test.html
+++ b/test/unit/unit_test.html
@@ -15,8 +15,8 @@
   <script type="text/javascript" src="../../src/chunked_stream.js"></script>
   <script type="text/javascript" src="../../src/pdf_manager.js"></script>
   <script type="text/javascript" src="../../src/core.js"></script>
-  <script type="text/javascript" src="../../src/api.js"></script>
   <script type="text/javascript" src="../../src/util.js"></script>
+  <script type="text/javascript" src="../../src/api.js"></script>
   <script type="text/javascript" src="../../src/canvas.js"></script>
   <script type="text/javascript" src="../../src/obj.js"></script>
   <script type="text/javascript" src="../../src/annotation.js"></script>
diff --git a/web/debugger.js b/web/debugger.js
index 2305bb773..ceefd19e1 100644
--- a/web/debugger.js
+++ b/web/debugger.js
@@ -220,26 +220,26 @@ var StepperManager = (function StepperManagerClosure() {
 
 // The stepper for each page's IRQueue.
 var Stepper = (function StepperClosure() {
+  // Shorter way to create element and optionally set textContent.
+  function c(tag, textContent) {
+    var d = document.createElement(tag);
+    if (textContent)
+      d.textContent = textContent;
+    return d;
+  }
+
   function Stepper(panel, pageIndex, initialBreakPoints) {
     this.panel = panel;
-    this.len = 0;
     this.breakPoint = 0;
     this.nextBreakPoint = null;
     this.pageIndex = pageIndex;
     this.breakPoints = initialBreakPoints;
     this.currentIdx = -1;
+    this.operatorListIdx = 0;
   }
   Stepper.prototype = {
-    init: function init(IRQueue) {
-      // Shorter way to create element and optionally set textContent.
-      function c(tag, textContent) {
-        var d = document.createElement(tag);
-        if (textContent)
-          d.textContent = textContent;
-        return d;
-      }
+    init: function init() {
       var panel = this.panel;
-      this.len = IRQueue.fnArray.length;
       var content = c('div', 'c=continue, s=step');
       var table = c('table');
       content.appendChild(table);
@@ -250,15 +250,18 @@ var Stepper = (function StepperClosure() {
       headerRow.appendChild(c('th', 'Idx'));
       headerRow.appendChild(c('th', 'fn'));
       headerRow.appendChild(c('th', 'args'));
-
+      panel.appendChild(content);
+      this.table = table;
+    },
+    updateOperatorList: function updateOperatorList(operatorList) {
       var self = this;
-      for (var i = 0; i < IRQueue.fnArray.length; i++) {
+      for (var i = this.operatorListIdx; i < operatorList.fnArray.length; i++) {
         var line = c('tr');
         line.className = 'line';
         line.dataset.idx = i;
-        table.appendChild(line);
+        this.table.appendChild(line);
         var checked = this.breakPoints.indexOf(i) != -1;
-        var args = IRQueue.argsArray[i] ? IRQueue.argsArray[i] : [];
+        var args = operatorList.argsArray[i] ? operatorList.argsArray[i] : [];
 
         var breakCell = c('td');
         var cbox = c('input');
@@ -278,11 +281,9 @@ var Stepper = (function StepperClosure() {
         breakCell.appendChild(cbox);
         line.appendChild(breakCell);
         line.appendChild(c('td', i.toString()));
-        line.appendChild(c('td', IRQueue.fnArray[i]));
+        line.appendChild(c('td', operatorList.fnArray[i]));
         line.appendChild(c('td', args.join(', ')));
       }
-      panel.appendChild(content);
-      var self = this;
     },
     getNextBreakPoint: function getNextBreakPoint() {
       this.breakPoints.sort(function(a, b) { return a - b; });
diff --git a/web/viewer.js b/web/viewer.js
index fd10930eb..fb6b3aecd 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -1553,8 +1553,12 @@ var PageView = function pageView(container, id, scale,
   };
 
   this.update = function pageViewUpdate(scale, rotation) {
-    this.renderingState = RenderingStates.INITIAL;
+    if (this.renderTask) {
+      this.renderTask.cancel();
+      this.renderTask = null;
+    }
     this.resume = null;
+    this.renderingState = RenderingStates.INITIAL;
 
     if (typeof rotation !== 'undefined') {
       this.rotation = rotation;
@@ -1818,13 +1822,9 @@ var PageView = function pageView(container, id, scale,
     // Rendering area
 
     var self = this;
-    var renderingWasReset = false;
     function pageViewDrawCallback(error) {
-      if (renderingWasReset) {
-        return;
-      }
-
       self.renderingState = RenderingStates.FINISHED;
+      self.renderTask = null;
 
       if (self.loadingIconDiv) {
         div.removeChild(self.loadingIconDiv);
@@ -1874,12 +1874,6 @@ var PageView = function pageView(container, id, scale,
       viewport: this.viewport,
       textLayer: textLayer,
       continueCallback: function pdfViewcContinueCallback(cont) {
-        if (self.renderingState === RenderingStates.INITIAL) {
-          // The page update() was called, we just need to abort any rendering.
-          renderingWasReset = true;
-          return;
-        }
-
         if (PDFView.highestPriorityPage !== 'page' + self.id) {
           self.renderingState = RenderingStates.PAUSED;
           self.resume = function resumeCallback() {
@@ -1891,7 +1885,9 @@ var PageView = function pageView(container, id, scale,
         cont();
       }
     };
-    this.pdfPage.render(renderContext).then(
+    this.renderTask = this.pdfPage.render(renderContext);
+
+    this.renderTask.then(
       function pdfPageRenderCallback() {
         pageViewDrawCallback(null);
       },