mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
092c893d38
16 changed files with 422 additions and 168 deletions
|
@ -37,6 +37,8 @@ var Document = this.Document = Base.extend({
|
|||
this.symbols = [];
|
||||
this.views = [new DocumentView(this)];
|
||||
this.activeView = this.views[0];
|
||||
this._selectedItems = {};
|
||||
this._selectedItemCount = 0;
|
||||
},
|
||||
|
||||
getCurrentStyle: function() {
|
||||
|
@ -56,41 +58,53 @@ var Document = this.Document = Base.extend({
|
|||
return false;
|
||||
},
|
||||
|
||||
getSelectionContext: function(param) {
|
||||
var context = this._selectionContext;
|
||||
if (!context) {
|
||||
var canvas = CanvasProvider.getCanvas(this.size);
|
||||
context = this._selectionContext = canvas.getContext('2d');
|
||||
context.strokeWidth = 1;
|
||||
getSelectedItems: function() {
|
||||
// TODO: return groups if their children are all selected,
|
||||
// and filter out their children from the list.
|
||||
var items = [];
|
||||
Base.each(this._selectedItems, function(item) {
|
||||
items.push(item);
|
||||
});
|
||||
return items;
|
||||
},
|
||||
|
||||
// TODO: implement setSelectedItems?
|
||||
|
||||
_selectItem: function(item, select) {
|
||||
if (select) {
|
||||
this._selectedItemCount++;
|
||||
this._selectedItems[item.getId()] = item;
|
||||
} else {
|
||||
this._selectedItemCount--;
|
||||
delete this._selectedItems[item.getId()];
|
||||
}
|
||||
context.strokeStyle = context.fillStyle = param.layerColor;
|
||||
return context;
|
||||
},
|
||||
|
||||
draw: function() {
|
||||
if (this.canvas) {
|
||||
var context = this.context;
|
||||
// Initial tests conclude that clearing the canvas using clearRect
|
||||
// is always faster than setting canvas.width = canvas.width
|
||||
// http://jsperf.com/clearrect-vs-setting-width/7
|
||||
this.context.clearRect(0, 0,
|
||||
context.clearRect(0, 0,
|
||||
this.size.width + 1, this.size.height + 1);
|
||||
this.context.save();
|
||||
context.save();
|
||||
var param = { offset: new Point(0, 0) };
|
||||
for (var i = 0, l = this.layers.length; i < l; i++) {
|
||||
// TODO: use Layer#color:
|
||||
param.layerColor = '#4f7aff';
|
||||
Item.draw(this.layers[i], this.context, param);
|
||||
}
|
||||
this.context.restore();
|
||||
|
||||
// If, during drawing, one of the paths was selected, there will
|
||||
// be a selectionContext which needs to be composited onto the
|
||||
// canvas:
|
||||
if (this._selectionContext) {
|
||||
var canvas = this._selectionContext.canvas;
|
||||
this.context.drawImage(canvas, 0, 0);
|
||||
CanvasProvider.returnCanvas(canvas);
|
||||
this._selectionContext = null;
|
||||
for (var i = 0, l = this.layers.length; i < l; i++)
|
||||
Item.draw(this.layers[i], context, param);
|
||||
context.restore();
|
||||
|
||||
// Draw the selection of the selected items in the document:
|
||||
if (this._selectedItemCount > 0) {
|
||||
context.save();
|
||||
context.strokeWidth = 1;
|
||||
// Todo: use Layer#color
|
||||
context.strokeStyle = context.fillStyle = '#4f7aff';
|
||||
param = { selection: true };
|
||||
Base.each(this._selectedItems, function(item) {
|
||||
item.draw(context, param);
|
||||
});
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@ var Item = this.Item = Base.extend({
|
|||
|
||||
initialize: function() {
|
||||
paper.document.activeLayer.appendTop(this);
|
||||
this.setStyle(this.document.getCurrentStyle());
|
||||
this.setStyle(this._document.getCurrentStyle());
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -60,20 +60,39 @@ var Item = this.Item = Base.extend({
|
|||
child.setSelected(selected);
|
||||
}
|
||||
} else {
|
||||
this._selected = selected;
|
||||
if (selected != this._selected) {
|
||||
// TODO: when an item is removed or moved to another
|
||||
// document, it needs to be removed from _selectedItems
|
||||
this._selected = selected;
|
||||
this._document._selectItem(this, selected);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getSelected: function() {
|
||||
if (this._children) {
|
||||
for (var i = 0, l = this._children.length; i < l; i++) {
|
||||
var child = this._children[i];
|
||||
if (child.getSelected())
|
||||
isSelected: function() {
|
||||
if (this.children) {
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
if (this.children[i].isSelected())
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return !!this._selected;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getDocument: function() {
|
||||
return this._document;
|
||||
},
|
||||
|
||||
setDocument: function(document) {
|
||||
if (document != this._document) {
|
||||
this._document = document;
|
||||
if (this.children) {
|
||||
for (var i = 0, l = this.children.length; i < l; i++)
|
||||
this.children[i].setDocument(document);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: isFullySelected / setFullySelected
|
||||
|
@ -252,6 +271,8 @@ var Item = this.Item = Base.extend({
|
|||
* Removes the item.
|
||||
*/
|
||||
remove: function() {
|
||||
if(this.isSelected())
|
||||
this.setSelected(false);
|
||||
return this.removeFromParent();
|
||||
},
|
||||
|
||||
|
@ -749,7 +770,7 @@ var Item = this.Item = Base.extend({
|
|||
item.removeFromParent();
|
||||
this.children.splice(top ? this.children.length : 0, 0, item);
|
||||
item.parent = this;
|
||||
item.document = this.document;
|
||||
item.setDocument(this._document);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -763,7 +784,7 @@ var Item = this.Item = Base.extend({
|
|||
item.parent.children.splice(item.getIndex()
|
||||
+ (above ? 1 : -1), 0, this);
|
||||
this.parent = item.parent;
|
||||
this.document = item.document;
|
||||
this.setDocument(item._document);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -19,13 +19,13 @@ var Layer = this.Layer = Group.extend({
|
|||
|
||||
initialize: function() {
|
||||
this.children = [];
|
||||
this.document = paper.document;
|
||||
this.document.layers.push(this);
|
||||
this._document = paper.document;
|
||||
this._document.layers.push(this);
|
||||
this.activate();
|
||||
},
|
||||
|
||||
getIndex: function() {
|
||||
return this.parent ? this.base() : this.document.layers.indexOf(this);
|
||||
return this.parent ? this.base() : this._document.layers.indexOf(this);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ var Layer = this.Layer = Group.extend({
|
|||
*/
|
||||
removeFromParent: function() {
|
||||
if (!this.parent) {
|
||||
return !!this.document.layers.splice(this.getIndex(), 1).length;
|
||||
return !!this._document.layers.splice(this.getIndex(), 1).length;
|
||||
} else {
|
||||
return this.base();
|
||||
}
|
||||
|
@ -42,16 +42,16 @@ var Layer = this.Layer = Group.extend({
|
|||
|
||||
getNextSibling: function() {
|
||||
return this.parent ? this.base()
|
||||
: this.document.layers[this.getIndex() + 1] || null;
|
||||
: this._document.layers[this.getIndex() + 1] || null;
|
||||
},
|
||||
|
||||
getPreviousSibling: function() {
|
||||
return this.parent ? this.base()
|
||||
: this.document.layers[this.getIndex() - 1] || null;
|
||||
: this._document.layers[this.getIndex() - 1] || null;
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.document.activeLayer = this;
|
||||
this._document.activeLayer = this;
|
||||
}
|
||||
}, new function () {
|
||||
function move(above) {
|
||||
|
@ -59,9 +59,9 @@ var Layer = this.Layer = Group.extend({
|
|||
// if the item is a layer and contained within Document#layers
|
||||
if (item instanceof Layer && !item.parent) {
|
||||
this.removeFromParent();
|
||||
item.document.layers.splice(item.getIndex() + (above ? 1 : -1),
|
||||
item._document.layers.splice(item.getIndex() + (above ? 1 : -1),
|
||||
0, this);
|
||||
this.document = item.document;
|
||||
this.setDocument(item._document);
|
||||
return true;
|
||||
} else {
|
||||
return this.base(item);
|
||||
|
|
|
@ -53,13 +53,14 @@ var PlacedSymbol = this.PlacedSymbol = Item.extend({
|
|||
},
|
||||
|
||||
draw: function(ctx, param) {
|
||||
ctx.save();
|
||||
this.matrix.applyToContext(ctx);
|
||||
Item.draw(this.symbol.getDefinition(), ctx, param);
|
||||
ctx.restore();
|
||||
if (this.getSelected()) {
|
||||
if (param.selection) {
|
||||
Item.drawSelectedBounds(this.symbol._definition.getStrokeBounds(),
|
||||
this.document.getSelectionContext(param), this.matrix);
|
||||
ctx, this.matrix);
|
||||
} else {
|
||||
ctx.save();
|
||||
this.matrix.applyToContext(ctx);
|
||||
Item.draw(this.symbol.getDefinition(), ctx, param);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -202,15 +202,15 @@ var Raster = this.Raster = Item.extend({
|
|||
},
|
||||
|
||||
draw: function(ctx, param) {
|
||||
ctx.save();
|
||||
this.matrix.applyToContext(ctx);
|
||||
ctx.drawImage(this._canvas || this._image,
|
||||
-this._size.width / 2, -this._size.height / 2);
|
||||
ctx.restore();
|
||||
if (this.getSelected()) {
|
||||
if (param.selection) {
|
||||
var bounds = new Rectangle(this._size).setCenter(0, 0);
|
||||
Item.drawSelectedBounds(bounds,
|
||||
this.document.getSelectionContext(param), this.matrix);
|
||||
Item.drawSelectedBounds(bounds, ctx, this.matrix);
|
||||
} else {
|
||||
ctx.save();
|
||||
this.matrix.applyToContext(ctx);
|
||||
ctx.drawImage(this._canvas || this._image,
|
||||
-this._size.width / 2, -this._size.height / 2);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}, new function() {
|
||||
|
|
|
@ -44,6 +44,7 @@ var sources = [
|
|||
|
||||
'src/path/Segment.js',
|
||||
'src/path/SegmentPoint.js',
|
||||
'src/path/SelectionState.js',
|
||||
'src/path/Curve.js',
|
||||
'src/path/CurveLocation.js',
|
||||
'src/path/PathItem.js',
|
||||
|
|
|
@ -92,6 +92,7 @@ Base.inject({
|
|||
|
||||
//#include "path/Segment.js"
|
||||
//#include "path/SegmentPoint.js"
|
||||
//#include "path/SelectionState.js"
|
||||
//#include "path/Curve.js"
|
||||
//#include "path/CurveLocation.js"
|
||||
//#include "path/PathItem.js"
|
||||
|
|
|
@ -19,9 +19,8 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
|
|||
this.base();
|
||||
this.children = [];
|
||||
if (items) {
|
||||
for (var i = 0, l = items.length; i < l; i++) {
|
||||
for (var i = 0, l = items.length; i < l; i++)
|
||||
this.appendTop(items[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -29,11 +28,11 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
|
|||
// code (from a utility script?)
|
||||
getBounds: function() {
|
||||
if (this.children.length) {
|
||||
var rect = this.children[0].getBounds();
|
||||
var x1 = rect.x;
|
||||
var y1 = rect.y;
|
||||
var x2 = rect.x + rect.width;
|
||||
var y2 = rect.y + rect.height;
|
||||
var rect = this.children[0].getBounds(),
|
||||
x1 = rect.x,
|
||||
y1 = rect.y,
|
||||
x2 = rect.x + rect.width,
|
||||
y2 = rect.y + rect.height;
|
||||
for (var i = 1, l = this.children.length; i < l; i++) {
|
||||
var rect2 = this.children[i].getBounds();
|
||||
x1 = Math.min(rect2.x, x1);
|
||||
|
@ -64,9 +63,8 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
|
|||
},
|
||||
|
||||
smooth: function() {
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
for (var i = 0, l = this.children.length; i < l; i++)
|
||||
this.children[i].smooth();
|
||||
}
|
||||
},
|
||||
|
||||
moveTo: function() {
|
||||
|
@ -79,9 +77,8 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
|
|||
var firstChild = this.children[0];
|
||||
ctx.beginPath();
|
||||
param.compound = true;
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
for (var i = 0, l = this.children.length; i < l; i++)
|
||||
Item.draw(this.children[i], ctx, param);
|
||||
}
|
||||
firstChild.setContextStyles(ctx);
|
||||
var fillColor = firstChild.getFillColor(),
|
||||
strokeColor = firstChild.getStrokeColor();
|
||||
|
@ -106,15 +103,14 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
|
|||
|
||||
var fields = {
|
||||
moveBy: function() {
|
||||
var point = arguments.length ? Point.read(arguments) : new Point();
|
||||
var path = getCurrentPath(this);
|
||||
var current = path.segments[path.segments.length - 1].point;
|
||||
var point = arguments.length ? Point.read(arguments) : new Point(),
|
||||
path = getCurrentPath(this),
|
||||
current = path.segments[path.segments.length - 1]._point;
|
||||
this.moveTo(current.add(point));
|
||||
},
|
||||
|
||||
closePath: function() {
|
||||
var path = getCurrentPath(this);
|
||||
path.closed = true;
|
||||
getCurrentPath(this).closed = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -140,6 +140,17 @@ var Curve = this.Curve = Base.extend({
|
|||
return curves && (curves[this._index1 - 1]
|
||||
|| this._path.closed && curves[curves.length - 1]) || null;
|
||||
},
|
||||
|
||||
// TODO: port back to Scriptographer?
|
||||
setSelected: function(selected) {
|
||||
this.getHandle1().setSelected(selected);
|
||||
this.getHandle2().setSelected(selected);
|
||||
},
|
||||
|
||||
// TODO: port back to Scriptographer?
|
||||
isSelected: function() {
|
||||
return this.getHandle1().isSelected() && this.getHandle2.isSelected();
|
||||
},
|
||||
|
||||
getCurveValues: function() {
|
||||
var p1 = this._segment1._point,
|
||||
|
|
188
src/path/Path.js
188
src/path/Path.js
|
@ -20,6 +20,7 @@ var Path = this.Path = PathItem.extend({
|
|||
initialize: function(segments) {
|
||||
this.base();
|
||||
this.closed = false;
|
||||
this._selectedSegmentCount = 0;
|
||||
// Support both passing of segments as array or arguments
|
||||
// If it is an array, it can also be a description of a point, so
|
||||
// check its first entry for object as well
|
||||
|
@ -128,6 +129,29 @@ var Path = this.Path = PathItem.extend({
|
|||
}
|
||||
},
|
||||
|
||||
isSelected: function() {
|
||||
return this._selectedSegmentCount > 0;
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
var wasSelected = this.isSelected(),
|
||||
length = this._segments.length;
|
||||
if (!wasSelected != !selected && length)
|
||||
this._document._selectItem(this, selected);
|
||||
this._selectedSegmentCount = selected ? length : 0;
|
||||
for (var i = 0; i < length; i++)
|
||||
this._segments[i]._selectionState = selected
|
||||
? SelectionState.POINT : null;
|
||||
},
|
||||
|
||||
isFullySelected: function() {
|
||||
return this._selectedSegmentCount == this._segments.length;
|
||||
},
|
||||
|
||||
setFullySelected: function(selected) {
|
||||
this.setSelected(selected);
|
||||
},
|
||||
|
||||
// TODO: pointsToCurves([tolerance[, threshold[, cornerRadius[, scale]]]])
|
||||
// TODO: curvesToPoints([maxPointDistance[, flatness]])
|
||||
// TODO: reduceSegments([flatness])
|
||||
|
@ -142,22 +166,22 @@ var Path = this.Path = PathItem.extend({
|
|||
|
||||
join: function(path) {
|
||||
if (path != null) {
|
||||
var segments = path.segments;
|
||||
var last1 = this.getLastSegment();
|
||||
var last2 = path.getLastSegment();
|
||||
if (last1.getPoint().equals(last2.getPoint()))
|
||||
var segments = path.segments,
|
||||
last1 = this.getLastSegment(),
|
||||
last2 = path.getLastSegment();
|
||||
if (last1._point.equals(last2._point))
|
||||
path.reverse();
|
||||
var first2 = path.getFirstSegment();
|
||||
if (last1.getPoint().equals(first2.getPoint())) {
|
||||
last1.setHandleOut(first2.getHandleOut());
|
||||
if (last1._point.equals(first2._point)) {
|
||||
last1.setHandleOut(first2._handleOut);
|
||||
for (var i = 1, l = segments.length; i < l; i++)
|
||||
this._add(segments[i]);
|
||||
} else {
|
||||
var first1 = this.getFirstSegment();
|
||||
if (first1.getPoint().equals(first2.getPoint()))
|
||||
if (first1._point.equals(first2._point))
|
||||
path.reverse();
|
||||
if (first1.getPoint().equals(last2.getPoint())) {
|
||||
first1.setHandleIn(last2.getHandleIn());
|
||||
if (first1._point.equals(last2._point)) {
|
||||
first1.setHandleIn(last2._handleIn);
|
||||
// Prepend all segments from path except last one
|
||||
for (var i = 0, l = segments.length - 1; i < l; i++)
|
||||
this._add(segments[i], 0);
|
||||
|
@ -170,8 +194,8 @@ var Path = this.Path = PathItem.extend({
|
|||
// Close if they touch in both places
|
||||
var first1 = this.getFirstSegment();
|
||||
last1 = this.getLastSegment();
|
||||
if (last1.getPoint().equals(first1.getPoint())) {
|
||||
first1.setHandleIn(last1.getHandleIn());
|
||||
if (last1._point.equals(first1._point)) {
|
||||
first1.setHandleIn(last1._handleIn);
|
||||
last1.remove();
|
||||
this.closed = true;
|
||||
}
|
||||
|
@ -182,13 +206,13 @@ var Path = this.Path = PathItem.extend({
|
|||
|
||||
// todo: getLocation(point, precision)
|
||||
getLocation: function(length) {
|
||||
var curves = this.getCurves();
|
||||
var currentLength = 0;
|
||||
var curves = this.getCurves(),
|
||||
currentLength = 0;
|
||||
for (var i = 0, l = curves.length; i < l; i++) {
|
||||
var startLength = currentLength;
|
||||
var curve = curves[i];
|
||||
var startLength = currentLength,
|
||||
curve = curves[i];
|
||||
currentLength += curve.getLength();
|
||||
if(currentLength >= length) {
|
||||
if (currentLength >= length) {
|
||||
// found the segment within which the length lies
|
||||
var t = curve.getParameter(length - startLength);
|
||||
return new CurveLocation(curve, t);
|
||||
|
@ -206,12 +230,12 @@ var Path = this.Path = PathItem.extend({
|
|||
|
||||
getLength: function(/* location */) {
|
||||
var location;
|
||||
if(arguments.length)
|
||||
if (arguments.length)
|
||||
location = arguments[0];
|
||||
var curves = this.getCurves();
|
||||
var index = location
|
||||
? location.getIndex()
|
||||
: curves.length;
|
||||
var curves = this.getCurves(),
|
||||
index = location
|
||||
? location.getIndex()
|
||||
: curves.length;
|
||||
if (index != -1) {
|
||||
var length = 0;
|
||||
for (var i = 0; i < index; i++)
|
||||
|
@ -254,26 +278,28 @@ var Path = this.Path = PathItem.extend({
|
|||
function drawHandles(ctx, segments) {
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var segment = segments[i],
|
||||
handleIn = segment.handleIn,
|
||||
handleOut = segment.handleOut,
|
||||
point = segment.point,
|
||||
rounded = point.round();
|
||||
point = segment._point,
|
||||
pointSelected = segment._selectionState == SelectionState.POINT;
|
||||
// TODO: draw handles depending on selection state of
|
||||
// segment.point and neighbouring segments.
|
||||
drawHandle(ctx, point, handleIn);
|
||||
drawHandle(ctx, point, handleOut);
|
||||
if (pointSelected || segment.isSelected(segment._handleIn))
|
||||
drawHandle(ctx, point, segment._handleIn);
|
||||
if (pointSelected || segment.isSelected(segment._handleOut))
|
||||
drawHandle(ctx, point, segment._handleOut);
|
||||
// Draw a rectangle at segment.point:
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(rounded.x - 2, rounded.y - 2, 4, 4);
|
||||
ctx.rect(point._x - 2, point._y - 2, 4, 4);
|
||||
ctx.fill();
|
||||
// TODO: Only draw white rectangle if point.isSelected()
|
||||
// is false:
|
||||
ctx.beginPath();
|
||||
ctx.rect(rounded.x - 1, rounded.y - 1, 2, 2);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
if (!pointSelected) {
|
||||
ctx.beginPath();
|
||||
ctx.rect(point._x - 1, point._y - 1, 2, 2);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,12 +307,11 @@ var Path = this.Path = PathItem.extend({
|
|||
if (!handle.isZero()) {
|
||||
handle = handle.add(point);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(point.x, point.y);
|
||||
ctx.moveTo(point._x, point._y);
|
||||
ctx.lineTo(handle.x, handle.y);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
var rounded = handle.round();
|
||||
ctx.rect(rounded.x - 1, rounded.y - 1, 2, 2);
|
||||
ctx.rect(handle.x - 1, handle.y - 1, 2, 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
@ -301,8 +326,8 @@ var Path = this.Path = PathItem.extend({
|
|||
for (var i = 0; i < length; i++) {
|
||||
var segment = segments[i],
|
||||
point = segment._point,
|
||||
x = point.x,
|
||||
y = point.y,
|
||||
x = point._x,
|
||||
y = point._y,
|
||||
handleIn = segment._handleIn;
|
||||
if (i == 0) {
|
||||
ctx.moveTo(x, y);
|
||||
|
@ -312,34 +337,34 @@ var Path = this.Path = PathItem.extend({
|
|||
} else {
|
||||
ctx.bezierCurveTo(
|
||||
outX, outY,
|
||||
handleIn.x + x, handleIn.y + y,
|
||||
handleIn._x + x, handleIn._y + y,
|
||||
x, y
|
||||
);
|
||||
}
|
||||
}
|
||||
handleOut = segment._handleOut;
|
||||
outX = handleOut.x + x;
|
||||
outY = handleOut.y + y;
|
||||
outX = handleOut._x + x;
|
||||
outY = handleOut._y + y;
|
||||
}
|
||||
if (this.closed && length > 1) {
|
||||
var segment = segments[0],
|
||||
point = segment._point,
|
||||
x = point.x,
|
||||
y = point.y,
|
||||
x = point._x,
|
||||
y = point._y,
|
||||
handleIn = segment._handleIn;
|
||||
ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
|
||||
ctx.bezierCurveTo(outX, outY, handleIn._x + x, handleIn._y + y, x, y);
|
||||
ctx.closePath();
|
||||
}
|
||||
// If the path is part of a compound path or doesn't have a fill or
|
||||
// stroke, there is no need to continue.
|
||||
var fillColor = this.getFillColor(),
|
||||
strokeColor = this.getStrokeColor();
|
||||
// If we are drawing onto the selection canvas, stroke the
|
||||
// path and draw its handles.
|
||||
// If we are drawing the selection of a path, stroke it and draw
|
||||
// its handles:
|
||||
if (param.selection) {
|
||||
ctx.stroke();
|
||||
drawHandles(ctx, this.segments);
|
||||
drawHandles(ctx, this._segments);
|
||||
} else {
|
||||
// If the path is part of a compound path or doesn't have a fill or
|
||||
// stroke, there is no need to continue.
|
||||
var fillColor = this.getFillColor(),
|
||||
strokeColor = this.getStrokeColor();
|
||||
if (!param.compound && (fillColor || strokeColor)) {
|
||||
this.setContextStyles(ctx);
|
||||
ctx.save();
|
||||
|
@ -358,14 +383,6 @@ var Path = this.Path = PathItem.extend({
|
|||
}
|
||||
ctx.restore();
|
||||
}
|
||||
// If the path is selected, draw it again on the separate
|
||||
// selection canvas, which will be composited onto the canvas
|
||||
// after drawing of the document is complete.
|
||||
if (this.getSelected()) {
|
||||
param.selection = true;
|
||||
this.draw(this.document.getSelectionContext(param), param);
|
||||
param.selection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -400,6 +417,7 @@ var Path = this.Path = PathItem.extend({
|
|||
this[name] = value;
|
||||
}, []);
|
||||
} else {
|
||||
this.setSelected(false);
|
||||
this._segments.length = 0;
|
||||
}
|
||||
for(var i = 0; i < length; i++) {
|
||||
|
@ -417,10 +435,10 @@ var Path = this.Path = PathItem.extend({
|
|||
* @return Solution vector.
|
||||
*/
|
||||
function getFirstControlPoints(rhs) {
|
||||
var n = rhs.length;
|
||||
var x = []; // Solution vector.
|
||||
var tmp = []; // Temporary workspace.
|
||||
var b = 2;
|
||||
var n = rhs.length,
|
||||
x = [], // Solution vector.
|
||||
tmp = [], // Temporary workspace.
|
||||
b = 2;
|
||||
x[0] = rhs[0] / b;
|
||||
// Decomposition and forward substitution.
|
||||
for (var i = 1; i < n; i++) {
|
||||
|
@ -446,8 +464,6 @@ var Path = this.Path = PathItem.extend({
|
|||
beans: true,
|
||||
|
||||
smooth: function() {
|
||||
var segments = this._segments;
|
||||
|
||||
// This code is based on the work by Oleg V. Polikarpotchkin,
|
||||
// http://ov-p.spaces.live.com/blog/cns!39D56F0C7A08D703!147.entry
|
||||
// It was extended to support closed paths by averaging overlapping
|
||||
|
@ -455,13 +471,15 @@ var Path = this.Path = PathItem.extend({
|
|||
// Polikarpotchkin's closed curve solution, but reuses the same
|
||||
// algorithm as for open paths, and is probably executing faster as
|
||||
// well, so it is preferred.
|
||||
var size = segments.length;
|
||||
var segments = this._segments,
|
||||
size = segments.length,
|
||||
n = size,
|
||||
// Add overlapping ends for averaging handles in closed paths
|
||||
overlap;
|
||||
|
||||
if (size <= 2)
|
||||
return;
|
||||
|
||||
var n = size;
|
||||
// Add overlapping ends for averaging handles in closed paths
|
||||
var overlap;
|
||||
if (this.closed) {
|
||||
// Overlap up to 4 points since averaging beziers affect the 4
|
||||
// neighboring points
|
||||
|
@ -489,17 +507,17 @@ var Path = this.Path = PathItem.extend({
|
|||
|
||||
// Set right hand side X values
|
||||
for (var i = 1; i < n - 1; i++)
|
||||
rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
|
||||
rhs[0] = knots[0].x + 2 * knots[1].x;
|
||||
rhs[n - 1] = 3 * knots[n - 1].x;
|
||||
rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
|
||||
rhs[0] = knots[0]._x + 2 * knots[1]._x;
|
||||
rhs[n - 1] = 3 * knots[n - 1]._x;
|
||||
// Get first control points X-values
|
||||
var x = getFirstControlPoints(rhs);
|
||||
|
||||
// Set right hand side Y values
|
||||
for (var i = 1; i < n - 1; i++)
|
||||
rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
|
||||
rhs[0] = knots[0].y + 2 * knots[1].y;
|
||||
rhs[n - 1] = 3 * knots[n - 1].y;
|
||||
rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
|
||||
rhs[0] = knots[0]._y + 2 * knots[1]._y;
|
||||
rhs[n - 1] = 3 * knots[n - 1]._y;
|
||||
// Get first control points Y-values
|
||||
var y = getFirstControlPoints(rhs);
|
||||
|
||||
|
@ -530,12 +548,12 @@ var Path = this.Path = PathItem.extend({
|
|||
new Point(x[i], y[i]).subtract(segment._point));
|
||||
if (i < n - 1)
|
||||
handleIn = new Point(
|
||||
2 * knots[i + 1].x - x[i + 1],
|
||||
2 * knots[i + 1].y - y[i + 1]);
|
||||
2 * knots[i + 1]._x - x[i + 1],
|
||||
2 * knots[i + 1]._y - y[i + 1]);
|
||||
else
|
||||
handleIn = new Point(
|
||||
(knots[n].x + x[n - 1]) / 2,
|
||||
(knots[n].y + y[n - 1]) / 2);
|
||||
(knots[n]._x + x[n - 1]) / 2,
|
||||
(knots[n]._y + y[n - 1]) / 2);
|
||||
}
|
||||
}
|
||||
if (this.closed && handleIn) {
|
||||
|
@ -591,8 +609,8 @@ var Path = this.Path = PathItem.extend({
|
|||
var current = getCurrentSegment(this);
|
||||
// Convert to relative values:
|
||||
current.setHandleOut(new Point(
|
||||
handle1.x - current._point.x,
|
||||
handle1.y - current._point.y));
|
||||
handle1.x - current._point._x,
|
||||
handle1.y - current._point._y));
|
||||
// And add the new segment, with handleIn set to c2
|
||||
this._add(new Segment(to, handle2.subtract(to), new Point()));
|
||||
},
|
||||
|
@ -626,8 +644,8 @@ var Path = this.Path = PathItem.extend({
|
|||
var current = getCurrentSegment(this)._point;
|
||||
// handle = (through - (1 - t)^2 * current - t^2 * to) /
|
||||
// (2 * (1 - t) * t)
|
||||
var t1 = 1 - t;
|
||||
var handle = through.subtract(current.multiply(t1 * t1)).subtract(
|
||||
var t1 = 1 - t,
|
||||
handle = through.subtract(current.multiply(t1 * t1)).subtract(
|
||||
to.multiply(t * t)).divide(2 * t * t1);
|
||||
if (handle.isNaN())
|
||||
throw new Error(
|
||||
|
@ -653,8 +671,8 @@ var Path = this.Path = PathItem.extend({
|
|||
: middle.add(-step.y, step.x);
|
||||
}
|
||||
|
||||
var x1 = current._point.x, x2 = through.x, x3 = to.x,
|
||||
y1 = current._point.y, y2 = through.y, y3 = to.y,
|
||||
var x1 = current._point._x, x2 = through.x, x3 = to.x,
|
||||
y1 = current._point._y, y2 = through.y, y3 = to.y,
|
||||
|
||||
f = x3 * x3 - x3 * x2 - x1 * x3 + x1 * x2 + y3 * y3 - y3 * y2
|
||||
- y1 * y3 + y1 * y2,
|
||||
|
|
|
@ -125,12 +125,111 @@ var Segment = this.Segment = Base.extend({
|
|||
return this._path && this._path._segments[this.getIndex() - 1] || null;
|
||||
},
|
||||
|
||||
// TODO:
|
||||
// isSelected: function() {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// setSelected: function(pt, selected)
|
||||
isSelected: function(/* point */) {
|
||||
var point = arguments.length ? arguments[0] : this._point;
|
||||
var state = this._selectionState;
|
||||
if (point == this._point) {
|
||||
return state == SelectionState.POINT;
|
||||
} else if (point == this._handleIn) {
|
||||
return (state & SelectionState.HANDLE_IN)
|
||||
== SelectionState.HANDLE_IN;
|
||||
} else if (point == this._handleOut) {
|
||||
return (state & SelectionState.HANDLE_OUT)
|
||||
== SelectionState.HANDLE_OUT;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Todo: port setSelected(selected) back to Scriptographer
|
||||
setSelected: function(/* pt, selected */) {
|
||||
var pt, selected;
|
||||
if (arguments.length == 2) {
|
||||
// setSelected(pt, selected)
|
||||
pt = arguments[0];
|
||||
selected = arguments[1];
|
||||
} else {
|
||||
// setSelected(selected)
|
||||
pt = this._point;
|
||||
selected = arguments[0];
|
||||
}
|
||||
if (!this._path)
|
||||
return;
|
||||
var wasSelected = !!this._selectionState;
|
||||
var state = this._selectionState,
|
||||
pointSelected = state == SelectionState.POINT,
|
||||
handleInSelected = (state & SelectionState.HANDLE_IN)
|
||||
== SelectionState.HANDLE_IN,
|
||||
handleOutSelected = (state & SelectionState.HANDLE_OUT)
|
||||
== SelectionState.HANDLE_OUT,
|
||||
previous = this.getPrevious(),
|
||||
next = this.getNext(),
|
||||
closed = this._path.closed,
|
||||
segments = this._path._segments,
|
||||
length = segments.length;
|
||||
if (length > 1 && closed) {
|
||||
if (previous == null)
|
||||
previous = segments[length - 1];
|
||||
if (next == null)
|
||||
next = segments[0];
|
||||
}
|
||||
if (pt == this._point) {
|
||||
if (pointSelected != selected) {
|
||||
if (selected) {
|
||||
handleInSelected = handleOutSelected = false;
|
||||
} else {
|
||||
// When deselecting a point, the handles get selected
|
||||
// instead depending on the selection state of their
|
||||
// neighbors.
|
||||
handleInSelected = previous != null
|
||||
&& (previous._point.isSelected()
|
||||
|| previous._handleOut.isSelected());
|
||||
handleOutSelected = next != null
|
||||
&& (next._point.isSelected()
|
||||
|| next._handleOut.isSelected());
|
||||
}
|
||||
pointSelected = selected;
|
||||
}
|
||||
} else if (pt == this._handleIn) {
|
||||
if (handleInSelected != selected) {
|
||||
// When selecting handles, the point get deselected.
|
||||
if (selected)
|
||||
pointSelected = false;
|
||||
handleInSelected = selected;
|
||||
}
|
||||
} else if (pt == this._handleOut) {
|
||||
if (handleOutSelected != selected) {
|
||||
// When selecting handles, the point get deselected.
|
||||
if (selected)
|
||||
pointSelected = false;
|
||||
handleOutSelected = selected;
|
||||
}
|
||||
}
|
||||
this._selectionState = pointSelected
|
||||
? SelectionState.POINT
|
||||
: handleInSelected
|
||||
? handleOutSelected
|
||||
? SelectionState.HANDLE_BOTH
|
||||
: SelectionState.HANDLE_IN
|
||||
: handleOutSelected
|
||||
? SelectionState.HANDLE_OUT
|
||||
: null;
|
||||
// If the selection state of the segment has changed, we need to let
|
||||
// it's path know and possibly add or remove it from
|
||||
// document._selectedItems
|
||||
if (wasSelected == !this._selectionState) {
|
||||
var path = this._path,
|
||||
selectedItems = path._document._selectedItems;
|
||||
if (!this._selectionState) {
|
||||
path._selectedSegmentCount--;
|
||||
if (path._selectedSegmentCount == 0)
|
||||
path._document._selectItem(path, false);
|
||||
} else {
|
||||
path._selectedSegmentCount++;
|
||||
if (path._selectedSegmentCount == 1)
|
||||
path._document._selectItem(path, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reverse: function() {
|
||||
return new Segment(this._point, this._handleOut, this._handleIn);
|
||||
|
@ -141,8 +240,12 @@ var Segment = this.Segment = Base.extend({
|
|||
},
|
||||
|
||||
remove: function() {
|
||||
if (this._path && this._path._segments)
|
||||
return !!this._path._segments.splice(this.getIndex(), 1).length;
|
||||
if (this._path) {
|
||||
this._path._segments.splice(this.getIndex(), 1);
|
||||
if (this.isSelected())
|
||||
this._path._selectedSegmentCount--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
|
|
@ -29,7 +29,15 @@ var SegmentPoint = Point.extend({
|
|||
this._y = y;
|
||||
// this._segment._markDirty(DirtyFlags.BOUNDS);
|
||||
},
|
||||
|
||||
|
||||
setSelected: function(selected) {
|
||||
this._segment.setSelected(this, selected);
|
||||
},
|
||||
|
||||
isSelected: function() {
|
||||
return this._segment.isSelected(this);
|
||||
},
|
||||
|
||||
statics: {
|
||||
create: function(segment, arg1, arg2) {
|
||||
var point;
|
||||
|
|
22
src/path/SelectionState.js
Normal file
22
src/path/SelectionState.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Paper.js
|
||||
*
|
||||
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
||||
* based on Scriptographer.org and designed to be largely API compatible.
|
||||
* http://paperjs.org/
|
||||
* http://scriptographer.org/
|
||||
*
|
||||
* Distributed under the MIT license. See LICENSE file for details.
|
||||
*
|
||||
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
||||
* http://lehni.org/ & http://jonathanpuckey.com/
|
||||
*
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
var SelectionState = {
|
||||
POINT: 1,
|
||||
HANDLE_IN: 2,
|
||||
HANDLE_OUT: 4,
|
||||
HANDLE_BOTH: 6
|
||||
};
|
|
@ -75,4 +75,17 @@ test('path.remove()', function() {
|
|||
path.remove();
|
||||
|
||||
equals(doc.activeLayer.children.length, 0);
|
||||
});
|
||||
|
||||
|
||||
test('Is the path deselected after setting a new list of segments?', function() {
|
||||
var doc = new Document();
|
||||
var path = new Path([0, 0]);
|
||||
path.selected = true;
|
||||
equals(path.selected, true);
|
||||
equals(doc.selectedItems.length, 1);
|
||||
|
||||
path.segments = [[0, 10]];
|
||||
equals(path.selected, false);
|
||||
equals(doc.selectedItems.length, 0);
|
||||
});
|
|
@ -48,4 +48,14 @@ test('segment.remove()', function() {
|
|||
var path = new Path([10, 10], [5, 5], [10, 10]);
|
||||
path.segments[1].remove();
|
||||
equals(path.segments.length, 2);
|
||||
});
|
||||
|
||||
test('segment.selected', function() {
|
||||
var doc = new Document();
|
||||
var path = new Path([10, 20], [50, 100]);
|
||||
path.segments[0].point.selected = true;
|
||||
equals(path.segments[0].point.selected, true);
|
||||
|
||||
path.segments[0].point.selected = false;
|
||||
equals(path.segments[0].point.selected, false);
|
||||
});
|
|
@ -147,4 +147,39 @@ test('reverseChildren()', function() {
|
|||
equals(doc.activeLayer.firstChild == path, false);
|
||||
equals(doc.activeLayer.firstChild == thirdPath, true);
|
||||
equals(doc.activeLayer.lastChild == path, true);
|
||||
})
|
||||
});
|
||||
|
||||
test('Check item#document when moving items across documents', function() {
|
||||
var doc1 = new Document();
|
||||
var path = new Path();
|
||||
var group = new Group();
|
||||
group.appendTop(new Path());
|
||||
|
||||
equals(path.document == doc1, true);
|
||||
var doc2 = new Document();
|
||||
doc2.activeLayer.appendTop(path);
|
||||
equals(path.document == doc2, true);
|
||||
|
||||
doc2.activeLayer.appendTop(group);
|
||||
equals(group.children[0].document == doc2, true);
|
||||
});
|
||||
|
||||
test('group.selected', function() {
|
||||
var doc = new Document();
|
||||
var path = new Path([0, 0]);
|
||||
var path2 = new Path([0, 0]);
|
||||
var group = new Group([path, path2]);
|
||||
path.selected = true;
|
||||
equals(group.selected, true);
|
||||
|
||||
path.selected = false;
|
||||
equals(group.selected, false);
|
||||
|
||||
group.selected = true;
|
||||
equals(path.selected, true);
|
||||
equals(path2.selected, true);
|
||||
|
||||
group.selected = false;
|
||||
equals(path.selected, false);
|
||||
equals(path2.selected, false);
|
||||
});
|
Loading…
Reference in a new issue