Implement non-scaling strokes through Style#strokeScaling.

Closes #418.
This commit is contained in:
Jürg Lehni 2014-05-13 13:38:51 +02:00
parent 68db4f9b59
commit 846c806034
12 changed files with 277 additions and 126 deletions

View file

@ -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);

View file

@ -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>

View 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>

View file

@ -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) {

View file

@ -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.

View file

@ -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();

View file

@ -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;
} }

View file

@ -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;

View file

@ -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);
} }

View file

@ -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.
* *

View file

@ -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:

View file

@ -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
}; };