mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
400 lines
12 KiB
JavaScript
400 lines
12 KiB
JavaScript
function NodeContainer(node, parent) {
|
|
this.node = node;
|
|
this.parent = parent;
|
|
this.stack = null;
|
|
this.bounds = null;
|
|
this.borders = null;
|
|
this.clip = [];
|
|
this.backgroundClip = [];
|
|
this.offsetBounds = null;
|
|
this.visible = null;
|
|
this.computedStyles = null;
|
|
this.styles = {};
|
|
this.backgroundImages = null;
|
|
this.transformData = null;
|
|
this.transformMatrix = null;
|
|
this.isPseudoElement = false;
|
|
this.opacity = null;
|
|
}
|
|
|
|
NodeContainer.prototype.cloneTo = function(stack) {
|
|
stack.visible = this.visible;
|
|
stack.borders = this.borders;
|
|
stack.bounds = this.bounds;
|
|
stack.clip = this.clip;
|
|
stack.backgroundClip = this.backgroundClip;
|
|
stack.computedStyles = this.computedStyles;
|
|
stack.styles = this.styles;
|
|
stack.backgroundImages = this.backgroundImages;
|
|
stack.opacity = this.opacity;
|
|
};
|
|
|
|
NodeContainer.prototype.getOpacity = function() {
|
|
return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;
|
|
};
|
|
|
|
NodeContainer.prototype.assignStack = function(stack) {
|
|
this.stack = stack;
|
|
stack.children.push(this);
|
|
};
|
|
|
|
NodeContainer.prototype.isElementVisible = function() {
|
|
return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (
|
|
this.css('display') !== "none" &&
|
|
this.css('visibility') !== "hidden" &&
|
|
!this.node.hasAttribute("data-html2canvas-ignore") &&
|
|
(this.node.nodeName !== "INPUT" || this.node.getAttribute("type") !== "hidden")
|
|
);
|
|
};
|
|
|
|
NodeContainer.prototype.css = function(attribute) {
|
|
if (!this.computedStyles) {
|
|
this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null);
|
|
}
|
|
|
|
return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
|
|
};
|
|
|
|
NodeContainer.prototype.prefixedCss = function(attribute) {
|
|
var prefixes = ["webkit", "moz", "ms", "o"];
|
|
var value = this.css(attribute);
|
|
if (value === undefined) {
|
|
prefixes.some(function(prefix) {
|
|
value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1));
|
|
return value !== undefined;
|
|
}, this);
|
|
}
|
|
return value === undefined ? null : value;
|
|
};
|
|
|
|
NodeContainer.prototype.computedStyle = function(type) {
|
|
return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
|
|
};
|
|
|
|
NodeContainer.prototype.cssInt = function(attribute) {
|
|
var value = parseInt(this.css(attribute), 10);
|
|
return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
|
|
};
|
|
|
|
NodeContainer.prototype.cssFloat = function(attribute) {
|
|
var value = parseFloat(this.css(attribute));
|
|
return (isNaN(value)) ? 0 : value;
|
|
};
|
|
|
|
NodeContainer.prototype.fontWeight = function() {
|
|
var weight = this.css("fontWeight");
|
|
switch(parseInt(weight, 10)){
|
|
case 401:
|
|
weight = "bold";
|
|
break;
|
|
case 400:
|
|
weight = "normal";
|
|
break;
|
|
}
|
|
return weight;
|
|
};
|
|
|
|
NodeContainer.prototype.parseClip = function() {
|
|
var matches = this.css('clip').match(this.CLIP);
|
|
if (matches) {
|
|
return {
|
|
top: parseInt(matches[1], 10),
|
|
right: parseInt(matches[2], 10),
|
|
bottom: parseInt(matches[3], 10),
|
|
left: parseInt(matches[4], 10)
|
|
};
|
|
}
|
|
return null;
|
|
};
|
|
|
|
NodeContainer.prototype.parseBackgroundImages = function() {
|
|
return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
|
|
};
|
|
|
|
NodeContainer.prototype.cssList = function(property, index) {
|
|
var value = (this.css(property) || '').split(',');
|
|
value = value[index || 0] || value[0] || 'auto';
|
|
value = value.trim().split(' ');
|
|
if (value.length === 1) {
|
|
value = [value[0], value[0]];
|
|
}
|
|
return value;
|
|
};
|
|
|
|
NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
|
|
var size = this.cssList("backgroundSize", index);
|
|
var width, height;
|
|
|
|
if (isPercentage(size[0])) {
|
|
width = bounds.width * parseFloat(size[0]) / 100;
|
|
} else if (/contain|cover/.test(size[0])) {
|
|
var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
|
|
return (targetRatio < currentRatio ^ size[0] === 'contain') ? {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
|
|
} else {
|
|
width = parseInt(size[0], 10);
|
|
}
|
|
|
|
if (size[0] === 'auto' && size[1] === 'auto') {
|
|
height = image.height;
|
|
} else if (size[1] === 'auto') {
|
|
height = width / image.width * image.height;
|
|
} else if (isPercentage(size[1])) {
|
|
height = bounds.height * parseFloat(size[1]) / 100;
|
|
} else {
|
|
height = parseInt(size[1], 10);
|
|
}
|
|
|
|
if (size[0] === 'auto') {
|
|
width = height / image.height * image.width;
|
|
}
|
|
|
|
return {width: width, height: height};
|
|
};
|
|
|
|
NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
|
|
var position = this.cssList('backgroundPosition', index);
|
|
var left, top;
|
|
|
|
if (isPercentage(position[0])){
|
|
left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
|
|
} else {
|
|
left = parseInt(position[0], 10);
|
|
}
|
|
|
|
if (position[1] === 'auto') {
|
|
top = left / image.width * image.height;
|
|
} else if (isPercentage(position[1])){
|
|
top = (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
|
|
} else {
|
|
top = parseInt(position[1], 10);
|
|
}
|
|
|
|
if (position[0] === 'auto') {
|
|
left = top / image.height * image.width;
|
|
}
|
|
|
|
return {left: left, top: top};
|
|
};
|
|
|
|
NodeContainer.prototype.parseBackgroundRepeat = function(index) {
|
|
return this.cssList("backgroundRepeat", index)[0];
|
|
};
|
|
|
|
NodeContainer.prototype.parseTextShadows = function() {
|
|
var textShadow = this.css("textShadow");
|
|
var results = [];
|
|
|
|
if (textShadow && textShadow !== 'none') {
|
|
var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY);
|
|
for (var i = 0; shadows && (i < shadows.length); i++) {
|
|
var s = shadows[i].match(this.TEXT_SHADOW_VALUES);
|
|
results.push({
|
|
color: s[0],
|
|
offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0,
|
|
offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0,
|
|
blur: s[3] ? s[3].replace('px', '') : 0
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
};
|
|
|
|
NodeContainer.prototype.parseTransform = function() {
|
|
if (!this.transformData) {
|
|
if (this.hasTransform()) {
|
|
var offset = this.parseBounds();
|
|
var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
|
|
origin[0] += offset.left;
|
|
origin[1] += offset.top;
|
|
this.transformData = {
|
|
origin: origin,
|
|
matrix: this.parseTransformMatrix()
|
|
};
|
|
} else {
|
|
this.transformData = {
|
|
origin: [0, 0],
|
|
matrix: [1, 0, 0, 1, 0, 0]
|
|
};
|
|
}
|
|
}
|
|
return this.transformData;
|
|
};
|
|
|
|
NodeContainer.prototype.parseTransformMatrix = function() {
|
|
if (!this.transformMatrix) {
|
|
var transform = this.prefixedCss("transform");
|
|
var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null;
|
|
this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0];
|
|
}
|
|
return this.transformMatrix;
|
|
};
|
|
|
|
NodeContainer.prototype.parseBounds = function() {
|
|
return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
|
|
};
|
|
|
|
NodeContainer.prototype.hasTransform = function() {
|
|
return this.parseTransformMatrix().join(",") !== "1,0,0,1,0,0" || (this.parent && this.parent.hasTransform());
|
|
};
|
|
|
|
NodeContainer.prototype.getValue = function() {
|
|
var value = this.node.value || "";
|
|
value = (this.node.tagName === "SELECT") ? selectionValue(this.node) : value;
|
|
return value.length === 0 ? (this.node.placeholder || "") : value;
|
|
};
|
|
|
|
NodeContainer.prototype.MATRIX_PROPERTY = /(matrix)\((.+)\)/;
|
|
NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
|
|
NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
|
|
NodeContainer.prototype.CLIP = /^rect\((\d+)px,? (\d+)px,? (\d+)px,? (\d+)px\)$/;
|
|
|
|
function selectionValue(node) {
|
|
var option = node.options[node.selectedIndex || 0];
|
|
return option ? (option.text || "") : "";
|
|
}
|
|
|
|
function parseMatrix(match) {
|
|
if (match && match[1] === "matrix") {
|
|
return match[2].split(",").map(function(s) {
|
|
return parseFloat(s.trim());
|
|
});
|
|
}
|
|
}
|
|
|
|
function isPercentage(value) {
|
|
return value.toString().indexOf("%") !== -1;
|
|
}
|
|
|
|
function parseBackgrounds(backgroundImage) {
|
|
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 = '';
|
|
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;
|
|
}
|
|
|
|
function removePx(str) {
|
|
return str.replace("px", "");
|
|
}
|
|
|
|
function asFloat(str) {
|
|
return parseFloat(str);
|
|
}
|
|
|
|
function getBounds(node) {
|
|
if (node.getBoundingClientRect) {
|
|
var clientRect = node.getBoundingClientRect();
|
|
var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth;
|
|
return {
|
|
top: clientRect.top,
|
|
bottom: clientRect.bottom || (clientRect.top + clientRect.height),
|
|
right: clientRect.left + width,
|
|
left: clientRect.left,
|
|
width: width,
|
|
height: node.offsetHeight == null ? clientRect.height : node.offsetHeight
|
|
};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function offsetBounds(node) {
|
|
var parent = node.offsetParent ? offsetBounds(node.offsetParent) : {top: 0, left: 0};
|
|
|
|
return {
|
|
top: node.offsetTop + parent.top,
|
|
bottom: node.offsetTop + node.offsetHeight + parent.top,
|
|
right: node.offsetLeft + parent.left + node.offsetWidth,
|
|
left: node.offsetLeft + parent.left,
|
|
width: node.offsetWidth,
|
|
height: node.offsetHeight
|
|
};
|
|
}
|