diff --git a/src/pattern.js b/src/pattern.js index 2f53c5c80..45c4a3279 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -61,6 +61,12 @@ var Pattern = (function PatternClosure() { var Shadings = {}; +// A small number to offset the first/last color stops so we can insert ones to +// support extend. Number.MIN_VALUE appears to be too small and breaks the +// extend. 1e-7 works in FF but chrome seems to use an even smaller sized number +// internally so we have to go bigger. +Shadings.SMALL_NUMBER = 1e-2; + // Radial and axial shading have very similar implementations // If needed, the implementations can be broken into two classes Shadings.RadialAxial = (function RadialAxialClosure() { @@ -69,7 +75,6 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.coordsArr = dict.get('Coords'); this.shadingType = dict.get('ShadingType'); this.type = 'Pattern'; - this.ctx = ctx; var cs = dict.get('ColorSpace', 'CS'); cs = ColorSpace.parse(cs, xref, res); @@ -87,7 +92,23 @@ Shadings.RadialAxial = (function RadialAxialClosure() { var extendArr = dict.get('Extend'); extendStart = extendArr[0]; extendEnd = extendArr[1]; - TODO('Support extend'); + } + + if (this.shadingType === PatternType.RADIAL && + (!extendStart || !extendEnd)) { + // Radial gradient only currently works if either circle is fully within + // the other circle. + var x1 = this.coordsArr[0]; + var y1 = this.coordsArr[1]; + var r1 = this.coordsArr[2]; + var x2 = this.coordsArr[3]; + var y2 = this.coordsArr[4]; + var r2 = this.coordsArr[5]; + var distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + if (r1 <= r2 + distance && + r2 <= r1 + distance) { + warn('Unsupported radial gradient.'); + } } this.extendStart = extendStart; @@ -103,16 +124,43 @@ Shadings.RadialAxial = (function RadialAxialClosure() { // 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 step = diff / 10; + + var colorStops = this.colorStops = []; + + // Protect against bad domains so we don't end up in an infinte loop below. + if (t0 >= t1 || step <= 0) { + // Acrobat doesn't seem to handle these cases so we'll ignore for + // now. + info('Bad shading domain.'); + return; + } - var colorStops = []; for (var i = t0; i <= t1; i += step) { var rgbColor = cs.getRgb(fn([i])); var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]); colorStops.push([(i - t0) / diff, cssColor]); } + var background = 'transparent'; + if (dict.has('Background')) { + var rgbColor = cs.getRgb(dict.get('Background')); + background = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]); + } + + if (!extendStart) { + // Insert a color stop at the front and offset the first real color stop + // so it doesn't conflict with the one we insert. + colorStops.unshift([0, background]); + colorStops[1][0] += Shadings.SMALL_NUMBER; + } + if (!extendEnd) { + // Same idea as above in extendStart but for the end. + colorStops[colorStops.length - 1][0] -= Shadings.SMALL_NUMBER; + colorStops.push([1, background]); + } + this.colorStops = colorStops; } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 7f2911983..60793fa31 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -34,3 +34,4 @@ !gradientfill.pdf !basicapi.pdf !mixedfonts.pdf +!shading_extend.pdf diff --git a/test/pdfs/shading_extend.pdf b/test/pdfs/shading_extend.pdf new file mode 100644 index 000000000..0c8c151da Binary files /dev/null and b/test/pdfs/shading_extend.pdf differ