Merge master

This commit is contained in:
kangax 2013-03-19 15:52:14 +01:00
commit 8bea909a54
64 changed files with 2081 additions and 960 deletions

View file

@ -3,6 +3,7 @@ node_js:
- 0.6
- 0.8
- 0.9
- 0.10
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq libgif-dev libpng-dev libjpeg8-dev libpango1.0-dev libcairo2-dev

View file

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

1314
dist/all.js vendored

File diff suppressed because it is too large Load diff

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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": "1.0.6",
"version": "1.1.1",
"author": "Juriy Zaytsev <kangax@gmail.com>",
"keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"],
"repository": "git://github.com/kangax/fabric.js",
@ -11,17 +11,17 @@
}],
"scripts": {
"build": "node build.js modules=ALL exclude=json,cufon,gestures",
"test": "node test.js"
"test": "node test.js && jshint src"
},
"dependencies": {
"canvas": "~1.0.0",
"jsdom": ">=0.2.3",
"xmldom": ">=0.1.7"
"canvas": "1.0.x",
"jsdom": "0.5.x",
"xmldom": "0.1.x"
},
"devDependencies": {
"qunit": "0.5.x",
"jshint": "0.9.x",
"uglify-js": ">=2.0.0"
"jshint": "1.1.x",
"uglify-js": "2.2.x"
},
"engines": { "node": ">=0.4.0 && <1.0.0" },
"main": "./dist/all.js"

View file

@ -389,6 +389,7 @@
target: this._currentTransform.target,
e: e
});
this._currentTransform.target.fire('scaling', { e: e });
}
// else if (this._currentTransform.action === 'scale') {
// this._scaleObject(x, y);

View file

