Conflicts:
	build/html2canvas.min.js
	dist/html2canvas.js
	src/core.js
This commit is contained in:
MoyuScript 2014-09-04 16:20:49 -05:00
commit 887bdc3927
16 changed files with 171 additions and 137 deletions

View File

@ -10,6 +10,7 @@
"boss": true, "boss": true,
"eqnull": true, "eqnull": true,
"browser": true, "browser": true,
"indent": 4,
"globals": { "globals": {
"jQuery": true "jQuery": true
}, },

View File

@ -25,7 +25,7 @@ module.exports = function(grunt) {
src: [ src: [
'src/promise.js', 'src/fallback.js', 'src/**/*.js' 'src/promise.js', 'src/fallback.js', 'src/**/*.js'
], ],
dest: 'build/<%= pkg.name %>.js' dest: 'dist/<%= pkg.name %>.js'
}, },
options:{ options:{
banner: meta.banner + meta.pre, banner: meta.banner + meta.pre,
@ -71,7 +71,7 @@ module.exports = function(grunt) {
uglify: { uglify: {
dist: { dist: {
src: ['<%= concat.dist.dest %>'], src: ['<%= concat.dist.dest %>'],
dest: 'build/<%= pkg.name %>.min.js' dest: 'dist/<%= pkg.name %>.min.js'
}, },
options: { options: {
banner: meta.banner banner: meta.banner

View File

@ -1,8 +1,8 @@
{ {
"name": "html2canvas", "name": "html2canvas",
"version": "0.4.1", "version": "0.5.0-alpha",
"description": "Screenshots with JavaScript", "description": "Screenshots with JavaScript",
"main": "build/html2canvas.js", "main": "dist/html2canvas.js",
"ignore": [ "ignore": [
"tests", "tests",
".travis.yml" ".travis.yml"

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/* /*
html2canvas 0.5.0-rc1 <http://html2canvas.hertzen.com> html2canvas 0.5.0-alpha <http://html2canvas.hertzen.com>
Copyright (c) 2014 Niklas von Hertzen Copyright (c) 2014 Niklas von Hertzen
Released under MIT License Released under MIT License
@ -47,6 +47,7 @@ window.html2canvas = function(nodeList, options) {
} }
options.async = typeof(options.async) === "undefined" ? true : options.async; options.async = typeof(options.async) === "undefined" ? true : options.async;
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0]; var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
@ -72,7 +73,7 @@ function renderDocument(document, options, windowWidth, windowHeight) {
var bounds = getBounds(node); var bounds = getBounds(node);
var width = options.type === "view" ? Math.min(bounds.width, windowWidth) : documentWidth(clonedWindow.document); var width = options.type === "view" ? Math.min(bounds.width, windowWidth) : documentWidth(clonedWindow.document);
var height = options.type === "view" ? Math.min(bounds.height, windowHeight) : documentHeight(clonedWindow.document); var height = options.type === "view" ? Math.min(bounds.height, windowHeight) : documentHeight(clonedWindow.document);
var renderer = new CanvasRenderer(width, height, imageLoader); var renderer = new CanvasRenderer(width, height, imageLoader, options);
var parser = new NodeParser(node, renderer, support, imageLoader, options); var parser = new NodeParser(node, renderer, support, imageLoader, options);
return parser.ready.then(function() { return parser.ready.then(function() {
log("Finished rendering"); log("Finished rendering");
@ -263,6 +264,7 @@ function ImageContainer(src, cors) {
this.src = src; this.src = src;
this.image = new Image(); this.image = new Image();
var self = this; var self = this;
this.tainted = null;
this.promise = new Promise(function(resolve, reject) { this.promise = new Promise(function(resolve, reject) {
self.image.onload = resolve; self.image.onload = resolve;
self.image.onerror = reject; self.image.onerror = reject;
@ -1083,7 +1085,7 @@ NodeParser.prototype.paintNode = function(container) {
case "IMG": case "IMG":
var imageContainer = this.images.get(container.node.src); var imageContainer = this.images.get(container.node.src);
if (imageContainer) { if (imageContainer) {
this.renderer.renderImage(container, bounds, borderData, imageContainer.image); this.renderer.renderImage(container, bounds, borderData, imageContainer);
} else { } else {
log("Error loading <img>", container.node.src); log("Error loading <img>", container.node.src);
} }
@ -1538,13 +1540,14 @@ function ProxyImageContainer(src, proxy) {
var proxyImageCount = 0; var proxyImageCount = 0;
function Renderer(width, height, images) { function Renderer(width, height, images, options) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.images = images; this.images = images;
this.options = options;
} }
Renderer.prototype.renderImage = function(container, bounds, borderData, image) { Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {
var paddingLeft = container.cssInt('paddingLeft'), var paddingLeft = container.cssInt('paddingLeft'),
paddingTop = container.cssInt('paddingTop'), paddingTop = container.cssInt('paddingTop'),
paddingRight = container.cssInt('paddingRight'), paddingRight = container.cssInt('paddingRight'),
@ -1552,11 +1555,11 @@ Renderer.prototype.renderImage = function(container, bounds, borderData, image)
borders = borderData.borders; borders = borderData.borders;
this.drawImage( this.drawImage(
image, imageContainer,
0, 0,
0, 0,
image.width, imageContainer.image.width,
image.height, imageContainer.image.height,
bounds.left + paddingLeft + borders[3].width, bounds.left + paddingLeft + borders[3].width,
bounds.top + paddingTop + borders[0].width, bounds.top + paddingTop + borders[0].width,
bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight),
@ -1649,6 +1652,7 @@ function CanvasRenderer(width, height) {
this.canvas.width = width; this.canvas.width = width;
this.canvas.height = height; this.canvas.height = height;
this.ctx = this.canvas.getContext("2d"); this.ctx = this.canvas.getContext("2d");
this.taintCtx = document.createElement("canvas").getContext("2d");
this.ctx.textBaseline = "bottom"; this.ctx.textBaseline = "bottom";
this.variables = {}; this.variables = {};
log("Initialized CanvasRenderer"); log("Initialized CanvasRenderer");
@ -1670,8 +1674,25 @@ CanvasRenderer.prototype.drawShape = function(shape, color) {
this.setFillStyle(color).fill(); this.setFillStyle(color).fill();
}; };
CanvasRenderer.prototype.drawImage = function(image, sx, sy, sw, sh, dx, dy, dw, dh) { CanvasRenderer.prototype.taints = function(imageContainer) {
this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh); if (imageContainer.tainted === null) {
this.taintCtx.drawImage(imageContainer.image, 0, 0);
try {
this.taintCtx.getImageData(0, 0, 1, 1);
imageContainer.tainted = false;
} catch(e) {
this.taintCtx = document.createElement("canvas").getContext("2d");
imageContainer.tainted = true;
}
}
return imageContainer.tainted;
};
CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {
if (!this.taints(imageContainer)) {
this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);
}
}; };
CanvasRenderer.prototype.clip = function(shape, callback, context) { CanvasRenderer.prototype.clip = function(shape, callback, context) {

8
dist/html2canvas.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -171,7 +171,7 @@
<p style="color: black; font-size: 1em; line-height: 1.3em; clear: both"> <p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>. This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
</p> </p>
<script type="text/javascript" src="../build/html2canvas.js"></script> <script type="text/javascript" src="../dist/html2canvas.js"></script>
<script type="text/javascript"> <script type="text/javascript">
html2canvas(document.body, { html2canvas(document.body, {
onrendered: function(canvas) { onrendered: function(canvas) {

View File

@ -53,7 +53,7 @@
</div> </div>
</div> </div>
<script type="text/javascript" src="../build/html2canvas.js"></script> <script type="text/javascript" src="../dist/html2canvas.js"></script>
<script type="text/javascript"> <script type="text/javascript">
html2canvas(document.body, { html2canvas(document.body, {
onrendered: function(canvas) { onrendered: function(canvas) {

View File

@ -2,7 +2,7 @@
"title": "html2canvas", "title": "html2canvas",
"name": "html2canvas", "name": "html2canvas",
"description": "Screenshots with JavaScript", "description": "Screenshots with JavaScript",
"version": "0.5.0-rc1", "version": "0.5.0-alpha",
"author": { "author": {
"name": "Niklas von Hertzen", "name": "Niklas von Hertzen",
"email": "niklasvh@gmail.com", "email": "niklasvh@gmail.com",
@ -20,7 +20,7 @@
"url": "https://github.com/niklasvh/html2canvas/issues" "url": "https://github.com/niklasvh/html2canvas/issues"
}, },
"devDependencies": { "devDependencies": {
"baconjs": "0.7.11", "baconjs": "^0.7.11",
"base64-arraybuffer": ">= 0.1.0", "base64-arraybuffer": ">= 0.1.0",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-contrib-concat": "*", "grunt-contrib-concat": "*",
@ -31,7 +31,7 @@
"grunt-contrib-watch": "~0.5.1", "grunt-contrib-watch": "~0.5.1",
"lodash": "^2.4.1", "lodash": "^2.4.1",
"png-js": ">= 0.1.1", "png-js": ">= 0.1.1",
"wd": "0.2.21" "wd": "^0.2.21"
}, },
"scripts": { "scripts": {
"test": "grunt travis --verbose" "test": "grunt travis --verbose"

View File

@ -8,6 +8,7 @@ window.html2canvas = function(nodeList, options) {
} }
options.async = typeof(options.async) === "undefined" ? true : options.async; options.async = typeof(options.async) === "undefined" ? true : options.async;
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0]; var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
@ -33,7 +34,7 @@ function renderDocument(document, options, windowWidth, windowHeight) {
var bounds = getBounds(node); var bounds = getBounds(node);
var width = options.type === "view" ? Math.min(bounds.width, windowWidth) : documentWidth(clonedWindow.document); var width = options.type === "view" ? Math.min(bounds.width, windowWidth) : documentWidth(clonedWindow.document);
var height = options.type === "view" ? Math.min(bounds.height, windowHeight) : documentHeight(clonedWindow.document); var height = options.type === "view" ? Math.min(bounds.height, windowHeight) : documentHeight(clonedWindow.document);
var renderer = new CanvasRenderer(width, height, imageLoader); var renderer = new CanvasRenderer(width, height, imageLoader, options);
var parser = new NodeParser(node, renderer, support, imageLoader, options); var parser = new NodeParser(node, renderer, support, imageLoader, options);
return parser.ready.then(function() { return parser.ready.then(function() {
log("Finished rendering"); log("Finished rendering");

View File

@ -2,6 +2,7 @@ function ImageContainer(src, cors) {
this.src = src; this.src = src;
this.image = new Image(); this.image = new Image();
var self = this; var self = this;
this.tainted = null;
this.promise = new Promise(function(resolve, reject) { this.promise = new Promise(function(resolve, reject) {
self.image.onload = resolve; self.image.onload = resolve;
self.image.onerror = reject; self.image.onerror = reject;

View File

@ -254,7 +254,7 @@ NodeParser.prototype.paintNode = function(container) {
case "IMG": case "IMG":
var imageContainer = this.images.get(container.node.src); var imageContainer = this.images.get(container.node.src);
if (imageContainer) { if (imageContainer) {
this.renderer.renderImage(container, bounds, borderData, imageContainer.image); this.renderer.renderImage(container, bounds, borderData, imageContainer);
} else { } else {
log("Error loading <img>", container.node.src); log("Error loading <img>", container.node.src);
} }

View File

@ -1,10 +1,11 @@
function Renderer(width, height, images) { function Renderer(width, height, images, options) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.images = images; this.images = images;
this.options = options;
} }
Renderer.prototype.renderImage = function(container, bounds, borderData, image) { Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {
var paddingLeft = container.cssInt('paddingLeft'), var paddingLeft = container.cssInt('paddingLeft'),
paddingTop = container.cssInt('paddingTop'), paddingTop = container.cssInt('paddingTop'),
paddingRight = container.cssInt('paddingRight'), paddingRight = container.cssInt('paddingRight'),
@ -12,11 +13,11 @@ Renderer.prototype.renderImage = function(container, bounds, borderData, image)
borders = borderData.borders; borders = borderData.borders;
this.drawImage( this.drawImage(
image, imageContainer,
0, 0,
0, 0,
image.width, imageContainer.image.width,
image.height, imageContainer.image.height,
bounds.left + paddingLeft + borders[3].width, bounds.left + paddingLeft + borders[3].width,
bounds.top + paddingTop + borders[0].width, bounds.top + paddingTop + borders[0].width,
bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight),

View File

@ -4,6 +4,7 @@ function CanvasRenderer(width, height) {
this.canvas.width = width; this.canvas.width = width;
this.canvas.height = height; this.canvas.height = height;
this.ctx = this.canvas.getContext("2d"); this.ctx = this.canvas.getContext("2d");
this.taintCtx = document.createElement("canvas").getContext("2d");
this.ctx.textBaseline = "bottom"; this.ctx.textBaseline = "bottom";
this.variables = {}; this.variables = {};
log("Initialized CanvasRenderer"); log("Initialized CanvasRenderer");
@ -25,8 +26,25 @@ CanvasRenderer.prototype.drawShape = function(shape, color) {
this.setFillStyle(color).fill(); this.setFillStyle(color).fill();
}; };
CanvasRenderer.prototype.drawImage = function(image, sx, sy, sw, sh, dx, dy, dw, dh) { CanvasRenderer.prototype.taints = function(imageContainer) {
this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh); if (imageContainer.tainted === null) {
this.taintCtx.drawImage(imageContainer.image, 0, 0);
try {
this.taintCtx.getImageData(0, 0, 1, 1);
imageContainer.tainted = false;
} catch(e) {
this.taintCtx = document.createElement("canvas").getContext("2d");
imageContainer.tainted = true;
}
}
return imageContainer.tainted;
};
CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {
if (!this.taints(imageContainer)) {
this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);
}
}; };
CanvasRenderer.prototype.clip = function(shape, callback, context) { CanvasRenderer.prototype.clip = function(shape, callback, context) {

View File

@ -1,86 +0,0 @@
/*
* jQuery helper plugin for examples and tests
*/
(function( $ ){
$.fn.html2canvas = function(options) {
if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") {
window.console.profile();
}
var date = new Date(),
$message = null,
timeoutTimer = false,
timer = date.getTime();
options = options || {};
var promise = html2canvas(this, options);
promise.catch(function(err) {
console.log("html2canvas threw an error", err);
});
promise.then(function(canvas) {
var $canvas = $(canvas),
finishTime = new Date();
if (options && options.profile && window.console && window.console.profileEnd) {
window.console.profileEnd();
}
$canvas.addClass("html2canvas")
.css({
position: 'absolute',
left: 0,
top: 0
}).appendTo(document.body);
if (window.location.search !== "?selenium") {
$canvas.siblings().toggle();
$(window).click(function(){
$canvas.toggle().siblings().toggle();
throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden"));
});
throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms<br />",4000);
} else {
$canvas.css('display', 'none');
}
// test if canvas is read-able
try {
$canvas[0].toDataURL();
} catch(e) {
if ($canvas[0].nodeName.toLowerCase() === "canvas") {
// TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed
window.alert("Canvas is tainted, unable to read data");
}
}
});
function throwMessage(msg,duration){
window.clearTimeout(timeoutTimer);
timeoutTimer = window.setTimeout(function(){
$message.fadeOut(function(){
$message.remove();
$message = null;
});
},duration || 2000);
if ($message)
$message.remove();
$message = $('<div />').html(msg).css({
margin:0,
padding:10,
background: "#000",
opacity:0.7,
position:"fixed",
top:10,
right:10,
fontFamily: 'Tahoma',
color:'#fff',
fontSize:12,
borderRadius:12,
width:'auto',
height:'auto',
textAlign:'center',
textDecoration:'none',
display:'none'
}).appendTo(document.body).fadeIn();
log(msg);
}
};
})( jQuery );

View File

@ -7,21 +7,100 @@
*/ */
var h2cSelector, h2cOptions; var h2cSelector, h2cOptions;
(function(document, window) { (function(document, window) {
var srcStart = '<script type="text/javascript" src="', scrEnd = '"></script>'; function appendScript(src) {
document.write('<script type="text/javascript" src="' + src + '.js?' + Math.random() + '"></script>');
document.write(srcStart + '/tests/assets/jquery-1.6.2.js' + scrEnd);
document.write(srcStart + '/tests/assets/jquery.plugin.html2canvas.js' + scrEnd);
var html2canvas = ['log', 'nodecontainer', 'stackingcontext', 'textcontainer', 'support', 'imagecontainer', 'dummyimagecontainer', 'proxyimagecontainer', 'gradientcontainer', 'lineargradientcontainer', 'webkitgradientcontainer',
'imageloader', 'nodeparser', 'font', 'fontmetrics', 'core', 'renderer', 'promise', 'renderers/canvas'], i;
if (window.location.search === "?selenium") {
document.write(srcStart + '/build/html2canvas.js' + scrEnd);
} else {
for (i = 0; i < html2canvas.length; ++i) {
document.write(srcStart + '/src/' + html2canvas[i] + '.js?' + Math.random() + scrEnd);
}
} }
var sources = ['log', 'nodecontainer', 'stackingcontext', 'textcontainer', 'support', 'imagecontainer', 'dummyimagecontainer', 'proxyimagecontainer', 'gradientcontainer',
'lineargradientcontainer', 'webkitgradientcontainer', 'imageloader', 'nodeparser', 'font', 'fontmetrics', 'core', 'renderer', 'promise', 'renderers/canvas'];
['/tests/assets/jquery-1.6.2'].concat(window.location.search === "?selenium" ? ['/dist/html2canvas'] : sources.map(function(src) { return '/src/' + src; })).forEach(appendScript);
window.onload = function() { window.onload = function() {
(function( $ ){
$.fn.html2canvas = function(options) {
if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") {
window.console.profile();
}
var date = new Date(),
$message = null,
timeoutTimer = false,
timer = date.getTime();
options = options || {};
var promise = html2canvas(this, options);
promise['catch'](function(err) {
console.log("html2canvas threw an error", err);
});
promise.then(function(canvas) {
var $canvas = $(canvas),
finishTime = new Date();
if (options && options.profile && window.console && window.console.profileEnd) {
window.console.profileEnd();
}
$canvas.addClass("html2canvas")
.css({
position: 'absolute',
left: 0,
top: 0
}).appendTo(document.body);
if (window.location.search !== "?selenium") {
$canvas.siblings().toggle();
$(window).click(function(){
$canvas.toggle().siblings().toggle();
throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden"));
});
throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms<br />",4000);
} else {
$canvas.css('display', 'none');
}
// test if canvas is read-able
try {
$canvas[0].toDataURL();
} catch(e) {
if ($canvas[0].nodeName.toLowerCase() === "canvas") {
// TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed
window.alert("Canvas is tainted, unable to read data");
}
}
});
function throwMessage(msg,duration){
window.clearTimeout(timeoutTimer);
timeoutTimer = window.setTimeout(function(){
$message.fadeOut(function(){
$message.remove();
$message = null;
});
},duration || 2000);
if ($message)
$message.remove();
$message = $('<div />').html(msg).css({
margin:0,
padding:10,
background: "#000",
opacity:0.7,
position:"fixed",
top:10,
right:10,
fontFamily: 'Tahoma',
color:'#fff',
fontSize:12,
borderRadius:12,
width:'auto',
height:'auto',
textAlign:'center',
textDecoration:'none',
display:'none'
}).appendTo(document.body).fadeIn();
log(msg);
}
};
})(jQuery);
h2cSelector = [document.documentElement]; h2cSelector = [document.documentElement];
if (window.setUp) { if (window.setUp) {
@ -29,9 +108,7 @@ var h2cSelector, h2cOptions;
} }
setTimeout(function() { setTimeout(function() {
$(h2cSelector).html2canvas($.extend({ $(h2cSelector).html2canvas($.extend({
flashcanvas: "../external/flashcanvas.min.js",
logging: true, logging: true,
profile: true, profile: true,
proxy: "http://html2canvas.appspot.com/query", proxy: "http://html2canvas.appspot.com/query",