Make image loading to work on top of Promises/polyfill

This commit is contained in:
Niklas von Hertzen 2014-01-19 21:05:07 +02:00
parent 8b8c080841
commit 2b8389cb64
8 changed files with 189 additions and 15 deletions

View File

@ -13,5 +13,6 @@
"globals": {
"jQuery": true
},
"predef": ["NodeContainer", "StackingContext", "TextContainer", "CanvasRenderer", "Renderer", "Support"]
"predef": ["NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise",
"ImageContainer", "ProxyImageContainer", "DummyImageContainer"]
}

View File

@ -21,16 +21,7 @@ module.exports = function(grunt) {
concat: {
dist: {
src: [
'src/Core.js',
'src/Font.js',
'src/Generate.js',
'src/Queue.js',
'src/Parse.js',
'src/Preload.js',
'src/Renderer.js',
'src/Support.js',
'src/Util.js',
'src/renderers/Canvas.js'
'src/**/*.js'
],
dest: 'build/<%= pkg.name %>.js'
},

View File

@ -34,13 +34,16 @@ function NodeParser(element, renderer, options) {
this.range = null;
this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null);
parent.blockFormattingContext = parent;
this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) {
return container.visible = container.isElementVisible();
});
this.imageLoader = new ImageLoader(this.nodes.filter(isElement), options, this.support);
this.createStackingContexts();
this.sortStackingContexts(this.stack);
this.parse(this.stack);
this.imageLoader.ready.then(bind(function() {
this.parse(this.stack);
}, this));
}
NodeParser.prototype.getChildren = function(parentContainer) {
@ -518,3 +521,9 @@ function zIndexSort(a, b) {
function hasOpacity(container) {
return container.css("opacity") < 1;
}
function bind(callback, context) {
return function() {
return callback.apply(context, arguments);
};
}

14
src/imagecontainer.js Normal file
View File

@ -0,0 +1,14 @@
function ImageContainer(src, cors) {
this.src = src;
this.image = new Image();
var image = this.image;
this.promise = new Promise(function(resolve, reject) {
image.onload = resolve;
image.onerror = reject;
if (cors) {
image.crossOrigin = "anonymous";
}
image.src = src;
});
}

56
src/imageloader.js Normal file
View File

@ -0,0 +1,56 @@
function ImageLoader(nodes, options, support) {
this.link = null;
this.options = options;
this.support = support;
this.origin = window.location.protocol + window.location.host;
this.images = nodes.reduce(bind(this.findImages, this), []);
this.ready = Promise.all(this.images.map(this.getPromise));
}
ImageLoader.prototype.findImages = function(images, container) {
var backgrounds = container.parseBackgroundImages();
var backgroundImages = backgrounds.filter(this.isImageBackground).map(this.getBackgroundUrl).filter(this.imageExists(images)).map(this.loadImage, this);
return images.concat(backgroundImages);
};
ImageLoader.prototype.getBackgroundUrl = function(imageData) {
return imageData.args[0];
};
ImageLoader.prototype.isImageBackground = function(imageData) {
return imageData.method === "url";
};
ImageLoader.prototype.loadImage = function(src) {
if (src.match(/data:image\/.*;base64,/i)) {
return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
} else if (this.isSameOrigin(src) || this.options.allowTaint === true) {
return new ImageContainer(src, false);
} else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
return new ImageContainer(src, true);
} else if (this.options.proxy) {
return new ProxyImageContainer(src);
} else {
return new DummyImageContainer(src);
}
};
ImageLoader.prototype.imageExists = function(images) {
return function(newImage) {
return !images.some(function(image) {
return image.src !== newImage.src;
});
};
};
ImageLoader.prototype.isSameOrigin = function(url) {
var link = this.link || (this.link = document.createElement("a"));
link.href = url;
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
var origin = link.protocol + link.host;
return (origin === this.origin);
};
ImageLoader.prototype.getPromise = function(container) {
return container.promise;
};

View File

@ -47,3 +47,101 @@ NodeContainer.prototype.fontWeight = function() {
}
return weight;
};
NodeContainer.prototype.parseBackgroundImages = function() {
var whitespace = ' \r\n\t',
method, definition, prefix, prefix_i, block, results = [],
mode = 0, numParen = 0, quote, args;
var appendResult = function() {
if(method) {
if (definition.substr(0, 1) === '"') {
definition = definition.substr(1, definition.length - 2);
}
if (definition) {
args.push(definition);
}
if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {
prefix = method.substr(0, prefix_i);
method = method.substr(prefix_i);
}
results.push({
prefix: prefix,
method: method.toLowerCase(),
value: block,
args: args,
image: null
});
}
args = [];
method = prefix = definition = block = '';
};
args = [];
method = prefix = definition = block = '';
this.css("backgroundImage").split("").forEach(function(c) {
if (mode === 0 && whitespace.indexOf(c) > -1) {
return;
}
switch(c) {
case '"':
if(!quote) {
quote = c;
}
else if(quote === c) {
quote = null;
}
break;
case '(':
if(quote) {
break;
} else if(mode === 0) {
mode = 1;
block += c;
return;
} else {
numParen++;
}
break;
case ')':
if (quote) {
break;
} else if(mode === 1) {
if(numParen === 0) {
mode = 0;
block += c;
appendResult();
return;
} else {
numParen--;
}
}
break;
case ',':
if (quote) {
break;
} else if(mode === 0) {
appendResult();
return;
} else if (mode === 1) {
if (numParen === 0 && !method.match(/^url$/i)) {
args.push(definition);
definition = '';
block += c;
return;
}
}
break;
}
block += c;
if (mode === 0) {
method += c;
} else {
definition += c;
}
});
appendResult();
return results;
};

View File

@ -1,5 +1,6 @@
function Support() {
this.rangeBounds = this.testRangeBounds();
this.cors = this.testCORS();
}
Support.prototype.testRangeBounds = function() {
@ -26,3 +27,7 @@ Support.prototype.testRangeBounds = function() {
return support;
};
Support.prototype.testCORS = function() {
return typeof((new Image()).crossOrigin) !== "undefined";
};

View File

@ -11,9 +11,9 @@ var h2cSelector, h2cOptions;
document.write(srcStart + '/tests/assets/jquery-1.6.2.js' + scrEnd);
document.write(srcStart + '/tests/assets/jquery.plugin.html2canvas.js' + scrEnd);
var html2canvas = ['nodecontainer', 'stackingcontext', 'textcontainer', 'support', 'core', 'renderer', 'renderers/canvas'], i;
var html2canvas = ['nodecontainer', 'stackingcontext', 'textcontainer', 'support', 'imagecontainer', 'imageloader', 'core', 'renderer', 'renderers/canvas'], i;
for (i = 0; i < html2canvas.length; ++i) {
document.write(srcStart + '/new/' + html2canvas[i] + '.js?' + Math.random() + scrEnd);
document.write(srcStart + '/src/' + html2canvas[i] + '.js?' + Math.random() + scrEnd);
}
window.onload = function() {
h2cSelector = [document.body];