Implement dashed stroke support. Work in progress.

This commit is contained in:
Jürg Lehni 2011-06-04 16:08:40 +01:00
parent e5290c3f47
commit 6c74ace1ed
2 changed files with 87 additions and 13 deletions

View file

@ -1,9 +1,12 @@
var CurveFlattener = Base.extend({
initialize: function(curve) {
this.curve = curve;
this.segments = [];
this.parts = [];
this.length = 0;
this._computeSegments(curve.getCurveValues(), 0, 1);
// 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._computeSegments(this.curve, 0, 1);
},
_computeSegments: function(values, minT, maxT) {
@ -13,7 +16,7 @@ var CurveFlattener = Base.extend({
&& !Curve.isSufficientlyFlat.apply(Curve, values)) {
var curves = Curve.subdivide.apply(Curve, values);
var halfT = (minT + maxT) / 2;
// Recursively subdive and compute segments again.
// Recursively subdive and compute parts again.
this._computeSegments(curves[0], minT, halfT);
this._computeSegments(curves[1], halfT, maxT);
} else {
@ -23,7 +26,7 @@ var CurveFlattener = Base.extend({
dist = Math.sqrt(x * x + y * y);
if (dist > Numerical.TOLERANCE) {
this.length += dist;
this.segments.push({
this.parts.push({
length: this.length,
value: maxT
});
@ -32,12 +35,21 @@ var CurveFlattener = Base.extend({
},
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 i = 0, l = this.segments.length; i < l; i++) {
var segment = this.segments[i];
for (var l = this.parts.length; i < l; i++) {
var segment = this.parts[i];
if (segment.length >= length) {
var prev = this.segments[i - 1],
this.index = i;
var prev = this.parts[i - 1],
prevValue = prev ? prev.value : 0,
prevLength = prev ? prev.length : 0;
// Interpolate
@ -46,5 +58,27 @@ var CurveFlattener = Base.extend({
}
}
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

@ -717,8 +717,7 @@ var Path = this.Path = PathItem.extend({
}
function drawSegments(ctx, segments) {
var length = segments.length,
handleOut, outX, outY;
var handleOut, outX, outY;
function drawSegment(i) {
var segment = segments[i],
@ -741,22 +740,56 @@ var Path = this.Path = PathItem.extend({
outY = handleOut._y + y;
}
for (var i = 0; i < length; i++)
for (var i = 0, l = segments.length; i < l; i++)
drawSegment(i);
// Close path by drawing first segment again
if (this._closed && length > 1)
drawSegment(0);
}
function drawDashes(ctx, curves, dashArray, dashOffset) {
var length = 0,
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;
}
}
}
}
return {
draw: function(ctx, param) {
if (!param.compound)
ctx.beginPath();
var fillColor = this.getFillColor(),
strokeColor = this.getStrokeColor();
strokeColor = this.getStrokeColor(),
dashArray = this.getDashArray() || [], // TODO: Always defined?
hasDash = !!dashArray.length;
if (param.compound || param.selection || param.clip || fillColor || strokeColor) {
if (param.compound || param.selection || param.clip || fillColor
|| strokeColor && !hasDash) {
drawSegments(ctx, this._segments);
}
@ -784,6 +817,13 @@ var Path = this.Path = PathItem.extend({
}
if (strokeColor) {
ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
if (hasDash) {
// We cannot use the path created by drawSegments above
// Use CurveFlatteners to draw dashed paths:
ctx.beginPath();
var curves = this.getCurves();
drawDashes(ctx, curves, dashArray, this.getDashOffset());
}
ctx.stroke();
}
ctx.restore();