mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-28 23:08:17 +00:00
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:
parent
da2ed018d8
commit
1706361616
6 changed files with 271 additions and 249 deletions
|
|
@ -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
257
dist/all.js
vendored
|
|
@ -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
4
dist/all.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/all.min.js.gz
vendored
BIN
dist/all.min.js.gz
vendored
Binary file not shown.
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue