diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66c63c84e4b..6e2dd637636 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,17 +3,17 @@
## [5.0.0]
+- fix(fabric.Canvas): unflag contextLost after a full re-render [`#7646`](https://github.com/fabricjs/fabric.js/pull/7646)
+- **BREAKING**: remove 4.x deprecated code [`#7630`](https://github.com/fabricjs/fabric.js/pull/7630)
+- feat(fabric.StaticCanvas, fabric.Canvas): limit breaking changes [`#7627`](https://github.com/fabricjs/fabric.js/pull/7627)
+- feat(animation): animations registry [`#7528`](https://github.com/fabricjs/fabric.js/pull/7528)
+- docs(): Remove not working badges [`#7623`](https://github.com/fabricjs/fabric.js/pull/7623)
+- ci(): add auto-changelog package to quickly draft a changelog [`#7615`](https://github.com/fabricjs/fabric.js/pull/7615)
- feat(fabric.EraserBrush): added `eraser` property to Object instead of attaching to `clipPath`, remove hacky `getClipPath`/`setClipPath` [#7470](https://github.com/fabricjs/fabric.js/pull/7470), see **BREAKING** comments.
- feat(fabric.EraserBrush): support `inverted` option to undo erasing [#7470](https://github.com/fabricjs/fabric.js/pull/7470)
- fix(fabric.EraserBrush): fix doubling opaic objects while erasing [#7445](https://github.com/fabricjs/fabric.js/issues/7445) [#7470](https://github.com/fabricjs/fabric.js/pull/7470)
-
-**BREAKING**:
-
-- fabric.EraserBrush: The Eraser object is now a subclass of Group. This means that loading from JSON will break between versions.
+- **BREAKING**: fabric.EraserBrush: The Eraser object is now a subclass of Group. This means that loading from JSON will break between versions.
Use this [code](https://gist.github.com/ShaMan123/6c5c4ca2cc720a2700848a2deb6addcd) to transform your json payload to the new version.
-
-
-## [next]
- feat(fabric.Canvas): fire an extra mouse up for the original control of the initial target [`#7612`](https://github.com/fabricjs/fabric.js/pull/7612)
- fix(fabric.Object) bounding box display with skewY when outside group [`#7611`](https://github.com/fabricjs/fabric.js/pull/7611)
- fix(fabric.text) fix rtl/ltr performance issues [`#7610`](https://github.com/fabricjs/fabric.js/pull/7610)
@@ -21,9 +21,9 @@ Use this [code](https://gist.github.com/ShaMan123/6c5c4ca2cc720a2700848a2deb6add
- feat(): `drop:before` event [`#7442`](https://github.com/fabricjs/fabric.js/pull/7442)
- ci(): Add codeql analysis step [`#7588`](https://github.com/fabricjs/fabric.js/pull/7588)
- security(): update onchange to solve security issue [`#7591`](https://github.com/fabricjs/fabric.js/pull/7591)
-- fix(): MAJOR prevent render canvas with quality less than 100% [`#7537`](https://github.com/fabricjs/fabric.js/pull/7537)
+- **BREAKING**: fix(): MAJOR prevent render canvas with quality less than 100% [`#7537`](https://github.com/fabricjs/fabric.js/pull/7537)
- docs(): fix broken link [`#7579`](https://github.com/fabricjs/fabric.js/pull/7579)
-- Deps(): MAJOR update to jsdom 19 node 14 [`#7587`](https://github.com/fabricjs/fabric.js/pull/7587)
+- **BREAKING**: Deps(): MAJOR update to jsdom 19 node 14 [`#7587`](https://github.com/fabricjs/fabric.js/pull/7587)
- Fix(): JSDOM transative vulnerability [`#7510`](https://github.com/fabricjs/fabric.js/pull/7510)
- fix(fabric.parser): attempt to resolve some issues with regexp [`#7520`](https://github.com/fabricjs/fabric.js/pull/7520)
- fix(fabric.IText) fix for possible error on copy paste [`#7526`](https://github.com/fabricjs/fabric.js/pull/7526)
diff --git a/HEADER.js b/HEADER.js
index 416b2451946..6071a959136 100644
--- a/HEADER.js
+++ b/HEADER.js
@@ -1,6 +1,6 @@
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || { version: '4.6.0' };
+var fabric = fabric || { version: '5.0.0' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
diff --git a/README.md b/README.md
index e195d3aff44..3e559718fc7 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ Fabric.js allows you to easily create simple shapes like rectangles, circles, tr
- Opera 9.64+
- Chrome (all versions)
- Edge (chromium based, all versions)
-- IE11 and Edge legacy, supported but deprecated.
+- IE11 and Edge legacy, not supported. Fabric up to 5.0 is written with ES5 in mind, but no specific tests are run for those browsers.
You can [run automated unit tests](http://fabricjs.com/test/unit/) right in the browser.
diff --git a/dist/fabric.js b/dist/fabric.js
index 86536ce06be..2ee10b2b0ac 100644
--- a/dist/fabric.js
+++ b/dist/fabric.js
@@ -1,7 +1,7 @@
/* build: `node build.js modules=ALL exclude=gestures,accessors,erasing requirejs minifier=uglifyjs` */
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || { version: '4.6.0' };
+var fabric = fabric || { version: '5.0.0' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
@@ -762,6 +762,135 @@ fabric.CommonMethods = {
};
},
+ /**
+ * Creates a vetor from points represented as a point
+ * @static
+ * @memberOf fabric.util
+ *
+ * @typedef {Object} Point
+ * @property {number} x
+ * @property {number} y
+ *
+ * @param {Point} from
+ * @param {Point} to
+ * @returns {Point} vector
+ */
+ createVector: function (from, to) {
+ return new fabric.Point(to.x - from.x, to.y - from.y);
+ },
+
+ /**
+ * Calculates angle between 2 vectors using dot product
+ * @static
+ * @memberOf fabric.util
+ * @param {Point} a
+ * @param {Point} b
+ * @returns the angle in radian between the vectors
+ */
+ calcAngleBetweenVectors: function (a, b) {
+ return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y)));
+ },
+
+ /**
+ * @static
+ * @memberOf fabric.util
+ * @param {Point} v
+ * @returns {Point} vector representing the unit vector of pointing to the direction of `v`
+ */
+ getHatVector: function (v) {
+ return new fabric.Point(v.x, v.y).multiply(1 / Math.hypot(v.x, v.y));
+ },
+
+ /**
+ * @static
+ * @memberOf fabric.util
+ * @param {Point} A
+ * @param {Point} B
+ * @param {Point} C
+ * @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle
+ */
+ getBisector: function (A, B, C) {
+ var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C);
+ var alpha = fabric.util.calcAngleBetweenVectors(AB, AC);
+ // check if alpha is relative to AB->BC
+ var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC);
+ var phi = alpha * (ro === 0 ? 1 : -1) / 2;
+ return {
+ vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)),
+ angle: alpha
+ };
+ },
+
+ /**
+ * Project stroke width on points returning 2 projections for each point as follows:
+ * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke.
+ * - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector.
+ * - `round`: same as `bevel`
+ * Used to calculate object's bounding box
+ * @static
+ * @memberOf fabric.util
+ * @param {Point[]} points
+ * @param {Object} options
+ * @param {number} options.strokeWidth
+ * @param {'miter'|'bevel'|'round'} options.strokeLineJoin
+ * @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit
+ * @param {boolean} options.strokeUniform
+ * @param {number} options.scaleX
+ * @param {number} options.scaleY
+ * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points
+ * @returns {fabric.Point[]} array of size 2n/4n of all suspected points
+ */
+ projectStrokeOnPoints: function (points, options, openPath) {
+ var coords = [], s = options.strokeWidth / 2,
+ strokeUniformScalar = options.strokeUniform ?
+ new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1),
+ getStrokeHatVector = function (v) {
+ var scalar = s / (Math.hypot(v.x, v.y));
+ return new fabric.Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y);
+ };
+ if (points.length <= 1) {return coords;}
+ points.forEach(function (p, index) {
+ var A = new fabric.Point(p.x, p.y), B, C;
+ if (index === 0) {
+ C = points[index + 1];
+ B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) : points[points.length - 1];
+ }
+ else if (index === points.length - 1) {
+ B = points[index - 1];
+ C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) : points[0];
+ }
+ else {
+ B = points[index - 1];
+ C = points[index + 1];
+ }
+ var bisector = fabric.util.getBisector(A, B, C),
+ bisectorVector = bisector.vector,
+ alpha = bisector.angle,
+ scalar,
+ miterVector;
+ if (options.strokeLineJoin === 'miter') {
+ scalar = -s / Math.sin(alpha / 2);
+ miterVector = new fabric.Point(
+ bisectorVector.x * scalar * strokeUniformScalar.x,
+ bisectorVector.y * scalar * strokeUniformScalar.y
+ );
+ if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) {
+ coords.push(A.add(miterVector));
+ coords.push(A.subtract(miterVector));
+ return;
+ }
+ }
+ scalar = -s * Math.SQRT2;
+ miterVector = new fabric.Point(
+ bisectorVector.x * scalar * strokeUniformScalar.x,
+ bisectorVector.y * scalar * strokeUniformScalar.y
+ );
+ coords.push(A.add(miterVector));
+ coords.push(A.subtract(miterVector));
+ });
+ return coords;
+ },
+
/**
* Apply transform t to point p
* @static
@@ -1074,6 +1203,25 @@ fabric.CommonMethods = {
});
},
+ /**
+ * Creates corresponding fabric instances residing in an object, e.g. `clipPath`
+ * @see {@link fabric.Object.ENLIVEN_PROPS}
+ * @param {Object} object
+ * @param {Object} [context] assign enlived props to this object (pass null to skip this)
+ * @param {(objects:fabric.Object[]) => void} callback
+ */
+ enlivenObjectEnlivables: function (object, context, callback) {
+ var enlivenProps = fabric.Object.ENLIVEN_PROPS.filter(function (key) { return !!object[key]; });
+ fabric.util.enlivenObjects(enlivenProps.map(function (key) { return object[key]; }), function (enlivedProps) {
+ var objects = {};
+ enlivenProps.forEach(function (key, index) {
+ objects[key] = enlivedProps[index];
+ context && (context[key] = enlivedProps[index]);
+ });
+ callback && callback(objects);
+ });
+ },
+
/**
* Create and wait for loading of patterns
* @static
@@ -1165,49 +1313,6 @@ fabric.CommonMethods = {
}
},
- /**
- * WARNING: THIS WAS TO SUPPORT OLD BROWSERS. deprecated.
- * WILL BE REMOVED IN FABRIC 5.0
- * Draws a dashed line between two points
- *
- * This method is used to draw dashed line around selection area.
- * See dotted stroke in canvas
- *
- * @param {CanvasRenderingContext2D} ctx context
- * @param {Number} x start x coordinate
- * @param {Number} y start y coordinate
- * @param {Number} x2 end x coordinate
- * @param {Number} y2 end y coordinate
- * @param {Array} da dash array pattern
- * @deprecated
- */
- drawDashedLine: function(ctx, x, y, x2, y2, da) {
- var dx = x2 - x,
- dy = y2 - y,
- len = sqrt(dx * dx + dy * dy),
- rot = atan2(dy, dx),
- dc = da.length,
- di = 0,
- draw = true;
-
- ctx.save();
- ctx.translate(x, y);
- ctx.moveTo(0, 0);
- ctx.rotate(rot);
-
- x = 0;
- while (len > x) {
- x += da[di++ % dc];
- if (x > len) {
- x = len;
- }
- ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
- draw = !draw;
- }
-
- ctx.restore();
- },
-
/**
* Creates canvas element
* @static
@@ -1335,7 +1440,7 @@ fabric.CommonMethods = {
* @param {Boolean} [options.flipX]
* @param {Boolean} [options.flipY]
* @param {Number} [options.skewX]
- * @param {Number} [options.skewX]
+ * @param {Number} [options.skewY]
* @return {Number[]} transform matrix
*/
calcDimensionsMatrix: function(options) {
@@ -1689,7 +1794,50 @@ fabric.CommonMethods = {
x: bbox.width,
y: bbox.height,
};
- }
+ },
+
+ /**
+ * Merges 2 clip paths into one visually equal clip path
+ *
+ * **IMPORTANT**:\
+ * Does **NOT** clone the arguments, clone them proir if necessary.
+ *
+ * Creates a wrapper (group) that contains one clip path and is clipped by the other so content is kept where both overlap.
+ * Use this method if both the clip paths may have nested clip paths of their own, so assigning one to the other's clip path property is not possible.
+ *
+ * In order to handle the `inverted` property we follow logic described in the following cases:\
+ * **(1)** both clip paths are inverted - the clip paths pass the inverted prop to the wrapper and loose it themselves.\
+ * **(2)** one is inverted and the other isn't - the wrapper shouldn't become inverted and the inverted clip path must clip the non inverted one to produce an identical visual effect.\
+ * **(3)** both clip paths are not inverted - wrapper and clip paths remain unchanged.
+ *
+ * @memberOf fabric.util
+ * @param {fabric.Object} c1
+ * @param {fabric.Object} c2
+ * @returns {fabric.Object} merged clip path
+ */
+ mergeClipPaths: function (c1, c2) {
+ var a = c1, b = c2;
+ if (a.inverted && !b.inverted) {
+ // case (2)
+ a = c2;
+ b = c1;
+ }
+ // `b` becomes `a`'s clip path so we transform `b` to `a` coordinate plane
+ fabric.util.applyTransformToObject(
+ b,
+ fabric.util.multiplyTransformMatrices(
+ fabric.util.invertTransform(a.calcTransformMatrix()),
+ b.calcTransformMatrix()
+ )
+ );
+ // assign the `inverted` prop to the wrapping group
+ var inverted = a.inverted && b.inverted;
+ if (inverted) {
+ // case (1)
+ a.inverted = b.inverted = false;
+ }
+ return new fabric.Group([a], { clipPath: b, inverted: inverted });
+ },
};
})(typeof exports !== 'undefined' ? exports : this);
@@ -2203,15 +2351,15 @@ fabric.CommonMethods = {
p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc;
// nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100
// the path
- while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) {
+ while (tmpLen < distance && nextStep > 0.0001) {
p = iterator(perc);
lastPerc = perc;
nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y);
// compare tmpLen each cycle with distance, decide next perc to test.
if ((nextLen + tmpLen) > distance) {
// we discard this step and we make smaller steps.
- nextStep /= 2;
perc -= nextStep;
+ nextStep /= 2;
}
else {
tempP = p;
@@ -2507,50 +2655,6 @@ fabric.CommonMethods = {
});
}
- /**
- * Calculate bounding box of a elliptic-arc
- * @deprecated
- * @param {Number} fx start point of arc
- * @param {Number} fy
- * @param {Number} rx horizontal radius
- * @param {Number} ry vertical radius
- * @param {Number} rot angle of horizontal axis
- * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
- * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
- * @param {Number} tx end point of arc
- * @param {Number} ty
- */
- function getBoundsOfArc(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
-
- var fromX = 0, fromY = 0, bound, bounds = [],
- segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
-
- for (var i = 0, len = segs.length; i < len; i++) {
- bound = getBoundsOfCurve(fromX, fromY, segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5], segs[i][6]);
- bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
- bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
- fromX = segs[i][5];
- fromY = segs[i][6];
- }
- return bounds;
- };
-
- /**
- * Draws arc
- * @deprecated
- * @param {CanvasRenderingContext2D} ctx
- * @param {Number} fx
- * @param {Number} fy
- * @param {Array} coords coords of the arc, without the front 'A/a'
- */
- function drawArc(ctx, fx, fy, coords) {
- coords = coords.slice(0).unshift('X'); // command A or a does not matter
- var beziers = fromArcToBeziers(fx, fy, coords);
- beziers.forEach(function(bezier) {
- ctx.bezierCurveTo.apply(ctx, bezier.slice(1));
- });
- };
-
/**
* Join path commands to go back to svg format
* @param {Array} pathData fabricJS parsed path commands
@@ -2566,16 +2670,6 @@ fabric.CommonMethods = {
fabric.util.getBoundsOfCurve = getBoundsOfCurve;
fabric.util.getPointOnPath = getPointOnPath;
fabric.util.transformPath = transformPath;
- /**
- * Typo of `fromArcToBeziers` kept for not breaking the api once corrected.
- * Will be removed in fabric 5.0
- * @deprecated
- */
- fabric.util.fromArcToBeizers = fromArcToBeziers;
- // kept because we do not want to make breaking changes.
- // but useless and deprecated.
- fabric.util.getBoundsOfArc = getBoundsOfArc;
- fabric.util.drawArc = drawArc;
})();
@@ -3476,7 +3570,117 @@ fabric.log = console.log;
fabric.warn = console.warn;
-(function() {
+(function () {
+
+ var extend = fabric.util.object.extend,
+ clone = fabric.util.object.clone;
+
+ /**
+ * @typedef {Object} AnimationOptions
+ * @property {Function} [options.onChange] Callback; invoked on every value change
+ * @property {Function} [options.onComplete] Callback; invoked when value change is completed
+ * @property {Number} [options.startValue=0] Starting value
+ * @property {Number} [options.endValue=100] Ending value
+ * @property {Number} [options.byValue=100] Value to modify the property by
+ * @property {Function} [options.easing] Easing function
+ * @property {Number} [options.duration=500] Duration of change (in ms)
+ * @property {Function} [options.abort] Additional function with logic. If returns true, animation aborts.
+ *
+ * @typedef {() => void} CancelFunction
+ *
+ * @typedef {Object} AnimationCurrentState
+ * @property {number} currentValue value in range [`startValue`, `endValue`]
+ * @property {number} completionRate value in range [0, 1]
+ * @property {number} durationRate value in range [0, 1]
+ *
+ * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext
+ */
+
+ /**
+ * Array holding all running animations
+ * @memberof fabric
+ * @type {AnimationContext[]}
+ */
+ var RUNNING_ANIMATIONS = [];
+ fabric.util.object.extend(RUNNING_ANIMATIONS, {
+
+ /**
+ * cancel all running animations at the next requestAnimFrame
+ * @returns {AnimationContext[]}
+ */
+ cancelAll: function () {
+ var animations = this.splice(0);
+ animations.forEach(function (animation) {
+ animation.cancel();
+ });
+ return animations;
+ },
+
+ /**
+ * cancel all running animations attached to canvas at the next requestAnimFrame
+ * @param {fabric.Canvas} canvas
+ * @returns {AnimationContext[]}
+ */
+ cancelByCanvas: function (canvas) {
+ if (!canvas) {
+ return [];
+ }
+ var cancelled = this.filter(function (animation) {
+ return typeof animation.target === 'object' && animation.target.canvas === canvas;
+ });
+ cancelled.forEach(function (animation) {
+ animation.cancel();
+ });
+ return cancelled;
+ },
+
+ /**
+ * cancel all running animations for target at the next requestAnimFrame
+ * @param {*} target
+ * @returns {AnimationContext[]}
+ */
+ cancelByTarget: function (target) {
+ var cancelled = this.findAnimationsByTarget(target);
+ cancelled.forEach(function (animation) {
+ animation.cancel();
+ });
+ return cancelled;
+ },
+
+ /**
+ *
+ * @param {CancelFunction} cancelFunc the function returned by animate
+ * @returns {number}
+ */
+ findAnimationIndex: function (cancelFunc) {
+ return this.indexOf(this.findAnimation(cancelFunc));
+ },
+
+ /**
+ *
+ * @param {CancelFunction} cancelFunc the function returned by animate
+ * @returns {AnimationContext | undefined} animation's options object
+ */
+ findAnimation: function (cancelFunc) {
+ return this.find(function (animation) {
+ return animation.cancel === cancelFunc;
+ });
+ },
+
+ /**
+ *
+ * @param {*} target the object that is assigned to the target property of the animation context
+ * @returns {AnimationContext[]} array of animation options object associated with target
+ */
+ findAnimationsByTarget: function (target) {
+ if (!target) {
+ return [];
+ }
+ return this.filter(function (animation) {
+ return animation.target === target;
+ });
+ }
+ });
function noop() {
return false;
@@ -3489,22 +3693,30 @@ fabric.warn = console.warn;
/**
* Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
* @memberOf fabric.util
- * @param {Object} [options] Animation options
- * @param {Function} [options.onChange] Callback; invoked on every value change
- * @param {Function} [options.onComplete] Callback; invoked when value change is completed
- * @param {Number} [options.startValue=0] Starting value
- * @param {Number} [options.endValue=100] Ending value
- * @param {Number} [options.byValue=100] Value to modify the property by
- * @param {Function} [options.easing] Easing function
- * @param {Number} [options.duration=500] Duration of change (in ms)
- * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
- * @returns {Function} abort function
+ * @param {AnimationOptions} [options] Animation options
+ * @returns {CancelFunction} cancel function
*/
function animate(options) {
- var cancel = false;
- requestAnimFrame(function(timestamp) {
- options || (options = { });
+ options || (options = {});
+ var cancel = false,
+ context,
+ removeFromRegistry = function () {
+ var index = fabric.runningAnimations.indexOf(context);
+ return index > -1 && fabric.runningAnimations.splice(index, 1)[0];
+ };
+
+ context = extend(clone(options), {
+ cancel: function () {
+ cancel = true;
+ return removeFromRegistry();
+ },
+ currentValue: 'startValue' in options ? options.startValue : 0,
+ completionRate: 0,
+ durationRate: 0
+ });
+ fabric.runningAnimations.push(context);
+ requestAnimFrame(function(timestamp) {
var start = timestamp || +new Date(),
duration = options.duration || 500,
finish = start + duration, time,
@@ -3519,25 +3731,31 @@ fabric.warn = console.warn;
options.onStart && options.onStart();
(function tick(ticktime) {
- // TODO: move abort call after calculation
- // and pass (current,valuePerc, timePerc) as arguments
time = ticktime || +new Date();
var currentTime = time > finish ? duration : (time - start),
timePerc = currentTime / duration,
current = easing(currentTime, startValue, byValue, duration),
valuePerc = Math.abs((current - startValue) / byValue);
+ // update context
+ context.currentValue = current;
+ context.completionRate = valuePerc;
+ context.durationRate = timePerc;
if (cancel) {
return;
}
if (abort(current, valuePerc, timePerc)) {
- // remove this in 4.0
- // does to even make sense to abort and run onComplete?
- onComplete(endValue, 1, 1);
+ removeFromRegistry();
return;
}
if (time > finish) {
+ // update context
+ context.currentValue = endValue;
+ context.completionRate = 1;
+ context.durationRate = 1;
+ // execute callbacks
onChange(endValue, 1, 1);
onComplete(endValue, 1, 1);
+ removeFromRegistry();
return;
}
else {
@@ -3546,9 +3764,8 @@ fabric.warn = console.warn;
}
})(start);
});
- return function() {
- cancel = true;
- };
+
+ return context.cancel;
}
var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -3580,6 +3797,7 @@ fabric.warn = console.warn;
fabric.util.animate = animate;
fabric.util.requestAnimFrame = requestAnimFrame;
fabric.util.cancelAnimFrame = cancelAnimFrame;
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
})();
@@ -5062,22 +5280,26 @@ fabric.warn = console.warn;
if (styleContents.trim() === '') {
continue;
}
- rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
- rules = rules.map(function(rule) { return rule.trim(); });
+ // recovers all the rule in this form `body { style code... }`
+ // rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
+ rules = styleContents.split('}');
+ // remove empty rules.
+ rules = rules.filter(function(rule) { return rule.trim(); });
+ // at this point we have hopefully an array of rules `body { style code... `
// eslint-disable-next-line no-loop-func
rules.forEach(function(rule) {
- var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
- ruleObj = { }, declaration = match[2].trim(),
- propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
+ var match = rule.split('{'),
+ ruleObj = { }, declaration = match[1].trim(),
+ propertyValuePairs = declaration.split(';').filter(function(pair) { return pair.trim(); });
for (i = 0, len = propertyValuePairs.length; i < len; i++) {
- var pair = propertyValuePairs[i].split(/\s*:\s*/),
- property = pair[0],
- value = pair[1];
+ var pair = propertyValuePairs[i].split(':'),
+ property = pair[0].trim(),
+ value = pair[1].trim();
ruleObj[property] = value;
}
- rule = match[1];
+ rule = match[0].trim();
rule.split(',').forEach(function(_rule) {
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
@@ -8652,8 +8874,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
imageSmoothingEnabled: true,
/**
- * The transformation (in the format of Canvas transform) which focuses the viewport
+ * The transformation (a Canvas 2D API transform matrix) which focuses the viewport
* @type Array
+ * @example
Default transform
+ * canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
+ * @example Scale by 70% and translate toward bottom-right by 50, without skewing
+ * canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50];
* @default
*/
viewportTransform: fabric.iMatrix.concat(),
@@ -8747,7 +8973,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* @private
*/
_isRetinaScaling: function() {
- return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
+ return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling);
},
/**
@@ -8755,7 +8981,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* @return {Number} retinaScaling if applied, otherwise 1;
*/
getRetinaScaling: function() {
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
+ return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1;
},
/**
@@ -9122,7 +9348,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
}
}
if (this._isCurrentlyDrawing) {
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
+ this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop);
}
this._initRetinaScaling();
this.calcOffset();
@@ -9189,8 +9415,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
},
/**
- * Sets viewport transform of this canvas instance
- * @param {Array} vpt the transform in the form of context.transform
+ * Sets viewport transformation of this canvas instance
+ * @param {Array} vpt a Canvas 2D API transform matrix
* @return {fabric.Canvas} instance
* @chainable true
*/
@@ -10277,6 +10503,10 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
}
this.forEachObject(function(object) {
object.dispose && object.dispose();
+ // animation module is still optional
+ if (fabric.runningAnimations) {
+ fabric.runningAnimations.cancelByTarget(object);
+ }
});
this._objects = [];
if (this.backgroundImage && this.backgroundImage.dispose) {
@@ -10291,7 +10521,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
this.contextContainer = null;
// restore canvas style
this.lowerCanvasEl.classList.remove('lower-canvas');
- this.lowerCanvasEl.style = this._originalCanvasStyle;
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
delete this._originalCanvasStyle;
// restore canvas size to original size in case retina scaling was applied
this.lowerCanvasEl.setAttribute('width', this.width);
@@ -10459,9 +10689,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
/**
* Sets brush styles
* @private
+ * @param {CanvasRenderingContext2D} ctx
*/
- _setBrushStyles: function() {
- var ctx = this.canvas.contextTop;
+ _setBrushStyles: function (ctx) {
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
ctx.lineCap = this.strokeLineCap;
@@ -10546,6 +10776,22 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
*/
decimate: 0.4,
+ /**
+ * Draws a straight line between last recorded point to current pointer
+ * Used for `shift` functionality
+ *
+ * @type boolean
+ * @default false
+ */
+ drawStraightLine: false,
+
+ /**
+ * The event modifier key that makes the brush draw a straight line.
+ * If `null` or 'none' or any other string that is not a modifier key the feature is disabled.
+ * @type {'altKey' | 'shiftKey' | 'ctrlKey' | 'none' | undefined | null}
+ */
+ straightLineKey: 'shiftKey',
+
/**
* Constructor
* @param {fabric.Canvas} canvas
@@ -10556,6 +10802,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
this._points = [];
},
+ needsFullRender: function () {
+ return this.callSuper('needsFullRender') || this._hasStraightLine;
+ },
+
/**
* Invoked inside on mouse down and mouse move
* @param {Object} pointer
@@ -10574,6 +10824,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
if (!this.canvas._isMainEvent(options.e)) {
return;
}
+ this.drawStraightLine = options.e[this.straightLineKey];
this._prepareForDrawing(pointer);
// capture coordinates immediately
// this allows to draw dots (when movement never occurs)
@@ -10589,6 +10840,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
if (!this.canvas._isMainEvent(options.e)) {
return;
}
+ this.drawStraightLine = options.e[this.straightLineKey];
if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
return;
}
@@ -10621,6 +10873,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
if (!this.canvas._isMainEvent(options.e)) {
return true;
}
+ this.drawStraightLine = false;
this.oldEnd = undefined;
this._finalizeAndAddPath();
return false;
@@ -10647,6 +10900,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) {
return false;
}
+ if (this.drawStraightLine && this._points.length > 1) {
+ this._hasStraightLine = true;
+ this._points.pop();
+ }
this._points.push(point);
return true;
},
@@ -10657,8 +10914,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
*/
_reset: function() {
this._points = [];
- this._setBrushStyles();
+ this._setBrushStyles(this.canvas.contextTop);
this._setShadow();
+ this._hasStraightLine = false;
},
/**
@@ -10673,12 +10931,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
/**
* Draw a smooth path on the topCanvas using quadraticCurveTo
* @private
+ * @param {CanvasRenderingContext2D} [ctx]
*/
- _render: function() {
- var ctx = this.canvas.contextTop, i, len,
+ _render: function(ctx) {
+ var i, len,
p1 = this._points[0],
p2 = this._points[1];
-
+ ctx = ctx || this.canvas.contextTop;
this._saveAndTransform(ctx);
ctx.beginPath();
//if we only have 2 points in the path and they are the same
@@ -11212,17 +11471,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
/**
* Creates "pattern" instance property
+ * @param {CanvasRenderingContext2D} ctx
*/
- getPattern: function() {
- return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
+ getPattern: function(ctx) {
+ return ctx.createPattern(this.source || this.getPatternSrc(), 'repeat');
},
/**
* Sets brush styles
+ * @param {CanvasRenderingContext2D} ctx
*/
- _setBrushStyles: function() {
- this.callSuper('_setBrushStyles');
- this.canvas.contextTop.strokeStyle = this.getPattern();
+ _setBrushStyles: function(ctx) {
+ this.callSuper('_setBrushStyles', ctx);
+ ctx.strokeStyle = this.getPattern(ctx);
},
/**
@@ -11281,15 +11542,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
* @fires dragover
* @fires dragenter
* @fires dragleave
+ * @fires drop:before before drop event. same native event. This is added to handle edge cases
* @fires drop
* @fires after:render at the end of the render process, receives the context in the callback
* @fires before:render at start the render process, receives the context in the callback
*
- * the following events are deprecated:
- * @fires object:rotated at the end of a rotation transform
- * @fires object:scaled at the end of a scale transform
- * @fires object:moved at the end of translation transform
- * @fires object:skewed at the end of a skew transform
*/
fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
@@ -11474,13 +11731,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
*/
freeDrawingCursor: 'crosshair',
- /**
- * Cursor value used for rotation point
- * @type String
- * @default
- */
- rotationCursor: 'crosshair',
-
/**
* Cursor value used for disabled elements ( corners with disabled action )
* @type String
@@ -11586,6 +11836,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
*/
targets: [],
+ /**
+ * When the option is enabled, PointerEvent is used instead of MouseEvent.
+ * @type Boolean
+ * @default
+ */
+ enablePointerEvents: false,
+
/**
* Keep track of the hovered target
* @type fabric.Object
@@ -11661,6 +11918,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
}
if (this.hasLostContext) {
this.renderTopLayer(this.contextTop);
+ this.hasLostContext = false;
}
var canvasToDrawOn = this.contextContainer;
this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
@@ -12330,17 +12588,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
e: e,
selected: added,
deselected: removed,
- // added for backward compatibility
- // deprecated
- updated: added[0] || removed[0],
- target: this._activeObject,
});
}
else if (objects.length > 0) {
this.fire('selection:created', {
e: e,
selected: added,
- target: this._activeObject,
});
}
else if (oldObjects.length > 0) {
@@ -12664,7 +12917,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
this._onDragOver = this._onDragOver.bind(this);
this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter');
this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave');
- this._onDrop = this._simpleEventHandler.bind(this, 'drop');
+ this._onDrop = this._onDrop.bind(this);
this.eventsBound = true;
},
@@ -12776,6 +13029,18 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
this._fireEnterLeaveEvents(target, e);
},
+ /**
+ * `drop:before` is a an event that allow you to schedule logic
+ * before the `drop` event. Prefer `drop` event always, but if you need
+ * to run some drop-disabling logic on an event, since there is no way
+ * to handle event handlers ordering, use `drop:before`
+ * @param {Event} e
+ */
+ _onDrop: function (e) {
+ this._simpleEventHandler('drop:before', e);
+ return this._simpleEventHandler('drop', e);
+ },
+
/**
* @private
* @param {Event} e Event object fired on mousedown
@@ -13008,25 +13273,34 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
);
}
}
+ var corner, pointer;
if (target) {
+ corner = target._findTargetCorner(
+ this.getPointer(e, true),
+ fabric.util.isTouchEvent(e)
+ );
if (target.selectable && target !== this._activeObject && target.activeOn === 'up') {
this.setActiveObject(target, e);
shouldRender = true;
}
else {
- var corner = target._findTargetCorner(
- this.getPointer(e, true),
- fabric.util.isTouchEvent(e)
- );
var control = target.controls[corner],
mouseUpHandler = control && control.getMouseUpHandler(e, target, control);
if (mouseUpHandler) {
- var pointer = this.getPointer(e);
+ pointer = this.getPointer(e);
mouseUpHandler(e, transform, pointer.x, pointer.y);
}
}
target.isMoving = false;
}
+ // if we are ending up a transform on a different control or a new object
+ // fire the original mouse up from the corner that started the transform
+ if (transform && (transform.target !== target || transform.corner !== corner)) {
+ var originalControl = transform.target && transform.target.controls[transform.corner],
+ originalMouseUpHandler = originalControl && originalControl.getMouseUpHandler(e, target, control);
+ pointer = pointer || this.getPointer(e);
+ originalMouseUpHandler && originalMouseUpHandler(e, transform, pointer.x, pointer.y);
+ }
this._setCursorFromEvent(e, target);
this._handleEvent(e, 'up', LEFT_CLICK, isClick);
this._groupSelector = null;
@@ -13108,7 +13382,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
var transform = this._currentTransform,
target = transform.target,
- eventName,
options = {
e: e,
target: target,
@@ -13123,59 +13396,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
target.setCoords();
if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) {
- if (transform.actionPerformed) {
- // this is not friendly to the new control api.
- // is deprecated.
- eventName = this._addEventOptions(options, transform);
- this._fire(eventName, options);
- }
this._fire('modified', options);
}
},
- /**
- * Mutate option object in order to add by property and give back the event name.
- * @private
- * @deprecated since 4.2.0
- * @param {Object} options to mutate
- * @param {Object} transform to inspect action from
- */
- _addEventOptions: function(options, transform) {
- // we can probably add more details at low cost
- // scale change, rotation changes, translation changes
- var eventName, by;
- switch (transform.action) {
- case 'scaleX':
- eventName = 'scaled';
- by = 'x';
- break;
- case 'scaleY':
- eventName = 'scaled';
- by = 'y';
- break;
- case 'skewX':
- eventName = 'skewed';
- by = 'x';
- break;
- case 'skewY':
- eventName = 'skewed';
- by = 'y';
- break;
- case 'scale':
- eventName = 'scaled';
- by = 'equally';
- break;
- case 'rotate':
- eventName = 'rotated';
- break;
- case 'drag':
- eventName = 'moved';
- break;
- }
- options.by = by;
- return eventName;
- },
-
/**
* @private
* @param {Event} e Event object fired on mousedown
@@ -14615,6 +14839,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
/**
* When `false`, the stoke width will scale with the object.
* When `true`, the stroke will always match the exact pixel size entered for stroke width.
+ * this Property does not work on Text classes or drawing call that uses strokeText,fillText methods
* default to false
* @since 2.6.0
* @type Boolean
@@ -15265,26 +15490,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
/**
* Execute the drawing operation for an object clipPath
* @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Object} clipPath
*/
- drawClipPathOnCache: function(ctx) {
- var path = this.clipPath;
+ drawClipPathOnCache: function(ctx, clipPath) {
ctx.save();
// DEBUG: uncomment this line, comment the following
// ctx.globalAlpha = 0.4
- if (path.inverted) {
+ if (clipPath.inverted) {
ctx.globalCompositeOperation = 'destination-out';
}
else {
ctx.globalCompositeOperation = 'destination-in';
}
//ctx.scale(1 / 2, 1 / 2);
- if (path.absolutePositioned) {
+ if (clipPath.absolutePositioned) {
var m = fabric.util.invertTransform(this.calcTransformMatrix());
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
- path.transform(ctx);
- ctx.scale(1 / path.zoomX, 1 / path.zoomY);
- ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY);
+ clipPath.transform(ctx);
+ ctx.scale(1 / clipPath.zoomX, 1 / clipPath.zoomY);
+ ctx.drawImage(clipPath._cacheCanvas, -clipPath.cacheTranslationX, -clipPath.cacheTranslationY);
ctx.restore();
},
@@ -15303,22 +15528,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
this._renderBackground(ctx);
}
this._render(ctx);
- this._drawClipPath(ctx);
+ this._drawClipPath(ctx, this.clipPath);
this.fill = originalFill;
this.stroke = originalStroke;
},
- _drawClipPath: function(ctx) {
- var path = this.clipPath;
- if (!path) { return; }
+ /**
+ * Prepare clipPath state and cache and draw it on instance's cache
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {fabric.Object} clipPath
+ */
+ _drawClipPath: function (ctx, clipPath) {
+ if (!clipPath) { return; }
// needed to setup a couple of variables
// path canvas gets overridden with this one.
// TODO find a better solution?
- path.canvas = this.canvas;
- path.shouldCache();
- path._transformDone = true;
- path.renderCache({ forClipping: true });
- this.drawClipPathOnCache(ctx);
+ clipPath.canvas = this.canvas;
+ clipPath.shouldCache();
+ clipPath._transformDone = true;
+ clipPath.renderCache({ forClipping: true });
+ this.drawClipPathOnCache(ctx, clipPath);
},
/**
@@ -15462,6 +15691,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
/**
* Renders controls and borders for the object
+ * the context here is not transformed
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Object} [styleOverride] properties to override the object style
*/
@@ -15480,7 +15710,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
if (!this.group) {
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
}
- ctx.rotate(degreesToRadians(options.angle));
+ if (this.flipX) {
+ options.angle -= 180;
+ }
+ ctx.rotate(degreesToRadians(this.group ? options.angle : this.angle));
if (styleOverride.forActiveSelection || this.group) {
drawBorders && this.drawBordersInGroup(ctx, options, styleOverride);
}
@@ -16016,6 +16249,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
if (this.globalCompositeOperation) {
ctx.globalCompositeOperation = this.globalCompositeOperation;
}
+ },
+
+ /**
+ * cancel instance's running animations
+ */
+ dispose: function () {
+ if (fabric.runningAnimations) {
+ fabric.runningAnimations.cancelByTarget(this);
+ }
}
});
@@ -16033,6 +16275,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
*/
fabric.Object.NUM_FRACTION_DIGITS = 2;
+ /**
+ * Defines which properties should be enlivened from the object passed to {@link fabric.Object._fromObject}
+ * @static
+ * @memberOf fabric.Object
+ * @constant
+ * @type string[]
+ */
+ fabric.Object.ENLIVEN_PROPS = ['clipPath'];
+
fabric.Object._fromObject = function(className, object, callback, extraParam) {
var klass = fabric[className];
object = clone(object, true);
@@ -16043,8 +16294,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
if (typeof patterns[1] !== 'undefined') {
object.stroke = patterns[1];
}
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
- object.clipPath = enlivedProps[0];
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
callback && callback(instance);
});
@@ -16365,7 +16615,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
/**
* Describe object's corner position in canvas element coordinates.
* includes padding. Used of object detection.
- * set and refreshed with setCoords and calcCoords.
+ * set and refreshed with setCoords.
* @memberOf fabric.Object.prototype
*/
lineCoords: null,
@@ -16749,21 +16999,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
return this.scale(value / this.height / boundingRectFactor);
},
- /**
- * Calculates and returns the .coords of an object.
- * unused by the library, only for the end dev.
- * @return {Object} Object with tl, tr, br, bl ....
- * @chainable
- * @deprecated
- */
- calcCoords: function(absolute) {
- // this is a compatibility function to avoid removing calcCoords now.
- if (absolute) {
- return this.calcACoords();
- }
- return this.calcOCoords();
- },
-
calcLineCoords: function() {
var vpt = this.getViewportTransform(),
padding = this.padding, angle = degreesToRadians(this.angle),
@@ -16838,7 +17073,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* oCoords are used to find the corners
* aCoords are used to quickly find an object on the canvas
* lineCoords are used to quickly find object during pointer events.
- * See {@link https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords|When-to-call-setCoords}
+ * See {@link https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas}
+ *
* @param {Boolean} [skipCorners] skip calculation of oCoords.
* @return {fabric.Object} thisArg
* @chainable
@@ -16935,23 +17171,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
return cache.value;
},
- /*
- * Calculate object dimensions from its properties
- * @private
- * @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix
- * not including or including flipX, flipY to emulate the flipping boolean
- * @return {Object} .x width dimension
- * @return {Object} .y height dimension
- */
- _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
- return util.calcDimensionsMatrix({
- skewX: skewX,
- skewY: skewY,
- scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1),
- scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1)
- });
- },
-
/*
* Calculate object dimensions from its properties
* @private
@@ -17816,8 +18035,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
* @param {Function} [callbacks.onComplete] Invoked on completion
* @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Canvas} thisArg
- * @chainable
+ * @return {fabric.AnimationContext} context
*/
fxCenterObjectH: function (object, callbacks) {
callbacks = callbacks || { };
@@ -17827,7 +18045,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
onChange = callbacks.onChange || empty,
_this = this;
- fabric.util.animate({
+ return fabric.util.animate({
+ target: this,
startValue: object.left,
endValue: this.getCenter().left,
duration: this.FX_DURATION,
@@ -17841,8 +18060,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
onComplete();
}
});
-
- return this;
},
/**
@@ -17851,8 +18068,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
* @param {Function} [callbacks.onComplete] Invoked on completion
* @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Canvas} thisArg
- * @chainable
+ * @return {fabric.AnimationContext} context
*/
fxCenterObjectV: function (object, callbacks) {
callbacks = callbacks || { };
@@ -17862,7 +18078,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
onChange = callbacks.onChange || empty,
_this = this;
- fabric.util.animate({
+ return fabric.util.animate({
+ target: this,
startValue: object.top,
endValue: this.getCenter().top,
duration: this.FX_DURATION,
@@ -17876,8 +18093,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
onComplete();
}
});
-
- return this;
},
/**
@@ -17886,8 +18101,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
* @param {Function} [callbacks.onComplete] Invoked on completion
* @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Canvas} thisArg
- * @chainable
+ * @return {fabric.AnimationContext} context
*/
fxRemove: function (object, callbacks) {
callbacks = callbacks || { };
@@ -17897,7 +18111,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
onChange = callbacks.onChange || empty,
_this = this;
- fabric.util.animate({
+ return fabric.util.animate({
+ target: this,
startValue: object.opacity,
endValue: 0,
duration: this.FX_DURATION,
@@ -17911,8 +18126,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
onComplete();
}
});
-
- return this;
}
});
@@ -17923,7 +18136,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @param {Number|Object} value Value to animate property to (if string was given first) or options object
* @return {fabric.Object} thisArg
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation}
- * @chainable
+ * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties)
*
* As object — multiple properties
*
@@ -17936,22 +18149,22 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* object.animate('left', { duration: ... });
*
*/
- animate: function() {
+ animate: function () {
if (arguments[0] && typeof arguments[0] === 'object') {
- var propsToAnimate = [], prop, skipCallbacks;
+ var propsToAnimate = [], prop, skipCallbacks, out = [];
for (prop in arguments[0]) {
propsToAnimate.push(prop);
}
for (var i = 0, len = propsToAnimate.length; i < len; i++) {
prop = propsToAnimate[i];
skipCallbacks = i !== len - 1;
- this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
+ out.push(this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks));
}
+ return out;
}
else {
- this._animate.apply(this, arguments);
+ return this._animate.apply(this, arguments);
}
- return this;
},
/**
@@ -17999,6 +18212,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
}
var _options = {
+ target: this,
startValue: options.from,
endValue: to,
byValue: options.by,
@@ -18370,7 +18584,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
'use strict';
var fabric = global.fabric || (global.fabric = { }),
- pi = Math.PI;
+ degreesToRadians = fabric.util.degreesToRadians;
if (fabric.Circle) {
fabric.warn('fabric.Circle is already defined.');
@@ -18400,22 +18614,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
radius: 0,
/**
- * Start angle of the circle, moving clockwise
- * deprecated type, this should be in degree, this was an oversight.
+ * degrees of start of the circle.
* probably will change to degrees in next major version
- * @type Number
+ * @type Number 0 - 359
* @default 0
*/
startAngle: 0,
/**
* End angle of the circle
- * deprecated type, this should be in degree, this was an oversight.
* probably will change to degrees in next major version
- * @type Number
- * @default 2Pi
+ * @type Number 1 - 360
+ * @default 360
*/
- endAngle: pi * 2,
+ endAngle: 360,
cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'),
@@ -18453,7 +18665,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
_toSVG: function() {
var svgString, x = 0, y = 0,
- angle = (this.endAngle - this.startAngle) % ( 2 * pi);
+ angle = (this.endAngle - this.startAngle) % 360;
if (angle === 0) {
svgString = [
@@ -18464,14 +18676,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
];
}
else {
- var startX = fabric.util.cos(this.startAngle) * this.radius,
- startY = fabric.util.sin(this.startAngle) * this.radius,
- endX = fabric.util.cos(this.endAngle) * this.radius,
- endY = fabric.util.sin(this.endAngle) * this.radius,
- largeFlag = angle > pi ? '1' : '0';
+ var start = degreesToRadians(this.startAngle),
+ end = degreesToRadians(this.endAngle),
+ radius = this.radius,
+ startX = fabric.util.cos(start) * radius,
+ startY = fabric.util.sin(start) * radius,
+ endX = fabric.util.cos(end) * radius,
+ endY = fabric.util.sin(end) * radius,
+ largeFlag = angle > 180 ? '1' : '0';
svgString = [
'\n'
];
@@ -18490,8 +18705,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
0,
0,
this.radius,
- this.startAngle,
- this.endAngle, false);
+ degreesToRadians(this.startAngle),
+ degreesToRadians(this.endAngle),
+ false
+ );
this._renderPaintInOrder(ctx);
},
@@ -19049,7 +19266,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
extend = fabric.util.object.extend,
min = fabric.util.array.min,
max = fabric.util.array.max,
- toFixed = fabric.util.toFixed;
+ toFixed = fabric.util.toFixed,
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
if (fabric.Polyline) {
fabric.warn('fabric.Polyline is already defined');
@@ -19078,6 +19296,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
points: null,
+ /**
+ * WARNING: Feature in progress
+ * Calculate the exact bounding box taking in account strokeWidth on acute angles
+ * this will be turned to true by default on fabric 6.0
+ * maybe will be left in as an optimization since calculations may be slow
+ * @deprecated
+ * @type Boolean
+ * @default false
+ */
+ exactBoundingBox: false,
+
cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'),
/**
@@ -19106,13 +19335,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this._setPositionDimensions(options);
},
+ /**
+ * @private
+ */
+ _projectStrokeOnPoints: function () {
+ return projectStrokeOnPoints(this.points, this, true);
+ },
+
_setPositionDimensions: function(options) {
- var calcDim = this._calcDimensions(options), correctLeftTop;
- this.width = calcDim.width;
- this.height = calcDim.height;
+ var calcDim = this._calcDimensions(options), correctLeftTop,
+ correctSize = this.exactBoundingBox ? this.strokeWidth : 0;
+ this.width = calcDim.width - correctSize;
+ this.height = calcDim.height - correctSize;
if (!options.fromSVG) {
correctLeftTop = this.translateToGivenOrigin(
- { x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2 },
+ {
+ // this looks bad, but is one way to keep it optional for now.
+ x: calcDim.left - this.strokeWidth / 2 + correctSize / 2,
+ y: calcDim.top - this.strokeWidth / 2 + correctSize / 2
+ },
'left',
'top',
this.originX,
@@ -19126,8 +19367,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this.top = options.fromSVG ? calcDim.top : correctLeftTop.y;
}
this.pathOffset = {
- x: calcDim.left + this.width / 2,
- y: calcDim.top + this.height / 2
+ x: calcDim.left + this.width / 2 + correctSize / 2,
+ y: calcDim.top + this.height / 2 + correctSize / 2
};
},
@@ -19143,7 +19384,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
_calcDimensions: function() {
- var points = this.points,
+ var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points,
minX = min(points, 'x') || 0,
minY = min(points, 'y') || 0,
maxX = max(points, 'x') || 0,
@@ -19155,7 +19396,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
left: minX,
top: minY,
width: width,
- height: height
+ height: height,
};
},
@@ -19291,7 +19532,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
'use strict';
- var fabric = global.fabric || (global.fabric = { });
+ var fabric = global.fabric || (global.fabric = {}),
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
if (fabric.Polygon) {
fabric.warn('fabric.Polygon is already defined');
@@ -19313,6 +19555,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
type: 'polygon',
+ /**
+ * @private
+ */
+ _projectStrokeOnPoints: function () {
+ return projectStrokeOnPoints(this.points, this);
+ },
+
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
@@ -19370,6 +19619,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
min = fabric.util.array.min,
max = fabric.util.array.max,
extend = fabric.util.object.extend,
+ clone = fabric.util.object.clone,
_toString = Object.prototype.toString,
toFixed = fabric.util.toFixed;
@@ -19411,23 +19661,26 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @param {Object} [options] Options object
* @return {fabric.Path} thisArg
*/
- initialize: function(path, options) {
- options = options || { };
+ initialize: function (path, options) {
+ options = clone(options || {});
+ delete options.path;
this.callSuper('initialize', options);
- if (!path) {
- path = [];
- }
+ this._setPath(path || [], options);
+ },
+ /**
+ * @private
+ * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
+ * @param {Object} [options] Options object
+ */
+ _setPath: function (path, options) {
var fromArray = _toString.call(path) === '[object Array]';
this.path = fabric.util.makePathSimpler(
fromArray ? path : fabric.util.parsePath(path)
);
- if (!this.path) {
- return;
- }
- fabric.Polyline.prototype._setPositionDimensions.call(this, options);
+ fabric.Polyline.prototype._setPositionDimensions.call(this, options || {});
},
/**
@@ -20084,7 +20337,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
for (var i = 0, len = this._objects.length; i < len; i++) {
this._objects[i].render(ctx);
}
- this._drawClipPath(ctx);
+ this._drawClipPath(ctx, this.clipPath);
},
/**
@@ -20130,25 +20383,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
return this;
},
- /**
- * Realises the transform from this group onto the supplied object
- * i.e. it tells you what would happen if the supplied object was in
- * the group, and then the group was destroyed. It mutates the supplied
- * object.
- * Warning: this method is not useful anymore, it has been kept to no break the api.
- * is not used in the fabricJS codebase
- * this method will be reduced to using the utility.
- * @private
- * @deprecated
- * @param {fabric.Object} object
- * @param {Array} parentMatrix parent transformation
- * @return {fabric.Object} transformedObject
- */
- realizeTransform: function(object, parentMatrix) {
- fabric.util.addTransformToObject(object, parentMatrix);
- return object;
- },
-
/**
* Destroys a group (restoring state of its objects)
* @return {fabric.Group} thisArg
@@ -20163,6 +20397,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
return this._restoreObjectsState();
},
+ dispose: function () {
+ this.callSuper('dispose');
+ this.forEachObject(function (object) {
+ object.dispose && object.dispose();
+ });
+ this._objects = [];
+ },
+
/**
* make a group an active selection, remove the group from canvas
* the group has to be on canvas for this to work.
@@ -20326,11 +20568,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
});
return;
}
- fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
- fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) {
- var options = fabric.util.object.clone(object, true);
- options.clipPath = enlivedClipPath[0];
- delete options.objects;
+ fabric.util.enlivenObjects(objects, function (enlivenedObjects) {
+ var options = fabric.util.object.clone(object, true);
+ delete options.objects;
+ fabric.util.enlivenObjectEnlivables(object, options, function () {
callback && callback(new fabric.Group(enlivenedObjects, options, true));
});
});
@@ -20700,7 +20941,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/**
* Delete textures, reference to elements and eventually JSDOM cleanup
*/
- dispose: function() {
+ dispose: function () {
+ this.callSuper('dispose');
this.removeTexture(this.cacheKey);
this.removeTexture(this.cacheKey + '_filtered');
this._cacheContext = undefined;
@@ -21210,8 +21452,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
object.filters = filters || [];
fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) {
object.resizeFilter = resizeFilters[0];
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
- object.clipPath = enlivedProps[0];
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
var image = new fabric.Image(img, object);
callback(image, false);
});
@@ -21282,8 +21523,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @chainable
*/
straighten: function() {
- this.rotate(this._getAngleValueForStraighten());
- return this;
+ return this.rotate(this._getAngleValueForStraighten());
},
/**
@@ -21292,7 +21532,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @param {Function} [callbacks.onComplete] Invoked on completion
* @param {Function} [callbacks.onChange] Invoked on every step of animation
* @return {fabric.Object} thisArg
- * @chainable
*/
fxStraighten: function(callbacks) {
callbacks = callbacks || { };
@@ -21302,7 +21541,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
onChange = callbacks.onChange || empty,
_this = this;
- fabric.util.animate({
+ return fabric.util.animate({
+ target: this,
startValue: this.get('angle'),
endValue: this._getAngleValueForStraighten(),
duration: this.FX_DURATION,
@@ -21315,8 +21555,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
onComplete();
},
});
-
- return this;
}
});
@@ -21338,13 +21576,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
* @param {fabric.Object} object Object to straighten
* @return {fabric.Canvas} thisArg
- * @chainable
*/
fxStraightenObject: function (object) {
- object.fxStraighten({
+ return object.fxStraighten({
onChange: this.requestRenderAllBound
});
- return this;
}
});
@@ -25511,7 +25747,7 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
var additionalProps =
('fontFamily fontWeight fontSize text underline overline linethrough' +
' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' +
- ' direction path pathStartOffset pathSide').split(' ');
+ ' direction path pathStartOffset pathSide pathAlign').split(' ');
/**
* Text class
@@ -25540,7 +25776,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
'styles',
'path',
'pathStartOffset',
- 'pathSide'
+ 'pathSide',
+ 'pathAlign'
],
/**
@@ -25737,6 +25974,16 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
*/
pathSide: 'left',
+ /**
+ * How text is aligned to the path. This property determines
+ * the perpendicular position of each character relative to the path.
+ * (one of "baseline", "center", "ascender", "descender")
+ * This feature is in BETA, and its behavior may change
+ * @type String
+ * @default
+ */
+ pathAlign: 'baseline',
+
/**
* @private
*/
@@ -25880,6 +26127,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
/**
* Return a context for measurement of text string.
* if created it gets stored for reuse
+ * this is for internal use, please do not use it
+ * @private
* @param {String} text Text string
* @param {Object} [options] Options object
* @return {fabric.Text} thisArg
@@ -26051,7 +26300,20 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
* @param {String} [charStyle.fontStyle] Font style (italic|normal)
*/
_setTextStyles: function(ctx, charStyle, forMeasuring) {
- ctx.textBaseline = 'alphabetic';
+ ctx.textBaseline = 'alphabetical';
+ if (this.path) {
+ switch (this.pathAlign) {
+ case 'center':
+ ctx.textBaseline = 'middle';
+ break;
+ case 'ascender':
+ ctx.textBaseline = 'top';
+ break;
+ case 'descender':
+ ctx.textBaseline = 'bottom';
+ break;
+ }
+ }
ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
},
@@ -26516,16 +26778,17 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
path = this.path,
shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path,
isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1,
- drawingLeft;
-
+ drawingLeft, currentDirection = ctx.canvas.getAttribute('dir');
ctx.save();
+ if (currentDirection !== this.direction) {
+ ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
+ ctx.direction = isLtr ? 'ltr' : 'rtl';
+ ctx.textAlign = isLtr ? 'left' : 'right';
+ }
top -= lineHeight * this._fontSizeFraction / this.lineHeight;
if (shortCut) {
// render all the line in one pass without checking
// drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
- ctx.direction = isLtr ? 'ltr' : 'rtl';
- ctx.textAlign = isLtr ? 'left' : 'right';
this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight);
ctx.restore();
return;
@@ -26562,9 +26825,6 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
}
else {
drawingLeft = left;
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
- ctx.direction = isLtr ? 'ltr' : 'rtl';
- ctx.textAlign = isLtr ? 'left' : 'right';
this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight);
}
charsToRender = '';
@@ -26815,19 +27075,12 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
* @return {Number} Line width
*/
getLineWidth: function(lineIndex) {
- if (this.__lineWidths[lineIndex]) {
+ if (this.__lineWidths[lineIndex] !== undefined) {
return this.__lineWidths[lineIndex];
}
- var width, line = this._textLines[lineIndex], lineInfo;
-
- if (line === '') {
- width = 0;
- }
- else {
- lineInfo = this.measureLine(lineIndex);
- width = lineInfo.width;
- }
+ var lineInfo = this.measureLine(lineIndex);
+ var width = lineInfo.width;
this.__lineWidths[lineIndex] = width;
return width;
},
@@ -28924,7 +29177,13 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle);
}
else if (copiedStyle) {
- this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];
+ // this test is required in order to close #6841
+ // when a pasted buffer begins with a newline then
+ // this.styles[cursorLoc.lineIndex + i] and copiedStyle[0]
+ // may be undefined for some reason
+ if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) {
+ this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];
+ }
}
copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1);
}
diff --git a/dist/fabric.min.js b/dist/fabric.min.js
index 34c1ff02fd9..53e549826fc 100644
--- a/dist/fabric.min.js
+++ b/dist/fabric.min.js
@@ -1 +1 @@
-var jsdom,virtualWindow,fabric=fabric||{version:"4.6.0"};function resizeCanvasIfNeeded(t){var e=t.targetCanvas,i=e.width,r=e.height,n=t.destinationWidth,t=t.destinationHeight;i===n&&r===t||(e.width=n,e.height=t)}function copyGLTo2DDrawImage(t,e){var i=t.canvas,r=e.targetCanvas,t=r.getContext("2d");t.translate(0,r.height),t.scale(1,-1);e=i.height-r.height;t.drawImage(i,0,e,r.width,r.height,0,0,r.width,r.height)}function copyGLTo2DPutImageData(t,e){var i=e.targetCanvas.getContext("2d"),r=e.destinationWidth,n=e.destinationHeight,s=r*n*4,e=new Uint8Array(this.imageBuffer,0,s),s=new Uint8ClampedArray(this.imageBuffer,0,s);t.readPixels(0,0,r,n,t.RGBA,t.UNSIGNED_BYTE,e);n=new ImageData(s,r,n);i.putImageData(n,0,0)}"undefined"!=typeof exports?exports.fabric=fabric:"function"==typeof define&&define.amd&&define([],function(){return fabric}),"undefined"!=typeof document&&"undefined"!=typeof window?(document instanceof("undefined"!=typeof HTMLDocument?HTMLDocument:Document)?fabric.document=document:fabric.document=document.implementation.createHTMLDocument(""),fabric.window=window):(jsdom=require("jsdom"),virtualWindow=new jsdom.JSDOM(decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E"),{features:{FetchExternalResources:["img"]},resources:"usable"}).window,fabric.document=virtualWindow.document,fabric.jsdomImplForWrapper=require("jsdom/lib/jsdom/living/generated/utils").implForWrapper,fabric.nodeCanvas=require("jsdom/lib/jsdom/utils").Canvas,fabric.window=virtualWindow,DOMParser=fabric.window.DOMParser),fabric.isTouchSupported="ontouchstart"in fabric.window||"ontouchstart"in fabric.document||fabric.window&&fabric.window.navigator&&0_)for(var C=1,S=v.length;Ct[i-2].x?1:s.x===t[i-2].x?0:-1,h=s.y>t[i-2].y?1:s.y===t[i-2].y?0:-1),n.push(["L",s.x+c*e,s.y+h*e]),n},fabric.util.getPathSegmentsInfo=l,fabric.util.getBoundsOfCurve=v,fabric.util.getPointOnPath=function(t,e,i){i=i||l(t);for(var r=0;0/g,">")},graphemeSplit:function(t){for(var e,i=0,r=[],i=0;it.x&&this.y>t.y},gte:function(t){return this.x>=t.x&&this.y>=t.y},lerp:function(t,e){return void 0===e&&(e=.5),e=Math.max(Math.min(1,e),0),new i(this.x+(t.x-this.x)*e,this.y+(t.y-this.y)*e)},distanceFrom:function(t){var e=this.x-t.x,t=this.y-t.y;return Math.sqrt(e*e+t*t)},midPointFrom:function(t){return this.lerp(t)},min:function(t){return new i(Math.min(this.x,t.x),Math.min(this.y,t.y))},max:function(t){return new i(Math.max(this.x,t.x),Math.max(this.y,t.y))},toString:function(){return this.x+","+this.y},setXY:function(t,e){return this.x=t,this.y=e,this},setX:function(t){return this.x=t,this},setY:function(t){return this.y=t,this},setFromPoint:function(t){return this.x=t.x,this.y=t.y,this},swap:function(t){var e=this.x,i=this.y;this.x=t.x,this.y=t.y,t.x=e,t.y=i},clone:function(){return new i(this.x,this.y)}}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var a=t.fabric||(t.fabric={});function c(t){this.status=t,this.points=[]}a.Intersection?a.warn("fabric.Intersection is already defined"):(a.Intersection=c,a.Intersection.prototype={constructor:c,appendPoint:function(t){return this.points.push(t),this},appendPoints:function(t){return this.points=this.points.concat(t),this}},a.Intersection.intersectLineLine=function(t,e,i,r){var n,s=(r.x-i.x)*(t.y-i.y)-(r.y-i.y)*(t.x-i.x),o=(e.x-t.x)*(t.y-i.y)-(e.y-t.y)*(t.x-i.x),r=(r.y-i.y)*(e.x-t.x)-(r.x-i.x)*(e.y-t.y);return 0!=r?(i=o/r,0<=(r=s/r)&&r<=1&&0<=i&&i<=1?(n=new c("Intersection")).appendPoint(new a.Point(t.x+r*(e.x-t.x),t.y+r*(e.y-t.y))):n=new c):n=new c(0==s||0==o?"Coincident":"Parallel"),n},a.Intersection.intersectLinePolygon=function(t,e,i){for(var r,n,s=new c,o=i.length,a=0;a=t&&(i.x-=t),i.x<=-t&&(i.x+=t),i.y>=t&&(i.y-=t),i.y<=t&&(i.y+=t),i.x-=o.offsetX,i.y-=o.offsetY,i}function w(t){return t.flipX!==t.flipY}function O(t,e,i,r,n){0!==t[e]&&(r=n/t._getTransformedDimensions()[r]*t[i],t.set(i,r))}function k(t,e,i,r){var n,s=e.target,o=s._getTransformedDimensions(0,s.skewY),i=T(e,e.originX,e.originY,i,r),r=Math.abs(2*i.x)-o.x,i=s.skewX;r<2?n=0:(n=g(Math.atan2(r/s.scaleX,o.y/s.scaleY)),e.originX===c&&e.originY===u&&(n=-n),e.originX===l&&e.originY===h&&(n=-n),w(s)&&(n=-n));e=i!==n;return e&&(i=s._getTransformedDimensions().y,s.set("skewX",n),O(s,"skewY","scaleY","y",i)),e}function P(t,e,i,r){var n,s=e.target,o=s._getTransformedDimensions(s.skewX,0),i=T(e,e.originX,e.originY,i,r),r=Math.abs(2*i.y)-o.y,i=s.skewY;r<2?n=0:(n=g(Math.atan2(r/s.scaleY,o.x/s.scaleX)),e.originX===c&&e.originY===u&&(n=-n),e.originX===l&&e.originY===h&&(n=-n),w(s)&&(n=-n));e=i!==n;return e&&(i=s._getTransformedDimensions().x,s.set("skewY",n),O(s,"skewX","scaleX","x",i)),e}function E(t,e,i,r,n){var s=e.target,o=s.lockScalingX,a=s.lockScalingY,c=(n=n||{}).by,h=b(t,s),n=_(s,c,h),t=e.gestureScale;if(n)return!1;if(t)l=e.scaleX*t,u=e.scaleY*t;else{if(n=T(e,e.originX,e.originY,i,r),t="y"!==c?p(n.x):1,f="x"!==c?p(n.y):1,e.signX||(e.signX=t),e.signY||(e.signY=f),s.lockScalingFlip&&(e.signX!==t||e.signY!==f))return!1;var l,u,i=s._getTransformedDimensions();u=h&&!c?(r=Math.abs(n.x)+Math.abs(n.y),h=e.original,r=r/(Math.abs(i.x*h.scaleX/s.scaleX)+Math.abs(i.y*h.scaleY/s.scaleY)),l=h.scaleX*r,h.scaleY*r):(l=Math.abs(n.x*s.scaleX/i.x),Math.abs(n.y*s.scaleY/i.y)),y(e)&&(l*=2,u*=2),e.signX!==t&&"y"!==c&&(e.originX=d[e.originX],l*=-1,e.signX=t),e.signY!==f&&"x"!==c&&(e.originY=d[e.originY],u*=-1,e.signY=f)}var e=s.scaleX,f=s.scaleY;return c?("x"===c&&s.set("scaleX",l),"y"===c&&s.set("scaleY",u)):(o||s.set("scaleX",l),a||s.set("scaleY",u)),e!==s.scaleX||f!==s.scaleY}o.scaleCursorStyleHandler=function(t,e,i){var r=b(t,i),t="";return 0!==e.x&&0===e.y?t="x":0===e.x&&0!==e.y&&(t="y"),_(i,t,r)?"not-allowed":(e=m(i,e),n[e]+"-resize")},o.skewCursorStyleHandler=function(t,e,i){var r="not-allowed";return 0!==e.x&&i.lockSkewingY||0!==e.y&&i.lockSkewingX?r:(e=m(i,e)%4,s[e]+"-resize")},o.scaleSkewCursorStyleHandler=function(t,e,i){return t[i.canvas.altActionKey]?o.skewCursorStyleHandler(t,e,i):o.scaleCursorStyleHandler(t,e,i)},o.rotationWithSnapping=S("rotating",C(function(t,e,i,r){var n=e,s=n.target,o=s.translateToOriginPoint(s.getCenterPoint(),n.originX,n.originY);return!s.lockRotation&&(e=Math.atan2(n.ey-o.y,n.ex-o.x),r=Math.atan2(r-o.y,i-o.x),i=g(r-e+n.theta),0r.r2,o=(this.gradientTransform||fabric.iMatrix).concat(),a=-this.offsetX,c=-this.offsetY,h=!!e.additionalTransform,l="pixels"===this.gradientUnits?"userSpaceOnUse":"objectBoundingBox";if(n.sort(function(t,e){return t.offset-e.offset}),"objectBoundingBox"==l?(a/=t.width,c/=t.height):(a+=t.width/2,c+=t.height/2),"path"===t.type&&"percentage"!==this.gradientUnits&&(a-=t.pathOffset.x,c-=t.pathOffset.y),o[4]-=a,o[5]-=c,l='id="SVGID_'+this.id+'" gradientUnits="'+l+'"',l+=' gradientTransform="'+(h?e.additionalTransform+" ":"")+fabric.util.matrixToSVG(o)+'" ',"linear"===this.type?i=["\n']:"radial"===this.type&&(i=["\n']),"radial"===this.type){if(s)for((n=n.concat()).reverse(),f=0,d=n.length;f\n')}return i.push("linear"===this.type?"\n":"\n"),i.join("")},toLive:function(t){var e,i,r,n=fabric.util.object.clone(this.coords);if(this.type){for("linear"===this.type?e=t.createLinearGradient(n.x1,n.y1,n.x2,n.y2):"radial"===this.type&&(e=t.createRadialGradient(n.x1,n.y1,n.r1,n.x2,n.y2,n.r2)),i=0,r=this.colorStops.length;i\n\n\n'},setOptions:function(t){for(var e in t)this[e]=t[e]},toLive:function(t){var e=this.source;if(!e)return"";if(void 0!==e.src){if(!e.complete)return"";if(0===e.naturalWidth||0===e.naturalHeight)return""}return t.createPattern(e,this.repeat)}})}(),function(t){"use strict";var o=t.fabric||(t.fabric={}),a=o.util.toFixed;o.Shadow?o.warn("fabric.Shadow is already defined."):(o.Shadow=o.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,nonScaling:!1,initialize:function(t){for(var e in t="string"==typeof t?this._parseShadow(t):t)this[e]=t[e];this.id=o.Object.__uid++},_parseShadow:function(t){var e=t.trim(),t=o.Shadow.reOffsetsAndBlur.exec(e)||[];return{color:(e.replace(o.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)").trim(),offsetX:parseFloat(t[1],10)||0,offsetY:parseFloat(t[2],10)||0,blur:parseFloat(t[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(t){var e=40,i=40,r=o.Object.NUM_FRACTION_DIGITS,n=o.util.rotateVector({x:this.offsetX,y:this.offsetY},o.util.degreesToRadians(-t.angle)),s=new o.Color(this.color);return t.width&&t.height&&(e=100*a((Math.abs(n.x)+this.blur)/t.width,r)+20,i=100*a((Math.abs(n.y)+this.blur)/t.height,r)+20),t.flipX&&(n.x*=-1),t.flipY&&(n.y*=-1),'\n\t\n\t\n\t\n\t\n\t\n\t\t\n\t\t\n\t\n\n'},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY,affectStroke:this.affectStroke,nonScaling:this.nonScaling};var e={},i=o.Shadow.prototype;return["color","blur","offsetX","offsetY","affectStroke","nonScaling"].forEach(function(t){this[t]!==i[t]&&(e[t]=this[t])},this),e}}),o.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(\d+(?:\.\d*)?(?:px)?)?(?:\s?|$)(?:$|\s)/)}("undefined"!=typeof exports?exports:this),function(){"use strict";var n,t,h,a,s,o,i,r,e;fabric.StaticCanvas?fabric.warn("fabric.StaticCanvas is already defined."):(n=fabric.util.object.extend,t=fabric.util.getElementOffset,h=fabric.util.removeFromArray,a=fabric.util.toFixed,s=fabric.util.transformPoint,o=fabric.util.invertTransform,i=fabric.util.getNodeCanvas,r=fabric.util.createCanvasElement,e=new Error("Could not initialize `canvas` element"),fabric.StaticCanvas=fabric.util.createClass(fabric.CommonMethods,{initialize:function(t,e){e=e||{},this.renderAndResetBound=this.renderAndReset.bind(this),this.requestRenderAllBound=this.requestRenderAll.bind(this),this._initStatic(t,e)},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!1,renderOnAddRemove:!0,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:fabric.iMatrix.concat(),backgroundVpt:!0,overlayVpt:!0,enableRetinaScaling:!0,vptCoords:{},skipOffscreen:!0,clipPath:void 0,_initStatic:function(t,e){var i=this.requestRenderAllBound;this._objects=[],this._createLowerCanvas(t),this._initOptions(e),this.interactive||this._initRetinaScaling(),e.overlayImage&&this.setOverlayImage(e.overlayImage,i),e.backgroundImage&&this.setBackgroundImage(e.backgroundImage,i),e.backgroundColor&&this.setBackgroundColor(e.backgroundColor,i),e.overlayColor&&this.setOverlayColor(e.overlayColor,i),this.calcOffset()},_isRetinaScaling:function(){return 1!==fabric.devicePixelRatio&&this.enableRetinaScaling},getRetinaScaling:function(){return this._isRetinaScaling()?fabric.devicePixelRatio:1},_initRetinaScaling:function(){var t;this._isRetinaScaling()&&(t=fabric.devicePixelRatio,this.__initRetinaScaling(t,this.lowerCanvasEl,this.contextContainer),this.upperCanvasEl&&this.__initRetinaScaling(t,this.upperCanvasEl,this.contextTop))},__initRetinaScaling:function(t,e,i){e.setAttribute("width",this.width*t),e.setAttribute("height",this.height*t),i.scale(t,t)},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(t,e,i){return this.__setBgOverlayImage("overlayImage",t,e,i)},setBackgroundImage:function(t,e,i){return this.__setBgOverlayImage("backgroundImage",t,e,i)},setOverlayColor:function(t,e){return this.__setBgOverlayColor("overlayColor",t,e)},setBackgroundColor:function(t,e){return this.__setBgOverlayColor("backgroundColor",t,e)},__setBgOverlayImage:function(r,t,n,s){return"string"==typeof t?fabric.util.loadImage(t,function(t,e){var i;t&&(i=new fabric.Image(t,s),(this[r]=i).canvas=this),n&&n(t,e)},this,s&&s.crossOrigin):(s&&t.setOptions(s),(this[r]=t)&&(t.canvas=this),n&&n(t,!1)),this},__setBgOverlayColor:function(t,e,i){return this[t]=e,this._initGradient(e,t),this._initPattern(e,t,i),this},_createCanvasElement:function(){var t=r();if(!t)throw e;if(t.style||(t.style={}),void 0===t.getContext)throw e;return t},_initOptions:function(t){var e=this.lowerCanvasEl;this._setOptions(t),this.width=this.width||parseInt(e.width,10)||0,this.height=this.height||parseInt(e.height,10)||0,this.lowerCanvasEl.style&&(e.width=this.width,e.height=this.height,e.style.width=this.width+"px",e.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(t){t&&t.getContext?this.lowerCanvasEl=t:this.lowerCanvasEl=fabric.util.getById(t)||this._createCanvasElement(),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this._originalCanvasStyle=this.lowerCanvasEl.style,this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(t,e){return this.setDimensions({width:t},e)},setHeight:function(t,e){return this.setDimensions({height:t},e)},setDimensions:function(t,e){var i,r;for(r in e=e||{},t)i=t[r],e.cssOnly||(this._setBackstoreDimension(r,t[r]),i+="px",this.hasLostContext=!0),e.backstoreOnly||this._setCssDimension(r,i);return this._isCurrentlyDrawing&&this.freeDrawingBrush&&this.freeDrawingBrush._setBrushStyles(),this._initRetinaScaling(),this.calcOffset(),e.cssOnly||this.requestRenderAll(),this},_setBackstoreDimension:function(t,e){return this.lowerCanvasEl[t]=e,this.upperCanvasEl&&(this.upperCanvasEl[t]=e),this.cacheCanvasEl&&(this.cacheCanvasEl[t]=e),this[t]=e,this},_setCssDimension:function(t,e){return this.lowerCanvasEl.style[t]=e,this.upperCanvasEl&&(this.upperCanvasEl.style[t]=e),this.wrapperEl&&(this.wrapperEl.style[t]=e),this},getZoom:function(){return this.viewportTransform[0]},setViewportTransform:function(t){var e,i,r,n=this._activeObject,s=this.backgroundImage,o=this.overlayImage;for(this.viewportTransform=t,i=0,r=this._objects.length;i\n'),this._setSVGBgOverlayColor(i,"background"),this._setSVGBgOverlayImage(i,"backgroundImage",e),this._setSVGObjects(i,e),this.clipPath&&i.push("\n"),this._setSVGBgOverlayColor(i,"overlay"),this._setSVGBgOverlayImage(i,"overlayImage",e),i.push(""),i.join("")},_setSVGPreamble:function(t,e){e.suppressPreamble||t.push('\n','\n')},_setSVGHeader:function(t,e){var i,r=e.width||this.width,n=e.height||this.height,s='viewBox="0 0 '+this.width+" "+this.height+'" ',o=fabric.Object.NUM_FRACTION_DIGITS;e.viewBox?s='viewBox="'+e.viewBox.x+" "+e.viewBox.y+" "+e.viewBox.width+" "+e.viewBox.height+'" ':this.svgViewportTransformation&&(i=this.viewportTransform,s='viewBox="'+a(-i[4]/i[0],o)+" "+a(-i[5]/i[3],o)+" "+a(this.width/i[0],o)+" "+a(this.height/i[3],o)+'" '),t.push(""),i.join("")},_setSVGPreamble:function(t,e){e.suppressPreamble||t.push('\n','\n')},_setSVGHeader:function(t,e){var i,r=e.width||this.width,n=e.height||this.height,s='viewBox="0 0 '+this.width+" "+this.height+'" ',o=fabric.Object.NUM_FRACTION_DIGITS;e.viewBox?s='viewBox="'+e.viewBox.x+" "+e.viewBox.y+" "+e.viewBox.width+" "+e.viewBox.height+'" ':this.svgViewportTransformation&&(i=this.viewportTransform,s='viewBox="'+a(-i[4]/i[0],o)+" "+a(-i[5]/i[3],o)+" "+a(this.width/i[0],o)+" "+a(this.height/i[3],o)+'" '),t.push("