]> git.parisson.com Git - pdf.js.git/commitdiff
Merge worker_pull with master
authorJulian Viereck <julian.viereck@gmail.com>
Sat, 8 Oct 2011 12:18:23 +0000 (14:18 +0200)
committerJulian Viereck <julian.viereck@gmail.com>
Sat, 8 Oct 2011 12:18:23 +0000 (14:18 +0200)
1  2 
fonts.js
pdf.js
test/driver.js
test/test_slave.html
web/viewer.html
web/viewer.js
worker.js
worker/console.js

diff --cc fonts.js
index 974c8e79a5a08dfecfac06d7ed2d591b77fedbfa,2ab3a90b76eee461e938d5b55ffb41994d6fa752..371a47239081cb0f0b5b31f84e642ab4b1778afb
mode 100755,100644..100644
+++ b/fonts.js
@@@ -185,43 -125,7 +190,43 @@@ if (!isWorker) 
  var FontLoader = {
    listeningForFontLoad: false,
  
 -  bind: function fontLoaderBind(fonts, callback) {
 +  /**
 +   * Attach the fontObj to the DOM.
 +   */
 +  bindDOM: function font_bindDom(fontObj) {
 +    // The browser isn't loading a font until it's used on the page. Attaching
 +    // a hidden div that uses the font 'tells' the browser to load the font.
 +    var div = document.createElement('div');
 +    div.setAttribute('style',
 +                     'visibility: hidden;' +
 +                     'width: 10px; height: 10px;' +
 +                     'position: absolute; top: 0px; left: 0px;' +
 +                     'font-family: ' + fontObj.loadedName);
 +    div.innerHTML = "Hi";
 +    document.body.appendChild(div);
 +    
 +    // Add the font-face rule to the document
 +    var fontName = fontObj.loadedName;
 +    var url = ('url(data:' + fontObj.mimetype + ';base64,' +
 +                 window.btoa(fontObj.str) + ');');
 +    var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
 +    var styleSheet = document.styleSheets[0];
 +    styleSheet.insertRule(rule, styleSheet.cssRules.length);
 +    return rule;
 +  },
 +
 +  bind: function fontLoaderBind(fonts, callback, objects) {
 +    var fontsToLoad = {};
 +    // check if there are twice the same font.
 +    for (var i = 0; i < fonts.length; i++) {
 +      var fontName = fonts[i].loadedName;
 +      if (fontsToLoad[fontName]) {
 +        throw "Got twice the same font!";
 +      } else {
 +        fontsToLoad[fontName] = true;
 +      }
 +    }
-     
++
      function checkFontsLoaded() {
        for (var i = 0; i < objs.length; i++) {
          var fontObj = objs[i];
@@@ -611,8 -415,8 +616,12 @@@ var Font = (function Font() 
    var constructor = function font_constructor(name, file, properties) {
      this.name = name;
      this.encoding = properties.encoding;
-     this.glyphs = properties.glyphs;
++    
++    // Glyhps are no needed anymore? MERGE
++    // this.glyphs = properties.glyphs;
 +    this.loadedName = properties.loadedName;
+     this.coded = properties.coded;
+     this.resources = properties.resources;
      this.sizes = [];
  
      var names = name.split('+');
        // name ArialBlack for example will be replaced by Helvetica.
        this.black = (name.search(/Black/g) != -1);
  
 -      this.loadedName = fontName.split('-')[0];
+       this.defaultWidth = properties.defaultWidth;
++      // MERGE
++      // this.loadedName = fontName.split('-')[0];
+       this.composite = properties.composite;
        this.loading = false;
        return;
      }
      }
  
      this.data = data;
-     this.type = properties.type;
-     this.textMatrix = properties.textMatrix;
++    // MERGE
++    //this.textMatrix = properties.textMatrix;
+     this.type = type;
+     this.fontMatrix = properties.fontMatrix;
+     this.defaultWidth = properties.defaultWidth;
 -    this.loadedName = getUniqueName();
++    this.loadedName = properties.loadedName;
      this.composite = properties.composite;
 +    
 +    // TODO: Remove this once we can be sure nothing got broken to du changes
 +    // in this commit.
 +    if (!this.loadedName) {
 +      throw "There has to be a `loadedName`";
 +    }
 +
      this.loading = true;
    };
  
        }
  
        return stringToArray(otf.file);
 -    bindWorker: function font_bindWorker(data) {
 -      postMessage({
 -        action: 'font',
 -        data: {
 -          raw: data,
 -          fontName: this.loadedName,
 -          mimetype: this.mimetype
 -        }
 -      });
 -    },
 -
 -    bindDOM: function font_bindDom(data) {
 -      var fontName = this.loadedName;
 -
 -      // Add the font-face rule to the document
 -      var url = ('url(data:' + this.mimetype + ';base64,' +
 -                 window.btoa(data) + ');');
 -      var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
 -      var styleSheet = document.styleSheets[0];
 -      if (!styleSheet) {
 -        document.documentElement.firstChild.appendChild(
 -          document.createElement('style'));
 -        styleSheet = document.styleSheets[0];
 -      }
 -      styleSheet.insertRule(rule, styleSheet.cssRules.length);
 -
 -      return rule;
 -    },
 -
+     },
+     fixWidths: function font_fixWidths(properties) {
+       if (properties.type !== 'CIDFontType0' &&
+           properties.type !== 'CIDFontType2')
+           return;
+       var encoding = properties.encoding;
+       if (encoding[0])
+         return;
+       var glyphsWidths = properties.widths;
+       if (!glyphsWidths)
+         return;
+       var defaultWidth = properties.defaultWidth;
+       var cidSystemInfo = properties.cidSystemInfo;
+       var cidToUnicode;
+       if (cidSystemInfo) {
+         cidToUnicode = CIDToUnicodeMaps[
+           cidSystemInfo.registry + '-' + cidSystemInfo.ordering];
+       }
+       if (!cidToUnicode) {
+         // the font is directly characters to glyphs with no encoding
+         // so create an identity encoding
+         for (i = 0; i < 0xD800; i++) {
+           var width = glyphsWidths[i];
+           encoding[i] = {
+             unicode: i,
+             width: isNum(width) ? width : defaultWidth
+           };
+         }
+         // skipping surrogates + 256-user defined
+         for (i = 0xE100; i <= 0xFFFF; i++) {
+           var width = glyphsWidths[i];
+           encoding[i] = {
+             unicode: i,
+             width: isNum(width) ? width : defaultWidth
+           };
+         }
+         return;
+       }
+       encoding[0] = { unicode: 0, width: 0 };
+       var glyph = 1, i, j, k;
+       for (i = 0; i < cidToUnicode.length; ++i) {
+         var unicode = cidToUnicode[i];
+         var width;
+         if (isArray(unicode)) {
+           var length = unicode.length;
+           width = glyphsWidths[glyph];
+           for (j = 0; j < length; j++) {
+             k = unicode[j];
+             encoding[k] = {
+               unicode: k,
+               width: isNum(width) ? width : defaultWidth
+             };
+           }
+           glyph++;
+         } else if (typeof unicode === 'object') {
+           var fillLength = unicode.f;
+           if (fillLength) {
+             k = unicode.c;
+             for (j = 0; j < fillLength; ++j) {
+               width = glyphsWidths[glyph++];
+               encoding[k] = {
+                 unicode: k,
+                 width: isNum(width) ? width : defaultWidth
+               };
+               k++;
+             }
+           } else
+             glyph += unicode.s;
+         } else if (unicode) {
+           width = glyphsWidths[glyph++];
+           encoding[unicode] = {
+             unicode: unicode,
+             width: isNum(width) ? width : defaultWidth
+           };
+         } else
+           glyph++;
+       }
+     },
+     charsToGlyphs: function fonts_chars2Glyphs(chars) {
+       var charsCache = this.charsCache;
+       var glyphs;
+       // if we translated this string before, just grab it from the cache
+       if (charsCache) {
+         glyphs = charsCache[chars];
+         if (glyphs)
+           return glyphs;
+       }
+       // lazily create the translation cache
+       if (!charsCache)
+         charsCache = this.charsCache = Object.create(null);
+       // translate the string using the font's encoding
+       var encoding = this.encoding;
+       if (!encoding)
+         return chars;
+       glyphs = [];
+       if (this.composite) {
+         // 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
+         // correct byte encoding according to the PDF spec
+         var length = chars.length - 1; // looping over two bytes at a time so
+                                        // loop should never end on the last byte
+         for (var i = 0; i < length; i++) {
+           var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]);
+           var glyph = encoding[charcode];
+           if ('undefined' == typeof(glyph)) {
+             warn('Unencoded charcode ' + charcode);
+             glyph = {
+               unicode: charcode,
+               width: this.defaultWidth
+             };
+           }
+           glyphs.push(glyph);
+           // placing null after each word break charcode (ASCII SPACE)
+           if (charcode == 0x20)
+             glyphs.push(null);
+         }
+       }
+       else {
+         for (var i = 0; i < chars.length; ++i) {
+           var charcode = chars.charCodeAt(i);
+           var glyph = encoding[charcode];
+           if ('undefined' == typeof(glyph)) {
+             warn('Unencoded charcode ' + charcode);
+             glyph = {
+               unicode: charcode,
+               width: this.defaultWidth
+             };
+           }
+           glyphs.push(glyph);
+           if (charcode == 0x20)
+             glyphs.push(null);
+         }
+       }
+       // Enter the translated string into the cache
+       return (charsCache[chars] = glyphs);
      }
    };
  
diff --cc pdf.js
index d7088e67c3a86de059970c37030064d70005b6f0,e804126fe3617b108188f35ea794c249ce419423..afb2ceaa3d451dc30836dcfbb29ff48f87976624
--- 1/pdf.js
--- 2/pdf.js
+++ b/pdf.js
@@@ -859,39 -895,12 +895,39 @@@ var PredictorStream = (function predict
    return constructor;
  })();
  
 +var JpegStreamIR = (function() {
 +  function JpegStreamIR(objId, IR, objs) {
 +    var src = 'data:image/jpeg;base64,' + window.btoa(IR);
 +
 +    // create DOM image
 +    var img = new Image();
 +    img.onload = (function() {
 +      this.loaded = true;
 +
 +      objs.resolve(objId, this);
 +
 +      if (this.onLoad)
 +        this.onLoad();
 +    }).bind(this);
 +    img.src = src;
 +    this.domImage = img;
 +  }
 +
 +  JpegStreamIR.prototype = {
 +    getImage: function() {
 +      return this.domImage;
 +    }
 +  }
 +
 +  return JpegStreamIR;
 +})()
 +
  // A JpegStream can't be read directly. We use the platform to render
  // the underlying JPEG data for us.
- var JpegStream = (function() {
-   function isYcckImage(bytes) {
+ var JpegStream = (function jpegStream() {
+   function isAdobeImage(bytes) {
      var maxBytesScanned = Math.max(bytes.length - 16, 1024);
-     // Looking for APP14, 'Adobe' and transform = 2
+     // Looking for APP14, 'Adobe'
      for (var i = 0; i < maxBytesScanned; ++i) {
        if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE &&
            bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E &&
      // TODO: per poppler, some images may have "junk" before that
      // need to be removed
      this.dict = dict;
-     
-     if (isYcckImage(bytes))
-       bytes = fixYcckImage(bytes);
  
 -    img.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes));
 -    this.domImage = img;
+     if (isAdobeImage(bytes))
+       bytes = fixAdobeImage(bytes);
+     // create DOM image
+     var img = new Image();
+     img.onload = (function jpegStreamOnload() {
+       this.loaded = true;
+       if (this.onLoad)
+         this.onLoad();
+     }).bind(this);
 +    this.src = bytesToString(bytes); 
    }
  
    constructor.prototype = {
 -    getImage: function jpegStreamGetImage() {
 -      return this.domImage;
 +    getIR: function() {
 +      return this.src;
      },
-     getChar: function() {
+     getChar: function jpegStreamGetChar() {
        error('internal error: getChar is not valid on JpegStream');
      }
    };
    return constructor;
  })();
  
- var DecryptStream = (function() {
 -// Simple object to track the loading images
 -// Initialy for every that is in loading call imageLoading()
 -// and, when images onload is fired, call imageLoaded()
 -// When all images are loaded, the onLoad event is fired.
 -var ImagesLoader = (function imagesLoader() {
 -  function constructor() {
 -    this.loading = 0;
 -  }
 -
 -  constructor.prototype = {
 -    imageLoading: function imagesLoaderImageLoading() {
 -      ++this.loading;
 -    },
 -
 -    imageLoaded: function imagesLoaderImageLoaded() {
 -      if (--this.loading == 0 && this.onLoad) {
 -        this.onLoad();
 -        delete this.onLoad;
 -      }
 -    },
 -
 -    bind: function imagesLoaderBind(jpegStream) {
 -      if (jpegStream.loaded || jpegStream.onLoad)
 -        return;
 -      this.imageLoading();
 -      jpegStream.onLoad = this.imageLoaded.bind(this);
 -    },
 -
 -    notifyOnLoad: function imagesLoaderNotifyOnLoad(callback) {
 -      if (this.loading == 0)
 -        callback();
 -      this.onLoad = callback;
 -    }
 -  };
 -
 -  return constructor;
 -})();
 -
+ var DecryptStream = (function decryptStream() {
    function constructor(str, decrypt) {
      this.str = str;
      this.dict = str.dict;
@@@ -3338,36 -3554,52 +3542,37 @@@ var Page = (function pagePage() 
        }
        return shadow(this, 'rotate', rotate);
      },
 -    startRendering: function pageStartRendering(canvasCtx, continuation) {
 +      
-     startRenderingFromIRQueue: function(gfx, IRQueue, fonts, continuation) {
++    startRenderingFromIRQueue: function startRenderingFromIRQueue(gfx, IRQueue, fonts, continuation) {
        var self = this;
 -      var stats = self.stats;
 -      stats.compile = stats.fonts = stats.render = 0;
 +      this.IRQueue = IRQueue;
 +      
-       var displayContinuation = function() { 
++      var displayContinuation = function pageDisplayContinuation() { 
 +        console.log('--display--');
 -      var gfx = new CanvasGraphics(canvasCtx);
 -      var fonts = [];
 -      var images = new ImagesLoader();
 -
 -      this.compile(gfx, fonts, images);
 -      stats.compile = Date.now();
 -
 -      var displayContinuation = function pageDisplayContinuation() {
          // Always defer call to display() to work around bug in
          // Firefox error reporting from XHR callbacks.
-         setTimeout(function() {
+         setTimeout(function pageSetTimeout() {
            var exc = null;
            try {
 -            self.display(gfx);
 -            stats.render = Date.now();
 +            self.display(gfx, continuation);
            } catch (e) {
              exc = e.toString();
 +            continuation(exc);
 +            throw e;
            }
 -          if (continuation) continuation(exc);
          });
        };
 -
 -      var fontObjs = FontLoader.bind(
 -        fonts,
 -        function pageFontObjs() {
 -          stats.fonts = Date.now();
 -          images.notifyOnLoad(function pageNotifyOnLoad() {
 -            stats.images = Date.now();
 -            displayContinuation();
 -          });
 -        });
 -
 -      for (var i = 0, ii = fonts.length; i < ii; ++i)
 -        fonts[i].dict.fontObj = fontObjs[i];
 +      
 +      this.ensureFonts(fonts, function() {
 +        displayContinuation();
 +      });
      },
  
 -
 -    compile: function pageCompile(gfx, fonts, images) {
 -      if (this.code) {
 +    getIRQueue: function(handler, dependency) {
 +      if (this.IRQueue) {
          // content was compiled
 -        return;
 +        return this.IRQueue;
        }
  
        var xref = this.xref;
              width: this.width,
              height: this.height,
              rotate: this.rotate });
 -      gfx.execute(this.code, xref, resources);
 -      gfx.endDrawing();
 +            
 +      var startIdx = 0;
 +      var length = this.IRQueue.fnArray.length;
 +      var IRQueue = this.IRQueue;
 +      
 +      var self = this;
 +      var startTime = Date.now();
 +      function next() {
 +        startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
 +        if (startIdx == length) {
 +          self.stats.render = Date.now();
 +          console.log("page=%d - executeIRQueue: time=%dms", 
 +            self.pageNumber + 1, self.stats.render - startTime);
 +          callback();
 +        }
 +      }
 +      next();
      },
-     rotatePoint: function(x, y) {
+     rotatePoint: function pageRotatePoint(x, y) {
        var rotate = this.rotate;
        switch (rotate) {
          case 180:
@@@ -4055,12 -4264,7 +4275,12 @@@ var EvalState = (function evalState() 
    return constructor;
  })();
  
- var PartialEvaluator = (function() {
 +var FontsMap = {};
 +var FontLoadedCounter = 0;
 +
 +var objIdCounter = 0;
 +
+ var PartialEvaluator = (function partialEvaluator() {
    function constructor() {
      this.state = new EvalState();
      this.stateStack = [];
    };
  
    constructor.prototype = {
-     getIRQueue: function(stream, xref, resources, queue, handler, 
 -    evaluate: function partialEvaluatorEvaluate(stream, xref, resources, fonts,
 -                                                images) {
++    getIRQueue: function partialEvaluatorGetIRQueue(stream, xref, resources, queue, handler, 
 +                          uniquePrefix, dependency) {
 +
 +      function insertDependency(depList) {
 +        fnArray.push("dependency");
 +        argsArray.push(depList);
 +        for (var i = 0; i < depList.length; i++) {
 +          var dep = depList[i];
 +          if (dependency.indexOf(dep) == -1) {
 +            dependency.push(depList[i]);
 +          }
 +        }
 +      }
 +
 +      function buildPaintImageXObject(image, inline) {
 +        var dict = image.dict;
 +        var w = dict.get('Width', 'W');
 +        var h = dict.get('Height', 'H');
 +
 +        if (image instanceof JpegStream) {
 +          var objId = 'img_' + ++objIdCounter;
 +          handler.send("obj", [objId, "JpegStream", image.getIR()]);
 +
 +          // Add the dependency on the image object.
 +          insertDependency([objId]);
 +
 +          // The normal fn.
 +          fn = 'paintJpegXObject';
 +          args = [ objId, w, h ];
 +        } else {
 +          // Needs to be rendered ourself.
 +      
 +          // Figure out if the image has an imageMask.
 +          var imageMask = dict.get('ImageMask', 'IM') || false;
 +
 +          // If there is no imageMask, create the PDFImage and a lot
 +          // of image processing can be done here.
 +          if (!imageMask) {
 +            var imageObj = new PDFImage(xref, resources, image, inline);
 +
 +            if (imageObj.imageMask) {
 +              throw "Can't handle this in the web worker :/";
 +            }
 +        
 +            var imgData = {
 +              width: w,
 +              height: h,
 +              data: new Uint8Array(w * h * 4)
 +            };
 +            var pixels = imgData.data;
 +            imageObj.fillRgbaBuffer(pixels, imageObj.decode);
 +        
 +            fn = "paintImageXObject";
 +            args = [ imgData ];
 +          } else /* imageMask == true */ {
 +            // This depends on a tmpCanvas beeing filled with the
 +            // current fillStyle, such that processing the pixel
 +            // data can't be done here. Instead of creating a
 +            // complete PDFImage, only read the information needed
 +            // for later.
 +            fn = "paintImageMaskXObject";
 +        
 +            var width = dict.get('Width', 'W');
 +            var height = dict.get('Height', 'H');
 +            var bitStrideLength = (width + 7) >> 3;
 +            var imgArray = image.getBytes(bitStrideLength * height);
 +            var decode = dict.get('Decode', 'D');
 +            var inverseDecode = !!decode && decode[0] > 0;
 +
 +            args = [ imgArray, inverseDecode, width, height ];
 +          }
 +        }
 +      }
 +      
 +      uniquePrefix = uniquePrefix || "";
 +      if (!queue.argsArray) {
 +        queue.argsArray = []
 +      }
 +      if (!queue.fnArray) {
 +        queue.fnArray = [];
 +      }
 +
 +      var fnArray = queue.fnArray, argsArray = queue.argsArray;
 +      var dependency = dependency || [];
 +      
        resources = xref.fetchIfRef(resources) || new Dict();
        var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict();
        var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict();
        var parser = new Parser(new Lexer(stream), false);
 -      var args = [], argsArray = [], fnArray = [], obj;
 +      var args = [], obj;
 +      var res = resources;
  
-       while (!IsEOF(obj = parser.getObj())) {
-         if (IsCmd(obj)) {
+       while (!isEOF(obj = parser.getObj())) {
+         if (isCmd(obj)) {
            var cmd = obj.cmd;
            var fn = OP_MAP[cmd];
+           if (!fn) {
+             // invalid content command, trying to recover
+             if (cmd.substr(-2) == 'BT') {
+               fn = OP_MAP[cmd.substr(0, cmd.length - 2)];
+               // feeding 'BT' on next interation
+               parser = {
+                 getObj: function() {
+                   parser = this.oldParser;
+                   return { name: 'BT' };
+                 },
+                 oldParser: parser
+               };
+             }
+           }
            assertWellFormed(fn, "Unknown command '" + cmd + "'");
            // TODO figure out how to type-check vararg functions
  
              // compile tiling patterns
              var patternName = args[args.length - 1];
              // SCN/scn applies patterns along with normal colors
-             if (IsName(patternName)) {
+             if (isName(patternName)) {
                var pattern = xref.fetchIfRef(patterns.get(patternName.name));
                if (pattern) {
-                 var dict = IsStream(pattern) ? pattern.dict : pattern;
+                 var dict = isStream(pattern) ? pattern.dict : pattern;
                  var typeNum = dict.get('PatternType');
 +
 +                // Type1 is TilingPattern
                  if (typeNum == 1) {
 -                  patternName.code = this.evaluate(pattern, xref,
 -                                                   dict.get('Resources'),
 -                                                   fonts, images);
 +                  // Create an IR of the pattern code.
 +                  var depIdx = dependency.length;
 +                  var codeIR = this.getIRQueue(pattern, xref,
 +                                    dict.get('Resources'), {}, handler, 
 +                                    uniquePrefix, dependency);
 +                  
 +                  // Add the dependencies that are required to execute the
 +                  // codeIR.
 +                  insertDependency(dependency.slice(depIdx));
 +                  
 +                  args = TilingPattern.getIR(codeIR, dict, args);
 +                } 
 +                // Type2 is ShadingPattern.
 +                else if (typeNum == 2) {
 +                  var shading = xref.fetchIfRef(dict.get('Shading'));
 +                  var matrix = dict.get('Matrix');
 +                  var pattern = Pattern.parseShading(shading, matrix, xref, res, null /*ctx*/);
 +                  args = pattern.getIR();
 +                } else {
 +                  error("Unkown PatternType " + typeNum);
                  }
                }
              }
              var fontRes = resources.get('Font');
              if (fontRes) {
                fontRes = xref.fetchIfRef(fontRes);
 -              var font = xref.fetchIfRef(fontRes.get(args[0].name));
 +              var font = xref.fetchIfRef(fontRes.get(fontName));
-               assertWellFormed(IsDict(font));
+               assertWellFormed(isDict(font));
                if (!font.translated) {
                  font.translated = this.translateFont(font, xref, resources);
 -                if (fonts && font.translated) {
 +                if (font.translated) {
                    // keep track of each font we translated so the caller can
                    // load them asynchronously before calling display on a page
 -                  fonts.push(font.translated);
 +                  var loadedName = "font_" + uniquePrefix + (FontLoadedCounter++);
 +                  font.translated.properties.loadedName = loadedName;
 +                  FontsMap[loadedName] = font;
 +
 +                  handler.send("obj", [
 +                      loadedName, 
 +                      "Font", 
 +                      font.translated.name,
 +                      font.translated.file,
 +                      font.translated.properties
 +                  ]);
                  }
                }
 +              args[0].name = font.translated.properties.loadedName;
 +              
 +              // Ensure the font is ready before the font is set
 +              // and later on used for drawing.
 +              // TODO: This should get insert to the IRQueue only once per
 +              // page.
 +              insertDependency([font.translated.properties.loadedName]);
 +            } else {
 +              // TODO: TOASK: Is it possible to get here? If so, what does
 +              // args[0].name should be like???
              }
 +          } else if (cmd == 'EI') {
 +            buildPaintImageXObject(args[0], true);
 +          }
 +
 +          // Transform some cmds.
 +          switch (fn) {
 +          // Parse the ColorSpace data to a raw format.
 +          case "setFillColorSpace":
 +          case "setStrokeColorSpace":
 +            args = [ ColorSpace.parseToIR(args[0], xref, resources) ];
 +            break;
 +          case "shadingFill":
 +            var shadingRes = xref.fetchIfRef(res.get('Shading'));
 +            if (!shadingRes)
 +              error('No shading resource found');
 +
 +            var shading = xref.fetchIfRef(shadingRes.get(args[0].name));
 +            if (!shading)
 +              error('No shading object found');
 +
 +            var shadingFill = Pattern.parseShading(shading, null, xref, res, /* ctx */ null);
 +            var patternIR = shadingFill.getIR();
 +
 +            args = [ patternIR ];
 +            fn = "shadingFill";
 +
 +            break;
            }
  
            fnArray.push(fn);
@@@ -4845,15 -4955,8 +5138,15 @@@ function ScratchCanvas(width, height) 
    return canvas;
  }
  
- var CanvasGraphics = (function() {
+ var CanvasGraphics = (function canvasGraphics() {
 -  function constructor(canvasCtx, imageCanvas) {
 +  // Defines the time the executeIRQueue gone be executing
 +  // before it stops and shedules a continue of execution.
 +  var kExecutionTime = 50;
 +  // Number of IR commands to execute before checking
 +  // if we execute longer then `kExecutionTime`.
 +  var kExecutionTimeCheck = 500;
 +
 +  function constructor(canvasCtx, objs) {
      this.ctx = canvasCtx;
      this.current = new CanvasExtraState();
      this.stateStack = [];
        this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
      },
  
-     executeIRQueue: function(codeIR, executionStartIdx, continueCallback) {
 -    compile: function canvasGraphicsCompile(stream, xref, resources, fonts,
 -                                            images) {
 -      var pe = new PartialEvaluator();
 -      return pe.evaluate(stream, xref, resources, fonts, images);
 -    },
++    executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, 
++                                  executionStartIdx, continueCallback) {
 +      var argsArray = codeIR.argsArray;
 +      var fnArray =   codeIR.fnArray;
 +      var i = executionStartIdx || 0;
 +      var argsArrayLen = argsArray.length;
 +      
 +      var executionEndIdx;
 +      var startTime = Date.now();
  
 -    execute: function canvasGraphicsExecute(code, xref, resources) {
 -      resources = xref.fetchIfRef(resources) || new Dict();
 -      var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs;
 -      this.xref = xref;
 -      this.res = resources || new Dict();
 -      this.xobjs = xref.fetchIfRef(this.res.get('XObject')) || new Dict();
 +      var objs = this.objs;
  
 -      code(this);
 +      do {
 +        executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck);
 +        
 +        for (i; i < executionEndIdx; i++) {
 +          if (fnArray[i] !== "dependency") {
 +            this[fnArray[i]].apply(this, argsArray[i]);
 +          } else {
 +            var deps = argsArray[i];
 +            for (var n = 0; n < deps.length; n++) {
 +              var depObjId = deps[n];
 +
 +              // If the promise isn't resolved yet, add the continueCallback
 +              // to the promise and bail out.
 +              if (!objs.isResolved(depObjId)) {
 +                objs.get(depObjId, continueCallback);
 +                return i;
 +              }
 +            }
 +          }
 +        }
 +
 +        // If the entire IRQueue was executed, stop as were done.
 +        if (i == argsArrayLen) {
 +          return i;
 +        } 
 +        // If the execution took longer then a certain amount of time, shedule
 +        // to continue exeution after a short delay.
 +        // However, this is only possible if a 'continueCallback' is passed in.
 +        else if (continueCallback && 
 +                (Date.now() - startTime) > kExecutionTime) {
 +          setTimeout(continueCallback, 0);
 +          return i;
 +        }          
  
 -      this.xobjs = savedXobjs;
 -      this.res = savedRes;
 -      this.xref = savedXref;
 +        // If the IRQueue isn't executed completly yet OR the execution time
 +        // was short enough, do another execution round.
 +      } while (true);
      },
  
-     endDrawing: function() {
+     endDrawing: function canvasGraphicsEndDrawing() {
        this.ctx.restore();
      },
  
        this.current.x = this.current.lineX = 0;
        this.current.y = this.current.lineY = 0;
      },
-     endText: function() {
+     endText: function canvasGraphicsEndText() {
      },
-     setCharSpacing: function(spacing) {
+     setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) {
        this.current.charSpacing = spacing;
      },
-     setWordSpacing: function(spacing) {
+     setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) {
        this.current.wordSpacing = spacing;
      },
-     setHScale: function(scale) {
+     setHScale: function canvasGraphicsSetHScale(scale) {
        this.current.textHScale = scale / 100;
      },
-     setLeading: function(leading) {
+     setLeading: function canvasGraphicsSetLeading(leading) {
        this.current.leading = -leading;
      },
-     setFont: function(fontRef, size) {
+     setFont: function canvasGraphicsSetFont(fontRef, size) {
 -      var font;
 -      // the tf command uses a name, but graphics state uses a reference
 -      if (isName(fontRef)) {
 -        font = this.xref.fetchIfRef(this.res.get('Font'));
 -        if (!isDict(font))
 -         return;
 -
 -        font = font.get(fontRef.name);
 -      } else if (isRef(fontRef)) {
 -        font = fontRef;
 -      }
 -      font = this.xref.fetchIfRef(font);
 -      if (!font)
 -        error('Referenced font is not found');
 -
 -      var fontObj = font.fontObj;
 +      // Lookup the fontObj using fontRef only.
 +      var fontRefName = fontRef.name;
 +      var fontObj = this.objs.get(fontRefName);
 +      
 +      if (!fontObj) {
 +        throw "Can't find font for " + fontRefName;
 +      }
 +      
-       var name = fontObj.loadedName;
-       if (!name) {
-         // TODO: fontDescriptor is not available, fallback to default font
-         name = 'sans-serif';
-       }
++      var name = fontObj.loadedName || 'sans-serif';
 +
        this.current.font = fontObj;
        this.current.fontSize = size;
  
--      var name = fontObj.loadedName || 'sans-serif';
        if (this.ctx.$setFont) {
          this.ctx.$setFont(name, size);
        } else {
 -        var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
 -                                   (fontObj.bold ? 'bold' : 'normal');
 -
 -        var italic = fontObj.italic ? 'italic' : 'normal';
 -        var serif = fontObj.serif ? 'serif' : 'sans-serif';
 -        var typeface = '"' + name + '", ' + serif;
 -        var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface;
 -        this.ctx.font = rule;
 +        this.ctx.font = fontObj.getRule(size);
        }
      },
-     setTextRenderingMode: function(mode) {
+     setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) {
        TODO('text rendering mode: ' + mode);
      },
-     setTextRise: function(rise) {
+     setTextRise: function canvasGraphicsSetTextRise(rise) {
        TODO('text rise: ' + rise);
      },
-     moveText: function(x, y) {
+     moveText: function canvasGraphicsMoveText(x, y) {
        this.current.x = this.current.lineX += x;
        this.current.y = this.current.lineY += y;
        if (this.ctx.$setCurrentX) {
        this.current.x = this.current.lineX = 0;
        this.current.y = this.current.lineY = 0;
      },
-     nextLine: function() {
+     nextLine: function canvasGraphicsNextLine() {
        this.moveText(0, this.current.leading);
      },
-     showText: function(text) {
++
+     showText: function canvasGraphicsShowText(text) {
 +      // If the current font isn't supported, we can't display the text and
 +      // bail out.
 +      if (!this.current.font.supported) {
 +        return;
 +      }
-       
++
        var ctx = this.ctx;
        var current = this.current;
-       var originalText = text;
-       ctx.save();
-       ctx.transform.apply(ctx, current.textMatrix);
-       ctx.scale(1, -1);
-       ctx.translate(current.x, -1 * current.y);
        var font = current.font;
-       if (font) {
-         ctx.transform.apply(ctx, font.textMatrix || IDENTITY_MATRIX);
-         text = font.charsToUnicode(text);
-       }
-       var composite = font.composite;
-       var encoding = font.encoding;
+       var glyphs = font.charsToGlyphs(text);
        var fontSize = current.fontSize;
        var charSpacing = current.charSpacing;
        var wordSpacing = current.wordSpacing;
        var textHScale = current.textHScale;
-       ctx.scale(1 / textHScale, 1);
-       var width = 0;
-       for (var i = 0; i < text.length; i++) {
-         if (composite) {
-           var position = i * 2 + 1;
-           var charcode = (originalText.charCodeAt(position - 1) << 8) +
-                           originalText.charCodeAt(position);
-         } else {
-           var charcode = originalText.charCodeAt(i);
+       var glyphsLength = glyphs.length;
+       if (font.coded) {
+         ctx.save();
+         ctx.transform.apply(ctx, current.textMatrix);
+         ctx.translate(current.x, current.y);
+         var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
+         ctx.scale(1 / textHScale, 1);
+         for (var i = 0; i < glyphsLength; ++i) {
+           var glyph = glyphs[i];
+           if (glyph === null) {
+             // word break
+             this.ctx.translate(wordSpacing, 0);
+             continue;
+           }
+           this.save();
+           ctx.scale(fontSize, fontSize);
+           ctx.transform.apply(ctx, fontMatrix);
+           this.execute(glyph.code, this.xref, font.resources);
+           this.restore();
+           var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
+           var width = transformed[0] * fontSize + charSpacing;
+           ctx.translate(width, 0);
+           current.x += width;
          }
+         ctx.restore();
+       } else {
+         ctx.save();
+         ctx.transform.apply(ctx, current.textMatrix);
+         ctx.scale(1, -1);
+         ctx.translate(current.x, -1 * current.y);
+         ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
+         ctx.scale(1 / textHScale, 1);
+         var width = 0;
+         for (var i = 0; i < glyphsLength; ++i) {
+           var glyph = glyphs[i];
+           if (glyph === null) {
+             // word break
+             width += wordSpacing;
+             continue;
+           }
  
-         var charWidth = font.encoding[charcode].width * fontSize * 0.001;
-         charWidth += charSpacing;
-         if (charcode == 32)
-           charWidth += wordSpacing;
+           var unicode = glyph.unicode;
+           var char = (unicode >= 0x10000) ?
+             String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
+             0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
  
-         ctx.fillText(text.charAt(i), 0, 0);
-         ctx.translate(charWidth, 0);
-         width += charWidth;
-       }
-       current.x += width;
+           ctx.fillText(char, width, 0);
+           width += glyph.width * fontSize * 0.001 + charSpacing;
+         }
+         current.x += width;
  
-       this.ctx.restore();
+         ctx.restore();
+       }
      },
-     showSpacedText: function(arr) {
++
+     showSpacedText: function canvasGraphicsShowSpacedText(arr) {
 +      // If the current font isn't supported, we can't display the text and
 +      // bail out.
 +      if (!this.current.font.supported) {
 +        return;
 +      }
 +      
-       for (var i = 0; i < arr.length; ++i) {
+       var ctx = this.ctx;
+       var current = this.current;
+       var fontSize = current.fontSize;
+       var textHScale = current.textHScale;
+       var arrLength = arr.length;
+       for (var i = 0; i < arrLength; ++i) {
          var e = arr[i];
-         if (IsNum(e)) {
-           if (this.ctx.$addCurrentX) {
-             this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize);
+         if (isNum(e)) {
+           if (ctx.$addCurrentX) {
+             ctx.$addCurrentX(-e * 0.001 * fontSize);
            } else {
-             this.current.x -= e * 0.001 * this.current.fontSize *
-                               this.current.textHScale;
+             current.x -= e * 0.001 * fontSize * textHScale;
            }
-         } else if (IsString(e)) {
+         } else if (isString(e)) {
            this.showText(e);
          } else {
-           malformed('TJ array element ' + e + " isn't string or num");
+           malformed('TJ array element ' + e + ' is not string or num');
          }
        }
      },
      },
  
      // Color
-     setStrokeColorSpace: function(raw) {
 -    setStrokeColorSpace: function canvasGraphicsSetStrokeColorSpace(space) {
++    setStrokeColorSpace: function canvasGraphicsSetStrokeColorSpacefunction(raw) {
        this.current.strokeColorSpace =
 -          ColorSpace.parse(space, this.xref, this.res);
 +            ColorSpace.fromIR(raw);
      },
-     setFillColorSpace: function(raw) {
 -    setFillColorSpace: function canvasGraphicsSetFillColorSpace(space) {
++    setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) {
        this.current.fillColorSpace =
 -          ColorSpace.parse(space, this.xref, this.res);
 +            ColorSpace.fromIR(raw);
      },
-     setStrokeColor: function(/*...*/) {
+     setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) {
        var cs = this.current.strokeColorSpace;
        var color = cs.getRgb(arguments);
        this.setStrokeRGBColor.apply(this, color);
      },
 -    setStrokeColorN: function canvasGraphicsSetStrokeColorN(/*...*/) {
 +    getColorN_IR_Pattern: function(IR, cs) {
 +      if (IR[0] == "TilingPatternIR") {
 +        // First, build the `color` var like it's done in the
 +        // Pattern.prototype.parse function.
 +        var args = IR[1];
 +        var base = cs.base;
 +        var color;
 +        if (base) {
 +          var baseComps = base.numComps;
 +
 +          color = [];
 +          for (var i = 0; i < baseComps; ++i)
 +            color.push(args[i]);
 +
 +          color = base.getRgb(color);
 +        }
 +
 +        // Build the pattern based on the IR data.
 +        var pattern = new TilingPatternIR(IR, color, this.ctx, this.objs);
 +      } else if (IR[0] == "RadialAxialShading" || IR[0] == "DummyShading") {
 +        var pattern = Pattern.shadingFromIR(this.ctx, IR); 
 +      } else {
 +        throw "Unkown IR type";
 +      }
 +      return pattern; 
 +    },
-     setStrokeColorN_IR: function(/*...*/) {
++    setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) {
        var cs = this.current.strokeColorSpace;
  
        if (cs.name == 'Pattern') {
        var color = cs.getRgb(arguments);
        this.setFillRGBColor.apply(this, color);
      },
-     setFillColorN_IR: function(/*...*/) {
 -    setFillColorN: function canvasGraphicsSetFillColorN(/*...*/) {
++    setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) {
        var cs = this.current.fillColorSpace;
  
        if (cs.name == 'Pattern') {
        this.current.fillColor = color;
      },
  
-     shadingFill: function(patternIR) {
 -    // Shading
 -    shadingFill: function canvasGraphicsShadingFill(shadingName) {
 -      var xref = this.xref;
 -      var res = this.res;
++    shadingFill: function canvasGraphicsShadingFill(patternIR) {
        var ctx = this.ctx;
 -
 -      var shadingRes = xref.fetchIfRef(res.get('Shading'));
 -      if (!shadingRes)
 -        error('No shading resource found');
 -
 -      var shading = xref.fetchIfRef(shadingRes.get(shadingName.name));
 -      if (!shading)
 -        error('No shading object found');
 -
 -      var shadingFill = Pattern.parseShading(shading, null, xref, res, ctx);
 -
 +      
        this.save();
 -      ctx.fillStyle = shadingFill.getPattern();
 +      ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR);
  
        var inv = ctx.mozCurrentTransformInverse;
        if (inv) {
      },
  
      // Images
-     beginInlineImage: function() {
+     beginInlineImage: function canvasGraphicsBeginInlineImage() {
        error('Should not call beginInlineImage');
      },
-     beginImageData: function() {
+     beginImageData: function canvasGraphicsBeginImageData() {
        error('Should not call beginImageData');
      },
 -    endInlineImage: function canvasGraphicsEndInlineImage(image) {
 -      this.paintImageXObject(null, image, true);
 -    },
 -
 -    // XObjects
 -    paintXObject: function canvasGraphicsPaintXObject(obj) {
 -      var xobj = this.xobjs.get(obj.name);
 -      if (!xobj)
 -        return;
 -      xobj = this.xref.fetchIfRef(xobj);
 -      assertWellFormed(isStream(xobj), 'XObject should be a stream');
 -
 -      var oc = xobj.dict.get('OC');
 -      if (oc) {
 -        TODO('oc for xobject');
 -      }
 -
 -      var opi = xobj.dict.get('OPI');
 -      if (opi) {
 -        TODO('opi for xobject');
 -      }
 -
 -      var type = xobj.dict.get('Subtype');
 -      assertWellFormed(isName(type), 'XObject should have a Name subtype');
 -      if ('Image' == type.name) {
 -        this.paintImageXObject(obj, xobj, false);
 -      } else if ('Form' == type.name) {
 -        this.paintFormXObject(obj, xobj);
 -      } else if ('PS' == type.name) {
 -        warn('(deprecated) PostScript XObjects are not supported');
 -      } else {
 -        malformed('Unknown XObject subtype ' + type.name);
 -      }
 -    },
 -
 -    paintFormXObject: function canvasGraphicsPaintFormXObject(ref, stream) {
 +  
-     paintFormXObjectBegin: function(matrix, bbox) {
++    paintFormXObjectBegin: function canvasGraphicsPaintFormXObject(matrix, bbox) {
        this.save();
  
-       if (matrix && IsArray(matrix) && 6 == matrix.length)
 -      var matrix = stream.dict.get('Matrix');
+       if (matrix && isArray(matrix) && 6 == matrix.length)
          this.transform.apply(this, matrix);
  
-       if (bbox && IsArray(bbox) && 4 == bbox.length) {
-         this.rectangle.apply(this, bbox);
 -      var bbox = stream.dict.get('BBox');
+       if (bbox && isArray(bbox) && 4 == bbox.length) {
+         var width = bbox[2] - bbox[0];
+         var height = bbox[3] - bbox[1];
+         this.rectangle(bbox[0], bbox[1], width, height);
          this.clip();
          this.endPath();
        }
        this.restore();
      },
  
 +    paintImageXObject: function(imgData) {
 +      this.save();
 +
 +      var ctx = this.ctx;
 +      var w = imgData.width;
 +      var h = imgData.height;
 +      // scale the image to the unit square
 +      ctx.scale(1 / w, -1 / h);
 +
 +      var tmpCanvas = new this.ScratchCanvas(w, h);
 +      var tmpCtx = tmpCanvas.getContext('2d');
 +      var tmpImgData;
 +      
 +      // Deactivating this for now until we have feature detection.
 +      // if (isGecko) {
 +      //  tmpImgData = imgData;
 +      // } else {
 +        tmpImgData = tmpCtx.getImageData(0, 0, w, h);
 +
 +        // Copy over the imageData.
 +        var tmpImgDataPixels = tmpImgData.data;
 +        var len = tmpImgDataPixels.length;
 +
 +        // TODO: There got to be a better way to copy an ImageData array
 +        // then coping over all the bytes one by one :/
 +        while (len--) 
 +          tmpImgDataPixels[len] = imgData.data[len];
 +      // }
 +
 +      tmpCtx.putImageData(tmpImgData, 0, 0);
 +      ctx.drawImage(tmpCanvas, 0, -h);
 +      this.restore();
 +    },
 +
      // Marked content
  
-     markPoint: function(tag) {
+     markPoint: function canvasGraphicsMarkPoint(tag) {
        TODO('Marked content');
      },
-     markPointProps: function(tag, properties) {
+     markPointProps: function canvasGraphicsMarkPointProps(tag, properties) {
        TODO('Marked content');
      },
-     beginMarkedContent: function(tag) {
+     beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) {
        TODO('Marked content');
      },
-     beginMarkedContentProps: function(tag, properties) {
+     beginMarkedContentProps:
+       function canvasGraphicsBeginMarkedContentProps(tag, properties) {
        TODO('Marked content');
      },
-     endMarkedContent: function() {
+     endMarkedContent: function canvasGraphicsEndMarkedContent() {
        TODO('Marked content');
      },
  
@@@ -5636,59 -5780,9 +6025,59 @@@ var ColorSpace = (function colorSpaceCo
    };
  
    constructor.parse = function colorspace_parse(cs, xref, res) {
-     if (IsArray(IR)) {
 +    var IR = constructor.parseToIR(cs, xref, res, true);
 +    if (!(IR instanceof SeparationCS)) {
 +      return constructor.fromIR(IR);
 +    } else {
 +      return IR
 +    }
 +  };
 +  
 +  constructor.fromIR = function(IR) {
 +    var name;
-     if (IsName(cs)) {
++    if (isArray(IR)) {
 +      name = IR[0];
 +    } else {
 +      name = IR;
 +    }
 +    
 +    switch (name) {
 +      case "DeviceGrayCS":
 +        return new DeviceGrayCS();
 +      case "DeviceRgbCS":
 +        return new DeviceRgbCS();
 +      case "DeviceCmykCS":
 +        return new DeviceCmykCS();
 +      case "PatternCS":
 +        var baseCS = IR[1];
 +        if (baseCS == null) {
 +          return new PatternCS(null);
 +        } else {
 +          return new PatternCS(ColorSpace.fromIR(baseCS));
 +        }
 +      case "IndexedCS":
 +        var baseCS = IR[1];
 +        var hiVal  = IR[2];
 +        var lookup = IR[3];
 +        return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup)
 +      case "SeparationCS":
 +        var alt       = IR[1];
 +        var tintFnIR  = IR[2];
 +        
 +        return new SeparationCS(
 +          ColorSpace.fromIR(alt),
 +          PDFFunction.fromIR(tintFnIR)
 +        );
 +      default:
 +        error("Unkown name " + name);
 +    }
 +    return null;
 +  }
 +  
 +  constructor.parseToIR = function colorspace_parse(cs, xref, res, parseOnly) {
 -      var colorSpaces = xref.fetchIfRef(res.get('ColorSpace'));
+     if (isName(cs)) {
-       if (IsDict(colorSpaces)) {
 +      var colorSpaces = res.get('ColorSpace');
+       if (isDict(colorSpaces)) {
          var refcs = colorSpaces.get(cs.name);
          if (refcs)
            cs = refcs;
        this.mode = mode;
  
        switch (mode) {
 -        case 'DeviceGray':
 -        case 'G':
 -          return new DeviceGrayCS();
 -        case 'DeviceRGB':
 -        case 'RGB':
 -          return new DeviceRgbCS();
 -        case 'DeviceCMYK':
 -        case 'CMYK':
 -          return new DeviceCmykCS();
 -        case 'Pattern':
 -          return new PatternCS(null);
 -        default:
 -          error('unrecognized colorspace ' + mode);
 +      case 'DeviceGray':
 +      case 'G':
 +        return "DeviceGrayCS";
 +      case 'DeviceRGB':
 +      case 'RGB':
 +        return "DeviceRgbCS";
 +      case 'DeviceCMYK':
 +      case 'CMYK':
 +        return "DeviceCmykCS";
 +      case 'Pattern':
 +        return ["PatternCS", null];
 +      default:
 +        error('unrecognized colorspace ' + mode);
        }
-     } else if (IsArray(cs)) {
+     } else if (isArray(cs)) {
        var mode = cs[0].name;
        this.mode = mode;
  
@@@ -6028,10 -6123,45 +6417,10 @@@ var Pattern = (function patternPattern(
      }
    };
  
-   constructor.shadingFromIR = function(ctx, raw) {
 -  constructor.parse = function pattern_parse(args, cs, xref, res, ctx) {
 -    var length = args.length;
 -
 -    var patternName = args[length - 1];
 -    if (!isName(patternName))
 -      error('Bad args to getPattern: ' + patternName);
 -
 -    var patternRes = xref.fetchIfRef(res.get('Pattern'));
 -    if (!patternRes)
 -      error('Unable to find pattern resource');
 -
 -    var pattern = xref.fetchIfRef(patternRes.get(patternName.name));
 -    var dict = isStream(pattern) ? pattern.dict : pattern;
 -    var typeNum = dict.get('PatternType');
 -
 -    switch (typeNum) {
 -      case 1:
 -        var base = cs.base;
 -        var color;
 -        if (base) {
 -          var baseComps = base.numComps;
 -
 -          color = [];
 -          for (var i = 0; i < baseComps; ++i)
 -            color.push(args[i]);
 -
 -          color = base.getRgb(color);
 -        }
 -        var code = patternName.code;
 -        return new TilingPattern(pattern, code, dict, color, xref, ctx);
 -      case 2:
 -        var shading = xref.fetchIfRef(dict.get('Shading'));
 -        var matrix = dict.get('Matrix');
 -        return Pattern.parseShading(shading, matrix, xref, res, ctx);
 -      default:
 -        error('Unknown type of pattern: ' + typeNum);
 -    }
 -    return null;
 -  };
++  constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) {
 +    var obj = window[raw[0]];
 +    return obj.fromIR(ctx, raw);
 +  }
  
    constructor.parseShading = function pattern_shading(shading, matrix,
        xref, res, ctx) {
@@@ -6102,11 -6229,11 +6491,11 @@@ var RadialAxialShading = (function radi
  
      var fnObj = dict.get('Function');
      fnObj = xref.fetchIfRef(fnObj);
-     if (IsArray(fnObj))
+     if (isArray(fnObj))
        error('No support for array of functions');
-     else if (!IsPDFFunction(fnObj))
+     else if (!isPDFFunction(fnObj))
        error('Invalid function');
 -    var fn = new PDFFunction(xref, fnObj);
 +    var fn = PDFFunction.parse(xref, fnObj);
  
      // 10 samples seems good enough for now, but probably won't work
      // if there are sharp color changes. Ideally, we would implement
      this.colorStops = colorStops;
    }
  
 +  constructor.fromIR = function(ctx, raw) {
 +    var type = raw[1];
 +    var colorStops = raw[2];
 +    var p0 = raw[3];
 +    var p1 = raw[4];
 +    var r0 = raw[5];
 +    var r1 = raw[6];
 +
 +    var curMatrix = ctx.mozCurrentTransform; 
 +    if (curMatrix) {
 +      var userMatrix = ctx.mozCurrentTransformInverse;
 +
 +      p0 = Util.applyTransform(p0, curMatrix);
 +      p0 = Util.applyTransform(p0, userMatrix);
 +
 +      p1 = Util.applyTransform(p1, curMatrix);
 +      p1 = Util.applyTransform(p1, userMatrix);
 +    }
 +
 +    if (type == 2)
 +      var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
 +    else if (type == 3)
 +      var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
 +
 +    for (var i = 0, ii = colorStops.length; i < ii; ++i) {
 +      var c = colorStops[i];
 +      grad.addColorStop(c[0], c[1]);
 +    }
 +    return grad;
 +  }
 +
    constructor.prototype = {
-     getIR: function() {
 -    getPattern: function radialAxialShadingGetPattern() {
++    getIR: function RadialAxialShading_getIR() {
        var coordsArr = this.coordsArr;
        var type = this.shadingType;
 -      var p0, p1, r0, r1;
        if (type == 2) {
 -        p0 = [coordsArr[0], coordsArr[1]];
 -        p1 = [coordsArr[2], coordsArr[3]];
 +        var p0 = [coordsArr[0], coordsArr[1]];
 +        var p1 = [coordsArr[2], coordsArr[3]];
 +        var r0 = null;
 +        var r1 = null;
        } else if (type == 3) {
 -        p0 = [coordsArr[0], coordsArr[1]];
 -        p1 = [coordsArr[3], coordsArr[4]];
 -        r0 = coordsArr[2];
 -        r1 = coordsArr[5];
 +        var p0 = [coordsArr[0], coordsArr[1]];
 +        var p1 = [coordsArr[3], coordsArr[4]];
 +        var r0 = coordsArr[2];
 +        var r1 = coordsArr[5];
        } else {
          error('getPattern type unknown: ' + type);
        }
    return constructor;
  })();
  
- var TilingPatternIR = (function() {
 -var TilingPattern = (function tilingPattern() {
++var TilingPatternIR = (function tilingPattern() {
    var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
  
 -  function constructor(pattern, code, dict, color, xref, ctx) {
 -      function multiply(m, tm) {
 -        var a = m[0] * tm[0] + m[1] * tm[2];
 -        var b = m[0] * tm[1] + m[1] * tm[3];
 -        var c = m[2] * tm[0] + m[3] * tm[2];
 -        var d = m[2] * tm[1] + m[3] * tm[3];
 -        var e = m[4] * tm[0] + m[5] * tm[2] + tm[4];
 -        var f = m[4] * tm[1] + m[5] * tm[3] + tm[5];
 -        return [a, b, c, d, e, f];
 -      }
 -
 -      TODO('TilingType');
 -
 -      this.matrix = dict.get('Matrix');
 -      this.curMatrix = ctx.mozCurrentTransform;
 -      this.invMatrix = ctx.mozCurrentTransformInverse;
 -      this.ctx = ctx;
 -      this.type = 'Pattern';
 -
 -      var bbox = dict.get('BBox');
 -      var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
 -
 -      var xstep = dict.get('XStep');
 -      var ystep = dict.get('YStep');
 -
 -      var topLeft = [x0, y0];
 -      // we want the canvas to be as large as the step size
 -      var botRight = [x0 + xstep, y0 + ystep];
 -
 -      var width = botRight[0] - topLeft[0];
 -      var height = botRight[1] - topLeft[1];
 -
 -      // TODO: hack to avoid OOM, we would idealy compute the tiling
 -      // pattern to be only as large as the acual size in device space
 -      // This could be computed with .mozCurrentTransform, but still
 -      // needs to be implemented
 -      while (Math.abs(width) > 512 || Math.abs(height) > 512) {
 -        width = 512;
 -        height = 512;
 -      }
 +  function TilingPatternIR(IR, color, ctx, objs) {
 +    // "Unfolding" the IR.
 +    var IRQueue   = IR[2];
 +    this.matrix   = IR[3];
 +    var bbox      = IR[4];
 +    var xstep     = IR[5];
 +    var ystep     = IR[6];
 +    var paintType = IR[7];
  
 -      var tmpCanvas = new ScratchCanvas(width, height);
 +    // 
 +    TODO('TilingType');
  
 -      // set the new canvas element context as the graphics context
 -      var tmpCtx = tmpCanvas.getContext('2d');
 -      var graphics = new CanvasGraphics(tmpCtx);
 -
 -      var paintType = dict.get('PaintType');
 -      switch (paintType) {
 -        case PAINT_TYPE_COLORED:
 -          tmpCtx.fillStyle = ctx.fillStyle;
 -          tmpCtx.strokeStyle = ctx.strokeStyle;
 -          break;
 -        case PAINT_TYPE_UNCOLORED:
 -          color = Util.makeCssRgb.apply(this, color);
 -          tmpCtx.fillStyle = color;
 -          tmpCtx.strokeStyle = color;
 -          break;
 -        default:
 -          error('Unsupported paint type: ' + paintType);
 -      }
 +    this.curMatrix = ctx.mozCurrentTransform;
 +    this.invMatrix = ctx.mozCurrentTransformInverse;
 +    this.ctx = ctx;
 +    this.type = 'Pattern';
  
 -      var scale = [width / xstep, height / ystep];
 -      this.scale = scale;
 +    var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
 +    var topLeft = [x0, y0];
 +    // we want the canvas to be as large as the step size
 +    var botRight = [x0 + xstep, y0 + ystep];
 +
 +    var width = botRight[0] - topLeft[0];
 +    var height = botRight[1] - topLeft[1];
 +
 +    // TODO: hack to avoid OOM, we would idealy compute the tiling
 +    // pattern to be only as large as the acual size in device space
 +    // This could be computed with .mozCurrentTransform, but still
 +    // needs to be implemented
 +    while (Math.abs(width) > 512 || Math.abs(height) > 512) {
 +      width = 512;
 +      height = 512;
 +    }
  
 -      // transform coordinates to pattern space
 -      var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
 -      var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
 -      graphics.transform.apply(graphics, tmpScale);
 -      graphics.transform.apply(graphics, tmpTranslate);
 +    var tmpCanvas = new ScratchCanvas(width, height);
 +
 +    // set the new canvas element context as the graphics context
 +    var tmpCtx = tmpCanvas.getContext('2d');
 +    var graphics = new CanvasGraphics(tmpCtx, objs);
 +
 +    switch (paintType) {
 +    case PAINT_TYPE_COLORED:
 +      tmpCtx.fillStyle = ctx.fillStyle;
 +      tmpCtx.strokeStyle = ctx.strokeStyle;
 +      break;
 +    case PAINT_TYPE_UNCOLORED:
 +      color = Util.makeCssRgb.apply(this, color);
 +      tmpCtx.fillStyle = color;
 +      tmpCtx.strokeStyle = color;
 +      break;
 +    default:
 +      error('Unsupported paint type: ' + paintType);
 +    }
  
 -      if (bbox && isArray(bbox) && 4 == bbox.length) {
 -        var bboxWidth = bbox[2] - bbox[0];
 -        var bboxHeight = bbox[3] - bbox[1];
 -        graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight);
 -        graphics.clip();
 -        graphics.endPath();
 -      }
 +    var scale = [width / xstep, height / ystep];
 +    this.scale = scale;
 +
 +    // transform coordinates to pattern space
 +    var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
 +    var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
 +    graphics.transform.apply(graphics, tmpScale);
 +    graphics.transform.apply(graphics, tmpTranslate);
 +
-     if (bbox && IsArray(bbox) && 4 == bbox.length) {
-       graphics.rectangle.apply(graphics, bbox);
++    if (bbox && isArray(bbox) && 4 == bbox.length) {
++      var bboxWidth = bbox[2] - bbox[0];
++      var bboxHeight = bbox[3] - bbox[1];
++      graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight);
 +      graphics.clip();
 +      graphics.endPath();
 +    }
  
 -      var res = xref.fetchIfRef(dict.get('Resources'));
 -      graphics.execute(code, xref, res);
 +    graphics.executeIRQueue(IRQueue);
  
 -      this.canvas = tmpCanvas;
 +    this.canvas = tmpCanvas;
    }
  
 -  constructor.prototype = {
 +  TilingPatternIR.prototype = {
      getPattern: function tiling_getPattern() {
        var matrix = this.matrix;
        var curMatrix = this.curMatrix;
  
        return ctx.createPattern(this.canvas, 'repeat');
      }
 -  };
 -  return constructor;
 +  }
 +
 +  return TilingPatternIR;
  })();
  
 +var TilingPattern = {
 +  getIR: function(codeIR, dict, args) {
 +    var matrix = dict.get('Matrix');
 +    var bbox = dict.get('BBox');
 +    var xstep = dict.get('XStep');
 +    var ystep = dict.get('YStep');
 +    var paintType = dict.get('PaintType');
 +    
 +    return ["TilingPatternIR", args, codeIR, matrix, bbox, xstep, ystep, paintType];
 +  }
 +};
  
- var PDFImage = (function() {
+ var PDFImage = (function pdfImage() {
    function constructor(xref, res, image, inline) {
      this.image = image;
      if (image.getParams) {
@@@ -6663,10 -6754,36 +7092,10 @@@ var PDFFunction = (function() 
          }
  
          return output;
 -      };
 -    },
 -    getSampleArray: function pdfFunctionGetSampleArray(size, outputSize, bps,
 -                                                       str) {
 -      var length = 1;
 -      for (var i = 0; i < size.length; i++)
 -        length *= size[i];
 -      length *= outputSize;
 -
 -      var array = [];
 -      var codeSize = 0;
 -      var codeBuf = 0;
 -
 -      var strBytes = str.getBytes((length * bps + 7) / 8);
 -      var strIdx = 0;
 -      for (var i = 0; i < length; i++) {
 -        var b;
 -        while (codeSize < bps) {
 -          codeBuf <<= 8;
 -          codeBuf |= strBytes[strIdx++];
 -          codeSize += 8;
 -        }
 -        codeSize -= bps;
 -        array.push(codeBuf >> codeSize);
 -        codeBuf &= (1 << codeSize) - 1;
        }
 -      return array;
      },
 -    constructInterpolated: function pdfFunctionConstructInterpolated(str,
 -                                                                     dict) {
 +
-     constructInterpolated: function(str, dict) {
++    constructInterpolated: function pdfFunctionConstructInterpolated(str, dict) {
        var c0 = dict.get('C0') || [0];
        var c1 = dict.get('C1') || [1];
        var n = dict.get('N');
        for (var i = 0; i < length; ++i)
          diff.push(c1[i] - c0[i]);
  
 -      this.func = function pdfFunctionConstructInterpolatedFunc(args) {
 +      return [ CONSTRUCT_INTERPOLATED, c0, diff, n, i ];
 +    },
 +
-     constructInterpolatedFromIR: function(IR) {
++    constructInterpolatedFromIR: function pdfFunctionconstructInterpolatedFromIR(IR) {
 +      var c0   = IR[1];
 +      var diff = IR[2];
 +      var n    = IR[3];
 +      var i    = IR[4];
 +      
 +      var length = diff.length;
 +
 +      return function(args) {
          var x = args[0];
  
          var out = [];
            out.push(c0[j] + (x^n * diff[i]));
  
          return out;
 -      };
 +        
 +      }
      },
-     constructStiched: function(fn, dict, xref) {
 +    
+     constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) {
        var domain = dict.get('Domain');
        var range = dict.get('Range');
  
        var bounds = dict.get('Bounds');
        var encode = dict.get('Encode');
  
 -      this.func = function pdfFunctionConstructStichedFunc(args) {
 -        var clip = function pdfFunctionConstructStichedFuncClip(v, min, max) {
 +      return [ CONSTRUCT_STICHED, domain, bounds, encoding, fns ];
 +    },
 +
-     constructStichedFromIR: function(IR) {
++    constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) {
 +      var domain    = IR[1];
 +      var bounds    = IR[2];
 +      var encoding  = IR[3];
 +      var fnsIR     = IR[4];
 +      var fns = [];
 +
 +      for (var i = 0; i < fnsIR.length; i++) {
 +        fns.push(PDFFunction.fromIR(fnsIR[i]));
 +      }
 +
 +      return function(args) {
 +        var clip = function(v, min, max) {
            if (v > max)
              v = max;
            else if (v < min)
          var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
  
          // call the appropropriate function
 -        return fns[i].func([v2]);
 +        return fns[i]([v2]);
        };
      },
-     constructPostScript: function() {
 +
-     constructPostScriptFromIR: function() {
+     constructPostScript: function pdfFunctionConstructPostScript() {
 +      return [ CONSTRUCT_POSTSCRIPT ];
 +    },
 +
++    constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() {
        TODO('unhandled type of function');
 -      this.func = function pdfFunctionConstructPostScriptFunc() {
 +      return function() {
          return [255, 105, 180];
        };
      }
diff --cc test/driver.js
index 79f94568b882efc21c1c3bb05b416a37a4c97e7e,4bc14bf15d43907834a0ad4ba21def003b0e4b8b..546397bb32b53e873752cea215ae9f03c481cd36
@@@ -47,14 -49,22 +49,27 @@@ function load() 
    };
    r.send(null);
  }
- window.onload = load;
+ function cleanup() {
+   var styleSheet = document.styleSheets[0];
+   if (styleSheet) {
+     while (styleSheet.cssRules.length > 0)
+       styleSheet.deleteRule(0);
+   }
+   var guard = document.getElementById('content-end');
+   var body = document.body;
+   while (body.lastChild !== guard)
+     body.removeChild(body.lastChild);
+ }
  
  function nextTask() {
 +  // If there is a pdfDoc on the last task executed, destroy it to free memory.
 +  if (task && task.pdfDoc) {
 +    task.pdfDoc.destroy();
 +    delete task.pdfDoc;
 +  }
+   cleanup();
    if (currentTaskIdx == manifest.length) {
      return done();
    }
  
    log('Loading file "' + task.file + '"\n');
  
-   var r = new XMLHttpRequest();
-   r.open('GET', task.file);
-   r.mozResponseType = r.responseType = 'arraybuffer';
-   r.onreadystatechange = function() {
+   getPdf(task.file, function nextTaskGetPdf(data) {
      var failure;
-     if (r.readyState == 4) {
-       var data = r.mozResponseArrayBuffer || r.mozResponse ||
-         r.responseArrayBuffer || r.response;
-       try {
-         task.pdfDoc = new WorkerPDFDoc(data);
-         // task.pdfDoc = new PDFDoc(new Stream(data));
-       } catch (e) {
-         failure = 'load PDF doc : ' + e.toString();
-       }
-       task.pageNum = task.firstPage || 1, nextPage(task, failure);
+     try {
+       task.pdfDoc = new PDFDoc(data);
+     } catch (e) {
+       failure = 'load PDF doc : ' + e.toString();
      }
-   };
-   r.send(null);
 -    task.pageNum = 1;
++    task.pageNum = task.firstPage || 1;
+     nextPage(task, failure);
+   });
  }
  
  function isLastPage(task) {
index bbec69d3abc79d3c69ca73309e46918b4ef807e6,9d8185bb9571a4ad2d7ad29938fb6a62b8a8a04c..ace21e133a5553b1c22e875fe773a48ffc0666fb
@@@ -7,11 -7,9 +7,13 @@@
    <script type="text/javascript" src="/crypto.js"></script>
    <script type="text/javascript" src="/glyphlist.js"></script>
    <script type="text/javascript" src="/metrics.js"></script>
+   <script type="text/javascript" src="/charsets.js"></script>
+   <script type="text/javascript" src="/cidmaps.js"></script>
    <script type="text/javascript" src="driver.js"></script>
 +  <script type="text/javascript" src="../worker.js"></script>
 +  <script type="text/javascript" src="../worker/message_handler.js"></script>
 +  <script type="text/javascript" src="../worker/processor_handler.js"></script>
 +  <script type="text/javascript" src="../worker/font_handler.js"></script>
  </head>
  
  <body onload="load();">
diff --cc web/viewer.html
index 6168710e43da4343471404f3eab19cab6ef8ca79,160c5ce57a0953616744775456a3322af1b8c1b9..beab965df629b606db43db048968c5409fe1dd98
          <script type="text/javascript" src="../crypto.js"></script>
          <script type="text/javascript" src="../glyphlist.js"></script>
          <script type="text/javascript" src="../metrics.js"></script>
+         <script type="text/javascript" src="../charsets.js"></script>
+         <script type="text/javascript" src="../cidmaps.js"></script>
 +        <script type="text/javascript" src="../worker.js"></script>
 +        <script type="text/javascript" src="../worker/message_handler.js"></script>
 +        <script type="text/javascript" src="../worker/processor_handler.js"></script>
    </head>
  
    <body>
diff --cc web/viewer.js
index 3d994dfdd75a396bebcf1a6847d7f95618160a08,34a1cf847699a831dd8652f0202e0b076fd1878c..11db7cc79245ec04a9d6529f1374ecc03b9635e4
@@@ -152,15 -181,10 +181,10 @@@ var PDFView = 
      while (container.hasChildNodes())
        container.removeChild(container.lastChild);
  
-     var pdf;
-     if (true /* Use Worker */) {
-       pdf = new WorkerPDFDoc(data);
-     } else {
-       pdf = new PDFDoc(new Stream(data));
-     }
 -    var pdf = new PDFDoc(data);
++    var pdf = new WorkerPDFDoc(data);
      var pagesCount = pdf.numPages;
      document.getElementById('numPages').innerHTML = pagesCount;
+     document.getElementById('pageNumber').max = pagesCount;
  
      var pages = this.pages = [];
      var pagesRefMap = {};
diff --cc worker.js
index 0f686b42bc9be362fd418bdebcd29623a250f8e9,0000000000000000000000000000000000000000..646d9d44cdf91e01a0fac6131809b9258127838f
mode 100644,000000..100644
--- /dev/null
+++ b/worker.js
@@@ -1,419 -1,0 +1,423 @@@
 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 +
 +'use strict';
 +
 +// Set this to true if you want to use workers.
 +var useWorker = false;
 +
 +var WorkerPage = (function() {
 +  function constructor(workerPDF, page, objs) {
 +    this.workerPDF = workerPDF;
 +    this.page = page;
 +    this.objs = objs;
 +    
 +    this.ref = page.ref;
 +  }
 +  
 +  constructor.prototype = {
 +    get width() {
 +      return this.page.width;
 +    },
 +    
 +    get height() {
 +      return this.page.height;
 +    },
 +    
 +    get stats() {
 +      return this.page.stats;
 +    },
 +    
++    get view() {
++      return this.page.view;
++    },
++
 +    startRendering: function(ctx, callback, errback)  {
 +      this.ctx = ctx;
 +      this.callback = callback;
 +      // TODO: Place the worker magic HERE.
 +      // this.page.startRendering(ctx, callback, errback);
 +      
 +      this.startRenderingTime = Date.now();
 +      this.workerPDF.startRendering(this)
 +    },
 +    
 +    startRenderingFromIRQueue: function(IRQueue, fonts) {
 +      var gfx = new CanvasGraphics(this.ctx, this.objs);
 +      
 +      var startTime = Date.now();
 +      var callback = function(err) {
 +        var pageNum = this.page.pageNumber + 1;
 +        console.log("page=%d - rendering time: time=%dms", 
 +          pageNum, Date.now() - startTime);
 +        console.log("page=%d - total time: time=%dms", 
 +          pageNum, Date.now() - this.startRenderingTime);
 +
 +        this.callback(err);
 +      }.bind(this);
 +      this.page.startRenderingFromIRQueue(gfx, IRQueue, fonts, callback);
 +    },
 +    
 +    getLinks: function() {
 +      return this.page.getLinks();
 +    }
 +  };
 +  
 +  return constructor;
 +})();
 +
 +var PDFObjects = (function() {
 +  function PDFObjects() {
 +    this.objs = {};
 +  }
 +
 +  PDFObjects.prototype = {
 +    objs: null,
 +
 +    /**
 +     * Ensures there is an object defined for `objId`. Stores `data` on the
 +     * object *if* it is created.
 +     */
 +    ensureObj: function(objId, data) {
 +      if (!this.objs[objId]) {
 +        return this.objs[objId] = new Promise(objId, data);
 +      } else {
 +        return this.objs[objId];
 +      }
 +    },
 +
 +    /**
 +     * If called *without* callback, this returns the data of `objId` but the
 +     * object needs to be resolved. If it isn't, this function throws.
 +     *
 +     * If called *with* a callback, the callback is called with the data of the
 +     * object once the object is resolved. That means, if you call this 
 +     * function and the object is already resolved, the callback gets called
 +     * right away.
 +     */
 +    get: function(objId, callback) {
 +      // If there is a callback, then the get can be async and the object is 
 +      // not required to be resolved right now
 +      if (callback) {
 +        this.ensureObj(objId).then(callback);
 +      } 
 +      // If there isn't a callback, the user expects to get the resolved data
 +      // directly.
 +      else {
 +        var obj = this.objs[objId];
 +
 +        // If there isn't an object yet or the object isn't resolved, then the
 +        // data isn't ready yet!
 +        if (!obj || !obj.isResolved) {
 +          throw "Requesting object that isn't resolved yet " + objId;
 +        } 
 +        // Direct access.
 +        else {
 +          return obj.data;
 +        }
 +      }
 +    },
 +
 +    /**
 +     * Resolves the object `objId` with optional `data`.
 +     */
 +    resolve: function(objId, data) {
 +      var objs = this.objs;
 +      
 +      // In case there is a promise already on this object, just resolve it.
 +      if (objs[objId]) {
 +        objs[objId].resolve(data);
 +      } else {
 +        this.ensureObj(objId, data);
 +      }
 +    },
 +
 +    onData: function(objId, callback) {
 +      this.ensureObj(objId).onData(callback);
 +    },
 +
 +    isResolved: function(objId) {
 +      var objs = this.objs;
 +      if (!objs[objId]) {
 +        return false;
 +      } else {
 +        return objs[objId].isResolved;
 +      }
 +    },
 +
 +    hasData: function(objId) {
 +      var objs = this.objs;
 +      if (!objs[objId]) {
 +        return false;
 +      } else {
 +        return objs[objId].hasData;
 +      }
 +    },
 +
 +    /**
 +     * Sets the data of an object but *doesn't* resolve it.
 +     */
 +    setData: function(objId, data) {
 +      // Watchout! If you call `this.ensureObj(objId, data)` you'll gone create
 +      // a *resolved* promise which shouldn't be the case!
 +      this.ensureObj(objId).data = data;
 +    }
 +  }
 +  return PDFObjects;
 +})();
 +
 +
 +/**
 + * "Promise" object.
 + */
 +var Promise = (function() {
 +  var EMPTY_PROMISE = {};
 +
 +  /**
 +   * If `data` is passed in this constructor, the promise is created resolved.
 +   * If there isn't data, it isn't resolved at the beginning.
 +   */
 +  function Promise(name, data) {
 +    this.name = name;
 +    // If you build a promise and pass in some data it's already resolved.
 +    if (data != null) {
 +      this.isResolved = true;
 +      this._data = data;
 +      this.hasData = true;
 +    } else {
 +      this.isResolved = false;      
 +      this._data = EMPTY_PROMISE;
 +    }
 +    this.callbacks = [];
 +  };
 +  
 +  Promise.prototype = {
 +    hasData: false,
 +
 +    set data(data) {
 +      if (data === undefined) {
 +        return;
 +      }
 +      if (this._data !== EMPTY_PROMISE) {
 +        throw "Promise " + this.name + ": Cannot set the data of a promise twice";
 +      }
 +      this._data = data;
 +      this.hasData = true;
 +
 +      if (this.onDataCallback) {
 +        this.onDataCallback(data);
 +      }
 +    },
 +    
 +    get data() {
 +      if (this._data === EMPTY_PROMISE) {
 +        throw "Promise " + this.name + ": Cannot get data that isn't set";
 +      }
 +      return this._data;
 +    },
 +
 +    onData: function(callback) {
 +      if (this._data !== EMPTY_PROMISE) {
 +        callback(this._data);
 +      } else {
 +        this.onDataCallback = callback;
 +      }
 +    },
 +    
 +    resolve: function(data) {
 +      if (this.isResolved) {
 +        throw "A Promise can be resolved only once " + this.name;
 +      }
 +
 +      this.isResolved = true;
 +      this.data = data;
 +      var callbacks = this.callbacks;
 +      
 +      for (var i = 0; i < callbacks.length; i++) {
 +        callbacks[i].call(null, data);
 +      }
 +    },
 +    
 +    then: function(callback) {
 +      if (!callback) {
 +        throw "Requiring callback" + this.name;
 +      }
 +      
 +      // If the promise is already resolved, call the callback directly.
 +      if (this.isResolved) {
 +        var data = this.data;
 +        callback.call(null, data);
 +      } else {
 +        this.callbacks.push(callback);        
 +      }
 +    }
 +  }
 +  return Promise;
 +})();
 +
 +var WorkerPDFDoc = (function() {
 +  function constructor(data) {
 +    
 +    this.data = data;
 +    this.stream = new Stream(data);
 +    this.pdf = new PDFDoc(this.stream);
 +    
 +    this.catalog = this.pdf.catalog;
 +    this.objs = new PDFObjects();
 +    
 +    this.pageCache = [];
 +    
 +    if (useWorker) {
 +      var worker = this.worker = new Worker("../worker/pdf_worker_loader.js");
 +    } else {
 +      // If we don't use a worker, just post/sendMessage to the main thread.
 +      var worker = {
 +        postMessage: function(obj) {
 +          worker.onmessage({data: obj});
 +        }
 +      }
 +    }
 +    
 +    this.fontsLoading = {};
 +
 +    var processorHandler = this.processorHandler = new MessageHandler("main", worker);
 +    processorHandler.on("page", function(data) {
 +      var pageNum = data.pageNum;
 +      var page = this.pageCache[pageNum];
 +     
 +      // DepFonts are all fonts are required to render the page. `fontsToLoad`
 +      // are all the fonts that are required to render the page AND that
 +      // aren't loaded on the page yet.
 +      var depFonts = data.depFonts;
 +      var objs = this.objs;
 +      var fontsToLoad = [];
 +      var fontsLoading = this.fontsLoading;
 +      // The `i` for the checkFontData is stored here to keep the state in
 +      // the closure.
 +      var i = 0;  
 +                  
 +
 +      function checkFontData() {
 +        // Check if all fontObjs have been processed. If not, shedule a
 +        // callback that is called once the data arrives and that checks
 +        // the next fonts.
 +        for (i; i < depFonts.length; i++) {
 +          var fontName = depFonts[i];
 +          if (!objs.hasData(fontName)) {
 +            console.log('need to wait for fontData', fontName);
 +            objs.onData(fontName, checkFontData);
 +            return;
 +          } else if (!objs.isResolved(fontName)) {
 +            fontsToLoad.push(fontName);
 +          }
 +        }
 +        
 +        // There can be edge cases where two pages wait for one font and then
 +        // call startRenderingFromIRQueue twice with the same font. That makes
 +        // the font getting loaded twice and throw an error later as the font
 +        // promise gets resolved twice.
 +        // This prevents thats fonts are loaded really only once.
 +        for (var j = 0; j < fontsToLoad.length; j++) {
 +          var fontName = fontsToLoad[j];
 +          if (fontsLoading[fontName]) {
 +            fontsToLoad.splice(j, 1);
 +            j--;
 +          } else {
 +            fontsLoading[fontName] = true;
 +          }
 +        }
 +
 +        // At this point, all font data ia loaded. Start the actuall rendering.
 +        page.startRenderingFromIRQueue(data.IRQueue, fontsToLoad);
 +      }
 +
 +      checkFontData();
 +    }, this);
 +
 +    processorHandler.on("obj", function(data) {
 +      var objId   = data[0];
 +      var objType = data[1];
 +
 +      switch (objType) {
 +        case "JpegStream":
 +          var IR = data[2];
 +          new JpegStreamIR(objId, IR, this.objs);
 +          console.log('got image');
 +        break;
 +        case "Font":
 +          var name = data[2];
 +          var file = data[3];
 +          var properties = data[4];
 +
 +          processorHandler.send("font", [objId, name, file, properties]);
 +        break;
 +        default:
 +          throw "Got unkown object type " + objType;
 +      }
 +    }, this);
 +
 +    processorHandler.on('font_ready', function(data) {
 +      var objId   = data[0];
 +      var fontObj = new FontShape(data[1]);
 +
 +      console.log('got fontData', objId);
 +
 +      // If there is no string, then there is nothing to attach to the DOM.
 +      if (!fontObj.str) {
 +        this.objs.resolve(objId, fontObj);
 +      } else {
 +        this.objs.setData(objId, fontObj);
 +      }
 +    }.bind(this));
 +    
 +    if (!useWorker) {
 +      // If the main thread is our worker, setup the handling for the messages
 +      // the main thread sends to it self.
 +      WorkerProcessorHandler.setup(processorHandler);
 +    }
 +    
 +    processorHandler.send("doc", data);
 +  }
 +
 +  constructor.prototype = {
 +    get numPages() {
 +      return this.pdf.numPages;
 +    },
 +    
 +    startRendering: function(page) {
 +      this.processorHandler.send("page_request", page.page.pageNumber + 1);
 +    },
 +    
 +    getPage: function(n) {
 +      if (this.pageCache[n]) {
 +        return this.pageCache[n];
 +      }
 +      
 +      var page = this.pdf.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;
 +      return this.pageCache[n] = new WorkerPage(this, page, this.objs);
 +    },
 +    
 +    destroy: function() {
 +      console.log("destroy worker");
 +      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 constructor;
 +})();
Simple merge