Fix bug with rendering of subsequent "S" commands, where reflection of control point would be considering 1st control point of previous command, not 2nd one. Closes #177.

This commit is contained in:
kangax 2012-06-17 13:25:17 +02:00
parent da2ed018d8
commit 1706361616
6 changed files with 271 additions and 249 deletions

View file

@ -1,6 +1,6 @@
/*! Fabric.js Copyright 2008-2012, Bitsonnet (Juriy Zaytsev, Maxim Chernyak) */
var fabric = fabric || { version: "0.8.20" };
var fabric = fabric || { version: "0.8.21" };
if (typeof exports != 'undefined') {
exports.fabric = fabric;

257
dist/all.js vendored
View file

@ -1,6 +1,6 @@
/*! Fabric.js Copyright 2008-2012, Bitsonnet (Juriy Zaytsev, Maxim Chernyak) */
var fabric = fabric || { version: "0.8.20" };
var fabric = fabric || { version: "0.8.21" };
if (typeof exports != 'undefined') {
exports.fabric = fabric;
@ -9721,7 +9721,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
//= require "object.class"
(function(global) {
var commandLengths = {
m: 2,
l: 2,
@ -9733,7 +9733,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
t: 2,
a: 7
};
function drawArc(ctx, x, y, coords) {
var rx = coords[0];
var ry = coords[1];
@ -9748,19 +9748,19 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
ctx.bezierCurveTo.apply(ctx, bez);
}
}
var arcToSegmentsCache = { },
segmentToBezierCache = { },
_join = Array.prototype.join,
var arcToSegmentsCache = { },
segmentToBezierCache = { },
_join = Array.prototype.join,
argsString;
// Copied from Inkscape svgtopdf, thanks!
function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
argsString = _join.call(arguments);
if (arcToSegmentsCache[argsString]) {
return arcToSegmentsCache[argsString];
}
var th = rotateX * (Math.PI/180);
var sin_th = Math.sin(th);
var cos_th = Math.cos(th);
@ -9818,7 +9818,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
if (segmentToBezierCache[argsString]) {
return segmentToBezierCache[argsString];
}
var a00 = cos_th * rx;
var a01 = -sin_th * ry;
var a10 = sin_th * rx;
@ -9832,22 +9832,22 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
var y3 = cy + Math.sin(th1);
var x2 = x3 + t * Math.sin(th1);
var y2 = y3 - t * Math.cos(th1);
return (segmentToBezierCache[argsString] = [
a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
]);
}
"use strict";
var fabric = global.fabric || (global.fabric = { }),
min = fabric.util.array.min,
max = fabric.util.array.max,
extend = fabric.util.object.extend,
_toString = Object.prototype.toString;
if (fabric.Path) {
fabric.warn('fabric.Path is already defined');
return;
@ -9856,7 +9856,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
fabric.warn('fabric.Path requires fabric.Object');
return;
}
/**
* @private
*/
@ -9866,7 +9866,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
}
return item[item.length - 2];
}
/**
* @private
*/
@ -9876,19 +9876,19 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
}
return item[item.length - 1];
}
/**
/**
* @class Path
* @extends fabric.Object
*/
fabric.Path = fabric.util.createClass(fabric.Object, /** @scope fabric.Path.prototype */ {
/**
* @property
* @type String
*/
type: 'path',
/**
* Constructor
* @method initialize
@ -9897,31 +9897,31 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
*/
initialize: function(path, options) {
options = options || { };
this.setOptions(options);
if (!path) {
throw Error('`path` argument is required');
}
var fromArray = _toString.call(path) === '[object Array]';
this.path = fromArray
? path
: path.match && path.match(/[a-zA-Z][^a-zA-Z]*/g);
if (!this.path) return;
// TODO (kangax): rewrite this idiocracy
if (!fromArray) {
this._initializeFromArray(options);
}
if (options.sourcePath) {
this.setSourcePath(options.sourcePath);
}
},
/**
* @private
* @method _initializeFromArray
@ -9929,9 +9929,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
_initializeFromArray: function(options) {
var isWidthSet = 'width' in options,
isHeightSet = 'height' in options;
this.path = this._parsePath();
if (!isWidthSet || !isHeightSet) {
extend(this, this._parseDimensions());
if (isWidthSet) {
@ -9942,72 +9942,72 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
}
}
},
/**
* @private
* @method _render
*/
_render: function(ctx) {
var current, // current instruction
x = 0, // current x
var current, // current instruction
x = 0, // current x
y = 0, // current y
controlX = 0, // current control point x
controlY = 0, // current control point y
tempX,
tempX,
tempY,
l = -(this.width / 2),
t = -(this.height / 2);
for (var i = 0, len = this.path.length; i < len; ++i) {
current = this.path[i];
switch (current[0]) { // first letter
case 'l': // lineto, relative
x += current[1];
y += current[2];
ctx.lineTo(x + l, y + t);
break;
case 'L': // lineto, absolute
x = current[1];
y = current[2];
ctx.lineTo(x + l, y + t);
break;
case 'h': // horizontal lineto, relative
x += current[1];
ctx.lineTo(x + l, y + t);
break;
case 'H': // horizontal lineto, absolute
x = current[1];
ctx.lineTo(x + l, y + t);
break;
case 'v': // vertical lineto, relative
y += current[1];
ctx.lineTo(x + l, y + t);
break;
case 'V': // verical lineto, absolute
y = current[1];
ctx.lineTo(x + l, y + t);
break;
case 'm': // moveTo, relative
x += current[1];
y += current[2];
ctx.moveTo(x + l, y + t);
break;
case 'M': // moveTo, absolute
x = current[1];
y = current[2];
ctx.moveTo(x + l, y + t);
break;
case 'c': // bezierCurveTo, relative
tempX = x + current[5];
tempY = y + current[6];
@ -10024,27 +10024,27 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
x = tempX;
y = tempY;
break;
case 'C': // bezierCurveTo, absolute
x = current[5];
y = current[6];
controlX = current[3];
controlY = current[4];
ctx.bezierCurveTo(
current[1] + l,
current[2] + t,
controlX + l,
controlY + t,
x + l,
current[1] + l,
current[2] + t,
controlX + l,
controlY + t,
x + l,
y + t
);
break;
case 's': // shorthand cubic bezierCurveTo, relative
// transform to absolute x,y
tempX = x + current[3];
tempY = y + current[4];
// calculate reflection of previous control points
// calculate reflection of previous control points
controlX = 2 * x - controlX;
controlY = 2 * y - controlY;
ctx.bezierCurveTo(
@ -10055,14 +10055,19 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
tempX + l,
tempY + t
);
// set control point to 2nd one of this command
// "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point."
controlX = x + current[1];
controlY = y + current[2];
x = tempX;
y = tempY;
break;
case 'S': // shorthand cubic bezierCurveTo, absolute
tempX = current[3];
tempY = current[4];
// calculate reflection of previous control points
// calculate reflection of previous control points
controlX = 2*x - controlX;
controlY = 2*y - controlY;
ctx.bezierCurveTo(
@ -10075,19 +10080,25 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
);
x = tempX;
y = tempY;
// set control point to 2nd one of this command
// "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point."
controlX = current[1];
controlY = current[2];
break;
case 'q': // quadraticCurveTo, relative
x += current[3];
y += current[4];
ctx.quadraticCurveTo(
current[1] + l,
current[2] + t,
x + l,
current[1] + l,
current[2] + t,
x + l,
y + t
);
break;
case 'Q': // quadraticCurveTo, absolute
x = current[3];
y = current[4];
@ -10100,7 +10111,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
y + t
);
break;
case 'T':
tempX = x;
tempY = y;
@ -10112,41 +10123,41 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
ctx.quadraticCurveTo(
controlX + l,
controlY + t,
x + l,
x + l,
y + t
);
break;
case 'a':
// TODO: optimize this
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
current[6] + x + l,
current[7] + y + t
]);
x += current[6];
y += current[7];
break;
case 'A':
// TODO: optimize this
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
current[6] + l,
current[7] + t
]);
x = current[6];
y = current[7];
break;
case 'z':
case 'Z':
ctx.closePath();
@ -10154,9 +10165,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
}
}
},
/**
* Renders path on a specified context
* Renders path on a specified context
* @method render
* @param {CanvasRenderingContext2D} ctx context to render path on
* @param {Boolean} noTransform When true, context is not transformed
@ -10178,14 +10189,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
else if (this.fill) {
ctx.fillStyle = this.fill;
}
if (this.stroke) {
ctx.strokeStyle = this.stroke;
}
ctx.beginPath();
this._render(ctx);
if (this.fill) {
ctx.fill();
}
@ -10201,17 +10212,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
}
ctx.restore();
},
/**
* Returns string representation of an instance
* @method toString
* @return {String} string representation of an instance
*/
toString: function() {
return '#<fabric.Path (' + this.complexity() +
return '#<fabric.Path (' + this.complexity() +
'): { "top": ' + this.top + ', "left": ' + this.left + ' }>';
},
/**
* Returns object representation of an instance
* @method toObject
@ -10229,7 +10240,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
}
return o;
},
/**
* Returns dataless object representation of an instance
* @method toDatalessObject
@ -10243,7 +10254,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
delete o.sourcePath;
return o;
},
/**
* Returns svg representation of an instance
* @method toSVG
@ -10266,7 +10277,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
'</g>'
].join('');
},
/**
* Returns number representation of an instance complexity
* @method complexity
@ -10275,14 +10286,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
complexity: function() {
return this.path.length;
},
/**
* @private
* @method _parsePath
*/
_parsePath: function() {
var result = [ ],
currentPath,
currentPath,
chunks,
parsed;
@ -10300,7 +10311,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
var command = chunksParsed[0].toLowerCase(),
commandLength = commandLengths[command];
if (chunksParsed.length - 1 > commandLength) {
for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) {
result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength)));
@ -10310,22 +10321,22 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
result.push(chunksParsed);
}
}
return result;
},
/**
* @method _parseDimensions
*/
_parseDimensions: function() {
var aX = [],
aY = [],
previousX,
previousY,
isLowerCase = false,
x,
var aX = [],
aY = [],
previousX,
previousY,
isLowerCase = false,
x,
y;
this.path.forEach(function(item, i) {
if (item[0] !== 'H') {
previousX = (i === 0) ? getX(item) : getX(this.path[i-1]);
@ -10333,57 +10344,57 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
if (item[0] !== 'V') {
previousY = (i === 0) ? getY(item) : getY(this.path[i-1]);
}
// lowercased letter denotes relative position;
// lowercased letter denotes relative position;
// transform to absolute
if (item[0] === item[0].toLowerCase()) {
isLowerCase = true;
}
// last 2 items in an array of coordinates are the actualy x/y (except H/V);
// collect them
// TODO (kangax): support relative h/v commands
x = isLowerCase
? previousX + getX(item)
: item[0] === 'V'
? previousX
: item[0] === 'V'
? previousX
: getX(item);
y = isLowerCase
? previousY + getY(item)
: item[0] === 'H'
? previousY
: item[0] === 'H'
? previousY
: getY(item);
var val = parseInt(x, 10);
if (!isNaN(val)) aX.push(val);
val = parseInt(y, 10);
if (!isNaN(val)) aY.push(val);
}, this);
var minX = min(aX),
minY = min(aY),
var minX = min(aX),
minY = min(aY),
deltaX = 0,
deltaY = 0;
var o = {
top: minY - deltaY,
left: minX - deltaX,
bottom: max(aY) - deltaY,
right: max(aX) - deltaX
};
o.width = o.right - o.left;
o.height = o.bottom - o.top;
return o;
}
});
/**
* Creates an instance of fabric.Path from an object
* @static
@ -10393,14 +10404,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
fabric.Path.fromObject = function(object) {
return new fabric.Path(object.path, object);
};
/**
* List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
* @static
* @see http://www.w3.org/TR/SVG/paths.html#PathElement
*/
fabric.Path.ATTRIBUTE_NAMES = 'd fill fill-opacity opacity fill-rule stroke stroke-width transform'.split(' ');
/**
* Creates an instance of fabric.Path from an SVG <path> element
* @static

4
dist/all.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/all.min.js.gz vendored

Binary file not shown.

View file

@ -1,7 +1,7 @@
{
"name": "fabric",
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
"version": "0.8.20",
"version": "0.8.21",
"author": "Juriy Zaytsev <kangax@gmail.com>",
"keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"],
"repository": "git://github.com/kangax/fabric.js",

View file

@ -1,7 +1,7 @@
//= require "object.class"
(function(global) {
var commandLengths = {
m: 2,
l: 2,
@ -13,7 +13,7 @@
t: 2,
a: 7
};
function drawArc(ctx, x, y, coords) {
var rx = coords[0];
var ry = coords[1];
@ -28,19 +28,19 @@
ctx.bezierCurveTo.apply(ctx, bez);
}
}
var arcToSegmentsCache = { },
segmentToBezierCache = { },
_join = Array.prototype.join,
var arcToSegmentsCache = { },
segmentToBezierCache = { },
_join = Array.prototype.join,
argsString;
// Copied from Inkscape svgtopdf, thanks!
function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
argsString = _join.call(arguments);
if (arcToSegmentsCache[argsString]) {
return arcToSegmentsCache[argsString];
}
var th = rotateX * (Math.PI/180);
var sin_th = Math.sin(th);
var cos_th = Math.cos(th);
@ -98,7 +98,7 @@
if (segmentToBezierCache[argsString]) {
return segmentToBezierCache[argsString];
}
var a00 = cos_th * rx;
var a01 = -sin_th * ry;
var a10 = sin_th * rx;
@ -112,22 +112,22 @@
var y3 = cy + Math.sin(th1);
var x2 = x3 + t * Math.sin(th1);
var y2 = y3 - t * Math.cos(th1);
return (segmentToBezierCache[argsString] = [
a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
]);
}
"use strict";
var fabric = global.fabric || (global.fabric = { }),
min = fabric.util.array.min,
max = fabric.util.array.max,
extend = fabric.util.object.extend,
_toString = Object.prototype.toString;
if (fabric.Path) {
fabric.warn('fabric.Path is already defined');
return;
@ -136,7 +136,7 @@
fabric.warn('fabric.Path requires fabric.Object');
return;
}
/**
* @private
*/
@ -146,7 +146,7 @@
}
return item[item.length - 2];
}
/**
* @private
*/
@ -156,19 +156,19 @@
}
return item[item.length - 1];
}
/**
/**
* @class Path
* @extends fabric.Object
*/
fabric.Path = fabric.util.createClass(fabric.Object, /** @scope fabric.Path.prototype */ {
/**
* @property
* @type String
*/
type: 'path',
/**
* Constructor
* @method initialize
@ -177,31 +177,31 @@
*/
initialize: function(path, options) {
options = options || { };
this.setOptions(options);
if (!path) {
throw Error('`path` argument is required');
}
var fromArray = _toString.call(path) === '[object Array]';
this.path = fromArray
? path
: path.match && path.match(/[a-zA-Z][^a-zA-Z]*/g);
if (!this.path) return;
// TODO (kangax): rewrite this idiocracy
if (!fromArray) {
this._initializeFromArray(options);
}
if (options.sourcePath) {
this.setSourcePath(options.sourcePath);
}
},
/**
* @private
* @method _initializeFromArray
@ -209,9 +209,9 @@
_initializeFromArray: function(options) {
var isWidthSet = 'width' in options,
isHeightSet = 'height' in options;
this.path = this._parsePath();
if (!isWidthSet || !isHeightSet) {
extend(this, this._parseDimensions());
if (isWidthSet) {
@ -222,72 +222,72 @@
}
}
},
/**
* @private
* @method _render
*/
_render: function(ctx) {
var current, // current instruction
x = 0, // current x
var current, // current instruction
x = 0, // current x
y = 0, // current y
controlX = 0, // current control point x
controlY = 0, // current control point y
tempX,
tempX,
tempY,
l = -(this.width / 2),
t = -(this.height / 2);
for (var i = 0, len = this.path.length; i < len; ++i) {
current = this.path[i];
switch (current[0]) { // first letter
case 'l': // lineto, relative
x += current[1];
y += current[2];
ctx.lineTo(x + l, y + t);
break;
case 'L': // lineto, absolute
x = current[1];
y = current[2];
ctx.lineTo(x + l, y + t);
break;
case 'h': // horizontal lineto, relative
x += current[1];
ctx.lineTo(x + l, y + t);
break;
case 'H': // horizontal lineto, absolute
x = current[1];
ctx.lineTo(x + l, y + t);
break;
case 'v': // vertical lineto, relative
y += current[1];
ctx.lineTo(x + l, y + t);
break;
case 'V': // verical lineto, absolute
y = current[1];
ctx.lineTo(x + l, y + t);
break;
case 'm': // moveTo, relative
x += current[1];
y += current[2];
ctx.moveTo(x + l, y + t);
break;
case 'M': // moveTo, absolute
x = current[1];
y = current[2];
ctx.moveTo(x + l, y + t);
break;
case 'c': // bezierCurveTo, relative
tempX = x + current[5];
tempY = y + current[6];
@ -304,27 +304,27 @@
x = tempX;
y = tempY;
break;
case 'C': // bezierCurveTo, absolute
x = current[5];
y = current[6];
controlX = current[3];
controlY = current[4];
ctx.bezierCurveTo(
current[1] + l,
current[2] + t,
controlX + l,
controlY + t,
x + l,
current[1] + l,
current[2] + t,
controlX + l,
controlY + t,
x + l,
y + t
);
break;
case 's': // shorthand cubic bezierCurveTo, relative
// transform to absolute x,y
tempX = x + current[3];
tempY = y + current[4];
// calculate reflection of previous control points
// calculate reflection of previous control points
controlX = 2 * x - controlX;
controlY = 2 * y - controlY;
ctx.bezierCurveTo(
@ -335,14 +335,19 @@
tempX + l,
tempY + t
);
// set control point to 2nd one of this command
// "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point."
controlX = x + current[1];
controlY = y + current[2];
x = tempX;
y = tempY;
break;
case 'S': // shorthand cubic bezierCurveTo, absolute
tempX = current[3];
tempY = current[4];
// calculate reflection of previous control points
// calculate reflection of previous control points
controlX = 2*x - controlX;
controlY = 2*y - controlY;
ctx.bezierCurveTo(
@ -355,19 +360,25 @@
);
x = tempX;
y = tempY;
// set control point to 2nd one of this command
// "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point."
controlX = current[1];
controlY = current[2];
break;
case 'q': // quadraticCurveTo, relative
x += current[3];
y += current[4];
ctx.quadraticCurveTo(
current[1] + l,
current[2] + t,
x + l,
current[1] + l,
current[2] + t,
x + l,
y + t
);
break;
case 'Q': // quadraticCurveTo, absolute
x = current[3];
y = current[4];
@ -380,7 +391,7 @@
y + t
);
break;
case 'T':
tempX = x;
tempY = y;
@ -392,41 +403,41 @@
ctx.quadraticCurveTo(
controlX + l,
controlY + t,
x + l,
x + l,
y + t
);
break;
case 'a':
// TODO: optimize this
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
current[6] + x + l,
current[7] + y + t
]);
x += current[6];
y += current[7];
break;
case 'A':
// TODO: optimize this
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
drawArc(ctx, x + l, y + t, [
current[1],
current[2],
current[3],
current[4],
current[5],
current[6] + l,
current[7] + t
]);
x = current[6];
y = current[7];
break;
case 'z':
case 'Z':
ctx.closePath();
@ -434,9 +445,9 @@
}
}
},
/**
* Renders path on a specified context
* Renders path on a specified context
* @method render
* @param {CanvasRenderingContext2D} ctx context to render path on
* @param {Boolean} noTransform When true, context is not transformed
@ -458,14 +469,14 @@
else if (this.fill) {
ctx.fillStyle = this.fill;
}
if (this.stroke) {
ctx.strokeStyle = this.stroke;
}
ctx.beginPath();
this._render(ctx);
if (this.fill) {
ctx.fill();
}
@ -481,17 +492,17 @@
}
ctx.restore();
},
/**
* Returns string representation of an instance
* @method toString
* @return {String} string representation of an instance
*/
toString: function() {
return '#<fabric.Path (' + this.complexity() +
return '#<fabric.Path (' + this.complexity() +
'): { "top": ' + this.top + ', "left": ' + this.left + ' }>';
},
/**
* Returns object representation of an instance
* @method toObject
@ -509,7 +520,7 @@
}
return o;
},
/**
* Returns dataless object representation of an instance
* @method toDatalessObject
@ -523,7 +534,7 @@
delete o.sourcePath;
return o;
},
/**
* Returns svg representation of an instance
* @method toSVG
@ -546,7 +557,7 @@
'</g>'
].join('');
},
/**
* Returns number representation of an instance complexity
* @method complexity
@ -555,14 +566,14 @@
complexity: function() {
return this.path.length;
},
/**
* @private
* @method _parsePath
*/
_parsePath: function() {
var result = [ ],
currentPath,
currentPath,
chunks,
parsed;
@ -580,7 +591,7 @@
var command = chunksParsed[0].toLowerCase(),
commandLength = commandLengths[command];
if (chunksParsed.length - 1 > commandLength) {
for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) {
result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength)));
@ -590,22 +601,22 @@
result.push(chunksParsed);
}
}
return result;
},
/**
* @method _parseDimensions
*/
_parseDimensions: function() {
var aX = [],
aY = [],
previousX,
previousY,
isLowerCase = false,
x,
var aX = [],
aY = [],
previousX,
previousY,
isLowerCase = false,
x,
y;
this.path.forEach(function(item, i) {
if (item[0] !== 'H') {
previousX = (i === 0) ? getX(item) : getX(this.path[i-1]);
@ -613,57 +624,57 @@
if (item[0] !== 'V') {
previousY = (i === 0) ? getY(item) : getY(this.path[i-1]);
}
// lowercased letter denotes relative position;
// lowercased letter denotes relative position;
// transform to absolute
if (item[0] === item[0].toLowerCase()) {
isLowerCase = true;
}
// last 2 items in an array of coordinates are the actualy x/y (except H/V);
// collect them
// TODO (kangax): support relative h/v commands
x = isLowerCase
? previousX + getX(item)
: item[0] === 'V'
? previousX
: item[0] === 'V'
? previousX
: getX(item);
y = isLowerCase
? previousY + getY(item)
: item[0] === 'H'
? previousY
: item[0] === 'H'
? previousY
: getY(item);
var val = parseInt(x, 10);
if (!isNaN(val)) aX.push(val);
val = parseInt(y, 10);
if (!isNaN(val)) aY.push(val);
}, this);
var minX = min(aX),
minY = min(aY),
var minX = min(aX),
minY = min(aY),
deltaX = 0,
deltaY = 0;
var o = {
top: minY - deltaY,
left: minX - deltaX,
bottom: max(aY) - deltaY,
right: max(aX) - deltaX
};
o.width = o.right - o.left;
o.height = o.bottom - o.top;
return o;
}
});
/**
* Creates an instance of fabric.Path from an object
* @static
@ -673,14 +684,14 @@
fabric.Path.fromObject = function(object) {
return new fabric.Path(object.path, object);
};
/**
* List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
* @static
* @see http://www.w3.org/TR/SVG/paths.html#PathElement
*/
fabric.Path.ATTRIBUTE_NAMES = 'd fill fill-opacity opacity fill-rule stroke stroke-width transform'.split(' ');
/**
* Creates an instance of fabric.Path from an SVG <path> element
* @static