diff --git a/examples/SVG Export/Shapes.html b/examples/SVG Export/Shapes.html new file mode 100644 index 00000000..8d6efaf1 --- /dev/null +++ b/examples/SVG Export/Shapes.html @@ -0,0 +1,57 @@ + + + + + Shapes + + + + + + + + + \ No newline at end of file diff --git a/src/item/Shape.js b/src/item/Shape.js index 11a98696..88ad9953 100644 --- a/src/item/Shape.js +++ b/src/item/Shape.js @@ -20,11 +20,15 @@ var Shape = Item.extend(/** @lends Shape# */{ _class: 'Shape', _transformContent: false, + _boundsSelected: true, - initialize: function Shape(type, point, size, props) { - this._initialize(props, point); + // TODO: SVG, serialization + + initialize: function Shape(type, point, size, radius, props) { this._type = type; this._size = size; + this._radius = radius; + this._initialize(props, point); }, /** @@ -39,28 +43,55 @@ var Shape = Item.extend(/** @lends Shape# */{ }, setSize: function(/* size */) { - var size = Size.read(arguments); + var type = this._type, + size = Size.read(arguments); if (!this._size.equals(size)) { - this._size.set(size.width, size.height); + var width = size.width, + height = size.height; + if (type === 'circle') { + // Use average of width and height as new size, then calculate + // radius as a number from that: + width = height = (width + height) / 2; + this._radius = width / 2; + } else if (type === 'ellipse') { + // The radius is a size. + this._radius.set(width / 2, height / 2); + } + this._size.set(width, height); this._changed(/*#=*/ Change.GEOMETRY); } }, /** - * The radius of the shape if it is a circle. + * The radius of the shape, as a number if it is a circle, or a size object + * for ellipses and rounded rectangles. * - * @type Size + * @type Number|Size * @bean */ getRadius: function() { - var size = this._size; - // Average half of width & height for radius... - return (size.width + size.height) / 4; + var rad = this._radius; + return this._type === 'circle' + ? rad + : new LinkedSize(rad.width, rad.height, this, 'setRadius'); }, setRadius: function(radius) { - var size = radius * 2; - this.setSize(size, size); + var type = this._type; + if (type === 'circle') { + if (radius === this._radius) + return; + var size = radius * 2; + this._size.set(size, size); + } else { + radius = Size.read(arguments); + if (this._radius.equals(radius)) + return; + this._radius.set(radius.width, radius.height); + if (type === 'ellipse') + this._size.set(radius.width * 2, radius.height * 2); + } + this._changed(/*#=*/ Change.GEOMETRY); }, isEmpty: function() { @@ -72,34 +103,52 @@ var Shape = Item.extend(/** @lends Shape# */{ _draw: function(ctx, param) { var style = this._style, - size = this._size, - width = size.width, - height = size.height, fillColor = style.getFillColor(), strokeColor = style.getStrokeColor(); if (fillColor || strokeColor || param.clip) { + var radius = this._radius, + type = this._type; ctx.beginPath(); - switch (this._type) { - case 'rect': - ctx.rect(-width / 2, -height / 2, width, height); - break; - case 'circle': - // Average half of width & height for radius... - ctx.arc(0, 0, (width + height) / 4, 0, Math.PI * 2, true); - break; - case 'ellipse': - // Use four bezier curves and KAPPA value to aproximate ellipse - var mx = width / 2, - my = height / 2, - kappa = Numerical.KAPPA, - cx = mx * kappa, - cy = my * kappa; - ctx.moveTo(-mx, 0); - ctx.bezierCurveTo(-mx, -cy, -cx, -my, 0, -my); - ctx.bezierCurveTo(cx, -my, mx, -cy, mx, 0); - ctx.bezierCurveTo(mx, cy, cx, my, 0, my); - ctx.bezierCurveTo(-cx, my, -mx, cy, -mx, 0); - break; + if (type === 'circle') { + ctx.arc(0, 0, radius, 0, Math.PI * 2, true); + } else { + var rx = radius.width, + ry = radius.height, + kappa = Numerical.KAPPA; + if (type === 'ellipse') { + // Use four bezier curves and KAPPA value to aproximate ellipse + var cx = rx * kappa, + cy = ry * kappa; + ctx.moveTo(-rx, 0); + ctx.bezierCurveTo(-rx, -cy, -cx, -ry, 0, -ry); + ctx.bezierCurveTo(cx, -ry, rx, -cy, rx, 0); + ctx.bezierCurveTo(rx, cy, cx, ry, 0, ry); + ctx.bezierCurveTo(-cx, ry, -rx, cy, -rx, 0); + } else { // rect + var size = this._size, + width = size.width, + height = size.height; + if (rx === 0 && ry === 0) { + // straight rect + ctx.rect(-width / 2, -height / 2, width, height); + } else { + // rounded rect. Use inverse kappa to calculate position + // of control points from the corners inwards. + kappa = 1 - kappa; + var x = width / 2, + y = height / 2, + cx = rx * kappa, + cy = ry * kappa; + ctx.moveTo(-x, -y + ry); + ctx.bezierCurveTo(-x, -y + cy, -x + cx, -y, -x + rx, -y); + ctx.lineTo(x - rx, -y); + ctx.bezierCurveTo(x - cx, -y, x, -y + cy, x, -y + ry); + ctx.lineTo(x, y - ry); + ctx.bezierCurveTo(x, y - cy, x - cx, y, x - rx, y); + ctx.lineTo(-x + rx, y); + ctx.bezierCurveTo(-x + cx, y, -x, y - cy, -x, y - ry); + } + } } } if (!param.clip && (fillColor || strokeColor)) { @@ -148,19 +197,18 @@ var Shape = Item.extend(/** @lends Shape# */{ break; case 'circle': case 'ellipse': - var size = this._size, - width = size.width, - height = size.height, - radius; + var radius; if (type === 'ellipse') { // Calculate ellipse radius at angle var angle = point.getAngleInRadians(), + size = this._size, + width = size.width, + height = size.height, x = width * Math.sin(angle), y = height * Math.cos(angle); radius = width * height / (2 * Math.sqrt(x * x + y * y)); } else { - // Average half of width & height for radius... - radius = (width + height) / 4; + radius = this._radius; } if (2 * Math.abs(point.getLength() - radius) <= strokeWidth) return new HitResult('stroke', this); @@ -171,8 +219,8 @@ var Shape = Item.extend(/** @lends Shape# */{ }, statics: new function() { - function createShape(type, point, size, args) { - return new Shape(type, point, size, Base.getNamed(args)); + function createShape(type, point, size, radius, args) { + return new Shape(type, point, size, radius, Base.getNamed(args)); } return /** @lends Shape */{ @@ -198,7 +246,7 @@ var Shape = Item.extend(/** @lends Shape# */{ var center = Point.readNamed(arguments, 'center'), radius = Base.readNamed(arguments, 'radius'); return createShape('circle', center, new Size(radius * 2), - arguments); + radius, arguments); }, /** @@ -276,7 +324,8 @@ var Shape = Item.extend(/** @lends Shape# */{ Rectangle: function(/* rectangle */) { var rect = Rectangle.readNamed(arguments, 'rectangle'); return createShape('rect', rect.getCenter(true), - rect.getSize(true), arguments); + rect.getSize(true), Size.readNamed(arguments, 'radius'), + arguments); }, /** @@ -301,9 +350,10 @@ var Shape = Item.extend(/** @lends Shape# */{ * }); */ Ellipse: function(/* rectangle */) { - var rect = Rectangle.readNamed(arguments, 'rectangle'); - return createShape('ellipse', rect.getCenter(true), - rect.getSize(true), arguments); + var rect = Rectangle.readNamed(arguments, 'rectangle'), + size = rect.getSize(true); + return createShape('ellipse', rect.getCenter(true), size, + new Size(size.width / 2, size.height / 2), arguments); } }; }