mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Merge branch 'refs/heads/master' into apply-matrix
This commit is contained in:
commit
3c257dcae0
14 changed files with 550 additions and 158 deletions
150
examples/Animated/WineGums.html
Normal file
150
examples/Animated/WineGums.html
Normal file
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Wine Gums</title>
|
||||
<link rel="stylesheet" href="../css/style.css">
|
||||
<script type="text/javascript" src="../../dist/paper.js"></script>
|
||||
<script type="text/paperscript" canvas="canvas">
|
||||
// kynd.info 2014
|
||||
|
||||
function Ball(r, p, v) {
|
||||
this.radius = r;
|
||||
this.point = p;
|
||||
this.vector = v;
|
||||
this.maxVec = 15;
|
||||
this.numSegment = Math.floor(r / 3 + 2);
|
||||
this.boundOffset = [];
|
||||
this.boundOffsetBuff = [];
|
||||
this.sidePoints = [];
|
||||
this.path = new Path({
|
||||
fillColor: {
|
||||
hue: Math.random() * 360,
|
||||
saturation: 1,
|
||||
brightness: 1
|
||||
},
|
||||
blendMode: 'screen'
|
||||
});
|
||||
|
||||
for (var i = 0; i < this.numSegment; i ++) {
|
||||
this.boundOffset.push(this.radius);
|
||||
this.boundOffsetBuff.push(this.radius);
|
||||
this.path.add(new Point());
|
||||
this.sidePoints.push(new Point({
|
||||
angle: 360 / this.numSegment * i,
|
||||
length: 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ball.prototype = {
|
||||
iterate: function() {
|
||||
this.checkWallCollision();
|
||||
if (this.vector.length > this.maxVec)
|
||||
this.vector.length = this.maxVec;
|
||||
this.point += this.vector;
|
||||
this.updateShape();
|
||||
},
|
||||
|
||||
checkWallCollision: function() {
|
||||
var size = view.size;
|
||||
if (this.point.x < -this.radius)
|
||||
this.point.x = size.width + this.radius;
|
||||
if (this.point.x > size.width + this.radius)
|
||||
this.point.x = -this.radius;
|
||||
if (this.point.y < -this.radius)
|
||||
this.point.y = size.height + this.radius;
|
||||
if (this.point.y > size.height + this.radius)
|
||||
this.point.y = -this.radius;
|
||||
},
|
||||
|
||||
updateShape: function() {
|
||||
var segments = this.path.segments;
|
||||
for (var i = 0; i < this.numSegment; i ++)
|
||||
segments[i].point = this.getSidePoint(i);
|
||||
|
||||
this.path.smooth();
|
||||
for (var i = 0; i < this.numSegment; i ++) {
|
||||
if (this.boundOffset[i] < this.radius / 4)
|
||||
this.boundOffset[i] = this.radius / 4;
|
||||
var next = (i + 1) % this.numSegment;
|
||||
var prev = (i > 0) ? i - 1 : this.numSegment - 1;
|
||||
var offset = this.boundOffset[i];
|
||||
offset += (this.radius - offset) / 15;
|
||||
offset += ((this.boundOffset[next] + this.boundOffset[prev]) / 2 - offset) / 3;
|
||||
this.boundOffsetBuff[i] = this.boundOffset[i] = offset;
|
||||
}
|
||||
},
|
||||
|
||||
react: function(b) {
|
||||
var dist = this.point.getDistance(b.point);
|
||||
if (dist < this.radius + b.radius && dist != 0) {
|
||||
var overlap = this.radius + b.radius - dist;
|
||||
var direc = (this.point - b.point).normalize(overlap * 0.015);
|
||||
this.vector += direc;
|
||||
b.vector -= direc;
|
||||
|
||||
this.calcBounds(b);
|
||||
b.calcBounds(this);
|
||||
this.updateBounds();
|
||||
b.updateBounds();
|
||||
}
|
||||
},
|
||||
|
||||
getBoundOffset: function(b) {
|
||||
var diff = this.point - b;
|
||||
var angle = (diff.angle + 180) % 360;
|
||||
return this.boundOffset[Math.floor(angle / 360 * this.boundOffset.length)];
|
||||
},
|
||||
|
||||
calcBounds: function(b) {
|
||||
for (var i = 0; i < this.numSegment; i ++) {
|
||||
var tp = this.getSidePoint(i);
|
||||
var bLen = b.getBoundOffset(tp);
|
||||
var td = tp.getDistance(b.point);
|
||||
if (td < bLen) {
|
||||
this.boundOffsetBuff[i] -= (bLen - td) / 2;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getSidePoint: function(index) {
|
||||
return this.point + this.sidePoints[index] * this.boundOffset[index];
|
||||
},
|
||||
|
||||
updateBounds: function() {
|
||||
for (var i = 0; i < this.numSegment; i ++)
|
||||
this.boundOffset[i] = this.boundOffsetBuff[i];
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------- main ---------------------
|
||||
|
||||
var balls = [];
|
||||
var numBalls = 18;
|
||||
for (var i = 0; i < numBalls; i++) {
|
||||
var position = Point.random() * view.size;
|
||||
var vector = new Point({
|
||||
angle: 360 * Math.random(),
|
||||
length: Math.random() * 10
|
||||
});
|
||||
balls.push(new Ball(Math.random() * 60 + 60, position, vector));
|
||||
}
|
||||
|
||||
function onFrame() {
|
||||
for (var i = 0; i < balls.length - 1; i++) {
|
||||
for (var j = i + 1; j < balls.length; j++) {
|
||||
balls[i].react(balls[j]);
|
||||
}
|
||||
}
|
||||
for (var i = 0, l = balls.length; i < l; i++) {
|
||||
balls[i].iterate();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" resize hidpi="off" style="background:black"></canvas>
|
||||
</body>
|
||||
</html>
|
76
examples/SVG Import/Arcs.html
Normal file
76
examples/SVG Import/Arcs.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Arcs Testing</title>
|
||||
<script type="text/javascript" src="../../dist/paper.js"></script>
|
||||
<script type="text/paperscript" canvas="canvas">
|
||||
project.importSVG(document.getElementById('svg'));
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1000" height="500" id="svg">
|
||||
<g transform="scale(0.5)">
|
||||
<path d="M300,200 h-150 a150,150 0 1,0 150,-150 z"
|
||||
fill="red" stroke="blue" stroke-width="5" />
|
||||
<path d="M275,175 v-150 a150,150 0 0,0 -150,150 z"
|
||||
fill="yellow" stroke="blue" stroke-width="5" />
|
||||
|
||||
<path d="M600,350 l 50,-25
|
||||
a25,25 -30 0,1 50,-25 l 50,-25
|
||||
a25,50 -30 0,1 50,-25 l 50,-25
|
||||
a25,75 -30 0,1 50,-25 l 50,-25
|
||||
a25,100 -30 0,1 50,-25 l 50,-25"
|
||||
fill="none" stroke="red" stroke-width="5" />
|
||||
<g font-family="Verdana" transform="translate(0,400)">
|
||||
<defs>
|
||||
<g id="baseEllipses" font-size="20" >
|
||||
<ellipse cx="125" cy="125" rx="100" ry="50"
|
||||
fill="none" stroke="#888888" stroke-width="2" />
|
||||
<ellipse cx="225" cy="75" rx="100" ry="50"
|
||||
fill="none" stroke="#888888" stroke-width="2" />
|
||||
<text x="35" y="70">Arc start</text>
|
||||
<text x="225" y="145">Arc end</text>
|
||||
</g>
|
||||
</defs>
|
||||
<rect x="1" y="1" width="1198" height="523"
|
||||
fill="none" stroke="blue" stroke-width="1" />
|
||||
|
||||
<g font-size="30" >
|
||||
<g transform="translate(0,0)">
|
||||
<use xlink:href="#baseEllipses"/>
|
||||
</g>
|
||||
<g transform="translate(400,0)">
|
||||
<text x="50" y="210">large-arc-flag=0</text>
|
||||
<text x="50" y="250">sweep-flag=0</text>
|
||||
<use xlink:href="#baseEllipses"/>
|
||||
<path d="M 125,75 a100,50 0 0,0 100,50"
|
||||
fill="none" stroke="red" stroke-width="6" />
|
||||
</g>
|
||||
<g transform="translate(800,0)">
|
||||
<text x="50" y="210">large-arc-flag=0</text>
|
||||
<text x="50" y="250">sweep-flag=1</text>
|
||||
<use xlink:href="#baseEllipses"/>
|
||||
<path d="M 125,75 a100,50 0 0,1 100,50"
|
||||
fill="none" stroke="red" stroke-width="6" />
|
||||
</g>
|
||||
<g transform="translate(400,250)">
|
||||
<text x="50" y="210">large-arc-flag=1</text>
|
||||
<text x="50" y="250">sweep-flag=0</text>
|
||||
<use xlink:href="#baseEllipses"/>
|
||||
<path d="M 125,75 a100,50 0 1,0 100,50"
|
||||
fill="none" stroke="red" stroke-width="6" />
|
||||
</g>
|
||||
<g transform="translate(800,250)">
|
||||
<text x="50" y="210">large-arc-flag=1</text>
|
||||
<text x="50" y="250">sweep-flag=1</text>
|
||||
<use xlink:href="#baseEllipses"/>
|
||||
<path d="M 125,75 a100,50 0 1,1 100,50"
|
||||
fill="none" stroke="red" stroke-width="6" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<canvas id="canvas" width="1000" height="500"></canvas>
|
||||
</body>
|
||||
</html>
|
|
@ -673,7 +673,7 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
c = Math.cos(angle);
|
||||
point = new Point(
|
||||
point.x * c - point.y * s,
|
||||
point.y * c + point.x * s
|
||||
point.x * s + point.y * c
|
||||
);
|
||||
return center ? point.add(center) : point;
|
||||
},
|
||||
|
|
|
@ -201,6 +201,13 @@ Base.inject(/** @lends Base# */{
|
|||
return list[list.__index = start || list.__index || 0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns how many arguments remain to be read in the argument list.
|
||||
*/
|
||||
remain: function(list) {
|
||||
return list.length - (list.__index || 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads all readable arguments from the list, handling nested arrays
|
||||
* separately.
|
||||
|
|
|
@ -46,6 +46,24 @@ var DomElement = new function() {
|
|||
return res;
|
||||
}
|
||||
|
||||
// Handles both getting and setting of vendor prefix values
|
||||
function handlePrefix(el, name, set, value) {
|
||||
var prefixes = ['webkit', 'moz', 'Moz', 'ms', 'o', ''],
|
||||
suffix = name[0].toUpperCase() + name.substring(1);
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var prefix = prefixes[i],
|
||||
key = prefix ? prefix + suffix : name;
|
||||
if (key in el) {
|
||||
if (set) {
|
||||
el[key] = value;
|
||||
} else {
|
||||
return el[key];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return /** @lends DomElement */{
|
||||
create: function(nodes, parent) {
|
||||
var isArray = Array.isArray(nodes),
|
||||
|
@ -203,13 +221,17 @@ var DomElement = new function() {
|
|||
* Gets the given property from the element, trying out all browser
|
||||
* prefix variants.
|
||||
*/
|
||||
getPrefixValue: function(el, name) {
|
||||
var value = el[name],
|
||||
prefixes = ['webkit', 'moz', 'ms', 'o'],
|
||||
suffix = name[0].toUpperCase() + name.substring(1);
|
||||
for (var i = 0; i < 4 && value == null; i++)
|
||||
value = el[prefixes[i] + suffix];
|
||||
return value;
|
||||
getPrefixed: function(el, name) {
|
||||
return handlePrefix(el, name);
|
||||
},
|
||||
|
||||
setPrefixed: function(el, name, value) {
|
||||
if (typeof name === 'object') {
|
||||
for (var key in name)
|
||||
handlePrefix(el, key, true, name[key]);
|
||||
} else {
|
||||
handlePrefix(el, name, true, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,13 +17,21 @@
|
|||
*/
|
||||
var DomEvent = /** @lends DomEvent */{
|
||||
add: function(el, events) {
|
||||
for (var type in events)
|
||||
el.addEventListener(type, events[type], false);
|
||||
for (var type in events) {
|
||||
var func = events[type],
|
||||
parts = type.split(/[\s,]+/g);
|
||||
for (var i = 0, l = parts.length; i < l; i++)
|
||||
el.addEventListener(parts[i], func, false);
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(el, events) {
|
||||
for (var type in events)
|
||||
el.removeEventListener(type, events[type], false);
|
||||
for (var type in events) {
|
||||
var func = events[type],
|
||||
parts = type.split(/[\s,]+/g);
|
||||
for (var i = 0, l = parts.length; i < l; i++)
|
||||
el.removeEventListener(parts[i], func, false);
|
||||
}
|
||||
},
|
||||
|
||||
getPoint: function(event) {
|
||||
|
@ -59,8 +67,7 @@ var DomEvent = /** @lends DomEvent */{
|
|||
};
|
||||
|
||||
DomEvent.requestAnimationFrame = new function() {
|
||||
var nativeRequest = DomElement.getPrefixValue(window,
|
||||
'requestAnimationFrame'),
|
||||
var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'),
|
||||
requested = false,
|
||||
callbacks = [],
|
||||
focused = true,
|
||||
|
|
164
src/path/Path.js
164
src/path/Path.js
|
@ -2295,58 +2295,126 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
this.quadraticCurveTo(handle, to);
|
||||
},
|
||||
|
||||
arcTo: function(/* to, clockwise | through, to */) {
|
||||
arcTo: function(/* to, clockwise | through, to
|
||||
| to, radius, rotation, large, sweep */) {
|
||||
// Get the start point:
|
||||
var current = getCurrentSegment(this),
|
||||
from = current._point,
|
||||
through,
|
||||
to = Point.read(arguments),
|
||||
// Peek at next value to see if it's clockwise,
|
||||
// with true as default value.
|
||||
clockwise = Base.pick(Base.peek(arguments), true);
|
||||
through,
|
||||
// Peek at next value to see if it's clockwise, with true as the
|
||||
// default value.
|
||||
peek = Base.peek(arguments),
|
||||
clockwise = Base.pick(peek, true),
|
||||
center, extent, vector, matrix;
|
||||
// We're handling three different approaches to drawing arcs in one
|
||||
// large function:
|
||||
if (typeof clockwise === 'boolean') {
|
||||
// arcTo(to, clockwise)
|
||||
// #1: arcTo(to, clockwise)
|
||||
var middle = from.add(to).divide(2),
|
||||
through = middle.add(middle.subtract(from).rotate(
|
||||
clockwise ? -90 : 90));
|
||||
} else {
|
||||
// arcTo(through, to)
|
||||
} else if (Base.remain(arguments) <= 2) {
|
||||
// #2: arcTo(through, to)
|
||||
through = to;
|
||||
to = Point.read(arguments);
|
||||
} else {
|
||||
// #3: arcTo(to, radius, rotation, large, sweep)
|
||||
// Drawing arcs in SVG style:
|
||||
var radius = Size.read(arguments);
|
||||
// If rx = 0 or ry = 0 then this arc is treated as a
|
||||
// straight line joining the endpoints.
|
||||
if (radius.isZero())
|
||||
return this.lineTo(to);
|
||||
// See for an explanation of the following calculations:
|
||||
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
||||
var rotation = Base.read(arguments),
|
||||
large = !!Base.read(arguments),
|
||||
sweep = !!Base.read(arguments),
|
||||
middle = from.add(to).divide(2),
|
||||
pt = from.subtract(middle).rotate(-rotation),
|
||||
x = pt.x,
|
||||
y = pt.y,
|
||||
abs = Math.abs,
|
||||
EPSILON = /*#=*/ Numerical.EPSILON,
|
||||
rx = abs(radius.width),
|
||||
ry = abs(radius.height),
|
||||
rxSq = rx * rx,
|
||||
rySq = ry * ry,
|
||||
xSq = x * x,
|
||||
ySq = y * y;
|
||||
// "...ensure radii are large enough"
|
||||
var factor = Math.sqrt(xSq / rxSq + ySq / rySq);
|
||||
if (factor > 1) {
|
||||
rx *= factor;
|
||||
ry *= factor;
|
||||
rxSq = rx * rx;
|
||||
rySq = ry * ry;
|
||||
}
|
||||
factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /
|
||||
(rxSq * ySq + rySq * xSq);
|
||||
if (abs(factor) < EPSILON)
|
||||
factor = 0;
|
||||
if (factor < 0)
|
||||
throw new Error(
|
||||
'Cannot create an arc with the given arguments');
|
||||
center = new Point(rx * y / ry, -ry * x / rx)
|
||||
// "...where the + sign is chosen if fA != fS,
|
||||
// and the − sign is chosen if fA = fS."
|
||||
.multiply((large == sweep ? -1 : 1) * Math.sqrt(factor))
|
||||
.rotate(rotation).add(middle);
|
||||
// Now create a matrix that maps the unit circle to the ellipse,
|
||||
// for easier construction below.
|
||||
matrix = new Matrix().translate(center).rotate(rotation)
|
||||
.scale(rx, ry);
|
||||
// Transform from and to to the unit circle coordinate space
|
||||
// and calculcate start vector and extend from there.
|
||||
vector = matrix._inverseTransform(from);
|
||||
extent = vector.getDirectedAngle(matrix._inverseTransform(to));
|
||||
// "...in other words, if sweep = 0 and extent is > 0, subtract
|
||||
// 360, whereas if sweep = 1 and extent < 0, then add 360."
|
||||
if (!sweep && extent > 0)
|
||||
extent -= 360;
|
||||
else if (sweep && extent < 0)
|
||||
extent += 360;
|
||||
}
|
||||
// Construct the two perpendicular middle lines to (from, through)
|
||||
// and (through, to), and intersect them to get the center
|
||||
var l1 = new Line(from.add(through).divide(2),
|
||||
through.subtract(from).rotate(90), true),
|
||||
l2 = new Line(through.add(to).divide(2),
|
||||
to.subtract(through).rotate(90), true),
|
||||
center = l1.intersect(l2, true),
|
||||
line = new Line(from, to),
|
||||
throughSide = line.getSide(through);
|
||||
if (!center) {
|
||||
if (through) {
|
||||
// Calculate center, vector and extend for non SVG versions:
|
||||
// Construct the two perpendicular middle lines to
|
||||
// (from, through) and (through, to), and intersect them to get
|
||||
// the center.
|
||||
var l1 = new Line(from.add(through).divide(2),
|
||||
through.subtract(from).rotate(90), true),
|
||||
l2 = new Line(through.add(to).divide(2),
|
||||
to.subtract(through).rotate(90), true),
|
||||
line = new Line(from, to),
|
||||
throughSide = line.getSide(through);
|
||||
center = l1.intersect(l2, true);
|
||||
// If the two lines are colinear, there cannot be an arc as the
|
||||
// circle is infinitely big and has no center point. If side is
|
||||
// 0, the connecting arc line of this huge circle is a line
|
||||
// between the two points, so we can use #lineTo instead.
|
||||
// Otherwise we bail out:
|
||||
if (!throughSide)
|
||||
return this.lineTo(to);
|
||||
throw new Error('Cannot put an arc through the given points: '
|
||||
+ [from, through, to]);
|
||||
}
|
||||
var vector = from.subtract(center),
|
||||
extent = vector.getDirectedAngle(to.subtract(center)),
|
||||
centerSide = line.getSide(center);
|
||||
if (centerSide == 0) {
|
||||
// If the center is lying on the line, we might have gotten the
|
||||
// wrong sign for extent above. Use the sign of the side of the
|
||||
// through point.
|
||||
extent = throughSide * Math.abs(extent);
|
||||
} else if (throughSide == centerSide) {
|
||||
// If the center is on the same side of the line (from, to) as
|
||||
// the through point, we're extending bellow 180 degrees and
|
||||
// need to adapt extent.
|
||||
extent -= 360 * (extent < 0 ? -1 : 1);
|
||||
if (!center) {
|
||||
if (!throughSide)
|
||||
return this.lineTo(to);
|
||||
throw new Error(
|
||||
'Cannot create an arc with the given arguments');
|
||||
}
|
||||
vector = from.subtract(center);
|
||||
extent = vector.getDirectedAngle(to.subtract(center));
|
||||
var centerSide = line.getSide(center);
|
||||
if (centerSide === 0) {
|
||||
// If the center is lying on the line, we might have gotten
|
||||
// the wrong sign for extent above. Use the sign of the side
|
||||
// of the through point.
|
||||
extent = throughSide * Math.abs(extent);
|
||||
} else if (throughSide === centerSide) {
|
||||
// If the center is on the same side of the line (from, to)
|
||||
// as the through point, we're extending bellow 180 degrees
|
||||
// and need to adapt extent.
|
||||
extent -= 360 * (extent < 0 ? -1 : 1);
|
||||
}
|
||||
}
|
||||
var ext = Math.abs(extent),
|
||||
count = ext >= 360 ? 4 : Math.ceil(ext / 90),
|
||||
|
@ -2357,15 +2425,29 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
for (var i = 0; i <= count; i++) {
|
||||
// Explicitely use to point for last segment, since depending
|
||||
// on values the calculation adds imprecision:
|
||||
var pt = i < count ? center.add(vector) : to;
|
||||
var out = i < count ? vector.rotate(90).multiply(z) : null;
|
||||
if (i == 0) {
|
||||
var pt = to,
|
||||
out = null;
|
||||
if (i < count) {
|
||||
out = vector.rotate(90).multiply(z);
|
||||
if (matrix) {
|
||||
pt = matrix._transformPoint(vector);
|
||||
out = matrix._transformPoint(vector.add(out))
|
||||
.subtract(pt);
|
||||
} else {
|
||||
pt = center.add(vector);
|
||||
}
|
||||
}
|
||||
if (i === 0) {
|
||||
// Modify startSegment
|
||||
current.setHandleOut(out);
|
||||
} else {
|
||||
// Add new Segment
|
||||
segments.push(
|
||||
new Segment(pt, vector.rotate(-90).multiply(z), out));
|
||||
var _in = vector.rotate(-90).multiply(z);
|
||||
if (matrix) {
|
||||
_in = matrix._transformPoint(vector.add(_in))
|
||||
.subtract(pt);
|
||||
}
|
||||
segments.push(new Segment(pt, _in, out));
|
||||
}
|
||||
vector = vector.rotate(inc);
|
||||
}
|
||||
|
|
|
@ -214,7 +214,9 @@ PathItem.inject(new function() {
|
|||
var loc = intersections[i],
|
||||
t = loc._parameter;
|
||||
// Check if we are splitting same curve multiple times
|
||||
if (prevLoc && prevLoc._curve === loc._curve) {
|
||||
if (prevLoc && prevLoc._curve === loc._curve
|
||||
// Avoid dividing with zero
|
||||
&& prevLoc._parameter > 0) {
|
||||
// Scale parameter after previous split.
|
||||
t /= prevLoc._parameter;
|
||||
} else {
|
||||
|
@ -404,8 +406,8 @@ PathItem.inject(new function() {
|
|||
seg = interSeg;
|
||||
dir = 1;
|
||||
} else if (w3 * w4 !== 0) {
|
||||
// Do not attempt to switch contours if we aren't absolutely
|
||||
// sure that there is a possible candidate.
|
||||
// Do not attempt to switch contours if we aren't
|
||||
// absolutely sure that there is a possible candidate.
|
||||
var curve = w3 < w4 ? c3 : c4,
|
||||
nextCurve = operator(curve._segment1._winding)
|
||||
? curve
|
||||
|
@ -469,8 +471,8 @@ PathItem.inject(new function() {
|
|||
*
|
||||
* @param {Point} point the location for which to determine the winding
|
||||
* direction
|
||||
* @param {Boolean} horizontal whether we need to consider this point as
|
||||
* part of a horizontal curve
|
||||
* @param {Boolean} horizontal whether we need to consider this point
|
||||
* as part of a horizontal curve
|
||||
* @param {Boolean} testContains whether we need to consider this point
|
||||
* as part of stationary points on the curve itself, used when checking
|
||||
* the winding about a point.
|
||||
|
|
|
@ -192,10 +192,11 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
relative = false,
|
||||
previous,
|
||||
control,
|
||||
current = new Point();
|
||||
current = new Point(),
|
||||
start = new Point();
|
||||
|
||||
function getCoord(index, coord) {
|
||||
var val = parseFloat(coords[index]);
|
||||
var val = +coords[index];
|
||||
if (relative)
|
||||
val += current[coord];
|
||||
return val;
|
||||
|
@ -219,6 +220,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g);
|
||||
var length = coords && coords.length;
|
||||
relative = command === lower;
|
||||
if (previous === 'z' && lower !== 'z')
|
||||
this.moveTo(current = start);
|
||||
switch (lower) {
|
||||
case 'm':
|
||||
case 'l':
|
||||
|
@ -226,6 +229,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo'](
|
||||
current = getPoint(j));
|
||||
control = current;
|
||||
if(lower == 'm')
|
||||
start = current;
|
||||
break;
|
||||
case 'h':
|
||||
case 'v':
|
||||
|
@ -248,12 +253,12 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
// Smooth cubicCurveTo
|
||||
for (var j = 0; j < length; j += 4) {
|
||||
this.cubicCurveTo(
|
||||
/[cs]/i.test(previous)
|
||||
/[cs]/.test(previous)
|
||||
? current.multiply(2).subtract(control)
|
||||
: current,
|
||||
control = getPoint(j),
|
||||
current = getPoint(j + 2));
|
||||
previous = command;
|
||||
previous = lower;
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
|
@ -266,23 +271,26 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
case 't':
|
||||
// Smooth quadraticCurveTo
|
||||
for (var j = 0; j < length; j += 2) {
|
||||
console.log(previous, /[qt]/i.test(previous));
|
||||
this.quadraticCurveTo(
|
||||
control = (/[qt]/i.test(previous)
|
||||
control = (/[qt]/.test(previous)
|
||||
? current.multiply(2).subtract(control)
|
||||
: current),
|
||||
current = getPoint(j));
|
||||
previous = command;
|
||||
previous = lower;
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
// TODO: Implement Arcs!
|
||||
for (var j = 0; j < length; j += 7) {
|
||||
this.arcTo(current = getPoint(j + 5),
|
||||
new Size(+coords[0], +coords[1]),
|
||||
+coords[2], +coords[3], +coords[4]);
|
||||
}
|
||||
break;
|
||||
case 'z':
|
||||
this.closePath();
|
||||
break;
|
||||
}
|
||||
previous = command;
|
||||
previous = lower;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -150,25 +150,27 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
},
|
||||
|
||||
_changed: function(point) {
|
||||
if (!this._path)
|
||||
var path = this._path;
|
||||
if (!path)
|
||||
return;
|
||||
// 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
|
||||
// _point or _handleIn changing when this segment is _segment1 of
|
||||
// the curve, for all other cases it's the next (e.g. _handleOut
|
||||
// when this segment is _segment2)
|
||||
if (other = (curve[point == this._point
|
||||
|| point == this._handleIn && curve._segment1 == this
|
||||
? 'getPrevious' : 'getNext']())) {
|
||||
other._changed();
|
||||
}
|
||||
// Delegate changes to affected curves if they exist.
|
||||
var curves = path._curves,
|
||||
index = this._index,
|
||||
curveIn, curveOut;
|
||||
if (curves) {
|
||||
// Updated the neighboring affected curves, depending on which point
|
||||
// is changing.
|
||||
// TODO: Consider exposing these curves too, through #curveIn,
|
||||
// and #curveOut, next to #curve?
|
||||
if ((!point || point === this._point || point === this._handleIn)
|
||||
&& (curveIn = curves[index - 1]
|
||||
|| path._closed && curves[curves.length - 1]))
|
||||
curveIn._changed();
|
||||
if ((!point || point === this._point || point === this._handleOut)
|
||||
&& (curveOut = curves[index]))
|
||||
curveOut._changed();
|
||||
}
|
||||
this._path._changed(/*#=*/ Change.GEOMETRY);
|
||||
path._changed(/*#=*/ Change.GEOMETRY);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -373,7 +375,8 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* The curve that the segment belongs to.
|
||||
* The curve that the segment belongs to. For the last segment of an open
|
||||
* path, the previous segment is returned.
|
||||
*
|
||||
* @type Curve
|
||||
* @bean
|
||||
|
@ -474,6 +477,16 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
return '{ ' + parts.join(', ') + ' }';
|
||||
},
|
||||
|
||||
/**
|
||||
* Transform the segment by the specified matrix.
|
||||
*
|
||||
* @param {Matrix} matrix the matrix to transform the segment by
|
||||
*/
|
||||
transform: function(matrix) {
|
||||
this._transformCoordinates(matrix, new Array(6), true);
|
||||
this._changed();
|
||||
},
|
||||
|
||||
_transformCoordinates: function(matrix, coords, change) {
|
||||
// Use matrix.transform version() that takes arrays of multiple
|
||||
// points for largely improved performance, as no calls to
|
||||
|
|
|
@ -71,7 +71,7 @@ var Color = Base.extend(new function() {
|
|||
// RGB / RGBA
|
||||
components = match[1].split(',');
|
||||
for (var i = 0, l = components.length; i < l; i++) {
|
||||
var value = parseFloat(components[i]);
|
||||
var value = +components[i];
|
||||
components[i] = i < 3 ? value / 255 : value;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -130,14 +130,10 @@ new function() {
|
|||
}
|
||||
|
||||
function importPath(node) {
|
||||
// Get the path data, and determine whether it is a compound path or a
|
||||
// normal path based on the amount of moveTo commands inside it.
|
||||
var data = node.getAttribute('d'),
|
||||
path = data.match(/m/gi).length > 1
|
||||
? new CompoundPath()
|
||||
: new Path();
|
||||
path.setPathData(data);
|
||||
return path;
|
||||
return new CompoundPath({
|
||||
pathData: node.getAttribute('d'),
|
||||
insert: false
|
||||
}).reduce();
|
||||
}
|
||||
|
||||
function importGradient(node, type) {
|
||||
|
|
|
@ -51,7 +51,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
|||
// Hi-DPI Canvas support based on:
|
||||
// http://www.html5rocks.com/en/tutorials/canvas/hidpi/
|
||||
var deviceRatio = window.devicePixelRatio || 1,
|
||||
backingStoreRatio = DomElement.getPrefixValue(this._context,
|
||||
backingStoreRatio = DomElement.getPrefixed(this._context,
|
||||
'backingStorePixelRatio') || 1;
|
||||
this._pixelRatio = deviceRatio / backingStoreRatio;
|
||||
}
|
||||
|
|
149
src/ui/View.js
149
src/ui/View.js
|
@ -38,7 +38,20 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
if (this._id == null)
|
||||
element.setAttribute('id', this._id = 'view-' + View._id++);
|
||||
// Install event handlers
|
||||
DomEvent.add(element, this._viewHandlers);
|
||||
DomEvent.add(element, this._viewEvents);
|
||||
// Borrowed from Hammer.js:
|
||||
var none = 'none';
|
||||
DomElement.setPrefixed(element.style, {
|
||||
userSelect: none,
|
||||
// This makes the element blocking in IE10+
|
||||
// You could experiment with the value, see this issue:
|
||||
// https://github.com/EightMedia/hammer.js/issues/241
|
||||
touchAction: none,
|
||||
touchCallout: none,
|
||||
contentZooming: none,
|
||||
userDrag: none,
|
||||
tapHighlightColor: 'rgba(0,0,0,0)'
|
||||
});
|
||||
// If the element has the resize attribute, resize the it to fill the
|
||||
// window and resize it again whenever the user resizes the window.
|
||||
if (PaperScope.hasAttribute(element, 'resize')) {
|
||||
|
@ -48,7 +61,7 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
that = this;
|
||||
size = DomElement.getViewportBounds(element)
|
||||
.getSize().subtract(offset);
|
||||
this._windowHandlers = {
|
||||
this._windowEvents = {
|
||||
resize: function() {
|
||||
// Only update element offset if it's not invisible, as
|
||||
// otherwise the offset would be wrong.
|
||||
|
@ -60,7 +73,7 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
.getSize().subtract(offset));
|
||||
}
|
||||
};
|
||||
DomEvent.add(window, this._windowHandlers);
|
||||
DomEvent.add(window, this._windowEvents);
|
||||
} else {
|
||||
// Try visible size first, since that will help handling previously
|
||||
// scaled canvases (e.g. when dealing with pixel-ratio)
|
||||
|
@ -130,8 +143,8 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
this._project.view = null;
|
||||
/*#*/ if (__options.environment == 'browser') {
|
||||
// Uninstall event handlers again for this view.
|
||||
DomEvent.remove(this._element, this._viewHandlers);
|
||||
DomEvent.remove(window, this._windowHandlers);
|
||||
DomEvent.remove(this._element, this._viewEvents);
|
||||
DomEvent.remove(window, this._windowEvents);
|
||||
/*#*/ } // __options.environment == 'browser'
|
||||
this._element = this._project = null;
|
||||
// Remove all onFrame handlers.
|
||||
|
@ -687,7 +700,68 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
}
|
||||
}
|
||||
|
||||
function mousedown(event) {
|
||||
function handleMouseMove(view, point, event) {
|
||||
view._handleEvent('mousemove', point, event);
|
||||
var tool = view._scope.tool;
|
||||
if (tool) {
|
||||
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
||||
tool._handleEvent(dragging && tool.responds('mousedrag')
|
||||
? 'mousedrag' : 'mousemove', point, event);
|
||||
}
|
||||
view.update();
|
||||
return tool;
|
||||
}
|
||||
|
||||
// Touch handling inspired by Hammer.js
|
||||
var navigator = window.navigator,
|
||||
mousedown, mousemove, mouseup;
|
||||
if (navigator.pointerEnabled || navigator.msPointerEnabled) {
|
||||
// HTML5 / MS pointer events
|
||||
mousedown = 'pointerdown MSPointerDown';
|
||||
mousemove = 'pointermove MSPointerMove';
|
||||
mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel';
|
||||
} else {
|
||||
mousedown = 'touchstart';
|
||||
mousemove = 'touchmove';
|
||||
mouseup = 'touchend touchcancel';
|
||||
// Do not add mouse events on mobile and tablet devices
|
||||
if (!('ontouchstart' in window && navigator.userAgent.match(
|
||||
/mobile|tablet|ip(ad|hone|od)|android|silk/i))) {
|
||||
// For non pointer events browsers and mixed browsers, like chrome
|
||||
// on Windows8 touch laptop.
|
||||
mousedown += ' mousedown';
|
||||
mousemove += ' mousemove';
|
||||
mouseup += ' mouseup';
|
||||
}
|
||||
}
|
||||
|
||||
var viewEvents = {
|
||||
'selectstart dragstart': function(event) {
|
||||
// Only stop this even if we're dragging already, since otherwise no
|
||||
// text whatsoever can be selected on the page.
|
||||
if (dragging)
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
var docEvents = {
|
||||
mouseout: function(event) {
|
||||
// When the moues leaves the document, fire one last mousemove
|
||||
// event, to give items the change to receive a mouseleave, etc.
|
||||
var view = View._focused,
|
||||
target = DomEvent.getRelatedTarget(event);
|
||||
if (view && (!target || target.nodeName === 'HTML'))
|
||||
handleMouseMove(view, viewToProject(view, event), event);
|
||||
},
|
||||
|
||||
scroll: updateFocus
|
||||
};
|
||||
|
||||
// mousemove and mouseup events need to be installed on document, not the
|
||||
// view element, since we want to catch the end of drag events even outside
|
||||
// our view. Only the mousedown events are installed on the view, as defined
|
||||
// by _viewEvents below.
|
||||
viewEvents[mousedown] = function(event) {
|
||||
// Get the view from the event, and store a reference to the view that
|
||||
// should receive keyboard input.
|
||||
var view = View._focused = getView(event),
|
||||
|
@ -701,29 +775,17 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
// In the end we always call update(), which only updates the view if
|
||||
// anything has changed in the above calls.
|
||||
view.update();
|
||||
}
|
||||
};
|
||||
|
||||
function handleMouseMove(view, point, event) {
|
||||
view._handleEvent('mousemove', point, event);
|
||||
var tool = view._scope.tool;
|
||||
if (tool) {
|
||||
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
||||
tool._handleEvent(dragging && tool.responds('mousedrag')
|
||||
? 'mousedrag' : 'mousemove', point, event);
|
||||
}
|
||||
view.update();
|
||||
return tool;
|
||||
}
|
||||
|
||||
function mousemove(event) {
|
||||
docEvents[mousemove] = function(event) {
|
||||
var view = View._focused;
|
||||
if (!dragging) {
|
||||
// See if we can get the view from the current event target, and
|
||||
// handle the mouse move over it.
|
||||
var target = getView(event);
|
||||
if (target) {
|
||||
// Temporarily focus this view without making it sticky, so
|
||||
// Key events are handled too during the mouse over
|
||||
// Temporarily focus this view without making it sticky, so Key
|
||||
// events are handled too during the mouse over.
|
||||
// If we switch view, fire one last mousemove in the old view,
|
||||
// to give items the change to receive a mouseleave, etc.
|
||||
if (view !== target)
|
||||
|
@ -741,18 +803,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
if (dragging || view.getBounds().contains(point))
|
||||
tool = handleMouseMove(view, point, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function mouseout(event) {
|
||||
// When the moues leaves the document, fire one last mousemove event,
|
||||
// to give items the change to receive a mouseleave, etc.
|
||||
var view = View._focused,
|
||||
target = DomEvent.getRelatedTarget(event);
|
||||
if (view && (!target || target.nodeName === 'HTML'))
|
||||
handleMouseMove(view, viewToProject(view, event), event);
|
||||
}
|
||||
|
||||
function mouseup(event) {
|
||||
docEvents[mouseup] = function(event) {
|
||||
var view = View._focused;
|
||||
if (!view || !dragging)
|
||||
return;
|
||||
|
@ -763,40 +816,16 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
if (tool)
|
||||
tool._handleEvent('mouseup', point, event);
|
||||
view.update();
|
||||
}
|
||||
};
|
||||
|
||||
function selectstart(event) {
|
||||
// Only stop this even if we're dragging already, since otherwise no
|
||||
// text whatsoever can be selected on the page.
|
||||
if (dragging)
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// mousemove and mouseup events need to be installed on document, not the
|
||||
// view element, since we want to catch the end of drag events even outside
|
||||
// our view. Only the mousedown events are installed on the view, as handled
|
||||
// by _createHandlers below.
|
||||
|
||||
DomEvent.add(document, {
|
||||
mousemove: mousemove,
|
||||
mouseout: mouseout,
|
||||
mouseup: mouseup,
|
||||
touchmove: mousemove,
|
||||
touchend: mouseup,
|
||||
selectstart: selectstart,
|
||||
scroll: updateFocus
|
||||
});
|
||||
DomEvent.add(document, docEvents);
|
||||
|
||||
DomEvent.add(window, {
|
||||
load: updateFocus
|
||||
});
|
||||
|
||||
return {
|
||||
_viewHandlers: {
|
||||
mousedown: mousedown,
|
||||
touchstart: mousedown,
|
||||
selectstart: selectstart
|
||||
},
|
||||
_viewEvents: viewEvents,
|
||||
|
||||
// To be defined in subclasses
|
||||
_handleEvent: function(/* type, point, event */) {},
|
||||
|
|
Loading…
Reference in a new issue