]> git.parisson.com Git - pdf.js.git/commitdiff
Merge branch 'master' into patterncs
authorsbarman <sbarman@L3CWZ5T.(none)>
Wed, 6 Jul 2011 17:36:49 +0000 (10:36 -0700)
committersbarman <sbarman@L3CWZ5T.(none)>
Wed, 6 Jul 2011 17:36:49 +0000 (10:36 -0700)
Conflicts:
pdf.js

1  2 
pdf.js

diff --cc pdf.js
index 4033330bd9d349e480420c476a20d36b5e649929,7d98e001b14a241e6d2acb83d30ad7ea89904730..14f801747ba0f04831b40671fa3f9f3c6929258e
--- 1/pdf.js
--- 2/pdf.js
+++ b/pdf.js
@@@ -3294,1154 -3346,1089 +3346,1157 @@@ 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) {
-             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 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 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 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.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.xref.fetchIfRef(this.res.get('Font'));
+       if (!IsDict(font))
+         return;
  
-                 var font = Fonts.lookup(this.current.fontName);
-                 if (font && font.properties.textMatrix)
-                   this.ctx.transform.apply(this.ctx, font.properties.textMatrix);
+       font = font.get(fontRef.name);
+       font = this.xref.fetchIfRef(font);
+       if (!font)
+         return;
  
-                 this.ctx.fillText(text, 0, 0);
-                 this.current.x += Fonts.measureText(text);
-             }
+       var fontName = '';
+       var fontDescriptor = font.get('FontDescriptor');
+       if (fontDescriptor && fontDescriptor.num) {
+         var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
+         fontName = fontDescriptor.get('FontName').name.replace('+', '_');
+       }
  
-             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();
-             var color = cs.getRgb(arguments);
-             this.setFillRGBColor.apply(this, color);
-         },
-         setFillColorN: function(/*...*/) {
-             var cs = this.getFillColorSpace();
-             if (cs.name == "Pattern") {
-                 var patternName = arguments[0];
-                 this.setFillPattern(patternName);
-             } else {
-                 // TODO real impl
-                 this.setFillColor.apply(this, arguments);
-             }
-         },
-         setFillPattern: function(patternName) {
-             if (!IsName(patternName))
-                 error("Bad args to getPattern");
-             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 dict = IsStream(pattern) ? pattern.dict : pattern;
-             var types = [null, this.setTilingPattern,
-                 this.setShadingPattern];
-             
-             var typeNum = dict.get("PatternType");
-             var patternFn = types[typeNum];
-             if (!patternFn)
-                 error("Unhandled pattern type");
-             patternFn.call(this, pattern, dict);
-         },
-         setShadingPattern: function(pattern, dict) {
-             var matrix = dict.get("Matrix");
-             var inv = [0,0,0,0,0,0];
-             var det = 1 / (matrix[0] * matrix[3] - matrix[1] * matrix[2]);
-             inv[0] = matrix[3] * det;
-             inv[1] = -matrix[1] * det;
-             inv[2] = -matrix[2] * det;
-             inv[3] = matrix[0] * det;
-             inv[4] = det * (matrix[2] * matrix[5] - matrix[3] * matrix[4]);
-             inv[5] = det * (matrix[1] * matrix[4] - matrix[0] * matrix[5]);
-             this.transform.apply(this, matrix);
-             var shading = this.getShading(pattern.get("Shading"));
-             this.restore();
-             this.ctx.fillStyle = shading;
-             // HACK to get the gradient to show at the right location. If
-             // removed, the gradient will show at the pre-transform coordinates.
-             this.ctx.fillRect(0,0,0,0);
-             this.transform.apply(this, inv);
-         },
-         setTilingPattern: function(pattern, dict) {
-             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 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");
-             }
+       if (!fontName) {
+         // TODO: fontDescriptor is not available, fallback to default font
+         fontName = 'sans-serif';
+       }
  
-             TODO("TilingType");
+       this.current.fontName = fontName;
+       this.current.fontSize = size;
  
-             var matrix = dict.get("Matrix") || IDENTITY_MATRIX;
+       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 bbox = dict.get("BBox");
-             var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
+       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);
++      this.current.strokeColorSpace = 
++          ColorSpace.parse(space, this.xref, this.res);
+     },
+     setFillColorSpace: function(space) {
 -      this.current.fillColorSpace =
 -      ColorSpace.parse(space, this.xref, this.res);
++      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') {
++      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);
 -        }
