text: 'use strict';
  
  const PI = Math.PI;
  const RAD_PER_DEG = PI / 180;
  const DOUBLE_PI = PI * 2;
  const HALF_PI = PI / 2;
  const QUARTER_PI = PI / 4;
  const TWO_THIRDS_PI = PI * 2 / 3;
  
  /**
   * @namespace Chart.helpers.canvas
   */
  module.exports = {
  	/**
  	 * Returns the aligned pixel value to avoid anti-aliasing blur
  	 * @param {Chart} chart - The chart instance.
  	 * @param {number} pixel - A pixel value.
  	 * @param {number} width - The width of the element.
  	 * @returns {number} The aligned pixel value.
  	 * @private
  	 */
  	_alignPixel: function(chart, pixel, width) {
  		const devicePixelRatio = chart.currentDevicePixelRatio;
  		const halfWidth = width / 2;
  		return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
  	},
  
  	/**
  	 * Clears the entire canvas associated to the given `chart`.
  	 * @param {Chart} chart - The chart for which to clear the canvas.
  	 */
  	clear: function(chart) {
  		chart.ctx.clearRect(0, 0, chart.width, chart.height);
  	},
  
  	/**
  	 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
  	 * given size (width, height) and the same `radius` for all corners.
  	 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
  	 * @param {number} x - The x axis of the coordinate for the rectangle starting point.
  	 * @param {number} y - The y axis of the coordinate for the rectangle starting point.
  	 * @param {number} width - The rectangle's width.
  	 * @param {number} height - The rectangle's height.
  	 * @param {number} radius - The rounded amount (in pixels) for the four corners.
  	 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
  	 */
  	roundedRect: function(ctx, x, y, width, height, radius) {
  		if (radius) {
  			var r = Math.min(radius, height / 2, width / 2);
  			var left = x + r;
  			var top = y + r;
  			var right = x + width - r;
  			var bottom = y + height - r;
  
  			ctx.moveTo(x, top);
  			if (left < right && top < bottom) {
  				ctx.arc(left, top, r, -PI, -HALF_PI);
  				ctx.arc(right, top, r, -HALF_PI, 0);
  				ctx.arc(right, bottom, r, 0, HALF_PI);
  				ctx.arc(left, bottom, r, HALF_PI, PI);
  			} else if (left < right) {
  				ctx.moveTo(left, y);
  				ctx.arc(right, top, r, -HALF_PI, HALF_PI);
  				ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);
  			} else if (top < bottom) {
  				ctx.arc(left, top, r, -PI, 0);
  				ctx.arc(left, bottom, r, 0, PI);
  			} else {
  				ctx.arc(left, top, r, -PI, PI);
  			}
  			ctx.closePath();
  			ctx.moveTo(x, y);
  		} else {
  			ctx.rect(x, y, width, height);
  		}
  	},
  
  	drawPoint: function(ctx, style, radius, x, y, rotation) {
  		var type, xOffset, yOffset, size, cornerRadius;
  		var rad = (rotation || 0) * RAD_PER_DEG;
  
  		if (style && typeof style === 'object') {
  			type = style.toString();
  			if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
  				ctx.save();
  				ctx.translate(x, y);
  				ctx.rotate(rad);
  				ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
  				ctx.restore();
  				return;
  			}
  		}
  
  		if (isNaN(radius) || radius <= 0) {
  			return;
  		}
  
  		ctx.beginPath();
  
  		switch (style) {
  		// Default includes circle
  		default:
  			ctx.arc(x, y, radius, 0, DOUBLE_PI);
  			ctx.closePath();
  			break;
  		case 'triangle':
  			ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  			rad += TWO_THIRDS_PI;
  			ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  			rad += TWO_THIRDS_PI;
  			ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  			ctx.closePath();
  			break;
  		case 'rectRounded':
  			// NOTE: the rounded rect implementation changed to use `arc` instead of
  			// `quadraticCurveTo` since it generates better results when rect is
  			// almost a circle. 0.516 (instead of 0.5) produces results with visually
  			// closer proportion to the previous impl and it is inscribed in the
  			// circle with `radius`. For more details, see the following PRs:
  			// https://github.com/chartjs/Chart.js/issues/5597
  			// https://github.com/chartjs/Chart.js/issues/5858
  			cornerRadius = radius * 0.516;
  			size = radius - cornerRadius;
  			xOffset = Math.cos(rad + QUARTER_PI) * size;
  			yOffset = Math.sin(rad + QUARTER_PI) * size;
  			ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
  			ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
  			ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
  			ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
  			ctx.closePath();
  			break;
  		case 'rect':
  			if (!rotation) {
  				size = Math.SQRT1_2 * radius;
  				ctx.rect(x - size, y - size, 2 * size, 2 * size);
  				break;
  			}
  			rad += QUARTER_PI;
  			/* falls through */
  		case 'rectRot':
  			xOffset = Math.cos(rad) * radius;
  			yOffset = Math.sin(rad) * radius;
  			ctx.moveTo(x - xOffset, y - yOffset);
  			ctx.lineTo(x + yOffset, y - xOffset);
  			ctx.lineTo(x + xOffset, y + yOffset);
  			ctx.lineTo(x - yOffset, y + xOffset);
  			ctx.closePath();
  			break;
  		case 'crossRot':
  			rad += QUARTER_PI;
  			/* falls through */
  		case 'cross':
  			xOffset = Math.cos(rad) * radius;
  			yOffset = Math.sin(rad) * radius;
  			ctx.moveTo(x - xOffset, y - yOffset);
  			ctx.lineTo(x + xOffset, y + yOffset);
  			ctx.moveTo(x + yOffset, y - xOffset);
  			ctx.lineTo(x - yOffset, y + xOffset);
  			break;
  		case 'star':
  			xOffset = Math.cos(rad) * radius;
  			yOffset = Math.sin(rad) * radius;
  			ctx.moveTo(x - xOffset, y - yOffset);
  			ctx.lineTo(x + xOffset, y + yOffset);
  			ctx.moveTo(x + yOffset, y - xOffset);
  			ctx.lineTo(x - yOffset, y + xOffset);
  			rad += QUARTER_PI;
  			xOffset = Math.cos(rad) * radius;
  			yOffset = Math.sin(rad) * radius;
  			ctx.moveTo(x - xOffset, y - yOffset);
  			ctx.lineTo(x + xOffset, y + yOffset);
  			ctx.moveTo(x + yOffset, y - xOffset);
  			ctx.lineTo(x - yOffset, y + xOffset);
  			break;
  		case 'line':
  			xOffset = Math.cos(rad) * radius;
  			yOffset = Math.sin(rad) * radius;
  			ctx.moveTo(x - xOffset, y - yOffset);
  			ctx.lineTo(x + xOffset, y + yOffset);
  			break;
  		case 'dash':
  			ctx.moveTo(x, y);
  			ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
  			break;
  		}
  
  		ctx.fill();
  		ctx.stroke();
  	},
  
  	/**
  	 * Returns true if the point is inside the rectangle
  	 * @param {object} point - The point to test
  	 * @param {object} area - The rectangle
  	 * @returns {boolean}
  	 * @private
  	 */
  	_isPointInArea: function(point, area) {
  		var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
  
  		return point.x > area.left - epsilon && point.x < area.right + epsilon &&
  			point.y > area.top - epsilon && point.y < area.bottom + epsilon;
  	},
  
  	clipArea: function(ctx, area) {
  		ctx.save();
  		ctx.beginPath();
  		ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
  		ctx.clip();
  	},
  
  	unclipArea: function(ctx) {
  		ctx.restore();
  	},
  
  	lineTo: function(ctx, previous, target, flip) {
  		var stepped = target.steppedLine;
  		if (stepped) {
  			if (stepped === 'middle') {
  				var midpoint = (previous.x + target.x) / 2.0;
  				ctx.lineTo(midpoint, flip ? target.y : previous.y);
  				ctx.lineTo(midpoint, flip ? previous.y : target.y);
  			} else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) {
  				ctx.lineTo(previous.x, target.y);
  			} else {
  				ctx.lineTo(target.x, previous.y);
  			}
  			ctx.lineTo(target.x, target.y);
  			return;
  		}
  
  		if (!target.tension) {
  			ctx.lineTo(target.x, target.y);
  			return;
  		}
  
  		ctx.bezierCurveTo(
  			flip ? previous.controlPointPreviousX : previous.controlPointNextX,
  			flip ? previous.controlPointPreviousY : previous.controlPointNextY,
  			flip ? target.controlPointNextX : target.controlPointPreviousX,
  			flip ? target.controlPointNextY : target.controlPointPreviousY,
  			target.x,
  			target.y);
  	}
  };
  