@ -59,12 +59,25 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
return ('<circle ' +
'cx="0" cy="0" ' +
'r="' + this.radius + '" ' +
'style="' + this.getSvgStyles() + '" ' +
'transform="' + this.getSvgTransform() + '" ' +
'/>');
var markup = [];
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, false));
}
markup.push(
'<circle ',
'cx="0" cy="0" ',
'r="', this.radius,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
);
return markup.join('');
},
/**

View file

@ -37,7 +37,14 @@
* @method _tryParsingColor
*/
_tryParsingColor: function(color) {
var source = Color.sourceFromHex(color);
var source;
if (color in Color.colorNameMap) {
color = Color.colorNameMap[color];
}
source = Color.sourceFromHex(color);
if (!source) {
source = Color.sourceFromRgb(color);
}
@ -197,6 +204,30 @@
*/
fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
/**
* Map of the 16 basic color names with HEX code
* @static
* @field
*/
fabric.Color.colorNameMap = {
'aqua': '#00FFFF',
'black': '#000000',
'blue': '#0000FF',
'fuchsia': '#FF00FF',
'gray': '#808080',
'green': '#008000',
'lime': '#00FF00',
'maroon': '#800000',
'navy': '#000080',
'olive': '#808000',
'purple': '#800080',
'red': '#FF0000',
'silver': '#C0C0C0',
'teal': '#008080',
'white': '#FFFFFF',
'yellow': '#FFFF00'
};
/**
* Returns new color object, when given a color in RGB format
* @method fromRgb

View file

@ -25,6 +25,20 @@
*/
type: 'ellipse',
/**
* Horizontal radius
* @property
* @type Number
*/
rx: 0,
/**
* Vertical radius
* @property
* @type Number
*/
ry: 0,
/**
* Constructor
* @method initialize
@ -62,14 +76,25 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
return [
var markup = [];
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, false));
}
markup.push(
'<ellipse ',
'rx="', this.get('rx'), '" ',
'ry="', this.get('ry'), '" ',
'style="', this.getSvgStyles(), '" ',
'transform="', this.getSvgTransform(), '" ',
'/>'
].join('');
'rx="', this.get('rx'),
'" ry="', this.get('ry'),
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
);
return markup.join('');
},
/**

View file

@ -1,7 +1,12 @@
(function() {
function getColorStopFromStyle(el) {
var style = el.getAttribute('style');
function getColorStop(el) {
var style = el.getAttribute('style'),
offset = el.getAttribute('offset'),
color, opacity;
// convert percents to absolute values
offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
if (style) {
var keyValuePairs = style.split(/\s*;\s*/);
@ -17,10 +22,29 @@
value = split[1].trim();
if (key === 'stop-color') {
return value;
color = value;
}
else if (key === 'stop-opacity') {
opacity = value;
}
}
}
if (!color) {
color = el.getAttribute('stop-color');
}
if (!opacity) {
opacity = el.getAttribute('stop-opacity');
}
// convert rgba color to rgb color - alpha value has no affect in svg
color = new fabric.Color(color).toRgb();
return {
offset: offset,
color: color,
opacity: opacity
};
}
/**
@ -33,19 +57,46 @@
/**
* Constructor
* @method initialize
* @param [options] Options object with x1, y1, x2, y2 and colorStops
* @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
* @return {fabric.Gradient} thisArg
*/
initialize: function(options) {
options || (options = { });
this.x1 = options.x1 || 0;
this.y1 = options.y1 || 0;
this.x2 = options.x2 || 0;
this.y2 = options.y2 || 0;
var coords = { };
this.colorStops = options.colorStops;
this.id = fabric.Object.__uid++;
this.type = options.type || 'linear';
coords = {
x1: options.coords.x1 || 0,
y1: options.coords.y1 || 0,
x2: options.coords.x2 || 0,
y2: options.coords.y2 || 0
};
if (this.type === 'radial') {
coords.r1 = options.coords.r1 || 0;
coords.r2 = options.coords.r2 || 0;
}
this.coords = coords;
this.gradientUnits = options.gradientUnits || 'objectBoundingBox';
this.colorStops = options.colorStops.slice();
},
/**
* Adds another colorStop
* @method add
* @param {Object} colorStop Object with offset and color
* @return {fabric.Gradient} thisArg
*/
addColorStop: function(colorStop) {
for (var position in colorStop) {
var color = new fabric.Color(colorStop[position]);
this.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()});
}
return this;
},
/**
@ -55,10 +106,9 @@
*/
toObject: function() {
return {
x1: this.x1,
x2: this.x2,
y1: this.y1,
y2: this.y2,
type: this.type,
coords: this.coords,
gradientUnits: this.gradientUnits,
colorStops: this.colorStops
};
},
@ -70,15 +120,98 @@
* @return {CanvasGradient}
*/
toLive: function(ctx) {
var gradient = ctx.createLinearGradient(
this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2);
var gradient;
for (var position in this.colorStops) {
var colorValue = this.colorStops[position];
gradient.addColorStop(parseFloat(position), colorValue);
if (!this.type) return;
if (this.type === 'linear') {
gradient = ctx.createLinearGradient(
this.coords.x1, this.coords.y1, this.coords.x2 || ctx.canvas.width, this.coords.y2);
}
else if (this.type === 'radial') {
gradient = ctx.createRadialGradient(
this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2);
}
for (var i = 0; i < this.colorStops.length; i++) {
var color = this.colorStops[i].color,
opacity = this.colorStops[i].opacity,
offset = this.colorStops[i].offset;
if (opacity) {
color = new fabric.Color(color).setAlpha(opacity).toRgba();
}
gradient.addColorStop(parseFloat(offset), color);
}
return gradient;
},
/**
* Returns SVG representation of an gradient
* @method toSVG
* @param {Object} object Object to create a gradient for
* @param {Boolean} normalize Whether coords should be normalized
* @return {String} SVG representation of an gradient (linear/radial)
*/
toSVG: function(object, normalize) {
var coords = fabric.util.object.clone(this.coords),
markup;
// colorStops must be sorted ascending
this.colorStops.sort(function(a, b) {
return a.offset - b.offset;
});
if (normalize && this.gradientUnits === 'userSpaceOnUse') {
coords.x1 += object.width / 2;
coords.y1 += object.height / 2;
coords.x2 += object.width / 2;
coords.y2 += object.height / 2;
}
else if (this.gradientUnits === 'objectBoundingBox') {
_convertValuesToPercentUnits(object, coords);
}
if (this.type === 'linear') {
markup = [
'<linearGradient ',
'id="SVGID_', this.id,
'" gradientUnits="', this.gradientUnits,
'" x1="', coords.x1,
'" y1="', coords.y1,
'" x2="', coords.x2,
'" y2="', coords.y2,
'">'
];
}
else if (this.type === 'radial') {
markup = [
'<radialGradient ',
'id="SVGID_', this.id,
'" gradientUnits="', this.gradientUnits,
'" cx="', coords.x2,
'" cy="', coords.y2,
'" r="', coords.r2,
'" fx="', coords.x1,
'" fy="', coords.y1,
'">'
];
}
for (var i = 0; i < this.colorStops.length; i++) {
markup.push(
'<stop ',
'offset="', (this.colorStops[i].offset * 100) + '%',
'" style="stop-color:', this.colorStops[i].color,
(this.colorStops[i].opacity ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),
'"/>'
);
}
markup.push((this.type === 'linear' ? '</linearGradient>' : '</radialGradient>'));
return markup.join('');
}
});
@ -90,52 +223,78 @@
* @static
* @memberof fabric.Gradient
* @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
* @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
*/
fromElement: function(el, instance) {
/**
* @example:
*
* <linearGradient id="grad1">
* <linearGradient id="linearGrad1">
* <stop offset="0%" stop-color="white"/>
* <stop offset="100%" stop-color="black"/>
* </linearGradient>
*
* OR
*
* <linearGradient id="grad1">
* <stop offset="0%" style="stop-color:rgb(255,255,255)"/>
* <stop offset="100%" style="stop-color:rgb(0,0,0)"/>
* <linearGradient id="linearGrad2">
* <stop offset="0" style="stop-color:rgb(255,255,255)"/>
* <stop offset="1" style="stop-color:rgb(0,0,0)"/>
* </linearGradient>
*
* OR
*
* <radialGradient id="radialGrad1">
* <stop offset="0%" stop-color="white" stop-opacity="1" />
* <stop offset="50%" stop-color="black" stop-opacity="0.5" />
* <stop offset="100%" stop-color="white" stop-opacity="1" />
* </radialGradient>
*
* OR
*
* <radialGradient id="radialGrad2">
* <stop offset="0" stop-color="rgb(255,255,255)" />
* <stop offset="0.5" stop-color="rgb(0,0,0)" />
* <stop offset="1" stop-color="rgb(255,255,255)" />
* </radialGradient>
*
*/
var colorStopEls = el.getElementsByTagName('stop'),
offset,
colorStops = { },
coords = {
x1: el.getAttribute('x1') || 0,
y1: el.getAttribute('y1') || 0,
x2: el.getAttribute('x2') || '100%',
y2: el.getAttribute('y2') || 0
};
type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),
gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
colorStops = [],
coords = { };
if (type === 'linear') {
coords = {
x1: el.getAttribute('x1') || 0,
y1: el.getAttribute('y1') || 0,
x2: el.getAttribute('x2') || '100%',
y2: el.getAttribute('y2') || 0
};
}
else if (type === 'radial') {
coords = {
x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
r1: 0,
x2: el.getAttribute('cx') || '50%',
y2: el.getAttribute('cy') || '50%',
r2: el.getAttribute('r') || '50%'
};
}
for (var i = colorStopEls.length; i--; ) {
el = colorStopEls[i];
offset = el.getAttribute('offset');
// convert percents to absolute values
offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
colorStops[offset] = getColorStopFromStyle(el) || el.getAttribute('stop-color');
colorStops.push(getColorStop(colorStopEls[i]));
}
_convertPercentUnitsToValues(instance, coords);
return new fabric.Gradient({
x1: coords.x1,
y1: coords.y1,
x2: coords.x2,
y2: coords.y2,
type: type,
coords: coords,
gradientUnits: gradientUnits,
colorStops: colorStops
});
},
@ -144,8 +303,8 @@
* Returns {@link fabric.Gradient} instance from its object representation
* @method forObject
* @static
* @param obj
* @param [options]
* @param {Object} obj
* @param {Object} [options] Options object
* @memberof fabric.Gradient
*/
forObject: function(obj, options) {
@ -155,11 +314,15 @@
}
});
/**
* @private
* @method _convertPercentUnitsToValues
*/
function _convertPercentUnitsToValues(object, options) {
for (var prop in options) {
if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
var percents = parseFloat(options[prop], 10);
if (prop === 'x1' || prop === 'x2') {
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
options[prop] = fabric.util.toFixed(object.width * percents / 100, 2);
}
else if (prop === 'y1' || prop === 'y2') {
@ -176,6 +339,29 @@
}
}
/**
* @private
* @method _convertValuesToPercentUnits
*/
function _convertValuesToPercentUnits(object, options) {
for (var prop in options) {
// normalize rendering point (should be from center rather than top/left corner of the shape)
if (prop === 'x1' || prop === 'x2') {
options[prop] += fabric.util.toFixed(object.width / 2, 2);
}
else if (prop === 'y1' || prop === 'y2') {
options[prop] += fabric.util.toFixed(object.height / 2, 2);
}
// convert to percent units
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
options[prop] = fabric.util.toFixed(options[prop] / object.width * 100, 2) + '%';
}
else if (prop === 'y1' || prop === 'y2') {
options[prop] = fabric.util.toFixed(options[prop] / object.height * 100, 2) + '%';
}
}
}
/**
* Parses an SVG document, returning all of the gradient declarations found in it
* @static

View file

@ -49,9 +49,12 @@
initialize: function(objects, options) {
options = options || { };
this.objects = objects || [];
this.originalState = { };
this._objects = objects || [];
for (var i = this._objects.length; i--; ) {
this._objects[i].group = this;
}
this.originalState = { };
this.callSuper('initialize');
this._calcBounds();
@ -91,7 +94,8 @@
object.setCoords();
// do not display corners of objects enclosed in a group
object.hideCorners = true;
object.__origHasControls = object.hasControls;
object.hasControls = false;
}, this);
},
@ -110,7 +114,7 @@
* @return {Array} group objects
*/
getObjects: function() {
return this.objects;
return this._objects;
},
/**
@ -122,7 +126,8 @@
*/
addWithUpdate: function(object) {
this._restoreObjectsState();
this.objects.push(object);
this._objects.push(object);
object.group = this;
this._calcBounds();
this._updateObjectsCoords();
return this;
@ -137,7 +142,8 @@
*/
removeWithUpdate: function(object) {
this._restoreObjectsState();
removeFromArray(this.objects, object);
removeFromArray(this._objects, object);
delete object.group;
object.setActive(false);
this._calcBounds();
this._updateObjectsCoords();
@ -152,7 +158,8 @@
* @chainable
*/
add: function(object) {
this.objects.push(object);
this._objects.push(object);
object.group = this;
return this;
},
@ -164,7 +171,8 @@
* @chainable
*/
remove: function(object) {
removeFromArray(this.objects, object);
removeFromArray(this._objects, object);
delete object.group;
return this;
},
@ -177,6 +185,8 @@
},
/**
* @param delegatedProperties
* @type Object
* Properties that are delegated to group objects when reading/writing
*/
delegatedProperties: {
@ -184,9 +194,12 @@
opacity: true,
fontFamily: true,
fontWeight: true,
fontSize: true,
fontStyle: true,
lineHeight: true,
textDecoration: true,
textShadow: true,
textAlign: true,
backgroundColor: true
},
@ -195,10 +208,10 @@
*/
_set: function(key, value) {
if (key in this.delegatedProperties) {
var i = this.objects.length;
var i = this._objects.length;
this[key] = value;
while (i--) {
this.objects[i].set(key, value);
this._objects[i].set(key, value);
}
}
else {
@ -213,7 +226,7 @@
* @return {Boolean} `true` if group contains an object
*/
contains: function(object) {
return this.objects.indexOf(object) > -1;
return this._objects.indexOf(object) > -1;
},
/**
@ -224,7 +237,7 @@
*/
toObject: function(propertiesToInclude) {
return extend(this.callSuper('toObject', propertiesToInclude), {
objects: invoke(this.objects, 'toObject', propertiesToInclude)
objects: invoke(this._objects, 'toObject', propertiesToInclude)
});
},
@ -234,18 +247,26 @@
* @param {CanvasRenderingContext2D} ctx context to render instance on
*/
render: function(ctx, noTransform) {
// do not render if object is not visible
if (!this.visible) return;
ctx.save();
this.transform(ctx);
var groupScaleFactor = Math.max(this.scaleX, this.scaleY);
//The array is now sorted in order of highest first, so start from end.
for (var i = this.objects.length; i > 0; i--) {
this.clipTo && fabric.util.clipContext(this, ctx);
var object = this.objects[i-1],
//The array is now sorted in order of highest first, so start from end.
for (var i = this._objects.length; i > 0; i--) {
var object = this._objects[i-1],
originalScaleFactor = object.borderScaleFactor,
originalHasRotatingPoint = object.hasRotatingPoint;
// do not render if object is not visible
if (!object.visible) continue;
object.borderScaleFactor = groupScaleFactor;
object.hasRotatingPoint = false;
@ -254,10 +275,11 @@
object.borderScaleFactor = originalScaleFactor;
object.hasRotatingPoint = originalHasRotatingPoint;
}
this.clipTo && ctx.restore();
if (!noTransform && this.active) {
this.drawBorders(ctx);
this.hideCorners || this.drawCorners(ctx);
this.drawControls(ctx);
}
ctx.restore();
this.setCoords();
@ -293,7 +315,7 @@
* @chainable
*/
_restoreObjectsState: function() {
this.objects.forEach(this._restoreObjectState, this);
this._objects.forEach(this._restoreObjectState, this);
return this;
},
@ -321,9 +343,11 @@
object.set('scaleY', object.get('scaleY') * this.get('scaleY'));
object.setCoords();
object.hideCorners = false;
object.hasControls = object.__origHasControls;
delete object.__origHasControls;
object.setActive(false);
object.setCoords();
delete object.group;
return this;
},
@ -429,10 +453,10 @@
aY = [],
minX, minY, maxX, maxY, o, width, height,
i = 0,
len = this.objects.length;
len = this._objects.length;
for (; i < len; ++i) {
o = this.objects[i];
o = this._objects[i];
o.setCoords();
for (var prop in o.oCoords) {
aX.push(o.oCoords[prop].x);
@ -481,9 +505,9 @@
* @chainable
*/
toGrayscale: function() {
var i = this.objects.length;
var i = this._objects.length;
while (i--) {
this.objects[i].toGrayscale();
this._objects[i].toGrayscale();
}
return this;
},
@ -495,8 +519,8 @@
*/
toSVG: function() {
var objectsMarkup = [ ];
for (var i = 0, len = this.objects.length; i < len; i++) {
objectsMarkup.push(this.objects[i].toSVG());
for (var i = this._objects.length; i--; ) {
objectsMarkup.push(this._objects[i].toSVG());
}
return (
@ -517,8 +541,8 @@
return this[prop];
}
else {
for (var i = 0, len = this.objects.length; i < len; i++) {
if (this.objects[i][prop]) {
for (var i = 0, len = this._objects.length; i < len; i++) {
if (this._objects[i][prop]) {
return true;
}
}
@ -526,6 +550,9 @@
}
}
else {
if (prop in this.delegatedProperties) {
return this._objects[0] && this._objects[0].get(prop);
}
return this[prop];
}
}
@ -553,4 +580,4 @@
*/
fabric.Group.async = true;
})(typeof exports !== 'undefined' ? exports : this);
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -104,12 +104,14 @@
}
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
this._render(ctx);
this.clipTo && ctx.restore();
this._removeShadow(ctx);
if (this.active && !noTransform) {
this.drawBorders(ctx);
this.hideCorners || this.drawCorners(ctx);
this.drawControls(ctx);
}
ctx.restore();
},
@ -403,11 +405,9 @@
* @return {fabric.Image}
*/
fabric.Image.fromElement = function(element, callback, options) {
options || (options = { });
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);
fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend(parsedAttributes, options));
fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
};
/**

View file

@ -1,5 +1,5 @@
/**
* @namespace
* @namespace Image filters
*/
fabric.Image.filters = { };

View file

@ -135,15 +135,23 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
return [
var markup = [];
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, true));
}
markup.push(
'<line ',
'x1="', this.get('x1'), '" ',
'y1="', this.get('y1'), '" ',
'x2="', this.get('x2'), '" ',
'y2="', this.get('y2'), '" ',
'style="', this.getSvgStyles(), '" ',
'/>'
].join('');
'x1="', this.get('x1'),
'" y1="', this.get('y1'),
'" x2="', this.get('x2'),
'" y2="', this.get('y2'),
'" style="', this.getSvgStyles(),
'"/>'
);
return markup.join('');
}
});

View file

@ -14,20 +14,12 @@
/** @private */
function request(url, encoding, callback) {
var oURL = URL.parse(url),
client = HTTP.createClient(oURL.port, oURL.hostname),
req = client.request('GET', oURL.pathname, { 'host': oURL.hostname });
client.addListener('error', function(err) {
if (err.errno === process.ECONNREFUSED) {
fabric.log('ECONNREFUSED: connection refused to ' + client.host + ':' + client.port);
}
else {
fabric.log(err.message);
}
});
req.end();
req.on('response', function (response) {
req = HTTP.request({
hostname: oURL.hostname,
port: oURL.port,
path: oURL.pathname,
method: 'GET'
}, function(response){
var body = "";
if (encoding) {
response.setEncoding(encoding);
@ -41,21 +33,47 @@
}
});
});
req.on('error', function(err) {
if (err.errno === process.ECONNREFUSED) {
fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port);
}
else {
fabric.log(err.message);
}
});
}
/** @private */
function request_fs(url, callback){
var fs = require('fs'),
stream = fs.createReadStream(url),
body = '';
stream.on('data', function(chunk){
body += chunk;
});
stream.on('end', function(){
callback(body);
});
}
fabric.util.loadImage = function(url, callback, context) {
var createImageAndCallBack = function(data){
img.src = new Buffer(data, 'binary');
// preserving original url, which seems to be lost in node-canvas
img._src = url;
callback && callback.call(context, img);
};
var img = new Image();
if (url && url.indexOf('data') === 0) {
img.src = img._src = url;
callback && callback.call(context, img);
}
else if (url && url.indexOf('http') !== 0) {
request_fs(url, createImageAndCallBack);
}
else if (url) {
request(url, 'binary', function(body) {
img.src = new Buffer(body, 'binary');
// preserving original url, which seems to be lost in node-canvas
img._src = url;
callback && callback.call(context, img);
});
request(url, 'binary', createImageAndCallBack);
}
};
@ -112,6 +130,7 @@
var fabricCanvas = new FabricCanvas(canvasEl);
fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
fabricCanvas.nodeCanvas = nodeCanvas;
fabricCanvas.Font = Canvas.Font;
return fabricCanvas;
};
@ -127,7 +146,7 @@
var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
fabric.StaticCanvas.prototype.setWidth = function(width) {
origSetWidth.call(this);
origSetWidth.call(this, width);
this.nodeCanvas.width = width;
return this;
};
@ -137,7 +156,7 @@
var origSetHeight = fabric.StaticCanvas.prototype.setHeight;
fabric.StaticCanvas.prototype.setHeight = function(height) {
origSetHeight.call(this);
origSetHeight.call(this, height);
this.nodeCanvas.height = height;
return this;
};

View file

@ -52,14 +52,14 @@
originY: 'center',
/**
* Top position of an object
* Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom}
* @property
* @type Number
*/
top: 0,
/**
* Left position of an object
* Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right}
* @property
* @type Number
*/
@ -240,6 +240,13 @@
*/
selectable: true,
/**
* When set to `false`, an object is not rendered on canvas
* @property
* @type Boolean
*/
visible: true,
/**
* When set to `false`, object's controls are not displayed and can not be used to manipulate object
* @property
@ -282,6 +289,13 @@
*/
includeDefaultValues: true,
/**
* Function that determines clipping of an object (context is passed as a first argument)
* @property
* @type Function
*/
clipTo: null,
/**
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged);
* as well as for history (undo/redo) purposes
@ -292,7 +306,7 @@
'top left width height scaleX scaleY flipX flipY ' +
'angle opacity cornerSize fill overlayFill originX originY ' +
'stroke strokeWidth strokeDashArray fillRule ' +
'borderScaleFactor transformMatrix selectable shadow'
'borderScaleFactor transformMatrix selectable shadow visible'
).split(' '),
/**
@ -405,7 +419,8 @@
hasRotatingPoint: this.hasRotatingPoint,
transparentCorners: this.transparentCorners,
perPixelTargetFind: this.perPixelTargetFind,
shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow
shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow,
visible: this.visible
};
if (!this.includeDefaultValues) {
@ -437,8 +452,9 @@
"stroke: ", (this.stroke ? this.stroke : 'none'), "; ",
"stroke-width: ", (this.strokeWidth ? this.strokeWidth : '0'), "; ",
"stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : "; "),
"fill: ", (this.fill ? this.fill : 'none'), "; ",
"opacity: ", (this.opacity ? this.opacity : '1'), ";"
"fill: ", (this.fill ? (this.fill && this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) : 'none'), "; ",
"opacity: ", (this.opacity ? this.opacity : '1'), ";",
(this.visible ? '' : " visibility: hidden;")
].join("");
},
@ -534,8 +550,8 @@
* Sets property to a given value
* @method set
* @param {String} name
* @param {Object|Function} value
* @return {fabric.Group} thisArg
* @param {Object|Function} value (if function, the value is passed into it and its return value is used as a new one)
* @return {fabric.Object} thisArg
* @chainable
*/
set: function(key, value) {
@ -619,8 +635,8 @@
*/
render: function(ctx, noTransform) {
// do not render if width or height are zeros
if (this.width === 0 || this.height === 0) return;
// do not render if width/height are zeros or object is not visible
if (this.width === 0 || this.height === 0 || !this.visible) return;
ctx.save();
@ -658,12 +674,14 @@
}
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
this._render(ctx, noTransform);
this.clipTo && ctx.restore();
this._removeShadow(ctx);
if (this.active && !noTransform) {
this.drawBorders(ctx);
this.hideCorners || this.drawCorners(ctx);
this.drawControls(ctx);
}
ctx.restore();
},
@ -724,13 +742,13 @@
};
var orig = {
angle: this.get('angle'),
flipX: this.get('flipX'),
flipY: this.get('flipY')
angle: this.getAngle(),
flipX: this.getFlipX(),
flipY: this.getFlipY()
};
// normalize angle
this.set('angle', 0).set('flipX', false).set('flipY', false);
this.set({ angle: 0, flipX: false, flipY: false });
this.toDataURL(function(dataURL) {
i.src = dataURL;
});
@ -769,7 +787,7 @@
clone.setActive(false);
canvas.add(clone);
var data = canvas.toDataURL('png');
var data = canvas.toDataURL();
canvas.dispose();
canvas = clone = null;
@ -792,13 +810,21 @@
/**
* Saves state of an object
* @method saveState
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state
* @return {fabric.Object} thisArg
* @chainable
*/
saveState: function() {
saveState: function(options) {
this.stateProperties.forEach(function(prop) {
this.originalState[prop] = this.get(prop);
}, this);
if (options && options.stateProperties) {
options.stateProperties.forEach(function(prop) {
this.originalState[prop] = this.get(prop);
}, this);
}
return this;
},
@ -846,7 +872,7 @@
/**
* Returns a JSON representation of an instance
* @method toJSON
* @param {Array} propertiesToInclude
* @param {Array} propertiesToInclude Any properties that you might want to additionally include in the output
* @return {String} json
*/
toJSON: function(propertiesToInclude) {
@ -855,16 +881,41 @@
},
/**
* Sets gradient fill of an object
* @method setGradientFill
* Sets gradient (fill or stroke) of an object
* @method setGradient
* @param {String} property Property name 'stroke' or 'fill'
* @param {Object} [options] Options object
*/
setGradientFill: function(options) {
this.set('fill', fabric.Gradient.forObject(this, options));
setGradient: function(property, options) {
options || (options = { });
var gradient = {colorStops: []};
gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear');
gradient.coords = {
x1: options.x1,
y1: options.y1,
x2: options.x2,
y2: options.y2
};
if (options.r1 || options.r2) {
gradient.coords.r1 = options.r1;
gradient.coords.r2 = options.r2;
}
for (var position in options.colorStops) {
var color = new fabric.Color(options.colorStops[position]);
gradient.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()});
}
this.set(property, fabric.Gradient.forObject(this, gradient));
},
/**
* Sets pattern fill of an object
* @method setPatternFill
* @param {Object} options
*/
setPatternFill: function(options) {
this.set('fill', new fabric.Pattern(options));
@ -873,6 +924,7 @@
/**
* Sets shadow of an object
* @method setShadow
* @param {Object} options
*/
setShadow: function(options) {
this.set('shadow', new fabric.Shadow(options));
@ -1010,7 +1062,12 @@
* @chainable
*/
sendToBack: function() {
this.canvas.sendToBack(this);
if (this.group) {
fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);
}
else {
this.canvas.sendToBack(this);
}
return this;
},
@ -1021,7 +1078,12 @@
* @chainable
*/
bringToFront: function() {
this.canvas.bringToFront(this);
if (this.group) {
fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);
}
else {
this.canvas.bringToFront(this);
}
return this;
},
@ -1032,7 +1094,12 @@
* @chainable
*/
sendBackwards: function() {
this.canvas.sendBackwards(this);
if (this.group) {
fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this);
}
else {
this.canvas.sendBackwards(this);
}
return this;
},
@ -1043,31 +1110,17 @@
* @chainable
*/
bringForward: function() {
this.canvas.bringForward(this);
if (this.group) {
fabric.StaticCanvas.prototype.bringForward.call(this.group, this);
}
else {
this.canvas.bringForward(this);
}
return this;
}
});
var proto = fabric.Object.prototype;
for (var i = proto.stateProperties.length; i--; ) {
var propName = proto.stateProperties[i],
capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
setterName = 'set' + capitalizedPropName,
getterName = 'get' + capitalizedPropName;
// using `new Function` for better introspection
if (!proto[getterName]) {
proto[getterName] = (function(property) {
return new Function('return this.get("' + property + '")');
})(propName);
}
if (!proto[setterName]) {
proto[setterName] = (function(property) {
return new Function('value', 'return this.set("' + property + '", value)');
})(propName);
}
}
fabric.util.createAccessors(fabric.Object);
/**
* Alias for {@link fabric.Object.prototype.setAngle}
@ -1084,4 +1137,10 @@
*/
fabric.Object.NUM_FRACTION_DIGITS = 2;
/**
* @static
* @type Number
*/
fabric.Object.__uid = 0;
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -383,12 +383,12 @@
* Draws corners of an object's bounding box.
* Requires public properties: width, height, scaleX, scaleY
* Requires public options: cornerSize, padding
* @method drawCorners
* @method drawControls
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @return {fabric.Object} thisArg
* @chainable
*/
drawCorners: function(ctx) {
drawControls: function(ctx) {
if (!this.hasControls) return;
var size = this.cornerSize,

View file

@ -51,6 +51,7 @@ fabric.Observable = {
/**
* Fires event with an optional options object
* @deprecated since 1.0.7
* @method fire
* @param {String} eventName
* @param {Object} [options]
@ -80,4 +81,11 @@ fabric.Observable.on = fabric.Observable.observe;
* @method off
* @type function
*/
fabric.Observable.off = fabric.Observable.stopObserving;
fabric.Observable.off = fabric.Observable.stopObserving;
/**
* Alias for fire
* @method trigger
* @type function
*/
fabric.Observable.trigger = fabric.Observable.fire;

View file

@ -695,13 +695,37 @@
if (markup) {
markup = [
'<defs>',
'<style type="text/css">',
'<![CDATA[',
markup,
']]>',
'</style>',
'</defs>'
'<style type="text/css">',
'<![CDATA[',
markup,
']]>',
'</style>'
].join('');
}
return markup;
}
/**
* Creates markup containing SVG referenced elements like patterns, gradients etc.
* @method createSVGRefElementsMarkup
* @param {fabric.Canvas} canvas instance of fabric.Canvas
* @return {String}
*/
function createSVGRefElementsMarkup(canvas) {
var markup = '';
if (canvas.backgroundColor && canvas.backgroundColor.source) {
markup = [
'<pattern x="0" y="0" id="backgroundColorPattern" ',
'width="', canvas.backgroundColor.source.width,
'" height="', canvas.backgroundColor.source.height,
'" patternUnits="userSpaceOnUse">',
'<image x="0" y="0" ',
'width="', canvas.backgroundColor.source.width,
'" height="', canvas.backgroundColor.source.height,
'" xlink:href="', canvas.backgroundColor.source.src,
'"></image></pattern>'
].join('');
}
@ -710,16 +734,17 @@
extend(fabric, {
parseAttributes: parseAttributes,
parseElements: parseElements,
parseStyleAttribute: parseStyleAttribute,
parsePointsAttribute: parsePointsAttribute,
getCSSRules: getCSSRules,
parseAttributes: parseAttributes,
parseElements: parseElements,
parseStyleAttribute: parseStyleAttribute,
parsePointsAttribute: parsePointsAttribute,
getCSSRules: getCSSRules,
loadSVGFromURL: loadSVGFromURL,
loadSVGFromString: loadSVGFromString,
loadSVGFromURL: loadSVGFromURL,
loadSVGFromString: loadSVGFromString,
createSVGFontFacesMarkup: createSVGFontFacesMarkup
createSVGFontFacesMarkup: createSVGFontFacesMarkup,
createSVGRefElementsMarkup: createSVGRefElementsMarkup
});
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -88,7 +88,8 @@
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
}
return (arcToSegmentsCache[argsString] = result);
arcToSegmentsCache[argsString] = result;
return result;
}
function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
@ -111,11 +112,13 @@
var x2 = x3 + t * Math.sin(th1);
var y2 = y3 - t * Math.cos(th1);
return (segmentToBezierCache[argsString] = [
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
]);
];
return segmentToBezierCache[argsString];
}
"use strict";
@ -553,14 +556,17 @@
? this.stroke.toLive(ctx)
: this.stroke;
}
ctx.beginPath();
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
ctx.beginPath();
this._render(ctx);
if (this.fill) {
ctx.fill();
}
this.clipTo && ctx.restore();
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
@ -575,7 +581,7 @@
if (!noTransform && this.active) {
this.drawBorders(ctx);
this.hideCorners || this.drawCorners(ctx);
this.drawControls(ctx);
}
ctx.restore();
},
@ -630,21 +636,33 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
var chunks = [];
var chunks = [],
markup = [];
for (var i = 0, len = this.path.length; i < len; i++) {
chunks.push(this.path[i].join(' '));
}
var path = chunks.join(' ');
return [
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, true));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, true));
}
markup.push(
'<g transform="', (this.group ? '' : this.getSvgTransform()), '">',
'<path ',
'width="', this.width, '" height="', this.height, '" ',
'd="', path, '" ',
'style="', this.getSvgStyles(), '" ',
'transform="translate(', (-this.width / 2), ' ', (-this.height/2), ')" />',
'd="', path,
'" style="', this.getSvgStyles(),
'" transform="translate(', (-this.width / 2), ' ', (-this.height/2), ')',
'" stroke-linecap="round" ',
'/>',
'</g>'
].join('');
);
return markup.join('');
},
/**

View file

@ -65,6 +65,10 @@
* @param {CanvasRenderingContext2D} ctx Context to render this instance on
*/
render: function(ctx) {
// do not render if object is not visible
if (!this.visible) return;
ctx.save();
var m = this.transformMatrix;
@ -75,14 +79,16 @@
this.transform(ctx);
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
for (var i = 0, l = this.paths.length; i < l; ++i) {
this.paths[i].render(ctx, true);
}
this.clipTo && ctx.restore();
this._removeShadow(ctx);
if (this.active) {
this.drawBorders(ctx);
this.hideCorners || this.drawCorners(ctx);
this.drawControls(ctx);
}
ctx.restore();
},
@ -142,8 +148,6 @@
var objects = this.getObjects();
var markup = [
'<g ',
'width="', this.width, '" ',
'height="', this.height, '" ',
'style="', this.getSvgStyles(), '" ',
'transform="', this.getSvgTransform(), '" ',
'>'

View file

@ -187,7 +187,7 @@
* @param {Array} points Array of points
* @return {String} SVG path
*/
convertPointsToSVGPath: function(points, minX, maxX, minY, maxY) {
convertPointsToSVGPath: function(points, minX, maxX, minY) {
var path = [];
var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY);
var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);

View file

@ -32,20 +32,21 @@
* @method initialize
* @param {Array} points Array of points
* @param {Object} [options] Options object
* @param {Boolean} Whether points offsetting should be skipped
* @return {fabric.Polygon} thisArg
*/
initialize: function(points, options) {
initialize: function(points, options, skipOffset) {
options = options || { };
this.points = points;
this.callSuper('initialize', options);
this._calcDimensions();
this._calcDimensions(skipOffset);
},
/**
* @private
* @method _calcDimensions
*/
_calcDimensions: function() {
_calcDimensions: function(skipOffset) {
var points = this.points,
minX = min(points, 'x'),
@ -56,17 +57,19 @@
this.width = (maxX - minX) || 1;
this.height = (maxY - minY) || 1;
// var halfWidth = this.width / 2,
// halfHeight = this.height / 2;
// change points to offset polygon into a bounding box
// this.points.forEach(function(p) {
// p.x -= halfWidth;
// p.y -= halfHeight;
// }, this);
this.minX = minX;
this.minY = minY;
if (skipOffset) return;
var halfWidth = this.width / 2,
halfHeight = this.height / 2;
// change points to offset polygon into a bounding box
this.points.forEach(function(p) {
p.x -= halfWidth;
p.y -= halfHeight;
}, this);
},
/**
@ -87,18 +90,29 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
var points = [];
var points = [],
markup = [];
for (var i = 0, len = this.points.length; i < len; i++) {
points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
}
return [
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, false));
}
markup.push(
'<polygon ',
'points="', points.join(''), '" ',
'style="', this.getSvgStyles(), '" ',
'transform="', this.getSvgTransform(), '" ',
'/>'
].join('');
'points="', points.join(''),
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
);
return markup.join('');
},
/**
@ -164,7 +178,7 @@
points[i].y -= (options.height / 2) || 0;
}
return new fabric.Polygon(points, extend(parsedAttributes, options));
return new fabric.Polygon(points, extend(parsedAttributes, options), true);
};
/**
@ -175,7 +189,7 @@
* @return {fabric.Polygon}
*/
fabric.Polygon.fromObject = function(object) {
return new fabric.Polygon(object.points, object);
return new fabric.Polygon(object.points, object, true);
};
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -29,21 +29,22 @@
* @method initialize
* @param {Array} points array of points
* @param {Object} [options] Options object
* @param {Boolean} Whether points offsetting should be skipped
* @return {Object} thisArg
*/
initialize: function(points, options) {
initialize: function(points, options, skipOffset) {
options = options || { };
this.set('points', points);
this.callSuper('initialize', options);
this._calcDimensions();
this._calcDimensions(skipOffset);
},
/**
* @private
* @method _calcDimensions
*/
_calcDimensions: function() {
return fabric.Polygon.prototype._calcDimensions.call(this);
_calcDimensions: function(skipOffset) {
return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset);
},
/**
@ -62,18 +63,29 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
var points = [];
var points = [],
markup = [];
for (var i = 0, len = this.points.length; i < len; i++) {
points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
}
return [
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, false));
}
markup.push(
'<polyline ',
'points="', points.join(''), '" ',
'style="', this.getSvgStyles(), '" ',
'transform="', this.getSvgTransform(), '" ',
'/>'
].join('');
'points="', points.join(''),
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
);
return markup.join('');
},
/**
@ -138,7 +150,7 @@
points[i].y -= (options.height / 2) || 0;
}
return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options), true);
};
/**
@ -150,7 +162,7 @@
*/
fabric.Polyline.fromObject = function(object) {
var points = object.points;
return new fabric.Polyline(points, object);
return new fabric.Polyline(points, object, true);
};
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -237,13 +237,26 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
return '<rect ' +
'x="' + (-1 * this.width / 2) + '" y="' + (-1 * this.height / 2) + '" ' +
'rx="' + this.get('rx') + '" ry="' + this.get('ry') + '" ' +
'width="' + this.width + '" height="' + this.height + '" ' +
'style="' + this.getSvgStyles() + '" ' +
'transform="' + this.getSvgTransform() + '" ' +
'/>';
var markup = [];
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, false));
}
markup.push(
'<rect ',
'x="', (-1 * this.width / 2), '" y="', (-1 * this.height / 2),
'" rx="', this.get('rx'), '" ry="', this.get('ry'),
'" width="', this.width, '" height="', this.height,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
);
return markup.join('');
}
});

View file

@ -117,7 +117,7 @@
clipTo: null,
/**
* Indicates whether object controls (borders/corners) are rendered above overlay image
* Indicates whether object controls (borders/controls) are rendered above overlay image
* @property
* @type Boolean
*/
@ -228,7 +228,7 @@
fabric.util.loadImage(backgroundColor.source, function(img) {
_this.backgroundColor = new fabric.Pattern({
source: img,
pattern: backgroundColor.pattern
repeat: backgroundColor.repeat
});
callback && callback();
});
@ -429,11 +429,11 @@
if (!object) return;
if (this.controlsAboveOverlay) {
var hasBorders = object.hasBorders, hasCorners = object.hasCorners;
object.hasBorders = object.hasCorners = false;
var hasBorders = object.hasBorders, hasControls = object.hasControls;
object.hasBorders = object.hasControls = false;
object.render(ctx);
object.hasBorders = hasBorders;
object.hasCorners = hasCorners;
object.hasControls = hasControls;
}
else {
object.render(ctx);
@ -563,7 +563,7 @@
this.fire('before:render');
if (this.clipTo) {
this._clipCanvas(canvasToDrawOn);
fabric.util.clipCanvas(this, canvasToDrawOn);
}
if (this.backgroundColor) {
@ -616,17 +616,6 @@
return this;
},
/**
* @private
* @method _clipCanvas
*/
_clipCanvas: function(canvasToDrawOn) {
canvasToDrawOn.save();
canvasToDrawOn.beginPath();
this.clipTo(canvasToDrawOn);
canvasToDrawOn.clip();
},
/**
* @private
* @method _drawBackroundImage
@ -661,7 +650,7 @@
}
// delegate rendering to group selection if one exists
// used for drawing selection borders/corners
// used for drawing selection borders/controls
var activeGroup = this.getActiveGroup();
if (activeGroup) {
activeGroup.render(ctx);
@ -677,7 +666,7 @@
},
/**
* Draws objects' controls (borders/corners)
* Draws objects' controls (borders/controls)
* @method drawControls
* @param {Object} ctx context to render controls on
*/
@ -686,7 +675,7 @@
if (activeGroup) {
ctx.save();
fabric.Group.prototype.transform.call(activeGroup, ctx);
activeGroup.drawBorders(ctx).drawCorners(ctx);
activeGroup.drawBorders(ctx).drawControls(ctx);
ctx.restore();
}
else {
@ -695,7 +684,7 @@
ctx.save();
fabric.Object.prototype.transform.call(this._objects[i], ctx);
this._objects[i].drawBorders(ctx).drawCorners(ctx);
this._objects[i].drawBorders(ctx).drawControls(ctx);
ctx.restore();
this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
@ -706,17 +695,39 @@
/**
* Exports canvas element to a dataurl image.
* @method toDataURL
* @param {String} format the format of the output image. Either "jpeg" or "png".
* @param {Number} quality quality level (0..1)
* @param {Object} options
*
* `format` the format of the output image. Either "jpeg" or "png".
* `quality` quality level (0..1)
* `multiplier` multiplier to scale by {Number}
*
* @return {String}
*/
toDataURL: function (format, quality) {
var canvasEl = this.upperCanvasEl || this.lowerCanvasEl;
toDataURL: function (options) {
options || (options = { });
var format = options.format || 'png',
quality = options.quality || 1,
multiplier = options.multiplier || 1;
if (multiplier !== 1) {
return this.__toDataURLWithMultiplier(format, quality, multiplier);
}
else {
return this.__toDataURL(format, quality);
}
},
/**
* @method _toDataURL
* @private
*/
__toDataURL: function(format, quality) {
this.renderAll(true);
var canvasEl = this.upperCanvasEl || this.lowerCanvasEl;
var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))
? canvasEl.toDataURL('image/' + format, quality)
: canvasEl.toDataURL('image/' + format);
? canvasEl.toDataURL('image/' + format, quality)
: canvasEl.toDataURL('image/' + format);
this.contextTop && this.clearContext(this.contextTop);
this.renderAll();
@ -724,14 +735,10 @@
},
/**
* Exports canvas element to a dataurl image (allowing to change image size via multiplier).
* @method toDataURLWithMultiplier
* @param {String} format (png|jpeg)
* @param {Number} multiplier
* @param {Number} quality (0..1)
* @return {String}
* @method _toDataURLWithMultiplier
* @private
*/
toDataURLWithMultiplier: function (format, multiplier, quality) {
__toDataURLWithMultiplier: function(format, quality, multiplier) {
var origWidth = this.getWidth(),
origHeight = this.getHeight(),
@ -747,7 +754,7 @@
if (activeGroup) {
// not removing group due to complications with restoring it with correct state afterwords
this._tempRemoveBordersCornersFromGroup(activeGroup);
this._tempRemoveBordersControlsFromGroup(activeGroup);
}
else if (activeObject && this.deactivateAll) {
this.deactivateAll();
@ -760,13 +767,13 @@
this.renderAll(true);
var dataURL = this.toDataURL(format, quality);
var data = this.__toDataURL(format, quality);
ctx.scale(1 / multiplier, 1 / multiplier);
this.setWidth(origWidth).setHeight(origHeight);
if (activeGroup) {
this._restoreBordersCornersOnGroup(activeGroup);
this._restoreBordersControlsOnGroup(activeGroup);
}
else if (activeObject && this.setActiveObject) {
this.setActiveObject(activeObject);
@ -775,18 +782,35 @@
this.contextTop && this.clearContext(this.contextTop);
this.renderAll();
return dataURL;
return data;
},
/**
* Exports canvas element to a dataurl image (allowing to change image size via multiplier).
* @deprecated since 1.0.13
* @method toDataURLWithMultiplier
* @param {String} format (png|jpeg)
* @param {Number} multiplier
* @param {Number} quality (0..1)
* @return {String}
*/
toDataURLWithMultiplier: function (format, multiplier, quality) {
return this.toDataURL({
format: format,
multiplier: multiplier,
quality: quality
});
},
/**
* @private
* @method _tempRemoveBordersCornersFromGroup
* @method _tempRemoveBordersControlsFromGroup
*/
_tempRemoveBordersCornersFromGroup: function(group) {
group.origHideCorners = group.hideCorners;
_tempRemoveBordersControlsFromGroup: function(group) {
group.origHasControls = group.hasControls;
group.origBorderColor = group.borderColor;
group.hideCorners = true;
group.hasControls = true;
group.borderColor = 'rgba(0,0,0,0)';
group.forEachObject(function(o) {
@ -797,10 +821,10 @@
/**
* @private
* @method _restoreBordersCornersOnGroup
* @method _restoreBordersControlsOnGroup
*/
_restoreBordersCornersOnGroup: function(group) {
group.hideCorners = group.origHideCorners;
_restoreBordersControlsOnGroup: function(group) {
group.hideControls = group.origHideControls;
group.borderColor = group.origBorderColor;
group.forEachObject(function(o) {
@ -940,8 +964,8 @@
if (!options.suppressPreamble) {
markup.push(
'<?xml version="1.0" standalone="no" ?>',
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" ',
'"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">'
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ',
'"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
);
}
markup.push(
@ -951,16 +975,27 @@
'version="1.1" ',
'width="', this.width, '" ',
'height="', this.height, '" ',
(this.backgroundColor && !this.backgroundColor.source) ? 'style="background-color: ' + this.backgroundColor +'" ' : null,
'xml:space="preserve">',
'<desc>Created with Fabric.js ', fabric.version, '</desc>',
fabric.createSVGFontFacesMarkup(this.getObjects())
'<defs>', fabric.createSVGFontFacesMarkup(this.getObjects()), fabric.createSVGRefElementsMarkup(this), '</defs>'
);
if (this.backgroundColor && this.backgroundColor.source) {
markup.push(
'<rect x="0" y="0" ',
'width="', (this.backgroundColor.repeat === 'repeat-y' || this.backgroundColor.repeat === 'no-repeat' ? this.backgroundColor.source.width : this.width),
'" height="', (this.backgroundColor.repeat === 'repeat-x' || this.backgroundColor.repeat === 'no-repeat' ? this.backgroundColor.source.height : this.height),
'" fill="url(#backgroundColorPattern)"',
'></rect>'
);
}
if (this.backgroundImage) {
markup.push(
'<image x="0" y="0" ',
'width="', this.width,
'" height="', this.height,
'width="', (this.backgroundImageStretch ? this.width : this.backgroundImage.width),
'" height="', (this.backgroundImageStretch ? this.height : this.backgroundImage.height),
'" preserveAspectRatio="', (this.backgroundImageStretch ? 'none' : 'defer'),
'" xlink:href="', this.backgroundImage.src,
'" style="opacity:', this.backgroundImageOpacity,
@ -1033,7 +1068,7 @@
sendToBack: function (object) {
removeFromArray(this._objects, object);
this._objects.unshift(object);
return this.renderAll();
return this.renderAll && this.renderAll();
},
/**
@ -1046,7 +1081,7 @@
bringToFront: function (object) {
removeFromArray(this._objects, object);
this._objects.push(object);
return this.renderAll();
return this.renderAll && this.renderAll();
},
/**
@ -1078,7 +1113,7 @@
removeFromArray(this._objects, object);
this._objects.splice(nextIntersectingIdx, 0, object);
}
return this.renderAll();
return this.renderAll && this.renderAll();
},
/**
@ -1112,7 +1147,7 @@
removeFromArray(objects, object);
objects.splice(nextIntersectingIdx, 0, object);
}
this.renderAll();
return this.renderAll && this.renderAll();
},
/**
@ -1280,6 +1315,7 @@
* Returs JSON representation of canvas
* @function
* @method toJSON
* @param {Array} propertiesToInclude
* @return {String} json string
*/
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;

View file

@ -12,6 +12,37 @@
return;
}
var dimensionAffectingProps = {
fontSize: true,
fontWeight: true,
fontFamily: true,
textDecoration: true,
fontStyle: true,
lineHeight: true,
strokeStyle: true,
strokeWidth: true,
text: true
};
var stateProperties = fabric.Object.prototype.stateProperties.concat();
stateProperties.push(
'fontFamily',
'fontWeight',
'fontSize',
'path',
'text',
'textDecoration',
'textShadow',
'textAlign',
'fontStyle',
'lineHeight',
'strokeStyle',
'strokeWidth',
'backgroundColor',
'textBackgroundColor',
'useNative'
);
/**
* Text class
* @class Text
@ -31,7 +62,7 @@
* @property
* @type Number
*/
fontWeight: 400,
fontWeight: 'normal',
/**
* Font family
@ -124,6 +155,14 @@
*/
useNative: true,
/**
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged)
* as well as for history (undo/redo) purposes
* @property
* @type Array
*/
stateProperties: stateProperties,
/**
* Constructor
* @method initialize
@ -134,7 +173,6 @@
initialize: function(text, options) {
options = options || { };
this._initStateProperties();
this.text = text;
this.setOptions(options);
this._initDimensions();
@ -148,38 +186,9 @@
*/
_initDimensions: function() {
var canvasEl = fabric.util.createCanvasElement();
this._render(canvasEl.getContext('2d'));
},
/**
* Creates `stateProperties` list on an instance, and adds `fabric.Text` -specific ones to it
* (such as "fontFamily", "fontWeight", etc.)
* @private
* @method _initStateProperties
*/
_initStateProperties: function() {
this.stateProperties = this.stateProperties.concat();
this.stateProperties.push(
'fontFamily',
'fontWeight',
'fontSize',
'path',
'text',
'textDecoration',
'textShadow',
'textAlign',
'fontStyle',
'lineHeight',
'strokeStyle',
'strokeWidth',
'backgroundColor',
'textBackgroundColor',
'useNative'
);
fabric.util.removeFromArray(this.stateProperties, 'width');
},
/**
* Returns string representation of an instance
* @method toString
@ -278,10 +287,12 @@
}
this._setTextShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
this._renderTextFill(ctx, textLines);
this._renderTextStroke(ctx, textLines);
this.clipTo && ctx.restore();
this.textShadow && ctx.restore();
this._renderTextStroke(ctx, textLines);
if (this.textAlign !== 'left' && this.textAlign !== 'justify') {
ctx.restore();
}
@ -589,8 +600,9 @@
*/
_getFontDeclaration: function() {
return [
this.fontStyle,
this.fontWeight,
// node-canvas needs "weight style", while browsers need "style weight"
(fabric.isLikelyNode ? this.fontWeight : this.fontStyle),
(fabric.isLikelyNode ? this.fontStyle : this.fontWeight),
this.fontSize + 'px',
(fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)
].join(' ');
@ -633,7 +645,7 @@
this._render(ctx);
if (!noTransform && this.active) {
this.drawBorders(ctx);
this.hideCorners || this.drawCorners(ctx);
this.drawControls(ctx);
}
ctx.restore();
},
@ -837,20 +849,6 @@
return this;
},
/**
* Sets fontSize of an instance and updates its coordinates
* @method setFontsize
* @param {Number} value
* @return {fabric.Text} thisArg
* @chainable
*/
setFontsize: function(value) {
this.set('fontSize', value);
this._initDimensions();
this.setCoords();
return this;
},
/**
* Returns actual text value of an instance
* @method getText
@ -860,20 +858,6 @@
return this.text;
},
/**
* Sets text of an instance, and updates its coordinates
* @method setText
* @param {String} value
* @return {fabric.Text} thisArg
* @chainable
*/
setText: function(value) {
this.set('text', value);
this._initDimensions();
this.setCoords();
return this;
},
/**
* Sets specified property to a specified value
* @method set
@ -887,6 +871,11 @@
this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3');
}
this.callSuper('_set', name, value);
if (name in dimensionAffectingProps) {
this._initDimensions();
this.setCoords();
}
}
});
@ -942,4 +931,6 @@
return text;
};
fabric.util.createAccessors(fabric.Text);
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -76,8 +76,8 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
var widthBy2 = this.width / 2,
var markup = [],
widthBy2 = this.width / 2,
heightBy2 = this.height / 2;
var points = [
@ -86,11 +86,22 @@
widthBy2 + " " + heightBy2
].join(",");
return '<polygon ' +
'points="' + points + '" ' +
'style="' + this.getSvgStyles() + '" ' +
'transform="' + this.getSvgTransform() + '" ' +
'/>';
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, true));
}
if (this.stroke && this.stroke.toLive) {
markup.push(this.stroke.toSVG(this, true));
}
markup.push(
'<polygon ',
'points="', points,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
);
return markup.join('');
}
});

View file

@ -223,10 +223,16 @@
if (fabric.isTouchSupported) {
pointerX = function(event) {
return (event.touches && event.touches[0] ? (event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX)) || event.clientX : event.clientX);
if (event.type !== 'touchend') {
return (event.touches && event.touches[0] ? (event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX)) || event.clientX : event.clientX);
}
return (event.changedTouches && event.changedTouches[0] ? (event.changedTouches[0].pageX - (event.changedTouches[0].pageX - event.changedTouches[0].clientX)) || event.clientX : event.clientX);
};
pointerY = function(event) {
return (event.touches && event.touches[0] ? (event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY)) || event.clientY : event.clientY);
if (event.type !== 'touchend') {
return (event.touches && event.touches[0] ? (event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY)) || event.clientY : event.clientY);
}
return (event.changedTouches && event.changedTouches[0] ? (event.changedTouches[0].pageY - (event.changedTouches[0].pageY - event.changedTouches[0].clientY)) || event.clientY : event.clientY);
};
}
@ -234,4 +240,4 @@
fabric.util.object.extend(fabric.util, fabric.Observable);
})();
})();

View file

@ -23,7 +23,7 @@
if (n !== n) { // shortcut for verifying if it's NaN
n = 0;
}
else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
@ -240,7 +240,9 @@
return result;
}
/** @namespace */
/**
* @namespace Array utilities
*/
fabric.util.array = {
invoke: invoke,
min: min,

View file

@ -1,5 +1,5 @@
(function(){
/**
* Copies all enumerable properties of one object to another
* @memberOf fabric.util.object
@ -25,10 +25,10 @@
return extend({ }, object);
}
/** @namespace fabric.util.object */
/** @namespace Object utilities */
fabric.util.object = {
extend: extend,
clone: clone
};
})();

View file

@ -51,7 +51,7 @@ function escapeXml(string) {
.replace(/>/g, '&gt;');
}
/** @namespace */
/** @namespace String utilities */
fabric.util.string = {
camelize: camelize,
capitalize: capitalize,

View file

@ -4,7 +4,7 @@
atan2 = Math.atan2;
/**
* @namespace
* @namespace Various utilities
*/
fabric.util = { };
@ -339,14 +339,63 @@
ctx.restore();
}
function createCanvasElement() {
var canvasEl = fabric.document.createElement('canvas');
/**
* Creates canvas element and initializes it via excanvas if necessary
* @static
* @memberOf fabric.util
* @method createCanvasElement
* @param {CanvasElement} [canvasEl] optional canvas element to initialize; when not given, element is created implicitly
* @return {CanvasElement} initialized canvas element
*/
function createCanvasElement(canvasEl) {
canvasEl || (canvasEl = fabric.document.createElement('canvas'));
if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
G_vmlCanvasManager.initElement(canvasEl);
}
return canvasEl;
}
/**
* Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
* @static
* @memberOf fabric.util
* @method createAccessors
* @param {Object} klass "Class" to create accessors for
*/
function createAccessors(klass) {
var proto = klass.prototype;
for (var i = proto.stateProperties.length; i--; ) {
var propName = proto.stateProperties[i],
capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
setterName = 'set' + capitalizedPropName,
getterName = 'get' + capitalizedPropName;
// using `new Function` for better introspection
if (!proto[getterName]) {
proto[getterName] = (function(property) {
return new Function('return this.get("' + property + '")');
})(propName);
}
if (!proto[setterName]) {
proto[setterName] = (function(property) {
return new Function('value', 'return this.set("' + property + '", value)');
})(propName);
}
}
}
/**
* @method clipContext
*/
function clipContext(receiver, ctx) {
ctx.save();
ctx.beginPath();
receiver.clipTo(ctx);
ctx.clip();
}
fabric.util.removeFromArray = removeFromArray;
fabric.util.degreesToRadians = degreesToRadians;
fabric.util.radiansToDegrees = radiansToDegrees;
@ -362,5 +411,7 @@
fabric.util.populateWithProperties = populateWithProperties;
fabric.util.drawDashedLine = drawDashedLine;
fabric.util.createCanvasElement = createCanvasElement;
fabric.util.createAccessors = createAccessors;
fabric.util.clipContext = clipContext;
})();

View file

@ -23,13 +23,13 @@
var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+
'"path":"http://example.com/"}],"background":""}';
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,'+
'"perPixelTargetFind":false,"shadow":null,"visible":true,"path":"http://example.com/"}],"background":""}';
var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+
'"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"rx":0,"ry":0}],'+
'"background":"#ff5555"}';
'"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+
'"visible":true,"rx":0,"ry":0}],"background":"#ff5555"}';
var el = fabric.document.createElement('canvas');
el.width = 600; el.height = 600;
@ -149,7 +149,7 @@
alert("toDataURL is not supported by this environment. Some of the tests can not be run.");
}
else {
var dataURL = canvas.toDataURL('png');
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
equal(typeof dataURL, 'string');
@ -708,14 +708,14 @@
equal(canvas.getWidth(), 600);
equal(canvas.setWidth(444), canvas, 'chainable');
equal(canvas.getWidth(), fabric.isLikelyNode ? undefined : 444);
equal(canvas.getWidth(), 444);
});
test('getSetHeight', function() {
ok(typeof canvas.getHeight == 'function');
equal(canvas.getHeight(), 600);
equal(canvas.setHeight(765), canvas, 'chainable');
equal(canvas.getHeight(), fabric.isLikelyNode ? undefined : 765);
equal(canvas.getHeight(), 765);
});
test('containsPoint', function() {

View file

@ -21,17 +21,17 @@
var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+
'"path":"http://example.com/"}],"background":""}';
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,'+
'"perPixelTargetFind":false,"shadow":null,"visible":true,"path":"http://example.com/"}],"background":""}';
var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"rx":0,"ry":0}],'+
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true,"rx":0,"ry":0}],'+
'"background":"#ff5555"}';
var RECT_JSON_WITH_PADDING = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":20,"fill":"rgb(0,0,0)","overlayFill":null,'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"padding":123,"foo":"bar","rx":0,"ry":0}],'+
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true,"padding":123,"foo":"bar","rx":0,"ry":0}],'+
'"background":""}';
// force creation of static canvas
@ -143,7 +143,7 @@
alert("toDataURL is not supported by this environment. Some of the tests can not be run.");
}
else {
var dataURL = canvas.toDataURL('png');
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
equal(typeof dataURL, 'string');
@ -543,14 +543,14 @@
ok(typeof canvas.getWidth == 'function');
equal(canvas.getWidth(), 600);
equal(canvas.setWidth(444), canvas, 'chainable');
equal(canvas.getWidth(), fabric.isLikelyNode ? undefined: 444);
equal(canvas.getWidth(), 444);
});
test('getSetHeight', function() {
ok(typeof canvas.getHeight == 'function');
equal(canvas.getHeight(), 600);
equal(canvas.setHeight(765), canvas, 'chainable');
equal(canvas.getHeight(), fabric.isLikelyNode ? undefined : 765);
equal(canvas.getHeight(), 765);
});
test('toGrayscale', function() {

View file

@ -85,6 +85,7 @@
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null,
'visible': true,
'radius': 0
};
ok(typeof circle.toObject == 'function');

View file

@ -48,7 +48,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
ok(typeof ellipse.toObject == 'function');
deepEqual(defaultProperties, ellipse.toObject());

View file

@ -4,14 +4,16 @@
function createGradient() {
return new fabric.Gradient({
x1: 0,
y1: 10,
x2: 100,
y2: 200,
colorStops: {
'0': 'red',
'1': 'green'
}
coords: {
x1: 0,
y1: 10,
x2: 100,
y2: 200,
},
colorStops: [
{ offset: 0, color: 'red' },
{ offset: 1, color: 'green' }
]
});
}
@ -25,13 +27,16 @@
test('properties', function() {
var gradient = createGradient();
equal(gradient.x1, 0);
equal(gradient.y1, 10);
equal(gradient.x2, 100);
equal(gradient.y2, 200);
equal(gradient.coords.x1, 0);
equal(gradient.coords.y1, 10);
equal(gradient.coords.x2, 100);
equal(gradient.coords.y2, 200);
equal(gradient.colorStops['0'], 'red');
equal(gradient.colorStops['1'], 'green');
equal(gradient.colorStops[0].offset, 0);
equal(gradient.colorStops[0].color, 'red');
equal(gradient.colorStops[1].offset, 1);
equal(gradient.colorStops[1].color, 'green');
});
test('toObject', function() {
@ -41,10 +46,11 @@
var object = gradient.toObject();
equal(object.x1, gradient.x1);
equal(object.x2, gradient.x2);
equal(object.y1, gradient.y1);
equal(object.y2, gradient.y2);
equal(object.coords.x1, gradient.coords.x1);
equal(object.coords.x2, gradient.coords.x2);
equal(object.coords.y1, gradient.coords.y1);
equal(object.coords.y2, gradient.coords.y2);
equal(object.colorStops, gradient.colorStops);
});
@ -77,14 +83,17 @@
// TODO: need to double check these values
equal(gradient.x1, -50);
equal(gradient.y1, -50);
equal(gradient.coords.x1, 0);
equal(gradient.coords.y1, 0);
equal(gradient.x2, 50);
equal(gradient.y2, -50);
//equal(gradient.coords.x2, 100);
//equal(gradient.coords.y2, 100);
equal(gradient.colorStops[0], 'white');
equal(gradient.colorStops[1], 'black');
equal(gradient.colorStops[0].offset, 1);
equal(gradient.colorStops[1].offset, 0);
equal(gradient.colorStops[0].color, 'rgb(0,0,0)');
equal(gradient.colorStops[1].color, 'rgb(255,255,255)');
});
test('forObject', function() {
@ -93,26 +102,28 @@
var object = new fabric.Object({ width: 50, height: 50 });
var gradient = fabric.Gradient.forObject(object, {
x1: 10,
y1: 10,
x2: 20,
y2: 20,
colorStops: {
'0': 'red',
'0.5': 'green',
'1': 'blue'
}
coords: {
x1: 10,
y1: 10,
x2: 20,
y2: 20,
},
colorStops: [
{ offset: 0, color: 'red' },
{ offset: 0.5, color: 'green' },
{ offset: 1, color: 'blue' }
]
});
ok(gradient instanceof fabric.Gradient);
// TODO: need to double check these values
equal(gradient.x1, -15);
equal(gradient.y1, -15);
equal(gradient.coords.x1, 10);
equal(gradient.coords.y1, 10);
equal(gradient.x2, -5);
equal(gradient.y2, -5);
equal(gradient.coords.x2, 20);
equal(gradient.coords.y2, 20);
});
})();

