mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-25 21:53:44 +00:00
Objectcaching (#3317)
This commit is contained in:
parent
161a3e8acb
commit
9422fd39be
23 changed files with 377 additions and 187 deletions
|
|
@ -198,9 +198,12 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
|
||||||
}
|
}
|
||||||
|
|
||||||
fabric.copiedText = selectedText;
|
fabric.copiedText = selectedText;
|
||||||
fabric.copiedTextStyle = this.getSelectionStyles(
|
fabric.copiedTextStyle = fabric.util.object.clone(
|
||||||
this.selectionStart,
|
this.getSelectionStyles(
|
||||||
this.selectionEnd);
|
this.selectionStart,
|
||||||
|
this.selectionEnd
|
||||||
|
)
|
||||||
|
);
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._copyDone = true;
|
this._copyDone = true;
|
||||||
|
|
|
||||||
|
|
@ -113,24 +113,8 @@
|
||||||
*/
|
*/
|
||||||
_getNonTransformedDimensions: function() {
|
_getNonTransformedDimensions: function() {
|
||||||
var strokeWidth = this.strokeWidth,
|
var strokeWidth = this.strokeWidth,
|
||||||
w = this.width,
|
w = this.width + strokeWidth,
|
||||||
h = this.height,
|
h = this.height + strokeWidth;
|
||||||
addStrokeToW = true,
|
|
||||||
addStrokeToH = true;
|
|
||||||
|
|
||||||
if (this.type === 'line' && this.strokeLineCap === 'butt') {
|
|
||||||
addStrokeToH = w;
|
|
||||||
addStrokeToW = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addStrokeToH) {
|
|
||||||
h += h < 0 ? -strokeWidth : strokeWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addStrokeToW) {
|
|
||||||
w += w < 0 ? -strokeWidth : strokeWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x: w, y: h };
|
return { x: w, y: h };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
var extend = fabric.util.object.extend;
|
var extend = fabric.util.object.extend,
|
||||||
|
originalSet = 'stateProperties';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Depends on `stateProperties`
|
Depends on `stateProperties`
|
||||||
|
|
@ -13,7 +14,7 @@
|
||||||
extend(origin[destination], tmpObj, deep);
|
extend(origin[destination], tmpObj, deep);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isEqual(origValue, currentValue) {
|
function _isEqual(origValue, currentValue, firstPass) {
|
||||||
if (!fabric.isLikelyNode && origValue instanceof Element) {
|
if (!fabric.isLikelyNode && origValue instanceof Element) {
|
||||||
// avoid checking deep html elements
|
// avoid checking deep html elements
|
||||||
return origValue === currentValue;
|
return origValue === currentValue;
|
||||||
|
|
@ -29,6 +30,9 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (origValue instanceof Object) {
|
else if (origValue instanceof Object) {
|
||||||
|
if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (var key in origValue) {
|
for (var key in origValue) {
|
||||||
if (!_isEqual(origValue[key], currentValue[key])) {
|
if (!_isEqual(origValue[key], currentValue[key])) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -46,10 +50,13 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if object state (one of its state properties) was changed
|
* Returns true if object state (one of its state properties) was changed
|
||||||
|
* @param {String} [propertySet] optional name for the set of property we want to save
|
||||||
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
|
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
|
||||||
*/
|
*/
|
||||||
hasStateChanged: function() {
|
hasStateChanged: function(propertySet) {
|
||||||
return !_isEqual(this.originalState, this);
|
propertySet = propertySet || originalSet;
|
||||||
|
propertySet = '_' + propertySet;
|
||||||
|
return !_isEqual(this[propertySet], this, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,9 +65,11 @@
|
||||||
* @return {fabric.Object} thisArg
|
* @return {fabric.Object} thisArg
|
||||||
*/
|
*/
|
||||||
saveState: function(options) {
|
saveState: function(options) {
|
||||||
saveProps(this, 'originalState', this.stateProperties);
|
var propertySet = options && options.propertySet || originalSet,
|
||||||
|
destination = '_' + propertySet;
|
||||||
|
saveProps(this, destination, this[propertySet]);
|
||||||
if (options && options.stateProperties) {
|
if (options && options.stateProperties) {
|
||||||
saveProps(this, 'originalState', options.stateProperties);
|
saveProps(this, destination, options.stateProperties);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
@ -71,7 +80,10 @@
|
||||||
* @return {fabric.Object} thisArg
|
* @return {fabric.Object} thisArg
|
||||||
*/
|
*/
|
||||||
setupState: function(options) {
|
setupState: function(options) {
|
||||||
this.originalState = { };
|
options = options || { };
|
||||||
|
var propertySet = options.propertySet || originalSet;
|
||||||
|
options.propertySet = propertySet;
|
||||||
|
this['_' + propertySet] = { };
|
||||||
this.saveState(options);
|
this.saveState(options);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,14 @@
|
||||||
value = '';
|
value = '';
|
||||||
}
|
}
|
||||||
else if (attr === 'strokeDashArray') {
|
else if (attr === 'strokeDashArray') {
|
||||||
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
|
if (value === 'none') {
|
||||||
return parseFloat(n);
|
value = null;
|
||||||
});
|
}
|
||||||
|
else {
|
||||||
|
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
|
||||||
|
return parseFloat(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (attr === 'transformMatrix') {
|
else if (attr === 'transformMatrix') {
|
||||||
if (parentAttributes && parentAttributes.transformMatrix) {
|
if (parentAttributes && parentAttributes.transformMatrix) {
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,8 @@
|
||||||
* @return {fabric.Circle} thisArg
|
* @return {fabric.Circle} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
options = options || { };
|
|
||||||
|
|
||||||
this.callSuper('initialize', options);
|
this.callSuper('initialize', options);
|
||||||
this.set('radius', options.radius || 0);
|
this.set('radius', options && options.radius || 0);
|
||||||
|
|
||||||
this.startAngle = options.startAngle || this.startAngle;
|
|
||||||
this.endAngle = options.endAngle || this.endAngle;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,9 @@
|
||||||
* @return {fabric.Ellipse} thisArg
|
* @return {fabric.Ellipse} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
options = options || { };
|
|
||||||
|
|
||||||
this.callSuper('initialize', options);
|
this.callSuper('initialize', options);
|
||||||
|
this.set('rx', options && options.rx || 0);
|
||||||
this.set('rx', options.rx || 0);
|
this.set('ry', options && options.ry || 0);
|
||||||
this.set('ry', options.ry || 0);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@
|
||||||
this.forEachObject(this._setObjectActive, this);
|
this.forEachObject(this._setObjectActive, this);
|
||||||
this._calcBounds();
|
this._calcBounds();
|
||||||
this._updateObjectsCoords();
|
this._updateObjectsCoords();
|
||||||
|
this.dirty = true;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -190,7 +191,7 @@
|
||||||
this.remove(object);
|
this.remove(object);
|
||||||
this._calcBounds();
|
this._calcBounds();
|
||||||
this._updateObjectsCoords();
|
this._updateObjectsCoords();
|
||||||
|
this.dirty = true;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -198,6 +199,7 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onObjectAdded: function(object) {
|
_onObjectAdded: function(object) {
|
||||||
|
this.dirty = true;
|
||||||
object.group = this;
|
object.group = this;
|
||||||
object._set('canvas', this.canvas);
|
object._set('canvas', this.canvas);
|
||||||
},
|
},
|
||||||
|
|
@ -206,6 +208,7 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onObjectRemoved: function(object) {
|
_onObjectRemoved: function(object) {
|
||||||
|
this.dirty = true;
|
||||||
delete object.group;
|
delete object.group;
|
||||||
object.set('active', false);
|
object.set('active', false);
|
||||||
},
|
},
|
||||||
|
|
@ -264,27 +267,40 @@
|
||||||
* @param {CanvasRenderingContext2D} ctx context to render instance on
|
* @param {CanvasRenderingContext2D} ctx context to render instance on
|
||||||
*/
|
*/
|
||||||
render: function(ctx) {
|
render: function(ctx) {
|
||||||
// do not render if object is not visible
|
|
||||||
if (!this.visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
if (this.transformMatrix) {
|
|
||||||
ctx.transform.apply(ctx, this.transformMatrix);
|
|
||||||
}
|
|
||||||
this.transform(ctx);
|
|
||||||
this._setShadow(ctx);
|
|
||||||
this.clipTo && fabric.util.clipContext(this, ctx);
|
|
||||||
this._transformDone = true;
|
this._transformDone = true;
|
||||||
// the array is now sorted in order of highest first, so start from end
|
this.callSuper('render', ctx);
|
||||||
|
this._transformDone = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the drawing operation for an object on a specified context
|
||||||
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
|
* @param {Boolean} [noTransform] When true, context is not transformed
|
||||||
|
*/
|
||||||
|
drawObject: function(ctx) {
|
||||||
for (var i = 0, len = this._objects.length; i < len; i++) {
|
for (var i = 0, len = this._objects.length; i < len; i++) {
|
||||||
this._renderObject(this._objects[i], ctx);
|
this._renderObject(this._objects[i], ctx);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
this.clipTo && ctx.restore();
|
/**
|
||||||
ctx.restore();
|
* Check if cache is dirty
|
||||||
this._transformDone = false;
|
*/
|
||||||
|
isCacheDirty: function() {
|
||||||
|
if (this.callSuper('isCacheDirty')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!this.statefullCache) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var i = 0, len = this._objects.length; i < len; i++) {
|
||||||
|
if (this._objects[i].isCacheDirty(true)) {
|
||||||
|
var dim = this._getNonTransformedDimensions();
|
||||||
|
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,15 @@
|
||||||
*/
|
*/
|
||||||
stateProperties: stateProperties,
|
stateProperties: stateProperties,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `true`, object is cached on an additional canvas.
|
||||||
|
* default to false for images
|
||||||
|
* since 1.7.0
|
||||||
|
* @type Boolean
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
objectCaching: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {HTMLImageElement | String} element Image element
|
* @param {HTMLImageElement | String} element Image element
|
||||||
|
|
|
||||||
|
|
@ -811,7 +811,7 @@
|
||||||
leftOffset = this._getLeftOffset(),
|
leftOffset = this._getLeftOffset(),
|
||||||
topOffset = this._getTopOffset(),
|
topOffset = this._getTopOffset(),
|
||||||
line, _char, style;
|
line, _char, style;
|
||||||
|
ctx.save();
|
||||||
for (var i = 0, len = this._textLines.length; i < len; i++) {
|
for (var i = 0, len = this._textLines.length; i < len; i++) {
|
||||||
heightOfLine = this._getHeightOfLine(ctx, i);
|
heightOfLine = this._getHeightOfLine(ctx, i);
|
||||||
line = this._textLines[i];
|
line = this._textLines[i];
|
||||||
|
|
@ -842,6 +842,7 @@
|
||||||
}
|
}
|
||||||
lineTopOffset += heightOfLine;
|
lineTopOffset += heightOfLine;
|
||||||
}
|
}
|
||||||
|
ctx.restore();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,6 @@
|
||||||
* @return {fabric.Line} thisArg
|
* @return {fabric.Line} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(points, options) {
|
initialize: function(points, options) {
|
||||||
options = options || { };
|
|
||||||
|
|
||||||
if (!points) {
|
if (!points) {
|
||||||
points = [0, 0, 0, 0];
|
points = [0, 0, 0, 0];
|
||||||
}
|
}
|
||||||
|
|
@ -206,6 +204,23 @@
|
||||||
return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());
|
return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate object dimensions from its properties
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getNonTransformedDimensions: function() {
|
||||||
|
var dim = this.callSuper('_getNonTransformedDimensions');
|
||||||
|
if (this.strokeLineCap === 'butt') {
|
||||||
|
if (dim.x === 0) {
|
||||||
|
dim.y -= this.strokeWidth;
|
||||||
|
}
|
||||||
|
if (dim.y === 0) {
|
||||||
|
dim.x -= this.strokeWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dim;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recalculates line points given width and height
|
* Recalculates line points given width and height
|
||||||
* @private
|
* @private
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
toFixed = fabric.util.toFixed,
|
toFixed = fabric.util.toFixed,
|
||||||
capitalize = fabric.util.string.capitalize,
|
capitalize = fabric.util.string.capitalize,
|
||||||
degreesToRadians = fabric.util.degreesToRadians,
|
degreesToRadians = fabric.util.degreesToRadians,
|
||||||
supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
|
supportsLineDash = fabric.StaticCanvas.supports('setLineDash'),
|
||||||
|
objectCaching = !fabric.isLikelyNode;
|
||||||
|
|
||||||
if (fabric.Object) {
|
if (fabric.Object) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -743,7 +744,6 @@
|
||||||
* @type Boolean
|
* @type Boolean
|
||||||
* @default
|
* @default
|
||||||
*/
|
*/
|
||||||
|
|
||||||
lockScalingFlip: false,
|
lockScalingFlip: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -752,8 +752,39 @@
|
||||||
* @type Boolean
|
* @type Boolean
|
||||||
* @default
|
* @default
|
||||||
*/
|
*/
|
||||||
|
excludeFromExport: false,
|
||||||
|
|
||||||
excludeFromExport: false,
|
/**
|
||||||
|
* When `true`, object is cached on an additional canvas.
|
||||||
|
* default to true
|
||||||
|
* since 1.7.0
|
||||||
|
* @type Boolean
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
objectCaching: objectCaching,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `true`, object properties are checked for cache invalidation. In some particular
|
||||||
|
* situation you may want this to be disabled ( spray brush, very big pathgroups, groups)
|
||||||
|
* or if your application does not allow you to modify properties for groups child you want
|
||||||
|
* to disable it for groups.
|
||||||
|
* default to true
|
||||||
|
* since 1.7.0
|
||||||
|
* @type Boolean
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
statefullCache: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `true`, cache does not get updated during scaling. The picture will get blocky if scaled
|
||||||
|
* too much and will be redrawn with correct details at the end of scaling.
|
||||||
|
* this setting is performance and application dependant.
|
||||||
|
* default to false
|
||||||
|
* since 1.7.0
|
||||||
|
* @type Boolean
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
noScaleCache: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of properties to consider when checking if state
|
* List of properties to consider when checking if state
|
||||||
|
|
@ -761,21 +792,80 @@
|
||||||
* as well as for history (undo/redo) purposes
|
* as well as for history (undo/redo) purposes
|
||||||
* @type Array
|
* @type Array
|
||||||
*/
|
*/
|
||||||
stateProperties: (
|
stateProperties: (
|
||||||
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
|
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
|
||||||
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
|
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
|
||||||
'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
|
'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
|
||||||
'skewX skewY'
|
'skewX skewY'
|
||||||
).split(' '),
|
).split(' '),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of properties to consider when checking if cache needs refresh
|
||||||
|
* @type Array
|
||||||
|
*/
|
||||||
|
cacheProperties: (
|
||||||
|
'fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray' +
|
||||||
|
' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor'
|
||||||
|
).split(' '),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} [options] Options object
|
* @param {Object} [options] Options object
|
||||||
*/
|
*/
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
|
options = options || { };
|
||||||
if (options) {
|
if (options) {
|
||||||
this.setOptions(options);
|
this.setOptions(options);
|
||||||
}
|
}
|
||||||
|
if (this.objectCaching) {
|
||||||
|
this._createCacheCanvas();
|
||||||
|
this.setupState({ propertySet: 'cacheProperties' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a the canvas used to keep the cached copy of the object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createCacheCanvas: function() {
|
||||||
|
this._cacheCanvas = fabric.document.createElement('canvas');
|
||||||
|
this._cacheContext = this._cacheCanvas.getContext('2d');
|
||||||
|
this._updateCacheCanvas();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update width and height of the canvas for cache
|
||||||
|
* returns true or false if canvas needed resize.
|
||||||
|
* @private
|
||||||
|
* @return {Boolean} true if the canvas has been resized
|
||||||
|
*/
|
||||||
|
_updateCacheCanvas: function() {
|
||||||
|
if (this.noScaleCache && this.canvas && this.canvas._currentTransform) {
|
||||||
|
var action = this.canvas._currentTransform.action;
|
||||||
|
if (action.slice(0, 5) === 'scale') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var zoom = this.getViewportTransform()[0],
|
||||||
|
objectScale = this.getObjectScaling(),
|
||||||
|
dim = this._getNonTransformedDimensions(),
|
||||||
|
retina = this.canvas && this.canvas._isRetinaScaling() ? fabric.devicePixelRatio : 1,
|
||||||
|
zoomX = objectScale.scaleX * zoom * retina,
|
||||||
|
zoomY = objectScale.scaleY * zoom * retina;
|
||||||
|
if (zoomX !== this.zoomX || zoomY !== this.zoomY) {
|
||||||
|
var width = dim.x * zoomX,
|
||||||
|
height = dim.y * zoomY;
|
||||||
|
this._cacheCanvas.width = width;
|
||||||
|
this._cacheCanvas.height = height;
|
||||||
|
this._cacheContext.translate(width / 2, height / 2);
|
||||||
|
this._cacheContext.scale(zoomX, zoomY);
|
||||||
|
this.cacheWidth = width;
|
||||||
|
this.cacheHeight = height;
|
||||||
|
this.zoomX = zoomX;
|
||||||
|
this.zoomY = zoomY;
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1087,9 +1177,7 @@
|
||||||
if ((this.width === 0 && this.height === 0) || !this.visible) {
|
if ((this.width === 0 && this.height === 0) || !this.visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
//setup fill rule for current object
|
//setup fill rule for current object
|
||||||
this._setupCompositeOperation(ctx);
|
this._setupCompositeOperation(ctx);
|
||||||
this.drawSelectionBackground(ctx);
|
this.drawSelectionBackground(ctx);
|
||||||
|
|
@ -1098,21 +1186,71 @@
|
||||||
}
|
}
|
||||||
this._setOpacity(ctx);
|
this._setOpacity(ctx);
|
||||||
this._setShadow(ctx);
|
this._setShadow(ctx);
|
||||||
this._renderBackground(ctx);
|
|
||||||
this._setStrokeStyles(ctx);
|
|
||||||
this._setFillStyles(ctx);
|
|
||||||
if (this.transformMatrix) {
|
if (this.transformMatrix) {
|
||||||
ctx.transform.apply(ctx, this.transformMatrix);
|
ctx.transform.apply(ctx, this.transformMatrix);
|
||||||
}
|
}
|
||||||
this.clipTo && fabric.util.clipContext(this, ctx);
|
this.clipTo && fabric.util.clipContext(this, ctx);
|
||||||
this._render(ctx, noTransform);
|
if (this.objectCaching && !this.group) {
|
||||||
|
if (this.isCacheDirty(noTransform)) {
|
||||||
|
this.saveState({ propertySet: 'cacheProperties' });
|
||||||
|
this.drawObject(this._cacheContext, noTransform);
|
||||||
|
this.dirty = false;
|
||||||
|
}
|
||||||
|
this.drawCacheOnCanvas(ctx);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.drawObject(ctx, noTransform);
|
||||||
|
noTransform && this.saveState({ propertySet: 'cacheProperties' });
|
||||||
|
}
|
||||||
this.clipTo && ctx.restore();
|
this.clipTo && ctx.restore();
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a background for the object big as its width and height;
|
* Execute the drawing operation for an object on a specified context
|
||||||
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
|
* @param {Boolean} [noTransform] When true, context is not transformed
|
||||||
|
*/
|
||||||
|
drawObject: function(ctx, noTransform) {
|
||||||
|
this._renderBackground(ctx);
|
||||||
|
this._setStrokeStyles(ctx);
|
||||||
|
this._setFillStyles(ctx);
|
||||||
|
this._render(ctx, noTransform);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paint the cached copy of the object on the target context.
|
||||||
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
|
*/
|
||||||
|
drawCacheOnCanvas: function(ctx) {
|
||||||
|
ctx.scale(1 / this.zoomX, 1 / this.zoomY);
|
||||||
|
ctx.drawImage(this._cacheCanvas, -this.cacheWidth / 2, -this.cacheHeight / 2);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cache is dirty
|
||||||
|
* @param {Boolean} skipCanvas skip canvas checks because this object is painted
|
||||||
|
* on parent canvas.
|
||||||
|
*/
|
||||||
|
isCacheDirty: function(skipCanvas) {
|
||||||
|
if (!skipCanvas && this._updateCacheCanvas()) {
|
||||||
|
// in this case the context is already cleared.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
|
||||||
|
if (!skipCanvas) {
|
||||||
|
var dim = this._getNonTransformedDimensions();
|
||||||
|
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a background for the object big as its untrasformed dimensions
|
||||||
* @private
|
* @private
|
||||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
*/
|
*/
|
||||||
|
|
@ -1120,14 +1258,14 @@
|
||||||
if (!this.backgroundColor) {
|
if (!this.backgroundColor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var dim = this._getNonTransformedDimensions();
|
||||||
ctx.fillStyle = this.backgroundColor;
|
ctx.fillStyle = this.backgroundColor;
|
||||||
|
|
||||||
ctx.fillRect(
|
ctx.fillRect(
|
||||||
-this.width / 2,
|
-dim.x / 2,
|
||||||
-this.height / 2,
|
-dim.y / 2,
|
||||||
this.width,
|
dim.x,
|
||||||
this.height
|
dim.y
|
||||||
);
|
);
|
||||||
// if there is background color no other shadows
|
// if there is background color no other shadows
|
||||||
// should be casted
|
// should be casted
|
||||||
|
|
@ -1139,9 +1277,6 @@
|
||||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
*/
|
*/
|
||||||
_setOpacity: function(ctx) {
|
_setOpacity: function(ctx) {
|
||||||
if (this.group) {
|
|
||||||
this.group._setOpacity(ctx);
|
|
||||||
}
|
|
||||||
ctx.globalAlpha *= this.opacity;
|
ctx.globalAlpha *= this.opacity;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,9 @@
|
||||||
initialize: function(path, options) {
|
initialize: function(path, options) {
|
||||||
options = options || { };
|
options = options || { };
|
||||||
|
|
||||||
this.setOptions(options);
|
if (options) {
|
||||||
|
this.setOptions(options);
|
||||||
|
}
|
||||||
|
|
||||||
if (!path) {
|
if (!path) {
|
||||||
path = [];
|
path = [];
|
||||||
|
|
@ -101,6 +103,10 @@
|
||||||
if (options.sourcePath) {
|
if (options.sourcePath) {
|
||||||
this.setSourcePath(options.sourcePath);
|
this.setSourcePath(options.sourcePath);
|
||||||
}
|
}
|
||||||
|
if (this.objectCaching) {
|
||||||
|
this._createCacheCanvas();
|
||||||
|
this.setupState({ propertySet: 'cacheProperties' });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
* @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
|
* @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
|
||||||
* @see {@link fabric.PathGroup#initialize} for constructor definition
|
* @see {@link fabric.PathGroup#initialize} for constructor definition
|
||||||
*/
|
*/
|
||||||
fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ {
|
fabric.PathGroup = fabric.util.createClass(fabric.Object, /** @lends fabric.PathGroup.prototype */ {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of an object
|
* Type of an object
|
||||||
|
|
@ -56,10 +56,13 @@
|
||||||
}
|
}
|
||||||
this.setOptions(options);
|
this.setOptions(options);
|
||||||
this.setCoords();
|
this.setCoords();
|
||||||
|
|
||||||
if (options.sourcePath) {
|
if (options.sourcePath) {
|
||||||
this.setSourcePath(options.sourcePath);
|
this.setSourcePath(options.sourcePath);
|
||||||
}
|
}
|
||||||
|
if (this.objectCaching) {
|
||||||
|
this._createCacheCanvas();
|
||||||
|
this.setupState({ propertySet: 'cacheProperties' });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,32 +96,39 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders this group on a specified context
|
* Execute the drawing operation for an object on a specified context
|
||||||
* @param {CanvasRenderingContext2D} ctx Context to render this instance on
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
|
* @param {Boolean} [noTransform] When true, context is not transformed
|
||||||
*/
|
*/
|
||||||
render: function(ctx) {
|
drawObject: function(ctx) {
|
||||||
// do not render if object is not visible
|
|
||||||
if (!this.visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
if (this.transformMatrix) {
|
|
||||||
ctx.transform.apply(ctx, this.transformMatrix);
|
|
||||||
}
|
|
||||||
this.transform(ctx);
|
|
||||||
|
|
||||||
this._setShadow(ctx);
|
|
||||||
this.clipTo && fabric.util.clipContext(this, ctx);
|
|
||||||
ctx.translate(-this.width / 2, -this.height / 2);
|
ctx.translate(-this.width / 2, -this.height / 2);
|
||||||
for (var i = 0, l = this.paths.length; i < l; ++i) {
|
for (var i = 0, l = this.paths.length; i < l; ++i) {
|
||||||
this.paths[i].render(ctx, true);
|
this.paths[i].render(ctx, true);
|
||||||
}
|
}
|
||||||
this.clipTo && ctx.restore();
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cache is dirty
|
||||||
|
*/
|
||||||
|
isCacheDirty: function() {
|
||||||
|
if (this.callSuper('isCacheDirty')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!this.statefullCache) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var i = 0, len = this.paths.length; i < len; i++) {
|
||||||
|
if (this.paths[i].isCacheDirty(true)) {
|
||||||
|
var dim = this._getNonTransformedDimensions();
|
||||||
|
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets certain property to a certain value
|
* Sets certain property to a certain value
|
||||||
* @param {String} prop
|
* @param {String} prop
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
* @return {fabric.Polygon} thisArg
|
* @return {fabric.Polygon} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(points, options) {
|
initialize: function(points, options) {
|
||||||
options = options || { };
|
options = options || {};
|
||||||
this.points = points || [];
|
this.points = points || [];
|
||||||
this.callSuper('initialize', options);
|
this.callSuper('initialize', options);
|
||||||
this._calcDimensions();
|
this._calcDimensions();
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,6 @@
|
||||||
* @return {Object} thisArg
|
* @return {Object} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
options = options || { };
|
|
||||||
|
|
||||||
this.callSuper('initialize', options);
|
this.callSuper('initialize', options);
|
||||||
this._initRxRy();
|
this._initRxRy();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,24 @@
|
||||||
'textAlign',
|
'textAlign',
|
||||||
'fontStyle',
|
'fontStyle',
|
||||||
'lineHeight',
|
'lineHeight',
|
||||||
'textBackgroundColor'
|
'textBackgroundColor',
|
||||||
|
'charSpacing'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
|
||||||
|
cacheProperties.push(
|
||||||
|
'fontFamily',
|
||||||
|
'fontWeight',
|
||||||
|
'fontSize',
|
||||||
|
'text',
|
||||||
|
'textDecoration',
|
||||||
|
'textAlign',
|
||||||
|
'fontStyle',
|
||||||
|
'lineHeight',
|
||||||
|
'textBackgroundColor',
|
||||||
|
'charSpacing',
|
||||||
|
'styles'
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* Text class
|
* Text class
|
||||||
* @class fabric.Text
|
* @class fabric.Text
|
||||||
|
|
@ -41,17 +56,16 @@
|
||||||
* @type Object
|
* @type Object
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_dimensionAffectingProps: {
|
_dimensionAffectingProps: [
|
||||||
fontSize: true,
|
'fontSize',
|
||||||
fontWeight: true,
|
'fontWeight',
|
||||||
fontFamily: true,
|
'fontFamily',
|
||||||
fontStyle: true,
|
'fontStyle',
|
||||||
lineHeight: true,
|
'lineHeight',
|
||||||
text: true,
|
'text',
|
||||||
charSpacing: true,
|
'charSpacing',
|
||||||
textAlign: true,
|
'textAlign'
|
||||||
strokeWidth: false,
|
],
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
|
@ -290,6 +304,12 @@
|
||||||
*/
|
*/
|
||||||
stateProperties: stateProperties,
|
stateProperties: stateProperties,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of properties to consider when checking if cache needs refresh
|
||||||
|
* @type Array
|
||||||
|
*/
|
||||||
|
cacheProperties: cacheProperties,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When defined, an object is rendered via stroke and this property specifies its color.
|
* When defined, an object is rendered via stroke and this property specifies its color.
|
||||||
* <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
|
* <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
|
||||||
|
|
@ -336,9 +356,10 @@
|
||||||
options = options || { };
|
options = options || { };
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.__skipDimension = true;
|
this.__skipDimension = true;
|
||||||
this.setOptions(options);
|
this.callSuper('initialize', options);
|
||||||
this.__skipDimension = false;
|
this.__skipDimension = false;
|
||||||
this._initDimensions();
|
this._initDimensions();
|
||||||
|
this.setupState({ propertySet: '_dimensionAffectingProps' });
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -377,16 +398,13 @@
|
||||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
*/
|
*/
|
||||||
_render: function(ctx) {
|
_render: function(ctx) {
|
||||||
this.clipTo && fabric.util.clipContext(this, ctx);
|
this._setTextStyles(ctx);
|
||||||
this._setOpacity(ctx);
|
if (this.group && this.group.type === 'path-group') {
|
||||||
this._setShadow(ctx);
|
ctx.translate(this.left, this.top);
|
||||||
this._setupCompositeOperation(ctx);
|
}
|
||||||
this._renderTextBackground(ctx);
|
this._renderTextLinesBackground(ctx);
|
||||||
this._setStrokeStyles(ctx);
|
|
||||||
this._setFillStyles(ctx);
|
|
||||||
this._renderText(ctx);
|
this._renderText(ctx);
|
||||||
this._renderTextDecoration(ctx);
|
this._renderTextDecoration(ctx);
|
||||||
this.clipTo && ctx.restore();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -629,15 +647,6 @@
|
||||||
return this.fontSize * this._fontSizeMult;
|
return this.fontSize * this._fontSizeMult;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
||||||
*/
|
|
||||||
_renderTextBackground: function(ctx) {
|
|
||||||
this._renderBackground(ctx);
|
|
||||||
this._renderTextLinesBackground(ctx);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||||
|
|
@ -647,7 +656,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var lineTopOffset = 0, heightOfLine,
|
var lineTopOffset = 0, heightOfLine,
|
||||||
lineWidth, lineLeftOffset;
|
lineWidth, lineLeftOffset, originalFill = ctx.fillStye;
|
||||||
|
|
||||||
ctx.fillStyle = this.textBackgroundColor;
|
ctx.fillStyle = this.textBackgroundColor;
|
||||||
for (var i = 0, len = this._textLines.length; i < len; i++) {
|
for (var i = 0, len = this._textLines.length; i < len; i++) {
|
||||||
|
|
@ -664,6 +673,7 @@
|
||||||
}
|
}
|
||||||
lineTopOffset += heightOfLine;
|
lineTopOffset += heightOfLine;
|
||||||
}
|
}
|
||||||
|
ctx.fillStyle = originalFill;
|
||||||
// if there is text background color no
|
// if there is text background color no
|
||||||
// other shadows should be casted
|
// other shadows should be casted
|
||||||
this._removeShadow(ctx);
|
this._removeShadow(ctx);
|
||||||
|
|
@ -695,17 +705,15 @@
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_shouldClearCache: function() {
|
_shouldClearDimensionCache: function() {
|
||||||
var shouldClear = false;
|
var shouldClear = false;
|
||||||
if (this._forceClearCache) {
|
if (this._forceClearCache) {
|
||||||
this._forceClearCache = false;
|
this._forceClearCache = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (var prop in this._dimensionAffectingProps) {
|
shouldClear = this.hasStateChanged('_dimensionAffectingProps');
|
||||||
if (this['__' + prop] !== this[prop]) {
|
if (shouldClear) {
|
||||||
this['__' + prop] = this[prop];
|
this.saveState({ propertySet: '_dimensionAffectingProps' });
|
||||||
shouldClear = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return shouldClear;
|
return shouldClear;
|
||||||
},
|
},
|
||||||
|
|
@ -836,25 +844,10 @@
|
||||||
if (!this.visible) {
|
if (!this.visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this._shouldClearDimensionCache()) {
|
||||||
ctx.save();
|
|
||||||
this._setTextStyles(ctx);
|
|
||||||
|
|
||||||
if (this._shouldClearCache()) {
|
|
||||||
this._initDimensions(ctx);
|
this._initDimensions(ctx);
|
||||||
}
|
}
|
||||||
this.drawSelectionBackground(ctx);
|
this.callSuper('render', ctx, noTransform);
|
||||||
if (!noTransform) {
|
|
||||||
this.transform(ctx);
|
|
||||||
}
|
|
||||||
if (this.transformMatrix) {
|
|
||||||
ctx.transform.apply(ctx, this.transformMatrix);
|
|
||||||
}
|
|
||||||
if (this.group && this.group.type === 'path-group') {
|
|
||||||
ctx.translate(this.left, this.top);
|
|
||||||
}
|
|
||||||
this._render(ctx);
|
|
||||||
ctx.restore();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1086,7 +1079,7 @@
|
||||||
_set: function(key, value) {
|
_set: function(key, value) {
|
||||||
this.callSuper('_set', key, value);
|
this.callSuper('_set', key, value);
|
||||||
|
|
||||||
if (key in this._dimensionAffectingProps) {
|
if (this._dimensionAffectingProps.indexOf(key) > -1) {
|
||||||
this._initDimensions();
|
this._initDimensions();
|
||||||
this.setCoords();
|
this.setCoords();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,12 @@
|
||||||
* @return {fabric.Textbox} thisArg
|
* @return {fabric.Textbox} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(text, options) {
|
initialize: function(text, options) {
|
||||||
this.ctx = fabric.util.createCanvasElement().getContext('2d');
|
|
||||||
this.callSuper('initialize', text, options);
|
this.callSuper('initialize', text, options);
|
||||||
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
|
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
|
||||||
|
this.ctx = this.objectCaching ? this._cacheContext : fabric.util.createCanvasElement().getContext('2d');
|
||||||
// add width to this list of props that effect line wrapping.
|
// add width to this list of props that effect line wrapping.
|
||||||
this._dimensionAffectingProps.width = true;
|
this._dimensionAffectingProps.push('width');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,9 @@
|
||||||
* @return {Object} thisArg
|
* @return {Object} thisArg
|
||||||
*/
|
*/
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
options = options || { };
|
|
||||||
|
|
||||||
this.callSuper('initialize', options);
|
this.callSuper('initialize', options);
|
||||||
|
this.set('width', options && options.width || 100)
|
||||||
this.set('width', options.width || 100)
|
.set('height', options && options.height || 100);
|
||||||
.set('height', options.height || 100);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@
|
||||||
* @type Boolean
|
* @type Boolean
|
||||||
* @default
|
* @default
|
||||||
*/
|
*/
|
||||||
stateful: true,
|
stateful: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
|
* Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
|
||||||
|
|
@ -645,7 +645,7 @@
|
||||||
* @return {Number}
|
* @return {Number}
|
||||||
*/
|
*/
|
||||||
getZoom: function () {
|
getZoom: function () {
|
||||||
return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
|
return this.viewportTransform[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@
|
||||||
}
|
}
|
||||||
else if (source instanceof Object) {
|
else if (source instanceof Object) {
|
||||||
for (var property in source) {
|
for (var property in source) {
|
||||||
destination[property] = clone(source[property], deep)
|
if (source.hasOwnProperty(property)) {
|
||||||
|
destination[property] = clone(source[property], deep)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,8 @@
|
||||||
var Canvas = fabric.Canvas;
|
var Canvas = fabric.Canvas;
|
||||||
fabric.Canvas = null;
|
fabric.Canvas = null;
|
||||||
var el = fabric.document.createElement('canvas');
|
var el = fabric.document.createElement('canvas');
|
||||||
el.width = 600; el.height = 600;
|
el.width = 600;
|
||||||
|
el.height = 600;
|
||||||
|
|
||||||
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el),
|
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el),
|
||||||
canvas2 = this.canvas2 = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el);
|
canvas2 = this.canvas2 = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el);
|
||||||
|
|
@ -180,7 +181,7 @@
|
||||||
ok('overlayVpt' in canvas);
|
ok('overlayVpt' in canvas);
|
||||||
|
|
||||||
equal(canvas.includeDefaultValues, true);
|
equal(canvas.includeDefaultValues, true);
|
||||||
equal(canvas.stateful, true);
|
equal(canvas.stateful, false);
|
||||||
equal(canvas.renderOnAddRemove, true);
|
equal(canvas.renderOnAddRemove, true);
|
||||||
equal(canvas.controlsAboveOverlay, false);
|
equal(canvas.controlsAboveOverlay, false);
|
||||||
equal(canvas.allowTouchScrolling, false);
|
equal(canvas.allowTouchScrolling, false);
|
||||||
|
|
|
||||||
|
|
@ -516,7 +516,7 @@
|
||||||
|
|
||||||
test('test group transformMatrix', function() {
|
test('test group transformMatrix', function() {
|
||||||
var rect1 = new fabric.Rect({ top: 1, left: 1, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
|
var rect1 = new fabric.Rect({ top: 1, left: 1, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
|
||||||
rect2 = new fabric.Rect({ top: 4, left: 4, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
|
rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
|
||||||
group = new fabric.Group([rect1, rect2], {opacity: 1, fill: 'blue', strokeWidth: 0}),
|
group = new fabric.Group([rect1, rect2], {opacity: 1, fill: 'blue', strokeWidth: 0}),
|
||||||
isTransparent = fabric.util.isTransparent,
|
isTransparent = fabric.util.isTransparent,
|
||||||
ctx = canvas.contextContainer;
|
ctx = canvas.contextContainer;
|
||||||
|
|
@ -526,14 +526,25 @@
|
||||||
equal(isTransparent(ctx, 1, 1, 0), false, '1,1 is opaque');
|
equal(isTransparent(ctx, 1, 1, 0), false, '1,1 is opaque');
|
||||||
equal(isTransparent(ctx, 2, 2, 0), false, '2,2 is opaque');
|
equal(isTransparent(ctx, 2, 2, 0), false, '2,2 is opaque');
|
||||||
equal(isTransparent(ctx, 3, 3, 0), true, '3,3 is transparent');
|
equal(isTransparent(ctx, 3, 3, 0), true, '3,3 is transparent');
|
||||||
equal(isTransparent(ctx, 4, 4, 0), false, '4,4 is opaque');
|
equal(isTransparent(ctx, 4, 4, 0), true, '4,4 is transparent');
|
||||||
group.transformMatrix = [2, 0, 0, 2, 1, 1];
|
equal(isTransparent(ctx, 5, 5, 0), false, '5,5 is opaque');
|
||||||
|
equal(isTransparent(ctx, 6, 6, 0), false, '6,6 is opaque');
|
||||||
|
equal(isTransparent(ctx, 7, 7, 0), true, '7,7 is transparent');
|
||||||
|
group.transformMatrix = [2, 0, 0, 2, 2, 2];
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
equal(isTransparent(ctx, 0, 0, 0), true, '0,0 is transparent');
|
equal(isTransparent(ctx, 0, 0, 0), false, '0,0 is opaque');
|
||||||
equal(isTransparent(ctx, 1, 1, 0), true, '1,1 is transparent');
|
equal(isTransparent(ctx, 1, 1, 0), false, '1,1 is opaque');
|
||||||
equal(isTransparent(ctx, 2, 2, 0), true, '2,2 is transparent');
|
equal(isTransparent(ctx, 2, 2, 0), false, '2,2 is opaque');
|
||||||
equal(isTransparent(ctx, 3, 3, 0), false, '3,3 is opaque');
|
equal(isTransparent(ctx, 3, 3, 0), false, '3,3 is opaque');
|
||||||
equal(isTransparent(ctx, 4, 4, 0), false, '4,4 is opaque');
|
equal(isTransparent(ctx, 4, 4, 0), true, '4,4 is transparent');
|
||||||
|
equal(isTransparent(ctx, 5, 5, 0), true, '5,5 is transparent');
|
||||||
|
equal(isTransparent(ctx, 6, 6, 0), true, '6,6 is transparent');
|
||||||
|
equal(isTransparent(ctx, 7, 7, 0), true, '7,7 is transparent');
|
||||||
|
equal(isTransparent(ctx, 8, 8, 0), false, '8,8 is opaque');
|
||||||
|
equal(isTransparent(ctx, 9, 9, 0), false, '9,9 is opaque');
|
||||||
|
equal(isTransparent(ctx, 10, 10, 0), false, '10,10 is opaque');
|
||||||
|
equal(isTransparent(ctx, 11, 11, 0), false, '11,11 is opaque');
|
||||||
|
equal(isTransparent(ctx, 12, 12, 0), true, '12,12 is transparent');
|
||||||
});
|
});
|
||||||
// asyncTest('cloning group with image', function() {
|
// asyncTest('cloning group with image', function() {
|
||||||
// var rect = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10 }),
|
// var rect = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10 }),
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
cObj.set('left', 123).set('top', 456);
|
cObj.set('left', 123).set('top', 456);
|
||||||
cObj.saveState();
|
cObj.saveState();
|
||||||
cObj.set('left', 223).set('top', 556);
|
cObj.set('left', 223).set('top', 556);
|
||||||
equal(cObj.originalState.left, 123);
|
equal(cObj._stateProperties.left, 123);
|
||||||
equal(cObj.originalState.top, 456);
|
equal(cObj._stateProperties.top, 456);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('saveState with extra props', function() {
|
test('saveState with extra props', function() {
|
||||||
|
|
@ -32,19 +32,19 @@
|
||||||
var extraProps = ['prop1', 'prop2'];
|
var extraProps = ['prop1', 'prop2'];
|
||||||
var options = { stateProperties: extraProps };
|
var options = { stateProperties: extraProps };
|
||||||
cObj.setupState(options);
|
cObj.setupState(options);
|
||||||
equal(cObj.originalState.prop1, 'a', 'it saves the extra props');
|
equal(cObj._stateProperties.prop1, 'a', 'it saves the extra props');
|
||||||
equal(cObj.originalState.prop2, 'b', 'it saves the extra props');
|
equal(cObj._stateProperties.prop2, 'b', 'it saves the extra props');
|
||||||
cObj.prop1 = 'c';
|
cObj.prop1 = 'c';
|
||||||
ok(cObj.hasStateChanged(), 'it detects changes in extra props');
|
ok(cObj.hasStateChanged(), 'it detects changes in extra props');
|
||||||
equal(cObj.originalState.left, 123, 'normal props are still there');
|
equal(cObj._stateProperties.left, 123, 'normal props are still there');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('saveState with array', function() {
|
test('saveState with array', function() {
|
||||||
var cObj = new fabric.Text('Hello');
|
var cObj = new fabric.Text('Hello');
|
||||||
cObj.set('textDecoration', ['underline']);
|
cObj.set('textDecoration', ['underline']);
|
||||||
cObj.setupState();
|
cObj.setupState();
|
||||||
deepEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in state is deepEqual');
|
deepEqual(cObj.textDecoration, cObj._stateProperties.textDecoration, 'textDecoration in state is deepEqual');
|
||||||
notEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in not same Object');
|
notEqual(cObj.textDecoration, cObj._stateProperties.textDecoration, 'textDecoration in not same Object');
|
||||||
cObj.textDecoration[0] = 'overline';
|
cObj.textDecoration[0] = 'overline';
|
||||||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props');
|
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue