Merge branch 'refs/heads/master' into apply-matrix

Conflicts:
	src/item/Item.js
This commit is contained in:
Jürg Lehni 2014-03-01 22:55:54 +01:00
commit 1a836a168f
38 changed files with 1090 additions and 2374 deletions

View file

@ -6,7 +6,6 @@
"build",
"components",
"dist/paper-node.js",
"fatline",
"projects",
"node_modules",
"package.json",

View file

@ -31,7 +31,7 @@ then
fi
./preprocess.sh $MODE ../src/paper.js "-i '../src/constants.js'" ../dist/paper-full.js
./preprocess.sh $MODE ../src/paper.js "-o '{ \"paperscript\": false, \"palette\": false }' -i '../src/constants.js'" ../dist/paper-core.js
./preprocess.sh $MODE ../src/paper.js "-o '{ \"paperScript\": false, \"palette\": false }' -i '../src/constants.js'" ../dist/paper-core.js
./preprocess.sh $MODE ../src/paper.js "-o '{ \"environment\": \"node\" }' -i '../src/constants.js'" ../dist/paper-node.js
# Remove the existing file and copy paper-full.js to paper.js now

View file

@ -1,9 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 Harikrishnan Gopalakrishnan
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,424 +0,0 @@
new function() {
var MAX_RECURSION = 20;
var MAX_ITERATION = 20;
/**
* This method is analogous to paperjs#PathItem.getIntersections, but calls
* Curve.getIntersections2 instead.
*/
PathItem.prototype.getIntersections2 = function(path) {
// First check the bounds of the two paths. If they don't intersect,
// we don't need to iterate through their curves.
if (!this.getBounds().touches(path.getBounds()))
return [];
var locations = [],
curves1 = this.getCurves(),
curves2 = path.getCurves(),
length2 = curves2.length,
values2 = [];
for (var i = 0; i < length2; i++)
values2[i] = curves2[i].getValues();
for (var i = 0, l = curves1.length; i < l; i++) {
var curve1 = curves1[i],
values1 = curve1.getValues();
for (var j = 0; j < length2; j++)
Curve.getIntersections2(values1, values2[j], curve1, curves2[j],
locations);
}
return locations;
};
/**
* This method is analogous to paperjs#Curve.getIntersections
*/
Curve.getIntersections2 = function(v1, v2, curve1, curve2, locations) {
var linear1 = Curve.isLinear(v1),
linear2 = Curve.isLinear(v2);
// Determine the correct intersection method based on values of linear1 & 2:
(linear1 && linear2
? getLineLineIntersection
: linear1 || linear2
? getCurveLineIntersections
: getCurveIntersections)(v1, v2, curve1, curve2, locations);
return locations;
};
function addLocation(locations, curve1, parameter, point, curve2) {
// Avoid duplicates when hitting segments (closed paths too)
var first = locations[0],
last = locations[locations.length - 1];
if ((!first || !point.equals(first._point))
&& (!last || !point.equals(last._point)))
locations.push(new CurveLocation(curve1, parameter, point, curve2));
}
function getCurveIntersections(v1, v2, curve1, curve2, locations,
range1, range2, recursion) {
// NOTE: range1 and range1 are only used for recusion
recursion = (recursion || 0) + 1;
// Avoid endless recursion.
// Perhaps we should fall back to a more expensive method after this, but
// so far endless recursion happens only when there is no real intersection
// and the infinite fatline continue to intersect with the other curve
// outside its bounds!
if (recursion > MAX_RECURSION)
return;
// Set up the parameter ranges.
range1 = range1 || [ 0, 1 ];
range2 = range2 || [ 0, 1 ];
// Get the clipped parts from the original curve, to avoid cumulative errors
var part1 = Curve.getPart(v1, range1[0], range1[1]),
part2 = Curve.getPart(v2, range2[0], range2[1]),
iteration = 0;
// markCurve(part1, '#f0f', true);
// markCurve(part2, '#0ff', false);
// Loop until both parameter range converge. We have to handle the
// degenerate case seperately, where fat-line clipping can become
// numerically unstable when one of the curves has converged to a point and
// the other hasn't.
while (iteration++ < MAX_ITERATION
&& (Math.abs(range1[1] - range1[0]) > /*#=*/ Numerical.TOLERANCE
|| Math.abs(range2[1] - range2[0]) > /*#=*/ Numerical.TOLERANCE)) {
// First we clip v2 with v1's fat-line
var range,
intersects1 = clipFatLine(part1, part2, range = range2.slice()),
intersects2 = 0;
// Stop if there are no possible intersections
if (intersects1 === 0)
break;
if (intersects1 > 0) {
// Get the clipped parts from the original v2, to avoid cumulative
// errors ...and reuse some objects.
range2 = range;
part2 = Curve.getPart(v2, range2[0], range2[1]);
// markCurve(part2, '#0ff', false);
// Next we clip v1 with nuv2's fat-line
intersects2 = clipFatLine(part2, part1, range = range1.slice());
// Stop if there are no possible intersections
if (intersects2 === 0)
break;
if (intersects1 > 0) {
// Get the clipped parts from the original v2, to avoid
// cumulative errors
range1 = range;
part1 = Curve.getPart(v1, range1[0], range1[1]);
}
// markCurve(part1, '#f0f', true);
}
// Get the clipped parts from the original v1
// Check if there could be multiple intersections
if (intersects1 < 0 || intersects2 < 0) {
// Subdivide the curve which has converged the least from the
// original range [0,1], which would be the curve with the largest
// parameter range after clipping
if (range1[1] - range1[0] > range2[1] - range2[0]) {
// subdivide v1 and recurse
var t = (range1[0] + range1[1]) / 2;
getCurveIntersections(v1, v2, curve1, curve2, locations,
[ range1[0], t ], range2, recursion);
getCurveIntersections(v1, v2, curve1, curve2, locations,
[ t, range1[1] ], range2, recursion);
break;
} else {
// subdivide v2 and recurse
var t = (range2[0] + range2[1]) / 2;
getCurveIntersections(v1, v2, curve1, curve2, locations, range1,
[ range2[0], t ], recursion);
getCurveIntersections(v1, v2, curve1, curve2, locations, range1,
[ t, range2[1] ], recursion);
break;
}
}
// We need to bailout of clipping and try a numerically stable method if
// any of the following are true.
// 1. One of the parameter ranges is converged to a point.
// 2. Both of the parameter ranges have converged reasonably well
// (according to Numerical.TOLERANCE).
// 3. One of the parameter range is converged enough so that it is
// *flat enough* to calculate line curve intersection implicitly.
//
// Check if one of the parameter range has converged completely to a
// point. Now things could get only worse if we iterate more for the
// other curve to converge if it hasn't yet happened so.
var converged1 = Math.abs(range1[1] - range1[0]) < /*#=*/ Numerical.TOLERANCE,
converged2 = Math.abs(range2[1] - range2[0]) < /*#=*/ Numerical.TOLERANCE;
if (converged1 || converged2) {
addLocation(locations, curve1, null, converged1
? curve1.getPointAt(range1[0], true)
: curve2.getPointAt(range2[0], true), curve2);
break;
}
// see if either or both of the curves are flat enough to be treated
// as lines.
var flat1 = Curve.isFlatEnough(part1, /*#=*/ Numerical.TOLERANCE),
flat2 = Curve.isFlatEnough(part2, /*#=*/ Numerical.TOLERANCE);
if (flat1 || flat2) {
(flat1 && flat2
? getLineLineIntersection
// Use curve line intersection method while specifying
// which curve to be treated as line
: getCurveLineIntersections)(part1, part2,
curve1, curve2, locations, flat1);
break;
}
}
}
/**
* Clip curve V2 with fat-line of v1
* @param {Array} v1 section of the first curve, for which we will make a
* fat-line
* @param {Array} v2 section of the second curve; we will clip this curve with
* the fat-line of v1
* @param {Array} range2 the parameter range of v2
* @return {Number} 0: no Intersection, 1: one intersection, -1: more than one
* ntersection
*/
function clipFatLine(v1, v2, range2) {
// P = first curve, Q = second curve
var p0x = v1[0], p0y = v1[1], p1x = v1[2], p1y = v1[3],
p2x = v1[4], p2y = v1[5], p3x = v1[6], p3y = v1[7],
q0x = v2[0], q0y = v2[1], q1x = v2[2], q1y = v2[3],
q2x = v2[4], q2y = v2[5], q3x = v2[6], q3y = v2[7],
// Calculate the fat-line L for P is the baseline l and two
// offsets which completely encloses the curve P.
d1 = getSignedDistance(p0x, p0y, p3x, p3y, p1x, p1y) || 0,
d2 = getSignedDistance(p0x, p0y, p3x, p3y, p2x, p2y) || 0,
factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9,
dmin = factor * Math.min(0, d1, d2),
dmax = factor * Math.max(0, d1, d2),
// Calculate non-parametric bezier curve D(ti, di(t)) - di(t) is the
// distance of Q from the baseline l of the fat-line, ti is equally
// spaced in [0, 1]
dq0 = getSignedDistance(p0x, p0y, p3x, p3y, q0x, q0y),
dq1 = getSignedDistance(p0x, p0y, p3x, p3y, q1x, q1y),
dq2 = getSignedDistance(p0x, p0y, p3x, p3y, q2x, q2y),
dq3 = getSignedDistance(p0x, p0y, p3x, p3y, q3x, q3y),
// Find the minimum and maximum distances from l, this is useful for
// checking whether the curves intersect with each other or not.
mindist = Math.min(dq0, dq1, dq2, dq3),
maxdist = Math.max(dq0, dq1, dq2, dq3);
// If the fatlines don't overlap, we have no intersections!
if (dmin > maxdist || dmax < mindist)
return 0;
var Dt = getConvexHull(dq0, dq1, dq2, dq3),
tmp;
if (dq3 < dq0) {
tmp = dmin;
dmin = dmax;
dmax = tmp;
}
// Calculate the convex hull for non-parametric bezier curve D(ti, di(t))
// Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax
// for the coorresponding t values (tmin, tmax): Portions of curve v2 before
// tmin and after tmax can safely be clipped away
var tmaxdmin = -Infinity,
tmin = Infinity,
tmax = -Infinity;
for (var i = 0, l = Dt.length; i < l; i++) {
var Dtl = Dt[i],
dtlx1 = Dtl[0],
dtly1 = Dtl[1],
dtlx2 = Dtl[2],
dtly2 = Dtl[3];
if (dtly2 < dtly1) {
tmp = dtly2;
dtly2 = dtly1;
dtly1 = tmp;
tmp = dtlx2;
dtlx2 = dtlx1;
dtlx1 = tmp;
}
// We know that (dtlx2 - dtlx1) is never 0
var inv = (dtly2 - dtly1) / (dtlx2 - dtlx1);
if (dmin >= dtly1 && dmin <= dtly2) {
var ixdx = dtlx1 + (dmin - dtly1) / inv;
if (ixdx < tmin)
tmin = ixdx;
if (ixdx > tmaxdmin)
tmaxdmin = ixdx;
}
if (dmax >= dtly1 && dmax <= dtly2) {
var ixdx = dtlx1 + (dmax - dtly1) / inv;
if (ixdx > tmax)
tmax = ixdx;
if (ixdx < tmin)
tmin = 0;
}
}
// Return the parameter values for v2 for which we can be sure that the
// intersection with v1 lies within.
if (tmin !== Infinity && tmax !== -Infinity) {
var mindmin = Math.min(dmin, dmax),
mindmax = Math.max(dmin, dmax);
if (dq3 > mindmin && dq3 < mindmax)
tmax = 1;
if (dq0 > mindmin && dq0 < mindmax)
tmin = 0;
if (tmaxdmin > tmax)
tmax = 1;
// tmin and tmax are within the range (0, 1). We need to project it to
// the original parameter range for v2.
var v2tmin = range2[0],
tdiff = range2[1] - v2tmin;
range2[0] = v2tmin + tmin * tdiff;
range2[1] = v2tmin + tmax * tdiff;
// If the new parameter range fails to converge by atleast 20% of the
// original range, possibly we have multiple intersections. We need to
// subdivide one of the curves.
if ((tdiff - (range2[1] - range2[0])) / tdiff >= 0.2)
return 1;
}
// TODO: Try checking with a perpendicular fatline to see if the curves
// overlap if it is any faster than this
if (Curve.getBounds(v1).touches(Curve.getBounds(v2)))
return -1;
return 0;
}
/**
* Calculate the convex hull for the non-paramertic bezier curve D(ti, di(t)).
* The ti is equally spaced across [0..1] [0, 1/3, 2/3, 1] for
* di(t), [dq0, dq1, dq2, dq3] respectively. In other words our CVs for the
* curve are already sorted in the X axis in the increasing order. Calculating
* convex-hull is much easier than a set of arbitrary points.
*/
function getConvexHull(dq0, dq1, dq2, dq3) {
var distq1 = getSignedDistance(0, dq0, 1, dq3, 1 / 3, dq1),
distq2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2);
// Check if [1/3, dq1] and [2/3, dq2] are on the same side of line
// [0,dq0, 1,dq3]
if (distq1 * distq2 < 0) {
// dq1 and dq2 lie on different sides on [0, q0, 1, q3]. The hull is a
// quadrilateral and line [0, q0, 1, q3] is NOT part of the hull so we
// are pretty much done here.
return [
[ 0, dq0, 1 / 3, dq1 ],
[ 1 / 3, dq1, 1, dq3 ],
[ 2 / 3, dq2, 0, dq0 ],
[ 1, dq3, 2 / 3, dq2 ]
];
}
// dq1 and dq2 lie on the same sides on [0, q0, 1, q3]. The hull can be
// a triangle or a quadrilateral and line [0, q0, 1, q3] is part of the
// hull. Check if the hull is a triangle or a quadrilateral.
var dqMaxX, dqMaxY, vqa1a2X, vqa1a2Y, vqa1MaxX, vqa1MaxY, vqa1MinX, vqa1MinY;
if (Math.abs(distq1) > Math.abs(distq2)) {
dqMaxX = 1 / 3;
dqMaxY = dq1;
// apex is dq3 and the other apex point is dq0 vector
// dqapex->dqapex2 or base vector which is already part of the hull.
vqa1a2X = 1;
vqa1a2Y = dq3 - dq0;
// vector dqapex->dqMax
vqa1MaxX = 2 / 3;
vqa1MaxY = dq3 - dq1;
// vector dqapex->dqmin
vqa1MinX = 1 / 3;
vqa1MinY = dq3 - dq2;
} else {
dqMaxX = 2 / 3;
dqMaxY = dq2;
// apex is dq0 in this case, and the other apex point is dq3 vector
// dqapex->dqapex2 or base vector which is already part of the hull.
vqa1a2X = -1;
vqa1a2Y = dq0 - dq3;
// vector dqapex->dqMax
vqa1MaxX = -2 / 3;
vqa1MaxY = dq0 - dq2;
// vector dqapex->dqmin
vqa1MinX = -1 / 3;
vqa1MinY = dq0 - dq1;
}
// Compare cross products of these vectors to determine, if
// point is in triangles [ dq3, dqMax, dq0 ] or [ dq0, dqMax, dq3 ]
var a1a2_a1Min = vqa1a2X * vqa1MinY - vqa1a2Y * vqa1MinX,
a1Max_a1Min = vqa1MaxX * vqa1MinY - vqa1MaxY * vqa1MinX;
return a1a2_a1Min * a1Max_a1Min < 0
// Point [2/3, dq2] is inside the triangle, the hull is a triangle.
? [
[ 0, dq0, dqMaxX, dqMaxY ],
[ dqMaxX, dqMaxY, 1, dq3 ],
[ 1, dq3, 0, dq0 ]
]
// Convexhull is a quadrilateral and we need all lines in the
// correct order where line [0, q0, 1, q3] is part of the hull.
: [
[ 0, dq0, 1 / 3, dq1 ],
[ 1 / 3, dq1, 2 / 3, dq2 ],
[ 2 / 3, dq2, 1, dq3 ],
[ 1, dq3, 0, dq0 ]
];
}
// This is basically an "unrolled" version of #Line.getDistance() with sign
// May be a static method could be better!
function getSignedDistance(a1x, a1y, a2x, a2y, bx, by) {
var m = (a2y - a1y) / (a2x - a1x),
b = a1y - (m * a1x);
return (by - (m * bx) - b) / Math.sqrt(m * m + 1);
}
/**
* Intersections between curve and line becomes rather simple here mostly
* because of Numerical class. We can rotate the curve and line so that the line
* is on X axis, and solve the implicit equations for X axis and the curve.
*/
function getCurveLineIntersections(v1, v2, curve1, curve2, locations, flip) {
if (flip === undefined)
flip = Curve.isLinear(v1);
var vc = flip ? v2 : v1,
vl = flip ? v1 : v2,
l1x = vl[0], l1y = vl[1],
l2x = vl[6], l2y = vl[7],
// Rotate both the curve and line around l1 so that line is on x axis
lvx = l2x - l1x,
lvy = l2y - l1y,
// Angle with x axis (1, 0)
angle = Math.atan2(-lvy, lvx),
sin = Math.sin(angle),
cos = Math.cos(angle),
// (rl1x, rl1y) = (0, 0)
rl2x = lvx * cos - lvy * sin,
rl2y = lvy * cos + lvx * sin,
vcr = [];
for(var i = 0; i < 8; i += 2) {
var x = vc[i] - l1x,
y = vc[i + 1] - l1y;
vcr.push(
x * cos - y * sin,
y * cos + x * sin);
}
var roots = [],
count = Curve.solveCubic(vcr, 1, 0, roots);
// NOTE: count could theoretically be -1 for inifnite solutions, although
// that should only happen with lines, in which case we should not be here.
for (var i = 0; i < count; i++) {
var t = roots[i];
if (t >= 0 && t <= 1) {
var point = Curve.evaluate(vcr, t, true, 0);
// We do have a point on the infinite line. Check if it falls on the
// line *segment*.
if (point.x >= 0 && point.x <= rl2x)
addLocation(locations,
flip ? curve2 : curve1,
// The actual intersection point
t, Curve.evaluate(vc, t, true, 0),
flip ? curve1 : curve2);
}
}
}
function getLineLineIntersection(v1, v2, curve1, curve2, locations) {
var point = Line.intersect(
v1[0], v1[1], v1[6], v1[7],
v2[0], v2[1], v2[6], v2[7], false);
// Passing null for parameter leads to lazy determination of parameter
// values in CurveLocation#getParameter() only once they are requested.
if (point)
addLocation(locations, curve1, null, point, curve2);
}
};

View file

@ -1,4 +0,0 @@
------
Harikrishnan Gopalakrishnan

View file

