/* * Paper.js * * This file is part of Paper.js, a JavaScript Vector Graphics Library, * based on Scriptographer.org and designed to be largely API compatible. * http://paperjs.org/ * http://scriptographer.org/ * * Distributed under the MIT license. See LICENSE file for details. * * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey * http://lehni.org/ & http://jonathanpuckey.com/ * * All rights reserved. */ var Segment = this.Segment = Base.extend({ /** @lends Segment# */ beans: true, /** * Creates a new Segment object. * * @example * var handleIn = new Point(-40, -50); * var handleOut = new Point(40, 50); * * var firstPoint = new Point(100, 50); * var firstSegment = new Segment(firstPoint, null, handleOut); * * var secondPoint = new Point(200, 50); * var secondSegment = new Segment(secondPoint, handleIn, null); * * var path = new Path(); * path.segments = [firstSegment, secondSegment]; * * @param {Point} [point={x: 0, y: 0}] the anchor point of the segment * @param {Point} [handleIn={x: 0, y: 0}] the handle point relative to the * anchor point of the segment that describes the in tangent of the * segment. * @param {Point} [handleOut={x: 0, y: 0}] the handle point relative to the * anchor point of the segment that describes the out tangent of the * segment. * @name Segment * @constructor * * @class The Segment object represents a part of a path which is * described by the {@link Path#segments} array. Every segment of a * path corresponds to an anchor point (anchor points are the path handles * that are visible when the path is selected). */ initialize: function(arg0, arg1, arg2, arg3, arg4, arg5) { var count = arguments.length, createPoint = SegmentPoint.create, point, handleIn, handleOut; if (count == 0) { // Nothing } else if (count == 1) { // TODO: If beans are not activated, this won't copy from existing // segments. OK? if (arg0.point) { point = arg0.point; handleIn = arg0.handleIn; handleOut = arg0.handleOut; } else { point = arg0; } } else if (count < 6) { if (count == 2 && !arg1.x) { point = [ arg0, arg1 ]; } else { point = arg0; // Doesn't matter if these arguments exist, SegmentPointcreate // produces creates points with (0, 0) otherwise handleIn = arg1; handleOut = arg2; } } else if (count == 6) { point = [ arg0, arg1 ]; handleIn = [ arg2, arg3 ]; handleOut = [ arg4, arg5 ]; } createPoint(this, '_point', point); createPoint(this, '_handleIn', handleIn); createPoint(this, '_handleOut', handleOut); }, _changed: function(point) { if (!this._path) return; // Delegate changes to affected curves if they exist var curve = this._path._curves && this.getCurve(), other; if (curve) { curve._changed(); // Get the other affected curve, which is the previous one for // _point or _handleIn changing when this segment is _segment1 of // the curve, for all other cases it's the next (e.g. _handleOut // when this segment is _segment2) if (other = (curve[point == this._point || point == this._handleIn && curve._segment1 == this ? 'getPrevious' : 'getNext']())) { other._changed(); } } this._path._changed(ChangeFlags.GEOMETRY); }, /** * The anchor point of the segment. * * @type Point * @bean */ getPoint: function() { return this._point; }, setPoint: function(point) { point = Point.read(arguments); // Do not replace the internal object but update it instead, so // references to it are kept alive. this._point.set(point.x, point.y); }, /** * The handle point relative to the anchor point of the segment that * describes the in tangent of the segment. * * @type Point * @bean */ getHandleIn: function() { return this._handleIn; }, setHandleIn: function(point) { point = Point.read(arguments); // See #setPoint: this._handleIn.set(point.x, point.y); // Update corner accordingly // this.corner = !this._handleIn.isColinear(this._handleOut); }, getHandleInIfSet: function() { return this._handleIn._x == 0 && this._handleIn._y == 0 ? null : this._handleIn; }, /** * The handle point relative to the anchor point of the segment that * describes the out tangent of the segment. * * @type Point * @bean */ getHandleOut: function() { return this._handleOut; }, setHandleOut: function(point) { point = Point.read(arguments); // See #setPoint: this._handleOut.set(point.x, point.y); // Update corner accordingly // this.corner = !this._handleIn.isColinear(this._handleOut); }, getHandleOutIfSet: function() { return this._handleOut._x == 0 && this._handleOut._y == 0 ? null : this._handleOut; }, /** * {@grouptitle Hierarchy} * * The index of the segment in the {@link Path#segments} array that the * segment belongs to. * * @type number * @bean */ getIndex: function() { return this._index !== undefined ? this._index : null; }, /** * The path that the segment belongs to. * * @type Path * @bean */ getPath: function() { return this._path || null; }, /** * The curve that the segment belongs to. * * @type Curve * @bean */ getCurve: function() { if (this._path) { var index = this._index; // The last segment of an open path belongs to the last curve if (!this._path._closed && index == this._path._segments.length - 1) index--; return this._path.getCurves()[index] || null; } return null; }, /** * {@grouptitle Sibling Segments} * * The next segment in the {@link Path#segments} array that the segment * belongs to. If the segments belongs to a closed path, the first segment * is returned for the last segment of the path. * * @type Segment * @bean */ getNext: function() { var segments = this._path && this._path._segments; return segments && (segments[this._index + 1] || this._path._closed && segments[0]) || null; }, /** * The previous segment in the {@link Path#segments} array that the * segment belongs to. If the segments belongs to a closed path, the last * segment is returned for the first segment of the path. * * @type Segment * @bean */ getPrevious: function() { var segments = this._path && this._path._segments; return segments && (segments[this._index - 1] || this._path._closed && segments[segments.length - 1]) || null; }, _isSelected: function(point) { var state = this._selectionState; return point == this._point ? !!(state & SelectionState.POINT) : point == this._handleIn ? !!(state & SelectionState.HANDLE_IN) : point == this._handleOut ? !!(state & SelectionState.HANDLE_OUT) : false; }, _setSelected: function(point, selected) { var path = this._path, selected = !!selected, // convert to boolean state = this._selectionState, wasSelected = !!state, // For performance reasons use array indices to access the various // selection states: 0 = point, 1 = handleIn, 2 = handleOut selection = [ !!(state & SelectionState.POINT), !!(state & SelectionState.HANDLE_IN), !!(state & SelectionState.HANDLE_OUT) ]; if (point == this._point) { if (selected) { // We're selecting point, deselect the handles selection[1] = selection[2] = false; } else { var previous = this.getPrevious(), next = this.getNext(); // When deselecting a point, the handles get selected instead // depending on the selection state of their neighbors. selection[1] = previous && (previous._point.isSelected() || previous._handleOut.isSelected()); selection[2] = next && (next._point.isSelected() || next._handleIn.isSelected()); } selection[0] = selected; } else { var index = point == this._handleIn ? 1 : 2; if (selection[index] != selected) { // When selecting handles, the point get deselected. if (selected) selection[0] = false; selection[index] = selected; } } this._selectionState = (selection[0] ? SelectionState.POINT : 0) | (selection[1] ? SelectionState.HANDLE_IN : 0) | (selection[2] ? SelectionState.HANDLE_OUT : 0); // If the selection state of the segment has changed, we need to let // it's path know and possibly add or remove it from // project._selectedItems if (path && wasSelected != !!this._selectionState) path._updateSelection(this); }, // DOCS: Segment#selected isSelected: function() { return this._isSelected(this._point); }, setSelected: function(selected) { this._setSelected(this._point, selected); }, /** * Returns the reversed the segment, without modifying the segment itself. * @return {Segment} the reversed segment */ reverse: function() { return new Segment(this._point, this._handleOut, this._handleIn); }, /** * Removes the segment from the path that it belongs to. */ remove: function() { return this._path ? !!this._path.removeSegment(this._index) : false; }, toString: function() { var parts = [ 'point: ' + this._point ]; if (!this._handleIn.isZero()) parts.push('handleIn: ' + this._handleIn); if (!this._handleOut.isZero()) parts.push('handleOut: ' + this._handleOut); return '{ ' + parts.join(', ') + ' }'; }, _transformCoordinates: function(matrix, coords, change) { // Use matrix.transform version() that takes arrays of multiple // points for largely improved performance, as no calls to // Point.read() and Point constructors are necessary. var point = this._point, // If a matrix is defined, only transform handles if they are set. // This saves some computation time. If no matrix is set, always // use the real handles, as we just want to receive a filled // coords array for getBounds(). handleIn = matrix && this.getHandleInIfSet() || this._handleIn, handleOut = matrix && this.getHandleOutIfSet() || this._handleOut, x = point._x, y = point._y, i = 2; coords[0] = x; coords[1] = y; // We need to convert handles to absolute coordinates in order // to transform them. if (handleIn) { coords[i++] = handleIn._x + x; coords[i++] = handleIn._y + y; } if (handleOut) { coords[i++] = handleOut._x + x; coords[i++] = handleOut._y + y; } if (matrix) { matrix._transformCoordinates(coords, 0, coords, 0, i / 2); x = coords[0]; y = coords[1]; if (change) { // If change is true, we need to set the new values back point._x = x; point._y = y; i = 2; if (handleIn) { handleIn._x = coords[i++] - x; handleIn._y = coords[i++] - y; } if (handleOut) { handleOut._x = coords[i++] - x; handleOut._y = coords[i++] - y; } } else { // We want to receive the results in coords, so make sure // handleIn and out are defined too, even if they're 0 if (!handleIn) { coords[i++] = x; coords[i++] = y; } if (!handleOut) { coords[i++] = x; coords[i++] = y; } } } } });