mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Implement non-scaling strokes through Style#strokeScaling.
Closes #418.
This commit is contained in:
parent
68db4f9b59
commit
846c806034
12 changed files with 277 additions and 126 deletions
|
@ -6,24 +6,39 @@
|
||||||
<link rel="stylesheet" href="../css/style.css">
|
<link rel="stylesheet" href="../css/style.css">
|
||||||
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
||||||
<script type="text/paperscript" canvas="canvas1">
|
<script type="text/paperscript" canvas="canvas1">
|
||||||
var ellipse = new Shape.Ellipse({
|
var circle = new Shape.Circle({
|
||||||
from: [10, 10],
|
center: [100, 100],
|
||||||
to: [200, 100],
|
radius: 50,
|
||||||
fillColor: 'red'
|
fillColor: 'red'
|
||||||
});
|
});
|
||||||
|
|
||||||
var circle = new Shape.Circle({
|
var ellipse = new Shape.Ellipse({
|
||||||
center: [50, 150],
|
center: [100, 200],
|
||||||
radius: 25,
|
radius: [50, 25],
|
||||||
fillColor: 'blue'
|
fillColor: 'blue',
|
||||||
|
strokeColor: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
rotation: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
var rectangle = new Shape.Rectangle({
|
var rect = new Shape.Rectangle({
|
||||||
from: [25, 200],
|
center: [100, 300],
|
||||||
to: [100, 225],
|
size: [100, 50],
|
||||||
fillColor: 'green'
|
fillColor: 'green',
|
||||||
|
strokeColor: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
rotation: -20
|
||||||
|
});
|
||||||
|
|
||||||
|
var roundRect = new Shape.Rectangle({
|
||||||
|
center: [100, 400],
|
||||||
|
size: [50, 100],
|
||||||
|
radius: [15, 20],
|
||||||
|
fillColor: 'orange',
|
||||||
|
strokeColor: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
rotation: 20
|
||||||
});
|
});
|
||||||
rectangle.rotate(30);
|
|
||||||
|
|
||||||
window._json = project.exportJSON();
|
window._json = project.exportJSON();
|
||||||
console.log(window._json);
|
console.log(window._json);
|
||||||
|
|
|
@ -6,55 +6,46 @@
|
||||||
<link rel="stylesheet" href="../css/style.css">
|
<link rel="stylesheet" href="../css/style.css">
|
||||||
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
||||||
<script type="text/paperscript" canvas="canvas">
|
<script type="text/paperscript" canvas="canvas">
|
||||||
var path = new Path.Circle({
|
var circle = new Shape.Circle({
|
||||||
center: [100, 100],
|
center: [100, 100],
|
||||||
radius: 50,
|
radius: 50,
|
||||||
fillColor: 'red'
|
fillColor: 'red'
|
||||||
});
|
});
|
||||||
var shape = path.toShape();
|
|
||||||
shape.position += [200, 0];
|
|
||||||
var path = shape.toPath();
|
|
||||||
path.position += [200, 0];
|
|
||||||
|
|
||||||
var path = new Path.Ellipse({
|
var ellipse = new Shape.Ellipse({
|
||||||
center: [100, 200],
|
center: [100, 200],
|
||||||
radius: [50, 25],
|
radius: [50, 25],
|
||||||
fillColor: 'blue',
|
fillColor: 'blue',
|
||||||
strokeColor: 'black',
|
strokeColor: 'black',
|
||||||
strokeWidth: 10
|
strokeWidth: 10,
|
||||||
|
rotation: 20
|
||||||
});
|
});
|
||||||
path.rotate(20);
|
|
||||||
var shape = path.toShape();
|
|
||||||
shape.position += [200, 0];
|
|
||||||
var path = shape.toPath();
|
|
||||||
path.position += [200, 0];
|
|
||||||
|
|
||||||
var path = new Path.Rectangle({
|
var rect = new Shape.Rectangle({
|
||||||
center: [100, 300],
|
center: [100, 300],
|
||||||
size: [100, 50],
|
size: [100, 50],
|
||||||
fillColor: 'green',
|
fillColor: 'green',
|
||||||
strokeColor: 'black',
|
strokeColor: 'black',
|
||||||
strokeWidth: 10
|
strokeWidth: 10,
|
||||||
|
rotation: -20
|
||||||
});
|
});
|
||||||
path.rotate(-20);
|
|
||||||
var shape = path.toShape();
|
|
||||||
shape.position += [200, 0];
|
|
||||||
var path = shape.toPath();
|
|
||||||
path.position += [200, 0];
|
|
||||||
|
|
||||||
var path = new Path.Rectangle({
|
var roundRect = new Shape.Rectangle({
|
||||||
center: [100, 400],
|
center: [100, 400],
|
||||||
size: [50, 100],
|
size: [50, 100],
|
||||||
radius: [5, 10],
|
radius: [15, 20],
|
||||||
fillColor: 'orange',
|
fillColor: 'orange',
|
||||||
strokeColor: 'black',
|
strokeColor: 'black',
|
||||||
strokeWidth: 10
|
strokeWidth: 10,
|
||||||
|
rotation: 20
|
||||||
});
|
});
|
||||||
path.rotate(20);
|
|
||||||
var shape = path.toShape();
|
[circle, ellipse, rect, roundRect].forEach(function(shape) {
|
||||||
shape.position += [200, 0];
|
var path = shape.toPath();
|
||||||
var path = shape.toPath();
|
path.position += [200, 0];
|
||||||
path.position += [200, 0];
|
var shape2 = path.toShape();
|
||||||
|
shape2.position += [200, 0];
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
66
examples/Scripts/StrokeScaling.html
Normal file
66
examples/Scripts/StrokeScaling.html
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Shapes</title>
|
||||||
|
<link rel="stylesheet" href="../css/style.css">
|
||||||
|
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
||||||
|
<script type="text/paperscript" canvas="canvas">
|
||||||
|
// view._context = new ProxyContext(view._context);
|
||||||
|
// view.zoom = 1.25;
|
||||||
|
settings.applyMatrix = false;
|
||||||
|
var path = new Path.Circle({
|
||||||
|
center: view.center - [0, 140],
|
||||||
|
radius: 50,
|
||||||
|
fillColor: 'red',
|
||||||
|
strokeColor: 'black',
|
||||||
|
strokeWidth: 10,
|
||||||
|
strokeScaling: false,
|
||||||
|
opacity: 0.5,
|
||||||
|
selected: true
|
||||||
|
});
|
||||||
|
path.scale(2, 1);
|
||||||
|
|
||||||
|
var shape = new Shape.Circle({
|
||||||
|
center: view.center,
|
||||||
|
radius: 50,
|
||||||
|
fillColor: 'red',
|
||||||
|
strokeColor: 'black',
|
||||||
|
strokeWidth: 10,
|
||||||
|
strokeScaling: false,
|
||||||
|
opacity: 0.5,
|
||||||
|
selected: true
|
||||||
|
});
|
||||||
|
shape.scale(2, 1);
|
||||||
|
|
||||||
|
var hole;
|
||||||
|
var compound = new CompoundPath({
|
||||||
|
children: [
|
||||||
|
new Path.Rectangle({
|
||||||
|
point: [0, 0],
|
||||||
|
size: [100, 100]
|
||||||
|
}),
|
||||||
|
hole = new Path.Circle({
|
||||||
|
center: [50, 50],
|
||||||
|
radius: 25
|
||||||
|
})
|
||||||
|
],
|
||||||
|
fillColor: 'red',
|
||||||
|
strokeColor: 'black',
|
||||||
|
strokeWidth: 10,
|
||||||
|
position: view.center + [0, 140],
|
||||||
|
strokeScaling: false,
|
||||||
|
opacity: 0.5,
|
||||||
|
selected: true
|
||||||
|
});
|
||||||
|
hole.position += 15;
|
||||||
|
compound.scale(2, 1);
|
||||||
|
|
||||||
|
document.getElementById('svg').appendChild(project.exportSVG());
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas" width="300" height="500"></canvas>
|
||||||
|
<svg id="svg" width="300" height="500"></svg>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -437,7 +437,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{
|
||||||
* as x, y value pairs
|
* as x, y value pairs
|
||||||
* @param {Number[]} dst the array into which to store the transformed
|
* @param {Number[]} dst the array into which to store the transformed
|
||||||
* point pairs
|
* point pairs
|
||||||
* @param {Number} count the number of points to tranform
|
* @param {Number} count the number of points to transform
|
||||||
* @return {Number[]} the dst array, containing the transformed coordinates.
|
* @return {Number[]} the dst array, containing the transformed coordinates.
|
||||||
*/
|
*/
|
||||||
transform: function(/* point | */ src, dst, count) {
|
transform: function(/* point | */ src, dst, count) {
|
||||||
|
|
|
@ -2546,8 +2546,8 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The shape to be used at the end of open {@link Path} items, when they
|
* The shape to be used at the beginning and end of open {@link Path} items,
|
||||||
* have a stroke.
|
* when they have a stroke.
|
||||||
*
|
*
|
||||||
* @name Item#strokeCap
|
* @name Item#strokeCap
|
||||||
* @property
|
* @property
|
||||||
|
@ -2579,7 +2579,8 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The shape to be used at the corners of paths when they have a stroke.
|
* The shape to be used at the segments and corners of {@link Path} items
|
||||||
|
* when they have a stroke.
|
||||||
*
|
*
|
||||||
* @name Item#strokeJoin
|
* @name Item#strokeJoin
|
||||||
* @property
|
* @property
|
||||||
|
@ -2615,6 +2616,17 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the stroke is to be drawn taking the current affine
|
||||||
|
* transformation into account (the default behavior), or whether it should
|
||||||
|
* appear as a non-scaling stroke.
|
||||||
|
*
|
||||||
|
* @name Style#strokeScaling
|
||||||
|
* @property
|
||||||
|
* @default true
|
||||||
|
* @type Boolean
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies an array containing the dash and gap lengths of the stroke.
|
* Specifies an array containing the dash and gap lengths of the stroke.
|
||||||
*
|
*
|
||||||
|
@ -3574,7 +3586,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
draw: function(ctx, param) {
|
draw: function(ctx, param, parentStrokeMatrix) {
|
||||||
// Each time the project gets drawn, it's _updateVersion is increased.
|
// Each time the project gets drawn, it's _updateVersion is increased.
|
||||||
// Keep the _updateVersion of drawn items in sync, so we have an easy
|
// Keep the _updateVersion of drawn items in sync, so we have an easy
|
||||||
// way to know for which selected items we need to draw selection info.
|
// way to know for which selected items we need to draw selection info.
|
||||||
|
@ -3631,7 +3643,9 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
// If native blending is possible, see if the item allows it
|
// If native blending is possible, see if the item allows it
|
||||||
|| (nativeBlend || normalBlend && opacity < 1)
|
|| (nativeBlend || normalBlend && opacity < 1)
|
||||||
&& this._canComposite(),
|
&& this._canComposite(),
|
||||||
|
pixelRatio = param.pixelRatio,
|
||||||
mainCtx, itemOffset, prevOffset;
|
mainCtx, itemOffset, prevOffset;
|
||||||
|
|
||||||
if (!direct) {
|
if (!direct) {
|
||||||
// Apply the parent's global matrix to the calculation of correct
|
// Apply the parent's global matrix to the calculation of correct
|
||||||
// bounds.
|
// bounds.
|
||||||
|
@ -3648,28 +3662,48 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
// it, instead of the mainCtx.
|
// it, instead of the mainCtx.
|
||||||
mainCtx = ctx;
|
mainCtx = ctx;
|
||||||
ctx = CanvasProvider.getContext(
|
ctx = CanvasProvider.getContext(
|
||||||
bounds.getSize().ceil().add(new Size(1, 1)),
|
bounds.getSize().ceil().add(new Size(1, 1)), pixelRatio);
|
||||||
param.pixelRatio);
|
|
||||||
}
|
}
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
// Get the transformation matrix for non-scaling strokes.
|
||||||
|
var strokeMatrix = parentStrokeMatrix
|
||||||
|
? parentStrokeMatrix.clone().concatenate(matrix)
|
||||||
|
: !this.getStrokeScaling() && getViewMatrix(globalMatrix),
|
||||||
|
// If we're drawing into a separate canvas and a clipItem is defined
|
||||||
|
// for the current rendering loop, we need to draw the clip item
|
||||||
|
// again.
|
||||||
|
clip = !direct && param.clipItem,
|
||||||
|
// If we're drawing with a strokeMatrix, the CTM is reset either way
|
||||||
|
// so we don't need to set it, except when we also have to draw a
|
||||||
|
// clipItem.
|
||||||
|
transform = !strokeMatrix || clip;
|
||||||
// If drawing directly, handle opacity and native blending now,
|
// If drawing directly, handle opacity and native blending now,
|
||||||
// otherwise we will do it later when the temporary canvas is composited.
|
// otherwise we will do it later when the temporary canvas is composited.
|
||||||
if (direct) {
|
if (direct) {
|
||||||
ctx.globalAlpha = opacity;
|
ctx.globalAlpha = opacity;
|
||||||
if (nativeBlend)
|
if (nativeBlend)
|
||||||
ctx.globalCompositeOperation = blendMode;
|
ctx.globalCompositeOperation = blendMode;
|
||||||
} else {
|
} else if (transform) {
|
||||||
// Translate the context so the topLeft of the item is at (0, 0)
|
// Translate the context so the topLeft of the item is at (0, 0)
|
||||||
// on the temporary canvas.
|
// on the temporary canvas.
|
||||||
ctx.translate(-itemOffset.x, -itemOffset.y);
|
ctx.translate(-itemOffset.x, -itemOffset.y);
|
||||||
}
|
}
|
||||||
// Apply globalMatrix when drawing into temporary canvas.
|
// Apply globalMatrix when drawing into temporary canvas.
|
||||||
(direct ? matrix : getViewMatrix(globalMatrix)).applyToContext(ctx);
|
if (transform)
|
||||||
// If we're drawing into a separate canvas and a clipItem is defined for
|
(direct ? matrix : getViewMatrix(globalMatrix)).applyToContext(ctx);
|
||||||
// the current rendering loop, we need to draw the clip item again.
|
if (clip)
|
||||||
if (!direct && param.clipItem)
|
|
||||||
param.clipItem.draw(ctx, param.extend({ clip: true }));
|
param.clipItem.draw(ctx, param.extend({ clip: true }));
|
||||||
this._draw(ctx, param);
|
if (strokeMatrix) {
|
||||||
|
// Reset the transformation but take HiDPI pixel ratio into account.
|
||||||
|
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||||
|
// Also offset again when drawing non-directly.
|
||||||
|
// NOTE: Don't use itemOffset since offset might be from the parent,
|
||||||
|
// e.g. CompoundPath
|
||||||
|
var offset = param.offset;
|
||||||
|
if (offset)
|
||||||
|
ctx.translate(-offset.x, -offset.y);
|
||||||
|
}
|
||||||
|
this._draw(ctx, param, strokeMatrix);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
matrices.pop();
|
matrices.pop();
|
||||||
if (param.clip && !param.dontFinish)
|
if (param.clip && !param.dontFinish)
|
||||||
|
@ -3681,7 +3715,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
BlendMode.process(blendMode, ctx, mainCtx, opacity,
|
BlendMode.process(blendMode, ctx, mainCtx, opacity,
|
||||||
// Calculate the pixel offset of the temporary canvas to the
|
// Calculate the pixel offset of the temporary canvas to the
|
||||||
// main canvas. We also need to factor in the pixel-ratio.
|
// main canvas. We also need to factor in the pixel-ratio.
|
||||||
itemOffset.subtract(prevOffset).multiply(param.pixelRatio));
|
itemOffset.subtract(prevOffset).multiply(pixelRatio));
|
||||||
// Return the temporary context, so it can be reused
|
// Return the temporary context, so it can be reused
|
||||||
CanvasProvider.release(ctx);
|
CanvasProvider.release(ctx);
|
||||||
// Restore previous offset.
|
// Restore previous offset.
|
||||||
|
|
|
@ -173,55 +173,71 @@ var Shape = Item.extend(/** @lends Shape# */{
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
_draw: function(ctx, param) {
|
_draw: function(ctx, param, strokeMatrix) {
|
||||||
var style = this._style,
|
var style = this._style,
|
||||||
hasFill = style.hasFill(),
|
hasFill = style.hasFill(),
|
||||||
hasStroke = style.hasStroke(),
|
hasStroke = style.hasStroke(),
|
||||||
dontPaint = param.dontFinish || param.clip;
|
dontPaint = param.dontFinish || param.clip,
|
||||||
|
untransformed = !strokeMatrix;
|
||||||
if (hasFill || hasStroke || dontPaint) {
|
if (hasFill || hasStroke || dontPaint) {
|
||||||
var radius = this._radius,
|
var type = this._type,
|
||||||
type = this._type;
|
radius = this._radius,
|
||||||
if (!param.dontStart)
|
isCircle = type === 'circle';
|
||||||
ctx.beginPath();
|
if (untransformed && isCircle) {
|
||||||
if (type === 'circle') {
|
|
||||||
ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
|
ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
|
||||||
} else {
|
} else {
|
||||||
var rx = radius.width,
|
var rx = isCircle ? radius : radius.width,
|
||||||
ry = radius.height,
|
ry = isCircle ? radius : radius.height,
|
||||||
kappa = /*#=*/ Numerical.KAPPA;
|
size = this._size,
|
||||||
if (type === 'ellipse') {
|
width = size.width,
|
||||||
// Approximate ellipse with four bezier curves and KAPPA.
|
height = size.height;
|
||||||
var cx = rx * kappa,
|
if (untransformed && type === 'rect' && rx === 0 && ry === 0) {
|
||||||
cy = ry * kappa;
|
// Rectangles with no rounding
|
||||||
ctx.moveTo(-rx, 0);
|
ctx.rect(-width / 2, -height / 2, width, height);
|
||||||
ctx.bezierCurveTo(-rx, -cy, -cx, -ry, 0, -ry);
|
} else {
|
||||||
ctx.bezierCurveTo(cx, -ry, rx, -cy, rx, 0);
|
// Round rectangles, ellipses, transformed circles
|
||||||
ctx.bezierCurveTo(rx, cy, cx, ry, 0, ry);
|
var x = width / 2,
|
||||||
ctx.bezierCurveTo(-cx, ry, -rx, cy, -rx, 0);
|
y = height / 2,
|
||||||
} else { // rect
|
// Use 1 - KAPPA to calculate position of control points
|
||||||
var size = this._size,
|
// from the corners inwards.
|
||||||
width = size.width,
|
kappa = 1 - /*#=*/ Numerical.KAPPA,
|
||||||
height = size.height;
|
cx = rx * kappa,
|
||||||
if (rx === 0 && ry === 0) {
|
cy = ry * kappa,
|
||||||
// straight rect
|
// Build the coordinates list, so it can optionally be
|
||||||
ctx.rect(-width / 2, -height / 2, width, height);
|
// transformed by a matrix.
|
||||||
} else {
|
c = [
|
||||||
// rounded rect. Use 1 - KAPPA to calculate position of
|
-x, -y + ry,
|
||||||
// control points from the corners inwards.
|
-x, -y + cy,
|
||||||
kappa = 1 - kappa;
|
-x + cx, -y,
|
||||||
var x = width / 2,
|
-x + rx, -y,
|
||||||
y = height / 2,
|
x - rx, -y,
|
||||||
cx = rx * kappa,
|
x - cx, -y,
|
||||||
cy = ry * kappa;
|
x, -y + cy,
|
||||||
ctx.moveTo(-x, -y + ry);
|
x, -y + ry,
|
||||||
ctx.bezierCurveTo(-x, -y + cy, -x + cx, -y, -x + rx, -y);
|
x, y - ry,
|
||||||
ctx.lineTo(x - rx, -y);
|
x, y - cy,
|
||||||
ctx.bezierCurveTo(x - cx, -y, x, -y + cy, x, -y + ry);
|
x - cx, y,
|
||||||
ctx.lineTo(x, y - ry);
|
x - rx, y,
|
||||||
ctx.bezierCurveTo(x, y - cy, x - cx, y, x - rx, y);
|
-x + rx, y,
|
||||||
ctx.lineTo(-x + rx, y);
|
-x + cx, y,
|
||||||
ctx.bezierCurveTo(-x + cx, y, -x, y - cy, -x, y - ry);
|
-x, y - cy,
|
||||||
}
|
-x, y - ry
|
||||||
|
];
|
||||||
|
if (strokeMatrix)
|
||||||
|
strokeMatrix.transform(c, c, 32);
|
||||||
|
if (!param.dontStart)
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(c[0], c[1]);
|
||||||
|
ctx.bezierCurveTo(c[2], c[3], c[4], c[5], c[6], c[7]);
|
||||||
|
if (x !== rx)
|
||||||
|
ctx.lineTo(c[8], c[9]);
|
||||||
|
ctx.bezierCurveTo(c[10], c[11], c[12], c[13], c[14], c[15]);
|
||||||
|
if (y !== ry)
|
||||||
|
ctx.lineTo(c[16], c[17]);
|
||||||
|
ctx.bezierCurveTo(c[18], c[19], c[20], c[21], c[22], c[23]);
|
||||||
|
if (x !== rx)
|
||||||
|
ctx.lineTo(c[24], c[25]);
|
||||||
|
ctx.bezierCurveTo(c[26], c[27], c[28], c[29], c[30], c[31]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
|
|
|
@ -247,7 +247,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
: new Base(options, { fill: false });
|
: new Base(options, { fill: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
_draw: function(ctx, param) {
|
_draw: function(ctx, param, strokeMatrix) {
|
||||||
var children = this._children;
|
var children = this._children;
|
||||||
// Return early if the compound path doesn't have any children:
|
// Return early if the compound path doesn't have any children:
|
||||||
if (children.length === 0)
|
if (children.length === 0)
|
||||||
|
@ -259,7 +259,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
param = param.extend({ dontStart: true, dontFinish: true });
|
param = param.extend({ dontStart: true, dontFinish: true });
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
for (var i = 0, l = children.length; i < l; i++)
|
||||||
children[i].draw(ctx, param);
|
children[i].draw(ctx, param, strokeMatrix);
|
||||||
this._currentPath = ctx.currentPath;
|
this._currentPath = ctx.currentPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1634,7 +1634,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the curve location of the specified offset on the path.
|
* Returns the curve location of the specified offset on the path.
|
||||||
*
|
*
|
||||||
* @param {Number} offset the offset on the path, where {@code 0} is at
|
* @param {Number} offset the offset on the path, where {@code 0} is at
|
||||||
* the beginning of the path and {@link Path#length} at the end.
|
* the beginning of the path and {@link Path#length} at the end.
|
||||||
* @param {Boolean} [isParameter=false]
|
* @param {Boolean} [isParameter=false]
|
||||||
|
@ -1997,11 +1997,11 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
inX, inY,
|
inX, inY,
|
||||||
outX, outY;
|
outX, outY;
|
||||||
|
|
||||||
function drawSegment(i) {
|
function drawSegment(segment) {
|
||||||
var segment = segments[i];
|
// Optimise code when no matrix is provided by accessing segment
|
||||||
// Optimise code when no matrix is provided by accessing semgent
|
|
||||||
// points hand handles directly, since this is the default when
|
// points hand handles directly, since this is the default when
|
||||||
// drawing paths. Matrix is only used for drawing selections.
|
// drawing paths. Matrix is only used for drawing selections and
|
||||||
|
// when #strokeScaling is false.
|
||||||
if (matrix) {
|
if (matrix) {
|
||||||
segment._transformCoordinates(matrix, coords, false);
|
segment._transformCoordinates(matrix, coords, false);
|
||||||
curX = coords[0];
|
curX = coords[0];
|
||||||
|
@ -2023,7 +2023,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
inX = curX + handle._x;
|
inX = curX + handle._x;
|
||||||
inY = curY + handle._y;
|
inY = curY + handle._y;
|
||||||
}
|
}
|
||||||
if (inX == curX && inY == curY && outX == prevX && outY == prevY) {
|
if (inX === curX && inY === curY
|
||||||
|
&& outX === prevX && outY === prevY) {
|
||||||
ctx.lineTo(curX, curY);
|
ctx.lineTo(curX, curY);
|
||||||
} else {
|
} else {
|
||||||
ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);
|
ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);
|
||||||
|
@ -2042,20 +2043,17 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
drawSegment(i);
|
drawSegment(segments[i]);
|
||||||
// Close path by drawing first segment again
|
// Close path by drawing first segment again
|
||||||
if (path._closed && length > 0)
|
if (path._closed && length > 0)
|
||||||
drawSegment(0);
|
drawSegment(segments[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_draw: function(ctx, param) {
|
_draw: function(ctx, param, strokeMatrix) {
|
||||||
var dontStart = param.dontStart,
|
var dontStart = param.dontStart,
|
||||||
dontPaint = param.dontFinish || param.clip;
|
dontPaint = param.dontFinish || param.clip,
|
||||||
if (!dontStart)
|
style = this.getStyle(),
|
||||||
ctx.beginPath();
|
|
||||||
|
|
||||||
var style = this.getStyle(),
|
|
||||||
hasFill = style.hasFill(),
|
hasFill = style.hasFill(),
|
||||||
hasStroke = style.hasStroke(),
|
hasStroke = style.hasStroke(),
|
||||||
dashArray = style.getDashArray(),
|
dashArray = style.getDashArray(),
|
||||||
|
@ -2063,18 +2061,15 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
dashLength = !paper.support.nativeDash && hasStroke
|
dashLength = !paper.support.nativeDash && hasStroke
|
||||||
&& dashArray && dashArray.length;
|
&& dashArray && dashArray.length;
|
||||||
|
|
||||||
function getOffset(i) {
|
if (!dontStart)
|
||||||
// Negative modulo is necessary since we're stepping back
|
ctx.beginPath();
|
||||||
// in the dash sequence first.
|
|
||||||
return dashArray[((i % dashLength) + dashLength) % dashLength];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dontStart && this._currentPath) {
|
if (!dontStart && this._currentPath) {
|
||||||
ctx.currentPath = this._currentPath;
|
ctx.currentPath = this._currentPath;
|
||||||
} else if (hasFill || hasStroke && !dashLength || dontPaint) {
|
} else if (hasFill || hasStroke && !dashLength || dontPaint) {
|
||||||
// Prepare the canvas path if we have any situation that
|
// Prepare the canvas path if we have any situation that
|
||||||
// requires it to be defined.
|
// requires it to be defined.
|
||||||
drawSegments(ctx, this);
|
drawSegments(ctx, this, strokeMatrix);
|
||||||
if (this._closed)
|
if (this._closed)
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
// CompoundPath collects its own _currentPath
|
// CompoundPath collects its own _currentPath
|
||||||
|
@ -2082,6 +2077,12 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
this._currentPath = ctx.currentPath;
|
this._currentPath = ctx.currentPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOffset(i) {
|
||||||
|
// Negative modulo is necessary since we're stepping back
|
||||||
|
// in the dash sequence first.
|
||||||
|
return dashArray[((i % dashLength) + dashLength) % dashLength];
|
||||||
|
}
|
||||||
|
|
||||||
if (!dontPaint && (hasFill || hasStroke)) {
|
if (!dontPaint && (hasFill || hasStroke)) {
|
||||||
// If the path is part of a compound path or doesn't have a fill
|
// If the path is part of a compound path or doesn't have a fill
|
||||||
// or stroke, there is no need to continue.
|
// or stroke, there is no need to continue.
|
||||||
|
@ -2102,7 +2103,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
// native dashes.
|
// native dashes.
|
||||||
if (!dontStart)
|
if (!dontStart)
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
var flattener = new PathFlattener(this),
|
var flattener = new PathFlattener(this, strokeMatrix),
|
||||||
length = flattener.length,
|
length = flattener.length,
|
||||||
from = -style.getDashOffset(), to,
|
from = -style.getDashOffset(), to,
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
var PathFlattener = Base.extend({
|
var PathFlattener = Base.extend({
|
||||||
initialize: function(path) {
|
initialize: function(path, matrix) {
|
||||||
this.curves = []; // The curve values as returned by getValues()
|
this.curves = []; // The curve values as returned by getValues()
|
||||||
this.parts = []; // The calculated, subdivided parts of the path
|
this.parts = []; // The calculated, subdivided parts of the path
|
||||||
this.length = 0; // The total length of the path
|
this.length = 0; // The total length of the path
|
||||||
|
@ -35,7 +35,7 @@ var PathFlattener = Base.extend({
|
||||||
that = this;
|
that = this;
|
||||||
|
|
||||||
function addCurve(segment1, segment2) {
|
function addCurve(segment1, segment2) {
|
||||||
var curve = Curve.getValues(segment1, segment2);
|
var curve = Curve.getValues(segment1, segment2, matrix);
|
||||||
that.curves.push(curve);
|
that.curves.push(curve);
|
||||||
that._computeParts(curve, segment1._index, 0, 1);
|
that._computeParts(curve, segment1._index, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ var Style = Base.extend(new function() {
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
strokeCap: 'butt',
|
strokeCap: 'butt',
|
||||||
strokeJoin: 'miter',
|
strokeJoin: 'miter',
|
||||||
|
strokeScaling: true,
|
||||||
miterLimit: 10,
|
miterLimit: 10,
|
||||||
dashOffset: 0,
|
dashOffset: 0,
|
||||||
dashArray: [],
|
dashArray: [],
|
||||||
|
@ -101,6 +102,8 @@ var Style = Base.extend(new function() {
|
||||||
strokeWidth: /*#=*/ Change.STROKE,
|
strokeWidth: /*#=*/ Change.STROKE,
|
||||||
strokeCap: /*#=*/ Change.STROKE,
|
strokeCap: /*#=*/ Change.STROKE,
|
||||||
strokeJoin: /*#=*/ Change.STROKE,
|
strokeJoin: /*#=*/ Change.STROKE,
|
||||||
|
// strokeScaling can change the coordinates of cached path items
|
||||||
|
strokeScaling: /*#=*/ Change.STROKE | Change.GEOMETRY,
|
||||||
miterLimit: /*#=*/ Change.STROKE,
|
miterLimit: /*#=*/ Change.STROKE,
|
||||||
fontFamily: /*#=*/ Change.GEOMETRY,
|
fontFamily: /*#=*/ Change.GEOMETRY,
|
||||||
fontWeight: /*#=*/ Change.GEOMETRY,
|
fontWeight: /*#=*/ Change.GEOMETRY,
|
||||||
|
@ -369,8 +372,8 @@ var Style = Base.extend(new function() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The shape to be used at the end of open {@link Path} items, when they
|
* The shape to be used at the beginning and end of open {@link Path} items,
|
||||||
* have a stroke.
|
* when they have a stroke.
|
||||||
*
|
*
|
||||||
* @name Style#strokeCap
|
* @name Style#strokeCap
|
||||||
* @property
|
* @property
|
||||||
|
@ -402,7 +405,8 @@ var Style = Base.extend(new function() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The shape to be used at the corners of paths when they have a stroke.
|
* The shape to be used at the segments and corners of {@link Path} items
|
||||||
|
* when they have a stroke.
|
||||||
*
|
*
|
||||||
* @name Style#strokeJoin
|
* @name Style#strokeJoin
|
||||||
* @property
|
* @property
|
||||||
|
@ -430,6 +434,17 @@ var Style = Base.extend(new function() {
|
||||||
* path3.strokeJoin = 'bevel';
|
* path3.strokeJoin = 'bevel';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the stroke is to be drawn taking the current affine
|
||||||
|
* transformation into account (the default behavior), or whether it should
|
||||||
|
* appear as a non-scaling stroke.
|
||||||
|
*
|
||||||
|
* @name Style#strokeScaling
|
||||||
|
* @property
|
||||||
|
* @default true
|
||||||
|
* @type Boolean
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dash offset of the stroke.
|
* The dash offset of the stroke.
|
||||||
*
|
*
|
||||||
|
|
|
@ -293,8 +293,10 @@ new function() {
|
||||||
var get = entry.get,
|
var get = entry.get,
|
||||||
type = entry.type,
|
type = entry.type,
|
||||||
value = item[get]();
|
value = item[get]();
|
||||||
if (!parent || !Base.equals(parent[get](), value)) {
|
if (entry.exportFilter
|
||||||
if (type === 'color' && value != null) {
|
? entry.exportFilter(item, value)
|
||||||
|
: !parent || !Base.equals(parent[get](), value)) {
|
||||||
|
if (type === 'color' && value !== 'none') {
|
||||||
// Support for css-style rgba() values is not in SVG 1.1, so
|
// Support for css-style rgba() values is not in SVG 1.1, so
|
||||||
// separate the alpha value of colors with alpha into the
|
// separate the alpha value of colors with alpha into the
|
||||||
// separate fill- / stroke-opacity attribute:
|
// separate fill- / stroke-opacity attribute:
|
||||||
|
|
|
@ -18,6 +18,16 @@ var SVGStyles = Base.each({
|
||||||
strokeWidth: ['stroke-width', 'number'],
|
strokeWidth: ['stroke-width', 'number'],
|
||||||
strokeCap: ['stroke-linecap', 'string'],
|
strokeCap: ['stroke-linecap', 'string'],
|
||||||
strokeJoin: ['stroke-linejoin', 'string'],
|
strokeJoin: ['stroke-linejoin', 'string'],
|
||||||
|
strokeScaling: ['vector-effect', 'lookup', {
|
||||||
|
true: 'none',
|
||||||
|
false: 'non-scaling-stroke'
|
||||||
|
}, function(item, value) {
|
||||||
|
// no inheritance, only applies to graphical elements
|
||||||
|
return !value // false, meaning non-scaling-stroke
|
||||||
|
&& (item instanceof PathItem
|
||||||
|
|| item instanceof Shape
|
||||||
|
|| item instanceof TextItem);
|
||||||
|
}],
|
||||||
miterLimit: ['stroke-miterlimit', 'number'],
|
miterLimit: ['stroke-miterlimit', 'number'],
|
||||||
dashArray: ['stroke-dasharray', 'array'],
|
dashArray: ['stroke-dasharray', 'array'],
|
||||||
dashOffset: ['stroke-dashoffset', 'number'],
|
dashOffset: ['stroke-dashoffset', 'number'],
|
||||||
|
@ -44,6 +54,7 @@ var SVGStyles = Base.each({
|
||||||
fromSVG: lookup && Base.each(lookup, function(value, name) {
|
fromSVG: lookup && Base.each(lookup, function(value, name) {
|
||||||
this[value] = name;
|
this[value] = name;
|
||||||
}, {}),
|
}, {}),
|
||||||
|
exportFilter: entry[3],
|
||||||
get: 'get' + part,
|
get: 'get' + part,
|
||||||
set: 'set' + part
|
set: 'set' + part
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue