Skip to content

Commit ad97086

Browse files
committed
Major fixes to html2canvas
1 parent 9f64bd6 commit ad97086

1 file changed

Lines changed: 135 additions & 9 deletions

File tree

lib/html2canvas.ek.js

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,8 @@ function html2canvas(nodeList, options) {
940940

941941
var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
942942
node.setAttribute(html2canvasNodeAttribute + index, index);
943-
return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
943+
// EK 2017-03-24: Edited from `node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight,`.
944+
return renderDocument(node.ownerDocument, options, node.clientWidth, node.clientHeight, index).then(function(canvas) {
944945
if (typeof(options.onrendered) === "function") {
945946
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
946947
options.onrendered(canvas);
@@ -998,6 +999,16 @@ function renderWindow(node, container, options, windowWidth, windowHeight) {
998999
canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
9991000
} else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
10001001
canvas = renderer.canvas;
1002+
} else if (options.scale) {
1003+
// EK 2017-03-25: Add dpi/scale.
1004+
var origBounds = {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0};
1005+
var cropBounds = {};
1006+
for (var key in origBounds) {
1007+
if (origBounds.hasOwnProperty(key)) { cropBounds[key] = origBounds[key] * options.scale; }
1008+
}
1009+
canvas = crop(renderer.canvas, cropBounds);
1010+
canvas.style.width = origBounds.width + 'px';
1011+
canvas.style.height = origBounds.height + 'px';
10011012
} else {
10021013
canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});
10031014
}
@@ -1740,6 +1751,11 @@ NodeContainer.prototype.parseTransformMatrix = function() {
17401751
return this.transformMatrix;
17411752
};
17421753

1754+
NodeContainer.prototype.inverseTransform = function() {
1755+
var transformData = this.parseTransform();
1756+
return { origin: transformData.origin, matrix: matrixInverse(transformData.matrix) };
1757+
}
1758+
17431759
NodeContainer.prototype.parseBounds = function() {
17441760
return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
17451761
};
@@ -1781,6 +1797,14 @@ function parseMatrix(match) {
17811797
}
17821798
}
17831799

1800+
function matrixInverse(m) {
1801+
// EK 2017-03-25: This is specifically programmed to work for transform matrices.
1802+
var a = m[0], b = m[2], c = m[4], d = m[1], e = m[3], f = m[5];
1803+
var det = a*e - b*d;
1804+
var M = [e, -d, -b, a, b*f-c*e, c*d-a*f].map(function(val) {return val/det});
1805+
return M;
1806+
}
1807+
17841808
function isPercentage(value) {
17851809
return value.toString().indexOf("%") !== -1;
17861810
}
@@ -2123,14 +2147,20 @@ NodeParser.prototype.paintNode = function(container) {
21232147
};
21242148

21252149
NodeParser.prototype.paintElement = function(container) {
2150+
// EK 2017-03-25: Added container to all .clip calls for transform (also in paintCheckbox/paintRadio, basically paintNode descendents).
21262151
var bounds = container.parseBounds();
21272152
this.renderer.clip(container.backgroundClip, function() {
21282153
this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
2154+
}, this, container);
2155+
2156+
// EK 2017-03-24: Add mask (opposite of clip) for rendering box-shadows.
2157+
this.renderer.mask(bounds, function() {
2158+
this.renderer.renderShadows(container, bounds);
21292159
}, this);
21302160

21312161
this.renderer.clip(container.clip, function() {
21322162
this.renderer.renderBorders(container.borders.borders);
2133-
}, this);
2163+
}, this, container);
21342164

21352165
this.renderer.clip(container.backgroundClip, function() {
21362166
switch (container.node.nodeName) {
@@ -2160,7 +2190,7 @@ NodeParser.prototype.paintElement = function(container) {
21602190
this.paintFormValue(container);
21612191
break;
21622192
}
2163-
}, this);
2193+
}, this, container);
21642194
};
21652195

21662196
NodeParser.prototype.paintCheckbox = function(container) {
@@ -2183,7 +2213,7 @@ NodeParser.prototype.paintCheckbox = function(container) {
21832213
this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + "px", 'arial');
21842214
this.renderer.text("\u2714", bounds.left + size / 6, bounds.top + size - 1);
21852215
}
2186-
}, this);
2216+
}, this, container);
21872217
};
21882218

21892219
NodeParser.prototype.paintRadio = function(container) {
@@ -2255,7 +2285,7 @@ NodeParser.prototype.paintText = function(container) {
22552285
this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
22562286
}
22572287
}, this);
2258-
}, this);
2288+
}, this, container.parent);
22592289
};
22602290

22612291
NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
@@ -2873,6 +2903,15 @@ Renderer.prototype.renderBackgroundColor = function(container, bounds) {
28732903
}
28742904
};
28752905

