/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name Path
 *
 * @class The path item represents a path in a Paper.js project.
 *
 * @extends PathItem
 */
// DOCS: Explain that path matrix is always applied with each transformation.
var Path = PathItem.extend(/** @lends Path# */{
    _class: 'Path',
    _serializeFields: {
        segments: [],
        closed: false
    },

    /**
     * Creates a new path item and places it at the top of the active layer.
     *
     * @name Path#initialize
     * @param {Segment[]} [segments] An array of segments (or points to be
     * converted to segments) that will be added to the path
     * @return {Path} the newly created path
     *
     * @example
     * // Create an empty path and add segments to it:
     * var path = new Path();
     * path.strokeColor = 'black';
     * path.add(new Point(30, 30));
     * path.add(new Point(100, 100));
     *
     * @example
     * // Create a path with two segments:
     * var segments = [new Point(30, 30), new Point(100, 100)];
     * var path = new Path(segments);
     * path.strokeColor = 'black';
     */
    /**
     * Creates a new path item from an object description and places it at the
     * top of the active layer.
     *
     * @name Path#initialize
     * @param {Object} object an object literal containing properties to
     * be set on the path
     * @return {Path} the newly created path
     *
     * @example {@paperscript}
     * var path = new Path({
     *     segments: [[20, 20], [80, 80], [140, 20]],
     *     fillColor: 'black',
     *     closed: true
     * });
     *
     * @example {@paperscript}
     * var path = new Path({
     *     segments: [[20, 20], [80, 80], [140, 20]],
     *     strokeColor: 'red',
     *     strokeWidth: 20,
     *     strokeCap: 'round',
     *     selected: true
     * });
     */
    /**
     * Creates a new path item from SVG path-data and places it at the top of
     * the active layer.
     *
     * @name Path#initialize
     * @param {String} pathData the SVG path-data that describes the geometry
     * of this path.
     * @return {Path} the newly created path
     *
     * @example {@paperscript}
     * var pathData = 'M100,50c0,27.614-22.386,50-50,50S0,77.614,0,50S22.386,0,50,0S100,22.386,100,50';
     * var path = new Path(pathData);
     * path.fillColor = 'red';
     */
    initialize: function Path(arg) {
        this._closed = false;
        this._segments = [];
        // arg can either be an object literal containing properties to be set
        // on the path, a list of segments to be set, or the first of multiple
        // arguments describing separate segments.
        // If it is an array, it can also be a description of a point, so
        // check its first entry for object as well.
        // But first see if segments are directly passed at all. If not, try
        // _set(arg).
        var segments = Array.isArray(arg)
            ? typeof arg[0] === 'object'
                ? arg
                : arguments
            // See if it behaves like a segment or a point, but filter out
            // rectangles, as accepted by some Path.Constructor constructors.
            : arg && (arg.size === undefined && (arg.x !== undefined
                    || arg.point !== undefined))
                ? arguments
                : null;
        // Always call setSegments() to initialize a few related variables.
        if (segments && segments.length > 0) {
            // This sets _curves and _selectedSegmentState too!
            this.setSegments(segments);
        } else {
            this._curves = undefined; // For hidden class optimization
            this._selectedSegmentState = 0;
            if (!segments && typeof arg === 'string') {
                this.setPathData(arg);
                // Erase for _initialize() call below.
                arg = null;
            }
        }
        // Only pass on arg as props if it wasn't consumed for segments already.
        this._initialize(!segments && arg);
    },

    _equals: function(item) {
        return this._closed === item._closed
                && Base.equals(this._segments, item._segments);
    },

    clone: function(insert) {
        var copy = new Path(Item.NO_INSERT);
        copy.setSegments(this._segments);
        copy._closed = this._closed;
        if (this._clockwise !== undefined)
            copy._clockwise = this._clockwise;
        return this._clone(copy, insert);
    },

    _changed: function _changed(flags) {
        _changed.base.call(this, flags);
        if (flags & /*#=*/ChangeFlag.GEOMETRY) {
            // The _currentPath is already cleared in Item, but clear it on the
            // parent too, for children of CompoundPaths, and Groups (ab)used as
            // clipping paths.
            var parent = this._parent;
            if (parent)
                parent._currentPath = undefined;
            // Clockwise state becomes undefined as soon as geometry changes.
            this._length = this._clockwise = undefined;
            // Only notify all curves if we're not told that only one Segment
            // has changed and took already care of notifications.
            if (this._curves && !(flags & /*#=*/ChangeFlag.SEGMENTS)) {
                for (var i = 0, l = this._curves.length; i < l; i++)
                    this._curves[i]._changed();
            }
            // Clear cached curves used for winding direction and containment
            // calculation.
            // NOTE: This is only needed with __options.booleanOperations
            this._monoCurves = undefined;
        } else if (flags & /*#=*/ChangeFlag.STROKE) {
            // TODO: We could preserve the purely geometric bounds that are not
            // affected by stroke: _bounds.bounds and _bounds.handleBounds
            this._bounds = undefined;
        }
    },

    getStyle: function() {
        // If this path is part of a compound-path, return the parent's style.
        var parent = this._parent;
        return (parent instanceof CompoundPath ? parent : this)._style;
    },

    /**
     * The segments contained within the path.
     *
     * @type Segment[]
     * @bean
     */
    getSegments: function() {
        return this._segments;
    },

    setSegments: function(segments) {
        var fullySelected = this.isFullySelected();
        this._segments.length = 0;
        this._selectedSegmentState = 0;
        // Calculate new curves next time we call getCurves()
        this._curves = undefined;
        if (segments && segments.length > 0)
            this._add(Segment.readAll(segments));
        // Preserve fullySelected state.
        // TODO: Do we still need this?
        if (fullySelected)
            this.setFullySelected(true);
    },

    /**
     * The first Segment contained within the path.
     *
     * @type Segment
     * @bean
     */
    getFirstSegment: function() {
        return this._segments[0];
    },

    /**
     * The last Segment contained within the path.
     *
     * @type Segment
     * @bean
     */
    getLastSegment: function() {
        return this._segments[this._segments.length - 1];
    },

    /**
     * The curves contained within the path.
     *
     * @type Curve[]
     * @bean
     */
    getCurves: function() {
        var curves = this._curves,
            segments = this._segments;
        if (!curves) {
            var length = this._countCurves();
            curves = this._curves = new Array(length);
            for (var i = 0; i < length; i++)
                curves[i] = new Curve(this, segments[i],
                    // Use first segment for segment2 of closing curve
                    segments[i + 1] || segments[0]);
        }
        return curves;
    },

    /**
     * The first Curve contained within the path.
     *
     * @type Curve
     * @bean
     */
    getFirstCurve: function() {
        return this.getCurves()[0];
    },

    /**
     * The last Curve contained within the path.
     *
     * @type Curve
     * @bean
     */
    getLastCurve: function() {
        var curves = this.getCurves();
        return curves[curves.length - 1];
    },

    /**
     * Specifies whether the path is closed. If it is closed, Paper.js connects
     * the first and last segments.
     *
     * @type Boolean
     * @bean
     *
     * @example {@paperscript}
     * var myPath = new Path();
     * myPath.strokeColor = 'black';
     * myPath.add(new Point(50, 75));
     * myPath.add(new Point(100, 25));
     * myPath.add(new Point(150, 75));
     *
     * // Close the path:
     * myPath.closed = true;
     */
    isClosed: function() {
        return this._closed;
    },

    setClosed: function(closed) {
        // On-the-fly conversion to boolean:
        if (this._closed != (closed = !!closed)) {
            this._closed = closed;
            // Update _curves length
            if (this._curves) {
                var length = this._curves.length = this._countCurves();
                // If we were closing this path, we need to add a new curve now
                if (closed)
                    this._curves[length - 1] = new Curve(this,
                        this._segments[length - 1], this._segments[0]);
            }
            // Use SEGMENTS notification instead of GEOMETRY since curves are
            // up-to-date and don't need notification.
            this._changed(/*#=*/Change.SEGMENTS);
        }
    }
}, /** @lends Path# */{
    // Enforce bean creation for getPathData(), as it has hidden parameters.
    beans: true,

    getPathData: function(_matrix, _precision) {
        // NOTE: #setPathData() is defined in PathItem.
        var segments = this._segments,
            length = segments.length,
            f = new Formatter(_precision),
            coords = new Array(6),
            first = true,
            curX, curY,
            prevX, prevY,
            inX, inY,
            outX, outY,
            parts = [];

        function addSegment(segment, skipLine) {
            segment._transformCoordinates(_matrix, coords, false);
            curX = coords[0];
            curY = coords[1];
            if (first) {
                parts.push('M' + f.pair(curX, curY));
                first = false;
            } else {
                inX = coords[2];
                inY = coords[3];
                // TODO: Add support for H/V and/or relative commands, where
                // appropriate and resulting in shorter strings.
                if (inX === curX && inY === curY
                        && outX === prevX && outY === prevY) {
                    // l = relative lineto:
                    if (!skipLine)
                        parts.push('l' + f.pair(curX - prevX, curY - prevY));
                } else {
                    // c = relative curveto:
                    parts.push('c' + f.pair(outX - prevX, outY - prevY)
                            + ' ' + f.pair(inX - prevX, inY - prevY)
                            + ' ' + f.pair(curX - prevX, curY - prevY));
                }
            }
            prevX = curX;
            prevY = curY;
            outX = coords[4];
            outY = coords[5];
        }

        if (length === 0)
            return '';

        for (var i = 0; i < length; i++)
            addSegment(segments[i]);
        // Close path by drawing first segment again
        if (this._closed && length > 0) {
            addSegment(segments[0], true);
            parts.push('z');
        }
        return parts.join('');
    }
}, /** @lends Path# */{

    // TODO: Consider adding getSubPath(a, b), returning a part of the current
    // path, with the added benefit that b can be < a, and closed looping is
    // taken into account.

    isEmpty: function() {
        return this._segments.length === 0;
    },

    isPolygon: function() {
        for (var i = 0, l = this._segments.length; i < l; i++) {
            if (!this._segments[i].isLinear())
                return false;
        }
        return true;
    },

    _transformContent: function(matrix) {
        var coords = new Array(6);
        for (var i = 0, l = this._segments.length; i < l; i++)
            this._segments[i]._transformCoordinates(matrix, coords, true);
        return true;
    },

    /**
     * Private method that adds a segment to the segment list. It assumes that
     * the passed object is a segment already and does not perform any checks.
     * If a curves list was requested, it will kept in sync with the segments
     * list automatically.
     */
    _add: function(segs, index) {
        // Local short-cuts:
        var segments = this._segments,
            curves = this._curves,
            amount = segs.length,
            append = index == null,
            index = append ? segments.length : index;
        // Scan through segments to add first, convert if necessary and set
        // _path and _index references on them.
        for (var i = 0; i < amount; i++) {
            var segment = segs[i];
            // If the segments belong to another path already, clone them before
            // adding:
            if (segment._path)
                segment = segs[i] = segment.clone();
            segment._path = this;
            segment._index = index + i;
            // If parts of this segment are selected, adjust the internal
            // _selectedSegmentState now
            if (segment._selectionState)
                this._updateSelection(segment, 0, segment._selectionState);
        }
        if (append) {
            // Append them all at the end by using push
            segments.push.apply(segments, segs);
        } else {
            // Insert somewhere else
            segments.splice.apply(segments, [index, 0].concat(segs));
            // Adjust the indices of the segments above.
            for (var i = index + amount, l = segments.length; i < l; i++)
                segments[i]._index = i;
        }
        // Keep the curves list in sync all the time in case it was requested
        // already.
        if (curves || segs._curves) {
            if (!curves)
                curves = this._curves = [];
            // We need to step one index down from the inserted segment to
            // get its curve, except for the first segment.
            var from = index > 0 ? index - 1 : index,
                start = from,
                to = Math.min(from + amount, this._countCurves());
            if (segs._curves) {
                // Reuse removed curves.
                curves.splice.apply(curves, [from, 0].concat(segs._curves));
                start += segs._curves.length;
            }
            // Insert new curves, but do not initialize their segments yet,
            // since #_adjustCurves() handles all that for us.
            for (var i = start; i < to; i++)
                curves.splice(i, 0, new Curve(this, null, null));
            // Adjust segments for the curves before and after the removed ones
            this._adjustCurves(from, to);
        }
        // Use SEGMENTS notification instead of GEOMETRY since curves are kept
        // up-to-date by _adjustCurves() and don't need notification.
        this._changed(/*#=*/Change.SEGMENTS);
        return segs;
    },

    /**
     * Adjusts segments of curves before and after inserted / removed segments.
     */
    _adjustCurves: function(from, to) {
        var segments = this._segments,
            curves = this._curves,
            curve;
        for (var i = from; i < to; i++) {
            curve = curves[i];
            curve._path = this;
            curve._segment1 = segments[i];
            curve._segment2 = segments[i + 1] || segments[0];
            curve._changed();
        }
        // If it's the first segment, correct the last segment of closed
        // paths too:
        if (curve = curves[this._closed && from === 0 ? segments.length - 1
                : from - 1]) {
            curve._segment2 = segments[from] || segments[0];
            curve._changed();
        }
        // Fix the segment after the modified range, if it exists
        if (curve = curves[to]) {
            curve._segment1 = segments[to];
            curve._changed();
        }
    },

    /**
     * Returns the amount of curves this path item is supposed to have, based
     * on its amount of #segments and #closed state.
     */
    _countCurves: function() {
        var length = this._segments.length;
        // Reduce length by one if it's an open path:
        return !this._closed && length > 0 ? length - 1 : length;
    },

    // DOCS: find a way to document the variable segment parameters of Path#add
    /**
     * Adds one or more segments to the end of the {@link #segments} array of
     * this path.
     *
     * @param {Segment|Point} segment the segment or point to be added.
     * @return {Segment} the added segment. This is not necessarily the same
     * object, e.g. if the segment to be added already belongs to another path.
     *
     * @example {@paperscript}
     * // Adding segments to a path using point objects:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * // Add a segment at {x: 30, y: 75}
     * path.add(new Point(30, 75));
     *
     * // Add two segments in one go at {x: 100, y: 20}
     * // and {x: 170, y: 75}:
     * path.add(new Point(100, 20), new Point(170, 75));
     *
     * @example {@paperscript}
     * // Adding segments to a path using arrays containing number pairs:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * // Add a segment at {x: 30, y: 75}
     * path.add([30, 75]);
     *
     * // Add two segments in one go at {x: 100, y: 20}
     * // and {x: 170, y: 75}:
     * path.add([100, 20], [170, 75]);
     *
     * @example {@paperscript}
     * // Adding segments to a path using objects:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * // Add a segment at {x: 30, y: 75}
     * path.add({x: 30, y: 75});
     *
     * // Add two segments in one go at {x: 100, y: 20}
     * // and {x: 170, y: 75}:
     * path.add({x: 100, y: 20}, {x: 170, y: 75});
     *
     * @example {@paperscript}
     * // Adding a segment with handles to a path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(30, 75));
     *
     * // Add a segment with handles:
     * var point = new Point(100, 20);
     * var handleIn = new Point(-50, 0);
     * var handleOut = new Point(50, 0);
     * var added = path.add(new Segment(point, handleIn, handleOut));
     *
     * // Select the added segment, so we can see its handles:
     * added.selected = true;
     *
     * path.add(new Point(170, 75));
     */
    add: function(segment1 /*, segment2, ... */) {
        return arguments.length > 1 && typeof segment1 !== 'number'
            // addSegments
            ? this._add(Segment.readAll(arguments))
            // addSegment
            : this._add([ Segment.read(arguments) ])[0];
    },

    /**
     * Inserts one or more segments at a given index in the list of this path's
     * segments.
     *
     * @param {Number} index the index at which to insert the segment.
     * @param {Segment|Point} segment the segment or point to be inserted.
     * @return {Segment} the added segment. This is not necessarily the same
     * object, e.g. if the segment to be added already belongs to another path.
     *
     * @example {@paperscript}
     * // Inserting a segment:
     * var myPath = new Path();
     * myPath.strokeColor = 'black';
     * myPath.add(new Point(50, 75));
     * myPath.add(new Point(150, 75));
     *
     * // Insert a new segment into myPath at index 1:
     * myPath.insert(1, new Point(100, 25));
     *
     * // Select the segment which we just inserted:
     * myPath.segments[1].selected = true;
     *
     * @example {@paperscript}
     * // Inserting multiple segments:
     * var myPath = new Path();
     * myPath.strokeColor = 'black';
     * myPath.add(new Point(50, 75));
     * myPath.add(new Point(150, 75));
     *
     * // Insert two segments into myPath at index 1:
     * myPath.insert(1, [80, 25], [120, 25]);
     *
     * // Select the segments which we just inserted:
     * myPath.segments[1].selected = true;
     * myPath.segments[2].selected = true;
     */
    insert: function(index, segment1 /*, segment2, ... */) {
        return arguments.length > 2 && typeof segment1 !== 'number'
            // insertSegments
            ? this._add(Segment.readAll(arguments, 1), index)
            // insertSegment
            : this._add([ Segment.read(arguments, 1) ], index)[0];
    },

    addSegment: function(/* segment */) {
        return this._add([ Segment.read(arguments) ])[0];
    },

    insertSegment: function(index /*, segment */) {
        return this._add([ Segment.read(arguments, 1) ], index)[0];
    },

    /**
     * Adds an array of segments (or types that can be converted to segments)
     * to the end of the {@link #segments} array.
     *
     * @param {Segment[]} segments
     * @return {Segment[]} an array of the added segments. These segments are
     * not necessarily the same objects, e.g. if the segment to be added already
     * belongs to another path.
     *
     * @example {@paperscript}
     * // Adding an array of Point objects:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     * var points = [new Point(30, 50), new Point(170, 50)];
     * path.addSegments(points);
     *
     * @example {@paperscript}
     * // Adding an array of [x, y] arrays:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     * var array = [[30, 75], [100, 20], [170, 75]];
     * path.addSegments(array);
     *
     * @example {@paperscript}
     * // Adding segments from one path to another:
     *
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     * path.addSegments([[30, 75], [100, 20], [170, 75]]);
     *
     * var path2 = new Path();
     * path2.strokeColor = 'red';
     *
     * // Add the second and third segments of path to path2:
     * path2.add(path.segments[1], path.segments[2]);
     *
     * // Move path2 30pt to the right:
     * path2.position.x += 30;
     */
    addSegments: function(segments) {
        return this._add(Segment.readAll(segments));
    },

    /**
     * Inserts an array of segments at a given index in the path's
     * {@link #segments} array.
     *
     * @param {Number} index the index at which to insert the segments.
     * @param {Segment[]} segments the segments to be inserted.
     * @return {Segment[]} an array of the added segments. These segments are
     * not necessarily the same objects, e.g. if the segment to be added already
     * belongs to another path.
     */
    insertSegments: function(index, segments) {
        return this._add(Segment.readAll(segments), index);
    },

    /**
     * Removes the segment at the specified index of the path's
     * {@link #segments} array.
     *
     * @param {Number} index the index of the segment to be removed
     * @return {Segment} the removed segment
     *
     * @example {@paperscript}
     * // Removing a segment from a path:
     *
     * // Create a circle shaped path at { x: 80, y: 50 }
     * // with a radius of 35:
     * var path = new Path.Circle({
     *     center: new Point(80, 50),
     *     radius: 35,
     *     strokeColor: 'black'
     * });
     *
     * // Remove its second segment:
     * path.removeSegment(1);
     *
     * // Select the path, so we can see its segments:
     * path.selected = true;
     */
    removeSegment: function(index) {
        return this.removeSegments(index, index + 1)[0] || null;
    },

    /**
     * Removes all segments from the path's {@link #segments} array.
     *
     * @name Path#removeSegments
     * @alias Path#clear
     * @function
     * @return {Segment[]} an array containing the removed segments
     */
    /**
     * Removes the segments from the specified {@code from} index to the
     * {@code to} index from the path's {@link #segments} array.
     *
     * @param {Number} from the beginning index, inclusive
     * @param {Number} [to=segments.length] the ending index, exclusive
     * @return {Segment[]} an array containing the removed segments
     *
     * @example {@paperscript}
     * // Removing segments from a path:
     *
     * // Create a circle shaped path at { x: 80, y: 50 }
     * // with a radius of 35:
     * var path = new Path.Circle({
     *     center: new Point(80, 50),
     *     radius: 35,
     *     strokeColor: 'black'
     * });
     *
     * // Remove the segments from index 1 till index 2:
     * path.removeSegments(1, 2);
     *
     * // Select the path, so we can see its segments:
     * path.selected = true;
     */
    removeSegments: function(from, to, _includeCurves) {
        from = from || 0;
        to = Base.pick(to, this._segments.length);
        var segments = this._segments,
            curves = this._curves,
            count = segments.length, // segment count before removal
            removed = segments.splice(from, to - from),
            amount = removed.length;
        if (!amount)
            return removed;
        // Update selection state accordingly
        for (var i = 0; i < amount; i++) {
            var segment = removed[i];
            if (segment._selectionState)
                this._updateSelection(segment, segment._selectionState, 0);
            // Clear the indices and path references of the removed segments
            segment._index = segment._path = null;
        }
        // Adjust the indices of the segments above.
        for (var i = from, l = segments.length; i < l; i++)
            segments[i]._index = i;
        // Keep curves in sync
        if (curves) {
            // If we're removing the last segment, remove the last curve (the
            // one to the left of the segment, not to the right, as normally).
            // Also take into account closed paths, which have one curve more
            // than segments.
            var index = from > 0 && to === count + (this._closed ? 1 : 0)
                    ? from - 1
                    : from,
                curves = curves.splice(index, amount);
            // Return the removed curves as well, if we're asked to include
            // them, but exclude the first curve, since that's shared with the
            // previous segment and does not connect the returned segments.
            if (_includeCurves)
                removed._curves = curves.slice(1);
            // Adjust segments for the curves before and after the removed ones
            this._adjustCurves(index, index);
        }
        // Use SEGMENTS notification instead of GEOMETRY since curves are kept
        // up-to-date by _adjustCurves() and don't need notification.
        this._changed(/*#=*/Change.SEGMENTS);
        return removed;
    },

    // DOCS Path#clear()
    clear: '#removeSegments',

    /**
     * The approximate length of the path in points.
     *
     * @type Number
     * @bean
     */
    getLength: function() {
        if (this._length == null) {
            var curves = this.getCurves();
            this._length = 0;
            for (var i = 0, l = curves.length; i < l; i++)
                this._length += curves[i].getLength();
        }
        return this._length;
    },

    /**
     * The area of the path in square points. Self-intersecting paths can
     * contain sub-areas that cancel each other out.
     *
     * @type Number
     * @bean
     */
    getArea: function() {
        var curves = this.getCurves();
        var area = 0;
        for (var i = 0, l = curves.length; i < l; i++)
            area += curves[i].getArea();
        return area;
    },

    /**
     * Specifies whether an path is selected and will also return {@code true}
     * if the path is partially selected, i.e. one or more of its segments is
     * selected.
     *
     * Paper.js draws the visual outlines of selected items on top of your
     * project. This can be useful for debugging, as it allows you to see the
     * construction of paths, position of path curves, individual segment points
     * and bounding boxes of symbol and raster items.
     *
     * @type Boolean
     * @bean
     * @see Project#selectedItems
     * @see Segment#selected
     * @see Point#selected
     *
     * @example {@paperscript}
     * // Selecting an item:
     * var path = new Path.Circle({
     *     center: new Size(80, 50),
     *     radius: 35
     * });
     * path.selected = true; // Select the path
     *
     * @example {@paperscript}
     * // A path is selected, if one or more of its segments is selected:
     * var path = new Path.Circle({
     *     center: new Size(80, 50),
     *     radius: 35
     * });
     *
     * // Select the second segment of the path:
     * path.segments[1].selected = true;
     *
     * // If the path is selected (which it is), set its fill color to red:
     * if (path.selected) {
     *     path.fillColor = 'red';
     * }
     *
     */
    /**
     * Specifies whether the path and all its segments are selected. Cannot be
     * {@code true} on an empty path.
     *
     * @type Boolean
     * @bean
     *
     * @example {@paperscript}
     * // A path is fully selected, if all of its segments are selected:
     * var path = new Path.Circle({
     *     center: new Size(80, 50),
     *     radius: 35
     * });
     * path.fullySelected = true;
     *
     * var path2 = new Path.Circle({
     *     center: new Size(180, 50),
     *     radius: 35
     * });
     *
     * // Deselect the second segment of the second path:
     * path2.segments[1].selected = false;
     *
     * // If the path is fully selected (which it is),
     * // set its fill color to red:
     * if (path.fullySelected) {
     *     path.fillColor = 'red';
     * }
     *
     * // If the second path is fully selected (which it isn't, since we just
     * // deselected its second segment),
     * // set its fill color to red:
     * if (path2.fullySelected) {
     *     path2.fillColor = 'red';
     * }
     */
    isFullySelected: function() {
        var length = this._segments.length;
        return this._selected && length > 0 && this._selectedSegmentState
                === length * /*#=*/SelectionState.SEGMENT;
    },

    setFullySelected: function(selected) {
        // No need to call _selectSegments() when selected is false, since
        // #setSelected() does that for us
        if (selected)
            this._selectSegments(true);
        this.setSelected(selected);
    },

    setSelected: function setSelected(selected) {
        // Deselect all segments when path is marked as not selected
        if (!selected)
            this._selectSegments(false);
        // No need to pass true for noChildren since Path has none anyway.
        setSelected.base.call(this, selected);
    },

    _selectSegments: function(selected) {
        var length = this._segments.length;
        this._selectedSegmentState = selected
                ? length * /*#=*/SelectionState.SEGMENT : 0;
        for (var i = 0; i < length; i++)
            this._segments[i]._selectionState = selected
                    ? /*#=*/SelectionState.SEGMENT : 0;
    },

    _updateSelection: function(segment, oldState, newState) {
        segment._selectionState = newState;
        var total = this._selectedSegmentState += newState - oldState;
        // Set this path as selected in case we have selected segments. Do not
        // unselect if we're down to 0, as the path itself can still remain
        // selected even when empty.
        if (total > 0)
            this.setSelected(true);
    },

    /**
     * Converts the curves in a path to straight lines with an even distribution
     * of points. The distance between the produced segments is as close as
     * possible to the value specified by the {@code maxDistance} parameter.
     *
     * @param {Number} maxDistance the maximum distance between the points
     *
     * @example {@paperscript}
     * // Flattening a circle shaped path:
     *
     * // Create a circle shaped path at { x: 80, y: 50 }
     * // with a radius of 35:
     * var path = new Path.Circle({
     *     center: new Size(80, 50),
     *     radius: 35
     * });
     *
     * // Select the path, so we can inspect its segments:
     * path.selected = true;
     *
     * // Create a copy of the path and move it 150 points to the right:
     * var copy = path.clone();
     * copy.position.x += 150;
     *
     * // Convert its curves to points, with a max distance of 20:
     * copy.flatten(20);
     */
    flatten: function(maxDistance) {
        var iterator = new PathIterator(this, 64, 0.1),
            pos = 0,
            // Adapt step = maxDistance so the points distribute evenly.
            step = iterator.length / Math.ceil(iterator.length / maxDistance),
            // Add/remove half of step to end, so imprecisions are ok too.
            // For closed paths, remove it, because we don't want to add last
            // segment again
            end = iterator.length + (this._closed ? -step : step) / 2;
        // Iterate over path and evaluate and add points at given offsets
        var segments = [];
        while (pos <= end) {
            segments.push(new Segment(iterator.evaluate(pos, 0)));
            pos += step;
        }
        this.setSegments(segments);
    },

    /**
     * Reduces the path by removing curves that have a lenght of 0.
     */
    reduce: function() {
        var curves = this.getCurves();
        for (var i = curves.length - 1; i >= 0; i--) {
            var curve = curves[i];
            if (curve.isLinear() && curve.getLength() === 0)
                curve.remove();
        }
        return this;
    },

    /**
     * Smooths a path by simplifying it. The {@link Path#segments} array is
     * analyzed and replaced by a more optimal set of segments, reducing memory
     * usage and speeding up drawing.
     *
     * @param {Number} [tolerance=2.5]
     *
     * @example {@paperscript height=300}
     * // Click and drag below to draw to draw a line, when you release the
     * // mouse, the is made smooth using path.simplify():
     *
     * var path;
     * function onMouseDown(event) {
     *     // If we already made a path before, deselect it:
     *     if (path) {
     *         path.selected = false;
     *     }
     *
     *     // Create a new path and add the position of the mouse
     *     // as its first segment. Select it, so we can see the
     *     // segment points:
     *     path = new Path({
     *         segments: [event.point],
     *         strokeColor: 'black',
     *         selected: true
     *     });
     * }
     *
     * function onMouseDrag(event) {
     *     // On every drag event, add a segment to the path
     *     // at the position of the mouse:
     *     path.add(event.point);
     * }
     *
     * function onMouseUp(event) {
     *     // When the mouse is released, simplify the path:
     *     path.simplify();
     *     path.selected = true;
     * }
     */
    simplify: function(tolerance) {
        if (this._segments.length > 2) {
            var fitter = new PathFitter(this, tolerance || 2.5);
            this.setSegments(fitter.fit());
        }
    },

    // TODO: reduceSegments([flatness])

    /**
     * Splits the path at the given offset. After splitting, the path will be
     * open. If the path was open already, splitting will result in two paths.
     *
     * @name Path#split
     * @function
     * @param {Number} offset the offset at which to split the path
     * as a number between 0 and {@link Path#length}
     * @return {Path} the newly created path after splitting, if any
     *
     * @example {@paperscript} // Splitting an open path
     * var path = new Path();
     * path.strokeColor = 'black';
     * path.add(20, 20);
     *
     * // Add an arc through {x: 90, y: 80} to {x: 160, y: 20}
     * path.arcTo([90, 80], [160, 20]);
     *
     * // Split the path at 30% of its length:
     * var path2 = path.split(path.length * 0.3);
     * path2.strokeColor = 'red';
     *
     * // Move the newly created path 40px to the right:
     * path2.position.x += 40;
     *
     * @example {@paperscript} // Splitting a closed path
     * var path = new Path.Rectangle({
     *     from: [20, 20],
     *     to: [80, 80],
     *     strokeColor: 'black'
     * });
     *
     * // Split the path at 60% of its length:
     * path.split(path.length * 0.6);
     *
     * // Move the first segment, to show where the path
     * // was split:
     * path.firstSegment.point.x += 20;
     *
     * // Select the first segment:
     * path.firstSegment.selected = true;
     */
    /**
     * Splits the path at the given curve location. After splitting, the path
     * will be open. If the path was open already, splitting will result in two
     * paths.
     *
     * @name Path#split
     * @function
     * @param {CurveLocation} location the curve location at which to split
     * the path
     * @return {Path} the newly created path after splitting, if any
     *
     * @example {@paperscript}
     * var path = new Path.Circle({
     *     center: view.center,
     *     radius: 40,
     *     strokeColor: 'black'
     * });
     *
     * var pointOnCircle = view.center + {
     *     length: 40,
     *     angle: 30
     * };
     *
     * var curveLocation = path.getNearestLocation(pointOnCircle);
     *
     * path.split(curveLocation);
     * path.lastSegment.selected = true;
     */
    /**
     * Splits the path at the given curve index and parameter. After splitting,
     * the path will be open. If the path was open already, splitting will
     * result in two paths.
     *
     * @example {@paperscript} // Splitting an open path
     * // Draw a V shaped path:
     * var path = new Path([20, 20], [50, 80], [80, 20]);
     * path.strokeColor = 'black';
     *
     * // Split the path half-way down its second curve:
     * var path2 = path.split(1, 0.5);
     *
     * // Give the resulting path a red stroke-color
     * // and move it 20px to the right:
     * path2.strokeColor = 'red';
     * path2.position.x += 20;
     *
     * @example {@paperscript} // Splitting a closed path
     * var path = new Path.Rectangle({
     *     from: [20, 20],
     *     to: [80, 80],
     *     strokeColor: 'black'
     * });
     *
     * // Split the path half-way down its second curve:
     * path.split(2, 0.5);
     *
     * // Move the first segment, to show where the path
     * // was split:
     * path.firstSegment.point.x += 20;
     *
     * // Select the first segment:
     * path.firstSegment.selected = true;
     *
     * @param {Number} index the index of the curve in the {@link Path#curves}
     * array at which to split
     * @param {Number} parameter the parameter at which the curve will be split
     * @return {Path} the newly created path after splitting, if any
     */
    split: function(index, parameter) {
        if (parameter === null)
            return null;
        if (arguments.length === 1) {
            var arg = index;
            // split(offset), convert offset to location
            if (typeof arg === 'number')
                arg = this.getLocationAt(arg);
            if (!arg)
                return null
            // split(location)
            index = arg.index;
            parameter = arg.parameter;
        }
        var tolerance = /*#=*/Numerical.TOLERANCE;
        if (parameter >= 1 - tolerance) {
            // t == 1 is the same as t == 0 and index ++
            index++;
            parameter--;
        }
        var curves = this.getCurves();
        if (index >= 0 && index < curves.length) {
            // Only divide curves if we're not on an existing segment already.
            if (parameter > tolerance) {
                // Divide the curve with the index at given parameter.
                // Increase because dividing adds more segments to the path.
                curves[index++].divide(parameter, true);
            }
            // Create the new path with the segments to the right of given
            // parameter, which are removed from the current path. Pass true
            // for includeCurves, since we want to preserve and move them to
            // the new path through _add(), allowing us to have CurveLocation
            // keep the connection to the new path through moved curves.
            var segs = this.removeSegments(index, this._segments.length, true),
                path;
            if (this._closed) {
                // If the path is closed, open it and move the segments round,
                // otherwise create two paths.
                this.setClosed(false);
                // Just have path point to this. The moving around of segments
                // will happen below.
                path = this;
            } else {
                // Pass true for _preserve, in case of CompoundPath, to avoid
                // reversing of path direction, which would mess with segs!
                // Use _clone to copy over all other attributes, including style
                path = this._clone(new Path().insertAbove(this, true));
            }
            path._add(segs, 0);
            // Add dividing segment again. In case of a closed path, that's the
            // beginning segment again at the end, since we opened it.
            this.addSegment(segs[0]);
            return path;
        }
        return null;
    },

    /**
     * Specifies whether the path is oriented clock-wise.
     *
     * @type Boolean
     * @bean
     */
    isClockwise: function() {
        if (this._clockwise !== undefined)
            return this._clockwise;
        return Path.isClockwise(this._segments);
    },

    setClockwise: function(clockwise) {
        // Only revers the path if its clockwise orientation is not the same
        // as what it is now demanded to be.
        // On-the-fly conversion to boolean:
        if (this.isClockwise() != (clockwise = !!clockwise))
            this.reverse();
        // Reverse only flips _clockwise state if it was already set, so let's
        // always set this here now.
        this._clockwise = clockwise;
    },

    /**
     * Reverses the orientation of the path, by reversing all its segments.
     */
    reverse: function() {
        this._segments.reverse();
        // Reverse the handles:
        for (var i = 0, l = this._segments.length; i < l; i++) {
            var segment = this._segments[i];
            var handleIn = segment._handleIn;
            segment._handleIn = segment._handleOut;
            segment._handleOut = handleIn;
            segment._index = i;
        }
        // Clear curves since it all has changed.
        this._curves = null;
        // Flip clockwise state if it's defined
        if (this._clockwise !== undefined)
            this._clockwise = !this._clockwise;
        this._changed(/*#=*/Change.GEOMETRY);
    },

    // DOCS: document Path#join(path) in more detail.
    // DOCS: document Path#join() (joining with itself)
    // TODO: Consider adding a distance / tolerance parameter for merging.
    /**
     * Joins the path with the specified path, which will be removed in the
     * process.
     *
     * @param {Path} path the path to join this path with
     * @return {Path} the joined path
     *
     * @example {@paperscript}
     * // Joining two paths:
     * var path = new Path({
     *     segments: [[30, 25], [30, 75]],
     *     strokeColor: 'black'
     * });
     *
     * var path2 = new Path({
     *     segments: [[200, 25], [200, 75]],
     *     strokeColor: 'black'
     * });
     *
     * // Join the paths:
     * path.join(path2);
     *
     * @example {@paperscript}
     * // Joining two paths that share a point at the start or end of their
     * // segments array:
     * var path = new Path({
     *     segments: [[30, 25], [30, 75]],
     *     strokeColor: 'black'
     * });
     *
     * var path2 = new Path({
     *     segments: [[30, 25], [80, 25]],
     *     strokeColor: 'black'
     * });
     *
     * // Join the paths:
     * path.join(path2);
     *
     * // After joining, path with have 3 segments, since it
     * // shared its first segment point with the first
     * // segment point of path2.
     *
     * // Select the path to show that they have joined:
     * path.selected = true;
     *
     * @example {@paperscript}
     * // Joining two paths that connect at two points:
     * var path = new Path({
     *     segments: [[30, 25], [80, 25], [80, 75]],
     *     strokeColor: 'black'
     * });
     *
     * var path2 = new Path({
     *     segments: [[30, 25], [30, 75], [80, 75]],
     *     strokeColor: 'black'
     * });
     *
     * // Join the paths:
     * path.join(path2);
     *
     * // Because the paths were joined at two points, the path is closed
     * // and has 4 segments.
     *
     * // Select the path to show that they have joined:
     * path.selected = true;
     */
    join: function(path) {
        if (path) {
            var segments = path._segments,
                last1 = this.getLastSegment(),
                last2 = path.getLastSegment();
            if (!last2) // an empty path?
                return this;
            if (last1 && last1._point.equals(last2._point))
                path.reverse();
            var first2 = path.getFirstSegment();
            if (last1 && last1._point.equals(first2._point)) {
                last1.setHandleOut(first2._handleOut);
                this._add(segments.slice(1));
            } else {
                var first1 = this.getFirstSegment();
                if (first1 && first1._point.equals(first2._point))
                    path.reverse();
                last2 = path.getLastSegment();
                if (first1 && first1._point.equals(last2._point)) {
                    first1.setHandleIn(last2._handleIn);
                    // Prepend all segments from path except the last one
                    this._add(segments.slice(0, segments.length - 1), 0);
                } else {
                    this._add(segments.slice());
                }
            }
            if (path.closed)
                this._add([segments[0]]);
            path.remove();
        }
        // Close the resulting path and merge first and last segment if they
        // touch, meaning the touched at path ends. Also do this if no path
        // argument was provided, in which cases the path is joined with itself
        // only if its ends touch.
        var first = this.getFirstSegment(),
            last = this.getLastSegment();
        if (first !== last && first._point.equals(last._point)) {
            first.setHandleIn(last._handleIn);
            last.remove();
            this.setClosed(true);
        }
        return this;
    },


    // DOCS: toShape

    toShape: function(insert) {
        if (!this._closed)
            return null;

        var segments = this._segments,
            type,
            size,
            radius,
            topCenter;

        function isColinear(i, j) {
            return segments[i].isColinear(segments[j]);
        }

        function isOrthogonal(i) {
            return segments[i].isOrthogonal();
        }

        function isArc(i) {
            return segments[i].isArc();
        }

        function getDistance(i, j) {
            return segments[i]._point.getDistance(segments[j]._point);
        }

        // See if actually have any curves in the path. Differentiate
        // between straight objects (line, polyline, rect, and  polygon) and
        // objects with curves(circle, ellipse, roundedRectangle).
        if (this.isPolygon() && segments.length === 4
                && isColinear(0, 2) && isColinear(1, 3) && isOrthogonal(1)) {
            type = Shape.Rectangle;
            size = new Size(getDistance(0, 3), getDistance(0, 1));
            topCenter = segments[1]._point.add(segments[2]._point).divide(2);
        } else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4)
                && isArc(6) && isColinear(1, 5) && isColinear(3, 7)) {
            // It's a rounded rectangle.
            type = Shape.Rectangle;
            size = new Size(getDistance(1, 6), getDistance(0, 3));
            // Subtract side lengths from total width and divide by 2 to get the
            // corner radius size.
            radius = size.subtract(new Size(getDistance(0, 7),
                    getDistance(1, 2))).divide(2);
            topCenter = segments[3]._point.add(segments[4]._point).divide(2);
        } else if (segments.length === 4
                && isArc(0) && isArc(1) && isArc(2) && isArc(3)) {
            // If the distance between (point0 and point2) and (point1
            // and point3) are equal, then it is a circle
            if (Numerical.isZero(getDistance(0, 2) - getDistance(1, 3))) {
                type = Shape.Circle;
                radius = getDistance(0, 2) / 2;
            } else {
                type = Shape.Ellipse;
                radius = new Size(getDistance(2, 0) / 2, getDistance(3, 1) / 2);
            }
            topCenter = segments[1]._point;
        }

        if (type) {
            var center = this.getPosition(true),
                shape = new type({
                    center: center,
                    size: size,
                    radius: radius,
                    insert: false
                });
            // Determine and apply the shape's angle of rotation.
            shape.rotate(topCenter.subtract(center).getAngle() + 90);
            shape.setStyle(this._style);
            // Insert is true by default.
            if (insert || insert === undefined)
                shape.insertAbove(this);
            return shape;
        }
        return null;
    },

    _hitTestSelf: function(point, options) {
        var that = this,
            style = this.getStyle(),
            segments = this._segments,
            numSegments = segments.length,
            closed = this._closed,
            // transformed tolerance padding, see Item#hitTest. We will add
            // stroke padding on top if stroke is defined.
            tolerancePadding = options._tolerancePadding,
            strokePadding = tolerancePadding,
            join, cap, miterLimit,
            area, loc, res,
            hitStroke = options.stroke && style.hasStroke(),
            hitFill = options.fill && style.hasFill(),
            hitCurves = options.curves,
            radius = hitStroke
                    ? style.getStrokeWidth() / 2
                    // Set radius to 0 when we're hit-testing fills with
                    // tolerance, to handle tolerance through stroke hit-test
                    // functionality. Also use 0 when hit-testing curves.
                    : hitFill && options.tolerance > 0 || hitCurves
                        ? 0 : null;
        if (radius !== null) {
            if (radius > 0) {
                join = style.getStrokeJoin();
                cap = style.getStrokeCap();
                miterLimit = radius * style.getMiterLimit();
                // Add the stroke radius to tolerance padding.
                strokePadding = tolerancePadding.add(new Point(radius, radius));
            } else {
                join = cap = 'round';
            }
            // Using tolerance padding for fill tests will also work if there is
            // no stroke, in which case radius = 0 and we will test for stroke
            // locations to extend the fill area by tolerance.
        }

        function isCloseEnough(pt, padding) {
            return point.subtract(pt).divide(padding).length <= 1;
        }

        function checkSegmentPoint(seg, pt, name) {
            if (!options.selected || pt.isSelected()) {
                var anchor = seg._point;
                if (pt !== anchor)
                    pt = pt.add(anchor);
                if (isCloseEnough(pt, strokePadding)) {
                    return new HitResult(name, that, {
                        segment: seg,
                        point: pt
                    });
                }
            }
        }

        function checkSegmentPoints(seg, ends) {
            // Note, when checking for ends, we don't also check for handles,
            // since this will happen afterwards in a separate loop, see below.
            return (ends || options.segments)
                && checkSegmentPoint(seg, seg._point, 'segment')
                || (!ends && options.handles) && (
                    checkSegmentPoint(seg, seg._handleIn, 'handle-in') ||
                    checkSegmentPoint(seg, seg._handleOut, 'handle-out'));
        }

        // Code to check stroke join / cap areas

        function addToArea(point) {
            area.add(point);
        }

        function checkSegmentStroke(segment) {
            // Handle joins / caps that are not round specificelly, by
            // hit-testing their polygon areas.
            if (join !== 'round' || cap !== 'round') {
                // Create an 'internal' path without id and outside the DOM
                // to run the hit-test on it.
                area = new Path({ internal: true, closed: true });
                if (closed || segment._index > 0
                        && segment._index < numSegments - 1) {
                    // It's a join. See that it's not a round one (one of
                    // the handles has to be zero too for this!)
                    if (join !== 'round' && (segment._handleIn.isZero()
                            || segment._handleOut.isZero()))
                        // _addBevelJoin() handles both 'bevel' and 'miter'!
                        Path._addBevelJoin(segment, join, radius, miterLimit,
                                addToArea, true);
                } else if (cap !== 'round') {
                    // It's a cap
                    Path._addSquareCap(segment, cap, radius, addToArea, true);
                }
                // See if the above produced an area to check for
                if (!area.isEmpty()) {
                    // Also use stroke check with tolerancePadding if the point
                    // is not inside the area itself, to use test caps and joins
                    // with same tolerance.
                    var loc;
                    return area.contains(point)
                        || (loc = area.getNearestLocation(point))
                            && isCloseEnough(loc.getPoint(), tolerancePadding);
                }
            }
            // Fallback scenario is a round join / cap.
            return isCloseEnough(segment._point, strokePadding);
        }

        // If we're asked to query for segments, ends or handles, do all that
        // before stroke or fill.
        if (options.ends && !options.segments && !closed) {
            if (res = checkSegmentPoints(segments[0], true)
                    || checkSegmentPoints(segments[numSegments - 1], true))
                return res;
        } else if (options.segments || options.handles) {
            for (var i = 0; i < numSegments; i++)
                if (res = checkSegmentPoints(segments[i]))
                    return res;
        }
        // If we're querying for stroke, perform that before fill
        if (radius !== null) {
            loc = this.getNearestLocation(point);
            // Note that paths need at least two segments to have an actual
            // stroke. But we still check for segments with the radius fallback
            // check if there is only one segment.
            if (loc) {
                // Now see if we're on a segment, and if so, check for its
                // stroke join / cap first. If not, do a normal radius check
                // for round strokes.
                var parameter = loc.getParameter();
                if (parameter === 0 || parameter === 1 && numSegments > 1) {
                    if (!checkSegmentStroke(loc.getSegment()))
                        loc = null;
                } else if (!isCloseEnough(loc.getPoint(), strokePadding)) {
                    loc = null;
                }
            }
            // If we have miter joins, we may not be done yet, since they can be
            // longer than the radius. Check for each segment within reach now.
            if (!loc && join === 'miter' && numSegments > 1) {
                for (var i = 0; i < numSegments; i++) {
                    var segment = segments[i];
                    if (point.getDistance(segment._point) <= miterLimit
                            && checkSegmentStroke(segment)) {
                        loc = segment.getLocation();
                        break;
                    }
                }
            }
        }
        // Don't process loc yet, as we also need to query for stroke after fill
        // in some cases. Simply skip fill query if we already have a matching
        // stroke. If we have a loc and no stroke then it's a result for fill.
        return !loc && hitFill && this._contains(point)
                || loc && !hitStroke && !hitCurves
                    ? new HitResult('fill', this)
                    : loc
                        ? new HitResult(hitStroke ? 'stroke' : 'curve', this, {
                            location: loc,
                            // It's fine performance wise to call getPoint()
                            // again since it was already called before.
                            point: loc.getPoint()
                        })
                        : null;
    }

    // TODO: intersects(item)
    // TODO: contains(item)
}, Base.each(['getPoint', 'getTangent', 'getNormal', 'getCurvature'],
    function(name) {
        this[name + 'At'] = function(offset, isParameter) {
            var loc = this.getLocationAt(offset, isParameter);
            return loc && loc[name]();
        };
    },
/** @lends Path# */{
    // Explicitly deactivate the creation of beans, as we have functions here
    // that look like bean getters but actually read arguments.
    // See #getLocationOf(), #getNearestLocation(), #getNearestPoint()
    beans: false,

    _getOffset: function(location) {
        var index = location && location.getIndex();
        if (index != null) {
            var curves = this.getCurves(),
                offset = 0;
            for (var i = 0; i < index; i++)
                offset += curves[i].getLength();
            var curve = curves[index],
                parameter = location.getParameter();
            if (parameter > 0)
                offset += curve.getPartLength(0, parameter);
            return offset;
        }
        return null;
    },

    /**
     * {@grouptitle Positions on Paths and Curves}
     *
     * Returns the curve location of the specified point if it lies on the
     * path, {@code null} otherwise.
     * @param {Point} point the point on the path.
     * @return {CurveLocation} the curve location of the specified point.
     */
    getLocationOf: function(/* point */) {
        var point = Point.read(arguments),
            curves = this.getCurves();
        for (var i = 0, l = curves.length; i < l; i++) {
            var loc = curves[i].getLocationOf(point);
            if (loc)
                return loc;
        }
        return null;
    },

    /**
     * Returns the length of the path from its beginning up to up to the
     * specified point if it lies on the path, {@code null} otherwise.
     * @param {Point} point the point on the path.
     * @return {Number} the length of the path up to the specified point.
     */
    getOffsetOf: function(/* point */) {
        var loc = this.getLocationOf.apply(this, arguments);
        return loc ? loc.getOffset() : null;
    },

    /**
     * Returns the curve location of the specified offset on the path.
     *
     * @param {Number} offset the offset on the path, where {@code 0} is at
     * the beginning of the path and {@link Path#length} at the end.
     * @param {Boolean} [isParameter=false]
     * @return {CurveLocation} the curve location at the specified offset
     */
    getLocationAt: function(offset, isParameter) {
        var curves = this.getCurves(),
            length = 0;
        if (isParameter) {
            // offset consists of curve index and curve parameter, before and
            // after the fractional digit.
            var index = ~~offset; // = Math.floor()
            return curves[index].getLocationAt(offset - index, true);
        }
        for (var i = 0, l = curves.length; i < l; i++) {
            var start = length,
                curve = curves[i];
            length += curve.getLength();
            if (length > offset) {
                // Found the segment within which the length lies
                return curve.getLocationAt(offset - start);
            }
        }
        // It may be that through imprecision of getLength, that the end of the
        // last curve was missed:
        if (offset <= this.getLength())
            return new CurveLocation(curves[curves.length - 1], 1);
        return null;
    },

    /**
     * Calculates the point on the path at the given offset.
     *
     * @name Path#getPointAt
     * @function
     * @param {Number} offset the offset on the path, where {@code 0} is at
     * the beginning of the path and {@link Path#length} at the end.
     * @param {Boolean} [isParameter=false]
     * @return {Point} the point at the given offset
     *
     * @example {@paperscript height=150}
     * // Finding the point on a path at a given offset:
     *
     * // Create an arc shaped path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(40, 100));
     * path.arcTo(new Point(150, 100));
     *
     * // We're going to be working with a third of the length
     * // of the path as the offset:
     * var offset = path.length / 3;
     *
     * // Find the point on the path:
     * var point = path.getPointAt(offset);
     *
     * // Create a small circle shaped path at the point:
     * var circle = new Path.Circle({
     *     center: point,
     *     radius: 3,
     *     fillColor: 'red'
     * });
     *
     * @example {@paperscript height=150}
     * // Iterating over the length of a path:
     *
     * // Create an arc shaped path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(40, 100));
     * path.arcTo(new Point(150, 100));
     *
     * var amount = 5;
     * var length = path.length;
     * for (var i = 0; i < amount + 1; i++) {
     *     var offset = i / amount * length;
     *
     *     // Find the point on the path at the given offset:
     *     var point = path.getPointAt(offset);
     *
     *     // Create a small circle shaped path at the point:
     *     var circle = new Path.Circle({
     *         center: point,
     *         radius: 3,
     *         fillColor: 'red'
     *     });
     * }
     */

    /**
     * Calculates the tangent vector of the path at the given offset.
     *
     * @name Path#getTangentAt
     * @function
     * @param {Number} offset the offset on the path, where {@code 0} is at
     * the beginning of the path and {@link Path#length} at the end.
     * @param {Boolean} [isParameter=false]
     * @return {Point} the tangent vector at the given offset
     *
     * @example {@paperscript height=150}
     * // Working with the tangent vector at a given offset:
     *
     * // Create an arc shaped path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(40, 100));
     * path.arcTo(new Point(150, 100));
     *
     * // We're going to be working with a third of the length
     * // of the path as the offset:
     * var offset = path.length / 3;
     *
     * // Find the point on the path:
     * var point = path.getPointAt(offset);
     *
     * // Find the tangent vector at the given offset:
     * var tangent = path.getTangentAt(offset);
     *
     * // Make the tangent vector 60pt long:
     * tangent.length = 60;
     *
     * var line = new Path({
     *     segments: [point, point + tangent],
     *     strokeColor: 'red'
     * })
     *
     * @example {@paperscript height=200}
     * // Iterating over the length of a path:
     *
     * // Create an arc shaped path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(40, 100));
     * path.arcTo(new Point(150, 100));
     *
     * var amount = 6;
     * var length = path.length;
     * for (var i = 0; i < amount + 1; i++) {
     *     var offset = i / amount * length;
     *
     *     // Find the point on the path at the given offset:
     *     var point = path.getPointAt(offset);
     *
     *     // Find the normal vector on the path at the given offset:
     *     var tangent = path.getTangentAt(offset);
     *
     *     // Make the tangent vector 60pt long:
     *     tangent.length = 60;
     *
     *     var line = new Path({
     *         segments: [point, point + tangent],
     *         strokeColor: 'red'
     *     })
     * }
     */

    /**
     * Calculates the normal vector of the path at the given offset.
     *
     * @name Path#getNormalAt
     * @function
     * @param {Number} offset the offset on the path, where {@code 0} is at
     * the beginning of the path and {@link Path#length} at the end.
     * @param {Boolean} [isParameter=false]
     * @return {Point} the normal vector at the given offset
     *
     * @example {@paperscript height=150}
     * // Working with the normal vector at a given offset:
     *
     * // Create an arc shaped path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(40, 100));
     * path.arcTo(new Point(150, 100));
     *
     * // We're going to be working with a third of the length
     * // of the path as the offset:
     * var offset = path.length / 3;
     *
     * // Find the point on the path:
     * var point = path.getPointAt(offset);
     *
     * // Find the normal vector at the given offset:
     * var normal = path.getNormalAt(offset);
     *
     * // Make the normal vector 30pt long:
     * normal.length = 30;
     *
     * var line = new Path({
     *     segments: [point, point + normal],
     *     strokeColor: 'red'
     * });
     *
     * @example {@paperscript height=200}
     * // Iterating over the length of a path:
     *
     * // Create an arc shaped path:
     * var path = new Path({
     *     strokeColor: 'black'
     * });
     *
     * path.add(new Point(40, 100));
     * path.arcTo(new Point(150, 100));
     *
     * var amount = 10;
     * var length = path.length;
     * for (var i = 0; i < amount + 1; i++) {
     *     var offset = i / amount * length;
     *
     *     // Find the point on the path at the given offset:
     *     var point = path.getPointAt(offset);
     *
     *     // Find the normal vector on the path at the given offset:
     *     var normal = path.getNormalAt(offset);
     *
     *     // Make the normal vector 30pt long:
     *     normal.length = 30;
     *
     *     var line = new Path({
     *         segments: [point, point + normal],
     *         strokeColor: 'red'
     *     });
     * }
     */

    /**
     * Calculates the curvature of the path at the given offset. Curvatures
     * indicate how sharply a path changes direction. A straight line has zero
     * curvature, where as a circle has a constant curvature. The path's radius
     * at the given offset is the reciprocal value of its curvature.
     *
     * @name Path#getCurvatureAt
     * @function
     * @param {Number} offset the offset on the path, where {@code 0} is at
     * the beginning of the path and {@link Path#length} at the end.
     * @param {Boolean} [isParameter=false]
     * @return {Number} the normal vector at the given offset
     *

    /**
     * Returns the nearest location on the path to the specified point.
     *
     * @function
     * @param point {Point} the point for which we search the nearest location
     * @return {CurveLocation} the location on the path that's the closest to
     * the specified point
     */
    getNearestLocation: function(/* point */) {
        var point = Point.read(arguments),
            curves = this.getCurves(),
            minDist = Infinity,
            minLoc = null;
        for (var i = 0, l = curves.length; i < l; i++) {
            var loc = curves[i].getNearestLocation(point);
            if (loc._distance < minDist) {
                minDist = loc._distance;
                minLoc = loc;
            }
        }
        return minLoc;
    },

    /**
     * Returns the nearest point on the path to the specified point.
     *
     * @function
     * @param point {Point} the point for which we search the nearest point
     * @return {Point} the point on the path that's the closest to the specified
     * point
     *
     * @example {@paperscript height=200}
     * var star = new Path.Star({
     *     center: view.center,
     *     points: 10,
     *     radius1: 30,
     *     radius2: 60,
     *     strokeColor: 'black'
     * });
     *
     * var circle = new Path.Circle({
     *     center: view.center,
     *     radius: 3,
     *     fillColor: 'red'
     * });
     *
     * function onMouseMove(event) {
     *     // Get the nearest point from the mouse position
     *     // to the star shaped path:
     *     var nearestPoint = star.getNearestPoint(event.point);
     *
     *     // Move the red circle to the nearest point:
     *     circle.position = nearestPoint;
     * }
     */
    getNearestPoint: function(/* point */) {
        return this.getNearestLocation.apply(this, arguments).getPoint();
    }
}), new function() { // Scope for drawing

    // Note that in the code below we're often accessing _x and _y on point
    // objects that were read from segments. This is because the SegmentPoint
    // class overrides the plain x / y properties with getter / setters and
    // stores the values in these private properties internally. To avoid
    // calling of getter functions all the time we directly access these private
    // properties here. The distinction between normal Point objects and
    // SegmentPoint objects maybe seem a bit tedious but is worth the benefit in
    // performance.

    function drawHandles(ctx, segments, matrix, size) {
        var half = size / 2;

        function drawHandle(index) {
            var hX = coords[index],
                hY = coords[index + 1];
            if (pX != hX || pY != hY) {
                ctx.beginPath();
                ctx.moveTo(pX, pY);
                ctx.lineTo(hX, hY);
                ctx.stroke();
                ctx.beginPath();
                ctx.arc(hX, hY, half, 0, Math.PI * 2, true);
                ctx.fill();
            }
        }

        var coords = new Array(6);
        for (var i = 0, l = segments.length; i < l; i++) {
            var segment = segments[i];
            segment._transformCoordinates(matrix, coords, false);
            var state = segment._selectionState,
                pX = coords[0],
                pY = coords[1];
            if (state & /*#=*/SelectionState.HANDLE_IN)
                drawHandle(2);
            if (state & /*#=*/SelectionState.HANDLE_OUT)
                drawHandle(4);
            // Draw a rectangle at segment.point:
            ctx.fillRect(pX - half, pY - half, size, size);
            // If the point is not selected, draw a white square that is 1 px
            // smaller on all sides:
            if (!(state & /*#=*/SelectionState.POINT)) {
                var fillStyle = ctx.fillStyle;
                ctx.fillStyle = '#ffffff';
                ctx.fillRect(pX - half + 1, pY - half + 1, size - 2, size - 2);
                ctx.fillStyle = fillStyle;
            }
        }
    }

    function drawSegments(ctx, path, matrix) {
        var segments = path._segments,
            length = segments.length,
            coords = new Array(6),
            first = true,
            curX, curY,
            prevX, prevY,
            inX, inY,
            outX, outY;

        function drawSegment(segment) {
            // Optimise code when no matrix is provided by accessing segment
            // points hand handles directly, since this is the default when
            // drawing paths. Matrix is only used for drawing selections and
            // when #strokeScaling is false.
            if (matrix) {
                segment._transformCoordinates(matrix, coords, false);
                curX = coords[0];
                curY = coords[1];
            } else {
                var point = segment._point;
                curX = point._x;
                curY = point._y;
            }
            if (first) {
                ctx.moveTo(curX, curY);
                first = false;
            } else {
                if (matrix) {
                    inX = coords[2];
                    inY = coords[3];
                } else {
                    var handle = segment._handleIn;
                    inX = curX + handle._x;
                    inY = curY + handle._y;
                }
                if (inX === curX && inY === curY
                        && outX === prevX && outY === prevY) {
                    ctx.lineTo(curX, curY);
                } else {
                    ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);
                }
            }
            prevX = curX;
            prevY = curY;
            if (matrix) {
                outX = coords[4];
                outY = coords[5];
            } else {
                var handle = segment._handleOut;
                outX = prevX + handle._x;
                outY = prevY + handle._y;
            }
        }

        for (var i = 0; i < length; i++)
            drawSegment(segments[i]);
        // Close path by drawing first segment again
        if (path._closed && length > 0)
            drawSegment(segments[0]);
    }

    return {
        _draw: function(ctx, param, strokeMatrix) {
            var dontStart = param.dontStart,
                dontPaint = param.dontFinish || param.clip,
                style = this.getStyle(),
                hasFill = style.hasFill(),
                hasStroke = style.hasStroke(),
                dashArray = style.getDashArray(),
                // dashLength is only set if we can't draw dashes natively
                dashLength = !paper.support.nativeDash && hasStroke
                        && dashArray && dashArray.length;

            if (!dontStart)
                ctx.beginPath();

            if (!dontStart && this._currentPath) {
                ctx.currentPath = this._currentPath;
            } else if (hasFill || hasStroke && !dashLength || dontPaint) {
                // Prepare the canvas path if we have any situation that
                // requires it to be defined.
                drawSegments(ctx, this, strokeMatrix);
                if (this._closed)
                    ctx.closePath();
                // CompoundPath collects its own _currentPath
                if (!dontStart)
                    this._currentPath = ctx.currentPath;
            }

            function getOffset(i) {
                // Negative modulo is necessary since we're stepping back
                // in the dash sequence first.
                return dashArray[((i % dashLength) + dashLength) % dashLength];
            }

            if (!dontPaint && (hasFill || hasStroke)) {
                // If the path is part of a compound path or doesn't have a fill
                // or stroke, there is no need to continue.
                this._setStyles(ctx);
                if (hasFill) {
                    ctx.fill(style.getWindingRule());
                    // If shadowColor is defined, clear it after fill, so it
                    // won't be applied to both fill and stroke. If the path is
                    // only stroked, we don't have to clear it.
                    ctx.shadowColor = 'rgba(0,0,0,0)';
                }
                if (hasStroke) {
                    if (dashLength) {
                        // We cannot use the path created by drawSegments above
                        // Use PathIterator to draw dashed paths:
                        // NOTE: We don't cache this path in another currentPath
                        // since browsers that support currentPath also support
                        // native dashes.
                        if (!dontStart)
                            ctx.beginPath();
                        var iterator = new PathIterator(this, 32, 0.25,
                                strokeMatrix),
                            length = iterator.length,
                            from = -style.getDashOffset(), to,
                            i = 0;
                        from = from % length;
                        // Step backwards in the dash sequence first until the
                        // from parameter is below 0.
                        while (from > 0) {
                            from -= getOffset(i--) + getOffset(i--);
                        }
                        while (from < length) {
                            to = from + getOffset(i++);
                            if (from > 0 || to > 0)
                                iterator.drawPart(ctx,
                                        Math.max(from, 0), Math.max(to, 0));
                            from = to + getOffset(i++);
                        }
                    }
                    ctx.stroke();
                }
            }
        },

        _drawSelected: function(ctx, matrix) {
            ctx.beginPath();
            drawSegments(ctx, this, matrix);
            // Now stroke it and draw its handles:
            ctx.stroke();
            drawHandles(ctx, this._segments, matrix, paper.settings.handleSize);
        }
    };
}, new function() { // Path Smoothing

    /**
     * Solves a tri-diagonal system for one of coordinates (x or y) of first
     * bezier control points.
     *
     * @param rhs right hand side vector.
     * @return Solution vector.
     */
    function getFirstControlPoints(rhs) {
        var n = rhs.length,
            x = [], // Solution vector.
            tmp = [], // Temporary workspace.
            b = 2;
        x[0] = rhs[0] / b;
        // Decomposition and forward substitution.
        for (var i = 1; i < n; i++) {
            tmp[i] = 1 / b;
            b = (i < n - 1 ? 4 : 2) - tmp[i];
            x[i] = (rhs[i] - x[i - 1]) / b;
        }
        // Back-substitution.
        for (var i = 1; i < n; i++) {
            x[n - i - 1] -= tmp[n - i] * x[n - i];
        }
        return x;
    }

    return {
        // Note: Documentation for smooth() is in PathItem
        smooth: function() {
            // This code is based on the work by Oleg V. Polikarpotchkin,
            // http://ov-p.spaces.live.com/blog/cns!39D56F0C7A08D703!147.entry
            // It was extended to support closed paths by averaging overlapping
            // beginnings and ends. The result of this approach is very close to
            // Polikarpotchkin's closed curve solution, but reuses the same
            // algorithm as for open paths, and is probably executing faster as
            // well, so it is preferred.
            var segments = this._segments,
                size = segments.length,
                closed = this._closed,
                n = size,
                // Add overlapping ends for averaging handles in closed paths
                overlap = 0;
            if (size <= 2)
                return;
            if (closed) {
                // Overlap up to 4 points since averaging beziers affect the 4
                // neighboring points
                overlap = Math.min(size, 4);
                n += Math.min(size, overlap) * 2;
            }
            var knots = [];
            for (var i = 0; i < size; i++)
                knots[i + overlap] = segments[i]._point;
            if (closed) {
                // If we're averaging, add the 4 last points again at the
                // beginning, and the 4 first ones at the end.
                for (var i = 0; i < overlap; i++) {
                    knots[i] = segments[i + size - overlap]._point;
                    knots[i + size + overlap] = segments[i]._point;
                }
            } else {
                n--;
            }
            // Calculate first Bezier control points
            // Right hand side vector
            var rhs = [];

            // Set right hand side X values
            for (var i = 1; i < n - 1; i++)
                rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
            rhs[0] = knots[0]._x + 2 * knots[1]._x;
            rhs[n - 1] = 3 * knots[n - 1]._x;
            // Get first control points X-values
            var x = getFirstControlPoints(rhs);

            // Set right hand side Y values
            for (var i = 1; i < n - 1; i++)
                rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
            rhs[0] = knots[0]._y + 2 * knots[1]._y;
            rhs[n - 1] = 3 * knots[n - 1]._y;
            // Get first control points Y-values
            var y = getFirstControlPoints(rhs);

            if (closed) {
                // Do the actual averaging simply by linearly fading between the
                // overlapping values.
                for (var i = 0, j = size; i < overlap; i++, j++) {
                    var f1 = i / overlap,
                        f2 = 1 - f1,
                        ie = i + overlap,
                        je = j + overlap;
                    // Beginning
                    x[j] = x[i] * f1 + x[j] * f2;
                    y[j] = y[i] * f1 + y[j] * f2;
                    // End
                    x[je] = x[ie] * f2 + x[je] * f1;
                    y[je] = y[ie] * f2 + y[je] * f1;
                }
                n--;
            }
            var handleIn = null;
            // Now set the calculated handles
            for (var i = overlap; i <= n - overlap; i++) {
                var segment = segments[i - overlap];
                if (handleIn)
                    segment.setHandleIn(handleIn.subtract(segment._point));
                if (i < n) {
                    segment.setHandleOut(
                            new Point(x[i], y[i]).subtract(segment._point));
                    handleIn = i < n - 1
                            ? new Point(
                                2 * knots[i + 1]._x - x[i + 1],
                                2 * knots[i + 1]._y - y[i + 1])
                            : new Point(
                                (knots[n]._x + x[n - 1]) / 2,
                                (knots[n]._y + y[n - 1]) / 2);
                }
            }
            if (closed && handleIn) {
                var segment = this._segments[0];
                segment.setHandleIn(handleIn.subtract(segment._point));
            }
        }
    };
}, new function() { // PostScript-style drawing commands
    /**
     * Helper method that returns the current segment and checks if a moveTo()
     * command is required first.
     */
    function getCurrentSegment(that) {
        var segments = that._segments;
        if (segments.length === 0)
            throw new Error('Use a moveTo() command first');
        return segments[segments.length - 1];
    }

    return {
        // Note: Documentation for these methods is found in PathItem, as they
        // are considered abstract methods of PathItem and need to be defined in
        // all implementing classes.
        moveTo: function(/* point */) {
            // moveTo should only be called at the beginning of paths. But it
            // can ce called again if there is nothing drawn yet, in which case
            // the first segment gets readjusted.
            var segments = this._segments;
            if (segments.length === 1)
                this.removeSegment(0);
            // Let's not be picky about calling moveTo() when not at the
            // beginning of a path, just bail out:
            if (!segments.length)
                this._add([ new Segment(Point.read(arguments)) ]);
        },

        moveBy: function(/* point */) {
            throw new Error('moveBy() is unsupported on Path items.');
        },

        lineTo: function(/* point */) {
            // Let's not be picky about calling moveTo() first:
            this._add([ new Segment(Point.read(arguments)) ]);
        },

        cubicCurveTo: function(/* handle1, handle2, to */) {
            var handle1 = Point.read(arguments),
                handle2 = Point.read(arguments),
                to = Point.read(arguments),
                // First modify the current segment:
                current = getCurrentSegment(this);
            // Convert to relative values:
            current.setHandleOut(handle1.subtract(current._point));
            // And add the new segment, with handleIn set to c2
            this._add([ new Segment(to, handle2.subtract(to)) ]);
        },

        quadraticCurveTo: function(/* handle, to */) {
            var handle = Point.read(arguments),
                to = Point.read(arguments),
                current = getCurrentSegment(this)._point;
            // This is exact:
            // If we have the three quad points: A E D,
            // and the cubic is A B C D,
            // B = E + 1/3 (A - E)
            // C = E + 1/3 (D - E)
            this.cubicCurveTo(
                handle.add(current.subtract(handle).multiply(1 / 3)),
                handle.add(to.subtract(handle).multiply(1 / 3)),
                to
            );
        },

        curveTo: function(/* through, to, parameter */) {
            var through = Point.read(arguments),
                to = Point.read(arguments),
                t = Base.pick(Base.read(arguments), 0.5),
                t1 = 1 - t,
                current = getCurrentSegment(this)._point,
                // handle = (through - (1 - t)^2 * current - t^2 * to) /
                // (2 * (1 - t) * t)
                handle = through.subtract(current.multiply(t1 * t1))
                    .subtract(to.multiply(t * t)).divide(2 * t * t1);
            if (handle.isNaN())
                throw new Error(
                    'Cannot put a curve through points with parameter = ' + t);
            this.quadraticCurveTo(handle, to);
        },

        arcTo: function(/* to, clockwise | through, to
                | to, radius, rotation, clockwise, large */) {
            // Get the start point:
            var current = getCurrentSegment(this),
                from = current._point,
                to = Point.read(arguments),
                through,
                // Peek at next value to see if it's clockwise, with true as the
                // default value.
                peek = Base.peek(arguments),
                clockwise = Base.pick(peek, true),
                center, extent, vector, matrix;
            // We're handling three different approaches to drawing arcs in one
            // large function:
            if (typeof clockwise === 'boolean') {
                // #1: arcTo(to, clockwise)
                var middle = from.add(to).divide(2),
                through = middle.add(middle.subtract(from).rotate(
                        clockwise ? -90 : 90));
            } else if (Base.remain(arguments) <= 2) {
                // #2: arcTo(through, to)
                through = to;
                to = Point.read(arguments);
            } else {
                // #3: arcTo(to, radius, rotation, clockwise, large)
                // Drawing arcs in SVG style:
                var radius = Size.read(arguments);
                // If rx = 0 or ry = 0 then this arc is treated as a
                // straight line joining the endpoints.
                if (radius.isZero())
                    return this.lineTo(to);
                // See for an explanation of the following calculations:
                // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
                var rotation = Base.read(arguments),
                    clockwise = !!Base.read(arguments),
                    large = !!Base.read(arguments),
                    middle = from.add(to).divide(2),
                    pt = from.subtract(middle).rotate(-rotation),
                    x = pt.x,
                    y = pt.y,
                    abs = Math.abs,
                    epsilon = /*#=*/Numerical.EPSILON,
                    rx = abs(radius.width),
                    ry = abs(radius.height),
                    rxSq = rx * rx,
                    rySq = ry * ry,
                    xSq =  x * x,
                    ySq =  y * y;
                // "...ensure radii are large enough"
                var factor = Math.sqrt(xSq / rxSq + ySq / rySq);
                if (factor > 1) {
                    rx *= factor;
                    ry *= factor;
                    rxSq = rx * rx;
                    rySq = ry * ry;
                }
                factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /
                        (rxSq * ySq + rySq * xSq);
                if (abs(factor) < epsilon)
                    factor = 0;
                if (factor < 0)
                    throw new Error(
                            'Cannot create an arc with the given arguments');
                center = new Point(rx * y / ry, -ry * x / rx)
                        // "...where the + sign is chosen if fA != fS,
                        // and the - sign is chosen if fA = fS."
                        .multiply((large === clockwise ? -1 : 1)
                            * Math.sqrt(factor))
                        .rotate(rotation).add(middle);
                // Now create a matrix that maps the unit circle to the ellipse,
                // for easier construction below.
                matrix = new Matrix().translate(center).rotate(rotation)
                        .scale(rx, ry);
                // Transform from and to to the unit circle coordinate space
                // and calculate start vector and extend from there.
                vector = matrix._inverseTransform(from);
                extent = vector.getDirectedAngle(matrix._inverseTransform(to));
                // "...if fS = 0 and extent is > 0, then subtract 360, whereas
                // if fS = 1 and extend is < 0, then add 360."
                if (!clockwise && extent > 0)
                    extent -= 360;
                else if (clockwise && extent < 0)
                    extent += 360;
            }
            if (through) {
                // Calculate center, vector and extend for non SVG versions:
                // Construct the two perpendicular middle lines to
                // (from, through) and (through, to), and intersect them to get
                // the center.
                var l1 = new Line(from.add(through).divide(2),
                            through.subtract(from).rotate(90), true),
                    l2 = new Line(through.add(to).divide(2),
                            to.subtract(through).rotate(90), true),
                    line = new Line(from, to),
                    throughSide = line.getSide(through);
                center = l1.intersect(l2, true);
                // If the two lines are collinear, there cannot be an arc as the
                // circle is infinitely big and has no center point. If side is
                // 0, the connecting arc line of this huge circle is a line
                // between the two points, so we can use #lineTo instead.
                // Otherwise we bail out:
                if (!center) {
                    if (!throughSide)
                        return this.lineTo(to);
                    throw new Error(
                            'Cannot create an arc with the given arguments');
                }
                vector = from.subtract(center);
                extent = vector.getDirectedAngle(to.subtract(center));
                var centerSide = line.getSide(center);
                if (centerSide === 0) {
                    // If the center is lying on the line, we might have gotten
                    // the wrong sign for extent above. Use the sign of the side
                    // of the through point.
                    extent = throughSide * Math.abs(extent);
                } else if (throughSide === centerSide) {
                    // If the center is on the same side of the line (from, to)
                    // as the through point, we're extending bellow 180 degrees
                    // and need to adapt extent.
                    extent += extent < 0 ? 360 : -360;
                }
            }
            var ext = Math.abs(extent),
                count = ext >= 360 ? 4 : Math.ceil(ext / 90),
                inc = extent / count,
                half = inc * Math.PI / 360,
                z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)),
                segments = [];
            for (var i = 0; i <= count; i++) {
                // Explicitly use to point for last segment, since depending
                // on values the calculation adds imprecision:
                var pt = to,
                    out = null;
                if (i < count) {
                    out = vector.rotate(90).multiply(z);
                    if (matrix) {
                        pt = matrix._transformPoint(vector);
                        out = matrix._transformPoint(vector.add(out))
                                .subtract(pt);
                    } else {
                        pt = center.add(vector);
                    }
                }
                if (i === 0) {
                    // Modify startSegment
                    current.setHandleOut(out);
                } else {
                    // Add new Segment
                    var _in = vector.rotate(-90).multiply(z);
                    if (matrix) {
                        _in = matrix._transformPoint(vector.add(_in))
                                .subtract(pt);
                    }
                    segments.push(new Segment(pt, _in, out));
                }
                vector = vector.rotate(inc);
            }
            // Add all segments at once at the end for higher performance
            this._add(segments);
        },

        lineBy: function(/* to */) {
            var to = Point.read(arguments),
                current = getCurrentSegment(this)._point;
            this.lineTo(current.add(to));
        },

        curveBy: function(/* through, to, parameter */) {
            var through = Point.read(arguments),
                to = Point.read(arguments),
                parameter = Base.read(arguments),
                current = getCurrentSegment(this)._point;
            this.curveTo(current.add(through), current.add(to), parameter);
        },

        cubicCurveBy: function(/* handle1, handle2, to */) {
            var handle1 = Point.read(arguments),
                handle2 = Point.read(arguments),
                to = Point.read(arguments),
                current = getCurrentSegment(this)._point;
            this.cubicCurveTo(current.add(handle1), current.add(handle2),
                    current.add(to));
        },

        quadraticCurveBy: function(/* handle, to */) {
            var handle = Point.read(arguments),
                to = Point.read(arguments),
                current = getCurrentSegment(this)._point;
            this.quadraticCurveTo(current.add(handle), current.add(to));
        },

        // TODO: Implement version for: (to, radius, rotation, clockwise, large)
        arcBy: function(/* to, clockwise | through, to */) {
            var current = getCurrentSegment(this)._point,
                point = current.add(Point.read(arguments)),
                // Peek at next value to see if it's clockwise, with true as
                // default value.
                clockwise = Base.pick(Base.peek(arguments), true);
            if (typeof clockwise === 'boolean') {
                this.arcTo(point, clockwise);
            } else {
                this.arcTo(point, current.add(Point.read(arguments)));
            }
        },

        closePath: function(join) {
            this.setClosed(true);
            if (join)
                this.join();
        }
    };
}, {  // A dedicated scope for the tricky bounds calculations
    // We define all the different getBounds functions as static methods on Path
    // and have #_getBounds directly access these. All static bounds functions
    // below have the same first four parameters: segments, closed, style,
    // matrix, so they can be called from #_getBounds() and also be used in
    // Curve. But not all of them use all these parameters, and some define
    // additional ones after.

    _getBounds: function(getter, matrix) {
        // See #draw() for an explanation of why we can access _style
        // properties directly here:
        return Path[getter](this._segments, this._closed, this.getStyle(),
                matrix);
    },

// Mess with indentation in order to get more line-space below:
statics: {
    /**
     * Determines whether the segments describe a path in clockwise or counter-
     * clockwise orientation.
     *
     * @private
     */
    isClockwise: function(segments) {
        var sum = 0;
        // TODO: Check if this works correctly for all open paths.
        for (var i = 0, l = segments.length; i < l; i++)
            sum += Curve.getEdgeSum(Curve.getValues(
                    segments[i], segments[i + 1 < l ? i + 1 : 0]));
        return sum > 0;
    },

    /**
     * Returns the bounding rectangle of the item excluding stroke width.
     *
     * @private
     */
    getBounds: function(segments, closed, style, matrix, strokePadding) {
        var first = segments[0];
        // If there are no segments, return "empty" rectangle, just like groups,
        // since #bounds is assumed to never return null.
        if (!first)
            return new Rectangle();
        var coords = new Array(6),
            // Make coordinates for first segment available in prevCoords.
            prevCoords = first._transformCoordinates(matrix, new Array(6), false),
            min = prevCoords.slice(0, 2), // Start with values of first point
            max = min.slice(), // clone
            roots = new Array(2);

        function processSegment(segment) {
            segment._transformCoordinates(matrix, coords, false);
            for (var i = 0; i < 2; i++) {
                Curve._addBounds(
                    prevCoords[i], // prev.point
                    prevCoords[i + 4], // prev.handleOut
                    coords[i + 2], // segment.handleIn
                    coords[i], // segment.point,
                    i, strokePadding ? strokePadding[i] : 0, min, max, roots);
            }
            // Swap coordinate buffers.
            var tmp = prevCoords;
            prevCoords = coords;
            coords = tmp;
        }

        for (var i = 1, l = segments.length; i < l; i++)
            processSegment(segments[i]);
        if (closed)
            processSegment(first);
        return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
    },

    /**
     * Returns the bounding rectangle of the item including stroke width.
     *
     * @private
     */
    getStrokeBounds: function(segments, closed, style, matrix) {
        // TODO: Find a way to reuse 'bounds' cache instead?
        if (!style.hasStroke())
            return Path.getBounds(segments, closed, style, matrix);
        var length = segments.length - (closed ? 0 : 1),
            radius = style.getStrokeWidth() / 2,
            padding = Path._getPenPadding(radius, matrix),
            bounds = Path.getBounds(segments, closed, style, matrix, padding),
            join = style.getStrokeJoin(),
            cap = style.getStrokeCap(),
            miterLimit = radius * style.getMiterLimit();
        // Create a rectangle of padding size, used for union with bounds
        // further down
        var joinBounds = new Rectangle(new Size(padding).multiply(2));

        function add(point) {
            bounds = bounds.include(matrix
                ? matrix._transformPoint(point, point) : point);
        }

        function addRound(segment) {
            bounds = bounds.unite(joinBounds.setCenter(matrix
                ? matrix._transformPoint(segment._point) : segment._point));
        }

        function addJoin(segment, join) {
            // When both handles are set in a segment and they are collinear,
            // the join setting is ignored and round is always used.
            var handleIn = segment._handleIn,
                handleOut = segment._handleOut;
            if (join === 'round' || !handleIn.isZero() && !handleOut.isZero()
                    && handleIn.isColinear(handleOut)) {
                addRound(segment);
            } else {
                Path._addBevelJoin(segment, join, radius, miterLimit, add);
            }
        }

        function addCap(segment, cap) {
            if (cap === 'round') {
                addRound(segment);
            } else {
                Path._addSquareCap(segment, cap, radius, add);
            }
        }

        for (var i = 1; i < length; i++)
            addJoin(segments[i], join);
        if (closed) {
            addJoin(segments[0], join);
        } else if (length > 0) {
            addCap(segments[0], cap);
            addCap(segments[segments.length - 1], cap);
        }
        return bounds;
    },

    /**
     * Returns the horizontal and vertical padding that a transformed round
     * stroke adds to the bounding box, by calculating the dimensions of a
     * rotated ellipse.
     */
    _getPenPadding: function(radius, matrix) {
        if (!matrix)
            return [radius, radius];
        // If a matrix is provided, we need to rotate the stroke circle
        // and calculate the bounding box of the resulting rotated elipse:
        // Get rotated hor and ver vectors, and determine rotation angle
        // and elipse values from them:
        var mx = matrix.shiftless(),
            hor = mx.transform(new Point(radius, 0)),
            ver = mx.transform(new Point(0, radius)),
            phi = hor.getAngleInRadians(),
            a = hor.getLength(),
            b = ver.getLength();
        // Formula for rotated ellipses:
        // x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
        // y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
        // Derivates (by Wolfram Alpha):
        // derivative of x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
        // dx/dt = a sin(t) cos(phi) + b cos(t) sin(phi) = 0
        // derivative of y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
        // dy/dt = b cos(t) cos(phi) - a sin(t) sin(phi) = 0
        // This can be simplified to:
        // tan(t) = -b * tan(phi) / a // x
        // tan(t) =  b * cot(phi) / a // y
        // Solving for t gives:
        // t = pi * n - arctan(b * tan(phi) / a) // x
        // t = pi * n + arctan(b * cot(phi) / a)
        //   = pi * n + arctan(b / tan(phi) / a) // y
        var sin = Math.sin(phi),
            cos = Math.cos(phi),
            tan = Math.tan(phi),
            tx = -Math.atan(b * tan / a),
            ty = Math.atan(b / (tan * a));
        // Due to symetry, we don't need to cycle through pi * n solutions:
        return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
                Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
    },

    _addBevelJoin: function(segment, join, radius, miterLimit, addPoint, area) {
        // Handles both 'bevel' and 'miter' joins, as they share a lot of code.
        var curve2 = segment.getCurve(),
            curve1 = curve2.getPrevious(),
            point = curve2.getPointAt(0, true),
            normal1 = curve1.getNormalAt(1, true),
            normal2 = curve2.getNormalAt(0, true),
            step = normal1.getDirectedAngle(normal2) < 0 ? -radius : radius;
        normal1.setLength(step);
        normal2.setLength(step);
        if (area) {
            addPoint(point);
            addPoint(point.add(normal1));
        }
        if (join === 'miter') {
            // Intersect the two lines
            var corner = new Line(
                    point.add(normal1),
                    new Point(-normal1.y, normal1.x), true
                ).intersect(new Line(
                    point.add(normal2),
                    new Point(-normal2.y, normal2.x), true
                ), true);
            // See if we actually get a bevel point and if its distance is below
            // the miterLimit. If not, make a normal bevel.
            if (corner && point.getDistance(corner) <= miterLimit) {
                addPoint(corner);
                if (!area)
                    return;
            }
        }
        // Produce a normal bevel
        if (!area)
            addPoint(point.add(normal1));
        addPoint(point.add(normal2));
    },

    _addSquareCap: function(segment, cap, radius, addPoint, area) {
        // Handles both 'square' and 'butt' caps, as they share a lot of code.
        // Calculate the corner points of butt and square caps
        var point = segment._point,
            loc = segment.getLocation(),
            normal = loc.getNormal().normalize(radius);
        if (area) {
            addPoint(point.subtract(normal));
            addPoint(point.add(normal));
        }
        // For square caps, we need to step away from point in the direction of
        // the tangent, which is the rotated normal.
        // Checking loc.getParameter() for 0 is to see whether this is the first
        // or the last segment of the open path, in order to determine in which
        // direction to move the point.
        if (cap === 'square')
            point = point.add(normal.rotate(loc.getParameter() === 0 ? -90 : 90));
        addPoint(point.add(normal));
        addPoint(point.subtract(normal));
    },

    /**
     * Returns the bounding rectangle of the item including handles.
     *
     * @private
     */
    getHandleBounds: function(segments, closed, style, matrix, strokePadding,
            joinPadding) {
        var coords = new Array(6),
            x1 = Infinity,
            x2 = -x1,
            y1 = x1,
            y2 = x2;
        for (var i = 0, l = segments.length; i < l; i++) {
            var segment = segments[i];
            segment._transformCoordinates(matrix, coords, false);
            for (var j = 0; j < 6; j += 2) {
                // Use different padding for points or handles
                var padding = j === 0 ? joinPadding : strokePadding,
                    paddingX = padding ? padding[0] : 0,
                    paddingY = padding ? padding[1] : 0,
                    x = coords[j],
                    y = coords[j + 1],
                    xn = x - paddingX,
                    xx = x + paddingX,
                    yn = y - paddingY,
                    yx = y + paddingY;
                if (xn < x1) x1 = xn;
                if (xx > x2) x2 = xx;
                if (yn < y1) y1 = yn;
                if (yx > y2) y2 = yx;
            }
        }
        return new Rectangle(x1, y1, x2 - x1, y2 - y1);
    },

    /**
     * Returns the rough bounding rectangle of the item that is sure to include
     * all of the drawing, including stroke width.
     *
     * @private
     */
    getRoughBounds: function(segments, closed, style, matrix) {
        // Delegate to handleBounds, but pass on radius values for stroke and
        // joins. Hanlde miter joins specially, by passing the largets radius
        // possible.
        var strokeRadius = style.hasStroke() ? style.getStrokeWidth() / 2 : 0,
            joinRadius = strokeRadius;
        if (strokeRadius > 0) {
            if (style.getStrokeJoin() === 'miter')
                joinRadius = strokeRadius * style.getMiterLimit();
            if (style.getStrokeCap() === 'square')
                joinRadius = Math.max(joinRadius, strokeRadius * Math.sqrt(2));
        }
        return Path.getHandleBounds(segments, closed, style, matrix,
                Path._getPenPadding(strokeRadius, matrix),
                Path._getPenPadding(joinRadius, matrix));
    }
}});
