diff --git a/src/pattern.js b/src/pattern.js index 2a2ca1d88..b0abf0ded 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -268,11 +268,11 @@ var TilingPattern = (function TilingPatternClosure() { COLORED: 1, UNCOLORED: 2 }; - var MAX_PATTERN_SIZE = 512; + var MAX_PATTERN_SIZE = 4096; function TilingPattern(IR, color, ctx, objs) { var operatorList = IR[2]; - this.matrix = IR[3]; + this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; var bbox = IR[4]; var xstep = IR[5]; var ystep = IR[6]; @@ -294,14 +294,21 @@ var TilingPattern = (function TilingPatternClosure() { var width = botRight[0] - topLeft[0]; var height = botRight[1] - topLeft[1]; - // TODO: hack to avoid OOM, we would ideally 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) > MAX_PATTERN_SIZE || - Math.abs(height) > MAX_PATTERN_SIZE) { - width = height = MAX_PATTERN_SIZE; - } + // Obtain scale from matrix and current transformation matrix. + var matrixScale = Util.singularValueDecompose2dScale(this.matrix); + var curMatrixScale = Util.singularValueDecompose2dScale(this.curMatrix); + var combinedScale = [matrixScale[0] * curMatrixScale[0], + matrixScale[1] * curMatrixScale[1]]; + + // MAX_PATTERN_SIZE is used to avoid OOM situation. + // Use width and height values that are as close as possible to the end + // result when the pattern is used. Too low value makes the pattern look + // blurry. Too large value makes it look too crispy. + width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])), + MAX_PATTERN_SIZE); + + height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), + MAX_PATTERN_SIZE); var tmpCanvas = createScratchCanvas(width, height); @@ -309,37 +316,16 @@ var TilingPattern = (function TilingPatternClosure() { var tmpCtx = tmpCanvas.getContext('2d'); var graphics = new CanvasGraphics(tmpCtx, null, objs); - switch (paintType) { - case PaintType.COLORED: - tmpCtx.fillStyle = ctx.fillStyle; - tmpCtx.strokeStyle = ctx.strokeStyle; - break; - case PaintType.UNCOLORED: - var rgbColor = new DeviceRgbCS().getRgb(color, 0); - var cssColor = Util.makeCssRgb(rgbColor); - tmpCtx.fillStyle = cssColor; - tmpCtx.strokeStyle = cssColor; - break; - default: - error('Unsupported paint type: ' + paintType); - } + this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color); - var scale = [width / xstep, height / ystep]; - this.scale = scale; + this.setScale(width, height, xstep, ystep); + this.transformToScale(graphics); // 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) { - var bboxWidth = x1 - x0; - var bboxHeight = y1 - y0; - graphics.rectangle(x0, y0, bboxWidth, bboxHeight); - graphics.clip(); - graphics.endPath(); - } + this.clipBbox(graphics, bbox, x0, y0, x1, y1); graphics.executeOperatorList(operatorList); @@ -361,19 +347,58 @@ var TilingPattern = (function TilingPatternClosure() { }; TilingPattern.prototype = { + setScale: function TilingPattern_setScale(width, height, xstep, ystep) { + this.scale = [width / xstep, height / ystep]; + }, + + transformToScale: function TilingPattern_transformToScale(graphics) { + var scale = this.scale; + var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; + graphics.transform.apply(graphics, tmpScale); + }, + + scaleToContext: function TilingPattern_scaleToContext() { + var scale = this.scale; + this.ctx.scale(1 / scale[0], 1 / scale[1]); + }, + + clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) { + if (bbox && isArray(bbox) && 4 == bbox.length) { + var bboxWidth = x1 - x0; + var bboxHeight = y1 - y0; + graphics.rectangle(x0, y0, bboxWidth, bboxHeight); + graphics.clip(); + graphics.endPath(); + } + }, + + setFillAndStrokeStyleToContext: + function setFillAndStrokeStyleToContext(context, paintType, color) { + switch (paintType) { + case PaintType.COLORED: + var ctx = this.ctx; + context.fillStyle = ctx.fillStyle; + context.strokeStyle = ctx.strokeStyle; + break; + case PaintType.UNCOLORED: + var rgbColor = new DeviceRgbCS().getRgb(color, 0); + var cssColor = Util.makeCssRgb(rgbColor); + context.fillStyle = cssColor; + context.strokeStyle = cssColor; + break; + default: + error('Unsupported paint type: ' + paintType); + } + }, + getPattern: function TilingPattern_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]); + ctx.setTransform.apply(ctx, curMatrix); + ctx.transform.apply(ctx, matrix); + this.scaleToContext(); return ctx.createPattern(this.canvas, 'repeat'); } diff --git a/src/util.js b/src/util.js index b6bfcbdde..15a4c14ce 100644 --- a/src/util.js +++ b/src/util.js @@ -257,6 +257,30 @@ var Util = PDFJS.Util = (function UtilClosure() { ]; }; + // This calculation uses Singular Value Decomposition. + // The SVD can be represented with formula A = USV. We are interested in the + // matrix S here because it represents the scale values. + Util.singularValueDecompose2dScale = + function Util_singularValueDecompose2dScale(m) { + + var transpose = [m[0], m[2], m[1], m[3]]; + + // Multiply matrix m with its transpose. + var a = m[0] * transpose[0] + m[1] * transpose[2]; + var b = m[0] * transpose[1] + m[1] * transpose[3]; + var c = m[2] * transpose[0] + m[3] * transpose[2]; + var d = m[2] * transpose[1] + m[3] * transpose[3]; + + // Solve the second degree polynomial to get roots. + var first = (a + d) / 2; + var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2; + var sx = first + second || 1; + var sy = first - second || 1; + + // Scale values are the square roots of the eigenvalues. + return [Math.sqrt(sx), Math.sqrt(sy)]; + }; + // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) // For coordinate systems whose origin lies in the bottom-left, this // means normalization to (BL,TR) ordering. For systems with origin in the diff --git a/test/pdfs/issue2177.pdf b/test/pdfs/issue2177.pdf new file mode 100644 index 000000000..666498c27 --- /dev/null +++ b/test/pdfs/issue2177.pdf @@ -0,0 +1,132 @@ +%PDF-1.7 + +1 0 obj % entry point +<< + /Type /Catalog + /Pages 2 0 R +>> +endobj + +2 0 obj +<< /Type /Pages + /MediaBox [ 0 0 900 900 ] + /Count 1 + /Kids [ 3 0 R ] +>> +endobj + +3 0 obj % Page object +<< /Type /Page + /Parent 2 0 R + /Resources 4 0 R + /Contents 7 0 R + /CropBox [ 0 0 225 225 ] +>> +endobj + +4 0 obj % Resource dictionary for page +<< /Pattern << /P1 5 0 R >> +>> +endobj + +5 0 obj % Pattern definition +<< /Type /Pattern + /PatternType 1 % Tiling pattern + /PaintType 1 % Colored + /TilingType 2 + /BBox [ 0 0 100 100 ] + /XStep 20 + /YStep 20 + /Resources 6 0 R + /Matrix [ 1.4 1.0 -0.5 0.7 0.0 0.0 ] + /Length 931 +>> +stream +1.0 0.0 0.0 RG % Set stroking color to red +1 4.75 m % Construct lower-left circle +1 6.81919 2.68081 8.5 4.75 8.5 c +6.81919 8.5 8.5 6.81919 8.5 4.75 c +8.5 2.68081 6.81919 1 4.75 1 c +2.68081 1 1 2.68081 1 4.75 c +S +0.0 1.0 0.0 RG % Set stroking color to green +12.25 4.75 m % Construct lower-right circle +12.25 6.81919 13.93081 8.5 16 8.5 c +18.06919 8.5 19.75 6.81919 19.75 4.75 c +19.75 2.68081 18.06919 1 16 1 c +13.93081 1 12.25 2.68081 12.25 4.75 c +S +0.0 0.0 1.0 RG % Set stroking color to blue +1 16 m % Construct upper-left circle +1 18.06919 2.68081 19.75 4.75 19.75 c +6.81919 19.75 8.5 18.06919 8.5 16 c +8.5 13.93081 6.81919 12.25 4.75 12.25 c +2.68081 12.25 1 13.93081 1 16 c +S +0.0 0.0 0.0 RG % Set stroking color to black +12.25 16 m % Construct upper-right circle +12.25 18.06919 13.93081 19.75 16 19.75 c +18.06919 19.75 19.75 18.06919 19.75 16 c +19.75 13.93081 18.06919 12.25 16 12.25 c +13.93081 12.25 12.25 13.93081 12.25 16 c +s +endstream +endobj + +6 0 obj % Resource dictionary for pattern +<< +>> +endobj + +7 0 obj % Contents of page +<< /Length 1082 >> +stream +0.0 G % Set stroking color to black +1.0 1.0 0.0 rg % Set nonstroking color to yellow +25 175 175 -150 re % Construct rectangular path +f % Fill path +/Pattern cs % Set pattern color space +/P1 scn % Set pattern as nonstroking color +99.92 49.92 m % Start new path +99.92 77.52 77.52 99.92 49.92 99.92 c % Construct lower-left circle +22.32 99.92 -0.08 77.52 -0.08 49.92 c +-0.08 22.32 22.32 -0.08 49.92 -0.08 c +77.52 -0.08 99.92 22.32 99.92 49.92 c +B % Fill and stroke path +224.96 49.92 m % Start new path +224.96 77.52 202.56 99.92 174.96 99.92 c % Construct lower-right circle +147.36 99.92 124.96 77.52 124.96 49.92 c +124.96 22.32 147.36 -0.08 174.96 -0.08 c +202.56 -0.08 224.96 22.32 224.96 49.92 c +B % Fill and stroke path +87.56 201.70 m % Start new path +63.66 187.90 55.46 157.32 69.26 133.40 c % Construct upper circle +83.06 109.50 113.66 101.30 137.56 115.10 c +161.46 128.90 169.66 159.50 155.86 183.40 c +142.06 207.30 111.46 215.50 87.56 201.70 c +B % Fill and stroke path +50 50 m % Start new path +175 50 l % Construct triangular path +112.5 158.253 l +b % Close, fill, and stroke path +endstream +endobj + +xref +0 8 +0000000000 65535 f +0000000010 00000 n +0000000078 00000 n +0000000173 00000 n +0000000305 00000 n +0000000383 00000 n +0000001594 00000 n +0000001650 00000 n +trailer +<< + /Size 8 + /Root 1 0 R +>> +startxref +2803 +%%EOF diff --git a/test/test_manifest.json b/test/test_manifest.json index 2d71a2158..4cce61f7f 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -957,5 +957,11 @@ "link": true, "rounds": 1, "type": "eq" + }, + { "id": "issue2177-eq", + "file": "pdfs/issue2177.pdf", + "md5": "48a808278bf31de8414c4e03ecd0900a", + "rounds": 1, + "type": "eq" } ]