mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-19 06:00:56 -05:00
Merge branch 'refs/heads/master' into uglifyjs2
Conflicts: build/preprocess.sh
This commit is contained in:
commit
1509a934b0
40 changed files with 662 additions and 544 deletions
|
@ -157,7 +157,7 @@ function stripComments(str) {
|
|||
if (quote) {
|
||||
// When checking for quote escaping, we also need to check that the
|
||||
// escape sign itself is not escaped, as otherwise '\\' would cause
|
||||
// the wrong impression of and endlessly open string:
|
||||
// the wrong impression of an unclosed string:
|
||||
if (str[i] === quoteSign && (str[i - 1] !== '\\' || str[i - 2] === '\\'))
|
||||
quote = false;
|
||||
} else if (blockComment) {
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
# stripped Formated but without comments
|
||||
# compressed Uses UglifyJS to reduce file size
|
||||
|
||||
VERSION=0.22
|
||||
VERSION=0.3
|
||||
DATE=$(git log -1 --pretty=format:%ad)
|
||||
|
||||
COMMAND="./prepro.js -d '{ \"version\": $VERSION, \"date\": \"$DATE\" }' -d '$3' -i '$4' $2"
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
var segments = [new Point(30, 90), new Point(100, 150)];
|
||||
var path2 = new Path(segments);
|
||||
path2.strokeColor = 'yellow';
|
||||
|
||||
var path3 = new Path();
|
||||
|
||||
document.getElementById('svg').appendChild(project.exportSvg());
|
||||
</script>
|
||||
|
|
25
examples/SVG Export/Group Transform.html
Normal file
25
examples/SVG Export/Group Transform.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Transform Testing</title>
|
||||
<link rel="stylesheet" href="../css/style.css">
|
||||
<script type="text/javascript" src="../../dist/paper.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="500px" height="500px"></canvas>
|
||||
<script type="text/paperscript" canvas="canvas">
|
||||
var circle1 = new Path.Circle(new Point(100, 100), 50);
|
||||
circle1.fillColor = 'red';
|
||||
var circle2 = new Path.Circle(new Point(200, 100), 50);
|
||||
circle2.fillColor = 'blue';
|
||||
var group = new Group(circle1, circle2);
|
||||
group.translate([100, 100]);
|
||||
group.scale(0.5);
|
||||
group.rotate(10);
|
||||
document.getElementById('svg').appendChild(project.exportSvg());
|
||||
</script>
|
||||
<svg id="svg" style="width: 500px; height: 500px"></svg>
|
||||
</body>
|
||||
</html>
|
20
examples/SVG Export/Raster.html
Normal file
20
examples/SVG Export/Raster.html
Normal file
File diff suppressed because one or more lines are too long
16
lib/acorn-min.js
vendored
16
lib/acorn-min.js
vendored
File diff suppressed because one or more lines are too long
7
lib/bootstrap.js
vendored
7
lib/bootstrap.js
vendored
|
@ -147,8 +147,11 @@ var Base = new function() { // Bootstrap scope
|
|||
// with optional arguments and as beans should not declare
|
||||
// the parameters and use the arguments array internally
|
||||
// instead.
|
||||
if (beans && val.length === 0
|
||||
&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
|
||||
if (beans && (bean = name.match(/^(get|is)(([A-Z])(.*))$/))
|
||||
// Reg-exp to detect non-hidden parameters. We only
|
||||
// produce beans if there are no parameters or only
|
||||
// hidden ones:
|
||||
&& !/^function\s*\(.*\b[^_,].*\)/.test(val))
|
||||
beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
|
||||
}
|
||||
// No need to look up getter if this is a function already.
|
||||
|
|
38
lib/esprima-min.js
vendored
38
lib/esprima-min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -148,13 +148,13 @@ var Point = this.Point = Base.extend(/** @lends Point# */{
|
|||
if (Array.isArray(arg0)) {
|
||||
this.x = arg0[0];
|
||||
this.y = arg0.length > 1 ? arg0[1] : arg0[0];
|
||||
} else if ('x' in arg0) {
|
||||
} else if (arg0.x != null) {
|
||||
this.x = arg0.x;
|
||||
this.y = arg0.y;
|
||||
} else if ('width' in arg0) {
|
||||
} else if (arg0.width != null) {
|
||||
this.x = arg0.width;
|
||||
this.y = arg0.height;
|
||||
} else if ('angle' in arg0) {
|
||||
} else if (arg0.angle != null) {
|
||||
this.x = arg0.length;
|
||||
this.y = 0;
|
||||
this.setAngle(arg0.angle);
|
||||
|
@ -504,9 +504,9 @@ var Point = this.Point = Base.extend(/** @lends Point# */{
|
|||
* @bean
|
||||
* @type Number
|
||||
*/
|
||||
getAngle: function(/* point */) {
|
||||
getAngle: function(_point) {
|
||||
// Hide parameters from Bootstrap so it injects bean too
|
||||
return this.getAngleInRadians(arguments[0]) * 180 / Math.PI;
|
||||
return this.getAngleInRadians(_point) * 180 / Math.PI;
|
||||
},
|
||||
|
||||
setAngle: function(angle) {
|
||||
|
@ -542,9 +542,9 @@ var Point = this.Point = Base.extend(/** @lends Point# */{
|
|||
* @bean
|
||||
* @type Number
|
||||
*/
|
||||
getAngleInRadians: function(/* point */) {
|
||||
getAngleInRadians: function(_point) {
|
||||
// Hide parameters from Bootstrap so it injects bean too
|
||||
if (arguments[0] === undefined) {
|
||||
if (_point === undefined) {
|
||||
if (this._angle == null)
|
||||
this._angle = Math.atan2(this.y, this.x);
|
||||
return this._angle;
|
||||
|
@ -559,8 +559,8 @@ var Point = this.Point = Base.extend(/** @lends Point# */{
|
|||
}
|
||||
},
|
||||
|
||||
getAngleInDegrees: function(/* point */) {
|
||||
return this.getAngle(arguments[0]);
|
||||
getAngleInDegrees: function(_point) {
|
||||
return this.getAngle(_point);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -71,7 +71,7 @@ var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{
|
|||
this.x = this.y = this.width = this.height = 0;
|
||||
if (this._read)
|
||||
this._read = arg0 === null ? 1 : 0;
|
||||
} else if (arguments.length > 1 && !('width' in arg0)) {
|
||||
} else if (arguments.length > 1 && arg0.width == null) {
|
||||
// We're checking arg0.width to rule out Rectangles, which are
|
||||
// handled separately below.
|
||||
// Read a point argument and look at the next value to see wether
|
||||
|
@ -167,11 +167,10 @@ var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{
|
|||
* @type Point
|
||||
* @bean
|
||||
*/
|
||||
getPoint: function(/* dontLink */) {
|
||||
// Pass on the optional argument dontLink which tells LinkedPoint to
|
||||
getPoint: function(_dontLink) {
|
||||
// Pass on the optional argument _dontLink which tells LinkedPoint to
|
||||
// produce a normal point instead. Used internally for speed reasons.
|
||||
return LinkedPoint.create(this, 'setPoint', this.x, this.y,
|
||||
arguments[0]);
|
||||
return LinkedPoint.create(this, 'setPoint', this.x, this.y, _dontLink);
|
||||
},
|
||||
|
||||
setPoint: function(point) {
|
||||
|
@ -181,16 +180,16 @@ var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{
|
|||
return this;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* The size of the rectangle
|
||||
*
|
||||
* @type Size
|
||||
* @bean
|
||||
*/
|
||||
getSize: function(/* dontLink */) {
|
||||
// See Rectangle#getPoint() about arguments[0]
|
||||
getSize: function(_dontLink) {
|
||||
return LinkedSize.create(this, 'setSize', this.width, this.height,
|
||||
arguments[0]);
|
||||
_dontLink);
|
||||
},
|
||||
|
||||
setSize: function(size) {
|
||||
|
@ -308,9 +307,9 @@ var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{
|
|||
* @type Point
|
||||
* @bean
|
||||
*/
|
||||
getCenter: function(/* dontLink */) {
|
||||
getCenter: function(_dontLink) {
|
||||
return LinkedPoint.create(this, 'setCenter',
|
||||
this.getCenterX(), this.getCenterY(), arguments[0]);
|
||||
this.getCenterX(), this.getCenterY(), _dontLink);
|
||||
},
|
||||
|
||||
setCenter: function(point) {
|
||||
|
@ -726,9 +725,9 @@ var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{
|
|||
setY = 'set' + y,
|
||||
get = 'get' + part,
|
||||
set = 'set' + part;
|
||||
this[get] = function(/* dontLink */) {
|
||||
this[get] = function(_dontLink) {
|
||||
return LinkedPoint.create(this, set,
|
||||
this[getX](), this[getY](), arguments[0]);
|
||||
this[getX](), this[getY](), _dontLink);
|
||||
};
|
||||
this[set] = function(point) {
|
||||
point = Point.read(arguments);
|
||||
|
|
|
@ -109,10 +109,10 @@ var Size = this.Size = Base.extend(/** @lends Size# */{
|
|||
if (Array.isArray(arg0)) {
|
||||
this.width = arg0[0];
|
||||
this.height = arg0.length > 1 ? arg0[1] : arg0[0];
|
||||
} else if ('width' in arg0) {
|
||||
} else if (arg0.width != null) {
|
||||
this.width = arg0.width;
|
||||
this.height = arg0.height;
|
||||
} else if ('x' in arg0) {
|
||||
} else if (arg0.x != null) {
|
||||
this.width = arg0.x;
|
||||
this.height = arg0.y;
|
||||
} else {
|
||||
|
|
|
@ -50,8 +50,10 @@ var Color = this.Color = Base.extend(new function() {
|
|||
var components = {
|
||||
gray: ['gray'],
|
||||
rgb: ['red', 'green', 'blue'],
|
||||
hsb: ['hue', 'saturation', 'brightness'],
|
||||
hsl: ['hue', 'saturation', 'lightness']
|
||||
hsl: ['hue', 'saturation', 'lightness'],
|
||||
// Define hsb last, so its converting saturation getter overrides the
|
||||
// one of HSL:
|
||||
hsb: ['hue', 'saturation', 'brightness']
|
||||
};
|
||||
|
||||
var colorCache = {},
|
||||
|
@ -334,6 +336,10 @@ var Color = this.Color = Base.extend(new function() {
|
|||
}, src);
|
||||
}
|
||||
return this.base(src);
|
||||
},
|
||||
|
||||
random: function() {
|
||||
return new RgbColor(Math.random(), Math.random(), Math.random());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -367,7 +373,7 @@ var Color = this.Color = Base.extend(new function() {
|
|||
* Called by various setters whenever a color value changes
|
||||
*/
|
||||
_changed: function() {
|
||||
this._cssString = null;
|
||||
this._css = null;
|
||||
if (this._owner)
|
||||
this._owner._changed(/*#=*/ Change.STYLE);
|
||||
},
|
||||
|
@ -473,23 +479,30 @@ var Color = this.Color = Base.extend(new function() {
|
|||
/**
|
||||
* @return {String} A css string representation of the color.
|
||||
*/
|
||||
toCssString: function() {
|
||||
if (!this._cssString) {
|
||||
toCss: function(noAlpha) {
|
||||
var css = this._css;
|
||||
// Only cache _css value if we're not ommiting alpha, as required
|
||||
// by SVG export.
|
||||
if (!css || noAlpha) {
|
||||
var color = this.convert('rgb'),
|
||||
alpha = color.getAlpha(),
|
||||
alpha = noAlpha ? 1 : color.getAlpha(),
|
||||
components = [
|
||||
Math.round(color._red * 255),
|
||||
Math.round(color._green * 255),
|
||||
Math.round(color._blue * 255),
|
||||
alpha != null ? alpha : 1
|
||||
Math.round(color._blue * 255)
|
||||
];
|
||||
this._cssString = 'rgba(' + components.join(', ') + ')';
|
||||
if (alpha < 1)
|
||||
components.push(alpha);
|
||||
var css = (components.length == 4 ? 'rgba(' : 'rgb(')
|
||||
+ components.join(', ') + ')';
|
||||
if (!noAlpha)
|
||||
this._css = css;
|
||||
}
|
||||
return this._cssString;
|
||||
return css;
|
||||
},
|
||||
|
||||
getCanvasStyle: function() {
|
||||
return this.toCssString();
|
||||
return this.toCss();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -238,7 +238,7 @@ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor#
|
|||
}
|
||||
for (var i = 0, l = this.gradient._stops.length; i < l; i++) {
|
||||
var stop = this.gradient._stops[i];
|
||||
gradient.addColorStop(stop._rampPoint, stop._color.toCssString());
|
||||
gradient.addColorStop(stop._rampPoint, stop._color.toCss());
|
||||
}
|
||||
return gradient;
|
||||
},
|
||||
|
|
|
@ -30,19 +30,22 @@ var GradientStop = this.GradientStop = Base.extend(/** @lends GradientStop# */{
|
|||
*/
|
||||
initialize: function(arg0, arg1) {
|
||||
if (arg0) {
|
||||
var color, rampPoint;
|
||||
if (arg1 === undefined && Array.isArray(arg0)) {
|
||||
// [color, rampPoint]
|
||||
this.setColor(arg0[0]);
|
||||
this.setRampPoint(arg0[1]);
|
||||
} else if (arg0 && arg0.color) {
|
||||
color = arg0[0];
|
||||
rampPoint = arg0[1];
|
||||
} else if (arg0.color) {
|
||||
// stop
|
||||
this.setColor(arg0.color);
|
||||
this.setRampPoint(arg0.rampPoint);
|
||||
color = arg0.color;
|
||||
rampPoint = arg0.rampPoint;
|
||||
} else {
|
||||
// color [, rampPoint]
|
||||
this.setColor(arg0);
|
||||
this.setRampPoint(arg1);
|
||||
// color, rampPoint
|
||||
color = arg0;
|
||||
rampPoint = arg1;
|
||||
}
|
||||
this.setColor(color);
|
||||
this.setRampPoint(rampPoint);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ this.Base = Base.inject(/** @lends Base# */{
|
|||
toString: function() {
|
||||
return '{ ' + Base.each(this, function(value, key) {
|
||||
// Hide internal properties even if they are enumerable
|
||||
if (key.charAt(0) != '_') {
|
||||
if (!/^_/.test(key)) {
|
||||
var type = typeof value;
|
||||
this.push(key + ': ' + (type === 'number'
|
||||
? Base.formatFloat(value)
|
||||
|
@ -70,7 +70,7 @@ this.Base = Base.inject(/** @lends Base# */{
|
|||
if (obj1.length !== obj2.length)
|
||||
return false;
|
||||
for (var i = 0, l = obj1.length; i < l; i++) {
|
||||
if (!Base.equals(obj1, obj2))
|
||||
if (!Base.equals(obj1[i], obj2[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
107
src/item/Item.js
107
src/item/Item.js
|
@ -75,12 +75,6 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
|
|||
}
|
||||
};
|
||||
|
||||
var onFrameItems = [];
|
||||
function onFrame(event) {
|
||||
for (var i = 0, l = onFrameItems.length; i < l; i++)
|
||||
onFrameItems[i].fire('frame', event);
|
||||
}
|
||||
|
||||
return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
|
||||
'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
|
||||
function(name) {
|
||||
|
@ -88,14 +82,10 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
|
|||
}, {
|
||||
onFrame: {
|
||||
install: function() {
|
||||
if (!onFrameItems.length)
|
||||
this._project.view.attach('frame', onFrame);
|
||||
onFrameItems.push(this);
|
||||
this._project.view._animateItem(this, true);
|
||||
},
|
||||
uninstall: function() {
|
||||
onFrameItems.splice(onFrameItems.indexOf(this), 1);
|
||||
if (!onFrameItems.length)
|
||||
this._project.view.detach('frame', onFrame);
|
||||
this._project.view._animateItem(this, false);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -522,7 +512,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
|
|||
* // Move the circle 100 points to the right
|
||||
* circle.position.x += 100;
|
||||
*/
|
||||
getPosition: function(/* dontLink */) {
|
||||
getPosition: function(_dontLink) {
|
||||
// Cache position value.
|
||||
// Pass true for dontLink in getCenter(), so receive back a normal point
|
||||
var pos = this._position
|
||||
|
@ -531,7 +521,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
|
|||
// 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.
|
||||
return arguments[0] ? pos
|
||||
return _dontLink ? pos
|
||||
: LinkedPoint.create(this, 'setPosition', pos.x, pos.y);
|
||||
},
|
||||
|
||||
|
@ -557,36 +547,48 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
|
|||
// Use Matrix#initialize to easily copy over values.
|
||||
this._matrix.initialize(matrix);
|
||||
this._changed(/*#=*/ Change.GEOMETRY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies wether 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.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
isEmpty: function() {
|
||||
return true;
|
||||
}
|
||||
}, Base.each(['bounds', 'strokeBounds', 'handleBounds', 'roughBounds'],
|
||||
function(name) {
|
||||
// Produce getters for bounds properties. These handle caching, matrices
|
||||
// and redirect the call to the private _getBounds, which can be
|
||||
// overridden by subclasses, see below.
|
||||
this['get' + Base.capitalize(name)] = function(/* matrix */) {
|
||||
var type = this._boundsType,
|
||||
bounds = this._getCachedBounds(
|
||||
// Allow subclasses to override _boundsType if they use the same
|
||||
// calculations for multiple types. The default is name:
|
||||
typeof type == 'string' ? type : type && type[name] || name,
|
||||
// Pass on the optional matrix
|
||||
arguments[0]);
|
||||
// If we're returning 'bounds', create a LinkedRectangle that uses the
|
||||
// setBounds() setter to update the Item whenever the bounds are
|
||||
// changed:
|
||||
return name == 'bounds' ? LinkedRectangle.create(this, 'setBounds',
|
||||
bounds.x, bounds.y, bounds.width, bounds.height) : bounds;
|
||||
};
|
||||
}, /** @lends Item# */{
|
||||
}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
|
||||
function(name) {
|
||||
// Produce getters for bounds properties. These handle caching, matrices
|
||||
// and redirect the call to the private _getBounds, which can be
|
||||
// overridden by subclasses, see below.
|
||||
this[name] = function(_matrix) {
|
||||
var getter = this._boundsGetter,
|
||||
// Allow subclasses to override _boundsGetter if they use
|
||||
// the same calculations for multiple type of bounds.
|
||||
// The default is name:
|
||||
bounds = this._getCachedBounds(typeof getter == 'string'
|
||||
? getter : getter && getter[name] || name, _matrix);
|
||||
// If we're returning 'bounds', create a LinkedRectangle that uses
|
||||
// the setBounds() setter to update the Item whenever the bounds are
|
||||
// changed:
|
||||
return name == 'bounds' ? LinkedRectangle.create(this, 'setBounds',
|
||||
bounds.x, bounds.y, bounds.width, bounds.height) : bounds;
|
||||
};
|
||||
},
|
||||
/** @lends Item# */{
|
||||
/**
|
||||
* Private method that deals with the calling of _getBounds, recursive
|
||||
* matrix concatenation and handles all the complicated caching mechanisms.
|
||||
*/
|
||||
_getCachedBounds: function(type, matrix, cacheItem) {
|
||||
_getCachedBounds: function(getter, matrix, cacheItem) {
|
||||
// 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).
|
||||
var cache = (!matrix || matrix.equals(this._matrix)) && type;
|
||||
var cache = (!matrix || matrix.equals(this._matrix)) && getter;
|
||||
// Set up a boundsCache structure that keeps track of items that keep
|
||||
// cached bounds that depend on this item. We store this in our parent,
|
||||
// for multiple reasons:
|
||||
|
@ -624,7 +626,7 @@ function(name) {
|
|||
: identity ? matrix : matrix.clone().concatenate(this._matrix);
|
||||
// If we're caching bounds on this item, pass it on as cacheItem, so the
|
||||
// children can setup the _boundsCache structures for it.
|
||||
var bounds = this._getBounds(type, matrix, cache ? this : cacheItem);
|
||||
var bounds = this._getBounds(getter, matrix, cache ? this : cacheItem);
|
||||
// If we can cache the result, update the _bounds cache structure
|
||||
// before returning
|
||||
if (cache) {
|
||||
|
@ -664,7 +666,7 @@ function(name) {
|
|||
* Subclasses override it to define calculations for the various required
|
||||
* bounding types.
|
||||
*/
|
||||
_getBounds: function(type, matrix, cacheItem) {
|
||||
_getBounds: function(getter, matrix, cacheItem) {
|
||||
// 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 PlacedItem.
|
||||
|
@ -680,7 +682,7 @@ function(name) {
|
|||
for (var i = 0, l = children.length; i < l; i++) {
|
||||
var child = children[i];
|
||||
if (child._visible && !child.isEmpty()) {
|
||||
var rect = child._getCachedBounds(type, matrix, cacheItem);
|
||||
var rect = child._getCachedBounds(getter, matrix, cacheItem);
|
||||
x1 = Math.min(rect.x, x1);
|
||||
y1 = Math.min(rect.y, y1);
|
||||
x2 = Math.max(rect.x + rect.width, x2);
|
||||
|
@ -690,10 +692,6 @@ function(name) {
|
|||
return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
|
||||
},
|
||||
|
||||
isEmpty: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
setBounds: function(rect) {
|
||||
rect = Rectangle.read(arguments);
|
||||
var bounds = this.getBounds(),
|
||||
|
@ -946,8 +944,10 @@ function(name) {
|
|||
copy.setStyle(this._style);
|
||||
// If this item has children, clone and append each of them:
|
||||
if (this._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 = this._children.length; i < l; i++)
|
||||
copy.addChild(this._children[i].clone());
|
||||
copy.addChild(this._children[i].clone(), true);
|
||||
}
|
||||
// Only copy over these fields if they are actually defined in 'this'
|
||||
// TODO: Consider moving this to Base once it's useful in more than one
|
||||
|
@ -1128,8 +1128,9 @@ function(name) {
|
|||
*
|
||||
* @param {Item} item The item to be added as a child
|
||||
*/
|
||||
addChild: function(item) {
|
||||
return this.insertChild(undefined, item);
|
||||
addChild: function(item, _cloning) {
|
||||
// Pass on internal _cloning boolean, for CompoundPath#insertChild
|
||||
return this.insertChild(undefined, item, _cloning);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1878,11 +1879,11 @@ function(name) {
|
|||
var rect = bounds[key];
|
||||
matrix._transformBounds(rect, rect);
|
||||
}
|
||||
// If we have cached 'bounds', update _position again as its
|
||||
// center. We need to take into account _boundsType here too, in
|
||||
// case another type is assigned to it, e.g. 'strokeBounds'.
|
||||
var type = this._boundsType,
|
||||
rect = bounds[type && type.bounds || 'bounds'];
|
||||
// If we have cached bounds, update _position again as its
|
||||
// center. We need to take into account _boundsGetter here too, in
|
||||
// case another getter is assigned to it, e.g. 'getStrokeBounds'.
|
||||
var getter = this._boundsGetter,
|
||||
rect = bounds[getter && getter.getBounds || getter || 'getBounds'];
|
||||
if (rect)
|
||||
this._position = rect.getCenter(true);
|
||||
this._bounds = bounds;
|
||||
|
@ -2458,8 +2459,8 @@ function(name) {
|
|||
// first, since otherwise their stroke is drawn half transparent
|
||||
// over their fill.
|
||||
if (item._blendMode !== 'normal' || item._opacity < 1
|
||||
&& !(item._segments
|
||||
&& (!item.getFillColor() || !item.getStrokeColor()))) {
|
||||
&& (item._type !== 'path'
|
||||
|| item.getFillColor() && item.getStrokeColor())) {
|
||||
var bounds = item.getStrokeBounds();
|
||||
if (!bounds.width || !bounds.height)
|
||||
return;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
*/
|
||||
var PlacedItem = this.PlacedItem = Item.extend(/** @lends PlacedItem# */{
|
||||
// PlacedItem uses strokeBounds for bounds
|
||||
_boundsType: { bounds: 'strokeBounds' },
|
||||
_boundsGetter: { getBounds: 'getStrokeBounds' },
|
||||
|
||||
_hitTest: function(point, options, matrix) {
|
||||
var hitResult = this._symbol._definition._hitTest(point, options, matrix);
|
||||
|
|
|
@ -94,11 +94,11 @@ var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend(/** @lends PlacedSymbol
|
|||
return this._clone(new PlacedSymbol(this.symbol, this._matrix.clone()));
|
||||
},
|
||||
|
||||
_getBounds: function(type, matrix) {
|
||||
_getBounds: function(getter, matrix) {
|
||||
// Redirect the call to the symbol definition to calculate the bounds
|
||||
// TODO: Implement bounds caching through passing on of cacheItem, so
|
||||
// that Symbol#_changed() notification become unnecessary!
|
||||
return this.symbol._definition._getCachedBounds(type, matrix);
|
||||
return this.symbol._definition._getCachedBounds(getter, matrix);
|
||||
},
|
||||
|
||||
draw: function(ctx, param) {
|
||||
|
|
|
@ -25,7 +25,7 @@ var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
|
|||
_type: 'raster',
|
||||
// Raster doesn't make the distinction between the different bounds,
|
||||
// so use the same name for all of them
|
||||
_boundsType: 'bounds',
|
||||
_boundsGetter: 'getBounds',
|
||||
|
||||
// TODO: Implement url / type, width, height.
|
||||
// TODO: Have PlacedSymbol & Raster inherit from a shared class?
|
||||
|
@ -45,28 +45,19 @@ var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
|
|||
if (typeof object === 'string') {
|
||||
var str = object,
|
||||
that = this;
|
||||
object = document.getElementById(str);
|
||||
if (!object) {
|
||||
// str could be a URL to load the image from?
|
||||
object = new Image();
|
||||
object.src = str;
|
||||
}
|
||||
// str can be a DOM ID or a URL to load the image from
|
||||
object = document.getElementById(str) || new Image();
|
||||
// Trigger the onLoad event on the image once it's loaded
|
||||
DomEvent.add(object, {
|
||||
load: function() {
|
||||
that.setImage(object);
|
||||
that.fire('load');
|
||||
if (that._project.view)
|
||||
that._project.view.draw(true);
|
||||
}
|
||||
});
|
||||
// If the image is already loaded, fire a 'load' event anyway,
|
||||
// so code does not need to make the distinction, and cachig is
|
||||
// transparently handled too.
|
||||
if (object.naturalWidth) {
|
||||
setTimeout(function() {
|
||||
that.fire('load');
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (!object.src)
|
||||
object.src = str;
|
||||
}
|
||||
/*#*/ } else if (options.server) {
|
||||
// If we're running on the server and it's a string,
|
||||
|
@ -105,13 +96,15 @@ var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
|
|||
},
|
||||
|
||||
setSize: function() {
|
||||
var size = Size.read(arguments),
|
||||
var size = Size.read(arguments);
|
||||
if (!this._size.equals(size)) {
|
||||
// Get reference to image before changing canvas
|
||||
image = this.getImage();
|
||||
// Setting canvas internally sets _size
|
||||
this.setCanvas(CanvasProvider.getCanvas(size));
|
||||
// Draw image back onto new canvas
|
||||
this.getContext(true).drawImage(image, 0, 0, size.width, size.height);
|
||||
var image = this.getImage();
|
||||
// Setting canvas internally sets _size
|
||||
this.setCanvas(CanvasProvider.getCanvas(size));
|
||||
// Draw image back onto new canvas
|
||||
this.getContext(true).drawImage(image, 0, 0, size.width, size.height);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -161,13 +154,13 @@ var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
|
|||
* @type Context
|
||||
* @bean
|
||||
*/
|
||||
getContext: function(/* notifyChange */) {
|
||||
getContext: function(_notifyChange) {
|
||||
if (!this._context)
|
||||
this._context = this.getCanvas().getContext('2d');
|
||||
// Support a hidden parameter that indicates if the context will be used
|
||||
// to modify the Raster object. We can notify such changes ahead since
|
||||
// they are only used afterwards for redrawing.
|
||||
if (arguments[0])
|
||||
if (_notifyChange)
|
||||
this._changed(/*#=*/ Change.PIXELS);
|
||||
return this._context;
|
||||
},
|
||||
|
@ -411,7 +404,7 @@ var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
|
|||
this.getContext(true).putImageData(data, point.x, point.y);
|
||||
},
|
||||
|
||||
_getBounds: function(type, matrix) {
|
||||
_getBounds: function(getter, matrix) {
|
||||
var rect = new Rectangle(this._size).setCenter(0, 0);
|
||||
return matrix ? matrix._transformBounds(rect) : rect;
|
||||
},
|
||||
|
|
|
@ -51,16 +51,16 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
|
|||
this.addChildren(Array.isArray(paths) ? paths : arguments);
|
||||
},
|
||||
|
||||
insertChild: function(index, item) {
|
||||
insertChild: function(index, item, _cloning) {
|
||||
// Only allow the insertion of paths
|
||||
if (!(item instanceof Path))
|
||||
if (item._type !== 'path')
|
||||
return null;
|
||||
var res = this.base(index, item);
|
||||
// All children except for the bottom one (first one in list) are set
|
||||
// to anti-clockwise orientation, so that they appear as holes, but
|
||||
// only if their orientation was not already specified before
|
||||
// (= _clockwise is defined).
|
||||
if (res && item._clockwise === undefined)
|
||||
if (!_cloning && res && item._clockwise === undefined)
|
||||
item.setClockwise(item._index == 0);
|
||||
return res;
|
||||
},
|
||||
|
@ -87,6 +87,20 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
|
|||
this._children[i].smooth();
|
||||
},
|
||||
|
||||
/**
|
||||
* All the curves contained within the compound-path, from all its child
|
||||
* {@link Path} items.
|
||||
*
|
||||
* @type Curve[]
|
||||
* @bean
|
||||
*/
|
||||
getCurves: function() {
|
||||
var curves = [];
|
||||
for (var i = 0, l = this._children.length; i < l; i++)
|
||||
curves = curves.concat(this._children[i].getCurves());
|
||||
return curves;
|
||||
},
|
||||
|
||||
isEmpty: function() {
|
||||
return this._children.length == 0;
|
||||
},
|
||||
|
|
|
@ -41,30 +41,36 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
|||
this._segment1 = new Segment();
|
||||
this._segment2 = new Segment();
|
||||
} else if (count == 1) {
|
||||
// TODO: If beans are not activated, this won't copy from
|
||||
// an existing segment. OK?
|
||||
// Note: This copies from existing segments through bean getters
|
||||
this._segment1 = new Segment(arg0.segment1);
|
||||
this._segment2 = new Segment(arg0.segment2);
|
||||
} else if (count == 2) {
|
||||
this._segment1 = new Segment(arg0);
|
||||
this._segment2 = new Segment(arg1);
|
||||
} else if (count == 4) {
|
||||
this._segment1 = new Segment(arg0, null, arg1);
|
||||
this._segment2 = new Segment(arg3, arg2, null);
|
||||
} else if (count == 8) {
|
||||
// An array as returned by getValues
|
||||
var p1 = Point.create(arg0, arg1),
|
||||
p2 = Point.create(arg6, arg7);
|
||||
this._segment1 = new Segment(p1, null,
|
||||
Point.create(arg2, arg3).subtract(p1));
|
||||
this._segment2 = new Segment(p2,
|
||||
Point.create(arg4, arg5).subtract(p2), null);
|
||||
} else {
|
||||
var point1, handle1, handle2, point2;
|
||||
if (count == 4) {
|
||||
point1 = arg0;
|
||||
handle1 = arg1;
|
||||
handle2 = arg2;
|
||||
point2 = arg3;
|
||||
} else if (count == 8) {
|
||||
// Convert getValue() array back to points and handles so we
|
||||
// can create segments for those.
|
||||
point1 = [arg0, arg1];
|
||||
point2 = [arg6, arg7];
|
||||
handle1 = [arg2 - arg0, arg7 - arg1];
|
||||
handle2 = [arg4 - arg6, arg5 - arg7];
|
||||
}
|
||||
this._segment1 = new Segment(point1, null, handle1);
|
||||
this._segment2 = new Segment(point2, handle2, null);
|
||||
}
|
||||
},
|
||||
|
||||
_changed: function() {
|
||||
// Clear cached values.
|
||||
delete this._length;
|
||||
delete this._bounds;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -228,14 +234,12 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
|||
* @type Number
|
||||
* @bean
|
||||
*/
|
||||
getLength: function(/* from, to */) {
|
||||
// Hide parameters from Bootstrap so it injects bean too
|
||||
var from = arguments[0],
|
||||
to = arguments[1],
|
||||
fullLength = arguments.length == 0 || from == 0 && to == 1;
|
||||
// Hide parameters from Bootstrap so it injects bean too
|
||||
getLength: function(_from, _to) {
|
||||
var fullLength = arguments.length == 0 || _from == 0 && _to == 1;
|
||||
if (fullLength && this._length != null)
|
||||
return this._length;
|
||||
var length = Curve.getLength(this.getValues(), from, to);
|
||||
var length = Curve.getLength(this.getValues(), _from, _to);
|
||||
if (fullLength)
|
||||
this._length = length;
|
||||
return length;
|
||||
|
@ -315,11 +319,11 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
|||
// Solve the y-axis cubic polynominal for point.y and count all
|
||||
// solutions to the right of point.x as crossings.
|
||||
var vals = this.getValues(),
|
||||
num = Curve.solveCubic(vals, 1, point.y, roots),
|
||||
count = Curve.solveCubic(vals, 1, point.y, roots),
|
||||
crossings = 0;
|
||||
for (var i = 0; i < num; i++) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
var t = roots[i];
|
||||
if (t >= 0 && t <= 1 && Curve.evaluate(vals, t, 0).x > point.x) {
|
||||
if (t >= 0 && t < 1 && Curve.evaluate(vals, t, 0).x > point.x) {
|
||||
// If we're close to 0 and are not changing y-direction from the
|
||||
// previous curve, do not count this root, as we're merely
|
||||
// touching a tip. Passing 1 for Curve.evaluate()'s type means
|
||||
|
@ -327,7 +331,7 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
|||
// a change of direction:
|
||||
if (t < Numerical.TOLERANCE && Curve.evaluate(
|
||||
this.getPrevious().getValues(), 1, 1).y
|
||||
* Curve.evaluate(vals, t, 1).y >= 0)
|
||||
* Curve.evaluate(vals, t, 1).y >= Numerical.TOLERANCE)
|
||||
continue;
|
||||
crossings++;
|
||||
}
|
||||
|
@ -551,7 +555,24 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
|||
return Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy) < 1;
|
||||
}
|
||||
}
|
||||
}, new function() { // Scope for methods that require numerical integration
|
||||
}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
|
||||
function(name) {
|
||||
this[name] = function() {
|
||||
if (!this._bounds)
|
||||
this._bounds = {};
|
||||
var bounds = this._bounds[name];
|
||||
if (!bounds) {
|
||||
// Calculate the curve bounds by passing a segment list for the
|
||||
// curve to the static Path.get*Boudns methods.
|
||||
bounds = this._bounds[name] = Path[name](
|
||||
[this._segment1, this._segment2], false, this._path._style);
|
||||
}
|
||||
return bounds.clone();
|
||||
};
|
||||
},
|
||||
/** @lends Curve# */{
|
||||
}),
|
||||
new function() { // Scope for methods that require numerical integration
|
||||
|
||||
function getLengthIntegrand(v) {
|
||||
// Calculate the coefficients of a Bezier derivative.
|
||||
|
|
268
src/path/Path.js
268
src/path/Path.js
|
@ -131,20 +131,29 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
* @type Curve[]
|
||||
* @bean
|
||||
*/
|
||||
getCurves: function() {
|
||||
if (!this._curves) {
|
||||
var segments = this._segments,
|
||||
length = segments.length;
|
||||
getCurves: function(_includeFill) {
|
||||
var curves = this._curves,
|
||||
segments = this._segments;
|
||||
if (!curves) {
|
||||
var length = segments.length;
|
||||
// Reduce length by one if it's an open path:
|
||||
if (!this._closed && length > 0)
|
||||
length--;
|
||||
this._curves = new Array(length);
|
||||
this._curves = curves = new Array(length);
|
||||
for (var i = 0; i < length; i++)
|
||||
this._curves[i] = Curve.create(this, segments[i],
|
||||
curves[i] = Curve.create(this, segments[i],
|
||||
// Use first segment for segment2 of closing curve
|
||||
segments[i + 1] || segments[0]);
|
||||
}
|
||||
return this._curves;
|
||||
// If we're asked to include the closing curve for fill, even if the
|
||||
// path is not closed for stroke, create a new uncached array and add
|
||||
// the closing curve. Used in Path#contains()
|
||||
if (_includeFill && !this._closed && this._style._fillColor) {
|
||||
curves = curves.concat([
|
||||
Curve.create(this, segments[segments.length - 1], segments[0])
|
||||
]);
|
||||
}
|
||||
return curves;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -588,7 +597,9 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
segments[i]._index = i;
|
||||
// Keep curves in sync
|
||||
if (curves) {
|
||||
curves.splice(from, amount);
|
||||
// If we're removing the last segment of a closing path, remove the
|
||||
// last curve.
|
||||
curves.splice(from == curves.length ? from - 1 : from, amount);
|
||||
// Adjust segments for the curves before and after the removed ones
|
||||
var curve;
|
||||
if (curve = curves[from - 1])
|
||||
|
@ -832,8 +843,10 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
// Only revers the path if its clockwise orientation is not the same
|
||||
// as what it is now demanded to be.
|
||||
this.reverse();
|
||||
this._clockwise = clockwise;
|
||||
}
|
||||
// Reverse only flips _clockwise state if it was already set, so let's
|
||||
// always set this here now.
|
||||
this._clockwise = clockwise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1259,20 +1272,23 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
|
||||
contains: function(point) {
|
||||
point = Point.read(arguments);
|
||||
// If the path is not closed, we should not bail out in case it has a
|
||||
// fill color!
|
||||
if (!this._closed && !this._style._fillColor
|
||||
|| !this.getRoughBounds()._containsPoint(point))
|
||||
return false;
|
||||
// Note: This only works correctly with even-odd fill rule, or paths
|
||||
// that do not overlap with themselves.
|
||||
// TODO: Find out how to implement the "Point In Polygon" problem for
|
||||
// non-zero fill rule.
|
||||
if (!this._closed || !this.getRoughBounds()._containsPoint(point))
|
||||
return false;
|
||||
// Use the crossing number algorithm, by counting the crossings of the
|
||||
// beam in right y-direction with the shape, and see if it's an odd
|
||||
// number, meaning the starting point is inside the shape.
|
||||
// http://en.wikipedia.org/wiki/Point_in_polygon
|
||||
var curves = this.getCurves(),
|
||||
var curves = this.getCurves(true),
|
||||
crossings = 0,
|
||||
// Reuse one array for root-finding, give garbage collector a break
|
||||
roots = [];
|
||||
roots = new Array(3);
|
||||
for (var i = 0, l = curves.length; i < l; i++)
|
||||
crossings += curves[i].getCrossings(point, roots);
|
||||
return (crossings & 1) == 1;
|
||||
|
@ -1841,19 +1857,36 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
this.setClosed(true);
|
||||
}
|
||||
};
|
||||
}, new function() { // A dedicated scope for the tricky bounds calculations
|
||||
}, { // A dedicated scope for the tricky bounds calculations
|
||||
// We define all the different getBounds functions as static methods on Path
|
||||
// and have #_getBounds directly access these. All static bounds functions
|
||||
// below have the same first four parameters: segments, closed, style,
|
||||
// matrix, so they can be called from #_getBounds() and also be used in
|
||||
// Curve. But not all of them use all these parameters, and some define
|
||||
// additional ones after.
|
||||
|
||||
_getBounds: function(getter, matrix) {
|
||||
// See #draw() for an explanation of why we can access _style
|
||||
// properties directly here:
|
||||
return Path[getter](this._segments, this._closed, this._style, matrix);
|
||||
},
|
||||
|
||||
// Mess with indentation in order to get more line-space below...
|
||||
statics: {
|
||||
/**
|
||||
* Returns the bounding rectangle of the item excluding stroke width.
|
||||
*/
|
||||
function getBounds(matrix, strokePadding) {
|
||||
getBounds: function(segments, closed, style, matrix, strokePadding) {
|
||||
// Code ported and further optimised from:
|
||||
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
|
||||
var segments = this._segments,
|
||||
first = segments[0];
|
||||
var first = segments[0];
|
||||
// If there are no segments, return "empty" rectangle, just like groups,
|
||||
// since #bounds is assumed to never return null.
|
||||
if (!first)
|
||||
return null;
|
||||
return new Rectangle();
|
||||
var coords = new Array(6),
|
||||
prevCoords = new Array(6);
|
||||
prevCoords = new Array(6),
|
||||
roots = new Array(2);
|
||||
// Make coordinates for first segment available in prevCoords.
|
||||
first._transformCoordinates(matrix, prevCoords, false);
|
||||
var min = prevCoords.slice(0, 2),
|
||||
|
@ -1898,115 +1931,89 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
|
||||
// Calculate derivative of our bezier polynomial, divided by 3.
|
||||
// Dividing by 3 allows for simpler calculations of a, b, c and
|
||||
// leads to the same quadratic roots below.
|
||||
// leads to the same quadratic roots.
|
||||
var a = 3 * (v1 - v2) - v0 + v3,
|
||||
b = 2 * (v0 + v2) - 4 * v1,
|
||||
c = v1 - v0;
|
||||
|
||||
// Solve for derivative for quadratic roots. Each good root
|
||||
// (meaning a solution 0 < t < 1) is an extrema in the cubic
|
||||
// polynomial and thus a potential point defining the bounds
|
||||
// TODO: Use tolerance here, just like Numerical.solveQuadratic
|
||||
if (a == 0) {
|
||||
if (b == 0)
|
||||
continue;
|
||||
var t = -c / b;
|
||||
// Test for good root and add to bounds if good (same below)
|
||||
count = Numerical.solveQuadratic(a, b, c, roots,
|
||||
Numerical.TOLERANCE);
|
||||
for (var j = 0; j < count; j++) {
|
||||
var t = roots[j];
|
||||
// Test for good roots and only add to bounds if good.
|
||||
if (tMin < t && t < tMax)
|
||||
add(null, t);
|
||||
continue;
|
||||
}
|
||||
|
||||
var q = b * b - 4 * a * c;
|
||||
if (q < 0)
|
||||
continue;
|
||||
// TODO: Match this with Numerical.solveQuadratic
|
||||
var sqrt = Math.sqrt(q),
|
||||
f = -0.5 / a,
|
||||
t1 = (b - sqrt) * f,
|
||||
t2 = (b + sqrt) * f;
|
||||
if (tMin < t1 && t1 < tMax)
|
||||
add(null, t1);
|
||||
if (tMin < t2 && t2 < tMax)
|
||||
add(null, t2);
|
||||
}
|
||||
// Swap coordinate buffers
|
||||
// Swap coordinate buffers.
|
||||
var tmp = prevCoords;
|
||||
prevCoords = coords;
|
||||
coords = tmp;
|
||||
}
|
||||
for (var i = 1, l = segments.length; i < l; i++)
|
||||
processSegment(segments[i]);
|
||||
if (this._closed)
|
||||
if (closed)
|
||||
processSegment(first);
|
||||
return Rectangle.create(min[0], min[1],
|
||||
max[0] - min[0], max[1] - min[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the horizontal and vertical padding that a transformed round
|
||||
* stroke adds to the bounding box, by calculating the dimensions of a
|
||||
* rotated ellipse.
|
||||
*/
|
||||
function getPenPadding(radius, matrix) {
|
||||
if (!matrix)
|
||||
return [radius, radius];
|
||||
// If a matrix is provided, we need to rotate the stroke circle
|
||||
// and calculate the bounding box of the resulting rotated elipse:
|
||||
// Get rotated hor and ver vectors, and determine rotation angle
|
||||
// and elipse values from them:
|
||||
var mx = matrix.createShiftless(),
|
||||
hor = mx.transform(Point.create(radius, 0)),
|
||||
ver = mx.transform(Point.create(0, radius)),
|
||||
phi = hor.getAngleInRadians(),
|
||||
a = hor.getLength(),
|
||||
b = ver.getLength();
|
||||
// Formula for rotated ellipses:
|
||||
// x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
||||
// y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
||||
// Derivates (by Wolfram Alpha):
|
||||
// derivative of x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
||||
// dx/dt = a sin(t) cos(phi) + b cos(t) sin(phi) = 0
|
||||
// derivative of y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
||||
// dy/dt = b cos(t) cos(phi) - a sin(t) sin(phi) = 0
|
||||
// This can be simplified to:
|
||||
// tan(t) = -b * tan(phi) / a // x
|
||||
// tan(t) = b * cot(phi) / a // y
|
||||
// Solving for t gives:
|
||||
// t = pi * n - arctan(b * tan(phi) / a) // x
|
||||
// t = pi * n + arctan(b * cot(phi) / a)
|
||||
// = pi * n + arctan(b / tan(phi) / a) // y
|
||||
var sin = Math.sin(phi),
|
||||
cos = Math.cos(phi),
|
||||
tan = Math.tan(phi),
|
||||
tx = -Math.atan(b * tan / a),
|
||||
ty = Math.atan(b / (tan * a));
|
||||
// Due to symetry, we don't need to cycle through pi * n solutions:
|
||||
return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
|
||||
Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
|
||||
}
|
||||
return Rectangle.create(min[0], min[1], max[0] - min[0], max[1] - min[1]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the bounding rectangle of the item including stroke width.
|
||||
*/
|
||||
function getStrokeBounds(matrix) {
|
||||
// See #draw() for an explanation of why we can access _style
|
||||
// properties directly here:
|
||||
var style = this._style;
|
||||
getStrokeBounds: function(segments, closed, style, matrix) {
|
||||
/**
|
||||
* Returns the horizontal and vertical padding that a transformed round
|
||||
* stroke adds to the bounding box, by calculating the dimensions of a
|
||||
* rotated ellipse.
|
||||
*/
|
||||
function getPenPadding(radius, matrix) {
|
||||
if (!matrix)
|
||||
return [radius, radius];
|
||||
// If a matrix is provided, we need to rotate the stroke circle
|
||||
// and calculate the bounding box of the resulting rotated elipse:
|
||||
// Get rotated hor and ver vectors, and determine rotation angle
|
||||
// and elipse values from them:
|
||||
var mx = matrix.createShiftless(),
|
||||
hor = mx.transform(Point.create(radius, 0)),
|
||||
ver = mx.transform(Point.create(0, radius)),
|
||||
phi = hor.getAngleInRadians(),
|
||||
a = hor.getLength(),
|
||||
b = ver.getLength();
|
||||
// Formula for rotated ellipses:
|
||||
// x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
||||
// y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
||||
// Derivates (by Wolfram Alpha):
|
||||
// derivative of x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
||||
// dx/dt = a sin(t) cos(phi) + b cos(t) sin(phi) = 0
|
||||
// derivative of y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
||||
// dy/dt = b cos(t) cos(phi) - a sin(t) sin(phi) = 0
|
||||
// This can be simplified to:
|
||||
// tan(t) = -b * tan(phi) / a // x
|
||||
// tan(t) = b * cot(phi) / a // y
|
||||
// Solving for t gives:
|
||||
// t = pi * n - arctan(b * tan(phi) / a) // x
|
||||
// t = pi * n + arctan(b * cot(phi) / a)
|
||||
// = pi * n + arctan(b / tan(phi) / a) // y
|
||||
var sin = Math.sin(phi),
|
||||
cos = Math.cos(phi),
|
||||
tan = Math.tan(phi),
|
||||
tx = -Math.atan(b * tan / a),
|
||||
ty = Math.atan(b / (tan * a));
|
||||
// Due to symetry, we don't need to cycle through pi * n solutions:
|
||||
return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
|
||||
Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
|
||||
}
|
||||
|
||||
// TODO: Find a way to reuse 'bounds' cache instead?
|
||||
if (!style._strokeColor || !style._strokeWidth)
|
||||
return getBounds.call(this, matrix);
|
||||
var width = style._strokeWidth,
|
||||
radius = width / 2,
|
||||
return Path.getBounds(segments, closed, style, matrix);
|
||||
var radius = style._strokeWidth / 2,
|
||||
padding = getPenPadding(radius, matrix),
|
||||
bounds = Path.getBounds(segments, closed, style, matrix, padding),
|
||||
join = style._strokeJoin,
|
||||
cap = style._strokeCap,
|
||||
// miter is relative to width. Divide it by 2 since we're
|
||||
// miter is relative to stroke width. Divide it by 2 since we're
|
||||
// measuring half the distance below
|
||||
miter = style._miterLimit * width / 2,
|
||||
segments = this._segments,
|
||||
length = segments.length,
|
||||
bounds = getBounds.call(this, matrix, padding);
|
||||
miter = style._miterLimit * style._strokeWidth / 2;
|
||||
// Create a rectangle of padding size, used for union with bounds
|
||||
// further down
|
||||
var joinBounds = new Rectangle(new Size(padding).multiply(2));
|
||||
|
@ -2076,35 +2083,35 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
}
|
||||
|
||||
for (var i = 1, l = length - (this._closed ? 0 : 1); i < l; i++) {
|
||||
for (var i = 1, l = segments.length - (closed ? 0 : 1); i < l; i++)
|
||||
addJoin(segments[i], join);
|
||||
}
|
||||
if (this._closed) {
|
||||
if (closed) {
|
||||
addJoin(segments[0], join);
|
||||
} else {
|
||||
addCap(segments[0], cap, 0);
|
||||
addCap(segments[length - 1], cap, 1);
|
||||
addCap(segments[segments.length - 1], cap, 1);
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the bounding rectangle of the item including handles.
|
||||
*/
|
||||
function getHandleBounds(matrix, stroke, join) {
|
||||
getHandleBounds: function(segments, closed, style, matrix, strokePadding,
|
||||
joinPadding) {
|
||||
var coords = new Array(6),
|
||||
x1 = Infinity,
|
||||
x2 = -x1,
|
||||
y1 = x1,
|
||||
y2 = x2;
|
||||
stroke = stroke / 2 || 0; // Stroke padding
|
||||
join = join / 2 || 0; // Join padding, for miterLimit
|
||||
for (var i = 0, l = this._segments.length; i < l; i++) {
|
||||
var segment = this._segments[i];
|
||||
strokePadding = strokePadding / 2 || 0;
|
||||
joinPadding = joinPadding / 2 || 0;
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var segment = segments[i];
|
||||
segment._transformCoordinates(matrix, coords, false);
|
||||
for (var j = 0; j < 6; j += 2) {
|
||||
// Use different padding for points or handles
|
||||
var padding = j == 0 ? join : stroke,
|
||||
var padding = j == 0 ? joinPadding : strokePadding,
|
||||
x = coords[j],
|
||||
y = coords[j + 1],
|
||||
xn = x - padding,
|
||||
|
@ -2118,34 +2125,21 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
}
|
||||
return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the rough bounding rectangle of the item that is shure to include
|
||||
* Returns the rough bounding rectangle of the item that is sure to include
|
||||
* all of the drawing, including stroke width.
|
||||
*/
|
||||
function getRoughBounds(matrix) {
|
||||
getRoughBounds: function(segments, closed, style, matrix) {
|
||||
// Delegate to handleBounds, but pass on radius values for stroke and
|
||||
// joins. Hanlde miter joins specially, by passing the largets radius
|
||||
// possible.
|
||||
var style = this._style,
|
||||
width = style._strokeColor ? style._strokeWidth : 0;
|
||||
return getHandleBounds.call(this, matrix, width,
|
||||
var strokeWidth = style._strokeColor ? style._strokeWidth : 0;
|
||||
return Path.getHandleBounds(segments, closed, style, matrix,
|
||||
strokeWidth,
|
||||
style._strokeJoin == 'miter'
|
||||
? width * style._miterLimit
|
||||
: width);
|
||||
? strokeWidth * style._miterLimit
|
||||
: strokeWidth);
|
||||
}
|
||||
|
||||
var get = {
|
||||
bounds: getBounds,
|
||||
strokeBounds: getStrokeBounds,
|
||||
handleBounds: getHandleBounds,
|
||||
roughBounds: getRoughBounds
|
||||
};
|
||||
|
||||
return {
|
||||
_getBounds: function(type, matrix) {
|
||||
return get[type].call(this, matrix);
|
||||
}
|
||||
};
|
||||
});
|
||||
}});
|
||||
|
|
|
@ -58,8 +58,7 @@ var Segment = this.Segment = Base.extend(/** @lends Segment# */{
|
|||
if (count == 0) {
|
||||
// Nothing
|
||||
} else if (count == 1) {
|
||||
// TODO: If beans are not activated, this won't copy from existing
|
||||
// segments. OK?
|
||||
// Note: This copies from existing segments through bean getters
|
||||
if (arg0.point) {
|
||||
point = arg0.point;
|
||||
handleIn = arg0.handleIn;
|
||||
|
@ -90,8 +89,10 @@ var Segment = this.Segment = Base.extend(/** @lends Segment# */{
|
|||
_changed: function(point) {
|
||||
if (!this._path)
|
||||
return;
|
||||
// Delegate changes to affected curves if they exist
|
||||
var curve = this._path._curves && this.getCurve(), other;
|
||||
// Delegate changes to affected curves if they exist. Check _curves
|
||||
// first to make sure we're not creating it by calling this.getCurve().
|
||||
var curve = this._path._curves && this.getCurve(),
|
||||
other;
|
||||
if (curve) {
|
||||
curve._changed();
|
||||
// Get the other affected curve, which is the previous one for
|
||||
|
|
|
@ -52,14 +52,14 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
// Activate straight away by passing true to base(), so paper.project is
|
||||
// set, as required by Layer and DoumentView constructors.
|
||||
this.base(true);
|
||||
this._currentStyle = new PathStyle();
|
||||
this._selectedItems = {};
|
||||
this._selectedItemCount = 0;
|
||||
this.layers = [];
|
||||
this.symbols = [];
|
||||
this.activeLayer = new Layer();
|
||||
if (view)
|
||||
this.view = view instanceof View ? view : View.create(view);
|
||||
this._currentStyle = new PathStyle();
|
||||
this._selectedItems = {};
|
||||
this._selectedItemCount = 0;
|
||||
// Change tracking, not in use for now. Activate once required:
|
||||
// this._changes = [];
|
||||
// this._changesById = {};
|
||||
|
@ -278,29 +278,23 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
// matrices by item id, to speed up drawing by eliminating repeated
|
||||
// concatenation of parent's matrices through caching.
|
||||
var matrices = {};
|
||||
// Descriptionf of the paramters to getGlobalMatrix():
|
||||
// Description of the paramters to getGlobalMatrix():
|
||||
// mx is the container for the final concatenated matrix, passed
|
||||
// to getGlobalMatrix() on the initial call.
|
||||
// cached defines wether the result of the concatenation should be
|
||||
// cached, only used for parents of items that this is called for.
|
||||
function getGlobalMatrix(item, mx, cached) {
|
||||
var cache = cached && matrices[item._id];
|
||||
if (cache) {
|
||||
// Found a cached version, copy over the values and return
|
||||
mx.concatenate(cache);
|
||||
return mx;
|
||||
}
|
||||
if (item._parent) {
|
||||
// Get concatenated matrix from all the parents, using
|
||||
// local caching (passing true for cached):
|
||||
getGlobalMatrix(item._parent, mx, true);
|
||||
// No need to concatenate if it's the identity matrix
|
||||
if (!item._matrix.isIdentity())
|
||||
mx.concatenate(item._matrix);
|
||||
} else {
|
||||
// Simply copy over the item's matrix, since it's the root
|
||||
mx.initialize(item._matrix);
|
||||
}
|
||||
// Found a cached version? Return a clone of it.
|
||||
if (cache)
|
||||
return cache.clone();
|
||||
// Get concatenated matrix from all the parents, using
|
||||
// local caching (passing true for cached):
|
||||
if (item._parent)
|
||||
mx = getGlobalMatrix(item._parent, mx, true);
|
||||
// No need to concatenate if it's the identity matrix
|
||||
if (!item._matrix.isIdentity())
|
||||
mx.concatenate(item._matrix);
|
||||
// If the result needs to be cached, create a copy since matrix
|
||||
// might be further modified through recursive calls
|
||||
if (cached)
|
||||
|
|
|
@ -48,34 +48,43 @@ new function() {
|
|||
return segments[index1]._point.getDistance(segments[index2]._point);
|
||||
}
|
||||
|
||||
function getTransform(item) {
|
||||
// Note, we're taking out the translation part of the matrix and move it
|
||||
// to x, y attributes, to produce more readable markup, and not have to
|
||||
// use center points in rotate(). To do so, SVG requries us to inverse
|
||||
// transform the translation point by the matrix itself, since they are
|
||||
// provided in local coordinates.
|
||||
var matrix = item._matrix.createShiftless(),
|
||||
trans = matrix._inverseTransform(item._matrix.getTranslation()),
|
||||
attrs = {
|
||||
x: trans.x,
|
||||
y: trans.y
|
||||
};
|
||||
function getTransform(item, coordinates) {
|
||||
var matrix = item._matrix,
|
||||
trans = matrix.getTranslation(),
|
||||
attrs = {};
|
||||
if (coordinates) {
|
||||
// If the item suppports x- and y- coordinates, we're taking out the
|
||||
// translation part of the matrix and move it to x, y attributes, to
|
||||
// produce more readable markup, and not have to use center points
|
||||
// in rotate(). To do so, SVG requries us to inverse transform the
|
||||
// translation point by the matrix itself, since they are provided
|
||||
// in local coordinates.
|
||||
matrix = matrix.createShiftless();
|
||||
var point = matrix._inverseTransform(trans);
|
||||
attrs.x = point.x;
|
||||
attrs.y = point.y;
|
||||
trans = null;
|
||||
}
|
||||
if (matrix.isIdentity())
|
||||
return attrs;
|
||||
// See if we can formulate this matrix as simple scale / rotate commands
|
||||
// Note: getScaling() returns values also when it's not a simple scale,
|
||||
// but angle is only != null if it is, so check for that.
|
||||
var transform = [],
|
||||
angle = matrix.getRotation(),
|
||||
scale = matrix.getScaling();
|
||||
var angle = matrix.getRotation(),
|
||||
parts = [];
|
||||
if (angle != null) {
|
||||
transform.push(angle
|
||||
? 'rotate(' + formatFloat(angle) + ')'
|
||||
: 'scale(' + formatPoint(scale) +')');
|
||||
matrix = matrix.clone().scale(1, -1);
|
||||
if (trans && !trans.isZero())
|
||||
parts.push('translate(' + formatPoint(trans) + ')');
|
||||
if (angle)
|
||||
parts.push('rotate(' + formatFloat(angle) + ')');
|
||||
var scale = matrix.getScaling();
|
||||
if (!Numerical.isZero(scale.x - 1) || !Numerical.isZero(scale.y - 1))
|
||||
parts.push('scale(' + formatPoint(scale) +')');
|
||||
} else {
|
||||
transform.push('matrix(' + matrix.getValues().join(',') + ')');
|
||||
parts.push('matrix(' + matrix.getValues().join(',') + ')');
|
||||
}
|
||||
attrs.transform = transform.join(' ');
|
||||
attrs.transform = parts.join(' ');
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
@ -176,9 +185,11 @@ new function() {
|
|||
return segments.length === 4 && path._closed
|
||||
&& isColinear(0, 2) && isColinear(1, 3)
|
||||
? 'rect'
|
||||
: segments.length >= 3
|
||||
? path._closed ? 'polygon' : 'polyline'
|
||||
: 'line';
|
||||
: segments.length === 0
|
||||
? 'empty'
|
||||
: segments.length >= 3
|
||||
? path._closed ? 'polygon' : 'polyline'
|
||||
: 'line';
|
||||
} else if (path._closed) {
|
||||
if (segments.length === 8
|
||||
&& isArc(0) && isArc(2) && isArc(4) && isArc(6)
|
||||
|
@ -203,13 +214,16 @@ new function() {
|
|||
// Override default SVG style on groups, then apply style.
|
||||
attrs.fill = 'none';
|
||||
var svg = createElement('g', attrs);
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
svg.appendChild(children[i].exportSvg());
|
||||
for (var i = 0, l = children.length; i < l; i++) {
|
||||
var child = children[i].exportSvg();
|
||||
if (child)
|
||||
svg.appendChild(child);
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
function exportText(item) {
|
||||
var attrs = getTransform(item),
|
||||
var attrs = getTransform(item, true),
|
||||
style = item._style;
|
||||
if (style._font != null)
|
||||
attrs['font-family'] = style._font;
|
||||
|
@ -227,6 +241,8 @@ new function() {
|
|||
angle = determineAngle(path, segments, type, center),
|
||||
attrs;
|
||||
switch (type) {
|
||||
case 'empty':
|
||||
return null;
|
||||
case 'path':
|
||||
attrs = {
|
||||
d: getPath(path, segments)
|
||||
|
@ -341,10 +357,15 @@ new function() {
|
|||
// (A layer or group which can have style values in SVG).
|
||||
var value = style[entry.get]();
|
||||
if (!parentStyle || !Base.equals(parentStyle[entry.get](), value)) {
|
||||
// Support for css-style rgba() values is not in SVG 1.1, so
|
||||
// separate the alpha value of colors with alpha into the
|
||||
// separate fill- / stroke-opacity attribute:
|
||||
if (entry.type === 'color' && value != null && value.getAlpha() < 1)
|
||||
attrs[entry.attribute + '-opacity'] = value.getAlpha();
|
||||
attrs[entry.attribute] = value == null
|
||||
? 'none'
|
||||
: entry.type === 'color'
|
||||
? value.toCssString()
|
||||
? value.toCss(true) // false for noAlpha, see above
|
||||
: entry.type === 'array'
|
||||
? value.join(',')
|
||||
: entry.type === 'number'
|
||||
|
@ -372,8 +393,8 @@ new function() {
|
|||
* @return {SVGSVGElement} the item converted to an SVG node
|
||||
*/
|
||||
exportSvg: function() {
|
||||
var exporter = exporters[this._type];
|
||||
var svg = exporter && exporter(this, this._type);
|
||||
var exporter = exporters[this._type],
|
||||
svg = exporter && exporter(this, this._type);
|
||||
return svg && applyStyle(this, svg);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -269,6 +269,19 @@ new function() {
|
|||
// http://www.w3.org/TR/SVG/pservers.html#RadialGradients
|
||||
radialgradient: importGradient,
|
||||
|
||||
// http://www.w3.org/TR/SVG/struct.html#ImageElement
|
||||
image: function (svg) {
|
||||
var raster = new Raster(getValue(svg, 'href'));
|
||||
raster.attach('load', function() {
|
||||
var size = getSize(svg, 'width', 'height');
|
||||
this.setSize(size);
|
||||
// Since x and y start from the top left of an image, add
|
||||
// half of its size:
|
||||
this.translate(getPoint(svg, 'x', 'y').add(size.divide(2)));
|
||||
});
|
||||
return raster;
|
||||
},
|
||||
|
||||
// http://www.w3.org/TR/SVG/struct.html#SymbolElement
|
||||
symbol: function(svg, type) {
|
||||
return new Symbol(applyAttributes(importGroup(svg, type), svg));
|
||||
|
@ -382,13 +395,20 @@ new function() {
|
|||
break;
|
||||
// http://www.w3.org/TR/SVG/masking.html#ClipPathProperty
|
||||
case 'clip-path':
|
||||
var clipPath = getDefinition(value).clone().reduce();
|
||||
item = createClipGroup(item, clipPath);
|
||||
item = createClipGroup(item,
|
||||
getDefinition(value).clone().reduce());
|
||||
break;
|
||||
// http://www.w3.org/TR/SVG/types.html#DataTypeTransformList
|
||||
case 'gradientTransform':
|
||||
case 'transform':
|
||||
applyTransform(item, svg, name);
|
||||
var transforms = svg[name].baseVal,
|
||||
matrix = new Matrix();
|
||||
for (var i = 0, l = transforms.numberOfItems; i < l; i++) {
|
||||
var mx = transforms.getItem(i).matrix;
|
||||
matrix.concatenate(
|
||||
new Matrix(mx.a, mx.b, mx.c, mx.d, mx.e, mx.f));
|
||||
}
|
||||
item.transform(matrix);
|
||||
break;
|
||||
// http://www.w3.org/TR/SVG/pservers.html#StopOpacityProperty
|
||||
case 'stop-opacity':
|
||||
|
@ -401,6 +421,15 @@ new function() {
|
|||
item.setOpacity(opacity);
|
||||
}
|
||||
break;
|
||||
// http://www.w3.org/TR/SVG/painting.html#FillOpacityProperty
|
||||
case 'fill-opacity':
|
||||
// http://www.w3.org/TR/SVG/painting.html#StrokeOpacityProperty
|
||||
case 'stroke-opacity':
|
||||
var color = item[name == 'fill-opacity' ? 'getFillColor'
|
||||
: 'getStrokeColor']();
|
||||
if (color)
|
||||
color.setAlpha(Base.toFloat(value));
|
||||
break;
|
||||
case 'visibility':
|
||||
item.setVisible(value === 'visible');
|
||||
break;
|
||||
|
@ -479,57 +508,11 @@ new function() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the transformations specified on the SVG node to a Paper.js item
|
||||
*
|
||||
* @param {SVGSVGElement} svg an SVG node
|
||||
* @param {Item} item a Paper.js item
|
||||
*/
|
||||
function applyTransform(item, svg, name) {
|
||||
var svgTransform = svg[name],
|
||||
transforms = svgTransform.baseVal,
|
||||
matrix = new Matrix();
|
||||
for (var i = 0, l = transforms.numberOfItems; i < l; i++) {
|
||||
var transform = transforms.getItem(i);
|
||||
if (transform.type === /*#=*/ SVGTransform.SVG_TRANSFORM_UNKNOWN)
|
||||
continue;
|
||||
// Convert SVG Matrix to Paper Matrix.
|
||||
// TODO: Should this be moved to our Matrix constructor?
|
||||
var mx = transform.matrix,
|
||||
a = mx.a,
|
||||
b = mx.b,
|
||||
c = mx.c,
|
||||
d = mx.d;
|
||||
switch (transform.type) {
|
||||
// Compensate for SVG's theta rotation going the opposite direction
|
||||
case /*#=*/ SVGTransform.SVG_TRANSFORM_MATRIX:
|
||||
var tmp = b;
|
||||
b = c;
|
||||
c = tmp;
|
||||
break;
|
||||
case /*#=*/ SVGTransform.SVG_TRANSFORM_SKEWX:
|
||||
b = c;
|
||||
c = 0;
|
||||
break;
|
||||
case /*#=*/ SVGTransform.SVG_TRANSFORM_SKEWY:
|
||||
c = b;
|
||||
b = 0;
|
||||
break;
|
||||
case /*#=*/ SVGTransform.SVG_TRANSFORM_ROTATE:
|
||||
b = -b;
|
||||
c = -c;
|
||||
break;
|
||||
}
|
||||
matrix.concatenate(new Matrix(a, c, b, d, mx.e, mx.f));
|
||||
}
|
||||
item.transform(matrix);
|
||||
}
|
||||
|
||||
function importSvg(svg) {
|
||||
var type = svg.nodeName.toLowerCase(),
|
||||
importer = importers[type],
|
||||
item = importer && importer(svg, type);
|
||||
return item ? applyAttributes(item, svg) : item;
|
||||
return item && applyAttributes(item, svg);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,13 +15,12 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* To shrink code, we automatically replace the long SVGPathSeg and SVGTransform
|
||||
* constants with their actual numeric values on preprocessing time, using
|
||||
* prepro statements.
|
||||
* To shrink code, we automatically replace the long SVGPathSeg constants with
|
||||
* their actual numeric values on preprocessing time, using prepro statements.
|
||||
* To do so, we need their values defined, which happens here.
|
||||
*/
|
||||
|
||||
// http://dxr.mozilla.org/mozilla-central/dom/interfaces/svg/nsIDOMSVGPathSeg.idl.html
|
||||
// http://dxr.mozilla.org/mozilla-central/dom/interfaces/svg/nsIDOMSVGPathSeg.idl.html
|
||||
var SVGPathSeg = {
|
||||
PATHSEG_UNKNOWN: 0,
|
||||
PATHSEG_CLOSEPATH: 1,
|
||||
|
@ -44,14 +43,3 @@ var SVGPathSeg = {
|
|||
PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: 18,
|
||||
PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: 19
|
||||
};
|
||||
|
||||
// http://dxr.mozilla.org/mozilla-central/dom/interfaces/svg/nsIDOMSVGTransform.idl.html
|
||||
var SVGTransform = {
|
||||
SVG_TRANSFORM_UNKNOWN: 0,
|
||||
SVG_TRANSFORM_MATRIX: 1,
|
||||
SVG_TRANSFORM_TRANSLATE: 2,
|
||||
SVG_TRANSFORM_SCALE: 3,
|
||||
SVG_TRANSFORM_ROTATE: 4,
|
||||
SVG_TRANSFORM_SKEWX: 5,
|
||||
SVG_TRANSFORM_SKEWY: 6
|
||||
};
|
||||
|
|
|
@ -96,7 +96,7 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{
|
|||
var context = null;
|
||||
|
||||
return {
|
||||
_getBounds: function(type, matrix) {
|
||||
_getBounds: function(getter, matrix) {
|
||||
// Create an in-memory canvas on which to do the measuring
|
||||
if (!context)
|
||||
context = CanvasProvider.getCanvas(
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
var TextItem = this.TextItem = Item.extend(/** @lends TextItem# */{
|
||||
// TextItem doesn't make the distinction between the different bounds,
|
||||
// so use the same name for all of them
|
||||
_boundsType: 'bounds',
|
||||
_boundsGetter: 'getBounds',
|
||||
|
||||
initialize: function(pointOrMatrix) {
|
||||
// Note that internally #characterStyle is the same as #style, but
|
||||
|
@ -93,7 +93,7 @@ var TextItem = this.TextItem = Item.extend(/** @lends TextItem# */{
|
|||
},
|
||||
|
||||
isEmpty: function() {
|
||||
return !!this._content;
|
||||
return !this._content;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -325,6 +325,8 @@ var Tool = this.Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
|||
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 && other[item._id])
|
||||
|
|
|
@ -72,6 +72,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
|||
lastPoint,
|
||||
overPoint,
|
||||
downItem,
|
||||
lastItem,
|
||||
overItem,
|
||||
hasDrag,
|
||||
doubleClick,
|
||||
|
@ -122,8 +123,8 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
|||
var item = handleEvent(this, 'mousedown', event, point);
|
||||
// See if we're clicking again on the same item, within the
|
||||
// double-click time. Firefox uses 300ms as the max time difference:
|
||||
doubleClick = downItem == item && Date.now() - clickTime < 300;
|
||||
downItem = item;
|
||||
doubleClick = lastItem == item && (Date.now() - clickTime < 300);
|
||||
downItem = lastItem = item;
|
||||
downPoint = lastPoint = overPoint = point;
|
||||
hasDrag = downItem && downItem.responds('mousedrag');
|
||||
},
|
||||
|
|
|
@ -57,10 +57,10 @@ var KeyEvent = this.KeyEvent = Event.extend(/** @lends KeyEvent# */{
|
|||
* @return {String} A string representation of the key event.
|
||||
*/
|
||||
toString: function() {
|
||||
return '{ type: ' + this.type
|
||||
+ ', key: ' + this.key
|
||||
+ ', character: ' + this.character
|
||||
+ ', modifiers: ' + this.getModifiers()
|
||||
+ ' }';
|
||||
return "{ type: '" + this.type
|
||||
+ "', key: '" + this.key
|
||||
+ "', character: '" + this.character
|
||||
+ "', modifiers: " + this.getModifiers()
|
||||
+ " }";
|
||||
}
|
||||
});
|
||||
|
|
|
@ -67,8 +67,8 @@ var MouseEvent = this.MouseEvent = Event.extend(/** @lends MouseEvent# */{
|
|||
* @return {String} A string representation of the mouse event.
|
||||
*/
|
||||
toString: function() {
|
||||
return '{ type: ' + this.type
|
||||
+ ', point: ' + this.point
|
||||
return "{ type: '" + this.type
|
||||
+ "', point: " + this.point
|
||||
+ ', target: ' + this.target
|
||||
+ (this.delta ? ', delta: ' + this.delta : '')
|
||||
+ ', modifiers: ' + this.getModifiers()
|
||||
|
|
164
src/ui/View.js
164
src/ui/View.js
|
@ -24,61 +24,6 @@
|
|||
* screen.
|
||||
*/
|
||||
var View = this.View = Base.extend(Callback, /** @lends View# */{
|
||||
_events: {
|
||||
onFrame: {
|
||||
install: function() {
|
||||
/*#*/ if (options.browser) {
|
||||
var that = this,
|
||||
requested = false,
|
||||
before,
|
||||
time = 0,
|
||||
count = 0;
|
||||
this._onFrameCallback = function(param, dontRequest) {
|
||||
requested = false;
|
||||
// See if we need to stop due to a call to uninstall()
|
||||
if (!that._onFrameCallback)
|
||||
return;
|
||||
// Set the global paper object to the current scope
|
||||
paper = that._scope;
|
||||
if (!dontRequest) {
|
||||
// Request next frame already
|
||||
requested = true;
|
||||
DomEvent.requestAnimationFrame(that._onFrameCallback,
|
||||
that._element);
|
||||
}
|
||||
var now = Date.now() / 1000,
|
||||
delta = before ? now - before : 0;
|
||||
// delta: Time elapsed since last redraw in seconds
|
||||
// time: Time since first call of frame() in seconds
|
||||
// Use Base.merge to convert into a Base object,
|
||||
// for #toString()
|
||||
that.fire('frame', Base.merge({
|
||||
delta: delta,
|
||||
time: time += delta,
|
||||
count: count++
|
||||
}));
|
||||
before = now;
|
||||
// Update framerate stats
|
||||
if (that._stats)
|
||||
that._stats.update();
|
||||
// Automatically draw view on each frame.
|
||||
that.draw(true);
|
||||
};
|
||||
// Call the onFrame handler straight away, initializing the
|
||||
// sequence of onFrame calls.
|
||||
if (!requested)
|
||||
this._onFrameCallback();
|
||||
/*#*/ } // options.browser
|
||||
},
|
||||
|
||||
uninstall: function() {
|
||||
delete this._onFrameCallback;
|
||||
}
|
||||
},
|
||||
|
||||
onResize: {}
|
||||
},
|
||||
|
||||
initialize: function(element) {
|
||||
// Store reference to the currently active global paper scope, and the
|
||||
// active project, which will be represented by this view
|
||||
|
@ -157,6 +102,9 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{
|
|||
// Make sure the first view is focused for keyboard input straight away
|
||||
if (!View._focused)
|
||||
View._focused = this;
|
||||
// Items that need the onFrame handler called on them
|
||||
this._frameItems = {};
|
||||
this._frameItemCount = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -178,18 +126,114 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{
|
|||
DomEvent.remove(this._element, this._viewHandlers);
|
||||
DomEvent.remove(window, this._windowHandlers);
|
||||
this._element = this._project = null;
|
||||
// Removing all onFrame handlers makes the _onFrameCallback handler stop
|
||||
// Removing all onFrame handlers makes the onFrame handler stop
|
||||
// automatically through its uninstall method.
|
||||
this.detach('frame');
|
||||
this._frameItems = {};
|
||||
return true;
|
||||
},
|
||||
|
||||
_events: {
|
||||
onFrame: {
|
||||
install: function() {
|
||||
/*#*/ if (options.browser) {
|
||||
// Call the onFrame handler straight away and initialize the
|
||||
// sequence of onFrame calls.
|
||||
if (!this._requested) {
|
||||
this._animate = true;
|
||||
this._handleFrame(true);
|
||||
}
|
||||
/*#*/ } // options.browser
|
||||
},
|
||||
|
||||
uninstall: function() {
|
||||
this._animate = false;
|
||||
}
|
||||
},
|
||||
|
||||
onResize: {}
|
||||
},
|
||||
|
||||
// These are default values for event related properties on the prototype.
|
||||
// Writing item._count++ does not change the defaults, it creates / updates
|
||||
// the property on the instance. Useful!
|
||||
_animate: false,
|
||||
_time: 0,
|
||||
_count: 0,
|
||||
|
||||
_handleFrame: function(request) {
|
||||
this._requested = false;
|
||||
// See if we need to stop due to a call to uninstall()
|
||||
if (!this._animate)
|
||||
return;
|
||||
// Set the global paper object to the current scope
|
||||
paper = this._scope;
|
||||
if (request) {
|
||||
// Request next frame already
|
||||
this._requested = true;
|
||||
var that = this;
|
||||
DomEvent.requestAnimationFrame(function() {
|
||||
that._handleFrame(true);
|
||||
}, this._element);
|
||||
}
|
||||
var now = Date.now() / 1000,
|
||||
delta = this._before ? now - this._before : 0;
|
||||
this._before = now;
|
||||
// Use Base.merge to convert into a Base object, for #toString()
|
||||
this.fire('frame', Base.merge({
|
||||
// Time elapsed since last redraw in seconds:
|
||||
delta: delta,
|
||||
// Time since first call of frame() in seconds:
|
||||
time: this._time += delta,
|
||||
count: this._count++
|
||||
}));
|
||||
// Update framerate stats
|
||||
if (this._stats)
|
||||
this._stats.update();
|
||||
// Automatically draw view on each frame.
|
||||
this.draw(true);
|
||||
},
|
||||
|
||||
_animateItem: function(item, animate) {
|
||||
var items = this._frameItems;
|
||||
if (animate) {
|
||||
items[item._id] = {
|
||||
item: item,
|
||||
// Additional information for the event callback
|
||||
time: 0,
|
||||
count: 0
|
||||
};
|
||||
if (++this._frameItemCount == 1)
|
||||
this.attach('frame', this._handleFrameItems);
|
||||
} else {
|
||||
delete items[item._id];
|
||||
if (--this._frameItemCount == 0) {
|
||||
// If this is the last one, just stop animating straight away.
|
||||
this.detach('frame', this._handleFrameItems);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// An empty callback that's only there so _frameItems can be handled
|
||||
// through the onFrame callback framework that automatically starts and
|
||||
// stops the animation for us whenever there's one or more frame handlers
|
||||
_handleFrameItems: function(event) {
|
||||
for (var i in this._frameItems) {
|
||||
var entry = this._frameItems[i];
|
||||
entry.item.fire('frame', Base.merge(event, {
|
||||
// Time since first call of frame() in seconds:
|
||||
time: entry.time += event.delta,
|
||||
count: entry.count++
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_redraw: function() {
|
||||
this._redrawNeeded = true;
|
||||
if (this._onFrameCallback) {
|
||||
// If there's a _onFrameCallback, call it staight away,
|
||||
// but without requesting another animation frame.
|
||||
this._onFrameCallback(0, true);
|
||||
if (this._animate) {
|
||||
// If we're animating, call _handleFrame staight away, but without
|
||||
// requesting another animation frame.
|
||||
this._handleFrame();
|
||||
} else {
|
||||
// Otherwise simply redraw the view now
|
||||
this.draw();
|
||||
|
|
|
@ -335,3 +335,9 @@ function compareItems(item, item2, checkIdentity) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SVG
|
||||
|
||||
function createSvg(xml) {
|
||||
return new DOMParser().parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + xml + '</svg>', 'application/xml');
|
||||
}
|
|
@ -20,38 +20,38 @@ test('Set named color', function() {
|
|||
var path = new Path();
|
||||
path.fillColor = 'red';
|
||||
compareRgbColors(path.fillColor, new RgbColor(1, 0, 0));
|
||||
equals(path.fillColor.toCssString(), 'rgba(255, 0, 0, 1)');
|
||||
equals(path.fillColor.toCss(), 'rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
test('Set color to hex', function() {
|
||||
var path = new Path();
|
||||
path.fillColor = '#ff0000';
|
||||
compareRgbColors(path.fillColor, new RgbColor(1, 0, 0));
|
||||
equals(path.fillColor.toCssString(), 'rgba(255, 0, 0, 1)');
|
||||
equals(path.fillColor.toCss(), 'rgb(255, 0, 0)');
|
||||
|
||||
var path = new Path();
|
||||
path.fillColor = '#f00';
|
||||
compareRgbColors(path.fillColor, new RgbColor(1, 0, 0));
|
||||
equals(path.fillColor.toCssString(), 'rgba(255, 0, 0, 1)');
|
||||
equals(path.fillColor.toCss(), 'rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
test('Set color to object', function() {
|
||||
var path = new Path();
|
||||
path.fillColor = { red: 1, green: 0, blue: 1};
|
||||
compareRgbColors(path.fillColor, new RgbColor(1, 0, 1));
|
||||
equals(path.fillColor.toCssString(), 'rgba(255, 0, 255, 1)');
|
||||
equals(path.fillColor.toCss(), 'rgb(255, 0, 255)');
|
||||
|
||||
var path = new Path();
|
||||
path.fillColor = { gray: 0.2 };
|
||||
compareRgbColors(path.fillColor, new RgbColor(0.8, 0.8, 0.8));
|
||||
equals(path.fillColor.toCssString(), 'rgba(204, 204, 204, 1)');
|
||||
equals(path.fillColor.toCss(), 'rgb(204, 204, 204)');
|
||||
});
|
||||
|
||||
test('Set color to array', function() {
|
||||
var path = new Path();
|
||||
path.fillColor = [1, 0, 0];
|
||||
compareRgbColors(path.fillColor, new RgbColor(1, 0, 0));
|
||||
equals(path.fillColor.toCssString(), 'rgba(255, 0, 0, 1)');
|
||||
equals(path.fillColor.toCss(), 'rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
test('Creating colors', function() {
|
||||
|
|
|
@ -70,4 +70,23 @@ test('clockwise', function() {
|
|||
equals(function() {
|
||||
return path3.clockwise;
|
||||
}, false);
|
||||
})
|
||||
});
|
||||
|
||||
test('Cloning with non-standard clockwise settings', function() {
|
||||
var path1 = new Path.Rectangle([200, 200], [100, 100]);
|
||||
var path2 = new Path.Rectangle([50, 50], [200, 200]);
|
||||
var path3 = new Path.Rectangle([0, 0], [400, 400]);
|
||||
path1.clockwise = false;
|
||||
path2.clockwise = true;
|
||||
path3.clockwise = true;
|
||||
var compound = new CompoundPath(path1, path2, path3);
|
||||
equals(function() {
|
||||
return path1.clockwise;
|
||||
}, false);
|
||||
equals(function() {
|
||||
return path2.clockwise;
|
||||
}, true);
|
||||
equals(function() {
|
||||
return path3.clockwise;
|
||||
}, true);
|
||||
});
|
|
@ -50,4 +50,12 @@ test('group.bounds when group contains empty group', function() {
|
|||
compareRectangles(group.bounds, { x: 75, y: 75, width: 100, height: 100 }, 'group.bounds without empty group');
|
||||
group.addChild(new Group());
|
||||
compareRectangles(group.bounds, { x: 75, y: 75, width: 100, height: 100 }, 'group.bounds with empty group');
|
||||
});
|
||||
|
||||
test('path.bounds when contained in a transformed group', function() {
|
||||
var path = new Path([10, 10], [60, 60]);
|
||||
var group = new Group([path]);
|
||||
compareRectangles(path.bounds, { x: 10, y: 10, width: 50, height: 50 }, 'path.bounds before group translation');
|
||||
group.translate(100, 100);
|
||||
compareRectangles(path.bounds, { x: 110, y: 110, width: 50, height: 50 }, 'path.bounds after group translation');
|
||||
});
|
|
@ -68,7 +68,10 @@ test('Curve list after removing a segment - 1', function() {
|
|||
return path.curves.length;
|
||||
}, 2, 'After creating a path with three segments, we should have two curves. By accessing path.curves we also make sure the curves are created internally.');
|
||||
|
||||
path.segments[1].remove();
|
||||
equals(function() {
|
||||
return path.segments[1].remove();
|
||||
}, true, 'Removing the paths second segment should be succesfull.');
|
||||
|
||||
equals(function() {
|
||||
return path.curves.length;
|
||||
}, 1, 'After removing the middle segment, we should be left with one curve');
|
||||
|
@ -81,7 +84,9 @@ test('Curve list after removing a segment - 2', function() {
|
|||
return path.curves.length;
|
||||
}, 2, 'After creating a path with three segments, we should have two curves. By accessing path.curves we also make sure the curves are created internally.');
|
||||
|
||||
path.segments[2].remove();
|
||||
equals(function() {
|
||||
return path.segments[2].remove();
|
||||
}, true, 'Removing the paths last segment should be succesfull.');
|
||||
|
||||
equals(function() {
|
||||
return path.curves.length;
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
|
||||
module('SvgImport');
|
||||
|
||||
test('Import complex CompoundPath and clone', function() {
|
||||
var svg = createSvg('<path id="path" fill="red" d="M4,14h20v-2H4V14z M15,26h7v-2h-7V26z M15,22h9v-2h-9V22z M15,18h9v-2h-9V18z M4,26h9V16H4V26z M28,10V6H0v22c0,0,0,4,4,4 h25c0,0,3-0.062,3-4V10H28z M4,30c-2,0-2-2-2-2V8h24v20c0,0.921,0.284,1.558,0.676,2H4z"/>;');
|
||||
var item = paper.project.importSvg(svg.getElementById('path'));
|
||||
compareItems(item, item.clone());
|
||||
});
|
||||
|
||||
test('make an svg line', function() {
|
||||
var svgns = 'http://www.w3.org/2000/svg';
|
||||
var shape = document.createElementNS(svgns, 'line');
|
||||
|
|
Loading…
Reference in a new issue