(function(global) { "use strict"; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, coordProps = { 'x1': 1, 'x2': 1, 'y1': 1, 'y2': 1 }, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Line) { fabric.warn('fabric.Line is already defined'); return; } /** * Line class * @class fabric.Line * @extends fabric.Object * @see {@link fabric.Line#initialize} for constructor definition */ fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { /** * Type of an object * @type String * @default */ type: 'line', /** * x value or first line edge * @type Number * @default */ x1: 0, /** * y value or first line edge * @type Number * @default */ y1: 0, /** * x value or second line edge * @type Number * @default */ x2: 0, /** * y value or second line edge * @type Number * @default */ y2: 0, /** * Constructor * @param {Array} [points] Array of points * @param {Object} [options] Options object * @return {fabric.Line} thisArg */ initialize: function(points, options) { options = options || { }; if (!points) { points = [0, 0, 0, 0]; } this.callSuper('initialize', options); this.set('x1', points[0]); this.set('y1', points[1]); this.set('x2', points[2]); this.set('y2', points[3]); this._setWidthHeight(options); }, /** * @private * @param {Object} [options] Options */ _setWidthHeight: function(options) { options || (options = { }); this.width = Math.abs(this.x2 - this.x1) || 1; this.height = Math.abs(this.y2 - this.y1) || 1; this.left = 'left' in options ? options.left : this._getLeftToOriginX(); this.top = 'top' in options ? options.top : this._getTopToOriginY(); }, /** * @private * @param {String} key * @param {Any} value */ _set: function(key, value) { this[key] = value; if (typeof coordProps[key] !== 'undefined') { this._setWidthHeight(); } return this; }, /** * @private * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. */ _getLeftToOriginX: makeEdgeToOriginGetter( { // property names origin: 'originX', axis1: 'x1', axis2: 'x2', dimension: 'width', }, { // possible values of origin nearest: 'left', center: 'center', farthest: 'right', } ), /** * @private * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. */ _getTopToOriginY: makeEdgeToOriginGetter( { // property names origin: 'originY', axis1: 'y1', axis2: 'y2', dimension: 'height', }, { // possible values of origin nearest: 'top', center: 'center', farthest: 'bottom', } ), /** * @private * @return {Number} centerToCenterX Distance from center of path group to horizontal center of Line. */ _getCenterToCenterX: makeCenterToCenterGetter( { // property names origin: 'originX', coordinate: 'left', dimension: 'width', }, { // possible non-center values of origin nearest: 'left', farthest: 'right', } ), /** * @private * @return {Number} centerToOriginY Distance from center of path group to vertical center of Line. */ _getCenterToCenterY: makeCenterToCenterGetter( { // property names origin: 'originY', coordinate: 'top', dimension: 'height', }, { // possible non-center values of origin nearest: 'top', farthest: 'bottom', } ), /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { ctx.beginPath(); var isInPathGroup = this.group && this.group.type === 'path-group'; if (isInPathGroup && !this.transformMatrix) { ctx.translate( this._getCenterToCenterX(), this._getCenterToCenterY() ); } if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { // move from center (of virtual box) to its left/top corner // we can't assume x1, y1 is top left and x2, y2 is bottom right var xMult = this.x1 <= this.x2 ? -1 : 1; var yMult = this.y1 <= this.y2 ? -1 : 1; ctx.moveTo( this.width === 1 ? 0 : (xMult * this.width / 2), this.height === 1 ? 0 : (yMult * this.height / 2)); ctx.lineTo( this.width === 1 ? 0 : (xMult * -1 * this.width / 2), this.height === 1 ? 0 : (yMult * -1 * this.height / 2)); } ctx.lineWidth = this.strokeWidth; // TODO: test this // make sure setting "fill" changes color of a line // (by copying fillStyle to strokeStyle, since line is stroked, not filled) var origStrokeStyle = ctx.strokeStyle; ctx.strokeStyle = this.stroke || ctx.fillStyle; this.stroke && this._renderStroke(ctx); ctx.strokeStyle = origStrokeStyle; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x = this.width === 1 ? 0 : xMult * this.width / 2, y = this.height === 1 ? 0 : yMult * this.height / 2; ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); ctx.closePath(); }, /** * Returns object representation of an instance * @methd toObject * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { x1: this.get('x1'), y1: this.get('y1'), x2: this.get('x2'), y2: this.get('y2') }); }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(); markup.push( '' ); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) * @static * @memberOf fabric.Line * @see http://www.w3.org/TR/SVG/shapes.html#LineElement */ fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); /** * Returns fabric.Line instance from an SVG element * @static * @memberOf fabric.Line * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Line} instance of fabric.Line */ fabric.Line.fromElement = function(element, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES); var points = [ parsedAttributes.x1 || 0, parsedAttributes.y1 || 0, parsedAttributes.x2 || 0, parsedAttributes.y2 || 0 ]; return new fabric.Line(points, extend(parsedAttributes, options)); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Line instance from an object representation * @static * @memberOf fabric.Line * @param {Object} object Object to create an instance from * @return {fabric.Line} instance of fabric.Line */ fabric.Line.fromObject = function(object) { var points = [object.x1, object.y1, object.x2, object.y2]; return new fabric.Line(points, object); }; /** * Produces a function that calculates distance from canvas edge to Line origin. */ function makeEdgeToOriginGetter(propertyNames, originValues) { var origin = propertyNames.origin, axis1 = propertyNames.axis1, axis2 = propertyNames.axis2, dimension = propertyNames.dimension, nearest = originValues.nearest, center = originValues.center, farthest = originValues.farthest; return function() { switch (this.get(origin)) { case nearest: return Math.min(this.get(axis1), this.get(axis2)); case center: return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); case farthest: return Math.max(this.get(axis1), this.get(axis2)); } }; } /** * Produces a function that calculates distance from path group center to center of Line dimension. * */ function makeCenterToCenterGetter(propertyNames, originValues) { var origin = propertyNames.origin, coordinate = propertyNames.coordinate, dimension = propertyNames.dimension, nearest = originValues.nearest, farthest = originValues.farthest; return function() { /* * Line coords are distances from left-top of canvas to origin of line. * * To render line in a path-group, we need to translate them to distances * from center of path-group to center of line. */ var toPathGroupEdge = (-0.5 * this.group.get(dimension)) var toLineCenter = 0; // assume center if (this.get(origin) === nearest) { toLineCenter = +0.5 * this.get(dimension); } else if (this.get(origin) === farthest) { toLineCenter = -0.5 * this.get(dimension); } // else center, don't change the initial value return toPathGroupEdge + this.get(coordinate) + toLineCenter; }; } })(typeof exports !== 'undefined' ? exports : this);