]> git.parisson.com Git - pdf.js.git/commitdiff
Merge branch 'master' of https://github.com/andreasgal/pdf.js
authorIonuț G. Stan <ionut.g.stan@gmail.com>
Fri, 28 Oct 2011 11:34:56 +0000 (14:34 +0300)
committerIonuț G. Stan <ionut.g.stan@gmail.com>
Fri, 28 Oct 2011 11:34:56 +0000 (14:34 +0300)
Conflicts:
pdf.js

1  2 
src/canvas.js
src/util.js

diff --cc src/canvas.js
index 0000000000000000000000000000000000000000,b7045dc39e24400bec2264de3fafa946f402e726..23258b71fcec06ffbbc131a0942e794e10446566
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,844 +1,848 @@@
 -      return this.fillStroke();
+ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+ 'use strict';
+ // <canvas> contexts store most of the state we need natively.
+ // However, PDF needs a bit more state, which we store here.
+ var CanvasExtraState = (function canvasExtraState() {
+   function constructor(old) {
+     // Are soft masks and alpha values shapes or opacities?
+     this.alphaIsShape = false;
+     this.fontSize = 0;
+     this.textMatrix = IDENTITY_MATRIX;
+     this.leading = 0;
+     // Current point (in user coordinates)
+     this.x = 0;
+     this.y = 0;
+     // Start of text line (in text coordinates)
+     this.lineX = 0;
+     this.lineY = 0;
+     // Character and word spacing
+     this.charSpacing = 0;
+     this.wordSpacing = 0;
+     this.textHScale = 1;
+     // Color spaces
++    this.fillColorSpace = new DeviceGrayCS;
+     this.fillColorSpaceObj = null;
++    this.strokeColorSpace = new DeviceGrayCS;
+     this.strokeColorSpaceObj = null;
+     this.fillColorObj = null;
+     this.strokeColorObj = null;
+     // Default fore and background colors
+     this.fillColor = '#000000';
+     this.strokeColor = '#000000';
+     this.old = old;
+   }
+   constructor.prototype = {
+     clone: function canvasextra_clone() {
+       return Object.create(this);
+     },
+     setCurrentPoint: function canvasextra_setCurrentPoint(x, y) {
+       this.x = x;
+       this.y = y;
+     }
+   };
+   return constructor;
+ })();
+ function ScratchCanvas(width, height) {
+   var canvas = document.createElement('canvas');
+   canvas.width = width;
+   canvas.height = height;
+   return canvas;
+ }
+ var CanvasGraphics = (function canvasGraphics() {
+   // Defines the time the executeIRQueue is going to be executing
+   // before it stops and shedules a continue of execution.
+   var kExecutionTime = 50;
+   // Number of IR commands to execute before checking
+   // if we execute longer then `kExecutionTime`.
+   var kExecutionTimeCheck = 500;
+   function constructor(canvasCtx, objs) {
+     this.ctx = canvasCtx;
+     this.current = new CanvasExtraState();
+     this.stateStack = [];
+     this.pendingClip = null;
+     this.res = null;
+     this.xobjs = null;
+     this.ScratchCanvas = ScratchCanvas;
+     this.objs = objs;
+   }
+   var LINE_CAP_STYLES = ['butt', 'round', 'square'];
+   var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
+   var NORMAL_CLIP = {};
+   var EO_CLIP = {};
+   constructor.prototype = {
+     beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) {
+       var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
+       this.ctx.save();
+       switch (mediaBox.rotate) {
+         case 0:
+           this.ctx.transform(1, 0, 0, -1, 0, ch);
+           break;
+         case 90:
+           this.ctx.transform(0, 1, 1, 0, 0, 0);
+           break;
+         case 180:
+           this.ctx.transform(-1, 0, 0, 1, cw, 0);
+           break;
+         case 270:
+           this.ctx.transform(0, -1, -1, 0, cw, ch);
+           break;
+       }
+       this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
+     },
+     executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
+                                   executionStartIdx, continueCallback) {
+       var argsArray = codeIR.argsArray;
+       var fnArray = codeIR.fnArray;
+       var i = executionStartIdx || 0;
+       var argsArrayLen = argsArray.length;
+       var executionEndIdx;
+       var startTime = Date.now();
+       var objs = this.objs;
+       do {
+         executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck);
+         for (i; i < executionEndIdx; i++) {
+           if (fnArray[i] !== 'dependency') {
+             this[fnArray[i]].apply(this, argsArray[i]);
+           } else {
+             var deps = argsArray[i];
+             for (var n = 0; n < deps.length; n++) {
+               var depObjId = deps[n];
+               // If the promise isn't resolved yet, add the continueCallback
+               // to the promise and bail out.
+               if (!objs.isResolved(depObjId)) {
+                 objs.get(depObjId, continueCallback);
+                 return i;
+               }
+             }
+           }
+         }
+         // If the entire IRQueue was executed, stop as were done.
+         if (i == argsArrayLen) {
+           return i;
+         }
+         // If the execution took longer then a certain amount of time, shedule
+         // to continue exeution after a short delay.
+         // However, this is only possible if a 'continueCallback' is passed in.
+         if (continueCallback && (Date.now() - startTime) > kExecutionTime) {
+           setTimeout(continueCallback, 0);
+           return i;
+         }
+         // If the IRQueue isn't executed completly yet OR the execution time
+         // was short enough, do another execution round.
+       } while (true);
+     },
+     endDrawing: function canvasGraphicsEndDrawing() {
+       this.ctx.restore();
+     },
+     // Graphics state
+     setLineWidth: function canvasGraphicsSetLineWidth(width) {
+       this.ctx.lineWidth = width;
+     },
+     setLineCap: function canvasGraphicsSetLineCap(style) {
+       this.ctx.lineCap = LINE_CAP_STYLES[style];
+     },
+     setLineJoin: function canvasGraphicsSetLineJoin(style) {
+       this.ctx.lineJoin = LINE_JOIN_STYLES[style];
+     },
+     setMiterLimit: function canvasGraphicsSetMiterLimit(limit) {
+       this.ctx.miterLimit = limit;
+     },
+     setDash: function canvasGraphicsSetDash(dashArray, dashPhase) {
+       this.ctx.mozDash = dashArray;
+       this.ctx.mozDashOffset = dashPhase;
+     },
+     setRenderingIntent: function canvasGraphicsSetRenderingIntent(intent) {
+       TODO('set rendering intent: ' + intent);
+     },
+     setFlatness: function canvasGraphicsSetFlatness(flatness) {
+       TODO('set flatness: ' + flatness);
+     },
+     setGState: function canvasGraphicsSetGState(states) {
+       for (var i = 0; i < states.length; i++) {
+         var state = states[i];
+         var key = state[0];
+         var value = state[1];
+         switch (key) {
+           case 'LW':
+             this.setLineWidth(value);
+             break;
+           case 'LC':
+             this.setLineCap(value);
+             break;
+           case 'LJ':
+             this.setLineJoin(value);
+             break;
+           case 'ML':
+             this.setMiterLimit(value);
+             break;
+           case 'D':
+             this.setDash(value[0], value[1]);
+             break;
+           case 'RI':
+             this.setRenderingIntent(value);
+             break;
+           case 'FL':
+             this.setFlatness(value);
+             break;
+           case 'Font':
+             this.setFont(state[1], state[2]);
+             break;
+         }
+       }
+     },
+     save: function canvasGraphicsSave() {
+       this.ctx.save();
+       var old = this.current;
+       this.stateStack.push(old);
+       this.current = old.clone();
+     },
+     restore: function canvasGraphicsRestore() {
+       var prev = this.stateStack.pop();
+       if (prev) {
+         this.current = prev;
+         this.ctx.restore();
+       }
+     },
+     transform: function canvasGraphicsTransform(a, b, c, d, e, f) {
+       this.ctx.transform(a, b, c, d, e, f);
+     },
+     // Path
+     moveTo: function canvasGraphicsMoveTo(x, y) {
+       this.ctx.moveTo(x, y);
+       this.current.setCurrentPoint(x, y);
+     },
+     lineTo: function canvasGraphicsLineTo(x, y) {
+       this.ctx.lineTo(x, y);
+       this.current.setCurrentPoint(x, y);
+     },
+     curveTo: function canvasGraphicsCurveTo(x1, y1, x2, y2, x3, y3) {
+       this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
+       this.current.setCurrentPoint(x3, y3);
+     },
+     curveTo2: function canvasGraphicsCurveTo2(x2, y2, x3, y3) {
+       var current = this.current;
+       this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3);
+       current.setCurrentPoint(x3, y3);
+     },
+     curveTo3: function canvasGraphicsCurveTo3(x1, y1, x3, y3) {
+       this.curveTo(x1, y1, x3, y3, x3, y3);
+       this.current.setCurrentPoint(x3, y3);
+     },
+     closePath: function canvasGraphicsClosePath() {
+       this.ctx.closePath();
+     },
+     rectangle: function canvasGraphicsRectangle(x, y, width, height) {
+       this.ctx.rect(x, y, width, height);
+     },
+     stroke: function canvasGraphicsStroke() {
+       var ctx = this.ctx;
+       var strokeColor = this.current.strokeColor;
+       if (strokeColor && strokeColor.hasOwnProperty('type') &&
+           strokeColor.type === 'Pattern') {
+         // for patterns, we transform to pattern space, calculate
+         // the pattern, call stroke, and restore to user space
+         ctx.save();
+         ctx.strokeStyle = strokeColor.getPattern(ctx);
+         ctx.stroke();
+         ctx.restore();
+       } else {
+         ctx.stroke();
+       }
+       this.consumePath();
+     },
+     closeStroke: function canvasGraphicsCloseStroke() {
+       this.closePath();
+       this.stroke();
+     },
+     fill: function canvasGraphicsFill() {
+       var ctx = this.ctx;
+       var fillColor = this.current.fillColor;
+       if (fillColor && fillColor.hasOwnProperty('type') &&
+           fillColor.type === 'Pattern') {
+         ctx.save();
+         ctx.fillStyle = fillColor.getPattern(ctx);
+         ctx.fill();
+         ctx.restore();
+       } else {
+         ctx.fill();
+       }
+       this.consumePath();
+     },
+     eoFill: function canvasGraphicsEoFill() {
+       var savedFillRule = this.setEOFillRule();
+       this.fill();
+       this.restoreFillRule(savedFillRule);
+     },
+     fillStroke: function canvasGraphicsFillStroke() {
+       var ctx = this.ctx;
+       var fillColor = this.current.fillColor;
+       if (fillColor && fillColor.hasOwnProperty('type') &&
+           fillColor.type === 'Pattern') {
+         ctx.save();
+         ctx.fillStyle = fillColor.getPattern(ctx);
+         ctx.fill();
+         ctx.restore();
+       } else {
+         ctx.fill();
+       }
+       var strokeColor = this.current.strokeColor;
+       if (strokeColor && strokeColor.hasOwnProperty('type') &&
+           strokeColor.type === 'Pattern') {
+         ctx.save();
+         ctx.strokeStyle = strokeColor.getPattern(ctx);
+         ctx.stroke();
+         ctx.restore();
+       } else {
+         ctx.stroke();
+       }
+       this.consumePath();
+     },
+     eoFillStroke: function canvasGraphicsEoFillStroke() {
+       var savedFillRule = this.setEOFillRule();
+       this.fillStroke();
+       this.restoreFillRule(savedFillRule);
+     },
+     closeFillStroke: function canvasGraphicsCloseFillStroke() {
++      this.closePath();
++      this.fillStroke();
+     },
+     closeEOFillStroke: function canvasGraphicsCloseEOFillStroke() {
+       var savedFillRule = this.setEOFillRule();
++      this.closePath();
+       this.fillStroke();
+       this.restoreFillRule(savedFillRule);
+     },
+     endPath: function canvasGraphicsEndPath() {
+       this.consumePath();
+     },
+     // Clipping
+     clip: function canvasGraphicsClip() {
+       this.pendingClip = NORMAL_CLIP;
+     },
+     eoClip: function canvasGraphicsEoClip() {
+       this.pendingClip = EO_CLIP;
+     },
+     // Text
+     beginText: function canvasGraphicsBeginText() {
+       this.current.textMatrix = IDENTITY_MATRIX;
+       this.current.x = this.current.lineX = 0;
+       this.current.y = this.current.lineY = 0;
+     },
+     endText: function canvasGraphicsEndText() {
+     },
+     setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) {
+       this.current.charSpacing = spacing;
+     },
+     setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) {
+       this.current.wordSpacing = spacing;
+     },
+     setHScale: function canvasGraphicsSetHScale(scale) {
+       this.current.textHScale = scale / 100;
+     },
+     setLeading: function canvasGraphicsSetLeading(leading) {
+       this.current.leading = -leading;
+     },
+     setFont: function canvasGraphicsSetFont(fontRefName, size) {
+       var fontObj = this.objs.get(fontRefName).fontObj;
+       if (!fontObj) {
+         throw 'Can\'t find font for ' + fontRefName;
+       }
+       var name = fontObj.loadedName || 'sans-serif';
+       this.current.font = fontObj;
+       this.current.fontSize = size;
+       var name = fontObj.loadedName || 'sans-serif';
+       var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
+                                  (fontObj.bold ? 'bold' : 'normal');
+       var italic = fontObj.italic ? 'italic' : 'normal';
+       var serif = fontObj.serif ? 'serif' : 'sans-serif';
+       var typeface = '"' + name + '", ' + serif;
+       var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface;
+       this.ctx.font = rule;
+     },
+     setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) {
+       TODO('text rendering mode: ' + mode);
+     },
+     setTextRise: function canvasGraphicsSetTextRise(rise) {
+       TODO('text rise: ' + rise);
+     },
+     moveText: function canvasGraphicsMoveText(x, y) {
+       this.current.x = this.current.lineX += x;
+       this.current.y = this.current.lineY += y;
+     },
+     setLeadingMoveText: function canvasGraphicsSetLeadingMoveText(x, y) {
+       this.setLeading(-y);
+       this.moveText(x, y);
+     },
+     setTextMatrix: function canvasGraphicsSetTextMatrix(a, b, c, d, e, f) {
+       this.current.textMatrix = [a, b, c, d, e, f];
+       this.current.x = this.current.lineX = 0;
+       this.current.y = this.current.lineY = 0;
+     },
+     nextLine: function canvasGraphicsNextLine() {
+       this.moveText(0, this.current.leading);
+     },
+     showText: function canvasGraphicsShowText(text) {
+       var ctx = this.ctx;
+       var current = this.current;
+       var font = current.font;
+       var glyphs = font.charsToGlyphs(text);
+       var fontSize = current.fontSize;
+       var charSpacing = current.charSpacing;
+       var wordSpacing = current.wordSpacing;
+       var textHScale = current.textHScale;
+       var glyphsLength = glyphs.length;
+       if (font.coded) {
+         ctx.save();
+         ctx.transform.apply(ctx, current.textMatrix);
+         ctx.translate(current.x, current.y);
+         var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
+         ctx.scale(1 / textHScale, 1);
+         for (var i = 0; i < glyphsLength; ++i) {
+           var glyph = glyphs[i];
+           if (glyph === null) {
+             // word break
+             this.ctx.translate(wordSpacing, 0);
+             continue;
+           }
+           this.save();
+           ctx.scale(fontSize, fontSize);
+           ctx.transform.apply(ctx, fontMatrix);
+           this.executeIRQueue(glyph.IRQueue);
+           this.restore();
+           var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
+           var width = transformed[0] * fontSize + charSpacing;
+           ctx.translate(width, 0);
+           current.x += width;
+         }
+         ctx.restore();
+       } else {
+         ctx.save();
+         ctx.transform.apply(ctx, current.textMatrix);
+         ctx.scale(1, -1);
+         ctx.translate(current.x, -1 * current.y);
+         ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
+         ctx.scale(1 / textHScale, 1);
+         var width = 0;
+         for (var i = 0; i < glyphsLength; ++i) {
+           var glyph = glyphs[i];
+           if (glyph === null) {
+             // word break
+             width += wordSpacing;
+             continue;
+           }
+           var unicode = glyph.unicode;
+           var char = (unicode >= 0x10000) ?
+             String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
+             0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
+           ctx.fillText(char, width, 0);
+           width += glyph.width * fontSize * 0.001 + charSpacing;
+         }
+         current.x += width;
+         ctx.restore();
+       }
+     },
+     showSpacedText: function canvasGraphicsShowSpacedText(arr) {
+       var ctx = this.ctx;
+       var current = this.current;
+       var fontSize = current.fontSize;
+       var textHScale = current.textHScale;
+       var arrLength = arr.length;
+       for (var i = 0; i < arrLength; ++i) {
+         var e = arr[i];
+         if (isNum(e)) {
+           current.x -= e * 0.001 * fontSize * textHScale;
+         } else if (isString(e)) {
+           this.showText(e);
+         } else {
+           malformed('TJ array element ' + e + ' is not string or num');
+         }
+       }
+     },
+     nextLineShowText: function canvasGraphicsNextLineShowText(text) {
+       this.nextLine();
+       this.showText(text);
+     },
+     nextLineSetSpacingShowText:
+       function canvasGraphicsNextLineSetSpacingShowText(wordSpacing,
+                                                         charSpacing,
+                                                         text) {
+       this.setWordSpacing(wordSpacing);
+       this.setCharSpacing(charSpacing);
+       this.nextLineShowText(text);
+     },
+     // Type3 fonts
+     setCharWidth: function canvasGraphicsSetCharWidth(xWidth, yWidth) {
+       // We can safely ignore this since the width should be the same
+       // as the width in the Widths array.
+     },
+     setCharWidthAndBounds: function canvasGraphicsSetCharWidthAndBounds(xWidth,
+                                                                         yWidth,
+                                                                         llx,
+                                                                         lly,
+                                                                         urx,
+                                                                         ury) {
+       // TODO According to the spec we're also suppose to ignore any operators
+       // that set color or include images while processing this type3 font.
+       this.rectangle(llx, lly, urx - llx, ury - lly);
+       this.clip();
+       this.endPath();
+     },
+     // Color
+     setStrokeColorSpace:
+     function canvasGraphicsSetStrokeColorSpacefunction(raw) {
+       this.current.strokeColorSpace = ColorSpace.fromIR(raw);
+     },
+     setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) {
+       this.current.fillColorSpace = ColorSpace.fromIR(raw);
+     },
+     setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) {
+       var cs = this.current.strokeColorSpace;
+       var color = cs.getRgb(arguments);
+       this.setStrokeRGBColor.apply(this, color);
+     },
+     getColorN_IR_Pattern: function(IR, cs) {
+       if (IR[0] == 'TilingPattern') {
+         var args = IR[1];
+         var base = cs.base;
+         var color;
+         if (base) {
+           var baseComps = base.numComps;
+           color = [];
+           for (var i = 0; i < baseComps; ++i)
+             color.push(args[i]);
+           color = base.getRgb(color);
+         }
+         var pattern = new TilingPattern(IR, color, this.ctx, this.objs);
+       } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
+         var pattern = Pattern.shadingFromIR(this.ctx, IR);
+       } else {
+         throw 'Unkown IR type';
+       }
+       return pattern;
+     },
+     setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) {
+       var cs = this.current.strokeColorSpace;
+       if (cs.name == 'Pattern') {
+         this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs);
+       } else {
+         this.setStrokeColor.apply(this, arguments);
+       }
+     },
+     setFillColor: function canvasGraphicsSetFillColor(/*...*/) {
+       var cs = this.current.fillColorSpace;
+       var color = cs.getRgb(arguments);
+       this.setFillRGBColor.apply(this, color);
+     },
+     setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) {
+       var cs = this.current.fillColorSpace;
+       if (cs.name == 'Pattern') {
+         this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs);
+       } else {
+         this.setFillColor.apply(this, arguments);
+       }
+     },
+     setStrokeGray: function canvasGraphicsSetStrokeGray(gray) {
+       this.setStrokeRGBColor(gray, gray, gray);
+     },
+     setFillGray: function canvasGraphicsSetFillGray(gray) {
+       this.setFillRGBColor(gray, gray, gray);
+     },
+     setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) {
+       var color = Util.makeCssRgb(r, g, b);
+       this.ctx.strokeStyle = color;
+       this.current.strokeColor = color;
+     },
+     setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) {
+       var color = Util.makeCssRgb(r, g, b);
+       this.ctx.fillStyle = color;
+       this.current.fillColor = color;
+     },
+     setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) {
+       var color = Util.makeCssCmyk(c, m, y, k);
+       this.ctx.strokeStyle = color;
+       this.current.strokeColor = color;
+     },
+     setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) {
+       var color = Util.makeCssCmyk(c, m, y, k);
+       this.ctx.fillStyle = color;
+       this.current.fillColor = color;
+     },
+     shadingFill: function canvasGraphicsShadingFill(patternIR) {
+       var ctx = this.ctx;
+       this.save();
+       ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR);
+       var inv = ctx.mozCurrentTransformInverse;
+       if (inv) {
+         var canvas = ctx.canvas;
+         var width = canvas.width;
+         var height = canvas.height;
+         var bl = Util.applyTransform([0, 0], inv);
+         var br = Util.applyTransform([0, width], inv);
+         var ul = Util.applyTransform([height, 0], inv);
+         var ur = Util.applyTransform([height, width], inv);
+         var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
+         var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
+         var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
+         var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
+         this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
+       } else {
+         // 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();
+     },
+     // Images
+     beginInlineImage: function canvasGraphicsBeginInlineImage() {
+       error('Should not call beginInlineImage');
+     },
+     beginImageData: function canvasGraphicsBeginImageData() {
+       error('Should not call beginImageData');
+     },
+     paintFormXObjectBegin:
+     function canvasGraphicsPaintFormXObject(matrix, bbox) {
+       this.save();
+       if (matrix && isArray(matrix) && 6 == matrix.length)
+         this.transform.apply(this, matrix);
+       if (bbox && isArray(bbox) && 4 == bbox.length) {
+         var width = bbox[2] - bbox[0];
+         var height = bbox[3] - bbox[1];
+         this.rectangle(bbox[0], bbox[1], width, height);
+         this.clip();
+         this.endPath();
+       }
+     },
+     paintFormXObjectEnd: function() {
+       this.restore();
+     },
+     paintJpegXObject: function(objId, w, h) {
+       var image = this.objs.get(objId);
+       if (!image) {
+         error('Dependent image isn\'t ready yet');
+       }
+       this.save();
+       var ctx = this.ctx;
+       // scale the image to the unit square
+       ctx.scale(1 / w, -1 / h);
+       var domImage = image.getImage();
+       ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
+                     0, -h, w, h);
+       this.restore();
+     },
+     paintImageMaskXObject: function(imgArray, inverseDecode, width, height) {
+       function applyStencilMask(buffer, inverseDecode) {
+         var imgArrayPos = 0;
+         var i, j, mask, buf;
+         // removing making non-masked pixels transparent
+         var bufferPos = 3; // alpha component offset
+         for (i = 0; i < height; i++) {
+           mask = 0;
+           for (j = 0; j < width; j++) {
+             if (!mask) {
+               buf = imgArray[imgArrayPos++];
+               mask = 128;
+             }
+             if (!(buf & mask) == inverseDecode) {
+               buffer[bufferPos] = 0;
+             }
+             bufferPos += 4;
+             mask >>= 1;
+           }
+         }
+       }
+       this.save();
+       var ctx = this.ctx;
+       var w = width, h = height;
+       // scale the image to the unit square
+       ctx.scale(1 / w, -1 / h);
+       var tmpCanvas = new this.ScratchCanvas(w, h);
+       var tmpCtx = tmpCanvas.getContext('2d');
+       var fillColor = this.current.fillColor;
+       tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
+                           fillColor.type === 'Pattern') ?
+                           fillColor.getPattern(tmpCtx) : fillColor;
+       tmpCtx.fillRect(0, 0, w, h);
+       var imgData = tmpCtx.getImageData(0, 0, w, h);
+       var pixels = imgData.data;
+       applyStencilMask(pixels, inverseDecode);
+       tmpCtx.putImageData(imgData, 0, 0);
+       ctx.drawImage(tmpCanvas, 0, -h);
+       this.restore();
+     },
+     paintImageXObject: function(imgData) {
+       this.save();
+       var ctx = this.ctx;
+       var w = imgData.width;
+       var h = imgData.height;
+       // scale the image to the unit square
+       ctx.scale(1 / w, -1 / h);
+       var tmpCanvas = new this.ScratchCanvas(w, h);
+       var tmpCtx = tmpCanvas.getContext('2d');
+       var tmpImgData;
+       // Some browsers can set an UInt8Array directly as imageData, some
+       // can't. As long as we don't have proper feature detection, just
+       // copy over each pixel and set the imageData that way.
+       tmpImgData = tmpCtx.getImageData(0, 0, w, h);
+       // Copy over the imageData.
+       var tmpImgDataPixels = tmpImgData.data;
+       var len = tmpImgDataPixels.length;
+       while (len--) {
+         tmpImgDataPixels[len] = imgData.data[len];
+       }
+       tmpCtx.putImageData(tmpImgData, 0, 0);
+       ctx.drawImage(tmpCanvas, 0, -h);
+       this.restore();
+     },
+     // Marked content
+     markPoint: function canvasGraphicsMarkPoint(tag) {
+       TODO('Marked content');
+     },
+     markPointProps: function canvasGraphicsMarkPointProps(tag, properties) {
+       TODO('Marked content');
+     },
+     beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) {
+       TODO('Marked content');
+     },
+     beginMarkedContentProps:
+       function canvasGraphicsBeginMarkedContentProps(tag, properties) {
+       TODO('Marked content');
+     },
+     endMarkedContent: function canvasGraphicsEndMarkedContent() {
+       TODO('Marked content');
+     },
+     // Compatibility
+     beginCompat: function canvasGraphicsBeginCompat() {
+       TODO('ignore undefined operators (should we do that anyway?)');
+     },
+     endCompat: function canvasGraphicsEndCompat() {
+       TODO('stop ignoring undefined operators');
+     },
+     // Helper functions
+     consumePath: function canvasGraphicsConsumePath() {
+       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();
+     },
+     // We generally keep the canvas context set for
+     // nonzero-winding, and just set evenodd for the operations
+     // that need them.
+     setEOFillRule: function canvasGraphicsSetEOFillRule() {
+       var savedFillRule = this.ctx.mozFillRule;
+       this.ctx.mozFillRule = 'evenodd';
+       return savedFillRule;
+     },
+     restoreFillRule: function canvasGraphicsRestoreFillRule(rule) {
+       this.ctx.mozFillRule = rule;
+     }
+   };
+   return constructor;
+ })();
diff --cc src/util.js
index 0000000000000000000000000000000000000000,d8d50337b41a1dce35c9a49e675f62c74fc058ed..9537320578e3bc6b8c9f6ce31b17550e26a1b303
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,287 +1,286 @@@
 -  var stackStr;
+ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+ 'use strict';
+ function log(msg) {
+   if (console && console.log)
+     console.log(msg);
+   else if (print)
+     print(msg);
+ }
+ function warn(msg) {
+   if (verbosity >= WARNINGS)
+     log('Warning: ' + msg);
+ }
+ function backtrace() {
 -    stackStr = e.stack;
+   try {
+     throw new Error();
+   } catch (e) {
 -  return stackStr.split('\n').slice(1).join('\n');
++    return e.stack ? e.stack.split('\n').slice(2).join('\n') : "";
+   }
 -    set data(data) {
 -      if (data === undefined) {
+ }
+ function error(msg) {
++  log("Error: " + msg);
+   log(backtrace());
+   throw new Error(msg);
+ }
+ function TODO(what) {
+   if (verbosity >= TODOS)
+     log('TODO: ' + what);
+ }
+ function malformed(msg) {
+   error('Malformed PDF: ' + msg);
+ }
+ function assert(cond, msg) {
+   if (!cond)
+     error(msg);
+ }
+ // In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
+ // behavior is undefined.
+ function assertWellFormed(cond, msg) {
+   if (!cond)
+     malformed(msg);
+ }
+ function shadow(obj, prop, value) {
+   Object.defineProperty(obj, prop, { value: value,
+                                      enumerable: true,
+                                      configurable: true,
+                                      writable: false });
+   return value;
+ }
+ function bytesToString(bytes) {
+   var str = '';
+   var length = bytes.length;
+   for (var n = 0; n < length; ++n)
+     str += String.fromCharCode(bytes[n]);
+   return str;
+ }
+ function stringToBytes(str) {
+   var length = str.length;
+   var bytes = new Uint8Array(length);
+   for (var n = 0; n < length; ++n)
+     bytes[n] = str.charCodeAt(n) & 0xFF;
+   return bytes;
+ }
+ var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
+ var Util = (function utilUtil() {
+   function constructor() {}
+   constructor.makeCssRgb = function makergb(r, g, b) {
+     var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0;
+     return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
+   };
+   constructor.makeCssCmyk = function makecmyk(c, m, y, k) {
+     c = (new DeviceCmykCS()).getRgb([c, m, y, k]);
+     var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0;
+     return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
+   };
+   constructor.applyTransform = function apply(p, m) {
+     var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+     var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+     return [xt, yt];
+   };
+   return constructor;
+ })();
+ var PDFStringTranslateTable = [
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
+   0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
+   0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
+   0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
+ ];
+ function stringToPDFString(str) {
+   var i, n = str.length, str2 = '';
+   if (str[0] === '\xFE' && str[1] === '\xFF') {
+     // UTF16BE BOM
+     for (i = 2; i < n; i += 2)
+       str2 += String.fromCharCode(
+         (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1));
+   } else {
+     for (i = 0; i < n; ++i) {
+       var code = PDFStringTranslateTable[str.charCodeAt(i)];
+       str2 += code ? String.fromCharCode(code) : str.charAt(i);
+     }
+   }
+   return str2;
+ }
+ function isBool(v) {
+   return typeof v == 'boolean';
+ }
+ function isInt(v) {
+   return typeof v == 'number' && ((v | 0) == v);
+ }
+ function isNum(v) {
+   return typeof v == 'number';
+ }
+ function isString(v) {
+   return typeof v == 'string';
+ }
+ function isNull(v) {
+   return v === null;
+ }
+ function isName(v) {
+   return v instanceof Name;
+ }
+ function isCmd(v, cmd) {
+   return v instanceof Cmd && (!cmd || v.cmd == cmd);
+ }
+ function isDict(v, type) {
+   return v instanceof Dict && (!type || v.get('Type').name == type);
+ }
+ function isArray(v) {
+   return v instanceof Array;
+ }
+ function isStream(v) {
+   return typeof v == 'object' && v != null && ('getChar' in v);
+ }
+ function isArrayBuffer(v) {
+   return typeof v == 'object' && v != null && ('byteLength' in v);
+ }
+ function isRef(v) {
+   return v instanceof Ref;
+ }
+ function isPDFFunction(v) {
+   var fnDict;
+   if (typeof v != 'object')
+     return false;
+   else if (isDict(v))
+     fnDict = v;
+   else if (isStream(v))
+     fnDict = v.dict;
+   else
+     return false;
+   return fnDict.has('FunctionType');
+ }
+ /**
+  * 'Promise' object.
+  * Each object that is stored in PDFObjects is based on a Promise object that
+  * contains the status of the object and the data. There migth be situations,
+  * where a function want to use the value of an object, but it isn't ready at
+  * that time. To get a notification, once the object is ready to be used, s.o.
+  * can add a callback using the `then` method on the promise that then calls
+  * the callback once the object gets resolved.
+  * A promise can get resolved only once and only once the data of the promise
+  * can be set. If any of these happens twice or the data is required before
+  * it was set, an exception is throw.
+  */
+ var Promise = (function() {
+   var EMPTY_PROMISE = {};
+   /**
+    * If `data` is passed in this constructor, the promise is created resolved.
+    * If there isn't data, it isn't resolved at the beginning.
+    */
+   function Promise(name, data) {
+     this.name = name;
+     // If you build a promise and pass in some data it's already resolved.
+     if (data != null) {
+       this.isResolved = true;
+       this._data = data;
+       this.hasData = true;
+     } else {
+       this.isResolved = false;
+       this._data = EMPTY_PROMISE;
+     }
+     this.callbacks = [];
+   };
+   Promise.prototype = {
+     hasData: false,
 -      this._data = data;
++    set data(value) {
++      if (value === undefined) {
+         return;
+       }
+       if (this._data !== EMPTY_PROMISE) {
+         throw 'Promise ' + this.name +
+                                 ': Cannot set the data of a promise twice';
+       }
 -        this.onDataCallback(data);
++      this._data = value;
+       this.hasData = true;
+       if (this.onDataCallback) {
++        this.onDataCallback(value);
+       }
+     },
+     get data() {
+       if (this._data === EMPTY_PROMISE) {
+         throw 'Promise ' + this.name + ': Cannot get data that isn\'t set';
+       }
+       return this._data;
+     },
+     onData: function(callback) {
+       if (this._data !== EMPTY_PROMISE) {
+         callback(this._data);
+       } else {
+         this.onDataCallback = callback;
+       }
+     },
+     resolve: function(data) {
+       if (this.isResolved) {
+         throw 'A Promise can be resolved only once ' + this.name;
+       }
+       this.isResolved = true;
+       this.data = data;
+       var callbacks = this.callbacks;
+       for (var i = 0; i < callbacks.length; i++) {
+         callbacks[i].call(null, data);
+       }
+     },
+     then: function(callback) {
+       if (!callback) {
+         throw 'Requiring callback' + this.name;
+       }
+       // If the promise is already resolved, call the callback directly.
+       if (this.isResolved) {
+         var data = this.data;
+         callback.call(null, data);
+       } else {
+         this.callbacks.push(callback);
+       }
+     }
+   };
+   return Promise;
+ })();