From d168fd6190dd88003acd5f8914f35626ed726e8e Mon Sep 17 00:00:00 2001 From: Rudrak'sPC Date: Wed, 17 Nov 2021 17:41:33 +0530 Subject: [PATCH] draw --- dist/paper.d.ts | 4959 +++++++++++---------- src/core/PaperScope.js | 538 +-- src/item/Item.js | 9511 ++++++++++++++++++++-------------------- src/item/Project.js | 1705 +++---- src/style/Style.js | 1292 +++--- 5 files changed, 9130 insertions(+), 8875 deletions(-) diff --git a/dist/paper.d.ts b/dist/paper.d.ts index 40a14ad113..0d556135c3 100644 --- a/dist/paper.d.ts +++ b/dist/paper.d.ts @@ -15,87 +15,85 @@ */ declare namespace paper { - - /** + /** * All properties and functions that expect color values in the form * of instances of Color objects, also accept named colors and hex values as * strings which are then converted to instances of * {@link Color} internally. */ - class Color { - /** + class Color { + /** * The type of the color as a string. */ - type: string + type: string; - /** + /** * The color components that define the color, including the alpha value * if defined. */ - readonly components: number[] + readonly components: number[]; - /** + /** * The color's alpha value as a number between `0` and `1`. * All colors of the different subclasses support alpha values. */ - alpha: number + alpha: number; - /** + /** * The amount of red in the color as a value between `0` and `1`. */ - red: number + red: number; - /** + /** * The amount of green in the color as a value between `0` and `1`. */ - green: number + green: number; - /** + /** * The amount of blue in the color as a value between `0` and `1`. */ - blue: number + blue: number; - /** + /** * The amount of gray in the color as a value between `0` and `1`. */ - gray: number + gray: number; - /** + /** * The hue of the color as a value in degrees between `0` and `360`. */ - hue: number + hue: number; - /** + /** * The saturation of the color as a value between `0` and `1`. */ - saturation: number + saturation: number; - /** + /** * The brightness of the color as a value between `0` and `1`. */ - brightness: number + brightness: number; - /** + /** * The lightness of the color as a value between `0` and `1`. - * + * * Note that all other components are shared with HSB. */ - lightness: number + lightness: number; - /** + /** * The gradient object describing the type of gradient and the stops. */ - gradient: Gradient + gradient: Gradient; - /** + /** * The highlight point of the gradient. */ - highlight: Point - + highlight: Point; - /** + /** * Creates a RGB Color object. - * + * * @param red - the amount of red in the color as a value between * `0` and `1` * @param green - the amount of green in the color as a value @@ -105,19 +103,19 @@ declare namespace paper { * @param alpha - the alpha of the color as a value between `0` * and `1` */ - constructor(red: number, green: number, blue: number, alpha?: number) + constructor(red: number, green: number, blue: number, alpha?: number); - /** + /** * Creates a gray Color object. - * + * * @param gray - the amount of gray in the color as a value * between `0` and `1` * @param alpha - the alpha of the color as a value between `0` * and `1` */ - constructor(gray: number, alpha?: number) + constructor(gray: number, alpha?: number); - /** + /** * Creates a Color object from a CSS string. All common CSS color string * formats are supported: * - Named colors (e.g. `'red'`, `'fuchsia'`, …) @@ -125,20 +123,25 @@ declare namespace paper { * - RGB strings (`'rgb(255, 128, 0)'`, `'rgba(255, 128, 0, 0.5)'`, …) * - HSL strings (`'hsl(180deg, 20%, 50%)'`, * `'hsla(3.14rad, 20%, 50%, 0.5)'`, …) - * + * * @param color - the color's CSS string representation */ - constructor(color: string) + constructor(color: string); - /** + /** * Creates a gradient Color object. */ - constructor(gradient: Gradient, origin: Point, destination: Point, highlight?: Point) + constructor( + gradient: Gradient, + origin: Point, + destination: Point, + highlight?: Point + ); - /** + /** * Creates a HSB, HSL or gradient Color object from the properties of * the provided object: - * + * * @option hsb.hue {Number} the hue of the color as a value in degrees * between `0` and `360` * @option hsb.saturation {Number} the saturation of the color as a @@ -164,265 +167,262 @@ declare namespace paper { * the gradient, as an alternative to providing a gradient object * @option gradient.radial {Boolean} controls whether the gradient is * radial, as an alternative to providing a gradient object - * + * * @param object - an object describing the components and * properties of the color */ - constructor(object: object) + constructor(object: object); - /** + /** * Sets the color to the passed values. Note that any sequence of * parameters that is supported by the various {@link Color} * constructors also work for calls of `set()`. */ - set(...values: any[]): Color + set(...values: any[]): Color; - /** + /** * Converts the color to another type. - * + * * @param type - the color type to convert to. Possible values: * {@values 'rgb', 'gray', 'hsb', 'hsl'} - * + * * @return the converted color as a new instance */ - convert(type: string): Color + convert(type: string): Color; - /** + /** * Checks if the color has an alpha value. - * + * * @return true if the color has an alpha value */ - hasAlpha(): boolean + hasAlpha(): boolean; - /** + /** * Checks if the component color values of the color are the * same as those of the supplied one. - * + * * @param color - the color to compare with - * + * * @return true if the colors are the same */ - equals(color: Color): boolean + equals(color: Color): boolean; - /** + /** * @return a copy of the color object */ - clone(): Color + clone(): Color; - /** + /** * @return a string representation of the color */ - toString(): string + toString(): string; - /** + /** * Returns the color as a CSS string. - * + * * @param hex - whether to return the color in hexadecimal * representation or as a CSS RGB / RGBA string. - * + * * @return a CSS string representation of the color */ - toCSS(hex: boolean): string + toCSS(hex: boolean): string; - /** + /** * Transform the gradient color by the specified matrix. - * + * * @param matrix - the matrix to transform the gradient color by */ - transform(matrix: Matrix): void + transform(matrix: Matrix): void; - /** + /** * Returns a color object with random {@link #red}, {@link #green} * and {@link #blue} values between `0` and `1`. - * + * * @return the newly created color object */ - static random(): Color + static random(): Color; - /** + /** * Returns the addition of the supplied value to both coordinates of * the color as a new color. * The object itself is not modified! - * + * * @param number - the number to add - * + * * @return the addition of the color and the value as a new * color */ - add(number: number): Color + add(number: number): Color; - /** + /** * Returns the addition of the supplied color to the color as a new * color. * The object itself is not modified! - * + * * @param color - the color to add - * + * * @return the addition of the two colors as a new color */ - add(color: Color): Color + add(color: Color): Color; - /** + /** * Returns the subtraction of the supplied value to both coordinates of * the color as a new color. * The object itself is not modified! - * + * * @param number - the number to subtract - * + * * @return the subtraction of the color and the value as a new * color */ - subtract(number: number): Color + subtract(number: number): Color; - /** + /** * Returns the subtraction of the supplied color to the color as a new * color. * The object itself is not modified! - * + * * @param color - the color to subtract - * + * * @return the subtraction of the two colors as a new color */ - subtract(color: Color): Color + subtract(color: Color): Color; - /** + /** * Returns the multiplication of the supplied value to both coordinates * of the color as a new color. * The object itself is not modified! - * + * * @param number - the number to multiply - * + * * @return the multiplication of the color and the value as a * new color */ - multiply(number: number): Color + multiply(number: number): Color; - /** + /** * Returns the multiplication of the supplied color to the color as a * new color. * The object itself is not modified! - * + * * @param color - the color to multiply - * + * * @return the multiplication of the two colors as a new color */ - multiply(color: Color): Color + multiply(color: Color): Color; - /** + /** * Returns the division of the supplied value to both coordinates of * the color as a new color. * The object itself is not modified! - * + * * @param number - the number to divide - * + * * @return the division of the color and the value as a new * color */ - divide(number: number): Color + divide(number: number): Color; - /** + /** * Returns the division of the supplied color to the color as a new * color. * The object itself is not modified! - * + * * @param color - the color to divide - * + * * @return the division of the two colors as a new color */ - divide(color: Color): Color - + divide(color: Color): Color; } - /** + /** * A compound path is a complex path that is made up of one or more * simple sub-paths. It can have the `nonzero` fill rule, or the `evenodd` rule * applied. Both rules use mathematical equations to determine if any region is * outside or inside the final shape. The `evenodd` rule is more predictable: * Every other region within a such a compound path is a hole, regardless of * path direction. - * + * * All the paths in a compound path take on the style of the compound path and * can be accessed through its {@link Item#children} list. */ class CompoundPath extends PathItem { - /** + /** * Specifies whether the compound-path is fully closed, meaning all its * contained sub-paths are closed path. - * + * * @see Path#closed */ - closed: boolean + closed: boolean; - /** + /** * The first Segment contained within the compound-path, a short-cut to * calling {@link Path#firstSegment} on {@link Item#firstChild}. */ - readonly firstSegment: Segment + readonly firstSegment: Segment; - /** + /** * The last Segment contained within the compound-path, a short-cut to * calling {@link Path#lastSegment} on {@link Item#lastChild}. */ - readonly lastSegment: Segment + readonly lastSegment: Segment; - /** + /** * All the curves contained within the compound-path, from all its child * {@link Path} items. */ - readonly curves: Curve[] + readonly curves: Curve[]; - /** + /** * The first Curve contained within the compound-path, a short-cut to * calling {@link Path#firstCurve} on {@link Item#firstChild}. */ - readonly firstCurve: Curve + readonly firstCurve: Curve; - /** + /** * The last Curve contained within the compound-path, a short-cut to * calling {@link Path#lastCurve} on {@link Item#lastChild}. */ - readonly lastCurve: Curve + readonly lastCurve: Curve; - /** + /** * The area that the compound-path's geometry is covering, calculated by * getting the {@link Path#area} of each sub-path and it adding up. * Note that self-intersecting paths and sub-paths of different orientation * can result in areas that cancel each other out. */ - readonly area: number + readonly area: number; - /** + /** * The total length of all sub-paths in this compound-path, calculated by * getting the {@link Path#length} of each sub-path and it adding up. */ - readonly length: number + readonly length: number; - - /** + /** * Creates a new compound path item from SVG path-data and places it at the * top of the active layer. - * + * * @param pathData - the SVG path-data that describes the geometry * of this path */ - constructor(pathData: string) + constructor(pathData: string); - /** + /** * Creates a new compound path item from an object description and places it * at the top of the active layer. - * + * * @param object - an object containing properties to be set on the * path */ - constructor(object: object) - + constructor(object: object); } - /** + /** * The Curve object represents the parts of a path that are connected by * two following {@link Segment} objects. The curves of a path can be accessed * through its {@link Path#curves} array. - * + * * While a segment describe the anchor point and its incoming and outgoing * handles, a Curve object describes the curve passing between two such * segments. Curves and segments represent two different ways of looking at the @@ -430,570 +430,573 @@ declare namespace paper { * convenient ways to work with parts of the path, finding lengths, positions or * tangents at given offsets. */ - class Curve { - /** + class Curve { + /** * The first anchor point of the curve. */ - point1: Point + point1: Point; - /** + /** * The second anchor point of the curve. */ - point2: Point + point2: Point; - /** + /** * The handle point that describes the tangent in the first anchor point. */ - handle1: Point + handle1: Point; - /** + /** * The handle point that describes the tangent in the second anchor point. */ - handle2: Point + handle2: Point; - /** + /** * The first segment of the curve. */ - readonly segment1: Segment + readonly segment1: Segment; - /** + /** * The second segment of the curve. */ - readonly segment2: Segment + readonly segment2: Segment; - /** + /** * The path that the curve belongs to. */ - readonly path: Path + readonly path: Path; - /** + /** * The index of the curve in the {@link Path#curves} array. */ - readonly index: number + readonly index: number; - /** + /** * The next curve in the {@link Path#curves} array that the curve * belongs to. */ - readonly next: Curve + readonly next: Curve; - /** + /** * The previous curve in the {@link Path#curves} array that the curve * belongs to. */ - readonly previous: Curve + readonly previous: Curve; - /** + /** * Specifies whether the points and handles of the curve are selected. */ - selected: boolean + selected: boolean; - /** + /** * An array of 8 float values, describing this curve's geometry in four * absolute x/y pairs (point1, handle1, handle2, point2). This format is * used internally for efficient processing of curve geometries, e.g. when * calculating intersections or bounds. - * + * * Note that the handles are converted to absolute coordinates. */ - readonly values: number[] + readonly values: number[]; - /** + /** * An array of 4 point objects, describing this curve's geometry in absolute * coordinates (point1, handle1, handle2, point2). - * + * * Note that the handles are converted to absolute coordinates. */ - readonly points: Point[] + readonly points: Point[]; - /** + /** * The approximated length of the curve. */ - readonly length: number + readonly length: number; - /** + /** * The area that the curve's geometry is covering. */ - readonly area: number + readonly area: number; - /** + /** * The bounding rectangle of the curve excluding stroke width. */ - bounds: Rectangle + bounds: Rectangle; - /** + /** * The bounding rectangle of the curve including stroke width. */ - strokeBounds: Rectangle + strokeBounds: Rectangle; - /** + /** * The bounding rectangle of the curve including handles. */ - handleBounds: Rectangle - + handleBounds: Rectangle; - /** + /** * Creates a new curve object. */ - constructor(segment1: Segment, segment2: Segment) + constructor(segment1: Segment, segment2: Segment); - /** + /** * Creates a new curve object. */ - constructor(point1: Point, handle1: Point, handle2: Point, point2: Point) + constructor( + point1: Point, + handle1: Point, + handle2: Point, + point2: Point + ); - /** + /** * Returns a copy of the curve. */ - clone(): Curve + clone(): Curve; - /** + /** * @return a string representation of the curve */ - toString(): string + toString(): string; - /** + /** * Determines the type of cubic Bézier curve via discriminant * classification, as well as the curve-time parameters of the associated * points of inflection, loops, cusps, etc. - * + * * @return the curve classification information as an object, see * options */ - classify(): object + classify(): object; - /** + /** * Removes the curve from the path that it belongs to, by removing its * second segment and merging its handle with the first segment. - * + * * @return true if the curve was removed */ - remove(): boolean + remove(): boolean; - /** + /** * Checks if the this is the first curve in the {@link Path#curves} array. - * + * * @return true if this is the first curve */ - isFirst(): boolean + isFirst(): boolean; - /** + /** * Checks if the this is the last curve in the {@link Path#curves} array. - * + * * @return true if this is the last curve */ - isLast(): boolean + isLast(): boolean; - /** + /** * Creates a new curve as a sub-curve from this curve, its range defined by * the given curve-time parameters. If `from` is larger than `to`, then * the resulting curve will have its direction reversed. - * + * * @param from - the curve-time parameter at which the sub-curve * starts * @param to - the curve-time parameter at which the sub-curve * ends - * + * * @return the newly create sub-curve */ - getPart(from: number, to: number): Curve + getPart(from: number, to: number): Curve; - /** + /** * Divides the curve into two curves at the given offset or location. The * curve itself is modified and becomes the first part, the second part is * returned as a new curve. If the curve belongs to a path item, a new * segment is inserted into the path at the given location, and the second * part becomes a part of the path as well. - * + * * @see #divideAtTime(time) - * + * * @param location - the offset or location on the * curve at which to divide - * + * * @return the second part of the divided curve if the location is * valid, {code null} otherwise */ - divideAt(location: number | CurveLocation): Curve + divideAt(location: number | CurveLocation): Curve; - /** + /** * Divides the curve into two curves at the given curve-time parameter. The * curve itself is modified and becomes the first part, the second part is * returned as a new curve. If the modified curve belongs to a path item, * the second part is also added to the path. - * + * * @see #divideAt(offset) - * + * * @param time - the curve-time parameter on the curve at which to * divide - * + * * @return the second part of the divided curve, if the offset is * within the valid range, {code null} otherwise. */ - divideAtTime(time: number): Curve + divideAtTime(time: number): Curve; - /** + /** * Splits the path this curve belongs to at the given offset. After * splitting, the path will be open. If the path was open already, splitting * will result in two paths. - * + * * @see Path#splitAt(offset) - * + * * @param location - the offset or location on the * curve at which to split - * + * * @return the newly created path after splitting, if any */ - splitAt(location: number | CurveLocation): Path + splitAt(location: number | CurveLocation): Path; - /** + /** * Splits the path this curve belongs to at the given offset. After * splitting, the path will be open. If the path was open already, splitting * will result in two paths. - * + * * @see Path#splitAt(offset) - * + * * @param time - the curve-time parameter on the curve at which to * split - * + * * @return the newly created path after splitting, if any */ - splitAtTime(time: number): Path + splitAtTime(time: number): Path; - /** + /** * Returns a reversed version of the curve, without modifying the curve * itself. - * + * * @return a reversed version of the curve */ - reversed(): Curve + reversed(): Curve; - /** + /** * Clears the curve's handles by setting their coordinates to zero, * turning the curve into a straight line. */ - clearHandles(): void + clearHandles(): void; - /** + /** * Checks if this curve has any curve handles set. - * + * * @see Curve#handle1 * @see Curve#handle2 * @see Segment#hasHandles() * @see Path#hasHandles() - * + * * @return true if the curve has handles set */ - hasHandles(): boolean + hasHandles(): boolean; - /** + /** * Checks if this curve has any length. - * + * * @param epsilon - the epsilon against which to compare the * curve's length - * + * * @return true if the curve is longer than the given epsilon */ - hasLength(epsilon?: number): boolean + hasLength(epsilon?: number): boolean; - /** + /** * Checks if this curve appears as a straight line. This can mean that * it has no handles defined, or that the handles run collinear with the * line that connects the curve's start and end point, not falling * outside of the line. - * + * * @return true if the curve is straight */ - isStraight(): boolean + isStraight(): boolean; - /** + /** * Checks if this curve is parametrically linear, meaning that it is * straight and its handles are positioned at 1/3 and 2/3 of the total * length of the curve. - * + * * @return true if the curve is parametrically linear */ - isLinear(): boolean + isLinear(): boolean; - /** + /** * Checks if the the two curves describe straight lines that are * collinear, meaning they run in parallel. - * + * * @param curve - the other curve to check against - * + * * @return true if the two lines are collinear */ - isCollinear(curve: Curve): boolean + isCollinear(curve: Curve): boolean; - /** + /** * Checks if the curve is a straight horizontal line. - * + * * @return true if the line is horizontal */ - isHorizontal(): boolean + isHorizontal(): boolean; - /** + /** * Checks if the curve is a straight vertical line. - * + * * @return true if the line is vertical */ - isVertical(): boolean + isVertical(): boolean; - /** + /** * Calculates the curve location at the specified offset on the curve. - * + * * @param offset - the offset on the curve - * + * * @return the curve location at the specified the offset */ - getLocationAt(offset: number): CurveLocation + getLocationAt(offset: number): CurveLocation; - /** + /** * Calculates the curve location at the specified curve-time parameter on * the curve. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the curve location at the specified the location */ - getLocationAtTime(time: number): CurveLocation + getLocationAtTime(time: number): CurveLocation; - /** + /** * Calculates the curve-time parameter of the specified offset on the path, * relative to the provided start parameter. If offset is a negative value, * the parameter is searched to the left of the start parameter. If no start * parameter is provided, a default of `0` for positive values of `offset` * and `1` for negative values of `offset`. - * + * * @param offset - the offset at which to find the curve-time, in * curve length units * @param start - the curve-time in relation to which the offset is * determined - * + * * @return the curve-time parameter at the specified location */ - getTimeAt(offset: number, start?: number): number + getTimeAt(offset: number, start?: number): number; - /** + /** * Calculates the curve-time parameters where the curve is tangential to * provided tangent. Note that tangents at the start or end are included. - * + * * @param tangent - the tangent to which the curve must be tangential - * + * * @return at most two curve-time parameters, where the curve is * tangential to the given tangent */ - getTimesWithTangent(tangent: Point): number[] + getTimesWithTangent(tangent: Point): number[]; - /** + /** * Calculates the curve offset at the specified curve-time parameter on * the curve. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the curve offset at the specified the location */ - getOffsetAtTime(time: number): number + getOffsetAtTime(time: number): number; - /** + /** * Returns the curve location of the specified point if it lies on the * curve, `null` otherwise. - * + * * @param point - the point on the curve - * + * * @return the curve location of the specified point */ - getLocationOf(point: Point): CurveLocation + getLocationOf(point: Point): CurveLocation; - /** + /** * Returns the length of the path from its beginning up to up to the * specified point if it lies on the path, `null` otherwise. - * + * * @param point - the point on the path - * + * * @return the length of the path up to the specified point */ - getOffsetOf(point: Point): number + getOffsetOf(point: Point): number; - /** + /** * Returns the curve-time parameter of the specified point if it lies on the * curve, `null` otherwise. * Note that if there is more than one possible solution in a * self-intersecting curve, the first found result is returned. - * + * * @param point - the point on the curve - * + * * @return the curve-time parameter of the specified point */ - getTimeOf(point: Point): number + getTimeOf(point: Point): number; - /** + /** * Returns the nearest location on the curve to the specified point. - * + * * @param point - the point for which we search the nearest location - * + * * @return the location on the curve that's the closest to * the specified point */ - getNearestLocation(point: Point): CurveLocation + getNearestLocation(point: Point): CurveLocation; - /** + /** * Returns the nearest point on the curve to the specified point. - * + * * @param point - the point for which we search the nearest point - * + * * @return the point on the curve that's the closest to the * specified point */ - getNearestPoint(point: Point): Point + getNearestPoint(point: Point): Point; - /** + /** * Calculates the point on the curve at the given location. - * + * * @param location - the offset or location on the * curve - * + * * @return the point on the curve at the given location */ - getPointAt(location: number | CurveLocation): Point + getPointAt(location: number | CurveLocation): Point; - /** + /** * Calculates the normalized tangent vector of the curve at the given * location. - * + * * @param location - the offset or location on the * curve - * + * * @return the normalized tangent of the curve at the given location */ - getTangentAt(location: number | CurveLocation): Point + getTangentAt(location: number | CurveLocation): Point; - /** + /** * Calculates the normal vector of the curve at the given location. - * + * * @param location - the offset or location on the * curve - * + * * @return the normal of the curve at the given location */ - getNormalAt(location: number | CurveLocation): Point + getNormalAt(location: number | CurveLocation): Point; - /** + /** * Calculates the weighted tangent vector of the curve at the given * location, its length reflecting the curve velocity at that location. - * + * * @param location - the offset or location on the * curve - * + * * @return the weighted tangent of the curve at the given location */ - getWeightedTangentAt(location: number | CurveLocation): Point + getWeightedTangentAt(location: number | CurveLocation): Point; - /** + /** * Calculates the weighted normal vector of the curve at the given location, * its length reflecting the curve velocity at that location. - * + * * @param location - the offset or location on the * curve - * + * * @return the weighted normal of the curve at the given location */ - getWeightedNormalAt(location: number | CurveLocation): Point + getWeightedNormalAt(location: number | CurveLocation): Point; - /** + /** * Calculates the curvature of the curve at the given location. Curvatures * indicate how sharply a curve changes direction. A straight line has zero * curvature, where as a circle has a constant curvature. The curve's radius * at the given location is the reciprocal value of its curvature. - * + * * @param location - the offset or location on the * curve - * + * * @return the curvature of the curve at the given location */ - getCurvatureAt(location: number | CurveLocation): number + getCurvatureAt(location: number | CurveLocation): number; - /** + /** * Calculates the point on the curve at the given location. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the point on the curve at the given location */ - getPointAtTime(time: number): Point + getPointAtTime(time: number): Point; - /** + /** * Calculates the normalized tangent vector of the curve at the given * location. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the normalized tangent of the curve at the given location */ - getTangentAtTime(time: number): Point + getTangentAtTime(time: number): Point; - /** + /** * Calculates the normal vector of the curve at the given location. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the normal of the curve at the given location */ - getNormalAtTime(time: number): Point + getNormalAtTime(time: number): Point; - /** + /** * Calculates the weighted tangent vector of the curve at the given * location, its length reflecting the curve velocity at that location. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the weighted tangent of the curve at the given location */ - getWeightedTangentAtTime(time: number): Point + getWeightedTangentAtTime(time: number): Point; - /** + /** * Calculates the weighted normal vector of the curve at the given location, * its length reflecting the curve velocity at that location. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the weighted normal of the curve at the given location */ - getWeightedNormalAtTime(time: number): Point + getWeightedNormalAtTime(time: number): Point; - /** + /** * Calculates the curvature of the curve at the given location. Curvatures * indicate how sharply a curve changes direction. A straight line has zero * curvature, where as a circle has a constant curvature. The curve's radius * at the given location is the reciprocal value of its curvature. - * + * * @param time - the curve-time parameter on the curve - * + * * @return the curvature of the curve at the given location */ - getCurvatureAtTime(time: number): number + getCurvatureAtTime(time: number): number; - /** + /** * Returns all intersections between two {@link Curve} objects as an * array of {@link CurveLocation} objects. - * + * * @param curve - the other curve to find the intersections with * (if the curve itself or `null` is passed, the self intersection * of the curve is returned, if it exists) - * + * * @return the locations of all intersections between * the curves */ - getIntersections(curve: Curve): CurveLocation[] - + getIntersections(curve: Curve): CurveLocation[]; } - /** + /** * CurveLocation objects describe a location on {@link Curve} objects, as * defined by the curve-time {@link #time}, a value between `0` (beginning * of the curve) and `1` (end of the curve). If the curve is part of a * {@link Path} item, its {@link #index} inside the {@link Path#curves} * array is also provided. - * + * * The class is in use in many places, such as * {@link Path#getLocationAt}, * {@link Path#getLocationOf}, @@ -1001,326 +1004,314 @@ declare namespace paper { * {@link PathItem#getIntersections}, * etc. */ - class CurveLocation { - /** + class CurveLocation { + /** * The segment of the curve which is closer to the described location. */ - readonly segment: Segment + readonly segment: Segment; - /** + /** * The curve that this location belongs to. */ - readonly curve: Curve + readonly curve: Curve; - /** + /** * The path that this locations is situated on. */ - readonly path: Path + readonly path: Path; - /** + /** * The index of the {@link #curve} within the {@link Path#curves} list, if * it is part of a {@link Path} item. */ - readonly index: number + readonly index: number; - /** + /** * The curve-time parameter, as used by various bezier curve calculations. * It is value between `0` (beginning of the curve) and `1` (end of the * curve). */ - readonly time: number + readonly time: number; - /** + /** * The point which is defined by the {@link #curve} and * {@link #time}. */ - readonly point: Point + readonly point: Point; - /** + /** * The length of the path from its beginning up to the location described * by this object. If the curve is not part of a path, then the length * within the curve is returned instead. */ - readonly offset: number + readonly offset: number; - /** + /** * The length of the curve from its beginning up to the location described * by this object. */ - readonly curveOffset: number + readonly curveOffset: number; - /** + /** * The curve location on the intersecting curve, if this location is the * result of a call to {@link PathItem#getIntersections} / * {@link Curve#getIntersections}. */ - readonly intersection: CurveLocation + readonly intersection: CurveLocation; - /** + /** * The tangential vector to the {@link #curve} at the given location. */ - readonly tangent: Point + readonly tangent: Point; - /** + /** * The normal vector to the {@link #curve} at the given location. */ - readonly normal: Point + readonly normal: Point; - /** + /** * The curvature of the {@link #curve} at the given location. */ - readonly curvature: number + readonly curvature: number; - /** + /** * The distance from the queried point to the returned location. - * + * * @see Curve#getNearestLocation(point) * @see PathItem#getNearestLocation(point) */ - readonly distance: number - + readonly distance: number; - /** + /** * Creates a new CurveLocation object. */ - constructor(curve: Curve, time: number, point?: Point) + constructor(curve: Curve, time: number, point?: Point); - /** + /** * Checks whether tow CurveLocation objects are describing the same location * on a path, by applying the same tolerances as elsewhere when dealing with * curve-time parameters. - * + * * @return true if the locations are equal */ - equals(location: CurveLocation): boolean + equals(location: CurveLocation): boolean; - /** + /** * @return a string representation of the curve location */ - toString(): string + toString(): string; - /** + /** * Checks if the location is an intersection with another curve and is * merely touching the other curve, as opposed to crossing it. - * + * * @see #isCrossing() - * + * * @return true if the location is an intersection that is * merely touching another curve */ - isTouching(): boolean + isTouching(): boolean; - /** + /** * Checks if the location is an intersection with another curve and is * crossing the other curve, as opposed to just touching it. - * + * * @see #isTouching() - * + * * @return true if the location is an intersection that is * crossing another curve */ - isCrossing(): boolean + isCrossing(): boolean; - /** + /** * Checks if the location is an intersection with another curve and is * part of an overlap between the two involved paths. - * + * * @see #isCrossing() * @see #isTouching() - * + * * @return true if the location is an intersection that is * part of an overlap between the two involved paths */ - hasOverlap(): boolean - + hasOverlap(): boolean; } - /** + /** * The Event object is the base class for any of the other event types, * such as {@link MouseEvent}, {@link ToolEvent} and {@link KeyEvent}. */ - class Event { - /** + class Event { + /** * The time at which the event was created, in milliseconds since the epoch. */ - readonly timeStamp: number + readonly timeStamp: number; - /** + /** * The current state of the keyboard modifiers. - * + * * @see Key.modifiers */ - readonly modifiers: any + readonly modifiers: any; - - /** + /** * Cancels the event if it is cancelable, without stopping further * propagation of the event. */ - preventDefault(): void + preventDefault(): void; - /** + /** * Prevents further propagation of the current event. */ - stopPropagation(): void + stopPropagation(): void; - /** + /** * Cancels the event if it is cancelable, and stops stopping further * propagation of the event. This is has the same effect as calling both * {@link #stopPropagation} and {@link #preventDefault}. - * + * * Any handler can also return `false` to indicate that `stop()` should be * called right after. */ - stop(): void - + stop(): void; } - /** + /** * The Gradient object. */ - class Gradient { - /** + class Gradient { + /** * The gradient stops on the gradient ramp. */ - stops: GradientStop[] + stops: GradientStop[]; - /** + /** * Specifies whether the gradient is radial or linear. */ - radial: boolean - + radial: boolean; - /** + /** * @return a copy of the gradient */ - clone(): Gradient + clone(): Gradient; - /** + /** * Checks whether the gradient is equal to the supplied gradient. - * + * * @return true if they are equal */ - equals(gradient: Gradient): boolean - + equals(gradient: Gradient): boolean; } - /** + /** * The GradientStop object. */ - class GradientStop { - /** + class GradientStop { + /** * The ramp-point of the gradient stop as a value between `0` and `1`. */ - offset: number + offset: number; - /** + /** * The color of the gradient stop. */ - color: Color - + color: Color; - /** + /** * Creates a GradientStop object. - * + * * @param color - the color of the stop * @param offset - the position of the stop on the gradient * ramp as a value between `0` and `1`; `null` or `undefined` for automatic * assignment. */ - constructor(color?: Color, offset?: number) + constructor(color?: Color, offset?: number); - /** + /** * @return a copy of the gradient-stop */ - clone(): GradientStop - + clone(): GradientStop; } - /** + /** * A Group is a collection of items. When you transform a Group, its * children are treated as a single unit without changing their relative * positions. */ class Group extends Item { - /** + /** * Specifies whether the group item is to be clipped. When setting to * `true`, the first child in the group is automatically defined as the * clipping mask. */ - clipped: boolean + clipped: boolean; - - /** + /** * Creates a new Group item and places it at the top of the active layer. - * + * * @param children - An array of children that will be added to the * newly created group */ - constructor(children?: Item[]) + constructor(children?: Item[]); - /** + /** * Creates a new Group item and places it at the top of the active layer. - * + * * @param object - an object containing the properties to be set on * the group */ - constructor(object: object) - + constructor(object: object); } - /** + /** * A HitResult object contains information about the results of a hit * test. It is returned by {@link Item#hitTest} and * {@link Project#hitTest}. */ - class HitResult { - /** + class HitResult { + /** * Describes the type of the hit result. For example, if you hit a segment * point, the type would be `'segment'`. */ - type: string + type: string; - /** + /** * If the HitResult has a {@link HitResult#type} of `'bounds'`, this * property describes which corner of the bounding rectangle was hit. */ - name: string + name: string; - /** + /** * The item that was hit. */ - item: Item + item: Item; - /** + /** * If the HitResult has a type of 'curve' or 'stroke', this property gives * more information about the exact position that was hit on the path. */ - location: CurveLocation + location: CurveLocation; - /** + /** * If the HitResult has a type of 'pixel', this property refers to the color * of the pixel on the {@link Raster} that was hit. */ - color: Color | null + color: Color | null; - /** + /** * If the HitResult has a type of 'stroke', 'segment', 'handle-in' or * 'handle-out', this property refers to the segment that was hit or that * is closest to the hitResult.location on the curve. */ - segment: Segment + segment: Segment; - /** + /** * Describes the actual coordinates of the segment, handle or bounding box * corner that was hit. */ - point: Point - - + point: Point; } - /** + /** * The Item type allows you to access and modify the items in * Paper.js projects. Its functionality is inherited by different project * item types such as {@link Path}, {@link CompoundPath}, {@link Group}, @@ -1328,162 +1319,162 @@ declare namespace paper { * is unique to their type, but share the underlying properties and functions * that they inherit from Item. */ - class Item { - /** + class Item { + /** * The unique id of the item. */ - readonly id: number + readonly id: number; - /** + /** * The class name of the item as a string. */ - className: string + className: string; - /** + /** * The name of the item. If the item has a name, it can be accessed by name * through its parent's children list. */ - name: string + name: string; - /** + /** * The path style of the item. */ - style: Style + style: Style; - /** + /** * Specifies whether the item is locked. When set to `true`, item * interactions with the mouse are disabled. */ - locked: boolean + locked: boolean; - /** + /** * Specifies whether the item is visible. When set to `false`, the item * won't be drawn. */ - visible: boolean + visible: boolean; - /** + /** * The blend mode with which the item is composited onto the canvas. Both * the standard canvas compositing modes, as well as the new CSS blend modes * are supported. If blend-modes cannot be rendered natively, they are * emulated. Be aware that emulation can have an impact on performance. */ - blendMode: string + blendMode: string; - /** + /** * The opacity of the item as a value between `0` and `1`. */ - opacity: number + opacity: number; - /** + /** * Specifies whether the item is selected. This will also return `true` for * {@link Group} items if they are partially selected, e.g. groups * containing selected or partially selected paths. - * + * * 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. - * + * * @see Project#selectedItems * @see Segment#selected * @see Curve#selected * @see Point#selected */ - selected: boolean + selected: boolean; - /** + /** * Specifies whether the item defines a clip mask. This can only be set on * paths and compound paths, and only if the item is already contained * within a clipping group. */ - clipMask: boolean + clipMask: boolean; - /** + /** * A plain javascript object which can be used to store * arbitrary data on the item. */ - data: any + data: any; - /** + /** * The item's position within the parent item's coordinate system. By * default, this is the {@link Rectangle#center} of the item's * {@link #bounds} rectangle. */ - position: Point + position: Point; - /** + /** * The item's pivot point specified in the item coordinate system, defining * the point around which all transformations are hinging. This is also the * reference point for {@link #position}. By default, it is set to `null`, * meaning the {@link Rectangle#center} of the item's {@link #bounds} * rectangle is used as pivot. */ - pivot: Point + pivot: Point; - /** + /** * The bounding rectangle of the item excluding stroke width. */ - bounds: Rectangle + bounds: Rectangle; - /** + /** * The bounding rectangle of the item including stroke width. */ - strokeBounds: Rectangle + strokeBounds: Rectangle; - /** + /** * The bounding rectangle of the item including handles. */ - handleBounds: Rectangle + handleBounds: Rectangle; - /** + /** * The bounding rectangle of the item without any matrix transformations. - * + * * Typical use case would be drawing a frame around the object where you * want to draw something of the same size, position, rotation, and scaling, * like a selection frame. */ - internalBounds: Rectangle + internalBounds: Rectangle; - /** + /** * The current rotation angle of the item, as described by its * {@link #matrix}. * Please note that this only returns meaningful values for items with * {@link #applyMatrix} set to `false`, meaning they do not directly bake * transformations into their content. */ - rotation: number + rotation: number; - /** + /** * The current scale factor of the item, as described by its * {@link #matrix}. * Please note that this only returns meaningful values for items with * {@link #applyMatrix} set to `false`, meaning they do not directly bake * transformations into their content. */ - scaling: Point + scaling: Point; - /** + /** * The item's transformation matrix, defining position and dimensions in * relation to its parent item in which it is contained. */ - matrix: Matrix + matrix: Matrix; - /** + /** * The item's global transformation matrix in relation to the global project * coordinate space. Note that the view's transformations resulting from * zooming and panning are not factored in. */ - readonly globalMatrix: Matrix + readonly globalMatrix: Matrix; - /** + /** * The item's global matrix in relation to the view coordinate space. This * means that the view's transformations resulting from zooming and panning * are factored in. */ - readonly viewMatrix: Matrix + readonly viewMatrix: Matrix; - /** + /** * Controls whether the transformations applied to the item (e.g. through * {@link #transform}, {@link #rotate}, * {@link #scale}, etc.) are stored in its {@link #matrix} property, @@ -1491,107 +1482,107 @@ declare namespace paper { * on to the segments in {@link Path} items, the children of {@link Group} * items, etc.). */ - applyMatrix: boolean + applyMatrix: boolean; - /** + /** * The project that this item belongs to. */ - readonly project: Project + readonly project: Project; - /** + /** * The view that this item belongs to. */ - readonly view: View + readonly view: View; - /** + /** * The layer that this item is contained within. */ - readonly layer: Layer + readonly layer: Layer; - /** + /** * The item that this item is contained within. */ - parent: Item + parent: Item; - /** + /** * The children items contained within this item. Items that define a * {@link #name} can also be accessed by name. - * + * * Please note: The children array should not be modified directly * using array functions. To remove single items from the children list, use * {@link Item#remove}, to remove all items from the children list, use * {@link Item#removeChildren}. To add items to the children list, use * {@link Item#addChild} or {@link Item#insertChild}. */ - children: Item[] + children: Item[]; - /** + /** * The first item contained within this item. This is a shortcut for * accessing `item.children[0]`. */ - readonly firstChild: Item + readonly firstChild: Item; - /** + /** * The last item contained within this item.This is a shortcut for * accessing `item.children[item.children.length - 1]`. */ - readonly lastChild: Item + readonly lastChild: Item; - /** + /** * The next item on the same level as this item. */ - readonly nextSibling: Item + readonly nextSibling: Item; - /** + /** * The previous item on the same level as this item. */ - readonly previousSibling: Item + readonly previousSibling: Item; - /** + /** * The index of this item within the list of its parent's children. */ - readonly index: number + readonly index: number; - /** + /** * The color of the stroke. */ - strokeColor: Color | null + strokeColor: Color | null; - /** + /** * The width of the stroke. */ - strokeWidth: number + strokeWidth: number; - /** + /** * The shape to be used at the beginning and end of open {@link Path} items, * when they have a stroke. */ - strokeCap: string + strokeCap: string; - /** + /** * The shape to be used at the segments and corners of {@link Path} items * when they have a stroke. */ - strokeJoin: string + strokeJoin: string; - /** + /** * The dash offset of the stroke. */ - dashOffset: number + dashOffset: number; - /** + /** * Specifies whether the stroke is to be drawn taking the current affine * transformation into account (the default behavior), or whether it should * appear as a non-scaling stroke. */ - strokeScaling: boolean + strokeScaling: boolean; - /** + /** * Specifies an array containing the dash and gap lengths of the stroke. */ - dashArray: number[] + dashArray: number[]; - /** + /** * The miter limit of the stroke. * When two line segments meet at a sharp angle and miter joins have been * specified for {@link Item#strokeJoin}, it is possible for the miter to @@ -1599,47 +1590,47 @@ declare namespace paper { * miterLimit imposes a limit on the ratio of the miter length to the * {@link Item#strokeWidth}. */ - miterLimit: number + miterLimit: number; - /** + /** * The fill color of the item. */ - fillColor: Color | null + fillColor: Color | null; - /** + /** * The fill-rule with which the shape gets filled. Please note that only * modern browsers support fill-rules other than `'nonzero'`. */ - fillRule: string + fillRule: string; - /** + /** * The shadow color. */ - shadowColor: Color | null + shadowColor: Color | null; - /** + /** * The shadow's blur radius. */ - shadowBlur: number + shadowBlur: number; - /** + /** * The shadow's offset. */ - shadowOffset: Point + shadowOffset: Point; - /** + /** * The color the item is highlighted with when selected. If the item does * not specify its own color, the color defined by its layer is used instead. */ - selectedColor: Color | null + selectedColor: Color | null; - /** + /** * Item level handler function to be called on each frame of an animation. * The function receives an event object which contains information about * the frame event: - * + * * @see View#onFrame - * + * * @option event.count {Number} the number of times the frame event was * fired * @option event.time {Number} the total amount of time passed since the @@ -1647,81 +1638,81 @@ declare namespace paper { * @option event.delta {Number} the time passed in seconds since the last * frame event */ - onFrame: Function | null + onFrame: Function | null; - /** + /** * The function to be called when the mouse button is pushed down on the * item. The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onMouseDown */ - onMouseDown: Function | null + onMouseDown: Function | null; - /** + /** * The function to be called when the mouse position changes while the mouse * is being dragged over the item. The function receives a {@link * MouseEvent} object which contains information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onMouseDrag */ - onMouseDrag: Function | null + onMouseDrag: Function | null; - /** + /** * The function to be called when the mouse button is released over the item. * The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onMouseUp */ - onMouseUp: Function | null + onMouseUp: Function | null; - /** + /** * The function to be called when the mouse clicks on the item. The function * receives a {@link MouseEvent} object which contains information about the * mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onClick */ - onClick: Function | null + onClick: Function | null; - /** + /** * The function to be called when the mouse double clicks on the item. The * function receives a {@link MouseEvent} object which contains information * about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onDoubleClick */ - onDoubleClick: Function | null + onDoubleClick: Function | null; - /** + /** * The function to be called repeatedly while the mouse moves over the item. * The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onMouseMove */ - onMouseMove: Function | null + onMouseMove: Function | null; - /** + /** * The function to be called when the mouse moves over the item. This * function will only be called again, once the mouse moved outside of the * item first. The function receives a {@link MouseEvent} object which @@ -1729,69 +1720,68 @@ declare namespace paper { * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onMouseEnter */ - onMouseEnter: Function | null + onMouseEnter: Function | null; - /** + /** * The function to be called when the mouse moves out of the item. * The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy and will * reach the view, unless they are stopped with {@link * Event#stopPropagation()} or by returning `false` from the handler. - * + * * @see View#onMouseLeave */ - onMouseLeave: Function | null - + onMouseLeave: Function | null; - /** + /** * Sets the properties of the passed object literal on this item to the * values defined in the object literal, if the item has property of the * given name (or a setter defined for it). - * + * * @return the item itself */ - set(props: object): this + set(props: object): this; - /** + /** * Clones the item within the same project and places the copy above the * item. - * + * * @option [insert=true] specifies whether the copy should be * inserted into the scene graph. When set to `true`, it is inserted * above the original * @option [deep=true] specifies whether the item's children should also be * cloned - * + * * @return the newly cloned item */ - clone(options?: object): this + clone(options?: object): this; - /** + /** * Copies the content of the specified item over to this item. - * + * * @param source - the item to copy the content from */ - copyContent(source: Item): void + copyContent(source: Item): void; - /** + /** * Copies all attributes of the specified item over to this item. This * includes its style, visibility, matrix, pivot, blend-mode, opacity, * selection state, data, name, etc. - * + * * @param source - the item to copy the attributes from * @param excludeMatrix - whether to exclude the transformation * matrix when copying all attributes */ - copyAttributes(source: Item, excludeMatrix: boolean): void + copyAttributes(source: Item, excludeMatrix: boolean): void; - /** + /** * Rasterizes the item into a newly created Raster object. The item itself * is not removed after rasterization. - * + * * @option [resolution=view.resolution] {Number} the desired resolution to * be used when rasterizing, in pixels per inch (DPI). If not specified, * the value of `view.resolution` is used by default. @@ -1803,38 +1793,38 @@ declare namespace paper { * @option [insert=true] {Boolean} specifies whether the raster should be * inserted into the scene graph. When set to `true`, it is inserted * above the rasterized item. - * + * * @param options - the rasterization options - * + * * @return the reused raster or the newly created raster item */ - rasterize(options?: object): Raster + rasterize(options?: object): Raster; - /** + /** * Checks whether the item's geometry contains the given point. - * + * * @param point - the point to check for */ - contains(point: Point): boolean + contains(point: Point): boolean; - /** + /** * @param rect - the rectangle to check against */ - isInside(rect: Rectangle): boolean + isInside(rect: Rectangle): boolean; - /** + /** * @param item - the item to check against */ - intersects(item: Item): boolean + intersects(item: Item): boolean; - /** + /** * Performs a hit-test on the item and its children (if it is a {@link * Group} or {@link Layer}) at the location of the specified point, * returning the first found hit. - * + * * The options object allows you to control the specifics of the hit- * test and may contain a combination of the following values: - * + * * @option [options.tolerance={@link PaperScope#settings}.hitTolerance] * {Number} the tolerance of the hit-test * @option options.class {Function} only hit-test against a specific item @@ -1865,88 +1855,88 @@ declare namespace paper { * @option options.guides {Boolean} hit-test items that have {@link * Item#guide} set to `true` * @option options.selected {Boolean} only hit selected items - * + * * @param point - the point where the hit-test should be performed * (in global coordinates system). - * + * * @return a hit result object describing what exactly was hit * or `null` if nothing was hit */ - hitTest(point: Point, options?: object): HitResult + hitTest(point: Point, options?: object): HitResult; - /** + /** * Performs a hit-test on the item and its children (if it is a {@link * Group} or {@link Layer}) at the location of the specified point, * returning all found hits. - * + * * The options object allows you to control the specifics of the hit- * test. See {@link #hitTest} for a list of all options. - * + * * @see #hitTest(point[, options]); - * + * * @param point - the point where the hit-test should be performed * (in global coordinates system). - * + * * @return hit result objects for all hits, describing what * exactly was hit or `null` if nothing was hit */ - hitTestAll(point: Point, options?: object): HitResult[] + hitTestAll(point: Point, options?: object): HitResult[]; - /** + /** * Checks whether the item matches the criteria described by the given * object, by iterating over all of its properties and matching against * their values through {@link #matches}. - * + * * See {@link Project#getItems} for a selection of illustrated * examples. - * + * * @see #getItems(options) - * + * * @param options - the criteria to match against - * + * * @return true if the item matches all the criteria */ - matches(options: object | Function): boolean + matches(options: object | Function): boolean; - /** + /** * Checks whether the item matches the given criteria. Extended matching is * possible by providing a compare function or a regular expression. * Matching points, colors only work as a comparison of the full object, not * partial matching (e.g. only providing the x-coordinate to match all * points with that x-value). Partial matching does work for * {@link Item#data}. - * + * * See {@link Project#getItems} for a selection of illustrated * examples. - * + * * @see #getItems(options) - * + * * @param name - the name of the state to match against * @param compare - the value, function or regular expression to * compare against - * + * * @return true if the item matches the state */ - matches(name: string, compare: object): boolean + matches(name: string, compare: object): boolean; - /** + /** * Fetch the descendants (children or children of children) of this item * that match the properties in the specified object. Extended matching is * possible by providing a compare function or regular expression. Matching * points, colors only work as a comparison of the full object, not partial * matching (e.g. only providing the x- coordinate to match all points with * that x-value). Partial matching does work for {@link Item#data}. - * + * * Matching items against a rectangular area is also possible, by setting * either `options.inside` or `options.overlapping` to a rectangle * describing the area in which the items either have to be fully or partly * contained. - * + * * See {@link Project#getItems} for a selection of illustrated * examples. - * + * * @see #matches(options) - * + * * @option [options.recursive=true] {Boolean} whether to loop recursively * through all children, or stop at the current level * @option options.match {Function} a match function to be called for each @@ -1959,14 +1949,14 @@ declare namespace paper { * to be fully contained * @option options.overlapping {Rectangle} the rectangle with which the * items need to at least partly overlap - * + * * @param options - the criteria to match against - * + * * @return the list of matching descendant items */ - getItems(options: object | Function): Item[] + getItems(options: object | Function): Item[]; - /** + /** * Fetch the first descendant (child or child of child) of this item * that matches the properties in the specified object. * Extended matching is possible by providing a compare function or @@ -1976,44 +1966,44 @@ declare namespace paper { * does work for {@link Item#data}. * See {@link Project#getItems} for a selection of illustrated * examples. - * + * * @see #getItems(options) - * + * * @param options - the criteria to match against - * + * * @return the first descendant item matching the given criteria */ - getItem(options: object | Function): Item + getItem(options: object | Function): Item; - /** + /** * Exports (serializes) the item with its content and child items to a JSON * data string. - * + * * @option [options.asString=true] {Boolean} whether the JSON is returned as * a `Object` or a `String` * @option [options.precision=5] {Number} the amount of fractional digits in * numbers used in JSON data - * + * * @param options - the serialization options - * + * * @return the exported JSON data */ - exportJSON(options?: object): string + exportJSON(options?: object): string; - /** + /** * Imports (deserializes) the stored JSON data into this item. If the data * describes an item of the same class or a parent class of the item, the * data is imported into the item itself. If not, the imported item is added * to this item's {@link Item#children} list. Note that not all type of * items can have children. - * + * * @param json - the JSON data to import from */ - importJSON(json: string): Item + importJSON(json: string): Item; - /** + /** * Exports the item with its content and child items as an SVG DOM. - * + * * @option [options.bounds='view'] {String|Rectangle} the bounds of the area * to export, either as a string ({@values 'view', content'}), or a * {@link Rectangle} object: `'view'` uses the view bounds, @@ -2032,19 +2022,19 @@ declare namespace paper { * @option [options.embedImages=true] {Boolean} whether raster images should * be embedded as base64 data inlined in the xlink:href attribute, or * kept as a link to their external URL. - * + * * @param options - the export options - * + * * @return the item converted to an SVG node or a * `String` depending on `option.asString` value */ - exportSVG(options?: object): SVGElement | string + exportSVG(options?: object): SVGElement | string; - /** + /** * Converts the provided SVG content into Paper.js items and adds them to * the this item's children list. Note that the item is not cleared first. * You can call {@link Item#removeChildren} to do so. - * + * * @option [options.expandShapes=false] {Boolean} whether imported shape * items should be expanded to path items * @option options.onLoad {Function} the callback function to call once the @@ -2059,506 +2049,506 @@ declare namespace paper { * @option [options.applyMatrix={@link PaperScope#settings}.applyMatrix] * {Boolean} whether the imported items should have their transformation * matrices applied to their contents or not - * + * * @param svg - the SVG content to import, either as a SVG * DOM node, a string containing SVG content, or a string describing the * URL of the SVG file to fetch. * @param options - the import options - * + * * @return the newly created Paper.js item containing the converted * SVG content */ - importSVG(svg: SVGElement | string, options?: object): Item + importSVG(svg: SVGElement | string, options?: object): Item; - /** + /** * Imports the provided external SVG file, converts it into Paper.js items * and adds them to the this item's children list. Note that the item is not * cleared first. You can call {@link Item#removeChildren} to do so. - * + * * @param svg - the URL of the SVG file to fetch. * @param onLoad - the callback function to call once the SVG * content is loaded from the given URL receiving two arguments: the * converted `item` and the original `svg` data as a string. Only * required when loading from external files. - * + * * @return the newly created Paper.js item containing the converted * SVG content */ - importSVG(svg: SVGElement | string, onLoad: Function): Item + importSVG(svg: SVGElement | string, onLoad: Function): Item; - /** + /** * Adds the specified item as a child of this item at the end of the its * {@link #children} list. You can use this function for groups, compound * paths and layers. - * + * * @param item - the item to be added as a child - * + * * @return the added item, or `null` if adding was not possible */ - addChild(item: Item): Item + addChild(item: Item): Item; - /** + /** * Inserts the specified item as a child of this item at the specified index * in its {@link #children} list. You can use this function for groups, * compound paths and layers. - * + * * @param index - the index at which to insert the item * @param item - the item to be inserted as a child - * + * * @return the inserted item, or `null` if inserting was not possible */ - insertChild(index: number, item: Item): Item + insertChild(index: number, item: Item): Item; - /** + /** * Adds the specified items as children of this item at the end of the its * children list. You can use this function for groups, compound paths and * layers. - * + * * @param items - the items to be added as children - * + * * @return the added items, or `null` if adding was not possible */ - addChildren(items: Item[]): Item[] + addChildren(items: Item[]): Item[]; - /** + /** * Inserts the specified items as children of this item at the specified * index in its {@link #children} list. You can use this function for * groups, compound paths and layers. - * + * * @param items - the items to be appended as children - * + * * @return the inserted items, or `null` if inserted was not * possible */ - insertChildren(index: number, items: Item[]): Item[] + insertChildren(index: number, items: Item[]): Item[]; - /** + /** * Inserts this item above the specified item. - * + * * @param item - the item above which it should be inserted - * + * * @return the inserted item, or `null` if inserting was not possible */ - insertAbove(item: Item): Item + insertAbove(item: Item): Item; - /** + /** * Inserts this item below the specified item. - * + * * @param item - the item below which it should be inserted - * + * * @return the inserted item, or `null` if inserting was not possible */ - insertBelow(item: Item): Item + insertBelow(item: Item): Item; - /** + /** * Sends this item to the back of all other items within the same parent. */ - sendToBack(): void + sendToBack(): void; - /** + /** * Brings this item to the front of all other items within the same parent. */ - bringToFront(): void + bringToFront(): void; - /** + /** * Adds it to the specified owner, which can be either a {@link Item} or a * {@link Project}. - * + * * @param owner - the item or project to * add the item to - * + * * @return the item itself, if it was successfully added */ - addTo(owner: Project | Layer | Group | CompoundPath): this + addTo(owner: Project | Layer | Group | CompoundPath): this; - /** + /** * Clones the item and adds it to the specified owner, which can be either * a {@link Item} or a {@link Project}. - * + * * @param owner - the item or project to * copy the item to - * + * * @return the new copy of the item, if it was successfully added */ - copyTo(owner: Project | Layer | Group | CompoundPath): this + copyTo(owner: Project | Layer | Group | CompoundPath): this; - /** + /** * If this is a group, layer or compound-path with only one child-item, * the child-item is moved outside and the parent is erased. Otherwise, the * item itself is returned unmodified. - * + * * @return the reduced item */ - reduce(options: any): Item + reduce(options: any): Item; - /** + /** * Removes the item and all its children from the project. The item is not * destroyed and can be inserted again after removal. - * + * * @return true if the item was removed */ - remove(): boolean + remove(): boolean; - /** + /** * Replaces this item with the provided new item which will takes its place * in the project hierarchy instead. - * + * * @param item - the item that will replace this item - * + * * @return true if the item was replaced */ - replaceWith(item: Item): boolean + replaceWith(item: Item): boolean; - /** + /** * Removes all of the item's {@link #children} (if any). - * + * * @return an array containing the removed items */ - removeChildren(): Item[] + removeChildren(): Item[]; - /** + /** * Removes the children from the specified `start` index to and excluding * the `end` index from the parent's {@link #children} array. - * + * * @param start - the beginning index, inclusive * @param end - the ending index, exclusive - * + * * @return an array containing the removed items */ - removeChildren(start: number, end?: number): Item[] + removeChildren(start: number, end?: number): Item[]; - /** + /** * Reverses the order of the item's children */ - reverseChildren(): void + reverseChildren(): void; - /** + /** * Specifies whether the item has any content or not. The meaning of what * content is differs from type to type. For example, a {@link Group} with * no children, a {@link TextItem} with no text content and a {@link Path} * with no segments all are considered empty. - * + * * @param recursively - whether an item with children should be * considered empty if all its descendants are empty */ - isEmpty(recursively?: boolean): boolean + isEmpty(recursively?: boolean): boolean; - /** + /** * Checks whether the item has a fill. - * + * * @return true if the item has a fill */ - hasFill(): boolean + hasFill(): boolean; - /** + /** * Checks whether the item has a stroke. - * + * * @return true if the item has a stroke */ - hasStroke(): boolean + hasStroke(): boolean; - /** + /** * Checks whether the item has a shadow. - * + * * @return true if the item has a shadow */ - hasShadow(): boolean + hasShadow(): boolean; - /** + /** * Checks if the item contains any children items. - * + * * @return true it has one or more children */ - hasChildren(): boolean + hasChildren(): boolean; - /** + /** * Checks whether the item and all its parents are inserted into scene graph * or not. - * + * * @return true if the item is inserted into the scene graph */ - isInserted(): boolean + isInserted(): boolean; - /** + /** * Checks if this item is above the specified item in the stacking order * of the project. - * + * * @param item - the item to check against - * + * * @return true if it is above the specified item */ - isAbove(item: Item): boolean + isAbove(item: Item): boolean; - /** + /** * Checks if the item is below the specified item in the stacking order of * the project. - * + * * @param item - the item to check against - * + * * @return true if it is below the specified item */ - isBelow(item: Item): boolean + isBelow(item: Item): boolean; - /** + /** * Checks whether the specified item is the parent of the item. - * + * * @param item - the item to check against - * + * * @return true if it is the parent of the item */ - isParent(item: Item): boolean + isParent(item: Item): boolean; - /** + /** * Checks whether the specified item is a child of the item. - * + * * @param item - the item to check against - * + * * @return true it is a child of the item */ - isChild(item: Item): boolean + isChild(item: Item): boolean; - /** + /** * Checks if the item is contained within the specified item. - * + * * @param item - the item to check against - * + * * @return true if it is inside the specified item */ - isDescendant(item: Item): boolean + isDescendant(item: Item): boolean; - /** + /** * Checks if the item is an ancestor of the specified item. - * + * * @param item - the item to check against - * + * * @return true if the item is an ancestor of the specified * item */ - isAncestor(item: Item): boolean + isAncestor(item: Item): boolean; - /** + /** * Checks if the item is an a sibling of the specified item. - * + * * @param item - the item to check against - * + * * @return true if the item is aa sibling of the specified item */ - isSibling(item: Item): boolean + isSibling(item: Item): boolean; - /** + /** * Checks whether the item is grouped with the specified item. - * + * * @return true if the items are grouped together */ - isGroupedWith(item: Item): boolean + isGroupedWith(item: Item): boolean; - /** + /** * Translates (moves) the item by the given offset views. - * + * * @param delta - the offset to translate the item by */ - translate(delta: Point): void + translate(delta: Point): void; - /** + /** * Rotates the item by a given angle around the given center point. - * + * * Angles are oriented clockwise and measured in degrees. - * + * * @see Matrix#rotate(angle[, center]) - * + * * @param angle - the rotation angle */ - rotate(angle: number, center?: Point): void + rotate(angle: number, center?: Point): void; - /** + /** * Scales the item by the given value from its center point, or optionally * from a supplied point. - * + * * @param scale - the scale factor */ - scale(scale: number, center?: Point): void + scale(scale: number, center?: Point): void; - /** + /** * Scales the item by the given values from its center point, or optionally * from a supplied point. - * + * * @param hor - the horizontal scale factor * @param ver - the vertical scale factor */ - scale(hor: number, ver: number, center?: Point): void + scale(hor: number, ver: number, center?: Point): void; - /** + /** * Shears the item by the given value from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(shear[, center]) - * + * * @param shear - the horizontal and vertical shear factors as a point */ - shear(shear: Point, center?: Point): void + shear(shear: Point, center?: Point): void; - /** + /** * Shears the item by the given values from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(hor, ver[, center]) - * + * * @param hor - the horizontal shear factor * @param ver - the vertical shear factor */ - shear(hor: number, ver: number, center?: Point): void + shear(hor: number, ver: number, center?: Point): void; - /** + /** * Skews the item by the given angles from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(skew[, center]) - * + * * @param skew - the horizontal and vertical skew angles in degrees */ - skew(skew: Point, center?: Point): void + skew(skew: Point, center?: Point): void; - /** + /** * Skews the item by the given angles from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(hor, ver[, center]) - * + * * @param hor - the horizontal skew angle in degrees * @param ver - the vertical sskew angle in degrees */ - skew(hor: number, ver: number, center?: Point): void + skew(hor: number, ver: number, center?: Point): void; - /** + /** * Transform the item. - * + * * @param matrix - the matrix by which the item shall be transformed */ - transform(matrix: Matrix): void + transform(matrix: Matrix): void; - /** + /** * Converts the specified point from global project coordinate space to the * item's own local coordinate space. - * + * * @param point - the point to be transformed - * + * * @return the transformed point as a new instance */ - globalToLocal(point: Point): Point + globalToLocal(point: Point): Point; - /** + /** * Converts the specified point from the item's own local coordinate space * to the global project coordinate space. - * + * * @param point - the point to be transformed - * + * * @return the transformed point as a new instance */ - localToGlobal(point: Point): Point + localToGlobal(point: Point): Point; - /** + /** * Converts the specified point from the parent's coordinate space to * item's own local coordinate space. - * + * * @param point - the point to be transformed - * + * * @return the transformed point as a new instance */ - parentToLocal(point: Point): Point + parentToLocal(point: Point): Point; - /** + /** * Converts the specified point from the item's own local coordinate space * to the parent's coordinate space. - * + * * @param point - the point to be transformed - * + * * @return the transformed point as a new instance */ - localToParent(point: Point): Point + localToParent(point: Point): Point; - /** + /** * Transform the item so that its {@link #bounds} fit within the specified * rectangle, without changing its aspect ratio. */ - fitBounds(rectangle: Rectangle, fill?: boolean): void + fitBounds(rectangle: Rectangle, fill?: boolean): void; - /** + /** * Attaches an event handler to the item. - * + * * @param type - the type of event: {@values 'frame', mousedown', * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', * 'mouseenter', 'mouseleave'} * @param function - the function to be called when the event * occurs, receiving a {@link MouseEvent} or {@link Event} object as its * sole argument - * + * * @return this item itself, so calls can be chained */ - on(type: string, callback: Function): this + on(type: string, callback: Function): this; - /** + /** * Attaches one or more event handlers to the item. - * + * * @param object - an object containing one or more of the following * properties: {@values frame, mousedown, mouseup, mousedrag, click, * doubleclick, mousemove, mouseenter, mouseleave} - * + * * @return this item itself, so calls can be chained */ - on(object: object): this + on(object: object): this; - /** + /** * Detach an event handler from the item. - * + * * @param type - the type of event: {@values 'frame', mousedown', * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', * 'mouseenter', 'mouseleave'} * @param function - the function to be detached - * + * * @return this item itself, so calls can be chained */ - off(type: string, callback: Function): this + off(type: string, callback: Function): this; - /** + /** * Detach one or more event handlers to the item. - * + * * @param object - an object containing one or more of the following * properties: {@values frame, mousedown, mouseup, mousedrag, click, * doubleclick, mousemove, mouseenter, mouseleave} - * + * * @return this item itself, so calls can be chained */ - off(object: object): this + off(object: object): this; - /** + /** * Emit an event on the item. - * + * * @param type - the type of event: {@values 'frame', mousedown', * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', * 'mouseenter', 'mouseleave'} * @param event - an object literal containing properties describing * the event - * + * * @return true if the event had listeners */ - emit(type: string, event: object): boolean + emit(type: string, event: object): boolean; - /** + /** * Check if the item has one or more event handlers of the specified type. - * + * * @param type - the type of event: {@values 'frame', mousedown', * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', * 'mouseenter', 'mouseleave'} - * + * * @return true if the item has one or more event handlers of * the specified type */ - responds(type: string): boolean + responds(type: string): boolean; - /** + /** * Removes the item when the events specified in the passed options object * occur. - * + * * @option options.move {Boolean) remove the item when the next {@link * Tool#onMouseMove} event is fired. * @option options.drag {Boolena) remove the item when the next {@link @@ -2568,31 +2558,31 @@ declare namespace paper { * @option options.up {Boolean) remove the item when the next {@link * Tool#onMouseUp} event is fired. */ - removeOn(options: object): void + removeOn(options: object): void; - /** + /** * Removes the item when the next {@link Tool#onMouseMove} event is fired. */ - removeOnMove(): void + removeOnMove(): void; - /** + /** * Removes the item when the next {@link Tool#onMouseDown} event is fired. */ - removeOnDown(): void + removeOnDown(): void; - /** + /** * Removes the item when the next {@link Tool#onMouseDrag} event is fired. */ - removeOnDrag(): void + removeOnDrag(): void; - /** + /** * Removes the item when the next {@link Tool#onMouseUp} event is fired. */ - removeOnUp(): void + removeOnUp(): void; - /** + /** * Tween item between two states. - * + * * @option options.duration {Number} the duration of the tweening * @option [options.easing='linear'] {Function|String} an easing function or the type * of the easing: {@values 'linear' 'easeInQuad' 'easeOutQuad' @@ -2600,59 +2590,57 @@ declare namespace paper { * 'easeInQuart' 'easeOutQuart' 'easeInOutQuart' 'easeInQuint' * 'easeOutQuint' 'easeInOutQuint'} * @option [options.start=true] {Boolean} whether to start tweening automatically - * + * * @param from - the state at the start of the tweening * @param to - the state at the end of the tweening * @param options - the options or the duration */ - tween(from: object, to: object, options: object | number): Tween + tween(from: object, to: object, options: object | number): Tween; - /** + /** * Tween item to a state. - * + * * @see Item#tween(from, to, options) - * + * * @param to - the state at the end of the tweening * @param options - the options or the duration */ - tween(to: object, options: object | number): Tween + tween(to: object, options: object | number): Tween; - /** + /** * Tween item. - * + * * @see Item#tween(from, to, options) - * + * * @param options - the options or the duration */ - tween(options: object | number): Tween + tween(options: object | number): Tween; - /** + /** * Tween item to a state. - * + * * @see Item#tween(to, options) - * + * * @param to - the state at the end of the tweening * @param options - the options or the duration */ - tweenTo(to: object, options: object | number): Tween + tweenTo(to: object, options: object | number): Tween; - /** + /** * Tween item from a state to its state before the tweening. - * + * * @see Item#tween(from, to, options) - * + * * @param from - the state at the start of the tweening * @param options - the options or the duration */ - tweenFrom(from: object, options: object | number): Tween - + tweenFrom(from: object, options: object | number): Tween; } - - class Key { - /** + class Key { + /** * The current state of the keyboard modifiers. - * + * * @option modifiers.shift {Boolean} {@true if the shift key is * pressed}. * @option modifiers.control {Boolean} {@true if the control key is @@ -2670,186 +2658,179 @@ declare namespace paper { * @option modifiers.command {Boolean} {@true if the meta key is pressed * on Mac, or the control key is pressed on Windows and Linux}. */ - static modifiers: any + static modifiers: any; - - /** + /** * Checks whether the specified key is pressed. - * + * * @param key - any character or special key descriptor: * {@values 'enter', 'space', 'shift', 'control', 'alt', 'meta', * 'caps-lock', 'left', 'up', 'right', 'down', 'escape', 'delete', * ...} - * + * * @return true if the key is pressed */ - static isDown(key: string): boolean - + static isDown(key: string): boolean; } - /** + /** * The KeyEvent object is received by the {@link Tool}'s keyboard * handlers {@link Tool#onKeyDown}, {@link Tool#onKeyUp}. The KeyEvent object is * the only parameter passed to these functions and contains information about * the keyboard event. */ class KeyEvent extends Event { - /** + /** * The type of mouse event. */ - type: string + type: string; - /** + /** * The character representation of the key that caused this key event, * taking into account the current key-modifiers (e.g. shift, control, * caps-lock, etc.) */ - character: string + character: string; - /** + /** * The key that caused this key event, either as a lower-case character or * special key descriptor. */ - key: string - + key: string; - /** + /** * @return a string representation of the key event */ - toString(): string - + toString(): string; } - /** + /** * The Layer item represents a layer in a Paper.js project. - * + * * The layer which is currently active can be accessed through * {@link Project#activeLayer}. * An array of all layers in a project can be accessed through * {@link Project#layers}. */ class Layer extends Group { - - /** + /** * Creates a new Layer item and places it at the end of the * {@link Project#layers} array. The newly created layer will be activated, * so all newly created items will be placed within it. - * + * * @param children - An array of items that will be added to the * newly created layer */ - constructor(children?: Item[]) + constructor(children?: Item[]); - /** + /** * Creates a new Layer item and places it at the end of the * {@link Project#layers} array. The newly created layer will be activated, * so all newly created items will be placed within it. - * + * * @param object - an object containing the properties to be set on * the layer */ - constructor(object: object) + constructor(object: object); - /** + /** * Activates the layer. */ - activate(): void - + activate(): void; } - /** + /** * An affine transformation matrix performs a linear mapping from 2D * coordinates to other 2D coordinates that preserves the "straightness" and * "parallelness" of lines. - * + * * Such a coordinate transformation can be represented by a 3 row by 3 * column matrix with an implied last row of `[ 0 0 1 ]`. This matrix * transforms source coordinates `(x, y)` into destination coordinates `(x',y')` * by considering them to be a column vector and multiplying the coordinate * vector by the matrix according to the following process: - * + * * [ x ] [ a c tx ] [ x ] [ a * x + c * y + tx ] * [ y ] = [ b d ty ] [ y ] = [ b * x + d * y + ty ] * [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ] - * + * * Note the locations of b and c. - * + * * This class is optimized for speed and minimizes calculations based on its * knowledge of the underlying matrix (as opposed to say simply performing * matrix multiplication). */ - class Matrix { - /** + class Matrix { + /** * The value that affects the transformation along the x axis when scaling * or rotating, positioned at (0, 0) in the transformation matrix. */ - a: number + a: number; - /** + /** * The value that affects the transformation along the y axis when rotating * or skewing, positioned at (1, 0) in the transformation matrix. */ - b: number + b: number; - /** + /** * The value that affects the transformation along the x axis when rotating * or skewing, positioned at (0, 1) in the transformation matrix. */ - c: number + c: number; - /** + /** * The value that affects the transformation along the y axis when scaling * or rotating, positioned at (1, 1) in the transformation matrix. */ - d: number + d: number; - /** + /** * The distance by which to translate along the x axis, positioned at (2, 0) * in the transformation matrix. */ - tx: number + tx: number; - /** + /** * The distance by which to translate along the y axis, positioned at (2, 1) * in the transformation matrix. */ - ty: number + ty: number; - /** + /** * The matrix values as an array, in the same sequence as they are passed * to {@link #initialize}. */ - readonly values: number[] + readonly values: number[]; - /** + /** * The translation of the matrix as a vector. */ - readonly translation: Point + readonly translation: Point; - /** + /** * The scaling values of the matrix, if it can be decomposed. - * + * * @see #decompose() */ - readonly scaling: Point + readonly scaling: Point; - /** + /** * The rotation angle of the matrix, if it can be decomposed. - * + * * @see #decompose() */ - readonly rotation: number + readonly rotation: number; - - /** + /** * Creates a 2D affine transformation matrix that describes the identity * transformation. */ - constructor() + constructor(); - /** + /** * Creates a 2D affine transformation matrix. - * + * * @param a - the a property of the transform * @param b - the b property of the transform * @param c - the c property of the transform @@ -2857,298 +2838,304 @@ declare namespace paper { * @param tx - the tx property of the transform * @param ty - the ty property of the transform */ - constructor(a: number, b: number, c: number, d: number, tx: number, ty: number) + constructor( + a: number, + b: number, + c: number, + d: number, + tx: number, + ty: number + ); - /** + /** * Creates a 2D affine transformation matrix. - * + * * @param values - the matrix values to initialize this matrix with */ - constructor(values: number[]) + constructor(values: number[]); - /** + /** * Creates a 2D affine transformation matrix. - * + * * @param matrix - the matrix to copy the values from */ - constructor(matrix: Matrix) + constructor(matrix: Matrix); - /** + /** * Sets the matrix to the passed values. Note that any sequence of * parameters that is supported by the various {@link Matrix} constructors * also work for calls of `set()`. */ - set(...values: any[]): Point + set(...values: any[]): Point; - /** + /** * @return a copy of this transform */ - clone(): Matrix + clone(): Matrix; - /** + /** * Checks whether the two matrices describe the same transformation. - * + * * @param matrix - the matrix to compare this matrix to - * + * * @return true if the matrices are equal */ - equals(matrix: Matrix): boolean + equals(matrix: Matrix): boolean; - /** + /** * @return a string representation of this transform */ - toString(): string + toString(): string; - /** + /** * Resets the matrix by setting its values to the ones of the identity * matrix that results in no transformation. */ - reset(): void + reset(): void; - /** + /** * Attempts to apply the matrix to the content of item that it belongs to, * meaning its transformation is baked into the item's content or children. - * + * * @param recursively - controls whether to apply * transformations recursively on children - * + * * @return true if the matrix was applied */ - apply(recursively?: boolean): boolean + apply(recursively?: boolean): boolean; - /** + /** * Concatenates this matrix with a translate transformation. - * + * * @param point - the vector to translate by - * + * * @return this affine transform */ - translate(point: Point): Matrix + translate(point: Point): Matrix; - /** + /** * Concatenates this matrix with a translate transformation. - * + * * @param dx - the distance to translate in the x direction * @param dy - the distance to translate in the y direction - * + * * @return this affine transform */ - translate(dx: number, dy: number): Matrix + translate(dx: number, dy: number): Matrix; - /** + /** * Concatenates this matrix with a scaling transformation. - * + * * @param scale - the scaling factor * @param center - the center for the scaling transformation - * + * * @return this affine transform */ - scale(scale: number, center?: Point): Matrix + scale(scale: number, center?: Point): Matrix; - /** + /** * Concatenates this matrix with a scaling transformation. - * + * * @param hor - the horizontal scaling factor * @param ver - the vertical scaling factor * @param center - the center for the scaling transformation - * + * * @return this affine transform */ - scale(hor: number, ver: number, center?: Point): Matrix + scale(hor: number, ver: number, center?: Point): Matrix; - /** + /** * Concatenates this matrix with a rotation transformation around an * anchor point. - * + * * @param angle - the angle of rotation measured in degrees * @param center - the anchor point to rotate around - * + * * @return this affine transform */ - rotate(angle: number, center: Point): Matrix + rotate(angle: number, center: Point): Matrix; - /** + /** * Concatenates this matrix with a rotation transformation around an * anchor point. - * + * * @param angle - the angle of rotation measured in degrees * @param x - the x coordinate of the anchor point * @param y - the y coordinate of the anchor point - * + * * @return this affine transform */ - rotate(angle: number, x: number, y: number): Matrix + rotate(angle: number, x: number, y: number): Matrix; - /** + /** * Concatenates this matrix with a shear transformation. - * + * * @param shear - the shear factor in x and y direction * @param center - the center for the shear transformation - * + * * @return this affine transform */ - shear(shear: Point, center?: Point): Matrix + shear(shear: Point, center?: Point): Matrix; - /** + /** * Concatenates this matrix with a shear transformation. - * + * * @param hor - the horizontal shear factor * @param ver - the vertical shear factor * @param center - the center for the shear transformation - * + * * @return this affine transform */ - shear(hor: number, ver: number, center?: Point): Matrix + shear(hor: number, ver: number, center?: Point): Matrix; - /** + /** * Concatenates this matrix with a skew transformation. - * + * * @param skew - the skew angles in x and y direction in degrees * @param center - the center for the skew transformation - * + * * @return this affine transform */ - skew(skew: Point, center?: Point): Matrix + skew(skew: Point, center?: Point): Matrix; - /** + /** * Concatenates this matrix with a skew transformation. - * + * * @param hor - the horizontal skew angle in degrees * @param ver - the vertical skew angle in degrees * @param center - the center for the skew transformation - * + * * @return this affine transform */ - skew(hor: number, ver: number, center?: Point): Matrix + skew(hor: number, ver: number, center?: Point): Matrix; - /** + /** * Appends the specified matrix to this matrix. This is the equivalent of * multiplying `(this matrix) * (specified matrix)`. - * + * * @param matrix - the matrix to append - * + * * @return this matrix, modified */ - append(matrix: Matrix): Matrix + append(matrix: Matrix): Matrix; - /** + /** * Prepends the specified matrix to this matrix. This is the equivalent of * multiplying `(specified matrix) * (this matrix)`. - * + * * @param matrix - the matrix to prepend - * + * * @return this matrix, modified */ - prepend(matrix: Matrix): Matrix + prepend(matrix: Matrix): Matrix; - /** + /** * Returns a new matrix as the result of appending the specified matrix to * this matrix. This is the equivalent of multiplying * `(this matrix) * (specified matrix)`. - * + * * @param matrix - the matrix to append - * + * * @return the newly created matrix */ - appended(matrix: Matrix): Matrix + appended(matrix: Matrix): Matrix; - /** + /** * Returns a new matrix as the result of prepending the specified matrix * to this matrix. This is the equivalent of multiplying * `(specified matrix) * (this matrix)`. - * + * * @param matrix - the matrix to prepend - * + * * @return the newly created matrix */ - prepended(matrix: Matrix): Matrix + prepended(matrix: Matrix): Matrix; - /** + /** * Inverts the matrix, causing it to perform the opposite transformation. * If the matrix is not invertible (in which case {@link #isSingular} * returns true), `null` is returned. - * + * * @return this matrix, or `null`, if the matrix is singular. */ - invert(): Matrix + invert(): Matrix; - /** + /** * Creates a new matrix that is the inversion of this matrix, causing it to * perform the opposite transformation. If the matrix is not invertible (in * which case {@link #isSingular} returns true), `null` is returned. - * + * * @return this matrix, or `null`, if the matrix is singular. */ - inverted(): Matrix + inverted(): Matrix; - /** + /** * @return whether this matrix is the identity matrix */ - isIdentity(): boolean + isIdentity(): boolean; - /** + /** * Checks whether the matrix is invertible. A matrix is not invertible if * the determinant is 0 or any value is infinite or NaN. - * + * * @return whether the matrix is invertible */ - isInvertible(): boolean + isInvertible(): boolean; - /** + /** * Checks whether the matrix is singular or not. Singular matrices cannot be * inverted. - * + * * @return whether the matrix is singular */ - isSingular(): boolean + isSingular(): boolean; - /** + /** * Transforms a point and returns the result. - * + * * @param point - the point to be transformed - * + * * @return the transformed point */ - transform(point: Point): Point + transform(point: Point): Point; - /** + /** * Transforms an array of coordinates by this matrix and stores the results * into the destination array, which is also returned. - * + * * @param src - the array containing the source points * as x, y value pairs * @param dst - the array into which to store the transformed * point pairs * @param count - the number of points to transform - * + * * @return the dst array, containing the transformed coordinates */ - transform(src: number[], dst: number[], count: number): number[] + transform(src: number[], dst: number[], count: number): number[]; - /** + /** * Inverse transforms a point and returns the result. - * + * * @param point - the point to be transformed */ - inverseTransform(point: Point): Point + inverseTransform(point: Point): Point; - /** + /** * Decomposes the affine transformation described by this matrix into * `scaling`, `rotation` and `skewing`, and returns an object with * these properties. - * + * * @return the decomposed matrix */ - decompose(): object + decompose(): object; - /** + /** * Applies this matrix to the specified Canvas Context. */ - applyToContext(ctx: CanvasRenderingContext2D): void - + applyToContext(ctx: CanvasRenderingContext2D): void; } - /** + /** * The MouseEvent object is received by the {@link Item}'s mouse event * handlers {@link Item#onMouseDown}, {@link Item#onMouseDrag}, * {@link Item#onMouseMove}, {@link Item#onMouseUp}, {@link Item#onClick}, @@ -3157,52 +3144,49 @@ declare namespace paper { * to these functions and contains information about the mouse event. */ class MouseEvent extends Event { - /** + /** * The type of mouse event. */ - type: string + type: string; - /** + /** * The position of the mouse in project coordinates when the event was * fired. */ - point: Point + point: Point; - /** + /** * The item that dispatched the event. It is different from * {@link #currentTarget} when the event handler is called during * the bubbling phase of the event. */ - target: Item + target: Item; - /** + /** * The current target for the event, as the event traverses the scene graph. * It always refers to the element the event handler has been attached to as * opposed to {@link #target} which identifies the element on * which the event occurred. */ - currentTarget: Item - - - delta: Point + currentTarget: Item; + delta: Point; - /** + /** * @return a string representation of the mouse event */ - toString(): string - + toString(): string; } - /** + /** * The `PaperScope` class represents the scope associated with a Paper * context. When working with PaperScript, these scopes are automatically * created for us, and through clever scoping the properties and methods of * the active scope seem to become part of the global scope. - * + * * When working with normal JavaScript code, `PaperScope` objects need to be * manually created and handled. - * + * * Paper classes can only be accessed through `PaperScope` objects. Thus in * PaperScript they are global, while in JavaScript, they are available on the * global {@link paper} object. For JavaScript you can use {@link @@ -3210,19 +3194,19 @@ declare namespace paper { * global scope. Note that when working with more than one scope, this still * works for classes, but not for objects like {@link PaperScope#project}, since * they are not updated in the injected scope if scopes are switched. - * + * * The global {@link paper} object is simply a reference to the currently active * `PaperScope`. */ - class PaperScope { - /** + class PaperScope { + /** * The version of Paper.js, as a string. */ - readonly version: string + readonly version: string; - /** + /** * Gives access to paper's configurable settings. - * + * * @option [settings.insertItems=true] {Boolean} controls whether newly * created items are automatically inserted into the scene graph, by * adding them to {@link Project#activeLayer} @@ -3234,168 +3218,169 @@ declare namespace paper { * @option [settings.hitTolerance=0] {Number} the default tolerance for hit- * tests, when no value is specified */ - settings: any + settings: any; - /** + /** * The currently active project. */ - project: Project + project: Project; - /** + /** * The list of all open projects within the current Paper.js context. */ - projects: Project[] + projects: Project[]; - /** + /** * The reference to the active project's view. */ - readonly view: View + readonly view: View; - /** + /** * The reference to the active tool. */ - tool: Tool + tool: Tool; - /** + /** * The list of available tools. */ - tools: Tool[] - - Color: typeof Color - CompoundPath: typeof CompoundPath - Curve: typeof Curve - CurveLocation: typeof CurveLocation - Event: typeof Event - Gradient: typeof Gradient - GradientStop: typeof GradientStop - Group: typeof Group - HitResult: typeof HitResult - Item: typeof Item - Key: typeof Key - KeyEvent: typeof KeyEvent - Layer: typeof Layer - Matrix: typeof Matrix - MouseEvent: typeof MouseEvent - PaperScope: typeof PaperScope - PaperScript: typeof PaperScript - Path: typeof Path - PathItem: typeof PathItem - Point: typeof Point - PointText: typeof PointText - Project: typeof Project - Raster: typeof Raster - Rectangle: typeof Rectangle - Segment: typeof Segment - Shape: typeof Shape - Size: typeof Size - Style: typeof Style - SymbolDefinition: typeof SymbolDefinition - SymbolItem: typeof SymbolItem - TextItem: typeof TextItem - Tool: typeof Tool - ToolEvent: typeof ToolEvent - Tween: typeof Tween - View: typeof View - - /** + tools: Tool[]; + + Color: typeof Color; + CompoundPath: typeof CompoundPath; + Curve: typeof Curve; + CurveLocation: typeof CurveLocation; + Event: typeof Event; + Gradient: typeof Gradient; + GradientStop: typeof GradientStop; + Group: typeof Group; + HitResult: typeof HitResult; + Item: typeof Item; + Key: typeof Key; + KeyEvent: typeof KeyEvent; + Layer: typeof Layer; + Matrix: typeof Matrix; + MouseEvent: typeof MouseEvent; + PaperScope: typeof PaperScope; + PaperScript: typeof PaperScript; + Path: typeof Path; + PathItem: typeof PathItem; + Point: typeof Point; + PointText: typeof PointText; + Project: typeof Project; + Raster: typeof Raster; + Rectangle: typeof Rectangle; + Segment: typeof Segment; + Shape: typeof Shape; + Size: typeof Size; + Style: typeof Style; + SymbolDefinition: typeof SymbolDefinition; + SymbolItem: typeof SymbolItem; + TextItem: typeof TextItem; + Tool: typeof Tool; + ToolEvent: typeof ToolEvent; + Tween: typeof Tween; + View: typeof View; + + /** * Creates a PaperScope object. */ - constructor() + constructor(); - /** + /** * Compiles the PaperScript code into a compiled function and executes it. * The compiled function receives all properties of this {@link PaperScope} * as arguments, to emulate a global scope with unaffected performance. It * also installs global view and tool handlers automatically on the * respective objects. - * + * * @option options.url {String} the url of the source, for source-map * debugging * @option options.source {String} the source to be used for the source- * mapping, in case the code that's passed in has already been mingled. - * + * * @param code - the PaperScript code * @param options - the compilation options */ - execute(code: string, options?: object): void + execute(code: string, options?: object): void; - /** + /** * Injects the paper scope into any other given scope. Can be used for * example to inject the currently active PaperScope into the window's * global scope, to emulate PaperScript-style globally accessible Paper * classes and objects. - * + * * Please note: Using this method may override native constructors * (e.g. Path). This may cause problems when using Paper.js in conjunction * with other libraries that rely on these constructors. Keep the library * scoped if you encounter issues caused by this. */ - install(scope: any): void + install(scope: any): void; - /** + /** * Sets up an empty project for us. If a canvas is provided, it also creates * a {@link View} for it, both linked to this scope. - * + * * @param element - the HTML canvas element * this scope should be associated with, or an ID string by which to find * the element, or the size of the canvas to be created for usage in a web * worker. */ - setup(element: HTMLCanvasElement | string | Size): void + setup(element: HTMLCanvasElement | string | Size): void; - /** + /** * Activates this PaperScope, so all newly created items will be placed * in its active project. */ - activate(): void + activate(): void; - /** + /** * Retrieves a PaperScope object with the given scope id. */ - static get(id: any): PaperScope - + static get(id: any): PaperScope; } - - class PaperScript { - - /** + class PaperScript { + /** * Compiles PaperScript code into JavaScript code. - * + * * @option options.url {String} the url of the source, for source-map * generation * @option options.source {String} the source to be used for the source- * mapping, in case the code that's passed in has already been mingled. - * + * * @param code - the PaperScript code * @param options - the compilation options - * + * * @return an object holding the compiled PaperScript translated * into JavaScript code along with source-maps and other information. */ - static compile(code: string, options?: object): object + static compile(code: string, options?: object): object; - /** + /** * Compiles the PaperScript code into a compiled function and executes it. * The compiled function receives all properties of the passed {@link * PaperScope} as arguments, to emulate a global scope with unaffected * performance. It also installs global view and tool handlers automatically * on the respective objects. - * + * * @option options.url {String} the url of the source, for source-map * generation * @option options.source {String} the source to be used for the source- * mapping, in case the code that's passed in has already been mingled. - * + * * @param code - the PaperScript code * @param scope - the scope for which the code is executed * @param options - the compilation options - * + * * @return the exports defined in the executed code */ - static execute(code: string, scope: PaperScope, options?: object): object + static execute( + code: string, + scope: PaperScope, + options?: object + ): object; - /** + /** * Loads, compiles and executes PaperScript code in the HTML document. Note * that this method is executed automatically for all scripts in the * document through a window load event. You can optionally call it earlier @@ -3403,580 +3388,573 @@ declare namespace paper { * setting the attribute `ignore="true"` or `data-paper-ignore="true"`, and * call the `PaperScript.load(script)` method for each script separately * when needed. - * + * * @param script - the script to load. If none is * provided, all scripts of the HTML document are iterated over and * loaded - * + * * @return the scope produced for the passed `script`, or * `undefined` of multiple scripts area loaded */ - static load(script?: HTMLScriptElement): PaperScope - + static load(script?: HTMLScriptElement): PaperScope; } - /** + /** * The path item represents a path in a Paper.js project. */ class Path extends PathItem { - /** + /** * The segments contained within the path. */ - segments: Segment[] + segments: Segment[]; - /** + /** * The first Segment contained within the path. */ - readonly firstSegment: Segment + readonly firstSegment: Segment; - /** + /** * The last Segment contained within the path. */ - readonly lastSegment: Segment + readonly lastSegment: Segment; - /** + /** * The curves contained within the path. */ - readonly curves: Curve[] + readonly curves: Curve[]; - /** + /** * The first Curve contained within the path. */ - readonly firstCurve: Curve + readonly firstCurve: Curve; - /** + /** * The last Curve contained within the path. */ - readonly lastCurve: Curve + readonly lastCurve: Curve; - /** + /** * Specifies whether the path is closed. If it is closed, Paper.js connects * the first and last segments. */ - closed: boolean + closed: boolean; - /** + /** * The approximate length of the path. */ - readonly length: number + readonly length: number; - /** + /** * The area that the path's geometry is covering. Self-intersecting paths * can contain sub-areas that cancel each other out. */ - readonly area: number + readonly area: number; - /** + /** * Specifies whether the path and all its segments are selected. Cannot be * `true` on an empty path. */ - fullySelected: boolean - + fullySelected: boolean; - /** + /** * Creates a new path item and places it at the top of the active layer. - * + * * @param segments - An array of segments (or points to be * converted to segments) that will be added to the path */ - constructor(segments?: Segment[]) + constructor(segments?: Segment[]); - /** + /** * Creates a new path item from SVG path-data and places it at the top of * the active layer. - * + * * @param pathData - the SVG path-data that describes the geometry * of this path */ - constructor(pathData: string) + constructor(pathData: string); - /** + /** * Creates a new path item from an object description and places it at the * top of the active layer. - * + * * @param object - an object containing properties to be set on the * path */ - constructor(object: object) + constructor(object: object); - /** + /** * Adds one or more segments to the end of the {@link #segments} array of * this path. - * + * * @param segment - the segment or point to be * added. - * + * * @return the added segment(s). This is not necessarily * the same object, e.g. if the segment to be added already belongs to * another path. */ - add(...segment: (Segment | Point | number[])[]): Segment | Segment[] + add(...segment: (Segment | Point | number[])[]): Segment | Segment[]; - /** + /** * Inserts one or more segments at a given index in the list of this path's * segments. - * + * * @param index - the index at which to insert the segment * @param segment - the segment or point to be inserted. - * + * * @return the added segment. This is not necessarily the same * object, e.g. if the segment to be added already belongs to another path */ - insert(index: number, segment: Segment | Point): Segment + insert(index: number, segment: Segment | Point): Segment; - /** + /** * Adds an array of segments (or types that can be converted to segments) * to the end of the {@link #segments} array. - * + * * @return 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 */ - addSegments(segments: Segment[]): Segment[] + addSegments(segments: Segment[]): Segment[]; - /** + /** * Inserts an array of segments at a given index in the path's * {@link #segments} array. - * + * * @param index - the index at which to insert the segments * @param segments - the segments to be inserted - * + * * @return 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(index: number, segments: Segment[]): Segment[] + insertSegments(index: number, segments: Segment[]): Segment[]; - /** + /** * Removes the segment at the specified index of the path's * {@link #segments} array. - * + * * @param index - the index of the segment to be removed - * + * * @return the removed segment */ - removeSegment(index: number): Segment + removeSegment(index: number): Segment; - /** + /** * Removes all segments from the path's {@link #segments} array. - * + * * @return an array containing the removed segments */ - removeSegments(): Segment[] + removeSegments(): Segment[]; - /** + /** * Removes the segments from the specified `from` index to the `to` index * from the path's {@link #segments} array. - * + * * @param from - the beginning index, inclusive * @param to - the ending index, exclusive - * + * * @return an array containing the removed segments */ - removeSegments(from: number, to?: number): Segment[] + removeSegments(from: number, to?: number): Segment[]; - /** + /** * Checks if any of the curves in the path have curve handles set. - * + * * @see Segment#hasHandles() * @see Curve#hasHandles() - * + * * @return true if the path has curve handles set */ - hasHandles(): boolean + hasHandles(): boolean; - /** + /** * Clears the path's handles by setting their coordinates to zero, * turning the path into a polygon (or a polyline if it isn't closed). */ - clearHandles(): void + clearHandles(): void; - /** + /** * Divides the path on the curve at the given offset or location into two * curves, by inserting a new segment at the given location. - * + * * @see Curve#divideAt(location) - * + * * @param location - the offset or location on the * path at which to divide the existing curve by inserting a new segment - * + * * @return the newly inserted segment if the location is valid, * `null` otherwise */ - divideAt(location: number | CurveLocation): Segment + divideAt(location: number | CurveLocation): Segment; - /** + /** * Splits the path at the given offset or location. After splitting, the * path will be open. If the path was open already, splitting will result in * two paths. - * + * * @param location - the offset or location at which to * split the path - * + * * @return the newly created path after splitting, if any */ - splitAt(location: number | CurveLocation): Path + splitAt(location: number | CurveLocation): Path; - /** + /** * Joins the path with the other specified path, which will be removed in * the process. They can be joined if the first or last segments of either * path lie in the same location. Locations are optionally compare with a * provide `tolerance` value. - * + * * If `null` or `this` is passed as the other path, the path will be joined * with itself if the first and last segment are in the same location. - * + * * @param path - the path to join this path with; `null` or `this` to * join the path with itself * @param tolerance - the tolerance with which to decide if two * segments are to be considered the same location when joining */ - join(path: Path, tolerance?: number): void + join(path: Path, tolerance?: number): void; - /** + /** * Reduces the path by removing curves that have a length of 0, * and unnecessary segments between two collinear flat curves. - * + * * @return the reduced path */ - reduce(options: any): Path + reduce(options: any): Path; - /** + /** * Attempts to create a new shape item with same geometry as this path item, * and inherits all settings from it, similar to {@link Item#clone}. - * + * * @see Shape#toPath(insert) - * + * * @param insert - specifies whether the new shape should be * inserted into the scene graph. When set to `true`, it is inserted above * the path item - * + * * @return the newly created shape item with the same geometry as * this path item if it can be matched, `null` otherwise */ - toShape(insert?: boolean): Shape + toShape(insert?: boolean): Shape; - /** + /** * Returns the curve location of the specified point if it lies on the * path, `null` otherwise. - * + * * @param point - the point on the path - * + * * @return the curve location of the specified point */ - getLocationOf(point: Point): CurveLocation + getLocationOf(point: Point): CurveLocation; - /** + /** * Returns the length of the path from its beginning up to up to the * specified point if it lies on the path, `null` otherwise. - * + * * @param point - the point on the path - * + * * @return the length of the path up to the specified point */ - getOffsetOf(point: Point): number + getOffsetOf(point: Point): number; - /** + /** * Returns the curve location of the specified offset on the path. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the curve location at the specified offset */ - getLocationAt(offset: number): CurveLocation + getLocationAt(offset: number): CurveLocation; - /** + /** * Calculates the point on the path at the given offset. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the point at the given offset */ - getPointAt(offset: number): Point + getPointAt(offset: number): Point; - /** + /** * Calculates the normalized tangent vector of the path at the given offset. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the normalized tangent vector at the given offset */ - getTangentAt(offset: number): Point + getTangentAt(offset: number): Point; - /** + /** * Calculates the normal vector of the path at the given offset. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the normal vector at the given offset */ - getNormalAt(offset: number): Point + getNormalAt(offset: number): Point; - /** + /** * Calculates the weighted tangent vector of the path at the given offset. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the weighted tangent vector at the given offset */ - getWeightedTangentAt(offset: number): Point + getWeightedTangentAt(offset: number): Point; - /** + /** * Calculates the weighted normal vector of the path at the given offset. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the weighted normal vector at the given offset */ - getWeightedNormalAt(offset: number): Point + getWeightedNormalAt(offset: number): Point; - /** + /** * 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. - * + * * @param offset - the offset on the path, where `0` is at * the beginning of the path and {@link Path#length} at the end - * + * * @return the normal vector at the given offset */ - getCurvatureAt(offset: number): number + getCurvatureAt(offset: number): number; - /** + /** * Calculates path offsets where the path is tangential to the provided * tangent. Note that tangents at the start or end are included. Tangents at * segment points are returned even if only one of their handles is * collinear with the provided tangent. - * + * * @param tangent - the tangent to which the path must be tangential - * + * * @return path offsets where the path is tangential to the * provided tangent */ - getOffsetsWithTangent(tangent: Point): number[] - + getOffsetsWithTangent(tangent: Point): number[]; } namespace Path { - class Line extends Path { - /** + /** * Creates a linear path item from two points describing a line. - * + * * @param from - the line's starting point * @param to - the line's ending point */ - constructor(from: Point, to: Point) + constructor(from: Point, to: Point); - /** + /** * Creates a linear path item from the properties described by an object * literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } class Circle extends Path { - /** + /** * Creates a circular path item. - * + * * @param center - the center point of the circle * @param radius - the radius of the circle */ - constructor(center: Point, radius: number) + constructor(center: Point, radius: number); - /** + /** * Creates a circular path item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } class Rectangle extends Path { - /** + /** * Creates a rectangular path item, with optionally rounded corners. - * + * * @param rectangle - the rectangle object describing the * geometry of the rectangular path to be created * @param radius - the size of the rounded corners */ - constructor(rectangle: paper.Rectangle, radius?: Size) + constructor(rectangle: paper.Rectangle, radius?: Size); - /** + /** * Creates a rectangular path item from a point and a size object. - * + * * @param point - the rectangle's top-left corner. * @param size - the rectangle's size. */ - constructor(point: Point, size: Size) + constructor(point: Point, size: Size); - /** + /** * Creates a rectangular path item from the passed points. These do not * necessarily need to be the top left and bottom right corners, the * constructor figures out how to fit a rectangle between them. - * + * * @param from - the first point defining the rectangle * @param to - the second point defining the rectangle */ - constructor(from: Point, to: Point) + constructor(from: Point, to: Point); - /** + /** * Creates a rectangular path item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } class Ellipse extends Path { - /** + /** * Creates an elliptical path item. - * + * * @param rectangle - the rectangle circumscribing the ellipse */ - constructor(rectangle: paper.Rectangle) + constructor(rectangle: paper.Rectangle); - /** + /** * Creates an elliptical path item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } class Arc extends Path { - /** + /** * Creates a circular arc path item. - * + * * @param from - the starting point of the circular arc * @param through - the point the arc passes through * @param to - the end point of the arc */ - constructor(from: Point, through: Point, to: Point) + constructor(from: Point, through: Point, to: Point); - /** + /** * Creates an circular arc path item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } class RegularPolygon extends Path { - /** + /** * Creates a regular polygon shaped path item. - * + * * @param center - the center point of the polygon * @param sides - the number of sides of the polygon * @param radius - the radius of the polygon */ - constructor(center: Point, sides: number, radius: number) + constructor(center: Point, sides: number, radius: number); - /** + /** * Creates a regular polygon shaped path item from the properties * described by an object literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } class Star extends Path { - /** + /** * Creates a star shaped path item. - * + * * The largest of `radius1` and `radius2` will be the outer radius of * the star. The smallest of radius1 and radius2 will be the inner * radius. - * + * * @param center - the center point of the star * @param points - the number of points of the star */ - constructor(center: Point, points: number, radius1: number, radius2: number) - - /** + constructor( + center: Point, + points: number, + radius1: number, + radius2: number + ); + + /** * Creates a star shaped path item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } } - /** + /** * The PathItem class is the base for any items that describe paths and * offer standardised methods for drawing and path manipulation, such as * {@link Path} and {@link CompoundPath}. */ class PathItem extends Item { - /** + /** * Returns a point that is guaranteed to be inside the path. */ - readonly interiorPoint: Point + readonly interiorPoint: Point; - /** + /** * Specifies whether the path as a whole is oriented clock-wise, by looking * at the path's area. * Note that self-intersecting paths and sub-paths of different orientation * can result in areas that cancel each other out. - * + * * @see Path#area * @see CompoundPath#area */ - clockwise: boolean + clockwise: boolean; - /** + /** * The path's geometry, formatted as SVG style path data. */ - pathData: string + pathData: string; - - /** + /** * Unites the geometry of the specified path with this path's geometry * and returns the result as a new path item. - * + * * @option [options.insert=true] {Boolean} whether the resulting item * should be inserted back into the scene graph, above both paths * involved in the operation - * + * * @param path - the path to unite with * @param options - the boolean operation options - * + * * @return the resulting path item */ - unite(path: PathItem, options?: object): PathItem + unite(path: PathItem, options?: object): PathItem; - /** + /** * Intersects the geometry of the specified path with this path's * geometry and returns the result as a new path item. - * + * * @option [options.insert=true] {Boolean} whether the resulting item * should be inserted back into the scene graph, above both paths * involved in the operation @@ -3985,18 +3963,18 @@ declare namespace paper { * of the paths are to be kept in the result, or whether the first * path is only to be split at intersections, keeping the parts of * the curves that intersect with the area of the second path. - * + * * @param path - the path to intersect with * @param options - the boolean operation options - * + * * @return the resulting path item */ - intersect(path: PathItem, options?: object): PathItem + intersect(path: PathItem, options?: object): PathItem; - /** + /** * Subtracts the geometry of the specified path from this path's * geometry and returns the result as a new path item. - * + * * @option [options.insert=true] {Boolean} whether the resulting item * should be inserted back into the scene graph, above both paths * involved in the operation @@ -4005,35 +3983,35 @@ declare namespace paper { * of the paths are to be kept in the result, or whether the first * path is only to be split at intersections, removing the parts of * the curves that intersect with the area of the second path. - * + * * @param path - the path to subtract * @param options - the boolean operation options - * + * * @return the resulting path item */ - subtract(path: PathItem, options?: object): PathItem + subtract(path: PathItem, options?: object): PathItem; - /** + /** * Excludes the intersection of the geometry of the specified path with * this path's geometry and returns the result as a new path item. - * + * * @option [options.insert=true] {Boolean} whether the resulting item * should be inserted back into the scene graph, above both paths * involved in the operation - * + * * @param path - the path to exclude the intersection of * @param options - the boolean operation options - * + * * @return the resulting path item */ - exclude(path: PathItem, options?: object): PathItem + exclude(path: PathItem, options?: object): PathItem; - /** + /** * Splits the geometry of this path along the geometry of the specified * path returns the result as a new group item. This is equivalent to * calling {@link #subtract} and {@link #intersect} and * putting the results into a new group. - * + * * @option [options.insert=true] {Boolean} whether the resulting item * should be inserted back into the scene graph, above both paths * involved in the operation @@ -4041,140 +4019,140 @@ declare namespace paper { * used, treating both paths as areas when determining which parts * of the paths are to be kept in the result, or whether the first * path is only to be split at intersections. - * + * * @param path - the path to divide by * @param options - the boolean operation options - * + * * @return the resulting path item */ - divide(path: PathItem, options?: object): PathItem + divide(path: PathItem, options?: object): PathItem; - /** + /** * Fixes the orientation of the sub-paths of a compound-path, assuming * that non of its sub-paths intersect, by reorienting them so that they * are of different winding direction than their containing paths, * except for disjoint sub-paths, i.e. islands, which are oriented so * that they have the same winding direction as the the biggest path. - * + * * @param nonZero - controls if the non-zero fill-rule * is to be applied, by counting the winding of each nested path and * discarding sub-paths that do not contribute to the final result * @param clockwise - if provided, the orientation of the root * paths will be set to the orientation specified by `clockwise`, * otherwise the orientation of the largest root child is used. - * + * * @return a reference to the item itself, reoriented */ - reorient(nonZero?: boolean, clockwise?: boolean): PathItem + reorient(nonZero?: boolean, clockwise?: boolean): PathItem; - /** + /** * Creates a path item from the given SVG path-data, determining if the * data describes a plain path or a compound-path with multiple * sub-paths. - * + * * @param pathData - the SVG path-data to parse - * + * * @return the newly created path item */ - static create(pathData: string): Path | CompoundPath + static create(pathData: string): Path | CompoundPath; - /** + /** * Creates a path item from the given segments array, determining if the * array describes a plain path or a compound-path with multiple * sub-paths. - * + * * @param segments - the segments array to parse - * + * * @return the newly created path item */ - static create(segments: number[][]): Path | CompoundPath + static create(segments: number[][]): Path | CompoundPath; - /** + /** * Creates a path item from the given object, determining if the * contained information describes a plain path or a compound-path with * multiple sub-paths. - * + * * @param object - an object containing the properties describing * the item to be created - * + * * @return the newly created path item */ - static create(object: object): Path | CompoundPath + static create(object: object): Path | CompoundPath; - /** + /** * Returns all intersections between two {@link PathItem} items as an array * of {@link CurveLocation} objects. {@link CompoundPath} items are also * supported. - * + * * @see #getCrossings(path) - * + * * @param path - the other item to find the intersections with * @param include - a callback function that can be used to * filter out undesired locations right while they are collected. When * defined, it shall return {@true to include a location}. - * + * * @return the locations of all intersection between the * paths */ - getIntersections(path: PathItem, include?: Function): CurveLocation[] + getIntersections(path: PathItem, include?: Function): CurveLocation[]; - /** + /** * Returns all crossings between two {@link PathItem} items as an array of * {@link CurveLocation} objects. {@link CompoundPath} items are also * supported. Crossings are intersections where the paths actually are * crossing each other, as opposed to simply touching. - * + * * @see #getIntersections(path) - * + * * @param path - the other item to find the crossings with - * + * * @return the locations of all crossings between the * paths */ - getCrossings(path: PathItem): CurveLocation[] + getCrossings(path: PathItem): CurveLocation[]; - /** + /** * Returns the nearest location on the path item to the specified point. - * + * * @param point - the point for which we search the nearest location - * + * * @return the location on the path that's the closest to * the specified point */ - getNearestLocation(point: Point): CurveLocation + getNearestLocation(point: Point): CurveLocation; - /** + /** * Returns the nearest point on the path item to the specified point. - * + * * @param point - the point for which we search the nearest point - * + * * @return the point on the path that's the closest to the specified * point */ - getNearestPoint(point: Point): Point + getNearestPoint(point: Point): Point; - /** + /** * Reverses the orientation of the path item. When called on * {@link CompoundPath} items, each of the nested paths is reversed. On * {@link Path} items, the sequence of {@link Path#segments} is reversed. */ - reverse(): void + reverse(): void; - /** + /** * Flattens the curves in path items to a sequence of straight lines, by * subdividing them enough times until the specified maximum error is met. - * + * * @param flatness - the maximum error between the flattened * lines and the original curves */ - flatten(flatness?: number): void + flatten(flatness?: number): void; - /** + /** * Smooths the path item without changing the amount of segments in the path * or moving the segments' locations, by smoothing and adjusting the angle * and length of the segments' handles based on the position and distance of * neighboring segments. - * + * * Smoothing works both for open paths and closed paths, and can be applied * to the full path, as well as a sub-range of it. If a range is defined * using the `options.from` and `options.to` properties, only the curve @@ -4182,43 +4160,43 @@ declare namespace paper { * are specified in negative indices, the indices are wrapped around the end * of the curve. That way, a smoothing range in a close path can even wrap * around the connection between the last and the first segment. - * + * * Four different smoothing methods are available: - * + * * - `'continuous'` smooths the path item by adjusting its curve handles so * that the first and second derivatives of all involved curves are * continuous across their boundaries. - * + * * This method tends to result in the smoothest results, but does not * allow for further parametrization of the handles. - * + * * - `'asymmetric'` is based on the same principle as `'continuous'` but * uses different factors so that the result is asymmetric. This used to * the only method available until v0.10.0, and is currently still the * default when no method is specified, for reasons of backward * compatibility. It will eventually be removed. - * + * * - `'catmull-rom'` uses the Catmull-Rom spline to smooth the segment. - * + * * The optionally passed factor controls the knot parametrization of the * algorithm: - * + * * - `0.0`: the standard, uniform Catmull-Rom spline * - `0.5`: the centripetal Catmull-Rom spline, guaranteeing no * self-intersections * - `1.0`: the chordal Catmull-Rom spline - * + * * - `'geometric'` use a simple heuristic and empiric geometric method to * smooth the segment's handles. The handles were weighted, meaning that * big differences in distances between the segments will lead to * probably undesired results. - * + * * The optionally passed factor defines the tension parameter (`0...1`), * controlling the amount of smoothing as a factor by which to scale * each handle. - * + * * @see Segment#smooth([options]) - * + * * @option [options.type='asymmetric'] {String} the type of smoothing * method: {@values 'continuous', 'asymmetric', 'catmull-rom', * 'geometric'} @@ -4234,35 +4212,35 @@ declare namespace paper { * either be a segment index, or a segment or curve object that is part * of the path. If the passed number is negative, the index is wrapped * around the end of the path. - * + * * @param options - the smoothing options */ - smooth(options?: object): void + smooth(options?: object): void; - /** + /** * Fits a sequence of as few curves as possible through the path's anchor * points, ignoring the path items's curve-handles, with an allowed maximum * error. When called on {@link CompoundPath} items, each of the nested * paths is simplified. On {@link Path} items, the {@link Path#segments} * array is processed and replaced by the resulting sequence of fitted * curves. - * + * * This method can be used to process and simplify the point data received * from a mouse or touch device. - * + * * @param tolerance - the allowed maximum error when fitting * the curves through the segment points - * + * * @return true if the method was capable of fitting curves * through the path's segment points */ - simplify(tolerance?: number): boolean + simplify(tolerance?: number): boolean; - /** + /** * Interpolates between the two specified path items and uses the result * as the geometry for this path item. The number of children and * segments in the two paths involved in the operation should be the same. - * + * * @param from - the path item defining the geometry when `factor` * is `0` * @param to - the path item defining the geometry when `factor` @@ -4270,76 +4248,76 @@ declare namespace paper { * @param factor - the interpolation coefficient, typically between * `0` and `1`, but extrapolation is possible too */ - interpolate(from: PathItem, to: PathItem, factor: number): void + interpolate(from: PathItem, to: PathItem, factor: number): void; - /** + /** * Compares the geometry of two paths to see if they describe the same * shape, detecting cases where paths start in different segments or even * use different amounts of curves to describe the same shape, as long as * their orientation is the same, and their segments and handles really * result in the same visual appearance of curves. - * + * * @param path - the path to compare this path's geometry with - * + * * @return true if two paths describe the same shape */ - compare(path: PathItem): boolean + compare(path: PathItem): boolean; - /** + /** * On a normal empty {@link Path}, the point is simply added as the path's * first segment. If called on a {@link CompoundPath}, a new {@link Path} is * created as a child and the point is added as its first segment. - * + * * @param point - the point in which to start the path */ - moveTo(point: Point): void + moveTo(point: Point): void; - /** + /** * Adds a straight curve to the path, from the the last segment in the path * to the specified point. - * + * * @param point - the destination point of the newly added straight * curve */ - lineTo(point: Point): void + lineTo(point: Point): void; - /** + /** * Adds an arc from the position of the last segment in the path, passing * through the specified `through` point, to the specified `to` point, by * adding one or more segments to the path. - * + * * @param through - the point where the arc should pass through * @param to - the point where the arc should end */ - arcTo(through: Point, to: Point): void + arcTo(through: Point, to: Point): void; - /** + /** * Adds an arc from the position of the last segment in the path to * the specified point, by adding one or more segments to the path. - * + * * @param to - the point where the arc should end * @param clockwise - specifies whether the arc should be * drawn in clockwise direction */ - arcTo(to: Point, clockwise?: boolean): void + arcTo(to: Point, clockwise?: boolean): void; - /** + /** * Adds a curve from the last segment in the path through the specified * `through` point, to the specified destination point by adding one segment * to the path. - * + * * @param through - the point through which the curve should pass * @param to - the destination point of the newly added curve * @param time - the curve-time parameter at which the * `through` point is to be located */ - curveTo(through: Point, to: Point, time?: number): void + curveTo(through: Point, to: Point, time?: number): void; - /** + /** * Adds a cubic bezier curve to the path, from the last segment to the * specified destination point, with the curve itself defined by two * specified handles. - * + * * @param handle1 - the location of the first handle of the newly * added curve in absolute coordinates, out of which the relative values * for {@link Segment#handleOut} of its first segment are calculated @@ -4348,16 +4326,16 @@ declare namespace paper { * for {@link Segment#handleIn} of its second segment are calculated * @param to - the destination point of the newly added curve */ - cubicCurveTo(handle1: Point, handle2: Point, to: Point): void + cubicCurveTo(handle1: Point, handle2: Point, to: Point): void; - /** + /** * Adds a quadratic bezier curve to the path, from the last segment to the * specified destination point, with the curve itself defined by the * specified handle. - * + * * Note that Paper.js only stores cubic curves, so the handle is actually * converted. - * + * * @param handle - the location of the handle of the newly added * quadratic curve in absolute coordinates, out of which the relative * values for {@link Segment#handleOut} of the resulting cubic curve's @@ -4365,698 +4343,692 @@ declare namespace paper { * calculated * @param to - the destination point of the newly added curve */ - quadraticCurveTo(handle: Point, to: Point): void + quadraticCurveTo(handle: Point, to: Point): void; - /** + /** * Closes the path. When closed, Paper.js connects the first and last * segment of the path with an additional curve. The difference to setting * {@link Path#closed} to `true` is that this will also merge the first * segment with the last if they lie in the same location. - * + * * @see Path#closed */ - closePath(): void + closePath(): void; - /** + /** * If called on a {@link CompoundPath}, a new {@link Path} is created as a * child and a point is added as its first segment relative to the position * of the last segment of the current path. */ - moveBy(to: Point): void + moveBy(to: Point): void; - /** + /** * Adds a straight curve to the path, from the the last segment in the path * to the `to` vector specified relatively to it. - * + * * @param point - the vector describing the destination of the newly * added straight curve */ - lineBy(point: Point): void + lineBy(point: Point): void; - /** + /** * Adds an arc from the position of the last segment in the path, passing * through the specified `through` vector, to the specified `to` vector, all * specified relatively to it by these given vectors, by adding one or more * segments to the path. - * + * * @param through - the vector where the arc should pass through * @param to - the vector where the arc should end */ - arcBy(through: Point, to: Point): void + arcBy(through: Point, to: Point): void; - /** + /** * Adds an arc from the position of the last segment in the path to the `to` * vector specified relatively to it, by adding one or more segments to the * path. - * + * * @param to - the vector where the arc should end * @param clockwise - specifies whether the arc should be * drawn in clockwise direction */ - arcBy(to: Point, clockwise?: boolean): void + arcBy(to: Point, clockwise?: boolean): void; - /** + /** * Adds a curve from the last segment in the path through the specified * `through` vector, to the specified `to` vector, all specified relatively * to it by these given vectors, by adding one segment to the path. - * + * * @param through - the vector through which the curve should pass * @param to - the destination vector of the newly added curve * @param time - the curve-time parameter at which the * `through` point is to be located */ - curveBy(through: Point, to: Point, time?: number): void + curveBy(through: Point, to: Point, time?: number): void; - /** + /** * Adds a cubic bezier curve to the path, from the last segment to the * to the specified `to` vector, with the curve itself defined by two * specified handles. - * + * * @param handle1 - the location of the first handle of the newly * added curve * @param handle2 - the location of the second handle of the newly * added curve * @param to - the destination point of the newly added curve */ - cubicCurveBy(handle1: Point, handle2: Point, to: Point): void + cubicCurveBy(handle1: Point, handle2: Point, to: Point): void; - /** + /** * Adds a quadratic bezier curve to the path, from the last segment to the * specified destination point, with the curve itself defined by the * specified handle. - * + * * Note that Paper.js only stores cubic curves, so the handle is actually * converted. - * + * * @param handle - the handle of the newly added quadratic curve out * of which the values for {@link Segment#handleOut} of the resulting * cubic curve's first segment and {@link Segment#handleIn} of its * second segment are calculated * @param to - the destination point of the newly added curve */ - quadraticCurveBy(handle: Point, to: Point): void - + quadraticCurveBy(handle: Point, to: Point): void; } - /** + /** * The Point object represents a point in the two dimensional space * of the Paper.js project. It is also used to represent two dimensional * vector objects. */ - class Point { - /** + class Point { + /** * The x coordinate of the point */ - x: number + x: number; - /** + /** * The y coordinate of the point */ - y: number + y: number; - /** + /** * The length of the vector that is represented by this point's coordinates. * Each point can be interpreted as a vector that points from the origin (`x * = 0`, `y = 0`) to the point's location. Setting the length changes the * location but keeps the vector's angle. */ - length: number + length: number; - /** + /** * The vector's angle in degrees, measured from the x-axis to the vector. */ - angle: number + angle: number; - /** + /** * The vector's angle in radians, measured from the x-axis to the vector. */ - angleInRadians: number + angleInRadians: number; - /** + /** * The quadrant of the {@link #angle} of the point. - * + * * Angles between 0 and 90 degrees are in quadrant `1`. Angles between 90 * and 180 degrees are in quadrant `2`, angles between 180 and 270 degrees * are in quadrant `3` and angles between 270 and 360 degrees are in * quadrant `4`. */ - readonly quadrant: number + readonly quadrant: number; - /** + /** * This property is only valid if the point is an anchor or handle point * of a {@link Segment} or a {@link Curve}, or the position of an * {@link Item}, as returned by {@link Item#position}, * {@link Segment#point}, {@link Segment#handleIn}, * {@link Segment#handleOut}, {@link Curve#point1}, {@link Curve#point2}, * {@link Curve#handle1}, {@link Curve#handle2}. - * + * * In those cases, it returns {@true if it the point is selected}. - * + * * Paper.js renders selected points on top of your project. This is very * useful when debugging. */ - selected: boolean + selected: boolean; - - /** + /** * Creates a Point object with the given x and y coordinates. - * + * * @param x - the x coordinate * @param y - the y coordinate */ - constructor(x: number, y: number) + constructor(x: number, y: number); - /** + /** * Creates a Point object using the numbers in the given array as * coordinates. */ - constructor(array: any[]) + constructor(array: any[]); - /** + /** * Creates a Point object using the width and height values of the given * Size object. */ - constructor(size: Size) + constructor(size: Size); - /** + /** * Creates a Point object using the coordinates of the given Point object. */ - constructor(point: Point) + constructor(point: Point); - /** + /** * Creates a Point object using the properties in the given object. - * + * * @param object - the object describing the point's properties */ - constructor(object: object) + constructor(object: object); - /** + /** * Sets the point to the passed values. Note that any sequence of parameters * that is supported by the various {@link Point} constructors also work * for calls of `set()`. */ - set(...values: any[]): Point + set(...values: any[]): Point; - /** + /** * Checks whether the coordinates of the point are equal to that of the * supplied point. - * + * * @return true if the points are equal */ - equals(point: Point): boolean + equals(point: Point): boolean; - /** + /** * Returns a copy of the point. - * + * * @return the cloned point */ - clone(): Point + clone(): Point; - /** + /** * @return a string representation of the point */ - toString(): string + toString(): string; - /** + /** * Returns the smaller angle between two vectors. The angle is unsigned, no * information about rotational direction is given. - * + * * @return the angle in degrees */ - getAngle(point: Point): number + getAngle(point: Point): number; - /** + /** * Returns the smaller angle between two vectors in radians. The angle is * unsigned, no information about rotational direction is given. - * + * * @return the angle in radians */ - getAngleInRadians(point: Point): number + getAngleInRadians(point: Point): number; - /** + /** * Returns the angle between two vectors. The angle is directional and * signed, giving information about the rotational direction. - * + * * Read more about angle units and orientation in the description of the * {@link #angle} property. - * + * * @return the angle between the two vectors */ - getDirectedAngle(point: Point): number + getDirectedAngle(point: Point): number; - /** + /** * Returns the distance between the point and another point. - * + * * @param squared - Controls whether the distance should * remain squared, or its square root should be calculated */ - getDistance(point: Point, squared?: boolean): number + getDistance(point: Point, squared?: boolean): number; - /** + /** * Normalize modifies the {@link #length} of the vector to `1` without * changing its angle and returns it as a new point. The optional `length` * parameter defines the length to normalize to. The object itself is not * modified! - * + * * @param length - The length of the normalized vector - * + * * @return the normalized vector of the vector that is represented * by this point's coordinates */ - normalize(length?: number): Point + normalize(length?: number): Point; - /** + /** * Rotates the point by the given angle around an optional center point. * The object itself is not modified. - * + * * Read more about angle units and orientation in the description of the * {@link #angle} property. - * + * * @param angle - the rotation angle * @param center - the center point of the rotation - * + * * @return the rotated point */ - rotate(angle: number, center: Point): Point + rotate(angle: number, center: Point): Point; - /** + /** * Transforms the point by the matrix as a new point. The object itself is * not modified! - * + * * @return the transformed point */ - transform(matrix: Matrix): Point + transform(matrix: Matrix): Point; - /** + /** * Returns the addition of the supplied value to both coordinates of * the point as a new point. * The object itself is not modified! - * + * * @param number - the number to add - * + * * @return the addition of the point and the value as a new point */ - add(number: number): Point + add(number: number): Point; - /** + /** * Returns the addition of the supplied point to the point as a new * point. * The object itself is not modified! - * + * * @param point - the point to add - * + * * @return the addition of the two points as a new point */ - add(point: Point): Point + add(point: Point): Point; - /** + /** * Returns the subtraction of the supplied value to both coordinates of * the point as a new point. * The object itself is not modified! - * + * * @param number - the number to subtract - * + * * @return the subtraction of the point and the value as a new point */ - subtract(number: number): Point + subtract(number: number): Point; - /** + /** * Returns the subtraction of the supplied point to the point as a new * point. * The object itself is not modified! - * + * * @param point - the point to subtract - * + * * @return the subtraction of the two points as a new point */ - subtract(point: Point): Point + subtract(point: Point): Point; - /** + /** * Returns the multiplication of the supplied value to both coordinates of * the point as a new point. * The object itself is not modified! - * + * * @param number - the number to multiply by - * + * * @return the multiplication of the point and the value as a new * point */ - multiply(number: number): Point + multiply(number: number): Point; - /** + /** * Returns the multiplication of the supplied point to the point as a new * point. * The object itself is not modified! - * + * * @param point - the point to multiply by - * + * * @return the multiplication of the two points as a new point */ - multiply(point: Point): Point + multiply(point: Point): Point; - /** + /** * Returns the division of the supplied value to both coordinates of * the point as a new point. * The object itself is not modified! - * + * * @param number - the number to divide by - * + * * @return the division of the point and the value as a new point */ - divide(number: number): Point + divide(number: number): Point; - /** + /** * Returns the division of the supplied point to the point as a new * point. * The object itself is not modified! - * + * * @param point - the point to divide by - * + * * @return the division of the two points as a new point */ - divide(point: Point): Point + divide(point: Point): Point; - /** + /** * The modulo operator returns the integer remainders of dividing the point * by the supplied value as a new point. - * + * * @return the integer remainders of dividing the point by the value * as a new point */ - modulo(value: number): Point + modulo(value: number): Point; - /** + /** * The modulo operator returns the integer remainders of dividing the point * by the supplied value as a new point. - * + * * @return the integer remainders of dividing the points by each * other as a new point */ - modulo(point: Point): Point + modulo(point: Point): Point; - /** + /** * Checks whether the point is inside the boundaries of the rectangle. - * + * * @param rect - the rectangle to check against - * + * * @return true if the point is inside the rectangle */ - isInside(rect: Rectangle): boolean + isInside(rect: Rectangle): boolean; - /** + /** * Checks if the point is within a given distance of another point. - * + * * @param point - the point to check against * @param tolerance - the maximum distance allowed - * + * * @return true if it is within the given distance */ - isClose(point: Point, tolerance: number): boolean + isClose(point: Point, tolerance: number): boolean; - /** + /** * Checks if the vector represented by this point is collinear (parallel) to * another vector. - * + * * @param point - the vector to check against - * + * * @return true it is collinear */ - isCollinear(point: Point): boolean + isCollinear(point: Point): boolean; - /** + /** * Checks if the vector represented by this point is orthogonal * (perpendicular) to another vector. - * + * * @param point - the vector to check against - * + * * @return true it is orthogonal */ - isOrthogonal(point: Point): boolean + isOrthogonal(point: Point): boolean; - /** + /** * Checks if this point has both the x and y coordinate set to 0. - * + * * @return true if both x and y are 0 */ - isZero(): boolean + isZero(): boolean; - /** + /** * Checks if this point has an undefined value for at least one of its * coordinates. - * + * * @return true if either x or y are not a number */ - isNaN(): boolean + isNaN(): boolean; - /** + /** * Checks if the vector is within the specified quadrant. Note that if the * vector lies on the boundary between two quadrants, `true` will be * returned for both quadrants. - * + * * @see #quadrant - * + * * @param quadrant - the quadrant to check against - * + * * @return true if either x or y are not a number */ - isInQuadrant(quadrant: number): boolean + isInQuadrant(quadrant: number): boolean; - /** + /** * Returns the dot product of the point and another point. - * + * * @return the dot product of the two points */ - dot(point: Point): number + dot(point: Point): number; - /** + /** * Returns the cross product of the point and another point. - * + * * @return the cross product of the two points */ - cross(point: Point): number + cross(point: Point): number; - /** + /** * Returns the projection of the point onto another point. * Both points are interpreted as vectors. - * + * * @return the projection of the point onto another point */ - project(point: Point): Point + project(point: Point): Point; - /** + /** * Returns a new point with rounded {@link #x} and {@link #y} values. The * object itself is not modified! */ - round(): Point + round(): Point; - /** + /** * Returns a new point with the nearest greater non-fractional values to the * specified {@link #x} and {@link #y} values. The object itself is not * modified! */ - ceil(): Point + ceil(): Point; - /** + /** * Returns a new point with the nearest smaller non-fractional values to the * specified {@link #x} and {@link #y} values. The object itself is not * modified! */ - floor(): Point + floor(): Point; - /** + /** * Returns a new point with the absolute values of the specified {@link #x} * and {@link #y} values. The object itself is not modified! */ - abs(): Point + abs(): Point; - /** + /** * Returns a new point object with the smallest {@link #x} and * {@link #y} of the supplied points. - * + * * @return the newly created point object */ - static min(point1: Point, point2: Point): Point + static min(point1: Point, point2: Point): Point; - /** + /** * Returns a new point object with the largest {@link #x} and * {@link #y} of the supplied points. - * + * * @return the newly created point object */ - static max(point1: Point, point2: Point): Point + static max(point1: Point, point2: Point): Point; - /** + /** * Returns a point object with random {@link #x} and {@link #y} values * between `0` and `1`. - * + * * @return the newly created point object */ - static random(): Point - + static random(): Point; } - /** + /** * A PointText item represents a piece of typography in your Paper.js * project which starts from a certain point and extends by the amount of * characters contained in it. */ class PointText extends TextItem { - /** + /** * The PointText's anchor point */ - point: Point - + point: Point; - /** + /** * Creates a point text item - * + * * @param point - the position where the text will start */ - constructor(point: Point) + constructor(point: Point); - /** + /** * Creates a point text item from the properties described by an object * literal. - * + * * @param object - an object containing properties describing the * path's attributes */ - constructor(object: object) - + constructor(object: object); } - /** + /** * A Project object in Paper.js is what usually is referred to as the * document: The top level object that holds all the items contained in the * scene graph. As the term document is already taken in the browser context, * it is called Project. - * + * * Projects allow the manipulation of the styles that are applied to all newly * created items, give access to the selected items, and will in future versions * offer ways to query for items in the scene graph defining specific * requirements, and means to persist and load from different formats, such as * SVG and PDF. - * + * * The currently active project can be accessed through the * {@link PaperScope#project} variable. - * + * * An array of all open projects is accessible through the * {@link PaperScope#projects} variable. */ - class Project { - /** + class Project { + /** * The reference to the project's view. */ - readonly view: View + readonly view: View; - /** + /** * The currently active path style. All selected items and newly * created items will be styled with this style. */ - currentStyle: Style + currentStyle: Style; - /** + /** * The index of the project in the {@link PaperScope#projects} list. */ - readonly index: number + readonly index: number; - /** + /** * The layers contained within the project. */ - readonly layers: Layer[] + readonly layers: Layer[]; - /** + /** * The layer which is currently active. New items will be created on this * layer by default. */ - readonly activeLayer: Layer + readonly activeLayer: Layer; - /** + /** * The symbol definitions shared by all symbol items contained place ind * project. */ - readonly symbolDefinitions: SymbolDefinition[] + readonly symbolDefinitions: SymbolDefinition[]; - /** + /** * The selected items contained within the project. */ - readonly selectedItems: Item[] - + readonly selectedItems: Item[]; - /** + /** * Creates a Paper.js project containing one empty {@link Layer}, referenced * by {@link Project#activeLayer}. - * + * * Note that when working with PaperScript, a project is automatically * created for us and the {@link PaperScope#project} variable points to it. - * + * * @param element - the HTML canvas element * that should be used as the element for the view, or an ID string by which * to find the element, or the size of the canvas to be created for usage in * a web worker. */ - constructor(element: HTMLCanvasElement | string | Size) + constructor(element: HTMLCanvasElement | string | Size); - /** + /** * Activates this project, so all newly created items will be placed * in it. */ - activate(): void + activate(): void; - /** + /** * Clears the project by removing all {@link Project#layers}. */ - clear(): void + clear(): void; - /** + /** * Checks whether the project has any content or not. */ - isEmpty(): boolean + isEmpty(): boolean; - /** + /** * Removes this project from the {@link PaperScope#projects} list, and also * removes its view, if one was defined. */ - remove(): void + remove(): void; - /** + /** * Selects all items in the project. */ - selectAll(): void + selectAll(): void; - /** + /** * Deselects all selected items in the project. */ - deselectAll(): void + deselectAll(): void; - /** + /** * Adds the specified layer at the end of the this project's {@link #layers} * list. - * + * * @param layer - the layer to be added to the project - * + * * @return the added layer, or `null` if adding was not possible */ - addLayer(layer: Layer): Layer + addLayer(layer: Layer): Layer; - /** + /** * Inserts the specified layer at the specified index in this project's * {@link #layers} list. - * + * * @param index - the index at which to insert the layer * @param layer - the layer to be inserted in the project - * + * * @return the added layer, or `null` if adding was not possible */ - insertLayer(index: number, layer: Layer): Layer + insertLayer(index: number, layer: Layer): Layer; - /** + /** * Performs a hit-test on the items contained within the project at the * location of the specified point. - * + * * The options object allows you to control the specifics of the hit-test * and may contain a combination of the following values: - * + * * @option [options.tolerance={@link PaperScope#settings}.hitTolerance] * {Number} the tolerance of the hit-test * @option options.class {Function} only hit-test against a specific item @@ -5087,49 +5059,49 @@ declare namespace paper { * @option options.guides {Boolean} hit-test items that have {@link * Item#guide} set to `true` * @option options.selected {Boolean} only hit selected items - * + * * @param point - the point where the hit-test should be performed - * + * * @return a hit result object that contains more information * about what exactly was hit or `null` if nothing was hit */ - hitTest(point: Point, options?: object): HitResult + hitTest(point: Point, options?: object): HitResult; - /** + /** * Performs a hit-test on the item and its children (if it is a {@link * Group} or {@link Layer}) at the location of the specified point, * returning all found hits. - * + * * The options object allows you to control the specifics of the hit- * test. See {@link #hitTest} for a list of all options. - * + * * @see #hitTest(point[, options]); - * + * * @param point - the point where the hit-test should be performed - * + * * @return hit result objects for all hits, describing what * exactly was hit or `null` if nothing was hit */ - hitTestAll(point: Point, options?: object): HitResult[] + hitTestAll(point: Point, options?: object): HitResult[]; - /** + /** * Fetch items contained within the project whose properties match the * criteria in the specified object. - * + * * Extended matching of properties is possible by providing a comparator * function or regular expression. Matching points, colors only work as a * comparison of the full object, not partial matching (e.g. only providing * the x- coordinate to match all points with that x-value). Partial * matching does work for {@link Item#data}. - * + * * Matching items against a rectangular area is also possible, by setting * either `options.inside` or `options.overlapping` to a rectangle * describing the area in which the items either have to be fully or partly * contained. - * + * * @see Item#matches(options) * @see Item#getItems(options) - * + * * @option [options.recursive=true] {Boolean} whether to loop recursively * through all children, or stop at the current level * @option options.match {Function} a match function to be called for each @@ -5142,14 +5114,14 @@ declare namespace paper { * to be fully contained * @option options.overlapping {Rectangle} the rectangle with which the * items need to at least partly overlap - * + * * @param options - the criteria to match against - * + * * @return the list of matching items contained in the project */ - getItems(options: object | Function): Item[] + getItems(options: object | Function): Item[]; - /** + /** * Fetch the first item contained within the project whose properties * match the criteria in the specified object. * Extended matching is possible by providing a compare function or @@ -5157,45 +5129,45 @@ declare namespace paper { * of the full object, not partial matching (e.g. only providing the x- * coordinate to match all points with that x-value). Partial matching * does work for {@link Item#data}. - * + * * See {@link #getItems} for a selection of illustrated examples. - * + * * @param options - the criteria to match against - * + * * @return the first item in the project matching the given criteria */ - getItem(options: object | Function): Item + getItem(options: object | Function): Item; - /** + /** * Exports (serializes) the project with all its layers and child items to a * JSON data object or string. - * + * * @option [options.asString=true] {Boolean} whether the JSON is returned as * a `Object` or a `String` * @option [options.precision=5] {Number} the amount of fractional digits in * numbers used in JSON data - * + * * @param options - the serialization options - * + * * @return the exported JSON data */ - exportJSON(options?: object): string + exportJSON(options?: object): string; - /** + /** * Imports (deserializes) the stored JSON data into the project. * Note that the project is not cleared first. You can call * {@link Project#clear} to do so. - * + * * @param json - the JSON data to import from - * + * * @return the imported item */ - importJSON(json: string): Item + importJSON(json: string): Item; - /** + /** * Exports the project with all its layers and child items as an SVG DOM, * all contained in one top level SVG group node. - * + * * @option [options.bounds='view'] {String|Rectangle} the bounds of the area * to export, either as a string ({@values 'view', content'}), or a * {@link Rectangle} object: `'view'` uses the view bounds, @@ -5214,20 +5186,20 @@ declare namespace paper { * @option [options.embedImages=true] {Boolean} whether raster images should * be embedded as base64 data inlined in the xlink:href attribute, or * kept as a link to their external URL. - * + * * @param options - the export options - * + * * @return the project converted to an SVG node or a * `String` depending on `option.asString` value */ - exportSVG(options?: object): SVGElement | string + exportSVG(options?: object): SVGElement | string; - /** + /** * Converts the provided SVG content into Paper.js items and adds them to * the active layer of this project. * Note that the project is not cleared first. You can call * {@link Project#clear} to do so. - * + * * @option [options.expandShapes=false] {Boolean} whether imported shape * items should be expanded to path items * @option options.onLoad {Function} the callback function to call once the @@ -5242,89 +5214,88 @@ declare namespace paper { * @option [options.applyMatrix={@link PaperScope#settings}.applyMatrix] * {Boolean} whether the imported items should have their transformation * matrices applied to their contents or not - * + * * @param svg - the SVG content to import, either as a SVG * DOM node, a string containing SVG content, or a string describing the * URL of the SVG file to fetch. * @param options - the import options - * + * * @return the newly created Paper.js item containing the converted * SVG content */ - importSVG(svg: SVGElement | string, options?: object): Item + importSVG(svg: SVGElement | string, options?: object): Item; - /** + /** * Imports the provided external SVG file, converts it into Paper.js items * and adds them to the active layer of this project. * Note that the project is not cleared first. You can call * {@link Project#clear} to do so. - * + * * @param svg - the URL of the SVG file to fetch. * @param onLoad - the callback function to call once the SVG * content is loaded from the given URL receiving two arguments: the * converted `item` and the original `svg` data as a string. Only * required when loading from external files. - * + * * @return the newly created Paper.js item containing the converted * SVG content */ - importSVG(svg: SVGElement | string, onLoad: Function): Item - + importSVG(svg: SVGElement | string, onLoad: Function): Item; } - /** + /** * The Raster item represents an image in a Paper.js project. */ class Raster extends Item { - /** + /** * The size of the raster in pixels. */ - size: Size + size: Size; - /** + /** * The width of the raster in pixels. */ - width: number + width: number; - /** + /** * The height of the raster in pixels. */ - height: number + height: number; - /** + /** * The loading state of the raster image. */ - readonly loaded: boolean + readonly loaded: boolean; - /** + /** * The resolution of the raster at its current size, in PPI (pixels per * inch). */ - readonly resolution: Size + readonly resolution: Size; - /** + /** * The HTMLImageElement or Canvas element of the raster, if one is * associated. * Note that for consistency, a {@link #onLoad} event will be triggered on * the raster even if the image has already finished loading before, or if * we are setting the raster to a canvas. */ - image: HTMLImageElement | HTMLCanvasElement + image: HTMLImageElement | HTMLCanvasElement; - /** + /** * The Canvas object of the raster. If the raster was created from an image, * accessing its canvas causes the raster to try and create one and draw the * image into it. Depending on security policies, this might fail, in which * case `null` is returned instead. */ - canvas: HTMLCanvasElement + canvas: HTMLCanvasElement; - /** + /** * The Canvas 2D drawing context of the raster. */ - context: CanvasRenderingContext2D + context: CanvasRenderingContext2D; - /** + /** * The source of the raster, which can be set using a DOM Image, a Canvas, * a data url, a string describing the URL to load the image from, or the * ID of a DOM element to get the image from (either a DOM Image or a @@ -5333,557 +5304,552 @@ declare namespace paper { * Note that for consistency, a {@link #onLoad} event will be triggered on * the raster even if the image has already finished loading before. */ - source: HTMLImageElement | HTMLCanvasElement | string + source: HTMLImageElement | HTMLCanvasElement | string; - /** + /** * The crossOrigin value to be used when loading the image resource, in * order to support CORS. Note that this needs to be set before setting the * {@link #source} property in order to always work (e.g. when the image is * cached in the browser). */ - crossOrigin: string + crossOrigin: string; - /** + /** * Determines if the raster is drawn with pixel smoothing when scaled up or * down, and if so, at which quality its pixels are to be smoothed. The * settings of this property control both the `imageSmoothingEnabled` and * `imageSmoothingQuality` properties of the `CanvasRenderingContext2D` * interface. - * + * * By default, smoothing is enabled at `'low'` quality. It can be set to of * `'off'` to scale the raster's pixels by repeating the nearest neighboring * pixels, or to `'low'`, `'medium'` or `'high'` to control the various * degrees of available image smoothing quality. - * + * * For backward compatibility, it can can also be set to `false` (= `'off'`) * or `true` (= `'low'`). */ - smoothing: string + smoothing: string; - /** + /** * The event handler function to be called when the underlying image has * finished loading and is ready to be used. This is also triggered when * the image is already loaded, or when a canvas is used instead of an * image. */ - onLoad: Function | null + onLoad: Function | null; - /** + /** * The event handler function to be called when there is an error loading * the underlying image. */ - onError: Function | null + onError: Function | null; - - /** + /** * Creates a new raster item from the passed argument, and places it in the * active layer. `source` can either be a DOM Image, a Canvas, or a string * describing the URL to load the image from, or the ID of a DOM element to * get the image from (either a DOM Image or a Canvas). - * + * * @param source - the source of * the raster * @param position - the center position at which the raster item is * placed */ - constructor(source?: HTMLImageElement | HTMLCanvasElement | string, position?: Point) + constructor( + source?: HTMLImageElement | HTMLCanvasElement | string, + position?: Point + ); - /** + /** * Creates a new empty raster of the given size, and places it in the * active layer. - * + * * @param size - the size of the raster * @param position - the center position at which the raster item is * placed */ - constructor(size: Size, position?: Point) + constructor(size: Size, position?: Point); - /** + /** * Creates a new raster from an object description, and places it in the * active layer. - * + * * @param object - an object containing properties to be set on the * raster */ - constructor(object: object) + constructor(object: object); - /** + /** * Extracts a part of the Raster's content as a sub image, and returns it as * a Canvas object. - * + * * @param rect - the boundaries of the sub image in pixel * coordinates - * + * * @return the sub image as a Canvas object */ - getSubCanvas(rect: Rectangle): HTMLCanvasElement + getSubCanvas(rect: Rectangle): HTMLCanvasElement; - /** + /** * Extracts a part of the raster item's content as a new raster item, placed * in exactly the same place as the original content. - * + * * @param rect - the boundaries of the sub raster in pixel * coordinates - * + * * @return the sub raster as a newly created raster item */ - getSubRaster(rect: Rectangle): Raster + getSubRaster(rect: Rectangle): Raster; - /** + /** * Returns a Base 64 encoded `data:` URL representation of the raster. */ - toDataURL(): string + toDataURL(): string; - /** + /** * Draws an image on the raster. - * + * * @param point - the offset of the image as a point in pixel * coordinates */ - drawImage(image: CanvasImageSource, point: Point): void + drawImage(image: CanvasImageSource, point: Point): void; - /** + /** * Calculates the average color of the image within the given path, * rectangle or point. This can be used for creating raster image * effects. - * + * * @return the average color contained in the area covered by the * specified path, rectangle or point */ - getAverageColor(object: Path | Rectangle | Point): Color + getAverageColor(object: Path | Rectangle | Point): Color; - /** + /** * Gets the color of a pixel in the raster. - * + * * @param x - the x offset of the pixel in pixel coordinates * @param y - the y offset of the pixel in pixel coordinates - * + * * @return the color of the pixel */ - getPixel(x: number, y: number): Color + getPixel(x: number, y: number): Color; - /** + /** * Gets the color of a pixel in the raster. - * + * * @param point - the offset of the pixel as a point in pixel * coordinates - * + * * @return the color of the pixel */ - getPixel(point: Point): Color + getPixel(point: Point): Color; - /** + /** * Sets the color of the specified pixel to the specified color. - * + * * @param x - the x offset of the pixel in pixel coordinates * @param y - the y offset of the pixel in pixel coordinates * @param color - the color that the pixel will be set to */ - setPixel(x: number, y: number, color: Color): void + setPixel(x: number, y: number, color: Color): void; - /** + /** * Sets the color of the specified pixel to the specified color. - * + * * @param point - the offset of the pixel as a point in pixel * coordinates * @param color - the color that the pixel will be set to */ - setPixel(point: Point, color: Color): void + setPixel(point: Point, color: Color): void; - /** + /** * Clears the image, if it is backed by a canvas. */ - clear(): void - - - createImageData(size: Size): ImageData + clear(): void; - - getImageData(rect: Rectangle): ImageData + createImageData(size: Size): ImageData; - - setImageData(data: ImageData, point: Point): void + getImageData(rect: Rectangle): ImageData; + setImageData(data: ImageData, point: Point): void; } - /** + /** * A Rectangle specifies an area that is enclosed by it's top-left * point (x, y), its width, and its height. It should not be confused with a * rectangular path, it is not an item. */ - class Rectangle { - /** + class Rectangle { + /** * The x position of the rectangle. */ - x: number + x: number; - /** + /** * The y position of the rectangle. */ - y: number + y: number; - /** + /** * The width of the rectangle. */ - width: number + width: number; - /** + /** * The height of the rectangle. */ - height: number + height: number; - /** + /** * The top-left point of the rectangle */ - point: Point + point: Point; - /** + /** * The size of the rectangle */ - size: Size + size: Size; - /** + /** * The position of the left hand side of the rectangle. Note that this * doesn't move the whole rectangle; the right hand side stays where it was. */ - left: number + left: number; - /** + /** * The top coordinate of the rectangle. Note that this doesn't move the * whole rectangle: the bottom won't move. */ - top: number + top: number; - /** + /** * The position of the right hand side of the rectangle. Note that this * doesn't move the whole rectangle; the left hand side stays where it was. */ - right: number + right: number; - /** + /** * The bottom coordinate of the rectangle. Note that this doesn't move the * whole rectangle: the top won't move. */ - bottom: number + bottom: number; - /** + /** * The center point of the rectangle. */ - center: Point + center: Point; - /** + /** * The top-left point of the rectangle. */ - topLeft: Point + topLeft: Point; - /** + /** * The top-right point of the rectangle. */ - topRight: Point + topRight: Point; - /** + /** * The bottom-left point of the rectangle. */ - bottomLeft: Point + bottomLeft: Point; - /** + /** * The bottom-right point of the rectangle. */ - bottomRight: Point + bottomRight: Point; - /** + /** * The left-center point of the rectangle. */ - leftCenter: Point + leftCenter: Point; - /** + /** * The top-center point of the rectangle. */ - topCenter: Point + topCenter: Point; - /** + /** * The right-center point of the rectangle. */ - rightCenter: Point + rightCenter: Point; - /** + /** * The bottom-center point of the rectangle. */ - bottomCenter: Point + bottomCenter: Point; - /** + /** * The area of the rectangle. */ - readonly area: number + readonly area: number; - /** + /** * Specifies whether an item's bounds are to appear as selected. - * + * * Paper.js draws the bounds of items with selected bounds on top of * your project. This is very useful when debugging. */ - selected: boolean - + selected: boolean; - /** + /** * Creates a Rectangle object. - * + * * @param point - the top-left point of the rectangle * @param size - the size of the rectangle */ - constructor(point: Point, size: Size) + constructor(point: Point, size: Size); - /** + /** * Creates a rectangle object. - * + * * @param x - the left coordinate * @param y - the top coordinate */ - constructor(x: number, y: number, width: number, height: number) + constructor(x: number, y: number, width: number, height: number); - /** + /** * Creates a rectangle object from the passed points. These do not * necessarily need to be the top left and bottom right corners, the * constructor figures out how to fit a rectangle between them. - * + * * @param from - the first point defining the rectangle * @param to - the second point defining the rectangle */ - constructor(from: Point, to: Point) + constructor(from: Point, to: Point); - /** + /** * Creates a new rectangle object from the passed rectangle object. */ - constructor(rectangle: Rectangle) + constructor(rectangle: Rectangle); - /** + /** * Creates a Rectangle object. - * + * * @param object - an object containing properties to be set on the * rectangle */ - constructor(object: object) + constructor(object: object); - /** + /** * Sets the rectangle to the passed values. Note that any sequence of * parameters that is supported by the various {@link Rectangle} * constructors also work for calls of `set()`. */ - set(...values: any[]): Rectangle + set(...values: any[]): Rectangle; - /** + /** * Returns a copy of the rectangle. */ - clone(): Rectangle + clone(): Rectangle; - /** + /** * Checks whether the coordinates and size of the rectangle are equal to * that of the supplied rectangle. - * + * * @return true if the rectangles are equal */ - equals(rect: Rectangle): boolean + equals(rect: Rectangle): boolean; - /** + /** * @return a string representation of this rectangle */ - toString(): string + toString(): string; - /** + /** * @return true if the rectangle is empty */ - isEmpty(): boolean + isEmpty(): boolean; - /** + /** * Tests if the specified point is inside the boundary of the rectangle. - * + * * @param point - the specified point - * + * * @return true if the point is inside the rectangle's boundary */ - contains(point: Point): boolean + contains(point: Point): boolean; - /** + /** * Tests if the interior of the rectangle entirely contains the specified * rectangle. - * + * * @param rect - the specified rectangle - * + * * @return true if the rectangle entirely contains the specified * rectangle */ - contains(rect: Rectangle): boolean + contains(rect: Rectangle): boolean; - /** + /** * Tests if the interior of this rectangle intersects the interior of * another rectangle. Rectangles just touching each other are considered as * non-intersecting, except if a `epsilon` value is specified by which this * rectangle's dimensions are increased before comparing. - * + * * @param rect - the specified rectangle * @param epsilon - the epsilon against which to compare the * rectangle's dimensions - * + * * @return true if the rectangle and the specified rectangle * intersect each other */ - intersects(rect: Rectangle, epsilon?: number): boolean + intersects(rect: Rectangle, epsilon?: number): boolean; - /** + /** * Returns a new rectangle representing the intersection of this rectangle * with the specified rectangle. - * + * * @param rect - the rectangle to be intersected with this * rectangle - * + * * @return the largest rectangle contained in both the specified * rectangle and in this rectangle */ - intersect(rect: Rectangle): Rectangle + intersect(rect: Rectangle): Rectangle; - /** + /** * Returns a new rectangle representing the union of this rectangle with the * specified rectangle. - * + * * @param rect - the rectangle to be combined with this rectangle - * + * * @return the smallest rectangle containing both the specified * rectangle and this rectangle */ - unite(rect: Rectangle): Rectangle + unite(rect: Rectangle): Rectangle; - /** + /** * Adds a point to this rectangle. The resulting rectangle is the smallest * rectangle that contains both the original rectangle and the specified * point. - * + * * After adding a point, a call to {@link #contains} with the added * point as an argument does not necessarily return `true`. The {@link * Rectangle#contains(point)} method does not return `true` for points on * the right or bottom edges of a rectangle. Therefore, if the added point * falls on the left or bottom edge of the enlarged rectangle, {@link * Rectangle#contains(point)} returns `false` for that point. - * + * * @return the smallest rectangle that contains both the * original rectangle and the specified point */ - include(point: Point): Rectangle + include(point: Point): Rectangle; - /** + /** * Returns a new rectangle expanded by the specified amount in horizontal * and vertical directions. - * + * * @param amount - the amount to expand the rectangle in * both directions - * + * * @return the expanded rectangle */ - expand(amount: number | Size | Point): Rectangle + expand(amount: number | Size | Point): Rectangle; - /** + /** * Returns a new rectangle expanded by the specified amounts in horizontal * and vertical directions. - * + * * @param hor - the amount to expand the rectangle in horizontal * direction * @param ver - the amount to expand the rectangle in vertical * direction - * + * * @return the expanded rectangle */ - expand(hor: number, ver: number): Rectangle + expand(hor: number, ver: number): Rectangle; - /** + /** * Returns a new rectangle scaled by the specified amount from its center. - * + * * @return the scaled rectangle */ - scale(amount: number): Rectangle + scale(amount: number): Rectangle; - /** + /** * Returns a new rectangle scaled in horizontal direction by the specified * `hor` amount and in vertical direction by the specified `ver` amount * from its center. - * + * * @return the scaled rectangle */ - scale(hor: number, ver: number): Rectangle - + scale(hor: number, ver: number): Rectangle; } - /** + /** * The Segment object represents the points of a path through which its * {@link Curve} objects pass. The segments of a path can be accessed through * its {@link Path#segments} array. - * + * * Each segment consists of an anchor point ({@link Segment#point}) and * optionaly an incoming and an outgoing handle ({@link Segment#handleIn} and * {@link Segment#handleOut}), describing the tangents of the two {@link Curve} * objects that are connected by this segment. */ - class Segment { - /** + class Segment { + /** * The anchor point of the segment. */ - point: Point + point: Point; - /** + /** * The handle point relative to the anchor point of the segment that * describes the in tangent of the segment. */ - handleIn: Point + handleIn: Point; - /** + /** * The handle point relative to the anchor point of the segment that * describes the out tangent of the segment. */ - handleOut: Point + handleOut: Point; - /** + /** * Specifies whether the segment is selected. */ - selected: boolean + selected: boolean; - /** + /** * The index of the segment in the {@link Path#segments} array that the * segment belongs to. */ - readonly index: number + readonly index: number; - /** + /** * The path that the segment belongs to. */ - readonly path: Path + readonly path: Path; - /** + /** * The curve that the segment belongs to. For the last segment of an open * path, the previous segment is returned. */ - readonly curve: Curve + readonly curve: Curve; - /** + /** * The curve location that describes this segment's position on the path. */ - readonly location: CurveLocation + readonly location: CurveLocation; - /** + /** * The next segment in the {@link Path#segments} array that the segment * belongs to. If the segments belongs to a closed path, the first segment * is returned for the last segment of the path. */ - readonly next: Segment + readonly next: Segment; - /** + /** * The previous segment in the {@link Path#segments} array that the * segment belongs to. If the segments belongs to a closed path, the last * segment is returned for the first segment of the path. */ - readonly previous: Segment - + readonly previous: Segment; - /** + /** * Creates a new Segment object. - * + * * @param point - the anchor point of the segment * @param handleIn - the handle point relative to the * anchor point of the segment that describes the in tangent of the @@ -5892,139 +5858,138 @@ declare namespace paper { * anchor point of the segment that describes the out tangent of the * segment */ - constructor(point?: Point, handleIn?: Point, handleOut?: Point) + constructor(point?: Point, handleIn?: Point, handleOut?: Point); - /** + /** * Creates a new Segment object. - * + * * @param object - an object containing properties to be set on the * segment */ - constructor(object: object) + constructor(object: object); - /** + /** * Checks if the segment has any curve handles set. - * + * * @see Segment#handleIn * @see Segment#handleOut * @see Curve#hasHandles() * @see Path#hasHandles() - * + * * @return true if the segment has handles set */ - hasHandles(): boolean + hasHandles(): boolean; - /** + /** * Checks if the segment connects two curves smoothly, meaning that its two * handles are collinear and segment does not form a corner. - * + * * @see Point#isCollinear() - * + * * @return true if the segment is smooth */ - isSmooth(): boolean + isSmooth(): boolean; - /** + /** * Clears the segment's handles by setting their coordinates to zero, * turning the segment into a corner. */ - clearHandles(): void + clearHandles(): void; - /** + /** * Smooths the bezier curves that pass through this segment by taking into * account the segment's position and distance to the neighboring segments * and changing the direction and length of the segment's handles * accordingly without moving the segment itself. - * + * * Two different smoothing methods are available: - * + * * - `'catmull-rom'` uses the Catmull-Rom spline to smooth the segment. - * + * * The optionally passed factor controls the knot parametrization of the * algorithm: - * + * * - `0.0`: the standard, uniform Catmull-Rom spline * - `0.5`: the centripetal Catmull-Rom spline, guaranteeing no * self-intersections * - `1.0`: the chordal Catmull-Rom spline - * + * * - `'geometric'` use a simple heuristic and empiric geometric method to * smooth the segment's handles. The handles were weighted, meaning that * big differences in distances between the segments will lead to * probably undesired results. - * + * * The optionally passed factor defines the tension parameter (`0...1`), * controlling the amount of smoothing as a factor by which to scale * each handle. - * + * * @see PathItem#smooth([options]) - * + * * @option [options.type='catmull-rom'] {String} the type of smoothing * method: {@values 'catmull-rom', 'geometric'} * @option options.factor {Number} the factor parameterizing the smoothing * method — default: `0.5` for `'catmull-rom'`, `0.4` for `'geometric'` - * + * * @param options - the smoothing options */ - smooth(options?: object): void + smooth(options?: object): void; - /** + /** * Checks if the this is the first segment in the {@link Path#segments} * array. - * + * * @return true if this is the first segment */ - isFirst(): boolean + isFirst(): boolean; - /** + /** * Checks if the this is the last segment in the {@link Path#segments} * array. - * + * * @return true if this is the last segment */ - isLast(): boolean + isLast(): boolean; - /** + /** * Reverses the {@link #handleIn} and {@link #handleOut} vectors of this * segment, modifying the actual segment without creating a copy. - * + * * @return the reversed segment */ - reverse(): Segment + reverse(): Segment; - /** + /** * Returns the reversed the segment, without modifying the segment itself. - * + * * @return the reversed segment */ - reversed(): Segment + reversed(): Segment; - /** + /** * Removes the segment from the path that it belongs to. - * + * * @return true if the segment was removed */ - remove(): boolean + remove(): boolean; - - clone(): Segment + clone(): Segment; - /** + /** * @return a string representation of the segment */ - toString(): string + toString(): string; - /** + /** * Transform the segment by the specified matrix. - * + * * @param matrix - the matrix to transform the segment by */ - transform(matrix: Matrix): void + transform(matrix: Matrix): void; - /** + /** * Interpolates between the two specified segments and sets the point and * handles of this segment accordingly. - * + * * @param from - the segment defining the geometry when `factor` is * `0` * @param to - the segment defining the geometry when `factor` is @@ -6032,568 +5997,574 @@ declare namespace paper { * @param factor - the interpolation coefficient, typically between * `0` and `1`, but extrapolation is possible too */ - interpolate(from: Segment, to: Segment, factor: number): void - + interpolate(from: Segment, to: Segment, factor: number): void; } - class Shape extends Item { - /** + /** * The type of shape of the item as a string. */ - type: string + type: string; - /** + /** * The size of the shape. */ - size: Size + size: Size; - /** + /** * The radius of the shape, as a number if it is a circle, or a size object * for ellipses and rounded rectangles. */ - radius: number | Size + radius: number | Size; - - /** + /** * Creates a new path item with same geometry as this shape item, and * inherits all settings from it, similar to {@link Item#clone}. - * + * * @see Path#toShape(insert) - * + * * @param insert - specifies whether the new path should be * inserted into the scene graph. When set to `true`, it is inserted * above the shape item - * + * * @return the newly created path item with the same geometry as * this shape item */ - toPath(insert?: boolean): Path - + toPath(insert?: boolean): Path; } namespace Shape { - class Circle extends Shape { - /** + /** * Creates a circular shape item. - * + * * @param center - the center point of the circle * @param radius - the radius of the circle */ - constructor(center: Point, radius: number) + constructor(center: Point, radius: number); - /** + /** * Creates a circular shape item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * shape's attributes */ - constructor(object: object) - + constructor(object: object); } class Rectangle extends Shape { - /** + /** * Creates a rectangular shape item, with optionally rounded corners. - * + * * @param rectangle - the rectangle object describing the * geometry of the rectangular shape to be created * @param radius - the size of the rounded corners */ - constructor(rectangle: paper.Rectangle, radius?: Size) + constructor(rectangle: paper.Rectangle, radius?: Size); - /** + /** * Creates a rectangular shape item from a point and a size object. - * + * * @param point - the rectangle's top-left corner. * @param size - the rectangle's size. */ - constructor(point: Point, size: Size) + constructor(point: Point, size: Size); - /** + /** * Creates a rectangular shape item from the passed points. These do not * necessarily need to be the top left and bottom right corners, the * constructor figures out how to fit a rectangle between them. - * + * * @param from - the first point defining the rectangle * @param to - the second point defining the rectangle */ - constructor(from: Point, to: Point) + constructor(from: Point, to: Point); - /** + /** * Creates a rectangular shape item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * shape's attributes */ - constructor(object: object) - + constructor(object: object); } class Ellipse extends Shape { - /** + /** * Creates an elliptical shape item. - * + * * @param rectangle - the rectangle circumscribing the ellipse */ - constructor(rectangle: paper.Rectangle) + constructor(rectangle: paper.Rectangle); - /** + /** * Creates an elliptical shape item from the properties described by an * object literal. - * + * * @param object - an object containing properties describing the * shape's attributes */ - constructor(object: object) - + constructor(object: object); } } - /** + /** * The Size object is used to describe the size or dimensions of * something, through its {@link #width} and {@link #height} properties. */ - class Size { - /** + class Size { + /** * The width of the size */ - width: number + width: number; - /** + /** * The height of the size */ - height: number + height: number; - - /** + /** * Creates a Size object with the given width and height values. - * + * * @param width - the width * @param height - the height */ - constructor(width: number, height: number) + constructor(width: number, height: number); - /** + /** * Creates a Size object using the numbers in the given array as * dimensions. */ - constructor(array: any[]) + constructor(array: any[]); - /** + /** * Creates a Size object using the coordinates of the given Size object. */ - constructor(size: Size) + constructor(size: Size); - /** + /** * Creates a Size object using the {@link Point#x} and {@link Point#y} * values of the given Point object. */ - constructor(point: Point) + constructor(point: Point); - /** + /** * Creates a Size object using the properties in the given object. */ - constructor(object: object) + constructor(object: object); - /** + /** * Sets the size to the passed values. Note that any sequence of parameters * that is supported by the various {@link Size} constructors also work * for calls of `set()`. */ - set(...values: any[]): Size + set(...values: any[]): Size; - /** + /** * Checks whether the width and height of the size are equal to those of the * supplied size. - * + * * @param size - the size to compare to */ - equals(size: Size): boolean + equals(size: Size): boolean; - /** + /** * Returns a copy of the size. */ - clone(): Size + clone(): Size; - /** + /** * @return a string representation of the size */ - toString(): string + toString(): string; - /** + /** * Returns the addition of the supplied value to the width and height of the * size as a new size. The object itself is not modified! - * + * * @param number - the number to add - * + * * @return the addition of the size and the value as a new size */ - add(number: number): Size + add(number: number): Size; - /** + /** * Returns the addition of the width and height of the supplied size to the * size as a new size. The object itself is not modified! - * + * * @param size - the size to add - * + * * @return the addition of the two sizes as a new size */ - add(size: Size): Size + add(size: Size): Size; - /** + /** * Returns the subtraction of the supplied value from the width and height * of the size as a new size. The object itself is not modified! * The object itself is not modified! - * + * * @param number - the number to subtract - * + * * @return the subtraction of the size and the value as a new size */ - subtract(number: number): Size + subtract(number: number): Size; - /** + /** * Returns the subtraction of the width and height of the supplied size from * the size as a new size. The object itself is not modified! - * + * * @param size - the size to subtract - * + * * @return the subtraction of the two sizes as a new size */ - subtract(size: Size): Size + subtract(size: Size): Size; - /** + /** * Returns the multiplication of the supplied value with the width and * height of the size as a new size. The object itself is not modified! - * + * * @param number - the number to multiply by - * + * * @return the multiplication of the size and the value as a new size */ - multiply(number: number): Size + multiply(number: number): Size; - /** + /** * Returns the multiplication of the width and height of the supplied size * with the size as a new size. The object itself is not modified! - * + * * @param size - the size to multiply by - * + * * @return the multiplication of the two sizes as a new size */ - multiply(size: Size): Size + multiply(size: Size): Size; - /** + /** * Returns the division of the supplied value by the width and height of the * size as a new size. The object itself is not modified! - * + * * @param number - the number to divide by - * + * * @return the division of the size and the value as a new size */ - divide(number: number): Size + divide(number: number): Size; - /** + /** * Returns the division of the width and height of the supplied size by the * size as a new size. The object itself is not modified! - * + * * @param size - the size to divide by - * + * * @return the division of the two sizes as a new size */ - divide(size: Size): Size + divide(size: Size): Size; - /** + /** * The modulo operator returns the integer remainders of dividing the size * by the supplied value as a new size. - * + * * @return the integer remainders of dividing the size by the value * as a new size */ - modulo(value: number): Size + modulo(value: number): Size; - /** + /** * The modulo operator returns the integer remainders of dividing the size * by the supplied size as a new size. - * + * * @return the integer remainders of dividing the sizes by each * other as a new size */ - modulo(size: Size): Size + modulo(size: Size): Size; - /** + /** * Checks if this size has both the width and height set to 0. - * + * * @return true if both width and height are 0 */ - isZero(): boolean + isZero(): boolean; - /** + /** * Checks if the width or the height of the size are NaN. - * + * * @return true if the width or height of the size are NaN */ - isNaN(): boolean + isNaN(): boolean; - /** + /** * Returns a new size with rounded {@link #width} and {@link #height} * values. The object itself is not modified! */ - round(): Size + round(): Size; - /** + /** * Returns a new size with the nearest greater non-fractional values to the * specified {@link #width} and {@link #height} values. The object itself is * not modified! */ - ceil(): Size + ceil(): Size; - /** + /** * Returns a new size with the nearest smaller non-fractional values to the * specified {@link #width} and {@link #height} values. The object itself is * not modified! */ - floor(): Size + floor(): Size; - /** + /** * Returns a new size with the absolute values of the specified * {@link #width} and {@link #height} values. The object itself is not * modified! */ - abs(): Size + abs(): Size; - /** + /** * Returns a new size object with the smallest {@link #width} and * {@link #height} of the supplied sizes. - * + * * @return the newly created size object */ - static min(size1: Size, size2: Size): Size + static min(size1: Size, size2: Size): Size; - /** + /** * Returns a new size object with the largest {@link #width} and * {@link #height} of the supplied sizes. - * + * * @return the newly created size object */ - static max(size1: Size, size2: Size): Size + static max(size1: Size, size2: Size): Size; - /** + /** * Returns a size object with random {@link #width} and {@link #height} * values between `0` and `1`. - * + * * @return the newly created size object */ - static random(): Size - + static random(): Size; } - /** + /** * Style is used for changing the visual styles of items * contained within a Paper.js project and is returned by * {@link Item#style} and {@link Project#currentStyle}. - * + * * All properties of Style are also reflected directly in {@link Item}, * i.e.: {@link Item#fillColor}. - * + * * To set multiple style properties in one go, you can pass an object to * {@link Item#style}. This is a convenient way to define a style once and * apply it to a series of items: */ - class Style { - /** + class Style { + /** * The view that this style belongs to. */ - readonly view: View + readonly view: View; - /** + /** * The color of the stroke. */ - strokeColor: Color | null + strokeColor: Color | null; - /** + /** * The width of the stroke. */ - strokeWidth: number + strokeWidth: number; - /** + /** * The shape to be used at the beginning and end of open {@link Path} items, * when they have a stroke. */ - strokeCap: string + strokeCap: string; - /** + /** * The shape to be used at the segments and corners of {@link Path} items * when they have a stroke. */ - strokeJoin: string + strokeJoin: string; - /** + /** * Specifies whether the stroke is to be drawn taking the current affine * transformation into account (the default behavior), or whether it should * appear as a non-scaling stroke. */ - strokeScaling: boolean + strokeScaling: boolean; - /** + /** * The dash offset of the stroke. */ - dashOffset: number + dashOffset: number; - /** + /** * Specifies an array containing the dash and gap lengths of the stroke. */ - dashArray: number[] + dashArray: number[]; - /** + /** * The miter limit of the stroke. When two line segments meet at a sharp * angle and miter joins have been specified for {@link #strokeJoin}, it is * possible for the miter to extend far beyond the {@link #strokeWidth} of * the path. The miterLimit imposes a limit on the ratio of the miter length * to the {@link #strokeWidth}. */ - miterLimit: number + miterLimit: number; - /** + /** * The fill color. */ - fillColor: Color | null + fillColor: Color | null; - /** + /** * The fill-rule with which the shape gets filled. Please note that only * modern browsers support fill-rules other than `'nonzero'`. */ - fillRule: string + fillRule: string; - /** + /** * The shadow color. */ - shadowColor: Color | null + shadowColor: Color | null; - /** + /** * The shadow's blur radius. */ - shadowBlur: number + shadowBlur: number; - /** + /** * The shadow's offset. */ - shadowOffset: Point + shadowOffset: Point; - /** + /** * The color the item is highlighted with when selected. If the item does * not specify its own color, the color defined by its layer is used instead. */ - selectedColor: Color | null + selectedColor: Color | null; + + /** + * The width of the selection stroke. + */ + selectionWidth: number; - /** + /** + * The shape to be used at the beginning and end of open {@link Path} items, + * when they have a selection stroke. + */ + selectionCap: string; + + /** + * The shape to be used at the segments and corners of {@link Path} items + * when they have a selection stroke. + */ + selectionJoin: string; + + /** + * The miter limit of the selection stroke. + */ + selectedMiterLimit: number; + + /** * The font-family to be used in text content. */ - fontFamily: string + fontFamily: string; - /** + /** * The font-weight to be used in text content. */ - fontWeight: string | number + fontWeight: string | number; - /** + /** * The font size of text content, as a number in pixels, or as a string with * optional units `'px'`, `'pt'` and `'em'`. */ - fontSize: number | string + fontSize: number | string; - /** + /** * The text leading of text content. */ - leading: number | string + leading: number | string; - /** + /** * The justification of text paragraphs. */ - justification: string + justification: string; - - /** + /** * Style objects don't need to be created directly. Just pass an object to * {@link Item#style} or {@link Project#currentStyle}, it will be converted * to a Style object internally. */ - constructor(style: object) - + constructor(style: object); } - /** + /** * Symbols allow you to place multiple instances of an item in your * project. This can save memory, since all instances of a symbol simply refer * to the original item and it can speed up moving around complex objects, since * internal properties such as segment lists and gradient positions don't need * to be updated with every transformation. */ - class SymbolDefinition { - /** + class SymbolDefinition { + /** * The project that this symbol belongs to. */ - readonly project: Project + readonly project: Project; - /** + /** * The item used as the symbol's definition. */ - item: Item - + item: Item; - /** + /** * Creates a Symbol definition. - * + * * @param item - the source item which is removed from the scene graph * and becomes the symbol's definition. */ - constructor(item: Item, dontCenter?: boolean) + constructor(item: Item, dontCenter?: boolean); - /** + /** * Places in instance of the symbol in the project. - * + * * @param position - the position of the placed symbol */ - place(position?: Point): SymbolItem + place(position?: Point): SymbolItem; - /** + /** * Returns a copy of the symbol. */ - clone(): SymbolDefinition + clone(): SymbolDefinition; - /** + /** * Checks whether the symbol's definition is equal to the supplied symbol. - * + * * @return true if they are equal */ - equals(symbol: SymbolDefinition): boolean - + equals(symbol: SymbolDefinition): boolean; } - /** + /** * A symbol item represents an instance of a symbol which has been * placed in a Paper.js project. */ class SymbolItem extends Item { - /** + /** * The symbol definition that the placed symbol refers to. */ - definition: SymbolDefinition - + definition: SymbolDefinition; - /** + /** * Creates a new symbol item. - * + * * @param definition - the definition to place or an * item to place as a symbol * @param point - the center point of the placed symbol */ - constructor(definition: SymbolDefinition | Item, point?: Point) - + constructor(definition: SymbolDefinition | Item, point?: Point); } - /** + /** * The TextItem type allows you to create typography. Its functionality * is inherited by different text item types such as {@link PointText}, and * {@link AreaText} (coming soon). They each add a layer of functionality @@ -6601,200 +6572,195 @@ declare namespace paper { * functions that they inherit from TextItem. */ class TextItem extends Item { - /** + /** * The text contents of the text item. */ - content: string + content: string; - /** + /** * The font-family to be used in text content. */ - fontFamily: string + fontFamily: string; - /** + /** * The font-weight to be used in text content. */ - fontWeight: string | number + fontWeight: string | number; - /** + /** * The font size of text content, as a number in pixels, or as a string with * optional units `'px'`, `'pt'` and `'em'`. */ - fontSize: number | string + fontSize: number | string; - /** + /** * The text leading of text content. */ - leading: number | string + leading: number | string; - /** + /** * The justification of text paragraphs. */ - justification: string - - + justification: string; } - /** + /** * The Tool object refers to a script that the user can interact with by * using the mouse and keyboard and can be accessed through the global * `tool` variable. All its properties are also available in the paper * scope. - * + * * The global `tool` variable only exists in scripts that contain mouse handler * functions ({@link #onMouseMove}, {@link #onMouseDown}, {@link #onMouseDrag}, * {@link #onMouseUp}) or a keyboard handler function ({@link #onKeyDown}, * {@link #onKeyUp}). */ - class Tool { - /** + class Tool { + /** * The minimum distance the mouse has to drag before firing the onMouseDrag * event, since the last onMouseDrag event. */ - minDistance: number + minDistance: number; - /** + /** * The maximum distance the mouse has to drag before firing the onMouseDrag * event, since the last onMouseDrag event. */ - maxDistance: number + maxDistance: number; - - fixedDistance: number + fixedDistance: number; - /** + /** * The function to be called when the mouse button is pushed down. The * function receives a {@link ToolEvent} object which contains information * about the tool event. */ - onMouseDown: Function | null + onMouseDown: Function | null; - /** + /** * The function to be called when the mouse position changes while the mouse * is being dragged. The function receives a {@link ToolEvent} object which * contains information about the tool event. */ - onMouseDrag: Function | null + onMouseDrag: Function | null; - /** + /** * The function to be called the mouse moves within the project view. The * function receives a {@link ToolEvent} object which contains information * about the tool event. */ - onMouseMove: Function | null + onMouseMove: Function | null; - /** + /** * The function to be called when the mouse button is released. The function * receives a {@link ToolEvent} object which contains information about the * tool event. */ - onMouseUp: Function | null + onMouseUp: Function | null; - /** + /** * The function to be called when the user presses a key on the keyboard. * The function receives a {@link KeyEvent} object which contains * information about the keyboard event. - * + * * If the function returns `false`, the keyboard event will be prevented * from bubbling up. This can be used for example to stop the window from * scrolling, when you need the user to interact with arrow keys. */ - onKeyDown: Function | null + onKeyDown: Function | null; - /** + /** * The function to be called when the user releases a key on the keyboard. * The function receives a {@link KeyEvent} object which contains * information about the keyboard event. - * + * * If the function returns `false`, the keyboard event will be prevented * from bubbling up. This can be used for example to stop the window from * scrolling, when you need the user to interact with arrow keys. */ - onKeyUp: Function | null - + onKeyUp: Function | null; - /** + /** * Activates this tool, meaning {@link PaperScope#tool} will * point to it and it will be the one that receives tool events. */ - activate(): void + activate(): void; - /** + /** * Removes this tool from the {@link PaperScope#tools} list. */ - remove(): void + remove(): void; - /** + /** * Attach an event handler to the tool. - * + * * @param type - the event type: {@values 'mousedown', 'mouseup', * 'mousedrag', 'mousemove', 'keydown', 'keyup'} * @param function - the function to be called when the event * occurs, receiving a {@link ToolEvent} object as its sole argument - * + * * @return this tool itself, so calls can be chained */ - on(type: string, callback: Function): Tool + on(type: string, callback: Function): Tool; - /** + /** * Attach one or more event handlers to the tool. - * + * * @param param - an object literal containing one or more of the * following properties: {@values mousedown, mouseup, mousedrag, * mousemove, keydown, keyup} - * + * * @return this tool itself, so calls can be chained */ - on(param: object): Tool + on(param: object): Tool; - /** + /** * Detach an event handler from the tool. - * + * * @param type - the event type: {@values 'mousedown', 'mouseup', * 'mousedrag', 'mousemove', 'keydown', 'keyup'} * @param function - the function to be detached - * + * * @return this tool itself, so calls can be chained */ - off(type: string, callback: Function): Tool + off(type: string, callback: Function): Tool; - /** + /** * Detach one or more event handlers from the tool. - * + * * @param param - an object literal containing one or more of the * following properties: {@values mousedown, mouseup, mousedrag, * mousemove, keydown, keyup} - * + * * @return this tool itself, so calls can be chained */ - off(param: object): Tool + off(param: object): Tool; - /** + /** * Emit an event on the tool. - * + * * @param type - the event type: {@values 'mousedown', 'mouseup', * 'mousedrag', 'mousemove', 'keydown', 'keyup'} * @param event - an object literal containing properties describing * the event - * + * * @return true if the event had listeners */ - emit(type: string, event: object): boolean + emit(type: string, event: object): boolean; - /** + /** * Check if the tool has one or more event handlers of the specified type. - * + * * @param type - the event type: {@values 'mousedown', 'mouseup', * 'mousedrag', 'mousemove', 'keydown', 'keyup'} - * + * * @return true if the tool has one or more event handlers of * the specified type */ - responds(type: string): boolean - + responds(type: string): boolean; } - /** + /** * ToolEvent The ToolEvent object is received by the {@link Tool}'s mouse * event handlers {@link Tool#onMouseDown}, {@link Tool#onMouseDrag}, * {@link Tool#onMouseMove} and {@link Tool#onMouseUp}. The ToolEvent @@ -6802,90 +6768,87 @@ declare namespace paper { * information about the mouse event. */ class ToolEvent extends Event { - /** + /** * The type of tool event. */ - type: string + type: string; - /** + /** * The position of the mouse in project coordinates when the event was * fired. */ - point: Point + point: Point; - /** + /** * The position of the mouse in project coordinates when the previous * event was fired. */ - lastPoint: Point + lastPoint: Point; - /** + /** * The position of the mouse in project coordinates when the mouse button * was last clicked. */ - downPoint: Point + downPoint: Point; - /** + /** * The point in the middle between {@link #lastPoint} and * {@link #point}. This is a useful position to use when creating * artwork based on the moving direction of the mouse, as returned by * {@link #delta}. */ - middlePoint: Point + middlePoint: Point; - /** + /** * The difference between the current position and the last position of the * mouse when the event was fired. In case of the mouseup event, the * difference to the mousedown position is returned. */ - delta: Point + delta: Point; - /** + /** * The number of times the mouse event was fired. */ - count: number + count: number; - /** + /** * The item at the position of the mouse (if any). - * + * * If the item is contained within one or more {@link Group} or * {@link CompoundPath} items, the most top level group or compound path * that it is contained within is returned. */ - item: Item - + item: Item; - /** + /** * @return a string representation of the tool event */ - toString(): string - + toString(): string; } - /** + /** * Allows tweening `Object` properties between two states for a given * duration. To tween properties on Paper.js {@link Item} instances, * {@link Item#tween} can be used, which returns created * tween instance. - * + * * @see Item#tween(from, to, options) * @see Item#tween(to, options) * @see Item#tween(options) * @see Item#tweenTo(to, options) * @see Item#tweenFrom(from, options) */ - class Tween { - /** + class Tween { + /** * The function to be called when the tween is updated. It receives an * object as its sole argument, containing the current progress of the * tweening and the factor calculated by the easing function. */ - onUpdate: Function | null + onUpdate: Function | null; - - /** + /** * Creates a new tween. - * + * * @param object - the object to tween the properties on * @param from - the state at the start of the tweening * @param to - the state at the end of the tweening @@ -6894,120 +6857,126 @@ declare namespace paper { * function or the easing function * @param start - whether to start tweening automatically */ - constructor(object: object, from: object, to: object, duration: number, easing?: string | Function, start?: boolean) + constructor( + object: object, + from: object, + to: object, + duration: number, + easing?: string | Function, + start?: boolean + ); - /** + /** * Set a function that will be executed when the tween completes. - * + * * @param function - the function to execute when the tween * completes */ - then(callback: Function): Tween + then(callback: Function): Tween; - /** + /** * Start tweening. */ - start(): Tween + start(): Tween; - /** + /** * Stop tweening. */ - stop(): Tween - + stop(): Tween; } - /** + /** * The View object wraps an HTML element and handles drawing and user * interaction through mouse and keyboard for it. It offer means to scroll the * view, find the currently visible bounds in project coordinates, or the * center, both useful for constructing artwork that should appear centered on * screen. */ - class View { - /** + class View { + /** * Controls whether the view is automatically updated in the next animation * frame on changes, or whether you prefer to manually call * {@link #update} or {@link #requestUpdate} after changes. * Note that this is `true` by default, except for Node.js, where manual * updates make more sense. */ - autoUpdate: boolean + autoUpdate: boolean; - /** + /** * The underlying native element. */ - readonly element: HTMLCanvasElement + readonly element: HTMLCanvasElement; - /** + /** * The ratio between physical pixels and device-independent pixels (DIPs) * of the underlying canvas / device. * It is `1` for normal displays, and `2` or more for * high-resolution displays. */ - readonly pixelRatio: number + readonly pixelRatio: number; - /** + /** * The resoltuion of the underlying canvas / device in pixel per inch (DPI). * It is `72` for normal displays, and `144` for high-resolution * displays with a pixel-ratio of `2`. */ - readonly resolution: number + readonly resolution: number; - /** + /** * The size of the view. Changing the view's size will resize it's * underlying element. */ - viewSize: Size + viewSize: Size; - /** + /** * The bounds of the currently visible area in project coordinates. */ - readonly bounds: Rectangle + readonly bounds: Rectangle; - /** + /** * The size of the visible area in project coordinates. */ - readonly size: Size + readonly size: Size; - /** + /** * The center of the visible area in project coordinates. */ - center: Point + center: Point; - /** + /** * The view's zoom factor by which the project coordinates are magnified. - * + * * @see #scaling */ - zoom: number + zoom: number; - /** + /** * The current rotation angle of the view, as described by its * {@link #matrix}. */ - rotation: number + rotation: number; - /** + /** * The current scale factor of the view, as described by its * {@link #matrix}. - * + * * @see #zoom */ - scaling: Point + scaling: Point; - /** + /** * The view's transformation matrix, defining the view onto the project's * contents (position, zoom level, rotation, etc). */ - matrix: Matrix + matrix: Matrix; - /** + /** * Handler function to be called on each frame of an animation. * The function receives an event object which contains information about * the frame event: - * + * * @see Item#onFrame - * + * * @option event.count {Number} the number of times the frame event was * fired * @option event.time {Number} the total amount of time passed since the @@ -7015,83 +6984,83 @@ declare namespace paper { * @option event.delta {Number} the time passed in seconds since the last * frame event */ - onFrame: Function | null + onFrame: Function | null; - /** + /** * Handler function that is called whenever a view is resized. */ - onResize: Function | null + onResize: Function | null; - /** + /** * The function to be called when the mouse button is pushed down on the * view. The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see Item#onMouseDown */ - onMouseDown: Function | null + onMouseDown: Function | null; - /** + /** * The function to be called when the mouse position changes while the mouse * is being dragged over the view. The function receives a {@link * MouseEvent} object which contains information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see Item#onMouseDrag */ - onMouseDrag: Function | null + onMouseDrag: Function | null; - /** + /** * The function to be called when the mouse button is released over the item. * The function receives a {@link MouseEvent} object which contains * information about the mouse event. - * + * * @see Item#onMouseUp */ - onMouseUp: Function | null + onMouseUp: Function | null; - /** + /** * The function to be called when the mouse clicks on the view. The function * receives a {@link MouseEvent} object which contains information about the * mouse event. * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see Item#onClick */ - onClick: Function | null + onClick: Function | null; - /** + /** * The function to be called when the mouse double clicks on the view. The * function receives a {@link MouseEvent} object which contains information * about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see Item#onDoubleClick */ - onDoubleClick: Function | null + onDoubleClick: Function | null; - /** + /** * The function to be called repeatedly while the mouse moves over the * view. The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see Item#onMouseMove */ - onMouseMove: Function | null + onMouseMove: Function | null; - /** + /** * The function to be called when the mouse moves over the view. This * function will only be called again, once the mouse moved outside of the * view first. The function receives a {@link MouseEvent} object which @@ -7099,271 +7068,269 @@ declare namespace paper { * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see Item#onMouseEnter */ - onMouseEnter: Function | null + onMouseEnter: Function | null; - /** + /** * The function to be called when the mouse moves out of the view. * The function receives a {@link MouseEvent} object which contains * information about the mouse event. * Note that such mouse events bubble up the scene graph hierarchy, reaching * the view at the end, unless they are stopped before with {@link * Event#stopPropagation()} or by returning `false` from a handler. - * + * * @see View#onMouseLeave */ - onMouseLeave: Function | null - + onMouseLeave: Function | null; - /** + /** * Removes this view from the project and frees the associated element. */ - remove(): void + remove(): void; - /** + /** * Updates the view if there are changes. Note that when using built-in * event hanlders for interaction, animation and load events, this method is * invoked for you automatically at the end. - * + * * @return true if the view was updated */ - update(): boolean + update(): boolean; - /** + /** * Requests an update of the view if there are changes through the browser's * requestAnimationFrame() mechanism for smooth animation. Note that when * using built-in event handlers for interaction, animation and load events, * updates are automatically invoked for you automatically at the end. */ - requestUpdate(): void + requestUpdate(): void; - /** + /** * Makes all animation play by adding the view to the request animation * loop. */ - play(): void + play(): void; - /** + /** * Makes all animation pause by removing the view from the request animation * loop. */ - pause(): void + pause(): void; - /** + /** * Checks whether the view is currently visible within the current browser * viewport. - * + * * @return true if the view is visible */ - isVisible(): boolean + isVisible(): boolean; - /** + /** * Checks whether the view is inserted into the browser DOM. - * + * * @return true if the view is inserted */ - isInserted(): boolean + isInserted(): boolean; - /** + /** * Translates (scrolls) the view by the given offset vector. - * + * * @param delta - the offset to translate the view by */ - translate(delta: Point): void + translate(delta: Point): void; - /** + /** * Rotates the view by a given angle around the given center point. - * + * * Angles are oriented clockwise and measured in degrees. - * + * * @see Matrix#rotate(angle[, center]) - * + * * @param angle - the rotation angle */ - rotate(angle: number, center?: Point): void + rotate(angle: number, center?: Point): void; - /** + /** * Scales the view by the given value from its center point, or optionally * from a supplied point. - * + * * @param scale - the scale factor */ - scale(scale: number, center?: Point): void + scale(scale: number, center?: Point): void; - /** + /** * Scales the view by the given values from its center point, or optionally * from a supplied point. - * + * * @param hor - the horizontal scale factor * @param ver - the vertical scale factor */ - scale(hor: number, ver: number, center?: Point): void + scale(hor: number, ver: number, center?: Point): void; - /** + /** * Shears the view by the given value from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(shear[, center]) - * + * * @param shear - the horizontal and vertical shear factors as a point */ - shear(shear: Point, center?: Point): void + shear(shear: Point, center?: Point): void; - /** + /** * Shears the view by the given values from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(hor, ver[, center]) - * + * * @param hor - the horizontal shear factor * @param ver - the vertical shear factor */ - shear(hor: number, ver: number, center?: Point): void + shear(hor: number, ver: number, center?: Point): void; - /** + /** * Skews the view by the given angles from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(skew[, center]) - * + * * @param skew - the horizontal and vertical skew angles in degrees */ - skew(skew: Point, center?: Point): void + skew(skew: Point, center?: Point): void; - /** + /** * Skews the view by the given angles from its center point, or optionally * by a supplied point. - * + * * @see Matrix#shear(hor, ver[, center]) - * + * * @param hor - the horizontal skew angle in degrees * @param ver - the vertical sskew angle in degrees */ - skew(hor: number, ver: number, center?: Point): void + skew(hor: number, ver: number, center?: Point): void; - /** + /** * Transform the view. - * + * * @param matrix - the matrix by which the view shall be transformed */ - transform(matrix: Matrix): void + transform(matrix: Matrix): void; - /** + /** * Converts the passed point from project coordinate space to view * coordinate space, which is measured in browser pixels in relation to the * position of the view element. - * + * * @param point - the point in project coordinates to be converted - * + * * @return the point converted into view coordinates */ - projectToView(point: Point): Point + projectToView(point: Point): Point; - /** + /** * Converts the passed point from view coordinate space to project * coordinate space. - * + * * @param point - the point in view coordinates to be converted - * + * * @return the point converted into project coordinates */ - viewToProject(point: Point): Point + viewToProject(point: Point): Point; - /** + /** * Determines and returns the event location in project coordinate space. - * + * * @param event - the native event object for which to determine the * location. - * + * * @return the event point in project coordinates. */ - getEventPoint(event: Event): Point + getEventPoint(event: Event): Point; - /** + /** * Attach an event handler to the view. - * + * * @param type - the type of event: {@values 'frame', 'resize', * 'mousedown', 'mouseup', 'mousedrag', 'click', 'doubleclick', * 'mousemove', 'mouseenter', 'mouseleave'} * @param function - the function to be called when the event * occurs, receiving a {@link MouseEvent} or {@link Event} object as its * sole argument - * + * * @return this view itself, so calls can be chained */ - on(type: string, callback: Function): View + on(type: string, callback: Function): View; - /** + /** * Attach one or more event handlers to the view. - * + * * @param param - an object literal containing one or more of the * following properties: {@values frame, resize} - * + * * @return this view itself, so calls can be chained */ - on(param: object): View + on(param: object): View; - /** + /** * Detach an event handler from the view. - * + * * @param type - the event type: {@values 'frame', 'resize', * 'mousedown', 'mouseup', 'mousedrag', 'click', 'doubleclick', * 'mousemove', 'mouseenter', 'mouseleave'} * @param function - the function to be detached - * + * * @return this view itself, so calls can be chained */ - off(type: string, callback: Function): View + off(type: string, callback: Function): View; - /** + /** * Detach one or more event handlers from the view. - * + * * @param param - an object literal containing one or more of the * following properties: {@values frame, resize} - * + * * @return this view itself, so calls can be chained */ - off(param: object): View + off(param: object): View; - /** + /** * Emit an event on the view. - * + * * @param type - the event type: {@values 'frame', 'resize', * 'mousedown', 'mouseup', 'mousedrag', 'click', 'doubleclick', * 'mousemove', 'mouseenter', 'mouseleave'} * @param event - an object literal containing properties describing * the event - * + * * @return true if the event had listeners */ - emit(type: string, event: object): boolean + emit(type: string, event: object): boolean; - /** + /** * Check if the view has one or more event handlers of the specified type. - * + * * @param type - the event type: {@values 'frame', 'resize', * 'mousedown', 'mouseup', 'mousedrag', 'click', 'doubleclick', * 'mousemove', 'mouseenter', 'mouseleave'} - * + * * @return true if the view has one or more event handlers of * the specified type */ - responds(type: string): boolean - + responds(type: string): boolean; } } - -declare module 'paper/dist/paper-core' -{ - const paperCore: Pick>; - export = paperCore +declare module "paper/dist/paper-core" { + const paperCore: Pick< + paper.PaperScope, + Exclude + >; + export = paperCore; } -declare module 'paper' -{ +declare module "paper" { const paperFull: paper.PaperScope; - export = paperFull + export = paperFull; } diff --git a/src/core/PaperScope.js b/src/core/PaperScope.js index 02f092002c..c92776180c 100644 --- a/src/core/PaperScope.js +++ b/src/core/PaperScope.js @@ -32,293 +32,299 @@ * The global {@link paper} object is simply a reference to the currently active * `PaperScope`. */ -var PaperScope = Base.extend(/** @lends PaperScope# */{ - _class: 'PaperScope', +var PaperScope = Base.extend( + /** @lends PaperScope# */ { + _class: "PaperScope", - /** - * Creates a PaperScope object. - * - * @name PaperScope#initialize - * @function - */ - // DOCS: initialize() parameters - initialize: function PaperScope() { - // element is only used internally when creating scopes for PaperScript. - // Whenever a PaperScope is created, it automatically becomes the active - // one. - paper = this; - // Default configurable settings. - this.settings = new Base({ - applyMatrix: true, - insertItems: true, - handleSize: 4, - hitTolerance: 0 - }); - this.project = null; - this.projects = []; - this.tools = []; - // Assign a unique id to each scope . - this._id = PaperScope._id++; - PaperScope._scopes[this._id] = this; - var proto = PaperScope.prototype; - if (!this.support) { - // Set up paper.support, as an object containing properties that - // describe the support of various features. - var ctx = CanvasProvider.getContext(1, 1) || {}; - proto.support = { - nativeDash: 'setLineDash' in ctx || 'mozDash' in ctx, - nativeBlendModes: BlendMode.nativeModes - }; - CanvasProvider.release(ctx); - } - if (!this.agent) { - // Use self.instead of window, to cover handle web-workers too. - var user = self.navigator.userAgent.toLowerCase(), - // Detect basic platforms, only mac internally required for now. - os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0], - platform = os === 'darwin' ? 'mac' : os, - agent = proto.agent = proto.browser = { platform: platform }; - if (platform) - agent[platform] = true; - // Use replace() to get all matches, and deal with Chrome/Webkit - // overlap: - // TODO: Do we need Mozilla next to Firefox? Other than the - // different treatment of the Chrome/Webkit overlap - // here: { chrome: true, webkit: false }, Mozilla missing is the - // only difference to jQuery.browser - user.replace( - /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g, - function(match, n, v1, v2, rv) { - // Do not set additional browsers once chrome is detected. - if (!agent.chrome) { - var v = n === 'opera' ? v2 : - /^(node|trident)$/.test(n) ? rv : v1; - agent.version = v; - agent.versionNumber = parseFloat(v); - n = { trident: 'msie', jsdom: 'node' }[n] || n; - agent.name = n; - agent[n] = true; + /** + * Creates a PaperScope object. + * + * @name PaperScope#initialize + * @function + */ + // DOCS: initialize() parameters + initialize: function PaperScope() { + // element is only used internally when creating scopes for PaperScript. + // Whenever a PaperScope is created, it automatically becomes the active + // one. + paper = this; + // Default configurable settings. + this.settings = new Base({ + applyMatrix: true, + insertItems: true, + handleSize: 4, + drawSelected: true, + hitTolerance: 0, + }); + this.project = null; + this.projects = []; + this.tools = []; + // Assign a unique id to each scope . + this._id = PaperScope._id++; + PaperScope._scopes[this._id] = this; + var proto = PaperScope.prototype; + if (!this.support) { + // Set up paper.support, as an object containing properties that + // describe the support of various features. + var ctx = CanvasProvider.getContext(1, 1) || {}; + proto.support = { + nativeDash: "setLineDash" in ctx || "mozDash" in ctx, + nativeBlendModes: BlendMode.nativeModes, + }; + CanvasProvider.release(ctx); + } + if (!this.agent) { + // Use self.instead of window, to cover handle web-workers too. + var user = self.navigator.userAgent.toLowerCase(), + // Detect basic platforms, only mac internally required for now. + os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user) || + [])[0], + platform = os === "darwin" ? "mac" : os, + agent = + (proto.agent = + proto.browser = + { platform: platform }); + if (platform) agent[platform] = true; + // Use replace() to get all matches, and deal with Chrome/Webkit + // overlap: + // TODO: Do we need Mozilla next to Firefox? Other than the + // different treatment of the Chrome/Webkit overlap + // here: { chrome: true, webkit: false }, Mozilla missing is the + // only difference to jQuery.browser + user.replace( + /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g, + function (match, n, v1, v2, rv) { + // Do not set additional browsers once chrome is detected. + if (!agent.chrome) { + var v = + n === "opera" + ? v2 + : /^(node|trident)$/.test(n) + ? rv + : v1; + agent.version = v; + agent.versionNumber = parseFloat(v); + n = { trident: "msie", jsdom: "node" }[n] || n; + agent.name = n; + agent[n] = true; + } } - } - ); - if (agent.chrome) - delete agent.webkit; - if (agent.atom) - delete agent.chrome; - } - }, + ); + if (agent.chrome) delete agent.webkit; + if (agent.atom) delete agent.chrome; + } + }, - /** - * The version of Paper.js, as a string. - * - * @type String - * @readonly - */ - version: /*#=*/__options.version, + /** + * The version of Paper.js, as a string. + * + * @type String + * @readonly + */ + version: /*#=*/ __options.version, - /** - * Gives access to paper's configurable settings. - * - * @name PaperScope#settings - * @type Object - * - * @option [settings.insertItems=true] {Boolean} controls whether newly - * created items are automatically inserted into the scene graph, by - * adding them to {@link Project#activeLayer} - * @option [settings.applyMatrix=true] {Boolean} controls what value newly - * created items have their {@link Item#applyMatrix} property set to - * (Note that not all items can set this to `false`) - * @option [settings.handleSize=4] {Number} the size of the curve handles - * when drawing selections - * @option [settings.hitTolerance=0] {Number} the default tolerance for hit- - * tests, when no value is specified - */ + /** + * Gives access to paper's configurable settings. + * + * @name PaperScope#settings + * @type Object + * + * @option [settings.insertItems=true] {Boolean} controls whether newly + * created items are automatically inserted into the scene graph, by + * adding them to {@link Project#activeLayer} + * @option [settings.applyMatrix=true] {Boolean} controls what value newly + * created items have their {@link Item#applyMatrix} property set to + * (Note that not all items can set this to `false`) + * @option [settings.handleSize=4] {Number} the size of the curve handles + * when drawing selections + * @option [settings.hitTolerance=0] {Number} the default tolerance for hit- + * tests, when no value is specified + */ - /** - * The currently active project. - * - * @name PaperScope#project - * @type Project - */ + /** + * The currently active project. + * + * @name PaperScope#project + * @type Project + */ - /** - * The list of all open projects within the current Paper.js context. - * - * @name PaperScope#projects - * @type Project[] - */ + /** + * The list of all open projects within the current Paper.js context. + * + * @name PaperScope#projects + * @type Project[] + */ - /** - * The reference to the active project's view. - * - * @bean - * @type View - */ - getView: function() { - var project = this.project; - return project && project._view; - }, + /** + * The reference to the active project's view. + * + * @bean + * @type View + */ + getView: function () { + var project = this.project; + return project && project._view; + }, - /** - * The reference to the active tool. - * - * @name PaperScope#tool - * @property - * @type Tool - */ + /** + * The reference to the active tool. + * + * @name PaperScope#tool + * @property + * @type Tool + */ - /** - * The list of available tools. - * - * @name PaperScope#tools - * @property - * @type Tool[] - */ + /** + * The list of available tools. + * + * @name PaperScope#tools + * @property + * @type Tool[] + */ - /** - * A reference to the local scope. This is required, so `paper` will always - * refer to the local scope, even when calling into it from another scope. - * `paper.activate();` will have to be called in such a situation. - * - * @bean - * @type PaperScript - * @private - */ - getPaper: function() { - return this; - }, + /** + * A reference to the local scope. This is required, so `paper` will always + * refer to the local scope, even when calling into it from another scope. + * `paper.activate();` will have to be called in such a situation. + * + * @bean + * @type PaperScript + * @private + */ + getPaper: function () { + return this; + }, - /** - * Compiles the PaperScript code into a compiled function and executes it. - * The compiled function receives all properties of this {@link PaperScope} - * as arguments, to emulate a global scope with unaffected performance. It - * also installs global view and tool handlers automatically on the - * respective objects. - * - * @option options.url {String} the url of the source, for source-map - * debugging - * @option options.source {String} the source to be used for the source- - * mapping, in case the code that's passed in has already been mingled. - * - * @param {String} code the PaperScript code - * @param {Object} [options] the compilation options - */ - execute: function(code, options) { -/*#*/ if (__options.paperScript) { - var exports = paper.PaperScript.execute(code, this, options); - View.updateFocus(); - return exports; -/*#*/ } - }, + /** + * Compiles the PaperScript code into a compiled function and executes it. + * The compiled function receives all properties of this {@link PaperScope} + * as arguments, to emulate a global scope with unaffected performance. It + * also installs global view and tool handlers automatically on the + * respective objects. + * + * @option options.url {String} the url of the source, for source-map + * debugging + * @option options.source {String} the source to be used for the source- + * mapping, in case the code that's passed in has already been mingled. + * + * @param {String} code the PaperScript code + * @param {Object} [options] the compilation options + */ + execute: function (code, options) { + /*#*/ if (__options.paperScript) { + var exports = paper.PaperScript.execute(code, this, options); + View.updateFocus(); + return exports; + /*#*/ + } + }, - /** - * Injects the paper scope into any other given scope. Can be used for - * example to inject the currently active PaperScope into the window's - * global scope, to emulate PaperScript-style globally accessible Paper - * classes and objects. - * - * Please note: Using this method may override native constructors - * (e.g. Path). This may cause problems when using Paper.js in conjunction - * with other libraries that rely on these constructors. Keep the library - * scoped if you encounter issues caused by this. - * - * @example - * paper.install(window); - */ - install: function(scope) { - // Define project, view and tool as getters that redirect to these - // values on the PaperScope, so they are kept up to date - var that = this; - Base.each(['project', 'view', 'tool'], function(key) { - Base.define(scope, key, { - configurable: true, - get: function() { - return that[key]; - } + /** + * Injects the paper scope into any other given scope. Can be used for + * example to inject the currently active PaperScope into the window's + * global scope, to emulate PaperScript-style globally accessible Paper + * classes and objects. + * + * Please note: Using this method may override native constructors + * (e.g. Path). This may cause problems when using Paper.js in conjunction + * with other libraries that rely on these constructors. Keep the library + * scoped if you encounter issues caused by this. + * + * @example + * paper.install(window); + */ + install: function (scope) { + // Define project, view and tool as getters that redirect to these + // values on the PaperScope, so they are kept up to date + var that = this; + Base.each(["project", "view", "tool"], function (key) { + Base.define(scope, key, { + configurable: true, + get: function () { + return that[key]; + }, + }); }); - }); - // Copy over all fields from this scope to the destination. - // Do not use Base.each, since we also want to enumerate over - // fields on PaperScope.prototype, e.g. all classes - for (var key in this) + // Copy over all fields from this scope to the destination. + // Do not use Base.each, since we also want to enumerate over + // fields on PaperScope.prototype, e.g. all classes // Exclude all 'hidden' fields - if (!/^_/.test(key) && this[key]) - scope[key] = this[key]; - }, + for (var key in this) + if (!/^_/.test(key) && this[key]) scope[key] = this[key]; + }, - /** - * Sets up an empty project for us. If a canvas is provided, it also creates - * a {@link View} for it, both linked to this scope. - * - * @param {HTMLCanvasElement|String|Size} element the HTML canvas element - * this scope should be associated with, or an ID string by which to find - * the element, or the size of the canvas to be created for usage in a web - * worker. - */ - setup: function(element) { - // Make sure this is the active scope, so the created project and view - // are automatically associated with it. - paper = this; - // Create an empty project for the scope. - this.project = new Project(element); - // This is needed in PaperScript.load(). - return this; - }, + /** + * Sets up an empty project for us. If a canvas is provided, it also creates + * a {@link View} for it, both linked to this scope. + * + * @param {HTMLCanvasElement|String|Size} element the HTML canvas element + * this scope should be associated with, or an ID string by which to find + * the element, or the size of the canvas to be created for usage in a web + * worker. + */ + setup: function (element) { + // Make sure this is the active scope, so the created project and view + // are automatically associated with it. + paper = this; + // Create an empty project for the scope. + this.project = new Project(element); + // This is needed in PaperScript.load(). + return this; + }, - createCanvas: function(width, height) { - return CanvasProvider.getCanvas(width, height); - }, + createCanvas: function (width, height) { + return CanvasProvider.getCanvas(width, height); + }, - /** - * Activates this PaperScope, so all newly created items will be placed - * in its active project. - */ - activate: function() { - paper = this; - }, + /** + * Activates this PaperScope, so all newly created items will be placed + * in its active project. + */ + activate: function () { + paper = this; + }, - clear: function() { - // Remove all projects, views and tools. - // This also removes the installed event handlers. - var projects = this.projects, - tools = this.tools; - for (var i = projects.length - 1; i >= 0; i--) - projects[i].remove(); - for (var i = tools.length - 1; i >= 0; i--) - tools[i].remove(); - }, + clear: function () { + // Remove all projects, views and tools. + // This also removes the installed event handlers. + var projects = this.projects, + tools = this.tools; + for (var i = projects.length - 1; i >= 0; i--) projects[i].remove(); + for (var i = tools.length - 1; i >= 0; i--) tools[i].remove(); + }, - remove: function() { - this.clear(); - delete PaperScope._scopes[this._id]; - }, + remove: function () { + this.clear(); + delete PaperScope._scopes[this._id]; + }, - statics: new function() { - // Produces helpers to e.g. check for both 'canvas' and - // 'data-paper-canvas' attributes: - function handleAttribute(name) { - name += 'Attribute'; - return function(el, attr) { - return el[name](attr) || el[name]('data-paper-' + attr); - }; - } + statics: new (function () { + // Produces helpers to e.g. check for both 'canvas' and + // 'data-paper-canvas' attributes: + function handleAttribute(name) { + name += "Attribute"; + return function (el, attr) { + return el[name](attr) || el[name]("data-paper-" + attr); + }; + } - return /** @lends PaperScope */{ - _scopes: {}, - _id: 0, + return /** @lends PaperScope */ { + _scopes: {}, + _id: 0, - /** - * Retrieves a PaperScope object with the given scope id. - * - * @param id - * @return {PaperScope} - */ - get: function(id) { - return this._scopes[id] || null; - }, + /** + * Retrieves a PaperScope object with the given scope id. + * + * @param id + * @return {PaperScope} + */ + get: function (id) { + return this._scopes[id] || null; + }, - getAttribute: handleAttribute('get'), - hasAttribute: handleAttribute('has') - }; + getAttribute: handleAttribute("get"), + hasAttribute: handleAttribute("has"), + }; + })(), } -}); +); diff --git a/src/item/Item.js b/src/item/Item.js index 83b34ed3f4..c598f7a959 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -20,273 +20,307 @@ * is unique to their type, but share the underlying properties and functions * that they inherit from Item. */ -var Item = Base.extend(Emitter, /** @lends Item# */{ - statics: /** @lends Item */{ - /** - * Override Item.extend() to merge the subclass' _serializeFields with - * the parent class' _serializeFields. - * - * @private - */ - extend: function extend(src) { - if (src._serializeFields) - src._serializeFields = Base.set({}, - this.prototype._serializeFields, src._serializeFields); - return extend.base.apply(this, arguments); - }, +var Item = Base.extend( + Emitter, + /** @lends Item# */ { + statics: /** @lends Item */ { + /** + * Override Item.extend() to merge the subclass' _serializeFields with + * the parent class' _serializeFields. + * + * @private + */ + extend: function extend(src) { + if (src._serializeFields) + src._serializeFields = Base.set( + {}, + this.prototype._serializeFields, + src._serializeFields + ); + return extend.base.apply(this, arguments); + }, - /** - * An object constant that can be passed to Item#initialize() to avoid - * insertion into the scene graph. - * - * @private - */ - NO_INSERT: { insert: false } - }, + /** + * An object constant that can be passed to Item#initialize() to avoid + * insertion into the scene graph. + * + * @private + */ + NO_INSERT: { insert: false }, + }, - _class: 'Item', - _name: null, - // All items apply their matrix by default. - // Exceptions are Raster, SymbolItem, Clip and Shape. - _applyMatrix: true, - _canApplyMatrix: true, - _canScaleStroke: false, - _pivot: null, - _visible: true, - _blendMode: 'normal', - _opacity: 1, - _locked: false, - _guide: false, - _clipMask: false, - _selection: 0, - // Controls whether bounds should appear selected when the item is selected. - // This is only turned off for Group, Layer and PathItem, where it can be - // selected separately by setting item.bounds.selected = true; - _selectBounds: true, - _selectChildren: false, - // Provide information about fields to be serialized, with their defaults - // that can be omitted. - _serializeFields: { - name: null, - applyMatrix: null, - matrix: new Matrix(), - pivot: null, - visible: true, - blendMode: 'normal', - opacity: 1, - locked: false, - guide: false, - clipMask: false, - selected: false, - data: {} + _class: "Item", + _name: null, + // All items apply their matrix by default. + // Exceptions are Raster, SymbolItem, Clip and Shape. + _applyMatrix: true, + _canApplyMatrix: true, + _canScaleStroke: false, + _pivot: null, + _visible: true, + _blendMode: "normal", + _opacity: 1, + _locked: false, + _guide: false, + _clipMask: false, + _selection: 0, + // Controls whether bounds should appear selected when the item is selected. + // This is only turned off for Group, Layer and PathItem, where it can be + // selected separately by setting item.bounds.selected = true; + _selectBounds: true, + _selectChildren: false, + // Provide information about fields to be serialized, with their defaults + // that can be omitted. + _serializeFields: { + name: null, + applyMatrix: null, + matrix: new Matrix(), + pivot: null, + visible: true, + blendMode: "normal", + opacity: 1, + locked: false, + guide: false, + clipMask: false, + selected: false, + data: {}, + }, + // Prioritize `applyMatrix` over `matrix`: + _prioritize: ["applyMatrix"], }, - // Prioritize `applyMatrix` over `matrix`: - _prioritize: ['applyMatrix'] -}, -new function() { // Injection scope for various item event handlers - var handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick', - 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave']; - return Base.each(handlers, - function(name) { - this._events[name] = { - install: function(type) { - this.getView()._countItemEvent(type, 1); - }, + new (function () { + // Injection scope for various item event handlers + var handlers = [ + "onMouseDown", + "onMouseUp", + "onMouseDrag", + "onClick", + "onDoubleClick", + "onMouseMove", + "onMouseEnter", + "onMouseLeave", + ]; + return Base.each( + handlers, + function (name) { + this._events[name] = { + install: function (type) { + this.getView()._countItemEvent(type, 1); + }, - uninstall: function(type) { - this.getView()._countItemEvent(type, -1); - } - }; - }, { - _events: { - onFrame: { - install: function() { - this.getView()._animateItem(this, true); + uninstall: function (type) { + this.getView()._countItemEvent(type, -1); + }, + }; + }, + { + _events: { + onFrame: { + install: function () { + this.getView()._animateItem(this, true); + }, + + uninstall: function () { + this.getView()._animateItem(this, false); + }, }, - uninstall: function() { - this.getView()._animateItem(this, false); - } + // Only for external sources, e.g. Raster + onLoad: {}, + onError: {}, + }, + statics: { + _itemHandlers: handlers, }, - - // Only for external sources, e.g. Raster - onLoad: {}, - onError: {} - }, - statics: { - _itemHandlers: handlers } - } - ); -}, /** @lends Item# */{ - initialize: function Item() { - // Do nothing, but declare it for named constructors. - }, + ); + })(), + /** @lends Item# */ { + initialize: function Item() { + // Do nothing, but declare it for named constructors. + }, - /** - * Private helper for #initialize() that tries setting properties from the - * passed props object, and apply the point translation to the internal - * matrix. - * - * @param {Object} props the properties to be applied to the item - * @param {Point} point the point by which to transform the internal matrix - * @return {Boolean} {@true if the properties were successfully be applied, - * or if none were provided} - */ - _initialize: function(props, point) { - // Define this Item's unique id. But allow the creation of internally - // used paths with no ids. - var hasProps = props && Base.isPlainObject(props), - internal = hasProps && props.internal === true, - matrix = this._matrix = new Matrix(), - // Allow setting another project than the currently active one. - project = hasProps && props.project || paper.project, - settings = paper.settings; - this._id = internal ? null : UID.get(); - this._parent = this._index = null; - // Inherit the applyMatrix setting from settings.applyMatrix - this._applyMatrix = this._canApplyMatrix && settings.applyMatrix; - // Handle matrix before everything else, to avoid issues with - // #addChild() calling _changed() and accessing _matrix already. - if (point) - matrix.translate(point); - matrix._owner = this; - this._style = new Style(project._currentStyle, this, project); - // Do not add to the project if it's an internal path, or if - // props.insert or settings.isnertItems is false. - if (internal || hasProps && props.insert == false - || !settings.insertItems && !(hasProps && props.insert === true)) { - this._setProject(project); - } else { - (hasProps && props.parent || project) - ._insertItem(undefined, this, true); // _created = true - } - // Filter out Item.NO_INSERT before _set(), for performance reasons. - if (hasProps && props !== Item.NO_INSERT) { - this.set(props, { - // Filter out these properties as they were handled above: - internal: true, insert: true, project: true, parent: true - }); - } - return hasProps; - }, + /** + * Private helper for #initialize() that tries setting properties from the + * passed props object, and apply the point translation to the internal + * matrix. + * + * @param {Object} props the properties to be applied to the item + * @param {Point} point the point by which to transform the internal matrix + * @return {Boolean} {@true if the properties were successfully be applied, + * or if none were provided} + */ + _initialize: function (props, point) { + // Define this Item's unique id. But allow the creation of internally + // used paths with no ids. + var hasProps = props && Base.isPlainObject(props), + internal = hasProps && props.internal === true, + matrix = (this._matrix = new Matrix()), + // Allow setting another project than the currently active one. + project = (hasProps && props.project) || paper.project, + settings = paper.settings; + this._id = internal ? null : UID.get(); + this._parent = this._index = null; + // Inherit the applyMatrix setting from settings.applyMatrix + this._applyMatrix = this._canApplyMatrix && settings.applyMatrix; + // Handle matrix before everything else, to avoid issues with + // #addChild() calling _changed() and accessing _matrix already. + if (point) matrix.translate(point); + matrix._owner = this; + this._style = new Style(project._currentStyle, this, project); + // Do not add to the project if it's an internal path, or if + // props.insert or settings.isnertItems is false. + if ( + internal || + (hasProps && props.insert == false) || + (!settings.insertItems && !(hasProps && props.insert === true)) + ) { + this._setProject(project); + } else { + ((hasProps && props.parent) || project)._insertItem( + undefined, + this, + true + ); // _created = true + } + // Filter out Item.NO_INSERT before _set(), for performance reasons. + if (hasProps && props !== Item.NO_INSERT) { + this.set(props, { + // Filter out these properties as they were handled above: + internal: true, + insert: true, + project: true, + parent: true, + }); + } + return hasProps; + }, - _serialize: function(options, dictionary) { - var props = {}, - that = this; - - function serialize(fields) { - for (var key in fields) { - // value is the default value, only serialize if the current - // value is different from it. - var value = that[key]; - // Style#leading is a special case, as its default value is - // dependent on the fontSize. Handle this here separately. - if (!Base.equals(value, key === 'leading' - ? fields.fontSize * 1.2 : fields[key])) { - props[key] = Base.serialize(value, options, + _serialize: function (options, dictionary) { + var props = {}, + that = this; + + function serialize(fields) { + for (var key in fields) { + // value is the default value, only serialize if the current + // value is different from it. + var value = that[key]; + // Style#leading is a special case, as its default value is + // dependent on the fontSize. Handle this here separately. + if ( + !Base.equals( + value, + key === "leading" + ? fields.fontSize * 1.2 + : fields[key] + ) + ) { + props[key] = Base.serialize( + value, + options, // Do not use compact mode for data - key !== 'data', dictionary); + key !== "data", + dictionary + ); + } } } - } - // Serialize fields that this Item subclass defines first - serialize(this._serializeFields); - // Serialize style fields, but only if they differ from defaults. - // Do not serialize styles on Groups and Layers, since they just unify - // their children's own styles. - if (!(this instanceof Group)) - serialize(this._style._defaults); - // There is no compact form for Item serialization, we always keep the - // class. - return [ this._class, props ]; - }, + // Serialize fields that this Item subclass defines first + serialize(this._serializeFields); + // Serialize style fields, but only if they differ from defaults. + // Do not serialize styles on Groups and Layers, since they just unify + // their children's own styles. + if (!(this instanceof Group)) serialize(this._style._defaults); + // There is no compact form for Item serialization, we always keep the + // class. + return [this._class, props]; + }, - /** - * Private notifier that is called whenever a change occurs in this item or - * its sub-elements, such as Segments, Curves, Styles, etc. - * - * @param {ChangeFlag} flags describes what exactly has changed - */ - _changed: function(flags) { - var symbol = this._symbol, - cacheParent = this._parent || symbol, - project = this._project; - if (flags & /*#=*/ChangeFlag.GEOMETRY) { - // Clear cached bounds, position and decomposed matrix whenever - // geometry changes. - this._bounds = this._position = this._decomposed = undefined; - } - if (flags & /*#=*/ChangeFlag.MATRIX) { - this._globalMatrix = undefined; - } - if (cacheParent - && (flags & /*#=*/(ChangeFlag.GEOMETRY | ChangeFlag.STROKE))) { - // Clear cached bounds of all items that this item contributes to. - // We call this on the parent, since the information is cached on - // the parent, see getBounds(). - Item._clearBoundsCache(cacheParent); - } - if (flags & /*#=*/ChangeFlag.CHILDREN) { - // Clear cached bounds of all items that this item contributes to. - // Here we don't call this on the parent, since adding / removing a - // child triggers this notification on the parent. - Item._clearBoundsCache(this); - } - if (project) - project._changed(flags, this); - // If this item is a symbol's definition, notify it of the change too - if (symbol) - symbol._changed(flags); - }, + /** + * Private notifier that is called whenever a change occurs in this item or + * its sub-elements, such as Segments, Curves, Styles, etc. + * + * @param {ChangeFlag} flags describes what exactly has changed + */ + _changed: function (flags) { + var symbol = this._symbol, + cacheParent = this._parent || symbol, + project = this._project; + if (flags & /*#=*/ ChangeFlag.GEOMETRY) { + // Clear cached bounds, position and decomposed matrix whenever + // geometry changes. + this._bounds = this._position = this._decomposed = undefined; + } + if (flags & /*#=*/ ChangeFlag.MATRIX) { + this._globalMatrix = undefined; + } + if ( + cacheParent && + flags & /*#=*/ (ChangeFlag.GEOMETRY | ChangeFlag.STROKE) + ) { + // Clear cached bounds of all items that this item contributes to. + // We call this on the parent, since the information is cached on + // the parent, see getBounds(). + Item._clearBoundsCache(cacheParent); + } + if (flags & /*#=*/ ChangeFlag.CHILDREN) { + // Clear cached bounds of all items that this item contributes to. + // Here we don't call this on the parent, since adding / removing a + // child triggers this notification on the parent. + Item._clearBoundsCache(this); + } + if (project) project._changed(flags, this); + // If this item is a symbol's definition, notify it of the change too + if (symbol) symbol._changed(flags); + }, - /** - * Sets the properties of the passed object literal on this item to the - * values defined in the object literal, if the item has property of the - * given name (or a setter defined for it). - * - * @name Item#set - * @function - * @param {Object} props - * @return {Item} the item itself - * @chainable - * - * @example {@paperscript} - * // Setting properties through an object literal - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * - * circle.set({ - * strokeColor: 'red', - * strokeWidth: 10, - * fillColor: 'black', - * selected: true - * }); - */ + /** + * Sets the properties of the passed object literal on this item to the + * values defined in the object literal, if the item has property of the + * given name (or a setter defined for it). + * + * @name Item#set + * @function + * @param {Object} props + * @return {Item} the item itself + * @chainable + * + * @example {@paperscript} + * // Setting properties through an object literal + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * + * circle.set({ + * strokeColor: 'red', + * strokeWidth: 10, + * fillColor: 'black', + * selected: true + * }); + */ - /** - * The unique id of the item. - * - * @bean - * @type Number - */ - getId: function() { - return this._id; - }, + /** + * The unique id of the item. + * + * @bean + * @type Number + */ + getId: function () { + return this._id; + }, - /** - * The class name of the item as a string. - * - * @name Item#className - * @type String - * @values 'Group', 'Layer', 'Path', 'CompoundPath', 'Shape', 'Raster', - * 'SymbolItem', 'PointText' - */ + /** + * The class name of the item as a string. + * + * @name Item#className + * @type String + * @values 'Group', 'Layer', 'Path', 'CompoundPath', 'Shape', 'Raster', + * 'SymbolItem', 'PointText' + */ - /** + /** * The name of the item. If the item has a name, it can be accessed by name * through its parent's children list. * @@ -309,4625 +343,4822 @@ new function() { // Injection scope for various item event handlers * // The path can be accessed by name: * group.children['example'].fillColor = 'red'; */ - getName: function() { - return this._name; - }, + getName: function () { + return this._name; + }, - setName: function(name) { - // NOTE: Don't check if the name has changed and bail out if it has not, - // because setName is used internally also to update internal structures - // when an item is moved from one parent to another. - - // If the item already had a name, remove the reference to it from the - // parent's children object: - if (this._name) - this._removeNamed(); - // See if the name is a simple number, which we cannot support due to - // the named lookup on the children array. - if (name === (+name) + '') - throw new Error( - 'Names consisting only of numbers are not supported.'); - var owner = this._getOwner(); - if (name && owner) { - var children = owner._children, - namedChildren = owner._namedChildren; - (namedChildren[name] = namedChildren[name] || []).push(this); - // Only set this item if there isn't one under the same name already - if (!(name in children)) - children[name] = this; - } - this._name = name || undefined; - this._changed(/*#=*/ChangeFlag.ATTRIBUTE); - }, + setName: function (name) { + // NOTE: Don't check if the name has changed and bail out if it has not, + // because setName is used internally also to update internal structures + // when an item is moved from one parent to another. + + // If the item already had a name, remove the reference to it from the + // parent's children object: + if (this._name) this._removeNamed(); + // See if the name is a simple number, which we cannot support due to + // the named lookup on the children array. + if (name === +name + "") + throw new Error( + "Names consisting only of numbers are not supported." + ); + var owner = this._getOwner(); + if (name && owner) { + var children = owner._children, + namedChildren = owner._namedChildren; + (namedChildren[name] = namedChildren[name] || []).push(this); + // Only set this item if there isn't one under the same name already + if (!(name in children)) children[name] = this; + } + this._name = name || undefined; + this._changed(/*#=*/ ChangeFlag.ATTRIBUTE); + }, - /** - * The path style of the item. - * - * @bean - * @name Item#getStyle - * @type Style - * - * @example {@paperscript} - * // Applying several styles to an item in one go, by passing an object - * // to its style property: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 30 - * }); - * circle.style = { - * fillColor: 'blue', - * strokeColor: 'red', - * strokeWidth: 5 - * }; - * - * @example {@paperscript split=true height=100} - * // Copying the style of another item: - * var path = new Path.Circle({ - * center: [50, 50], - * radius: 30, - * fillColor: 'red' - * }); - * - * var path2 = new Path.Circle({ - * center: new Point(180, 50), - * radius: 20 - * }); - * - * // Copy the path style of path: - * path2.style = path.style; - * - * @example {@paperscript} - * // Applying the same style object to multiple items: - * var myStyle = { - * fillColor: 'red', - * strokeColor: 'blue', - * strokeWidth: 4 - * }; - * - * var path = new Path.Circle({ - * center: [50, 50], - * radius: 30 - * }); - * path.style = myStyle; - * - * var path2 = new Path.Circle({ - * center: new Point(150, 50), - * radius: 20 - * }); - * path2.style = myStyle; - */ - getStyle: function() { - return this._style; - }, + /** + * The path style of the item. + * + * @bean + * @name Item#getStyle + * @type Style + * + * @example {@paperscript} + * // Applying several styles to an item in one go, by passing an object + * // to its style property: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 30 + * }); + * circle.style = { + * fillColor: 'blue', + * strokeColor: 'red', + * strokeWidth: 5 + * }; + * + * @example {@paperscript split=true height=100} + * // Copying the style of another item: + * var path = new Path.Circle({ + * center: [50, 50], + * radius: 30, + * fillColor: 'red' + * }); + * + * var path2 = new Path.Circle({ + * center: new Point(180, 50), + * radius: 20 + * }); + * + * // Copy the path style of path: + * path2.style = path.style; + * + * @example {@paperscript} + * // Applying the same style object to multiple items: + * var myStyle = { + * fillColor: 'red', + * strokeColor: 'blue', + * strokeWidth: 4 + * }; + * + * var path = new Path.Circle({ + * center: [50, 50], + * radius: 30 + * }); + * path.style = myStyle; + * + * var path2 = new Path.Circle({ + * center: new Point(150, 50), + * radius: 20 + * }); + * path2.style = myStyle; + */ + getStyle: function () { + return this._style; + }, - setStyle: function(style) { - // Don't access _style directly so Path#getStyle() can be overridden for - // CompoundPaths. - this.getStyle().set(style); - } -}, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'], - // Produce getter/setters for properties. We need setters because we want to - // call _changed() if a property was modified. - function(name) { - var part = Base.capitalize(name), - key = '_' + name, - flags = { - // #locked does not change appearance, all others do: - locked: /*#=*/ChangeFlag.ATTRIBUTE, - // #visible changes appearance - visible: /*#=*/(Change.ATTRIBUTE | Change.GEOMETRY) - }; - this['get' + part] = function() { - return this[key]; - }; - this['set' + part] = function(value) { - if (value != this[key]) { - this[key] = value; - this._changed(flags[name] || /*#=*/Change.ATTRIBUTE); - } - }; + setStyle: function (style) { + // Don't access _style directly so Path#getStyle() can be overridden for + // CompoundPaths. + this.getStyle().set(style); + }, }, -{}), /** @lends Item# */{ - // Enforce creation of beans, as bean getters have hidden parameters. - // See #getPosition() below. - beans: true, + Base.each( + ["locked", "visible", "blendMode", "opacity", "guide"], + // Produce getter/setters for properties. We need setters because we want to + // call _changed() if a property was modified. + function (name) { + var part = Base.capitalize(name), + key = "_" + name, + flags = { + // #locked does not change appearance, all others do: + locked: /*#=*/ ChangeFlag.ATTRIBUTE, + // #visible changes appearance + visible: /*#=*/ Change.ATTRIBUTE | Change.GEOMETRY, + }; + this["get" + part] = function () { + return this[key]; + }; + this["set" + part] = function (value) { + if (value != this[key]) { + this[key] = value; + this._changed(flags[name] || /*#=*/ Change.ATTRIBUTE); + } + }; + }, + {} + ), + /** @lends Item# */ { + // Enforce creation of beans, as bean getters have hidden parameters. + // See #getPosition() below. + beans: true, - // NOTE: These properties have their getter / setters produced in the - // injection scope above. + // NOTE: These properties have their getter / setters produced in the + // injection scope above. - /** - * Specifies whether the item is locked. When set to `true`, item - * interactions with the mouse are disabled. - * - * @name Item#locked - * @type Boolean - * @default false - * - * @example {@paperscript} - * var unlockedItem = new Path.Circle({ - * center: view.center - [35, 0], - * radius: 30, - * fillColor: 'springgreen', - * onMouseDown: function() { - * this.fillColor = Color.random(); - * } - * }); - * - * var lockedItem = new Path.Circle({ - * center: view.center + [35, 0], - * radius: 30, - * fillColor: 'crimson', - * locked: true, - * // This event won't be triggered because the item is locked. - * onMouseDown: function() { - * this.fillColor = Color.random(); - * } - * }); - * - * new PointText({ - * content: 'Click on both circles to see which one is locked.', - * point: view.center - [0, 35], - * justification: 'center' - * }); - */ + /** + * Specifies whether the item is locked. When set to `true`, item + * interactions with the mouse are disabled. + * + * @name Item#locked + * @type Boolean + * @default false + * + * @example {@paperscript} + * var unlockedItem = new Path.Circle({ + * center: view.center - [35, 0], + * radius: 30, + * fillColor: 'springgreen', + * onMouseDown: function() { + * this.fillColor = Color.random(); + * } + * }); + * + * var lockedItem = new Path.Circle({ + * center: view.center + [35, 0], + * radius: 30, + * fillColor: 'crimson', + * locked: true, + * // This event won't be triggered because the item is locked. + * onMouseDown: function() { + * this.fillColor = Color.random(); + * } + * }); + * + * new PointText({ + * content: 'Click on both circles to see which one is locked.', + * point: view.center - [0, 35], + * justification: 'center' + * }); + */ - /** - * Specifies whether the item is visible. When set to `false`, the item - * won't be drawn. - * - * @name Item#visible - * @type Boolean - * @default true - * - * @example {@paperscript} - * // Hiding an item: - * var path = new Path.Circle({ - * center: [50, 50], - * radius: 20, - * fillColor: 'red' - * }); - * - * // Hide the path: - * path.visible = false; - */ + /** + * Specifies whether the item is visible. When set to `false`, the item + * won't be drawn. + * + * @name Item#visible + * @type Boolean + * @default true + * + * @example {@paperscript} + * // Hiding an item: + * var path = new Path.Circle({ + * center: [50, 50], + * radius: 20, + * fillColor: 'red' + * }); + * + * // Hide the path: + * path.visible = false; + */ - /** - * The blend mode with which the item is composited onto the canvas. Both - * the standard canvas compositing modes, as well as the new CSS blend modes - * are supported. If blend-modes cannot be rendered natively, they are - * emulated. Be aware that emulation can have an impact on performance. - * - * @name Item#blendMode - * @type String - * @values 'normal', 'multiply', 'screen', 'overlay', 'soft-light', 'hard- - * light', 'color-dodge', 'color-burn', 'darken', 'lighten', - * 'difference', 'exclusion', 'hue', 'saturation', 'luminosity', - * 'color', 'add', 'subtract', 'average', 'pin-light', 'negation', - * 'source-over', 'source-in', 'source-out', 'source-atop', - * 'destination-over', 'destination-in', 'destination-out', - * 'destination-atop', 'lighter', 'darker', 'copy', 'xor' - * @default 'normal' - * - * @example {@paperscript} - * // Setting an item's blend mode: - * - * // Create a white rectangle in the background - * // with the same dimensions as the view: - * var background = new Path.Rectangle(view.bounds); - * background.fillColor = 'white'; - * - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35, - * fillColor: 'red' - * }); - * - * var circle2 = new Path.Circle({ - * center: new Point(120, 50), - * radius: 35, - * fillColor: 'blue' - * }); - * - * // Set the blend mode of circle2: - * circle2.blendMode = 'multiply'; - */ + /** + * The blend mode with which the item is composited onto the canvas. Both + * the standard canvas compositing modes, as well as the new CSS blend modes + * are supported. If blend-modes cannot be rendered natively, they are + * emulated. Be aware that emulation can have an impact on performance. + * + * @name Item#blendMode + * @type String + * @values 'normal', 'multiply', 'screen', 'overlay', 'soft-light', 'hard- + * light', 'color-dodge', 'color-burn', 'darken', 'lighten', + * 'difference', 'exclusion', 'hue', 'saturation', 'luminosity', + * 'color', 'add', 'subtract', 'average', 'pin-light', 'negation', + * 'source-over', 'source-in', 'source-out', 'source-atop', + * 'destination-over', 'destination-in', 'destination-out', + * 'destination-atop', 'lighter', 'darker', 'copy', 'xor' + * @default 'normal' + * + * @example {@paperscript} + * // Setting an item's blend mode: + * + * // Create a white rectangle in the background + * // with the same dimensions as the view: + * var background = new Path.Rectangle(view.bounds); + * background.fillColor = 'white'; + * + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35, + * fillColor: 'red' + * }); + * + * var circle2 = new Path.Circle({ + * center: new Point(120, 50), + * radius: 35, + * fillColor: 'blue' + * }); + * + * // Set the blend mode of circle2: + * circle2.blendMode = 'multiply'; + */ - /** - * The opacity of the item as a value between `0` and `1`. - * - * @name Item#opacity - * @type Number - * @default 1 - * - * @example {@paperscript} - * // Making an item 50% transparent: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35, - * fillColor: 'red' - * }); - * - * var circle2 = new Path.Circle({ - * center: new Point(120, 50), - * radius: 35, - * fillColor: 'blue', - * strokeColor: 'green', - * strokeWidth: 10 - * }); - * - * // Make circle2 50% transparent: - * circle2.opacity = 0.5; - */ + /** + * The opacity of the item as a value between `0` and `1`. + * + * @name Item#opacity + * @type Number + * @default 1 + * + * @example {@paperscript} + * // Making an item 50% transparent: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35, + * fillColor: 'red' + * }); + * + * var circle2 = new Path.Circle({ + * center: new Point(120, 50), + * radius: 35, + * fillColor: 'blue', + * strokeColor: 'green', + * strokeWidth: 10 + * }); + * + * // Make circle2 50% transparent: + * circle2.opacity = 0.5; + */ - // TODO: Implement guides - /** - * Specifies whether the item functions as a guide. When set to `true`, the - * item will be drawn at the end as a guide. - * - * @name Item#guide - * @type Boolean - * @default true - * @ignore - */ + // TODO: Implement guides + /** + * Specifies whether the item functions as a guide. When set to `true`, the + * item will be drawn at the end as a guide. + * + * @name Item#guide + * @type Boolean + * @default true + * @ignore + */ - getSelection: function() { - return this._selection; - }, + getSelection: function () { + return this._selection; + }, - setSelection: function(selection) { - if (selection !== this._selection) { - this._selection = selection; - var project = this._project; - if (project) { - project._updateSelection(this); - this._changed(/*#=*/Change.ATTRIBUTE); + setSelection: function (selection) { + if (selection !== this._selection) { + this._selection = selection; + var project = this._project; + if (project) { + project._updateSelection(this); + this._changed(/*#=*/ Change.ATTRIBUTE); + } } - } - }, - - _changeSelection: function(flag, selected) { - var selection = this._selection; - this.setSelection(selected ? selection | flag : selection & ~flag); - }, - - /** - * Specifies whether the item is selected. This will also return `true` for - * {@link Group} items if they are partially selected, e.g. groups - * containing selected or partially selected paths. - * - * 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. - * - * @bean - * @type Boolean - * @default false - * @see Project#selectedItems - * @see Segment#selected - * @see Curve#selected - * @see Point#selected - * - * @example {@paperscript} - * // Selecting an item: - * var path = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * path.selected = true; // Select the path - */ - isSelected: function() { - if (this._selectChildren) { - var children = this._children; - for (var i = 0, l = children.length; i < l; i++) - if (children[i].isSelected()) - return true; - } - return !!(this._selection & /*#=*/ItemSelection.ITEM); - }, - - setSelected: function(selected) { - if (this._selectChildren) { - var children = this._children; - for (var i = 0, l = children.length; i < l; i++) - children[i].setSelected(selected); - } - this._changeSelection(/*#=*/ItemSelection.ITEM, selected); - }, + }, - isFullySelected: function() { - var children = this._children, - selected = !!(this._selection & /*#=*/ItemSelection.ITEM); - if (children && selected) { - for (var i = 0, l = children.length; i < l; i++) - if (!children[i].isFullySelected()) - return false; - return true; - } - // If there are no children, this is the same as #selected - return selected; - }, + _changeSelection: function (flag, selected) { + var selection = this._selection; + this.setSelection(selected ? selection | flag : selection & ~flag); + }, - setFullySelected: function(selected) { - var children = this._children; - if (children) { - for (var i = 0, l = children.length; i < l; i++) - children[i].setFullySelected(selected); - } - this._changeSelection(/*#=*/ItemSelection.ITEM, selected); - }, + /** + * Specifies whether the item is selected. This will also return `true` for + * {@link Group} items if they are partially selected, e.g. groups + * containing selected or partially selected paths. + * + * 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. + * + * @bean + * @type Boolean + * @default false + * @see Project#selectedItems + * @see Segment#selected + * @see Curve#selected + * @see Point#selected + * + * @example {@paperscript} + * // Selecting an item: + * var path = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * path.selected = true; // Select the path + */ + isSelected: function () { + if (this._selectChildren) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + if (children[i].isSelected()) return true; + } + return !!(this._selection & /*#=*/ ItemSelection.ITEM); + }, - /** - * Specifies whether the item defines a clip mask. This can only be set on - * paths and compound paths, and only if the item is already contained - * within a clipping group. - * - * @bean - * @type Boolean - * @default false - */ - isClipMask: function() { - return this._clipMask; - }, + setSelected: function (selected) { + if (this._selectChildren) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + children[i].setSelected(selected); + } + this._changeSelection(/*#=*/ ItemSelection.ITEM, selected); + }, - setClipMask: function(clipMask) { - // On-the-fly conversion to boolean: - if (this._clipMask != (clipMask = !!clipMask)) { - this._clipMask = clipMask; - if (clipMask) { - this.setFillColor(null); - this.setStrokeColor(null); + isFullySelected: function () { + var children = this._children, + selected = !!(this._selection & /*#=*/ ItemSelection.ITEM); + if (children && selected) { + for (var i = 0, l = children.length; i < l; i++) + if (!children[i].isFullySelected()) return false; + return true; } - this._changed(/*#=*/Change.ATTRIBUTE); - // Tell the parent the clipping mask has changed - if (this._parent) - this._parent._changed(/*#=*/ChangeFlag.CLIPPING); - } - }, + // If there are no children, this is the same as #selected + return selected; + }, - // TODO: get/setIsolated (print specific feature) - // TODO: get/setKnockout (print specific feature) - // TODO: get/setAlphaIsShape + setFullySelected: function (selected) { + var children = this._children; + if (children) { + for (var i = 0, l = children.length; i < l; i++) + children[i].setFullySelected(selected); + } + this._changeSelection(/*#=*/ ItemSelection.ITEM, selected); + }, - /** - * A plain javascript object which can be used to store - * arbitrary data on the item. - * - * @bean - * @type Object - * - * @example - * var path = new Path(); - * path.data.remember = 'milk'; - * - * @example - * var path = new Path(); - * path.data.malcolm = new Point(20, 30); - * console.log(path.data.malcolm.x); // 20 - * - * @example - * var path = new Path(); - * path.data = { - * home: 'Omicron Theta', - * found: 2338, - * pets: ['Spot'] - * }; - * console.log(path.data.pets.length); // 1 - * - * @example - * var path = new Path({ - * data: { - * home: 'Omicron Theta', - * found: 2338, - * pets: ['Spot'] - * } - * }); - * console.log(path.data.pets.length); // 1 - */ - getData: function() { - if (!this._data) - this._data = {}; - return this._data; - }, + /** + * Specifies whether the item defines a clip mask. This can only be set on + * paths and compound paths, and only if the item is already contained + * within a clipping group. + * + * @bean + * @type Boolean + * @default false + */ + isClipMask: function () { + return this._clipMask; + }, - setData: function(data) { - this._data = data; - }, + setClipMask: function (clipMask) { + // On-the-fly conversion to boolean: + if (this._clipMask != (clipMask = !!clipMask)) { + this._clipMask = clipMask; + if (clipMask) { + this.setFillColor(null); + this.setStrokeColor(null); + } + this._changed(/*#=*/ Change.ATTRIBUTE); + // Tell the parent the clipping mask has changed + if (this._parent) + this._parent._changed(/*#=*/ ChangeFlag.CLIPPING); + } + }, - /** - * {@grouptitle Position and Bounding Boxes} - * - * The item's position within the parent item's coordinate system. By - * default, this is the {@link Rectangle#center} of the item's - * {@link #bounds} rectangle. - * - * @bean - * @type Point - * - * @example {@paperscript} - * // Changing the position of a path: - * - * // Create a circle at position { x: 10, y: 10 } - * var circle = new Path.Circle({ - * center: new Point(10, 10), - * radius: 10, - * fillColor: 'red' - * }); - * - * // Move the circle to { x: 20, y: 20 } - * circle.position = new Point(20, 20); - * - * // Move the circle 100 points to the right and 50 points down - * circle.position += new Point(100, 50); - * - * @example {@paperscript split=true height=100} - * // Changing the x coordinate of an item's position: - * - * // Create a circle at position { x: 20, y: 20 } - * var circle = new Path.Circle({ - * center: new Point(20, 20), - * radius: 10, - * fillColor: 'red' - * }); - * - * // Move the circle 100 points to the right - * circle.position.x += 100; - */ - getPosition: function(_dontLink) { - // Cache position value. - // Pass true for _dontLink in getCenter(), so receive back a normal point - var ctor = _dontLink ? Point : LinkedPoint; - // Do not cache LinkedPoints directly, since we would not be able to - // use them to calculate the difference in #setPosition, as when it is - // modified, it would hold new values already and only then cause the - // calling of #setPosition. - var position = this._position || - (this._position = this._getPositionFromBounds()); - return new ctor(position.x, position.y, this, 'setPosition'); - }, + // TODO: get/setIsolated (print specific feature) + // TODO: get/setKnockout (print specific feature) + // TODO: get/setAlphaIsShape - setPosition: function(/* point */) { - // Calculate the distance to the current position, by which to - // translate the item. Pass true for _dontLink, as we do not need a - // LinkedPoint to simply calculate this distance. - this.translate(Point.read(arguments).subtract(this.getPosition(true))); - }, + /** + * A plain javascript object which can be used to store + * arbitrary data on the item. + * + * @bean + * @type Object + * + * @example + * var path = new Path(); + * path.data.remember = 'milk'; + * + * @example + * var path = new Path(); + * path.data.malcolm = new Point(20, 30); + * console.log(path.data.malcolm.x); // 20 + * + * @example + * var path = new Path(); + * path.data = { + * home: 'Omicron Theta', + * found: 2338, + * pets: ['Spot'] + * }; + * console.log(path.data.pets.length); // 1 + * + * @example + * var path = new Path({ + * data: { + * home: 'Omicron Theta', + * found: 2338, + * pets: ['Spot'] + * } + * }); + * console.log(path.data.pets.length); // 1 + */ + getData: function () { + if (!this._data) this._data = {}; + return this._data; + }, - /** - * Internal method used to calculate position either from pivot point or - * bounds. - * @param {Rectangle} bounds if provided, these bounds are used instead of - * calling getBounds() - * @return {Point} the transformed pivot point or the center of the bounds - * @private - */ - _getPositionFromBounds: function(bounds) { - // If an pivot point is provided, use it to determine position - // based on the matrix. Otherwise use the center of the bounds. - return this._pivot + setData: function (data) { + this._data = data; + }, + + /** + * {@grouptitle Position and Bounding Boxes} + * + * The item's position within the parent item's coordinate system. By + * default, this is the {@link Rectangle#center} of the item's + * {@link #bounds} rectangle. + * + * @bean + * @type Point + * + * @example {@paperscript} + * // Changing the position of a path: + * + * // Create a circle at position { x: 10, y: 10 } + * var circle = new Path.Circle({ + * center: new Point(10, 10), + * radius: 10, + * fillColor: 'red' + * }); + * + * // Move the circle to { x: 20, y: 20 } + * circle.position = new Point(20, 20); + * + * // Move the circle 100 points to the right and 50 points down + * circle.position += new Point(100, 50); + * + * @example {@paperscript split=true height=100} + * // Changing the x coordinate of an item's position: + * + * // Create a circle at position { x: 20, y: 20 } + * var circle = new Path.Circle({ + * center: new Point(20, 20), + * radius: 10, + * fillColor: 'red' + * }); + * + * // Move the circle 100 points to the right + * circle.position.x += 100; + */ + getPosition: function (_dontLink) { + // Cache position value. + // Pass true for _dontLink in getCenter(), so receive back a normal point + var ctor = _dontLink ? Point : LinkedPoint; + // Do not cache LinkedPoints directly, since we would not be able to + // use them to calculate the difference in #setPosition, as when it is + // modified, it would hold new values already and only then cause the + // calling of #setPosition. + var position = + this._position || + (this._position = this._getPositionFromBounds()); + return new ctor(position.x, position.y, this, "setPosition"); + }, + + setPosition: function (/* point */) { + // Calculate the distance to the current position, by which to + // translate the item. Pass true for _dontLink, as we do not need a + // LinkedPoint to simply calculate this distance. + this.translate( + Point.read(arguments).subtract(this.getPosition(true)) + ); + }, + + /** + * Internal method used to calculate position either from pivot point or + * bounds. + * @param {Rectangle} bounds if provided, these bounds are used instead of + * calling getBounds() + * @return {Point} the transformed pivot point or the center of the bounds + * @private + */ + _getPositionFromBounds: function (bounds) { + // If an pivot point is provided, use it to determine position + // based on the matrix. Otherwise use the center of the bounds. + return this._pivot ? this._matrix._transformPoint(this._pivot) : (bounds || this.getBounds()).getCenter(true); - }, + }, - /** - * The item's pivot point specified in the item coordinate system, defining - * the point around which all transformations are hinging. This is also the - * reference point for {@link #position}. By default, it is set to `null`, - * meaning the {@link Rectangle#center} of the item's {@link #bounds} - * rectangle is used as pivot. - * - * @bean - * @type Point - * @default null - */ - getPivot: function() { - var pivot = this._pivot; - return pivot - ? new LinkedPoint(pivot.x, pivot.y, this, 'setPivot') + /** + * The item's pivot point specified in the item coordinate system, defining + * the point around which all transformations are hinging. This is also the + * reference point for {@link #position}. By default, it is set to `null`, + * meaning the {@link Rectangle#center} of the item's {@link #bounds} + * rectangle is used as pivot. + * + * @bean + * @type Point + * @default null + */ + getPivot: function () { + var pivot = this._pivot; + return pivot + ? new LinkedPoint(pivot.x, pivot.y, this, "setPivot") : null; - }, - - setPivot: function(/* point */) { - // Clone existing points since we're caching internally. - this._pivot = Point.read(arguments, 0, { clone: true, readNull: true }); - // No need for _changed() since the only thing this affects is _position - this._position = undefined; - } -}, Base.each({ // Produce getters for bounds properties: - getStrokeBounds: { stroke: true }, - getHandleBounds: { handle: true }, - getInternalBounds: { internal: true } - }, - function(options, key) { - this[key] = function(matrix) { - return this.getBounds(matrix, options); - }; - }, -/** @lends Item# */{ - // Enforce creation of beans, as bean getters have hidden parameters. - // See _matrix parameter above. - beans: true, - - getBounds: function(matrix, options) { - var hasMatrix = options || matrix instanceof Matrix, - opts = Base.set({}, hasMatrix ? options : matrix, - this._boundsOptions); - // We can only cache the bounds if the path uses stroke-scaling, or if - // no stroke is involved in the calculation of the bounds. - // When strokeScaling is false, the bounds are affected by the zoom - // level of the view, hence we can't cache. - // TODO: Look more into handling of stroke-scaling, e.g. on groups with - // some children that have strokeScaling, as well as SymbolItem with - // SymbolDefinition that have strokeScaling! - // TODO: Once that is resolved, we should be able to turn off - // opts.stroke if a resolved item definition does not have a stroke, - // allowing the code to share caches between #strokeBounds and #bounds. - if (!opts.stroke || this.getStrokeScaling()) - opts.cacheItem = this; - // If we're caching bounds, pass on this item as cacheItem, so - // the children can setup _boundsCache structures for it. - var rect = this._getCachedBounds(hasMatrix && matrix, opts).rect; - // If we're returning '#bounds', create a LinkedRectangle that uses - // the setBounds() setter to update the Item whenever the bounds are - // changed: - return !arguments.length - ? new LinkedRectangle(rect.x, rect.y, rect.width, rect.height, - this, 'setBounds') - : rect; - }, - - setBounds: function(/* rect */) { - var rect = Rectangle.read(arguments), - bounds = this.getBounds(), - _matrix = this._matrix, - matrix = new Matrix(), - center = rect.getCenter(); - // Read this from bottom to top: - // Translate to new center: - matrix.translate(center); - // Scale to new Size, if size changes and avoid divisions by 0: - if (rect.width != bounds.width || rect.height != bounds.height) { - // If a previous transformation resulted in a non-invertible matrix, - // Restore to the last revertible matrix stored in _backup, and get - // the bounds again. That way, we can prevent collapsing to 0-size. - if (!_matrix.isInvertible()) { - _matrix.set(_matrix._backup - || new Matrix().translate(_matrix.getTranslation())); - bounds = this.getBounds(); - } - matrix.scale( - bounds.width !== 0 ? rect.width / bounds.width : 0, - bounds.height !== 0 ? rect.height / bounds.height : 0); - } - // Translate to bounds center: - center = bounds.getCenter(); - matrix.translate(-center.x, -center.y); - // Now execute the transformation - this.transform(matrix); - }, + }, - /** - * Protected method used in all the bounds getters. It loops through all the - * children, gets their bounds and finds the bounds around all of them. - * Subclasses override it to define calculations for the various required - * bounding types. - */ - _getBounds: function(matrix, options) { - // NOTE: We cannot cache these results here, since we do not get - // _changed() notifications here for changing geometry in children. - // But cacheName is used in sub-classes such as SymbolItem and Raster. - var children = this._children; - // TODO: What to return if nothing is defined, e.g. empty Groups? - // Scriptographer behaves weirdly then too. - if (!children || !children.length) - return new Rectangle(); - // Call _updateBoundsCache() even when the group only holds empty / - // invisible items), so future changes in these items will cause right - // handling of _boundsCache. - Item._updateBoundsCache(this, options.cacheItem); - return Item._getBounds(children, matrix, options); + setPivot: function (/* point */) { + // Clone existing points since we're caching internally. + this._pivot = Point.read(arguments, 0, { + clone: true, + readNull: true, + }); + // No need for _changed() since the only thing this affects is _position + this._position = undefined; + }, }, + Base.each( + { + // Produce getters for bounds properties: + getStrokeBounds: { stroke: true }, + getHandleBounds: { handle: true }, + getInternalBounds: { internal: true }, + }, + function (options, key) { + this[key] = function (matrix) { + return this.getBounds(matrix, options); + }; + }, + /** @lends Item# */ { + // Enforce creation of beans, as bean getters have hidden parameters. + // See _matrix parameter above. + beans: true, + + getBounds: function (matrix, options) { + var hasMatrix = options || matrix instanceof Matrix, + opts = Base.set( + {}, + hasMatrix ? options : matrix, + this._boundsOptions + ); + // We can only cache the bounds if the path uses stroke-scaling, or if + // no stroke is involved in the calculation of the bounds. + // When strokeScaling is false, the bounds are affected by the zoom + // level of the view, hence we can't cache. + // TODO: Look more into handling of stroke-scaling, e.g. on groups with + // some children that have strokeScaling, as well as SymbolItem with + // SymbolDefinition that have strokeScaling! + // TODO: Once that is resolved, we should be able to turn off + // opts.stroke if a resolved item definition does not have a stroke, + // allowing the code to share caches between #strokeBounds and #bounds. + if (!opts.stroke || this.getStrokeScaling()) + opts.cacheItem = this; + // If we're caching bounds, pass on this item as cacheItem, so + // the children can setup _boundsCache structures for it. + var rect = this._getCachedBounds( + hasMatrix && matrix, + opts + ).rect; + // If we're returning '#bounds', create a LinkedRectangle that uses + // the setBounds() setter to update the Item whenever the bounds are + // changed: + return !arguments.length + ? new LinkedRectangle( + rect.x, + rect.y, + rect.width, + rect.height, + this, + "setBounds" + ) + : rect; + }, - _getBoundsCacheKey: function(options, internal) { - return [ - options.stroke ? 1 : 0, - options.handle ? 1 : 0, - internal ? 1 : 0 - ].join(''); - }, + setBounds: function (/* rect */) { + var rect = Rectangle.read(arguments), + bounds = this.getBounds(), + _matrix = this._matrix, + matrix = new Matrix(), + center = rect.getCenter(); + // Read this from bottom to top: + // Translate to new center: + matrix.translate(center); + // Scale to new Size, if size changes and avoid divisions by 0: + if ( + rect.width != bounds.width || + rect.height != bounds.height + ) { + // If a previous transformation resulted in a non-invertible matrix, + // Restore to the last revertible matrix stored in _backup, and get + // the bounds again. That way, we can prevent collapsing to 0-size. + if (!_matrix.isInvertible()) { + _matrix.set( + _matrix._backup || + new Matrix().translate(_matrix.getTranslation()) + ); + bounds = this.getBounds(); + } + matrix.scale( + bounds.width !== 0 ? rect.width / bounds.width : 0, + bounds.height !== 0 ? rect.height / bounds.height : 0 + ); + } + // Translate to bounds center: + center = bounds.getCenter(); + matrix.translate(-center.x, -center.y); + // Now execute the transformation + this.transform(matrix); + }, - /** - * Private method that deals with the calling of _getBounds, recursive - * matrix concatenation and handles all the complicated caching mechanisms. - */ - _getCachedBounds: function(matrix, options, noInternal) { - // See if we can cache these bounds. We only cache the bounds - // transformed with the internally stored _matrix, (the default if no - // matrix is passed). - matrix = matrix && matrix._orNullIfIdentity(); - // Do not transform by the internal matrix for internal, untransformed - // bounds. - var internal = options.internal && !noInternal, - cacheItem = options.cacheItem, - _matrix = internal ? null : this._matrix._orNullIfIdentity(), - // Create a key for caching, reflecting all bounds options. - cacheKey = cacheItem && (!matrix || matrix.equals(_matrix)) - && this._getBoundsCacheKey(options, internal), - bounds = this._bounds; - // NOTE: This needs to happen before returning cached values, since even - // then, _boundsCache needs to be kept up-to-date. - Item._updateBoundsCache(this._parent || this._symbol, cacheItem); - if (cacheKey && bounds && cacheKey in bounds) { - var cached = bounds[cacheKey]; - return { - rect: cached.rect.clone(), - nonscaling: cached.nonscaling - }; - } - var res = this._getBounds(matrix || _matrix, options), - // Support two versions of _getBounds(): One that directly returns a - // Rectangle, and one that returns a bounds object with nonscaling. - rect = res.rect || res, - style = this._style, - nonscaling = res.nonscaling || style.hasStroke() - && !style.getStrokeScaling(); - // If we can cache the result, update the _bounds cache structure - // before returning - if (cacheKey) { - if (!bounds) { - this._bounds = bounds = {}; - } - var cached = bounds[cacheKey] = { - rect: rect.clone(), - nonscaling: nonscaling, - // Mark as internal, so Item#transform() won't transform it - internal: internal - }; - } - return { - rect: rect, - nonscaling: nonscaling - }; - }, + /** + * Protected method used in all the bounds getters. It loops through all the + * children, gets their bounds and finds the bounds around all of them. + * Subclasses override it to define calculations for the various required + * bounding types. + */ + _getBounds: function (matrix, options) { + // NOTE: We cannot cache these results here, since we do not get + // _changed() notifications here for changing geometry in children. + // But cacheName is used in sub-classes such as SymbolItem and Raster. + var children = this._children; + // TODO: What to return if nothing is defined, e.g. empty Groups? + // Scriptographer behaves weirdly then too. + if (!children || !children.length) return new Rectangle(); + // Call _updateBoundsCache() even when the group only holds empty / + // invisible items), so future changes in these items will cause right + // handling of _boundsCache. + Item._updateBoundsCache(this, options.cacheItem); + return Item._getBounds(children, matrix, options); + }, - /** - * Returns to correct matrix to use to transform stroke related geometries - * when calculating bounds: the item's matrix if {@link #strokeScaling} is - * `true`, otherwise the parent's inverted view matrix. The returned matrix - * is always shiftless, meaning its translation vector is reset to zero. - */ - _getStrokeMatrix: function(matrix, options) { - var parent = this.getStrokeScaling() ? null - : options && options.internal ? this - : this._parent || this._symbol && this._symbol._item, - mx = parent ? parent.getViewMatrix().invert() : matrix; - return mx && mx._shiftless(); - }, + _getBoundsCacheKey: function (options, internal) { + return [ + options.stroke ? 1 : 0, + options.handle ? 1 : 0, + internal ? 1 : 0, + ].join(""); + }, - statics: /** @lends Item */{ - /** - * Set up a boundsCache structure that keeps track of items that keep - * cached bounds that depend on this item. We store this in the parent, - * for multiple reasons: - * The parent receives CHILDREN change notifications for when its - * children are added or removed and can thus clear the cache, and we - * save a lot of memory, e.g. when grouping 100 items and asking the - * group for its bounds. If stored on the children, we would have 100 - * times the same structure. - */ - _updateBoundsCache: function(parent, item) { - if (parent && item) { - // Set-up the parent's boundsCache structure if it does not - // exist yet and add the item to it. - var id = item._id, - ref = parent._boundsCache = parent._boundsCache || { - // Use a hash-table for ids and an array for the list, - // so we can keep track of items that were added already - ids: {}, - list: [] + /** + * Private method that deals with the calling of _getBounds, recursive + * matrix concatenation and handles all the complicated caching mechanisms. + */ + _getCachedBounds: function (matrix, options, noInternal) { + // See if we can cache these bounds. We only cache the bounds + // transformed with the internally stored _matrix, (the default if no + // matrix is passed). + matrix = matrix && matrix._orNullIfIdentity(); + // Do not transform by the internal matrix for internal, untransformed + // bounds. + var internal = options.internal && !noInternal, + cacheItem = options.cacheItem, + _matrix = internal + ? null + : this._matrix._orNullIfIdentity(), + // Create a key for caching, reflecting all bounds options. + cacheKey = + cacheItem && + (!matrix || matrix.equals(_matrix)) && + this._getBoundsCacheKey(options, internal), + bounds = this._bounds; + // NOTE: This needs to happen before returning cached values, since even + // then, _boundsCache needs to be kept up-to-date. + Item._updateBoundsCache( + this._parent || this._symbol, + cacheItem + ); + if (cacheKey && bounds && cacheKey in bounds) { + var cached = bounds[cacheKey]; + return { + rect: cached.rect.clone(), + nonscaling: cached.nonscaling, }; - if (!ref.ids[id]) { - ref.list.push(item); - ref.ids[id] = item; } - } - }, - - /** - * Clears cached bounds of all items that the children of this item are - * contributing to. See _updateBoundsCache() for an explanation why this - * information is stored on parents, not the children themselves. - */ - _clearBoundsCache: function(item) { - // This is defined as a static method so Symbol can used it too. - // Clear the position as well, since it's depending on bounds. - var cache = item._boundsCache; - if (cache) { - // Erase cache before looping, to prevent circular recursion. - item._bounds = item._position = item._boundsCache = undefined; - for (var i = 0, list = cache.list, l = list.length; i < l; i++){ - var other = list[i]; - if (other !== item) { - other._bounds = other._position = undefined; - // We need to recursively call _clearBoundsCache, as - // when the cache for the other item's children is not - // valid anymore, that propagates up the scene graph. - if (other._boundsCache) - Item._clearBoundsCache(other); + var res = this._getBounds(matrix || _matrix, options), + // Support two versions of _getBounds(): One that directly returns a + // Rectangle, and one that returns a bounds object with nonscaling. + rect = res.rect || res, + style = this._style, + nonscaling = + res.nonscaling || + (style.hasStroke() && !style.getStrokeScaling()); + // If we can cache the result, update the _bounds cache structure + // before returning + if (cacheKey) { + if (!bounds) { + this._bounds = bounds = {}; } + var cached = (bounds[cacheKey] = { + rect: rect.clone(), + nonscaling: nonscaling, + // Mark as internal, so Item#transform() won't transform it + internal: internal, + }); } - } - }, - - /** - * Gets the combined bounds of all specified items. - */ - _getBounds: function(items, matrix, options) { - var x1 = Infinity, - x2 = -x1, - y1 = x1, - y2 = x2, - nonscaling = false; - // NOTE: As soon as one child-item has non-scaling strokes, the full - // bounds need to be considered non-scaling for caching purposes. - options = options || {}; - for (var i = 0, l = items.length; i < l; i++) { - var item = items[i]; - // Item is handled if it is visible and not recursively empty. - // This avoid errors with nested empty groups (#1467). - if (item._visible && !item.isEmpty(true)) { - // Pass true for noInternal, since even when getting - // internal bounds for this item, we need to apply the - // matrices to its children. - var bounds = item._getCachedBounds( - matrix && matrix.appended(item._matrix), options, true), - rect = bounds.rect; - x1 = Math.min(rect.x, x1); - y1 = Math.min(rect.y, y1); - x2 = Math.max(rect.x + rect.width, x2); - y2 = Math.max(rect.y + rect.height, y2); - if (bounds.nonscaling) - nonscaling = true; - } - } - return { - rect: isFinite(x1) - ? new Rectangle(x1, y1, x2 - x1, y2 - y1) - : new Rectangle(), - nonscaling: nonscaling - }; - } - } + return { + rect: rect, + nonscaling: nonscaling, + }; + }, - /** - * The bounding rectangle of the item excluding stroke width. - * - * @name Item#bounds - * @type Rectangle - */ + /** + * Returns to correct matrix to use to transform stroke related geometries + * when calculating bounds: the item's matrix if {@link #strokeScaling} is + * `true`, otherwise the parent's inverted view matrix. The returned matrix + * is always shiftless, meaning its translation vector is reset to zero. + */ + _getStrokeMatrix: function (matrix, options) { + var parent = this.getStrokeScaling() + ? null + : options && options.internal + ? this + : this._parent || (this._symbol && this._symbol._item), + mx = parent ? parent.getViewMatrix().invert() : matrix; + return mx && mx._shiftless(); + }, - /** - * The bounding rectangle of the item including stroke width. - * - * @name Item#strokeBounds - * @type Rectangle - */ + statics: /** @lends Item */ { + /** + * Set up a boundsCache structure that keeps track of items that keep + * cached bounds that depend on this item. We store this in the parent, + * for multiple reasons: + * The parent receives CHILDREN change notifications for when its + * children are added or removed and can thus clear the cache, and we + * save a lot of memory, e.g. when grouping 100 items and asking the + * group for its bounds. If stored on the children, we would have 100 + * times the same structure. + */ + _updateBoundsCache: function (parent, item) { + if (parent && item) { + // Set-up the parent's boundsCache structure if it does not + // exist yet and add the item to it. + var id = item._id, + ref = (parent._boundsCache = + parent._boundsCache || { + // Use a hash-table for ids and an array for the list, + // so we can keep track of items that were added already + ids: {}, + list: [], + }); + if (!ref.ids[id]) { + ref.list.push(item); + ref.ids[id] = item; + } + } + }, - /** - * The bounding rectangle of the item including handles. - * - * @name Item#handleBounds - * @type Rectangle - */ + /** + * Clears cached bounds of all items that the children of this item are + * contributing to. See _updateBoundsCache() for an explanation why this + * information is stored on parents, not the children themselves. + */ + _clearBoundsCache: function (item) { + // This is defined as a static method so Symbol can used it too. + // Clear the position as well, since it's depending on bounds. + var cache = item._boundsCache; + if (cache) { + // Erase cache before looping, to prevent circular recursion. + item._bounds = + item._position = + item._boundsCache = + undefined; + for ( + var i = 0, list = cache.list, l = list.length; + i < l; + i++ + ) { + var other = list[i]; + if (other !== item) { + other._bounds = other._position = undefined; + // We need to recursively call _clearBoundsCache, as + // when the cache for the other item's children is not + // valid anymore, that propagates up the scene graph. + if (other._boundsCache) + Item._clearBoundsCache(other); + } + } + } + }, - /** - * The bounding rectangle of the item without any matrix transformations. - * - * Typical use case would be drawing a frame around the object where you - * want to draw something of the same size, position, rotation, and scaling, - * like a selection frame. - * - * @name Item#internalBounds - * @type Rectangle - */ + /** + * Gets the combined bounds of all specified items. + */ + _getBounds: function (items, matrix, options) { + var x1 = Infinity, + x2 = -x1, + y1 = x1, + y2 = x2, + nonscaling = false; + // NOTE: As soon as one child-item has non-scaling strokes, the full + // bounds need to be considered non-scaling for caching purposes. + options = options || {}; + for (var i = 0, l = items.length; i < l; i++) { + var item = items[i]; + // Item is handled if it is visible and not recursively empty. + // This avoid errors with nested empty groups (#1467). + if (item._visible && !item.isEmpty(true)) { + // Pass true for noInternal, since even when getting + // internal bounds for this item, we need to apply the + // matrices to its children. + var bounds = item._getCachedBounds( + matrix && matrix.appended(item._matrix), + options, + true + ), + rect = bounds.rect; + x1 = Math.min(rect.x, x1); + y1 = Math.min(rect.y, y1); + x2 = Math.max(rect.x + rect.width, x2); + y2 = Math.max(rect.y + rect.height, y2); + if (bounds.nonscaling) nonscaling = true; + } + } + return { + rect: isFinite(x1) + ? new Rectangle(x1, y1, x2 - x1, y2 - y1) + : new Rectangle(), + nonscaling: nonscaling, + }; + }, + }, - /** - * The rough bounding rectangle of the item that is sure to include all of - * the drawing, including stroke width. - * - * @name Item#roughBounds - * @type Rectangle - * @ignore - */ -}), /** @lends Item# */{ - // Enforce creation of beans, as bean getters have hidden parameters. - // See #getGlobalMatrix() below. - beans: true, - - _decompose: function() { - // Only decompose if the item isn't directly baking transformations into - // its content. - return this._applyMatrix - ? null - : this._decomposed || (this._decomposed = this._matrix.decompose()); - }, + /** + * The bounding rectangle of the item excluding stroke width. + * + * @name Item#bounds + * @type Rectangle + */ + + /** + * The bounding rectangle of the item including stroke width. + * + * @name Item#strokeBounds + * @type Rectangle + */ + + /** + * The bounding rectangle of the item including handles. + * + * @name Item#handleBounds + * @type Rectangle + */ + + /** + * The bounding rectangle of the item without any matrix transformations. + * + * Typical use case would be drawing a frame around the object where you + * want to draw something of the same size, position, rotation, and scaling, + * like a selection frame. + * + * @name Item#internalBounds + * @type Rectangle + */ + + /** + * The rough bounding rectangle of the item that is sure to include all of + * the drawing, including stroke width. + * + * @name Item#roughBounds + * @type Rectangle + * @ignore + */ + } + ), + /** @lends Item# */ { + // Enforce creation of beans, as bean getters have hidden parameters. + // See #getGlobalMatrix() below. + beans: true, + + _decompose: function () { + // Only decompose if the item isn't directly baking transformations into + // its content. + return this._applyMatrix + ? null + : this._decomposed || + (this._decomposed = this._matrix.decompose()); + }, - /** - * The current rotation angle of the item, as described by its - * {@link #matrix}. - * Please note that this only returns meaningful values for items with - * {@link #applyMatrix} set to `false`, meaning they do not directly bake - * transformations into their content. - * - * @bean - * @type Number - */ - getRotation: function() { - var decomposed = this._decompose(); - // Return 0 if matrix wasn't decomposed, e.g. on items with #applyMatrix - return decomposed ? decomposed.rotation : 0; - }, + /** + * The current rotation angle of the item, as described by its + * {@link #matrix}. + * Please note that this only returns meaningful values for items with + * {@link #applyMatrix} set to `false`, meaning they do not directly bake + * transformations into their content. + * + * @bean + * @type Number + */ + getRotation: function () { + var decomposed = this._decompose(); + // Return 0 if matrix wasn't decomposed, e.g. on items with #applyMatrix + return decomposed ? decomposed.rotation : 0; + }, - setRotation: function(rotation) { - var current = this.getRotation(); - if (current != null && rotation != null) { - // Preserve the cached _decomposed values over rotation, and only - // update the rotation property on it. - var decomposed = this._decomposed; - this.rotate(rotation - current); - if (decomposed) { - decomposed.rotation = rotation; - this._decomposed = decomposed; + setRotation: function (rotation) { + var current = this.getRotation(); + if (current != null && rotation != null) { + // Preserve the cached _decomposed values over rotation, and only + // update the rotation property on it. + var decomposed = this._decomposed; + this.rotate(rotation - current); + if (decomposed) { + decomposed.rotation = rotation; + this._decomposed = decomposed; + } } - } - }, + }, - /** - * The current scale factor of the item, as described by its - * {@link #matrix}. - * Please note that this only returns meaningful values for items with - * {@link #applyMatrix} set to `false`, meaning they do not directly bake - * transformations into their content. - * - * @bean - * @type Point - */ - getScaling: function() { - var decomposed = this._decompose(), - s = decomposed && decomposed.scaling; - // Return [1, 1] if matrix wasn't decomposed, e.g. with #applyMatrix. - return new LinkedPoint(s ? s.x : 1, s ? s.y : 1, this, 'setScaling'); - }, + /** + * The current scale factor of the item, as described by its + * {@link #matrix}. + * Please note that this only returns meaningful values for items with + * {@link #applyMatrix} set to `false`, meaning they do not directly bake + * transformations into their content. + * + * @bean + * @type Point + */ + getScaling: function () { + var decomposed = this._decompose(), + s = decomposed && decomposed.scaling; + // Return [1, 1] if matrix wasn't decomposed, e.g. with #applyMatrix. + return new LinkedPoint( + s ? s.x : 1, + s ? s.y : 1, + this, + "setScaling" + ); + }, - setScaling: function(/* scaling */) { - var current = this.getScaling(), - // Clone existing points since we're caching internally. - scaling = Point.read(arguments, 0, { clone: true, readNull: true }); - if (current && scaling && !current.equals(scaling)) { - // See #setRotation() for preservation of _decomposed. - var rotation = this.getRotation(), - decomposed = this._decomposed, - matrix = new Matrix(), - isZero = Numerical.isZero; - // Create a matrix in which the scaling is applied in the non- - // rotated state, so it is always applied before the rotation. - // TODO: What about skewing? Do we need separately stored values for - // these properties, and apply them separately from the matrix? - if (isZero(current.x) || isZero(current.y)) { - // If current scaling is destructive (at least one axis is 0), - // create a new matrix that applies the desired rotation, - // translation and scaling, without also preserving skewing. - matrix.translate(decomposed.translation); - if (rotation) { - matrix.rotate(rotation); + setScaling: function (/* scaling */) { + var current = this.getScaling(), + // Clone existing points since we're caching internally. + scaling = Point.read(arguments, 0, { + clone: true, + readNull: true, + }); + if (current && scaling && !current.equals(scaling)) { + // See #setRotation() for preservation of _decomposed. + var rotation = this.getRotation(), + decomposed = this._decomposed, + matrix = new Matrix(), + isZero = Numerical.isZero; + // Create a matrix in which the scaling is applied in the non- + // rotated state, so it is always applied before the rotation. + // TODO: What about skewing? Do we need separately stored values for + // these properties, and apply them separately from the matrix? + if (isZero(current.x) || isZero(current.y)) { + // If current scaling is destructive (at least one axis is 0), + // create a new matrix that applies the desired rotation, + // translation and scaling, without also preserving skewing. + matrix.translate(decomposed.translation); + if (rotation) { + matrix.rotate(rotation); + } + matrix.scale(scaling.x, scaling.y); + this._matrix.set(matrix); + } else { + var center = this.getPosition(true); + matrix.translate(center); + if (rotation) matrix.rotate(rotation); + matrix.scale(scaling.x / current.x, scaling.y / current.y); + if (rotation) matrix.rotate(-rotation); + matrix.translate(center.negate()); + this.transform(matrix); + } + if (decomposed) { + decomposed.scaling = scaling; + this._decomposed = decomposed; } - matrix.scale(scaling.x, scaling.y); - this._matrix.set(matrix); - } else { - var center = this.getPosition(true); - matrix.translate(center); - if (rotation) - matrix.rotate(rotation); - matrix.scale(scaling.x / current.x, scaling.y / current.y); - if (rotation) - matrix.rotate(-rotation); - matrix.translate(center.negate()); - this.transform(matrix); - } - if (decomposed) { - decomposed.scaling = scaling; - this._decomposed = decomposed; } - } - }, + }, - /** - * The item's transformation matrix, defining position and dimensions in - * relation to its parent item in which it is contained. - * - * @bean - * @type Matrix - */ - getMatrix: function() { - return this._matrix; - }, + /** + * The item's transformation matrix, defining position and dimensions in + * relation to its parent item in which it is contained. + * + * @bean + * @type Matrix + */ + getMatrix: function () { + return this._matrix; + }, - setMatrix: function() { - // Use Matrix#initialize to easily copy over values. - // NOTE: calling initialize() also calls #_changed() for us, through its - // call to #set() / #reset(), and this also handles _applyMatrix for us. - var matrix = this._matrix; - matrix.set.apply(matrix, arguments); - }, + setMatrix: function () { + // Use Matrix#initialize to easily copy over values. + // NOTE: calling initialize() also calls #_changed() for us, through its + // call to #set() / #reset(), and this also handles _applyMatrix for us. + var matrix = this._matrix; + matrix.set.apply(matrix, arguments); + }, - /** - * The item's global transformation matrix in relation to the global project - * coordinate space. Note that the view's transformations resulting from - * zooming and panning are not factored in. - * - * @bean - * @type Matrix - */ - getGlobalMatrix: function(_dontClone) { - var matrix = this._globalMatrix; - if (matrix) { - // If there's a cached global matrix for this item, check if all its - // parents also have one. If it's missing in any of its parents, it - // means the child's cached version isn't valid anymore. - // For better performance, we also use the occasion of this loop to - // clear cached version of items parents. - var parent = this._parent; - var parents = []; - while (parent) { - if (!parent._globalMatrix) { - matrix = null; - // Also clear global matrix of item's parents. - for (var i = 0, l = parents.length; i < l; i++) { - parents[i]._globalMatrix = null; + /** + * The item's global transformation matrix in relation to the global project + * coordinate space. Note that the view's transformations resulting from + * zooming and panning are not factored in. + * + * @bean + * @type Matrix + */ + getGlobalMatrix: function (_dontClone) { + var matrix = this._globalMatrix; + if (matrix) { + // If there's a cached global matrix for this item, check if all its + // parents also have one. If it's missing in any of its parents, it + // means the child's cached version isn't valid anymore. + // For better performance, we also use the occasion of this loop to + // clear cached version of items parents. + var parent = this._parent; + var parents = []; + while (parent) { + if (!parent._globalMatrix) { + matrix = null; + // Also clear global matrix of item's parents. + for (var i = 0, l = parents.length; i < l; i++) { + parents[i]._globalMatrix = null; + } + break; } - break; + parents.push(parent); + parent = parent._parent; } - parents.push(parent); - parent = parent._parent; } - } - if (!matrix) { - matrix = this._globalMatrix = this._matrix.clone(); - var parent = this._parent; - if (parent) - matrix.prepend(parent.getGlobalMatrix(true)); - } - return _dontClone ? matrix : matrix.clone(); - }, + if (!matrix) { + matrix = this._globalMatrix = this._matrix.clone(); + var parent = this._parent; + if (parent) matrix.prepend(parent.getGlobalMatrix(true)); + } + return _dontClone ? matrix : matrix.clone(); + }, - /** - * The item's global matrix in relation to the view coordinate space. This - * means that the view's transformations resulting from zooming and panning - * are factored in. - * - * @bean - * @type Matrix - */ - getViewMatrix: function() { - return this.getGlobalMatrix().prepend(this.getView()._matrix); - }, + /** + * The item's global matrix in relation to the view coordinate space. This + * means that the view's transformations resulting from zooming and panning + * are factored in. + * + * @bean + * @type Matrix + */ + getViewMatrix: function () { + return this.getGlobalMatrix().prepend(this.getView()._matrix); + }, - /** - * Controls whether the transformations applied to the item (e.g. through - * {@link #transform(matrix)}, {@link #rotate(angle)}, - * {@link #scale(scale)}, etc.) are stored in its {@link #matrix} property, - * or whether they are directly applied to its contents or children (passed - * on to the segments in {@link Path} items, the children of {@link Group} - * items, etc.). - * - * @bean - * @type Boolean - * @default true - */ - getApplyMatrix: function() { - return this._applyMatrix; - }, + /** + * Controls whether the transformations applied to the item (e.g. through + * {@link #transform(matrix)}, {@link #rotate(angle)}, + * {@link #scale(scale)}, etc.) are stored in its {@link #matrix} property, + * or whether they are directly applied to its contents or children (passed + * on to the segments in {@link Path} items, the children of {@link Group} + * items, etc.). + * + * @bean + * @type Boolean + * @default true + */ + getApplyMatrix: function () { + return this._applyMatrix; + }, - setApplyMatrix: function(apply) { - // Tell #transform() to apply the internal matrix if _applyMatrix - // can be set to true. - if (this._applyMatrix = this._canApplyMatrix && !!apply) - this.transform(null, true); - }, + setApplyMatrix: function (apply) { + // Tell #transform() to apply the internal matrix if _applyMatrix + // can be set to true. + if ((this._applyMatrix = this._canApplyMatrix && !!apply)) + this.transform(null, true); + }, - /** - * @bean - * @deprecated use {@link #applyMatrix} instead. - */ - getTransformContent: '#getApplyMatrix', - setTransformContent: '#setApplyMatrix', -}, /** @lends Item# */{ - /** - * {@grouptitle Project Hierarchy} - * The project that this item belongs to. - * - * @type Project - * @bean - */ - getProject: function() { - return this._project; + /** + * @bean + * @deprecated use {@link #applyMatrix} instead. + */ + getTransformContent: "#getApplyMatrix", + setTransformContent: "#setApplyMatrix", }, + /** @lends Item# */ { + /** + * {@grouptitle Project Hierarchy} + * The project that this item belongs to. + * + * @type Project + * @bean + */ + getProject: function () { + return this._project; + }, - _setProject: function(project, installEvents) { - if (this._project !== project) { - // Uninstall events before switching project, then install them - // again. - // NOTE: _installEvents handles all children too! - if (this._project) - this._installEvents(false); - this._project = project; + _setProject: function (project, installEvents) { + if (this._project !== project) { + // Uninstall events before switching project, then install them + // again. + // NOTE: _installEvents handles all children too! + if (this._project) this._installEvents(false); + this._project = project; + var children = this._children; + for (var i = 0, l = children && children.length; i < l; i++) + children[i]._setProject(project); + // We need to call _installEvents(true) again, but merge it with + // handling of installEvents argument below. + installEvents = true; + } + if (installEvents) this._installEvents(true); + }, + + /** + * The view that this item belongs to. + * @type View + * @bean + */ + getView: function () { + return this._project._view; + }, + + /** + * Overrides Emitter#_installEvents to also call _installEvents on all + * children. + */ + _installEvents: function _installEvents(install) { + _installEvents.base.call(this, install); var children = this._children; for (var i = 0, l = children && children.length; i < l; i++) - children[i]._setProject(project); - // We need to call _installEvents(true) again, but merge it with - // handling of installEvents argument below. - installEvents = true; - } - if (installEvents) - this._installEvents(true); - }, + children[i]._installEvents(install); + }, - /** - * The view that this item belongs to. - * @type View - * @bean - */ - getView: function() { - return this._project._view; - }, + /** + * The layer that this item is contained within. + * + * @type Layer + * @bean + */ + getLayer: function () { + var parent = this; + while ((parent = parent._parent)) { + if (parent instanceof Layer) return parent; + } + return null; + }, - /** - * Overrides Emitter#_installEvents to also call _installEvents on all - * children. - */ - _installEvents: function _installEvents(install) { - _installEvents.base.call(this, install); - var children = this._children; - for (var i = 0, l = children && children.length; i < l; i++) - children[i]._installEvents(install); - }, + /** + * The item that this item is contained within. + * + * @type Item + * @bean + * + * @example + * var path = new Path(); + * + * // New items are placed in the active layer: + * console.log(path.parent == project.activeLayer); // true + * + * var group = new Group(); + * group.addChild(path); + * + * // Now the parent of the path has become the group: + * console.log(path.parent == group); // true + * + * @example // Setting the parent of the item to another item + * var path = new Path(); + * + * // New items are placed in the active layer: + * console.log(path.parent == project.activeLayer); // true + * + * var group = new Group(); + * path.parent = group; + * + * // Now the parent of the path has become the group: + * console.log(path.parent == group); // true + * + * // The path is now contained in the children list of group: + * console.log(group.children[0] == path); // true + * + * @example // Setting the parent of an item in the constructor + * var group = new Group(); + * + * var path = new Path({ + * parent: group + * }); + * + * // The parent of the path is the group: + * console.log(path.parent == group); // true + * + * // The path is contained in the children list of group: + * console.log(group.children[0] == path); // true + */ + getParent: function () { + return this._parent; + }, - /** - * The layer that this item is contained within. - * - * @type Layer - * @bean - */ - getLayer: function() { - var parent = this; - while (parent = parent._parent) { - if (parent instanceof Layer) - return parent; - } - return null; - }, + setParent: function (item) { + return item.addChild(this); + }, - /** - * The item that this item is contained within. - * - * @type Item - * @bean - * - * @example - * var path = new Path(); - * - * // New items are placed in the active layer: - * console.log(path.parent == project.activeLayer); // true - * - * var group = new Group(); - * group.addChild(path); - * - * // Now the parent of the path has become the group: - * console.log(path.parent == group); // true - * - * @example // Setting the parent of the item to another item - * var path = new Path(); - * - * // New items are placed in the active layer: - * console.log(path.parent == project.activeLayer); // true - * - * var group = new Group(); - * path.parent = group; - * - * // Now the parent of the path has become the group: - * console.log(path.parent == group); // true - * - * // The path is now contained in the children list of group: - * console.log(group.children[0] == path); // true - * - * @example // Setting the parent of an item in the constructor - * var group = new Group(); - * - * var path = new Path({ - * parent: group - * }); - * - * // The parent of the path is the group: - * console.log(path.parent == group); // true - * - * // The path is contained in the children list of group: - * console.log(group.children[0] == path); // true - */ - getParent: function() { - return this._parent; - }, + /** + * Private helper to return the owner, either the parent, or the project + * for top-level layers. See Layer#_getOwner() + */ + _getOwner: "#getParent", - setParent: function(item) { - return item.addChild(this); - }, + /** + * The children items contained within this item. Items that define a + * {@link #name} can also be accessed by name. + * + * Please note: The children array should not be modified directly + * using array functions. To remove single items from the children list, use + * {@link Item#remove()}, to remove all items from the children list, use + * {@link Item#removeChildren()}. To add items to the children list, use + * {@link Item#addChild(item)} or {@link Item#insertChild(index,item)}. + * + * @type Item[] + * @bean + * + * @example {@paperscript} + * // Accessing items in the children array: + * var path = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * + * // Create a group and move the path into it: + * var group = new Group(); + * group.addChild(path); + * + * // Access the path through the group's children array: + * group.children[0].fillColor = 'red'; + * + * @example {@paperscript} + * // Accessing children by name: + * var path = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * // Set the name of the path: + * path.name = 'example'; + * + * // Create a group and move the path into it: + * var group = new Group(); + * group.addChild(path); + * + * // The path can be accessed by name: + * group.children['example'].fillColor = 'orange'; + * + * @example {@paperscript} + * // Passing an array of items to item.children: + * var path = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * + * var group = new Group(); + * group.children = [path]; + * + * // The path is the first child of the group: + * group.firstChild.fillColor = 'green'; + */ + getChildren: function () { + return this._children; + }, - /** - * Private helper to return the owner, either the parent, or the project - * for top-level layers. See Layer#_getOwner() - */ - _getOwner: '#getParent', + setChildren: function (items) { + this.removeChildren(); + this.addChildren(items); + }, - /** - * The children items contained within this item. Items that define a - * {@link #name} can also be accessed by name. - * - * Please note: The children array should not be modified directly - * using array functions. To remove single items from the children list, use - * {@link Item#remove()}, to remove all items from the children list, use - * {@link Item#removeChildren()}. To add items to the children list, use - * {@link Item#addChild(item)} or {@link Item#insertChild(index,item)}. - * - * @type Item[] - * @bean - * - * @example {@paperscript} - * // Accessing items in the children array: - * var path = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * - * // Create a group and move the path into it: - * var group = new Group(); - * group.addChild(path); - * - * // Access the path through the group's children array: - * group.children[0].fillColor = 'red'; - * - * @example {@paperscript} - * // Accessing children by name: - * var path = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * // Set the name of the path: - * path.name = 'example'; - * - * // Create a group and move the path into it: - * var group = new Group(); - * group.addChild(path); - * - * // The path can be accessed by name: - * group.children['example'].fillColor = 'orange'; - * - * @example {@paperscript} - * // Passing an array of items to item.children: - * var path = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * - * var group = new Group(); - * group.children = [path]; - * - * // The path is the first child of the group: - * group.firstChild.fillColor = 'green'; - */ - getChildren: function() { - return this._children; - }, + /** + * The first item contained within this item. This is a shortcut for + * accessing `item.children[0]`. + * + * @type Item + * @bean + */ + getFirstChild: function () { + return (this._children && this._children[0]) || null; + }, - setChildren: function(items) { - this.removeChildren(); - this.addChildren(items); - }, + /** + * The last item contained within this item.This is a shortcut for + * accessing `item.children[item.children.length - 1]`. + * + * @type Item + * @bean + */ + getLastChild: function () { + return ( + (this._children && this._children[this._children.length - 1]) || + null + ); + }, - /** - * The first item contained within this item. This is a shortcut for - * accessing `item.children[0]`. - * - * @type Item - * @bean - */ - getFirstChild: function() { - return this._children && this._children[0] || null; - }, + /** + * The next item on the same level as this item. + * + * @type Item + * @bean + */ + getNextSibling: function () { + var owner = this._getOwner(); + return (owner && owner._children[this._index + 1]) || null; + }, - /** - * The last item contained within this item.This is a shortcut for - * accessing `item.children[item.children.length - 1]`. - * - * @type Item - * @bean - */ - getLastChild: function() { - return this._children && this._children[this._children.length - 1] - || null; - }, + /** + * The previous item on the same level as this item. + * + * @type Item + * @bean + */ + getPreviousSibling: function () { + var owner = this._getOwner(); + return (owner && owner._children[this._index - 1]) || null; + }, - /** - * The next item on the same level as this item. - * - * @type Item - * @bean - */ - getNextSibling: function() { - var owner = this._getOwner(); - return owner && owner._children[this._index + 1] || null; - }, + /** + * The index of this item within the list of its parent's children. + * + * @type Number + * @bean + */ + getIndex: function () { + return this._index; + }, - /** - * The previous item on the same level as this item. - * - * @type Item - * @bean - */ - getPreviousSibling: function() { - var owner = this._getOwner(); - return owner && owner._children[this._index - 1] || null; - }, + equals: function (item) { + // NOTE: We do not compare name and selected state. + // TODO: Consider not comparing locked and visible also? + return ( + item === this || + (item && + this._class === item._class && + this._style.equals(item._style) && + this._matrix.equals(item._matrix) && + this._locked === item._locked && + this._visible === item._visible && + this._blendMode === item._blendMode && + this._opacity === item._opacity && + this._clipMask === item._clipMask && + this._guide === item._guide && + this._equals(item)) || + false + ); + }, - /** - * The index of this item within the list of its parent's children. - * - * @type Number - * @bean - */ - getIndex: function() { - return this._index; - }, + /** + * A private helper for #equals(), to be overridden in sub-classes. When it + * is called, item is always defined, of the same class as `this` and has + * equal general state attributes such as matrix, style, opacity, etc. + */ + _equals: function (item) { + return Base.equals(this._children, item._children); + }, - equals: function(item) { - // NOTE: We do not compare name and selected state. - // TODO: Consider not comparing locked and visible also? - return item === this || item && this._class === item._class - && this._style.equals(item._style) - && this._matrix.equals(item._matrix) - && this._locked === item._locked - && this._visible === item._visible - && this._blendMode === item._blendMode - && this._opacity === item._opacity - && this._clipMask === item._clipMask - && this._guide === item._guide - && this._equals(item) - || false; - }, + /** + * Clones the item within the same project and places the copy above the + * item. + * + * @option [insert=true] specifies whether the copy should be + * inserted into the scene graph. When set to `true`, it is inserted + * above the original + * @option [deep=true] specifies whether the item's children should also be + * cloned + * + * @param {Object} [options={ insert: true, deep: true }] + * + * @return {Item} the newly cloned item + * @chainable + * + * @example {@paperscript} + * // Cloning items: + * var circle = new Path.Circle({ + * center: [50, 50], + * radius: 10, + * fillColor: 'red' + * }); + * + * // Make 20 copies of the circle: + * for (var i = 0; i < 20; i++) { + * var copy = circle.clone(); + * + * // Distribute the copies horizontally, so we can see them: + * copy.position.x += i * copy.bounds.width; + * } + */ + clone: function (options) { + var copy = new this.constructor(Item.NO_INSERT), + children = this._children, + // Both `insert` and `deep` are true by default: + insert = Base.pick( + options ? options.insert : undefined, + // Also support boolean parameter for insert, default: true. + options === undefined || options === true + ), + deep = Base.pick(options ? options.deep : undefined, true); + // On items with children, for performance reasons due to the way that + // styles are currently "flattened" into existing children, we need to + // clone attributes first, then content. + // For all other items, it's the other way around, since applying + // attributes might have an impact on their content. + if (children) copy.copyAttributes(this); + // Only copy content if we don't have children or if we're ask to create + // a deep clone, which is the default. + if (!children || deep) copy.copyContent(this); + if (!children) copy.copyAttributes(this); + if (insert) copy.insertAbove(this); + // Make sure we're not overriding the original name in the same parent + var name = this._name, + parent = this._parent; + if (name && parent) { + var children = parent._children, + orig = name, + i = 1; + while (children[name]) name = orig + " " + i++; + if (name !== orig) copy.setName(name); + } + return copy; + }, - /** - * A private helper for #equals(), to be overridden in sub-classes. When it - * is called, item is always defined, of the same class as `this` and has - * equal general state attributes such as matrix, style, opacity, etc. - */ - _equals: function(item) { - return Base.equals(this._children, item._children); - }, + /** + * Copies the content of the specified item over to this item. + * + * @param {Item} source the item to copy the content from + */ + copyContent: function (source) { + var children = source._children; + // Clone all children and add them to the copy. tell #addChild we're + // cloning, as needed by CompoundPath#insertChild(). + for (var i = 0, l = children && children.length; i < l; i++) { + this.addChild(children[i].clone(false), true); + } + }, - /** - * Clones the item within the same project and places the copy above the - * item. - * - * @option [insert=true] specifies whether the copy should be - * inserted into the scene graph. When set to `true`, it is inserted - * above the original - * @option [deep=true] specifies whether the item's children should also be - * cloned - * - * @param {Object} [options={ insert: true, deep: true }] - * - * @return {Item} the newly cloned item - * @chainable - * - * @example {@paperscript} - * // Cloning items: - * var circle = new Path.Circle({ - * center: [50, 50], - * radius: 10, - * fillColor: 'red' - * }); - * - * // Make 20 copies of the circle: - * for (var i = 0; i < 20; i++) { - * var copy = circle.clone(); - * - * // Distribute the copies horizontally, so we can see them: - * copy.position.x += i * copy.bounds.width; - * } - */ - clone: function(options) { - var copy = new this.constructor(Item.NO_INSERT), - children = this._children, - // Both `insert` and `deep` are true by default: - insert = Base.pick(options ? options.insert : undefined, - // Also support boolean parameter for insert, default: true. - options === undefined || options === true), - deep = Base.pick(options ? options.deep : undefined, true); - // On items with children, for performance reasons due to the way that - // styles are currently "flattened" into existing children, we need to - // clone attributes first, then content. - // For all other items, it's the other way around, since applying - // attributes might have an impact on their content. - if (children) - copy.copyAttributes(this); - // Only copy content if we don't have children or if we're ask to create - // a deep clone, which is the default. - if (!children || deep) - copy.copyContent(this); - if (!children) - copy.copyAttributes(this); - if (insert) - copy.insertAbove(this); - // Make sure we're not overriding the original name in the same parent - var name = this._name, - parent = this._parent; - if (name && parent) { - var children = parent._children, - orig = name, - i = 1; - while (children[name]) - name = orig + ' ' + (i++); - if (name !== orig) - copy.setName(name); - } - return copy; + /** + * Copies all attributes of the specified item over to this item. This + * includes its style, visibility, matrix, pivot, blend-mode, opacity, + * selection state, data, name, etc. + * + * @param {Item} source the item to copy the attributes from + * @param {Boolean} excludeMatrix whether to exclude the transformation + * matrix when copying all attributes + */ + copyAttributes: function (source, excludeMatrix) { + // Copy over style + this.setStyle(source._style); + // Only copy over these fields if they are actually defined in 'source', + // meaning the default value has been overwritten (default is on + // prototype). + var keys = [ + "_locked", + "_visible", + "_blendMode", + "_opacity", + "_clipMask", + "_guide", + ]; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + if (source.hasOwnProperty(key)) this[key] = source[key]; + } + // Use Matrix#initialize to easily copy over values. + if (!excludeMatrix) this._matrix.set(source._matrix, true); + // We can't just set _applyMatrix as many item types won't allow it, + // e.g. creating a Shape in Path#toShape(). + // Using the setter instead takes care of it. + // NOTE: This will also bake in the matrix that we just initialized, + // in case #applyMatrix is true. + this.setApplyMatrix(source._applyMatrix); + this.setPivot(source._pivot); + // Copy over the selection state, use setSelection so the item + // is also added to Project#_selectionItems if it is selected. + this.setSelection(source._selection); + // Copy over data and name as well. + var data = source._data, + name = source._name; + this._data = data ? Base.clone(data) : null; + if (name) this.setName(name); + }, + + /** + * @name Item#rasterize + * @function + * @param {Number} [resolution=view.resolution] + * @param {Boolean} [insert=true] + * @deprecated use {@link #rasterize(options)} instead. + */ + /** + * Rasterizes the item into a newly created Raster object. The item itself + * is not removed after rasterization. + * + * @option [resolution=view.resolution] {Number} the desired resolution to + * be used when rasterizing, in pixels per inch (DPI). If not specified, + * the value of `view.resolution` is used by default. + * @option [raster=null] {Raster} specifies a raster to be reused when + * rasterizing. If the raster has the desired size already, then the + * underlying canvas is reused and no new memory needs to be allocated. + * If no raster is provided, a new raster item is created and returned + * instead. + * @option [insert=true] {Boolean} specifies whether the raster should be + * inserted into the scene graph. When set to `true`, it is inserted + * above the rasterized item. + * + * @name Item#rasterize + * @function + * @param {Object} [options={}] the rasterization options + * @return {Raster} the reused raster or the newly created raster item + * + * @example {@paperscript} + * // Rasterizing an item: + * var circle = new Path.Circle({ + * center: [50, 50], + * radius: 5, + * fillColor: 'red' + * }); + * + * // Create a rasterized version of the path: + * var raster = circle.rasterize(); + * + * // Move it 100pt to the right: + * raster.position.x += 100; + * + * // Scale the path and the raster by 300%, so we can compare them: + * circle.scale(5); + * raster.scale(5); + */ + rasterize: function (arg0, arg1) { + var resolution, insert, raster; + if (Base.isPlainObject(arg0)) { + resolution = arg0.resolution; + insert = arg0.insert; + raster = arg0.raster; + } else { + resolution = arg0; + insert = arg1; + } + if (!raster) { + raster = new Raster(Item.NO_INSERT); + } + var bounds = this.getStrokeBounds(), + scale = (resolution || this.getView().getResolution()) / 72, + // Floor top-left corner and ceil bottom-right corner, to never + // blur or cut pixels. + topLeft = bounds.getTopLeft().floor(), + bottomRight = bounds.getBottomRight().ceil(), + boundsSize = new Size(bottomRight.subtract(topLeft)), + rasterSize = boundsSize.multiply(scale); + // Pass `true` for clear, so reused rasters don't draw over old pixels. + raster.setSize(rasterSize, true); + + if (!rasterSize.isZero()) { + var ctx = raster.getContext(true), + matrix = new Matrix() + .scale(scale) + .translate(topLeft.negate()); + ctx.save(); + matrix.applyToContext(ctx); + // See Project#draw() for an explanation of new Base() + this.draw(ctx, new Base({ matrices: [matrix] })); + ctx.restore(); + } + raster._matrix.set( + new Matrix() + .translate(topLeft.add(boundsSize.divide(2))) + // Take resolution into account and scale back to original size. + .scale(1 / scale) + ); + if (insert === undefined || insert) { + raster.insertAbove(this); + } + return raster; + }, + + /** + * {@grouptitle Geometric Tests} + * + * Checks whether the item's geometry contains the given point. + * + * @example {@paperscript} // Click within and outside the star below + * // Create a star shaped path: + * var path = new Path.Star({ + * center: [50, 50], + * points: 12, + * radius1: 20, + * radius2: 40, + * fillColor: 'black' + * }); + * + * // Whenever the user presses the mouse: + * function onMouseDown(event) { + * // If the position of the mouse is within the path, + * // set its fill color to red, otherwise set it to + * // black: + * if (path.contains(event.point)) { + * path.fillColor = 'red'; + * } else { + * path.fillColor = 'black'; + * } + * } + * + * @param {Point} point the point to check for + * @return {Boolean} + */ + contains: function (/* point */) { + // See CompoundPath#_contains() for the reason for !! + var matrix = this._matrix; + return ( + matrix.isInvertible() && + !!this._contains( + matrix._inverseTransform(Point.read(arguments)) + ) + ); + }, + + _contains: function (point) { + var children = this._children; + if (children) { + for (var i = children.length - 1; i >= 0; i--) { + if (children[i].contains(point)) return true; + } + return false; + } + // We only implement it here for items with rectangular content, + // for anything else we need to override #contains() + return point.isInside(this.getInternalBounds()); + }, + + // DOCS: + // TEST: + /** + * @param {Rectangle} rect the rectangle to check against + * @return {Boolean} + */ + isInside: function (/* rect */) { + return Rectangle.read(arguments).contains(this.getBounds()); + }, + + // Internal helper function, used at the moment for intersects check only. + // TODO: Move #getIntersections() to Item, make it handle all type of items + // through _asPathItem(), and support Group items as well, taking nested + // matrices into account properly! + _asPathItem: function () { + // Creates a temporary rectangular path item with this item's bounds. + return new Path.Rectangle({ + rectangle: this.getInternalBounds(), + matrix: this._matrix, + insert: false, + }); + }, + + // DOCS: + // TEST: + /** + * @param {Item} item the item to check against + * @return {Boolean} + */ + intersects: function (item, _matrix) { + if (!(item instanceof Item)) return false; + // Tell getIntersections() to return as soon as some intersections are + // found, because all we care for here is there are some or none: + return ( + this._asPathItem().getIntersections( + item._asPathItem(), + null, + _matrix, + true + ).length > 0 + ); + }, }, + new (function () { + // Injection scope for hit-test functions shared with project + function hitTest(/* point, options */) { + var args = arguments; + return this._hitTest(Point.read(args), HitResult.getOptions(args)); + } - /** - * Copies the content of the specified item over to this item. - * - * @param {Item} source the item to copy the content from - */ - copyContent: function(source) { - var children = source._children; - // Clone all children and add them to the copy. tell #addChild we're - // cloning, as needed by CompoundPath#insertChild(). - for (var i = 0, l = children && children.length; i < l; i++) { - this.addChild(children[i].clone(false), true); + function hitTestAll(/* point, options */) { + var args = arguments, + point = Point.read(args), + options = HitResult.getOptions(args), + all = []; + this._hitTest(point, new Base({ all: all }, options)); + return all; } - }, - /** - * Copies all attributes of the specified item over to this item. This - * includes its style, visibility, matrix, pivot, blend-mode, opacity, - * selection state, data, name, etc. - * - * @param {Item} source the item to copy the attributes from - * @param {Boolean} excludeMatrix whether to exclude the transformation - * matrix when copying all attributes - */ - copyAttributes: function(source, excludeMatrix) { - // Copy over style - this.setStyle(source._style); - // Only copy over these fields if they are actually defined in 'source', - // meaning the default value has been overwritten (default is on - // prototype). - var keys = ['_locked', '_visible', '_blendMode', '_opacity', - '_clipMask', '_guide']; - for (var i = 0, l = keys.length; i < l; i++) { - var key = keys[i]; - if (source.hasOwnProperty(key)) - this[key] = source[key]; + function hitTestChildren(point, options, viewMatrix, _exclude) { + // NOTE: _exclude is only used in Group#_hitTestChildren() + // to exclude #clipItem + var children = this._children; + if (children) { + // Loop backwards, so items that get drawn last are tested first. + for (var i = children.length - 1; i >= 0; i--) { + var child = children[i]; + var res = + child !== _exclude && + child._hitTest(point, options, viewMatrix); + // Only return the found result if we're not asked to collect + // all matches through hitTestAll() + if (res && !options.all) return res; + } + } + return null; } - // Use Matrix#initialize to easily copy over values. - if (!excludeMatrix) - this._matrix.set(source._matrix, true); - // We can't just set _applyMatrix as many item types won't allow it, - // e.g. creating a Shape in Path#toShape(). - // Using the setter instead takes care of it. - // NOTE: This will also bake in the matrix that we just initialized, - // in case #applyMatrix is true. - this.setApplyMatrix(source._applyMatrix); - this.setPivot(source._pivot); - // Copy over the selection state, use setSelection so the item - // is also added to Project#_selectionItems if it is selected. - this.setSelection(source._selection); - // Copy over data and name as well. - var data = source._data, - name = source._name; - this._data = data ? Base.clone(data) : null; - if (name) - this.setName(name); - }, - /** - * @name Item#rasterize - * @function - * @param {Number} [resolution=view.resolution] - * @param {Boolean} [insert=true] - * @deprecated use {@link #rasterize(options)} instead. - */ - /** - * Rasterizes the item into a newly created Raster object. The item itself - * is not removed after rasterization. - * - * @option [resolution=view.resolution] {Number} the desired resolution to - * be used when rasterizing, in pixels per inch (DPI). If not specified, - * the value of `view.resolution` is used by default. - * @option [raster=null] {Raster} specifies a raster to be reused when - * rasterizing. If the raster has the desired size already, then the - * underlying canvas is reused and no new memory needs to be allocated. - * If no raster is provided, a new raster item is created and returned - * instead. - * @option [insert=true] {Boolean} specifies whether the raster should be - * inserted into the scene graph. When set to `true`, it is inserted - * above the rasterized item. - * - * @name Item#rasterize - * @function - * @param {Object} [options={}] the rasterization options - * @return {Raster} the reused raster or the newly created raster item - * - * @example {@paperscript} - * // Rasterizing an item: - * var circle = new Path.Circle({ - * center: [50, 50], - * radius: 5, - * fillColor: 'red' - * }); - * - * // Create a rasterized version of the path: - * var raster = circle.rasterize(); - * - * // Move it 100pt to the right: - * raster.position.x += 100; - * - * // Scale the path and the raster by 300%, so we can compare them: - * circle.scale(5); - * raster.scale(5); - */ - rasterize: function(arg0, arg1) { - var resolution, - insert, - raster; - if (Base.isPlainObject(arg0)) { - resolution = arg0.resolution; - insert = arg0.insert; - raster = arg0.raster; - } else { - resolution = arg0; - insert = arg1; - } - if (!raster) { - raster = new Raster(Item.NO_INSERT); - } - var bounds = this.getStrokeBounds(), - scale = (resolution || this.getView().getResolution()) / 72, - // Floor top-left corner and ceil bottom-right corner, to never - // blur or cut pixels. - topLeft = bounds.getTopLeft().floor(), - bottomRight = bounds.getBottomRight().ceil(), - boundsSize = new Size(bottomRight.subtract(topLeft)), - rasterSize = boundsSize.multiply(scale); - // Pass `true` for clear, so reused rasters don't draw over old pixels. - raster.setSize(rasterSize, true); - - if (!rasterSize.isZero()) { - var ctx = raster.getContext(true), - matrix = new Matrix().scale(scale).translate(topLeft.negate()); - ctx.save(); - matrix.applyToContext(ctx); - // See Project#draw() for an explanation of new Base() - this.draw(ctx, new Base({ matrices: [matrix] })); - ctx.restore(); - } - raster._matrix.set( - new Matrix() - .translate(topLeft.add(boundsSize.divide(2))) - // Take resolution into account and scale back to original size. - .scale(1 / scale) - ); - if (insert === undefined || insert) { - raster.insertAbove(this); - } - return raster; - }, - - /** - * {@grouptitle Geometric Tests} - * - * Checks whether the item's geometry contains the given point. - * - * @example {@paperscript} // Click within and outside the star below - * // Create a star shaped path: - * var path = new Path.Star({ - * center: [50, 50], - * points: 12, - * radius1: 20, - * radius2: 40, - * fillColor: 'black' - * }); - * - * // Whenever the user presses the mouse: - * function onMouseDown(event) { - * // If the position of the mouse is within the path, - * // set its fill color to red, otherwise set it to - * // black: - * if (path.contains(event.point)) { - * path.fillColor = 'red'; - * } else { - * path.fillColor = 'black'; - * } - * } - * - * @param {Point} point the point to check for - * @return {Boolean} - */ - contains: function(/* point */) { - // See CompoundPath#_contains() for the reason for !! - var matrix = this._matrix; - return ( - matrix.isInvertible() && - !!this._contains(matrix._inverseTransform(Point.read(arguments))) - ); - }, - - _contains: function(point) { - var children = this._children; - if (children) { - for (var i = children.length - 1; i >= 0; i--) { - if (children[i].contains(point)) - return true; - } - return false; - } - // We only implement it here for items with rectangular content, - // for anything else we need to override #contains() - return point.isInside(this.getInternalBounds()); - }, - - // DOCS: - // TEST: - /** - * @param {Rectangle} rect the rectangle to check against - * @return {Boolean} - */ - isInside: function(/* rect */) { - return Rectangle.read(arguments).contains(this.getBounds()); - }, - - // Internal helper function, used at the moment for intersects check only. - // TODO: Move #getIntersections() to Item, make it handle all type of items - // through _asPathItem(), and support Group items as well, taking nested - // matrices into account properly! - _asPathItem: function() { - // Creates a temporary rectangular path item with this item's bounds. - return new Path.Rectangle({ - rectangle: this.getInternalBounds(), - matrix: this._matrix, - insert: false, + Project.inject({ + hitTest: hitTest, + hitTestAll: hitTestAll, + _hitTest: hitTestChildren, }); - }, - // DOCS: - // TEST: - /** - * @param {Item} item the item to check against - * @return {Boolean} - */ - intersects: function(item, _matrix) { - if (!(item instanceof Item)) - return false; - // Tell getIntersections() to return as soon as some intersections are - // found, because all we care for here is there are some or none: - return this._asPathItem().getIntersections(item._asPathItem(), null, - _matrix, true).length > 0; - } -}, -new function() { // Injection scope for hit-test functions shared with project - function hitTest(/* point, options */) { - var args = arguments; - return this._hitTest( - Point.read(args), - HitResult.getOptions(args)); - } + return { + // NOTE: Documentation is in the scope that follows. + hitTest: hitTest, + hitTestAll: hitTestAll, + _hitTestChildren: hitTestChildren, + }; + })(), + /** @lends Item# */ { + /** + * {@grouptitle Hit-testing, Fetching and Matching Items} + * + * Performs a hit-test on the item and its children (if it is a {@link + * Group} or {@link Layer}) at the location of the specified point, + * returning the first found hit. + * + * The options object allows you to control the specifics of the hit- + * test and may contain a combination of the following values: + * + * @name Item#hitTest + * @function + * + * @option [options.tolerance={@link PaperScope#settings}.hitTolerance] + * {Number} the tolerance of the hit-test + * @option options.class {Function} only hit-test against a specific item + * class, or any of its sub-classes, by providing the constructor + * function against which an `instanceof` check is performed: + * {@values Group, Layer, Path, CompoundPath, Shape, Raster, + * SymbolItem, PointText, ...} + * @option options.match {Function} a match function to be called for each + * found hit result: Return `true` to return the result, `false` to keep + * searching + * @option [options.fill=true] {Boolean} hit-test the fill of items + * @option [options.stroke=true] {Boolean} hit-test the stroke of path + * items, taking into account the setting of stroke color and width + * @option [options.segments=true] {Boolean} hit-test for {@link + * Segment#point} of {@link Path} items + * @option options.curves {Boolean} hit-test the curves of path items, + * without taking the stroke color or width into account + * @option options.handles {Boolean} hit-test for the handles ({@link + * Segment#handleIn} / {@link Segment#handleOut}) of path segments. + * @option options.ends {Boolean} only hit-test for the first or last + * segment points of open path items + * @option options.position {Boolean} hit-test the {@link Item#position} of + * of items, which depends on the setting of {@link Item#pivot} + * @option options.center {Boolean} hit-test the {@link Rectangle#center} of + * the bounding rectangle of items ({@link Item#bounds}) + * @option options.bounds {Boolean} hit-test the corners and side-centers of + * the bounding rectangle of items ({@link Item#bounds}) + * @option options.guides {Boolean} hit-test items that have {@link + * Item#guide} set to `true` + * @option options.selected {Boolean} only hit selected items + * + * @param {Point} point the point where the hit-test should be performed + * (in global coordinates system). + * @param {Object} [options={ fill: true, stroke: true, segments: true, + * tolerance: settings.hitTolerance }] + * @return {HitResult} a hit result object describing what exactly was hit + * or `null` if nothing was hit + */ - function hitTestAll(/* point, options */) { - var args = arguments, - point = Point.read(args), - options = HitResult.getOptions(args), - all = []; - this._hitTest(point, new Base({ all: all }, options)); - return all; - } + /** + * Performs a hit-test on the item and its children (if it is a {@link + * Group} or {@link Layer}) at the location of the specified point, + * returning all found hits. + * + * The options object allows you to control the specifics of the hit- + * test. See {@link #hitTest(point[, options])} for a list of all options. + * + * @name Item#hitTestAll + * @function + * @param {Point} point the point where the hit-test should be performed + * (in global coordinates system). + * @param {Object} [options={ fill: true, stroke: true, segments: true, + * tolerance: settings.hitTolerance }] + * @return {HitResult[]} hit result objects for all hits, describing what + * exactly was hit or `null` if nothing was hit + * @see #hitTest(point[, options]); + */ - function hitTestChildren(point, options, viewMatrix, _exclude) { - // NOTE: _exclude is only used in Group#_hitTestChildren() - // to exclude #clipItem - var children = this._children; - if (children) { - // Loop backwards, so items that get drawn last are tested first. - for (var i = children.length - 1; i >= 0; i--) { - var child = children[i]; - var res = child !== _exclude && child._hitTest(point, options, - viewMatrix); - // Only return the found result if we're not asked to collect - // all matches through hitTestAll() - if (res && !options.all) - return res; + _hitTest: function (point, options, parentViewMatrix) { + if ( + this._locked || + !this._visible || + (this._guide && !options.guides) || + this.isEmpty() + ) { + return null; } - } - return null; - } - - Project.inject({ - hitTest: hitTest, - hitTestAll: hitTestAll, - _hitTest: hitTestChildren - }); - - return { - // NOTE: Documentation is in the scope that follows. - hitTest: hitTest, - hitTestAll: hitTestAll, - _hitTestChildren: hitTestChildren, - }; -}, /** @lends Item# */{ - /** - * {@grouptitle Hit-testing, Fetching and Matching Items} - * - * Performs a hit-test on the item and its children (if it is a {@link - * Group} or {@link Layer}) at the location of the specified point, - * returning the first found hit. - * - * The options object allows you to control the specifics of the hit- - * test and may contain a combination of the following values: - * - * @name Item#hitTest - * @function - * - * @option [options.tolerance={@link PaperScope#settings}.hitTolerance] - * {Number} the tolerance of the hit-test - * @option options.class {Function} only hit-test against a specific item - * class, or any of its sub-classes, by providing the constructor - * function against which an `instanceof` check is performed: - * {@values Group, Layer, Path, CompoundPath, Shape, Raster, - * SymbolItem, PointText, ...} - * @option options.match {Function} a match function to be called for each - * found hit result: Return `true` to return the result, `false` to keep - * searching - * @option [options.fill=true] {Boolean} hit-test the fill of items - * @option [options.stroke=true] {Boolean} hit-test the stroke of path - * items, taking into account the setting of stroke color and width - * @option [options.segments=true] {Boolean} hit-test for {@link - * Segment#point} of {@link Path} items - * @option options.curves {Boolean} hit-test the curves of path items, - * without taking the stroke color or width into account - * @option options.handles {Boolean} hit-test for the handles ({@link - * Segment#handleIn} / {@link Segment#handleOut}) of path segments. - * @option options.ends {Boolean} only hit-test for the first or last - * segment points of open path items - * @option options.position {Boolean} hit-test the {@link Item#position} of - * of items, which depends on the setting of {@link Item#pivot} - * @option options.center {Boolean} hit-test the {@link Rectangle#center} of - * the bounding rectangle of items ({@link Item#bounds}) - * @option options.bounds {Boolean} hit-test the corners and side-centers of - * the bounding rectangle of items ({@link Item#bounds}) - * @option options.guides {Boolean} hit-test items that have {@link - * Item#guide} set to `true` - * @option options.selected {Boolean} only hit selected items - * - * @param {Point} point the point where the hit-test should be performed - * (in global coordinates system). - * @param {Object} [options={ fill: true, stroke: true, segments: true, - * tolerance: settings.hitTolerance }] - * @return {HitResult} a hit result object describing what exactly was hit - * or `null` if nothing was hit - */ - - /** - * Performs a hit-test on the item and its children (if it is a {@link - * Group} or {@link Layer}) at the location of the specified point, - * returning all found hits. - * - * The options object allows you to control the specifics of the hit- - * test. See {@link #hitTest(point[, options])} for a list of all options. - * - * @name Item#hitTestAll - * @function - * @param {Point} point the point where the hit-test should be performed - * (in global coordinates system). - * @param {Object} [options={ fill: true, stroke: true, segments: true, - * tolerance: settings.hitTolerance }] - * @return {HitResult[]} hit result objects for all hits, describing what - * exactly was hit or `null` if nothing was hit - * @see #hitTest(point[, options]); - */ - _hitTest: function(point, options, parentViewMatrix) { - if (this._locked || !this._visible || this._guide && !options.guides - || this.isEmpty()) { - return null; - } - - // Check if the point is withing roughBounds + tolerance, but only if - // this item does not have children, since we'd have to travel up the - // chain already to determine the rough bounds. - var matrix = this._matrix, - // Keep the accumulated matrices up to this item in options, so we - // can keep calculating the correct _tolerancePadding values. - viewMatrix = parentViewMatrix + // Check if the point is withing roughBounds + tolerance, but only if + // this item does not have children, since we'd have to travel up the + // chain already to determine the rough bounds. + var matrix = this._matrix, + // Keep the accumulated matrices up to this item in options, so we + // can keep calculating the correct _tolerancePadding values. + viewMatrix = parentViewMatrix ? parentViewMatrix.appended(matrix) - // If this is the first one in the recursion, factor in the - // zoom of the view and the globalMatrix of the item. - : this.getGlobalMatrix().prepend(this.getView()._matrix), - // Calculate the transformed padding as 2D size that describes the - // transformed tolerance circle / ellipse. Make sure it's never 0 - // since we're using it for division (see checkBounds()). - tolerance = Math.max(options.tolerance, /*#=*/Numerical.EPSILON), - // Hit-tests are performed in the item's local coordinate space. - // To calculate the correct 2D padding for tolerance, we therefore - // need to apply the inverted item matrix. - tolerancePadding = options._tolerancePadding = new Size( - Path._getStrokePadding(tolerance, - matrix._shiftless().invert())); - // Transform point to local coordinates. - point = matrix._inverseTransform(point); - // If the matrix is non-reversible, point will now be `null`: - if (!point || !this._children && - !this.getBounds({ internal: true, stroke: true, handle: true }) - .expand(tolerancePadding.multiply(2))._containsPoint(point)) { - return null; - } - - // See if we should check self (own content), by filtering for type, - // guides and selected items if that's required. - var checkSelf = !(options.guides && !this._guide - || options.selected && !this.isSelected() - // Support legacy Item#type property to match hyphenated - // class-names. - || options.type && options.type !== Base.hyphenate(this._class) - || options.class && !(this instanceof options.class)), - match = options.match, - that = this, - bounds, - res; - - function filter(hit) { - if (hit && match && !match(hit)) - hit = null; - // If we're collecting all matches, add it to options.all - if (hit && options.all) - options.all.push(hit); - return hit; - } + : // If this is the first one in the recursion, factor in the + // zoom of the view and the globalMatrix of the item. + this.getGlobalMatrix().prepend(this.getView()._matrix), + // Calculate the transformed padding as 2D size that describes the + // transformed tolerance circle / ellipse. Make sure it's never 0 + // since we're using it for division (see checkBounds()). + tolerance = Math.max( + options.tolerance, + /*#=*/ Numerical.EPSILON + ), + // Hit-tests are performed in the item's local coordinate space. + // To calculate the correct 2D padding for tolerance, we therefore + // need to apply the inverted item matrix. + tolerancePadding = (options._tolerancePadding = new Size( + Path._getStrokePadding( + tolerance, + matrix._shiftless().invert() + ) + )); + // Transform point to local coordinates. + point = matrix._inverseTransform(point); + // If the matrix is non-reversible, point will now be `null`: + if ( + !point || + (!this._children && + !this.getBounds({ + internal: true, + stroke: true, + handle: true, + }) + .expand(tolerancePadding.multiply(2)) + ._containsPoint(point)) + ) { + return null; + } - function checkPoint(type, part) { - var pt = part ? bounds['get' + part]() : that.getPosition(); - // Since there are transformations, we cannot simply use a numerical - // tolerance value. Instead, we divide by a padding size, see above. - if (point.subtract(pt).divide(tolerancePadding).length <= 1) { - return new HitResult(type, that, { - name: part ? Base.hyphenate(part) : type, - point: pt - }); + // See if we should check self (own content), by filtering for type, + // guides and selected items if that's required. + var checkSelf = !( + (options.guides && !this._guide) || + (options.selected && !this.isSelected()) || + // Support legacy Item#type property to match hyphenated + // class-names. + (options.type && + options.type !== Base.hyphenate(this._class)) || + (options.class && !(this instanceof options.class)) + ), + match = options.match, + that = this, + bounds, + res; + + function filter(hit) { + if (hit && match && !match(hit)) hit = null; + // If we're collecting all matches, add it to options.all + if (hit && options.all) options.all.push(hit); + return hit; } - } - var checkPosition = options.position, - checkCenter = options.center, - checkBounds = options.bounds; - // Ignore top level layers by checking for _parent: - if (checkSelf && this._parent - && (checkPosition || checkCenter || checkBounds)) { - if (checkCenter || checkBounds) { - // Get the internal, untransformed bounds, as we check against - // transformed points. - bounds = this.getInternalBounds(); + function checkPoint(type, part) { + var pt = part ? bounds["get" + part]() : that.getPosition(); + // Since there are transformations, we cannot simply use a numerical + // tolerance value. Instead, we divide by a padding size, see above. + if (point.subtract(pt).divide(tolerancePadding).length <= 1) { + return new HitResult(type, that, { + name: part ? Base.hyphenate(part) : type, + point: pt, + }); + } } - res = checkPosition && checkPoint('position') || - checkCenter && checkPoint('center', 'Center'); - if (!res && checkBounds) { - // TODO: Move these into a static property on Rectangle? - var points = [ - 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', - 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter' - ]; - for (var i = 0; i < 8 && !res; i++) { - res = checkPoint('bounds', points[i]); + + var checkPosition = options.position, + checkCenter = options.center, + checkBounds = options.bounds; + // Ignore top level layers by checking for _parent: + if ( + checkSelf && + this._parent && + (checkPosition || checkCenter || checkBounds) + ) { + if (checkCenter || checkBounds) { + // Get the internal, untransformed bounds, as we check against + // transformed points. + bounds = this.getInternalBounds(); } + res = + (checkPosition && checkPoint("position")) || + (checkCenter && checkPoint("center", "Center")); + if (!res && checkBounds) { + // TODO: Move these into a static property on Rectangle? + var points = [ + "TopLeft", + "TopRight", + "BottomLeft", + "BottomRight", + "LeftCenter", + "TopCenter", + "RightCenter", + "BottomCenter", + ]; + for (var i = 0; i < 8 && !res; i++) { + res = checkPoint("bounds", points[i]); + } + } + res = filter(res); } - res = filter(res); - } - if (!res) { - res = this._hitTestChildren(point, options, viewMatrix) - // NOTE: We don't call match on _hitTestChildren() because - // it is already called internally. - || checkSelf - && filter(this._hitTestSelf(point, options, viewMatrix, - // If the item has a non-scaling stroke, we need to - // apply the inverted viewMatrix to stroke dimensions. - this.getStrokeScaling() ? null - : viewMatrix._shiftless().invert())) - || null; - } - // Transform the point back to the outer coordinate system. - if (res && res.point) { - res.point = matrix.transform(res.point); - } - return res; - }, + if (!res) { + res = + this._hitTestChildren(point, options, viewMatrix) || + // NOTE: We don't call match on _hitTestChildren() because + // it is already called internally. + (checkSelf && + filter( + this._hitTestSelf( + point, + options, + viewMatrix, + // If the item has a non-scaling stroke, we need to + // apply the inverted viewMatrix to stroke dimensions. + this.getStrokeScaling() + ? null + : viewMatrix._shiftless().invert() + ) + )) || + null; + } + // Transform the point back to the outer coordinate system. + if (res && res.point) { + res.point = matrix.transform(res.point); + } + return res; + }, - _hitTestSelf: function(point, options) { - // The default implementation honly handles 'fill' through #_contains() - if (options.fill && this.hasFill() && this._contains(point)) - return new HitResult('fill', this); - }, + _hitTestSelf: function (point, options) { + // The default implementation honly handles 'fill' through #_contains() + if (options.fill && this.hasFill() && this._contains(point)) + return new HitResult("fill", this); + }, - /** - * Checks whether the item matches the criteria described by the given - * object, by iterating over all of its properties and matching against - * their values through {@link #matches(name, compare)}. - * - * See {@link Project#getItems(options)} for a selection of illustrated - * examples. - * - * @name Item#matches - * @function - * - * @param {Object|Function} options the criteria to match against - * @return {Boolean} {@true if the item matches all the criteria} - * @see #getItems(options) - */ - /** - * Checks whether the item matches the given criteria. Extended matching is - * possible by providing a compare function or a regular expression. - * Matching points, colors only work as a comparison of the full object, not - * partial matching (e.g. only providing the x-coordinate to match all - * points with that x-value). Partial matching does work for - * {@link Item#data}. - * - * See {@link Project#getItems(options)} for a selection of illustrated - * examples. - * - * @name Item#matches - * @function - * - * @param {String} name the name of the state to match against - * @param {Object} compare the value, function or regular expression to - * compare against - * @return {Boolean} {@true if the item matches the state} - * @see #getItems(options) - */ - matches: function(name, compare) { - // matchObject() is used to match against objects in a nested manner. - // This is useful for matching against Item#data. - function matchObject(obj1, obj2) { - for (var i in obj1) { - if (obj1.hasOwnProperty(i)) { - var val1 = obj1[i], - val2 = obj2[i]; - if (Base.isPlainObject(val1) && Base.isPlainObject(val2)) { - if (!matchObject(val1, val2)) + /** + * Checks whether the item matches the criteria described by the given + * object, by iterating over all of its properties and matching against + * their values through {@link #matches(name, compare)}. + * + * See {@link Project#getItems(options)} for a selection of illustrated + * examples. + * + * @name Item#matches + * @function + * + * @param {Object|Function} options the criteria to match against + * @return {Boolean} {@true if the item matches all the criteria} + * @see #getItems(options) + */ + /** + * Checks whether the item matches the given criteria. Extended matching is + * possible by providing a compare function or a regular expression. + * Matching points, colors only work as a comparison of the full object, not + * partial matching (e.g. only providing the x-coordinate to match all + * points with that x-value). Partial matching does work for + * {@link Item#data}. + * + * See {@link Project#getItems(options)} for a selection of illustrated + * examples. + * + * @name Item#matches + * @function + * + * @param {String} name the name of the state to match against + * @param {Object} compare the value, function or regular expression to + * compare against + * @return {Boolean} {@true if the item matches the state} + * @see #getItems(options) + */ + matches: function (name, compare) { + // matchObject() is used to match against objects in a nested manner. + // This is useful for matching against Item#data. + function matchObject(obj1, obj2) { + for (var i in obj1) { + if (obj1.hasOwnProperty(i)) { + var val1 = obj1[i], + val2 = obj2[i]; + if ( + Base.isPlainObject(val1) && + Base.isPlainObject(val2) + ) { + if (!matchObject(val1, val2)) return false; + } else if (!Base.equals(val1, val2)) { return false; - } else if (!Base.equals(val1, val2)) { - return false; + } } } + return true; } - return true; - } - var type = typeof name; - if (type === 'object') { - // `name` is the match object, not a string - for (var key in name) { - if (name.hasOwnProperty(key) && !this.matches(key, name[key])) - return false; - } - return true; - } else if (type === 'function') { - return name(this); - } else if (name === 'match') { - return compare(this); - } else { - var value = /^(empty|editable)$/.test(name) - // Handle boolean test functions separately, by calling them - // to get the value. - ? this['is' + Base.capitalize(name)]() - // Support legacy Item#type property to match hyphenated + var type = typeof name; + if (type === "object") { + // `name` is the match object, not a string + for (var key in name) { + if ( + name.hasOwnProperty(key) && + !this.matches(key, name[key]) + ) + return false; + } + return true; + } else if (type === "function") { + return name(this); + } else if (name === "match") { + return compare(this); + } else { + var value = /^(empty|editable)$/.test(name) + ? // Handle boolean test functions separately, by calling them + // to get the value. + this["is" + Base.capitalize(name)]() + : // Support legacy Item#type property to match hyphenated // class-names. // TODO: Remove after December 2016. - : name === 'type' - ? Base.hyphenate(this._class) - : this[name]; - if (name === 'class') { - if (typeof compare === 'function') - return this instanceof compare; - // Compare further with the _class property value instead. - value = this._class; - } - if (typeof compare === 'function') { - return !!compare(value); - } else if (compare) { - if (compare.test) { // RegExp-ish - return compare.test(value); - } else if (Base.isPlainObject(compare)) { - return matchObject(compare, value); + name === "type" + ? Base.hyphenate(this._class) + : this[name]; + if (name === "class") { + if (typeof compare === "function") + return this instanceof compare; + // Compare further with the _class property value instead. + value = this._class; + } + if (typeof compare === "function") { + return !!compare(value); + } else if (compare) { + if (compare.test) { + // RegExp-ish + return compare.test(value); + } else if (Base.isPlainObject(compare)) { + return matchObject(compare, value); + } } + return Base.equals(value, compare); } - return Base.equals(value, compare); - } - }, + }, - /** - * Fetch the descendants (children or children of children) of this item - * that match the properties in the specified object. Extended matching is - * possible by providing a compare function or regular expression. Matching - * points, colors only work as a comparison of the full object, not partial - * matching (e.g. only providing the x- coordinate to match all points with - * that x-value). Partial matching does work for {@link Item#data}. - * - * Matching items against a rectangular area is also possible, by setting - * either `options.inside` or `options.overlapping` to a rectangle - * describing the area in which the items either have to be fully or partly - * contained. - * - * See {@link Project#getItems(options)} for a selection of illustrated - * examples. - * - * @option [options.recursive=true] {Boolean} whether to loop recursively - * through all children, or stop at the current level - * @option options.match {Function} a match function to be called for each - * item, allowing the definition of more flexible item checks that are - * not bound to properties. If no other match properties are defined, - * this function can also be passed instead of the `options` object - * @option options.class {Function} the constructor function of the item - * type to match against - * @option options.inside {Rectangle} the rectangle in which the items need - * to be fully contained - * @option options.overlapping {Rectangle} the rectangle with which the - * items need to at least partly overlap - * - * @param {Object|Function} options the criteria to match against - * @return {Item[]} the list of matching descendant items - * @see #matches(options) - */ - getItems: function(options) { - return Item._getItems(this, options, this._matrix); - }, + /** + * Fetch the descendants (children or children of children) of this item + * that match the properties in the specified object. Extended matching is + * possible by providing a compare function or regular expression. Matching + * points, colors only work as a comparison of the full object, not partial + * matching (e.g. only providing the x- coordinate to match all points with + * that x-value). Partial matching does work for {@link Item#data}. + * + * Matching items against a rectangular area is also possible, by setting + * either `options.inside` or `options.overlapping` to a rectangle + * describing the area in which the items either have to be fully or partly + * contained. + * + * See {@link Project#getItems(options)} for a selection of illustrated + * examples. + * + * @option [options.recursive=true] {Boolean} whether to loop recursively + * through all children, or stop at the current level + * @option options.match {Function} a match function to be called for each + * item, allowing the definition of more flexible item checks that are + * not bound to properties. If no other match properties are defined, + * this function can also be passed instead of the `options` object + * @option options.class {Function} the constructor function of the item + * type to match against + * @option options.inside {Rectangle} the rectangle in which the items need + * to be fully contained + * @option options.overlapping {Rectangle} the rectangle with which the + * items need to at least partly overlap + * + * @param {Object|Function} options the criteria to match against + * @return {Item[]} the list of matching descendant items + * @see #matches(options) + */ + getItems: function (options) { + return Item._getItems(this, options, this._matrix); + }, - /** - * Fetch the first descendant (child or child of child) of this item - * that matches the properties in the specified object. - * Extended matching is possible by providing a compare function or - * regular expression. Matching points, colors only work as a comparison - * of the full object, not partial matching (e.g. only providing the x- - * coordinate to match all points with that x-value). Partial matching - * does work for {@link Item#data}. - * See {@link Project#getItems(match)} for a selection of illustrated - * examples. - * - * @param {Object|Function} options the criteria to match against - * @return {Item} the first descendant item matching the given criteria - * @see #getItems(options) - */ - getItem: function(options) { - return Item._getItems(this, options, this._matrix, null, true)[0] - || null; - }, + /** + * Fetch the first descendant (child or child of child) of this item + * that matches the properties in the specified object. + * Extended matching is possible by providing a compare function or + * regular expression. Matching points, colors only work as a comparison + * of the full object, not partial matching (e.g. only providing the x- + * coordinate to match all points with that x-value). Partial matching + * does work for {@link Item#data}. + * See {@link Project#getItems(match)} for a selection of illustrated + * examples. + * + * @param {Object|Function} options the criteria to match against + * @return {Item} the first descendant item matching the given criteria + * @see #getItems(options) + */ + getItem: function (options) { + return ( + Item._getItems(this, options, this._matrix, null, true)[0] || + null + ); + }, - statics: /** @lends Item */{ - // NOTE: We pass children instead of item as first argument so the - // method can be used for Project#layers as well in Project. - _getItems: function _getItems(item, options, matrix, param, firstOnly) { - if (!param) { - // Set up a couple of "side-car" values for the recursive calls - // of _getItems below, mainly related to the handling of - // inside / overlapping: - var obj = typeof options === 'object' && options, - overlapping = obj && obj.overlapping, - inside = obj && obj.inside, - // If overlapping is set, we also perform the inside check: - bounds = overlapping || inside, - rect = bounds && Rectangle.read([bounds]); - param = { - items: [], // The list to contain the results. - recursive: obj && obj.recursive !== false, - inside: !!inside, - overlapping: !!overlapping, - rect: rect, - path: overlapping && new Path.Rectangle({ - rectangle: rect, - insert: false - }) - }; - if (obj) { - // Create a copy of the options object that doesn't contain - // these special properties: - options = Base.filter({}, options, { - recursive: true, inside: true, overlapping: true - }); - } - } - var children = item._children, - items = param.items, - rect = param.rect; - matrix = rect && (matrix || new Matrix()); - for (var i = 0, l = children && children.length; i < l; i++) { - var child = children[i], - childMatrix = matrix && matrix.appended(child._matrix), - add = true; - if (rect) { - var bounds = child.getBounds(childMatrix); - // Regardless of the setting of inside / overlapping, if the - // bounds don't even intersect, we can skip this child. - if (!rect.intersects(bounds)) - continue; - if (!(rect.contains(bounds) - // First check the bounds, if the rect is fully - // contained, we are always overlapping, and don't - // need to perform further checks, otherwise perform - // a proper #intersects() check: - || param.overlapping && (bounds.contains(rect) - || param.path.intersects(child, childMatrix)))) - add = false; - } - if (add && child.matches(options)) { - items.push(child); - if (firstOnly) - break; + statics: /** @lends Item */ { + // NOTE: We pass children instead of item as first argument so the + // method can be used for Project#layers as well in Project. + _getItems: function _getItems( + item, + options, + matrix, + param, + firstOnly + ) { + if (!param) { + // Set up a couple of "side-car" values for the recursive calls + // of _getItems below, mainly related to the handling of + // inside / overlapping: + var obj = typeof options === "object" && options, + overlapping = obj && obj.overlapping, + inside = obj && obj.inside, + // If overlapping is set, we also perform the inside check: + bounds = overlapping || inside, + rect = bounds && Rectangle.read([bounds]); + param = { + items: [], // The list to contain the results. + recursive: obj && obj.recursive !== false, + inside: !!inside, + overlapping: !!overlapping, + rect: rect, + path: + overlapping && + new Path.Rectangle({ + rectangle: rect, + insert: false, + }), + }; + if (obj) { + // Create a copy of the options object that doesn't contain + // these special properties: + options = Base.filter({}, options, { + recursive: true, + inside: true, + overlapping: true, + }); + } } - if (param.recursive !== false) { - _getItems(child, options, childMatrix, param, firstOnly); + var children = item._children, + items = param.items, + rect = param.rect; + matrix = rect && (matrix || new Matrix()); + for (var i = 0, l = children && children.length; i < l; i++) { + var child = children[i], + childMatrix = matrix && matrix.appended(child._matrix), + add = true; + if (rect) { + var bounds = child.getBounds(childMatrix); + // Regardless of the setting of inside / overlapping, if the + // bounds don't even intersect, we can skip this child. + if (!rect.intersects(bounds)) continue; + if ( + !( + rect.contains(bounds) || + // First check the bounds, if the rect is fully + // contained, we are always overlapping, and don't + // need to perform further checks, otherwise perform + // a proper #intersects() check: + (param.overlapping && + (bounds.contains(rect) || + param.path.intersects( + child, + childMatrix + ))) + ) + ) + add = false; + } + if (add && child.matches(options)) { + items.push(child); + if (firstOnly) break; + } + if (param.recursive !== false) { + _getItems( + child, + options, + childMatrix, + param, + firstOnly + ); + } + if (firstOnly && items.length > 0) break; } - if (firstOnly && items.length > 0) - break; - } - return items; - } - } -}, /** @lends Item# */{ - /** - * {@grouptitle Importing / Exporting JSON and SVG} - * - * Exports (serializes) the item with its content and child items to a JSON - * data string. - * - * @name Item#exportJSON - * @function - * - * @option [options.asString=true] {Boolean} whether the JSON is returned as - * a `Object` or a `String` - * @option [options.precision=5] {Number} the amount of fractional digits in - * numbers used in JSON data - * - * @param {Object} [options] the serialization options - * @return {String} the exported JSON data - */ - - /** - * Imports (deserializes) the stored JSON data into this item. If the data - * describes an item of the same class or a parent class of the item, the - * data is imported into the item itself. If not, the imported item is added - * to this item's {@link Item#children} list. Note that not all type of - * items can have children. - * - * @param {String} json the JSON data to import from - * @return {Item} - */ - importJSON: function(json) { - // Try importing into `this`. If another item is returned, try adding - // it as a child (this won't be successful on some classes, returning - // null). - var res = Base.importJSON(json, this); - return res !== this ? this.addChild(res) : res; + return items; + }, + }, }, + /** @lends Item# */ { + /** + * {@grouptitle Importing / Exporting JSON and SVG} + * + * Exports (serializes) the item with its content and child items to a JSON + * data string. + * + * @name Item#exportJSON + * @function + * + * @option [options.asString=true] {Boolean} whether the JSON is returned as + * a `Object` or a `String` + * @option [options.precision=5] {Number} the amount of fractional digits in + * numbers used in JSON data + * + * @param {Object} [options] the serialization options + * @return {String} the exported JSON data + */ - /** - * Exports the item with its content and child items as an SVG DOM. - * - * @name Item#exportSVG - * @function - * - * @option [options.bounds='view'] {String|Rectangle} the bounds of the area - * to export, either as a string ({@values 'view', content'}), or a - * {@link Rectangle} object: `'view'` uses the view bounds, - * `'content'` uses the stroke bounds of all content - * @option [options.matrix=paper.view.matrix] {Matrix} the matrix with which - * to transform the exported content: If `options.bounds` is set to - * `'view'`, `paper.view.matrix` is used, for all other settings of - * `options.bounds` the identity matrix is used. - * @option [options.asString=false] {Boolean} whether a SVG node or a - * `String` is to be returned - * @option [options.precision=5] {Number} the amount of fractional digits in - * numbers used in SVG data - * @option [options.matchShapes=false] {Boolean} whether path items should - * tried to be converted to SVG shape items (rect, circle, ellipse, - * line, polyline, polygon), if their geometries match - * @option [options.embedImages=true] {Boolean} whether raster images should - * be embedded as base64 data inlined in the xlink:href attribute, or - * kept as a link to their external URL. - * - * @param {Object} [options] the export options - * @return {SVGElement|String} the item converted to an SVG node or a - * `String` depending on `option.asString` value - */ + /** + * Imports (deserializes) the stored JSON data into this item. If the data + * describes an item of the same class or a parent class of the item, the + * data is imported into the item itself. If not, the imported item is added + * to this item's {@link Item#children} list. Note that not all type of + * items can have children. + * + * @param {String} json the JSON data to import from + * @return {Item} + */ + importJSON: function (json) { + // Try importing into `this`. If another item is returned, try adding + // it as a child (this won't be successful on some classes, returning + // null). + var res = Base.importJSON(json, this); + return res !== this ? this.addChild(res) : res; + }, - /** - * Converts the provided SVG content into Paper.js items and adds them to - * the this item's children list. Note that the item is not cleared first. - * You can call {@link Item#removeChildren()} to do so. - * - * @name Item#importSVG - * @function - * - * @option [options.expandShapes=false] {Boolean} whether imported shape - * items should be expanded to path items - * @option options.onLoad {Function} the callback function to call once the - * SVG content is loaded from the given URL receiving two arguments: the - * converted `item` and the original `svg` data as a string. Only - * required when loading from external resources. - * @option options.onError {Function} the callback function to call if an - * error occurs during loading. Only required when loading from external - * resources. - * @option [options.insert=true] {Boolean} whether the imported items should - * be added to the item that `importSVG()` is called on - * @option [options.applyMatrix={@link PaperScope#settings}.applyMatrix] - * {Boolean} whether the imported items should have their transformation - * matrices applied to their contents or not - * - * @param {SVGElement|String} svg the SVG content to import, either as a SVG - * DOM node, a string containing SVG content, or a string describing the - * URL of the SVG file to fetch. - * @param {Object} [options] the import options - * @return {Item} the newly created Paper.js item containing the converted - * SVG content - */ - /** - * Imports the provided external SVG file, converts it into Paper.js items - * and adds them to the this item's children list. Note that the item is not - * cleared first. You can call {@link Item#removeChildren()} to do so. - * - * @name Item#importSVG - * @function - * - * @param {SVGElement|String} svg the URL of the SVG file to fetch. - * @param {Function} onLoad the callback function to call once the SVG - * content is loaded from the given URL receiving two arguments: the - * converted `item` and the original `svg` data as a string. Only - * required when loading from external files. - * @return {Item} the newly created Paper.js item containing the converted - * SVG content - */ + /** + * Exports the item with its content and child items as an SVG DOM. + * + * @name Item#exportSVG + * @function + * + * @option [options.bounds='view'] {String|Rectangle} the bounds of the area + * to export, either as a string ({@values 'view', content'}), or a + * {@link Rectangle} object: `'view'` uses the view bounds, + * `'content'` uses the stroke bounds of all content + * @option [options.matrix=paper.view.matrix] {Matrix} the matrix with which + * to transform the exported content: If `options.bounds` is set to + * `'view'`, `paper.view.matrix` is used, for all other settings of + * `options.bounds` the identity matrix is used. + * @option [options.asString=false] {Boolean} whether a SVG node or a + * `String` is to be returned + * @option [options.precision=5] {Number} the amount of fractional digits in + * numbers used in SVG data + * @option [options.matchShapes=false] {Boolean} whether path items should + * tried to be converted to SVG shape items (rect, circle, ellipse, + * line, polyline, polygon), if their geometries match + * @option [options.embedImages=true] {Boolean} whether raster images should + * be embedded as base64 data inlined in the xlink:href attribute, or + * kept as a link to their external URL. + * + * @param {Object} [options] the export options + * @return {SVGElement|String} the item converted to an SVG node or a + * `String` depending on `option.asString` value + */ - /** - * {@grouptitle Hierarchy Operations} - * - * Adds the specified item as a child of this item at the end of the its - * {@link #children} list. You can use this function for groups, compound - * paths and layers. - * - * @param {Item} item the item to be added as a child - * @return {Item} the added item, or `null` if adding was not possible - */ - addChild: function(item) { - return this.insertChild(undefined, item); - }, + /** + * Converts the provided SVG content into Paper.js items and adds them to + * the this item's children list. Note that the item is not cleared first. + * You can call {@link Item#removeChildren()} to do so. + * + * @name Item#importSVG + * @function + * + * @option [options.expandShapes=false] {Boolean} whether imported shape + * items should be expanded to path items + * @option options.onLoad {Function} the callback function to call once the + * SVG content is loaded from the given URL receiving two arguments: the + * converted `item` and the original `svg` data as a string. Only + * required when loading from external resources. + * @option options.onError {Function} the callback function to call if an + * error occurs during loading. Only required when loading from external + * resources. + * @option [options.insert=true] {Boolean} whether the imported items should + * be added to the item that `importSVG()` is called on + * @option [options.applyMatrix={@link PaperScope#settings}.applyMatrix] + * {Boolean} whether the imported items should have their transformation + * matrices applied to their contents or not + * + * @param {SVGElement|String} svg the SVG content to import, either as a SVG + * DOM node, a string containing SVG content, or a string describing the + * URL of the SVG file to fetch. + * @param {Object} [options] the import options + * @return {Item} the newly created Paper.js item containing the converted + * SVG content + */ + /** + * Imports the provided external SVG file, converts it into Paper.js items + * and adds them to the this item's children list. Note that the item is not + * cleared first. You can call {@link Item#removeChildren()} to do so. + * + * @name Item#importSVG + * @function + * + * @param {SVGElement|String} svg the URL of the SVG file to fetch. + * @param {Function} onLoad the callback function to call once the SVG + * content is loaded from the given URL receiving two arguments: the + * converted `item` and the original `svg` data as a string. Only + * required when loading from external files. + * @return {Item} the newly created Paper.js item containing the converted + * SVG content + */ - /** - * Inserts the specified item as a child of this item at the specified index - * in its {@link #children} list. You can use this function for groups, - * compound paths and layers. - * - * @param {Number} index the index at which to insert the item - * @param {Item} item the item to be inserted as a child - * @return {Item} the inserted item, or `null` if inserting was not possible - */ - insertChild: function(index, item) { - var res = item ? this.insertChildren(index, [item]) : null; - return res && res[0]; - }, + /** + * {@grouptitle Hierarchy Operations} + * + * Adds the specified item as a child of this item at the end of the its + * {@link #children} list. You can use this function for groups, compound + * paths and layers. + * + * @param {Item} item the item to be added as a child + * @return {Item} the added item, or `null` if adding was not possible + */ + addChild: function (item) { + return this.insertChild(undefined, item); + }, - /** - * Adds the specified items as children of this item at the end of the its - * children list. You can use this function for groups, compound paths and - * layers. - * - * @param {Item[]} items the items to be added as children - * @return {Item[]} the added items, or `null` if adding was not possible - */ - addChildren: function(items) { - return this.insertChildren(this._children.length, items); - }, + /** + * Inserts the specified item as a child of this item at the specified index + * in its {@link #children} list. You can use this function for groups, + * compound paths and layers. + * + * @param {Number} index the index at which to insert the item + * @param {Item} item the item to be inserted as a child + * @return {Item} the inserted item, or `null` if inserting was not possible + */ + insertChild: function (index, item) { + var res = item ? this.insertChildren(index, [item]) : null; + return res && res[0]; + }, - /** - * Inserts the specified items as children of this item at the specified - * index in its {@link #children} list. You can use this function for - * groups, compound paths and layers. - * - * @param {Number} index - * @param {Item[]} items the items to be appended as children - * @return {Item[]} the inserted items, or `null` if inserted was not - * possible - */ - insertChildren: function(index, items) { - var children = this._children; - if (children && items && items.length > 0) { - // We need to clone items because it may be an Item#children array. - // Also, we're removing elements if they don't match _type. - // Use Base.slice() because items can be an arguments object. - items = Base.slice(items); - // Remove the items from their parents first, since they might be - // inserted into their own parents, affecting indices. - // Use the loop also to filter invalid items. - var inserted = {}; - for (var i = items.length - 1; i >= 0; i--) { - var item = items[i], - id = item && item._id; - // If an item was inserted already, it must be included multiple - // times in the items array. Only insert once. - if (!item || inserted[id]) { - items.splice(i, 1); - } else { - // Notify parent of change. Don't notify item itself yet, - // as we're doing so when adding it to the new owner below. - item._remove(false, true); - inserted[id] = true; + /** + * Adds the specified items as children of this item at the end of the its + * children list. You can use this function for groups, compound paths and + * layers. + * + * @param {Item[]} items the items to be added as children + * @return {Item[]} the added items, or `null` if adding was not possible + */ + addChildren: function (items) { + return this.insertChildren(this._children.length, items); + }, + + /** + * Inserts the specified items as children of this item at the specified + * index in its {@link #children} list. You can use this function for + * groups, compound paths and layers. + * + * @param {Number} index + * @param {Item[]} items the items to be appended as children + * @return {Item[]} the inserted items, or `null` if inserted was not + * possible + */ + insertChildren: function (index, items) { + var children = this._children; + if (children && items && items.length > 0) { + // We need to clone items because it may be an Item#children array. + // Also, we're removing elements if they don't match _type. + // Use Base.slice() because items can be an arguments object. + items = Base.slice(items); + // Remove the items from their parents first, since they might be + // inserted into their own parents, affecting indices. + // Use the loop also to filter invalid items. + var inserted = {}; + for (var i = items.length - 1; i >= 0; i--) { + var item = items[i], + id = item && item._id; + // If an item was inserted already, it must be included multiple + // times in the items array. Only insert once. + if (!item || inserted[id]) { + items.splice(i, 1); + } else { + // Notify parent of change. Don't notify item itself yet, + // as we're doing so when adding it to the new owner below. + item._remove(false, true); + inserted[id] = true; + } } + Base.splice(children, items, index, 0); + var project = this._project, + // See #_remove() for an explanation of this: + notifySelf = project._changes; + for (var i = 0, l = items.length; i < l; i++) { + var item = items[i], + name = item._name; + item._parent = this; + item._setProject(project, true); + // Set the name again to make sure all name lookup structures + // are kept in sync. + if (name) item.setName(name); + if (notifySelf) item._changed(/*#=*/ Change.INSERTION); + } + this._changed(/*#=*/ Change.CHILDREN); + } else { + items = null; } - Base.splice(children, items, index, 0); - var project = this._project, - // See #_remove() for an explanation of this: - notifySelf = project._changes; - for (var i = 0, l = items.length; i < l; i++) { - var item = items[i], - name = item._name; - item._parent = this; - item._setProject(project, true); - // Set the name again to make sure all name lookup structures - // are kept in sync. - if (name) - item.setName(name); - if (notifySelf) - item._changed(/*#=*/Change.INSERTION); - } - this._changed(/*#=*/Change.CHILDREN); - } else { - items = null; - } - return items; - }, - - // Internal alias, so both Project and Item can be used in #copyTo(), and - // through _getOwner() in the various Item#insert*() methods. - _insertItem: '#insertChild', + return items; + }, - /** - * Private helper method used by {@link #insertAbove(item)} and - * {@link #insertBelow(item)}, to insert this item in relation to a - * specified other item. - * - * @param {Item} item the item in relation to which which it should be - * inserted - * @param {Number} offset the offset at which the item should be inserted - * @return {Item} the inserted item, or `null` if inserting was not possible - */ - _insertAt: function(item, offset) { - var owner = item && item._getOwner(), - // Only insert if the item is not the same as `this`, and if it - // actually has an owner into which we can insert. - res = item !== this && owner ? this : null; - if (res) { - // Notify parent of change. Don't notify item itself yet, - // as we're doing so when adding it to the new owner below. - res._remove(false, true); - owner._insertItem(item._index + offset, res); - } - return res; - }, + // Internal alias, so both Project and Item can be used in #copyTo(), and + // through _getOwner() in the various Item#insert*() methods. + _insertItem: "#insertChild", - /** - * Inserts this item above the specified item. - * - * @param {Item} item the item above which it should be inserted - * @return {Item} the inserted item, or `null` if inserting was not possible - */ - insertAbove: function(item) { - return this._insertAt(item, 1); - }, + /** + * Private helper method used by {@link #insertAbove(item)} and + * {@link #insertBelow(item)}, to insert this item in relation to a + * specified other item. + * + * @param {Item} item the item in relation to which which it should be + * inserted + * @param {Number} offset the offset at which the item should be inserted + * @return {Item} the inserted item, or `null` if inserting was not possible + */ + _insertAt: function (item, offset) { + var owner = item && item._getOwner(), + // Only insert if the item is not the same as `this`, and if it + // actually has an owner into which we can insert. + res = item !== this && owner ? this : null; + if (res) { + // Notify parent of change. Don't notify item itself yet, + // as we're doing so when adding it to the new owner below. + res._remove(false, true); + owner._insertItem(item._index + offset, res); + } + return res; + }, - /** - * Inserts this item below the specified item. - * - * @param {Item} item the item below which it should be inserted - * @return {Item} the inserted item, or `null` if inserting was not possible - */ - insertBelow: function(item) { - return this._insertAt(item, 0); - }, + /** + * Inserts this item above the specified item. + * + * @param {Item} item the item above which it should be inserted + * @return {Item} the inserted item, or `null` if inserting was not possible + */ + insertAbove: function (item) { + return this._insertAt(item, 1); + }, - /** - * Sends this item to the back of all other items within the same parent. - */ - sendToBack: function() { - var owner = this._getOwner(); - return owner ? owner._insertItem(0, this) : null; - }, + /** + * Inserts this item below the specified item. + * + * @param {Item} item the item below which it should be inserted + * @return {Item} the inserted item, or `null` if inserting was not possible + */ + insertBelow: function (item) { + return this._insertAt(item, 0); + }, - /** - * Brings this item to the front of all other items within the same parent. - */ - bringToFront: function() { - var owner = this._getOwner(); - return owner ? owner._insertItem(undefined, this) : null; - }, + /** + * Sends this item to the back of all other items within the same parent. + */ + sendToBack: function () { + var owner = this._getOwner(); + return owner ? owner._insertItem(0, this) : null; + }, - /** - * Inserts the specified item as a child of this item by appending it to - * the list of children and moving it above all other children. You can - * use this function for groups, compound paths and layers. - * - * @function - * @param {Item} item the item to be appended as a child - * @deprecated use {@link #addChild(item)} instead. - */ - appendTop: '#addChild', + /** + * Brings this item to the front of all other items within the same parent. + */ + bringToFront: function () { + var owner = this._getOwner(); + return owner ? owner._insertItem(undefined, this) : null; + }, - /** - * Inserts the specified item as a child of this item by appending it to - * the list of children and moving it below all other children. You can - * use this function for groups, compound paths and layers. - * - * @param {Item} item the item to be appended as a child - * @deprecated use {@link #insertChild(index, item)} instead. - */ - appendBottom: function(item) { - return this.insertChild(0, item); - }, + /** + * Inserts the specified item as a child of this item by appending it to + * the list of children and moving it above all other children. You can + * use this function for groups, compound paths and layers. + * + * @function + * @param {Item} item the item to be appended as a child + * @deprecated use {@link #addChild(item)} instead. + */ + appendTop: "#addChild", - /** - * Moves this item above the specified item. - * - * @function - * @param {Item} item the item above which it should be moved - * @return {Boolean} {@true if it was moved} - * @deprecated use {@link #insertAbove(item)} instead. - */ - moveAbove: '#insertAbove', + /** + * Inserts the specified item as a child of this item by appending it to + * the list of children and moving it below all other children. You can + * use this function for groups, compound paths and layers. + * + * @param {Item} item the item to be appended as a child + * @deprecated use {@link #insertChild(index, item)} instead. + */ + appendBottom: function (item) { + return this.insertChild(0, item); + }, - /** - * Moves the item below the specified item. - * - * @function - * @param {Item} item the item below which it should be moved - * @return {Boolean} {@true if it was moved} - * @deprecated use {@link #insertBelow(item)} instead. - */ - moveBelow: '#insertBelow', + /** + * Moves this item above the specified item. + * + * @function + * @param {Item} item the item above which it should be moved + * @return {Boolean} {@true if it was moved} + * @deprecated use {@link #insertAbove(item)} instead. + */ + moveAbove: "#insertAbove", - /** - * Adds it to the specified owner, which can be either a {@link Item} or a - * {@link Project}. - * - * @param {Project|Layer|Group|CompoundPath} owner the item or project to - * add the item to - * @return {Item} the item itself, if it was successfully added - * @chainable - */ - addTo: function(owner) { - return owner._insertItem(undefined, this); - }, + /** + * Moves the item below the specified item. + * + * @function + * @param {Item} item the item below which it should be moved + * @return {Boolean} {@true if it was moved} + * @deprecated use {@link #insertBelow(item)} instead. + */ + moveBelow: "#insertBelow", - /** - * Clones the item and adds it to the specified owner, which can be either - * a {@link Item} or a {@link Project}. - * - * @param {Project|Layer|Group|CompoundPath} owner the item or project to - * copy the item to - * @return {Item} the new copy of the item, if it was successfully added - * @chainable - */ - copyTo: function(owner) { - return this.clone(false).addTo(owner); - }, + /** + * Adds it to the specified owner, which can be either a {@link Item} or a + * {@link Project}. + * + * @param {Project|Layer|Group|CompoundPath} owner the item or project to + * add the item to + * @return {Item} the item itself, if it was successfully added + * @chainable + */ + addTo: function (owner) { + return owner._insertItem(undefined, this); + }, - /** - * If this is a group, layer or compound-path with only one child-item, - * the child-item is moved outside and the parent is erased. Otherwise, the - * item itself is returned unmodified. - * - * @return {Item} the reduced item - */ - reduce: function(options) { - var children = this._children; - if (children && children.length === 1) { - var child = children[0].reduce(options); - // Make sure the reduced item has the same parent as the original. - if (this._parent) { - child.insertAbove(this); - this.remove(); - } else { - child.remove(); - } - return child; - } - return this; - }, + /** + * Clones the item and adds it to the specified owner, which can be either + * a {@link Item} or a {@link Project}. + * + * @param {Project|Layer|Group|CompoundPath} owner the item or project to + * copy the item to + * @return {Item} the new copy of the item, if it was successfully added + * @chainable + */ + copyTo: function (owner) { + return this.clone(false).addTo(owner); + }, - /** - * Removes the item from its parent's named children list. - */ - _removeNamed: function() { - var owner = this._getOwner(); - if (owner) { - var children = owner._children, - namedChildren = owner._namedChildren, - name = this._name, - namedArray = namedChildren[name], - index = namedArray ? namedArray.indexOf(this) : -1; - if (index !== -1) { - // Remove the named reference - if (children[name] == this) - delete children[name]; - // Remove this entry - namedArray.splice(index, 1); - // If there are any items left in the named array, set the first - // of them to be children[this.name] - if (namedArray.length) { - children[name] = namedArray[0]; + /** + * If this is a group, layer or compound-path with only one child-item, + * the child-item is moved outside and the parent is erased. Otherwise, the + * item itself is returned unmodified. + * + * @return {Item} the reduced item + */ + reduce: function (options) { + var children = this._children; + if (children && children.length === 1) { + var child = children[0].reduce(options); + // Make sure the reduced item has the same parent as the original. + if (this._parent) { + child.insertAbove(this); + this.remove(); } else { - // Otherwise delete the empty array - delete namedChildren[name]; + child.remove(); } + return child; } - } - }, + return this; + }, - /** - * Removes the item from its parent's children list. - */ - _remove: function(notifySelf, notifyParent) { - var owner = this._getOwner(), - project = this._project, - index = this._index; - if (this._style) - this._style._dispose(); - if (owner) { - // Handle named children separately from index: - if (this._name) - this._removeNamed(); - // Handle index separately from owner: There are situations where - // the item is already removed from its list through Base.splice() - // and index set to undefined, but the owner is still set, - // e.g. in #removeChildren(): - if (index != null) { - // Only required for layers but not enough to merit an override. - if (project._activeLayer === this) - project._activeLayer = this.getNextSibling() - || this.getPreviousSibling(); - Base.splice(owner._children, null, index, 1); + /** + * Removes the item from its parent's named children list. + */ + _removeNamed: function () { + var owner = this._getOwner(); + if (owner) { + var children = owner._children, + namedChildren = owner._namedChildren, + name = this._name, + namedArray = namedChildren[name], + index = namedArray ? namedArray.indexOf(this) : -1; + if (index !== -1) { + // Remove the named reference + if (children[name] == this) delete children[name]; + // Remove this entry + namedArray.splice(index, 1); + // If there are any items left in the named array, set the first + // of them to be children[this.name] + if (namedArray.length) { + children[name] = namedArray[0]; + } else { + // Otherwise delete the empty array + delete namedChildren[name]; + } + } } - this._installEvents(false); - // Notify self of the insertion change. We only need this - // notification if we're tracking changes for now. - if (notifySelf && project._changes) - this._changed(/*#=*/Change.INSERTION); - // Notify owner of changed children (this can be the project too). - if (notifyParent) - owner._changed(/*#=*/Change.CHILDREN, this); - this._parent = null; - return true; - } - return false; - }, + }, - /** - * Removes the item and all its children from the project. The item is not - * destroyed and can be inserted again after removal. - * - * @return {Boolean} {@true if the item was removed} - */ - remove: function() { - // Notify self and parent of change: - return this._remove(true, true); - }, + /** + * Removes the item from its parent's children list. + */ + _remove: function (notifySelf, notifyParent) { + var owner = this._getOwner(), + project = this._project, + index = this._index; + if (this._style) this._style._dispose(); + if (owner) { + // Handle named children separately from index: + if (this._name) this._removeNamed(); + // Handle index separately from owner: There are situations where + // the item is already removed from its list through Base.splice() + // and index set to undefined, but the owner is still set, + // e.g. in #removeChildren(): + if (index != null) { + // Only required for layers but not enough to merit an override. + if (project._activeLayer === this) + project._activeLayer = + this.getNextSibling() || this.getPreviousSibling(); + Base.splice(owner._children, null, index, 1); + } + this._installEvents(false); + // Notify self of the insertion change. We only need this + // notification if we're tracking changes for now. + if (notifySelf && project._changes) + this._changed(/*#=*/ Change.INSERTION); + // Notify owner of changed children (this can be the project too). + if (notifyParent) owner._changed(/*#=*/ Change.CHILDREN, this); + this._parent = null; + return true; + } + return false; + }, - /** - * Replaces this item with the provided new item which will takes its place - * in the project hierarchy instead. - * - * @param {Item} item the item that will replace this item - * @return {Boolean} {@true if the item was replaced} - */ - replaceWith: function(item) { - var ok = item && item.insertBelow(this); - if (ok) - this.remove(); - return ok; - }, + /** + * Removes the item and all its children from the project. The item is not + * destroyed and can be inserted again after removal. + * + * @return {Boolean} {@true if the item was removed} + */ + remove: function () { + // Notify self and parent of change: + return this._remove(true, true); + }, - /** - * Removes all of the item's {@link #children} (if any). - * - * @name Item#removeChildren - * @alias Item#clear - * @function - * @return {Item[]} an array containing the removed items - */ - /** - * Removes the children from the specified `start` index to and excluding - * the `end` index from the parent's {@link #children} array. - * - * @name Item#removeChildren - * @function - * @param {Number} start the beginning index, inclusive - * @param {Number} [end=children.length] the ending index, exclusive - * @return {Item[]} an array containing the removed items - */ - removeChildren: function(start, end) { - if (!this._children) - return null; - start = start || 0; - end = Base.pick(end, this._children.length); - // Use Base.splice(), which adjusts #_index for the items above, and - // deletes it for the removed items. Calling #_remove() afterwards is - // fine, since it only calls Base.splice() if #_index is set. - var removed = Base.splice(this._children, null, start, end - start); - for (var i = removed.length - 1; i >= 0; i--) { - // Don't notify parent each time, notify it separately after. - removed[i]._remove(true, false); - } - if (removed.length > 0) - this._changed(/*#=*/Change.CHILDREN); - return removed; - }, + /** + * Replaces this item with the provided new item which will takes its place + * in the project hierarchy instead. + * + * @param {Item} item the item that will replace this item + * @return {Boolean} {@true if the item was replaced} + */ + replaceWith: function (item) { + var ok = item && item.insertBelow(this); + if (ok) this.remove(); + return ok; + }, - // DOCS Item#clear() - clear: '#removeChildren', + /** + * Removes all of the item's {@link #children} (if any). + * + * @name Item#removeChildren + * @alias Item#clear + * @function + * @return {Item[]} an array containing the removed items + */ + /** + * Removes the children from the specified `start` index to and excluding + * the `end` index from the parent's {@link #children} array. + * + * @name Item#removeChildren + * @function + * @param {Number} start the beginning index, inclusive + * @param {Number} [end=children.length] the ending index, exclusive + * @return {Item[]} an array containing the removed items + */ + removeChildren: function (start, end) { + if (!this._children) return null; + start = start || 0; + end = Base.pick(end, this._children.length); + // Use Base.splice(), which adjusts #_index for the items above, and + // deletes it for the removed items. Calling #_remove() afterwards is + // fine, since it only calls Base.splice() if #_index is set. + var removed = Base.splice(this._children, null, start, end - start); + for (var i = removed.length - 1; i >= 0; i--) { + // Don't notify parent each time, notify it separately after. + removed[i]._remove(true, false); + } + if (removed.length > 0) this._changed(/*#=*/ Change.CHILDREN); + return removed; + }, - /** - * Reverses the order of the item's children - */ - reverseChildren: function() { - if (this._children) { - this._children.reverse(); - // Adjust indices - for (var i = 0, l = this._children.length; i < l; i++) - this._children[i]._index = i; - this._changed(/*#=*/Change.CHILDREN); - } - }, + // DOCS Item#clear() + clear: "#removeChildren", - /** - * {@grouptitle Tests} - * Specifies whether the item has any content or not. The meaning of what - * content is differs from type to type. For example, a {@link Group} with - * no children, a {@link TextItem} with no text content and a {@link Path} - * with no segments all are considered empty. - * - * @param {Boolean} [recursively=false] whether an item with children should be - * considered empty if all its descendants are empty - * @return {Boolean} - */ - isEmpty: function(recursively) { - var children = this._children; - var numChildren = children ? children.length : 0; - if (recursively) { - // In recursive check, item is empty if all its children are empty. - for (var i = 0; i < numChildren; i++) { - if (!children[i].isEmpty(recursively)) { - return false; + /** + * Reverses the order of the item's children + */ + reverseChildren: function () { + if (this._children) { + this._children.reverse(); + // Adjust indices + for (var i = 0, l = this._children.length; i < l; i++) + this._children[i]._index = i; + this._changed(/*#=*/ Change.CHILDREN); + } + }, + + /** + * {@grouptitle Tests} + * Specifies whether the item has any content or not. The meaning of what + * content is differs from type to type. For example, a {@link Group} with + * no children, a {@link TextItem} with no text content and a {@link Path} + * with no segments all are considered empty. + * + * @param {Boolean} [recursively=false] whether an item with children should be + * considered empty if all its descendants are empty + * @return {Boolean} + */ + isEmpty: function (recursively) { + var children = this._children; + var numChildren = children ? children.length : 0; + if (recursively) { + // In recursive check, item is empty if all its children are empty. + for (var i = 0; i < numChildren; i++) { + if (!children[i].isEmpty(recursively)) { + return false; + } } + return true; } - return true; - } - return !numChildren; - }, + return !numChildren; + }, - /** - * Checks whether the item is editable. - * - * @return {Boolean} {@true when neither the item, nor its parents are - * locked or hidden} - * @ignore - */ - // TODO: Item#isEditable is currently ignored in the documentation, as - // locking an item currently has no effect - isEditable: function() { - var item = this; - while (item) { - if (!item._visible || item._locked) - return false; - item = item._parent; - } - return true; - }, + /** + * Checks whether the item is editable. + * + * @return {Boolean} {@true when neither the item, nor its parents are + * locked or hidden} + * @ignore + */ + // TODO: Item#isEditable is currently ignored in the documentation, as + // locking an item currently has no effect + isEditable: function () { + var item = this; + while (item) { + if (!item._visible || item._locked) return false; + item = item._parent; + } + return true; + }, - /** - * Checks whether the item is valid, i.e. it hasn't been removed. - * - * @return {Boolean} {@true if the item is valid} - */ - // TODO: isValid / checkValid + /** + * Checks whether the item is valid, i.e. it hasn't been removed. + * + * @return {Boolean} {@true if the item is valid} + */ + // TODO: isValid / checkValid - /** - * {@grouptitle Style Tests} - * - * Checks whether the item has a fill. - * - * @return {Boolean} {@true if the item has a fill} - */ - hasFill: function() { - return this.getStyle().hasFill(); - }, + /** + * {@grouptitle Style Tests} + * + * Checks whether the item has a fill. + * + * @return {Boolean} {@true if the item has a fill} + */ + hasFill: function () { + return this.getStyle().hasFill(); + }, - /** - * Checks whether the item has a stroke. - * - * @return {Boolean} {@true if the item has a stroke} - */ - hasStroke: function() { - return this.getStyle().hasStroke(); - }, + /** + * Checks whether the item has a stroke. + * + * @return {Boolean} {@true if the item has a stroke} + */ + hasStroke: function () { + return this.getStyle().hasStroke(); + }, - /** - * Checks whether the item has a shadow. - * - * @return {Boolean} {@true if the item has a shadow} - */ - hasShadow: function() { - return this.getStyle().hasShadow(); - }, + /** + * Checks whether the item has a shadow. + * + * @return {Boolean} {@true if the item has a shadow} + */ + hasShadow: function () { + return this.getStyle().hasShadow(); + }, - /** - * Returns -1 if 'this' is above 'item', 1 if below, 0 if their order is not - * defined in such a way, e.g. if one is a descendant of the other. - */ - _getOrder: function(item) { - // Private method that produces a list of ancestors, starting with the - // root and ending with the actual element as the last entry. - function getList(item) { - var list = []; - do { - list.unshift(item); - } while (item = item._parent); - return list; - } - var list1 = getList(this), - list2 = getList(item); - for (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) { - if (list1[i] != list2[i]) { - // Found the position in the parents list where the two start - // to differ. Look at who's above who. - return list1[i]._index < list2[i]._index ? 1 : -1; + /** + * Returns -1 if 'this' is above 'item', 1 if below, 0 if their order is not + * defined in such a way, e.g. if one is a descendant of the other. + */ + _getOrder: function (item) { + // Private method that produces a list of ancestors, starting with the + // root and ending with the actual element as the last entry. + function getList(item) { + var list = []; + do { + list.unshift(item); + } while ((item = item._parent)); + return list; } - } - return 0; - }, - - /** - * {@grouptitle Hierarchy Tests} - * - * Checks if the item contains any children items. - * - * @return {Boolean} {@true it has one or more children} - */ - hasChildren: function() { - return this._children && this._children.length > 0; - }, + var list1 = getList(this), + list2 = getList(item); + for ( + var i = 0, l = Math.min(list1.length, list2.length); + i < l; + i++ + ) { + if (list1[i] != list2[i]) { + // Found the position in the parents list where the two start + // to differ. Look at who's above who. + return list1[i]._index < list2[i]._index ? 1 : -1; + } + } + return 0; + }, - /** - * Checks whether the item and all its parents are inserted into scene graph - * or not. - * - * @return {Boolean} {@true if the item is inserted into the scene graph} - */ - isInserted: function() { - return this._parent ? this._parent.isInserted() : false; - }, + /** + * {@grouptitle Hierarchy Tests} + * + * Checks if the item contains any children items. + * + * @return {Boolean} {@true it has one or more children} + */ + hasChildren: function () { + return this._children && this._children.length > 0; + }, - /** - * Checks if this item is above the specified item in the stacking order - * of the project. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true if it is above the specified item} - */ - isAbove: function(item) { - return this._getOrder(item) === -1; - }, + /** + * Checks whether the item and all its parents are inserted into scene graph + * or not. + * + * @return {Boolean} {@true if the item is inserted into the scene graph} + */ + isInserted: function () { + return this._parent ? this._parent.isInserted() : false; + }, - /** - * Checks if the item is below the specified item in the stacking order of - * the project. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true if it is below the specified item} - */ - isBelow: function(item) { - return this._getOrder(item) === 1; - }, + /** + * Checks if this item is above the specified item in the stacking order + * of the project. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true if it is above the specified item} + */ + isAbove: function (item) { + return this._getOrder(item) === -1; + }, - /** - * Checks whether the specified item is the parent of the item. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true if it is the parent of the item} - */ - isParent: function(item) { - return this._parent === item; - }, + /** + * Checks if the item is below the specified item in the stacking order of + * the project. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true if it is below the specified item} + */ + isBelow: function (item) { + return this._getOrder(item) === 1; + }, - /** - * Checks whether the specified item is a child of the item. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true it is a child of the item} - */ - isChild: function(item) { - return item && item._parent === this; - }, + /** + * Checks whether the specified item is the parent of the item. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true if it is the parent of the item} + */ + isParent: function (item) { + return this._parent === item; + }, - /** - * Checks if the item is contained within the specified item. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true if it is inside the specified item} - */ - isDescendant: function(item) { - var parent = this; - while (parent = parent._parent) { - if (parent === item) - return true; - } - return false; - }, + /** + * Checks whether the specified item is a child of the item. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true it is a child of the item} + */ + isChild: function (item) { + return item && item._parent === this; + }, - /** - * Checks if the item is an ancestor of the specified item. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true if the item is an ancestor of the specified - * item} - */ - isAncestor: function(item) { - return item ? item.isDescendant(this) : false; - }, + /** + * Checks if the item is contained within the specified item. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true if it is inside the specified item} + */ + isDescendant: function (item) { + var parent = this; + while ((parent = parent._parent)) { + if (parent === item) return true; + } + return false; + }, - /** - * Checks if the item is an a sibling of the specified item. - * - * @param {Item} item the item to check against - * @return {Boolean} {@true if the item is aa sibling of the specified item} - */ - isSibling: function(item) { - return this._parent === item._parent; - }, + /** + * Checks if the item is an ancestor of the specified item. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true if the item is an ancestor of the specified + * item} + */ + isAncestor: function (item) { + return item ? item.isDescendant(this) : false; + }, - /** - * Checks whether the item is grouped with the specified item. - * - * @param {Item} item - * @return {Boolean} {@true if the items are grouped together} - */ - isGroupedWith: function(item) { - var parent = this._parent; - while (parent) { - // Find group parents. Check for parent._parent, since don't want - // top level layers, because they also inherit from Group - if (parent._parent - && /^(Group|Layer|CompoundPath)$/.test(parent._class) - && item.isDescendant(parent)) + /** + * Checks if the item is an a sibling of the specified item. + * + * @param {Item} item the item to check against + * @return {Boolean} {@true if the item is aa sibling of the specified item} + */ + isSibling: function (item) { + return this._parent === item._parent; + }, + + /** + * Checks whether the item is grouped with the specified item. + * + * @param {Item} item + * @return {Boolean} {@true if the items are grouped together} + */ + isGroupedWith: function (item) { + var parent = this._parent; + while (parent) { + // Find group parents. Check for parent._parent, since don't want + // top level layers, because they also inherit from Group + if ( + parent._parent && + /^(Group|Layer|CompoundPath)$/.test(parent._class) && + item.isDescendant(parent) + ) return true; - // Keep walking up otherwise - parent = parent._parent; - } - return false; - }, + // Keep walking up otherwise + parent = parent._parent; + } + return false; + }, - // Document all style properties which get injected into Item by Style: + // Document all style properties which get injected into Item by Style: - /** - * {@grouptitle Stroke Style} - * - * The color of the stroke. - * - * @name Item#strokeColor - * @property - * @type ?Color - * - * @example {@paperscript} - * // Setting the stroke color of a path: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * - * // Set its stroke color to RGB red: - * circle.strokeColor = new Color(1, 0, 0); - */ + /** + * {@grouptitle Stroke Style} + * + * The color of the stroke. + * + * @name Item#strokeColor + * @property + * @type ?Color + * + * @example {@paperscript} + * // Setting the stroke color of a path: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * + * // Set its stroke color to RGB red: + * circle.strokeColor = new Color(1, 0, 0); + */ - /** - * The width of the stroke. - * - * @name Item#strokeWidth - * @property - * @type Number - * - * @example {@paperscript} - * // Setting an item's stroke width: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35, - * strokeColor: 'red' - * }); - * - * // Set its stroke width to 10: - * circle.strokeWidth = 10; - */ + /** + * The width of the stroke. + * + * @name Item#strokeWidth + * @property + * @type Number + * + * @example {@paperscript} + * // Setting an item's stroke width: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35, + * strokeColor: 'red' + * }); + * + * // Set its stroke width to 10: + * circle.strokeWidth = 10; + */ - /** - * The shape to be used at the beginning and end of open {@link Path} items, - * when they have a stroke. - * - * @name Item#strokeCap - * @property - * @type String - * @values 'round', 'square', 'butt' - * @default 'butt' - * - * @example {@paperscript height=200} - * // A look at the different stroke caps: - * - * var line = new Path({ - * segments: [[80, 50], [420, 50]], - * strokeColor: 'black', - * strokeWidth: 20, - * selected: true - * }); - * - * // Set the stroke cap of the line to be round: - * line.strokeCap = 'round'; - * - * // Copy the path and set its stroke cap to be square: - * var line2 = line.clone(); - * line2.position.y += 50; - * line2.strokeCap = 'square'; - * - * // Make another copy and set its stroke cap to be butt: - * var line2 = line.clone(); - * line2.position.y += 100; - * line2.strokeCap = 'butt'; - */ + /** + * The shape to be used at the beginning and end of open {@link Path} items, + * when they have a stroke. + * + * @name Item#strokeCap + * @property + * @type String + * @values 'round', 'square', 'butt' + * @default 'butt' + * + * @example {@paperscript height=200} + * // A look at the different stroke caps: + * + * var line = new Path({ + * segments: [[80, 50], [420, 50]], + * strokeColor: 'black', + * strokeWidth: 20, + * selected: true + * }); + * + * // Set the stroke cap of the line to be round: + * line.strokeCap = 'round'; + * + * // Copy the path and set its stroke cap to be square: + * var line2 = line.clone(); + * line2.position.y += 50; + * line2.strokeCap = 'square'; + * + * // Make another copy and set its stroke cap to be butt: + * var line2 = line.clone(); + * line2.position.y += 100; + * line2.strokeCap = 'butt'; + */ - /** - * The shape to be used at the segments and corners of {@link Path} items - * when they have a stroke. - * - * @name Item#strokeJoin - * @property - * @type String - * @values 'miter', 'round', 'bevel' - * @default 'miter' - * - * @example {@paperscript height=120} - * // A look at the different stroke joins: - * var path = new Path({ - * segments: [[80, 100], [120, 40], [160, 100]], - * strokeColor: 'black', - * strokeWidth: 20, - * // Select the path, in order to see where the stroke is formed: - * selected: true - * }); - * - * var path2 = path.clone(); - * path2.position.x += path2.bounds.width * 1.5; - * path2.strokeJoin = 'round'; - * - * var path3 = path2.clone(); - * path3.position.x += path3.bounds.width * 1.5; - * path3.strokeJoin = 'bevel'; - */ + /** + * The shape to be used at the segments and corners of {@link Path} items + * when they have a stroke. + * + * @name Item#strokeJoin + * @property + * @type String + * @values 'miter', 'round', 'bevel' + * @default 'miter' + * + * @example {@paperscript height=120} + * // A look at the different stroke joins: + * var path = new Path({ + * segments: [[80, 100], [120, 40], [160, 100]], + * strokeColor: 'black', + * strokeWidth: 20, + * // Select the path, in order to see where the stroke is formed: + * selected: true + * }); + * + * var path2 = path.clone(); + * path2.position.x += path2.bounds.width * 1.5; + * path2.strokeJoin = 'round'; + * + * var path3 = path2.clone(); + * path3.position.x += path3.bounds.width * 1.5; + * path3.strokeJoin = 'bevel'; + */ - /** - * The dash offset of the stroke. - * - * @name Item#dashOffset - * @property - * @type Number - * @default 0 - */ + /** + * The dash offset of the stroke. + * + * @name Item#dashOffset + * @property + * @type Number + * @default 0 + */ - /** - * Specifies whether the stroke is to be drawn taking the current affine - * transformation into account (the default behavior), or whether it should - * appear as a non-scaling stroke. - * - * @name Item#strokeScaling - * @property - * @type Boolean - * @default true - */ + /** + * Specifies whether the stroke is to be drawn taking the current affine + * transformation into account (the default behavior), or whether it should + * appear as a non-scaling stroke. + * + * @name Item#strokeScaling + * @property + * @type Boolean + * @default true + */ - /** - * Specifies an array containing the dash and gap lengths of the stroke. - * - * @example {@paperscript} - * var path = new Path.Circle({ - * center: [80, 50], - * radius: 40, - * strokeWidth: 2, - * strokeColor: 'black' - * }); - * - * // Set the dashed stroke to [10pt dash, 4pt gap]: - * path.dashArray = [10, 4]; - * - * @name Item#dashArray - * @property - * @type Number[] - * @default [] - */ + /** + * Specifies an array containing the dash and gap lengths of the stroke. + * + * @example {@paperscript} + * var path = new Path.Circle({ + * center: [80, 50], + * radius: 40, + * strokeWidth: 2, + * strokeColor: 'black' + * }); + * + * // Set the dashed stroke to [10pt dash, 4pt gap]: + * path.dashArray = [10, 4]; + * + * @name Item#dashArray + * @property + * @type Number[] + * @default [] + */ - /** - * The miter limit of the stroke. - * When two line segments meet at a sharp angle and miter joins have been - * specified for {@link Item#strokeJoin}, it is possible for the miter to - * extend far beyond the {@link Item#strokeWidth} of the path. The - * miterLimit imposes a limit on the ratio of the miter length to the - * {@link Item#strokeWidth}. - * - * @name Item#miterLimit - * @property - * @type Number - * @default 10 - */ + /** + * The miter limit of the stroke. + * When two line segments meet at a sharp angle and miter joins have been + * specified for {@link Item#strokeJoin}, it is possible for the miter to + * extend far beyond the {@link Item#strokeWidth} of the path. The + * miterLimit imposes a limit on the ratio of the miter length to the + * {@link Item#strokeWidth}. + * + * @name Item#miterLimit + * @property + * @type Number + * @default 10 + */ - /** - * {@grouptitle Fill Style} - * - * The fill color of the item. - * - * @name Item#fillColor - * @property - * @type ?Color - * - * @example {@paperscript} - * // Setting the fill color of a path to red: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35 - * }); - * - * // Set the fill color of the circle to RGB red: - * circle.fillColor = new Color(1, 0, 0); - */ + /** + * {@grouptitle Fill Style} + * + * The fill color of the item. + * + * @name Item#fillColor + * @property + * @type ?Color + * + * @example {@paperscript} + * // Setting the fill color of a path to red: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35 + * }); + * + * // Set the fill color of the circle to RGB red: + * circle.fillColor = new Color(1, 0, 0); + */ - /** - * The fill-rule with which the shape gets filled. Please note that only - * modern browsers support fill-rules other than `'nonzero'`. - * - * @name Item#fillRule - * @property - * @type String - * @values 'nonzero', 'evenodd' - * @default 'nonzero' - */ + /** + * The fill-rule with which the shape gets filled. Please note that only + * modern browsers support fill-rules other than `'nonzero'`. + * + * @name Item#fillRule + * @property + * @type String + * @values 'nonzero', 'evenodd' + * @default 'nonzero' + */ - /** - * {@grouptitle Shadow Style} - * - * The shadow color. - * - * @property - * @name Item#shadowColor - * @type ?Color - * - * @example {@paperscript} - * // Creating a circle with a black shadow: - * - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35, - * fillColor: 'white', - * // Set the shadow color of the circle to RGB black: - * shadowColor: new Color(0, 0, 0), - * // Set the shadow blur radius to 12: - * shadowBlur: 12, - * // Offset the shadow by { x: 5, y: 5 } - * shadowOffset: new Point(5, 5) - * }); - */ + /** + * {@grouptitle Shadow Style} + * + * The shadow color. + * + * @property + * @name Item#shadowColor + * @type ?Color + * + * @example {@paperscript} + * // Creating a circle with a black shadow: + * + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35, + * fillColor: 'white', + * // Set the shadow color of the circle to RGB black: + * shadowColor: new Color(0, 0, 0), + * // Set the shadow blur radius to 12: + * shadowBlur: 12, + * // Offset the shadow by { x: 5, y: 5 } + * shadowOffset: new Point(5, 5) + * }); + */ - /** - * The shadow's blur radius. - * - * @property - * @name Item#shadowBlur - * @type Number - * @default 0 - */ + /** + * The shadow's blur radius. + * + * @property + * @name Item#shadowBlur + * @type Number + * @default 0 + */ - /** - * The shadow's offset. - * - * @property - * @name Item#shadowOffset - * @type Point - * @default 0 - */ + /** + * The shadow's offset. + * + * @property + * @name Item#shadowOffset + * @type Point + * @default 0 + */ - // TODO: Find a better name than selectedColor. It should also be used for - // guides, etc. - /** - * {@grouptitle Selection Style} - * - * The color the item is highlighted with when selected. If the item does - * not specify its own color, the color defined by its layer is used instead. - * - * @name Item#selectedColor - * @property - * @type ?Color - */ -}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { - var rotate = key === 'rotate'; - this[key] = function(/* value, center */) { - var args = arguments, - value = (rotate ? Base : Point).read(args), - center = Point.read(args, 0, { readNull: true }); - return this.transform(new Matrix()[key](value, - center || this.getPosition(true))); - }; -}, /** @lends Item# */{ - /** - * {@grouptitle Transform Functions} - * - * Translates (moves) the item by the given offset views. - * - * @param {Point} delta the offset to translate the item by - */ - translate: function(/* delta */) { - var mx = new Matrix(); - return this.transform(mx.translate.apply(mx, arguments)); + // TODO: Find a better name than selectedColor. It should also be used for + // guides, etc. + /** + * {@grouptitle Selection Style} + * + * The color the item is highlighted with when selected. If the item does + * not specify its own color, the color defined by its layer is used instead. + * + * @name Item#selectedColor + * @property + * @type ?Color + */ }, + Base.each( + ["rotate", "scale", "shear", "skew"], + function (key) { + var rotate = key === "rotate"; + this[key] = function (/* value, center */) { + var args = arguments, + value = (rotate ? Base : Point).read(args), + center = Point.read(args, 0, { readNull: true }); + return this.transform( + new Matrix()[key](value, center || this.getPosition(true)) + ); + }; + }, + /** @lends Item# */ { + /** + * {@grouptitle Transform Functions} + * + * Translates (moves) the item by the given offset views. + * + * @param {Point} delta the offset to translate the item by + */ + translate: function (/* delta */) { + var mx = new Matrix(); + return this.transform(mx.translate.apply(mx, arguments)); + }, - /** - * Rotates the item by a given angle around the given center point. - * - * Angles are oriented clockwise and measured in degrees. - * - * @name Item#rotate - * @function - * @param {Number} angle the rotation angle - * @param {Point} [center={@link Item#position}] - * @see Matrix#rotate(angle[, center]) - * - * @example {@paperscript} - * // Rotating an item: - * - * // Create a rectangle shaped path with its top left - * // point at {x: 80, y: 25} and a size of {width: 50, height: 50}: - * var path = new Path.Rectangle(new Point(80, 25), new Size(50, 50)); - * path.fillColor = 'black'; - * - * // Rotate the path by 30 degrees: - * path.rotate(30); - * - * @example {@paperscript height=200} - * // Rotating an item around a specific point: - * - * // Create a rectangle shaped path with its top left - * // point at {x: 175, y: 50} and a size of {width: 100, height: 100}: - * var topLeft = new Point(175, 50); - * var size = new Size(100, 100); - * var path = new Path.Rectangle(topLeft, size); - * path.fillColor = 'black'; - * - * // Draw a circle shaped path in the center of the view, - * // to show the rotation point: - * var circle = new Path.Circle({ - * center: view.center, - * radius: 5, - * fillColor: 'white' - * }); - * - * // Each frame rotate the path 3 degrees around the center point - * // of the view: - * function onFrame(event) { - * path.rotate(3, view.center); - * } - */ - - /** - * Scales the item by the given value from its center point, or optionally - * from a supplied point. - * - * @name Item#scale - * @function - * @param {Number} scale the scale factor - * @param {Point} [center={@link Item#position}] - * - * @example {@paperscript} - * // Scaling an item from its center point: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 20: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 20, - * fillColor: 'red' - * }); - * - * // Scale the path by 150% from its center point - * circle.scale(1.5); - * - * @example {@paperscript} - * // Scaling an item from a specific point: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 20: - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 20, - * fillColor: 'red' - * }); - * - * // Scale the path 150% from its bottom left corner - * circle.scale(1.5, circle.bounds.bottomLeft); - */ - /** - * Scales the item by the given values from its center point, or optionally - * from a supplied point. - * - * @name Item#scale - * @function - * @param {Number} hor the horizontal scale factor - * @param {Number} ver the vertical scale factor - * @param {Point} [center={@link Item#position}] - * - * @example {@paperscript} - * // Scaling an item horizontally by 300%: - * - * // Create a circle shaped path at { x: 100, y: 50 } - * // with a radius of 20: - * var circle = new Path.Circle({ - * center: [100, 50], - * radius: 20, - * fillColor: 'red' - * }); - * - * // Scale the path horizontally by 300% - * circle.scale(3, 1); - */ - - // TODO: Add test for item shearing, as it might be behaving oddly. - /** - * Shears the item by the given value from its center point, or optionally - * by a supplied point. - * - * @name Item#shear - * @function - * @param {Point} shear the horizontal and vertical shear factors as a point - * @param {Point} [center={@link Item#position}] - * @see Matrix#shear(shear[, center]) - */ - /** - * Shears the item by the given values from its center point, or optionally - * by a supplied point. - * - * @name Item#shear - * @function - * @param {Number} hor the horizontal shear factor - * @param {Number} ver the vertical shear factor - * @param {Point} [center={@link Item#position}] - * @see Matrix#shear(hor, ver[, center]) - */ - - /** - * Skews the item by the given angles from its center point, or optionally - * by a supplied point. - * - * @name Item#skew - * @function - * @param {Point} skew the horizontal and vertical skew angles in degrees - * @param {Point} [center={@link Item#position}] - * @see Matrix#shear(skew[, center]) - */ - /** - * Skews the item by the given angles from its center point, or optionally - * by a supplied point. - * - * @name Item#skew - * @function - * @param {Number} hor the horizontal skew angle in degrees - * @param {Number} ver the vertical sskew angle in degrees - * @param {Point} [center={@link Item#position}] - * @see Matrix#shear(hor, ver[, center]) - */ - - /** - * Transform the item. - * - * @param {Matrix} matrix the matrix by which the item shall be transformed - */ - // TODO: Implement flags: - // @param {String[]} flags array of any of the following: 'objects', - // 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns', - // 'lines'. Default: ['objects', 'children'] - transform: function(matrix, _applyRecursively, _setApplyMatrix) { - var _matrix = this._matrix, - transformMatrix = matrix && !matrix.isIdentity(), - // If no matrix is provided, or the matrix is the identity, we might - // still have some work to do: _setApplyMatrix or _applyRecursively. - applyMatrix = ( - _setApplyMatrix && this._canApplyMatrix || - this._applyMatrix && ( - // Don't apply _matrix if the result of concatenating with - // matrix would be identity. - transformMatrix || !_matrix.isIdentity() || - // Even if it's an identity matrix, we may still need to - // recursively apply the matrix to children. - _applyRecursively && this._children - ) - ); - // Bail out if there is nothing to do. - if (!transformMatrix && !applyMatrix) - return this; - // Simply prepend the internal matrix with the passed one: - if (transformMatrix) { - // Keep a backup of the last valid state before the matrix becomes - // non-invertible. This is then used again in setBounds to restore. - if (!matrix.isInvertible() && _matrix.isInvertible()) - _matrix._backup = _matrix.getValues(); - // Pass `true` for _dontNotify, as we're handling this after. - _matrix.prepend(matrix, true); - // When a new matrix was applied, we also need to transform gradient - // color points. These always need transforming, regardless of - // #applyMatrix, as they are defined in the parent's coordinate - // system. - // TODO: Introduce options to control whether fills should be - // transformed or not. - var style = this._style, - // Pass true for _dontMerge so we don't recursively transform - // styles on groups' children. - fillColor = style.getFillColor(true), - strokeColor = style.getStrokeColor(true); - if (fillColor) - fillColor.transform(matrix); - if (strokeColor) - strokeColor.transform(matrix); - } - // Call #_transformContent() now, if we need to directly apply the - // internal _matrix transformations to the item's content. - // Application is not possible on Raster, PointText, SymbolItem, since - // the matrix is where the actual transformation state is stored. - - if (applyMatrix && (applyMatrix = this._transformContent( - _matrix, _applyRecursively, _setApplyMatrix))) { - // Pivot is provided in the parent's coordinate system, so transform - // it along too. - var pivot = this._pivot; - if (pivot) - _matrix._transformPoint(pivot, pivot, true); - // Reset the internal matrix to the identity transformation if - // it was possible to apply it, but do not notify owner of change. - _matrix.reset(true); - // Set the internal _applyMatrix flag to true if we're told to - // do so - if (_setApplyMatrix && this._canApplyMatrix) - this._applyMatrix = true; - } - // Calling _changed will clear _bounds and _position, but depending - // on matrix we can calculate and set them again, so preserve them. - var bounds = this._bounds, - position = this._position; - if (transformMatrix || applyMatrix) { - this._changed(/*#=*/Change.MATRIX); - } - // Detect matrices that contain only translations and scaling - // and transform the cached _bounds and _position without having to - // fully recalculate each time. - var decomp = transformMatrix && bounds && matrix.decompose(); - if (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) { - // Transform the old bound by looping through all the cached - // bounds in _bounds and transform each. - for (var key in bounds) { - var cache = bounds[key]; - // If any item involved in the determination of these bounds has - // non-scaling strokes, delete the cache now as it can't be - // preserved through the transformation. - if (cache.nonscaling) { - delete bounds[key]; - } else if (applyMatrix || !cache.internal) { - // If these are internal bounds, only transform them if this - // item applied its matrix. - var rect = cache.rect; - matrix._transformBounds(rect, rect); + /** + * Rotates the item by a given angle around the given center point. + * + * Angles are oriented clockwise and measured in degrees. + * + * @name Item#rotate + * @function + * @param {Number} angle the rotation angle + * @param {Point} [center={@link Item#position}] + * @see Matrix#rotate(angle[, center]) + * + * @example {@paperscript} + * // Rotating an item: + * + * // Create a rectangle shaped path with its top left + * // point at {x: 80, y: 25} and a size of {width: 50, height: 50}: + * var path = new Path.Rectangle(new Point(80, 25), new Size(50, 50)); + * path.fillColor = 'black'; + * + * // Rotate the path by 30 degrees: + * path.rotate(30); + * + * @example {@paperscript height=200} + * // Rotating an item around a specific point: + * + * // Create a rectangle shaped path with its top left + * // point at {x: 175, y: 50} and a size of {width: 100, height: 100}: + * var topLeft = new Point(175, 50); + * var size = new Size(100, 100); + * var path = new Path.Rectangle(topLeft, size); + * path.fillColor = 'black'; + * + * // Draw a circle shaped path in the center of the view, + * // to show the rotation point: + * var circle = new Path.Circle({ + * center: view.center, + * radius: 5, + * fillColor: 'white' + * }); + * + * // Each frame rotate the path 3 degrees around the center point + * // of the view: + * function onFrame(event) { + * path.rotate(3, view.center); + * } + */ + + /** + * Scales the item by the given value from its center point, or optionally + * from a supplied point. + * + * @name Item#scale + * @function + * @param {Number} scale the scale factor + * @param {Point} [center={@link Item#position}] + * + * @example {@paperscript} + * // Scaling an item from its center point: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 20: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 20, + * fillColor: 'red' + * }); + * + * // Scale the path by 150% from its center point + * circle.scale(1.5); + * + * @example {@paperscript} + * // Scaling an item from a specific point: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 20: + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 20, + * fillColor: 'red' + * }); + * + * // Scale the path 150% from its bottom left corner + * circle.scale(1.5, circle.bounds.bottomLeft); + */ + /** + * Scales the item by the given values from its center point, or optionally + * from a supplied point. + * + * @name Item#scale + * @function + * @param {Number} hor the horizontal scale factor + * @param {Number} ver the vertical scale factor + * @param {Point} [center={@link Item#position}] + * + * @example {@paperscript} + * // Scaling an item horizontally by 300%: + * + * // Create a circle shaped path at { x: 100, y: 50 } + * // with a radius of 20: + * var circle = new Path.Circle({ + * center: [100, 50], + * radius: 20, + * fillColor: 'red' + * }); + * + * // Scale the path horizontally by 300% + * circle.scale(3, 1); + */ + + // TODO: Add test for item shearing, as it might be behaving oddly. + /** + * Shears the item by the given value from its center point, or optionally + * by a supplied point. + * + * @name Item#shear + * @function + * @param {Point} shear the horizontal and vertical shear factors as a point + * @param {Point} [center={@link Item#position}] + * @see Matrix#shear(shear[, center]) + */ + /** + * Shears the item by the given values from its center point, or optionally + * by a supplied point. + * + * @name Item#shear + * @function + * @param {Number} hor the horizontal shear factor + * @param {Number} ver the vertical shear factor + * @param {Point} [center={@link Item#position}] + * @see Matrix#shear(hor, ver[, center]) + */ + + /** + * Skews the item by the given angles from its center point, or optionally + * by a supplied point. + * + * @name Item#skew + * @function + * @param {Point} skew the horizontal and vertical skew angles in degrees + * @param {Point} [center={@link Item#position}] + * @see Matrix#shear(skew[, center]) + */ + /** + * Skews the item by the given angles from its center point, or optionally + * by a supplied point. + * + * @name Item#skew + * @function + * @param {Number} hor the horizontal skew angle in degrees + * @param {Number} ver the vertical sskew angle in degrees + * @param {Point} [center={@link Item#position}] + * @see Matrix#shear(hor, ver[, center]) + */ + + /** + * Transform the item. + * + * @param {Matrix} matrix the matrix by which the item shall be transformed + */ + // TODO: Implement flags: + // @param {String[]} flags array of any of the following: 'objects', + // 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns', + // 'lines'. Default: ['objects', 'children'] + transform: function (matrix, _applyRecursively, _setApplyMatrix) { + var _matrix = this._matrix, + transformMatrix = matrix && !matrix.isIdentity(), + // If no matrix is provided, or the matrix is the identity, we might + // still have some work to do: _setApplyMatrix or _applyRecursively. + applyMatrix = + (_setApplyMatrix && this._canApplyMatrix) || + (this._applyMatrix && + // Don't apply _matrix if the result of concatenating with + // matrix would be identity. + (transformMatrix || + !_matrix.isIdentity() || + // Even if it's an identity matrix, we may still need to + // recursively apply the matrix to children. + (_applyRecursively && this._children))); + // Bail out if there is nothing to do. + if (!transformMatrix && !applyMatrix) return this; + // Simply prepend the internal matrix with the passed one: + if (transformMatrix) { + // Keep a backup of the last valid state before the matrix becomes + // non-invertible. This is then used again in setBounds to restore. + if (!matrix.isInvertible() && _matrix.isInvertible()) + _matrix._backup = _matrix.getValues(); + // Pass `true` for _dontNotify, as we're handling this after. + _matrix.prepend(matrix, true); + // When a new matrix was applied, we also need to transform gradient + // color points. These always need transforming, regardless of + // #applyMatrix, as they are defined in the parent's coordinate + // system. + // TODO: Introduce options to control whether fills should be + // transformed or not. + var style = this._style, + // Pass true for _dontMerge so we don't recursively transform + // styles on groups' children. + fillColor = style.getFillColor(true), + strokeColor = style.getStrokeColor(true); + if (fillColor) fillColor.transform(matrix); + if (strokeColor) strokeColor.transform(matrix); } - } - this._bounds = bounds; - // If we have cached bounds, try to determine _position as its - // center. Use _boundsOptions do get the cached default bounds. - var cached = bounds[this._getBoundsCacheKey( - this._boundsOptions || {})]; - if (cached) { - // use this method to handle pivot case (see #1503) - this._position = this._getPositionFromBounds(cached.rect); - } - } else if (transformMatrix && position && this._pivot) { - // If the item has a pivot defined, it means that the default - // position defined as the center of the bounds won't shift with - // arbitrary transformations and we can therefore update _position: - this._position = matrix._transformPoint(position, position); - } - // Allow chaining here, since transform() is related to Matrix functions - return this; - }, + // Call #_transformContent() now, if we need to directly apply the + // internal _matrix transformations to the item's content. + // Application is not possible on Raster, PointText, SymbolItem, since + // the matrix is where the actual transformation state is stored. + + if ( + applyMatrix && + (applyMatrix = this._transformContent( + _matrix, + _applyRecursively, + _setApplyMatrix + )) + ) { + // Pivot is provided in the parent's coordinate system, so transform + // it along too. + var pivot = this._pivot; + if (pivot) _matrix._transformPoint(pivot, pivot, true); + // Reset the internal matrix to the identity transformation if + // it was possible to apply it, but do not notify owner of change. + _matrix.reset(true); + // Set the internal _applyMatrix flag to true if we're told to + // do so + if (_setApplyMatrix && this._canApplyMatrix) + this._applyMatrix = true; + } + // Calling _changed will clear _bounds and _position, but depending + // on matrix we can calculate and set them again, so preserve them. + var bounds = this._bounds, + position = this._position; + if (transformMatrix || applyMatrix) { + this._changed(/*#=*/ Change.MATRIX); + } + // Detect matrices that contain only translations and scaling + // and transform the cached _bounds and _position without having to + // fully recalculate each time. + var decomp = transformMatrix && bounds && matrix.decompose(); + if ( + decomp && + decomp.skewing.isZero() && + decomp.rotation % 90 === 0 + ) { + // Transform the old bound by looping through all the cached + // bounds in _bounds and transform each. + for (var key in bounds) { + var cache = bounds[key]; + // If any item involved in the determination of these bounds has + // non-scaling strokes, delete the cache now as it can't be + // preserved through the transformation. + if (cache.nonscaling) { + delete bounds[key]; + } else if (applyMatrix || !cache.internal) { + // If these are internal bounds, only transform them if this + // item applied its matrix. + var rect = cache.rect; + matrix._transformBounds(rect, rect); + } + } + this._bounds = bounds; + // If we have cached bounds, try to determine _position as its + // center. Use _boundsOptions do get the cached default bounds. + var cached = + bounds[ + this._getBoundsCacheKey(this._boundsOptions || {}) + ]; + if (cached) { + // use this method to handle pivot case (see #1503) + this._position = this._getPositionFromBounds( + cached.rect + ); + } + } else if (transformMatrix && position && this._pivot) { + // If the item has a pivot defined, it means that the default + // position defined as the center of the bounds won't shift with + // arbitrary transformations and we can therefore update _position: + this._position = matrix._transformPoint(position, position); + } + // Allow chaining here, since transform() is related to Matrix functions + return this; + }, - _transformContent: function(matrix, applyRecursively, setApplyMatrix) { - var children = this._children; - if (children) { - for (var i = 0, l = children.length; i < l; i++) { - children[i].transform(matrix, applyRecursively, setApplyMatrix); - } - return true; - } - }, + _transformContent: function ( + matrix, + applyRecursively, + setApplyMatrix + ) { + var children = this._children; + if (children) { + for (var i = 0, l = children.length; i < l; i++) { + children[i].transform( + matrix, + applyRecursively, + setApplyMatrix + ); + } + return true; + } + }, - /** - * Converts the specified point from global project coordinate space to the - * item's own local coordinate space. - * - * @param {Point} point the point to be transformed - * @return {Point} the transformed point as a new instance - */ - globalToLocal: function(/* point */) { - return this.getGlobalMatrix(true)._inverseTransform( - Point.read(arguments)); - }, + /** + * Converts the specified point from global project coordinate space to the + * item's own local coordinate space. + * + * @param {Point} point the point to be transformed + * @return {Point} the transformed point as a new instance + */ + globalToLocal: function (/* point */) { + return this.getGlobalMatrix(true)._inverseTransform( + Point.read(arguments) + ); + }, - /** - * Converts the specified point from the item's own local coordinate space - * to the global project coordinate space. - * - * @param {Point} point the point to be transformed - * @return {Point} the transformed point as a new instance - */ - localToGlobal: function(/* point */) { - return this.getGlobalMatrix(true)._transformPoint( - Point.read(arguments)); - }, + /** + * Converts the specified point from the item's own local coordinate space + * to the global project coordinate space. + * + * @param {Point} point the point to be transformed + * @return {Point} the transformed point as a new instance + */ + localToGlobal: function (/* point */) { + return this.getGlobalMatrix(true)._transformPoint( + Point.read(arguments) + ); + }, - /** - * Converts the specified point from the parent's coordinate space to - * item's own local coordinate space. - * - * @param {Point} point the point to be transformed - * @return {Point} the transformed point as a new instance - */ - parentToLocal: function(/* point */) { - return this._matrix._inverseTransform(Point.read(arguments)); - }, + /** + * Converts the specified point from the parent's coordinate space to + * item's own local coordinate space. + * + * @param {Point} point the point to be transformed + * @return {Point} the transformed point as a new instance + */ + parentToLocal: function (/* point */) { + return this._matrix._inverseTransform(Point.read(arguments)); + }, - /** - * Converts the specified point from the item's own local coordinate space - * to the parent's coordinate space. - * - * @param {Point} point the point to be transformed - * @return {Point} the transformed point as a new instance - */ - localToParent: function(/* point */) { - return this._matrix._transformPoint(Point.read(arguments)); - }, + /** + * Converts the specified point from the item's own local coordinate space + * to the parent's coordinate space. + * + * @param {Point} point the point to be transformed + * @return {Point} the transformed point as a new instance + */ + localToParent: function (/* point */) { + return this._matrix._transformPoint(Point.read(arguments)); + }, - /** - * Transform the item so that its {@link #bounds} fit within the specified - * rectangle, without changing its aspect ratio. - * - * @param {Rectangle} rectangle - * @param {Boolean} [fill=false] - * - * @example {@paperscript height=100} - * // Fitting an item to the bounding rectangle of another item's bounding - * // rectangle: - * - * // Create a rectangle shaped path with its top left corner - * // at {x: 80, y: 25} and a size of {width: 75, height: 50}: - * var path = new Path.Rectangle({ - * point: [80, 25], - * size: [75, 50], - * fillColor: 'black' - * }); - * - * // Create a circle shaped path with its center at {x: 80, y: 50} - * // and a radius of 30. - * var circlePath = new Path.Circle({ - * center: [80, 50], - * radius: 30, - * fillColor: 'red' - * }); - * - * // Fit the circlePath to the bounding rectangle of - * // the rectangular path: - * circlePath.fitBounds(path.bounds); - * - * @example {@paperscript height=100} - * // Fitting an item to the bounding rectangle of another item's bounding - * // rectangle with the fill parameter set to true: - * - * // Create a rectangle shaped path with its top left corner - * // at {x: 80, y: 25} and a size of {width: 75, height: 50}: - * var path = new Path.Rectangle({ - * point: [80, 25], - * size: [75, 50], - * fillColor: 'black' - * }); - * - * // Create a circle shaped path with its center at {x: 80, y: 50} - * // and a radius of 30. - * var circlePath = new Path.Circle({ - * center: [80, 50], - * radius: 30, - * fillColor: 'red' - * }); - * - * // Fit the circlePath to the bounding rectangle of - * // the rectangular path: - * circlePath.fitBounds(path.bounds, true); - * - * @example {@paperscript height=200} - * // Fitting an item to the bounding rectangle of the view - * var path = new Path.Circle({ - * center: [80, 50], - * radius: 30, - * fillColor: 'red' - * }); - * - * // Fit the path to the bounding rectangle of the view: - * path.fitBounds(view.bounds); - */ - fitBounds: function(rectangle, fill) { - // TODO: Think about passing options with various ways of defining - // fitting. Compare with InDesign fitting to see possible options. - rectangle = Rectangle.read(arguments); - var bounds = this.getBounds(), - itemRatio = bounds.height / bounds.width, - rectRatio = rectangle.height / rectangle.width, - scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio) - ? rectangle.width / bounds.width - : rectangle.height / bounds.height, - newBounds = new Rectangle(new Point(), - new Size(bounds.width * scale, bounds.height * scale)); - newBounds.setCenter(rectangle.getCenter()); - this.setBounds(newBounds); - } -}), /** @lends Item# */{ - /** - * {@grouptitle Event Handlers} - * - * Item level handler function to be called on each frame of an animation. - * The function receives an event object which contains information about - * the frame event: - * - * @option event.count {Number} the number of times the frame event was - * fired - * @option event.time {Number} the total amount of time passed since the - * first frame event in seconds - * @option event.delta {Number} the time passed in seconds since the last - * frame event - * - * @name Item#onFrame - * @property - * @type ?Function - * @see View#onFrame - * - * @example {@paperscript} - * // Creating an animation: - * - * // Create a rectangle shaped path with its top left point at: - * // {x: 50, y: 25} and a size of {width: 50, height: 50} - * var path = new Path.Rectangle(new Point(50, 25), new Size(50, 50)); - * path.fillColor = 'black'; - * - * path.onFrame = function(event) { - * // Every frame, rotate the path by 3 degrees: - * this.rotate(3); - * } - */ + /** + * Transform the item so that its {@link #bounds} fit within the specified + * rectangle, without changing its aspect ratio. + * + * @param {Rectangle} rectangle + * @param {Boolean} [fill=false] + * + * @example {@paperscript height=100} + * // Fitting an item to the bounding rectangle of another item's bounding + * // rectangle: + * + * // Create a rectangle shaped path with its top left corner + * // at {x: 80, y: 25} and a size of {width: 75, height: 50}: + * var path = new Path.Rectangle({ + * point: [80, 25], + * size: [75, 50], + * fillColor: 'black' + * }); + * + * // Create a circle shaped path with its center at {x: 80, y: 50} + * // and a radius of 30. + * var circlePath = new Path.Circle({ + * center: [80, 50], + * radius: 30, + * fillColor: 'red' + * }); + * + * // Fit the circlePath to the bounding rectangle of + * // the rectangular path: + * circlePath.fitBounds(path.bounds); + * + * @example {@paperscript height=100} + * // Fitting an item to the bounding rectangle of another item's bounding + * // rectangle with the fill parameter set to true: + * + * // Create a rectangle shaped path with its top left corner + * // at {x: 80, y: 25} and a size of {width: 75, height: 50}: + * var path = new Path.Rectangle({ + * point: [80, 25], + * size: [75, 50], + * fillColor: 'black' + * }); + * + * // Create a circle shaped path with its center at {x: 80, y: 50} + * // and a radius of 30. + * var circlePath = new Path.Circle({ + * center: [80, 50], + * radius: 30, + * fillColor: 'red' + * }); + * + * // Fit the circlePath to the bounding rectangle of + * // the rectangular path: + * circlePath.fitBounds(path.bounds, true); + * + * @example {@paperscript height=200} + * // Fitting an item to the bounding rectangle of the view + * var path = new Path.Circle({ + * center: [80, 50], + * radius: 30, + * fillColor: 'red' + * }); + * + * // Fit the path to the bounding rectangle of the view: + * path.fitBounds(view.bounds); + */ + fitBounds: function (rectangle, fill) { + // TODO: Think about passing options with various ways of defining + // fitting. Compare with InDesign fitting to see possible options. + rectangle = Rectangle.read(arguments); + var bounds = this.getBounds(), + itemRatio = bounds.height / bounds.width, + rectRatio = rectangle.height / rectangle.width, + scale = ( + fill ? itemRatio > rectRatio : itemRatio < rectRatio + ) + ? rectangle.width / bounds.width + : rectangle.height / bounds.height, + newBounds = new Rectangle( + new Point(), + new Size(bounds.width * scale, bounds.height * scale) + ); + newBounds.setCenter(rectangle.getCenter()); + this.setBounds(newBounds); + }, + } + ), + /** @lends Item# */ { + /** + * {@grouptitle Event Handlers} + * + * Item level handler function to be called on each frame of an animation. + * The function receives an event object which contains information about + * the frame event: + * + * @option event.count {Number} the number of times the frame event was + * fired + * @option event.time {Number} the total amount of time passed since the + * first frame event in seconds + * @option event.delta {Number} the time passed in seconds since the last + * frame event + * + * @name Item#onFrame + * @property + * @type ?Function + * @see View#onFrame + * + * @example {@paperscript} + * // Creating an animation: + * + * // Create a rectangle shaped path with its top left point at: + * // {x: 50, y: 25} and a size of {width: 50, height: 50} + * var path = new Path.Rectangle(new Point(50, 25), new Size(50, 50)); + * path.fillColor = 'black'; + * + * path.onFrame = function(event) { + * // Every frame, rotate the path by 3 degrees: + * this.rotate(3); + * } + */ - /** - * The function to be called when the mouse button is pushed down on the - * item. The function receives a {@link MouseEvent} object which contains - * information about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onMouseDown - * @property - * @type ?Function - * @see View#onMouseDown - * - * @example {@paperscript} - * // Press the mouse button down on the circle shaped path, to make it red: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse is pressed on the item, - * // set its fill color to red: - * path.onMouseDown = function(event) { - * this.fillColor = 'red'; - * } - * - * @example {@paperscript} - * // Press the mouse on the circle shaped paths to remove them: - * - * // Loop 30 times: - * for (var i = 0; i < 30; i++) { - * // Create a circle shaped path at a random position - * // in the view: - * var path = new Path.Circle({ - * center: Point.random() * view.size, - * radius: 25, - * fillColor: 'black', - * strokeColor: 'white' - * }); - * - * // When the mouse is pressed on the item, remove it: - * path.onMouseDown = function(event) { - * this.remove(); - * } - * } - */ + /** + * The function to be called when the mouse button is pushed down on the + * item. The function receives a {@link MouseEvent} object which contains + * information about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onMouseDown + * @property + * @type ?Function + * @see View#onMouseDown + * + * @example {@paperscript} + * // Press the mouse button down on the circle shaped path, to make it red: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse is pressed on the item, + * // set its fill color to red: + * path.onMouseDown = function(event) { + * this.fillColor = 'red'; + * } + * + * @example {@paperscript} + * // Press the mouse on the circle shaped paths to remove them: + * + * // Loop 30 times: + * for (var i = 0; i < 30; i++) { + * // Create a circle shaped path at a random position + * // in the view: + * var path = new Path.Circle({ + * center: Point.random() * view.size, + * radius: 25, + * fillColor: 'black', + * strokeColor: 'white' + * }); + * + * // When the mouse is pressed on the item, remove it: + * path.onMouseDown = function(event) { + * this.remove(); + * } + * } + */ - /** - * The function to be called when the mouse position changes while the mouse - * is being dragged over the item. The function receives a {@link - * MouseEvent} object which contains information about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onMouseDrag - * @property - * @type ?Function - * @see View#onMouseDrag - * - * @example {@paperscript height=240} - * // Press and drag the mouse on the blue circle to move it: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 50, - * fillColor: 'blue' - * }); - * - * // Install a drag event handler that moves the path along. - * path.onMouseDrag = function(event) { - * path.position += event.delta; - * } - */ + /** + * The function to be called when the mouse position changes while the mouse + * is being dragged over the item. The function receives a {@link + * MouseEvent} object which contains information about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onMouseDrag + * @property + * @type ?Function + * @see View#onMouseDrag + * + * @example {@paperscript height=240} + * // Press and drag the mouse on the blue circle to move it: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 50, + * fillColor: 'blue' + * }); + * + * // Install a drag event handler that moves the path along. + * path.onMouseDrag = function(event) { + * path.position += event.delta; + * } + */ - /** - * The function to be called when the mouse button is released over the item. - * The function receives a {@link MouseEvent} object which contains - * information about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onMouseUp - * @property - * @type ?Function - * @see View#onMouseUp - * - * @example {@paperscript} - * // Release the mouse button over the circle shaped path, to make it red: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse is released over the item, - * // set its fill color to red: - * path.onMouseUp = function(event) { - * this.fillColor = 'red'; - * } - */ + /** + * The function to be called when the mouse button is released over the item. + * The function receives a {@link MouseEvent} object which contains + * information about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onMouseUp + * @property + * @type ?Function + * @see View#onMouseUp + * + * @example {@paperscript} + * // Release the mouse button over the circle shaped path, to make it red: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse is released over the item, + * // set its fill color to red: + * path.onMouseUp = function(event) { + * this.fillColor = 'red'; + * } + */ - /** - * The function to be called when the mouse clicks on the item. The function - * receives a {@link MouseEvent} object which contains information about the - * mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onClick - * @property - * @type ?Function - * @see View#onClick - * - * @example {@paperscript} - * // Click on the circle shaped path, to make it red: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse is clicked on the item, - * // set its fill color to red: - * path.onClick = function(event) { - * this.fillColor = 'red'; - * } - * - * @example {@paperscript} - * // Click on the circle shaped paths to remove them: - * - * // Loop 30 times: - * for (var i = 0; i < 30; i++) { - * // Create a circle shaped path at a random position - * // in the view: - * var path = new Path.Circle({ - * center: Point.random() * view.size, - * radius: 25, - * fillColor: 'black', - * strokeColor: 'white' - * }); - * - * // When the mouse clicks on the item, remove it: - * path.onClick = function(event) { - * this.remove(); - * } - * } - */ + /** + * The function to be called when the mouse clicks on the item. The function + * receives a {@link MouseEvent} object which contains information about the + * mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onClick + * @property + * @type ?Function + * @see View#onClick + * + * @example {@paperscript} + * // Click on the circle shaped path, to make it red: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse is clicked on the item, + * // set its fill color to red: + * path.onClick = function(event) { + * this.fillColor = 'red'; + * } + * + * @example {@paperscript} + * // Click on the circle shaped paths to remove them: + * + * // Loop 30 times: + * for (var i = 0; i < 30; i++) { + * // Create a circle shaped path at a random position + * // in the view: + * var path = new Path.Circle({ + * center: Point.random() * view.size, + * radius: 25, + * fillColor: 'black', + * strokeColor: 'white' + * }); + * + * // When the mouse clicks on the item, remove it: + * path.onClick = function(event) { + * this.remove(); + * } + * } + */ - /** - * The function to be called when the mouse double clicks on the item. The - * function receives a {@link MouseEvent} object which contains information - * about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onDoubleClick - * @property - * @type ?Function - * @see View#onDoubleClick - * - * @example {@paperscript} - * // Double click on the circle shaped path, to make it red: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse is double clicked on the item, - * // set its fill color to red: - * path.onDoubleClick = function(event) { - * this.fillColor = 'red'; - * } - * - * @example {@paperscript} - * // Double click on the circle shaped paths to remove them: - * - * // Loop 30 times: - * for (var i = 0; i < 30; i++) { - * // Create a circle shaped path at a random position - * // in the view: - * var path = new Path.Circle({ - * center: Point.random() * view.size, - * radius: 25, - * fillColor: 'black', - * strokeColor: 'white' - * }); - * - * // When the mouse is double clicked on the item, remove it: - * path.onDoubleClick = function(event) { - * this.remove(); - * } - * } - */ + /** + * The function to be called when the mouse double clicks on the item. The + * function receives a {@link MouseEvent} object which contains information + * about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onDoubleClick + * @property + * @type ?Function + * @see View#onDoubleClick + * + * @example {@paperscript} + * // Double click on the circle shaped path, to make it red: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse is double clicked on the item, + * // set its fill color to red: + * path.onDoubleClick = function(event) { + * this.fillColor = 'red'; + * } + * + * @example {@paperscript} + * // Double click on the circle shaped paths to remove them: + * + * // Loop 30 times: + * for (var i = 0; i < 30; i++) { + * // Create a circle shaped path at a random position + * // in the view: + * var path = new Path.Circle({ + * center: Point.random() * view.size, + * radius: 25, + * fillColor: 'black', + * strokeColor: 'white' + * }); + * + * // When the mouse is double clicked on the item, remove it: + * path.onDoubleClick = function(event) { + * this.remove(); + * } + * } + */ - /** - * The function to be called repeatedly while the mouse moves over the item. - * The function receives a {@link MouseEvent} object which contains - * information about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onMouseMove - * @property - * @type ?Function - * @see View#onMouseMove - * - * @example {@paperscript} - * // Move over the circle shaped path, to change its opacity: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse moves on top of the item, set its opacity - * // to a random value between 0 and 1: - * path.onMouseMove = function(event) { - * this.opacity = Math.random(); - * } - */ + /** + * The function to be called repeatedly while the mouse moves over the item. + * The function receives a {@link MouseEvent} object which contains + * information about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onMouseMove + * @property + * @type ?Function + * @see View#onMouseMove + * + * @example {@paperscript} + * // Move over the circle shaped path, to change its opacity: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse moves on top of the item, set its opacity + * // to a random value between 0 and 1: + * path.onMouseMove = function(event) { + * this.opacity = Math.random(); + * } + */ - /** - * The function to be called when the mouse moves over the item. This - * function will only be called again, once the mouse moved outside of the - * item first. The function receives a {@link MouseEvent} object which - * contains information about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onMouseEnter - * @property - * @type ?Function - * @see View#onMouseEnter - * - * @example {@paperscript} - * // When you move the mouse over the item, its fill color is set to red. - * // When you move the mouse outside again, its fill color is set back - * // to black. - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse enters the item, set its fill color to red: - * path.onMouseEnter = function(event) { - * this.fillColor = 'red'; - * } - * - * // When the mouse leaves the item, set its fill color to black: - * path.onMouseLeave = function(event) { - * this.fillColor = 'black'; - * } - * @example {@paperscript} - * // When you click the mouse, you create new circle shaped items. When you - * // move the mouse over the item, its fill color is set to red. When you - * // move the mouse outside again, its fill color is set back - * // to black. - * - * function enter(event) { - * this.fillColor = 'red'; - * } - * - * function leave(event) { - * this.fillColor = 'black'; - * } - * - * // When the mouse is pressed: - * function onMouseDown(event) { - * // Create a circle shaped path at the position of the mouse: - * var path = new Path.Circle(event.point, 25); - * path.fillColor = 'black'; - * - * // When the mouse enters the item, set its fill color to red: - * path.onMouseEnter = enter; - * - * // When the mouse leaves the item, set its fill color to black: - * path.onMouseLeave = leave; - * } - */ + /** + * The function to be called when the mouse moves over the item. This + * function will only be called again, once the mouse moved outside of the + * item first. The function receives a {@link MouseEvent} object which + * contains information about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onMouseEnter + * @property + * @type ?Function + * @see View#onMouseEnter + * + * @example {@paperscript} + * // When you move the mouse over the item, its fill color is set to red. + * // When you move the mouse outside again, its fill color is set back + * // to black. + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse enters the item, set its fill color to red: + * path.onMouseEnter = function(event) { + * this.fillColor = 'red'; + * } + * + * // When the mouse leaves the item, set its fill color to black: + * path.onMouseLeave = function(event) { + * this.fillColor = 'black'; + * } + * @example {@paperscript} + * // When you click the mouse, you create new circle shaped items. When you + * // move the mouse over the item, its fill color is set to red. When you + * // move the mouse outside again, its fill color is set back + * // to black. + * + * function enter(event) { + * this.fillColor = 'red'; + * } + * + * function leave(event) { + * this.fillColor = 'black'; + * } + * + * // When the mouse is pressed: + * function onMouseDown(event) { + * // Create a circle shaped path at the position of the mouse: + * var path = new Path.Circle(event.point, 25); + * path.fillColor = 'black'; + * + * // When the mouse enters the item, set its fill color to red: + * path.onMouseEnter = enter; + * + * // When the mouse leaves the item, set its fill color to black: + * path.onMouseLeave = leave; + * } + */ - /** - * The function to be called when the mouse moves out of the item. - * The function receives a {@link MouseEvent} object which contains - * information about the mouse event. - * Note that such mouse events bubble up the scene graph hierarchy and will - * reach the view, unless they are stopped with {@link - * Event#stopPropagation()} or by returning `false` from the handler. - * - * @name Item#onMouseLeave - * @property - * @type ?Function - * @see View#onMouseLeave - * - * @example {@paperscript} - * // Move the mouse over the circle shaped path and then move it out - * // of it again to set its fill color to red: - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse leaves the item, set its fill color to red: - * path.onMouseLeave = function(event) { - * this.fillColor = 'red'; - * } - */ + /** + * The function to be called when the mouse moves out of the item. + * The function receives a {@link MouseEvent} object which contains + * information about the mouse event. + * Note that such mouse events bubble up the scene graph hierarchy and will + * reach the view, unless they are stopped with {@link + * Event#stopPropagation()} or by returning `false` from the handler. + * + * @name Item#onMouseLeave + * @property + * @type ?Function + * @see View#onMouseLeave + * + * @example {@paperscript} + * // Move the mouse over the circle shaped path and then move it out + * // of it again to set its fill color to red: + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse leaves the item, set its fill color to red: + * path.onMouseLeave = function(event) { + * this.fillColor = 'red'; + * } + */ - /** - * {@grouptitle Event Handling} - * - * Attaches an event handler to the item. - * - * @name Item#on - * @function - * @param {String} type the type of event: {@values 'frame', mousedown', - * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', - * 'mouseenter', 'mouseleave'} - * @param {Function} function the function to be called when the event - * occurs, receiving a {@link MouseEvent} or {@link Event} object as its - * sole argument - * @return {Item} this item itself, so calls can be chained - * @chainable - * - * @example {@paperscript} - * // Change the fill color of the path to red when the mouse enters its - * // shape and back to black again, when it leaves its shape. - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25, - * fillColor: 'black' - * }); - * - * // When the mouse enters the item, set its fill color to red: - * path.on('mouseenter', function() { - * this.fillColor = 'red'; - * }); - * - * // When the mouse leaves the item, set its fill color to black: - * path.on('mouseleave', function() { - * this.fillColor = 'black'; - * }); - */ - /** - * Attaches one or more event handlers to the item. - * - * @name Item#on - * @function - * @param {Object} object an object containing one or more of the following - * properties: {@values frame, mousedown, mouseup, mousedrag, click, - * doubleclick, mousemove, mouseenter, mouseleave} - * @return {Item} this item itself, so calls can be chained - * @chainable - * - * @example {@paperscript} - * // Change the fill color of the path to red when the mouse enters its - * // shape and back to black again, when it leaves its shape. - * - * // Create a circle shaped path at the center of the view: - * var path = new Path.Circle({ - * center: view.center, - * radius: 25 - * }); - * path.fillColor = 'black'; - * - * // When the mouse enters the item, set its fill color to red: - * path.on({ - * mouseenter: function(event) { - * this.fillColor = 'red'; - * }, - * mouseleave: function(event) { - * this.fillColor = 'black'; - * } - * }); - * @example {@paperscript} - * // When you click the mouse, you create new circle shaped items. When you - * // move the mouse over the item, its fill color is set to red. When you - * // move the mouse outside again, its fill color is set black. - * - * var pathHandlers = { - * mouseenter: function(event) { - * this.fillColor = 'red'; - * }, - * mouseleave: function(event) { - * this.fillColor = 'black'; - * } - * } - * - * // When the mouse is pressed: - * function onMouseDown(event) { - * // Create a circle shaped path at the position of the mouse: - * var path = new Path.Circle({ - * center: event.point, - * radius: 25, - * fillColor: 'black' - * }); - * - * // Attach the handers inside the object literal to the path: - * path.on(pathHandlers); - * } - */ + /** + * {@grouptitle Event Handling} + * + * Attaches an event handler to the item. + * + * @name Item#on + * @function + * @param {String} type the type of event: {@values 'frame', mousedown', + * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', + * 'mouseenter', 'mouseleave'} + * @param {Function} function the function to be called when the event + * occurs, receiving a {@link MouseEvent} or {@link Event} object as its + * sole argument + * @return {Item} this item itself, so calls can be chained + * @chainable + * + * @example {@paperscript} + * // Change the fill color of the path to red when the mouse enters its + * // shape and back to black again, when it leaves its shape. + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25, + * fillColor: 'black' + * }); + * + * // When the mouse enters the item, set its fill color to red: + * path.on('mouseenter', function() { + * this.fillColor = 'red'; + * }); + * + * // When the mouse leaves the item, set its fill color to black: + * path.on('mouseleave', function() { + * this.fillColor = 'black'; + * }); + */ + /** + * Attaches one or more event handlers to the item. + * + * @name Item#on + * @function + * @param {Object} object an object containing one or more of the following + * properties: {@values frame, mousedown, mouseup, mousedrag, click, + * doubleclick, mousemove, mouseenter, mouseleave} + * @return {Item} this item itself, so calls can be chained + * @chainable + * + * @example {@paperscript} + * // Change the fill color of the path to red when the mouse enters its + * // shape and back to black again, when it leaves its shape. + * + * // Create a circle shaped path at the center of the view: + * var path = new Path.Circle({ + * center: view.center, + * radius: 25 + * }); + * path.fillColor = 'black'; + * + * // When the mouse enters the item, set its fill color to red: + * path.on({ + * mouseenter: function(event) { + * this.fillColor = 'red'; + * }, + * mouseleave: function(event) { + * this.fillColor = 'black'; + * } + * }); + * @example {@paperscript} + * // When you click the mouse, you create new circle shaped items. When you + * // move the mouse over the item, its fill color is set to red. When you + * // move the mouse outside again, its fill color is set black. + * + * var pathHandlers = { + * mouseenter: function(event) { + * this.fillColor = 'red'; + * }, + * mouseleave: function(event) { + * this.fillColor = 'black'; + * } + * } + * + * // When the mouse is pressed: + * function onMouseDown(event) { + * // Create a circle shaped path at the position of the mouse: + * var path = new Path.Circle({ + * center: event.point, + * radius: 25, + * fillColor: 'black' + * }); + * + * // Attach the handers inside the object literal to the path: + * path.on(pathHandlers); + * } + */ - /** - * Detach an event handler from the item. - * - * @name Item#off - * @function - * @param {String} type the type of event: {@values 'frame', mousedown', - * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', - * 'mouseenter', 'mouseleave'} - * @param {Function} function the function to be detached - * @return {Item} this item itself, so calls can be chained - * @chainable - */ - /** - * Detach one or more event handlers to the item. - * - * @name Item#off - * @function - * @param {Object} object an object containing one or more of the following - * properties: {@values frame, mousedown, mouseup, mousedrag, click, - * doubleclick, mousemove, mouseenter, mouseleave} - * @return {Item} this item itself, so calls can be chained - * @chainable - */ + /** + * Detach an event handler from the item. + * + * @name Item#off + * @function + * @param {String} type the type of event: {@values 'frame', mousedown', + * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', + * 'mouseenter', 'mouseleave'} + * @param {Function} function the function to be detached + * @return {Item} this item itself, so calls can be chained + * @chainable + */ + /** + * Detach one or more event handlers to the item. + * + * @name Item#off + * @function + * @param {Object} object an object containing one or more of the following + * properties: {@values frame, mousedown, mouseup, mousedrag, click, + * doubleclick, mousemove, mouseenter, mouseleave} + * @return {Item} this item itself, so calls can be chained + * @chainable + */ - /** - * Emit an event on the item. - * - * @name Item#emit - * @function - * @param {String} type the type of event: {@values 'frame', mousedown', - * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', - * 'mouseenter', 'mouseleave'} - * @param {Object} event an object literal containing properties describing - * the event - * @return {Boolean} {@true if the event had listeners} - */ + /** + * Emit an event on the item. + * + * @name Item#emit + * @function + * @param {String} type the type of event: {@values 'frame', mousedown', + * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', + * 'mouseenter', 'mouseleave'} + * @param {Object} event an object literal containing properties describing + * the event + * @return {Boolean} {@true if the event had listeners} + */ - /** - * Check if the item has one or more event handlers of the specified type. - * - * @name Item#responds - * @function - * @param {String} type the type of event: {@values 'frame', mousedown', - * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', - * 'mouseenter', 'mouseleave'} - * @return {Boolean} {@true if the item has one or more event handlers of - * the specified type} - */ + /** + * Check if the item has one or more event handlers of the specified type. + * + * @name Item#responds + * @function + * @param {String} type the type of event: {@values 'frame', mousedown', + * 'mouseup', 'mousedrag', 'click', 'doubleclick', 'mousemove', + * 'mouseenter', 'mouseleave'} + * @return {Boolean} {@true if the item has one or more event handlers of + * the specified type} + */ - /** - * Private method that sets Path related styles on the canvas context. - * Not defined in Path as it is required by other classes too, - * e.g. PointText. - */ - _setStyles: function(ctx, param, viewMatrix) { - // We can access internal properties since we're only using this on - // items without children, where styles would be merged. - var style = this._style, - matrix = this._matrix; - if (style.hasFill()) { - ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix); - } - if (style.hasStroke()) { - ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix); - ctx.lineWidth = style.getStrokeWidth(); - var strokeJoin = style.getStrokeJoin(), - strokeCap = style.getStrokeCap(), - miterLimit = style.getMiterLimit(); - if (strokeJoin) - ctx.lineJoin = strokeJoin; - if (strokeCap) - ctx.lineCap = strokeCap; - if (miterLimit) - ctx.miterLimit = miterLimit; - if (paper.support.nativeDash) { - var dashArray = style.getDashArray(), - dashOffset = style.getDashOffset(); - if (dashArray && dashArray.length) { - if ('setLineDash' in ctx) { - ctx.setLineDash(dashArray); - ctx.lineDashOffset = dashOffset; - } else { - ctx.mozDash = dashArray; - ctx.mozDashOffset = dashOffset; + /** + * Private method that sets Path related styles on the canvas context. + * Not defined in Path as it is required by other classes too, + * e.g. PointText. + */ + _setStyles: function (ctx, param, viewMatrix) { + // We can access internal properties since we're only using this on + // items without children, where styles would be merged. + var style = this._style, + matrix = this._matrix; + if (style.hasFill()) { + ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix); + } + if (style.hasStroke()) { + ctx.strokeStyle = style + .getStrokeColor() + .toCanvasStyle(ctx, matrix); + ctx.lineWidth = style.getStrokeWidth(); + var strokeJoin = style.getStrokeJoin(), + strokeCap = style.getStrokeCap(), + miterLimit = style.getMiterLimit(); + if (strokeJoin) ctx.lineJoin = strokeJoin; + if (strokeCap) ctx.lineCap = strokeCap; + if (miterLimit) ctx.miterLimit = miterLimit; + if (paper.support.nativeDash) { + var dashArray = style.getDashArray(), + dashOffset = style.getDashOffset(); + if (dashArray && dashArray.length) { + if ("setLineDash" in ctx) { + ctx.setLineDash(dashArray); + ctx.lineDashOffset = dashOffset; + } else { + ctx.mozDash = dashArray; + ctx.mozDashOffset = dashOffset; + } } } } - } - if (style.hasShadow()) { - // In Canvas, shadows unfortunately ignore all transformations - // completely. As almost no browser supports ctx.currentTransform, - // we need to calculate our own here, and then use it to transform - // the shadow-blur and offset accordingly. - var pixelRatio = param.pixelRatio || 1, - mx = viewMatrix._shiftless().prepend( - new Matrix().scale(pixelRatio, pixelRatio)), - // Transform the blur value as a vector and use its new length: - blur = mx.transform(new Point(style.getShadowBlur(), 0)), - offset = mx.transform(this.getShadowOffset()); - ctx.shadowColor = style.getShadowColor().toCanvasStyle(ctx); - ctx.shadowBlur = blur.getLength(); - ctx.shadowOffsetX = offset.x; - ctx.shadowOffsetY = offset.y; - } - }, + if (style.hasShadow()) { + // In Canvas, shadows unfortunately ignore all transformations + // completely. As almost no browser supports ctx.currentTransform, + // we need to calculate our own here, and then use it to transform + // the shadow-blur and offset accordingly. + var pixelRatio = param.pixelRatio || 1, + mx = viewMatrix + ._shiftless() + .prepend(new Matrix().scale(pixelRatio, pixelRatio)), + // Transform the blur value as a vector and use its new length: + blur = mx.transform(new Point(style.getShadowBlur(), 0)), + offset = mx.transform(this.getShadowOffset()); + ctx.shadowColor = style.getShadowColor().toCanvasStyle(ctx); + ctx.shadowBlur = blur.getLength(); + ctx.shadowOffsetX = offset.x; + ctx.shadowOffsetY = offset.y; + } + }, - draw: function(ctx, param, parentStrokeMatrix) { - // Each time the project gets drawn, it's _updateVersion is increased. - // Keep the _updateVersion of drawn items in sync, so we have an easy - // way to know for which selected items we need to draw selection info. - var updateVersion = this._updateVersion = this._project._updateVersion; - // Now bail out if no actual drawing is required. - if (!this._visible || this._opacity === 0) - return; - // Keep calculating the current global matrix, by keeping a history - // and pushing / popping as we go along. - var matrices = param.matrices, - viewMatrix = param.viewMatrix, - matrix = this._matrix, - globalMatrix = matrices[matrices.length - 1].appended(matrix); - // If this item is not invertible, do not draw it. It appears to be a - // good idea generally to not draw in such circumstances, e.g. SVG - // handles it the same way. - if (!globalMatrix.isInvertible()) - return; - - // Since globalMatrix does not take the view's matrix into account (we - // could have multiple views with different zooms), we may have to - // prepend the view's matrix. - // NOTE: viewMatrix is only provided if it isn't the identity matrix. - viewMatrix = viewMatrix ? viewMatrix.appended(globalMatrix) + draw: function (ctx, param, parentStrokeMatrix) { + // Each time the project gets drawn, it's _updateVersion is increased. + // Keep the _updateVersion of drawn items in sync, so we have an easy + // way to know for which selected items we need to draw selection info. + var updateVersion = (this._updateVersion = + this._project._updateVersion); + // Now bail out if no actual drawing is required. + if (!this._visible || this._opacity === 0) return; + // Keep calculating the current global matrix, by keeping a history + // and pushing / popping as we go along. + var matrices = param.matrices, + viewMatrix = param.viewMatrix, + matrix = this._matrix, + globalMatrix = matrices[matrices.length - 1].appended(matrix); + // If this item is not invertible, do not draw it. It appears to be a + // good idea generally to not draw in such circumstances, e.g. SVG + // handles it the same way. + if (!globalMatrix.isInvertible()) return; + + // Since globalMatrix does not take the view's matrix into account (we + // could have multiple views with different zooms), we may have to + // prepend the view's matrix. + // NOTE: viewMatrix is only provided if it isn't the identity matrix. + viewMatrix = viewMatrix + ? viewMatrix.appended(globalMatrix) : globalMatrix; - // Only keep track of transformation if told so. See Project#draw() - matrices.push(globalMatrix); - if (param.updateMatrix) { - this._globalMatrix = globalMatrix; - } + // Only keep track of transformation if told so. See Project#draw() + matrices.push(globalMatrix); + if (param.updateMatrix) { + this._globalMatrix = globalMatrix; + } - // If the item has a blendMode or is defining an opacity, draw it on - // a temporary canvas first and composite the canvas afterwards. - // Paths with an opacity < 1 that both define a fillColor - // and strokeColor also need to be drawn on a temporary canvas - // first, since otherwise their stroke is drawn half transparent - // over their fill. - // Exclude Raster items since they never draw a stroke and handle - // opacity by themselves (they also don't call _setStyles) - var blendMode = this._blendMode, - opacity = Numerical.clamp(this._opacity, 0, 1), - normalBlend = blendMode === 'normal', - nativeBlend = BlendMode.nativeModes[blendMode], - // Determine if we can draw directly, or if we need to draw into a - // separate canvas and then composite onto the main canvas. - direct = normalBlend && opacity === 1 - || param.dontStart // e.g. CompoundPath - || param.clip + // If the item has a blendMode or is defining an opacity, draw it on + // a temporary canvas first and composite the canvas afterwards. + // Paths with an opacity < 1 that both define a fillColor + // and strokeColor also need to be drawn on a temporary canvas + // first, since otherwise their stroke is drawn half transparent + // over their fill. + // Exclude Raster items since they never draw a stroke and handle + // opacity by themselves (they also don't call _setStyles) + var blendMode = this._blendMode, + opacity = Numerical.clamp(this._opacity, 0, 1), + normalBlend = blendMode === "normal", + nativeBlend = BlendMode.nativeModes[blendMode], + // Determine if we can draw directly, or if we need to draw into a + // separate canvas and then composite onto the main canvas. + direct = + (normalBlend && opacity === 1) || + param.dontStart || // e.g. CompoundPath + param.clip || // If native blending is possible, see if the item allows it - || (nativeBlend || normalBlend && opacity < 1) - && this._canComposite(), - pixelRatio = param.pixelRatio || 1, - mainCtx, itemOffset, prevOffset; - if (!direct) { - // Apply the parent's global matrix to the calculation of correct - // bounds. - var bounds = this.getStrokeBounds(viewMatrix); - if (!bounds.width || !bounds.height) { - // Item won't be drawn so its global matrix need to be removed - // from the stack (#1561). - matrices.pop(); - return; + ((nativeBlend || (normalBlend && opacity < 1)) && + this._canComposite()), + pixelRatio = param.pixelRatio || 1, + mainCtx, + itemOffset, + prevOffset; + if (!direct) { + // Apply the parent's global matrix to the calculation of correct + // bounds. + var bounds = this.getStrokeBounds(viewMatrix); + if (!bounds.width || !bounds.height) { + // Item won't be drawn so its global matrix need to be removed + // from the stack (#1561). + matrices.pop(); + return; + } + // Store previous offset and save the main context, so we can + // draw onto it later. + prevOffset = param.offset; + // Floor the offset and ceil the size, so we don't cut off any + // antialiased pixels when drawing onto the temporary canvas. + itemOffset = param.offset = bounds.getTopLeft().floor(); + // Set ctx to the context of the temporary canvas, so we draw onto + // it, instead of the mainCtx. + mainCtx = ctx; + ctx = CanvasProvider.getContext( + bounds.getSize().ceil().add(1).multiply(pixelRatio) + ); + if (pixelRatio !== 1) ctx.scale(pixelRatio, pixelRatio); } - // Store previous offset and save the main context, so we can - // draw onto it later. - prevOffset = param.offset; - // Floor the offset and ceil the size, so we don't cut off any - // antialiased pixels when drawing onto the temporary canvas. - itemOffset = param.offset = bounds.getTopLeft().floor(); - // Set ctx to the context of the temporary canvas, so we draw onto - // it, instead of the mainCtx. - mainCtx = ctx; - ctx = CanvasProvider.getContext(bounds.getSize().ceil().add(1) - .multiply(pixelRatio)); - if (pixelRatio !== 1) - ctx.scale(pixelRatio, pixelRatio); - } - ctx.save(); - // Get the transformation matrix for non-scaling strokes. - var strokeMatrix = parentStrokeMatrix - ? parentStrokeMatrix.appended(matrix) - // pass `true` for dontMerge - : this._canScaleStroke && !this.getStrokeScaling(true) - && viewMatrix, - // If we're drawing into a separate canvas and a clipItem is defined - // for the current rendering loop, we need to draw the clip item - // again. - clip = !direct && param.clipItem, - // If we're drawing with a strokeMatrix, the CTM is reset either way - // so we don't need to set it, except when we also have to draw a - // clipItem. - transform = !strokeMatrix || clip; - // If drawing directly, handle opacity and native blending now, - // otherwise we will do it later when the temporary canvas is composited. - if (direct) { - ctx.globalAlpha = opacity; - if (nativeBlend) - ctx.globalCompositeOperation = blendMode; - } else if (transform) { - // Translate the context so the topLeft of the item is at (0, 0) - // on the temporary canvas. - ctx.translate(-itemOffset.x, -itemOffset.y); - } - if (transform) { - // Apply viewMatrix when drawing into temporary canvas. - (direct ? matrix : viewMatrix).applyToContext(ctx); - } - if (clip) { - param.clipItem.draw(ctx, param.extend({ clip: true })); - } - if (strokeMatrix) { - // Reset the transformation but take HiDPI pixel ratio into account. - ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - // Also offset again when drawing non-directly. - // NOTE: Don't use itemOffset since offset might be from the parent, - // e.g. CompoundPath - var offset = param.offset; - if (offset) - ctx.translate(-offset.x, -offset.y); - } - this._draw(ctx, param, viewMatrix, strokeMatrix); - ctx.restore(); - matrices.pop(); - if (param.clip && !param.dontFinish) { - // Pass fill-rule to handle clipping with compound-paths (#1361). - ctx.clip(this.getFillRule()); - } - // If a temporary canvas was created, composite it onto the main canvas: - if (!direct) { - // Use BlendMode.process even for processing normal blendMode with - // opacity. - BlendMode.process(blendMode, ctx, mainCtx, opacity, + ctx.save(); + // Get the transformation matrix for non-scaling strokes. + var strokeMatrix = parentStrokeMatrix + ? parentStrokeMatrix.appended(matrix) + : // pass `true` for dontMerge + this._canScaleStroke && + !this.getStrokeScaling(true) && + viewMatrix, + // If we're drawing into a separate canvas and a clipItem is defined + // for the current rendering loop, we need to draw the clip item + // again. + clip = !direct && param.clipItem, + // If we're drawing with a strokeMatrix, the CTM is reset either way + // so we don't need to set it, except when we also have to draw a + // clipItem. + transform = !strokeMatrix || clip; + // If drawing directly, handle opacity and native blending now, + // otherwise we will do it later when the temporary canvas is composited. + if (direct) { + ctx.globalAlpha = opacity; + if (nativeBlend) ctx.globalCompositeOperation = blendMode; + } else if (transform) { + // Translate the context so the topLeft of the item is at (0, 0) + // on the temporary canvas. + ctx.translate(-itemOffset.x, -itemOffset.y); + } + if (transform) { + // Apply viewMatrix when drawing into temporary canvas. + (direct ? matrix : viewMatrix).applyToContext(ctx); + } + if (clip) { + param.clipItem.draw(ctx, param.extend({ clip: true })); + } + if (strokeMatrix) { + // Reset the transformation but take HiDPI pixel ratio into account. + ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + // Also offset again when drawing non-directly. + // NOTE: Don't use itemOffset since offset might be from the parent, + // e.g. CompoundPath + var offset = param.offset; + if (offset) ctx.translate(-offset.x, -offset.y); + } + this._draw(ctx, param, viewMatrix, strokeMatrix); + ctx.restore(); + matrices.pop(); + if (param.clip && !param.dontFinish) { + // Pass fill-rule to handle clipping with compound-paths (#1361). + ctx.clip(this.getFillRule()); + } + // If a temporary canvas was created, composite it onto the main canvas: + if (!direct) { + // Use BlendMode.process even for processing normal blendMode with + // opacity. + BlendMode.process( + blendMode, + ctx, + mainCtx, + opacity, // Calculate the pixel offset of the temporary canvas to the // main canvas. We also need to factor in the pixel-ratio. - itemOffset.subtract(prevOffset).multiply(pixelRatio)); - // Return the temporary context, so it can be reused - CanvasProvider.release(ctx); - // Restore previous offset. - param.offset = prevOffset; - } - }, + itemOffset.subtract(prevOffset).multiply(pixelRatio) + ); + // Return the temporary context, so it can be reused + CanvasProvider.release(ctx); + // Restore previous offset. + param.offset = prevOffset; + } + }, - /** - * Checks the _updateVersion of the item to see if it got drawn in the draw - * loop. If the version is out of sync, the item is either not in the scene - * graph anymore or is invisible. - */ - _isUpdated: function(updateVersion) { - var parent = this._parent; - // For compound-paths, use the _updateVersion of the parent, because the - // shape gets drawn at once at might get cached (e.g. Path2D soon). - if (parent instanceof CompoundPath) - return parent._isUpdated(updateVersion); - // In case a parent is visible but isn't drawn (e.g. opacity == 0), the - // _updateVersion of all its children will not be updated, but the - // children should still be considered updated, and selections should be - // drawn for them. Excluded are only items with _visible == false: - var updated = this._updateVersion === updateVersion; - if (!updated && parent && parent._visible - && parent._isUpdated(updateVersion)) { - this._updateVersion = updateVersion; - updated = true; - } - return updated; - }, + /** + * Checks the _updateVersion of the item to see if it got drawn in the draw + * loop. If the version is out of sync, the item is either not in the scene + * graph anymore or is invisible. + */ + _isUpdated: function (updateVersion) { + var parent = this._parent; + // For compound-paths, use the _updateVersion of the parent, because the + // shape gets drawn at once at might get cached (e.g. Path2D soon). + if (parent instanceof CompoundPath) + return parent._isUpdated(updateVersion); + // In case a parent is visible but isn't drawn (e.g. opacity == 0), the + // _updateVersion of all its children will not be updated, but the + // children should still be considered updated, and selections should be + // drawn for them. Excluded are only items with _visible == false: + var updated = this._updateVersion === updateVersion; + if ( + !updated && + parent && + parent._visible && + parent._isUpdated(updateVersion) + ) { + this._updateVersion = updateVersion; + updated = true; + } + return updated; + }, - _drawSelection: function(ctx, matrix, size, selectionItems, updateVersion) { - var selection = this._selection, - itemSelected = selection & /*#=*/ItemSelection.ITEM, - boundsSelected = selection & /*#=*/ItemSelection.BOUNDS - || itemSelected && this._selectBounds, - positionSelected = selection & /*#=*/ItemSelection.POSITION; - if (!this._drawSelected) - itemSelected = false; - if ((itemSelected || boundsSelected || positionSelected) - && this._isUpdated(updateVersion)) { - // Allow definition of selected color on a per item and per - // layer level, with a fallback to #009dec - var layer, - color = this.getSelectedColor(true) || (layer = this.getLayer()) - && layer.getSelectedColor(true), - mx = matrix.appended(this.getGlobalMatrix(true)), - half = size / 2; - ctx.strokeStyle = ctx.fillStyle = color - ? color.toCanvasStyle(ctx) : '#009dec'; - if (itemSelected) - this._drawSelected(ctx, mx, selectionItems); - if (positionSelected) { - // Convert position from the parent's coordinates system to the - // global one: - var pos = this.getPosition(true), - parent = this._parent, - point = parent ? parent.localToGlobal(pos) : pos, - x = point.x, - y = point.y; - ctx.beginPath(); - ctx.arc(x, y, half, 0, Math.PI * 2, true); - ctx.stroke(); - var deltas = [[0, -1], [1, 0], [0, 1], [-1, 0]], - start = half, - end = size + 1; - for (var i = 0; i < 4; i++) { - var delta = deltas[i], - dx = delta[0], - dy = delta[1]; - ctx.moveTo(x + dx * start, y + dy * start); - ctx.lineTo(x + dx * end, y + dy * end); + _drawSelection: function ( + ctx, + matrix, + size, + selectionItems, + updateVersion + ) { + ctx.lineWidth = this._style.selectedWidth; + ctx.lineCap = this._style.selectedCap; + ctx.lineJoin = this._style.selectedJoin; + ctx.miterLimit = this._style.selectedMiterLimit; + var selection = this._selection, + itemSelected = selection & /*#=*/ ItemSelection.ITEM, + boundsSelected = + selection & /*#=*/ ItemSelection.BOUNDS || + (itemSelected && this._selectBounds), + positionSelected = selection & /*#=*/ ItemSelection.POSITION; + if (!this._drawSelected) itemSelected = false; + if ( + (itemSelected || boundsSelected || positionSelected) && + this._isUpdated(updateVersion) + ) { + // Allow definition of selected color on a per item and per + // layer level, with a fallback to #009dec + var layer, + color = + this.getSelectedColor(true) || + ((layer = this.getLayer()) && + layer.getSelectedColor(true)), + mx = matrix.appended(this.getGlobalMatrix(true)), + half = size / 2; + ctx.strokeStyle = ctx.fillStyle = color + ? color.toCanvasStyle(ctx) + : "#009dec"; + if (itemSelected) this._drawSelected(ctx, mx, selectionItems); + if (positionSelected) { + // Convert position from the parent's coordinates system to the + // global one: + var pos = this.getPosition(true), + parent = this._parent, + point = parent ? parent.localToGlobal(pos) : pos, + x = point.x, + y = point.y; + ctx.beginPath(); + ctx.arc(x, y, half, 0, Math.PI * 2, true); ctx.stroke(); + var deltas = [ + [0, -1], + [1, 0], + [0, 1], + [-1, 0], + ], + start = half, + end = size + 1; + for (var i = 0; i < 4; i++) { + var delta = deltas[i], + dx = delta[0], + dy = delta[1]; + ctx.moveTo(x + dx * start, y + dy * start); + ctx.lineTo(x + dx * end, y + dy * end); + ctx.stroke(); + } } - } - if (boundsSelected) { - var coords = mx._transformCorners(this.getInternalBounds()); - // Now draw a rectangle that connects the transformed - // bounds corners, and draw the corners. - ctx.beginPath(); - for (var i = 0; i < 8; i++) { - ctx[!i ? 'moveTo' : 'lineTo'](coords[i], coords[++i]); - } - ctx.closePath(); - ctx.stroke(); - for (var i = 0; i < 8; i++) { - ctx.fillRect(coords[i] - half, coords[++i] - half, - size, size); + if (boundsSelected) { + var coords = mx._transformCorners(this.getInternalBounds()); + // Now draw a rectangle that connects the transformed + // bounds corners, and draw the corners. + ctx.beginPath(); + for (var i = 0; i < 8; i++) { + ctx[!i ? "moveTo" : "lineTo"](coords[i], coords[++i]); + } + ctx.closePath(); + ctx.stroke(); + for (var i = 0; i < 8; i++) { + ctx.fillRect( + coords[i] - half, + coords[++i] - half, + size, + size + ); + } } } - } - }, - - _canComposite: function() { - return false; - } -}, Base.each(['down', 'drag', 'up', 'move'], function(key) { - this['removeOn' + Base.capitalize(key)] = function() { - var hash = {}; - hash[key] = true; - return this.removeOn(hash); - }; -}, /** @lends Item# */{ - /** - * {@grouptitle Remove On Event} - * - * Removes the item when the events specified in the passed options object - * occur. - * - * @option options.move {Boolean) remove the item when the next {@link - * Tool#onMouseMove} event is fired. - * - * @option options.drag {Boolena) remove the item when the next {@link - * Tool#onMouseDrag} event is fired. - * - * @option options.down {Boolean) remove the item when the next {@link - * Tool#onMouseDown} event is fired. - * - * @option options.up {Boolean) remove the item when the next {@link - * Tool#onMouseUp} event is fired. - * - * @name Item#removeOn - * @function - * @param {Object} options - * - * @example {@paperscript height=200} - * // Click and drag below: - * function onMouseDrag(event) { - * // Create a circle shaped path at the mouse position, - * // with a radius of 10: - * var path = new Path.Circle({ - * center: event.point, - * radius: 10, - * fillColor: 'black' - * }); - * - * // Remove the path on the next onMouseDrag or onMouseDown event: - * path.removeOn({ - * drag: true, - * down: true - * }); - * } - */ - - /** - * Removes the item when the next {@link Tool#onMouseMove} event is fired. - * - * @name Item#removeOnMove - * @function - * - * @example {@paperscript height=200} - * // Move your mouse below: - * function onMouseMove(event) { - * // Create a circle shaped path at the mouse position, - * // with a radius of 10: - * var path = new Path.Circle({ - * center: event.point, - * radius: 10, - * fillColor: 'black' - * }); - * - * // On the next move event, automatically remove the path: - * path.removeOnMove(); - * } - */ - - /** - * Removes the item when the next {@link Tool#onMouseDown} event is fired. - * - * @name Item#removeOnDown - * @function - * - * @example {@paperscript height=200} - * // Click a few times below: - * function onMouseDown(event) { - * // Create a circle shaped path at the mouse position, - * // with a radius of 10: - * var path = new Path.Circle({ - * center: event.point, - * radius: 10, - * fillColor: 'black' - * }); - * - * // Remove the path, next time the mouse is pressed: - * path.removeOnDown(); - * } - */ - - /** - * Removes the item when the next {@link Tool#onMouseDrag} event is fired. - * - * @name Item#removeOnDrag - * @function - * - * @example {@paperscript height=200} - * // Click and drag below: - * function onMouseDrag(event) { - * // Create a circle shaped path at the mouse position, - * // with a radius of 10: - * var path = new Path.Circle({ - * center: event.point, - * radius: 10, - * fillColor: 'black' - * }); - * - * // On the next drag event, automatically remove the path: - * path.removeOnDrag(); - * } - */ + }, - /** - * Removes the item when the next {@link Tool#onMouseUp} event is fired. - * - * @name Item#removeOnUp - * @function - * - * @example {@paperscript height=200} - * // Click a few times below: - * function onMouseDown(event) { - * // Create a circle shaped path at the mouse position, - * // with a radius of 10: - * var path = new Path.Circle({ - * center: event.point, - * radius: 10, - * fillColor: 'black' - * }); - * - * // Remove the path, when the mouse is released: - * path.removeOnUp(); - * } - */ - // TODO: implement Item#removeOnFrame - removeOn: function(obj) { - for (var name in obj) { - if (obj[name]) { - var key = 'mouse' + name, - project = this._project, - sets = project._removeSets = project._removeSets || {}; - sets[key] = sets[key] || {}; - sets[key][this._id] = this; - } + _canComposite: function () { + return false; + }, + }, + Base.each( + ["down", "drag", "up", "move"], + function (key) { + this["removeOn" + Base.capitalize(key)] = function () { + var hash = {}; + hash[key] = true; + return this.removeOn(hash); + }; + }, + /** @lends Item# */ { + /** + * {@grouptitle Remove On Event} + * + * Removes the item when the events specified in the passed options object + * occur. + * + * @option options.move {Boolean) remove the item when the next {@link + * Tool#onMouseMove} event is fired. + * + * @option options.drag {Boolena) remove the item when the next {@link + * Tool#onMouseDrag} event is fired. + * + * @option options.down {Boolean) remove the item when the next {@link + * Tool#onMouseDown} event is fired. + * + * @option options.up {Boolean) remove the item when the next {@link + * Tool#onMouseUp} event is fired. + * + * @name Item#removeOn + * @function + * @param {Object} options + * + * @example {@paperscript height=200} + * // Click and drag below: + * function onMouseDrag(event) { + * // Create a circle shaped path at the mouse position, + * // with a radius of 10: + * var path = new Path.Circle({ + * center: event.point, + * radius: 10, + * fillColor: 'black' + * }); + * + * // Remove the path on the next onMouseDrag or onMouseDown event: + * path.removeOn({ + * drag: true, + * down: true + * }); + * } + */ + + /** + * Removes the item when the next {@link Tool#onMouseMove} event is fired. + * + * @name Item#removeOnMove + * @function + * + * @example {@paperscript height=200} + * // Move your mouse below: + * function onMouseMove(event) { + * // Create a circle shaped path at the mouse position, + * // with a radius of 10: + * var path = new Path.Circle({ + * center: event.point, + * radius: 10, + * fillColor: 'black' + * }); + * + * // On the next move event, automatically remove the path: + * path.removeOnMove(); + * } + */ + + /** + * Removes the item when the next {@link Tool#onMouseDown} event is fired. + * + * @name Item#removeOnDown + * @function + * + * @example {@paperscript height=200} + * // Click a few times below: + * function onMouseDown(event) { + * // Create a circle shaped path at the mouse position, + * // with a radius of 10: + * var path = new Path.Circle({ + * center: event.point, + * radius: 10, + * fillColor: 'black' + * }); + * + * // Remove the path, next time the mouse is pressed: + * path.removeOnDown(); + * } + */ + + /** + * Removes the item when the next {@link Tool#onMouseDrag} event is fired. + * + * @name Item#removeOnDrag + * @function + * + * @example {@paperscript height=200} + * // Click and drag below: + * function onMouseDrag(event) { + * // Create a circle shaped path at the mouse position, + * // with a radius of 10: + * var path = new Path.Circle({ + * center: event.point, + * radius: 10, + * fillColor: 'black' + * }); + * + * // On the next drag event, automatically remove the path: + * path.removeOnDrag(); + * } + */ + + /** + * Removes the item when the next {@link Tool#onMouseUp} event is fired. + * + * @name Item#removeOnUp + * @function + * + * @example {@paperscript height=200} + * // Click a few times below: + * function onMouseDown(event) { + * // Create a circle shaped path at the mouse position, + * // with a radius of 10: + * var path = new Path.Circle({ + * center: event.point, + * radius: 10, + * fillColor: 'black' + * }); + * + * // Remove the path, when the mouse is released: + * path.removeOnUp(); + * } + */ + // TODO: implement Item#removeOnFrame + removeOn: function (obj) { + for (var name in obj) { + if (obj[name]) { + var key = "mouse" + name, + project = this._project, + sets = (project._removeSets = + project._removeSets || {}); + sets[key] = sets[key] || {}; + sets[key][this._id] = this; + } + } + return this; + }, } - return this; - } -}), /** @lends Item# */{ - /** - * {@grouptitle Tweening Functions} - * - * Tween item between two states. - * - * @name Item#tween - * - * @option options.duration {Number} the duration of the tweening - * @option [options.easing='linear'] {Function|String} an easing function or the type - * of the easing: {@values 'linear' 'easeInQuad' 'easeOutQuad' - * 'easeInOutQuad' 'easeInCubic' 'easeOutCubic' 'easeInOutCubic' - * 'easeInQuart' 'easeOutQuart' 'easeInOutQuart' 'easeInQuint' - * 'easeOutQuint' 'easeInOutQuint'} - * @option [options.start=true] {Boolean} whether to start tweening automatically - * - * @function - * @param {Object} from the state at the start of the tweening - * @param {Object} to the state at the end of the tweening - * @param {Object|Number} options the options or the duration - * @return {Tween} - * - * @example {@paperscript height=100} - * // Tween fillColor: - * var path = new Path.Circle({ - * radius: view.bounds.height * 0.4, - * center: view.center - * }); - * path.tween( - * { fillColor: 'blue' }, - * { fillColor: 'red' }, - * 3000 - * ); - * @example {@paperscript height=100} - * // Tween rotation: - * var path = new Shape.Rectangle({ - * fillColor: 'red', - * center: [50, view.center.y], - * size: [60, 60] - * }); - * path.tween({ - * rotation: 180, - * 'position.x': view.bounds.width - 50, - * 'fillColor.hue': '+= 90' - * }, { - * easing: 'easeInOutCubic', - * duration: 2000 - * }); - */ - /** - * Tween item to a state. - * - * @name Item#tween - * - * @function - * @param {Object} to the state at the end of the tweening - * @param {Object|Number} options the options or the duration - * @return {Tween} - * - * @example {@paperscript height=200} - * // Tween a nested property with relative values - * var path = new Path.Rectangle({ - * size: [100, 100], - * position: view.center, - * fillColor: 'red', - * }); - * - * var delta = { x: path.bounds.width / 2, y: 0 }; - * - * path.tween({ - * 'segments[1].point': ['+=', delta], - * 'segments[2].point.x': '-= 50' - * }, 3000); - * - * @see Item#tween(from, to, options) - */ - /** - * Tween item. - * - * @name Item#tween - * - * @function - * @param {Object|Number} options the options or the duration - * @return {Tween} - * - * @see Item#tween(from, to, options) - * - * @example {@paperscript height=100} - * // Start an empty tween and just use the update callback: - * var path = new Path.Circle({ - * fillColor: 'blue', - * radius: view.bounds.height * 0.4, - * center: view.center, - * }); - * var pathFrom = path.clone({ insert: false }) - * var pathTo = new Path.Rectangle({ - * position: view.center, - * rectangle: path.bounds, - * insert: false - * }); - * path.tween(2000).onUpdate = function(event) { - * path.interpolate(pathFrom, pathTo, event.factor) - * }; - */ - tween: function(from, to, options) { - if (!options) { - // If there are only two or one arguments, shift arguments to the - // left by one (omit `from`): - options = to; - to = from; - from = null; + ), + /** @lends Item# */ { + /** + * {@grouptitle Tweening Functions} + * + * Tween item between two states. + * + * @name Item#tween + * + * @option options.duration {Number} the duration of the tweening + * @option [options.easing='linear'] {Function|String} an easing function or the type + * of the easing: {@values 'linear' 'easeInQuad' 'easeOutQuad' + * 'easeInOutQuad' 'easeInCubic' 'easeOutCubic' 'easeInOutCubic' + * 'easeInQuart' 'easeOutQuart' 'easeInOutQuart' 'easeInQuint' + * 'easeOutQuint' 'easeInOutQuint'} + * @option [options.start=true] {Boolean} whether to start tweening automatically + * + * @function + * @param {Object} from the state at the start of the tweening + * @param {Object} to the state at the end of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @example {@paperscript height=100} + * // Tween fillColor: + * var path = new Path.Circle({ + * radius: view.bounds.height * 0.4, + * center: view.center + * }); + * path.tween( + * { fillColor: 'blue' }, + * { fillColor: 'red' }, + * 3000 + * ); + * @example {@paperscript height=100} + * // Tween rotation: + * var path = new Shape.Rectangle({ + * fillColor: 'red', + * center: [50, view.center.y], + * size: [60, 60] + * }); + * path.tween({ + * rotation: 180, + * 'position.x': view.bounds.width - 50, + * 'fillColor.hue': '+= 90' + * }, { + * easing: 'easeInOutCubic', + * duration: 2000 + * }); + */ + /** + * Tween item to a state. + * + * @name Item#tween + * + * @function + * @param {Object} to the state at the end of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @example {@paperscript height=200} + * // Tween a nested property with relative values + * var path = new Path.Rectangle({ + * size: [100, 100], + * position: view.center, + * fillColor: 'red', + * }); + * + * var delta = { x: path.bounds.width / 2, y: 0 }; + * + * path.tween({ + * 'segments[1].point': ['+=', delta], + * 'segments[2].point.x': '-= 50' + * }, 3000); + * + * @see Item#tween(from, to, options) + */ + /** + * Tween item. + * + * @name Item#tween + * + * @function + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @see Item#tween(from, to, options) + * + * @example {@paperscript height=100} + * // Start an empty tween and just use the update callback: + * var path = new Path.Circle({ + * fillColor: 'blue', + * radius: view.bounds.height * 0.4, + * center: view.center, + * }); + * var pathFrom = path.clone({ insert: false }) + * var pathTo = new Path.Rectangle({ + * position: view.center, + * rectangle: path.bounds, + * insert: false + * }); + * path.tween(2000).onUpdate = function(event) { + * path.interpolate(pathFrom, pathTo, event.factor) + * }; + */ + tween: function (from, to, options) { if (!options) { + // If there are only two or one arguments, shift arguments to the + // left by one (omit `from`): options = to; - to = null; + to = from; + from = null; + if (!options) { + options = to; + to = null; + } } - } - var easing = options && options.easing, - start = options && options.start, - duration = options != null && ( - typeof options === 'number' ? options : options.duration - ), - tween = new Tween(this, from, to, duration, easing, start); - function onFrame(event) { - tween._handleFrame(event.time * 1000); - if (!tween.running) { - this.off('frame', onFrame); + var easing = options && options.easing, + start = options && options.start, + duration = + options != null && + (typeof options === "number" ? options : options.duration), + tween = new Tween(this, from, to, duration, easing, start); + function onFrame(event) { + tween._handleFrame(event.time * 1000); + if (!tween.running) { + this.off("frame", onFrame); + } } - } - if (duration) { - this.on('frame', onFrame); - } - return tween; - }, + if (duration) { + this.on("frame", onFrame); + } + return tween; + }, - /** - * - * Tween item to a state. - * - * @function - * @param {Object} to the state at the end of the tweening - * @param {Object|Number} options the options or the duration - * @return {Tween} - * - * @see Item#tween(to, options) - */ - tweenTo: function(to, options) { - return this.tween(null, to, options); - }, + /** + * + * Tween item to a state. + * + * @function + * @param {Object} to the state at the end of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @see Item#tween(to, options) + */ + tweenTo: function (to, options) { + return this.tween(null, to, options); + }, - /** - * - * Tween item from a state to its state before the tweening. - * - * @function - * @param {Object} from the state at the start of the tweening - * @param {Object|Number} options the options or the duration - * @return {Tween} - * - * @see Item#tween(from, to, options) - * - * @example {@paperscript height=100} - * // Tween fillColor from red to the path's initial fillColor: - * var path = new Path.Circle({ - * fillColor: 'blue', - * radius: view.bounds.height * 0.4, - * center: view.center - * }); - * path.tweenFrom({ fillColor: 'red' }, { duration: 1000 }); - */ - tweenFrom: function(from, options) { - return this.tween(from, null, options); + /** + * + * Tween item from a state to its state before the tweening. + * + * @function + * @param {Object} from the state at the start of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @see Item#tween(from, to, options) + * + * @example {@paperscript height=100} + * // Tween fillColor from red to the path's initial fillColor: + * var path = new Path.Circle({ + * fillColor: 'blue', + * radius: view.bounds.height * 0.4, + * center: view.center + * }); + * path.tweenFrom({ fillColor: 'red' }, { duration: 1000 }); + */ + tweenFrom: function (from, options) { + return this.tween(from, null, options); + }, } -}); +); diff --git a/src/item/Project.js b/src/item/Project.js index 232a900f9d..85036015a8 100644 --- a/src/item/Project.js +++ b/src/item/Project.js @@ -30,869 +30,874 @@ * An array of all open projects is accessible through the * {@link PaperScope#projects} variable. */ -var Project = PaperScopeItem.extend(/** @lends Project# */{ - _class: 'Project', - _list: 'projects', - _reference: 'project', - _compactSerialize: true, // Never include the class name for Project - - // TODO: Add arguments to define pages - /** - * Creates a Paper.js project containing one empty {@link Layer}, referenced - * by {@link Project#activeLayer}. - * - * Note that when working with PaperScript, a project is automatically - * created for us and the {@link PaperScope#project} variable points to it. - * - * @param {HTMLCanvasElement|String|Size} element the HTML canvas element - * that should be used as the element for the view, or an ID string by which - * to find the element, or the size of the canvas to be created for usage in - * a web worker. - */ - initialize: function Project(element) { - // Activate straight away by passing true to PaperScopeItem constructor, - // so paper.project is set, as required by Layer and DoumentView - // constructors. - PaperScopeItem.call(this, true); - this._children = []; - this._namedChildren = {}; - this._activeLayer = null; - this._currentStyle = new Style(null, null, this); - // If no view is provided, we create a 1x1 px canvas view just so we - // have something to do size calculations with. - // (e.g. PointText#_getBounds) - this._view = View.create(this, - element || CanvasProvider.getCanvas(1, 1)); - this._selectionItems = {}; - this._selectionCount = 0; - // See Item#draw() for an explanation of _updateVersion - this._updateVersion = 0; - // Change tracking, not in use for now. Activate once required: - // this._changes = []; - // this._changesById = {}; - }, - - _serialize: function(options, dictionary) { - // Just serialize layers to an array for now, they will be unserialized - // into the active project automatically. We might want to add proper - // project serialization later, but deserialization of a layers array - // will always work. - return Base.serialize(this._children, options, true, dictionary); - }, - - /** - * Private notifier that is called whenever a change occurs in the project. - * - * @param {ChangeFlag} flags describes what exactly has changed - * @param {Item} item the item that has caused the change - */ - _changed: function(flags, item) { - if (flags & /*#=*/ChangeFlag.APPEARANCE) { - var view = this._view; - if (view) { - // Never draw changes right away. Simply mark view as "dirty" - // and request an update through view.requestUpdate(). - view._needsUpdate = true; - if (!view._requested && view._autoUpdate) - view.requestUpdate(); +var Project = PaperScopeItem.extend( + /** @lends Project# */ { + _class: "Project", + _list: "projects", + _reference: "project", + _compactSerialize: true, // Never include the class name for Project + + // TODO: Add arguments to define pages + /** + * Creates a Paper.js project containing one empty {@link Layer}, referenced + * by {@link Project#activeLayer}. + * + * Note that when working with PaperScript, a project is automatically + * created for us and the {@link PaperScope#project} variable points to it. + * + * @param {HTMLCanvasElement|String|Size} element the HTML canvas element + * that should be used as the element for the view, or an ID string by which + * to find the element, or the size of the canvas to be created for usage in + * a web worker. + */ + initialize: function Project(element) { + // Activate straight away by passing true to PaperScopeItem constructor, + // so paper.project is set, as required by Layer and DoumentView + // constructors. + PaperScopeItem.call(this, true); + this._children = []; + this._namedChildren = {}; + this._activeLayer = null; + this._currentStyle = new Style(null, null, this); + // If no view is provided, we create a 1x1 px canvas view just so we + // have something to do size calculations with. + // (e.g. PointText#_getBounds) + this._view = View.create( + this, + element || CanvasProvider.getCanvas(1, 1) + ); + this._selectionItems = {}; + this._selectionCount = 0; + // See Item#draw() for an explanation of _updateVersion + this._updateVersion = 0; + // Change tracking, not in use for now. Activate once required: + // this._changes = []; + // this._changesById = {}; + }, + + _serialize: function (options, dictionary) { + // Just serialize layers to an array for now, they will be unserialized + // into the active project automatically. We might want to add proper + // project serialization later, but deserialization of a layers array + // will always work. + return Base.serialize(this._children, options, true, dictionary); + }, + + /** + * Private notifier that is called whenever a change occurs in the project. + * + * @param {ChangeFlag} flags describes what exactly has changed + * @param {Item} item the item that has caused the change + */ + _changed: function (flags, item) { + if (flags & /*#=*/ ChangeFlag.APPEARANCE) { + var view = this._view; + if (view) { + // Never draw changes right away. Simply mark view as "dirty" + // and request an update through view.requestUpdate(). + view._needsUpdate = true; + if (!view._requested && view._autoUpdate) + view.requestUpdate(); + } } - } - // Have project keep track of changed items so they can be iterated. - // This can be used for example to update the SVG tree. Needs to be - // activated in Project - var changes = this._changes; - if (changes && item) { - var changesById = this._changesById, - id = item._id, - entry = changesById[id]; - if (entry) { - entry.flags |= flags; - } else { - changes.push(changesById[id] = { item: item, flags: flags }); + // Have project keep track of changed items so they can be iterated. + // This can be used for example to update the SVG tree. Needs to be + // activated in Project + var changes = this._changes; + if (changes && item) { + var changesById = this._changesById, + id = item._id, + entry = changesById[id]; + if (entry) { + entry.flags |= flags; + } else { + changes.push( + (changesById[id] = { item: item, flags: flags }) + ); + } } - } - }, - - /** - * Activates this project, so all newly created items will be placed - * in it. - * - * @name Project#activate - * @function - */ - - /** - * Clears the project by removing all {@link Project#layers}. - */ - clear: function() { - var children = this._children; - for (var i = children.length - 1; i >= 0; i--) - children[i].remove(); - }, - - /** - * Checks whether the project has any content or not. - * - * @return {Boolean} - */ - isEmpty: function() { - return !this._children.length; - }, - - /** - * Removes this project from the {@link PaperScope#projects} list, and also - * removes its view, if one was defined. - */ - remove: function remove() { - if (!remove.base.call(this)) - return false; - if (this._view) - this._view.remove(); - return true; - }, - - /** - * The reference to the project's view. - * - * @bean - * @type View - */ - getView: function() { - return this._view; - }, - - /** - * The currently active path style. All selected items and newly - * created items will be styled with this style. - * - * @bean - * @type Style - * - * @example {@paperscript} - * project.currentStyle = { - * fillColor: 'red', - * strokeColor: 'black', - * strokeWidth: 5 - * } - * - * // The following paths will take over all style properties of - * // the current style: - * var path = new Path.Circle(new Point(75, 50), 30); - * var path2 = new Path.Circle(new Point(175, 50), 20); - * - * @example {@paperscript} - * project.currentStyle.fillColor = 'red'; - * - * // The following path will take over the fill color we just set: - * var path = new Path.Circle(new Point(75, 50), 30); - * var path2 = new Path.Circle(new Point(175, 50), 20); - */ - getCurrentStyle: function() { - return this._currentStyle; - }, - - setCurrentStyle: function(style) { - // TODO: Style selected items with the style: - this._currentStyle.set(style); - }, - - /** - * The index of the project in the {@link PaperScope#projects} list. - * - * @bean - * @type Number - */ - getIndex: function() { - return this._index; - }, - - /** - * Gives access to the project's configurable options. - * - * @bean - * @type Object - * @deprecated use {@link PaperScope#settings} instead. - */ - getOptions: function() { - return this._scope.settings; - }, - - /** - * {@grouptitle Project Content} - * - * The layers contained within the project. - * - * @bean - * @type Layer[] - */ - getLayers: function() { - return this._children; - }, - - // TODO: Define #setLayers()? - - /** - * The layer which is currently active. New items will be created on this - * layer by default. - * - * @bean - * @type Layer - */ - getActiveLayer: function() { - return this._activeLayer || new Layer({ project: this, insert: true }); - }, - - /** - * The symbol definitions shared by all symbol items contained place ind - * project. - * - * @bean - * @type SymbolDefinition[] - */ - getSymbolDefinitions: function() { - var definitions = [], - ids = {}; - this.getItems({ - class: SymbolItem, - match: function(item) { - var definition = item._definition, - id = definition._id; - if (!ids[id]) { - ids[id] = true; - definitions.push(definition); + }, + + /** + * Activates this project, so all newly created items will be placed + * in it. + * + * @name Project#activate + * @function + */ + + /** + * Clears the project by removing all {@link Project#layers}. + */ + clear: function () { + var children = this._children; + for (var i = children.length - 1; i >= 0; i--) children[i].remove(); + }, + + /** + * Checks whether the project has any content or not. + * + * @return {Boolean} + */ + isEmpty: function () { + return !this._children.length; + }, + + /** + * Removes this project from the {@link PaperScope#projects} list, and also + * removes its view, if one was defined. + */ + remove: function remove() { + if (!remove.base.call(this)) return false; + if (this._view) this._view.remove(); + return true; + }, + + /** + * The reference to the project's view. + * + * @bean + * @type View + */ + getView: function () { + return this._view; + }, + + /** + * The currently active path style. All selected items and newly + * created items will be styled with this style. + * + * @bean + * @type Style + * + * @example {@paperscript} + * project.currentStyle = { + * fillColor: 'red', + * strokeColor: 'black', + * strokeWidth: 5 + * } + * + * // The following paths will take over all style properties of + * // the current style: + * var path = new Path.Circle(new Point(75, 50), 30); + * var path2 = new Path.Circle(new Point(175, 50), 20); + * + * @example {@paperscript} + * project.currentStyle.fillColor = 'red'; + * + * // The following path will take over the fill color we just set: + * var path = new Path.Circle(new Point(75, 50), 30); + * var path2 = new Path.Circle(new Point(175, 50), 20); + */ + getCurrentStyle: function () { + return this._currentStyle; + }, + + setCurrentStyle: function (style) { + // TODO: Style selected items with the style: + this._currentStyle.set(style); + }, + + /** + * The index of the project in the {@link PaperScope#projects} list. + * + * @bean + * @type Number + */ + getIndex: function () { + return this._index; + }, + + /** + * Gives access to the project's configurable options. + * + * @bean + * @type Object + * @deprecated use {@link PaperScope#settings} instead. + */ + getOptions: function () { + return this._scope.settings; + }, + + /** + * {@grouptitle Project Content} + * + * The layers contained within the project. + * + * @bean + * @type Layer[] + */ + getLayers: function () { + return this._children; + }, + + // TODO: Define #setLayers()? + + /** + * The layer which is currently active. New items will be created on this + * layer by default. + * + * @bean + * @type Layer + */ + getActiveLayer: function () { + return ( + this._activeLayer || new Layer({ project: this, insert: true }) + ); + }, + + /** + * The symbol definitions shared by all symbol items contained place ind + * project. + * + * @bean + * @type SymbolDefinition[] + */ + getSymbolDefinitions: function () { + var definitions = [], + ids = {}; + this.getItems({ + class: SymbolItem, + match: function (item) { + var definition = item._definition, + id = definition._id; + if (!ids[id]) { + ids[id] = true; + definitions.push(definition); + } + return false; // No need to collect them. + }, + }); + return definitions; + }, + + /** + * @bean + * @deprecated use {@link #symbolDefinitions} instead. + */ + getSymbols: "getSymbolDefinitions", + + /** + * The selected items contained within the project. + * + * @bean + * @type Item[] + */ + getSelectedItems: function () { + // TODO: Return groups if their children are all selected, and filter + // out their children from the list. + // TODO: The order of these items should be that of their drawing order. + var selectionItems = this._selectionItems, + items = []; + for (var id in selectionItems) { + var item = selectionItems[id], + selection = item._selection; + if ( + selection & /*#=*/ ItemSelection.ITEM && + item.isInserted() + ) { + items.push(item); + } else if (!selection) { + this._updateSelection(item); } - return false; // No need to collect them. } - }); - return definitions; - }, - - /** - * @bean - * @deprecated use {@link #symbolDefinitions} instead. - */ - getSymbols: 'getSymbolDefinitions', - - /** - * The selected items contained within the project. - * - * @bean - * @type Item[] - */ - getSelectedItems: function() { - // TODO: Return groups if their children are all selected, and filter - // out their children from the list. - // TODO: The order of these items should be that of their drawing order. - var selectionItems = this._selectionItems, - items = []; - for (var id in selectionItems) { - var item = selectionItems[id], - selection = item._selection; - if ((selection & /*#=*/ItemSelection.ITEM) && item.isInserted()) { - items.push(item); - } else if (!selection) { - this._updateSelection(item); + return items; + }, + // TODO: Implement setSelectedItems? + + _updateSelection: function (item) { + var id = item._id, + selectionItems = this._selectionItems; + if (item._selection) { + if (selectionItems[id] !== item) { + this._selectionCount++; + selectionItems[id] = item; + } + } else if (selectionItems[id] === item) { + this._selectionCount--; + delete selectionItems[id]; } - } - return items; - }, - // TODO: Implement setSelectedItems? - - _updateSelection: function(item) { - var id = item._id, - selectionItems = this._selectionItems; - if (item._selection) { - if (selectionItems[id] !== item) { - this._selectionCount++; - selectionItems[id] = item; + }, + + /** + * Selects all items in the project. + */ + selectAll: function () { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + children[i].setFullySelected(true); + }, + + /** + * Deselects all selected items in the project. + */ + deselectAll: function () { + var selectionItems = this._selectionItems; + for (var i in selectionItems) + selectionItems[i].setFullySelected(false); + }, + + /** + * {@grouptitle Hierarchy Operations} + * + * Adds the specified layer at the end of the this project's {@link #layers} + * list. + * + * @param {Layer} layer the layer to be added to the project + * @return {Layer} the added layer, or `null` if adding was not possible + */ + addLayer: function (layer) { + return this.insertLayer(undefined, layer); + }, + + /** + * Inserts the specified layer at the specified index in this project's + * {@link #layers} list. + * + * @param {Number} index the index at which to insert the layer + * @param {Layer} layer the layer to be inserted in the project + * @return {Layer} the added layer, or `null` if adding was not possible + */ + insertLayer: function (index, layer) { + if (layer instanceof Layer) { + // Notify parent of change. Don't notify item itself yet, + // as we're doing so when adding it to the new owner below. + layer._remove(false, true); + Base.splice(this._children, [layer], index, 0); + layer._setProject(this, true); + // Set the name again to make sure all name lookup structures + // are kept in sync. + var name = layer._name; + if (name) layer.setName(name); + // See Item#_remove() for an explanation of this: + if (this._changes) layer._changed(/*#=*/ Change.INSERTION); + // TODO: this._changed(/*#=*/Change.LAYERS); + // Also activate this layer if there was none before + if (!this._activeLayer) this._activeLayer = layer; + } else { + layer = null; } - } else if (selectionItems[id] === item) { - this._selectionCount--; - delete selectionItems[id]; - } - }, - - /** - * Selects all items in the project. - */ - selectAll: function() { - var children = this._children; - for (var i = 0, l = children.length; i < l; i++) - children[i].setFullySelected(true); - }, - - /** - * Deselects all selected items in the project. - */ - deselectAll: function() { - var selectionItems = this._selectionItems; - for (var i in selectionItems) - selectionItems[i].setFullySelected(false); - }, - - /** - * {@grouptitle Hierarchy Operations} - * - * Adds the specified layer at the end of the this project's {@link #layers} - * list. - * - * @param {Layer} layer the layer to be added to the project - * @return {Layer} the added layer, or `null` if adding was not possible - */ - addLayer: function(layer) { - return this.insertLayer(undefined, layer); - }, - - /** - * Inserts the specified layer at the specified index in this project's - * {@link #layers} list. - * - * @param {Number} index the index at which to insert the layer - * @param {Layer} layer the layer to be inserted in the project - * @return {Layer} the added layer, or `null` if adding was not possible - */ - insertLayer: function(index, layer) { - if (layer instanceof Layer) { - // Notify parent of change. Don't notify item itself yet, - // as we're doing so when adding it to the new owner below. - layer._remove(false, true); - Base.splice(this._children, [layer], index, 0); - layer._setProject(this, true); - // Set the name again to make sure all name lookup structures - // are kept in sync. - var name = layer._name; - if (name) - layer.setName(name); - // See Item#_remove() for an explanation of this: - if (this._changes) - layer._changed(/*#=*/Change.INSERTION); - // TODO: this._changed(/*#=*/Change.LAYERS); - // Also activate this layer if there was none before - if (!this._activeLayer) - this._activeLayer = layer; - } else { - layer = null; - } - return layer; - }, - - // Project#_insertItem() and Item#_insertItem() are helper functions called - // in Item#copyTo(), and through _getOwner() in the various Item#insert*() - // methods. They are called the same to facilitate so duck-typing. - _insertItem: function(index, item, _created) { - item = this.insertLayer(index, item) + return layer; + }, + + // Project#_insertItem() and Item#_insertItem() are helper functions called + // in Item#copyTo(), and through _getOwner() in the various Item#insert*() + // methods. They are called the same to facilitate so duck-typing. + _insertItem: function (index, item, _created) { + item = + this.insertLayer(index, item) || // Anything else than layers needs to be added to a layer first. // If none exists yet, create one now, then add the item to it. - || (this._activeLayer || this._insertItem(undefined, - new Layer(Item.NO_INSERT), true)) // _created = true - .insertChild(index, item); - // If a layer was newly created, also activate it. - if (_created && item.activate) - item.activate(); - return item; - }, - - /** - * {@grouptitle Hit-testing, Fetching and Matching Items} - * - * Performs a hit-test on the items contained within the project at the - * location of the specified point. - * - * The options object allows you to control the specifics of the hit-test - * and may contain a combination of the following values: - * - * @name Project#hitTest - * @function - * - * @option [options.tolerance={@link PaperScope#settings}.hitTolerance] - * {Number} the tolerance of the hit-test - * @option options.class {Function} only hit-test against a specific item - * class, or any of its sub-classes, by providing the constructor - * function against which an `instanceof` check is performed: - * {@values Group, Layer, Path, CompoundPath, Shape, Raster, - * SymbolItem, PointText, ...} - * @option options.match {Function} a match function to be called for each - * found hit result: Return `true` to return the result, `false` to keep - * searching - * @option [options.fill=true] {Boolean} hit-test the fill of items - * @option [options.stroke=true] {Boolean} hit-test the stroke of path - * items, taking into account the setting of stroke color and width - * @option [options.segments=true] {Boolean} hit-test for {@link - * Segment#point} of {@link Path} items - * @option options.curves {Boolean} hit-test the curves of path items, - * without taking the stroke color or width into account - * @option options.handles {Boolean} hit-test for the handles ({@link - * Segment#handleIn} / {@link Segment#handleOut}) of path segments. - * @option options.ends {Boolean} only hit-test for the first or last - * segment points of open path items - * @option options.position {Boolean} hit-test the {@link Item#position} of - * of items, which depends on the setting of {@link Item#pivot} - * @option options.center {Boolean} hit-test the {@link Rectangle#center} of - * the bounding rectangle of items ({@link Item#bounds}) - * @option options.bounds {Boolean} hit-test the corners and side-centers of - * the bounding rectangle of items ({@link Item#bounds}) - * @option options.guides {Boolean} hit-test items that have {@link - * Item#guide} set to `true` - * @option options.selected {Boolean} only hit selected items - * - * @param {Point} point the point where the hit-test should be performed - * @param {Object} [options={ fill: true, stroke: true, segments: true, - * tolerance: settings.hitTolerance }] - * @return {HitResult} a hit result object that contains more information - * about what exactly was hit or `null` if nothing was hit - */ - // NOTE: Implementation is in Item#hitTest() - - /** - * Performs a hit-test on the item and its children (if it is a {@link - * Group} or {@link Layer}) at the location of the specified point, - * returning all found hits. - * - * The options object allows you to control the specifics of the hit- - * test. See {@link #hitTest(point[, options])} for a list of all options. - * - * @name Project#hitTestAll - * @function - * @param {Point} point the point where the hit-test should be performed - * @param {Object} [options={ fill: true, stroke: true, segments: true, - * tolerance: settings.hitTolerance }] - * @return {HitResult[]} hit result objects for all hits, describing what - * exactly was hit or `null` if nothing was hit - * @see #hitTest(point[, options]); - */ - // NOTE: Implementation is in Item#hitTestAll() - - /** - * - * Fetch items contained within the project whose properties match the - * criteria in the specified object. - * - * Extended matching of properties is possible by providing a comparator - * function or regular expression. Matching points, colors only work as a - * comparison of the full object, not partial matching (e.g. only providing - * the x- coordinate to match all points with that x-value). Partial - * matching does work for {@link Item#data}. - * - * Matching items against a rectangular area is also possible, by setting - * either `options.inside` or `options.overlapping` to a rectangle - * describing the area in which the items either have to be fully or partly - * contained. - * - * @option [options.recursive=true] {Boolean} whether to loop recursively - * through all children, or stop at the current level - * @option options.match {Function} a match function to be called for each - * item, allowing the definition of more flexible item checks that are - * not bound to properties. If no other match properties are defined, - * this function can also be passed instead of the `match` object - * @option options.class {Function} the constructor function of the item - * type to match against - * @option options.inside {Rectangle} the rectangle in which the items need - * to be fully contained - * @option options.overlapping {Rectangle} the rectangle with which the - * items need to at least partly overlap - * - * @see Item#matches(options) - * @see Item#getItems(options) - * @param {Object|Function} options the criteria to match against - * @return {Item[]} the list of matching items contained in the project - * - * @example {@paperscript} // Fetch all selected path items: - * var path1 = new Path.Circle({ - * center: [50, 50], - * radius: 25, - * fillColor: 'black' - * }); - * - * var path2 = new Path.Circle({ - * center: [150, 50], - * radius: 25, - * fillColor: 'black' - * }); - * - * // Select path2: - * path2.selected = true; - * - * // Fetch all selected path items: - * var items = project.getItems({ - * selected: true, - * class: Path - * }); - * - * // Change the fill color of the selected path to red: - * items[0].fillColor = 'red'; - * - * @example {@paperscript} // Fetch all items with a specific fill color: - * var path1 = new Path.Circle({ - * center: [50, 50], - * radius: 25, - * fillColor: 'black' - * }); - * - * var path2 = new Path.Circle({ - * center: [150, 50], - * radius: 25, - * fillColor: 'purple' - * }); - * - * // Fetch all items with a purple fill color: - * var items = project.getItems({ - * fillColor: 'purple' - * }); - * - * // Select the fetched item: - * items[0].selected = true; - * - * @example {@paperscript} // Fetch items at a specific position: - * var path1 = new Path.Circle({ - * center: [50, 50], - * radius: 25, - * fillColor: 'black' - * }); - * - * var path2 = new Path.Circle({ - * center: [150, 50], - * radius: 25, - * fillColor: 'black' - * }); - * - * // Fetch all path items positioned at {x: 150, y: 150}: - * var items = project.getItems({ - * position: [150, 50] - * }); - * - * // Select the fetched path: - * items[0].selected = true; - * - * @example {@paperscript} // Fetch items using a comparator function: - * - * // Create a circle shaped path: - * var path1 = new Path.Circle({ - * center: [50, 50], - * radius: 25, - * fillColor: 'black' - * }); - * - * // Create a circle shaped path with 50% opacity: - * var path2 = new Path.Circle({ - * center: [150, 50], - * radius: 25, - * fillColor: 'black', - * opacity: 0.5 - * }); - * - * // Fetch all items whose opacity is smaller than 1 - * var items = paper.project.getItems({ - * opacity: function(value) { - * return value < 1; - * } - * }); - * - * // Select the fetched item: - * items[0].selected = true; - * - * @example {@paperscript} // Fetch items using a comparator function (2): - * - * // Create a rectangle shaped path (4 segments): - * var path1 = new Path.Rectangle({ - * from: [25, 25], - * to: [75, 75], - * strokeColor: 'black', - * strokeWidth: 10 - * }); - * - * // Create a line shaped path (2 segments): - * var path2 = new Path.Line({ - * from: [125, 50], - * to: [175, 50], - * strokeColor: 'black', - * strokeWidth: 10 - * }); - * - * // Fetch all paths with 2 segments: - * var items = project.getItems({ - * class: Path, - * segments: function(segments) { - * return segments.length == 2; - * } - * }); - * - * // Select the fetched path: - * items[0].selected = true; - * - * @example {@paperscript} // Match (nested) properties of the data property: - * - * // Create a black circle shaped path: - * var path1 = new Path.Circle({ - * center: [50, 50], - * radius: 25, - * fillColor: 'black', - * data: { - * person: { - * name: 'john', - * length: 200, - * hair: true - * } - * } - * }); - * - * // Create a red circle shaped path: - * var path2 = new Path.Circle({ - * center: [150, 50], - * radius: 25, - * fillColor: 'red', - * data: { - * person: { - * name: 'john', - * length: 180, - * hair: false - * } - * } - * }); - * - * // Fetch all items whose data object contains a person - * // object whose name is john and length is 180: - * var items = paper.project.getItems({ - * data: { - * person: { - * name: 'john', - * length: 180 - * } - * } - * }); - * - * // Select the fetched item: - * items[0].selected = true; - * - * @example {@paperscript} // Match strings using regular expressions: - * - * // Create a path named 'aardvark': - * var path1 = new Path.Circle({ - * center: [50, 50], - * radius: 25, - * fillColor: 'black', - * name: 'aardvark' - * }); - * - * // Create a path named 'apple': - * var path2 = new Path.Circle({ - * center: [150, 50], - * radius: 25, - * fillColor: 'black', - * name: 'apple' - * }); - * - * // Create a path named 'banana': - * var path2 = new Path.Circle({ - * center: [250, 50], - * radius: 25, - * fillColor: 'black', - * name: 'banana' - * }); - * - * // Fetch all items that have a name starting with 'a': - * var items = project.getItems({ - * name: /^a/ - * }); - * - * // Change the fill color of the matched items: - * for (var i = 0; i < items.length; i++) { - * items[i].fillColor = 'red'; - * } - */ - getItems: function(options) { - return Item._getItems(this, options); - }, - - /** - * Fetch the first item contained within the project whose properties - * match the criteria in the specified object. - * Extended matching is possible by providing a compare function or - * regular expression. Matching points, colors only work as a comparison - * of the full object, not partial matching (e.g. only providing the x- - * coordinate to match all points with that x-value). Partial matching - * does work for {@link Item#data}. - * - * See {@link #getItems(options)} for a selection of illustrated examples. - * - * @param {Object|Function} options the criteria to match against - * @return {Item} the first item in the project matching the given criteria - */ - getItem: function(options) { - return Item._getItems(this, options, null, null, true)[0] || null; - }, - - /** - * {@grouptitle Importing / Exporting JSON and SVG} - * - * Exports (serializes) the project with all its layers and child items to a - * JSON data object or string. - * - * @name Project#exportJSON - * @function - * - * @option [options.asString=true] {Boolean} whether the JSON is returned as - * a `Object` or a `String` - * @option [options.precision=5] {Number} the amount of fractional digits in - * numbers used in JSON data - * - * @param {Object} [options] the serialization options - * @return {String} the exported JSON data - */ - - /** - * Imports (deserializes) the stored JSON data into the project. - * Note that the project is not cleared first. You can call - * {@link Project#clear()} to do so. - * - * @param {String} json the JSON data to import from - * @return {Item} the imported item - */ - importJSON: function(json) { - this.activate(); - // Provide the activeLayer as a possible target for layers, but only if - // it's empty. - var layer = this._activeLayer; - return Base.importJSON(json, layer && layer.isEmpty() && layer); - }, - - /** - * Exports the project with all its layers and child items as an SVG DOM, - * all contained in one top level SVG group node. - * - * @name Project#exportSVG - * @function - * - * @option [options.bounds='view'] {String|Rectangle} the bounds of the area - * to export, either as a string ({@values 'view', content'}), or a - * {@link Rectangle} object: `'view'` uses the view bounds, - * `'content'` uses the stroke bounds of all content - * @option [options.matrix=paper.view.matrix] {Matrix} the matrix with which - * to transform the exported content: If `options.bounds` is set to - * `'view'`, `paper.view.matrix` is used, for all other settings of - * `options.bounds` the identity matrix is used. - * @option [options.asString=false] {Boolean} whether a SVG node or a - * `String` is to be returned - * @option [options.precision=5] {Number} the amount of fractional digits in - * numbers used in SVG data - * @option [options.matchShapes=false] {Boolean} whether path items should - * tried to be converted to SVG shape items (rect, circle, ellipse, - * line, polyline, polygon), if their geometries match - * @option [options.embedImages=true] {Boolean} whether raster images should - * be embedded as base64 data inlined in the xlink:href attribute, or - * kept as a link to their external URL. - * - * @param {Object} [options] the export options - * @return {SVGElement|String} the project converted to an SVG node or a - * `String` depending on `option.asString` value - */ - - /** - * Converts the provided SVG content into Paper.js items and adds them to - * the active layer of this project. - * Note that the project is not cleared first. You can call - * {@link Project#clear()} to do so. - * - * @name Project#importSVG - * @function - * - * @option [options.expandShapes=false] {Boolean} whether imported shape - * items should be expanded to path items - * @option options.onLoad {Function} the callback function to call once the - * SVG content is loaded from the given URL receiving two arguments: the - * converted `item` and the original `svg` data as a string. Only - * required when loading from external resources. - * @option options.onError {Function} the callback function to call if an - * error occurs during loading. Only required when loading from external - * resources. - * @option [options.insert=true] {Boolean} whether the imported items should - * be added to the project that `importSVG()` is called on - * @option [options.applyMatrix={@link PaperScope#settings}.applyMatrix] - * {Boolean} whether the imported items should have their transformation - * matrices applied to their contents or not - * - * @param {SVGElement|String} svg the SVG content to import, either as a SVG - * DOM node, a string containing SVG content, or a string describing the - * URL of the SVG file to fetch. - * @param {Object} [options] the import options - * @return {Item} the newly created Paper.js item containing the converted - * SVG content - */ - /** - * Imports the provided external SVG file, converts it into Paper.js items - * and adds them to the active layer of this project. - * Note that the project is not cleared first. You can call - * {@link Project#clear()} to do so. - * - * @name Project#importSVG - * @function - * - * @param {SVGElement|String} svg the URL of the SVG file to fetch. - * @param {Function} onLoad the callback function to call once the SVG - * content is loaded from the given URL receiving two arguments: the - * converted `item` and the original `svg` data as a string. Only - * required when loading from external files. - * @return {Item} the newly created Paper.js item containing the converted - * SVG content - */ - - removeOn: function(type) { - var sets = this._removeSets; - if (sets) { - // Always clear the drag set on mouseup - if (type === 'mouseup') - sets.mousedrag = null; - var set = sets[type]; - if (set) { - for (var id in set) { - var item = set[id]; - // If we remove this item, we also need to erase it from all - // other sets. - for (var key in sets) { - var other = sets[key]; - if (other && other != set) - delete other[item._id]; + ( + this._activeLayer || + this._insertItem(undefined, new Layer(Item.NO_INSERT), true) + ) // _created = true + .insertChild(index, item); + // If a layer was newly created, also activate it. + if (_created && item.activate) item.activate(); + return item; + }, + + /** + * {@grouptitle Hit-testing, Fetching and Matching Items} + * + * Performs a hit-test on the items contained within the project at the + * location of the specified point. + * + * The options object allows you to control the specifics of the hit-test + * and may contain a combination of the following values: + * + * @name Project#hitTest + * @function + * + * @option [options.tolerance={@link PaperScope#settings}.hitTolerance] + * {Number} the tolerance of the hit-test + * @option options.class {Function} only hit-test against a specific item + * class, or any of its sub-classes, by providing the constructor + * function against which an `instanceof` check is performed: + * {@values Group, Layer, Path, CompoundPath, Shape, Raster, + * SymbolItem, PointText, ...} + * @option options.match {Function} a match function to be called for each + * found hit result: Return `true` to return the result, `false` to keep + * searching + * @option [options.fill=true] {Boolean} hit-test the fill of items + * @option [options.stroke=true] {Boolean} hit-test the stroke of path + * items, taking into account the setting of stroke color and width + * @option [options.segments=true] {Boolean} hit-test for {@link + * Segment#point} of {@link Path} items + * @option options.curves {Boolean} hit-test the curves of path items, + * without taking the stroke color or width into account + * @option options.handles {Boolean} hit-test for the handles ({@link + * Segment#handleIn} / {@link Segment#handleOut}) of path segments. + * @option options.ends {Boolean} only hit-test for the first or last + * segment points of open path items + * @option options.position {Boolean} hit-test the {@link Item#position} of + * of items, which depends on the setting of {@link Item#pivot} + * @option options.center {Boolean} hit-test the {@link Rectangle#center} of + * the bounding rectangle of items ({@link Item#bounds}) + * @option options.bounds {Boolean} hit-test the corners and side-centers of + * the bounding rectangle of items ({@link Item#bounds}) + * @option options.guides {Boolean} hit-test items that have {@link + * Item#guide} set to `true` + * @option options.selected {Boolean} only hit selected items + * + * @param {Point} point the point where the hit-test should be performed + * @param {Object} [options={ fill: true, stroke: true, segments: true, + * tolerance: settings.hitTolerance }] + * @return {HitResult} a hit result object that contains more information + * about what exactly was hit or `null` if nothing was hit + */ + // NOTE: Implementation is in Item#hitTest() + + /** + * Performs a hit-test on the item and its children (if it is a {@link + * Group} or {@link Layer}) at the location of the specified point, + * returning all found hits. + * + * The options object allows you to control the specifics of the hit- + * test. See {@link #hitTest(point[, options])} for a list of all options. + * + * @name Project#hitTestAll + * @function + * @param {Point} point the point where the hit-test should be performed + * @param {Object} [options={ fill: true, stroke: true, segments: true, + * tolerance: settings.hitTolerance }] + * @return {HitResult[]} hit result objects for all hits, describing what + * exactly was hit or `null` if nothing was hit + * @see #hitTest(point[, options]); + */ + // NOTE: Implementation is in Item#hitTestAll() + + /** + * + * Fetch items contained within the project whose properties match the + * criteria in the specified object. + * + * Extended matching of properties is possible by providing a comparator + * function or regular expression. Matching points, colors only work as a + * comparison of the full object, not partial matching (e.g. only providing + * the x- coordinate to match all points with that x-value). Partial + * matching does work for {@link Item#data}. + * + * Matching items against a rectangular area is also possible, by setting + * either `options.inside` or `options.overlapping` to a rectangle + * describing the area in which the items either have to be fully or partly + * contained. + * + * @option [options.recursive=true] {Boolean} whether to loop recursively + * through all children, or stop at the current level + * @option options.match {Function} a match function to be called for each + * item, allowing the definition of more flexible item checks that are + * not bound to properties. If no other match properties are defined, + * this function can also be passed instead of the `match` object + * @option options.class {Function} the constructor function of the item + * type to match against + * @option options.inside {Rectangle} the rectangle in which the items need + * to be fully contained + * @option options.overlapping {Rectangle} the rectangle with which the + * items need to at least partly overlap + * + * @see Item#matches(options) + * @see Item#getItems(options) + * @param {Object|Function} options the criteria to match against + * @return {Item[]} the list of matching items contained in the project + * + * @example {@paperscript} // Fetch all selected path items: + * var path1 = new Path.Circle({ + * center: [50, 50], + * radius: 25, + * fillColor: 'black' + * }); + * + * var path2 = new Path.Circle({ + * center: [150, 50], + * radius: 25, + * fillColor: 'black' + * }); + * + * // Select path2: + * path2.selected = true; + * + * // Fetch all selected path items: + * var items = project.getItems({ + * selected: true, + * class: Path + * }); + * + * // Change the fill color of the selected path to red: + * items[0].fillColor = 'red'; + * + * @example {@paperscript} // Fetch all items with a specific fill color: + * var path1 = new Path.Circle({ + * center: [50, 50], + * radius: 25, + * fillColor: 'black' + * }); + * + * var path2 = new Path.Circle({ + * center: [150, 50], + * radius: 25, + * fillColor: 'purple' + * }); + * + * // Fetch all items with a purple fill color: + * var items = project.getItems({ + * fillColor: 'purple' + * }); + * + * // Select the fetched item: + * items[0].selected = true; + * + * @example {@paperscript} // Fetch items at a specific position: + * var path1 = new Path.Circle({ + * center: [50, 50], + * radius: 25, + * fillColor: 'black' + * }); + * + * var path2 = new Path.Circle({ + * center: [150, 50], + * radius: 25, + * fillColor: 'black' + * }); + * + * // Fetch all path items positioned at {x: 150, y: 150}: + * var items = project.getItems({ + * position: [150, 50] + * }); + * + * // Select the fetched path: + * items[0].selected = true; + * + * @example {@paperscript} // Fetch items using a comparator function: + * + * // Create a circle shaped path: + * var path1 = new Path.Circle({ + * center: [50, 50], + * radius: 25, + * fillColor: 'black' + * }); + * + * // Create a circle shaped path with 50% opacity: + * var path2 = new Path.Circle({ + * center: [150, 50], + * radius: 25, + * fillColor: 'black', + * opacity: 0.5 + * }); + * + * // Fetch all items whose opacity is smaller than 1 + * var items = paper.project.getItems({ + * opacity: function(value) { + * return value < 1; + * } + * }); + * + * // Select the fetched item: + * items[0].selected = true; + * + * @example {@paperscript} // Fetch items using a comparator function (2): + * + * // Create a rectangle shaped path (4 segments): + * var path1 = new Path.Rectangle({ + * from: [25, 25], + * to: [75, 75], + * strokeColor: 'black', + * strokeWidth: 10 + * }); + * + * // Create a line shaped path (2 segments): + * var path2 = new Path.Line({ + * from: [125, 50], + * to: [175, 50], + * strokeColor: 'black', + * strokeWidth: 10 + * }); + * + * // Fetch all paths with 2 segments: + * var items = project.getItems({ + * class: Path, + * segments: function(segments) { + * return segments.length == 2; + * } + * }); + * + * // Select the fetched path: + * items[0].selected = true; + * + * @example {@paperscript} // Match (nested) properties of the data property: + * + * // Create a black circle shaped path: + * var path1 = new Path.Circle({ + * center: [50, 50], + * radius: 25, + * fillColor: 'black', + * data: { + * person: { + * name: 'john', + * length: 200, + * hair: true + * } + * } + * }); + * + * // Create a red circle shaped path: + * var path2 = new Path.Circle({ + * center: [150, 50], + * radius: 25, + * fillColor: 'red', + * data: { + * person: { + * name: 'john', + * length: 180, + * hair: false + * } + * } + * }); + * + * // Fetch all items whose data object contains a person + * // object whose name is john and length is 180: + * var items = paper.project.getItems({ + * data: { + * person: { + * name: 'john', + * length: 180 + * } + * } + * }); + * + * // Select the fetched item: + * items[0].selected = true; + * + * @example {@paperscript} // Match strings using regular expressions: + * + * // Create a path named 'aardvark': + * var path1 = new Path.Circle({ + * center: [50, 50], + * radius: 25, + * fillColor: 'black', + * name: 'aardvark' + * }); + * + * // Create a path named 'apple': + * var path2 = new Path.Circle({ + * center: [150, 50], + * radius: 25, + * fillColor: 'black', + * name: 'apple' + * }); + * + * // Create a path named 'banana': + * var path2 = new Path.Circle({ + * center: [250, 50], + * radius: 25, + * fillColor: 'black', + * name: 'banana' + * }); + * + * // Fetch all items that have a name starting with 'a': + * var items = project.getItems({ + * name: /^a/ + * }); + * + * // Change the fill color of the matched items: + * for (var i = 0; i < items.length; i++) { + * items[i].fillColor = 'red'; + * } + */ + getItems: function (options) { + return Item._getItems(this, options); + }, + + /** + * Fetch the first item contained within the project whose properties + * match the criteria in the specified object. + * Extended matching is possible by providing a compare function or + * regular expression. Matching points, colors only work as a comparison + * of the full object, not partial matching (e.g. only providing the x- + * coordinate to match all points with that x-value). Partial matching + * does work for {@link Item#data}. + * + * See {@link #getItems(options)} for a selection of illustrated examples. + * + * @param {Object|Function} options the criteria to match against + * @return {Item} the first item in the project matching the given criteria + */ + getItem: function (options) { + return Item._getItems(this, options, null, null, true)[0] || null; + }, + + /** + * {@grouptitle Importing / Exporting JSON and SVG} + * + * Exports (serializes) the project with all its layers and child items to a + * JSON data object or string. + * + * @name Project#exportJSON + * @function + * + * @option [options.asString=true] {Boolean} whether the JSON is returned as + * a `Object` or a `String` + * @option [options.precision=5] {Number} the amount of fractional digits in + * numbers used in JSON data + * + * @param {Object} [options] the serialization options + * @return {String} the exported JSON data + */ + + /** + * Imports (deserializes) the stored JSON data into the project. + * Note that the project is not cleared first. You can call + * {@link Project#clear()} to do so. + * + * @param {String} json the JSON data to import from + * @return {Item} the imported item + */ + importJSON: function (json) { + this.activate(); + // Provide the activeLayer as a possible target for layers, but only if + // it's empty. + var layer = this._activeLayer; + return Base.importJSON(json, layer && layer.isEmpty() && layer); + }, + + /** + * Exports the project with all its layers and child items as an SVG DOM, + * all contained in one top level SVG group node. + * + * @name Project#exportSVG + * @function + * + * @option [options.bounds='view'] {String|Rectangle} the bounds of the area + * to export, either as a string ({@values 'view', content'}), or a + * {@link Rectangle} object: `'view'` uses the view bounds, + * `'content'` uses the stroke bounds of all content + * @option [options.matrix=paper.view.matrix] {Matrix} the matrix with which + * to transform the exported content: If `options.bounds` is set to + * `'view'`, `paper.view.matrix` is used, for all other settings of + * `options.bounds` the identity matrix is used. + * @option [options.asString=false] {Boolean} whether a SVG node or a + * `String` is to be returned + * @option [options.precision=5] {Number} the amount of fractional digits in + * numbers used in SVG data + * @option [options.matchShapes=false] {Boolean} whether path items should + * tried to be converted to SVG shape items (rect, circle, ellipse, + * line, polyline, polygon), if their geometries match + * @option [options.embedImages=true] {Boolean} whether raster images should + * be embedded as base64 data inlined in the xlink:href attribute, or + * kept as a link to their external URL. + * + * @param {Object} [options] the export options + * @return {SVGElement|String} the project converted to an SVG node or a + * `String` depending on `option.asString` value + */ + + /** + * Converts the provided SVG content into Paper.js items and adds them to + * the active layer of this project. + * Note that the project is not cleared first. You can call + * {@link Project#clear()} to do so. + * + * @name Project#importSVG + * @function + * + * @option [options.expandShapes=false] {Boolean} whether imported shape + * items should be expanded to path items + * @option options.onLoad {Function} the callback function to call once the + * SVG content is loaded from the given URL receiving two arguments: the + * converted `item` and the original `svg` data as a string. Only + * required when loading from external resources. + * @option options.onError {Function} the callback function to call if an + * error occurs during loading. Only required when loading from external + * resources. + * @option [options.insert=true] {Boolean} whether the imported items should + * be added to the project that `importSVG()` is called on + * @option [options.applyMatrix={@link PaperScope#settings}.applyMatrix] + * {Boolean} whether the imported items should have their transformation + * matrices applied to their contents or not + * + * @param {SVGElement|String} svg the SVG content to import, either as a SVG + * DOM node, a string containing SVG content, or a string describing the + * URL of the SVG file to fetch. + * @param {Object} [options] the import options + * @return {Item} the newly created Paper.js item containing the converted + * SVG content + */ + /** + * Imports the provided external SVG file, converts it into Paper.js items + * and adds them to the active layer of this project. + * Note that the project is not cleared first. You can call + * {@link Project#clear()} to do so. + * + * @name Project#importSVG + * @function + * + * @param {SVGElement|String} svg the URL of the SVG file to fetch. + * @param {Function} onLoad the callback function to call once the SVG + * content is loaded from the given URL receiving two arguments: the + * converted `item` and the original `svg` data as a string. Only + * required when loading from external files. + * @return {Item} the newly created Paper.js item containing the converted + * SVG content + */ + + removeOn: function (type) { + var sets = this._removeSets; + if (sets) { + // Always clear the drag set on mouseup + if (type === "mouseup") sets.mousedrag = null; + var set = sets[type]; + if (set) { + for (var id in set) { + var item = set[id]; + // If we remove this item, we also need to erase it from all + // other sets. + for (var key in sets) { + var other = sets[key]; + if (other && other != set) delete other[item._id]; + } + item.remove(); } - item.remove(); + sets[type] = null; } - sets[type] = null; } - } - }, - - draw: function(ctx, matrix, pixelRatio) { - // Increase the _updateVersion before the draw-loop. After that, items - // that are visible will have their _updateVersion set to the new value. - this._updateVersion++; - ctx.save(); - matrix.applyToContext(ctx); - // Use new Base() so we can use param.extend() to easily override values - var children = this._children, - param = new Base({ - offset: new Point(0, 0), - pixelRatio: pixelRatio, - viewMatrix: matrix.isIdentity() ? null : matrix, - matrices: [new Matrix()], // Start with the identity matrix. - // Tell the drawing routine that we want to keep _globalMatrix - // up to date. Item#rasterize() and Raster#getAverageColor() - // should not set this. - updateMatrix: true - }); - for (var i = 0, l = children.length; i < l; i++) { - children[i].draw(ctx, param); - } - ctx.restore(); + }, - // Draw the selection of the selected items in the project: - if (this._selectionCount > 0) { + draw: function (ctx, matrix, pixelRatio) { + // Increase the _updateVersion before the draw-loop. After that, items + // that are visible will have their _updateVersion set to the new value. + this._updateVersion++; ctx.save(); - ctx.strokeWidth = 1; - var items = this._selectionItems, - size = this._scope.settings.handleSize, - version = this._updateVersion; - for (var id in items) { - items[id]._drawSelection(ctx, matrix, size, items, version); + matrix.applyToContext(ctx); + // Use new Base() so we can use param.extend() to easily override values + var children = this._children, + param = new Base({ + offset: new Point(0, 0), + pixelRatio: pixelRatio, + viewMatrix: matrix.isIdentity() ? null : matrix, + matrices: [new Matrix()], // Start with the identity matrix. + // Tell the drawing routine that we want to keep _globalMatrix + // up to date. Item#rasterize() and Raster#getAverageColor() + // should not set this. + updateMatrix: true, + }); + for (var i = 0, l = children.length; i < l; i++) { + children[i].draw(ctx, param); } ctx.restore(); - } + + // Draw the selection of the selected items in the project: + if (this._selectionCount > 0 && this._scope.settings.drawSelected) { + ctx.save(); + ctx.strokeWidth = 1; + var items = this._selectionItems, + size = this._scope.settings.handleSize, + version = this._updateVersion; + for (var id in items) { + items[id]._drawSelection(ctx, matrix, size, items, version); + } + ctx.restore(); + } + }, } -}); +); diff --git a/src/style/Style.js b/src/style/Style.js index 72609b9197..78b9214e0e 100644 --- a/src/style/Style.js +++ b/src/style/Style.js @@ -67,639 +67,685 @@ * }; * */ -var Style = Base.extend(new function() { - // Defaults for items without text-styles (PathItem, Shape, Raster, ...): - var itemDefaults = { - // Paths - fillColor: null, - fillRule: 'nonzero', - strokeColor: null, - strokeWidth: 1, - strokeCap: 'butt', - strokeJoin: 'miter', - strokeScaling: true, - miterLimit: 10, - dashOffset: 0, - dashArray: [], - // Shadows - shadowColor: null, - shadowBlur: 0, - shadowOffset: new Point(), - // Selection - selectedColor: null - }, - // Defaults for Group, Layer and Project (anything item that allows nesting - // needs to be able to pass down text styles as well): - groupDefaults = Base.set({}, itemDefaults, { - // Characters - fontFamily: 'sans-serif', - fontWeight: 'normal', - fontSize: 12, - leading: null, - // Paragraphs - justification: 'left' - }), - // Defaults for TextItem (override default fillColor to black): - textDefaults = Base.set({}, groupDefaults, { - fillColor: new Color() // black - }), - flags = { - strokeWidth: /*#=*/Change.STROKE, - strokeCap: /*#=*/Change.STROKE, - strokeJoin: /*#=*/Change.STROKE, - // strokeScaling can change the coordinates of cached path items - strokeScaling: /*#=*/(Change.STROKE | Change.GEOMETRY), - miterLimit: /*#=*/Change.STROKE, - fontFamily: /*#=*/Change.GEOMETRY, - fontWeight: /*#=*/Change.GEOMETRY, - fontSize: /*#=*/Change.GEOMETRY, - font: /*#=*/Change.GEOMETRY, // deprecated, links to fontFamily - leading: /*#=*/Change.GEOMETRY, - justification: /*#=*/Change.GEOMETRY - }, - item = { - // Enforce creation of beans, as bean getters have hidden parameters, - // see _dontMerge argument below. - beans: true - }, - fields = /** @lends Style# */{ - _class: 'Style', - beans: true, - - initialize: function Style(style, _owner, _project) { - // We keep values in a separate object that we can iterate over. - this._values = {}; - this._owner = _owner; - this._project = _owner && _owner._project || _project - || paper.project; - // Use different defaults based on the owner - this._defaults = !_owner || _owner instanceof Group ? groupDefaults - : _owner instanceof TextItem ? textDefaults - : itemDefaults; - if (style) - this.set(style); - } - }; - - // Iterate over groupDefaults to inject getters / setters, to cover all - // properties - Base.each(groupDefaults, function(value, key) { - var isColor = /Color$/.test(key), - isPoint = key === 'shadowOffset', - part = Base.capitalize(key), - flag = flags[key], - set = 'set' + part, - get = 'get' + part; - - // Define getters and setters to be injected into this class. - // This is how style values are handled: - // - Style values are all stored in this._values - // - The style object starts with an empty _values object, with fallback - // on _defaults through code in the getter below. - // - Only the styles that are explicitly set on the object get defined - // in _values. - // - Color values are not stored as converted colors immediately. The - // raw value is stored, and conversion only happens in the getter. - fields[set] = function(value) { - var owner = this._owner, - children = owner && owner._children, - applyToChildren = children && children.length > 0 - && !(owner instanceof CompoundPath); - // Only unify styles on children of Groups, excluding CompoundPaths. - if (applyToChildren) { - for (var i = 0, l = children.length; i < l; i++) - children[i]._style[set](value); - } - // Always store selectedColor in item _values to make sure that - // group selected bounds and position color is coherent whether it - // has children or not when the value is set. - if ((key === 'selectedColor' || !applyToChildren) - && key in this._defaults) { - var old = this._values[key]; - if (old !== value) { - if (isColor) { - // The old value may be a native string or other color - // description that wasn't coerced to a color object yet - if (old) { - Color._setOwner(old, null); - old._canvasStyle = null; - } - if (value && value.constructor === Color) { - // NOTE: If value is not a Color, it is only - // converted and cloned in the getter further down. - value = Color._setOwner(value, owner, +var Style = Base.extend( + new (function () { + // Defaults for items without text-styles (PathItem, Shape, Raster, ...): + var itemDefaults = { + // Paths + fillColor: null, + fillRule: "nonzero", + strokeColor: null, + strokeWidth: 1, + strokeCap: "butt", + strokeJoin: "miter", + strokeScaling: true, + miterLimit: 10, + dashOffset: 0, + dashArray: [], + // Shadows + shadowColor: null, + shadowBlur: 0, + shadowOffset: new Point(), + // Selection + selectedColor: null, + selectedWidth: 2, + selectedCap: "round", + selectedJoin: "round", + selectedMiterLimit: 1, + }, + // Defaults for Group, Layer and Project (anything item that allows nesting + // needs to be able to pass down text styles as well): + groupDefaults = Base.set({}, itemDefaults, { + // Characters + fontFamily: "sans-serif", + fontWeight: "normal", + fontSize: 12, + leading: null, + // Paragraphs + justification: "left", + }), + // Defaults for TextItem (override default fillColor to black): + textDefaults = Base.set({}, groupDefaults, { + fillColor: new Color(), // black + }), + flags = { + strokeWidth: /*#=*/ Change.STROKE, + strokeCap: /*#=*/ Change.STROKE, + strokeJoin: /*#=*/ Change.STROKE, + // strokeScaling can change the coordinates of cached path items + strokeScaling: /*#=*/ Change.STROKE | Change.GEOMETRY, + miterLimit: /*#=*/ Change.STROKE, + fontFamily: /*#=*/ Change.GEOMETRY, + fontWeight: /*#=*/ Change.GEOMETRY, + fontSize: /*#=*/ Change.GEOMETRY, + font: /*#=*/ Change.GEOMETRY, // deprecated, links to fontFamily + leading: /*#=*/ Change.GEOMETRY, + justification: /*#=*/ Change.GEOMETRY, + }, + item = { + // Enforce creation of beans, as bean getters have hidden parameters, + // see _dontMerge argument below. + beans: true, + }, + fields = /** @lends Style# */ { + _class: "Style", + beans: true, + + initialize: function Style(style, _owner, _project) { + // We keep values in a separate object that we can iterate over. + this._values = {}; + this._owner = _owner; + this._project = + (_owner && _owner._project) || + _project || + paper.project; + // Use different defaults based on the owner + this._defaults = + !_owner || _owner instanceof Group + ? groupDefaults + : _owner instanceof TextItem + ? textDefaults + : itemDefaults; + if (style) this.set(style); + }, + }; + + // Iterate over groupDefaults to inject getters / setters, to cover all + // properties + Base.each(groupDefaults, function (value, key) { + var isColor = /Color$/.test(key), + isPoint = key === "shadowOffset", + part = Base.capitalize(key), + flag = flags[key], + set = "set" + part, + get = "get" + part; + + // Define getters and setters to be injected into this class. + // This is how style values are handled: + // - Style values are all stored in this._values + // - The style object starts with an empty _values object, with fallback + // on _defaults through code in the getter below. + // - Only the styles that are explicitly set on the object get defined + // in _values. + // - Color values are not stored as converted colors immediately. The + // raw value is stored, and conversion only happens in the getter. + fields[set] = function (value) { + var owner = this._owner, + children = owner && owner._children, + applyToChildren = + children && + children.length > 0 && + !(owner instanceof CompoundPath); + // Only unify styles on children of Groups, excluding CompoundPaths. + if (applyToChildren) { + for (var i = 0, l = children.length; i < l; i++) + children[i]._style[set](value); + } + // Always store selectedColor in item _values to make sure that + // group selected bounds and position color is coherent whether it + // has children or not when the value is set. + if ( + (key === "selectedColor" || !applyToChildren) && + key in this._defaults + ) { + var old = this._values[key]; + if (old !== value) { + if (isColor) { + // The old value may be a native string or other color + // description that wasn't coerced to a color object yet + if (old) { + Color._setOwner(old, null); + old._canvasStyle = null; + } + if (value && value.constructor === Color) { + // NOTE: If value is not a Color, it is only + // converted and cloned in the getter further down. + value = Color._setOwner( + value, + owner, // Only provide a color-setter if the style // is to be applied to the children: - applyToChildren && set); + applyToChildren && set + ); + } } - } - // NOTE: We do not convert the values to Colors in the - // setter. This only happens once the getter is called. - this._values[key] = value; - // Notify the owner of the style change STYLE is always set, - // additional flags come from flags, as used for STROKE: - if (owner) - owner._changed(flag || /*#=*/Change.STYLE); - } - } - }; - - fields[get] = function(_dontMerge) { - var owner = this._owner, - children = owner && owner._children, - applyToChildren = children && children.length > 0 - && !(owner instanceof CompoundPath), - value; - // If the owner has children, walk through all of them and see if - // they all have the same style. - // If true is passed for _dontMerge, don't merge children styles. - if (applyToChildren && !_dontMerge) { - for (var i = 0, l = children.length; i < l; i++) { - var childValue = children[i]._style[get](); - if (!i) { - value = childValue; - } else if (!Base.equals(value, childValue)) { - // If there is another child with a different - // style, the style is not defined: - return undefined; + // NOTE: We do not convert the values to Colors in the + // setter. This only happens once the getter is called. + this._values[key] = value; + // Notify the owner of the style change STYLE is always set, + // additional flags come from flags, as used for STROKE: + if (owner) owner._changed(flag || /*#=*/ Change.STYLE); } } - } else if (key in this._defaults) { - var value = this._values[key]; - if (value === undefined) { - value = this._defaults[key]; - // Clone defaults if available: - if (value && value.clone) { - value = value.clone(); + }; + + fields[get] = function (_dontMerge) { + var owner = this._owner, + children = owner && owner._children, + applyToChildren = + children && + children.length > 0 && + !(owner instanceof CompoundPath), + value; + // If the owner has children, walk through all of them and see if + // they all have the same style. + // If true is passed for _dontMerge, don't merge children styles. + if (applyToChildren && !_dontMerge) { + for (var i = 0, l = children.length; i < l; i++) { + var childValue = children[i]._style[get](); + if (!i) { + value = childValue; + } else if (!Base.equals(value, childValue)) { + // If there is another child with a different + // style, the style is not defined: + return undefined; + } } - } else { - var ctor = isColor ? Color : isPoint ? Point : null; - if (ctor && !(value && value.constructor === ctor)) { - // Convert to a Color / Point, and stored result of the - // conversion. - this._values[key] = value = ctor.read([value], 0, - { readNull: true, clone: true }); + } else if (key in this._defaults) { + var value = this._values[key]; + if (value === undefined) { + value = this._defaults[key]; + // Clone defaults if available: + if (value && value.clone) { + value = value.clone(); + } + } else { + var ctor = isColor ? Color : isPoint ? Point : null; + if (ctor && !(value && value.constructor === ctor)) { + // Convert to a Color / Point, and stored result of the + // conversion. + this._values[key] = value = ctor.read([value], 0, { + readNull: true, + clone: true, + }); + } } } + if (value && isColor) { + // Color._setOwner() may clone the color if it already has a + // different owner (e.g. resulting from `childValue` above). + // Only provide a color-setter if the style is to be applied to + // the children: + value = Color._setOwner( + value, + owner, + applyToChildren && set + ); + } + return value; + }; + + // Inject style getters and setters into the Item class, which redirect + // calls to the linked style object. + item[get] = function (_dontMerge) { + return this._style[get](_dontMerge); + }; + + item[set] = function (value) { + this._style[set](value); + }; + }); + + // Create aliases for deprecated properties. The lookup table contains the + // part after 'get' / 'set': + // TODO: Remove once deprecated long enough, after December 2016. + Base.each( + { + Font: "FontFamily", + WindingRule: "FillRule", + }, + function (value, key) { + var get = "get" + key, + set = "set" + key; + fields[get] = item[get] = "#get" + value; + fields[set] = item[set] = "#set" + value; } - if (value && isColor) { - // Color._setOwner() may clone the color if it already has a - // different owner (e.g. resulting from `childValue` above). - // Only provide a color-setter if the style is to be applied to - // the children: - value = Color._setOwner(value, owner, applyToChildren && set); - } - return value; - }; - - // Inject style getters and setters into the Item class, which redirect - // calls to the linked style object. - item[get] = function(_dontMerge) { - return this._style[get](_dontMerge); - }; - - item[set] = function(value) { - this._style[set](value); - }; - }); - - // Create aliases for deprecated properties. The lookup table contains the - // part after 'get' / 'set': - // TODO: Remove once deprecated long enough, after December 2016. - Base.each({ - Font: 'FontFamily', - WindingRule: 'FillRule' - }, function(value, key) { - var get = 'get' + key, - set = 'set' + key; - fields[get] = item[get] = '#get' + value; - fields[set] = item[set] = '#set' + value; - }); - - Item.inject(item); - return fields; -}, /** @lends Style# */{ - set: function(style) { - // If the passed style object is also a Style, clone its cloneable - // fields rather than simply copying them. - var isStyle = style instanceof Style, - // Use the other stlyle's _values object for iteration - values = isStyle ? style._values : style; - if (values) { - for (var key in values) { - if (key in this._defaults) { - var value = values[key]; - // Delegate to setter, so Group styles work too. - this[key] = value && isStyle && value.clone - ? value.clone() : value; + ); + + Item.inject(item); + return fields; + })(), + /** @lends Style# */ { + set: function (style) { + // If the passed style object is also a Style, clone its cloneable + // fields rather than simply copying them. + var isStyle = style instanceof Style, + // Use the other stlyle's _values object for iteration + values = isStyle ? style._values : style; + if (values) { + for (var key in values) { + if (key in this._defaults) { + var value = values[key]; + // Delegate to setter, so Group styles work too. + this[key] = + value && isStyle && value.clone + ? value.clone() + : value; + } } } - } - }, - - equals: function(style) { - // Since we're dealing with defaults, loop through style values in both - // objects and compare with default fall-back. But in the secondary pass - // only check against keys that weren't already in the first object: - function compare(style1, style2, secondary) { - var values1 = style1._values, - values2 = style2._values, - defaults2 = style2._defaults; - for (var key in values1) { - var value1 = values1[key], - value2 = values2[key]; - if (!(secondary && key in values2) && !Base.equals(value1, - value2 === undefined ? defaults2[key] : value2)) - return false; + }, + + equals: function (style) { + // Since we're dealing with defaults, loop through style values in both + // objects and compare with default fall-back. But in the secondary pass + // only check against keys that weren't already in the first object: + function compare(style1, style2, secondary) { + var values1 = style1._values, + values2 = style2._values, + defaults2 = style2._defaults; + for (var key in values1) { + var value1 = values1[key], + value2 = values2[key]; + if ( + !(secondary && key in values2) && + !Base.equals( + value1, + value2 === undefined ? defaults2[key] : value2 + ) + ) + return false; + } + return true; } - return true; - } - - return style === this || style && this._class === style._class - && compare(this, style) - && compare(style, this, true) - || false; - }, - - _dispose: function() { - var color; - color = this.getFillColor(); - if (color) color._canvasStyle = null; - color = this.getStrokeColor(); - if (color) color._canvasStyle = null; - color = this.getShadowColor(); - if (color) color._canvasStyle = null; - }, - - // DOCS: Style#hasFill() - hasFill: function() { - var color = this.getFillColor(); - return !!color && color.alpha > 0; - }, - - // DOCS: Style#hasStroke() - hasStroke: function() { - var color = this.getStrokeColor(); - return !!color && color.alpha > 0 && this.getStrokeWidth() > 0; - }, - - // DOCS: Style#hasShadow() - hasShadow: function() { - var color = this.getShadowColor(); - // In order to draw a shadow, we need either a shadow blur or an - // offset, or both. - return !!color && color.alpha > 0 && (this.getShadowBlur() > 0 - || !this.getShadowOffset().isZero()); - }, - - /** - * The view that this style belongs to. - * - * @bean - * @type View - */ - getView: function() { - return this._project._view; - }, - - // Overrides - - getFontStyle: function() { - var fontSize = this.getFontSize(); - // To prevent an obscure iOS 7 crash, we have to convert the size to a - // string first before passing it to the regular expression. - // The following nonsensical statement would also prevent the bug, - // proving that the issue is not the regular expression itself, but - // something deeper down in the optimizer: - // `if (size === 0) size = 0;` - return this.getFontWeight() - + ' ' + fontSize + (/[a-z]/i.test(fontSize + '') ? ' ' : 'px ') - + this.getFontFamily(); - }, - - /** - * @bean - * @private - * @deprecated use {@link #fontFamily} instead. - */ - getFont: '#getFontFamily', - setFont: '#setFontFamily', - - getLeading: function getLeading() { - // Override leading to return fontSize * 1.2 by default. - var leading = getLeading.base.call(this), - fontSize = this.getFontSize(); - if (/pt|em|%|px/.test(fontSize)) - fontSize = this.getView().getPixelSize(fontSize); - return leading != null ? leading : fontSize * 1.2; - } - // DOCS: why isn't the example code showing up? - /** - * Style objects don't need to be created directly. Just pass an object to - * {@link Item#style} or {@link Project#currentStyle}, it will be converted - * to a Style object internally. - * - * @name Style#initialize - * @param {Object} style - */ - - /** - * {@grouptitle Stroke Style} - * - * The color of the stroke. - * - * @name Style#strokeColor - * @property - * @type ?Color - * - * @example {@paperscript} - * // Setting the stroke color of a path: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle(new Point(80, 50), 35); - * - * // Set its stroke color to RGB red: - * circle.strokeColor = new Color(1, 0, 0); - */ - - /** - * The width of the stroke. - * - * @name Style#strokeWidth - * @property - * @type Number - * @default 1 - * - * @example {@paperscript} - * // Setting an item's stroke width: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle(new Point(80, 50), 35); - * - * // Set its stroke color to black: - * circle.strokeColor = 'black'; - * - * // Set its stroke width to 10: - * circle.strokeWidth = 10; - */ - - /** - * The shape to be used at the beginning and end of open {@link Path} items, - * when they have a stroke. - * - * @name Style#strokeCap - * @property - * @type String - * @values 'round', 'square', 'butt' - * @default 'butt' - * - * @example {@paperscript height=200} - * // A look at the different stroke caps: - * - * var line = new Path(new Point(80, 50), new Point(420, 50)); - * line.strokeColor = 'black'; - * line.strokeWidth = 20; - * - * // Select the path, so we can see where the stroke is formed: - * line.selected = true; - * - * // Set the stroke cap of the line to be round: - * line.strokeCap = 'round'; - * - * // Copy the path and set its stroke cap to be square: - * var line2 = line.clone(); - * line2.position.y += 50; - * line2.strokeCap = 'square'; - * - * // Make another copy and set its stroke cap to be butt: - * var line2 = line.clone(); - * line2.position.y += 100; - * line2.strokeCap = 'butt'; - */ - - /** - * The shape to be used at the segments and corners of {@link Path} items - * when they have a stroke. - * - * @name Style#strokeJoin - * @property - * @type String - * @values 'miter', 'round', 'bevel' - * @default 'miter' - * - * @example {@paperscript height=120} - * // A look at the different stroke joins: - * var path = new Path(); - * path.add(new Point(80, 100)); - * path.add(new Point(120, 40)); - * path.add(new Point(160, 100)); - * path.strokeColor = 'black'; - * path.strokeWidth = 20; - * - * // Select the path, so we can see where the stroke is formed: - * path.selected = true; - * - * var path2 = path.clone(); - * path2.position.x += path2.bounds.width * 1.5; - * path2.strokeJoin = 'round'; - * - * var path3 = path2.clone(); - * path3.position.x += path3.bounds.width * 1.5; - * path3.strokeJoin = 'bevel'; - */ - - /** - * Specifies whether the stroke is to be drawn taking the current affine - * transformation into account (the default behavior), or whether it should - * appear as a non-scaling stroke. - * - * @name Style#strokeScaling - * @property - * @type Boolean - * @default true - */ - - /** - * The dash offset of the stroke. - * - * @name Style#dashOffset - * @property - * @type Number - * @default 0 - */ - - /** - * Specifies an array containing the dash and gap lengths of the stroke. - * - * @example {@paperscript} - * var path = new Path.Circle(new Point(80, 50), 40); - * path.strokeWidth = 2; - * path.strokeColor = 'black'; - * - * // Set the dashed stroke to [10pt dash, 4pt gap]: - * path.dashArray = [10, 4]; - * - * @name Style#dashArray - * @property - * @type Number[] - * @default [] - */ - - /** - * The miter limit of the stroke. When two line segments meet at a sharp - * angle and miter joins have been specified for {@link #strokeJoin}, it is - * possible for the miter to extend far beyond the {@link #strokeWidth} of - * the path. The miterLimit imposes a limit on the ratio of the miter length - * to the {@link #strokeWidth}. - * - * @name Style#miterLimit - * @property - * @default 10 - * @type Number - */ - - /** - * {@grouptitle Fill Style} - * - * The fill color. - * - * @name Style#fillColor - * @property - * @type ?Color - * - * @example {@paperscript} - * // Setting the fill color of a path to red: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle(new Point(80, 50), 35); - * - * // Set the fill color of the circle to RGB red: - * circle.fillColor = new Color(1, 0, 0); - */ - - /** - * The fill-rule with which the shape gets filled. Please note that only - * modern browsers support fill-rules other than `'nonzero'`. - * - * @name Style#fillRule - * @property - * @type String - * @values 'nonzero', 'evenodd' - * @default 'nonzero' - */ - - /** - * {@grouptitle Shadow Style} - * - * The shadow color. - * - * @property - * @name Style#shadowColor - * @type ?Color - * - * @example {@paperscript} - * // Creating a circle with a black shadow: - * - * var circle = new Path.Circle({ - * center: [80, 50], - * radius: 35, - * fillColor: 'white', - * // Set the shadow color of the circle to RGB black: - * shadowColor: new Color(0, 0, 0), - * // Set the shadow blur radius to 12: - * shadowBlur: 12, - * // Offset the shadow by { x: 5, y: 5 } - * shadowOffset: new Point(5, 5) - * }); - */ - - /** - * The shadow's blur radius. - * - * @property - * @name Style#shadowBlur - * @type Number - * @default 0 - */ - - /** - * The shadow's offset. - * - * @property - * @name Style#shadowOffset - * @type Point - * @default 0 - */ - - /** - * {@grouptitle Selection Style} - * - * The color the item is highlighted with when selected. If the item does - * not specify its own color, the color defined by its layer is used instead. - * - * @name Style#selectedColor - * @property - * @type ?Color - */ - - /** - * {@grouptitle Character Style} - * - * The font-family to be used in text content. - * - * @name Style#fontFamily - * @type String - * @default 'sans-serif' - */ - - /** - * - * The font-weight to be used in text content. - * - * @name Style#fontWeight - * @type String|Number - * @default 'normal' - */ - - /** - * The font size of text content, as a number in pixels, or as a string with - * optional units `'px'`, `'pt'` and `'em'`. - * - * @name Style#fontSize - * @type Number|String - * @default 10 - */ - - /** - * - * The font-family to be used in text content, as one string. - * - * @name Style#font - * @type String - * @default 'sans-serif' - * @deprecated use {@link #fontFamily} instead. - */ - - /** - * The text leading of text content. - * - * @name Style#leading - * @type Number|String - * @default fontSize * 1.2 - */ - - /** - * {@grouptitle Paragraph Style} - * - * The justification of text paragraphs. - * - * @name Style#justification - * @type String - * @values 'left', 'right', 'center' - * @default 'left' - */ -}); + return ( + style === this || + (style && + this._class === style._class && + compare(this, style) && + compare(style, this, true)) || + false + ); + }, + + _dispose: function () { + var color; + color = this.getFillColor(); + if (color) color._canvasStyle = null; + color = this.getStrokeColor(); + if (color) color._canvasStyle = null; + color = this.getShadowColor(); + if (color) color._canvasStyle = null; + }, + + // DOCS: Style#hasFill() + hasFill: function () { + var color = this.getFillColor(); + return !!color && color.alpha > 0; + }, + + // DOCS: Style#hasStroke() + hasStroke: function () { + var color = this.getStrokeColor(); + return !!color && color.alpha > 0 && this.getStrokeWidth() > 0; + }, + + // DOCS: Style#hasShadow() + hasShadow: function () { + var color = this.getShadowColor(); + // In order to draw a shadow, we need either a shadow blur or an + // offset, or both. + return ( + !!color && + color.alpha > 0 && + (this.getShadowBlur() > 0 || !this.getShadowOffset().isZero()) + ); + }, + + /** + * The view that this style belongs to. + * + * @bean + * @type View + */ + getView: function () { + return this._project._view; + }, + + // Overrides + + getFontStyle: function () { + var fontSize = this.getFontSize(); + // To prevent an obscure iOS 7 crash, we have to convert the size to a + // string first before passing it to the regular expression. + // The following nonsensical statement would also prevent the bug, + // proving that the issue is not the regular expression itself, but + // something deeper down in the optimizer: + // `if (size === 0) size = 0;` + return ( + this.getFontWeight() + + " " + + fontSize + + (/[a-z]/i.test(fontSize + "") ? " " : "px ") + + this.getFontFamily() + ); + }, + + /** + * @bean + * @private + * @deprecated use {@link #fontFamily} instead. + */ + getFont: "#getFontFamily", + setFont: "#setFontFamily", + + getLeading: function getLeading() { + // Override leading to return fontSize * 1.2 by default. + var leading = getLeading.base.call(this), + fontSize = this.getFontSize(); + if (/pt|em|%|px/.test(fontSize)) + fontSize = this.getView().getPixelSize(fontSize); + return leading != null ? leading : fontSize * 1.2; + }, + + // DOCS: why isn't the example code showing up? + /** + * Style objects don't need to be created directly. Just pass an object to + * {@link Item#style} or {@link Project#currentStyle}, it will be converted + * to a Style object internally. + * + * @name Style#initialize + * @param {Object} style + */ + + /** + * {@grouptitle Stroke Style} + * + * The color of the stroke. + * + * @name Style#strokeColor + * @property + * @type ?Color + * + * @example {@paperscript} + * // Setting the stroke color of a path: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle(new Point(80, 50), 35); + * + * // Set its stroke color to RGB red: + * circle.strokeColor = new Color(1, 0, 0); + */ + + /** + * The width of the stroke. + * + * @name Style#strokeWidth + * @property + * @type Number + * @default 1 + * + * @example {@paperscript} + * // Setting an item's stroke width: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle(new Point(80, 50), 35); + * + * // Set its stroke color to black: + * circle.strokeColor = 'black'; + * + * // Set its stroke width to 10: + * circle.strokeWidth = 10; + */ + + /** + * The shape to be used at the beginning and end of open {@link Path} items, + * when they have a stroke. + * + * @name Style#strokeCap + * @property + * @type String + * @values 'round', 'square', 'butt' + * @default 'butt' + * + * @example {@paperscript height=200} + * // A look at the different stroke caps: + * + * var line = new Path(new Point(80, 50), new Point(420, 50)); + * line.strokeColor = 'black'; + * line.strokeWidth = 20; + * + * // Select the path, so we can see where the stroke is formed: + * line.selected = true; + * + * // Set the stroke cap of the line to be round: + * line.strokeCap = 'round'; + * + * // Copy the path and set its stroke cap to be square: + * var line2 = line.clone(); + * line2.position.y += 50; + * line2.strokeCap = 'square'; + * + * // Make another copy and set its stroke cap to be butt: + * var line2 = line.clone(); + * line2.position.y += 100; + * line2.strokeCap = 'butt'; + */ + + /** + * The shape to be used at the segments and corners of {@link Path} items + * when they have a stroke. + * + * @name Style#strokeJoin + * @property + * @type String + * @values 'miter', 'round', 'bevel' + * @default 'miter' + * + * @example {@paperscript height=120} + * // A look at the different stroke joins: + * var path = new Path(); + * path.add(new Point(80, 100)); + * path.add(new Point(120, 40)); + * path.add(new Point(160, 100)); + * path.strokeColor = 'black'; + * path.strokeWidth = 20; + * + * // Select the path, so we can see where the stroke is formed: + * path.selected = true; + * + * var path2 = path.clone(); + * path2.position.x += path2.bounds.width * 1.5; + * path2.strokeJoin = 'round'; + * + * var path3 = path2.clone(); + * path3.position.x += path3.bounds.width * 1.5; + * path3.strokeJoin = 'bevel'; + */ + + /** + * Specifies whether the stroke is to be drawn taking the current affine + * transformation into account (the default behavior), or whether it should + * appear as a non-scaling stroke. + * + * @name Style#strokeScaling + * @property + * @type Boolean + * @default true + */ + + /** + * The dash offset of the stroke. + * + * @name Style#dashOffset + * @property + * @type Number + * @default 0 + */ + + /** + * Specifies an array containing the dash and gap lengths of the stroke. + * + * @example {@paperscript} + * var path = new Path.Circle(new Point(80, 50), 40); + * path.strokeWidth = 2; + * path.strokeColor = 'black'; + * + * // Set the dashed stroke to [10pt dash, 4pt gap]: + * path.dashArray = [10, 4]; + * + * @name Style#dashArray + * @property + * @type Number[] + * @default [] + */ + + /** + * The miter limit of the stroke. When two line segments meet at a sharp + * angle and miter joins have been specified for {@link #strokeJoin}, it is + * possible for the miter to extend far beyond the {@link #strokeWidth} of + * the path. The miterLimit imposes a limit on the ratio of the miter length + * to the {@link #strokeWidth}. + * + * @name Style#miterLimit + * @property + * @default 10 + * @type Number + */ + + /** + * {@grouptitle Fill Style} + * + * The fill color. + * + * @name Style#fillColor + * @property + * @type ?Color + * + * @example {@paperscript} + * // Setting the fill color of a path to red: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle(new Point(80, 50), 35); + * + * // Set the fill color of the circle to RGB red: + * circle.fillColor = new Color(1, 0, 0); + */ + + /** + * The fill-rule with which the shape gets filled. Please note that only + * modern browsers support fill-rules other than `'nonzero'`. + * + * @name Style#fillRule + * @property + * @type String + * @values 'nonzero', 'evenodd' + * @default 'nonzero' + */ + + /** + * {@grouptitle Shadow Style} + * + * The shadow color. + * + * @property + * @name Style#shadowColor + * @type ?Color + * + * @example {@paperscript} + * // Creating a circle with a black shadow: + * + * var circle = new Path.Circle({ + * center: [80, 50], + * radius: 35, + * fillColor: 'white', + * // Set the shadow color of the circle to RGB black: + * shadowColor: new Color(0, 0, 0), + * // Set the shadow blur radius to 12: + * shadowBlur: 12, + * // Offset the shadow by { x: 5, y: 5 } + * shadowOffset: new Point(5, 5) + * }); + */ + + /** + * The shadow's blur radius. + * + * @property + * @name Style#shadowBlur + * @type Number + * @default 0 + */ + + /** + * The shadow's offset. + * + * @property + * @name Style#shadowOffset + * @type Point + * @default 0 + */ + + /** + * {@grouptitle Selection Style} + * + * The color the item is highlighted with when selected. If the item does + * not specify its own color, the color defined by its layer is used instead. + * + * @name Style#selectedColor + * @property + * @type ?Color + */ + + /** + * {@grouptitle Character Style} + * + * The font-family to be used in text content. + * + * @name Style#fontFamily + * @type String + * @default 'sans-serif' + */ + + /** + * + * The font-weight to be used in text content. + * + * @name Style#fontWeight + * @type String|Number + * @default 'normal' + */ + + /** + * The font size of text content, as a number in pixels, or as a string with + * optional units `'px'`, `'pt'` and `'em'`. + * + * @name Style#fontSize + * @type Number|String + * @default 10 + */ + + /** + * + * The font-family to be used in text content, as one string. + * + * @name Style#font + * @type String + * @default 'sans-serif' + * @deprecated use {@link #fontFamily} instead. + */ + + /** + * The text leading of text content. + * + * @name Style#leading + * @type Number|String + * @default fontSize * 1.2 + */ + + /** + * {@grouptitle Paragraph Style} + * + * The justification of text paragraphs. + * + * @name Style#justification + * @type String + * @values 'left', 'right', 'center' + * @default 'left' + */ + } +);