@ -1,470 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Poly-bézier path Intersection Study</title>
<script type="text/javascript" src="../lib/prepro.js"></script>
<script type="text/javascript" src="../src/load.js"></script>
<script type="text/javascript" src="Intersect.js"></script>
<script type="text/javascript" src="intersectTests.js"></script>
<style>
body { height: 100%; overflow: auto; }
#container { display: block; width: 1000px; margin: 0 auto 50px; }
h1, h3 { font-family: 'Helvetica Neue'; font-weight: 300; margin: 50px 0 20px; }
footer{display: block; width: 1000px; margin: 30px auto; color: #999; }
footer p, footer a { font-family: 'Helvetica Neue'; font-style: italic; font-weight: 300; }
canvas { cursor: crosshair; width: 100%; height: 220px; margin: 5px 0;} canvas.big { height: 400px;}
footer ul{ list-style: none; padding: 0; } footer ul li p, footer ul li a{ font-style: normal; }
footer ul li a{ margin-left: 2em; } canvas.big2 { height: 300px;}
footer ul li.caption p {font-weight: bold; opacity: 0.6; }
.error { color: #a00; } .hide{ display: none; }
</style>
</head>
<body>
<div id="container">
<h1>Cubic bézier fat-line clipping study (using paperjs)</h1>
<button id="testStart" value="Start tests" onClick="runTests();">Start tests</button>
</div>
<footer>
<p>Fat-line clipping and other operations on cubic bézier curves.</p>
<ul>
<li class="caption"><p>References</p></li>
<li><a href="http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf">T. W. Sederberg, T. Nishita Curve intersection using bezier clipping.</a></li>
</ul>
<p>--<br />
hari</p>
</footer>
<svg class="hide" version="1.1" id="svgrandom1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M121.938,15.632c-13.717-12.536 -86.488,106.314 -97.28853,96.70168c-10.79979,-9.61253 74.75206,22.03522 62.05339,14.94206c-12.69866,-7.09316 -39.33589,-120.29063 -50.13215,-122.55235c-10.79626,-2.26173 20.61549,120.92844 6.21122,120.91582c-14.40427,-0.01263 95.8736,-97.47028 79.15607,-110.0072" />
<path fill="none" d="M112.56135,26.45534c-11.3324,-19.66938 2.46546,15.5079 -15.74899,1.46029c-18.21444,-14.04761 -66.32694,49.96681 -85.68888,45.03851c-19.36194,-4.9283 25.00762,66.13871 9.65758,51.54906c-15.35004,-14.58966 80.2128,-61.80948 76.57166,-81.27628c-3.64115,-19.4668 26.54102,2.89779 15.20863,-16.77158" />
</svg>
<svg class="hide" version="1.1" id="svgrandom2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M55.84967,115.22826c-18.46018,-11.52267 -5.13012,-104.06365 -21.53505,-114.49647c-16.40494,-10.43282 37.27459,91.17124 19.75792,84.78494c-17.51668,-6.3863 0.26851,-73.4895 -13.30075,-74.14695c-13.56926,-0.65745 42.04716,92.37097 23.83192,92.28519c-18.21524,-0.08578 19.29393,-4.87778 16.98435,-4.92088c-2.30958,-0.0431 -51.21527,-19.25571 -54.68432,-27.34524c-3.46904,-8.08953 83.90965,10.70419 70.0436,2.83256c-13.86605,-7.87163 -22.4316,45.08116 -40.02591,30.30112c-17.59431,-14.78004 78.05669,16.02252 60.81191,7.3855c-17.24478,-8.63702 -102.15822,-1.12427 -107.57647,-17.95819c-5.41825,-16.83392 64.06382,50.60482 52.5646,33.01986c-11.49922,-17.58496 44.06467,-104.79755 35.13656,-115.92523c-8.9281,-11.12768 21.83572,22.02208 17.93124,16.77489c-3.90448,-5.24719 -73.60183,85.82912 -80.11354,81.74953c-6.51171,-4.07958 52.09858,-37.99084 38.6322,-40.45908c-13.46638,-2.46824 -27.57103,-4.69586 -39.27681,-8.64429c-11.70578,-3.94843 43.5615,61.40122 35.24959,61.16567c-8.31191,-0.23555 -22.81477,-84.27143 -37.4408,-96.38241c-14.62603,-12.11098 2.73944,73.29997 -15.75259,62.98147c-18.49202,-10.3185 57.22255,38.52067 38.76237,26.998" />
<path fill="none" d="M124.25112,1.79465c-16.70696,-18.16066 -84.58936,25.58959 -101.49108,15.0504c-16.90171,-10.53919 46.75597,68.79952 42.13449,57.7377c-4.62148,-11.06182 72.86975,49.45108 59.02445,33.74034c-13.8453,-15.71074 -4.64367,-61.75728 -9.946,-70.16595c-5.30233,-8.40867 -59.46844,65.85202 -68.2972,64.0792c-8.82876,-1.77282 -1.54202,5.91022 -3.69332,5.25971c-2.1513,-0.65051 2.98295,17.37807 2.6116,4.13995c-0.37136,-13.23813 87.27724,-74.83845 73.45397,-77.0332c-13.82327,-2.19475 15.33624,70.7949 3.66016,50.87465c-11.67608,-19.92026 -22.02588,-66.90805 -26.33953,-82.18776c-4.31365,-15.27971 -40.34671,96.56568 -53.32969,82.34647c-12.98297,-14.21921 18.6211,9.62964 5.16889,5.52245c-13.4522,-4.10718 -7.48163,-27.24575 -14.65512,-42.22678c-7.17349,-14.98103 -3.83069,6.03324 -12.16872,-11.55839c-8.33804,-17.59163 69.56243,3.66432 49.802,-4.30838c-19.76043,-7.9727 -8.91008,-16.5344 -28.22815,-25.86978c-19.31807,-9.33537 77.84701,76.11681 74.06207,62.04286c-3.78494,-14.07394 -11.3097,19.48559 -12.85907,19.2506c-1.54937,-0.235 -81.9015,-69.71187 -91.64027,-80.20854c-9.73878,-10.49667 129.43749,11.67511 112.73053,-6.48555" />
</svg>
<svg class="hide" version="1.1" id="svgrandom3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M54.01772,11.22619c-18.20709,-12.52602 61.88252,17.37566 49.98879,0.41251c-11.89373,-16.96315 -57.48885,97.79587 -67.34346,91.15361c-9.85461,-6.64226 60.06622,-52.50884 57.16797,-53.51388c-2.89825,-1.00504 14.98219,21.59751 8.75775,15.74124c-6.22444,-5.85627 -81.47973,22.0899 -87.2512,21.93702c-5.77147,-0.15288 115.79014,19.6188 97.11956,13.97034c-18.67057,-5.64846 -101.98956,27.79453 -104.1477,11.23101c-2.15813,-16.56352 80.90365,10.68429 79.81834,-4.37386c-1.08531,-15.05815 -65.38517,-82.08106 -80.14536,-88.08628c-14.76019,-6.00522 113.80993,107.20587 97.98042,90.69978c-15.82951,-16.50609 -89.07394,-20.59276 -105.0168,-23.10521c-15.94286,-2.51245 75.78212,-72.15859 58.16052,-79.11261c-17.6216,-6.95402 15.13181,122.721 7.09601,115.18191c-8.0358,-7.53909 59.80187,-57.16136 57.37929,-64.37279c-2.42257,-7.21143 -89.22172,-8.27199 -108.94442,-24.80971c-19.7227,-16.53772 -7.53422,-23.5325 -12.86198,-32.73198c-5.32776,-9.19949 125.74382,102.82176 109.25335,84.53176c-16.49047,-18.29 10.62278,-21.24625 3.54689,-34.64539c-7.07589,-13.39914 -84.53036,-23.58012 -94.96962,-37.56896c-10.43927,-13.98884 52.61874,9.98751 34.41164,-2.53851" />
<path fill="none" d="M69.78945,75.24214c-12.03895,-11.30586 73.92983,34.48045 57.1316,15.38415c-16.79824,-19.0963 -58.31298,-32.94201 -75.0704,-44.28449c-16.75743,-11.34248 32.47247,28.1257 22.33688,25.80446c-10.13558,-2.32124 2.52538,19.16885 -14.73964,2.68085c-17.26502,-16.488 24.41384,34.60425 18.65739,19.03774c-5.75646,-15.56651 32.70649,-17.43621 27.27121,-35.27665c-5.43528,-17.84044 -21.3295,-28.98105 -27.18693,-36.14508c-5.85743,-7.16403 -36.93801,10.54966 -37.18618,-4.38317c-0.24817,-14.93283 61.34618,44.08845 59.46353,27.80393c-1.88264,-16.28453 7.98343,-11.52748 -1.74259,-26.87787c-9.72601,-15.35039 -23.36546,36.18479 -30.042,28.15101c-6.67654,-8.03378 33.52966,38.18172 18.6684,36.64374c-14.86127,-1.53798 -59.798,-26.9086 -70.33709,-46.79513c-10.53909,-19.88653 30.19654,28.52041 16.2994,25.38554c-13.89714,-3.13487 47.1942,-40.33974 32.41691,-59.30792c-14.77729,-18.96818 66.04269,88.15785 62.42887,72.45904c-3.61382,-15.69881 6.28228,35.32465 -7.29971,20.98492c-13.58198,-14.33973 -44.65756,-88.82039 -64.02677,-90.76017c-19.36921,-1.93977 -8.19355,12.06314 -10.14185,7.97409c-1.9483,-4.08906 35.13791,72.82689 23.09896,61.52103" />
</svg>
<svg class="hide" version="1.1" id="glyphsys" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M68.836,146.216c8.889,5.698,21.647,10.021,35.324,10.021c20.283,0,32.142-10.689,32.142-26.203
c0-14.122-8.203-22.557-28.949-30.305c-25.072-9.121-40.577-22.343-40.577-43.759c0-23.93,19.829-41.708,49.688-41.708
c15.495,0,27.112,3.646,33.719,7.516l-5.456,16.182c-4.787-2.959-15.031-7.293-28.949-7.293c-20.961,0-28.94,12.536-28.94,23.021
c0,14.354,9.344,21.425,30.536,29.627c25.981,10.03,38.971,22.565,38.971,45.123c0,23.717-17.313,44.444-53.557,44.444
c-14.809,0-30.991-4.546-39.194-10.039L68.836,146.216z"/>
<path fill="none" d="M82.734,183.337v-66.265L33.15,27.158h23.17l22.009,43.104c5.792,11.811,10.66,21.321,15.528,32.207h0.462
c4.17-10.207,9.736-20.396,15.764-32.207l22.473-43.104h22.707l-52.131,89.669v66.51H82.734z"/>
</svg>
<svg class="hide" version="1.1" id="glyphsacirc" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M107.585,161.74c-3.868,0-9.281-2.318-12.117-5.156c-3.351-3.35-4.898-6.961-6.186-11.342
c-10.312,6.961-22.688,16.498-30.421,16.498c-18.304,0-30.935-15.211-30.935-31.451c0-12.631,6.703-20.621,20.881-25.52
c15.468-5.157,34.287-12.118,39.958-16.757v-4.383c0-18.818-8.766-29.13-21.913-29.13c-4.898,0-8.765,1.805-11.601,4.898
c-3.093,3.609-5.413,9.281-7.476,17.015c-1.288,4.124-3.868,5.929-7.477,5.929c-4.64,0-11.084-4.898-11.084-11.085
c0-3.609,3.094-6.703,7.991-10.312c6.961-5.156,23.976-14.952,38.67-18.045c7.733,0,15.466,2.321,21.139,6.961
c9.024,7.733,11.601,18.045,11.601,31.708v47.693c0,11.6,4.64,15.467,9.024,15.467c3.094,0,6.7-1.289,9.279-2.836l2.577,6.703
L107.585,161.74z M88.766,96.778c-5.415,2.835-17.788,8.248-23.202,10.828c-10.054,4.639-15.725,9.537-15.725,19.076
c0,13.146,10.31,19.592,18.303,19.592c6.701,0,15.725-4.383,20.623-9.279V96.778z"/>
<circle fill="none" cx="112.681" cy="105.821" r="52.076"/>
</svg>
<svg class="hide" version="1.1" id="svggears" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M127.11,135.58c0.899,0.19,2.568,0.312,3.713,0.269l1.001-0.038c1.145-0.043,2.215-1.005,2.381-2.138
l0.588-5.705c0.071-1.142-0.778-2.305-1.888-2.585l-0.956-0.242c-1.11-0.279-2.77-0.509-3.689-0.509s-1.724-0.936-1.785-2.077
l-0.674-6.489c-0.181-1.13,0.407-2.211,1.305-2.401c0.899-0.191,2.475-0.76,3.5-1.266l0.901-0.441
c1.026-0.505,1.617-1.819,1.311-2.923l-1.762-5.453c-0.397-1.072-1.644-1.791-2.771-1.597l-1.005,0.176
c-1.127,0.194-2.737,0.66-3.578,1.034c-0.84,0.375-1.954-0.152-2.475-1.172l-3.263-5.647c-0.625-0.958-0.525-2.186,0.221-2.728
c0.744-0.542,1.954-1.704,2.687-2.582l0.641-0.769c0.734-0.879,0.735-2.318,0.004-3.198l-3.842-4.259
c-0.8-0.817-2.232-0.964-3.184-0.327l-0.823,0.552c-0.95,0.637-2.232,1.718-2.849,2.402c-0.615,0.685-1.849,0.655-2.737-0.064
l-5.276-3.833c-0.96-0.623-1.37-1.785-0.908-2.583c0.459-0.797,1.092-2.35,1.403-3.451l0.275-0.974
c0.313-1.101-0.271-2.417-1.296-2.925l-5.237-2.329c-1.064-0.42-2.433,0.027-3.042,0.996l-0.536,0.853
c-0.609,0.968-1.34,2.476-1.624,3.35c-0.285,0.874-1.421,1.346-2.525,1.047l-6.383-1.349c-1.13-0.18-1.976-1.075-1.879-1.989
c0.096-0.914,0.042-2.588-0.121-3.72l-0.141-0.983c-0.163-1.132-1.229-2.122-2.371-2.199c0,0-1.201-0.081-2.866-0.081
c-1.669,0-2.876,0.081-2.876,0.081c-1.141,0.077-2.208,1.067-2.371,2.199l-0.141,0.982c-0.163,1.132-0.217,2.807-0.121,3.721
c0.096,0.914-0.75,1.81-1.879,1.99l-6.383,1.35c-1.105,0.298-2.241-0.173-2.525-1.046c-0.284-0.874-1.015-2.381-1.624-3.35
l-0.537-0.854c-0.609-0.969-1.978-1.417-3.041-0.996l-5.238,2.331c-1.024,0.508-1.608,1.825-1.295,2.925l0.275,0.973
c0.313,1.1,0.945,2.653,1.406,3.452c0.46,0.798,0.052,1.96-0.907,2.583l-5.278,3.834c-0.889,0.72-2.122,0.75-2.737,0.065
c-0.616-0.684-1.897-1.765-2.848-2.402L36.61,82.52c-0.951-0.637-2.384-0.49-3.185,0.327l-3.841,4.26
c-0.73,0.881-0.729,2.32,0.004,3.199l0.641,0.768c0.733,0.878,1.943,2.041,2.688,2.582c0.746,0.542,0.845,1.77,0.221,2.729
l-3.263,5.649c-0.52,1.019-1.633,1.547-2.473,1.173c-0.841-0.374-2.45-0.84-3.577-1.035l-1.006-0.175
c-1.127-0.196-2.375,0.522-2.771,1.596l-1.762,5.455c-0.306,1.102,0.285,2.417,1.312,2.921l0.899,0.441
c1.027,0.505,2.602,1.074,3.5,1.265c0.899,0.191,1.487,1.271,1.306,2.401c0,0-0.784,4.917-0.784,8.558c0,0.003,0,0.003,0,0.003
c0,0.004-0.751,0.007-1.671,0.007s-2.579,0.23-3.688,0.509l-0.959,0.242c-1.109,0.28-1.958,1.443-1.887,2.586l0.59,5.706
c0.166,1.132,1.237,2.093,2.38,2.137l1.001,0.037c1.144,0.043,2.814-0.078,3.713-0.269c0.898-0.19,1.877,0.557,2.175,1.662
l2.015,6.205c0.412,1.067,0.062,2.246-0.778,2.621c-0.84,0.373-2.263,1.258-3.163,1.965l-0.796,0.626
c-0.9,0.706-1.2,2.114-0.667,3.126l2.872,4.963c0.615,0.966,1.986,1.406,3.049,0.981l0.926-0.372
c1.062-0.425,2.541-1.218,3.287-1.758c0.746-0.542,1.945-0.258,2.665,0.631l4.365,4.848c0.808,0.81,0.966,2.031,0.35,2.716
s-1.557,2.071-2.091,3.083l-0.463,0.877c-0.534,1.012-0.239,2.424,0.655,3.137l4.633,3.378c0.951,0.637,2.38,0.486,3.178-0.334
l0.708-0.729c0.797-0.821,1.826-2.145,2.287-2.943c0.46-0.797,1.67-1.023,2.689-0.502l5.959,2.653
c1.067,0.412,1.708,1.462,1.425,2.335c-0.283,0.875-0.578,2.522-0.654,3.665l-0.068,1.012c-0.076,1.141,0.768,2.307,1.876,2.589
l5.611,1.181c1.128,0.189,2.374-0.535,2.769-1.609l0.343-0.933c0.395-1.074,0.796-2.701,0.892-3.614
c0.097-0.914,1.108-1.589,2.249-1.499c0,0,1.458,0.114,3.26,0.114c1.805,0,3.271-0.115,3.271-0.115
c1.141-0.09,2.152,0.584,2.248,1.497c0.097,0.914,0.498,2.541,0.892,3.614l0.344,0.935c0.394,1.073,1.64,1.798,2.769,1.608
l5.61-1.182c1.108-0.282,1.953-1.448,1.876-2.59l-0.067-1.009c-0.077-1.143-0.372-2.791-0.656-3.665s0.358-1.926,1.425-2.337
l5.958-2.656c1.019-0.521,2.228-0.296,2.689,0.501c0.46,0.799,1.489,2.122,2.287,2.943l0.708,0.729
c0.799,0.82,2.227,0.97,3.178,0.334l4.632-3.379c0.895-0.714,1.188-2.125,0.655-3.137l-0.462-0.877
c-0.534-1.011-1.477-2.398-2.092-3.082c-0.617-0.686-0.46-1.907,0.349-2.718l4.364-4.848c0.719-0.89,1.918-1.175,2.663-0.634
c0.745,0.543,2.224,1.333,3.286,1.758l0.927,0.373c1.063,0.425,2.435-0.017,3.05-0.981l2.87-4.963
c0.532-1.014,0.231-2.42-0.668-3.127l-0.795-0.625c-0.899-0.706-2.323-1.591-3.164-1.966c-0.84-0.374-1.19-1.554-0.778-2.621
l2.013-6.206C125.234,136.137,126.212,135.39,127.11,135.58z"/>
<path fill="none" d="M175.403,87.286c0.899,0.19,2.568,0.311,3.713,0.268l1.002-0.038c1.145-0.042,2.215-1.005,2.381-2.137
l0.587-5.705c0.072-1.141-0.777-2.305-1.888-2.585l-0.956-0.242c-1.109-0.279-2.769-0.509-3.689-0.509
c-0.919,0-1.723-0.935-1.785-2.077l-0.673-6.489c-0.181-1.13,0.406-2.211,1.305-2.401c0.898-0.192,2.474-0.76,3.5-1.266l0.901-0.442
c1.025-0.504,1.616-1.819,1.311-2.922l-1.763-5.453c-0.396-1.073-1.644-1.792-2.771-1.596l-1.004,0.175
c-1.127,0.195-2.738,0.66-3.579,1.035c-0.839,0.375-1.954-0.152-2.474-1.171l-3.264-5.648c-0.624-0.958-0.524-2.186,0.221-2.728
c0.744-0.542,1.954-1.704,2.688-2.582l0.64-0.769c0.734-0.879,0.735-2.318,0.005-3.198l-3.842-4.26
c-0.801-0.816-2.233-0.964-3.184-0.327l-0.823,0.552c-0.951,0.637-2.233,1.718-2.849,2.402s-1.849,0.655-2.737-0.064l-5.277-3.833
c-0.96-0.623-1.37-1.785-0.908-2.583c0.46-0.797,1.092-2.35,1.404-3.451l0.275-0.974c0.312-1.101-0.271-2.417-1.296-2.925
l-5.237-2.329c-1.063-0.42-2.434,0.027-3.042,0.996l-0.536,0.853c-0.608,0.968-1.34,2.476-1.624,3.35
c-0.285,0.875-1.421,1.346-2.524,1.048l-6.384-1.349c-1.13-0.18-1.976-1.075-1.879-1.989c0.096-0.914,0.042-2.588-0.121-3.72
l-0.142-0.983c-0.162-1.132-1.229-2.122-2.369-2.199c0,0-1.202-0.081-2.867-0.081c-1.669,0-2.876,0.082-2.876,0.082
c-1.142,0.077-2.209,1.066-2.371,2.199l-0.142,0.982c-0.161,1.132-0.217,2.807-0.121,3.721c0.097,0.914-0.749,1.81-1.879,1.99
l-6.384,1.351c-1.105,0.298-2.24-0.173-2.524-1.046c-0.284-0.874-1.016-2.381-1.623-3.35l-0.538-0.854
c-0.607-0.969-1.978-1.417-3.04-0.996l-5.238,2.33c-1.024,0.509-1.608,1.825-1.295,2.926l0.275,0.973
c0.313,1.1,0.945,2.653,1.406,3.452c0.46,0.797,0.052,1.96-0.907,2.583l-5.278,3.834c-0.889,0.72-2.122,0.75-2.737,0.065
c-0.616-0.684-1.897-1.765-2.848-2.402l-0.823-0.553c-0.951-0.637-2.384-0.49-3.185,0.327l-3.841,4.261
c-0.73,0.881-0.729,2.32,0.004,3.199l0.641,0.768c0.733,0.878,1.943,2.041,2.688,2.582c0.746,0.542,0.845,1.77,0.221,2.729
l-3.263,5.649c-0.52,1.019-1.633,1.547-2.473,1.173c-0.841-0.374-2.45-0.84-3.577-1.035l-1.006-0.175
c-1.127-0.196-2.375,0.522-2.771,1.595l-1.762,5.455c-0.306,1.102,0.285,2.417,1.312,2.921l0.899,0.442
c1.027,0.504,2.602,1.074,3.5,1.265c0.899,0.19,1.487,1.271,1.306,2.401c0,0-0.784,4.917-0.784,8.557c0,0.003,0,0.003,0,0.003
c0,0.003-0.751,0.007-1.671,0.007s-2.579,0.23-3.688,0.509L66.496,77.1c-1.109,0.28-1.958,1.442-1.887,2.585l0.59,5.706
c0.166,1.132,1.237,2.094,2.38,2.136l1.001,0.038c1.144,0.043,2.814-0.078,3.713-0.269c0.898-0.19,1.877,0.556,2.175,1.661
l2.015,6.206c0.412,1.067,0.062,2.247-0.778,2.621c-0.84,0.373-2.263,1.258-3.163,1.964l-0.796,0.625
c-0.9,0.706-1.2,2.114-0.667,3.128l2.872,4.962c0.615,0.966,1.986,1.405,3.049,0.981l0.926-0.372
c1.062-0.426,2.541-1.217,3.287-1.758c0.746-0.542,1.945-0.258,2.665,0.63l4.365,4.848c0.808,0.81,0.966,2.031,0.35,2.718
c-0.616,0.684-1.557,2.069-2.091,3.083l-0.463,0.877c-0.534,1.011-0.239,2.424,0.655,3.137l4.633,3.376
c0.951,0.637,2.38,0.487,3.178-0.334l0.708-0.729c0.797-0.82,1.826-2.144,2.287-2.941c0.46-0.799,1.67-1.025,2.689-0.503
l5.959,2.653c1.066,0.412,1.708,1.462,1.426,2.336c-0.285,0.874-0.579,2.522-0.655,3.664l-0.068,1.011
c-0.076,1.142,0.769,2.308,1.877,2.59l5.611,1.182c1.129,0.188,2.374-0.536,2.769-1.61l0.343-0.933
c0.396-1.074,0.797-2.701,0.892-3.614c0.097-0.915,1.108-1.59,2.249-1.5c0,0,1.458,0.114,3.26,0.114
c1.805,0,3.271-0.114,3.271-0.114c1.142-0.09,2.152,0.583,2.249,1.498c0.096,0.912,0.497,2.54,0.893,3.614l0.343,0.933
c0.395,1.074,1.641,1.798,2.769,1.61l5.61-1.182c1.109-0.283,1.954-1.449,1.876-2.59l-0.066-1.009
c-0.076-1.144-0.373-2.792-0.655-3.666c-0.284-0.875,0.356-1.927,1.424-2.337l5.96-2.656c1.018-0.521,2.228-0.296,2.688,0.502
c0.459,0.798,1.488,2.121,2.286,2.941l0.709,0.731c0.798,0.82,2.227,0.968,3.177,0.334l4.633-3.379
c0.895-0.716,1.188-2.126,0.654-3.137l-0.462-0.877c-0.533-1.011-1.477-2.399-2.092-3.083c-0.616-0.687-0.46-1.908,0.349-2.718
l4.364-4.847c0.719-0.891,1.918-1.176,2.664-0.635c0.744,0.542,2.223,1.334,3.286,1.758l0.927,0.372
c1.062,0.426,2.434-0.016,3.049-0.979l2.87-4.965c0.532-1.013,0.232-2.419-0.668-3.125l-0.795-0.626
c-0.899-0.706-2.323-1.592-3.163-1.966s-1.191-1.554-0.778-2.621l2.012-6.206C173.528,87.844,174.506,87.095,175.403,87.286z"/>
</svg>
<svg class="hide" version="1.1" id="svggreenland" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M172.189,76.894c-0.651,0.691-2.171,0.642-3.206,0.797c-0.596,0.087-1.085,0.335-1.678,0.422
c-0.75,0.111-1.668-0.134-2.337,0.133c0.275,0.186,0.555,0.276,0.84,0.27c-0.22,0.446-1.029,0.344-1.575,0.335
c-0.67-0.016-1.193,0.186-1.808,0.273c-1.274,0.18-2.74-0.13-4.092-0.149c-0.53-0.006-0.801,0.35-1.287,0.434
c-0.604,0.102-1.296,0.062-1.979,0.006c-1.745-0.139-3.273-0.006-4.852,0.183c1.095,0.127,1.975-0.118,2.954-0.205
c0.875-0.074,1.857,0.074,2.797,0.164c2.257,0.217,4.241-0.288,6.573,0.174c-1.358,0.654-2.937,0.788-4.729,0.806
c-1.193,0.009-2.533-0.254-3.695-0.195c-0.8,0.041-1.38,0.397-2.155,0.471c0.794,0.137-0.183,0.409-0.418,0.617
c0.657-0.022,0.958,0.263,1.605,0.056c-0.821,0.133,0.258-0.456,0.543-0.515c0.555-0.115,1.19-0.103,1.811-0.093
c1.169,0.019,2.533-0.635,3.882,0.105c-1.253,0.515-2.348,0.654-3.956,0.624c-0.887-0.019-0.89,0.313-1.451,0.573
c-0.195,0.09,0.167,0.192,0.09,0.307c-0.111,0.18-0.279,0.326-0.499,0.434c0.666,0.049,0.921-0.366,1.792-0.192
c-0.195,0.292-0.385,0-0.707,0.084c-0.267,0.068-0.348,0.282-0.614,0.35c-0.418,0.108-0.8-0.019-0.911,0.341
c-0.121,0.387-0.871,0.248-1.37,0.189c-0.556-0.068-1.032,0.118-1.491,0.202c1.026,0.074-0.258,0.362-0.416,0.434
c0.232-0.087,0.496-0.05,0.791,0.108c-0.248,0.09-0.837,0.359-0.731,0.645c0.167,0.446-0.58,0.381-0.971,0.409
c0.254,0.019,0.505,0.037,0.76,0.056c-0.593,0.26-1.163,0.558-1.51,1.001c0.181,0,1.371,0.042,1.148,0.226
c0.434-0.215,0.578-0.676,1.108-0.846c0.533-0.17,1.125,0.043,1.764,0.118c0.134,0.015,0.327,0.024,0.541,0.035
c-0.269-0.055-0.539-0.119-0.808-0.211c0.633-0.081,2.053-0.068,2.729,0.406c-0.084-0.005-0.168-0.01-0.251-0.014
c0.397,0.09,0.74,0.225,0.911,0.448c-1.447,0.09-3.175-0.465-4.479-0.108c0.449-0.041,1.104,0.031,1.603,0.245
c-0.146-0.021-1.364-0.031-0.359,0.109c-0.462,0.059-1.002-0.075-1.482-0.056c0.794,0.103,1.56,0.105,2.362,0.245
c-0.028,0.006-0.062,0.004-0.092,0.008c0.447-0.001,0.89,0.038,1.187,0.203c0.366,0.201,0.831,0.416,1.166,0.208
c0.679-0.421,3.045,0.068,2.154,0.946c-0.449,0.45-2.089,0.09-2.88-0.034c-1.259-0.192-2.747-0.434-3.776-0.14
c0.229-0.043,0.483-0.019,0.757,0.081c-0.238,0.155-0.595,0.158-0.979,0.127c0.132,0.02,0.264,0.043,0.401,0.09
c-0.552,0.155-1.259,0.515-1.996,0.366c-0.413-0.083-0.8-0.496-1.24-0.462c-0.189,0.016-0.819,0.363-0.905,0.478
c0.288,0.019,0.579,0.17,0.769,0.378c0.124-0.394,1.33-0.146,0.686-0.056c0.214-0.074,0.462-0.056,0.744,0.056
c-0.224,0.034-0.44,0.077-0.648,0.136c0.22-0.05,0.462-0.068,0.723-0.056c-0.446,0.232-1.303-0.14-1.702,0.136
c0.558-0.012,1.28-0.152,1.916,0.161c-0.05,0.103-0.103,0.202-0.161,0.298c0.549,0.09,0.908-0.152,1.451-0.081
c0.515,0.068,1.051,0.369,1.54,0.325c-0.533-0.09-1.075-0.332-1.611-0.406c0.312-0.056,0.667-0.029,1.029,0.011
c-0.197-0.048-0.394-0.088-0.593-0.147c0.245,0.011,0.498,0.082,0.75,0.162c0.436,0.052,0.877,0.117,1.274,0.095
c0.657-0.037,1.256,0.493,0.432,0.484c-0.475-0.006-0.901,0.084-1.343,0.156c0.807,0.056,1.61,0.223,2.347,0.646
c-0.087,0.053-0.174,0.108-0.26,0.161c0.629,0.245,0.691,0.85,0.437,1.11c-0.264,0.272-0.877,0.232-1.383,0.108
c0.224-0.527-1.984-0.351-2.428-0.487c-0.89-0.276-2.093-0.915-2.855-0.651c0.54,0.083,1.11,0.118,1.628,0.462
c-0.518-0.1-0.974-0.034-1.473-0.081c1.293,0.251,2.602,0.121,3.879,0.865c-0.71,0.217-1.659,0.071-2.385,0.282
c-0.691,0.192-1.199-0.279-1.912-0.307c0.703,0.056,1.156,0.571,1.891,0.443c0.713-0.133,1.314-0.313,2.161-0.226
c-0.158,0.22-0.499,0.511,0.04,0.784c-0.254,0.152-0.62,0.183-0.979,0.199c0.735,0.081,1.38-0.295,2.151-0.043
c0.775,0.254,1.266-0.009,1.947-0.031c0.016,0.316,0.53,0.468,0.865,0.447c-0.57,0.257-0.998,0.629-1.82,0.573
c-0.288-0.019-0.583,0.006-0.88-0.081c-0.264-0.074-0.574-0.394-0.847-0.394c-0.896,0-1.798-0.034-2.691-0.019
c-0.471,0.006-0.861-0.341-1.271-0.304c0.285,0.078,0.561,0.229,0.74,0.422c-0.396,0.22-1.026,0.297-1.618,0.22
c0.279,0.021,0.546,0.102,0.8,0.248c-0.077,0.316-0.552,0.226-0.896,0.297c1.094,0.223,1.37,0.208,1.562,0.989
c0.778-0.118-0.81-0.937,0.316-0.946c1.007-0.006,2.198,0.381,3.249,0.403c0.062,0.291-0.338,0.394-0.412,0.632
c-0.105,0.35,0.248,0.391-0.45,0.431c-0.85,0.049-1.637-0.143-2.35,0.111c-1.736,0.62-3.37,0.103-5.513-0.508
c0.062-0.468,1.072-0.267,1.597-0.158c0.955,0.198,1.711,0.313,2.521,0.108c-0.971,0.016-1.699,0.003-2.737-0.205
c-0.698-0.143-1.612-0.359-2.093-0.065c-0.372,0.229-0.633,0.642-1.374,0.294c-0.471-0.223-1.413-0.859-1.882-0.667
c0.623,0.391,1.634,0.66,2.325,0.939c-0.545,0.043-1.178-0.003-1.612,0.192c-0.428,0.189-0.669,0.403-1.265,0.431
c-0.645,0.028-1.392-0.18-2.068-0.22c-0.514-0.031-1.19-0.524-1.611-0.304c0.747,0.146,1.491,0.412,2.204,0.397
c-0.264,0.313-0.555,0.381-1.172,0.267c-0.53-0.103-0.939,0.068-1.43,0.053c0.478-0.009,0.694,0.406,0.311,0.496
c0.425,0.034,0.502-0.254,0.771-0.378c0.372-0.167,1.11-0.021,1.572-0.124c0.666-0.149,1.305-0.037,2.086,0.105
c0.487,0.09,0.943-0.009,1.442,0.174c-0.189,0.341-0.747,0.307-1.231,0.347c-0.471,0.037-0.809,0.245-1.283,0.279
s-1.001-0.205-1.485-0.205c-0.539,0-1.051,0.053-1.603,0.025c0.766,0.254,1.389,0,2.143,0.173c-0.189,0.078-0.403,0.127-0.636,0.149
c0.354,0,0.716,0.009,1.051-0.025c-0.059,0.093-0.124,0.183-0.192,0.273c0.623-0.214,3.061-1.014,3.91,0.013
c0.459,0.558-2.288,0.465-2.604,0.456c0.385,0.118,0.707,0.041,1.054,0.05c0.022,0.201-0.077,0.372-0.267,0.471
c0.784-0.078,1.032-0.704,2.034-0.471c0.589,0.139,1.628,0.583,2.124,0.396c0.117,0.385,0.84,0.254,1.383,0.533
c0.452,0.233,1.02,0.031,1.568,0.335c-0.418,0.041-1.256,0.208-1.243,0.592c0.372-0.117,0.753-0.354,1.274-0.223
c-0.239,0.034-0.359,0.161-0.229,0.372c0.396-0.307,0.942-0.332,1.554-0.322c-0.85,0.137-1.414,0.369-1.237,1.092
c0.518-0.41,0.914-0.559,1.845-0.372c-0.438,0.065-0.409,0.13,0.077,0.198c-0.183,0.071-0.385,0.121-0.604,0.148
c0.248-0.037,0.446,0.057,0.521,0.273c-0.409-0.162-0.883,0.186-1.19,0.244c0.329,0.01,0.639-0.024,0.955-0.049
c-0.071,0.084-0.143,0.167-0.214,0.248c0.217-0.034,0.449-0.028,0.701,0.024c-0.264,0.099-0.248,0.524-0.701,0.372
c0.248,0.043,0.496,0.111,0.738,0.199c-0.276-0.016-0.425,0.117-0.403,0.322c0.868-0.105-0.028,0.924-0.509,0.369
c-0.275-0.314-1.534,0.223-1.146-0.816c-0.747-0.31-0.195,0.57-0.376,0.757c-0.278,0.298-1.444,0.081-1.925-0.024
c-0.858-0.193-1.705-0.326-1.876-1.095c-0.093-0.419-1.014-1.023-1.553-0.986c-0.939,0.065-1.777-0.495-2.731-0.558
c-0.348-0.021-0.754-0.171-1.082-0.148c-0.248,0.018-0.366,0.158-0.599,0.188c-0.564,0.068-1.541-0.58-2.14-0.788
c-0.8-0.282-1.516-0.359-2.354-0.639c0.354,0.109,0.524,0.307,0.376,0.496c0.449-0.012,0.917-0.015,1.407,0.075
c-0.934-0.09,0.937,0.403,1.2,0.443c-0.075,0.153-0.278,0.237-0.534,0.288c0.245,0.006,1.548,0.002,0.757,0.184
c0.701,0,1.442,0.102,2.143,0.107c0.422,0.007,0.8-0.102,1.253-0.018c0.26,0.049,0.731,0.17,0.868,0.406
c0.251,0.418-0.546,0.053-0.571,0.226c-0.08,0.593-2.188,0.7-2.954,0.714c-0.406,0.006-0.952,0.102-1.396-0.152
c-0.282-0.164-0.57-0.416-0.884-0.344c0.131,0.188,0.369,0.338,0.627,0.396c-0.425,0.123-1.135,0.096-1.678-0.025
c1.433,0.348,0.391,0.599,0.065,0.949c-0.329,0.354-1.427,0.375-2.071,0.338c0.438,0.223,1.417,0.034,1.742-0.068
c0.276-0.087,0.276,0.229,0.596,0.254c0.338,0.025,0.636-0.021,0.945-0.049c0.02-0.002,0.045-0.006,0.065-0.008
c-0.244-0.02-0.489-0.062-0.735-0.154c0.267-0.064,0.189-0.347,0.465-0.394c0.192-0.028,0.379-0.059,0.562-0.096
c0.567-0.14,0.964-0.456,1.581-0.549c0.651-0.097,1.321-0.022,1.926-0.204c0.356-0.112,0.747-0.225,1.188-0.218
c-0.01,0.075-0.016,0.149-0.021,0.224c0.399-0.047,1.215,0.471,0.576,0.521c0.14,0.074,0.276,0.149,0.412,0.224
c-1.078,0.362-2.378,0.285-3.615,0.381c-0.026,0.002-0.054,0.007-0.079,0.01c0.418,0.01,0.821,0.079,1.152,0.275
c-0.362,0.311-0.967,0.177-1.525,0.224c-0.412,0.034-0.738,0.198-1.061,0.338c-0.521,0.229-1.854,0.285-2.576,0.204
c0.57,0.205,1.581,0.208,2.064,0.134c0.556-0.084,0.894,0.255,1.297-0.158c0.515-0.54,1.624-0.226,2.468-0.369
c0.645-0.111,1.196-0.437,1.978-0.322c-0.046,0.093-0.103,0.183-0.161,0.272c0.636-0.136,1.15-0.17,1.922,0.09
c0.646,0.221,1.256,0.112,1.892,0.252c0.726,0.158,1.56,0.18,2.214,0.114c0.381-0.04,0.995-0.204,1.426,0.087
c-0.446,0.312-1.785,0.44-2.455,0.478c0.227-0.022,0.449,0.024,0.673,0.14c-0.285,0.143-0.683-0.053-1.014-0.14
c0.297,0.425-0.928,0.276-1.203,0.257c0.526-0.002,0.756,0.292,0.384,0.479c-0.248,0.12-0.924-0.041-1.277-0.06
c0.825,0.118-0.006,0.385-0.241,0.403c-0.298,0.019-0.633,0.006-0.952-0.053c0.66,0.428-0.558,0.27,0.27,0.372
c-0.391,0.111-1.022-0.235-1.265,0.071c0.58-0.065,0.369,0.235,0.134,0.272c-0.354,0.056-0.636-0.226-0.354,0.192
c-0.369-0.093-1.34,0.09-0.586,0.071c-0.577,0.103-0.847,0.512-1.504,0.512c-0.311,0-0.348,0.226-0.604,0.285
c-0.341,0.077-0.757-0.016-1.095,0.077c-0.465,0.127-0.961,0.041-1.398,0.174c-0.27,0.084-1.011,0.245-1.357-0.021
c0.105,0.227-0.087,0.35-0.4,0.248c-0.351-0.111-0.493,0.049-0.729,0.124c-0.44,0.136-1.001,0.278-1.594,0.046
c0.348,0.472-0.893,0.373-1.222,0.289c-0.167-0.041-0.797-0.416-0.787-0.158c0.013,0.304-0.366,0.152-0.651,0.195
c0.261,0.003,0.506,0.137,0.633,0.328c-0.288,0.016-0.536,0.094-0.871-0.018c-0.304-0.1-0.533-0.299-0.856-0.311
c0.264,0.077,0.493,0.201,0.683,0.375c-0.704,0.117-1.464,0.062-2.227,0.064c-0.412,0-1.066-0.328-0.439-0.368
c-0.63-0.151-0.992,0.013-1.327-0.562c-0.323,0.242-0.493-0.133-0.694-0.372c0.074,0.255-0.354,0.202-0.158,0.472
c0.13,0.18,0.61,0.248,0.809,0.391c-0.158,0.09-0.359,0.115-0.604,0.071c0.335,0.136,0.772,0.09,0.952,0.419
c-0.406-0.068-0.202,0.217-0.242,0.384c-0.05,0.211-0.8,0.047-1.042,0.128c-0.359,0.12-1.032,0.223-1.305,0.387
c-0.227,0.133-0.164,0.412-0.63,0.313c0.189,0.257,0.056,0.552-0.372,0.421c0.348,0.273-0.102,0.271-0.037,0.521
c0.078,0.316-1.433,0.174-0.766,0.502c-0.483-0.13-0.385,0.242-0.198,0.469c-0.409-0.192-0.683,0.318-0.878,0.418
c0.214-0.496-0.108,0.162-0.301-0.371c-0.198,0.099-0.217,0.285,0.078,0.465c-0.245,0.033-1.575,0.977-1.733,0.303
c-0.223,0.104-0.201,0.283,0.127,0.443c-0.31-0.002-0.647-0.033-0.977-0.129c-0.412-0.121-0.546-0.49-0.831-0.268
c0.519,0.121,0.363,0.4,0.654,0.654c-0.48,0.078-0.617-0.021-0.905,0.273c-0.217,0.223-0.713,0.086-0.971,0.26
c0.022-0.186-0.167-0.354-0.446-0.418c0.189,0.514-0.893,0.232-1.014,0.582c-0.248-0.167-0.27-0.35-0.118-0.465
c-0.592-0.037,0.1,0.555-0.415,0.509c-0.378-0.031-1.156-0.362-1.38-0.183c-0.14-0.521,0.862-0.971,1.451-1.002
c-0.734-0.006-1.538-0.18-2.264-0.164c0.242,0.105,0.484,0.168,0.723,0.186c-0.254,0.094-0.121,0.403-0.412,0.443
c-0.359,0.047,0.124,0.184,0.024,0.305c-0.164,0.201-0.781,0.402-1.048-0.072c-0.214,0.292,0.366,0.416,0.686,0.516
c-0.04,0.533-0.596,0.25-0.815,0.536c-0.108-0.394-0.248-0.202-0.645-0.211c0.13,0.077,0.26,0.155,0.391,0.232
c-0.559,0.028-1.104-0.151-1.702-0.304c0.394,0.26,0.062,0.465-0.252,0.422c-0.204-0.028-0.319-0.32-0.555-0.335
c-0.298-0.016-0.177,0.344-0.425,0.375c-0.312,0.04-0.766-0.271-0.691,0.146c0.264,0.043,0.527,0.1,0.797,0.164
c-0.087,0.044-0.17,0.09-0.251,0.14c0.118,0.081,0.235,0.158,0.359,0.233c-0.499-0.105-0.431,0.31-0.865,0.257
c0.744,0.22-0.248,0.384-0.474,0.315c-0.184-0.052-0.503-0.275-0.673-0.229c-0.235,0.064-0.942-0.068-0.992,0.161
c-0.034,0.151,0.13,0.251-0.164,0.282c0.496,0.394,1.454,0.586,1.116,1.092c0.235,0.009,0.424,0.114,0.523,0.287
c-0.388,0.094-0.84-0.148-1.243-0.009c-0.412,0.143-1.179-0.121-1.398,0.211c0.593,0.369,1.141,0.188,1.693,0.226
c0.688,0.047,0.483,0.481,0.096,0.531c0.475,0.084,0.311,0.354,0.499,0.576c-0.478-0.117-0.865,0.016-1.339-0.084
c-0.369-0.074-1.015-0.341-1.179-0.07c0.443-0.078,1.671,0.136,2.006,0.579c-0.121,0.024-0.598-0.111-0.636-0.003
c-0.056,0.174-0.154,0.205-0.443,0.047c0.841,0.514-0.245,0.027-0.399-0.044c0.217,0.248,0.148,0.298-0.211,0.155
c0.26,0.086,0.512,0.198,0.75,0.334c-0.347-0.129-0.694-0.275-1.051-0.355c0.121,0.046,1.203,0.514,1.116,0.586
c-0.227,0.191-1.398-0.426-1.718-0.518c0.025,0.455,0.884,0.176,0.958,0.691c0.038,0.272-0.853-0.137-1.048-0.137
c0.251,0.059,0.493,0.198,0.627,0.378c-0.289,0.084-1.029-0.103-1.309-0.356c-0.059,0.567,1.082,0.186,1.212,0.67
c-0.188-0.062-0.567,0.148-0.757,0.18c-0.468,0.077-0.026,0.05-0.098,0.125c0.011,0.001,0.022,0.003,0.033,0.005
c-0.028,0.011-0.061,0.019-0.089,0.028c-0.042,0.02-0.102,0.042-0.2,0.071c-0.013,0.004-0.03,0.003-0.043,0.007
c-0.189,0.055-0.389,0.098-0.621,0.095c-0.002,0-0.004,0.001-0.006,0.001c-0.001,0-0.001,0-0.002,0
c-0.008-0.066,0.012-0.113,0.042-0.151c0.004-0.005,0.007-0.009,0.011-0.013c0.067-0.072,0.194-0.1,0.346-0.1
c0.008,0,0.015,0,0.022,0c0.072,0.001,0.147,0.006,0.224,0.015c-0.401-0.165-0.707-0.12-1.088-0.173
c0.434,0.08,0.444,0.117,0.028,0.108c0.093,0.074,0.14,0.148,0.137,0.226c-0.028,0.097-0.22,0.104-0.348,0.104
c-0.334-0.004-0.753-0.06-1.116-0.171c0.323,0.186,0.946,0.452,1.281,0.431c0.347-0.025,0.946,0.304,0.319,0.214
c0.229,0.09,0.461,0.174,0.694,0.245c-0.526,0.152-1.298-0.267-1.906-0.312c0.391,0.173,1.206,0.263,1.514,0.526
c0.391,0.338-0.208,0.647-0.404,0.788c0.199-0.051,0.406-0.097,0.617-0.134c0.193,0.192,0.134,0.304-0.148,0.267
c0.815,0.552-0.936,0.311-1.209,0.312c0.416,0.174,0.927,0.332,1.219,0.156c-0.232,0.08-0.211,0.328-0.453,0.396
c-0.313,0.09-0.955-0.028-1.342-0.084c0.418,0.248,0.843,0.154,1.215,0.154c0.49,0.721-1.001,0.487-1.532,0.443
c0.214,0.051,1.376,0.1,0.726,0.246c0.189,0.006,0.375,0,0.549-0.021c-0.105,0.281-0.778,0.111-1.128,0.111
c0.325,0.043,0.654,0.086,0.989,0.154c-0.478,0.279-1.386-0.003-2.028,0.025c0.667,0.167,1.504,0.27,2.071,0.176
c-0.292,0.097-0.232,0.379-0.608,0.406c-0.353,0.028-0.763-0.024-1.163-0.117c0.558,0.201,1.079,0.174,1.628,0.312
c-0.381,0.149-0.999,0.004-1.488-0.046c0.428,0.103,0.815,0.056,1.247,0.18c0.118,0.391-0.524,0.248-0.872,0.224
c0.462,0.108,0.81,0.09,1.129-0.047c-0.087,0.236-0.425,0.242-0.766,0.201c0.211,0.037,0.419,0.06,0.624,0.065
c-0.323,0.103-0.72,0.155-1.15,0.068c0.822,0.208-0.453,0.241-0.806-0.202c-0.118,0.279-0.719,0.143-1.101,0.047
c0.558,0.227,1.085,0.205,1.637,0.335c-0.043,0.161-0.165,0.294-0.338,0.378c0.267-0.053,0.481-0.167,0.766-0.201
c-0.505,0.115,0.13,0.124,0.391,0.379c-0.217-0.022-0.4,0.009-0.54,0.09c0.202-0.041,0.394,0.046,0.53,0.223
c-0.133,0.082-0.307,0.092-0.497,0.073c0.019,0.006,0.034,0.014,0.052,0.021c0.018,0.006,0.033,0.013,0.05,0.02
c0.043,0.018,0.088,0.035,0.123,0.055c0.062,0.035,0.113,0.074,0.142,0.121c-0.288,0.006-0.599-0.103-0.902-0.177
c0.018,0.024,0.017,0.04,0.029,0.062c0.021,0.039,0.044,0.078,0.05,0.107c0.003,0.015-0.003,0.025-0.004,0.039
c-0.002,0.026-0.008,0.051-0.026,0.067c-0.012,0.013-0.029,0.021-0.047,0.029c-0.016,0.006-0.031,0.012-0.05,0.015
c-0.074,0.018-0.166,0.022-0.287,0.002c-0.179-0.03-0.482-0.072-0.512-0.131c-0.014-0.005-0.031-0.01-0.043-0.014
c0.02-0.007,0.039-0.014,0.058-0.021c0.026-0.021,0.083-0.042,0.19-0.066c0.004-0.001,0.008-0.003,0.012-0.004
c-0.025,0.001-0.052-0.007-0.078-0.008c-0.13-0.003-0.261-0.03-0.395-0.077c-0.064-0.021-0.128-0.038-0.194-0.07
c0.002-0.003,0.011-0.005,0.014-0.008c0.083-0.127,0.242-0.171,0.479-0.125c-0.18-0.021-0.36-0.043-0.542-0.068
c0.105-0.117,0.13-0.267,0.077-0.443c0.019,0.229-0.146,0.258-0.44,0.068c0.217,0.385-0.192,0.291-0.416,0.443
c-0.09,0.062-0.608,0.719-0.558,0.155c0,0.229-0.251,0.304-0.478,0c-0.083,0.173-0.322,0.157-0.574-0.022
c0.422-0.387,1.079-0.582,1.495-1.001c-0.58,0.14-1.048,0.617-1.532,0.868c-0.102-0.344,0.391-0.469,0.567-0.676
c0.177-0.205,0.167-0.506,0.434-0.66c-0.617,0.064-0.657,0.619-1.367,0.602c0.037-0.164,0.155-0.298,0.329-0.379
c-0.267,0.078-0.474,0.215-0.741,0.291c0.102-0.21,0.307-0.368,0.617-0.424c-0.49-0.034-0.698,0.207-0.887,0.446
c-0.161-0.388-0.62-0.14-0.177-0.378c-0.344-0.028-0.391,0.264-0.775,0.176c0.223-0.105,0.458-0.204,0.729-0.266
c-0.67,0,0.177-0.336,0.344-0.41c0.232-0.107,0.793,0.004,0.347-0.347c-0.298,0.232-0.592,0.592-1.153,0.577
c0.074-0.057,0.146-0.115,0.214-0.178c-0.527-0.143-0.54,0.391-1.045,0.202c0.174-0.155,0.468-0.202,0.713-0.292
c-0.214-0.016-0.396,0.016-0.542,0.09c0.502-0.502,1.913-0.198,2.173-0.89c-0.235,0.146-0.378,0.316-0.62-0.046
c0.013,0.529-0.8,0.505-1.249,0.713c-0.133-0.396,0.595-0.289,0.713-0.559c-0.124-0.049-0.245-0.102-0.369-0.154
c0.056-0.078,0.121-0.152,0.189-0.224c-0.329-0.021-0.416,0.251-0.803,0.134c0.673,0.086,0.056,0.536-0.434,0.402
c0.515,0.118,0.369,0.199,0.149,0.268c-0.403-0.521-1.026,0.127-1.42,0.09c0.341,0.318-0.105,0.381-0.44,0.22
c0.062-0.068,0.13-0.134,0.199-0.198c-0.729-0.072-1.371,0.009-2.025,0.064c0.338,0.103,0.614,0.047,0.893,0.025
c-0.104,0.152-0.501,0.197-0.927,0.2c0.03,0.008,0.058,0.01,0.089,0.02c-0.021,0.331-0.626,0.413-1.032,0.024
c0.086-0.024,0.177-0.047,0.271-0.064c-0.145-0.009-0.279-0.019-0.376-0.025c0.351-0.198,0.834-0.266,1.228-0.424
c-0.48,0-0.939,0.037-1.414,0.046c0.248-0.136,1.293,0.044,1.088-0.378c-0.248,0.074-0.527,0.183-0.856-0.111
c0.248-0.1,0.341-0.332,0.688-0.336c-0.769-0.105-0.527,0.496-1.008,0.469c0.041-0.074,0.084-0.148,0.127-0.223
c-0.183,0.07-0.577,0.176-0.71-0.156c0.242,0.022,0.412-0.03,0.505-0.154c-0.478-0.111-1.166-0.254-1.43-0.025
c0.162-0.391,0.992-0.355,1.529-0.399c-0.381,0-0.397-0.031-0.053-0.09c-0.698-0.028-1.197,0.44-2.046,0.046
c0.211-0.257,0.937-0.133,1.336-0.201c-0.298-0.006-0.567,0.037-0.88,0c0.031-0.164,0.146-0.297,0.319-0.378
c-0.14,0.093-0.323,0.136-0.549,0.134c-0.09-0.299,0.409-0.496,0.704-0.556c-0.729-0.211-0.589,0.475-1.101,0.512
c0.019-0.084,0.041-0.165,0.059-0.245c-0.502,0.307-0.056-0.285,0.136-0.365c0.232-0.094,0.521-0.174,0.837-0.171
c-0.229,0.013-0.394-0.087-0.44-0.267c-0.282,0.068-0.264,0.328-0.54,0.4c-0.254,0.067-0.629-0.047-0.846,0.067
c0.201-0.13,0.3-0.183-0.143-0.245c0.205-0.154,0.636,0.037,0.853-0.111c-0.22-0.037-0.431-0.053-0.629-0.043
c0.059-0.146,0.226-0.236,0.487-0.202c-0.292-0.05-2.301-0.232-2.208-0.71c0.077-0.406,0.685-0.57,0.958-0.871
c-0.242,0.18-0.543,0.351-0.967,0.356c0.118-0.226,0.347-0.394,0.601-0.533c-0.394-0.028-0.493,0.224-0.707,0.356
c0.037-0.174,0.108-0.328,0.21-0.468c-0.093,0.148-0.759,0.059-0.685-0.174c0.056-0.177,0.694-0.292,0.912-0.362
c-0.356,0.006-0.716,0.006-1.061,0.031c-0.164,0.009-1.029,0.241-0.453-0.053c-0.127,0.102-0.793,0.012-0.626-0.187
c0.094-0.113,0.33-0.133,0.597-0.134c-0.282-0.027-0.575-0.185-0.848-0.17c0.289-0.14,0.71-0.143,1.039-0.245
c-0.27,0.03-0.552,0.046-0.84,0.046c-0.075-0.402,0.905-0.201,1.23-0.312c-0.421,0.025-1.593-0.133-1.448,0.335
c-0.292-0.181-1.138-0.403-0.561-0.49c-0.794,0.128,0.403-0.151,0.499-0.177c0.261-0.074,0.434-0.22,0.694-0.291
c-0.322,0.056-0.672,0.27-1.134,0.09c0.155-0.164,0.006-0.307-0.351-0.379c0.713,0.115,1.401-0.055,1.907-0.223
c0.229-0.074,1.342-0.146,1.625,0c-0.279-0.381-0.793-0.111-1.113-0.155c0.139-0.105,0.267-0.214,0.381-0.335
c-0.53,0.41-1.848,0.915-2.998,0.533c0.133-0.086,0.31-0.13,0.527-0.133c-0.223-0.016-0.443-0.028-0.667-0.043
c0.602-0.137,1.6,0.201,2.003-0.156c-0.837-0.17,0.242-0.331,0.508-0.322c0.413,0.02,0.937,0.09,1.051-0.191
c-0.239,0.108-1.38-0.021-0.726-0.134c-0.254-0.074-0.508-0.158-0.763-0.245c0.124-0.019,1.42-0.009,0.533-0.133
c0.512-0.174,0.632,0.198,1.073,0.468c0.384,0.235,0.89,0.295,1.296,0.4c-0.211-0.45-1.594-0.466-0.89-0.89
c-0.639,0.183-0.149-0.336-0.006-0.4c-0.409,0.124-0.887,0.543-1.532-0.04c-0.051-0.046-0.101-0.101-0.147-0.157
c0.017,0.036,0.039,0.062,0.051,0.107c0.118,0.434-0.676,0.754-1.255,0.691c0.046-0.152,0.18-0.267,0.388-0.313
c-1.377-0.351-1.001,1.349-2.18,1.293c-0.13-0.158-0.422-1.106-0.304-1.209c0.228-0.192,0.685-0.334,1.137-0.45
c0.052-0.052,0.101-0.107,0.141-0.167c-0.508-0.217-0.713,0.178-1.003,0.358c-0.086,0.012-0.336,0.032-0.402-0.166
c-0.003-0.1,0.078-0.143,0.239-0.127c-0.508-0.4-0.239-0.639,0.006-0.834c-0.133,0.08-0.285,0.148-0.45,0.207
c0.025-0.074,0.043-0.151,0.053-0.232c-0.478-0.167-0.493,0.187-0.729,0.279c0.17-0.102,0.006-0.387,0.205-0.465
c0.223-0.053,0.431-0.117,0.623-0.198c0.282-0.171,0.642-0.181,0.847-0.415c0.57-0.648,1.993,0.002,2.93-0.134
c-0.307,0.021-0.403-0.034-0.282-0.161c-0.518,0.09-1.231-0.174-1.767-0.068c-0.422,0.08-0.871,0.183-1.116,0.409
c-0.22,0.204-0.44,0.335-0.843,0.394c-0.198,0.021-0.394,0.05-0.586,0.08c0.155-0.244,0.071-0.307-0.245-0.186
c0.133-0.133,0.242-0.281,0.323-0.443c-0.149,0.152-0.289,0.443-0.757,0.279c0.1-0.087,0.853-0.341,0.109-0.279
c0.214-0.024,0.263-0.143,0.133-0.325c-0.105,0.27-0.524,0.44-1.088,0.187c0.245,0.002,0.428-0.051,0.542-0.165
c-0.164,0.068-0.372,0.084-0.623,0.046c0.372-0.176,0.803-0.27,1.349-0.263c0.074,0,0.713,0.105,0.766,0.067
c0.205-0.145,0.267-0.291,0.62-0.331c0.316-0.037,0.822,0.204,0.552-0.195c-0.341,0.093-0.781,0.127-1.066,0.272
c-0.397,0.199-0.22-0.053-0.654-0.086c-0.598-0.044-0.952,0.535-1.711,0.186c0.147-0.038,0.276-0.092,0.412-0.141
c-0.218,0.074-0.467,0.123-0.809-0.045c0.223,0.012,0.415-0.01,0.577-0.068c-0.217-0.01-0.44-0.035-0.673-0.072
c0.552-0.22,1.392-0.22,1.742-0.604c0.06-0.064,0.154-0.1,0.244-0.139c-0.177,0.053-0.348,0.11-0.495,0.185
c-0.453,0.229-1.066,0.604-1.916,0.372c0.183-0.301,0.75-0.301,1.203-0.372c-0.065,0.003-1.724,0.103-1.352-0.183
c0.174-0.133,0.26-0.348,0.54-0.409c0.121-0.028,1.039,0.03,0.279,0.03c0.502,0.1,1.805,0.031,2.118-0.093
c-0.48,0.075-1.733,0.174-2.375-0.232c0.505-0.167,1.268-0.037,1.789-0.187c-0.332-0.018-0.583,0.094-0.974-0.024
c0.27-0.062,0.664,0,0.803-0.186c-0.22-0.013-0.446-0.034-0.679-0.068c0.388,0.062,0.626-0.065,0.986-0.044
c0.313,0.021,0.691,0.165,0.905,0.044c-0.487-0.077-0.543-0.149-0.177-0.211c-0.479-0.224-1.15-0.11-1.735,0.039
c-0.602-0.031-1.167-0.001-1.843-0.179c0.422,0,0.828-0.019,1.228-0.047c-0.341-0.03-0.651,0.016-1.045-0.164
c0.319,0.01,0.623-0.012,0.868-0.093c-0.363,0.068-0.769,0.108-1.265-0.046c0.236-0.189,0.747-0.109,1.104-0.187
c-0.952-0.022,0.307-0.118,0.391-0.124c0.394-0.031,0.8-0.043,1.19-0.074c0.698-0.057,1.432-0.062,2.127-0.115
c0.744-0.056,0.716,0.059,1.429,0.435c0.487,0.257,1.584-0.06,1.959-0.1c-0.912-0.177-1.56,0.48-2.496-0.394
c0.307-0.016,0.589-0.062,0.84-0.143c-1.101,0.227-2.552-0.01-3.667,0.214c-0.465,0.097-1.054-0.053-1.513,0.028
c-0.391,0.07-0.707,0.211-1.104,0.272c-0.419-0.442,0.312-0.668,0.651-0.776c-0.118,0.022-0.228,0.054-0.373,0.051
c-0.418-0.438,0.602-0.273,0.149-0.604c-0.676-0.452,1.547,0.294,0.471-0.325c0.341,0.13,1.023-0.065,0.267-0.047
c0.217-0.115,0.533-0.137,0.815-0.189c-0.211,0.013-0.44-0.009-0.691-0.068c0.325-0.059,0.717-0.004,1.117,0.063
c-0.134-0.035-0.268-0.071-0.407-0.132c-0.44-0.188-0.607-0.281-1.042-0.281c0.645-0.134,1.56,0.096,2.365,0.257
c-0.27-0.111-0.542-0.198-0.815-0.257c0.453-0.14,1.163,0.183,1.73,0.257c0.524,0.068,1.011,0.047,1.466,0.013
c0.899-0.074,2.089,0.455,2.948,0.312c-0.933-0.059-1.962-0.439-2.933-0.604c0.229-0.115,0.62-0.02,0.945,0
c-0.242-0.068-0.483-0.121-0.722-0.165c0.304-0.03,0.164-0.263,0.366-0.356c0.217-0.1,0.537-0.102,0.75-0.201
c-0.648,0.009-1.101,0.279-1.86,0.139c0.273,0.141,0.527,0.195,0.756,0.162c-0.058,0.177-0.311,0.24-0.614,0.265
c-0.239,0.245-0.831,0.291-1.302,0.321c0.629-0.273-0.14-0.065-0.45-0.164c0.495-0.035,0.987-0.077,1.458-0.143
c-0.29,0.005-0.588-0.004-0.794,0.021c-0.465,0.053-1.705-0.254-1.885,0.027c0.043-0.164,0.223-0.264,0.493-0.258
c-0.604-0.096-1.001,0.105-1.55,0.094c0.319-0.143,0.76-0.18,1.16-0.251c0.149-0.024,1.268-0.261,0.462-0.261
c0.298-0.146,0.757-0.183,1.203-0.164c-0.239-0.021-0.465-0.031-0.685-0.021c0.276-0.199,0.877,0.016,1.184-0.143
c-0.136,0.07-0.294,0.123-0.468,0.164c0.413-0.084,1.023,0.031,1.52,0.062c0.493,0.033,0.722-0.305,1.292-0.156
c-0.48-0.325,0.038-0.396,0.267-0.465c-0.422,0.016-0.695,0.221-1.191,0.14c0.016-0.245,0.465-0.214,0.416-0.465
c-0.053-0.276,0.093-0.503,0.546-0.422c-0.056,0.062-0.118,0.124-0.18,0.187c0.351-0.02,0.859-0.174,1.185,0.257
c0.103-0.114,0.27-0.167,0.505-0.164c-0.248-0.09-0.49-0.143-0.726-0.165c0.143-0.182,0.558-0.083,0.797-0.186
c-0.289-0.04-0.549-0.328-0.784-0.328c-0.53,0-0.62,0.348-1.296,0.214c0.313-0.429,0.996-0.837,1.947-0.723
c-0.468-0.187-0.89,0-1.265,0c-0.288-0.446,0.527-0.288,0.924-0.232c-0.279-0.149-0.527-0.195-0.744-0.143
c0.21-0.276,0.669-0.229,1.184-0.208c-0.053-0.189-0.273-0.332-0.549-0.35c0.121-0.125,0.48-0.141,0.478-0.336
c-0.855-0.024-1.46,0.255-2.356,0.171c0.177-0.05,0.363-0.09,0.558-0.12c-0.536-0.255-0.735,0.023-0.995,0.12
c-0.103,0.034-0.483-0.062-0.62-0.081c-0.837-0.107-1.206-0.006-2.142-0.449c-1.026-0.48-1.888-0.344-2.8-0.49
c-0.664-0.105-1.333-0.449-2.015-0.709c0.375-0.438,1.395-0.41,2.375-0.242c1.054,0.18,1.771-0.115,2.924,0.238
c1.169,0.359,1.885,0.524,2.979,0.695c0.31,0.046,1.101,0.27,1.194,0.07c0.068-0.136-0.022-0.301,0.108-0.418
c-0.214,0.232-0.604,0.388-1.234,0.148c0.862,0.057-0.264-0.167-0.524-0.272c0.356-0.031,0.902-0.04,1.361,0.099
c-0.17-0.077-0.338-0.16-0.505-0.244c0.208-0.031,0.44-0.031,0.691,0c-0.446-0.1-1.417-0.074-1.808-0.273
c0.425-0.006,0.679-0.13,1.209,0.016c0.307,0.087,0.521-0.198,0.124-0.267c-0.366-0.059-0.732-0.062-1.07-0.077
c-0.502-0.021-1.029-0.232-1.569-0.342c0.478-0.365,1.932,0.416,2.394,0c-0.195,0.022-1.625-0.09-0.877-0.272
c-0.54,0.024-1.333,0.295-2.065-0.024c0.298-0.124,0.623-0.227,1.023-0.261c0.496,0.105,0.567,0.019,0.21-0.257
c-0.623,0.077-1.091,0.716-2.049,0.369c0.524-0.081,0.685-0.432,1.203-0.518c0.474-0.081,1.24,0.074,1.562-0.149
c-1.429-0.248-2.508,0.241-3.854,0.173c0.732-0.489,2.639-0.039,3.11-0.719c-0.691-0.14-0.979,0.258-1.603,0.217
c-0.36-0.024-0.742-0.059-1.126-0.096c-0.247,0.165-0.59,0.278-0.979,0.326c-0.31,0.012-0.694-0.233-0.418-0.336
c0.273-0.105,0.819-0.005,1.229-0.006c-0.281-0.027-0.563-0.055-0.836-0.08c0.155-0.255,0.775-0.54,1.101-0.695
c-0.48,0.059-0.989,0.316-1.321,0.496c-0.239-0.624-1.838-1.352-0.651-1.386c-0.366,0-0.744-0.081-0.626,0.248
c0.105,0.301,0.478,0.372,0.766,0.648c0.8,0.772-1.299,0.363-0.53,0.812c-0.229,0.051-0.499-0.009-0.812-0.173
c0.105,0.443,0.837,0.458,0.273,0.713c-0.208,0.093-0.468,0.146-0.75,0.171c-0.617,0.053-1.46-0.007-2.278-0.338
c0.781,0.452-0.18,0.281-0.509,0.055c-0.288-0.197-0.53-0.449-0.905-0.502c0.217-0.325,0.999-0.456,1.578-0.508
c0.905-0.086,1.299-0.332,1.764-0.753c-0.806,0.27-1.079,0.937-2.564,0.521c0.096-0.276,0.632-0.372,0.983-0.483
c0.369-0.118,0.458-0.431,0.945-0.459c-0.75-0.292-1.42,0.173-2.22-0.199c0.682-0.369,2.186,0.09,3.06-0.099
c-0.508-0.056-0.992-0.078-1.522-0.173c0.391-0.118,0.515-0.413,0.893-0.543c-0.13,0.105-0.35,0.112-0.654,0.025
c0.521-0.034,0.534-0.48-0.31-0.669c-0.555-0.127-0.834,0.056-1.513-0.149c0.561,0.146,0.542-0.202,0.871-0.273
c-0.921-0.124,0.13-0.381,0.48-0.27c-0.316-0.257-2.539-0.682-1.851-0.794c-1.011-0.074,0.57-0.074,0.632-0.099
c-0.583-0.04-0.632-0.344-0.254-0.372c-0.217-0.021-1.374-0.282-0.626-0.344c-0.264-0.003-0.552-0.046-0.862-0.124
c0.192-0.086,0.31-0.226,0.341-0.396c-0.753,0.189-1.5,0.44-2.57,0.322c0.676-0.251,1.689-0.217,2.4-0.446
c-0.22,0.049-0.487,0.015-0.81-0.1c0.326-0.034,0.741,0.013,0.921-0.148c-0.326,0.037-0.682,0.062-1.141-0.1
c0.487,0.059,1.082,0.031,1.188-0.22c-0.334,0.115-1.395-0.599-1.758-0.273c-0.322-0.341,0.264-0.26,0.406-0.397
c-0.986-0.505-2.021-0.316-3.042-0.648c0.031-0.074,0.068-0.146,0.108-0.217c-0.536,0.04-0.756-0.419-0.567-0.54
c-0.391-0.016-0.437,0.235-0.955,0.108c0.31-0.121,1.067-0.021,0.896-0.425c-0.196-0.456-1.231-0.282-0.53-0.496
c-0.36-0.009-0.608,0.081-0.974,0.068c-0.292-0.013-1.284-0.344-0.583-0.366c-0.602-0.115-0.868,0.093-1.476-0.028
c0.019-0.083,0.04-0.164,0.071-0.242c-0.195,0.041-0.413,0.068-0.651,0.081c-0.285-0.443-1.531-0.186-1.95-0.245
c-0.028-0.09-0.062-0.18-0.096-0.27c-0.989-0.146-1.771-0.074-2.747-0.202c-0.685-0.09-1.699-0.276-1.829,0.065
c-0.558-0.84-2.917,0.115-3.153,0.108c-0.459-0.443-0.949-0.335-1.321-0.251c-0.164,0.037,0.074,0.242-0.198,0.22
c-0.211-0.041-0.372-0.041-0.493-0.003c-0.118,0.108-0.974-0.053-0.955,0.031c0.068,0.307-0.297,0.434-1.066,0.167
c0.013-0.162,0.112-0.279,0.294-0.354c-1.184-0.344-1.336,0.511-2.074,0.257c-1.172-0.409-1.02-0.227-1.758-0.149
c0.478,0.034,1.101,0.369,1.628,0.487c-0.471,0.254-1.823-0.108-2.635-0.124c-0.375-0.006-0.732,0.003-1.153-0.043
c-0.289-0.031-1.185-0.121-0.577-0.239c-0.676-0.019-1.892-0.22-2.834-0.595c0.363-0.273,1.457-0.484,2.254-0.471
c0.422,0.006,1.95,0.248,1.367-0.289c-1.314,0.115-2.796,0.115-4.57-0.161c-0.099-0.347,0.471-0.536,1.004-0.54
c-1.652-0.359-1.107,1.11-3.32,0.217c0.239-0.012,0.458-0.041,0.651-0.081c-0.335,0.028-0.868-0.115-1.166-0.056
c-1.752-1.2,11.112-0.13,11.815-0.596c-0.989,0.236-2.545-0.031-3.773,0.006c-0.769,0.022-1.656,0.189-2.855-0.223
c0.942-0.18,2.151-0.214,3.308-0.242c0.862-0.022,2.468,0.403,3.128,0.27c-0.387,0.043-1.02-0.384-0.595-0.372
c0.394,0.009,1.008,0.019,1.144-0.143c-1.06-0.701-2.031-0.496-2.48-0.354c-0.679,0.214-1.312,0.428-2.443,0.323
c-0.344-0.031-1.476-0.515-1.364-0.245c0.115,0.282,0.031,0.313-0.406,0.341c-0.915,0.059-2.059-0.006-3.317-0.254
c0.562-0.1,1.46-0.043,1.628-0.381c-0.443,0.22-1.436,0.322-2.496,0.056c0.552-0.068,1.07-0.17,1.492-0.326
c-1.299-0.077-1.907,0.372-3.401,0.137c0.14-0.078,0.292-0.149,0.45-0.217c-0.484,0.053-1.219,0.053-1.95-0.108
c-0.766-0.167-1.609-0.505-2.322-0.595c0.394,0.065,1.705-0.251,0.546-0.406c0.483-0.031,0.363-0.391,0.837-0.428
c0.515-0.041,1.091-0.031,1.557-0.105c0.71-0.118,1.578-0.013,2.344-0.065c0.375-0.028,0.45-0.273,0.818-0.326
C33.521,84,34.023,84,34.432,83.938c0.502-0.078,1.131-0.065,1.671-0.115c0.149-0.013,1.423-0.103,0.223-0.224
c0.782-0.254,2.019-0.195,3.026-0.294c0.769-0.074,1.299-0.242,2.254-0.183c1.714,0.108,2.576-0.137,3.789-0.381
c0.502-0.103,1.107-0.205,1.237-0.518c0.096-0.229-0.4-0.611-0.319-0.85c0.108-0.654,1.63-0.663,2.774-0.626
c-1.813-0.232-2.799,0.326-4.393,0.285c-0.669-0.016-1.445-0.183-2.034-0.124c-0.562,0.056-1.495-0.304-1.984-0.264
c-0.478-0.651,0.707-0.759,1.215-0.874c0.939-0.211,1.885-0.406,2.905-0.564c1.218-0.189,2.009-0.664,3.271-0.828
c0.443-0.059,0.927-0.5,1.78-0.133c0.599,0.257,1.197,0.617,1.789,0.846c-0.434-0.257-1.488-0.741-1.358-0.983
c0.18-0.329,1.057-0.217,1.727-0.18c1.29,0.071,3.181,0.502,3.919,0.115c-0.226-0.112-0.456-0.223-0.682-0.338
c-0.006-0.375,0.735-0.282,0.977-0.505c-1.885-0.211-0.899-1.014-0.323-1.085c0.933-0.108,2.146,0.022,3.274,0.084
c0.456,0.025,1.026-0.103,1.724,0.273c0.645,0.347,0.877,0.425,1.404,0.449c1.188,0.053,2.834,0.583,3.91,0.549
c-0.75-0.139-1.584-0.391-2.35-0.545c-0.502-0.103-0.936-0.037-1.491-0.22c-0.512-0.167-1.107-0.484-1.59-0.586
c-0.524-0.112-0.868-0.037-1.417-0.17c1.625-0.388,3.764-0.357,5.714-0.484c0.726-0.046,1.553,0.046,2.254-0.025
c0.285-0.028,2.453-0.257,1.364-0.201c0.406-0.034,3.808-0.534,4.502,0.511c0.44,0.67-0.552,0.744-0.326,1.314
c0.719,0.1,1.098-0.474,1.578-0.614c0.341-0.096,0.18-0.487,0.502-0.577c0.02-0.005,0.053-0.002,0.077-0.006
c-0.391-0.095-0.782-0.217-1.159-0.394c0.09-0.48,1.156-0.478,2.22-0.037c1.07,0.449,1.609,0.012,2.729,0.545
c-0.38,0.046-0.896,0.1-1.48,0.114c0.773,0.073,1.555,0.147,2.367,0.292c0.974,0.177,2.136,0.639,2.799,0.44
c-1.11-0.381-2.009-0.394-3.131-0.778c1.396,0.223,2.865,0.363,3.714,0.034c-0.735-0.108-1.727-0.208-2.583-0.521
c-0.53-0.192-0.775-0.607-1.355-0.831c1.268-0.152,2.691-0.217,4.347,0.034c1.234,0.186,2.669,0.319,3.975,0.642
c1.386,0.347,2.437,0.223,3.835,0.577c1.681,0.428,3.138,0.66,4.25,0.369c-0.245-0.009-1.358-0.037-0.676-0.304
c-0.279-0.021-0.558-0.043-0.84-0.068c0.162-0.229,0.642-0.195,0.989-0.27c-1.014-0.065-0.319-0.49-0.078-0.533
c0.403-0.078,0.943-0.013,1.46,0.046c1.243,0.143,2.505,0.248,3.575,0.183c-1.268-0.202-2.322-0.109-3.59-0.304
c0.871-0.226-0.54-0.257-0.803-0.294c-0.372-0.056-0.75-0.115-1.129-0.177c-0.75-0.121-1.513-0.26-2.3-0.44
c0.549-0.335,1.833-0.093,2.722-0.105c0.986-0.013,1.975-0.022,2.942-0.053c0.787-0.028,1.671-0.087,2.639,0.121
c0.328,0.068,1.605,1.12,1.203,0.273c-0.184-0.074-0.369-0.14-0.553-0.201c0.553-0.298,2.019-0.01,2.979,0.205
c0.484,0.105,1.058,0.242,1.575,0.515c0.307,0.161,0.797-0.217-0.08-0.316c0.812-0.326-1.768-0.549-2.09-0.558
c-0.952-0.025-1.973-0.14-2.93-0.174c-2.385-0.083-4.664-0.04-7.072-0.146c1.193,0.195-0.208,0.155-0.357,0.158
c-0.425,0.012-0.865,0.009-1.299,0.009c-0.694,0.006-1.429,0.037-2.332-0.236c0.45-0.136,1.193,0.041,1.798,0.068
c-0.542-0.17-1.029-0.223-1.436-0.202c0.607-0.416,2.031-0.096,3.138-0.009c0.93,0.068,1.82,0.093,2.759,0.177
c-1.128-0.198-2.139-0.22-3.333-0.505c1.188,0.146,1.987-0.127,3.119-0.043c1.091,0.081,2.127,0.093,3.19,0.143
c-0.828-0.133-1.503-0.034-2.399-0.27c0.759,0,1.615,0.103,2.279,0c-0.341-0.031-0.67-0.041-0.98-0.034
c0.45-0.229,1.315-0.192,2.043-0.143c0.304,0.021,2.347,0.288,1.33,0.446c1.182-0.272,3.141,0.341,4.526,0.301
c0.607-0.019,1.193-0.037,1.96,0.189c0.492,0.146,1.345,0.449,1.494,0.152c-0.394,0.083-1.005-0.323-1.473-0.372
c0.401-0.112,0.879-0.146,1.375-0.157c-0.121-0.016-0.232-0.019-0.361-0.045c1.327-0.115,2.958,0.103,4.12-0.205
c-0.468,0.056-3.969,0.174-4.384-0.642c1.069,0.102,1.879-0.087,2.803-0.158c0.881-0.068,2.127,0.403,2.974,0.294
c-0.356-0.152-0.691-0.242-1.008-0.27c1.058-0.425,2.933-0.065,4.328-0.121c0.428-0.016,1.141-0.177,1.562-0.115
c0.49,0.071,0.995,0.409,1.398,0.236c-0.205-0.068-0.406-0.137-0.611-0.205c2.06-0.18,4.366-0.021,6.595,0.034
c1.116,0.031,2.168-0.034,3.262-0.034c0.536,0,1.169,0.211,1.718,0.239c0.499,0.025,0.821-0.173,1.407,0
c-0.285-0.012-0.558,0-0.815,0.034c0.268,0.046,2.44,0.121,1.219,0c2.047,0.059,4.037-0.356,6.402,0.438
c-2.982,0.387-6.321,0.239-9.556,0.239c-1.463,0-2.877,0.074-4.244,0.201c-1.354,0.127-2.97-0.133-4.412-0.133
c0.624,0.062,1.185,0.009,1.895,0.27c-2.372,0-4.731,0.037-6.889,0.304c1.004,0.083-0.04,0.264-0.224,0.304
c1.073,0.006,1.848-0.356,2.964-0.341c0.165,0,1.216,0.242,1.287,0.167c0.267-0.273,0.651-0.264,1.185-0.273
c2.183-0.043,4.188-0.093,6.253-0.279c2.385-0.214,5.008,0.016,7.531,0.016c2.03,0,3.983-0.664,6.399,0.167
c-0.788,0.26-1.606,0.49-2.555,0.54c1.634,0.046,2.861-0.716,4.712-0.27c-0.09,0.105-0.186,0.208-0.288,0.304
c1.86-0.202,4.071-0.425,6.337,0.372c-0.523,0.334-1.51,0.304-2.16,0.499c-0.899,0.27-1.963,0.229-3.032,0.235
c-2.195,0.006-4.186,0.344-6.378,0.351c-2.267,0.009-4.632-0.155-6.803,0.006c-0.964,0.071-2.031-0.018-3.087-0.091
c0.244,0.057,0.49,0.132,0.74,0.252c-1.352,0.335-3.004,0.282-4.585,0.304c-0.71,0.012-2.744-0.074-2.469,0.741
c2.999-0.41,6.204-0.537,9.302-0.837c1.621-0.158,3.423-0.093,5.168-0.062c0.965,0.016,2.81-0.338,3.96,0.257
c-0.06,0.778-1.675,0.614-2.453,0.695c-0.979,0.099-2.402-0.031-3.075,0.421c0.288,0.009,0.592,0.065,0.911,0.167
c-0.238,0.031-0.489,0.041-0.753,0.034c1.299,0.183,2.434-0.285,3.581-0.381c1.165-0.093,2.191-0.344,3.407-0.36
c1.008-0.012,0.97-0.716,1.808-0.862c0.731-0.127,1.634-0.102,2.421-0.149c0.375-0.021,2.05,0.016,2.013,0.741
c-0.025,0.459-0.577,0.738-0.955,0.936c-0.651,0.344-1.485,0.478-2.155,0.8c-0.827,0.396-1.581,0.877-2.561,1.101
c1.048-0.031,0.006,0.279-0.158,0.372c0.881-0.229,1.488-0.524,2.248-0.875c0.713-0.326,1.845-0.425,2.657-0.679
c1.829-0.571,3.674-1.058,5.751-1.321c-0.372,0.071-0.741,0.22-0.707,0.607c0.75-0.053,1.352-0.335,2.121-0.359
c0.492-0.019,1.014,0.158,1.528,0.226c0.902,0.121,2.067,0.022,2.666-0.338c-0.998,0.065,0.232-0.257,0.636-0.236
c-0.14-0.111-0.276-0.226-0.412-0.338c2.285-0.186,4.585-0.356,7.034-0.167C168.128,76.345,170.205,76.751,172.189,76.894z"/>
<circle fill="none" cx="99.197" cy="100.656" r="82.295"/>
</svg>
</body>
</html>

View file

@ -1,848 +0,0 @@
// Uncomment this to turn on fatline clipping in paper.js too
options.fatline = true;
paper.install(window);
/**
* http://stackoverflow.com/questions/6875625/does-javascript-provide-a-high-resolution-timer
*/
if (window.performance && window.performance.now) {
console.log("Using high performance timer");
getTimestamp = function() { return window.performance.now(); };
} else {
if (window.performance && window.performance.webkitNow) {
console.log("Using webkit high performance timer");
getTimestamp = function() { return window.performance.webkitNow(); };
} else {
console.log("Using low performance timer");
getTimestamp = function() { return new Date().getTime(); };
}
}
function runTests() {
var caption, pathA, pathB, group, testdata = [], randomtestdata = [], testQueued = 0, testExecuted = 0;
var container = document.getElementById('container');
function runTest(testName, handler) {
var caption = document.createElement('h3');
var canvas = document.createElement('canvas');
caption.appendChild(document.createTextNode(testName));
container.appendChild(caption);
container.appendChild(canvas);
++testQueued;
setTimeout(function() {
console.log('\n' + testName);
paper.setup(canvas);
var paths = handler();
var success = testIntersections(paths[0], paths[1], caption, testName, testdata);
testExecuted++;
if (testExecuted === testQueued) {
plotData();
}
}, 0);
return caption;
}
var caption = document.createElement('h3');
caption.appendChild(document.createTextNode("Randomised tests (may take a while...)"));
container.appendChild(caption);
var canvas = document.createElement('CANVAS');
container.appendChild(canvas);
paper.setup(canvas);
doRandomTests(randomtestdata);
window.d = randomtestdata;
container.removeChild(canvas);
runTest('random', function() {
pathA = getRandomPath(20);
pathB = getRandomPath(20);
return [pathA, pathB];
});
runTest('failcase 1', function() {
group = paper.project.importSVG(document.getElementById('svgrandom1'));
pathA = group.children[0];
pathB = group.children[1];
pathA.style = pathB.style = null;
return [pathA, pathB];
});
runTest('failcase 2', function() {
group = paper.project.importSVG(document.getElementById('svgrandom2'));
pathA = group.children[0];
pathB = group.children[1];
pathA.style = pathB.style = null;
return [pathA, pathB];
});
runTest('failcase 3', function() {
group = paper.project.importSVG(document.getElementById('svgrandom3'));
pathA = group.children[0];
pathB = group.children[1];
pathA.style = pathB.style = null;
return [pathA, pathB];
});
runTest('Overlapping circles', function() {
pathA = new Path.Circle(new Point(80, 110), 50);
pathB = new Path.Circle(new Point(150, 110), 70);
return [pathA, pathB];
});
runTest('Polygon and square', function() {
pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80);
pathB = new Path.Rectangle(new Point(100, 80), [80, 80]);
return [pathA, pathB];
});
runTest('Circle and square (overlaps exactly on existing segments)', function() {
pathA = new Path.Circle(new Point(110, 110), 80);
pathB = new Path.Rectangle(new Point(110, 110), [80, 80]);
return [pathA, pathB];
});
runTest('Circle and square (existing segments overlaps on curves)', function() {
pathA = new Path.Circle(new Point(110, 110), 80);
pathB = new Path.Rectangle(new Point(110, 110), [100, 100]);
return [pathA, pathB];
});
runTest('Square and square (one segment overlaps on a line)', function() {
pathA = new Path.Rectangle(new Point(80, 125), [50, 50]);
pathA.rotate(45);
pathB = new Path.Rectangle(new Point(pathA.segments[2].point.x, 110), [80, 80]);
return [pathA, pathB];
});
runTest('Rectangle and rectangle (overlaps exactly on existing curves)', function() {
pathA = new Path.Rectangle(new Point(30.5, 50.5), [100, 150]);
pathB = new Path.Rectangle(new Point(130.5, 60.5), [100, 150]);
return [pathA, pathB];
});
runTest('Overlapping stars 1', function() {
pathA = new Path.Star(new Point(80, 110), 10, 20, 80);
pathB = new Path.Star(new Point(120, 110), 10, 30, 100);
return [pathA, pathB];
});
runTest('Overlapping stars 2', function() {
pathA = new Path.Star(new Point(110, 110), 20, 20, 80);
pathB = new Path.Star(new Point(110, 110), 6, 30, 100);
return [pathA, pathB];
});
runTest('Circle and banana (multiple intersections within same curve segment)', function() {
pathA = new Path.Circle(new Point(80, 110), 80);
pathB = new Path.Circle(new Point(130, 110), 80);
pathB.segments[3].point = pathB.segments[3].point.add([ 0, -120 ]);
return [pathA, pathB];
});
runTest('Maximum possible intersections between 2 cubic bezier curve segments - 9', function() {
pathA = new Path();
pathA.add(new Segment([173, 44], [-281, 268], [-86, 152]));
pathA.add(new Segment([47, 93], [-89, 100], [240, -239]));
pathA.closed = true;
pathB = pathA.clone();
pathB.rotate(-90);
pathA.translate([-10,0]);
pathB.translate([10,0]);
return [pathA, pathB];
});
runTest('SVG gears', function() {
group = paper.project.importSVG(document.getElementById('svggears'));
pathA = group.children[0];
pathB = group.children[1];
return [pathA, pathB];
});
runTest('Glyphs imported from SVG', function() {
group = paper.project.importSVG(document.getElementById('glyphsys'));
pathA = group.children[0];
pathB = group.children[1];
return [pathA, pathB];
});
runTest('CompoundPaths 1', function() {
group = paper.project.importSVG(document.getElementById('glyphsacirc'));
pathA = group.children[0];
pathB = group.children[1];
return [pathA, pathB];
});
runTest('CompoundPaths 2 - holes', function() {
group = paper.project.importSVG(document.getElementById('glyphsacirc'));
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([110, 110], 30);
pathB.addChild(npath);
return [pathA, pathB];
});
runTest('CompoundPaths 3 !', function() {
group = paper.project.importSVG(document.getElementById('svggreenland'));
pathA = group.children[0];
pathB = group.children[1];
pathB.scale(0.5, 1).translate([25.5, 0]);
// pathA.scale(2);
// pathB.scale(2);
return [pathA, pathB];
});
runTest('CompoundPaths 4 - holes and islands 1', function() {
group = paper.project.importSVG(document.getElementById('glyphsacirc'));
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([40, 80], 20);
pathB.addChild(npath);
return [pathA, pathB];
});
runTest('CompoundPaths 5 - holes and islands 2', function() {
group = paper.project.importSVG(document.getElementById('glyphsacirc'));
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([40, 80], 20);
pathB.addChild(npath);
npath = new Path.Circle([120, 110], 30);
pathB.addChild(npath);
return [pathA, pathB];
});
runTest('CompoundPaths 6 - holes and islands 3', function() {
group = paper.project.importSVG(document.getElementById('glyphsacirc'));
pathA = group.children[0];
pathB = new CompoundPath();
var npath = new Path.Circle([110, 110], 100);
pathB.addChild(npath);
npath = new Path.Circle([110, 110], 60);
pathB.addChild(npath);
npath = new Path.Circle([110, 110], 30);
pathB.addChild(npath);
return [pathA, pathB];
});
runTest('CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)', function() {
pathA = new Path.Rectangle(new Point(50.5, 50.5), [100, 120]);
pathB = new CompoundPath();
pathB.addChild(new Path.Rectangle(new Point(140.5, 30.5), [100, 150]));
pathB.addChild(new Path.Rectangle(new Point(150.5, 65.5), [50, 100]));
// pathB = new Path.Rectangle(new Point(150.5, 80.5), [80, 80]);
return [pathA, pathB];
});
// Plot the run times
function plotData() {
prepareTest('Results - Random tests (Intersections/Curve vs Time)', container, 'big2');
plotDataRandom(randomtestdata);
prepareTest('Results - Boolean tests (Time taken per test)', container, 'big');
var x = 80.5, y = 15.5, width = 500, height = 190, i, txt, ny,
yy = y + height, xx = x + width;
var ppaper = new Path(),
pfat = new Path();
var max = testdata.reduce(function(a, b) {
return Math.max(a, b.paperTime + b.fatTime);
}, 0) + 20;
var vscale = height / max, hscale = width / testdata.length;
var caxes = '#999', ctxt = '#222', ctxt2 = '#555', cpaper = '#268BD2', cpaperfill ='#B5E1FF',
cfat = '#D33682', cfatfill = '#FFADD4';
new Path.Line(x, yy, xx, yy).strokeColor = caxes;
new Path.Line(x, yy, x, y).strokeColor = caxes;
for (i = 0; i < 10 ; i++) {
ny = yy - vscale * max * i / 10;
new Path.Line(x, ny, x-5, ny).strokeColor = caxes;
txt = new PointText([x-10, ny]);
txt.justification = 'right';
txt.fillColor = (i%2)? ctxt: ctxt2;
txt.content = (max * i / 10).toFixed(1) + ((!i)? ' ms' : '');
}
ppaper.add(x, yy);
pfat.add(x, yy);
var vx = x, clr = ctxt;
var coords = [], avgPaper = 0, avgFat = 0,
maxSpeedup = -Infinity, minSpeedup = Infinity, avgSpeedup = 0;
testdata.map(function(data) {
avgPaper += data.paperTime;
ny = yy - (data.paperTime /*+ data.fatTime */) * vscale;
ppaper.add(vx, ny);
var np = new Point(vx, ny);
np._data = data;
np._datatype = 'paper';
coords.push(np);
avgFat += data.fatTime;
ny = yy - (data.fatTime) * vscale;
pfat.add(vx, ny);
np = new Point(vx, ny);
np._data = data;
np._datatype = 'fat';
coords.push(np);
var speedup = data.paperTime / data.fatTime;
if (speedup > maxSpeedup) maxSpeedup = speedup;
if (speedup < minSpeedup) minSpeedup = speedup;
avgSpeedup += speedup;
new Path.Line(vx, yy, vx, yy + 5).strokeColor = caxes;
txt = new PointText([vx, yy+18]);
txt.justification = 'left';
txt.fillColor = clr;
txt.content = data.name;
txt.rotate(30, new Point(vx, yy+10));
if (!data.success) {
var p = new Path.Line(vx, y, vx, yy);
p.strokeWidth = 5;
p.strokeColor = '#f00';
}
clr = (clr === ctxt)? ctxt2 : ctxt;
vx += hscale;
});
ppaper.strokeWidth = 2;
ppaper.strokeColor = cpaper;
ppaper.add(vx-hscale, yy);
ppaper.closed = true;
ppaper.fillColor = cpaperfill;
ppaper.opacity = 0.75;
pfat.strokeWidth = 2;
pfat.strokeColor = cfat;
pfat.add(vx-hscale, yy);
pfat.closed = true;
pfat.fillColor = cfatfill;
pfat.opacity = 0.75;
avgPaper/= testdata.length;
avgFat/= testdata.length;
avgSpeedup = Math.round(avgSpeedup / testdata.length);
maxSpeedup = Math.round(maxSpeedup);
minSpeedup = Math.round(minSpeedup);
ny = Math.round(yy - avgPaper * vscale) + 0.5;
new Path.Line(x, ny, xx, ny).strokeColor = cpaper;
txt = new PointText([xx, ny]);
txt.justification = 'right';
txt.fillColor = cpaper;
txt.content = avgPaper.toFixed(1);
ny = Math.round(yy - avgFat * vscale) + 0.5;
new Path.Line(x, ny, xx, ny).strokeColor = cfat;
txt = new PointText([xx, ny]);
txt.justification = 'right';
txt.fillColor = cfat;
txt.content = avgFat.toFixed(1);
txt = new PointText([610, 75]);
txt.justification = 'center';
txt.fillColor = '#000';
txt.content = 'fatline vs subdiv';
new Path.Rectangle([600, 90], [20, 100]).style = { fillColor: '#ccc', strokeColor: '#000' };
ny = 190 - (avgSpeedup - minSpeedup) * 100.0 / (maxSpeedup - minSpeedup);
new Path.Line([600, ny], [620, ny]).style = { strokeWidth: 2, strokeColor: '#000' };
txt = new PointText([630, 95]);
txt.fillColor = '#000';
txt.content = maxSpeedup;
txt = new PointText([630, 195]);
txt.fillColor = '#000';
txt.content = minSpeedup;
txt = new PointText([630, ny+5]);
txt.fillColor = '#000';
txt.content = avgSpeedup + ' times';
view.draw();
var tool = new Tool();
tool.onMouseMove = function(e) {
var len = coords.length;
var data = null, dist = Infinity, dst, pnt = null, type = 'paper';
while (len--) {
dst = e.point.getDistance(coords[len], true);
if (dst < dist) {
pnt = coords[len];
data = coords[len]._data;
type = coords[len]._datatype;
dist = dst;
}
}
if (dist > 500) { return; }
if (pnt && data) {
var p = new Path.Line(pnt.x+0.5, y, pnt.x+0.5, yy);
p.strokeColor = '#000';
p.removeOnMove();
p = new Path.Circle(pnt, 3);
p.fillColor = (type === 'fat')? '#D33682' :'#268BD2';
p.removeOnMove();
var txt = new PointText([ 500, 20 ]);
txt.content = 'subdiv : ' + data.paperTime.toFixed(1) + ' ms';
txt.fillColor = '#222';
txt.removeOnMove();
txt = new PointText([ 500, 36 ]);
txt.content = 'fatline : ' + data.fatTime.toFixed(1) + ' ms';
txt.fillColor = '#222';
txt.removeOnMove();
}
};
}
function prepareTest(testName, parentNode, _big) {
console.log('\n' + testName);
var caption = document.createElement('h3');
caption.appendChild(document.createTextNode(testName));
var canvas = document.createElement('CANVAS');
canvas.className += ' ' + _big;
parentNode.appendChild(caption);
parentNode.appendChild(canvas);
paper.setup(canvas);
return caption;
}
}
var pathStyleIx = {
fillColor: new Color(0.8, 0, 0),
strokeColor: new Color(0, 0, 0)
};
var pathStyleNormal = {
strokeColor: new Color(0, 0, 0),
// fillColor: new Color(0, 0, 0, 0.1),
strokeWidth: 1
};
var pathStyleBoolean = {
strokeColor: new Color(0,0,0,0.4),
fillColor: new Color(0, 0, 0, 0.0),
strokeWidth: 1
};
// Better if path1 and path2 fit nicely inside a 200x200 pixels rect
function testIntersections(path1, path2, caption, testname, testdata, nomark) {
var i, l, maxCount = 10, count = maxCount, st, t1, t2,
ixsPaper, ixsFatline, success = false, maxdiff = -Infinity;
try {
path1.style = path2.style = pathStyleNormal;
if (!nomark) console.time('paperjs x ' + maxCount);
st = getTimestamp();
while (count--) {
ixsPaper = path1.getIntersections(path2);
}
t1 = (getTimestamp() - st) / maxCount;
if (!nomark) console.timeEnd('paperjs x ' + maxCount);
count = maxCount;
if (!nomark) console.time('fatline x ' + maxCount);
st = getTimestamp();
while (count--) {
ixsFatline = path1.getIntersections2(path2);
}
t2 = (getTimestamp() - st) / maxCount;
if (!nomark) console.timeEnd('fatline x ' + maxCount);
var found = 0, tol = 0.1;
if (ixsFatline.length === ixsPaper.length) {
for (i=0, l=ixsFatline.length; i<l; i++) {
pa = ixsFatline[i].point;
for (j = 0; j < ixsPaper.length; j++) {
if (!ixsPaper[j]._found) {
pb = ixsPaper[j].point;
if (Math.abs(pa.x - pb.x) < tol && Math.abs(pa.y - pb.y) < tol) {
++found;
ixsPaper[j]._found = true;
}
}
}
}
}
success = ixsPaper.length === found;
window.pap = ixsPaper;
window.fat = ixsFatline;
if (!nomark) {
markIntersections(ixsPaper, '#00f', 'paperjs');
markIntersections(ixsFatline, '#f00', 'fatline');
}
} catch(e) {
console.timeEnd('paperjs x ' + maxCount);
console.timeEnd('fatline x ' + maxCount);
t1 = t2 = 0;
console.error(e.name + ": " + e.message);
if (caption) { caption.className += ' error'; }
} finally {
view.draw();
testdata.push({
name: testname,
ratio: ixsFatline.length / (path1.curves.length + path2.curves.length),
paperTime: t1,
fatTime: t2,
success: success
});
console.log(ixsPaper.length, found);
if (!success) {
var ser = new XMLSerializer();
console.log('failcase:', ser.serializeToString(path1.exportSVG()),
ser.serializeToString(path2.exportSVG()));
}
}
return success;
}
function doRandomTests(testdata) {
var p1 = new Path(), p2 = new Path(), ixspaper, ixsfat;
var seg = 5, maxseg = 30, maxiter = 10;
var i, j, halfseg = (maxseg / 2) | 0;
var p, hi, ho, st, t1, t2, success;
while (seg <= maxseg) {
for (i = 0; i < maxiter; i++) {
p1.removeSegments();
p2.removeSegments();
for (j = 0; j < seg; j++) {
p = new Point.random().multiply([100, 100]);
v = new Point.random().multiply([20, 20]);
p1.add(new Segment(p, v, v.multiply(-1)));
p1.closed = true;
p = new Point.random().multiply([100, 100]);
v = new Point.random().multiply([20, 20]);
p2.add(new Segment(p, v, v.multiply(-1)));
p2.closed = true;
}
st = getTimestamp();
ixspaper = p1.getIntersections(p2);
t1 = (getTimestamp() - st);
st = getTimestamp();
ixsfat = p1.getIntersections2(p2);
t2 = (getTimestamp() - st);
// Check against paperjs output
// tol - tolerence for computed points with in 1/10 th of a pixel
var found = 0, tol = 0.1;
if (ixsfat.length === ixspaper.length) {
for (i=0, l=ixsfat.length; i<l; i++) {
pa = ixsfat[i].point;
for (j = 0; j < ixspaper.length; j++) {
if (!ixspaper[j]._found) {
pb = ixspaper[j].point;
if (Math.abs(pa.x - pb.x) < tol && Math.abs(pa.y - pb.y) < tol) {
++found;
ixspaper[j]._found = true;
}
}
}
}
}
success = ixspaper.length === found;
testdata.push({
curves: p1.curves.length + p2.curves.length,
ixsfat: ixsfat.length,
ixspaper: ixspaper.length,
ratio: ixsfat.length / (seg),
paperTime: t1,
fatTime: t2,
speedup: t1 / t2,
success: success
});
}
++seg;
if (seg === halfseg) maxiter = (maxiter / 2) | 0;
}
}
function getRandomPath(seg) {
seg = seg || 3;
var p = new Path(), pnt, hi, ho, v;
for (j = 0; j < seg; j++) {
pnt = new Point.random().multiply([130, 130]);
v = new Point.random().multiply([20, 20]);
p.add(new Segment(pnt, v, v.multiply(-1)));
p.closed = true;
}
return p;
}
function markIntersections(ixs, c, txt) {
for (i = 0, len = ixs.length; i < len; i++) {
// force calculate the parameter for debugging
var a = ixs[i].parameter;
// markPoint(ixs[i].point, ixs[i].parameter);
markPoint(ixs[i].point, ' ', c, null, false);
// console.log(txt , ixs[i].parameter)
}
}
// ==============================================================
// On screen debug helpers
function markPoint(pnt, t, c, tc, remove) {
if (!pnt) return;
c = c || '#000';
if (remove === undefined) { remove = true; }
var cir = new Path.Circle(pnt, 2);
cir.fillColor = c;
cir.strokeColor = tc;
if (t !== undefined || t !== null) {
var text = new PointText(pnt.add([0, -3]));
text.justification = 'center';
text.fillColor = c;
text.content = t;
if (remove) {
text.removeOnMove();
}
}
if (remove) {
cir.removeOnMove();
}
}
function markCurve(crv, c, flag) {
if (!crv) return;
c = c || '#000';
if (flag) {
if (window.__p1) window.__p1.remove();
window.__p1 = new Path(
new Segment([crv[0], crv[1]], null, [crv[2] - crv[0], crv[3] - crv[1]]),
new Segment([crv[6], crv[7]], [crv[4] - crv[6], crv[5] - crv[7]], null)
);
window.__p1.strokeColor = c;
// window.__p1.fullySelected = true;
} else {
if (window.__p2) window.__p2.remove();
window.__p2 = new Path(
new Segment([crv[0], crv[1]], null, [crv[2] - crv[0], crv[3] - crv[1]]),
new Segment([crv[6], crv[7]], [crv[4] - crv[6], crv[5] - crv[7]], null)
);
window.__p2.strokeColor = c;
// window.__p2.fullySelected = true;
}
view.draw();
}
function annotatePath(path, t, c, tc, remove) {
if (!path) return;
var crvs = path.curves;
for (i = crvs.length - 1; i >= 0; i--) {
annotateCurve(crvs[i], t, c, tc, remove);
}
var segs = path.segments;
for (i = segs.length - 1; i >= 0; i--) {
annotateSegment(segs[i], t, c, tc, remove, true);
}
}
function annotateSegment(s, t, c, tc, remove, skipCurves) {
if (!s) return;
c = c || '#000';
tc = tc || '#ccc';
t = t || s.index;
if (remove === undefined) { remove = true; }
var crv = s.curve;
var t1 = crv.getNormal(0).normalize(10);
var p = s.point.clone().add(t1);
var cir = new Path.Circle(s.point, 2);
cir.fillColor = c;
cir.strokeColor = tc;
var text = new PointText(p);
text.justification = 'center';
text.fillColor = c;
text.content = t;
if (remove) {
cir.removeOnMove();
text.removeOnMove();
}
if (!skipCurves) {
annotateCurve(s.curveIn, null, c, tc, remove);
annotateCurve(s.curveOut, null, c, tc, remove);
}
}
function annotateCurve(crv, t, c, tc, remove) {
if (!crv) return;
c = c || '#000';
tc = tc || '#ccc';
t = t || crv.index;
if (remove === undefined) { remove = true; }
var p = crv.getPoint(0.57);
var t1 = crv.getTangent(0.57).normalize(-10);
var p2 = p.clone().add(t1);
var l = new Path.Line(p, p2).rotate(30, p);
var l2 = new Path.Line(p, p2).rotate(-30, p);
p = crv.getPoint(0.43);
var cir = new Path.Circle(p, 8);
var text = new PointText(p.subtract([0, -4]));
text.justification = 'center';
text.fillColor = tc;
text.content = t;
l.strokeColor = l2.strokeColor = c;
cir.fillColor = c;
if (remove) {
l.removeOnMove();
l2.removeOnMove();
cir.removeOnMove();
text.removeOnMove();
}
}
// Plot the run times
function plotDataRandom(testdata) {
var x = 80.5, y = 15.5, width = 500, height = 190, i, txt, ny,
yy = y + height, xx = x + width;
var ppaper = new Path(), pfat = new Path();
var max = testdata.reduce(function(a, b) { return Math.max(a, b.paperTime, b.fatTime); }, 0) + 20;
testdata.sort(function(a,b) { return a.ratio - b.ratio; });
var vscale = height / max, hscale = width / testdata.length;
var caxes = '#999', ctxt = '#222', cpaper = '#268BD2', cfat = '#D33682';
new Path.Line(x, yy, xx, yy).strokeColor = caxes;
new Path.Line(x, yy, x, y).strokeColor = caxes;
for (i = 0; i < 10 ; i++) {
ny = yy - vscale * max * i / 10;
new Path.Line(x, ny, x-5, ny).strokeColor = caxes;
txt = new PointText([x-10, ny]);
txt.justification = 'right';
txt.fillColor = ctxt;
txt.content = (max * i / 10).toFixed(1) + ((!i)? ' ms' : '');
}
txt = new PointText([xx + 20, yy + 18 ]);
txt.justification = 'left';
txt.fillColor = ctxt;
txt.content = 'ixs / curve';
txt = new PointText([xx + 20, yy + 40]);
txt.justification = 'left';
txt.fillColor = '#999';
txt.content = '(Total Curves)';
var vx = x, step = 15, count = 0;
var avgPaper = 0, avgFat = 0;
testdata.map(function(data) {
avgPaper += data.paperTime;
ny = yy - (data.paperTime /* + data.fatTime*/) * vscale;
ppaper.add(vx, ny);
avgFat += data.fatTime;
ny = yy - (data.fatTime) * vscale;
pfat.add(vx, ny);
new Path.Line(vx, yy, vx, yy + 5 + ((count%2)? step:0)).strokeColor = caxes;
txt = new PointText([vx, yy+18 + ((count%2)? step:0) ]);
txt.justification = 'center';
txt.fillColor = ctxt;
txt.content = data.ratio.toFixed(1);
txt = new PointText([vx -5, yy+40 ]);
txt.justification = 'left';
txt.fillColor = '#999';
txt.content = data.curves;
txt.rotate(90, [vx-5, yy+40 ]);
if (!data.success) {
var p = new Path.Line(vx, y, vx, yy);
p.strokeWidth = 5;
p.strokeColor = '#f00';
}
++count;
vx += hscale;
});
// ppaper.smooth();
ppaper.strokeWidth = 2;
ppaper.strokeColor = cpaper;
ppaper.opacity = 0.75;
// pfat.smooth();
pfat.strokeWidth = 2;
pfat.strokeColor = cfat;
pfat.opacity = 0.75;
avgPaper/= testdata.length;
avgFat/= testdata.length;
ny = Math.round(yy - avgPaper * vscale) + 0.5;
new Path.Line(x, ny, xx, ny).strokeColor = cpaper;
txt = new PointText([xx, ny]);
txt.justification = 'right';
txt.fillColor = cpaper;
txt.content = avgPaper.toFixed(1);
ny = Math.round(yy - avgFat * vscale) + 0.5;
new Path.Line(x, ny, xx, ny).strokeColor = cfat;
txt = new PointText([xx, ny]);
txt.justification = 'right';
txt.fillColor = cfat;
txt.content = avgFat.toFixed(1);
view.draw();
}
function drawFatline(v1) {
function signum(num) {
return (num > 0)? 1 : (num < 0)? -1 : 0;
}
var l = new Line([v1[0], v1[1]], [v1[6], v1[7]], false);
var p1 = new Point(v1[2], v1[3]), p2 = new Point(v1[4], v1[5]);
var d1 = l.getSide(p1) * l.getDistance(p1) || 0;
var d2 = l.getSide(p2) * l.getDistance(p2) || 0;
var dmin, dmax;
if (d1 * d2 > 0) {
// 3/4 * min{0, d1, d2}
dmin = 0.75 * Math.min(0, d1, d2);
dmax = 0.75 * Math.max(0, d1, d2);
} else {
// 4/9 * min{0, d1, d2}
dmin = 4 * Math.min(0, d1, d2) / 9.0;
dmax = 4 * Math.max(0, d1, d2) / 9.0;
}
var ll = new Path.Line(v1[0], v1[1], v1[6], v1[7]);
window.__p3.push(ll);
window.__p3[window.__p3.length-1].strokeColor = new Color(0,0,0.9, 0.8);
var lp1 = ll.segments[0].point;
var lp2 = ll.segments[1].point;
var pm = l.vector, pm1 = pm.rotate(signum(dmin) * -90), pm2 = pm.rotate(signum(dmax) * -90);
var p11 = lp1.add(pm1.normalize(Math.abs(dmin)));
var p12 = lp2.add(pm1.normalize(Math.abs(dmin)));
var p21 = lp1.add(pm2.normalize(Math.abs(dmax)));
var p22 = lp2.add(pm2.normalize(Math.abs(dmax)));
window.__p3.push(new Path.Line(p11, p12));
window.__p3[window.__p3.length-1].strokeColor = new Color(0,0,0.9);
window.__p3.push(new Path.Line(p21, p22));
window.__p3[window.__p3.length-1].strokeColor = new Color(0,0,0.9);
}
function plotD_vs_t(x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue) {
yscale = yscale || 1;
new Path.Line(x, y-100, x, y+100).strokeColor = '#aaa';
new Path.Line(x, y, x + 200, y).strokeColor = '#aaa';
var clr = (tvalue)? '#a00' : '#00a';
if (window.__p3) window.__p3.map(function(a) {a.remove();});
window.__p3 = [];
drawFatline(v);
window.__p3.push(new Path.Line(x, y + dmin * yscale, x + 200, y + dmin * yscale));
window.__p3[window.__p3.length-1].strokeColor = '#000';
window.__p3.push(new Path.Line(x, y + dmax * yscale, x + 200, y + dmax * yscale));
window.__p3[window.__p3.length-1].strokeColor = '#000';
window.__p3.push(new Path.Line(x + tmin * 190, y-100, x + tmin * 190, y+100));
window.__p3[window.__p3.length-1].strokeColor = clr;
window.__p3.push(new Path.Line(x + tmax * 190, y-100, x + tmax * 190, y+100));
window.__p3[window.__p3.length-1].strokeColor = clr;
for (var i = 0; i < arr.length; i++) {
window.__p3.push(new Path.Line(new Point(x + arr[i][0] * 190, y + arr[i][1] * yscale),
new Point(x + arr[i][2] * 190, y + arr[i][3] * yscale)));
window.__p3[window.__p3.length-1].strokeColor = '#999';
}
var pnt = [];
var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ];
for (var i = 0; i < arr2.length; i++) {
pnt.push(new Point(x + arr2x[i] * 190, y + arr2[i] * yscale));
window.__p3.push(new Path.Circle(pnt[pnt.length-1], 2));
window.__p3[window.__p3.length-1].fillColor = '#000';
}
// var pth = new Path(pnt[0], pnt[1], pnt[2], pnt[3]);
// pth.closed = true;
window.__p3.push(new Path(
new Segment(pnt[0], null, pnt[1].subtract(pnt[0])),
new Segment(pnt[3], pnt[2].subtract(pnt[3]), null)));
window.__p3[window.__p3.length-1].strokeColor = clr;
view.draw();
}