2906+
// EK 2017-03-24: Add box-shadow rendering.
2907+
Renderer.prototype.renderShadows = function(container, bounds) {
2908+
var boxShadow = container.css('boxShadow');
2909+
if (boxShadow !== 'none') {
2910+
var shadows = boxShadow.split(/,(?![^(]*\))/);
2911+
this.shadow(bounds.left, bounds.top, bounds.width, bounds.height, shadows);
2912+
}
2913+
};
2914+
28762915
Renderer.prototype.renderBorders = function(borders) {
28772916
borders.forEach(this.renderBorder, this);
28782917
};
@@ -2944,11 +2983,24 @@ var log = _dereq_('../log');
29442983
function CanvasRenderer(width, height) {
29452984
Renderer.apply(this, arguments);
29462985
this.canvas = this.options.canvas || this.document.createElement("canvas");
2986+
this.ctx = this.canvas.getContext("2d");
29472987
if (!this.options.canvas) {
2948-
this.canvas.width = width;
2949-
this.canvas.height = height;
2988+
// EK 2017-03-25: Add scale/dpi.
2989+
if (this.options.dpi) {
2990+
// Default canvas is 96dpi (1 CSS inch = 96px).
2991+
this.options.scale = this.options.dpi / 96;
2992+
}
2993+
if (this.options.scale) {
2994+
this.canvas.style.width = width + 'px';
2995+
this.canvas.style.height = height + 'px';
2996+
this.canvas.width = Math.floor(width * this.options.scale);
2997+
this.canvas.height = Math.floor(height * this.options.scale);
2998+
this.ctx.scale(this.options.scale, this.options.scale);
2999+
} else {
3000+
this.canvas.width = width;
3001+
this.canvas.height = height;
3002+
}
29503003
}
2951-
this.ctx = this.canvas.getContext("2d");
29523004
this.taintCtx = this.document.createElement("canvas").getContext("2d");
29533005
this.ctx.textBaseline = "bottom";
29543006
this.variables = {};
@@ -2966,6 +3018,49 @@ CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {
29663018
this.setFillStyle(color).fillRect(left, top, width, height);
29673019
};
29683020

3021+
// EK 2017-03-24: Add boxShadow handling.
3022+
CanvasRenderer.prototype.shadow = function(left, top, width, height, shadows) {
3023+
var parseShadow = function(str) {
3024+
// Setup matching info.
3025+
var propertyFilters = { color: /^(#|rgb|hsl|(?!(inset|initial|inherit))\D+)/i, inset: /^inset/i, px: /px$/i };
3026+
var pxPropertyNames = [ 'x', 'y', 'blur', 'spread' ];
3027+
3028+
// Extract info from the string.
3029+
var properties = str.split(/ (?![^(]*\))/);
3030+
var info = {};
3031+
for (var key in propertyFilters) {
3032+
info[key] = properties.filter(propertyFilters[key].test.bind(propertyFilters[key]));
3033+
info[key] = info[key].length === 0 ? null : info[key].length === 1 ? info[key][0] : info[key];
3034+
}
3035+
for (var i=0; i<info.px.length; i++) {
3036+
info[pxPropertyNames[i]] = parseInt(info.px[i]);
3037+
}
3038+
3039+
// Return the info object.
3040+
return info;
3041+
}
3042+
var drawShadow = function(shadow) {
3043+
var info = parseShadow(shadow);
3044+
if (!info.inset) {
3045+
context.shadowOffsetX = info.x;
3046+
context.shadowOffsetY = info.y;
3047+
context.shadowColor = info.color;
3048+
context.shadowBlur = info.blur;
3049+
context.fill();
3050+
}
3051+
}
3052+
3053+
// Create a bounding rectangle with arbitrary opaque color.
3054+
var context = this.setFillStyle('white');
3055+
context.save();
3056+
context.beginPath();
3057+
context.rect(left, top, width, height);
3058+
3059+
// Create each shadow and restore the context.
3060+
shadows.forEach(drawShadow, this);
3061+
context.restore();
3062+
};
3063+
29693064
CanvasRenderer.prototype.circle = function(left, top, size, color) {
29703065
this.setFillStyle(color);
29713066
this.ctx.beginPath();
@@ -3006,11 +3101,31 @@ CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx
30063101
}
30073102
};
30083103

3009-
CanvasRenderer.prototype.clip = function(shapes, callback, context) {
3104+
CanvasRenderer.prototype.clip = function(shapes, callback, context, container) {
3105+
// EK 2017-03-25: Clip transform fix.
3106+
var hasTransform = container && container.hasTransform();
30103107
this.ctx.save();
3108+
if (hasTransform) {
3109+
this.setTransform(container.inverseTransform());
3110+
}
30113111
shapes.filter(hasEntries).forEach(function(shape) {
30123112
this.shape(shape).clip();
30133113
}, this);
3114+
if (hasTransform) {
3115+
this.setTransform(container.parseTransform());
3116+
}
3117+
callback.call(context);
3118+
this.ctx.restore();
3119+
};
3120+
3121+
// EK 2017-03-24: Add mask (opposite of clip) for rendering box-shadows.
3122+
CanvasRenderer.prototype.mask = function(bounds, callback, context) {
3123+
var canvasBorderCCW = ["rect", this.canvas.width, 0, -this.canvas.width, this.canvas.height];
3124+
var boundsCW = ["rect", bounds.left, bounds.top, bounds.width, bounds.height];
3125+
3126+
this.ctx.save();
3127+
var shape = [canvasBorderCCW, boundsCW];
3128+
this.shape(shape).clip();
30143129
callback.call(context);
30153130
this.ctx.restore();
30163131
};
@@ -3027,6 +3142,17 @@ CanvasRenderer.prototype.shape = function(shape) {
30273142
this.ctx.closePath();
30283143
return this.ctx;
30293144
};
3145+
CanvasRenderer.prototype.shape2 = function(shape) {
3146+
this.ctx.beginPath();
3147+
shape.forEach(function(point, index) {
3148+
if (point[0] === "rect") {
3149+
this.ctx.rect.apply(this.ctx, point.slice(1));
3150+
} else {
3151+
this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1));
3152+
}
3153+
}, this);
3154+
return this.ctx;
3155+
};
30303156

30313157
CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {
30323158
this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ").split(",")[0];

0 commit comments

Comments
 (0)