From 8fce6938c4c280fc77ac954c4268b5e985484eba Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 5 Jul 2011 15:09:45 -0500 Subject: [PATCH 1/7] Initial implementation of shading patterns. Radial shading is somewhat broken. --- pdf.js | 224 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 165 insertions(+), 59 deletions(-) diff --git a/pdf.js b/pdf.js index f7a3359de..733097c21 100644 --- a/pdf.js +++ b/pdf.js @@ -3940,10 +3940,6 @@ var CanvasGraphics = (function() { }, 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); }, @@ -3952,28 +3948,43 @@ var CanvasGraphics = (function() { 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.ctx.fillStyle = this.getPattern(patternName); } else { // TODO real impl this.setFillColor.apply(this, arguments); } }, - tilingFill: function(pattern) { + getPattern: 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 patternDict = IsStream(pattern) ? pattern.dict : pattern; + + var types = [null, this.getTilingPattern, + this.getShadingPattern]; + var typeNum = patternDict.get("PatternType"); + var patternFn = types[typeNum]; + if (!patternFn) + error("Unhandled pattern type"); + return patternFn.call(this, pattern, patternDict); + }, + getShadingPattern: function(pattern) { + var matrix = pattern.get("Matrix"); + + this.save(); + this.transform.apply(this, matrix); + var shading = this.getShading(pattern.get("Shading")); + this.restore(); + + return shading; + }, + getTilingPattern: function(pattern) { function applyMatrix(point, m) { var x = point[0] * m[0] + point[1] * m[2] + m[4]; var y = point[0] * m[1] + point[1] * m[3] + m[5]; @@ -4065,7 +4076,7 @@ var CanvasGraphics = (function() { TODO("Inverse pattern is painted"); pattern = this.ctx.createPattern(tmpCanvas, "repeat"); - this.ctx.fillStyle = pattern; + return pattern; }, setStrokeGray: function(gray) { this.setStrokeRGBColor(gray, gray, gray); @@ -4085,9 +4096,8 @@ var CanvasGraphics = (function() { 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; @@ -4095,11 +4105,26 @@ var CanvasGraphics = (function() { if (!shadingRes) 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"); + + 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) { @@ -4108,28 +4133,25 @@ var CanvasGraphics = (function() { this.endPath(); } - var cs = shading.get2("ColorSpace", "CS"); - TODO("shading-fill color space"); - 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, - this.fillFunctionShading, - this.fillAxialShading, - this.fillRadialShading]; + null, + this.getAxialShading, + this.getRadialShading]; var typeNum = shading.get("ShadingType"); - var fillFn = types[typeNum]; - if (!fillFn) + var shadingFn = types[typeNum]; + if (!shadingFn) error("Unknown or NYI type of shading '"+ typeNum +"'"); - fillFn.apply(this, [shading]); - - this.restore(); + return shadingFn.call(this, shading, cs); }, - - fillAxialShading: function(sh) { + getAxialShading: function(sh, cs) { var coordsArr = sh.get("Coords"); var x0 = coordsArr[0], y0 = coordsArr[1], x1 = coordsArr[2], y1 = coordsArr[3]; @@ -4160,24 +4182,61 @@ var CanvasGraphics = (function() { // 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)); } - this.ctx.fillStyle = gradient; - - // HACK to draw the gradient onto an infinite rectangle. - // PDF gradients are drawn across the entire image while - // Canvas only allows gradients to be drawn in a rectangle - // The following bug should allow us to remove this. - // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 - this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + 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]; - fillRadialShading: function(sh) { - TODO("radial shading"); + 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 @@ -4569,10 +4628,10 @@ var DeviceRgbCS = (function() { 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; } }; @@ -4586,14 +4645,61 @@ var DeviceCmykCS = (function() { 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]; + 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 graycs_getRgbBuffer(colorBuf) { + getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) { error("conversion from rgb to cmyk not implemented for images"); return colorBuf; } From 493fb45efd09bf06fd73f87a612e9fa8287a515e Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 5 Jul 2011 22:51:58 -0700 Subject: [PATCH 2/7] Added hack to display gradients --- pdf.js | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index 733097c21..4033330bd 100644 --- a/pdf.js +++ b/pdf.js @@ -3948,13 +3948,13 @@ var CanvasGraphics = (function() { if (cs.name == "Pattern") { var patternName = arguments[0]; - this.ctx.fillStyle = this.getPattern(patternName); + this.setFillPattern(patternName); } else { // TODO real impl this.setFillColor.apply(this, arguments); } }, - getPattern: function(patternName) { + setFillPattern: function(patternName) { if (!IsName(patternName)) error("Bad args to getPattern"); @@ -3964,27 +3964,40 @@ var CanvasGraphics = (function() { error("Unable to find pattern resource"); var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); - var patternDict = IsStream(pattern) ? pattern.dict : pattern; + var dict = IsStream(pattern) ? pattern.dict : pattern; - var types = [null, this.getTilingPattern, - this.getShadingPattern]; - var typeNum = patternDict.get("PatternType"); + var types = [null, this.setTilingPattern, + this.setShadingPattern]; + + var typeNum = dict.get("PatternType"); var patternFn = types[typeNum]; if (!patternFn) error("Unhandled pattern type"); - return patternFn.call(this, pattern, patternDict); + patternFn.call(this, pattern, dict); }, - getShadingPattern: function(pattern) { - var matrix = pattern.get("Matrix"); + 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.save(); this.transform.apply(this, matrix); var shading = this.getShading(pattern.get("Shading")); this.restore(); + this.ctx.fillStyle = shading; - return 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); }, - getTilingPattern: function(pattern) { + 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]; @@ -4002,7 +4015,6 @@ var CanvasGraphics = (function() { }; this.save(); - var dict = pattern.dict; var ctx = this.ctx; var paintType = dict.get("PaintType"); @@ -4075,8 +4087,7 @@ var CanvasGraphics = (function() { this.restore(); TODO("Inverse pattern is painted"); - pattern = this.ctx.createPattern(tmpCanvas, "repeat"); - return pattern; + this.ctx.fillStyle = this.ctx.createPattern(tmpCanvas, "repeat"); }, setStrokeGray: function(gray) { this.setStrokeRGBColor(gray, gray, gray); From b83d2cdffceb6de1c5122a5b9273c894588a9e37 Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 6 Jul 2011 10:41:44 -0700 Subject: [PATCH 3/7] cleanup --- pdf.js | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/pdf.js b/pdf.js index 14f801747..b07ef74e7 100644 --- a/pdf.js +++ b/pdf.js @@ -4002,7 +4002,7 @@ var CanvasGraphics = (function() { setFillColorN: function(/*...*/) { var cs = this.getFillColorSpace(); - if (cs.name == "Pattern") { + if (cs.name == 'Pattern') { var patternName = arguments[0]; this.setFillPattern(patternName); } else { @@ -4121,8 +4121,10 @@ var CanvasGraphics = (function() { 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) { @@ -4168,13 +4170,13 @@ var CanvasGraphics = (function() { 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(shadingName.name)); if (!shading) - error("No shading object found"); + error('No shading object found'); var shadingFill = this.getShading(shading); @@ -4193,18 +4195,18 @@ var CanvasGraphics = (function() { getShading: function(shading) { shading = this.xref.fetchIfRef(shading); - var bbox = shading.get("BBox"); + 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"); + var background = shading.get('Background'); if (background) - TODO("handle background colors"); + TODO('handle background colors'); - var cs = shading.get2("ColorSpace", "CS"); + var cs = shading.get2('ColorSpace', 'CS'); cs = ColorSpace.parse(cs, this.xref, this.res); var types = [null, @@ -4212,14 +4214,14 @@ var CanvasGraphics = (function() { this.getAxialShading, this.getRadialShading]; - var typeNum = shading.get("ShadingType"); + 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 coordsArr = sh.get('Coords'); var x0 = coordsArr[0], y0 = coordsArr[1], x1 = coordsArr[2], y1 = coordsArr[3]; @@ -4230,17 +4232,17 @@ var CanvasGraphics = (function() { } 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); @@ -4261,28 +4263,28 @@ var CanvasGraphics = (function() { return gradient; }, getRadialShading: function(sh, cs) { - var coordsArr = sh.get("Coords"); + 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"); + 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"); + 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 = From d749b7ccbdd301098fee7849ef5501d98e1e196b Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 6 Jul 2011 10:44:01 -0700 Subject: [PATCH 4/7] cleanup --- pdf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index b07ef74e7..d183072c1 100644 --- a/pdf.js +++ b/pdf.js @@ -4792,9 +4792,9 @@ var PDFImage = (function() { 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; From 55bb98317dee19b0088a3d566fc9137ec48810fa Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 6 Jul 2011 10:44:56 -0700 Subject: [PATCH 5/7] cleanup --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index d183072c1..7d27b65c0 100644 --- a/pdf.js +++ b/pdf.js @@ -4768,7 +4768,7 @@ var DeviceCmykCS = (function() { return [r, g, b]; }, getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) { - error("conversion from rgb to cmyk not implemented for images"); + error('conversion from rgb to cmyk not implemented for images'); return colorBuf; } }; From 5022e2411cff0025870fcb55d69db02aeafa9e0e Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 6 Jul 2011 12:53:18 -0700 Subject: [PATCH 6/7] removed extra restore --- pdf.js | 1 - 1 file changed, 1 deletion(-) diff --git a/pdf.js b/pdf.js index 7d27b65c0..2de561bf7 100644 --- a/pdf.js +++ b/pdf.js @@ -4045,7 +4045,6 @@ var CanvasGraphics = (function() { 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 From 0c2b6aea5f36b425135ea3650810591fa527d65a Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 6 Jul 2011 14:28:52 -0700 Subject: [PATCH 7/7] cleanup --- pdf.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index 2de561bf7..2151c0497 100644 --- a/pdf.js +++ b/pdf.js @@ -4022,8 +4022,7 @@ var CanvasGraphics = (function() { var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); var dict = IsStream(pattern) ? pattern.dict : pattern; - var types = [null, this.setTilingPattern, - this.setShadingPattern]; + var types = [null, this.setTilingPattern, this.setShadingPattern]; var typeNum = dict.get("PatternType"); var patternFn = types[typeNum];