View file

@ -199,10 +199,11 @@ var Point = Base.extend(/** @lends Point# */{
* console.log(point != new Point(1, 1)); // true
*/
equals: function(point) {
return point === this || point && (this.x === point.x
&& this.y === point.y
|| Array.isArray(point) && this.x === point[0]
&& this.y === point[1]) || false;
return this === point || point
&& (this.x === point.x && this.y === point.y
|| Array.isArray(point)
&& this.x === point[0] && this.y === point[1])
|| false;
},
/**
@ -515,7 +516,8 @@ var Point = Base.extend(/** @lends Point# */{
scale = current !== 0 ? length / current : 0,
point = new Point(this.x * scale, this.y * scale);
// Preserve angle.
point._angle = this._angle;
if (scale >= 0)
point._angle = this._angle;
return point;
},
@ -707,7 +709,7 @@ var Point = Base.extend(/** @lends Point# */{
* @returns {Boolean} {@true it is colinear}
*/
isColinear: function(point) {
return this.cross(point) < /*#=*/ Numerical.TOLERANCE;
return Math.abs(this.cross(point)) < /*#=*/ Numerical.TOLERANCE;
},
/**
@ -718,13 +720,13 @@ var Point = Base.extend(/** @lends Point# */{
* @returns {Boolean} {@true it is orthogonal}
*/
isOrthogonal: function(point) {
return this.dot(point) < /*#=*/ Numerical.TOLERANCE;
return Math.abs(this.dot(point)) < /*#=*/ Numerical.TOLERANCE;
},
/**
* Checks if this point has both the x and y coordinate set to 0.
*
* @returns {Boolean} {@true both x and y are 0}
* @returns {Boolean} {@true if both x and y are 0}
*/
isZero: function() {
return Numerical.isZero(this.x) && Numerical.isZero(this.y);
@ -790,7 +792,7 @@ var Point = Base.extend(/** @lends Point# */{
*
* @name Point#selected
* @property
* @return {Boolean} {@true the point is selected}
* @return {Boolean} {@true if the point is selected}
*/
/**

View file

@ -482,7 +482,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
/**
* @return {Boolean} {@true the rectangle is empty}
* @return {Boolean} {@true if the rectangle is empty}
*/
isEmpty: function() {
return this.width === 0 || this.height === 0;

View file

@ -375,7 +375,7 @@ var Size = Base.extend(/** @lends Size# */{
* {@grouptitle Tests}
* Checks if this size has both the width and height set to 0.
*
* @return {Boolean} {@true both width and height are 0}
* @return {Boolean} {@true if both width and height are 0}
*/
isZero: function() {
return Numerical.isZero(this.width) && Numerical.isZero(this.height);

View file

@ -15,19 +15,19 @@
var CanvasProvider = {
canvases: [],
getCanvas: function(width, height, ratio) {
getCanvas: function(width, height, pixelRatio) {
var canvas,
init = true;
if (typeof width === 'object') {
ratio = height;
pixelRatio = height;
height = width.height;
width = width.width;
}
if (!ratio) {
ratio = 1;
} else if (ratio !== 1) {
width *= ratio;
height *= ratio;
if (!pixelRatio) {
pixelRatio = 1;
} else if (pixelRatio !== 1) {
width *= pixelRatio;
height *= pixelRatio;
}
if (this.canvases.length) {
canvas = this.canvases.pop();
@ -52,8 +52,8 @@ var CanvasProvider = {
}
// We save on retrieval and restore on release.
ctx.save();
if (ratio !== 1)
ctx.scale(ratio, ratio);
if (pixelRatio !== 1)
ctx.scale(pixelRatio, pixelRatio);
return canvas;
},

View file

@ -78,7 +78,7 @@ var Callback = {
return false;
var args = [].slice.call(arguments, 1),
that = this;
for (var i in handlers) {
for (var i = 0, l = handlers.length; i < l; i++) {
// When the handler function returns false, prevent the default
// behaviour and stop propagation of the event by calling stop()
if (handlers[i].apply(that, args) === false

View file

@ -39,7 +39,15 @@ var Item = Base.extend(Callback, /** @lends Item# */{
if (name)
proto._type = Base.hyphenate(name);
return res;
}
},
/**
* An object constant that can be passed to Item#initialize() to avoid
* insertion into the DOM.
*
* @private
*/
NO_INSERT: { insert: false }
},
_class: 'Item',
@ -69,6 +77,16 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// Do nothing, but declare it for named constructors.
},
/**
* Private helper for #initialize() that tries setting properties from the
* passed props object, and apply the point translation to the internal
* matrix.
*
* @param {Object} props the properties to be applied to the item
* @param {Point} point the point by which to transform the internal matrix
* @returns {Boolean} {@true if the properties were successfully be applied,
* or if none were provided}
*/
_initialize: function(props, point) {
// Define this Item's unique id. But allow the creation of internally
// used paths with no ids.
@ -96,7 +114,10 @@ var Item = Base.extend(Callback, /** @lends Item# */{
(project.activeLayer || new Layer()).addChild(this);
}
}
return props ? this._set(props, { insert: true }) : true;
// Filter out Item.NO_INSERT before _set(), for performance reasons
return props && props !== Item.NO_INSERT
? this._set(props, { insert: true }) // Filter out insert prop.
: true;
},
_events: new function() {
@ -1445,7 +1466,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* }
*/
clone: function(insert) {
return this._clone(new this.constructor({ insert: false }), insert);
return this._clone(new this.constructor(Item.NO_INSERT), insert);
},
_clone: function(copy, insert) {
@ -1503,7 +1524,9 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* Rasterizes the item into a newly created Raster object. The item itself
* is not removed after rasterization.
*
* @param {Number} [resolution=72] the resolution of the raster in dpi
* @param {Number} [resolution=view.resolution] the resolution of the raster
* in pixels per inch (DPI). If not speceified, the value of
* {@code view.resolution} is used.
* @return {Raster} the newly created raster item
*
* @example {@paperscript}
@ -1526,13 +1549,14 @@ var Item = Base.extend(Callback, /** @lends Item# */{
*/
rasterize: function(resolution) {
var bounds = this.getStrokeBounds(),
scale = (resolution || 72) / 72,
// floor top-left corner and ceil bottom-right corner, to never
view = this._project.view,
scale = (resolution || view && view.getResolution() || 72) / 72,
// Floor top-left corner and ceil bottom-right corner, to never
// blur or cut pixels.
topLeft = bounds.getTopLeft().floor(),
bottomRight = bounds.getBottomRight().ceil()
size = new Size(bottomRight.subtract(topLeft)),
canvas = CanvasProvider.getCanvas(size),
canvas = CanvasProvider.getCanvas(size.multiply(scale)),
ctx = canvas.getContext('2d'),
matrix = new Matrix().scale(scale).translate(topLeft.negate());
ctx.save();
@ -1540,11 +1564,11 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// See Project#draw() for an explanation of new Base()
this.draw(ctx, new Base({ transforms: [matrix] }));
ctx.restore();
var raster = new Raster({
canvas: canvas,
insert: false
});
raster.setPosition(topLeft.add(size.divide(2)));
var raster = new Raster(Item.NO_INSERT);
raster.setCanvas(canvas);
raster.transform(new Matrix().translate(topLeft.add(size.divide(2)))
// Take resolution into account and scale back to original size.
.scale(1 / scale));
raster.insertAbove(this);
// NOTE: We don't need to release the canvas since it now belongs to the
// Raster!
@ -1861,6 +1885,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* @return {SVGSVGElement} the item converted to an SVG node
*/
// DOCS: Document importSVG('file.svg', callback);
/**
* Converts the provided SVG content into Paper.js items and adds them to
* the this item's children list.
@ -2049,7 +2074,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* Moves this item above the specified item.
*
* @param {Item} item The item above which it should be moved
* @return {Boolean} {@true it was moved}
* @return {Boolean} {@true if it was moved}
* @deprecated use {@link #insertAbove(item)} instead.
*/
moveAbove: '#insertAbove',
@ -2058,7 +2083,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* Moves the item below the specified item.
*
* @param {Item} item the item below which it should be moved
* @return {Boolean} {@true it was moved}
* @return {Boolean} {@true if it was moved}
* @deprecated use {@link #insertBelow(item)} instead.
*/
moveBelow: '#insertBelow',
@ -2072,7 +2097,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
*/
reduce: function() {
if (this._children && this._children.length === 1) {
var child = this._children[0];
var child = this._children[0].reduce();
child.insertAbove(this);
child.setStyle(this._style);
this.remove();
@ -2130,7 +2155,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* Removes the item from the project. If the item has children, they are also
* removed.
*
* @return {Boolean} {@true the item was removed}
* @return {Boolean} {@true if the item was removed}
*/
remove: function() {
return this._remove(true);
@ -2221,7 +2246,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
/**
* Checks whether the item is valid, i.e. it hasn't been removed.
*
* @return {Boolean} {@true the item is valid}
* @return {Boolean} {@true if the item is valid}
*/
// TODO: isValid / checkValid
@ -3495,7 +3520,8 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// it, instead of the mainCtx.
mainCtx = ctx;
ctx = CanvasProvider.getContext(
bounds.getSize().ceil().add(new Size(1, 1)), param.ratio);
bounds.getSize().ceil().add(new Size(1, 1)),
param.pixelRatio);
}
ctx.save();
// If drawing directly, handle opacity and native blending now,
@ -3527,8 +3553,8 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// opacity.
BlendMode.process(blendMode, ctx, mainCtx, opacity,
// Calculate the pixel offset of the temporary canvas to the
// main canvas. We also need to factor in the pixel ratio.
itemOffset.subtract(prevOffset).multiply(param.ratio));
// main canvas. We also need to factor in the pixel-ratio.
itemOffset.subtract(prevOffset).multiply(param.pixelRatio));
// Return the temporary context, so it can be reused
CanvasProvider.release(ctx);
// Restore previous offset.

View file

@ -99,10 +99,9 @@ var PlacedSymbol = Item.extend(/** @lends PlacedSymbol# */{
},
clone: function(insert) {
return this._clone(new PlacedSymbol({
symbol: this.symbol,
insert: false
}), insert);
var copy = new PlacedSymbol(Item.NO_INSERT);
copy.setSymbol(this._symbol);
return this._clone(copy, insert);
},
isEmpty: function() {

View file

@ -97,17 +97,19 @@ var Raster = Item.extend(/** @lends Raster# */{
},
clone: function(insert) {
var param = { insert: false },
image = this._image;
var copy = new Raster(Item.NO_INSERT),
image = this._image,
canvas = this._canvas;
if (image) {
param.image = image;
} else if (this._canvas) {
// If the Raster contains a Canvas object, we need to create
// a new one and draw this raster's canvas on it.
var canvas = param.canvas = CanvasProvider.getCanvas(this._size);
canvas.getContext('2d').drawImage(this._canvas, 0, 0);
copy.setImage(image);
} else if (canvas) {
// If the Raster contains a Canvas object, we need to create a new
// one and draw this raster's canvas on it.
var copyCanvas = CanvasProvider.getCanvas(this._size);
copyCanvas.getContext('2d').drawImage(canvas, 0, 0);
copy.setCanvas(copyCanvas);
}
return this._clone(new Raster(param), insert);
return this._clone(copy, insert);
},
/**
@ -394,10 +396,8 @@ var Raster = Item.extend(/** @lends Raster# */{
*/
getSubRaster: function(rect) { // TODO: Fix argument assignment!
var rect = Rectangle.read(arguments),
raster = new Raster({
canvas: this.getSubCanvas(rect),
insert: false
});
raster = new Raster(Item.NO_INSERT);
raster.setCanvas(this.getSubCanvas(rect));
raster.translate(rect.getCenter().subtract(this.getSize().divide(2)));
raster._matrix.preConcatenate(this._matrix);
raster.insertAbove(this);

View file

@ -39,12 +39,11 @@ var Shape = Item.extend(/** @lends Shape# */{
},
clone: function(insert) {
return this._clone(new Shape({
shape: this._shape,
size: this._size,
radius: this._radius,
insert: false
}), insert);
var copy = new Shape(Item.NO_INSERT);
copy.setShape(this._shape);
copy.setSize(this._size);
copy.setRadius(this._radius);
return this._clone(copy, insert);
},
/**
@ -179,7 +178,7 @@ var Shape = Item.extend(/** @lends Shape# */{
} else {
var rx = radius.width,
ry = radius.height,
kappa = Numerical.KAPPA;
kappa = /*#=*/ Numerical.KAPPA;
if (shape === 'ellipse') {
// Approximate ellipse with four bezier curves and KAPPA.
var cx = rx * kappa,

View file

@ -20,9 +20,10 @@ var __options = {
environment: 'browser',
stats: true,
svg: true,
fatline: true,
paperscript: true,
palette: true,
fatlineClipping: true,
booleanOperations: true,
nativeContains: false,
paperScript: true,
palette: true,
debug: false
};

View file

@ -81,9 +81,11 @@ var paper = new function(undefined) {
/*#*/ include('path/Path.js');
/*#*/ include('path/Path.Constructors.js');
/*#*/ include('path/CompoundPath.js');
/*#*/ if (__options.booleanOperations) {
/*#*/ include('path/PathItem.Boolean.js');
/*#*/ } // __options.booleanOperations
/*#*/ include('path/PathFlattener.js');
/*#*/ include('path/PathFitter.js');
/*#*/ include('path/PathItem.Boolean.js');
/*#*/ include('text/TextItem.js');
/*#*/ include('text/PointText.js');
@ -120,9 +122,9 @@ var paper = new function(undefined) {
/*#*/ include('tool/Tool.js');
// Http is used both for PaperScript and SVGImport
/*#*/ if (__options.paperscript || __options.svg) {
/*#*/ if (__options.paperScript || __options.svg) {
/*#*/ include('net/Http.js');
/*#*/ } // __options.paperscript || __options.svg
/*#*/ } // __options.paperScript || __options.svg
/*#*/ } // __options.environment == 'browser'
/*#*/ include('canvas/CanvasProvider.js');
@ -138,9 +140,9 @@ var paper = new function(undefined) {
/*#*/ include('svg/SVGImport.js');
/*#*/ } // __options.svg
/*#*/ if (__options.paperscript) {
/*#*/ if (__options.paperScript) {
/*#*/ include('core/PaperScript.js');
/*#*/ } // __options.paperscript
/*#*/ } // __options.paperScript
/*#*/ include('export.js');
return paper;

View file

@ -187,7 +187,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
var children = this._children,
curves = [];
for (var i = 0, l = children.length; i < l; i++)
curves = curves.concat(children[i].getCurves());
curves.push.apply(curves, children[i].getCurves());
return curves;
},
@ -236,14 +236,6 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
return paths.join(' ');
},
_getWinding: function(point) {
var children = this._children,
winding = 0;
for (var i = 0, l = children.length; i < l; i++)
winding += children[i]._getWinding(point);
return winding;
},
_getChildHitTestOptions: function(options) {
// If we're not specifically asked to returns paths through
// options.type == 'path' do not test children for fill, since a

View file

@ -267,8 +267,12 @@ var Curve = Base.extend(/** @lends Curve# */{
* @bean
*/
getLength: function() {
if (this._length == null)
this._length = Curve.getLength(this.getValues(), 0, 1);
if (this._length == null) {
// Use simple point distance for linear curves
this._length = this.isLinear()
? this._segment2._point.getDistance(this._segment1._point)
: Curve.getLength(this.getValues(), 0, 1);
}
return this._length;
},
@ -280,7 +284,7 @@ var Curve = Base.extend(/** @lends Curve# */{
return new Curve(Curve.getPart(this.getValues(), from, to));
},
// DOCS: document Curve#getPartLength(from, to)
// DOCS: Curve#getPartLength(from, to)
getPartLength: function(from, to) {
return Curve.getLength(this.getValues(), from, to);
},
@ -289,13 +293,19 @@ var Curve = Base.extend(/** @lends Curve# */{
* Checks if this curve is linear, meaning it does not define any curve
* handle.
* @return {Boolean} {@true the curve is linear}
* @return {Boolean} {@true if the curve is linear}
*/
isLinear: function() {
return this._segment1._handleOut.isZero()
&& this._segment2._handleIn.isZero();
},
isHorizontal: function() {
return this.isLinear() && Numerical.isZero(
this._segment1._point._y - this._segment2._point._y);
},
// DOCS: Curve#getIntersections()
getIntersections: function(curve) {
return Curve.getIntersections(this.getValues(), curve.getValues(),
this, curve, []);
@ -303,16 +313,6 @@ var Curve = Base.extend(/** @lends Curve# */{
// TODO: adjustThroughPoint
/**
* Returns a reversed version of the curve, without modifying the curve
* itself.
*
* @return {Curve} a reversed version of the curve
*/
reverse: function() {
return new Curve(this._segment2.reverse(), this._segment1.reverse());
},
/**
* Private method that handles all types of offset / isParameter pairs and
* converts it to a curve parameter.
@ -344,13 +344,13 @@ var Curve = Base.extend(/** @lends Curve# */{
* @return {Curve} the second part of the divided curve
*/
// TODO: Rename to divideAt()?
divide: function(offset, isParameter) {
divide: function(offset, isParameter, ignoreLinear) {
var parameter = this._getParameter(offset, isParameter),
tolerance = /*#=*/ Numerical.TOLERANCE,
res = null;
if (parameter > tolerance && parameter < 1 - tolerance) {
var parts = Curve.subdivide(this.getValues(), parameter),
isLinear = this.isLinear(),
isLinear = ignoreLinear ? false : this.isLinear(),
left = parts[0],
right = parts[1];
@ -418,6 +418,33 @@ var Curve = Base.extend(/** @lends Curve# */{
: null;
},
/**
* Returns a reversed version of the curve, without modifying the curve
* itself.
*
* @return {Curve} a reversed version of the curve
*/
reverse: function() {
return new Curve(this._segment2.reverse(), this._segment1.reverse());
},
/**
* Removes the curve from the path that it belongs to, by merging its two
* path segments.
* @return {Boolean} {@true if the curve was removed}
*/
remove: function() {
var removed = false;
if (this._path) {
var segment2 = this._segment2,
handleOut = segment2._handleOut;
removed = segment2.remove();
if (removed)
this._segment1._handleOut.set(handleOut.x, handleOut.y);
}
return removed;
},
/**
* Returns a copy of the curve.
*
@ -463,12 +490,14 @@ statics: {
c1x = v[2], c1y = v[3],
c2x = v[4], c2y = v[5],
p2x = v[6], p2y = v[7],
tolerance = /*#=*/ Numerical.TOLERANCE,
x, y;
// Handle special case at beginning / end of curve
if (type === 0 && (t === 0 || t === 1)) {
x = t === 0 ? p1x : p2x;
y = t === 0 ? p1y : p2y;
if (type === 0 && (t < tolerance || t > 1 - tolerance)) {
var isZero = t < tolerance;
x = isZero ? p1x : p2x;
y = isZero ? p1y : p2y;
} else {
// Calculate the polynomial coefficients.
var cx = 3 * (c1x - p1x),
@ -488,11 +517,16 @@ statics: {
// 3: curvature, 1st derivative & 2nd derivative
// Prevent tangents and normals of length 0:
// http://stackoverflow.com/questions/10506868/
var tolerance = /*#=*/ Numerical.TOLERANCE;
if (t < tolerance && c1x === p1x && c1y === p1y
|| t > 1 - tolerance && c2x === p2x && c2y === p2y) {
x = p2x - p1x;
y = p2y - p1y;
} else if (t < tolerance) {
x = cx;
y = cy;
} else if (t > 1 - tolerance) {
x = 3 * (p2x - c2x);
y = 3 * (p2y - c2y);
} else {
// Simply use the derivation of the bezier function for both
// the x and y coordinates:
@ -510,7 +544,7 @@ statics: {
}
}
// The normal is simply the rotated tangent:
return type == 2 ? new Point(y, -x) : new Point(x, y);
return type === 2 ? new Point(y, -x) : new Point(x, y);
},
subdivide: function(v, t) {
@ -690,141 +724,6 @@ statics: {
+ t * t * t * v3,
padding);
}
},
_getWinding: function(v, prev, x, y, roots1, roots2) {
// Implementation of the crossing number algorithm:
// http://en.wikipedia.org/wiki/Point_in_polygon
// Solve the y-axis cubic polynomial for y and count all solutions
// to the right of x as crossings.
var tolerance = /*#=*/ Numerical.TOLERANCE,
abs = Math.abs;
// Looks at the curve's start and end y coordinates to determine
// orientation. This only makes sense for curves with clear orientation,
// which is why we need to split them at y extrema, see below.
// Returns 0 if the curve is outside the boundaries and is not to be
// considered.
function getDirection(v) {
var y0 = v[1],
y1 = v[7],
dir = y0 > y1 ? -1 : 1;
// Bounds check: Reverse y0 and y1 if direction is -1.
// Include end points, so we can handle them depending on different
// edge cases.
return dir === 1 && (y < y0 || y > y1)
|| dir === -1 && (y < y1 || y > y0)
? 0
: dir;
}
if (Curve.isLinear(v)) {
// Special simplified case for handling lines.
var dir = getDirection(v);
if (!dir)
return 0;
var cross = (v[6] - v[0]) * (y - v[1]) - (v[7] - v[1]) * (x - v[0]);
return (cross < -tolerance ? -1 : 1) == dir ? 0 : dir;
}
// Handle bezier curves. We need to chop them into smaller curves with
// defined orientation, by solving the derrivative curve for Y extrema.
var y0 = v[1],
y1 = v[3],
y2 = v[5],
y3 = v[7];
// Split the curve at y extrema, to get bezier curves with clear
// orientation: Calculate the derivative and find its roots.
var a = 3 * (y1 - y2) - y0 + y3,
b = 2 * (y0 + y2) - 4 * y1,
c = y1 - y0;
// Keep then range to 0 .. 1 (excluding) in the search for y extrema
var count = Numerical.solveQuadratic(a, b, c, roots1, tolerance,
1 - tolerance),
part, // The part of the curve that's chopped off.
rest = v, // The part that's left to be chopped.
t1 = roots1[0], // The first root
winding = 0;
for (var i = 0; i <= count; i++) {
if (i === count) {
part = rest;
} else {
// Divide the curve at t1.
var curves = Curve.subdivide(rest, t1);
part = curves[0];
rest = curves[1];
t1 = roots1[i];
// TODO: Watch for divide by 0
// Now renormalize t1 to the range of the next iteration.
t1 = (roots1[i + 1] - t1) / (1 - t1);
}
// Make sure that the connecting y extrema are flat
if (i > 0)
part[3] = part[1]; // curve2.handle1.y = curve2.point1.y;
if (i < count)
part[5] = rest[1]; // curve1.handle2.y = curve2.point1.y;
var dir = getDirection(part);
if (!dir)
continue;
// Adjust start and end range depending on if curve was flipped.
// In normal orientation we exclude the end point since it's also
// the start point of the next curve. If flipped, we have to exclude
// the end point instead.
var t2,
px;
// Since we've split at y extrema, there can only be 0, 1, or
// infinite solutions now.
if (Curve.solveCubic(part, 1, y, roots2, -tolerance, 1 + -tolerance)
=== 1) {
t2 = roots2[0];
px = Curve.evaluate(part, t2, 0).x;
} else {
var mid = (part[1] + part[7]) / 2;
// Pick t2 based on the direction of the curve. If y < mid,
// choose the beginning (which is the end of a curve with
// negative orientation, as we're not actually flipping curves).
t2 = y < mid && dir > 0 ? 0 : 1;
// Filter out the end point, as it'll be the start point of the
// next curve.
if (t2 === 1 && y == part[7])
continue;
px = t2 === 0 ? part[0] : part[6];
}
// See if we're touching a horizontal stationary point by looking at
// the tanget's y coordinate. There are two cases 0:
// A) The slope is 0, meaning we're touching a stationary
// point inside the curve.
// B) t2 == 0 and the slope changes between the current and the
// previous curve.
var slope = Curve.evaluate(part, t2, 1).y,
stationary = abs(slope) < tolerance || t2 < tolerance
&& Curve.evaluate(prev, 1, 1).y * slope < 0;
// Calculate compare tolerance based on curve orientation (dir), to
// add a bit of tolerance when considering points lying on the curve
// as inside. But if we're touching a horizontal stationary point,
// set compare tolerance to -tolerance, since we don't want to step
// side-ways in tolerance based on orientation. This is needed e.g.
// when touching the bottom tip of a circle.
// Pass 1 for Curve.evaluate() type to calculate tangent
if (x >= px + (stationary ? -tolerance : tolerance * dir)
// When touching a stationary point, only count it if we're
// actuall on it.
&& !(stationary && (abs(t2) < tolerance
&& abs(x - part[0]) > tolerance
|| abs(t2 - 1) < tolerance
&& abs(x - part[6]) > tolerance))) {
// If this is a horizontal stationary point, and we're at the
// end of the curve (or at the beginning of a curve with
// negative direction, as we're not actually flipping them),
// flip dir, as the curve is about to change orientation.
winding += stationary && abs(t2 - (dir > 0 ? 1 : 0)) < tolerance
? -dir : dir;
}
// Point the previous curve to the newly split part, so stationary
// points are correctly detected.
prev = part;
}
return winding;
}
}}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
// Note: Although Curve.getBounds() exists, we are using Path.getBounds() to
@ -1138,26 +1037,23 @@ new function() { // Scope for methods that require numerical integration
}
};
}, new function() { // Scope for intersection using bezier fat-line clipping
function addLocation(locations, curve1, t1, point1, curve2, t2, point2) {
// Avoid duplicates when hitting segments (closed paths too)
var first = locations[0],
last = locations[locations.length - 1],
epsilon = /*#=*/ Numerical.EPSILON;
if ((!first || !point1.isClose(first._point, epsilon))
&& (!last || !point1.isClose(last._point, epsilon)))
locations.push(
new CurveLocation(curve1, t1, point1, curve2, t2, point2));
function addLocation(locations, include, curve1, t1, point1, curve2, t2,
point2) {
var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2);
if (!include || include(loc))
locations.push(loc);
}
function addCurveIntersections(v1, v2, curve1, curve2, locations,
function addCurveIntersections(v1, v2, curve1, curve2, locations, include,
tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) {
/*#*/ if (__options.fatline) {
/*#*/ if (__options.fatlineClipping) {
// Avoid deeper recursion.
if (recursion > 20)
return;
// Let P be the first curve and Q be the second
var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],
tolerance = /*#=*/ Numerical.TOLERANCE,
hullEpsilon = 1e-9,
getSignedDistance = Line.getSignedDistance,
// Calculate the fat-line L for Q is the baseline l and two
// offsets which completely encloses the curve P.
@ -1174,7 +1070,7 @@ new function() { // Scope for methods that require numerical integration
dp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]),
dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]),
tMinNew, tMaxNew, tDiff;
if (q0x === q3x && uMax - uMin <= Numerical.EPSILON && recursion > 3) {
if (q0x === q3x && uMax - uMin <= hullEpsilon && recursion > 3) {
// The fatline of Q has converged to a point, the clipping is not
// reliable. Return the value we have even though we will miss the
// precision.
@ -1209,16 +1105,20 @@ new function() { // Scope for methods that require numerical integration
if (tMaxNew - tMinNew > uMax - uMin) {
var parts = Curve.subdivide(v1, 0.5),
t = tMinNew + (tMaxNew - tMinNew) / 2;
addCurveIntersections(v2, parts[0], curve2, curve1, locations,
addCurveIntersections(
v2, parts[0], curve2, curve1, locations, include,
uMin, uMax, tMinNew, t, tDiff, !reverse, ++recursion);
addCurveIntersections(v2, parts[1], curve2, curve1, locations,
addCurveIntersections(
v2, parts[1], curve2, curve1, locations, include,
uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion);
} else {
var parts = Curve.subdivide(v2, 0.5),
t = uMin + (uMax - uMin) / 2;
addCurveIntersections(parts[0], v1, curve2, curve1, locations,
addCurveIntersections(
parts[0], v1, curve2, curve1, locations, include,
uMin, t, tMinNew, tMaxNew, tDiff, !reverse, ++recursion);
addCurveIntersections(parts[1], v1, curve2, curve1, locations,
addCurveIntersections(
parts[1], v1, curve2, curve1, locations, include,
t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
}
} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) {
@ -1226,19 +1126,19 @@ new function() { // Scope for methods that require numerical integration
var t1 = tMinNew + (tMaxNew - tMinNew) / 2,
t2 = uMin + (uMax - uMin) / 2;
if (reverse) {
addLocation(locations,
addLocation(locations, include,
curve2, t2, Curve.evaluate(v2, t2, 0),
curve1, t1, Curve.evaluate(v1, t1, 0));
} else {
addLocation(locations,
addLocation(locations, include,
curve1, t1, Curve.evaluate(v1, t1, 0),
curve2, t2, Curve.evaluate(v2, t2, 0));
}
} else { // Iterate
addCurveIntersections(v2, v1, curve2, curve1, locations,
addCurveIntersections(v2, v1, curve2, curve1, locations, include,
uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, ++recursion);
}
/*#*/ } else { // !__options.fatline
/*#*/ } else { // !__options.fatlineClipping
// Subdivision method
var bounds1 = Curve.getBounds(v1),
bounds2 = Curve.getBounds(v2),
@ -1251,7 +1151,7 @@ new function() { // Scope for methods that require numerical integration
if ((Curve.isLinear(v1) || Curve.isFlatEnough(v1, tolerance))
&& (Curve.isLinear(v2) || Curve.isFlatEnough(v2, tolerance))) {
// See if the parametric equations of the lines interesct.
addLineIntersection(v1, v2, curve1, curve2, locations);
addLineIntersection(v1, v2, curve1, curve2, locations, include);
} else {
// Subdivide both curves, and see if they intersect.
// If one of the curves is flat already, no further subdivion
@ -1260,15 +1160,14 @@ new function() { // Scope for methods that require numerical integration
v2s = Curve.subdivide(v2);
for (var i = 0; i < 2; i++)
for (var j = 0; j < 2; j++)
Curve.getIntersections(v1s[i], v2s[j], curve1, curve2,
locations);
addCurveIntersections(v1s[i], v2s[j], curve1, curve2,
locations, include);
}
}
return locations;
/*#*/ } // !__options.fatline
/*#*/ } // !__options.fatlineClipping
}
/*#*/ if (__options.fatline) {
/*#*/ if (__options.fatlineClipping) {
/**
* Calculate the convex hull for the non-paramertic bezier curve D(ti, di(t))
* The ti is equally spaced across [0..1] [0, 1/3, 2/3, 1] for
@ -1382,7 +1281,7 @@ new function() { // Scope for methods that require numerical integration
}
return tVal;
}
/*#*/ } // __options.fatline
/*#*/ } // __options.fatlineClipping
/**
* Intersections between curve and line becomes rather simple here mostly
@ -1390,7 +1289,8 @@ new function() { // Scope for methods that require numerical integration
* line is on the X axis, and solve the implicit equations for the X axis
* and the curve.
*/
function addCurveLineIntersections(v1, v2, curve1, curve2, locations) {
function addCurveLineIntersections(v1, v2, curve1, curve2, locations,
include) {
var flip = Curve.isLinear(v1),
vc = flip ? v2 : v1,
vl = flip ? v1 : v2,
@ -1430,39 +1330,45 @@ new function() { // Scope for methods that require numerical integration
var tl = Curve.getParameterOf(rvl, x, 0),
t1 = flip ? tl : tc,
t2 = flip ? tc : tl;
addLocation(locations,
addLocation(locations, include,
curve1, t1, Curve.evaluate(v1, t1, 0),
curve2, t2, Curve.evaluate(v2, t2, 0));
}
}
}
function addLineIntersection(v1, v2, curve1, curve2, locations) {
function addLineIntersection(v1, v2, curve1, curve2, locations, include) {
var point = Line.intersect(
v1[0], v1[1], v1[6], v1[7],
v2[0], v2[1], v2[6], v2[7]);
// Passing null for parameter leads to lazy determination of parameter
// values in CurveLocation#getParameter() only once they are requested.
if (point)
addLocation(locations, curve1, null, point, curve2);
if (point) {
// We need to return the parameters for the intersection,
// since they will be used for sorting
var x = point.x,
y = point.y;
addLocation(locations, include,
curve1, Curve.getParameterOf(v1, x, y), point,
curve2, Curve.getParameterOf(v2, x, y), point);
}
}
return { statics: /** @lends Curve */{
// We need to provide the original left curve reference to the
// #getIntersections() calls as it is required to create the resulting
// CurveLocation objects.
getIntersections: function(v1, v2, curve1, curve2, locations) {
getIntersections: function(v1, v2, curve1, curve2, locations, include) {
var linear1 = Curve.isLinear(v1),
linear2 = Curve.isLinear(v2);
(linear1 && linear2
? addLineIntersection
: linear1 || linear2
? addCurveLineIntersections
: addCurveIntersections)(v1, v2, curve1, curve2, locations,
: addCurveIntersections)(
v1, v2, curve1, curve2, locations, include,
// Define the defaults for these parameters of
// addCurveIntersections():
// tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion
0, 1, 0, 1, 1, false, 0);
0, 1, 0, 1, 0, false, 0);
return locations;
}
}};

View file

@ -268,6 +268,25 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
return curve && curve.split(this.getParameter(true), true);
},
/**
* Checks whether tow CurveLocation objects are describing the same location
* on a path, by applying the same tolerances as elsewhere when dealing with
* curve time parameters.
*
* @param {CurveLocation} location
* @return {Boolean} {@true if the locations are equal}
*/
equals: function(loc) {
var isZero = Numerical.isZero;
return this === loc
|| loc
&& this._curve === loc._curve
&& this._curve2 === loc._curve2
&& isZero(this._parameter - loc._parameter)
&& isZero(this._parameter2 - loc._parameter2)
|| false;
},
/**
* @return {String} a string representation of the curve location
*/

View file

@ -12,7 +12,7 @@
Path.inject({ statics: new function() {
var kappa = Numerical.KAPPA,
var kappa = /*#=*/ Numerical.KAPPA,
ellipseSegments = [
new Segment([-1, 0], [0, kappa ], [0, -kappa]),
new Segment([0, -1], [-kappa, 0], [kappa, 0 ]),
@ -20,9 +20,19 @@ Path.inject({ statics: new function() {
new Segment([0, 1], [kappa, 0 ], [-kappa, 0])
];
function createPath(segments, closed, args) {
var props = Base.getNamed(args),
path = new Path(props && props.insert === false && Item.NO_INSERT);
path._add(segments);
// No need to use setter for _closed since _add() called _changed().
path._closed = true;
// Set named arguments at the end, since some depend on geometry to be
// defined (e.g. #clockwise)
return path.set(props);
}
function createEllipse(center, radius, args) {
var path = new Path(),
segments = new Array(4);
var segments = new Array(4);
for (var i = 0; i < 4; i++) {
var segment = ellipseSegments[i];
segments[i] = new Segment(
@ -31,11 +41,7 @@ Path.inject({ statics: new function() {
segment._handleOut.multiply(radius)
);
}
path._add(segments);
path._closed = true;
// Set named arguments at the end, since some depend on geometry to be
// defined (e.g. #clockwise)
return path.set(Base.getNamed(args));
return createPath(segments, true, args);
}
@ -73,10 +79,10 @@ Path.inject({ statics: new function() {
* });
*/
Line: function(/* from, to */) {
return new Path(
Point.readNamed(arguments, 'from'),
Point.readNamed(arguments, 'to')
).set(Base.getNamed(arguments));
return createPath([
new Segment(Point.readNamed(arguments, 'from')),
new Segment(Point.readNamed(arguments, 'to'))
], false, arguments);
},
/**
@ -210,22 +216,22 @@ Path.inject({ statics: new function() {
bl = rect.getBottomLeft(true),
tl = rect.getTopLeft(true),
tr = rect.getTopRight(true),
br = rect.getBottomRight(true);
path = new Path();
br = rect.getBottomRight(true),
segments;
if (!radius || radius.isZero()) {
path._add([
segments = [
new Segment(bl),
new Segment(tl),
new Segment(tr),
new Segment(br)
]);
];
} else {
radius = Size.min(radius, rect.getSize(true).divide(2));
var rx = radius.width,
ry = radius.height,
hx = rx * kappa,
hy = ry * kappa;
path._add([
segments = [
new Segment(bl.add(rx, 0), null, [-hx, 0]),
new Segment(bl.subtract(0, ry), [0, hy]),
new Segment(tl.add(0, ry), null, [0, -hy]),
@ -234,11 +240,9 @@ Path.inject({ statics: new function() {
new Segment(tr.add(0, ry), [0, -hy], null),
new Segment(br.subtract(0, ry), null, [0, hy]),
new Segment(br.subtract(rx, 0), [hx, 0])
]);
];
}
// No need to use setter for _closed since _add() called _changed().
path._closed = true;
return path.set(Base.getNamed(arguments));
return createPath(segments, true, arguments);
},
/**
@ -329,10 +333,13 @@ Path.inject({ statics: new function() {
var from = Point.readNamed(arguments, 'from'),
through = Point.readNamed(arguments, 'through'),
to = Point.readNamed(arguments, 'to'),
path = new Path();
props = Base.getNamed(arguments),
// See createPath() for an explanation of the following sequence
path = new Path(props && props.insert === false
&& Item.NO_INSERT);
path.moveTo(from);
path.arcTo(through, to);
return path.set(Base.getNamed(arguments));
return path.set(props);
},
/**
@ -372,19 +379,15 @@ Path.inject({ statics: new function() {
var center = Point.readNamed(arguments, 'center'),
sides = Base.readNamed(arguments, 'sides'),
radius = Base.readNamed(arguments, 'radius'),
path = new Path(),
step = 360 / sides,
three = !(sides % 3),
vector = new Point(0, three ? -radius : radius),
offset = three ? -1 : 0.5,
segments = new Array(sides);
for (var i = 0; i < sides; i++) {
for (var i = 0; i < sides; i++)
segments[i] = new Segment(center.add(
vector.rotate((i + offset) * step)));
}
path._add(segments);
path._closed = true;
return path.set(Base.getNamed(arguments));
return createPath(segments, true, arguments);
},
/**
@ -432,17 +435,13 @@ Path.inject({ statics: new function() {
points = Base.readNamed(arguments, 'points') * 2,
radius1 = Base.readNamed(arguments, 'radius1'),
radius2 = Base.readNamed(arguments, 'radius2'),
path = new Path(),
step = 360 / points,
vector = new Point(0, -1),
segments = new Array(points);
for (var i = 0; i < points; i++) {
segments[i] = new Segment(center.add(
vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
}
path._add(segments);
path._closed = true;
return path.set(Base.getNamed(arguments));
for (var i = 0; i < points; i++)
segments[i] = new Segment(center.add(vector.rotate(step * i)
.multiply(i % 2 ? radius2 : radius1)));
return createPath(segments, true, arguments);
}
};
}});

View file

@ -106,11 +106,17 @@ var Path = PathItem.extend(/** @lends Path# */{
? arguments
: null;
// Always call setSegments() to initialize a few related variables.
this.setSegments(segments || []);
if (!segments && typeof arg === 'string') {
this.setPathData(arg);
// Erase for _initialize() call below.
arg = null;
if (segments && segments.length > 0) {
// This sets _curves and _selectedSegmentState too!
this.setSegments(segments);
} else {
this._curves = undefined; // For hidden class optimization
this._selectedSegmentState = 0;
if (!segments && typeof arg === 'string') {
this.setPathData(arg);
// Erase for _initialize() call below.
arg = null;
}
}
// Only pass on arg as props if it wasn't consumed for segments already.
this._initialize(!segments && arg);
@ -121,15 +127,12 @@ var Path = PathItem.extend(/** @lends Path# */{
},
clone: function(insert) {
var copy = this._clone(new Path({
segments: this._segments,
insert: false
}), insert);
// Speed up things a little by copy over values that don't need checking
var copy = new Path(Item.NO_INSERT);
copy.setSegments(this._segments);
copy._closed = this._closed;
if (this._clockwise !== undefined)
copy._clockwise = this._clockwise;
return copy;
return this._clone(copy, insert);
},
_changed: function _changed(flags) {
@ -144,6 +147,10 @@ var Path = PathItem.extend(/** @lends Path# */{
for (var i = 0, l = this._curves.length; i < l; i++)
this._curves[i]._changed(/*#=*/ Change.GEOMETRY);
}
// Clear cached curves used for winding direction and containment
// calculation.
// NOTE: This is only needed with __options.booleanOperations
this._monoCurves = undefined;
} else if (flags & /*#=*/ ChangeFlag.STROKE) {
// TODO: We could preserve the purely geometric bounds that are not
// affected by stroke: _bounds.bounds and _bounds.handleBounds
@ -173,7 +180,10 @@ var Path = PathItem.extend(/** @lends Path# */{
this._selectedSegmentState = 0;
// Calculate new curves next time we call getCurves()
this._curves = undefined;
this._add(Segment.readAll(segments));
if (segments && segments.length > 0)
this._add(Segment.readAll(segments));
// Preserve fullySelected state.
// TODO: Do we still need this?
if (fullySelected)
this.setFullySelected(true);
},
@ -897,6 +907,19 @@ var Path = PathItem.extend(/** @lends Path# */{
this.setSegments(segments);
},
/**
* Reduces the path by removing curves that have a lenght of 0.
*/
reduce: function() {
var curves = this.getCurves();
for (var i = curves.length - 1; i >= 0; i--) {
var curve = curves[i];
if (curve.isLinear() && curve.getLength() === 0)
curve.remove();
}
return this;
},
/**
* Smooths a path by simplifying it. The {@link Path#segments} array is
* analyzed and replaced by a more optimal set of segments, reducing memory
@ -1068,7 +1091,8 @@ var Path = PathItem.extend(/** @lends Path# */{
index = arg.index;
parameter = arg.parameter;
}
if (parameter >= 1) {
var tolerance = /*#=*/ Numerical.TOLERANCE;
if (parameter >= 1 - tolerance) {
// t == 1 is the same as t == 0 and index ++
index++;
parameter--;
@ -1076,7 +1100,7 @@ var Path = PathItem.extend(/** @lends Path# */{
var curves = this.getCurves();
if (index >= 0 && index < curves.length) {
// Only divide curves if we're not on an existing segment already.
if (parameter > 0) {
if (parameter > tolerance) {
// Divide the curve with the index at given parameter.
// Increase because dividing adds more segments to the path.
curves[index++].divide(parameter, true);
@ -1344,13 +1368,13 @@ var Path = PathItem.extend(/** @lends Path# */{
var start = length,
curve = curves[i];
length += curve.getLength();
if (length >= offset) {
if (length > offset) {
// Found the segment within which the length lies
return curve.getLocationAt(offset - start);
}
}
// It may be that through impreciseness of getLength, that the end
// of the curves was missed:
// It may be that through imprecision of getLength, that the end of the
// last curve was missed:
if (offset <= this.getLength())
return new CurveLocation(curves[curves.length - 1], 1);
return null;
@ -1705,49 +1729,6 @@ var Path = PathItem.extend(/** @lends Path# */{
return null;
},
_getWinding: function(point) {
var closed = this._closed;
// If the path is not closed, we should not bail out in case it has a
// fill color!
if (!closed && !this.hasFill()
|| !this.getInternalRoughBounds()._containsPoint(point))
return 0;
// 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(),
segments = this._segments,
winding = 0,
// Reuse arrays for root-finding, give garbage collector a break
roots1 = [],
roots2 = [],
last = (closed
? curves[curves.length - 1]
// Create a straight closing line for open paths, just like
// how filling open paths works.
: new Curve(segments[segments.length - 1]._point,
segments[0]._point)).getValues(),
previous = last;
for (var i = 0, l = curves.length; i < l; i++) {
var curve = curves[i].getValues(),
x = curve[0],
y = curve[1];
// Filter out curves with 0-length (all 4 points in the same place):
if (!(x === curve[2] && y === curve[3] && x === curve[4]
&& y === curve[5] && x === curve[6] && y === curve[7])) {
winding += Curve._getWinding(curve, previous, point.x, point.y,
roots1, roots2);
previous = curve;
}
}
if (!closed) {
winding += Curve._getWinding(last, previous, point.x, point.y,
roots1, roots2);
}
return winding;
},
_hitTest: function(point, options) {
var that = this,
style = this.getStyle(),
@ -1810,7 +1791,7 @@ var Path = PathItem.extend(/** @lends Path# */{
// Code to check stroke join / cap areas
function addAreaPoint(point) {
function addToArea(point) {
area.add(point);
}
@ -1827,11 +1808,12 @@ var Path = PathItem.extend(/** @lends Path# */{
// the handles has to be zero too for this!)
if (join !== 'round' && (segment._handleIn.isZero()
|| segment._handleOut.isZero()))
Path._addSquareJoin(segment, join, radius, miterLimit,
addAreaPoint, true);
// _addBevelJoin() handles both 'bevel' and 'miter'!
Path._addBevelJoin(segment, join, radius, miterLimit,
addToArea, true);
} else if (cap !== 'round') {
// It's a cap
Path._addSquareCap(segment, cap, radius, addAreaPoint, true);
Path._addSquareCap(segment, cap, radius, addToArea, true);
}
// See if the above produced an area to check for
if (!area.isEmpty()) {
@ -2555,21 +2537,27 @@ statics: {
? matrix._transformPoint(point, point) : point);
}
function addRound(segment) {
bounds = bounds.unite(joinBounds.setCenter(matrix
? matrix._transformPoint(segment._point) : segment._point));
}
function addJoin(segment, join) {
// When both handles are set in a segment, the join setting is
// ignored and round is always used.
if (join === 'round' || !segment._handleIn.isZero()
&& !segment._handleOut.isZero()) {
bounds = bounds.unite(joinBounds.setCenter(matrix
? matrix._transformPoint(segment._point) : segment._point));
// When both handles are set in a segment and they are collinear,
// the join setting is ignored and round is always used.
var handleIn = segment._handleIn,
handleOut = segment._handleOut
if (join === 'round' || !handleIn.isZero() && !handleOut.isZero()
&& handleIn.isColinear(handleOut)) {
addRound(segment);
} else {
Path._addSquareJoin(segment, join, radius, miterLimit, add);
Path._addBevelJoin(segment, join, radius, miterLimit, add);
}
}
function addCap(segment, cap) {
if (cap === 'round') {
addJoin(segment, cap);
addRound(segment);
} else {
Path._addSquareCap(segment, cap, radius, add);
}
@ -2629,8 +2617,8 @@ statics: {
Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
},
_addSquareJoin: function(segment, join, radius, miterLimit, addPoint, area) {
// Treat bevel and miter in one go, since they share a lot of code.
_addBevelJoin: function(segment, join, radius, miterLimit, addPoint, area) {
// Handles both 'bevel' and 'miter' joins, as they share a lot of code.
var curve2 = segment.getCurve(),
curve1 = curve2.getPrevious(),
point = curve2.getPointAt(0, true),
@ -2667,6 +2655,7 @@ statics: {
},
_addSquareCap: function(segment, cap, radius, addPoint, area) {
// Handles both 'square' and 'butt' caps, as they share a lot of code.
// Calculate the corner points of butt and square caps
var point = segment._point,
loc = segment.getLocation(),

View file

@ -32,47 +32,13 @@
*/
PathItem.inject(new function() {
function splitPath(intersections, collectOthers) {
// Sort intersections by paths ids, curve index and parameter, so we
// can loop through all intersections, divide paths and never need to
// readjust indices.
intersections.sort(function(loc1, loc2) {
var path1 = loc1.getPath(),
path2 = loc2.getPath();
return path1 === path2
// We can add parameter (0 <= t <= 1) to index (a integer)
// to compare both at the same time
? (loc1.getIndex() + loc1.getParameter())
- (loc2.getIndex() + loc2.getParameter())
// Sort by path index to group all locations on the same
// path in the sequnence that they are encountered within
// compound paths.
: path1._index - path2._index;
});
var others = collectOthers && [];
for (var i = intersections.length - 1; i >= 0; i--) {
var loc = intersections[i],
other = loc.getIntersection(),
curve = loc.divide(),
// When the curve doesn't need to be divided since t = 0, 1,
// #divide() returns null and we can use the existing segment.
segment = curve && curve.getSegment1() || loc.getSegment();
if (others)
others.push(other);
segment._intersection = other;
loc._segment = segment;
}
return others;
}
/**
/*
* To deal with a HTML5 canvas requirement where CompoundPaths' child
* contours has to be of different winding direction for correctly filling
* holes. But if some individual countours are disjoint, i.e. islands, we
* have to reorient them so that:
* - the holes have opposit winding direction (already handled by paper.js)
* - islands have to have the same winding direction as the first child
* contours has to be of different winding direction for correctly
* filling holes. But if some individual countours are disjoint, i.e.
* islands, we have to reorient them so that:
* - The holes have opposite winding direction, already handled by paper
* - Islands have to have the same winding direction as the first child
*
* NOTE: Does NOT handle self-intersecting CompoundPaths.
*/
@ -94,7 +60,7 @@ PathItem.inject(new function() {
}
for (var i = 0; i < length; i++) {
for (var j = 1; j < length; j++) {
if (i !== j && bounds[i].contains(bounds[j]))
if (i !== j && bounds[i].intersects(bounds[j]))
counters[j]++;
}
// Omit the first child
@ -105,124 +71,403 @@ PathItem.inject(new function() {
return path;
}
// Boolean operators return true if a curve with the given winding
// contribution contributes to the final result or not. They are called
// for each curve in the graph after curves in the operands are
// split at intersections.
function computeBoolean(path1, path2, operator, subtract) {
// We do not modify the operands themselves
// The result might not belong to the same type
// i.e. subtraction(A:Path, B:Path):CompoundPath etc.
// Also apply matrices to both paths in case they were transformed.
path1 = reorientPath(path1.clone(false).applyMatrix());
path2 = reorientPath(path2.clone(false).applyMatrix());
var path1Clockwise = path1.isClockwise(),
path2Clockwise = path2.isClockwise(),
// Calculate all the intersections
intersections = path1.getIntersections(path2);
// Split intersections on both paths, by asking the first call to
// collect the intersections on the other path for us and passing the
// result of that on to the second call.
splitPath(splitPath(intersections, true));
// We call reduce() on both cloned paths to simplify compound paths and
// remove empty curves. We also apply matrices to both paths in case
// they were transformed.
var _path1 = reorientPath(path1.clone(false).reduce().applyMatrix());
_path2 = path2 && path1 !== path2
&& reorientPath(path2.clone(false).reduce().applyMatrix());
// Do operator specific calculations before we begin
// Make both paths at clockwise orientation, except when @subtract = true
// We need both paths at opposit orientation for subtraction
if (!path1Clockwise)
path1.reverse();
if (!(subtract ^ path2Clockwise))
path2.reverse();
path1Clockwise = true;
path2Clockwise = !subtract;
var paths = []
.concat(path1._children || [path1])
.concat(path2._children || [path2]),
// Make both paths at clockwise orientation, except when subtract = true
// We need both paths at opposite orientation for subtraction.
if (!_path1.isClockwise())
_path1.reverse();
if (_path2 && !(subtract ^ _path2.isClockwise()))
_path2.reverse();
// Split curves at intersections on both paths. Note that for self
// intersection, _path2 will be null and getIntersections() handles it.
splitPath(_path1.getIntersections(_path2, true));
var chain = [],
windings = [],
lengths = [],
segments = [],
result = new CompoundPath();
// Step 1: Discard invalid links according to the boolean operator
for (var i = 0, l = paths.length; i < l; i++) {
var path = paths[i],
parent = path._parent,
clockwise = path.isClockwise(),
segs = path._segments;
path = parent instanceof CompoundPath ? parent : path;
for (var j = segs.length - 1; j >= 0; j--) {
var segment = segs[j],
midPoint = segment.getCurve().getPoint(0.5),
insidePath1 = path !== path1 && path1.contains(midPoint)
&& (clockwise === path1Clockwise || subtract
|| !testOnCurve(path1, midPoint)),
insidePath2 = path !== path2 && path2.contains(midPoint)
&& (clockwise === path2Clockwise
|| !testOnCurve(path2, midPoint));
if (operator(path === path1, insidePath1, insidePath2)) {
// The segment is to be discarded. Don't add it to segments,
// and mark it as invalid since it might still be found
// through curves / intersections, see below.
segment._invalid = true;
} else {
segments.push(segment);
}
// Aggregate of all curves in both operands, monotonic in y
monoCurves = [];
function collect(paths) {
for (var i = 0, l = paths.length; i < l; i++) {
var path = paths[i];
segments.push.apply(segments, path._segments);
monoCurves.push.apply(monoCurves, path._getMonoCurves());
}
}
// Step 2: Retrieve the resulting paths from the graph
// Collect all segments and monotonic curves
collect(_path1._children || [_path1]);
if (_path2)
collect(_path2._children || [_path2]);
// Propagate the winding contribution. Winding contribution of curves
// does not change between two intersections.
// First, sort all segments with an intersection to the begining.
segments.sort(function(a, b) {
var _a = a._intersection,
_b = b._intersection;
return !_a && !_b || _a && _b ? 0 : _a ? -1 : 1;
});
for (var i = 0, l = segments.length; i < l; i++) {
var segment = segments[i];
if (segment._visited)
if (segment._winding != null)
continue;
var path = new Path(),
loc = segment._intersection,
intersection = loc && loc.getSegment(true);
if (segment.getPrevious()._invalid)
segment.setHandleIn(intersection
? intersection._handleIn
: new Point(0, 0));
// Here we try to determine the most probable winding number
// contribution for this curve-chain. Once we have enough confidence
// in the winding contribution, we can propagate it until the
// intersection or end of a curve chain.
chain.length = windings.length = lengths.length = 0;
var totalLength = 0,
startSeg = segment;
do {
segment._visited = true;
if (segment._invalid && segment._intersection) {
var inter = segment._intersection.getSegment(true);
path.add(new Segment(segment._point, segment._handleIn,
inter._handleOut));
inter._visited = true;
segment = inter;
} else {
path.add(segment.clone());
}
chain.push(segment);
lengths.push(totalLength += segment.getCurve().getLength());
segment = segment.getNext();
} while (segment && !segment._visited && segment !== intersection);
// Avoid stray segments and incomplete paths
var amount = path._segments.length;
if (amount > 1 && (amount > 2 || !path.isPolygon())) {
path.setClosed(true);
result.addChild(path, true);
} else {
path.remove();
} while (segment && !segment._intersection && segment !== startSeg);
// Select the median winding of three random points along this curve
// chain, as a representative winding number. The random selection
// gives a better chance of returning a correct winding than equally
// dividing the curve chain, with the same (amortised) time.
for (var j = 0; j < 3; j++) {
var length = totalLength * Math.random(),
amount = lengths.length;
k = 0;
do {
if (lengths[k] >= length) {
if (k > 0)
length -= lengths[k - 1];
break;
}
} while (++k < amount);
var curve = chain[k].getCurve(),
point = curve.getPointAt(length),
hor = curve.isHorizontal(),
path = curve._path;
if (path._parent instanceof CompoundPath)
path = path._parent;
// While subtracting, we need to omit this curve if this
// curve is contributing to the second operand and is outside
// the first operand.
windings[j] = subtract && _path2
&& (path === _path1 && _path2._getWinding(point, hor)
|| path === _path2 && !_path1._getWinding(point, hor))
? 0
: getWinding(point, monoCurves, hor);
}
windings.sort();
// Assign the median winding to the entire curve chain.
var winding = windings[1];
for (var j = chain.length - 1; j >= 0; j--)
chain[j]._winding = winding;
}
// Remove the proxies
path1.remove();
path2.remove();
// Trace closed contours and insert them into the result.
var result = new CompoundPath();
result.addChildren(tracePaths(segments, operator), true);
// Delete the proxies
_path1.remove();
if (_path2)
_path2.remove();
// And then, we are done.
return result.reduce();
}
function testOnCurve(path, point) {
var curves = path.getCurves(),
bounds = path.getBounds();
if (bounds.contains(point)) {
for (var i = 0, l = curves.length; i < l; i++) {
var curve = curves[i];
if (curve.getBounds().contains(point)
&& curve.getParameterOf(point))
return true;
/**
* Private method for splitting a PathItem at the given intersections.
* The routine works for both self intersections and intersections
* between PathItems.
* @param {CurveLocation[]} intersections Array of CurveLocation objects
*/
function splitPath(intersections) {
var TOLERANCE = /*#=*/ Numerical.TOLERANCE,
linearSegments;
function resetLinear() {
// Reset linear segments if they were part of a linear curve
// and if we are done with the entire curve.
for (var i = 0, l = linearSegments.length; i < l; i++) {
var segment = linearSegments[i];
// FIXME: Don't reset the appropriate handle if the intersection
// was on t == 0 && t == 1.
segment._handleOut.set(0, 0);
segment._handleIn.set(0, 0);
}
}
return false;
for (var i = intersections.length - 1, curve, prevLoc; i >= 0; i--) {
var loc = intersections[i],
t = loc._parameter;
// Check if we are splitting same curve multiple times
if (prevLoc && prevLoc._curve === loc._curve) {
// Scale parameter after previous split.
t /= prevLoc._parameter;
} else {
if (linearSegments)
resetLinear();
curve = loc._curve;
linearSegments = curve.isLinear() && [];
}
var newCurve,
segment;
// Split the curve at t, while ignoring linearity of curves
if (newCurve = curve.divide(t, true, true)) {
segment = newCurve._segment1;
curve = newCurve.getPrevious();
} else {
segment = t < TOLERANCE
? curve._segment1
: t > 1 - TOLERANCE
? curve._segment2
: curve.getPartLength(0, t) < curve.getPartLength(t, 1)
? curve._segment1
: curve._segment2;
}
// Link the new segment with the intersection on the other curve
segment._intersection = loc.getIntersection();
loc._segment = segment;
if (linearSegments)
linearSegments.push(segment);
prevLoc = loc;
}
if (linearSegments)
resetLinear();
}
// Boolean operators are binary operator functions of the form:
// function(isPath1, isInPath1, isInPath2)
//
// Operators return true if a segment in the operands is to be discarded.
// They are called for each segment in the graph after all the intersections
// between the operands are calculated and curves in the operands were split
// at intersections.
return /** @lends Path# */{
/**
* Private method that returns the winding contribution of the given point
* with respect to a given set of monotone curves.
*/
function getWinding(point, curves, horizontal, testContains) {
var TOLERANCE = /*#=*/ Numerical.TOLERANCE,
x = point.x,
y = point.y,
windLeft = 0,
windRight = 0,
roots = [],
abs = Math.abs,
MAX = 1 - TOLERANCE;
// Absolutely horizontal curves may return wrong results, since
// the curves are monotonic in y direction and this is an
// indeterminate state.
if (horizontal) {
var yTop = -Infinity,
yBottom = Infinity,
yBefore = y - TOLERANCE,
yAfter = y + TOLERANCE;
// Find the closest top and bottom intercepts for the same vertical
// line.
for (var i = 0, l = curves.length; i < l; i++) {
var values = curves[i].values;
if (Curve.solveCubic(values, 0, x, roots, 0, 1) > 0) {
for (var j = roots.length - 1; j >= 0; j--) {
var y0 = Curve.evaluate(values, roots[j], 0).y;
if (y0 < yBefore && y0 > yTop) {
yTop = y0;
} else if (y0 > yAfter && y0 < yBottom) {
yBottom = y0;
}
}
}
}
// Shift the point lying on the horizontal curves by
// half of closest top and bottom intercepts.
yTop = (yTop + y) / 2;
yBottom = (yBottom + y) / 2;
if (yTop > -Infinity)
windLeft = getWinding(new Point(x, yTop), curves);
if (yBottom < Infinity)
windRight = getWinding(new Point(x, yBottom), curves);
} else {
var xBefore = x - TOLERANCE,
xAfter = x + TOLERANCE;
// Find the winding number for right side of the curve, inclusive of
// the curve itself, while tracing along its +-x direction.
for (var i = 0, l = curves.length; i < l; i++) {
var curve = curves[i],
values = curve.values,
winding = curve.winding,
next = curve.next;
// Since the curves are monotone in y direction, we can just
// compare the endpoints of the curve to determine if the
// ray from query point along +-x direction will intersect
// the monotone curve. Results in quite significant speedup.
if (winding && (winding === 1
&& y >= values[1] && y <= values[7]
|| y >= values[7] && y <= values[1])
&& Curve.solveCubic(values, 1, y, roots, 0,
// If the next curve is horizontal, we have to include
// the end of this curve to make sure we won't miss an
// intercept.
!next.winding && next.values[1] === y ? 1 : MAX) === 1){
var t = roots[0],
x0 = Curve.evaluate(values, t, 0).x,
slope = Curve.evaluate(values, t, 1).y;
// Take care of cases where the curve and the preceeding
// curve merely touches the ray towards +-x direction, but
// proceeds to the same side of the ray. This essentially is
// not a crossing.
if (abs(slope) < TOLERANCE && !Curve.isLinear(values)
|| t < TOLERANCE && slope * Curve.evaluate(
curve.previous.values, t, 1).y < 0) {
if (testContains && x0 >= xBefore && x0 <= xAfter) {
++windLeft;
++windRight;
}
} else if (x0 <= xBefore) {
windLeft += winding;
} else if (x0 >= xAfter) {
windRight += winding;
}
}
}
}
return Math.max(abs(windLeft), abs(windRight));
}
/**
* Private method to trace closed contours from a set of segments according
* to a set of constraintswinding contribution and a custom operator.
*
* @param {Segment[]} segments Array of 'seed' segments for tracing closed
* contours
* @param {Function} the operator function that receives as argument the
* winding number contribution of a curve and returns a boolean value
* indicating whether the curve should be included in the final contour or
* not
* @return {Path[]} the contours traced
*/
function tracePaths(segments, operator, selfOp) {
// Choose a default operator which will return all contours
operator = operator || function() {
return true;
};
var paths = [],
// Values for getTangentAt() that are almost 0 and 1.
// TODO: Correctly support getTangentAt(0) / (1)?
ZERO = 1e-3,
ONE = 1 - 1e-3;
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
seg = startSeg = segments[i];
if (seg._visited || !operator(seg._winding))
continue;
var path = new Path(Item.NO_INSERT),
inter = seg._intersection,
startInterSeg = inter && inter._segment,
added = false, // Wether a first segment as added already
dir = 1;
do {
var handleIn = dir > 0 ? seg._handleIn : seg._handleOut,
handleOut = dir > 0 ? seg._handleOut : seg._handleIn,
interSeg;
// If the intersection segment is valid, try switching to
// it, with an appropriate direction to continue traversal.
// Else, stay on the same contour.
if (added && (!operator(seg._winding) || selfOp)
&& (inter = seg._intersection)
&& (interSeg = inter._segment)
&& interSeg !== startSeg) {
var c1 = seg.getCurve();
if (dir > 0)
c1 = c1.getPrevious();
var t1 = c1.getTangentAt(dir < 1 ? ZERO : ONE, true),
// Get both curves at the intersection (except the entry
// curves) along with their winding values and tangents.
c4 = interSeg.getCurve(),
c3 = c4.getPrevious(),
t3 = c3.getTangentAt(ONE, true),
t4 = c4.getTangentAt(ZERO, true),
// Cross product of the entry and exit tangent vectors
// at the intersection, will let us select the correct
// countour to traverse next.
w3 = t1.cross(t3),
w4 = t1.cross(t4);
// Do not attempt to switch contours if we aren't absolutely
// sure that there is a possible candidate.
if (w3 * w4 !== 0) {
var curve = w3 < w4 ? c3 : c4,
nextCurve = operator(curve._segment1._winding)
? curve
: w3 < w4 ? c4 : c3,
nextSeg = nextCurve._segment1;
dir = nextCurve === c3 ? -1 : 1;
// If we didn't manage to find a suitable direction for
// next contour to traverse, stay on the same contour.
if (nextSeg._visited && seg._path !== nextSeg._path
|| !operator(nextSeg._winding)) {
dir = 1;
} else {
// Switch to the intersection segment.
seg._visited = interSeg._visited;
seg = interSeg;
if (nextSeg._visited)
dir = 1;
}
} else {
dir = 1;
}
handleOut = dir > 0 ? seg._handleOut : seg._handleIn;
}
// Add the current segment to the path, and mark the added
// segment as visited.
path.add(new Segment(seg._point, added && handleIn, handleOut));
added = true;
seg._visited = true;
// Move to the next segment according to the traversal direction
seg = dir > 0 ? seg.getNext() : seg. getPrevious();
} while (seg && !seg._visited
&& seg !== startSeg && seg !== startInterSeg
&& (seg._intersection || operator(seg._winding)));
// Finish with closing the paths if necessary, correctly linking up
// curves etc.
if (seg && (seg === startSeg || seg === startInterSeg)) {
path.firstSegment.setHandleIn((seg === startInterSeg
? startInterSeg : seg)._handleIn);
path.setClosed(true);
} else {
path.lastSegment._handleOut.set(0, 0);
}
// Add the path to the result.
// Try to avoid stray segments and incomplete paths.
var count = path._segments.length;
if (count > 2 || count === 2 && path._closed && !path.isPolygon())
paths.push(path);
}
return paths;
}
return /** @lends PathItem# */{
/**
* Returns the winding contribution of the given point with respect to
* this PathItem.
*
* @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} 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.
* @return {Number} the winding number
*/
_getWinding: function(point, horizontal, testContains) {
return getWinding(point, this._getMonoCurves(),
horizontal, testContains);
},
/**
* {@grouptitle Boolean Path Operations}
*
@ -233,10 +478,9 @@ PathItem.inject(new function() {
* @return {PathItem} the resulting path item
*/
unite: function(path) {
return computeBoolean(this, path,
function(isPath1, isInPath1, isInPath2) {
return isInPath1 || isInPath2;
});
return computeBoolean(this, path, function(w) {
return w === 1 || w === 0;
}, false);
},
/**
@ -247,10 +491,9 @@ PathItem.inject(new function() {
* @return {PathItem} the resulting path item
*/
intersect: function(path) {
return computeBoolean(this, path,
function(isPath1, isInPath1, isInPath2) {
return !(isInPath1 || isInPath2);
});
return computeBoolean(this, path, function(w) {
return w === 2;
}, false);
},
/**
@ -261,15 +504,13 @@ PathItem.inject(new function() {
* @return {PathItem} the resulting path item
*/
subtract: function(path) {
return computeBoolean(this, path,
function(isPath1, isInPath1, isInPath2) {
return isPath1 && isInPath2 || !isPath1 && !isInPath1;
}, true);
return computeBoolean(this, path, function(w) {
return w === 1;
}, true);
},
// Compound boolean operators combine the basic boolean operations such
// as union, intersection, subtract etc.
// TODO: cache the split objects and find a way to properly clone them!
// as union, intersection, subtract etc.
/**
* Excludes the intersection of the geometry of the specified path with
* this path's geometry and returns the result as a new group item.
@ -280,7 +521,7 @@ PathItem.inject(new function() {
exclude: function(path) {
return new Group([this.subtract(path), path.subtract(this)]);
},
/**
* Splits the geometry of this path along the geometry of the specified
* path returns the result as a new group item.
@ -293,3 +534,122 @@ PathItem.inject(new function() {
}
};
});
Path.inject(/** @lends Path# */{
/**
* Private method that returns and caches all the curves in this Path, which
* are monotonically decreasing or increasing in the y-direction.
* Used by getWinding().
*/
_getMonoCurves: function() {
var monoCurves = this._monoCurves,
prevCurve;
// Insert curve values into a cached array
function insertCurve(v) {
var y0 = v[1],
y1 = v[7],
curve = {
values: v,
winding: y0 === y1
? 0 // Horizontal
: y0 > y1
? -1 // Decreasing
: 1, // Increasing
// Add a reference to neighboring curves.
previous: prevCurve,
next: null // Always set it for hidden class optimization.
};
if (prevCurve)
prevCurve.next = curve;
monoCurves.push(curve);
prevCurve = curve;
}
// Handle bezier curves. We need to chop them into smaller curves with
// defined orientation, by solving the derivative curve for y extrema.
function handleCurve(v) {
// Filter out curves of zero length.
// TODO: Do not filter this here.
if (Curve.getLength(v) === 0)
return;
var y0 = v[1],
y1 = v[3],
y2 = v[5],
y3 = v[7];
if (Curve.isLinear(v)) {
// Handling linear curves is easy.
insertCurve(v);
} else {
// Split the curve at y extrema, to get bezier curves with clear
// orientation: Calculate the derivative and find its roots.
var a = 3 * (y1 - y2) - y0 + y3,
b = 2 * (y0 + y2) - 4 * y1,
c = y1 - y0,
TOLERANCE = /*#=*/ Numerical.TOLERANCE,
roots = [];
// Keep then range to 0 .. 1 (excluding) in the search for y
// extrema.
var count = Numerical.solveQuadratic(a, b, c, roots, TOLERANCE,
1 - TOLERANCE);
if (count === 0) {
insertCurve(v);
} else {
roots.sort();
var t = roots[0],
parts = Curve.subdivide(v, t);
insertCurve(parts[0]);
if (count > 1) {
// If there are two extremas, renormalize t to the range
// of the second range and split again.
t = (roots[1] - t) / (1 - t);
// Since we already processed parts[0], we can override
// the parts array with the new pair now.
parts = Curve.subdivide(parts[1], t);
insertCurve(parts[0]);
}
insertCurve(parts[1]);
}
}
}
if (!monoCurves) {
// Insert curves that are monotonic in y direction into cached array
monoCurves = this._monoCurves = [];
var curves = this.getCurves(),
segments = this._segments;
for (var i = 0, l = curves.length; i < l; i++)
handleCurve(curves[i].getValues());
// If the path is not closed, we need to join the end points with a
// straight line, just like how filling open paths works.
if (!this._closed && segments.length > 1) {
var p1 = segments[segments.length - 1]._point,
p2 = segments[0]._point,
p1x = p1._x, p1y = p1._y,
p2x = p2._x, p2y = p2._y;
handleCurve([p1x, p1y, p1x, p1y, p2x, p2y, p2x, p2y]);
}
// Link first and last curves
var first = monoCurves[0],
last = monoCurves[monoCurves.length - 1];
first.previous = last;
last.next = first;
}
return monoCurves;
}
});
CompoundPath.inject(/** @lends CompoundPath# */{
/**
* Private method that returns all the curves in this CompoundPath, which
* are monotonically decreasing or increasing in the 'y' direction.
* Used by getWinding().
*/
_getMonoCurves: function() {
var children = this._children,
monoCurves = [];
for (var i = 0, l = children.length; i < l; i++)
monoCurves.push.apply(monoCurves, children[i]._getMonoCurves());
return monoCurves;
}
});

View file

@ -35,8 +35,6 @@ var PathItem = Item.extend(/** @lends PathItem# */{
* @function
*
* @param {PathItem} path the other item to find the intersections with
* @param {Boolean} [sorted=true] controls wether to sort the results by
* offset
* @return {CurveLocation[]} the locations of all intersection between the
* paths
* @example {@paperscript}
@ -64,45 +62,121 @@ var PathItem = Item.extend(/** @lends PathItem# */{
* }
* }
*/
getIntersections: function(path, sorted) {
getIntersections: function(path, _expand) {
// NOTE: For self-intersection, path is null. This means you can also
// just call path.getIntersections() without an argument to get self
// intersections.
if (this === path)
path = null;
// First check the bounds of the two paths. If they don't intersect,
// we don't need to iterate through their curves.
if (!this.getBounds().touches(path.getBounds()))
if (path && !this.getBounds().touches(path.getBounds()))
return [];
var locations = [],
curves1 = this.getCurves(),
curves2 = path.getCurves(),
curves2 = path ? path.getCurves() : curves1,
matrix1 = this._matrix.orNullIfIdentity(),
matrix2 = path._matrix.orNullIfIdentity(),
matrix2 = path ? path._matrix.orNullIfIdentity() : matrix1,
length1 = curves1.length,
length2 = curves2.length,
values2 = [];
length2 = path ? curves2.length : length1,
values2 = [],
MIN = /*#=*/ Numerical.EPSILON,
MAX = 1 - /*#=*/ Numerical.EPSILON;
for (var i = 0; i < length2; i++)
values2[i] = curves2[i].getValues(matrix2);
for (var i = 0; i < length1; i++) {
var curve1 = curves1[i],
values1 = curve1.getValues(matrix1);
for (var j = 0; j < length2; j++)
Curve.getIntersections(values1, values2[j], curve1, curves2[j],
locations);
values1 = path ? curve1.getValues(matrix1) : values2[i];
if (!path) {
// First check for self-intersections within the same curve
var seg1 = curve1.getSegment1(),
seg2 = curve1.getSegment2(),
h1 = seg1._handleOut,
h2 = seg2._handleIn;
// Check if extended handles of endpoints of this curve
// intersects each other. We cannot have a self intersection
// within this curve if they don't intersect due to convex-hull
// property.
if (new Line(seg1._point.subtract(h1), h1.multiply(2), true)
.intersect(new Line(seg2._point.subtract(h2),
h2.multiply(2), true), false)) {
// Self intersectin is found by dividng the curve in two and
// and then applying the normal curve intersection code.
var parts = Curve.subdivide(values1);
Curve.getIntersections(
parts[0], parts[1], curve1, curve1, locations,
function(loc) {
if (loc._parameter <= MAX) {
// Since the curve was split above, we need to
// adjust the parameters for both locations.
loc._parameter /= 2;
loc._parameter2 = 0.5 + loc._parameter2 / 2;
return true;
}
}
);
}
}
// Check for intersections with other curves. For self intersection,
// we can start at i + 1 instead of 0
for (var j = path ? 0 : i + 1; j < length2; j++) {
Curve.getIntersections(
values1, values2[j], curve1, curves2[j], locations,
// Avoid end point intersections on consecutive curves whe
// self intersecting.
!path && (j === i + 1 || j === length2 - 1 && i === 0)
&& function(loc) {
var t = loc._parameter;
return t >= MIN && t <= MAX;
}
);
}
}
if (sorted || sorted === undefined) {
// Now sort the results into the right sequence.
// TODO: Share this code with PathItem.Boolean.js, potentially by
// using the new BinHeap class that's in preparation.
locations.sort(function(loc1, loc2) {
var path1 = loc1.getPath(),
path2 = loc2.getPath();
return path1 === path2
// We can add parameter (0 <= t <= 1) to index (integer)
// to compare both at the same time
? (loc1.getIndex() + loc1.getParameter())
// Now filter the locations and process _expand:
var last = locations.length - 1;
// Merge intersections very close to the end of a curve to the begining
// of the next curve.
for (var i = last; i >= 0; i--) {
var loc = locations[i],
next = loc._curve.getNext(),
next2 = loc._curve2.getNext();
if (next && loc._parameter >= MAX) {
loc._parameter = 0;
loc._curve = next;
}
if (next2 && loc._parameter2 >= MAX) {
loc._parameter2 = 0;
loc._curve2 = next2;
}
}
// Compare helper to filter locations
function compare(loc1, loc2) {
var path1 = loc1.getPath(),
path2 = loc2.getPath();
return path1 === path2
// We can add parameter (0 <= t <= 1) to index
// (a integer) to compare both at the same time
? (loc1.getIndex() + loc1.getParameter())
- (loc2.getIndex() + loc2.getParameter())
// Sort by path index to group all locations on the same
// path in the sequnence that they are encountered
// within compound paths.
: path1._index - path2._index;
});
// Sort by path id to group all locations on the same path.
: path1._id - path2._id;
}
if (last > 0) {
locations.sort(compare);
// Filter out duplicate locations
for (var i = last; i >= 0; i--) {
if (locations[i].equals(locations[i === 0 ? last : i - 1])) {
locations.splice(i, 1);
last--;
}
}
}
if (_expand) {
for (var i = last; i >= 0; i--)
locations.push(locations[i].getIntersection());
locations.sort(compare);
}
return locations;
},
@ -216,7 +290,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
_contains: function(point) {
// NOTE: point is reverse transformed by _matrix, so we don't need to
// apply here.
/*#*/ if (__options.nativeContains) {
/*#*/ if (__options.nativeContains || !__options.booleanOperations) {
// To compare with native canvas approach:
var ctx = CanvasProvider.getContext(1, 1);
// Abuse clip = true to get a shape for ctx.isPointInPath().
@ -224,11 +298,11 @@ var PathItem = Item.extend(/** @lends PathItem# */{
var res = ctx.isPointInPath(point.x, point.y, this.getWindingRule());
CanvasProvider.release(ctx);
return res;
/*#*/ } else { // !__options.nativeContains
var winding = this._getWinding(point);
/*#*/ } else { // !__options.nativeContains && __options.booleanOperations
var winding = this._getWinding(point, false, true);
return !!(this.getWindingRule() === 'evenodd' ? winding & 1 : winding);
/*#*/ } // !__options.nativeContains
}
/*#*/ } // !__options.nativeContains && __options.booleanOperations
},
/**
* Smooth bezier curves without changing the amount of segments or their

View file

@ -279,7 +279,7 @@ var Segment = Base.extend(/** @lends Segment# */{
var next = this.getNext(),
handle1 = this._handleOut,
handle2 = next._handleIn,
kappa = Numerical.KAPPA;
kappa = /*#=*/ Numerical.KAPPA;
if (handle1.isOrthogonal(handle2)) {
var from = this._point,
to = next._point,
@ -444,6 +444,7 @@ var Segment = Base.extend(/** @lends Segment# */{
/**
* Removes the segment from the path that it belongs to.
* @return {Boolean} {@true if the segment was removed}
*/
remove: function() {
return this._path ? !!this._path.removeSegment(this._index) : false;

View file

@ -183,7 +183,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
// NOTE: If there is no layer and this project is not the active
// one, passing insert: false and calling addChild on the
// project will handle it correctly.
|| this.addChild(new Layer({ insert: false }))).addChild(child);
|| this.addChild(new Layer(Item.NO_INSERT))).addChild(child);
} else {
child = null;
}
@ -382,6 +382,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* @return {SVGSVGElement} the project converted to an SVG node
*/
// DOCS: Document importSVG('file.svg', callback);
/**
* Converts the provided SVG content into Paper.js items and adds them to
* the active layer of this project.
@ -423,7 +424,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* @type Symbol[]
*/
draw: function(ctx, matrix, ratio) {
draw: function(ctx, matrix, pixelRatio) {
// Increase the _updateVersion before the draw-loop. After that, items
// that are visible will have their _updateVersion set to the new value.
this._updateVersion++;
@ -433,7 +434,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
// values
var param = new Base({
offset: new Point(0, 0),
ratio: ratio,
pixelRatio: pixelRatio,
// Tell the drawing routine that we want to track nested matrices
// in param.transforms, and that we want it to set _globalMatrix
// as used below. Item#rasterize() and Raster#getAverageColor() do

View file

@ -177,7 +177,7 @@ var Gradient = Base.extend(/** @lends Gradient# */{
* Checks whether the gradient is equal to the supplied gradient.
*
* @param {Gradient} gradient
* @return {Boolean} {@true they are equal}
* @return {Boolean} {@true if they are equal}
*/
equals: function(gradient) {
if (gradient === this)

View file

@ -534,13 +534,14 @@ new function() {
}
if (isRoot) {
// See if it's a string but handle markup separately
if (typeof source === 'string' && !/^.*</.test(source)) {
/*#*/ if (__options.environment == 'browser') {
// First see if we're meant to import an element with the given
// id.
node = document.getElementById(source);
// Check if the string does not represent SVG data, in which
// case it must be a url of a SVG to be loaded.
// case it must be the URL of a SVG to be loaded.
if (node) {
source = null;
} else {

View file

@ -59,7 +59,7 @@ var PointText = TextItem.extend(/** @lends PointText# */{
},
clone: function(insert) {
return this._clone(new PointText({ insert: false }), insert);
return this._clone(new PointText(Item.NO_INSERT), insert);
},
/**

View file

@ -45,7 +45,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
this._context = canvas.getContext('2d');
// Have Item count installed mouse events.
this._eventCounters = {};
this._ratio = 1;
this._pixelRatio = 1;
/*#*/ if (__options.environment == 'browser') {
if (PaperScope.getAttribute(canvas, 'hidpi') !== 'off') {
// Hi-DPI Canvas support based on:
@ -53,7 +53,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
var deviceRatio = window.devicePixelRatio || 1,
backingStoreRatio = DomElement.getPrefixValue(this._context,
'backingStorePixelRatio') || 1;
this._ratio = deviceRatio / backingStoreRatio;
this._pixelRatio = deviceRatio / backingStoreRatio;
}
/*#*/ } // __options.environment == 'browser'
View.call(this, canvas);
@ -62,18 +62,18 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
_setViewSize: function(size) {
var width = size.width,
height = size.height,
ratio = this._ratio,
pixelRatio = this._pixelRatio,
element = this._element,
style = element.style;
// Upscale the canvas if the two ratios don't match.
element.width = width * ratio;
element.height = height * ratio;
if (ratio !== 1) {
element.width = width * pixelRatio;
element.height = height * pixelRatio;
if (pixelRatio !== 1) {
style.width = width + 'px';
style.height = height + 'px';
// Now scale the context to counter the fact that we've manually
// scaled our canvas element.
this._context.scale(ratio, ratio);
this._context.scale(pixelRatio, pixelRatio);
}
},
@ -91,7 +91,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
var ctx = this._context,
size = this._viewSize;
ctx.clearRect(0, 0, size.width + 1, size.height + 1);
this._project.draw(ctx, this._matrix, this._ratio);
this._project.draw(ctx, this._matrix, this._pixelRatio);
this._project._needsUpdate = false;
return true;
}

View file

@ -30,6 +30,9 @@ var View = Base.extend(Callback, /** @lends View# */{
this._element = element;
var size;
/*#*/ if (__options.environment == 'browser') {
// Sub-classes may set _pixelRatio first
if (!this._pixelRatio)
this._pixelRatio = window.devicePixelRatio || 1;
// Generate an id for this view / element if it does not have one
this._id = element.getAttribute('id');
if (this._id == null)
@ -60,7 +63,7 @@ var View = Base.extend(Callback, /** @lends View# */{
DomEvent.add(window, this._windowHandlers);
} else {
// Try visible size first, since that will help handling previously
// scaled canvases (e.g. when dealing with ratio)
// scaled canvases (e.g. when dealing with pixel-ratio)
size = DomElement.getSize(element);
// If the element is invisible, we cannot directly access
// element.width / height, because they would appear 0.
@ -88,6 +91,9 @@ var View = Base.extend(Callback, /** @lends View# */{
document.body.appendChild(stats);
}
/*#*/ } else if (__options.environment == 'node') {
// Sub-classes may set _pixelRatio first
if (!this._pixelRatio)
this._pixelRatio = 1;
// Generate an id for this view
this._id = 'view-' + View._id++;
size = new Size(element.width, element.height);
@ -278,6 +284,32 @@ var View = Base.extend(Callback, /** @lends View# */{
return this._element;
},
/**
* The ratio between physical pixels and device-independent pixels (DIPs)
* of the underlying canvas / device.
* It is {@code 1} for normal displays, and {@code 2} or more for
* high-resolution displays.
*
* @type Number
* @bean
*/
getPixelRatio: function() {
return this._pixelRatio;
},
/**
* The resoltuion of the underlying canvas / device in pixel per inch (DPI).
* It is {@code 72} for normal displays, and {@code 144} for high-resolution
* displays with a pixel-ratio of {@code 2}.
*
* @type Number
* @bean
*/
getResolution: function() {
return this._pixelRatio * 72;
},
/**
* The size of the view. Changing the view's size will resize it's
* underlying element.

View file

@ -401,5 +401,5 @@ function compareProjects(project, project2) {
function createSVG(xml) {
return new DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg">' + xml + '</svg>',
'application/xml');
'text/xml');
}

View file

@ -18,7 +18,7 @@ test('Decomposition: rotate()', function() {
equals(m.getRotation(), Base.pick(ea, a),
s + '.getRotation()',
Numerical.TOLERANCE);
equals(m.getScaling(), new Point(1, 1),
comparePoints(m.getScaling(), new Point(1, 1),
s + '.getScaling()');
}
@ -41,11 +41,12 @@ test('Decomposition: scale()', function() {
function testScale(sx, sy, ex, ey, ea) {
var m = new Matrix().scale(sx, sy),
s = 'new Matrix().scale(' + sx + ', ' + sy + ')';
equals(m.getScaling(), new Point(Base.pick(ex, sx), Base.pick(ey, sy)),
s + '.getScaling()');
equals(m.getRotation(), ea || 0,
s + '.getRotation()',
Numerical.TOLERANCE);
s + '.getRotation()',
Numerical.TOLERANCE);
comparePoints(m.getScaling(), new Point(Base.pick(ex, sx),
Base.pick(ey, sy)),
s + '.getScaling()');
}
testScale(1, 1);
@ -63,11 +64,12 @@ test('Decomposition: rotate() & scale()', function() {
function testAngleAndScale(sx, sy, a, ex, ey, ea) {
var m = new Matrix().scale(sx, sy).rotate(a),
s = 'new Matrix().scale(' + sx + ', ' + sy + ').rotate(' + a + ')';
equals(m.getScaling(), new Point(Base.pick(ex, sx), Base.pick(ey, sy)),
s + '.getScaling()');
equals(m.getRotation(), ea || a,
s + '.getRotation()',
Numerical.TOLERANCE);
s + '.getRotation()',
Numerical.TOLERANCE);
comparePoints(m.getScaling(), new Point(Base.pick(ex, sx),
Base.pick(ey, sy)),
s + '.getScaling()');
}
testAngleAndScale(2, 4, 45);

View file

@ -10,11 +10,11 @@
* All rights reserved.
*/
module('Item Contains');
module('PathItem Contains');
function testPoint(item, point, inside) {
equals(item.contains(point), inside, 'The point ' + point
+ ' should be ' + (inside ? 'inside' : 'outside') + '.');
function testPoint(item, point, inside, message) {
equals(item.contains(point), inside, message || ('The point ' + point
+ ' should be ' + (inside ? 'inside' : 'outside') + '.'));
}
test('Path#contains() (Regular Polygon)', function() {
@ -96,29 +96,40 @@ test('CompoundPath#contains() (Donut)', function() {
new Path.Circle([0, 0], 25)
]);
equals(path.contains(new Point(0, 0)), false,
testPoint(path, new Point(0, -50), true,
'The top center point of the outer circle should be inside the donut.');
testPoint(path, new Point(0, 0), false,
'The center point should be outside the donut.');
equals(path.contains(new Point(-35, 0)), true,
testPoint(path, new Point(-35, 0), true,
'A vertically centered point on the left side should be inside the donut.');
equals(path.contains(new Point(35, 0)), true,
testPoint(path, new Point(35, 0), true,
'A vertically centered point on the right side should be inside the donut.');
equals(path.contains(new Point(0, 49)), true,
testPoint(path, new Point(0, 49), true,
'The near bottom center point of the outer circle should be inside the donut.');
equals(path.contains(new Point(0, 50)), true,
testPoint(path, new Point(0, 50), true,
'The bottom center point of the outer circle should be inside the donut.');
equals(path.contains(new Point(0, 51)), false,
testPoint(path, new Point(0, 51), false,
'The near bottom center point of the outer circle should be outside the donut.');
equals(path.contains(new Point({ length: 50, angle: 30 })), true,
testPoint(path, new Point({ length: 50, angle: 30 }), true,
'A random point on the periphery of the outer circle should be inside the donut.');
equals(path.contains(new Point(0, 25)), false,
'The bottom center point of the inner circle should be outside the donut.');
equals(path.contains(new Point({ length: 25, angle: 30 })), false,
'A random point on the periphery of the inner circle should be outside the donut.');
equals(path.contains(new Point(-50, -50)), false,
// False positive and negatives.
// testPoint(path, new Point(0, 25), false,
// 'The bottom center point of the inner circle should be outside the donut.');
// testPoint(path, new Point({ length: 25, angle: 30 }), false,
// 'A random point on the periphery of the inner circle should be outside the donut.');
testPoint(path, new Point(0, 25), true,
'The bottom center point of the inner circle should be inside the donut.');
testPoint(path, new Point({x: 21.654222720313882, y: 12.502112923650227}), true,
'A random point on the periphery of the inner circle should be inside the donut.');
testPoint(path, new Point(-50, -50), false,
'The top left point of bounding box should be outside the donut.');
equals(path.contains(new Point(-50, 50)), false,
testPoint(path, new Point(50, -50), false,
'The top right point of the bounding box should be inside the donut.');
testPoint(path, new Point(-50, 50), false,
'The bottom left point of bounding box should be outside the donut.');
equals(path.contains(new Point(-45, 45)), false,
testPoint(path, new Point(50, 50), false,
'The bottom right point of the bounding box should be inside the donut.');
testPoint(path, new Point(-45, 45), false,
'The near bottom left point of bounding box should be outside the donut.');
});
@ -188,4 +199,19 @@ test('Path#contains() (touching stationary point with changing orientation)', fu
});
testPoint(path, new Point(200, 200), true);
})
});
test('Path#contains() (complex shape)', function() {
var path = new Path({
pathData: 'M301 162L307 154L315 149L325 139.5L332.5 135.5L341 128.5L357.5 117.5L364.5 114.5L368.5 110.5L380 105.5L390.5 102L404 96L410.5 96L415 97.5L421 104L425.5 113.5L428.5 126L429.5 134L429.5 141L429.5 148L425.5 161.5L425.5 169L414 184.5L409.5 191L401 201L395 209L386 214.5L378.5 217L368 220L348 219.5L338 218L323.5 212.5L312 205.5L302.5 197.5L295.5 189L291.5 171.5L294 168L298 165.5L301 162z',
fillColor: 'blue',
strokeColor: 'green',
strokeWidth: 2
});
testPoint(path, new Point(360, 160), true);
testPoint(path, new Point(377, 96), false);
testPoint(path, new Point(410, 218), false);
testPoint(path, new Point(431, 104), false);
});

View file

@ -88,7 +88,17 @@ test('path.strokeBounds on closed path with single segment and stroke color', fu
]);
path.strokeColor = 'black';
path.closed = true;
compareRectangles(path.strokeBounds, { x: 120.5, y: 312.88324 , width: 19.91643, height: 30.53977 });
compareRectangles(path.strokeBounds, { x: 120.44098, y: 312.88324 , width: 19.97544, height: 30.53977 });
});
test('path.strokeBounds with corners and miter limit', function() {
var path = new Path({
pathData: 'M47,385c120,-100 120,-100 400,-40c-280,140 -280,140 -400,40z',
strokeWidth: 5,
strokeJoin: "miter",
strokeColor: "black"
});
compareRectangles(path.strokeBounds, { x: 43.09488, y: 301.5525, width: 411.3977, height: 156.57543 });
});
test('path.bounds & path.strokeBounds with stroke styles', function() {

View file

@ -130,3 +130,33 @@ test('equals()', function() {
return new Point(0, 0).equals(null);
}, false);
});
test('isColinear()', function() {
equals(function() {
return new Point(10, 5).isColinear(new Point(20, 10));
}, true);
equals(function() {
return new Point(5, 10).isColinear(new Point(-5, -10));
}, true);
equals(function() {
return new Point(10, 10).isColinear(new Point(20, 10));
}, false);
equals(function() {
return new Point(10, 10).isColinear(new Point(10, -10));
}, false);
});
test('isOrthogonal()', function() {
equals(function() {
return new Point(10, 5).isOrthogonal(new Point(5, -10));
}, true);
equals(function() {
return new Point(5, 10).isOrthogonal(new Point(-10, 5));
}, true);
equals(function() {
return new Point(10, 10).isOrthogonal(new Point(20, 20));
}, false);
equals(function() {
return new Point(10, 10).isOrthogonal(new Point(10, -20));
}, false);
});

View file

@ -23,7 +23,6 @@
/*#*/ include('Item_Cloning.js');
/*#*/ include('Item_Order.js');
/*#*/ include('Item_Bounds.js');
/*#*/ include('Item_Contains.js');
/*#*/ include('Layer.js');
/*#*/ include('Group.js');
@ -39,6 +38,8 @@
/*#*/ include('Path_Length.js');
/*#*/ include('CompoundPath.js');
/*#*/ include('PathItem_Contains.js');
/*#*/ include('PlacedSymbol.js');
/*#*/ include('Raster.js');