html2canvas
Version:
Screenshots with JavaScript
447 lines (376 loc) • 18.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.transformWebkitRadialGradientArgs = exports.parseGradient = exports.RadialGradient = exports.LinearGradient = exports.RADIAL_GRADIENT_SHAPE = exports.GRADIENT_TYPE = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _NodeContainer = require('./NodeContainer');
var _NodeContainer2 = _interopRequireDefault(_NodeContainer);
var _Angle = require('./Angle');
var _Color = require('./Color');
var _Color2 = _interopRequireDefault(_Color);
var _Length = require('./Length');
var _Length2 = _interopRequireDefault(_Length);
var _Util = require('./Util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var SIDE_OR_CORNER = /^(to )?(left|top|right|bottom)( (left|top|right|bottom))?$/i;
var PERCENTAGE_ANGLES = /^([+-]?\d*\.?\d+)% ([+-]?\d*\.?\d+)%$/i;
var ENDS_WITH_LENGTH = /(px)|%|( 0)$/i;
var FROM_TO_COLORSTOP = /^(from|to|color-stop)\((?:([\d.]+)(%)?,\s*)?(.+?)\)$/i;
var RADIAL_SHAPE_DEFINITION = /^\s*(circle|ellipse)?\s*((?:([\d.]+)(px|r?em|%)\s*(?:([\d.]+)(px|r?em|%))?)|closest-side|closest-corner|farthest-side|farthest-corner)?\s*(?:at\s*(?:(left|center|right)|([\d.]+)(px|r?em|%))\s+(?:(top|center|bottom)|([\d.]+)(px|r?em|%)))?(?:\s|$)/i;
var GRADIENT_TYPE = exports.GRADIENT_TYPE = {
LINEAR_GRADIENT: 0,
RADIAL_GRADIENT: 1
};
var RADIAL_GRADIENT_SHAPE = exports.RADIAL_GRADIENT_SHAPE = {
CIRCLE: 0,
ELLIPSE: 1
};
var LENGTH_FOR_POSITION = {
left: new _Length2.default('0%'),
top: new _Length2.default('0%'),
center: new _Length2.default('50%'),
right: new _Length2.default('100%'),
bottom: new _Length2.default('100%')
};
var LinearGradient = exports.LinearGradient = function LinearGradient(colorStops, direction) {
_classCallCheck(this, LinearGradient);
this.type = GRADIENT_TYPE.LINEAR_GRADIENT;
this.colorStops = colorStops;
this.direction = direction;
};
var RadialGradient = exports.RadialGradient = function RadialGradient(colorStops, shape, center, radius) {
_classCallCheck(this, RadialGradient);
this.type = GRADIENT_TYPE.RADIAL_GRADIENT;
this.colorStops = colorStops;
this.shape = shape;
this.center = center;
this.radius = radius;
};
var parseGradient = exports.parseGradient = function parseGradient(container, _ref, bounds) {
var args = _ref.args,
method = _ref.method,
prefix = _ref.prefix;
if (method === 'linear-gradient') {
return parseLinearGradient(args, bounds, !!prefix);
} else if (method === 'gradient' && args[0] === 'linear') {
// TODO handle correct angle
return parseLinearGradient(['to bottom'].concat(transformObsoleteColorStops(args.slice(3))), bounds, !!prefix);
} else if (method === 'radial-gradient') {
return parseRadialGradient(container, prefix === '-webkit-' ? transformWebkitRadialGradientArgs(args) : args, bounds);
} else if (method === 'gradient' && args[0] === 'radial') {
return parseRadialGradient(container, transformObsoleteColorStops(transformWebkitRadialGradientArgs(args.slice(1))), bounds);
}
};
var parseColorStops = function parseColorStops(args, firstColorStopIndex, lineLength) {
var colorStops = [];
for (var i = firstColorStopIndex; i < args.length; i++) {
var value = args[i];
var HAS_LENGTH = ENDS_WITH_LENGTH.test(value);
var lastSpaceIndex = value.lastIndexOf(' ');
var _color = new _Color2.default(HAS_LENGTH ? value.substring(0, lastSpaceIndex) : value);
var _stop = HAS_LENGTH ? new _Length2.default(value.substring(lastSpaceIndex + 1)) : i === firstColorStopIndex ? new _Length2.default('0%') : i === args.length - 1 ? new _Length2.default('100%') : null;
colorStops.push({ color: _color, stop: _stop });
}
var absoluteValuedColorStops = colorStops.map(function (_ref2) {
var color = _ref2.color,
stop = _ref2.stop;
var absoluteStop = lineLength === 0 ? 0 : stop ? stop.getAbsoluteValue(lineLength) / lineLength : null;
return {
color: color,
// $FlowFixMe
stop: absoluteStop
};
});
var previousColorStop = absoluteValuedColorStops[0].stop;
for (var _i = 0; _i < absoluteValuedColorStops.length; _i++) {
if (previousColorStop !== null) {
var _stop2 = absoluteValuedColorStops[_i].stop;
if (_stop2 === null) {
var n = _i;
while (absoluteValuedColorStops[n].stop === null) {
n++;
}
var steps = n - _i + 1;
var nextColorStep = absoluteValuedColorStops[n].stop;
var stepSize = (nextColorStep - previousColorStop) / steps;
for (; _i < n; _i++) {
previousColorStop = absoluteValuedColorStops[_i].stop = previousColorStop + stepSize;
}
} else {
previousColorStop = _stop2;
}
}
}
return absoluteValuedColorStops;
};
var parseLinearGradient = function parseLinearGradient(args, bounds, hasPrefix) {
var angle = (0, _Angle.parseAngle)(args[0]);
var HAS_SIDE_OR_CORNER = SIDE_OR_CORNER.test(args[0]);
var HAS_DIRECTION = HAS_SIDE_OR_CORNER || angle !== null || PERCENTAGE_ANGLES.test(args[0]);
var direction = HAS_DIRECTION ? angle !== null ? calculateGradientDirection(
// if there is a prefix, the 0° angle points due East (instead of North per W3C)
hasPrefix ? angle - Math.PI * 0.5 : angle, bounds) : HAS_SIDE_OR_CORNER ? parseSideOrCorner(args[0], bounds) : parsePercentageAngle(args[0], bounds) : calculateGradientDirection(Math.PI, bounds);
var firstColorStopIndex = HAS_DIRECTION ? 1 : 0;
// TODO: Fix some inaccuracy with color stops with px values
var lineLength = Math.min((0, _Util.distance)(Math.abs(direction.x0) + Math.abs(direction.x1), Math.abs(direction.y0) + Math.abs(direction.y1)), bounds.width * 2, bounds.height * 2);
return new LinearGradient(parseColorStops(args, firstColorStopIndex, lineLength), direction);
};
var parseRadialGradient = function parseRadialGradient(container, args, bounds) {
var m = args[0].match(RADIAL_SHAPE_DEFINITION);
var shape = m && (m[1] === 'circle' || // explicit shape specification
m[3] !== undefined && m[5] === undefined) // only one radius coordinate
? RADIAL_GRADIENT_SHAPE.CIRCLE : RADIAL_GRADIENT_SHAPE.ELLIPSE;
var radius = {};
var center = {};
if (m) {
// Radius
if (m[3] !== undefined) {
radius.x = (0, _Length.calculateLengthFromValueWithUnit)(container, m[3], m[4]).getAbsoluteValue(bounds.width);
}
if (m[5] !== undefined) {
radius.y = (0, _Length.calculateLengthFromValueWithUnit)(container, m[5], m[6]).getAbsoluteValue(bounds.height);
}
// Position
if (m[7]) {
center.x = LENGTH_FOR_POSITION[m[7].toLowerCase()];
} else if (m[8] !== undefined) {
center.x = (0, _Length.calculateLengthFromValueWithUnit)(container, m[8], m[9]);
}
if (m[10]) {
center.y = LENGTH_FOR_POSITION[m[10].toLowerCase()];
} else if (m[11] !== undefined) {
center.y = (0, _Length.calculateLengthFromValueWithUnit)(container, m[11], m[12]);
}
}
var gradientCenter = {
x: center.x === undefined ? bounds.width / 2 : center.x.getAbsoluteValue(bounds.width),
y: center.y === undefined ? bounds.height / 2 : center.y.getAbsoluteValue(bounds.height)
};
var gradientRadius = calculateRadius(m && m[2] || 'farthest-corner', shape, gradientCenter, radius, bounds);
return new RadialGradient(parseColorStops(args, m ? 1 : 0, Math.min(gradientRadius.x, gradientRadius.y)), shape, gradientCenter, gradientRadius);
};
var calculateGradientDirection = function calculateGradientDirection(radian, bounds) {
var width = bounds.width;
var height = bounds.height;
var HALF_WIDTH = width * 0.5;
var HALF_HEIGHT = height * 0.5;
var lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
var HALF_LINE_LENGTH = lineLength / 2;
var x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
var y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
var x1 = width - x0;
var y1 = height - y0;
return { x0: x0, x1: x1, y0: y0, y1: y1 };
};
var parseTopRight = function parseTopRight(bounds) {
return Math.acos(bounds.width / 2 / ((0, _Util.distance)(bounds.width, bounds.height) / 2));
};
var parseSideOrCorner = function parseSideOrCorner(side, bounds) {
switch (side) {
case 'bottom':
case 'to top':
return calculateGradientDirection(0, bounds);
case 'left':
case 'to right':
return calculateGradientDirection(Math.PI / 2, bounds);
case 'right':
case 'to left':
return calculateGradientDirection(3 * Math.PI / 2, bounds);
case 'top right':
case 'right top':
case 'to bottom left':
case 'to left bottom':
return calculateGradientDirection(Math.PI + parseTopRight(bounds), bounds);
case 'top left':
case 'left top':
case 'to bottom right':
case 'to right bottom':
return calculateGradientDirection(Math.PI - parseTopRight(bounds), bounds);
case 'bottom left':
case 'left bottom':
case 'to top right':
case 'to right top':
return calculateGradientDirection(parseTopRight(bounds), bounds);
case 'bottom right':
case 'right bottom':
case 'to top left':
case 'to left top':
return calculateGradientDirection(2 * Math.PI - parseTopRight(bounds), bounds);
case 'top':
case 'to bottom':
default:
return calculateGradientDirection(Math.PI, bounds);
}
};
var parsePercentageAngle = function parsePercentageAngle(angle, bounds) {
var _angle$split$map = angle.split(' ').map(parseFloat),
_angle$split$map2 = _slicedToArray(_angle$split$map, 2),
left = _angle$split$map2[0],
top = _angle$split$map2[1];
var ratio = left / 100 * bounds.width / (top / 100 * bounds.height);
return calculateGradientDirection(Math.atan(isNaN(ratio) ? 1 : ratio) + Math.PI / 2, bounds);
};
var findCorner = function findCorner(bounds, x, y, closest) {
var corners = [{ x: 0, y: 0 }, { x: 0, y: bounds.height }, { x: bounds.width, y: 0 }, { x: bounds.width, y: bounds.height }];
// $FlowFixMe
return corners.reduce(function (stat, corner) {
var d = (0, _Util.distance)(x - corner.x, y - corner.y);
if (closest ? d < stat.optimumDistance : d > stat.optimumDistance) {
return {
optimumCorner: corner,
optimumDistance: d
};
}
return stat;
}, {
optimumDistance: closest ? Infinity : -Infinity,
optimumCorner: null
}).optimumCorner;
};
var calculateRadius = function calculateRadius(extent, shape, center, radius, bounds) {
var x = center.x;
var y = center.y;
var rx = 0;
var ry = 0;
switch (extent) {
case 'closest-side':
// The ending shape is sized so that that it exactly meets the side of the gradient box closest to the gradient’s center.
// If the shape is an ellipse, it exactly meets the closest side in each dimension.
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
rx = ry = Math.min(Math.abs(x), Math.abs(x - bounds.width), Math.abs(y), Math.abs(y - bounds.height));
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
rx = Math.min(Math.abs(x), Math.abs(x - bounds.width));
ry = Math.min(Math.abs(y), Math.abs(y - bounds.height));
}
break;
case 'closest-corner':
// The ending shape is sized so that that it passes through the corner of the gradient box closest to the gradient’s center.
// If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified.
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
rx = ry = Math.min((0, _Util.distance)(x, y), (0, _Util.distance)(x, y - bounds.height), (0, _Util.distance)(x - bounds.width, y), (0, _Util.distance)(x - bounds.width, y - bounds.height));
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
// Compute the ratio ry/rx (which is to be the same as for "closest-side")
var c = Math.min(Math.abs(y), Math.abs(y - bounds.height)) / Math.min(Math.abs(x), Math.abs(x - bounds.width));
var corner = findCorner(bounds, x, y, true);
rx = (0, _Util.distance)(corner.x - x, (corner.y - y) / c);
ry = c * rx;
}
break;
case 'farthest-side':
// Same as closest-side, except the ending shape is sized based on the farthest side(s)
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
rx = ry = Math.max(Math.abs(x), Math.abs(x - bounds.width), Math.abs(y), Math.abs(y - bounds.height));
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
rx = Math.max(Math.abs(x), Math.abs(x - bounds.width));
ry = Math.max(Math.abs(y), Math.abs(y - bounds.height));
}
break;
case 'farthest-corner':
// Same as closest-corner, except the ending shape is sized based on the farthest corner.
// If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
rx = ry = Math.max((0, _Util.distance)(x, y), (0, _Util.distance)(x, y - bounds.height), (0, _Util.distance)(x - bounds.width, y), (0, _Util.distance)(x - bounds.width, y - bounds.height));
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
// Compute the ratio ry/rx (which is to be the same as for "farthest-side")
var _c = Math.max(Math.abs(y), Math.abs(y - bounds.height)) / Math.max(Math.abs(x), Math.abs(x - bounds.width));
var _corner = findCorner(bounds, x, y, false);
rx = (0, _Util.distance)(_corner.x - x, (_corner.y - y) / _c);
ry = _c * rx;
}
break;
default:
// pixel or percentage values
rx = radius.x || 0;
ry = radius.y !== undefined ? radius.y : rx;
break;
}
return {
x: rx,
y: ry
};
};
var transformWebkitRadialGradientArgs = exports.transformWebkitRadialGradientArgs = function transformWebkitRadialGradientArgs(args) {
var shape = '';
var radius = '';
var extent = '';
var position = '';
var idx = 0;
var POSITION = /^(left|center|right|\d+(?:px|r?em|%)?)(?:\s+(top|center|bottom|\d+(?:px|r?em|%)?))?$/i;
var SHAPE_AND_EXTENT = /^(circle|ellipse)?\s*(closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)?$/i;
var RADIUS = /^\d+(px|r?em|%)?(?:\s+\d+(px|r?em|%)?)?$/i;
var matchStartPosition = args[idx].match(POSITION);
if (matchStartPosition) {
idx++;
}
var matchShapeExtent = args[idx].match(SHAPE_AND_EXTENT);
if (matchShapeExtent) {
shape = matchShapeExtent[1] || '';
extent = matchShapeExtent[2] || '';
if (extent === 'contain') {
extent = 'closest-side';
} else if (extent === 'cover') {
extent = 'farthest-corner';
}
idx++;
}
var matchStartRadius = args[idx].match(RADIUS);
if (matchStartRadius) {
idx++;
}
var matchEndPosition = args[idx].match(POSITION);
if (matchEndPosition) {
idx++;
}
var matchEndRadius = args[idx].match(RADIUS);
if (matchEndRadius) {
idx++;
}
var matchPosition = matchEndPosition || matchStartPosition;
if (matchPosition && matchPosition[1]) {
position = matchPosition[1] + (/^\d+$/.test(matchPosition[1]) ? 'px' : '');
if (matchPosition[2]) {
position += ' ' + matchPosition[2] + (/^\d+$/.test(matchPosition[2]) ? 'px' : '');
}
}
var matchRadius = matchEndRadius || matchStartRadius;
if (matchRadius) {
radius = matchRadius[0];
if (!matchRadius[1]) {
radius += 'px';
}
}
if (position && !shape && !radius && !extent) {
radius = position;
position = '';
}
if (position) {
position = 'at ' + position;
}
return [[shape, extent, radius, position].filter(function (s) {
return !!s;
}).join(' ')].concat(args.slice(idx));
};
var transformObsoleteColorStops = function transformObsoleteColorStops(args) {
return args.map(function (color) {
return color.match(FROM_TO_COLORSTOP);
})
// $FlowFixMe
.map(function (v, index) {
if (!v) {
return args[index];
}
switch (v[1]) {
case 'from':
return v[4] + ' 0%';
case 'to':
return v[4] + ' 100%';
case 'color-stop':
if (v[3] === '%') {
return v[4] + ' ' + v[2];
}
return v[4] + ' ' + parseFloat(v[2]) * 100 + '%';
}
});
};