diff --git a/pdf.js b/pdf.js index 385b4fe6e..0af728baf 100644 --- a/pdf.js +++ b/pdf.js @@ -3851,7 +3851,7 @@ var PartialEvaluator = (function() { // contexts store most of the state we need natively. // However, PDF needs a bit more state, which we store here. var CanvasExtraState = (function() { - function constructor() { + function constructor(old) { // Are soft masks and alpha values shapes or opacities? this.alphaIsShape = false; this.fontSize = 0; @@ -3868,10 +3868,18 @@ var CanvasExtraState = (function() { this.wordSpace = 0; this.textHScale = 100; // Color spaces - this.fillColorSpace = null; - this.strokeColorSpace = null; + this.fillColorSpaceObj = null; + this.strokeColorSpaceObj = null; + this.fillColorObj = null; + this.strokeColorObj = null; + + this.old = old; } + constructor.prototype = { + clone: function canvasextra_clone() { + return Object.create(this); + } }; return constructor; })(); @@ -3902,9 +3910,6 @@ var CanvasGraphics = (function() { var NORMAL_CLIP = {}; var EO_CLIP = {}; - // Used for tiling patterns - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - constructor.prototype = { beginDrawing: function(mediaBox) { var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; @@ -3967,8 +3972,9 @@ var CanvasGraphics = (function() { if (this.ctx.$saveCurrentX) { this.ctx.$saveCurrentX(); } - this.stateStack.push(this.current); - this.current = new CanvasExtraState(); + var old = this.current; + this.stateStack.push(old); + this.current = old.clone(); }, restore: function() { var prev = this.stateStack.pop(); @@ -4007,7 +4013,19 @@ var CanvasGraphics = (function() { this.ctx.rect(x, y, width, height); }, stroke: function() { - this.ctx.stroke(); + var ctx = this.ctx; + var strokeColor = this.current.strokeColor; + if (strokeColor && 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() { @@ -4015,7 +4033,18 @@ var CanvasGraphics = (function() { this.stroke(); }, fill: function() { - this.ctx.fill(); + var ctx = this.ctx; + var fillColor = this.current.fillColor; + + if (fillColor && fillColor.type === "Pattern") { + ctx.save(); + ctx.fillStyle = fillColor.getPattern(ctx); + ctx.fill(); + ctx.restore(); + } else { + ctx.fill(); + } + this.consumePath(); }, eoFill: function() { @@ -4024,8 +4053,28 @@ var CanvasGraphics = (function() { this.restoreFillRule(savedFillRule); }, fillStroke: function() { - this.ctx.fill(); - this.ctx.stroke(); + var ctx = this.ctx; + + var fillColor = this.current.fillColor; + if (fillColor && 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.type === "Pattern") { + ctx.save(); + ctx.strokeStyle = strokeColor.getPattern(ctx); + ctx.stroke(); + ctx.restore(); + } else { + ctx.stroke(); + } + this.consumePath(); }, eoFillStroke: function() { @@ -4208,168 +4257,41 @@ var CanvasGraphics = (function() { ColorSpace.parse(space, this.xref, this.res); }, setStrokeColor: function(/*...*/) { - var cs = this.getStrokeColorSpace(); + var cs = this.current.strokeColorSpace; var color = cs.getRgb(arguments); this.setStrokeRGBColor.apply(this, color); }, setStrokeColorN: function(/*...*/) { - var cs = this.getStrokeColorSpace(); + var cs = this.current.strokeColorSpace; if (cs.name == 'Pattern') { - this.ctx.strokeStyle = this.getPattern(cs, arguments); + // wait until fill to actually get the pattern, since Canvas + // calcualtes the pattern according to the current coordinate space, + // not the space when the pattern is set. + var pattern = Pattern.parse(arguments, cs, this.xref, this.res, + this.ctx); + this.current.strokeColor = pattern; } else { this.setStrokeColor.apply(this, arguments); } }, setFillColor: function(/*...*/) { - var cs = this.getFillColorSpace(); + var cs = this.current.fillColorSpace; var color = cs.getRgb(arguments); this.setFillRGBColor.apply(this, color); }, setFillColorN: function(/*...*/) { - var cs = this.getFillColorSpace(); + var cs = this.current.fillColorSpace; if (cs.name == 'Pattern') { - this.ctx.fillStyle = this.getPattern(cs, arguments); + // wait until fill to actually get the pattern + var pattern = Pattern.parse(arguments, cs, this.xref, this.res, + this.ctx); + this.current.fillColor = pattern; } else { this.setFillColor.apply(this, arguments); } }, - getPattern: function(cs, args) { - var length = args.length; - var base = cs.base; - if (base) { - var baseComps = base.numComps; - - var color = []; - for (var i = 0; i < baseComps; ++i) - color.push(args[i]); - - color = base.getRgb(color); - } - - var patternName = args[length - 1]; - 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.getTilingPattern, this.getShadingPattern]; - - var typeNum = dict.get("PatternType"); - var patternFn = types[typeNum]; - if (!patternFn) - error("Unhandled pattern type"); - return patternFn.call(this, pattern, dict, color); - }, - getShadingPattern: function(pattern, dict) { - var matrix = dict.get("Matrix"); - - this.save(); - this.transform.apply(this, matrix); - var shading = this.getShading(pattern.get("Shading")); - this.restore(); - - TODO('store transform so it can be applied before every fill'); - return shading; - }, - getTilingPattern: function(pattern, dict, color) { - 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; - - - TODO('TilingType'); - - var matrix = dict.get('Matrix') || IDENTITY_MATRIX; - - var bbox = dict.get('BBox'); - var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; - - var xstep = dict.get('XStep'); - var ystep = dict.get('YStep'); - - // top left corner should correspond to the top left of the bbox - var topLeft = this.applyTransform(x0, y0, matrix); - // we want the canvas to be as large as the step size - var botRight = this.applyTransform(x0 + xstep, y0 + ystep, matrix); - - var width = botRight[0] - topLeft[0]; - var height = botRight[1] - topLeft[1]; - - // TODO: hack to avoid OOM, remove then pattern code is fixed - if (Math.abs(width) > 8192 || Math.abs(height) > 8192) { - this.restore(); - return 'hotpink'; - } - - var tmpCanvas = new this.ScratchCanvas(width, height); - - // set the new canvas element context as the graphics context - var tmpCtx = tmpCanvas.getContext('2d'); - var savedCtx = ctx; - this.ctx = tmpCtx; - - var paintType = dict.get('PaintType'); - switch (paintType) { - case PAINT_TYPE_COLORED: - tmpCtx.fillStyle = savedCtx.fillStyle; - tmpCtx.strokeStyle = savedCtx.strokeStyle; - break; - case PAINT_TYPE_UNCOLORED: - color = this.makeCssRgb.apply(this, color); - tmpCtx.fillStyle = color; - tmpCtx.strokeStyle = color; - break; - default: - error('Unsupported paint type'); - } - - // 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 = this.applyTransform(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(); - - return this.ctx.createPattern(tmpCanvas, 'repeat'); - }, setStrokeGray: function(gray) { this.setStrokeRGBColor(gray, gray, gray); }, @@ -4377,16 +4299,24 @@ var CanvasGraphics = (function() { this.setFillRGBColor(gray, gray, gray); }, setStrokeRGBColor: function(r, g, b) { - this.ctx.strokeStyle = this.makeCssRgb(r, g, b); + var color = Util.makeCssRgb(r, g, b); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; }, setFillRGBColor: function(r, g, b) { - this.ctx.fillStyle = this.makeCssRgb(r, g, b); + var color = Util.makeCssRgb(r, g, b); + this.ctx.fillStyle = color; + this.current.fillColor = color; }, setStrokeCMYKColor: function(c, m, y, k) { - this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k); + var color = Util.makeCssCmyk(c, m, y, k); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; }, setFillCMYKColor: function(c, m, y, k) { - this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k); + var color = Util.makeCssCmyk(c, m, y, k); + this.ctx.fillStyle = color; + this.current.fillColor = color; }, // Shading @@ -4403,10 +4333,10 @@ var CanvasGraphics = (function() { if (!shading) error('No shading object found'); - var shadingFill = this.getShading(shading); + var shadingFill = Pattern.parseShading(shading, null, xref, res, ctx); this.save(); - ctx.fillStyle = shadingFill; + ctx.fillStyle = shadingFill.getPattern(); var inv = ctx.mozCurrentTransformInverse; if (inv) { @@ -4414,10 +4344,10 @@ var CanvasGraphics = (function() { var width = canvas.width; var height = canvas.height; - var bl = this.applyTransform(0, 0, inv); - var br = this.applyTransform(0, width, inv); - var ul = this.applyTransform(height, 0, inv); - var ur = this.applyTransform(height, width, inv); + 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]); @@ -4437,129 +4367,6 @@ var CanvasGraphics = (function() { this.restore(); }, - getShading: function(shading) { - this.save(); - - shading = this.xref.fetchIfRef(shading); - var dict = IsStream(shading) ? shading.dict : shading; - - var bbox = dict.get('BBox'); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } - - var background = dict.get('Background'); - if (background) - TODO('handle background colors'); - - var cs = dict.get('ColorSpace', 'CS'); - cs = ColorSpace.parse(cs, this.xref, this.res); - - var types = [null, - null, - this.getAxialShading, - this.getRadialShading]; - - var typeNum = dict.get('ShadingType'); - var shadingFn = types[typeNum]; - - this.restore(); - - // Most likely we will not implement other types of shading - // unless the browser supports them - if (!shadingFn) { - warn("Unknown or NYI type of shading '"+ typeNum +"'"); - return 'hotpink'; - } - - 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 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)); - } - - 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)); - } - return gradient; - }, // Images beginInlineImage: function() { @@ -4698,48 +4505,6 @@ var CanvasGraphics = (function() { } 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. @@ -4751,16 +4516,31 @@ var CanvasGraphics = (function() { restoreFillRule: function(rule) { this.ctx.mozFillRule = rule; }, - applyTransform: function(x0, y0, m) { - var xt = x0 * m[0] + y0 * m[2] + m[4]; - var yt = x0 * m[1] + y0 * m[3] + m[5]; - return [xt, yt]; - } }; return constructor; })(); +var Util = (function() { + 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) { + var 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 ColorSpace = (function() { // Constructor should define this.numComps, this.defaultColor, this.name function constructor() { @@ -5112,6 +4892,307 @@ var DeviceCmykCS = (function() { return constructor; })(); +var Pattern = (function() { + // Constructor should define this.getPattern + function constructor() { + error('should not call Pattern constructor'); + }; + + constructor.prototype = { + // Input: current Canvas context + // Output: the appropriate fillStyle or strokeStyle + getPattern: function pattern_getStyle(ctx) { + error('Should not call Pattern.getStyle'); + }, + }; + + constructor.parse = function pattern_parse(args, cs, xref, res, ctx) { + var length = args.length; + + var patternName = args[length - 1]; + if (!IsName(patternName)) + error("Bad args to getPattern"); + + var patternRes = xref.fetchIfRef(res.get("Pattern")); + if (!patternRes) + error("Unable to find pattern resource"); + + var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); + var dict = IsStream(pattern) ? pattern.dict : pattern; + var typeNum = dict.get("PatternType"); + + switch(typeNum) { + case 1: + var base = cs.base; + var color; + if (base) { + var baseComps = base.numComps; + + color = []; + for (var i = 0; i < baseComps; ++i) + color.push(args[i]); + + color = base.getRgb(color); + } + return new TilingPattern(pattern, dict, color, xref, ctx); + case 2: + var shading = xref.fetchIfRef(dict.get('Shading')); + var matrix = dict.get('Matrix'); + return Pattern.parseShading(shading, matrix, xref, res, ctx); + default: + error('Unknown type of pattern'); + } + }; + + constructor.parseShading = function pattern_shading(shading, matrix, + xref, res, ctx) { + + var dict = IsStream(shading) ? shading.dict : shading; + var type = dict.get('ShadingType'); + + switch (type) { + case 2: + case 3: + // both radial and axial shadings are handled by RadialAxial shading + return new RadialAxialShading(dict, matrix, xref, res, ctx); + default: + return new DummyShading(); + } + } + return constructor; +})(); + +var DummyShading = (function() { + function constructor() { + this.type = 'Pattern'; + }; + constructor.prototype = { + getPattern: function dummy_getpattern() { + return 'hotpink'; + } + }; + return constructor; +})(); + +// Radial and axial shading have very similar implementations +// If needed, the implementations can be broken into two classes +var RadialAxialShading = (function() { + function constructor(dict, matrix, xref, res, ctx) { + this.matrix = matrix; + var bbox = dict.get('BBox'); + var background = dict.get('Background'); + this.coordsArr = dict.get('Coords'); + this.shadingType = dict.get('ShadingType'); + this.type = 'Pattern'; + + this.ctx = ctx; + this.curMatrix = ctx.mozCurrentTransform; + + var cs = dict.get('ColorSpace', 'CS'); + cs = ColorSpace.parse(cs, xref, res); + this.cs = cs; + + var t0 = 0.0, t1 = 1.0; + if (dict.has('Domain')) { + var domainArr = dict.get('Domain'); + t0 = domainArr[0], t1 = domainArr[1]; + } + + var extendStart = false, extendEnd = false; + if (dict.has('Extend')) { + var extendArr = dict.get('Extend'); + extendStart = extendArr[0], extendEnd = extendArr[1]; + TODO('Support extend'); + } + + this.extendStart = extendStart; + this.extendEnd = extendEnd; + + var fnObj = dict.get('Function'); + fnObj = xref.fetchIfRef(fnObj); + if (IsArray(fnObj)) + error('No support for array of functions'); + else if (!IsPDFFunction(fnObj)) + error('Invalid function'); + var fn = new PDFFunction(xref, fnObj); + + // 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; + + var colorStops = []; + for (var i = t0; i <= t1; i += step) { + var color = fn.func([i]); + var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color)); + colorStops.push([(i - t0) / diff, rgbColor]); + } + + this.colorStops = colorStops; + }; + + constructor.prototype = { + getPattern: function() { + var coordsArr = this.coordsArr; + var type = this.shadingType; + if (type == 2) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[2], coordsArr[3]]; + } else if (type == 3) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[3], coordsArr[4]]; + var r0 = coordsArr[2], r1 = coordsArr[5] + } else { + error() + } + + var matrix = this.matrix; + if (matrix) { + p0 = Util.applyTransform(p0, matrix); + p1 = Util.applyTransform(p1, matrix); + } + + // if the browser supports getting the tranform matrix, convert + // gradient coordinates from pattern space to current user space + var curMatrix = this.curMatrix; + var ctx = this.ctx; + if (curMatrix) { + var userMatrix = ctx.mozCurrentTransformInverse; + + p0 = Util.applyTransform(p0, curMatrix); + p0 = Util.applyTransform(p0, userMatrix); + + p1 = Util.applyTransform(p1, curMatrix); + p1 = Util.applyTransform(p1, userMatrix); + } + + var colorStops = this.colorStops; + if (type == 2) + var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + else if (type == 3) + var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); + + for (var i = 0, ii = colorStops.length; i < ii; ++i) { + var c = colorStops[i]; + grad.addColorStop(c[0], c[1]); + } + return grad; + } + }; + return constructor; +})(); + +var TilingPattern = (function() { + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + + function constructor(pattern, dict, color, xref, ctx) { + function multiply(m, tm) { + var a = m[0] * tm[0] + m[1] * tm[2]; + var b = m[0] * tm[1] + m[1] * tm[3]; + var c = m[2] * tm[0] + m[3] * tm[2]; + var d = m[2] * tm[1] + m[3] * tm[3]; + var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; + var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; + return [a, b, c, d, e, f]; + }; + + TODO('TilingType'); + + this.matrix = dict.get("Matrix"); + this.curMatrix = ctx.mozCurrentTransform; + this.invMatrix = ctx.mozCurrentTransformInverse; + this.ctx = ctx; + this.type = 'Pattern'; + + var bbox = dict.get('BBox'); + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); + + var topLeft = [x0, y0]; + // we want the canvas to be as large as the step size + var botRight = [x0 + xstep, y0 + ystep] + + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; + + // TODO: hack to avoid OOM, we would idealy compute the tiling + // pattern to be only as large as the acual size in device space + // This could be computed with .mozCurrentTransform, but still + // needs to be implemented + while (Math.abs(width) > 512 || Math.abs(height) > 512) { + width = 512; + height = 512; + } + + var tmpCanvas = new ScratchCanvas(width, height); + + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext('2d'); + var graphics = new CanvasGraphics(tmpCtx); + + var paintType = dict.get('PaintType'); + switch (paintType) { + case PAINT_TYPE_COLORED: + tmpCtx.fillStyle = ctx.fillStyle; + tmpCtx.strokeStyle = ctx.strokeStyle; + break; + case PAINT_TYPE_UNCOLORED: + color = Util.makeCssRgb.apply(this, color); + tmpCtx.fillStyle = color; + tmpCtx.strokeStyle = color; + break; + default: + error('Unsupported paint type'); + } + + var scale = [width / xstep, height / ystep]; + this.scale = scale; + + // transform coordinates to pattern space + var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; + var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; + graphics.transform.apply(graphics, tmpScale); + graphics.transform.apply(graphics, tmpTranslate); + + if (bbox && IsArray(bbox) && 4 == bbox.length) { + graphics.rectangle.apply(graphics, bbox); + graphics.clip(); + graphics.endPath(); + } + + var res = xref.fetchIfRef(dict.get('Resources')); + if (!pattern.code) + pattern.code = graphics.compile(pattern, xref, res, []); + graphics.execute(pattern.code, xref, res); + + this.canvas = tmpCanvas; + }; + + constructor.prototype = { + getPattern: function tiling_getPattern() { + var matrix = this.matrix; + var curMatrix = this.curMatrix; + var ctx = this.ctx; + + if (curMatrix) + ctx.setTransform.apply(ctx, curMatrix); + + if (matrix) + ctx.transform.apply(ctx, matrix); + + var scale = this.scale; + ctx.scale(1 / scale[0], 1 / scale[1]); + + return ctx.createPattern(this.canvas, 'repeat'); + } + }; + return constructor; +})(); + + var PDFImage = (function() { function constructor(xref, res, image, inline) { this.image = image;