From 9fc97d94e84ca7eb67dfab24bd108cd275a00349 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 14 Jun 2011 11:55:27 -0700 Subject: [PATCH 1/4] Implemented type 2 shading for the pdf (aka gradients) --- pdf.js | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 194 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index 418db2462..87335317a 100644 --- a/pdf.js +++ b/pdf.js @@ -1911,14 +1911,19 @@ var CanvasGraphics = (function() { // Shading shadingFill: function(entryRef) { + if (!this.current.bbox) + TODO("bbox"); + var shadingRes = this.res.get("Shading"); if (!shadingRes) return; - shadingRes = this.xref.fetchIfRef(shadingRes); + + var xref = this.xref; + shadingRes = xref.fetchIfRef(shadingRes); var shading = shadingRes.get(entryRef.name); if (!shading) return; - shading = this.xref.fetchIfRef(shading); + shading = xref.fetchIfRef(shading); if (!shading) return; @@ -1931,8 +1936,13 @@ 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 type = shading.get("ShadingType"); switch (type) { case 1: @@ -1952,10 +1962,10 @@ var CanvasGraphics = (function() { this.restore(); }, + fillAxialShading: function(sh) { - var coordsArr = sh.get("Coords"); - var x0 = coordsArr[0], y0 = coordsArr[1], - x1 = coordsArr[2], y1 = coordsArr[3]; + var cds = sh.get("Coords"); + var t0 = 0.0, t1 = 1.0; if (sh.has("Domain")) { @@ -1967,19 +1977,29 @@ var CanvasGraphics = (function() { 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 (!IsFunction(fnObj)) + error("invalid function"); + fn = new Function(this.xref, fnObj); + + var gradient = this.ctx.createLinearGradient(cds[0], cds[1], cds[2], cds[3]); + var step = (t1 - t0) / 10; + + for (var i = t0; i <= t1; i += step) { + var c = fn.func([i]); + var clength = c.length; + for (var j = 0; j < clength; ++j) + c[j] = Math.round(c[j] * 255); + gradient.addColorStop(i, "rgb("+c[0] + "," + c[1] + "," + c[2] + ")"); } - var fn = sh.get("Function"); - fn = this.xref.fetchIfRef(fn); - - var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); - - gradient.addColorStop(0, 'rgb(0,0,255)'); - gradient.addColorStop(1, 'rgb(0,255,0)'); - this.ctx.fillStyle = gradient; - this.ctx.fill(); - this.consumePath(); + + // HACK to draw the gradient onto an infinite rectangle + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); }, // XObjects @@ -2292,3 +2312,162 @@ var ColorSpace = (function() { return constructor; })(); + +var Function = (function() { + function constructor(xref, fn) { + var dict = fn.dict; + if (!dict) + dict = fn; + + var type = dict.get("FunctionType"); + + switch(type) { + case 0: + this.constructSampled(fn, dict); + break; + case 2: + this.constructInterpolated(); + break; + case 3: + this.constructStiched(); + break; + case 4: + this.constructPostScript(); + break; + default: + error("Unknown type of function"); + } + }; + + constructor.prototype = { + constructSampled: function(str, dict) { + var domain = dict.get("Domain"); + var range = dict.get("Range"); + + if (!domain || !range) + error("No domain or range"); + + var inputSize = domain.length / 2; + var outputSize = range.length / 2; + + if (inputSize != 1) + error("No support for multi-variable inputs to functions"); + + var size = dict.get("Size"); + var bps = dict.get("BitsPerSample"); + var order = dict.get("Order"); + if (!order) + order = 1; + if (order !== 1) + error ("No support for cubic spline interpolation"); + + var encode = dict.get("Encode"); + if (!encode) { + encode = []; + for (var i = 0; i < inputSize; ++i) { + encode.push(0); + encode.push(size[i] - 1); + } + } + var decode = dict.get("Decode"); + if (!decode) + decode = range; + + var samples = this.getSampleArray(size, outputSize, bps, str); + + this.func = function(args) { + var clip = function(v, min, max) { + if (v > max) + v = max; + else if (v < min) + v = min + return v; + } + + if (inputSize != args.length) + error("Incorrect number of arguments"); + + for (var i = 0; i < inputSize; i++) { + var i2 = i * 2; + + // clip to the domain + var v = clip(args[i], domain[i2], domain[i2 + 1]); + + // encode + v = encode[i2] + ((v - domain[i2]) * + (encode[i2 + 1] - encode[i2]) / + (domain[i2 + 1] - domain[i2])); + + // clip to the size + args[i] = clip(v, 0, size[i] - 1); + } + + // interpolate to table + TODO("Multi-dimensional interpolation"); + var floor = Math.floor(args[0]); + var ceil = Math.ceil(args[0]); + var scale = args[0] - floor; + + floor *= outputSize; + ceil *= outputSize; + + var output = []; + for (var i = 0; i < outputSize; ++i) { + if (ceil == floor) { + var v = samples[ceil + i]; + } else { + var low = samples[floor + i]; + var high = samples[ceil + i]; + var v = low * scale + high * (1 - scale); + } + + var i2 = i * 2; + // decode + v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) / + ((1 << bps) - 1)); + + // clip to the domain + output.push(clip(v, range[i2], range[i2 + 1])); + } + + return output; + } + }, + getSampleArray: function(size, outputSize, bps, str) { + var length = 1; + for (var i = 0; i < size.length; i++) + length *= size[i]; + length *= outputSize; + + var array = []; + var codeSize = 0; + var codeBuf = 0; + + var strBytes = str.getBytes((length * bps + 7) / 8); + var strIdx = 0; + for (var i = 0; i < length; i++) { + var b; + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps + array.push(codeBuf >> codeSize); + codeBuf &= (1 << codeSize) - 1; + } + return array; + }, + constructInterpolated: function() { + error("unhandled type of function"); + }, + constructStiched: function() { + error("unhandled type of function"); + }, + constructPostScript: function() { + error("unhandled type of function"); + } + }; + + return constructor; +})(); From aa6d9f75a38996651f499362f67ce07110da80b5 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 14 Jun 2011 12:19:35 -0700 Subject: [PATCH 2/4] cleaned up code --- pdf.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pdf.js b/pdf.js index 87335317a..1910a216a 100644 --- a/pdf.js +++ b/pdf.js @@ -1911,9 +1911,6 @@ var CanvasGraphics = (function() { // Shading shadingFill: function(entryRef) { - if (!this.current.bbox) - TODO("bbox"); - var shadingRes = this.res.get("Shading"); if (!shadingRes) return; @@ -1965,8 +1962,7 @@ var CanvasGraphics = (function() { fillAxialShading: function(sh) { var cds = sh.get("Coords"); - - + var t0 = 0.0, t1 = 1.0; if (sh.has("Domain")) { var domainArr = sh.get("Domain"); @@ -1981,8 +1977,10 @@ var CanvasGraphics = (function() { } var fnObj = sh.get("Function"); fnObj = this.xref.fetchIfRef(fnObj); - if (!IsFunction(fnObj)) - error("invalid function"); + if (IsArray(fnObj)) + error("No support for array of functions"); + else if (!IsFunction(fnObj)) + error("Invalid function"); fn = new Function(this.xref, fnObj); var gradient = this.ctx.createLinearGradient(cds[0], cds[1], cds[2], cds[3]); From b7360823a2bc4df527f2ac84a852431909b15566 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 14 Jun 2011 13:49:50 -0700 Subject: [PATCH 3/4] Cleaned up code, renamed PDFFunction class --- pdf.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pdf.js b/pdf.js index 1910a216a..0beb5e22e 100644 --- a/pdf.js +++ b/pdf.js @@ -1961,7 +1961,9 @@ var CanvasGraphics = (function() { }, fillAxialShading: function(sh) { - var cds = sh.get("Coords"); + 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")) { @@ -1981,17 +1983,14 @@ var CanvasGraphics = (function() { error("No support for array of functions"); else if (!IsFunction(fnObj)) error("Invalid function"); - fn = new Function(this.xref, fnObj); + fn = new PDFFunction(this.xref, fnObj); - var gradient = this.ctx.createLinearGradient(cds[0], cds[1], cds[2], cds[3]); + var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); var step = (t1 - t0) / 10; for (var i = t0; i <= t1; i += step) { var c = fn.func([i]); - var clength = c.length; - for (var j = 0; j < clength; ++j) - c[j] = Math.round(c[j] * 255); - gradient.addColorStop(i, "rgb("+c[0] + "," + c[1] + "," + c[2] + ")"); + gradient.addColorStop(i, this.makeCssRgb.apply(this,c)); } this.ctx.fillStyle = gradient; @@ -2311,7 +2310,7 @@ var ColorSpace = (function() { return constructor; })(); -var Function = (function() { +var PDFFunction = (function() { function constructor(xref, fn) { var dict = fn.dict; if (!dict) From 0f2d4c7011d605620bbcea543ec6f66004ff89c0 Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 15 Jun 2011 11:34:47 -0700 Subject: [PATCH 4/4] cleaned up fetchIfRef, changed switch-case to array lookup, changed IsFunction to IsPDFFunction --- pdf.js | 78 +++++++++++++++++++++++----------------------------------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/pdf.js b/pdf.js index 0beb5e22e..d4df52452 100644 --- a/pdf.js +++ b/pdf.js @@ -617,7 +617,7 @@ function IsRef(v) { return v instanceof Ref; } -function IsFunction(v) { +function IsPDFFunction(v) { var fnDict; if (typeof v != "object") return false; @@ -1911,18 +1911,16 @@ var CanvasGraphics = (function() { // Shading shadingFill: function(entryRef) { - var shadingRes = this.res.get("Shading"); - if (!shadingRes) - return; - var xref = this.xref; - shadingRes = xref.fetchIfRef(shadingRes); - var shading = shadingRes.get(entryRef.name); + var res = this.res; + + var shadingRes = xref.fetchIfRef(res.get("Shading")); + if (!shadingRes) + error("No shading resource found"); + + var shading = xref.fetchIfRef(shadingRes.get(entryRef.name)); if (!shading) - return; - shading = xref.fetchIfRef(shading); - if (!shading) - return; + error("No shading object found"); this.save(); @@ -1940,22 +1938,14 @@ var CanvasGraphics = (function() { if (background) TODO("handle background colors"); - var type = shading.get("ShadingType"); - switch (type) { - case 1: - this.fillFunctionShading(shading); - break; - case 2: - this.fillAxialShading(shading); - break; - case 3: - this.fillRadialShading(shading); - break; - case 4: case 5: case 6: case 7: - TODO("shading fill type "+ type); - default: - malformed("Unknown shading type "+ type); - } + const types = [null, this.fillFunctionShading, + this.fillAxialShading, this.fillRadialShading]; + + var typeNum = shading.get("ShadingType"); + var fillFn = types[typeNum]; + if (!fillFn) + error("Unknown type of shading"); + fillFn.apply(this, [shading]); this.restore(); }, @@ -1981,7 +1971,7 @@ var CanvasGraphics = (function() { fnObj = this.xref.fetchIfRef(fnObj); if (IsArray(fnObj)) error("No support for array of functions"); - else if (!IsFunction(fnObj)) + else if (!IsPDFFunction(fnObj)) error("Invalid function"); fn = new PDFFunction(this.xref, fnObj); @@ -1990,12 +1980,14 @@ var CanvasGraphics = (function() { for (var i = t0; i <= t1; i += step) { var c = fn.func([i]); - gradient.addColorStop(i, this.makeCssRgb.apply(this,c)); + gradient.addColorStop(i, this.makeCssRgb.apply(this, c)); } this.ctx.fillStyle = gradient; - // HACK to draw the gradient onto an infinite rectangle + // 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 this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); }, @@ -2316,24 +2308,16 @@ var PDFFunction = (function() { if (!dict) dict = fn; - var type = dict.get("FunctionType"); - - switch(type) { - case 0: - this.constructSampled(fn, dict); - break; - case 2: - this.constructInterpolated(); - break; - case 3: - this.constructStiched(); - break; - case 4: - this.constructPostScript(); - break; - default: + const types = [this.constructSampled, null, + this.constructInterpolated, this.constructStiched, + this.constructPostScript]; + + var typeNum = dict.get("FunctionType"); + var typeFn = types[typeNum]; + if (!typeFn) error("Unknown type of function"); - } + + typeFn.apply(this, [fn, dict]); }; constructor.prototype = {