UNPKG

html2canvas

Version:
447 lines (376 loc) 18.5 kB
'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 + '%'; } }); };