mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-03-20 16:00:25 +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.copiedTextStyle = this.getSelectionStyles(
|
||||
this.selectionStart,
|
||||
this.selectionEnd);
|
||||
fabric.copiedTextStyle = fabric.util.object.clone(
|
||||
this.getSelectionStyles(
|
||||
this.selectionStart,
|
||||
this.selectionEnd
|
||||
)
|
||||
);
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
this._copyDone = true;
|
||||
|
|
|
|||
|
|
@ -113,24 +113,8 @@
|
|||
*/
|
||||
_getNonTransformedDimensions: function() {
|
||||
var strokeWidth = this.strokeWidth,
|
||||
w = this.width,
|
||||
h = this.height,
|
||||
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;
|
||||
}
|
||||
|
||||
w = this.width + strokeWidth,
|
||||
h = this.height + strokeWidth;
|
||||
return { x: w, y: h };
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
(function() {
|
||||
|
||||
var extend = fabric.util.object.extend;
|
||||
var extend = fabric.util.object.extend,
|
||||
originalSet = 'stateProperties';
|
||||
|
||||
/*
|
||||
Depends on `stateProperties`
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
extend(origin[destination], tmpObj, deep);
|
||||
}
|
||||
|
||||
function _isEqual(origValue, currentValue) {
|
||||
function _isEqual(origValue, currentValue, firstPass) {
|
||||
if (!fabric.isLikelyNode && origValue instanceof Element) {
|
||||
// avoid checking deep html elements
|
||||
return origValue === currentValue;
|
||||
|
|
@ -29,6 +30,9 @@
|
|||
});
|
||||
}
|
||||
else if (origValue instanceof Object) {
|
||||
if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
|
||||
return false;
|
||||
}
|
||||
for (var key in origValue) {
|
||||
if (!_isEqual(origValue[key], currentValue[key])) {
|
||||
return false;
|
||||
|
|
@ -46,10 +50,13 @@
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
hasStateChanged: function() {
|
||||
return !_isEqual(this.originalState, this);
|
||||
hasStateChanged: function(propertySet) {
|
||||
propertySet = propertySet || originalSet;
|
||||
propertySet = '_' + propertySet;
|
||||
return !_isEqual(this[propertySet], this, true);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -58,9 +65,11 @@
|
|||
* @return {fabric.Object} thisArg
|
||||
*/
|
||||
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) {
|
||||
saveProps(this, 'originalState', options.stateProperties);
|
||||
saveProps(this, destination, options.stateProperties);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
|
@ -71,7 +80,10 @@
|
|||
* @return {fabric.Object} thisArg
|
||||
*/
|
||||
setupState: function(options) {
|
||||
this.originalState = { };
|
||||
options = options || { };
|
||||
var propertySet = options.propertySet || originalSet;
|
||||
options.propertySet = propertySet;
|
||||
this['_' + propertySet] = { };
|
||||
this.saveState(options);
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,9 +69,14 @@
|
|||
value = '';
|
||||
}
|
||||
else if (attr === 'strokeDashArray') {
|
||||
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
|
||||
return parseFloat(n);
|
||||
});
|
||||
if (value === 'none') {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
|
||||
return parseFloat(n);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (attr === 'transformMatrix') {
|
||||
if (parentAttributes && parentAttributes.transformMatrix) {
|
||||
|
|
|
|||
|
|
@ -53,13 +53,8 @@
|
|||
* @return {fabric.Circle} thisArg
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || { };
|
||||
|
||||
this.callSuper('initialize', options);
|
||||
this.set('radius', options.radius || 0);
|
||||
|
||||
this.startAngle = options.startAngle || this.startAngle;
|
||||
this.endAngle = options.endAngle || this.endAngle;
|
||||
this.set('radius', options && options.radius || 0);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -47,12 +47,9 @@
|
|||
* @return {fabric.Ellipse} thisArg
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || { };
|
||||
|
||||
this.callSuper('initialize', options);
|
||||
|
||||
this.set('rx', options.rx || 0);
|
||||
this.set('ry', options.ry || 0);
|
||||
this.set('rx', options && options.rx || 0);
|
||||
this.set('ry', options && options.ry || 0);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@
|
|||
this.forEachObject(this._setObjectActive, this);
|
||||
this._calcBounds();
|
||||
this._updateObjectsCoords();
|
||||
this.dirty = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
@ -190,7 +191,7 @@
|
|||
this.remove(object);
|
||||
this._calcBounds();
|
||||
this._updateObjectsCoords();
|
||||
|
||||
this.dirty = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
@ -198,6 +199,7 @@
|
|||
* @private
|
||||
*/
|
||||
_onObjectAdded: function(object) {
|
||||
this.dirty = true;
|
||||
object.group = this;
|
||||
object._set('canvas', this.canvas);
|
||||
},
|
||||
|
|
@ -206,6 +208,7 @@
|
|||
* @private
|
||||
*/
|
||||
_onObjectRemoved: function(object) {
|
||||
this.dirty = true;
|
||||
delete object.group;
|
||||
object.set('active', false);
|
||||
},
|
||||
|
|
@ -264,27 +267,40 @@
|
|||
* @param {CanvasRenderingContext2D} ctx context to render instance on
|
||||
*/
|
||||
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;
|
||||
// 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++) {
|
||||
this._renderObject(this._objects[i], ctx);
|
||||
}
|
||||
},
|
||||
|
||||
this.clipTo && ctx.restore();
|
||||
ctx.restore();
|
||||
this._transformDone = false;
|
||||
/**
|
||||
* Check if cache is dirty
|
||||
*/
|
||||
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,
|
||||
|
||||
/**
|
||||
* When `true`, object is cached on an additional canvas.
|
||||
* default to false for images
|
||||
* since 1.7.0
|
||||
* @type Boolean
|
||||
* @default
|
||||
*/
|
||||
objectCaching: false,
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {HTMLImageElement | String} element Image element
|
||||
|
|
|
|||
|
|
@ -811,7 +811,7 @@
|
|||
leftOffset = this._getLeftOffset(),
|
||||
topOffset = this._getTopOffset(),
|
||||
line, _char, style;
|
||||
|
||||
ctx.save();
|
||||
for (var i = 0, len = this._textLines.length; i < len; i++) {
|
||||
heightOfLine = this._getHeightOfLine(ctx, i);
|
||||
line = this._textLines[i];
|
||||
|
|
@ -842,6 +842,7 @@
|
|||
}
|
||||
lineTopOffset += heightOfLine;
|
||||
}
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -62,8 +62,6 @@
|
|||
* @return {fabric.Line} thisArg
|
||||
*/
|
||||
initialize: function(points, options) {
|
||||
options = options || { };
|
||||
|
||||
if (!points) {
|
||||
points = [0, 0, 0, 0];
|
||||
}
|
||||
|
|
@ -206,6 +204,23 @@
|
|||
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
|
||||
* @private
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
toFixed = fabric.util.toFixed,
|
||||
capitalize = fabric.util.string.capitalize,
|
||||
degreesToRadians = fabric.util.degreesToRadians,
|
||||
supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
|
||||
supportsLineDash = fabric.StaticCanvas.supports('setLineDash'),
|
||||
objectCaching = !fabric.isLikelyNode;
|
||||
|
||||
if (fabric.Object) {
|
||||
return;
|
||||
|
|
@ -743,7 +744,6 @@
|
|||
* @type Boolean
|
||||
* @default
|
||||
*/
|
||||
|
||||
lockScalingFlip: false,
|
||||
|
||||
/**
|
||||
|
|
@ -752,8 +752,39 @@
|
|||
* @type Boolean
|
||||
* @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
|
||||
|
|
@ -761,21 +792,80 @@
|
|||
* as well as for history (undo/redo) purposes
|
||||
* @type Array
|
||||
*/
|
||||
stateProperties: (
|
||||
stateProperties: (
|
||||
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
|
||||
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
|
||||
'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
|
||||
'skewX skewY'
|
||||
).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
|
||||
* @param {Object} [options] Options object
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || { };
|
||||
if (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) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
|
||||
//setup fill rule for current object
|
||||
this._setupCompositeOperation(ctx);
|
||||
this.drawSelectionBackground(ctx);
|
||||
|
|
@ -1098,21 +1186,71 @@
|
|||
}
|
||||
this._setOpacity(ctx);
|
||||
this._setShadow(ctx);
|
||||
this._renderBackground(ctx);
|
||||
this._setStrokeStyles(ctx);
|
||||
this._setFillStyles(ctx);
|
||||
if (this.transformMatrix) {
|
||||
ctx.transform.apply(ctx, this.transformMatrix);
|
||||
}
|
||||
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();
|
||||
|
||||
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
|
||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
*/
|
||||
|
|
@ -1120,14 +1258,14 @@
|
|||
if (!this.backgroundColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dim = this._getNonTransformedDimensions();
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
|
||||
ctx.fillRect(
|
||||
-this.width / 2,
|
||||
-this.height / 2,
|
||||
this.width,
|
||||
this.height
|
||||
-dim.x / 2,
|
||||
-dim.y / 2,
|
||||
dim.x,
|
||||
dim.y
|
||||
);
|
||||
// if there is background color no other shadows
|
||||
// should be casted
|
||||
|
|
@ -1139,9 +1277,6 @@
|
|||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
*/
|
||||
_setOpacity: function(ctx) {
|
||||
if (this.group) {
|
||||
this.group._setOpacity(ctx);
|
||||
}
|
||||
ctx.globalAlpha *= this.opacity;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@
|
|||
initialize: function(path, options) {
|
||||
options = options || { };
|
||||
|
||||
this.setOptions(options);
|
||||
if (options) {
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
path = [];
|
||||
|
|
@ -101,6 +103,10 @@
|
|||
if (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}
|
||||
* @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
|
||||
|
|
@ -56,10 +56,13 @@
|
|||
}
|
||||
this.setOptions(options);
|
||||
this.setCoords();
|
||||
|
||||
if (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
|
||||
* @param {CanvasRenderingContext2D} ctx Context to render this instance on
|
||||
* 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
|
||||
*/
|
||||
render: function(ctx) {
|
||||
// do not render if object is not visible
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawObject: function(ctx) {
|
||||
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);
|
||||
for (var i = 0, l = this.paths.length; i < l; ++i) {
|
||||
this.paths[i].render(ctx, true);
|
||||
}
|
||||
this.clipTo && 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
|
||||
* @param {String} prop
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
* @return {fabric.Polygon} thisArg
|
||||
*/
|
||||
initialize: function(points, options) {
|
||||
options = options || { };
|
||||
options = options || {};
|
||||
this.points = points || [];
|
||||
this.callSuper('initialize', options);
|
||||
this._calcDimensions();
|
||||
|
|
|
|||
|
|
@ -62,8 +62,6 @@
|
|||
* @return {Object} thisArg
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || { };
|
||||
|
||||
this.callSuper('initialize', options);
|
||||
this._initRxRy();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,24 @@
|
|||
'textAlign',
|
||||
'fontStyle',
|
||||
'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
|
||||
* @class fabric.Text
|
||||
|
|
@ -41,17 +56,16 @@
|
|||
* @type Object
|
||||
* @private
|
||||
*/
|
||||
_dimensionAffectingProps: {
|
||||
fontSize: true,
|
||||
fontWeight: true,
|
||||
fontFamily: true,
|
||||
fontStyle: true,
|
||||
lineHeight: true,
|
||||
text: true,
|
||||
charSpacing: true,
|
||||
textAlign: true,
|
||||
strokeWidth: false,
|
||||
},
|
||||
_dimensionAffectingProps: [
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontFamily',
|
||||
'fontStyle',
|
||||
'lineHeight',
|
||||
'text',
|
||||
'charSpacing',
|
||||
'textAlign'
|
||||
],
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
|
@ -290,6 +304,12 @@
|
|||
*/
|
||||
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.
|
||||
* <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
|
||||
|
|
@ -336,9 +356,10 @@
|
|||
options = options || { };
|
||||
this.text = text;
|
||||
this.__skipDimension = true;
|
||||
this.setOptions(options);
|
||||
this.callSuper('initialize', options);
|
||||
this.__skipDimension = false;
|
||||
this._initDimensions();
|
||||
this.setupState({ propertySet: '_dimensionAffectingProps' });
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -377,16 +398,13 @@
|
|||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
*/
|
||||
_render: function(ctx) {
|
||||
this.clipTo && fabric.util.clipContext(this, ctx);
|
||||
this._setOpacity(ctx);
|
||||
this._setShadow(ctx);
|
||||
this._setupCompositeOperation(ctx);
|
||||
this._renderTextBackground(ctx);
|
||||
this._setStrokeStyles(ctx);
|
||||
this._setFillStyles(ctx);
|
||||
this._setTextStyles(ctx);
|
||||
if (this.group && this.group.type === 'path-group') {
|
||||
ctx.translate(this.left, this.top);
|
||||
}
|
||||
this._renderTextLinesBackground(ctx);
|
||||
this._renderText(ctx);
|
||||
this._renderTextDecoration(ctx);
|
||||
this.clipTo && ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -629,15 +647,6 @@
|
|||
return this.fontSize * this._fontSizeMult;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
*/
|
||||
_renderTextBackground: function(ctx) {
|
||||
this._renderBackground(ctx);
|
||||
this._renderTextLinesBackground(ctx);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
|
|
@ -647,7 +656,7 @@
|
|||
return;
|
||||
}
|
||||
var lineTopOffset = 0, heightOfLine,
|
||||
lineWidth, lineLeftOffset;
|
||||
lineWidth, lineLeftOffset, originalFill = ctx.fillStye;
|
||||
|
||||
ctx.fillStyle = this.textBackgroundColor;
|
||||
for (var i = 0, len = this._textLines.length; i < len; i++) {
|
||||
|
|
@ -664,6 +673,7 @@
|
|||
}
|
||||
lineTopOffset += heightOfLine;
|
||||
}
|
||||
ctx.fillStyle = originalFill;
|
||||
// if there is text background color no
|
||||
// other shadows should be casted
|
||||
this._removeShadow(ctx);
|
||||
|
|
@ -695,17 +705,15 @@
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
_shouldClearCache: function() {
|
||||
_shouldClearDimensionCache: function() {
|
||||
var shouldClear = false;
|
||||
if (this._forceClearCache) {
|
||||
this._forceClearCache = false;
|
||||
return true;
|
||||
}
|
||||
for (var prop in this._dimensionAffectingProps) {
|
||||
if (this['__' + prop] !== this[prop]) {
|
||||
this['__' + prop] = this[prop];
|
||||
shouldClear = true;
|
||||
}
|
||||
shouldClear = this.hasStateChanged('_dimensionAffectingProps');
|
||||
if (shouldClear) {
|
||||
this.saveState({ propertySet: '_dimensionAffectingProps' });
|
||||
}
|
||||
return shouldClear;
|
||||
},
|
||||
|
|
@ -836,25 +844,10 @@
|
|||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
this._setTextStyles(ctx);
|
||||
|
||||
if (this._shouldClearCache()) {
|
||||
if (this._shouldClearDimensionCache()) {
|
||||
this._initDimensions(ctx);
|
||||
}
|
||||
this.drawSelectionBackground(ctx);
|
||||
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();
|
||||
this.callSuper('render', ctx, noTransform);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -1086,7 +1079,7 @@
|
|||
_set: function(key, value) {
|
||||
this.callSuper('_set', key, value);
|
||||
|
||||
if (key in this._dimensionAffectingProps) {
|
||||
if (this._dimensionAffectingProps.indexOf(key) > -1) {
|
||||
this._initDimensions();
|
||||
this.setCoords();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,12 +66,12 @@
|
|||
* @return {fabric.Textbox} thisArg
|
||||
*/
|
||||
initialize: function(text, options) {
|
||||
this.ctx = fabric.util.createCanvasElement().getContext('2d');
|
||||
|
||||
this.callSuper('initialize', text, options);
|
||||
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.
|
||||
this._dimensionAffectingProps.width = true;
|
||||
this._dimensionAffectingProps.push('width');
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -31,12 +31,9 @@
|
|||
* @return {Object} thisArg
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || { };
|
||||
|
||||
this.callSuper('initialize', options);
|
||||
|
||||
this.set('width', options.width || 100)
|
||||
.set('height', options.height || 100);
|
||||
this.set('width', options && options.width || 100)
|
||||
.set('height', options && options.height || 100);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
* @type Boolean
|
||||
* @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.
|
||||
|
|
@ -645,7 +645,7 @@
|
|||
* @return {Number}
|
||||
*/
|
||||
getZoom: function () {
|
||||
return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
|
||||
return this.viewportTransform[0];
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@
|
|||
}
|
||||
else if (source instanceof Object) {
|
||||
for (var property in source) {
|
||||
destination[property] = clone(source[property], deep)
|
||||
if (source.hasOwnProperty(property)) {
|
||||
destination[property] = clone(source[property], deep)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@
|
|||
var Canvas = fabric.Canvas;
|
||||
fabric.Canvas = null;
|
||||
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),
|
||||
canvas2 = this.canvas2 = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el);
|
||||
|
|
@ -180,7 +181,7 @@
|
|||
ok('overlayVpt' in canvas);
|
||||
|
||||
equal(canvas.includeDefaultValues, true);
|
||||
equal(canvas.stateful, true);
|
||||
equal(canvas.stateful, false);
|
||||
equal(canvas.renderOnAddRemove, true);
|
||||
equal(canvas.controlsAboveOverlay, false);
|
||||
equal(canvas.allowTouchScrolling, false);
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@
|
|||
|
||||
test('test group transformMatrix', function() {
|
||||
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}),
|
||||
isTransparent = fabric.util.isTransparent,
|
||||
ctx = canvas.contextContainer;
|
||||
|
|
@ -526,14 +526,25 @@
|
|||
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, 3, 3, 0), true, '3,3 is transparent');
|
||||
equal(isTransparent(ctx, 4, 4, 0), false, '4,4 is opaque');
|
||||
group.transformMatrix = [2, 0, 0, 2, 1, 1];
|
||||
equal(isTransparent(ctx, 4, 4, 0), true, '4,4 is transparent');
|
||||
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();
|
||||
equal(isTransparent(ctx, 0, 0, 0), true, '0,0 is transparent');
|
||||
equal(isTransparent(ctx, 1, 1, 0), true, '1,1 is transparent');
|
||||
equal(isTransparent(ctx, 2, 2, 0), true, '2,2 is transparent');
|
||||
equal(isTransparent(ctx, 0, 0, 0), false, '0,0 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, 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() {
|
||||
// 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.saveState();
|
||||
cObj.set('left', 223).set('top', 556);
|
||||
equal(cObj.originalState.left, 123);
|
||||
equal(cObj.originalState.top, 456);
|
||||
equal(cObj._stateProperties.left, 123);
|
||||
equal(cObj._stateProperties.top, 456);
|
||||
});
|
||||
|
||||
test('saveState with extra props', function() {
|
||||
|
|
@ -32,19 +32,19 @@
|
|||
var extraProps = ['prop1', 'prop2'];
|
||||
var options = { stateProperties: extraProps };
|
||||
cObj.setupState(options);
|
||||
equal(cObj.originalState.prop1, 'a', 'it saves the extra props');
|
||||
equal(cObj.originalState.prop2, 'b', 'it saves the extra props');
|
||||
equal(cObj._stateProperties.prop1, 'a', 'it saves the extra props');
|
||||
equal(cObj._stateProperties.prop2, 'b', 'it saves the extra props');
|
||||
cObj.prop1 = 'c';
|
||||
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() {
|
||||
var cObj = new fabric.Text('Hello');
|
||||
cObj.set('textDecoration', ['underline']);
|
||||
cObj.setupState();
|
||||
deepEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in state is deepEqual');
|
||||
notEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in not same Object');
|
||||
deepEqual(cObj.textDecoration, cObj._stateProperties.textDecoration, 'textDecoration in state is deepEqual');
|
||||
notEqual(cObj.textDecoration, cObj._stateProperties.textDecoration, 'textDecoration in not same Object');
|
||||
cObj.textDecoration[0] = 'overline';
|
||||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue