// 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;
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:
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")) {
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
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;
+})();