View file

@ -144,6 +144,7 @@
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null,
'visible': true,
'angle': 0,
'flipX': false,
'flipY': false,
@ -290,7 +291,7 @@
var group = makeGroupWith2Objects();
ok(typeof group.toSVG == 'function');
var expectedSVG = '<g transform="translate(80 117.5)"><rect x="-15" y="-5" rx="0" ry="0" width="30" height="10" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(20 -17.5)" /><rect x="-5" y="-20" rx="0" ry="0" width="10" height="40" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(-30 2.5)" /></g>';
var expectedSVG = '<g transform="translate(80 117.5)"><rect x="-5" y="-20" rx="0" ry="0" width="10" height="40" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(-30 2.5)"/><rect x="-15" y="-5" rx="0" ry="0" width="30" height="10" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(20 -17.5)"/></g>';
equal(group.toSVG(), expectedSVG);
});
@ -310,23 +311,62 @@
equal(group.get('lockMovementX'), false);
group.objects[0].lockMovementX = true;
group.getObjects()[0].lockMovementX = true;
equal(group.get('lockMovementX'), true);
group.objects[0].lockMovementX = false;
group.getObjects()[0].lockMovementX = false;
equal(group.get('lockMovementX'), false);
group.set('lockMovementX', true);
equal(group.get('lockMovementX'), true);
group.set('lockMovementX', false);
group.objects[0].lockMovementY = true;
group.objects[1].lockRotation = true;
group.getObjects()[0].lockMovementY = true;
group.getObjects()[1].lockRotation = true;
equal(group.get('lockMovementY'), true);
equal(group.get('lockRotation'), true);
});
test('z-index methods with group objects', function() {
var textBg = new fabric.Rect({
fill : '#abc',
width : 100,
height : 100
});
var text = new fabric.Text('text');
var group = new fabric.Group([ textBg, text ]);
canvas.add(group);
ok(group.getObjects()[0] === textBg);
ok(group.getObjects()[1] === text);
textBg.bringToFront();
ok(group.getObjects()[0] === text);
ok(group.getObjects()[1] === textBg);
textBg.sendToBack();
ok(group.getObjects()[0] === textBg);
ok(group.getObjects()[1] === text);
});
test('group reference on an object', function() {
var group = makeGroupWith2Objects();
var firstObjInGroup = group.getObjects()[0];
var secondObjInGroup = group.getObjects()[1];
equal(firstObjInGroup.group, group);
equal(secondObjInGroup.group, group);
group.remove(firstObjInGroup);
ok(typeof firstObjInGroup.group == 'undefined');
});
// asyncTest('cloning group with image', function() {
// var rect = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10 }),
// img = new fabric.Image(_createImageElement()),

View file

@ -20,8 +20,8 @@
'originY': 'center',
'left': 0,
'top': 0,
'width': 0, // node-canvas doesn't seem to allow setting width/height on image objects
'height': 0,
'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects
'height': IMG_HEIGHT, // or does it now?
'fill': 'rgb(0,0,0)',
'overlayFill': null,
'stroke': null,
@ -41,6 +41,7 @@
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null,
'visible': true,
'filters': []
};
@ -97,7 +98,15 @@
asyncTest('toObject', function() {
createImageObject(function(image) {
ok(typeof image.toObject == 'function');
deepEqual(image.toObject(), REFERENCE_IMG_OBJECT);
var toObject = image.toObject();
// workaround for node-canvas sometimes producing images with width/height and sometimes not
if (toObject.width === 0) {
toObject.width = IMG_WIDTH;
}
if (toObject.height === 0) {
toObject.height = IMG_HEIGHT;
}
deepEqual(toObject, REFERENCE_IMG_OBJECT);
start();
});
});

View file

@ -29,7 +29,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
QUnit.module('fabric.Line');

View file

@ -111,11 +111,13 @@
test('toJSON', function() {
var emptyObjectJSON = '{"type":"object","originX":"center","originY":"center","left":0,"top":0,"width":0,"height":0,"fill":"rgb(0,0,0)",'+
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,'+
'"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null}';
'"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+
'"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true}';
var augmentedJSON = '{"type":"object","originX":"center","originY":"center","left":0,"top":0,"width":122,"height":0,"fill":"rgb(0,0,0)",'+
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1.3,"scaleY":1,"angle":0,'+
'"flipX":false,"flipY":true,"opacity":0.88,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null}';
'"flipX":false,"flipY":true,"opacity":0.88,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+
'"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true}';
var cObj = new fabric.Object();
ok(typeof cObj.toJSON == 'function');
@ -151,7 +153,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
var augmentedObjectRepr = {
@ -179,7 +182,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
var cObj = new fabric.Object();
@ -392,7 +396,7 @@
equal(cObj.drawBorders(dummyContext), cObj, 'chainable');
});
test('drawCorners', function() {
test('drawControls', function() {
var cObj = new fabric.Object(), canvas = fabric.document.createElement('canvas');
//let excanvas kick in for IE8 and lower
@ -400,8 +404,8 @@
G_vmlCanvasManager.initElement(canvas)
}
var dummyContext = canvas.getContext('2d');
ok(typeof cObj.drawCorners == 'function');
equal(cObj.drawCorners(dummyContext), cObj, 'chainable');
ok(typeof cObj.drawControls == 'function');
equal(cObj.drawControls(dummyContext), cObj, 'chainable');
});
test('clone', function() {
@ -790,27 +794,31 @@
test('gradient serialization', function() {
var object = new fabric.Object();
object.fill = new fabric.Gradient({
object.setGradient('fill', {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
colorStops: {
'0': 'red',
'1': 'green'
'0': 'rgb(255,0,0)',
'1': 'rgb(0,128,0)'
}
});
ok(typeof object.toObject().fill == 'object');
equal(object.toObject().fill.x1, 0);
equal(object.toObject().fill.y1, 0);
equal(object.toObject().fill.type, 'linear');
equal(object.toObject().fill.x2, 100);
equal(object.toObject().fill.y2, 100);
equal(object.toObject().fill.coords.x1, 0);
equal(object.toObject().fill.coords.y1, 0);
equal(object.toObject().fill.colorStops['0'], 'red');
equal(object.toObject().fill.colorStops['1'], 'green');
equal(object.toObject().fill.coords.x2, 100);
equal(object.toObject().fill.coords.y2, 100);
equal(object.toObject().fill.colorStops[0].offset, 0);
equal(object.toObject().fill.colorStops[1].offset, 1);
equal(object.toObject().fill.colorStops[0].color, 'rgb(255,0,0)');
equal(object.toObject().fill.colorStops[1].color, 'rgb(0,128,0)');
});
test('setShadow', function() {

View file

@ -98,4 +98,17 @@ test('event options', function() {
foo.fire('foo:bar', { value: 'sekret' });
equal('sekret', someValue);
});
test('trigger', function() {
var foo = { };
fabric.util.object.extend(foo, fabric.Observable);
var eventFired = false;
foo.on('bar:baz', function() {
eventFired = true;
});
foo.trigger('bar:baz');
equal(true, eventFired);
});

View file

@ -26,7 +26,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
function getPathElement(path) {

View file

@ -26,6 +26,7 @@
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null,
'visible': true,
'paths': getPathObjects()
};

View file

@ -33,7 +33,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
QUnit.module('fabric.Polygon');
@ -83,7 +84,13 @@
var polygon = fabric.Polygon.fromElement(elPolygon);
ok(polygon instanceof fabric.Polygon);
deepEqual(REFERENCE_OBJECT, polygon.toObject());
var expected = fabric.util.object.extend(
fabric.util.object.clone(REFERENCE_OBJECT), {
points: [ { x: 10, y: 12 }, { x: 20, y: 22 } ]
});
deepEqual(expected, polygon.toObject());
var elPolygonWithAttrs = fabric.document.createElement('polygon');
elPolygonWithAttrs.setAttribute('points', '10,10 20,20 30,30 10,10');
@ -95,10 +102,10 @@
var polygonWithAttrs = fabric.Polygon.fromElement(elPolygonWithAttrs);
var expectedPoints = [
{ x: 0, y: 0 },
{ x: 10, y: 10 },
{ x: 20, y: 20 },
{ x: 0, y: 0 }
{ x: 30, y: 30 },
{ x: 10, y: 10 }
];
deepEqual(fabric.util.object.extend(REFERENCE_OBJECT, {

View file

@ -33,7 +33,8 @@
'hasRotatingPoint': true,
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null
'shadow': null,
'visible': true
};
QUnit.module('fabric.Polyline');

View file

@ -26,6 +26,7 @@
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null,
'visible': true,
'rx': 0,
'ry': 0
};
@ -124,6 +125,6 @@
var rect = new fabric.Rect({ width: 100, height: 100, rx: 20, ry: 30 });
var svg = rect.toSVG();
equal('<rect x="-50" y="-50" rx="20" ry="30" width="100" height="100" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(0 0)" />', svg);
equal('<rect x="-50" y="-50" rx="20" ry="30" width="100" height="100" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(0 0)"/>', svg);
});
})();

View file

@ -32,9 +32,10 @@
'transparentCorners': true,
'perPixelTargetFind': false,
'shadow': null,
'visible': true,
'text': 'x',
'fontSize': 40,
'fontWeight': 400,
'fontWeight': 'normal',
'fontFamily': 'Times New Roman',
'fontStyle': '',
'lineHeight': 1.3,
@ -106,10 +107,10 @@
equal(text.get('fill'), '123456');
});
test('setFontsize', function(){
test('setFontSize', function(){
var text = createTextObject();
ok(typeof text.setFontsize == 'function');
equal(text.setFontsize(12), text);
ok(typeof text.setFontSize == 'function');
equal(text.setFontSize(12), text);
equal(text.get('fontSize'), 12);
});
@ -146,7 +147,7 @@
// temp workaround for text objects not obtaining width under node
// text.width = 20;
var expectedObject = fabric.util.object.extend(REFERENCE_TEXT_OBJECT, {
var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), {
left: 10,
top: -26
});
@ -177,9 +178,9 @@
ok(textWithAttrs instanceof fabric.Text);
var expectedObject = fabric.util.object.extend(REFERENCE_TEXT_OBJECT, {
/* left varies slightly due to node-canvas rendering so we're not testing for it */
left: textWithAttrs.left,
var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), {
/* left varies slightly due to node-canvas rendering */
left: fabric.util.toFixed(textWithAttrs.left + '', 2),
top: -59.95,
width: 20,
height: 159.9,

View file

@ -127,9 +127,9 @@
// borrowed from Prototype.js
equal('foo bar', escapeXml('foo bar'));
equal('foo &lt;span&gt;bar&lt;/span&gt;', escapeXml('foo <span>bar</span>'));
equal('foo ß bar', escapeXml('foo ß bar'));
//equal('foo ß bar', escapeXml('foo ß bar'));
equal('ウィメンズ2007\nクルーズコレクション', escapeXml('ウィメンズ2007\nクルーズコレクション'));
//equal('ウィメンズ2007\nクルーズコレクション', escapeXml('ウィメンズ2007\nクルーズコレクション'));
equal('a&lt;a href=&quot;blah&quot;&gt;blub&lt;/a&gt;b&lt;span&gt;&lt;div&gt;&lt;/div&gt;&lt;/span&gt;cdef&lt;strong&gt;!!!!&lt;/strong&gt;g',
escapeXml('a<a href="blah">blub</a>b<span><div></div></span>cdef<strong>!!!!</strong>g'));