]> git.parisson.com Git - pdf.js.git/commitdiff
Merge upstream.
authorRob Sayre <sayrer@gmail.com>
Wed, 6 Jul 2011 06:23:12 +0000 (23:23 -0700)
committerRob Sayre <sayrer@gmail.com>
Wed, 6 Jul 2011 06:23:12 +0000 (23:23 -0700)
1  2 
pdf.js
worker/client.js

diff --cc pdf.js
index 7d8e432a2adc927f0715f829fde7b737032cef53,132d16ec1e7a854cb73bcee7b21c287ca8ac94b8..620e98f169acba2179fe2dc76a6ef9666baebdee
--- 1/pdf.js
--- 2/pdf.js
+++ b/pdf.js
@@@ -3346,1089 -3294,1085 +3346,1089 @@@ function ScratchCanvas(width, height) 
  }
  
  var CanvasGraphics = (function() {
 -    function constructor(canvasCtx, imageCanvas) {
 -        this.ctx = canvasCtx;
 -        this.current = new CanvasExtraState();
 -        this.stateStack = [ ];
 -        this.pendingClip = null;
 -        this.res = null;
 -        this.xobjs = null;
 -        this.ScratchCanvas = imageCanvas || ScratchCanvas;
 -    }
 +  function constructor(canvasCtx, imageCanvas) {
 +    this.ctx = canvasCtx;
 +    this.current = new CanvasExtraState();
 +    this.stateStack = [];
 +    this.pendingClip = null;
 +    this.res = null;
 +    this.xobjs = null;
 +    this.ScratchCanvas = imageCanvas || ScratchCanvas;
 +  }
  
 -    var LINE_CAP_STYLES = [ "butt", "round", "square" ];
 -    var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ];
 -    var NORMAL_CLIP = {};
 -    var EO_CLIP = {};
 -
 -    // Used for tiling patterns
 -    var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
 -
 -    constructor.prototype = {
 -        map: {
 -            // Graphics state
 -            w: "setLineWidth",
 -            J: "setLineCap",
 -            j: "setLineJoin",
 -            M: "setMiterLimit",
 -            d: "setDash",
 -            ri: "setRenderingIntent",
 -            i: "setFlatness",
 -            gs: "setGState",
 -            q: "save",
 -            Q: "restore",
 -            cm: "transform",
 -
 -            // Path
 -            m: "moveTo",
 -            l: "lineTo",
 -            c: "curveTo",
 -            v: "curveTo2",
 -            y: "curveTo3",
 -            h: "closePath",
 -            re: "rectangle",
 -            S: "stroke",
 -            s: "closeStroke",
 -            f: "fill",
 -            F: "fill",
 -            "f*": "eoFill",
 -            B: "fillStroke",
 -            "B*": "eoFillStroke",
 -            b: "closeFillStroke",
 -            "b*": "closeEOFillStroke",
 -            n: "endPath",
 -
 -            // Clipping
 -            W: "clip",
 -            "W*": "eoClip",
 -
 -            // Text
 -            BT: "beginText",
 -            ET: "endText",
 -            Tc: "setCharSpacing",
 -            Tw: "setWordSpacing",
 -            Tz: "setHScale",
 -            TL: "setLeading",
 -            Tf: "setFont",
 -            Tr: "setTextRenderingMode",
 -            Ts: "setTextRise",
 -            Td: "moveText",
 -            TD: "setLeadingMoveText",
 -            Tm: "setTextMatrix",
 -            "T*": "nextLine",
 -            Tj: "showText",
 -            TJ: "showSpacedText",
 -            "'": "nextLineShowText",
 -            '"': "nextLineSetSpacingShowText",
 -
 -            // Type3 fonts
 -            d0: "setCharWidth",
 -            d1: "setCharWidthAndBounds",
 -
 -            // Color
 -            CS: "setStrokeColorSpace",
 -            cs: "setFillColorSpace",
 -            SC: "setStrokeColor",
 -            SCN: "setStrokeColorN",
 -            sc: "setFillColor",
 -            scn: "setFillColorN",
 -            G: "setStrokeGray",
 -            g: "setFillGray",
 -            RG: "setStrokeRGBColor",
 -            rg: "setFillRGBColor",
 -            K: "setStrokeCMYKColor",
 -            k: "setFillCMYKColor",
 -
 -            // Shading
 -            sh: "shadingFill",
 -
 -            // Images
 -            BI: "beginInlineImage",
 -
 -            // XObjects
 -            Do: "paintXObject",
 -
 -            // Marked content
 -            MP: "markPoint",
 -            DP: "markPointProps",
 -            BMC: "beginMarkedContent",
 -            BDC: "beginMarkedContentProps",
 -            EMC: "endMarkedContent",
 -
 -            // Compatibility
 -            BX: "beginCompat",
 -            EX: "endCompat",
 -        },
 -
 -        translateFont: function(fontDict, xref, resources) {
 -            var fd = fontDict.get("FontDescriptor");
 -            if (!fd)
 -                // XXX deprecated "special treatment" for standard
 -                // fonts?  What do we need to do here?
 -                return null;
 -            var descriptor = xref.fetch(fd);
 -
 -            var fontName = descriptor.get("FontName");
 -            assertWellFormed(IsName(fontName), "invalid font name");
 -            fontName = fontName.name.replace("+", "_");
 -
 -            var fontFile = descriptor.get3("FontFile", "FontFile2", "FontFile3");
 -            if (!fontFile)
 -                error("FontFile not found for font: " + fontName);
 -            fontFile = xref.fetchIfRef(fontFile);
 -
 -            var encodingMap = {};
 -            var charset = [];
 -            if (fontDict.has("Encoding")) {
 -                var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
 -                if (IsDict(encoding)) {
 -                    // Build a map between codes and glyphs
 -                    var differences = encoding.get("Differences");
 -                    var index = 0;
 -                    for (var j = 0; j < differences.length; j++) {
 -                        var data = differences[j];
 -                        IsNum(data) ? index = data : encodingMap[index++] = data;
 -                    }
 -
 -                    // Get the font charset if any
 -                    var charset = descriptor.get("CharSet");
 -                    if (charset) {
 -                        assertWellFormed(IsString(charset), "invalid charset");
 -                        charset = charset.split("/");
 -                        charset.shift();
 -                    }
 -                } else if (IsName(encoding)) {
 -                    var encoding = Encodings[encoding.name];
 -                    if (!encoding)
 -                        error("Unknown font encoding");
 -
 -                    var index = 0;
 -                    for (var j = 0; j < encoding.length; j++)
 -                        encodingMap[index++] = GlyphsUnicode[encoding[j]];
 -
 -                    var firstChar = xref.fetchIfRef(fontDict.get("FirstChar"));
 -                    var widths = xref.fetchIfRef(fontDict.get("Widths"));
 -                    assertWellFormed(IsArray(widths) && IsInt(firstChar),
 -                                     "invalid font Widths or FirstChar");
 -
 -                    for (var j = 0; j < widths.length; j++) {
 -                        if (widths[j])
 -                            charset.push(encoding[j + firstChar]);
 -                    }
 -                }
 -            } else if (fontDict.has("ToUnicode")) {
 -                encodingMap = {empty: true};
 -                var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode"));
 -                if (IsName(cmapObj)) {
 -                    error("ToUnicode file cmap translation not implemented");
 -                } else if (IsStream(cmapObj)) {
 -                    var encoding = Encodings["WinAnsiEncoding"];
 -                    var firstChar = xref.fetchIfRef(fontDict.get("FirstChar"));
 -
 -                    var tokens = [];
 -                    var token = "";
 -
 -                    var cmap = cmapObj.getBytes(cmapObj.length);
 -                    for (var i =0; i < cmap.length; i++) {
 -                      var byte = cmap[i];
 -                      if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
 -                        switch (token) {
 -                          case "useCMap":
 -                            error("useCMap is not implemented");
 -                            break;
 -
 -                          case "beginbfrange":
 -                          case "begincodespacerange":
 -                            token = "";
 -                            tokens = [];
 -                            break;
 -
 -                          case "endcodespacerange":
 -                            TODO("Support CMap ranges");
 -                            break;
 -
 -                          case "endbfrange":
 -                            for (var j = 0; j < tokens.length; j+=3) {
 -                              var startRange = parseInt("0x" + tokens[j]);
 -                              var endRange = parseInt("0x" + tokens[j+1]);
 -                              var code = parseInt("0x" + tokens[j+2]);
 -
 -                              for (var k = startRange; k <= endRange; k++) {
 -                                // The encoding mapping table will be filled
 -                                // later during the building phase
 -                                //encodingMap[k] = GlyphsUnicode[encoding[code]];
 -                                charset.push(encoding[code++] || ".notdef");
 -                              }
 -                            }
 -                            break;
 -
 -                          case "beginfbchar":
 -                          case "endfbchar":
 -                            error("fbchar parsing is not implemented");
 -                            break;
 -
 -                          default:
 -                            if (token.length) {
 -                              tokens.push(token);
 -                              token = "";
 -                            }
 -                            break;
 -                        }
 -                    } else if (byte == 0x5B || byte == 0x5D) {
 -                        error("CMAP list parsing is not implemented");
 -                    } else {
 -                        token += String.fromCharCode(byte);
 -                    }
 -                  }
 -               }
 -            }
 +  var LINE_CAP_STYLES = ['butt', 'round', 'square'];
 +  var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
 +  var NORMAL_CLIP = {};
 +  var EO_CLIP = {};
 +
 +  // Used for tiling patterns
 +  var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
 +
 +  constructor.prototype = {
 +    map: {
 +      // Graphics state
 +      w: 'setLineWidth',
 +      J: 'setLineCap',
 +      j: 'setLineJoin',
 +      M: 'setMiterLimit',
 +      d: 'setDash',
 +      ri: 'setRenderingIntent',
 +      i: 'setFlatness',
 +      gs: 'setGState',
 +      q: 'save',
 +      Q: 'restore',
 +      cm: 'transform',
 +
 +      // Path
 +      m: 'moveTo',
 +      l: 'lineTo',
 +      c: 'curveTo',
 +      v: 'curveTo2',
 +      y: 'curveTo3',
 +      h: 'closePath',
 +      re: 'rectangle',
 +      S: 'stroke',
 +      s: 'closeStroke',
 +      f: 'fill',
 +      F: 'fill',
 +      'f*': 'eoFill',
 +      B: 'fillStroke',
 +      'B*': 'eoFillStroke',
 +      b: 'closeFillStroke',
 +      'b*': 'closeEOFillStroke',
 +      n: 'endPath',
 +
 +      // Clipping
 +      W: 'clip',
 +      'W*': 'eoClip',
 +
 +      // Text
 +      BT: 'beginText',
 +      ET: 'endText',
 +      Tc: 'setCharSpacing',
 +      Tw: 'setWordSpacing',
 +      Tz: 'setHScale',
 +      TL: 'setLeading',
 +      Tf: 'setFont',
 +      Tr: 'setTextRenderingMode',
 +      Ts: 'setTextRise',
 +      Td: 'moveText',
 +      TD: 'setLeadingMoveText',
 +      Tm: 'setTextMatrix',
 +      'T*': 'nextLine',
 +      Tj: 'showText',
 +      TJ: 'showSpacedText',
 +      "'": 'nextLineShowText',
 +      '"': 'nextLineSetSpacingShowText',
 +
 +      // Type3 fonts
 +      d0: 'setCharWidth',
 +      d1: 'setCharWidthAndBounds',
 +
 +      // Color
 +      CS: 'setStrokeColorSpace',
 +      cs: 'setFillColorSpace',
 +      SC: 'setStrokeColor',
 +      SCN: 'setStrokeColorN',
 +      sc: 'setFillColor',
 +      scn: 'setFillColorN',
 +      G: 'setStrokeGray',
 +      g: 'setFillGray',
 +      RG: 'setStrokeRGBColor',
 +      rg: 'setFillRGBColor',
 +      K: 'setStrokeCMYKColor',
 +      k: 'setFillCMYKColor',
 +
 +      // Shading
 +      sh: 'shadingFill',
 +
 +      // Images
 +      BI: 'beginInlineImage',
 +
 +      // XObjects
 +      Do: 'paintXObject',
 +
 +      // Marked content
 +      MP: 'markPoint',
 +      DP: 'markPointProps',
 +      BMC: 'beginMarkedContent',
 +      BDC: 'beginMarkedContentProps',
 +      EMC: 'endMarkedContent',
 +
 +      // Compatibility
 +      BX: 'beginCompat',
 +      EX: 'endCompat'
 +    },
 +
 +    translateFont: function(fontDict, xref, resources) {
 +      var fd = fontDict.get('FontDescriptor');
 +      if (!fd)
 +        // XXX deprecated "special treatment" for standard
 +        // fonts?  What do we need to do here?
 +        return null;
 +      var descriptor = xref.fetch(fd);
 +
 +      var fontName = descriptor.get('FontName');
 +      assertWellFormed(IsName(fontName), 'invalid font name');
 +      fontName = fontName.name.replace('+', '_');
 +
 +      var fontFile = descriptor.get3('FontFile', 'FontFile2', 'FontFile3');
 +      if (!fontFile)
 +        error('FontFile not found for font: ' + fontName);
 +      fontFile = xref.fetchIfRef(fontFile);
 +
 +      var encodingMap = {};
 +      var charset = [];
 +      if (fontDict.has('Encoding')) {
 +        var encoding = xref.fetchIfRef(fontDict.get('Encoding'));
 +        if (IsDict(encoding)) {
 +          // Build a map between codes and glyphs
 +          var differences = encoding.get('Differences');
 +          var index = 0;
 +          for (var j = 0; j < differences.length; j++) {
 +            var data = differences[j];
 +            IsNum(data) ? index = data : encodingMap[index++] = data;
 +          }
 +
 +          // Get the font charset if any
 +          var charset = descriptor.get('CharSet');
 +          if (charset) {
 +            assertWellFormed(IsString(charset), 'invalid charset');
 +            charset = charset.split('/');
 +            charset.shift();
 +          }
 +        } else if (IsName(encoding)) {
 +          var encoding = Encodings[encoding.name];
 +          if (!encoding)
 +            error('Unknown font encoding');
 +
 +          var index = 0;
 +          for (var j = 0; j < encoding.length; j++)
 +            encodingMap[index++] = GlyphsUnicode[encoding[j]];
 +
 +          var firstChar = xref.fetchIfRef(fontDict.get('FirstChar'));
 +          var widths = xref.fetchIfRef(fontDict.get('Widths'));
 +          assertWellFormed(IsArray(widths) && IsInt(firstChar),
 +                           'invalid font Widths or FirstChar');
 +
 +          for (var j = 0; j < widths.length; j++) {
 +            if (widths[j])
 +              charset.push(encoding[j + firstChar]);
 +          }
 +        }
 +      } else if (fontDict.has('ToUnicode')) {
 +        encodingMap = {empty: true};
 +        var cmapObj = xref.fetchIfRef(fontDict.get('ToUnicode'));
 +        if (IsName(cmapObj)) {
 +          error('ToUnicode file cmap translation not implemented');
 +        } else if (IsStream(cmapObj)) {
 +          var encoding = Encodings['WinAnsiEncoding'];
 +          var firstChar = xref.fetchIfRef(fontDict.get('FirstChar'));
 +
 +          var tokens = [];
 +          var token = '';
 +
 +          var cmap = cmapObj.getBytes(cmapObj.length);
 +          for (var i = 0; i < cmap.length; i++) {
 +            var byte = cmap[i];
 +            if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
 +              switch (token) {
 +              case 'useCMap':
 +                error('useCMap is not implemented');
 +                break;
  
 -            var subType = fontDict.get("Subtype");
 -            assertWellFormed(IsName(subType), "invalid font Subtype");
 -
 -            var properties = {
 -                type: subType.name,
 -                encoding: encodingMap,
 -                charset: charset,
 -                firstChar: fontDict.get("FirstChar"),
 -                lastChar: fontDict.get("LastChar"),
 -                bbox: descriptor.get("FontBBox"),
 -                ascent: descriptor.get("Ascent"),
 -                descent: descriptor.get("Descent"),
 -                xHeight: descriptor.get("XHeight"),
 -                capHeight: descriptor.get("CapHeight"),
 -                flags: descriptor.get("Flags"),
 -                italicAngle: descriptor.get("ItalicAngle"),
 -                fixedPitch: false,
 -                textMatrix: IDENTITY_MATRIX
 -            };
 -
 -            return {
 -                name: fontName,
 -                file: fontFile,
 -                properties: properties
 -            }
 -        },
 -
 -        beginDrawing: function(mediaBox) {
 -            var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
 -            this.ctx.save();
 -            this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height);
 -            this.ctx.translate(0, -mediaBox.height);
 -        },
 -
 -        execute: function(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();
 -
 -            code(this);
 -
 -            this.xobjs = savedXobjs;
 -            this.res = savedRes;
 -            this.xref = savedXref;
 -        },
 -
 -        compile: function(stream, xref, resources, fonts) {
 -            resources = xref.fetchIfRef(resources) || new Dict();
 -            var xobjs = xref.fetchIfRef(resources.get("XObject")) || new Dict();
 -
 -            var parser = new Parser(new Lexer(stream), false);
 -            var objpool = [];
 -
 -            function emitArg(arg) {
 -                if (typeof arg == "object" || typeof arg == "string") {
 -                    var index = objpool.length;
 -                    objpool[index] = arg;
 -                    return "objpool[" + index + "]";
 +              case 'beginbfrange':
 +              case 'begincodespacerange':
 +                token = '';
 +                tokens = [];
 +                break;
 +
 +              case 'endcodespacerange':
 +                TODO('Support CMap ranges');
 +                break;
 +
 +              case 'endbfrange':
 +                for (var j = 0; j < tokens.length; j += 3) {
 +                  var startRange = parseInt('0x' + tokens[j]);
 +                  var endRange = parseInt('0x' + tokens[j + 1]);
 +                  var code = parseInt('0x' + tokens[j + 2]);
 +
 +                  for (var k = startRange; k <= endRange; k++) {
 +                    // The encoding mapping table will be filled
 +                    // later during the building phase
 +                    //encodingMap[k] = GlyphsUnicode[encoding[code]];
 +                    charset.push(encoding[code++] || '.notdef');
 +                  }
                  }
 -                return arg;
 -            }
 +                break;
 +
 +              case 'beginfbchar':
 +              case 'endfbchar':
 +                error('fbchar parsing is not implemented');
 +                break;
  
 -            var src = "";
 -
 -            var args = [];
 -            var map = this.map;
 -            var obj;
 -            while (!IsEOF(obj = parser.getObj())) {
 -                if (IsCmd(obj)) {
 -                    var cmd = obj.cmd;
 -                    var fn = map[cmd];
 -                    assertWellFormed(fn, "Unknown command '" + cmd + "'");
 -                    // TODO figure out how to type-check vararg functions
 -
 -                    if (cmd == "Do" && !args[0].code) { // eagerly compile XForm objects
 -                        var name = args[0].name;
 -                        var xobj = xobjs.get(name);
 -                        if (xobj) {
 -                            xobj = xref.fetchIfRef(xobj);
 -                            assertWellFormed(IsStream(xobj), "XObject should be a stream");
 -
 -                            var type = xobj.dict.get("Subtype");
 -                            assertWellFormed(IsName(type), "XObject should have a Name subtype");
 -
 -                            if ("Form" == type.name) {
 -                                args[0].code = this.compile(xobj,
 -                                                            xref,
 -                                                            xobj.dict.get("Resources"),
 -                                                            fonts);
 -                            }
 -                        }
 -                    } else if (cmd == "Tf") { // eagerly collect all fonts
 -                        var fontRes = resources.get("Font");
 -                        if (fontRes) {
 -                            fontRes = xref.fetchIfRef(fontRes);
 -                            var font = xref.fetchIfRef(fontRes.get(args[0].name));
 -                            assertWellFormed(IsDict(font));
 -                            if (!font.translated) {
 -                                font.translated = this.translateFont(font, xref, resources);
 -                                if (fonts && 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);
 -                                }
 -                            }
 -                        }
 -                    }
 -
 -                    src += "this.";
 -                    src += fn;
 -                    src += "(";
 -                    src += args.map(emitArg).join(",");
 -                    src += ");\n";
 -
 -                    args.length = 0;
 -                } else {
 -                    assertWellFormed(args.length <= 33, "Too many arguments");
 -                    args.push(obj);
 +              default:
 +                if (token.length) {
 +                  tokens.push(token);
 +                  token = '';
                  }
 +                break;
 +              }
 +            } else if (byte == 0x5B || byte == 0x5D) {
 +              error('CMAP list parsing is not implemented');
 +            } else {
 +              token += String.fromCharCode(byte);
              }
 +          }
 +        }
 +      }
  
 -            var fn = Function("objpool", src);
 -            return function (gfx) { fn.call(gfx, objpool); };
 -        },
 -
 -        endDrawing: function() {
 -            this.ctx.restore();
 -        },
 -
 -        // Graphics state
 -        setLineWidth: function(width) {
 -            this.ctx.lineWidth = width;
 -        },
 -        setLineCap: function(style) {
 -            this.ctx.lineCap = LINE_CAP_STYLES[style];
 -        },
 -        setLineJoin: function(style) {
 -            this.ctx.lineJoin = LINE_JOIN_STYLES[style];
 -        },
 -        setMiterLimit: function(limit) {
 -            this.ctx.miterLimit = limit;
 -        },
 -        setDash: function(dashArray, dashPhase) {
 -            this.ctx.mozDash = dashArray;
 -            this.ctx.mozDashOffset = dashPhase;
 -        },
 -        setRenderingIntent: function(intent) {
 -            TODO("set rendering intent");
 -        },
 -        setFlatness: function(flatness) {
 -            TODO("set flatness");
 -        },
 -        setGState: function(dictName) {
 -            TODO("set graphics state from dict");
 -        },
 -        save: function() {
 -            this.ctx.save();
 -            if (this.ctx.$saveCurrentX) {
 -                this.ctx.$saveCurrentX();
 +      var subType = fontDict.get('Subtype');
 +      assertWellFormed(IsName(subType), 'invalid font Subtype');
 +
 +      var properties = {
 +        type: subType.name,
 +        encoding: encodingMap,
 +        charset: charset,
 +        firstChar: fontDict.get('FirstChar'),
 +        lastChar: fontDict.get('LastChar'),
 +        bbox: descriptor.get('FontBBox'),
 +        ascent: descriptor.get('Ascent'),
 +        descent: descriptor.get('Descent'),
 +        xHeight: descriptor.get('XHeight'),
 +        capHeight: descriptor.get('CapHeight'),
 +        flags: descriptor.get('Flags'),
 +        italicAngle: descriptor.get('ItalicAngle'),
 +        fixedPitch: false,
 +        textMatrix: IDENTITY_MATRIX
 +      };
 +
 +      return {
 +        name: fontName,
 +          file: fontFile,
 +          properties: properties
 +          };
 +    },
 +
 +    beginDrawing: function(mediaBox) {
 +      var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
 +      this.ctx.save();
 +      this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height);
 +      this.ctx.translate(0, -mediaBox.height);
 +    },
 +
 +    execute: function(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();
 +
 +      code(this);
 +
 +      this.xobjs = savedXobjs;
 +      this.res = savedRes;
 +      this.xref = savedXref;
 +    },
 +
 +    compile: function(stream, xref, resources, fonts) {
 +      resources = xref.fetchIfRef(resources) || new Dict();
 +      var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict();
 +
 +      var parser = new Parser(new Lexer(stream), false);
 +      var objpool = [];
 +
 +      function emitArg(arg) {
 +        if (typeof arg == 'object' || typeof arg == 'string') {
 +          var index = objpool.length;
 +          objpool[index] = arg;
 +          return 'objpool[' + index + ']';
 +        }
 +        return arg;
 +      }
 +
 +      var src = '';
 +
 +      var args = [];
 +      var map = this.map;
 +      var obj;
 +      while (!IsEOF(obj = parser.getObj())) {
 +        if (IsCmd(obj)) {
 +          var cmd = obj.cmd;
 +          var fn = map[cmd];
 +          assertWellFormed(fn, "Unknown command '" + cmd + "'");
 +          // TODO figure out how to type-check vararg functions
 +
 +          if (cmd == 'Do' && !args[0].code) { // eagerly compile XForm objects
 +            var name = args[0].name;
 +            var xobj = xobjs.get(name);
 +            if (xobj) {
 +              xobj = xref.fetchIfRef(xobj);
 +              assertWellFormed(IsStream(xobj), 'XObject should be a stream');
 +
 +              var type = xobj.dict.get('Subtype');
 +              assertWellFormed(
 +                IsName(type),
 +                'XObject should have a Name subtype'
 +              );
 +
 +              if ('Form' == type.name) {
 +                args[0].code = this.compile(xobj,
 +                                            xref,
 +                                            xobj.dict.get('Resources'),
 +                                            fonts);
 +              }
              }
 -            this.stateStack.push(this.current);
 -            this.current = new CanvasExtraState();
 -        },
 -        restore: function() {
 -            var prev = this.stateStack.pop();
 -            if (prev) {
 -                if (this.ctx.$restoreCurrentX) {
 -                    this.ctx.$restoreCurrentX();
 +          } else if (cmd == 'Tf') { // eagerly collect all fonts
 +            var fontRes = resources.get('Font');
 +            if (fontRes) {
 +              fontRes = xref.fetchIfRef(fontRes);
 +              var font = xref.fetchIfRef(fontRes.get(args[0].name));
 +              assertWellFormed(IsDict(font));
 +              if (!font.translated) {
 +                font.translated = this.translateFont(font, xref, resources);
 +                if (fonts && 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);
                  }
 -                this.current = prev;
 -                this.ctx.restore();
 -            }
 -        },
 -        transform: function(a, b, c, d, e, f) {
 -            this.ctx.transform(a, b, c, d, e, f);
 -        },
 -
 -        // Path
 -        moveTo: function(x, y) {
 -            this.ctx.moveTo(x, y);
 -        },
 -        lineTo: function(x, y) {
 -            this.ctx.lineTo(x, y);
 -        },
 -        curveTo: function(x1, y1, x2, y2, x3, y3) {
 -            this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
 -        },
 -        curveTo2: function(x2, y2, x3, y3) {
 -            TODO("'v' operator: need current point in gfx context");
 -        },
 -        curveTo3: function(x1, y1, x3, y3) {
 -            this.curveTo(x1, y1, x3, y3, x3, y3);
 -        },
 -        closePath: function() {
 -            this.ctx.closePath();
 -        },
 -        rectangle: function(x, y, width, height) {
 -            this.ctx.rect(x, y, width, height);
 -        },
 -        stroke: function() {
 -            this.ctx.stroke();
 -            this.consumePath();
 -        },
 -        closeStroke: function() {
 -            this.closePath();
 -            this.stroke();
 -        },
 -        fill: function() {
 -            this.ctx.fill();
 -            this.consumePath();
 -        },
 -        eoFill: function() {
 -            var savedFillRule = this.setEOFillRule();
 -            this.fill();
 -            this.restoreFillRule(savedFillRule);
 -        },
 -        fillStroke: function() {
 -            this.ctx.fill();
 -            this.ctx.stroke();
 -            this.consumePath();
 -        },
 -        eoFillStroke: function() {
 -            var savedFillRule = this.setEOFillRule();
 -            this.fillStroke();
 -            this.restoreFillRule(savedFillRule);
 -        },
 -        closeFillStroke: function() {
 -            return this.fillStroke();
 -        },
 -        closeEOFillStroke: function() {
 -            var savedFillRule = this.setEOFillRule();
 -            this.fillStroke();
 -            this.restoreFillRule(savedFillRule);
 -        },
 -        endPath: function() {
 -            this.consumePath();
 -        },
 -
 -        // Clipping
 -        clip: function() {
 -            this.pendingClip = NORMAL_CLIP;
 -        },
 -        eoClip: function() {
 -            this.pendingClip = EO_CLIP;
 -        },
 -
 -        // Text
 -        beginText: function() {
 -            this.current.textMatrix = IDENTITY_MATRIX;
 -            if (this.ctx.$setCurrentX) {
 -                this.ctx.$setCurrentX(0)
 -            }
 -            this.current.x = this.current.lineX = 0;
 -            this.current.y = this.current.lineY = 0;
 -        },
 -        endText: function() {
 -        },
 -        setCharSpacing: function(spacing) {
 -            this.ctx.charSpacing = spacing;
 -        },
 -        setWordSpacing: function(spacing) {
 -            this.ctx.wordSpacing = spacing;
 -        },
 -        setHScale: function(scale) {
 -            this.ctx.textHScale = (scale % 100) * 0.01;
 -        },
 -        setLeading: function(leading) {
 -            this.current.leading = leading;
 -        },
 -        setFont: function(fontRef, size) {
 -            var font = this.xref.fetchIfRef(this.res.get("Font"));
 -            if (!IsDict(font))
 -              return;
 -
 -            font = font.get(fontRef.name);
 -            font = this.xref.fetchIfRef(font);
 -            if (!font)
 -                return;
 -
 -            var fontName = "";
 -            var fontDescriptor = font.get("FontDescriptor");
 -            if (fontDescriptor && fontDescriptor.num) {
 -                var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
 -                fontName = fontDescriptor.get("FontName").name.replace("+", "_");
 +              }
              }
 +          }
  
 -            if (!fontName) {
 -                // TODO: fontDescriptor is not available, fallback to default font
 -                fontName = "sans-serif";
 -            }
 +          src += 'this.';
 +          src += fn;
 +          src += '(';
 +          src += args.map(emitArg).join(',');
 +          src += ');\n';
  
 -            this.current.fontName = fontName;
 -            this.current.fontSize = size;
 +          args.length = 0;
 +        } else {
 +          assertWellFormed(args.length <= 33, 'Too many arguments');
 +          args.push(obj);
 +        }
 +      }
  
 -            if (this.ctx.$setFont) {
 -              this.ctx.$setFont(fontName, size);
 -            } else {
 -              this.ctx.font = size + 'px "' + fontName + '"';
 -              Fonts.setActive(fontName, size);
 -            }
 -        },
 -        setTextRenderingMode: function(mode) {
 -            TODO("text rendering mode");
 -        },
 -        setTextRise: function(rise) {
 -            TODO("text rise");
 -        },
 -        moveText: function (x, y) {
 -            this.current.x = this.current.lineX += x;
 -            this.current.y = this.current.lineY += y;
 -            if (this.ctx.$setCurrentX) {
 -                this.ctx.$setCurrentX(this.current.x)
 -            }
 -        },
 -        setLeadingMoveText: function(x, y) {
 -            this.setLeading(-y);
 -            this.moveText(x, y);
 -        },
 -        setTextMatrix: function(a, b, c, d, e, f) {
 -            this.current.textMatrix = [ a, b, c, d, e, f ];
 -
 -            if (this.ctx.$setCurrentX) {
 -                this.ctx.$setCurrentX(0)
 -            }
 -            this.current.x = this.current.lineX = 0;
 -            this.current.y = this.current.lineY = 0;
 -        },
 -        nextLine: function() {
 -            this.moveText(0, this.current.leading);
 -        },
 -        showText: function(text) {
 -            // TODO: apply charSpacing, wordSpacing, textHScale
 -
 -            this.ctx.save();
 -            this.ctx.transform.apply(this.ctx, this.current.textMatrix);
 -            this.ctx.scale(1, -1);
 -
 -            if (this.ctx.$showText) {
 -                this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
 -            } else {
 -                text = Fonts.charsToUnicode(text);
 -                this.ctx.translate(this.current.x, -1 * this.current.y);
 +      var fn = Function('objpool', src);
 +      return function(gfx) { fn.call(gfx, objpool); };
 +    },
 +
 +    endDrawing: function() {
 +      this.ctx.restore();
 +    },
 +
 +    // Graphics state
 +    setLineWidth: function(width) {
 +      this.ctx.lineWidth = width;
 +    },
 +    setLineCap: function(style) {
 +      this.ctx.lineCap = LINE_CAP_STYLES[style];
 +    },
 +    setLineJoin: function(style) {
 +      this.ctx.lineJoin = LINE_JOIN_STYLES[style];
 +    },
 +    setMiterLimit: function(limit) {
 +      this.ctx.miterLimit = limit;
 +    },
 +    setDash: function(dashArray, dashPhase) {
 +      this.ctx.mozDash = dashArray;
 +      this.ctx.mozDashOffset = dashPhase;
 +    },
 +    setRenderingIntent: function(intent) {
 +      TODO('set rendering intent');
 +    },
 +    setFlatness: function(flatness) {
 +      TODO('set flatness');
 +    },
 +    setGState: function(dictName) {
 +      TODO('set graphics state from dict');
 +    },
 +    save: function() {
 +      this.ctx.save();
 +      if (this.ctx.$saveCurrentX) {
 +        this.ctx.$saveCurrentX();
 +      }
 +      this.stateStack.push(this.current);
 +      this.current = new CanvasExtraState();
 +    },
 +    restore: function() {
 +      var prev = this.stateStack.pop();
 +      if (prev) {
 +        if (this.ctx.$restoreCurrentX) {
 +          this.ctx.$restoreCurrentX();
 +        }
 +        this.current = prev;
 +        this.ctx.restore();
 +      }
 +    },
 +    transform: function(a, b, c, d, e, f) {
 +      this.ctx.transform(a, b, c, d, e, f);
 +    },
++    
 +    // Path
 +    moveTo: function(x, y) {
 +      this.ctx.moveTo(x, y);
 +    },
 +    lineTo: function(x, y) {
 +      this.ctx.lineTo(x, y);
 +    },
 +    curveTo: function(x1, y1, x2, y2, x3, y3) {
 +      this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
 +    },
 +    curveTo2: function(x2, y2, x3, y3) {
 +      TODO("'v' operator: need current point in gfx context");
 +    },
 +    curveTo3: function(x1, y1, x3, y3) {
 +      this.curveTo(x1, y1, x3, y3, x3, y3);
 +    },
 +    closePath: function() {
 +      this.ctx.closePath();
 +    },
 +    rectangle: function(x, y, width, height) {
 +      this.ctx.rect(x, y, width, height);
 +    },
 +    stroke: function() {
 +      this.ctx.stroke();
 +      this.consumePath();
 +    },
 +    closeStroke: function() {
 +      this.closePath();
 +      this.stroke();
 +    },
 +    fill: function() {
 +      this.ctx.fill();
 +      this.consumePath();
 +    },
 +    eoFill: function() {
 +      var savedFillRule = this.setEOFillRule();
 +      this.fill();
 +      this.restoreFillRule(savedFillRule);
 +    },
 +    fillStroke: function() {
 +      this.ctx.fill();
 +      this.ctx.stroke();
 +      this.consumePath();
 +    },
 +    eoFillStroke: function() {
 +      var savedFillRule = this.setEOFillRule();
 +      this.fillStroke();
 +      this.restoreFillRule(savedFillRule);
 +    },
 +    closeFillStroke: function() {
 +      return this.fillStroke();
 +    },
 +    closeEOFillStroke: function() {
 +      var savedFillRule = this.setEOFillRule();
 +      this.fillStroke();
 +      this.restoreFillRule(savedFillRule);
 +    },
 +    endPath: function() {
 +      this.consumePath();
 +    },
++    
 +    // Clipping
 +    clip: function() {
 +      this.pendingClip = NORMAL_CLIP;
 +    },
 +    eoClip: function() {
 +      this.pendingClip = EO_CLIP;
 +    },
++    
 +    // Text
 +    beginText: function() {
 +      this.current.textMatrix = IDENTITY_MATRIX;
 +      if (this.ctx.$setCurrentX) {
 +        this.ctx.$setCurrentX(0);
 +      }
 +      this.current.x = this.current.lineX = 0;
 +      this.current.y = this.current.lineY = 0;
 +    },
 +    endText: function() {
 +    },
 +    setCharSpacing: function(spacing) {
 +      this.ctx.charSpacing = spacing;
 +    },
 +    setWordSpacing: function(spacing) {
 +      this.ctx.wordSpacing = spacing;
 +    },
 +    setHScale: function(scale) {
 +      this.ctx.textHScale = (scale % 100) * 0.01;
 +    },
 +    setLeading: function(leading) {
 +      this.current.leading = leading;
 +    },
 +    setFont: function(fontRef, size) {
-       var font = this.res.get('Font');
++      var font = this.xref.fetchIfRef(this.res.get("Font"));
 +      if (!IsDict(font))
 +        return;
++      
 +      font = font.get(fontRef.name);
 +      font = this.xref.fetchIfRef(font);
 +      if (!font)
 +        return;
-       var fontName = '';
-       var fontDescriptor = font.get('FontDescriptor');
++      
++      var fontName = "";
++      var fontDescriptor = font.get("FontDescriptor");
 +      if (fontDescriptor && fontDescriptor.num) {
 +        var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
-         fontName = fontDescriptor.get('FontName').name.replace('+', '_');
++        fontName = fontDescriptor.get("FontName").name.replace("+", "_");
 +      }
++      
 +      if (!fontName) {
 +        // TODO: fontDescriptor is not available, fallback to default font
 +        fontName = 'sans-serif';
 +      }
++      
 +      this.current.fontName = fontName;
 +      this.current.fontSize = size;
 +
 +      if (this.ctx.$setFont) {
 +        this.ctx.$setFont(fontName, size);
 +      } else {
 +        this.ctx.font = size + 'px "' + fontName + '"';
 +        Fonts.setActive(fontName, size);
 +      }
 +    },
 +    setTextRenderingMode: function(mode) {
 +      TODO('text rendering mode');
 +    },
 +    setTextRise: function(rise) {
 +      TODO('text rise');
 +    },
 +    moveText: function(x, y) {
 +      this.current.x = this.current.lineX += x;
 +      this.current.y = this.current.lineY += y;
 +      if (this.ctx.$setCurrentX) {
 +        this.ctx.$setCurrentX(this.current.x);
 +      }
 +    },
 +    setLeadingMoveText: function(x, y) {
 +      this.setLeading(-y);
 +      this.moveText(x, y);
 +    },
 +    setTextMatrix: function(a, b, c, d, e, f) {
 +      this.current.textMatrix = [a, b, c, d, e, f];
 +
 +      if (this.ctx.$setCurrentX) {
 +        this.ctx.$setCurrentX(0);
 +      }
 +      this.current.x = this.current.lineX = 0;
 +      this.current.y = this.current.lineY = 0;
 +    },
 +    nextLine: function() {
 +      this.moveText(0, this.current.leading);
 +    },
 +    showText: function(text) {
 +      // TODO: apply charSpacing, wordSpacing, textHScale
 +
 +      this.ctx.save();
 +      this.ctx.transform.apply(this.ctx, this.current.textMatrix);
 +      this.ctx.scale(1, -1);
 +
 +      if (this.ctx.$showText) {
 +        this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
 +      } else {
 +        text = Fonts.charsToUnicode(text);
 +        this.ctx.translate(this.current.x, -1 * this.current.y);
 +
 +        var font = Fonts.lookup(this.current.fontName);
 +        if (font && font.properties.textMatrix)
 +          this.ctx.transform.apply(this.ctx, font.properties.textMatrix);
 +
 +        this.ctx.fillText(text, 0, 0);
 +        this.current.x += Fonts.measureText(text);
 +      }
  
 -                var font = Fonts.lookup(this.current.fontName);
 -                if (font && font.properties.textMatrix)
 -                  this.ctx.transform.apply(this.ctx, font.properties.textMatrix);
 +      this.ctx.restore();
 +    },
 +    showSpacedText: function(arr) {
 +      for (var i = 0; i < arr.length; ++i) {
 +        var e = arr[i];
 +        if (IsNum(e)) {
 +          if (this.ctx.$addCurrentX) {
 +            this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize);
 +          } else {
 +            this.current.x -= e * 0.001 * this.current.fontSize;
 +          }
 +        } else if (IsString(e)) {
 +          this.showText(e);
 +        } else {
 +          malformed('TJ array element ' + e + " isn't string or num");
 +        }
 +      }
 +    },
 +    nextLineShowText: function(text) {
 +      this.nextLine();
 +      this.showText(text);
 +    },
 +    nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) {
 +      this.setWordSpacing(wordSpacing);
 +      this.setCharSpacing(charSpacing);
 +      this.nextLineShowText(text);
 +    },
 +
 +    // Type3 fonts
 +    setCharWidth: function(xWidth, yWidth) {
 +      TODO("type 3 fonts ('d0' operator)");
 +    },
 +    setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) {
 +      TODO("type 3 fonts ('d1' operator)");
 +    },
 +
 +    // Color
 +    setStrokeColorSpace: function(space) {
 +      this.current.strokeColorSpace =
 +      ColorSpace.parse(space, this.xref, this.res);
 +    },
 +    setFillColorSpace: function(space) {
 +      this.current.fillColorSpace =
 +      ColorSpace.parse(space, this.xref, this.res);
 +    },
 +    setStrokeColor: function(/*...*/) {
 +      var cs = this.getStrokeColorSpace();
 +      var color = cs.getRgb(arguments);
 +      this.setStrokeRGBColor.apply(this, color);
 +    },
 +    setStrokeColorN: function(/*...*/) {
 +      // TODO real impl
 +      TODO('check for special color spaces');
 +      this.setStrokeColor.apply(this, arguments);
 +    },
 +    setFillColor: function(/*...*/) {
 +      var cs = this.getFillColorSpace();
 +      if (cs.name == 'Pattern') {
 +        TODO('implement Pattern fill');
 +        return;
 +      }
 +      var color = cs.getRgb(arguments);
 +      this.setFillRGBColor.apply(this, color);
 +    },
 +    setFillColorN: function(/*...*/) {
 +      var cs = this.getFillColorSpace();
 +
 +      if (cs.name == 'Pattern') {
 +        var patternName = arguments[0];
 +        if (IsName(patternName)) {
 +          var xref = this.xref;
 +          var patternRes = xref.fetchIfRef(this.res.get('Pattern'));
 +          if (!patternRes)
 +            error('Unable to find pattern resource');
 +
 +          var pattern = xref.fetchIfRef(patternRes.get(patternName.name));
 +          var patternDict = IsStream(pattern) ? pattern.dict : pattern;
 +          var types = [null, this.tilingFill,
 +                       function() { TODO('Shading Patterns'); }];
 +          var typeNum = patternDict.get('PatternType');
 +          var patternFn = types[typeNum];
 +          if (!patternFn)
 +            error('Unhandled pattern type');
 +          patternFn.call(this, pattern, patternDict);
 +        }
 +      } else {
 +        // TODO real impl
 +        this.setFillColor.apply(this, arguments);
 +      }
 +    },
 +    tilingFill: function(pattern) {
 +      function applyMatrix(point, m) {
 +        var x = point[0] * m[0] + point[1] * m[2] + m[4];
 +        var y = point[0] * m[1] + point[1] * m[3] + m[5];
 +        return [x, y];
 +      };
 +
 +      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];
 +      };
 +
 +      this.save();
 +      var dict = pattern.dict;
 +      var ctx = this.ctx;
 +
 +      var paintType = dict.get('PaintType');
 +      switch (paintType) {
 +      case PAINT_TYPE_COLORED:
 +        // should go to default for color space
 +        ctx.fillStyle = this.makeCssRgb(1, 1, 1);
 +        ctx.strokeStyle = this.makeCssRgb(0, 0, 0);
 +        break;
 +      case PAINT_TYPE_UNCOLORED:
 +      default:
 +        error('Unsupported paint type');
 +      }
  
 -                this.ctx.fillText(text, 0, 0);
 -                this.current.x += Fonts.measureText(text);
 -            }
 +      TODO('TilingType');
  
 -            this.ctx.restore();
 -        },
 -        showSpacedText: function(arr) {
 -            for (var i = 0; i < arr.length; ++i) {
 -                var e = arr[i];
 -                if (IsNum(e)) {
 -                    if (this.ctx.$addCurrentX) {
 -                        this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize)
 -                    } else {
 -                        this.current.x -= e * 0.001 * this.current.fontSize;
 -                    }
 -                } else if (IsString(e)) {
 -                    this.showText(e);
 -                } else {
 -                    malformed("TJ array element "+ e +" isn't string or num");
 -                }
 -            }
 -        },
 -        nextLineShowText: function(text) {
 -            this.nextLine();
 -            this.showText(text);
 -        },
 -        nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) {
 -            this.setWordSpacing(wordSpacing);
 -            this.setCharSpacing(charSpacing);
 -            this.nextLineShowText(text);
 -        },
 -
 -        // Type3 fonts
 -        setCharWidth: function(xWidth, yWidth) {
 -            TODO("type 3 fonts ('d0' operator)");
 -        },
 -        setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) {
 -            TODO("type 3 fonts ('d1' operator)");
 -        },
 -
 -        // Color
 -        setStrokeColorSpace: function(space) {
 -            this.current.strokeColorSpace = 
 -                ColorSpace.parse(space, this.xref, this.res);
 -        },
 -        setFillColorSpace: function(space) {
 -            this.current.fillColorSpace = 
 -                ColorSpace.parse(space, this.xref, this.res);
 -        },
 -        setStrokeColor: function(/*...*/) {
 -            var cs = this.getStrokeColorSpace();
 -            var color = cs.getRgb(arguments);
 -            this.setStrokeRGBColor.apply(this, color);
 -        },
 -        setStrokeColorN: function(/*...*/) {
 -            // TODO real impl
 -            TODO("check for special color spaces");
 -            this.setStrokeColor.apply(this, arguments);
 -        },
 -        setFillColor: function(/*...*/) {
 -            var cs = this.getFillColorSpace();
 -            if (cs.name == "Pattern") {
 -                TODO("implement Pattern fill");
 -                return;
 -            }
 -            var color = cs.getRgb(arguments);
 -            this.setFillRGBColor.apply(this, color);
 -        },
 -        setFillColorN: function(/*...*/) {
 -            var cs = this.getFillColorSpace();
 -
 -            if (cs.name == "Pattern") {
 -                var patternName = arguments[0];
 -                if (IsName(patternName)) {
 -                    var xref = this.xref;
 -                    var patternRes = xref.fetchIfRef(this.res.get("Pattern"));
 -                    if (!patternRes)
 -                        error("Unable to find pattern resource");
 -
 -                    var pattern = xref.fetchIfRef(patternRes.get(patternName.name));
 -                    var patternDict = IsStream(pattern) ? pattern.dict : pattern;
 -                    var types = [null, this.tilingFill,
 -                                   function() { TODO("Shading Patterns"); }];
 -                    var typeNum = patternDict.get("PatternType");
 -                    var patternFn = types[typeNum];
 -                    if (!patternFn)
 -                        error("Unhandled pattern type");
 -                    patternFn.call(this, pattern, patternDict);
 -                }
 -            } else {
 -                // TODO real impl
 -                this.setFillColor.apply(this, arguments);
 -            }
 -        },
 -        tilingFill: function(pattern) {
 -            function applyMatrix(point, m) {
 -                var x = point[0] * m[0] + point[1] * m[2] + m[4];
 -                var y = point[0] * m[1] + point[1] * m[3] + m[5];
 -                return [x,y];
 -            };
 -
 -            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]
 -            };
 -
 -            this.save();
 -            var dict = pattern.dict;
 -            var ctx = this.ctx;
 -
 -            var paintType = dict.get("PaintType");
 -            switch (paintType) {
 -            case PAINT_TYPE_COLORED:
 -                // should go to default for color space
 -                ctx.fillStyle = this.makeCssRgb(1, 1, 1);
 -                ctx.strokeStyle = this.makeCssRgb(0, 0, 0);
 -                break;
 -            case PAINT_TYPE_UNCOLORED:
 -            default:
 -                error("Unsupported paint type");
 -            }
 +      var matrix = dict.get('Matrix') || IDENTITY_MATRIX;
  
 -            TODO("TilingType");
 +      var bbox = dict.get('BBox');
 +      var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
  
 -            var matrix = dict.get("Matrix") || IDENTITY_MATRIX;
 +      var xstep = dict.get('XStep');
 +      var ystep = dict.get('YStep');
  
 -            var bbox = dict.get("BBox");
 -            var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
 +      // top left corner should correspond to the top left of the bbox
 +      var topLeft = applyMatrix([x0, y0], matrix);
 +      // we want the canvas to be as large as the step size
 +      var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
  
 -            var xstep = dict.get("XStep");
 -            var ystep = dict.get("YStep");
 +      var width = botRight[0] - topLeft[0];
 +      var height = botRight[1] - topLeft[1];
  
 -            // top left corner should correspond to the top left of the bbox
 -            var topLeft = applyMatrix([x0,y0], matrix);
 -            // we want the canvas to be as large as the step size
 -            var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
 +      // TODO: hack to avoid OOM, remove then pattern code is fixed
 +      if (Math.abs(width) > 8192 || Math.abs(height) > 8192)
 +        return false;
  
 -            var width = botRight[0] - topLeft[0];
 -            var height = botRight[1] - topLeft[1];
 +      var tmpCanvas = new this.ScratchCanvas(width, height);
  
 -            // TODO: hack to avoid OOM, remove then pattern code is fixed
 -            if (Math.abs(width) > 8192 || Math.abs(height) > 8192)
 -                return false;
 +      // set the new canvas element context as the graphics context
 +      var tmpCtx = tmpCanvas.getContext('2d');
 +      var savedCtx = ctx;
 +      this.ctx = tmpCtx;
  
 -            var tmpCanvas = new this.ScratchCanvas(width, height);
 +      // normalize transform matrix so each step
 +      // takes up the entire tmpCanvas (need to remove white borders)
 +      if (matrix[1] === 0 && matrix[2] === 0) {
 +        matrix[0] = tmpCanvas.width / xstep;
 +        matrix[3] = tmpCanvas.height / ystep;
 +        topLeft = applyMatrix([x0, y0], matrix);
 +      }
  
 -            // set the new canvas element context as the graphics context
 -            var tmpCtx = tmpCanvas.getContext("2d");
 -            var savedCtx = ctx;
 -            this.ctx = tmpCtx;
 +      // move the top left corner of bounding box to [0,0]
 +      matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]);
  
 -            // normalize transform matrix so each step
 -            // takes up the entire tmpCanvas (need to remove white borders)
 -            if (matrix[1] === 0 && matrix[2] === 0) {
 -                matrix[0] = tmpCanvas.width / xstep;
 -                matrix[3] = tmpCanvas.height / ystep;
 -                topLeft = applyMatrix([x0,y0], matrix);
 -            }
 +      this.transform.apply(this, matrix);
  
 -            // move the top left corner of bounding box to [0,0]
 -            matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]);
 +      if (bbox && IsArray(bbox) && 4 == bbox.length) {
 +        this.rectangle.apply(this, bbox);
 +        this.clip();
 +        this.endPath();
 +      }
  
 -            this.transform.apply(this, matrix);
 +      var xref = this.xref;
 +      var res = xref.fetchIfRef(dict.get('Resources'));
 +      if (!pattern.code)
 +        pattern.code = this.compile(pattern, xref, res, []);
 +      this.execute(pattern.code, xref, res);
 +
 +      this.ctx = savedCtx;
 +      this.restore();
 +
 +      TODO('Inverse pattern is painted');
 +      pattern = this.ctx.createPattern(tmpCanvas, 'repeat');
 +      this.ctx.fillStyle = pattern;
 +    },
 +    setStrokeGray: function(gray) {
 +      this.setStrokeRGBColor(gray, gray, gray);
 +    },
 +    setFillGray: function(gray) {
 +      this.setFillRGBColor(gray, gray, gray);
 +    },
 +    setStrokeRGBColor: function(r, g, b) {
 +      this.ctx.strokeStyle = this.makeCssRgb(r, g, b);
 +    },
 +    setFillRGBColor: function(r, g, b) {
 +      this.ctx.fillStyle = this.makeCssRgb(r, g, b);
 +    },
 +    setStrokeCMYKColor: function(c, m, y, k) {
 +      this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k);
 +    },
 +    setFillCMYKColor: function(c, m, y, k) {
 +      this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k);
 +    },
 +
 +    // Shading
 +    shadingFill: function(entryRef) {
 +      var xref = this.xref;
 +      var res = this.res;
 +
 +      var shadingRes = xref.fetchIfRef(res.get('Shading'));
 +      if (!shadingRes)
 +        error('No shading resource found');
 +
 +      var shading = xref.fetchIfRef(shadingRes.get(entryRef.name));
 +      if (!shading)
 +        error('No shading object found');
 +
 +      this.save();
 +
 +      var bbox = shading.get('BBox');
 +      if (bbox && IsArray(bbox) && 4 == bbox.length) {
 +        this.rectangle.apply(this, bbox);
 +        this.clip();
 +        this.endPath();
 +      }
  
 -            if (bbox && IsArray(bbox) && 4 == bbox.length) {
 -                this.rectangle.apply(this, bbox);
 -                this.clip();
 -                this.endPath();
 -            }
 +      var cs = shading.get2('ColorSpace', 'CS');
 +      TODO('shading-fill color space');
  
 -            var xref = this.xref;
 -            var res = xref.fetchIfRef(dict.get("Resources"));
 -            if (!pattern.code)
 -                pattern.code = this.compile(pattern, xref, res, []);
 -            this.execute(pattern.code, xref, res);
 -
 -            this.ctx = savedCtx;
 -            this.restore();
 -
 -            TODO("Inverse pattern is painted");
 -            pattern = this.ctx.createPattern(tmpCanvas, "repeat");
 -            this.ctx.fillStyle = pattern;
 -        },
 -        setStrokeGray: function(gray) {
 -            this.setStrokeRGBColor(gray, gray, gray);
 -        },
 -        setFillGray: function(gray) {
 -            this.setFillRGBColor(gray, gray, gray);
 -        },
 -        setStrokeRGBColor: function(r, g, b) {
 -            this.ctx.strokeStyle = this.makeCssRgb(r, g, b);
 -        },
 -        setFillRGBColor: function(r, g, b) {
 -            this.ctx.fillStyle = this.makeCssRgb(r, g, b);
 -        },
 -        setStrokeCMYKColor: function(c, m, y, k) {
 -            this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k);
 -        },
 -        setFillCMYKColor: function(c, m, y, k) {
 -            this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k);
 -        },
 -
 -        // Shading
 -        shadingFill: function(entryRef) {
 -            var xref = this.xref;
 -            var res = this.res;
 -
 -            var shadingRes = xref.fetchIfRef(res.get("Shading"));
 -            if (!shadingRes)
 -                error("No shading resource found");
 -
 -            var shading = xref.fetchIfRef(shadingRes.get(entryRef.name));
 -            if (!shading)
 -                error("No shading object found");
 -
 -            this.save();
 -
 -            var bbox = shading.get("BBox");
 -            if (bbox && IsArray(bbox) && 4 == bbox.length) {
 -                this.rectangle.apply(this, bbox);
 -                this.clip();
 -                this.endPath();
 -            }
 +      var background = shading.get('Background');
 +      if (background)
 +        TODO('handle background colors');
  
 -            var cs = shading.get2("ColorSpace", "CS");
 -            TODO("shading-fill color space");
 +      var types = [null,
 +                   this.fillFunctionShading,
 +                   this.fillAxialShading,
 +                   this.fillRadialShading];
  
 -            var background = shading.get("Background");
 -            if (background)
 -                TODO("handle background colors");
 +      var typeNum = shading.get('ShadingType');
 +      var fillFn = types[typeNum];
 +      if (!fillFn)
 +        error("Unknown or NYI type of shading '" + typeNum + "'");
 +      fillFn.apply(this, [shading]);
  
 -            var types = [null,
 -                           this.fillFunctionShading,
 -                           this.fillAxialShading,
 -                           this.fillRadialShading];
 +      this.restore();
 +    },
  
 -            var typeNum = shading.get("ShadingType");
 -            var fillFn = types[typeNum];
 -            if (!fillFn)
 -                error("Unknown or NYI type of shading '"+ typeNum +"'");
 -            fillFn.apply(this, [shading]);
 +    fillAxialShading: function(sh) {
 +      var coordsArr = sh.get('Coords');
 +      var x0 = coordsArr[0], y0 = coordsArr[1],
 +      x1 = coordsArr[2], y1 = coordsArr[3];
  
 -            this.restore();
 -        },
 +      var t0 = 0.0, t1 = 1.0;
 +      if (sh.has('Domain')) {
 +        var domainArr = sh.get('Domain');
 +        t0 = domainArr[0], t1 = domainArr[1];
 +      }
  
 -        fillAxialShading: function(sh) {
 -            var coordsArr = sh.get("Coords");
 -            var x0 = coordsArr[0], y0 = coordsArr[1],
 -                x1 = coordsArr[2], y1 = coordsArr[3];
 +      var extendStart = false, extendEnd = false;
 +      if (sh.has('Extend')) {
 +        var extendArr = sh.get('Extend');
 +        extendStart = extendArr[0], extendEnd = extendArr[1];
 +        TODO('Support extend');
 +      }
 +      var fnObj = sh.get('Function');
 +      fnObj = this.xref.fetchIfRef(fnObj);
 +      if (IsArray(fnObj))
 +        error('No support for array of functions');
 +      else if (!IsPDFFunction(fnObj))
 +        error('Invalid function');
 +      var fn = new PDFFunction(this.xref, fnObj);
 +
 +      var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
 +
 +      // 10 samples seems good enough for now, but probably won't work
 +      // if there are sharp color changes. Ideally, we would implement
 +      // the spec faithfully and add lossless optimizations.
 +      var step = (t1 - t0) / 10;
 +
 +      for (var i = t0; i <= t1; i += step) {
 +        var c = fn.func([i]);
 +        gradient.addColorStop(i, this.makeCssRgb.apply(this, c));
 +      }
  
 -            var t0 = 0.0, t1 = 1.0;
 -            if (sh.has("Domain")) {
 -                var domainArr = sh.get("Domain");
 -                t0 = domainArr[0], t1 = domainArr[1];
 -            }
 +      this.ctx.fillStyle = gradient;
 +
 +      // HACK to draw the gradient onto an infinite rectangle.
 +      // PDF gradients are drawn across the entire image while
 +      // Canvas only allows gradients to be drawn in a rectangle
 +      // The following bug should allow us to remove this.
 +      // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
 +      this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
 +    },
 +
 +    fillRadialShading: function(sh) {
 +      TODO('radial shading');
 +    },
 +
 +    // Images
 +    beginInlineImage: function() {
 +      TODO('inline images');
 +      error('(Stream will not be parsed properly, bailing now)');
 +      // Like an inline stream:
 +      //  - key/value pairs up to Cmd(ID)
 +      //  - then image data up to Cmd(EI)
 +    },
 +
 +    // XObjects
 +    paintXObject: function(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 extendStart = false, extendEnd = false;
 -            if (sh.has("Extend")) {
 -                var extendArr = sh.get("Extend");
 -                extendStart = extendArr[0], extendEnd = extendArr[1];
 -                TODO("Support extend");
 -            }
 -            var fnObj = sh.get("Function");
 -            fnObj = this.xref.fetchIfRef(fnObj);
 -            if (IsArray(fnObj))
 -                error("No support for array of functions");
 -            else if (!IsPDFFunction(fnObj))
 -                error("Invalid function");
 -            var fn = new PDFFunction(this.xref, fnObj);
 -
 -            var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
 -
 -            // 10 samples seems good enough for now, but probably won't work
 -            // if there are sharp color changes. Ideally, we would implement
 -            // the spec faithfully and add lossless optimizations.
 -            var step = (t1 - t0) / 10;
 -
 -            for (var i = t0; i <= t1; i += step) {
 -                var c = fn.func([i]);
 -                gradient.addColorStop(i, this.makeCssRgb.apply(this, c));
 -            }
 +      var oc = xobj.dict.get('OC');
 +      if (oc) {
 +        TODO('oc for xobject');
 +      }
  
 -            this.ctx.fillStyle = gradient;
 -
 -            // HACK to draw the gradient onto an infinite rectangle.
 -            // PDF gradients are drawn across the entire image while
 -            // Canvas only allows gradients to be drawn in a rectangle
 -            // The following bug should allow us to remove this.
 -            // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
 -            this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
 -        },
 -
 -        fillRadialShading: function(sh) {
 -            TODO("radial shading");
 -        },
 -
 -        // Images
 -        beginInlineImage: function() {
 -            TODO("inline images");
 -            error("(Stream will not be parsed properly, bailing now)");
 -            // Like an inline stream:
 -            //  - key/value pairs up to Cmd(ID)
 -            //  - then image data up to Cmd(EI)
 -        },
 -
 -        // XObjects
 -        paintXObject: function(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 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);
 +      }
 +    },
  
 -            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(ref, stream) {
 +      this.save();
  
 -        paintFormXObject: function(ref, stream) {
 -            this.save();
 +      var matrix = stream.dict.get('Matrix');
 +      if (matrix && IsArray(matrix) && 6 == matrix.length)
 +        this.transform.apply(this, matrix);
  
 -            var matrix = stream.dict.get("Matrix");
 -            if (matrix && IsArray(matrix) && 6 == matrix.length)
 -                this.transform.apply(this, matrix);
 +      var bbox = stream.dict.get('BBox');
 +      if (bbox && IsArray(bbox) && 4 == bbox.length) {
 +        this.rectangle.apply(this, bbox);
 +        this.clip();
 +        this.endPath();
 +      }
  
 -            var bbox = stream.dict.get("BBox");
 -            if (bbox && IsArray(bbox) && 4 == bbox.length) {
 -                this.rectangle.apply(this, bbox);
 -                this.clip();
 -                this.endPath();
 -            }
 +      this.execute(ref.code, this.xref, stream.dict.get('Resources'));
 +
 +      this.restore();
 +    },
 +
 +    paintImageXObject: function(ref, image, inline) {
 +      this.save();
 +
 +      var ctx = this.ctx;
 +      var dict = image.dict;
 +      var w = dict.get2('Width', 'W');
 +      var h = dict.get2('Height', 'H');
 +      // scale the image to the unit square
 +      ctx.scale(1 / w, -1 / h);
 +
 +      // If the platform can render the image format directly, the
 +      // stream has a getImage property which directly returns a
 +      // suitable DOM Image object.
 +      if (image.getImage) {
 +        var domImage = image.getImage();
 +        ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
 +                      0, -h, w, h);
 +        this.restore();
 +        return;
 +      }
  
 -            this.execute(ref.code, this.xref, stream.dict.get("Resources"));
 -
 -            this.restore();
 -        },
 -
 -        paintImageXObject: function(ref, image, inline) {
 -            this.save();
 -
 -            var ctx = this.ctx;
 -            var dict = image.dict;
 -            var w = dict.get2("Width", "W");
 -            var h = dict.get2("Height", "H");
 -            // scale the image to the unit square
 -            ctx.scale(1/w, -1/h);
 -
 -            // If the platform can render the image format directly, the
 -            // stream has a getImage property which directly returns a
 -            // suitable DOM Image object.
 -            if (image.getImage) {
 -                var domImage = image.getImage();
 -                ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
 -                              0, -h, w, h);
 -                this.restore();
 -                return;
 -            }
 +      var imageObj = new PDFImage(this.xref, this.res, image, inline);
 +
 +      var tmpCanvas = new this.ScratchCanvas(w, h);
 +      var tmpCtx = tmpCanvas.getContext('2d');
 +      var imgData = tmpCtx.getImageData(0, 0, w, h);
 +      var pixels = imgData.data;
 +
 +      imageObj.fillRgbaBuffer(pixels);
 +
 +      tmpCtx.putImageData(imgData, 0, 0);
 +      ctx.drawImage(tmpCanvas, 0, -h);
 +      this.restore();
 +    },
 +
 +    // Marked content
 +
 +    markPoint: function(tag) {
 +      TODO('Marked content');
 +    },
 +    markPointProps: function(tag, properties) {
 +      TODO('Marked content');
 +    },
 +    beginMarkedContent: function(tag) {
 +      TODO('Marked content');
 +    },
 +    beginMarkedContentProps: function(tag, properties) {
 +      TODO('Marked content');
 +    },
 +    endMarkedContent: function() {
 +      TODO('Marked content');
 +    },
 +
 +    // Compatibility
 +
 +    beginCompat: function() {
 +      TODO('ignore undefined operators (should we do that anyway?)');
 +    },
 +    endCompat: function() {
 +      TODO('stop ignoring undefined operators');
 +    },
 +
 +    // Helper functions
 +
 +    consumePath: function() {
 +      if (this.pendingClip) {
 +        var savedFillRule = null;
 +        if (this.pendingClip == EO_CLIP)
 +          savedFillRule = this.setEOFillRule();
 +
 +        this.ctx.clip();
  
 -            var imageObj = new PDFImage(this.xref, this.res, image, inline); 
 -
 -            var tmpCanvas = new this.ScratchCanvas(w, h);
 -            var tmpCtx = tmpCanvas.getContext("2d");
 -            var imgData = tmpCtx.getImageData(0, 0, w, h);
 -            var pixels = imgData.data;
 -
 -            imageObj.fillRgbaBuffer(pixels);
 -            
 -            tmpCtx.putImageData(imgData, 0, 0);
 -            ctx.drawImage(tmpCanvas, 0, -h);
 -            this.restore();
 -        },
 -
 -        // Marked content
 -
 -        markPoint: function(tag) {
 -            TODO("Marked content");
 -        },
 -        markPointProps: function(tag, properties) {
 -            TODO("Marked content");
 -        },
 -        beginMarkedContent: function(tag) {
 -            TODO("Marked content");
 -        },
 -        beginMarkedContentProps: function(tag, properties) {
 -            TODO("Marked content");
 -        },
 -        endMarkedContent: function() {
 -            TODO("Marked content");
 -        },
 -
 -        // Compatibility
 -
 -        beginCompat: function() {
 -            TODO("ignore undefined operators (should we do that anyway?)");
 -        },
 -        endCompat: function() {
 -            TODO("stop ignoring undefined operators");
 -        },
 -
 -        // Helper functions
 -
 -        consumePath: function() {
 -            if (this.pendingClip) {
 -                var savedFillRule = null;
 -                if (this.pendingClip == EO_CLIP)
 -                    savedFillRule = this.setEOFillRule();
 -
 -                this.ctx.clip();
 -
 -                this.pendingClip = null;
 -                if (savedFillRule !== null)
 -                    this.restoreFillRule(savedFillRule);
 -            }
 -            this.ctx.beginPath();
 -        },
 -        makeCssRgb: function(r, g, b) {
 -            var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0;
 -            return "rgb("+ ri +","+ gi +","+ bi +")";
 -        },
 -        makeCssCmyk: function(c, m, y, k) {
 -            // while waiting on CSS's cmyk()... http://www.ilkeratalay.com/colorspacesfaq.php#rgb
 -            var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0;
 -            var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0;
 -            var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0;
 -            return "rgb("+ ri +","+ gi +","+ bi +")";
 -        },
 -        getFillColorSpace: function() {
 -            var cs = this.current.fillColorSpace;
 -            if (cs)
 -                return cs;
 -
 -            var states = this.stateStack;
 -            var i = states.length - 1;
 -            while (i >= 0 && !(cs = states[i].fillColorSpace))
 -                --i;
 -
 -            if (cs)
 -                return cs;
 -            else
 -                return new DeviceRgbCS();
 -        },
 -        getStrokeColorSpace: function() {
 -            var cs = this.current.strokeColorSpace;
 -            if (cs)
 -                return cs;
 -            
 -            var states = this.stateStack;
 -            var i = states.length - 1;
 -            while (i >= 0 && !(cs = states[i].strokeColorSpace))
 -                --i;
 -
 -            if (cs)
 -                return cs;
 -            else
 -                return new DeviceRgbCS();
 -        },
 -        // We generally keep the canvas context set for
 -        // nonzero-winding, and just set evenodd for the operations
 -        // that need them.
 -        setEOFillRule: function() {
 -            var savedFillRule = this.ctx.mozFillRule;
 -            this.ctx.mozFillRule = "evenodd";
 -            return savedFillRule;
 -        },
 -        restoreFillRule: function(rule) {
 -            this.ctx.mozFillRule = rule;
 -        }
 -    };
 +        this.pendingClip = null;
 +        if (savedFillRule !== null)
 +          this.restoreFillRule(savedFillRule);
 +      }
 +      this.ctx.beginPath();
 +    },
 +    makeCssRgb: function(r, g, b) {
 +      var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0;
 +      return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
 +    },
 +    makeCssCmyk: function(c, m, y, k) {
 +      // while waiting on CSS's cmyk()...
 +      // http://www.ilkeratalay.com/colorspacesfaq.php#rgb
 +      var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0;
 +      var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0;
 +      var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0;
 +      return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
 +    },
 +    getFillColorSpace: function() {
 +      var cs = this.current.fillColorSpace;
 +      if (cs)
 +        return cs;
 +
 +      var states = this.stateStack;
 +      var i = states.length - 1;
 +      while (i >= 0 && !(cs = states[i].fillColorSpace))
 +        --i;
 +
 +      if (cs)
 +        return cs;
 +      else
 +        return new DeviceRgbCS();
 +    },
 +    getStrokeColorSpace: function() {
 +      var cs = this.current.strokeColorSpace;
 +      if (cs)
 +        return cs;
 +
 +      var states = this.stateStack;
 +      var i = states.length - 1;
 +      while (i >= 0 && !(cs = states[i].strokeColorSpace))
 +        --i;
 +
 +      if (cs)
 +        return cs;
 +      else
 +        return new DeviceRgbCS();
 +    },
 +    // We generally keep the canvas context set for
 +    // nonzero-winding, and just set evenodd for the operations
 +    // that need them.
 +    setEOFillRule: function() {
 +      var savedFillRule = this.ctx.mozFillRule;
 +      this.ctx.mozFillRule = 'evenodd';
 +      return savedFillRule;
 +    },
 +    restoreFillRule: function(rule) {
 +      this.ctx.mozFillRule = rule;
 +    }
 +  };
  
 -    return constructor;
 +  return constructor;
  })();
  
  var ColorSpace = (function() {
index 7e8140590ba6098762e8cdd82927395c31f4f9a0,359c69ff487893bccc216e51756a7b567a47b8df..a00eab59e752f1e5f6bc9b1b56b912d5dd15532c
@@@ -283,11 -282,10 +283,11 @@@ function WorkerPDFDoc(canvas) 
        var base64 = window.btoa(data.raw);
  
        // Add the @font-face rule to the document
 -      var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
 -      var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
 +      var url = 'url(data:' + data.mimetype + ';base64,' + base64 + ');';
 +      var rule = ("@font-face { font-family:'" + data.fontName +
 +                  "';src:" + url + '}');
        var styleSheet = document.styleSheets[0];
-       styleSheet.insertRule(rule, styleSheet.length);
+       styleSheet.insertRule(rule, styleSheet.cssRules.length);
  
        // Just adding the font-face to the DOM doesn't make it load. It
        // seems it's loaded once Gecko notices it's used. Therefore,