Convert CurveFlattener to PathFlattener, which can handle drawing of parts accross curve boundaries.

This commit is contained in:
Jürg Lehni 2011-06-04 18:25:41 +01:00
parent e097ff1303
commit fafb6d1d76
6 changed files with 165 additions and 141 deletions

View file

@ -50,12 +50,12 @@ var sources = [
'src/path/SegmentPoint.js',
'src/path/SelectionState.js',
'src/path/Curve.js',
'src/path/CurveFlattener.js',
'src/path/CurveLocation.js',
'src/path/PathItem.js',
'src/path/Path.js',
'src/path/CompoundPath.js',
'src/path/Path.Constructors.js',
'src/path/PathFlattener.js',
'src/text/ParagraphStyle.js',
'src/text/CharacterStyle.js',

View file

@ -67,12 +67,12 @@ var paper = new function() {
//#include "path/SegmentPoint.js"
//#include "path/SelectionState.js"
//#include "path/Curve.js"
//#include "path/CurveFlattener.js"
//#include "path/CurveLocation.js"
//#include "path/PathItem.js"
//#include "path/Path.js"
//#include "path/CompoundPath.js"
//#include "path/Path.Constructors.js"
//#include "path/PathFlattener.js"
//#include "text/ParagraphStyle.js"
//#include "text/CharacterStyle.js"

View file

@ -195,16 +195,7 @@ var Curve = this.Curve = Base.extend({
},
getCurveValues: function() {
var p1 = this._segment1._point,
h1 = this._segment1._handleOut,
h2 = this._segment2._handleIn,
p2 = this._segment2._point;
return [
p1.x, p1.y,
p1.x + h1.x, p1.y + h1.y,
p2.x + h2.x, p2.y + h2.y,
p2.x, p2.y
];
return Curve.getCurveValues(this._segment1, this._segment2);
},
// DOCS: document Curve#getLength(from, to)
@ -445,6 +436,19 @@ var Curve = this.Curve = Base.extend({
},
statics: {
getCurveValues: function(segment1, segment2) {
var p1 = segment1._point,
h1 = segment1._handleOut,
h2 = segment2._handleIn,
p2 = segment2._point;
return [
p1.x, p1.y,
p1.x + h1.x, p1.y + h1.y,
p2.x + h2.x, p2.y + h2.y,
p2.x, p2.y
];
},
getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, a, b) {
if (a === undefined)
a = 0;
@ -529,6 +533,25 @@ var Curve = this.Curve = Base.extend({
];
},
// TODO: Find better name
getPart: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, from, to) {
var curve = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
if (from > 0) {
// 8th argument of Curve.subdivide() == t, and values can be
// directly used as arguments list for apply().
curve[8] = from;
curve = Curve.subdivide.apply(Curve, curve)[1]; // right
}
if (to < 1) {
// Se above about curve[8].
// Interpolate the parameter at 'to' in the new curve and
// cut there
curve[8] = (to - from) / (1 - from);
curve = Curve.subdivide.apply(Curve, curve)[0]; // left
}
return curve;
},
isSufficientlyFlat: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
// Inspired by Skia, but to be tested:
// Calculate 1/3 (m1) and 2/3 (m2) along the line between start

View file

@ -1,100 +0,0 @@
/*
* Paper.js
*
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
* based on Scriptographer.org and designed to be largely API compatible.
* http://paperjs.org/
* http://scriptographer.org/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
* http://lehni.org/ & http://jonathanpuckey.com/
*
* All rights reserved.
*/
var CurveFlattener = Base.extend({
initialize: function(curve) {
this.parts = [];
this.length = 0;
// Keep a current index from where we last where in getParameter(), to
// optimise for iterator-like usage of the flattener.
this.index = 0;
this.curve = curve.getCurveValues();
this._computeParts(this.curve, 0, 1);
},
_computeParts: function(values, minT, maxT) {
// Check if the t-span is big enough for subdivision.
// We're not subdividing more than 32 times...
if ((maxT - minT) > 1 / 32
&& !Curve.isSufficientlyFlat.apply(Curve, values)) {
var curves = Curve.subdivide.apply(Curve, values);
var halfT = (minT + maxT) / 2;
// Recursively subdive and compute parts again.
this._computeParts(curves[0], minT, halfT);
this._computeParts(curves[1], halfT, maxT);
} else {
// Calculate distance between p1 and p2
var x = values[6] - values[0],
y = values[7] - values[1],
dist = Math.sqrt(x * x + y * y);
if (dist > Numerical.TOLERANCE) {
this.length += dist;
this.parts.push({
length: this.length,
value: maxT
});
}
}
},
getParameter: function(length) {
// Make sure we're not beyond the requested length already. Search the
// start position backwards from where to then process the loop below.
var i, j = this.index;
for (;;) {
i = j;
if (j == 0 || this.parts[--j].length < length)
break;
}
// Find the segment that succeeds the given length, then interpolate
// with the previous segment
for (var l = this.parts.length; i < l; i++) {
var segment = this.parts[i];
if (segment.length >= length) {
this.index = i;
var prev = this.parts[i - 1],
prevValue = prev ? prev.value : 0,
prevLength = prev ? prev.length : 0;
// Interpolate
return prevValue + (segment.value - prevValue)
* (length - prevLength) / (segment.length - prevLength);
}
}
return 1;
},
drawDash: function(ctx, from, to, moveTo) {
from = this.getParameter(from);
to = this.getParameter(to);
var curve = this.curve;
if (from > 0) {
// 8th argument of Curve.subdivide() == t, and values can be
// directly used as arguments list for apply().
curve[8] = from; // See above
curve = Curve.subdivide.apply(Curve, curve)[1]; // right
}
if (moveTo) {
ctx.moveTo(curve[0], curve[1]);
}
if (to < 1) {
// Se above about curve[8].
// Interpolate the parameter at 'to' in the new curve and cut there
curve[8] = (to - from) / (1 - from);
curve = Curve.subdivide.apply(Curve, curve)[0]; // left
}
ctx.bezierCurveTo.apply(ctx, curve.slice(2));
}
});

View file

@ -760,34 +760,14 @@ var Path = this.Path = PathItem.extend({
drawSegment(0);
}
function drawDashes(ctx, curves, dashArray, dashOffset) {
var length = 0,
function drawDashes(ctx, path, dashArray, dashOffset) {
var flattener = new PathFlattener(path),
from = dashOffset, to,
open = false;
for (var i = 0, j = 0, l = curves.length; i < l; i++) {
var curve = curves[i];
var flattener = new CurveFlattener(curve);
length = flattener.length;
while (true) {
if (open) {
flattener.drawDash(ctx, from, to, false);
from = to + dashArray[(j++) % dashArray.length];
open = false;
}
to = from + dashArray[(j++) % dashArray.length];
flattener.drawDash(ctx, from, to, true);
if (to > length) {
from = 0;
to -= length;
open = true;
break;
}
from = to + dashArray[(j++) % dashArray.length];
if (from >= length) {
from -= length;
break;
}
}
i = 0;
while (from < flattener.length) {
to = from + dashArray[(i++) % dashArray.length];
flattener.drawPart(ctx, from, to, true);
from = to + dashArray[(i++) % dashArray.length];
}
}
@ -834,8 +814,7 @@ var Path = this.Path = PathItem.extend({
// We cannot use the path created by drawSegments above
// Use CurveFlatteners to draw dashed paths:
ctx.beginPath();
drawDashes(ctx, this.getCurves(), dashArray,
this.getDashOffset());
drawDashes(ctx, this, dashArray, this.getDashOffset());
}
ctx.stroke();
}

122
src/path/PathFlattener.js Normal file
View file

@ -0,0 +1,122 @@
/*
* Paper.js
*
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
* based on Scriptographer.org and designed to be largely API compatible.
* http://paperjs.org/
* http://scriptographer.org/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
* http://lehni.org/ & http://jonathanpuckey.com/
*
* All rights reserved.
*/
var PathFlattener = Base.extend({
initialize: function(path) {
this.parts = [];
this.curves = [];
this.length = 0;
// Keep a current index from the part where we last where in
// getParameter(), to optimise for iterator-like usage of the flattener.
this.index = 0;
var segments = path._segments,
segment1 = segments[0],
segment2,
that = this;
function addCurve(segment1, segment2) {
var curve = Curve.getCurveValues(segment1, segment2);
that.curves.push(curve);
that._computeParts(curve, segment1._index, 0, 1);
}
for (var i = 1, l = segments.length; i < l; i++) {
segment2 = segments[i];
addCurve(segment1, segment2);
segment1 = segment2;
}
if (path._closed)
addCurve(segment2, segments[0]);
},
_computeParts: function(curve, index, minT, maxT) {
// Check if the t-span is big enough for subdivision.
// We're not subdividing more than 32 times...
if ((maxT - minT) > 1 / 32
&& !Curve.isSufficientlyFlat.apply(Curve, curve)) {
var curves = Curve.subdivide.apply(Curve, curve);
var halfT = (minT + maxT) / 2;
// Recursively subdive and compute parts again.
this._computeParts(curves[0], index, minT, halfT);
this._computeParts(curves[1], index, halfT, maxT);
} else {
// Calculate distance between p1 and p2
var x = curve[6] - curve[0],
y = curve[7] - curve[1],
dist = Math.sqrt(x * x + y * y);
if (dist > Numerical.TOLERANCE) {
this.length += dist;
this.parts.push({
length: this.length,
value: maxT,
index: index
});
}
}
},
getParameter: function(length) {
// Make sure we're not beyond the requested length already. Search the
// start position backwards from where to then process the loop below.
var i, j = this.index;
for (;;) {
i = j;
if (j == 0 || this.parts[--j].length < length)
break;
}
// Find the part that succeeds the given length, then interpolate
// with the previous part
for (var l = this.parts.length; i < l; i++) {
var part = this.parts[i];
if (part.length >= length) {
this.index = i;
var prev = this.parts[i - 1];
// Make sure we only use the previous parameter value if its
// for the same curve, by checking index. Use 0 otherwise.
var prevVal = prev && prev.index == part.index ? prev.value : 0,
prevLen = prev ? prev.length : 0;
return {
// Interpolate
value: prevVal + (part.value - prevVal)
* (length - prevLen) / (part.length - prevLen),
index: part.index
};
}
}
// Return last one
var part = this.parts[this.parts.length - 1];
return {
value: 1,
index: part.index
};
},
drawPart: function(ctx, from, to, moveTo) {
from = this.getParameter(from);
to = this.getParameter(to);
for (var i = from.index; i <= to.index; i++) {
var curve = Curve.getPart.apply(Curve, this.curves[i].concat(
i == from.index ? from.value : 0,
i == to.index ? to.value : 1));
if (moveTo) {
ctx.moveTo(curve[0], curve[1]);
moveTo = false;
}
ctx.bezierCurveTo.apply(ctx, curve.slice(2));
}
}
});