Merge master. Enable hasRotatingPoint by default (now that objects are not rotated via corners)

This commit is contained in:
kangax 2012-12-26 11:18:17 +01:00
commit 2735d543e2
16 changed files with 248 additions and 131 deletions

View file

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

117
dist/all.js vendored
View file

@ -1,7 +1,7 @@
/* build: `node build.js modules=ALL` */
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
var fabric = fabric || { version: "0.9.33" };
var fabric = fabric || { version: "0.9.35" };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
@ -2100,14 +2100,35 @@ fabric.Observable.off = fabric.Observable.stopObserving;
* @static
* @memberOf fabric.util
* @method groupSVGElements
* @param {Array} elements
* @param {Array} elements SVG elements to group
* @param {Object} [options] Options object
* @return {fabric.Object|fabric.PathGroup}
*/
function groupSVGElements(elements, options, path) {
var object = elements.length > 1
? new fabric.PathGroup(elements, options)
: elements[0];
var object;
if (elements.length > 1) {
var hasText = elements.some(function(el) { return el.type === 'text'; });
if (hasText) {
object = new fabric.Group([ ], options);
elements.reverse().forEach(function(obj) {
if (obj.cx) {
obj.left = obj.cx;
}
if (obj.cy) {
obj.top = obj.cy;
}
object.addWithUpdate(obj);
});
}
else {
object = new fabric.PathGroup(elements, options);
}
}
else {
object = elements[0];
}
if (typeof path !== 'undefined') {
object.setSourcePath(path);
@ -2120,9 +2141,9 @@ fabric.Observable.off = fabric.Observable.stopObserving;
* @static
* @memberOf fabric.util
* @method populateWithProperties
* @param {Object} source
* @param {Object} destination
* @return {Array} properties
* @param {Object} source Source object
* @param {Object} destination Destination object
* @return {Array} properties Propertie names to include
*/
function populateWithProperties(source, destination, properties) {
if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
@ -3875,14 +3896,22 @@ fabric.util.string = {
if (typeof style === 'string') {
style = style.replace(/;$/, '').split(';').forEach(function (current) {
var attr = current.split(':');
oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = attr[1].trim();
var value = attr[1].trim();
// TODO: need to normalize em, %, pt, etc. to px (!)
var parsed = parseFloat(value);
oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = isNaN(parsed) ? value : parsed;
});
}
else {
for (var prop in style) {
if (typeof style[prop] === 'undefined') continue;
oStyle[normalizeAttr(prop.toLowerCase())] = style[prop];
var parsed = parseFloat(style[prop]);
oStyle[normalizeAttr(prop.toLowerCase())] = isNaN(parsed) ? style[prop] : parsed;
}
}
@ -8251,8 +8280,13 @@ fabric.util.string = {
return this;
},
/**
* @private
* @method _adjustPosition
* @param obj
* @param {String} to One of left, center, right
*/
_adjustPosition: function(obj, to) {
console.log('adjusting position');
var angle = fabric.util.degreesToRadians(obj.angle);
@ -8486,7 +8520,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
delete obj[pathProp];
if (typeof path !== 'string') {
if (obj.type === 'image') {
if (obj.type === 'image' || obj.type === 'group') {
fabric[fabric.util.string.capitalize(obj.type)].fromObject(obj, function (o) {
onObjectLoaded(o, index);
});
@ -8727,6 +8761,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
return;
}
var Image = global.Image;
try {
var NodeImage = (typeof require !== 'undefined') && require('canvas').Image;
if (NodeImage) {
Image = NodeImage;
}
}
catch(err) {
fabric.log(err);
}
/**
* Root object class from which all 2d shape classes inherit from
* @class Object
@ -8830,7 +8875,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
* @property
* @type Number
*/
cornersize: 12,
cornerSize: 12,
/**
* When true, object's corners are rendered as transparent inside (i.e. stroke instead of fill)
@ -8956,7 +9001,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
* @property
* @type Boolean
*/
hasRotatingPoint: false,
hasRotatingPoint: true,
/**
* Offset for object's rotating point (when enabled via `hasRotatingPoint`)
@ -8987,7 +9032,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
*/
stateProperties: (
'top left width height scaleX scaleY flipX flipY ' +
'angle opacity cornersize fill overlayFill originX originY ' +
'angle opacity cornerSize fill overlayFill originX originY ' +
'stroke strokeWidth strokeDashArray fillRule ' +
'borderScaleFactor transformMatrix selectable'
).split(' '),
@ -9812,7 +9857,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
/**
* Draws corners of an object's bounding box.
* Requires public properties: width, height, scaleX, scaleY
* Requires public options: cornersize, padding
* Requires public options: cornerSize, padding
* @method drawCorners
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @return {fabric.Object} thisArg
@ -9821,7 +9866,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
drawCorners: function(ctx) {
if (!this.hasControls) return;
var size = this.cornersize,
var size = this.cornerSize,
size2 = size / 2,
strokeWidth2 = this.strokeWidth / 2,
left = -(this.width / 2),
@ -10275,7 +10320,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
var coords = this.oCoords,
theta = degreesToRadians(this.angle),
newTheta = degreesToRadians(45 - this.angle),
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornersize, 2)) / 2,
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
sinTh = Math.sin(theta),
@ -10528,14 +10573,18 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
_animate: function(property, to, options) {
var obj = this;
to = to.toString();
options || (options = { });
if (!('from' in options)) {
options.from = this.get(property);
}
if (/[+\-]/.test((to + '').charAt(0))) {
to = this.get(property) + parseFloat(to);
if (~to.indexOf('=')) {
to = this.get(property) + parseFloat(to.replace('=', ''));
}
else {
to = parseFloat(to);
}
fabric.util.animate({
@ -11024,7 +11073,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
if ('top' in parsedAttributes) {
parsedAttributes.top -= (options.height / 2) || 0;
}
return new fabric.Circle(extend(parsedAttributes, options));
var obj = new fabric.Circle(extend(parsedAttributes, options));
obj.cx = parseFloat(element.getAttribute('cx')) || 0;
obj.cy = parseFloat(element.getAttribute('cy')) || 0;
return obj;
};
/**
@ -13889,19 +13943,11 @@ fabric.util.object.extend(fabric.Object.prototype, {
* @return {Number} angle value
*/
_getAngleValueForStraighten: function() {
var angle = this.get('angle');
// TODO (kangax): can this be simplified?
if (angle > -225 && angle <= -135) { return -180; }
else if (angle > -135 && angle <= -45) { return -90; }
else if (angle > -45 && angle <= 45) { return 0; }
else if (angle > 45 && angle <= 135) { return 90; }
else if (angle > 135 && angle <= 225 ) { return 180; }
else if (angle > 225 && angle <= 315) { return 270; }
else if (angle > 315) { return 360; }
return 0;
var angle = this.getAngle() % 360;
if (angle > 0) {
return Math.round((angle-1)/90) * 90;
}
return Math.round(angle/90) * 90;
},
/**
@ -13911,8 +13957,7 @@ fabric.util.object.extend(fabric.Object.prototype, {
* @chainable
*/
straighten: function() {
var angle = this._getAngleValueForStraighten();
this.setAngle(angle);
this.setAngle(this._getAngleValueForStraighten());
return this;
},

10
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.9.33",
"version": "0.9.35",
"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

@ -56,7 +56,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
delete obj[pathProp];
if (typeof path !== 'string') {
if (obj.type === 'image') {
if (obj.type === 'image' || obj.type === 'group') {
fabric[fabric.util.string.capitalize(obj.type)].fromObject(obj, function (o) {
onObjectLoaded(o, index);
});

View file

@ -152,7 +152,12 @@
if ('top' in parsedAttributes) {
parsedAttributes.top -= (options.height / 2) || 0;
}
return new fabric.Circle(extend(parsedAttributes, options));
var obj = new fabric.Circle(extend(parsedAttributes, options));
obj.cx = parseFloat(element.getAttribute('cx')) || 0;
obj.cy = parseFloat(element.getAttribute('cy')) || 0;
return obj;
};
/**

View file

@ -13,6 +13,17 @@
return;
}
var Image = global.Image;
try {
var NodeImage = (typeof require !== 'undefined') && require('canvas').Image;
if (NodeImage) {
Image = NodeImage;
}
}
catch(err) {
fabric.log(err);
}
/**
* Root object class from which all 2d shape classes inherit from
* @class Object
@ -116,7 +127,7 @@
* @property
* @type Number
*/
cornersize: 12,
cornerSize: 12,
/**
* When true, object's corners are rendered as transparent inside (i.e. stroke instead of fill)
@ -242,7 +253,7 @@
* @property
* @type Boolean
*/
hasRotatingPoint: false,
hasRotatingPoint: true,
/**
* Offset for object's rotating point (when enabled via `hasRotatingPoint`)
@ -273,7 +284,7 @@
*/
stateProperties: (
'top left width height scaleX scaleY flipX flipY ' +
'angle opacity cornersize fill overlayFill originX originY ' +
'angle opacity cornerSize fill overlayFill originX originY ' +
'stroke strokeWidth strokeDashArray fillRule ' +
'borderScaleFactor transformMatrix selectable'
).split(' '),
@ -1098,7 +1109,7 @@
/**
* Draws corners of an object's bounding box.
* Requires public properties: width, height, scaleX, scaleY
* Requires public options: cornersize, padding
* Requires public options: cornerSize, padding
* @method drawCorners
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @return {fabric.Object} thisArg
@ -1107,7 +1118,7 @@
drawCorners: function(ctx) {
if (!this.hasControls) return;
var size = this.cornersize,
var size = this.cornerSize,
size2 = size / 2,
strokeWidth2 = this.strokeWidth / 2,
left = -(this.width / 2),
@ -1561,7 +1572,7 @@
var coords = this.oCoords,
theta = degreesToRadians(this.angle),
newTheta = degreesToRadians(45 - this.angle),
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornersize, 2)) / 2,
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
sinTh = Math.sin(theta),
@ -1814,14 +1825,18 @@
_animate: function(property, to, options) {
var obj = this;
to = to.toString();
options || (options = { });
if (!('from' in options)) {
options.from = this.get(property);
}
if (/[+\-]/.test((to + '').charAt(0))) {
to = this.get(property) + parseFloat(to);
if (~to.indexOf('=')) {
to = this.get(property) + parseFloat(to.replace('=', ''));
}
else {
to = parseFloat(to);
}
fabric.util.animate({

View file

@ -6,19 +6,11 @@ fabric.util.object.extend(fabric.Object.prototype, {
* @return {Number} angle value
*/
_getAngleValueForStraighten: function() {
var angle = this.get('angle');
// TODO (kangax): can this be simplified?
if (angle > -225 && angle <= -135) { return -180; }
else if (angle > -135 && angle <= -45) { return -90; }
else if (angle > -45 && angle <= 45) { return 0; }
else if (angle > 45 && angle <= 135) { return 90; }
else if (angle > 135 && angle <= 225 ) { return 180; }
else if (angle > 225 && angle <= 315) { return 270; }
else if (angle > 315) { return 360; }
return 0;
var angle = this.getAngle() % 360;
if (angle > 0) {
return Math.round((angle-1)/90) * 90;
}
return Math.round(angle/90) * 90;
},
/**
@ -28,8 +20,7 @@ fabric.util.object.extend(fabric.Object.prototype, {
* @chainable
*/
straighten: function() {
var angle = this._getAngleValueForStraighten();
this.setAngle(angle);
this.setAngle(this._getAngleValueForStraighten());
return this;
},

View file

@ -283,14 +283,22 @@
if (typeof style === 'string') {
style = style.replace(/;$/, '').split(';').forEach(function (current) {
var attr = current.split(':');
oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = attr[1].trim();
var value = attr[1].trim();
// TODO: need to normalize em, %, pt, etc. to px (!)
var parsed = parseFloat(value);
oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = isNaN(parsed) ? value : parsed;
});
}
else {
for (var prop in style) {
if (typeof style[prop] === 'undefined') continue;
oStyle[normalizeAttr(prop.toLowerCase())] = style[prop];
var parsed = parseFloat(style[prop]);
oStyle[normalizeAttr(prop.toLowerCase())] = isNaN(parsed) ? style[prop] : parsed;
}
}

View file

@ -240,14 +240,35 @@
* @static
* @memberOf fabric.util
* @method groupSVGElements
* @param {Array} elements
* @param {Array} elements SVG elements to group
* @param {Object} [options] Options object
* @return {fabric.Object|fabric.PathGroup}
*/
function groupSVGElements(elements, options, path) {
var object = elements.length > 1
? new fabric.PathGroup(elements, options)
: elements[0];
var object;
if (elements.length > 1) {
var hasText = elements.some(function(el) { return el.type === 'text'; });
if (hasText) {
object = new fabric.Group([ ], options);
elements.reverse().forEach(function(obj) {
if (obj.cx) {
obj.left = obj.cx;
}
if (obj.cy) {
obj.top = obj.cy;
}
object.addWithUpdate(obj);
});
}
else {
object = new fabric.PathGroup(elements, options);
}
}
else {
object = elements[0];
}
if (typeof path !== 'undefined') {
object.setSourcePath(path);
@ -260,9 +281,9 @@
* @static
* @memberOf fabric.util
* @method populateWithProperties
* @param {Object} source
* @param {Object} destination
* @return {Array} properties
* @param {Object} source Source object
* @param {Object} destination Destination object
* @return {Array} properties Propertie names to include
*/
function populateWithProperties(source, destination, properties) {
if (properties && Object.prototype.toString.call(properties) === '[object Array]') {

View file

@ -31,7 +31,10 @@
'"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false,"transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0}],'+
'"background":"#ff5555"}';
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.Canvas();
var el = fabric.document.createElement('canvas');
el.width = 600; el.height = 600;
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.Canvas(el);
var upperCanvasEl = canvas.upperCanvasEl;
var lowerCanvasEl = canvas.lowerCanvasEl;
@ -878,4 +881,29 @@
});
});
asyncTest('loadFromDatalessJSON with async content', function() {
var circ1 = new fabric.Circle({ radius: 30, fill: '#55f', top: 0, left: 0 });
var circ2 = new fabric.Circle({ radius: 30, fill: '#f55', top: 50, left: 50 });
var circ3 = new fabric.Circle({ radius: 30, fill: '#5f5', top: 50, left: 50 });
var arr = [circ1, circ2];
var group = new fabric.Group(arr, { top: 150, left: 150 });
canvas.add(circ3);
canvas.add(group);
canvas.renderAll();
canvas.deactivateAll();
var json = JSON.stringify( canvas.toDatalessJSON() );
canvas.clear();
canvas.loadFromDatalessJSON(json, function() {
equal(2, canvas.getObjects().length);
equal('group', canvas.getObjects()[1].type);
start();
});
});
})();

View file

@ -38,7 +38,10 @@
// TODO: fix this
var Canvas = fabric.Canvas;
fabric.Canvas = null;
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas();
var el = fabric.document.createElement('canvas');
el.width = 600; el.height = 600;
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el);
fabric.Canvas = Canvas;
var lowerCanvasEl = canvas.lowerCanvasEl;

View file

@ -419,30 +419,30 @@
equal(cObj.getAngle(), 0);
});
// asyncTest('cloneAsImage', function() {
// var cObj = new fabric.Rect({ width: 100, height: 100, fill: 'red' });
asyncTest('cloneAsImage', function() {
var cObj = new fabric.Rect({ width: 100, height: 100, fill: 'red' });
// ok(typeof cObj.cloneAsImage == 'function');
ok(typeof cObj.cloneAsImage == 'function');
// if (!fabric.Canvas.supports('toDataURL')) {
// //alert('`toDataURL` is not supported by this environment; skipping `cloneAsImage` test (as it relies on `toDataURL`)');
// start();
// }
// else {
// var image;
// var _this = this;
if (!fabric.Canvas.supports('toDataURL')) {
fabric.log('`toDataURL` is not supported by this environment; skipping `cloneAsImage` test (as it relies on `toDataURL`)');
start();
}
else {
var image;
var _this = this;
// setTimeout(function() {
// ok(image);
// ok(image instanceof fabric.Image);
// start();
// }, 500);
setTimeout(function() {
ok(image);
ok(image instanceof fabric.Image);
start();
}, 500);
// cObj.cloneAsImage(function(i) {
// image = i;
// });
// }
// });
cObj.cloneAsImage(function(i) {
image = i;
});
}
});
asyncTest('toDataURL', function() {
var data =
@ -591,7 +591,7 @@
object.setAngle(999);
object.straighten();
equal(object.get('angle'), 360);
equal(object.get('angle'), 270);
});
test('toGrayscale', function() {
@ -639,36 +639,36 @@
}, 1000);
});
asyncTest('animate', function() {
var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 43 });
// asyncTest('animate', function() {
// var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 43 });
ok(typeof object.animate == 'function');
// ok(typeof object.animate == 'function');
object.animate('left', 40);
ok(true, 'animate without options does not crash');
// object.animate('left', 40);
// ok(true, 'animate without options does not crash');
setTimeout(function() {
// setTimeout(function() {
equal(40, Math.round(object.getLeft()));
start();
// equal(40, Math.round(object.getLeft()));
// start();
}, 1000);
});
// }, 1000);
// });
asyncTest('animate multiple properties', function() {
var object = new fabric.Object({ left: 123, top: 124 });
// asyncTest('animate multiple properties', function() {
// var object = new fabric.Object({ left: 123, top: 124 });
object.animate({ left: 223, top: 224 });
// object.animate({ left: 223, top: 224 });
setTimeout(function() {
// setTimeout(function() {
equal(223, Math.round(object.get('left')));
equal(224, Math.round(object.get('top')));
// equal(223, Math.round(object.get('left')));
// equal(224, Math.round(object.get('top')));
start();
// start();
}, 1000);
});
// }, 1000);
// });
test('observable', function() {
var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 43 });
@ -712,8 +712,8 @@
canvas.add(object);
canvas.insertAt(object2, 0);
equal(object.canvas, canvas);
equal(object2.canvas, canvas);
ok(object.canvas === canvas);
ok(object2.canvas === canvas);
});
test('remove', function() {

View file

@ -127,10 +127,10 @@
element.setAttribute('style', 'left:10px;top:22.3em;width:103.45pt;height:20%;');
var expectedObject = {
'left': '10px',
'top': '22.3em',
'width': '103.45pt',
'height': '20%'
'left': 10,
'top': 22.3,
'width': 103.45,
'height': 20
};
deepEqual(expectedObject, fabric.parseStyleAttribute(element));
});
@ -140,7 +140,7 @@
element.setAttribute('style', 'left:10px');
var expectedObject = {
'left': '10px'
'left': 10
};
deepEqual(expectedObject, fabric.parseStyleAttribute(element));
});

View file

@ -1,11 +1,13 @@
(function() {
var path = require("path");
QUnit.module('fabric.util');
function K (x) { return x }
function _createImageElement() {
return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img');
}
function getAbsolutePath(path) {
var isAbsolute = /^https?:/.test(path);
if (isAbsolute) return path;
@ -17,7 +19,7 @@
}
var IMG_URL = fabric.isLikelyNode
? path.join(__dirname, '../fixtures/', 'very_large_image.jpg')
? require("path").join(__dirname, '../fixtures/', 'very_large_image.jpg')
: getAbsolutePath('../fixtures/very_large_image.jpg');
test('fabric.util.toFixed', function(){
@ -400,8 +402,7 @@
ok(typeof fabric.util.loadImage == 'function');
var callbackInvoked = false,
objectPassedToCallback,
NodeCanvasImage = require('canvas').Image;
objectPassedToCallback;
if (IMG_URL.indexOf('/home/travis') === 0) {
// image can not be accessed on travis so we're returning early