++        this.setFillPattern(patternName);
+       } else {
+         // TODO real impl
+         this.setFillColor.apply(this, arguments);
+       }
+     },
 -    tilingFill: function(pattern) {
++    setFillPattern: function(patternName) {
++      if (!IsName(patternName))
++        error("Bad args to getPattern");
++
++      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 dict = IsStream(pattern) ? pattern.dict : pattern;
++
++      var types = [null, this.setTilingPattern,
++          this.setShadingPattern];
++
++      var typeNum = dict.get("PatternType");
++      var patternFn = types[typeNum];
++      if (!patternFn)
++        error("Unhandled pattern type");
++      patternFn.call(this, pattern, dict);
++    },
++    setShadingPattern: function(pattern, dict) {
++      var matrix = dict.get("Matrix");
++
++      var inv = [0,0,0,0,0,0];
++      var det = 1 / (matrix[0] * matrix[3] - matrix[1] * matrix[2]);
++      inv[0] = matrix[3] * det;
++      inv[1] = -matrix[1] * det;
++      inv[2] = -matrix[2] * det;
++      inv[3] = matrix[0] * det;
++      inv[4] = det * (matrix[2] * matrix[5] - matrix[3] * matrix[4]);
++      inv[5] = det * (matrix[1] * matrix[4] - matrix[0] * matrix[5]);
++
++      this.transform.apply(this, matrix);
++      var shading = this.getShading(pattern.get("Shading"));
++      this.restore();
++      this.ctx.fillStyle = shading;
++
++      // HACK to get the gradient to show at the right location. If
++      // removed, the gradient will show at the pre-transform coordinates.
++      this.ctx.fillRect(0,0,0,0);
++      this.transform.apply(this, inv);
++    },
++    setTilingPattern: function(pattern, dict) {
+       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 xstep = dict.get("XStep");
-             var ystep = dict.get("YStep");
+       TODO('TilingType');
  
-             // 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 matrix = dict.get('Matrix') || IDENTITY_MATRIX;
  
-             var width = botRight[0] - topLeft[0];
-             var height = botRight[1] - topLeft[1];
+       var bbox = dict.get('BBox');
+       var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
  
-             // TODO: hack to avoid OOM, remove then pattern code is fixed
-             if (Math.abs(width) > 8192 || Math.abs(height) > 8192)
-                 return false;
+       var xstep = dict.get('XStep');
+       var ystep = dict.get('YStep');
  
-             var tmpCanvas = new this.ScratchCanvas(width, height);
+       // 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);
  
-             // set the new canvas element context as the graphics context
-             var tmpCtx = tmpCanvas.getContext("2d");
-             var savedCtx = ctx;
-             this.ctx = tmpCtx;
+       var width = botRight[0] - topLeft[0];
+       var height = botRight[1] - 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);
-             }
+       // TODO: hack to avoid OOM, remove then pattern code is fixed
+       if (Math.abs(width) > 8192 || Math.abs(height) > 8192)
+         return false;
  
-             // move the top left corner of bounding box to [0,0]
-             matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]);
+       var tmpCanvas = new this.ScratchCanvas(width, height);
  
-             this.transform.apply(this, matrix);
+       // set the new canvas element context as the graphics context
+       var tmpCtx = tmpCanvas.getContext('2d');
+       var savedCtx = ctx;
+       this.ctx = tmpCtx;
  
-             if (bbox && IsArray(bbox) && 4 == bbox.length) {
-                 this.rectangle.apply(this, bbox);
-                 this.clip();
-                 this.endPath();
-             }
+       // 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);
+       }
 -
+       // move the top left corner of bounding box to [0,0]
+       matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]);
 -
+       this.transform.apply(this, matrix);
+       if (bbox && IsArray(bbox) && 4 == bbox.length) {
+         this.rectangle.apply(this, bbox);
+         this.clip();
+         this.endPath();
+       }
  
-             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");
-             this.ctx.fillStyle = this.ctx.createPattern(tmpCanvas, "repeat");
-         },
-         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(shadingName) {
-             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(shadingName.name));
-             if (!shading)
-                 error("No shading object found");
-             
-             var shadingFill = this.getShading(shading);
-             this.save();
-             this.ctx.fillStyle = shadingFill;
-             // 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);
-             this.restore();
-         },
-         getShading: function(shading) {
-             shading = this.xref.fetchIfRef(shading);
-             var bbox = shading.get("BBox");
-             if (bbox && IsArray(bbox) && 4 == bbox.length) {
-                 this.rectangle.apply(this, bbox);
-                 this.clip();
-                 this.endPath();
-             }
+       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) {
++    shadingFill: function(shadingName) {
+       var xref = this.xref;
+       var res = this.res;
 -      var shadingRes = xref.fetchIfRef(res.get('Shading'));
++      var shadingRes = xref.fetchIfRef(res.get("Shading"));
+       if (!shadingRes)
 -        error('No shading resource found');
++        error("No shading resource found");
 -      var shading = xref.fetchIfRef(shadingRes.get(entryRef.name));
++      var shading = xref.fetchIfRef(shadingRes.get(shadingName.name));
+       if (!shading)
 -        error('No shading object found');
++        error("No shading object found");
++
++      var shadingFill = this.getShading(shading);
+       this.save();
++      this.ctx.fillStyle = shadingFill;
 -      var bbox = shading.get('BBox');
++      // 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);
++
++      this.restore();
++    },
++    getShading: function(shading) {
++      shading = this.xref.fetchIfRef(shading);
++
++      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");
-             cs = ColorSpace.parse(cs, this.xref, this.res);
-             
-             var types = [null,
-                          null,
-                          this.getAxialShading,
-                          this.getRadialShading];
-             var typeNum = shading.get("ShadingType");
-             var shadingFn = types[typeNum];
-             if (!shadingFn)
-                 error("Unknown or NYI type of shading '"+ typeNum +"'");
-             return shadingFn.call(this, shading, cs);
-         },
-         getAxialShading: function(sh, cs) {
-             var coordsArr = sh.get("Coords");
-             var x0 = coordsArr[0], y0 = coordsArr[1],
-                 x1 = coordsArr[2], y1 = coordsArr[3];
 -      var cs = shading.get2('ColorSpace', 'CS');
 -      TODO('shading-fill color space');
--
-             var t0 = 0.0, t1 = 1.0;
-             if (sh.has("Domain")) {
-                 var domainArr = sh.get("Domain");
-                 t0 = domainArr[0], t1 = domainArr[1];
-             }
 -      var background = shading.get('Background');
++      var background = shading.get("Background");
+       if (background)
 -        TODO('handle background colors');
++        TODO("handle background colors");
 -      var types = [null,
 -                   this.fillFunctionShading,
 -                   this.fillAxialShading,
 -                   this.fillRadialShading];
 -
 -      var typeNum = shading.get('ShadingType');
 -      var fillFn = types[typeNum];
 -      if (!fillFn)
 -        error("Unknown or NYI type of shading '" + typeNum + "'");
 -      fillFn.apply(this, [shading]);
++      var cs = shading.get2("ColorSpace", "CS");
++      cs = ColorSpace.parse(cs, this.xref, this.res);
 -      this.restore();
 -    },
 -
 -    fillAxialShading: function(sh) {
 -      var coordsArr = sh.get('Coords');
++      var types = [null,
++          null,
++          this.getAxialShading,
++          this.getRadialShading];
++
++      var typeNum = shading.get("ShadingType");
++      var shadingFn = types[typeNum];
++      if (!shadingFn)
++        error("Unknown or NYI type of shading '"+ typeNum +"'");
++      return shadingFn.call(this, shading, cs);
++                },
++    getAxialShading: function(sh, cs) {
++      var coordsArr = sh.get("Coords");
+       var x0 = coordsArr[0], y0 = coordsArr[1],
 -      x1 = coordsArr[2], y1 = coordsArr[3];
++          x1 = coordsArr[2], y1 = coordsArr[3];
+       var t0 = 0.0, t1 = 1.0;
+       if (sh.has('Domain')) {
+         var domainArr = sh.get('Domain');
+         t0 = domainArr[0], t1 = domainArr[1];
+       }
  
-             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;
-             var diff = t1 - t0;
-             for (var i = t0; i <= t1; i += step) {
-                 var color = fn.func([i]);
-                 var rgbColor = cs.getRgb(color);
-                 gradient.addColorStop((i - t0) / diff,
-                         this.makeCssRgb.apply(this, rgbColor));
-             }
+       var extendStart = false, extendEnd = false;
 -      if (sh.has('Extend')) {
 -        var extendArr = sh.get('Extend');
++      if (sh.has("Extend")) {
++        var extendArr = sh.get("Extend");
+         extendStart = extendArr[0], extendEnd = extendArr[1];
 -        TODO('Support extend');
++        TODO("Support extend");
+       }
 -      var fnObj = sh.get('Function');
++      var fnObj = sh.get("Function");
+       fnObj = this.xref.fetchIfRef(fnObj);
+       if (IsArray(fnObj))
 -        error('No support for array of functions');
++        error("No support for array of functions");
+       else if (!IsPDFFunction(fnObj))
 -        error('Invalid function');
++        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;
++      var diff = t1 - t0;
+       for (var i = t0; i <= t1; i += step) {
 -        var c = fn.func([i]);
 -        gradient.addColorStop(i, this.makeCssRgb.apply(this, c));
++        var color = fn.func([i]);
++        var rgbColor = cs.getRgb(color);
++        gradient.addColorStop((i - t0) / diff,
++            this.makeCssRgb.apply(this, rgbColor));
+       }
  
-             return gradient;
-         },
-         getRadialShading: function(sh, cs) {
-             var coordsArr = sh.get("Coords");
-             var x0 = coordsArr[0], y0 = coordsArr[1],
-                 r0 = coordsArr[2];
-             var x1 = coordsArr[3], y1 = coordsArr[4],
-                 r1 = coordsArr[5];
 -      this.ctx.fillStyle = gradient;
--
-             var t0 = 0.0, t1 = 1.0;
-             if (sh.has("Domain")) {
-                 var domainArr = sh.get("Domain");
-                 t0 = domainArr[0], t1 = domainArr[1];
-             }
 -      // 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);
++      return gradient;
+     },
++    getRadialShading: function(sh, cs) {
++      var coordsArr = sh.get("Coords");
++      var x0 = coordsArr[0], y0 = coordsArr[1], r0 = coordsArr[2];
++      var x1 = coordsArr[3], y1 = coordsArr[4], r1 = coordsArr[5];
++
++      var t0 = 0.0, t1 = 1.0;
++      if (sh.has("Domain")) {
++        var domainArr = sh.get("Domain");
++        t0 = domainArr[0], t1 = domainArr[1];
++      }
 +
-             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.createRadialGradient(x0, y0, r0, x1, y1, r1);
-             // 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;
-             var diff = t1 - t0;
-             for (var i = t0; i <= t1; i += step) {
-                 var color = fn.func([i]);
-                 var rgbColor = cs.getRgb(color);
-                 gradient.addColorStop((i - t0) / diff, 
-                         this.makeCssRgb.apply(this, rgbColor));
-             }
++      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.createRadialGradient(x0, y0, r0, x1, y1, r1);
 -    fillRadialShading: function(sh) {
 -      TODO('radial shading');
++      // 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;
++      var diff = t1 - t0;
++
++      for (var i = t0; i <= t1; i += step) {
++        var color = fn.func([i]);
++        var rgbColor = cs.getRgb(color);
++        gradient.addColorStop((i - t0) / diff, 
++            this.makeCssRgb.apply(this, rgbColor));
++      }
 +
-             return gradient;
-         },
-         // 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");
-             }
++      return gradient;
+     },
+     // 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 opi = xobj.dict.get("OPI");
-             if (opi) {
-                 TODO("opi for xobject");
-             }
+       var oc = xobj.dict.get('OC');
+       if (oc) {
+         TODO('oc 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 opi = xobj.dict.get('OPI');
+       if (opi) {
+         TODO('opi for xobject');
+       }
  
-         paintFormXObject: function(ref, stream) {
-             this.save();
+       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 matrix = stream.dict.get("Matrix");
-             if (matrix && IsArray(matrix) && 6 == matrix.length)
-                 this.transform.apply(this, matrix);
+     paintFormXObject: function(ref, stream) {
+       this.save();
  
-             var bbox = stream.dict.get("BBox");
-             if (bbox && IsArray(bbox) && 4 == bbox.length) {
-                 this.rectangle.apply(this, bbox);
-                 this.clip();
-                 this.endPath();
-             }
+       var matrix = stream.dict.get('Matrix');
+       if (matrix && IsArray(matrix) && 6 == matrix.length)
+         this.transform.apply(this, matrix);
  
-             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 bbox = stream.dict.get('BBox');
+       if (bbox && IsArray(bbox) && 4 == bbox.length) {
+         this.rectangle.apply(this, bbox);
+         this.clip();
+         this.endPath();
+       }
  
-             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.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;
+       }
  
-     return constructor;
+       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;
+     }
+   };
+   return constructor;
  })();
  
  var ColorSpace = (function() {
@@@ -4633,283 -4620,236 +4688,283 @@@ var DeviceGrayCS = (function() 
  })();
  
  var DeviceRgbCS = (function() {
-     function constructor() {
-         this.name = "DeviceRGB";
-         this.numComps = 3;
-         this.defaultColor = [0, 0, 0];
+   function constructor() {
+     this.name = 'DeviceRGB';
+     this.numComps = 3;
+     this.defaultColor = [0, 0, 0];
+   }
+   constructor.prototype = {
 -    getRgb: function graycs_getRgb(color) {
++    getRgb: function rgbcs_getRgb(color) {
+       return color;
+     },
 -    getRgbBuffer: function graycs_getRgbBuffer(input) {
++    getRgbBuffer: function rgbcs_getRgbBuffer(input) {
+       return input;
      }
-     constructor.prototype = {
-         getRgb: function rgbcs_getRgb(color) {
-             return color;
-         },
-         getRgbBuffer: function rgbcs_getRgbBuffer(input) {
-             return input;
-         }
-     };
-     return constructor;
+   };
+   return constructor;
  })();
  
  var DeviceCmykCS = (function() {
-     function constructor() {
-         this.name = "DeviceCMYK";
-         this.numComps = 4;
-         this.defaultColor = [0, 0, 0, 1];
+   function constructor() {
+     this.name = 'DeviceCMYK';
+     this.numComps = 4;
+     this.defaultColor = [0, 0, 0, 1];
+   }
+   constructor.prototype = {
 -    getRgb: function graycs_getRgb(color) {
 -      var c = color[0], y = color[1], m = color[2], k = color[3];
 -      var ri = (1 - Math.min(1, c * (1 - k) + k)) | 0;
 -      var gi = (1 - Math.min(1, m * (1 - k) + k)) | 0;
 -      var bi = (1 - Math.min(1, y * (1 - k) + k)) | 0;
 -      return [ri, gi, bi];
 -    },
 -    getRgbBuffer: function graycs_getRgbBuffer(colorBuf) {
 -      error('conversion from rgb to cmyk not implemented for images');
++    getRgb: function cmykcs_getRgb(color) {
++      var c = color[0], m = color[1], y = color[2], k = color[3];
++      var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k;
++
++      var x, r, g, b;
++      // this is a matrix multiplication, unrolled for performance
++      // code is taken from the poppler implementation
++      x = c1 * m1 * y1 * k1; // 0 0 0 0
++      r = g = b = x;
++      x = c1 * m1 * y1 * k;  // 0 0 0 1
++      r += 0.1373 * x;
++      g += 0.1216 * x;
++      b += 0.1255 * x;
++      x = c1 * m1 * y  * k1; // 0 0 1 0
++      r += x;
++      g += 0.9490 * x;
++      x = c1 * m1 * y  * k;  // 0 0 1 1
++      r += 0.1098 * x;
++      g += 0.1020 * x;
++      x = c1 * m  * y1 * k1; // 0 1 0 0
++      r += 0.9255 * x;
++      b += 0.5490 * x;
++      x = c1 * m  * y1 * k;  // 0 1 0 1
++      r += 0.1412 * x;
++      x = c1 * m  * y  * k1; // 0 1 1 0
++      r += 0.9294 * x;
++      g += 0.1098 * x;
++      b += 0.1412 * x;
++      x = c1 * m  * y  * k;  // 0 1 1 1
++      r += 0.1333 * x;
++      x = c  * m1 * y1 * k1; // 1 0 0 0
++      g += 0.6784 * x;
++      b += 0.9373 * x;
++      x = c  * m1 * y1 * k;  // 1 0 0 1
++      g += 0.0588 * x;
++      b += 0.1412 * x;
++      x = c  * m1 * y  * k1; // 1 0 1 0
++      g += 0.6510 * x;
++      b += 0.3137 * x;
++      x = c  * m1 * y  * k;  // 1 0 1 1
++      g += 0.0745 * x;
++      x = c  * m  * y1 * k1; // 1 1 0 0
++      r += 0.1804 * x;
++      g += 0.1922 * x;
++      b += 0.5725 * x;
++      x = c  * m  * y1 * k;  // 1 1 0 1
++      b += 0.0078 * x;
++      x = c  * m  * y  * k1; // 1 1 1 0
++      r += 0.2118 * x;
++      g += 0.2119 * x;
++      b += 0.2235 * x;
++
++      return [r, g, b];
++    },
++    getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) {
++      error("conversion from rgb to cmyk not implemented for images");
+       return colorBuf;
      }
-     constructor.prototype = {
-         getRgb: function cmykcs_getRgb(color) {
-             var c = color[0], m = color[1], y = color[2], k = color[3];
-             var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k;
-             var x, r, g, b;
-             // this is a matrix multiplication, unrolled for performance
-             // code is taken from the poppler implementation
-             x = c1 * m1 * y1 * k1; // 0 0 0 0
-             r = g = b = x;
-             x = c1 * m1 * y1 * k;  // 0 0 0 1
-             r += 0.1373 * x;
-             g += 0.1216 * x;
-             b += 0.1255 * x;
-             x = c1 * m1 * y  * k1; // 0 0 1 0
-             r += x;
-             g += 0.9490 * x;
-             x = c1 * m1 * y  * k;  // 0 0 1 1
-             r += 0.1098 * x;
-             g += 0.1020 * x;
-             x = c1 * m  * y1 * k1; // 0 1 0 0
-             r += 0.9255 * x;
-             b += 0.5490 * x;
-             x = c1 * m  * y1 * k;  // 0 1 0 1
-             r += 0.1412 * x;
-             x = c1 * m  * y  * k1; // 0 1 1 0
-             r += 0.9294 * x;
-             g += 0.1098 * x;
-             b += 0.1412 * x;
-             x = c1 * m  * y  * k;  // 0 1 1 1
-             r += 0.1333 * x;
-             x = c  * m1 * y1 * k1; // 1 0 0 0
-             g += 0.6784 * x;
-             b += 0.9373 * x;
-             x = c  * m1 * y1 * k;  // 1 0 0 1
-             g += 0.0588 * x;
-             b += 0.1412 * x;
-             x = c  * m1 * y  * k1; // 1 0 1 0
-             g += 0.6510 * x;
-             b += 0.3137 * x;
-             x = c  * m1 * y  * k;  // 1 0 1 1
-             g += 0.0745 * x;
-             x = c  * m  * y1 * k1; // 1 1 0 0
-             r += 0.1804 * x;
-             g += 0.1922 * x;
-             b += 0.5725 * x;
-             x = c  * m  * y1 * k;  // 1 1 0 1
-             b += 0.0078 * x;
-             x = c  * m  * y  * k1; // 1 1 1 0
-             r += 0.2118 * x;
-             g += 0.2119 * x;
-             b += 0.2235 * x;
-             return [r, g, b];
-         },
-         getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) {
-             error("conversion from rgb to cmyk not implemented for images");
-             return colorBuf;
-         }
-     };
-     return constructor;
+   };
+   return constructor;
  })();
  
  var PDFImage = (function() {
-     function constructor(xref, res, image, inline) {
-         this.image = image;
-         if (image.getParams) {
-             // JPX/JPEG2000 streams directly contain bits per component
-             // and color space mode information.
-             TODO("get params from actual stream");
-             // var bits = ...
-             // var colorspace = ...
+   function constructor(xref, res, image, inline) {
+     this.image = image;
+     if (image.getParams) {
+       // JPX/JPEG2000 streams directly contain bits per component
+       // and color space mode information.
+       TODO('get params from actual stream');
+       // var bits = ...
+       // var colorspace = ...
+     }
+     // TODO cache rendered images?
+     var dict = image.dict;
+     this.width = dict.get2('Width', 'W');
+     this.height = dict.get2('Height', 'H');
+     if (this.width < 1 || this.height < 1)
 -      error('Invalid image width or height');
++  error('Invalid image width or height');
 -    this.interpolate = dict.get2('Interpolate', 'I') || false;
++this.interpolate = dict.get2('Interpolate', 'I') || false;
+     this.imageMask = dict.get2('ImageMask', 'IM') || false;
+     var bitsPerComponent = image.bitsPerComponent;
+     if (!bitsPerComponent) {
+       bitsPerComponent = dict.get2('BitsPerComponent', 'BPC');
+       if (!bitsPerComponent) {
+         if (this.imageMask)
+           bitsPerComponent = 1;
+         else
+           error('Bits per component missing in image');
+       }
+     }
+     this.bpc = bitsPerComponent;
+     var colorSpace = dict.get2('ColorSpace', 'CS');
+     this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
+     this.numComps = this.colorSpace.numComps;
+     this.decode = dict.get2('Decode', 'D');
+     var mask = xref.fetchIfRef(image.dict.get('Mask'));
+     var smask = xref.fetchIfRef(image.dict.get('SMask'));
+     if (mask) {
+       TODO('masked images');
+     } else if (smask) {
+       this.smask = new PDFImage(xref, res, smask);
+     }
+   };
+   constructor.prototype = {
+     getComponents: function getComponents(buffer) {
+       var bpc = this.bpc;
+       if (bpc == 8)
+         return buffer;
+       var width = this.width;
+       var height = this.height;
+       var numComps = this.numComps;
+       var length = width * height;
+       var bufferPos = 0;
+       var output = new Uint8Array(length);
+       if (bpc == 1) {
+         var rowComps = width * numComps;
+         var mask = 0;
+         var buf = 0;
+         for (var i = 0, ii = length; i < ii; ++i) {
+           if (i % rowComps == 0) {
+             mask = 0;
+             buf = 0;
+           } else {
+             mask >>= 1;
+           }
+           if (mask <= 0) {
+             buf = buffer[bufferPos++];
+             mask = 128;
+           }
+           var t = buf & mask;
+           if (t == 0)
+             output[i] = 0;
+           else
+             output[i] = 255;
          }
-         // TODO cache rendered images?
-         var dict = image.dict;
-         this.width = dict.get2("Width", "W");
-         this.height = dict.get2("Height", "H");
-         if (this.width < 1 || this.height < 1)
-             error("Invalid image width or height");
-         this.interpolate = dict.get2("Interpolate", "I") || false;
-         this.imageMask = dict.get2("ImageMask", "IM") || false;
-         var bitsPerComponent = image.bitsPerComponent;
-         if (!bitsPerComponent) {
-             bitsPerComponent = dict.get2("BitsPerComponent", "BPC");
-             if (!bitsPerComponent) {
-                 if (this.imageMask)
-                     bitsPerComponent = 1;
-                 else
-                     error("Bits per component missing in image");
-             }
+       } else {
+         var rowComps = width * numComps;
+         var bits = 0;
+         var buf = 0;
+         for (var i = 0, ii = length; i < ii; ++i) {
+           while (bits < bpc) {
+             buf = (buf << 8) | buffer[bufferPos++];
+             bits += 8;
+           }
+           var remainingBits = bits - bpc;
+           var ret = buf >> remainingBits;
+           if (i % rowComps == 0) {
+             buf = 0;
+             bits = 0;
+           } else {
+             buf = buf & ((1 << remainingBits) - 1);
+             bits = remainingBits;
+           }
+           output[i] = Math.round(255 * ret / ((1 << bpc) - 1));
          }
-         this.bpc = bitsPerComponent;
+       }
+       return this.colorSpace.getRbaBuffer(output);
+     },
+     getOpacity: function getOpacity() {
+       var smask = this.smask;
+       var width = this.width;
+       var height = this.height;
+       var buf = new Uint8Array(width * height);
+       if (smask) {
+         var sw = smask.width;
+         var sh = smask.height;
+         if (sw != this.width || sh != this.height)
+           error('smask dimensions do not match image dimensions');
+         smask.fillGrayBuffer(buf);
+         return buf;
+       } else {
+         for (var i = 0, ii = width * height; i < ii; ++i)
+           buf[i] = 255;
+       }
+       return buf;
+     },
+     fillRgbaBuffer: function fillRgbaBuffer(buffer) {
+       var numComps = this.numComps;
+       var width = this.width;
+       var height = this.height;
+       var bpc = this.bpc;
+       // rows start at byte boundary;
+       var rowBytes = (width * numComps * bpc + 7) >> 3;
+       var imgArray = this.image.getBytes(height * rowBytes);
+       var comps = this.getComponents(imgArray);
+       var compsPos = 0;
+       var opacity = this.getOpacity();
+       var opacityPos = 0;
+       var length = width * height * 4;
+       switch (numComps) {
+       case 1:
+         for (var i = 0; i < length; i += 4) {
+           var p = comps[compsPos++];
+           buffer[i] = p;
+           buffer[i + 1] = p;
+           buffer[i + 2] = p;
+           buffer[i + 3] = opacity[opacityPos++];
+         }
+         break;
+       case 3:
+         for (var i = 0; i < length; i += 4) {
+           buffer[i] = comps[compsPos++];
+           buffer[i + 1] = comps[compsPos++];
+           buffer[i + 2] = comps[compsPos++];
+           buffer[i + 3] = opacity[opacityPos++];
+         }
+         break;
+       default:
+         TODO('Images with ' + numComps + ' components per pixel');
+       }
+     },
+     fillGrayBuffer: function fillGrayBuffer(buffer) {
+       var numComps = this.numComps;
+       if (numComps != 1)
+         error('Reading gray scale from a color image');
  
-         var colorSpace = dict.get2("ColorSpace", "CS");
-         this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
+       var width = this.width;
+       var height = this.height;
+       var bpc = this.bpc;
  
-         this.numComps = this.colorSpace.numComps;
-         this.decode = dict.get2("Decode", "D");
+       // rows start at byte boundary;
+       var rowBytes = (width * numComps * bpc + 7) >> 3;
+       var imgArray = this.image.getBytes(height * rowBytes);
  
-         var mask = xref.fetchIfRef(image.dict.get("Mask"));
-         var smask = xref.fetchIfRef(image.dict.get("SMask"));
+       var comps = this.getComponents(imgArray);
+       var length = width * height;
  
-         if (mask) {
-             TODO("masked images");
-         } else if (smask) {
-             this.smask = new PDFImage(xref, res, smask);
-         }
-     };
-     
-     constructor.prototype = {
-         getComponents: function getComponents(buffer) {
-             var bpc = this.bpc;
-             if (bpc == 8)
-                 return buffer;
-             var width = this.width;
-             var height = this.height;
-             var numComps = this.numComps;
-         
-             var length = width * height;
-             var bufferPos = 0;
-             var output = new Uint8Array(length);
-             if (bpc == 1) {
-                 var rowComps = width * numComps;
-                 var mask = 0;
-                 var buf = 0;
-                 
-                 for (var i = 0, ii = length; i < ii; ++i) {
-                     if (i % rowComps == 0) {
-                         mask = 0;
-                         buf = 0;
-                     } else {
-                         mask >>= 1;
-                     }
-                     if (mask <= 0) {
-                         buf = buffer[bufferPos++];
-                         mask = 128;
-                     }
-                     var t = buf & mask;
-                     if (t == 0)
-                         output[i] = 0;
-                     else
-                         output[i] = 255;
-                 }
-             } else {
-                 var rowComps = width * numComps;
-                 var bits = 0;
-                 var buf = 0;
-                 for (var i = 0, ii = length; i < ii; ++i) {
-                     while (bits < bpc) {
-                         buf = (buf << 8) | buffer[bufferPos++];
-                         bits += 8;
-                     }
-                     var remainingBits = bits - bpc;
-                     var ret = buf >> remainingBits;
-                     
-                     if (i % rowComps == 0) {
-                         buf = 0;
-                         bits = 0;
-                     } else {
-                         buf = buf & ((1 << remainingBits) - 1);
-                         bits = remainingBits;
-                     }
-                     output[i] = Math.round(255 * ret / ((1 << bpc) - 1));
-                 }
-             }
-             return this.colorSpace.getRbaBuffer(output);
-         },
-         getOpacity: function getOpacity() {
-             var smask = this.smask;
-             var width = this.width;
-             var height = this.height;
-             var buf = new Uint8Array(width * height);
-             
-             if (smask) {
-                 var sw = smask.width;
-                 var sh = smask.height;
-                 if (sw != this.width || sh != this.height)
-                     error("smask dimensions do not match image dimensions");
-                 
-                 smask.fillGrayBuffer(buf);
-                 return buf;
-             } else {
-                 for (var i = 0, ii = width * height; i < ii; ++i)
-                     buf[i] = 255;
-             }
-             return buf;
-         },
-         fillRgbaBuffer: function fillRgbaBuffer(buffer) {
-             var numComps = this.numComps;
-             var width = this.width;
-             var height = this.height;
-             var bpc = this.bpc;
-             // rows start at byte boundary;
-             var rowBytes = (width * numComps * bpc + 7) >> 3;
-             var imgArray = this.image.getBytes(height * rowBytes);
-             
-             var comps = this.getComponents(imgArray);
-             var compsPos = 0;
-             var opacity = this.getOpacity();
-             var opacityPos = 0;
-             var length = width * height * 4;
-             switch (numComps) {
-                 case 1:
-                     for (var i = 0; i < length; i += 4) {
-                         var p = comps[compsPos++];
-                         buffer[i] = p;
-                         buffer[i+1] = p;
-                         buffer[i+2] = p;
-                         buffer[i+3] = opacity[opacityPos++];
-                     }
-                     break;
-                 case 3:
-                     for (var i = 0; i < length; i += 4) {
-                         buffer[i] = comps[compsPos++];
-                         buffer[i+1] = comps[compsPos++];
-                         buffer[i+2] = comps[compsPos++];
-                         buffer[i+3] = opacity[opacityPos++];
-                     }
-                     break;
-                 default:
-                     TODO("Images with "+ numComps + " components per pixel");
-             }
-         },
-         fillGrayBuffer: function fillGrayBuffer(buffer) {
-             var numComps = this.numComps;
-             if (numComps != 1)
-                 error("Reading gray scale from a color image");
-             var width = this.width;
-             var height = this.height;
-             var bpc = this.bpc;
-             // rows start at byte boundary;
-             var rowBytes = (width * numComps * bpc + 7) >> 3;
-             var imgArray = this.image.getBytes(height * rowBytes);
-             
-             var comps = this.getComponents(imgArray);
-             var length = width * height;
-             for (var i = 0; i < length; ++i)
-                 buffer[i] = comps[i];
-         },
-     };
-     return constructor;
+       for (var i = 0; i < length; ++i)
+         buffer[i] = comps[i];
+     }
+   };
+   return constructor;
  })();
  
  var PDFFunction = (function() {