diff --git a/index.js b/index.js
index 80adf8175ec..dc074638330 100644
--- a/index.js
+++ b/index.js
@@ -5,11 +5,6 @@ import './src/mixins/collection.mixin';
import './src/mixins/shared_methods.mixin';
import './src/util/misc/misc';
// import './src/util/named_accessors.mixin'; i would imagine dead forever or proper setters/getters
-import './src/util/lang_class';
-import './src/util/dom_misc';
-import './src/util/animate'; // optional animation
-import './src/util/animate_color'; // optional animation
-import './src/util/anim_ease'; // optional easing
import './src/parser'; // optional parser
import './src/point.class';
import './src/intersection.class';
diff --git a/src/cache.ts b/src/cache.ts
index ff56089cbf8..6abad7709bb 100644
--- a/src/cache.ts
+++ b/src/cache.ts
@@ -1,3 +1,4 @@
+import { config } from './config';
export class Cache {
/**
@@ -44,6 +45,21 @@ export class Cache {
}
}
+ /**
+ * Given current aspect ratio, determines the max width and height that can
+ * respect the total allowed area for the cache.
+ * @memberOf fabric.util
+ * @param {number} ar aspect ratio
+ * @return {number[]} Limited dimensions X and Y
+ */
+ limitDimsByArea(ar: number) {
+ const { perfLimitSizeTotal } = config;
+ const roughWidth = Math.sqrt(perfLimitSizeTotal * ar);
+ // we are not returning a point on purpose, to avoid circular dependencies
+ // this is an internal utility
+ return [Math.floor(roughWidth), Math.floor(perfLimitSizeTotal / roughWidth)];
+ }
+
/**
* This object contains the result of arc to bezier conversion for faster retrieving if the same arc needs to be converted again.
* It was an internal variable, is accessible since version 2.3.4
diff --git a/src/canvas.class.ts b/src/canvas.class.ts
index 5ad7f28a555..980ac09a6c4 100644
--- a/src/canvas.class.ts
+++ b/src/canvas.class.ts
@@ -1044,19 +1044,20 @@ import { Point } from './point.class';
* @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
*/
_createUpperCanvas: function () {
- var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''),
- lowerCanvasEl = this.lowerCanvasEl, upperCanvasEl = this.upperCanvasEl;
+ var lowerCanvasEl = this.lowerCanvasEl, upperCanvasEl = this.upperCanvasEl;
- // there is no need to create a new upperCanvas element if we have already one.
- if (upperCanvasEl) {
- upperCanvasEl.className = '';
- }
- else {
+ // if there is no upperCanvas (most common case) we create one.
+ if (!upperCanvasEl) {
upperCanvasEl = this._createCanvasElement();
this.upperCanvasEl = upperCanvasEl;
}
- fabric.util.addClass(upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
- this.upperCanvasEl.setAttribute('data-fabric', 'top');
+ // we assign the same classname of the lowerCanvas
+ upperCanvasEl.className = lowerCanvasEl.className;
+ // but then we remove the lower-canvas specific className
+ upperCanvasEl.classList.remove('lower-canvas');
+ // we add the specific upper-canvas class
+ upperCanvasEl.classList.add('upper-canvas');
+ upperCanvasEl.setAttribute('data-fabric', 'top');
this.wrapperEl.appendChild(upperCanvasEl);
this._copyCanvasStyle(lowerCanvasEl, upperCanvasEl);
@@ -1082,9 +1083,9 @@ import { Point } from './point.class';
if (this.wrapperEl) {
return;
}
- this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
- 'class': this.containerClass
- });
+ const container = fabric.document.createElement('div');
+ container.classList.add(this.containerClass);
+ this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, container);
this.wrapperEl.setAttribute('data-fabric', 'wrapper');
fabric.util.setStyle(this.wrapperEl, {
width: this.width + 'px',
diff --git a/src/constants.ts b/src/constants.ts
index f6c28147cb5..59ac5f12ba0 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,8 +1,9 @@
import { TMat2D } from "./typedefs";
export { version as VERSION } from '../package.json';
-export const noop = () => {};
+export function noop() {};
export const halfPI = Math.PI / 2;
+export const twoMathPi = Math.PI * 2;
export const PiBy180 = Math.PI / 180;
export const iMatrix = Object.freeze([1, 0, 0, 1, 0, 0]) as TMat2D;
export const DEFAULT_SVG_FONT_SIZE = 16;
diff --git a/src/parser/parseSVGDocument.ts b/src/parser/parseSVGDocument.ts
index b65751a7b86..e6eb9e9ed1a 100644
--- a/src/parser/parseSVGDocument.ts
+++ b/src/parser/parseSVGDocument.ts
@@ -31,7 +31,7 @@ export function parseSVGDocument(doc, callback, reviver, parsingOptions) {
}
parseUseDirectives(doc);
- let svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
+ let svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = Array.from(doc.getElementsByTagName('*'));
options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
options.svgUid = svgUid;
options.signal = parsingOptions && parsingOptions.signal;
@@ -61,7 +61,7 @@ export function parseSVGDocument(doc, callback, reviver, parsingOptions) {
return el.nodeName.replace('svg:', '') === 'clipPath';
}).forEach(function (el) {
const id = el.getAttribute('id');
- localClipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function (el) {
+ localClipPaths[id] = Array.from(el.getElementsByTagName('*')).filter(function (el) {
return svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', ''));
});
});
diff --git a/src/point.class.ts b/src/point.class.ts
index c868a5e368a..a9a3c82220d 100644
--- a/src/point.class.ts
+++ b/src/point.class.ts
@@ -1,5 +1,7 @@
import { fabric } from '../HEADER';
import { TMat2D, TRadian } from './typedefs';
+import { sin } from './util/misc/sin';
+import { cos } from './util/misc/cos';
export interface IPoint {
x: number
@@ -350,16 +352,22 @@ export class Point {
/**
* Rotates `point` around `origin` with `radians`
- * WARNING: this is probably a source of circular dependency.
- * evaluate what to do when importing rotateVector directly from the file
* @static
* @memberOf fabric.util
* @param {Point} origin The origin of the rotation
* @param {TRadian} radians The radians of the angle for the rotation
* @return {Point} The new rotated point
*/
- rotate(origin: Point, radians: TRadian): Point {
- return fabric.util.rotateVector(this.subtract(origin), radians).add(origin);
+ rotate(radians: TRadian, origin: Point = originZero): Point {
+ // TODO benchmark and verify the add and subtract how much cost
+ // and then in case early return if no origin is passed
+ const sinus = sin(radians), cosinus = cos(radians);
+ const p = this.subtract(origin);
+ const rotated = new Point(
+ p.x * cosinus - p.y * sinus,
+ p.x * sinus + p.y * cosinus,
+ );
+ return rotated.add(origin);
}
/**
@@ -378,4 +386,6 @@ export class Point {
}
}
+const originZero = new Point(0, 0);
+
fabric.Point = Point;
diff --git a/src/shadow.class.ts b/src/shadow.class.ts
index 3c641573d1e..10bad11f110 100644
--- a/src/shadow.class.ts
+++ b/src/shadow.class.ts
@@ -2,6 +2,7 @@
import { Color } from "./color";
import { config } from "./config";
+import { Point } from "./point.class";
(function(global) {
var fabric = global.fabric || (global.fabric = { }),
@@ -119,7 +120,7 @@ import { config } from "./config";
toSVG: function(object) {
var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS,
offset = fabric.util.rotateVector(
- { x: this.offsetX, y: this.offsetY },
+ new Point(this.offsetX, this.offsetY),
fabric.util.degreesToRadians(-object.angle)),
BLUR_BOX = 20, color = new Color(this.color);
diff --git a/src/shapes/image.class.ts b/src/shapes/image.class.ts
index 8962a6f1bfc..99743e8693c 100644
--- a/src/shapes/image.class.ts
+++ b/src/shapes/image.class.ts
@@ -163,6 +163,7 @@
this._element = element;
this._originalElement = element;
this._initConfig(options);
+ element.classList.add(fabric.Image.CSS_CANVAS);
if (this.filters.length !== 0) {
this.applyFilters();
}
@@ -477,7 +478,7 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx) {
- fabric.util.setImageSmoothing(ctx, this.imageSmoothing);
+ ctx.imageSmoothingEnabled = this.imageSmoothing;
if (this.isMoving !== true && this.resizeFilter && this._needsResize()) {
this.applyResizeFilters();
}
@@ -491,7 +492,7 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
drawCacheOnCanvas: function(ctx) {
- fabric.util.setImageSmoothing(ctx, this.imageSmoothing);
+ ctx.imageSmoothingEnabled = this.imageSmoothing;
fabric.Object.prototype.drawCacheOnCanvas.call(this, ctx);
},
@@ -557,8 +558,7 @@
* @param {Object} [options] Options object
*/
_initElement: function(element, options) {
- this.setElement(fabric.util.getById(element), options);
- fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
+ this.setElement(fabric.document.getElementById(element) || element, options);
},
/**
diff --git a/src/shapes/object.class.ts b/src/shapes/object.class.ts
index 8370146e374..7f95ed976ff 100644
--- a/src/shapes/object.class.ts
+++ b/src/shapes/object.class.ts
@@ -1,10 +1,11 @@
//@ts-nocheck
-
+import { cache } from '../cache';
import { config } from '../config';
import { VERSION } from '../constants';
import { Point } from '../point.class';
import { capValue } from '../util/misc/capValue';
import { pick } from '../util/misc/pick';
+import { runningAnimations } from '../util/animation_registry';
(function(global) {
var fabric = global.fabric || (global.fabric = { }),
@@ -684,10 +685,9 @@ import { pick } from '../util/misc/pick';
* @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache
*/
_limitCacheSize: function(dims) {
- var perfLimitSizeTotal = config.perfLimitSizeTotal,
- width = dims.width, height = dims.height,
+ var width = dims.width, height = dims.height,
max = config.maxCacheSideLimit, min = config.minCacheSideLimit;
- if (width <= max && height <= max && width * height <= perfLimitSizeTotal) {
+ if (width <= max && height <= max && width * height <= config.perfLimitSizeTotal) {
if (width < min) {
dims.width = min;
}
@@ -696,9 +696,9 @@ import { pick } from '../util/misc/pick';
}
return dims;
}
- var ar = width / height, limitedDims = fabric.util.limitDimsByArea(ar, perfLimitSizeTotal),
- x = capValue(min, limitedDims.x, max),
- y = capValue(min, limitedDims.y, max);
+ var ar = width / height, [limX, limY] = cache.limitDimsByArea(ar),
+ x = capValue(min, limX, max),
+ y = capValue(min, limY, max);
if (width > x) {
dims.zoomX /= width / x;
dims.width = x;
@@ -1904,8 +1904,10 @@ import { pick } from '../util/misc/pick';
* override if necessary to dispose artifacts such as `clipPath`
*/
dispose: function () {
- if (fabric.runningAnimations) {
- fabric.runningAnimations.cancelByTarget(this);
+ // todo verify this.
+ // runningAnimations is always truthy
+ if (runningAnimations) {
+ runningAnimations.cancelByTarget(this);
}
}
});
diff --git a/src/static_canvas.class.ts b/src/static_canvas.class.ts
index a2b65cc3ae9..ba2a0e934cb 100644
--- a/src/static_canvas.class.ts
+++ b/src/static_canvas.class.ts
@@ -310,14 +310,14 @@ import { pick } from './util/misc/pick';
this.lowerCanvasEl = canvasEl;
}
else {
- this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
+ this.lowerCanvasEl = fabric.document.getElementById(canvasEl) || canvasEl || this._createCanvasElement();
}
if (this.lowerCanvasEl.hasAttribute('data-fabric')) {
/* _DEV_MODE_START_ */
throw new Error('fabric.js: trying to initialize a canvas that has already been initialized');
/* _DEV_MODE_END_ */
}
- fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
+ this.lowerCanvasEl.classList.add('lower-canvas');
this.lowerCanvasEl.setAttribute('data-fabric', 'main');
if (this.interactive) {
this._originalCanvasStyle = this.lowerCanvasEl.style.cssText;
@@ -752,7 +752,7 @@ import { pick } from './util/misc/pick';
this.cancelRequestedRender();
this.calcViewportBoundaries();
this.clearContext(ctx);
- fabric.util.setImageSmoothing(ctx, this.imageSmoothingEnabled);
+ ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;
ctx.patternQuality = 'best';
this.fire('before:render', { ctx: ctx, });
this._renderBackground(ctx);
diff --git a/src/util/anim_ease.ts b/src/util/anim_ease.ts
index 52d9c5187c5..0361060e644 100644
--- a/src/util/anim_ease.ts
+++ b/src/util/anim_ease.ts
@@ -1,399 +1,328 @@
-//@ts-nocheck
-(function(global) {
- var fabric = global.fabric;
- function normalize(a, c, p, s) {
- if (a < Math.abs(c)) {
- a = c;
- s = p / 4;
+import { twoMathPi, halfPI } from "../constants";
+
+type TEasingFunction = (
+ currentTime: number,
+ startValue: number,
+ byValue: number,
+ duration: number) => number;
+
+/**
+ * Easing functions
+ * See Easing Equations by Robert Penner
+ * @namespace fabric.util.ease
+ */
+
+const normalize = (a: number, c: number, p: number, s: number) => {
+ if (a < Math.abs(c)) {
+ a = c;
+ s = p / 4;
+ }
+ else {
+ //handle the 0/0 case:
+ if (c === 0 && a === 0) {
+ s = p / twoMathPi * Math.asin(1);
}
else {
- //handle the 0/0 case:
- if (c === 0 && a === 0) {
- s = p / (2 * Math.PI) * Math.asin(1);
- }
- else {
- s = p / (2 * Math.PI) * Math.asin(c / a);
- }
+ s = p / twoMathPi * Math.asin(c / a);
}
- return { a: a, c: c, p: p, s: s };
- }
-
- function elastic(opts, t, d) {
- return opts.a *
- Math.pow(2, 10 * (t -= 1)) *
- Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
}
-
- /**
- * Cubic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutCubic(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t + 1) + b;
+ return { a, c, p, s };
+}
+
+const elastic = (a: number, s: number, p: number, t: number, d: number): number => a *
+ Math.pow(2, 10 * (t -= 1)) *
+ Math.sin((t * d - s) * twoMathPi / p);
+
+/**
+ * Cubic easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutCubic:TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 2 + 1) + b;
+
+/**
+ * Cubic easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutCubic:TEasingFunction = (t, b, c, d) => {
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * t ** 3 + b;
}
-
- /**
- * Cubic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutCubic(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return c / 2 * t * t * t + b;
- }
- return c / 2 * ((t -= 2) * t * t + 2) + b;
+ return c / 2 * ((t -= 2) * t ** 2 + 2) + b;
+}
+
+/**
+ * Quartic easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInQuart:TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 3 + b;
+
+/**
+ * Quartic easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutQuart:TEasingFunction = (t, b, c, d) => -c * ((t = t / d - 1) * t ** 3 - 1) + b;
+
+/**
+ * Quartic easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutQuart:TEasingFunction = (t, b, c, d) => {
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * t ** 4 + b;
}
-
- /**
- * Quartic easing in
- * @memberOf fabric.util.ease
- */
- function easeInQuart(t, b, c, d) {
- return c * (t /= d) * t * t * t + b;
+ return -c / 2 * ((t -= 2) * t ** 3 - 2) + b;
+}
+
+/**
+ * Quintic easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInQuint:TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 4 + b;
+
+/**
+ * Quintic easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutQuint:TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 4 + 1) + b;
+
+/**
+ * Quintic easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutQuint:TEasingFunction = (t, b, c, d) => {
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * t ** 5 + b;
}
-
- /**
- * Quartic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutQuart(t, b, c, d) {
- return -c * ((t = t / d - 1) * t * t * t - 1) + b;
- }
-
- /**
- * Quartic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutQuart(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return c / 2 * t * t * t * t + b;
- }
- return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
- }
-
- /**
- * Quintic easing in
- * @memberOf fabric.util.ease
- */
- function easeInQuint(t, b, c, d) {
- return c * (t /= d) * t * t * t * t + b;
- }
-
- /**
- * Quintic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutQuint(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
+ return c / 2 * ((t -= 2) * t ** 4 + 2) + b;
+}
+
+/**
+ * Sinusoidal easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInSine:TEasingFunction = (t, b, c, d) => -c * Math.cos(t / d * halfPI) + c + b;
+
+/**
+ * Sinusoidal easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutSine:TEasingFunction = (t, b, c, d) => c * Math.sin(t / d * halfPI) + b;
+
+/**
+ * Sinusoidal easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutSine:TEasingFunction = (t, b, c, d) => -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
+
+/**
+ * Exponential easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInExpo:TEasingFunction = (t, b, c, d) => (t === 0) ? b : c * 2 ** (10 * (t / d - 1)) + b;
+
+/**
+ * Exponential easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutExpo:TEasingFunction = (t, b, c, d) => (t === d) ? b + c : c * -(2 ** (-10 * t / d) + 1) + b;
+
+/**
+ * Exponential easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutExpo:TEasingFunction = (t, b, c, d) => {
+ if (t === 0) {
+ return b;
}
-
- /**
- * Quintic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutQuint(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return c / 2 * t * t * t * t * t + b;
- }
- return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
+ if (t === d) {
+ return b + c;
}
-
- /**
- * Sinusoidal easing in
- * @memberOf fabric.util.ease
- */
- function easeInSine(t, b, c, d) {
- return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * 2 ** (10 * (t - 1)) + b;
}
-
- /**
- * Sinusoidal easing out
- * @memberOf fabric.util.ease
- */
- function easeOutSine(t, b, c, d) {
- return c * Math.sin(t / d * (Math.PI / 2)) + b;
+ return c / 2 * -(2 ** (-10 * --t) + 2) + b;
+}
+
+/**
+ * Circular easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInCirc:TEasingFunction = (t, b, c, d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
+
+/**
+ * Circular easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutCirc:TEasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
+
+/**
+ * Circular easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutCirc:TEasingFunction = (t, b, c, d) => {
+ t /= d / 2;
+ if (t < 1) {
+ return -c / 2 * (Math.sqrt(1 - t ** 2) - 1) + b;
}
-
- /**
- * Sinusoidal easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutSine(t, b, c, d) {
- return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
+ return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
+}
+
+/**
+ * Elastic easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInElastic:TEasingFunction = (t, b, c, d) => {
+ const s = 1.70158, a = c;
+ let p = 0;
+ if (t === 0) {
+ return b;
}
-
- /**
- * Exponential easing in
- * @memberOf fabric.util.ease
- */
- function easeInExpo(t, b, c, d) {
- return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
+ t /= d;
+ if (t === 1) {
+ return b + c;
}
-
- /**
- * Exponential easing out
- * @memberOf fabric.util.ease
- */
- function easeOutExpo(t, b, c, d) {
- return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
+ if (!p) {
+ p = d * 0.3;
}
-
- /**
- * Exponential easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutExpo(t, b, c, d) {
- if (t === 0) {
- return b;
- }
- if (t === d) {
- return b + c;
- }
- t /= d / 2;
- if (t < 1) {
- return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
- }
- return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ const { a: normA, s: normS, p: normP } = normalize(a, c, p, s);
+ return -elastic(normA, normS, normP, t, d) + b;
+}
+
+/**
+ * Elastic easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutElastic:TEasingFunction = (t, b, c, d) => {
+ const s = 1.70158, a = c;
+ let p = 0;
+ if (t === 0) {
+ return b;
}
-
- /**
- * Circular easing in
- * @memberOf fabric.util.ease
- */
- function easeInCirc(t, b, c, d) {
- return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
+ t /= d;
+ if (t === 1) {
+ return b + c;
}
-
- /**
- * Circular easing out
- * @memberOf fabric.util.ease
- */
- function easeOutCirc(t, b, c, d) {
- return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
+ if (!p) {
+ p = d * 0.3;
}
-
- /**
- * Circular easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutCirc(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
- }
- return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
+ const { a: normA, s: normS, p: normP, c: normC } = normalize(a, c, p, s);
+ return normA * 2 ** (-10 * t) * Math.sin((t * d - normS) * (twoMathPi) / normP ) + normC + b;
+}
+
+/**
+ * Elastic easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutElastic:TEasingFunction = (t, b, c, d) => {
+ const s = 1.70158, a = c;
+ let p = 0;
+ if (t === 0) {
+ return b;
}
-
- /**
- * Elastic easing in
- * @memberOf fabric.util.ease
- */
- function easeInElastic(t, b, c, d) {
- var s = 1.70158, p = 0, a = c;
- if (t === 0) {
- return b;
- }
- t /= d;
- if (t === 1) {
- return b + c;
- }
- if (!p) {
- p = d * 0.3;
- }
- var opts = normalize(a, c, p, s);
- return -elastic(opts, t, d) + b;
+ t /= d / 2;
+ if (t === 2) {
+ return b + c;
}
-
- /**
- * Elastic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutElastic(t, b, c, d) {
- var s = 1.70158, p = 0, a = c;
- if (t === 0) {
- return b;
- }
- t /= d;
- if (t === 1) {
- return b + c;
- }
- if (!p) {
- p = d * 0.3;
- }
- var opts = normalize(a, c, p, s);
- return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;
+ if (!p) {
+ p = d * (0.3 * 1.5);
}
-
- /**
- * Elastic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutElastic(t, b, c, d) {
- var s = 1.70158, p = 0, a = c;
- if (t === 0) {
- return b;
- }
- t /= d / 2;
- if (t === 2) {
- return b + c;
- }
- if (!p) {
- p = d * (0.3 * 1.5);
- }
- var opts = normalize(a, c, p, s);
- if (t < 1) {
- return -0.5 * elastic(opts, t, d) + b;
- }
- return opts.a * Math.pow(2, -10 * (t -= 1)) *
- Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;
+ const { a: normA, s: normS, p: normP, c: normC } = normalize(a, c, p, s);
+ if (t < 1) {
+ return -0.5 * elastic(normA, normS, normP, t, d) + b;
}
-
- /**
- * Backwards easing in
- * @memberOf fabric.util.ease
- */
- function easeInBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- return c * (t /= d) * t * ((s + 1) * t - s) + b;
+ return normA * Math.pow(2, -10 * (t -= 1)) *
+ Math.sin((t * d - normS) * (twoMathPi) / normP ) * 0.5 + normC + b;
+}
+
+/**
+ * Backwards easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInBack:TEasingFunction = (t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b;
+
+/**
+ * Backwards easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutBack:TEasingFunction = (t, b, c, d, s = 1.70158) => c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
+
+/**
+ * Backwards easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutBack:TEasingFunction = (t, b, c, d, s = 1.70158) => {
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
}
-
- /**
- * Backwards easing out
- * @memberOf fabric.util.ease
- */
- function easeOutBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
+ return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
+}
+
+/**
+ * Bouncing easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutBounce:TEasingFunction = (t, b, c, d) => {
+ if ((t /= d) < (1 / 2.75)) {
+ return c * (7.5625 * t * t) + b;
}
-
- /**
- * Backwards easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- t /= d / 2;
- if (t < 1) {
- return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
- }
- return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
+ else if (t < (2 / 2.75)) {
+ return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
}
-
- /**
- * Bouncing easing in
- * @memberOf fabric.util.ease
- */
- function easeInBounce(t, b, c, d) {
- return c - easeOutBounce (d - t, 0, c, d) + b;
+ else if (t < (2.5 / 2.75)) {
+ return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
-
- /**
- * Bouncing easing out
- * @memberOf fabric.util.ease
- */
- function easeOutBounce(t, b, c, d) {
- if ((t /= d) < (1 / 2.75)) {
- return c * (7.5625 * t * t) + b;
- }
- else if (t < (2 / 2.75)) {
- return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
- }
- else if (t < (2.5 / 2.75)) {
- return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
- }
- else {
- return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
- }
+ else {
+ return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
}
-
- /**
- * Bouncing easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutBounce(t, b, c, d) {
- if (t < d / 2) {
- return easeInBounce (t * 2, 0, c, d) * 0.5 + b;
- }
- return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
+}
+
+/**
+ * Bouncing easing in
+ * @memberOf fabric.util.ease
+ */
+ export const easeInBounce:TEasingFunction = (t, b, c, d) => c - easeOutBounce(d - t, 0, c, d) + b;
+
+/**
+ * Bouncing easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutBounce:TEasingFunction = (t, b, c, d) =>
+ t < d / 2 ?
+ easeInBounce(t * 2, 0, c, d) * 0.5 + b :
+ easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
+
+
+/**
+ * Quadratic easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInQuad:TEasingFunction = (t, b, c, d) => c * (t /= d) * t + b;
+
+/**
+ * Quadratic easing out
+ * @memberOf fabric.util.ease
+ */
+export const easeOutQuad:TEasingFunction = (t, b, c, d) => -c * (t /= d) * (t - 2) + b;
+
+/**
+ * Quadratic easing in and out
+ * @memberOf fabric.util.ease
+ */
+export const easeInOutQuad:TEasingFunction = (t, b, c, d) => {
+ t /= (d / 2);
+ if (t < 1) {
+ return c / 2 * t ** 2 + b;
}
-
- /**
- * Easing functions
- * See Easing Equations by Robert Penner
- * @namespace fabric.util.ease
- */
- fabric.util.ease = {
-
- /**
- * Quadratic easing in
- * @memberOf fabric.util.ease
- */
- easeInQuad: function(t, b, c, d) {
- return c * (t /= d) * t + b;
- },
-
- /**
- * Quadratic easing out
- * @memberOf fabric.util.ease
- */
- easeOutQuad: function(t, b, c, d) {
- return -c * (t /= d) * (t - 2) + b;
- },
-
- /**
- * Quadratic easing in and out
- * @memberOf fabric.util.ease
- */
- easeInOutQuad: function(t, b, c, d) {
- t /= (d / 2);
- if (t < 1) {
- return c / 2 * t * t + b;
- }
- return -c / 2 * ((--t) * (t - 2) - 1) + b;
- },
-
- /**
- * Cubic easing in
- * @memberOf fabric.util.ease
- */
- easeInCubic: function(t, b, c, d) {
- return c * (t /= d) * t * t + b;
- },
-
- easeOutCubic: easeOutCubic,
- easeInOutCubic: easeInOutCubic,
- easeInQuart: easeInQuart,
- easeOutQuart: easeOutQuart,
- easeInOutQuart: easeInOutQuart,
- easeInQuint: easeInQuint,
- easeOutQuint: easeOutQuint,
- easeInOutQuint: easeInOutQuint,
- easeInSine: easeInSine,
- easeOutSine: easeOutSine,
- easeInOutSine: easeInOutSine,
- easeInExpo: easeInExpo,
- easeOutExpo: easeOutExpo,
- easeInOutExpo: easeInOutExpo,
- easeInCirc: easeInCirc,
- easeOutCirc: easeOutCirc,
- easeInOutCirc: easeInOutCirc,
- easeInElastic: easeInElastic,
- easeOutElastic: easeOutElastic,
- easeInOutElastic: easeInOutElastic,
- easeInBack: easeInBack,
- easeOutBack: easeOutBack,
- easeInOutBack: easeInOutBack,
- easeInBounce: easeInBounce,
- easeOutBounce: easeOutBounce,
- easeInOutBounce: easeInOutBounce
- };
-
-})(typeof exports !== 'undefined' ? exports : window);
+ return -c / 2 * ((--t) * (t - 2) - 1) + b;
+};
+
+/**
+ * Cubic easing in
+ * @memberOf fabric.util.ease
+ */
+export const easeInCubic:TEasingFunction = (t, b, c, d) => c * (t /= d) * t * t + b;
diff --git a/src/util/animate.ts b/src/util/animate.ts
index c8ecf56efef..4bb4d0b7a73 100644
--- a/src/util/animate.ts
+++ b/src/util/animate.ts
@@ -1,267 +1,174 @@
//@ts-nocheck
-import { extend } from './lang_object';
-
-(function (global) {
- /**
- *
- * @typedef {Object} AnimationOptions
- * Animation of a value or list of values.
- * @property {Function} [onChange] Callback; invoked on every value change
- * @property {Function} [onComplete] Callback; invoked when value change is completed
- * @property {number | number[]} [startValue=0] Starting value
- * @property {number | number[]} [endValue=100] Ending value
- * @property {number | number[]} [byValue=100] Value to modify the property by
- * @property {Function} [easing] Easing function
- * @property {number} [duration=500] Duration of change (in ms)
- * @property {Function} [abort] Additional function with logic. If returns true, animation aborts.
- * @property {number} [delay] Delay of animation start (in ms)
- *
- * @typedef {() => void} CancelFunction
- *
- * @typedef {Object} AnimationCurrentState
- * @property {number | 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 fabric = global.fabric, RUNNING_ANIMATIONS = [];
- 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 [];
+import { fabric } from '../../HEADER';
+import { runningAnimations } from './animation_registry';
+import { noop } from '../constants';
+
+/**
+ *
+ * @typedef {Object} AnimationOptions
+ * Animation of a value or list of values.
+ * @property {Function} [onChange] Callback; invoked on every value change
+ * @property {Function} [onComplete] Callback; invoked when value change is completed
+ * @property {number | number[]} [startValue=0] Starting value
+ * @property {number | number[]} [endValue=100] Ending value
+ * @property {number | number[]} [byValue=100] Value to modify the property by
+ * @property {Function} [easing] Easing function
+ * @property {number} [duration=500] Duration of change (in ms)
+ * @property {Function} [abort] Additional function with logic. If returns true, animation aborts.
+ * @property {number} [delay] Delay of animation start (in ms)
+ *
+ * @typedef {() => void} CancelFunction
+ *
+ * @typedef {Object} AnimationCurrentState
+ * @property {number | 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
+ */
+
+const defaultEasing = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
+
+/**
+ * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
+ * @memberOf fabric.util
+ * @param {AnimationOptions} [options] Animation options
+ * When using lists, think of something like this:
+ * @example
+ * fabric.util.animate({
+ * startValue: [1, 2, 3],
+ * endValue: [2, 4, 6],
+ * onChange: function([x, y, zoom]) {
+ * canvas.zoomToPoint(new Point(x, y), zoom);
+ * canvas.requestRenderAll();
+ * }
+ * });
+ *
+ * @example
+ * fabric.util.animate({
+ * startValue: 1,
+ * endValue: 0,
+ * onChange: function(v) {
+ * obj.set('opacity', v);
+ * canvas.requestRenderAll();
+ * }
+ * });
+ *
+ * @returns {CancelFunction} cancel function
+ */
+export function animate(options = {}) {
+ let cancel = false;
+
+ const {
+ startValue = 0,
+ duration = 500,
+ easing = defaultEasing,
+ onChange = noop,
+ abort = noop,
+ onComplete = noop,
+ endValue = 100,
+ delay = 0,
+ } = options;
+
+ const context = {
+ ...options,
+ currentValue: startValue,
+ completionRate: 0,
+ durationRate: 0
+ };
+
+ const removeFromRegistry = () => {
+ const index = runningAnimations.indexOf(context);
+ return index > -1 && runningAnimations.splice(index, 1)[0];
+ };
+
+ context.cancel = function () {
+ cancel = true;
+ return removeFromRegistry();
+ };
+ runningAnimations.push(context);
+
+ const runner = function (timestamp) {
+ const start = timestamp || +new Date(),
+ finish = start + duration,
+ isMany = Array.isArray(startValue),
+ byValue = options.byValue || (
+ isMany ?
+ startValue.map((value, i) => endValue[i] - value)
+ : endValue - startValue
+ );
+
+ options.onStart && options.onStart();
+
+ (function tick(ticktime) {
+ const time = ticktime || +new Date();
+ const currentTime = time > finish ? duration : (time - start),
+ timePerc = currentTime / duration,
+ current = isMany ?
+ startValue.map(
+ (_value, i) => easing(currentTime, _value, byValue[i], duration)
+ ) : easing(currentTime, startValue, byValue, duration),
+ valuePerc = isMany ? Math.abs((current[0] - startValue[0]) / byValue[0])
+ : Math.abs((current - startValue) / byValue);
+ // update context
+ context.currentValue = isMany ? current.slice() : current;
+ context.completionRate = valuePerc;
+ context.durationRate = timePerc;
+
+ if (cancel) {
+ 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 [];
+ if (abort(current, valuePerc, timePerc)) {
+ removeFromRegistry();
+ return;
}
- return this.filter(function (animation) {
- return animation.target === target;
- });
- }
- });
-
- function noop() {
- return false;
- }
-
- function defaultEasing(t, b, c, d) {
- return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
- }
-
- /**
- * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
- * @memberOf fabric.util
- * @param {AnimationOptions} [options] Animation options
- * When using lists, think of something like this:
- * @example
- * fabric.util.animate({
- * startValue: [1, 2, 3],
- * endValue: [2, 4, 6],
- * onChange: function([x, y, zoom]) {
- * canvas.zoomToPoint(new Point(x, y), zoom);
- * canvas.requestRenderAll();
- * }
- * });
- *
- * @example
- * fabric.util.animate({
- * startValue: 1,
- * endValue: 0,
- * onChange: function(v) {
- * obj.set('opacity', v);
- * canvas.requestRenderAll();
- * }
- * });
- *
- * @returns {CancelFunction} cancel function
- */
- function animate(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 = Object.assign({}, options, {
- cancel: function () {
- cancel = true;
- return removeFromRegistry();
- },
- currentValue: 'startValue' in options ? options.startValue : 0,
- completionRate: 0,
- durationRate: 0
- });
- fabric.runningAnimations.push(context);
-
- var runner = function (timestamp) {
- var start = timestamp || +new Date(),
- duration = options.duration || 500,
- finish = start + duration, time,
- onChange = options.onChange || noop,
- abort = options.abort || noop,
- onComplete = options.onComplete || noop,
- easing = options.easing || defaultEasing,
- isMany = 'startValue' in options ? options.startValue.length > 0 : false,
- startValue = 'startValue' in options ? options.startValue : 0,
- endValue = 'endValue' in options ? options.endValue : 100,
- byValue = options.byValue || (isMany ? startValue.map(function(value, i) {
- return endValue[i] - startValue[i];
- }) : endValue - startValue);
-
- options.onStart && options.onStart();
-
- (function tick(ticktime) {
- time = ticktime || +new Date();
- var currentTime = time > finish ? duration : (time - start),
- timePerc = currentTime / duration,
- current = isMany ? startValue.map(function(_value, i) {
- return easing(currentTime, startValue[i], byValue[i], duration);
- }) : easing(currentTime, startValue, byValue, duration),
- valuePerc = isMany ? Math.abs((current[0] - startValue[0]) / byValue[0])
- : Math.abs((current - startValue) / byValue);
+ if (time > finish) {
// update context
- context.currentValue = isMany ? current.slice() : current;
- context.completionRate = valuePerc;
- context.durationRate = timePerc;
- if (cancel) {
- return;
- }
- if (abort(current, valuePerc, timePerc)) {
- removeFromRegistry();
- return;
- }
- if (time > finish) {
- // update context
- context.currentValue = isMany ? endValue.slice() : endValue;
- context.completionRate = 1;
- context.durationRate = 1;
- // execute callbacks
- onChange(isMany ? endValue.slice() : endValue, 1, 1);
- onComplete(endValue, 1, 1);
- removeFromRegistry();
- return;
- }
- else {
- onChange(current, valuePerc, timePerc);
- requestAnimFrame(tick);
- }
- })(start);
- };
-
- if (options.delay) {
- setTimeout(function () {
- requestAnimFrame(runner);
- }, options.delay);
- }
- else {
- requestAnimFrame(runner);
- }
-
- return context.cancel;
- }
-
- var _requestAnimFrame = fabric.window.requestAnimationFrame ||
- fabric.window.webkitRequestAnimationFrame ||
- fabric.window.mozRequestAnimationFrame ||
- fabric.window.oRequestAnimationFrame ||
- fabric.window.msRequestAnimationFrame ||
- function(callback) {
- return fabric.window.setTimeout(callback, 1000 / 60);
- };
-
- var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout;
-
- /**
- * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
- * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
- * @memberOf fabric.util
- * @param {Function} callback Callback to invoke
- * @param {DOMElement} element optional Element to associate with animation
- */
- function requestAnimFrame() {
- return _requestAnimFrame.apply(fabric.window, arguments);
- }
+ context.currentValue = isMany ? endValue.slice() : endValue;
+ context.completionRate = 1;
+ context.durationRate = 1;
+ // execute callbacks
+ onChange(isMany ? endValue.slice() : endValue, 1, 1);
+ onComplete(endValue, 1, 1);
+ removeFromRegistry();
+ return;
+ }
+ else {
+ onChange(current, valuePerc, timePerc);
+ requestAnimFrame(tick);
+ }
+ })(start);
+ };
- function cancelAnimFrame() {
- return _cancelAnimFrame.apply(fabric.window, arguments);
+ if (delay > 0 ) {
+ setTimeout(() => requestAnimFrame(runner), delay);
+ } else {
+ requestAnimFrame(runner);
}
- fabric.util.animate = animate;
- fabric.util.requestAnimFrame = requestAnimFrame;
- fabric.util.cancelAnimFrame = cancelAnimFrame;
- fabric.runningAnimations = RUNNING_ANIMATIONS;
-})(typeof exports !== 'undefined' ? exports : window);
+ return context.cancel;
+}
+
+const _requestAnimFrame =
+ fabric.window.requestAnimationFrame ||
+ function(callback) {
+ return fabric.window.setTimeout(callback, 1000 / 60);
+ };
+
+const _cancelAnimFrame =
+ fabric.window.cancelAnimationFrame || fabric.window.clearTimeout;
+
+/**
+ * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
+ * @memberOf fabric.util
+ * @param {Function} callback Callback to invoke
+ * @param {DOMElement} element optional Element to associate with animation
+ */
+export function requestAnimFrame(...args) {
+ return _requestAnimFrame.apply(fabric.window, args);
+}
+
+export function cancelAnimFrame(...args) {
+ return _cancelAnimFrame.apply(fabric.window, args);
+}
diff --git a/src/util/animate_color.ts b/src/util/animate_color.ts
index 9fc25124630..92772b2da81 100644
--- a/src/util/animate_color.ts
+++ b/src/util/animate_color.ts
@@ -1,79 +1,84 @@
//@ts-nocheck
-
import { Color } from "../color";
+import { animate } from './animate';
-(function(global) {
- var fabric = global.fabric;
- // Calculate an in-between color. Returns a "rgba()" string.
- // Credit: Edwin Martin
- // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js
- function calculateColor(begin, end, pos) {
- var color = 'rgba('
- + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
- + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
- + parseInt((begin[2] + pos * (end[2] - begin[2])), 10);
+// Calculate an in-between color. Returns a "rgba()" string.
+// Credit: Edwin Martin
+// http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js
+// const calculateColor = (begin: number[], end: number[], pos) => {
+// const [r, g, b, _a] = begin.map((beg, index) => beg + pos * (end[index] - beg));
+// const a = begin && end ? parseFloat(_a) : 1;
+// return `rgba(${parseInt(r, 10)},${parseInt(g, 10)},${parseInt(b, 10)},${a})`;
+// }
- color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
- color += ')';
- return color;
- }
+// color animation is broken. This function pass the tests for some reasons
+// but begin and end aren't array anymore since we improved animate function
+// to handler arrays internally.
+function calculateColor(begin, end, pos) {
+ let color = 'rgba('
+ + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
+ + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
+ + parseInt((begin[2] + pos * (end[2] - begin[2])), 10);
- /**
- * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed.
- * @memberOf fabric.util
- * @param {String} fromColor The starting color in hex or rgb(a) format.
- * @param {String} toColor The starting color in hex or rgb(a) format.
- * @param {Number} [duration] Duration of change (in ms).
- * @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 {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
- * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
- * @returns {Function} abort function
- */
- function animateColor(fromColor, toColor, duration, options) {
- var startColor = new Color(fromColor).getSource(),
- endColor = new Color(toColor).getSource(),
- originalOnComplete = options.onComplete,
- originalOnChange = options.onChange;
- options = options || {};
+ color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
+ color += ')';
+ return color;
+}
+
+const defaultColorEasing = (currentTime, duration) => 1 - Math.cos(currentTime / duration * (Math.PI / 2));
- return fabric.util.animate(Object.assign(options, {
- duration: duration || 500,
- startValue: startColor,
- endValue: endColor,
- byValue: endColor,
- easing: function (currentTime, startValue, byValue, duration) {
- var posValue = options.colorEasing
- ? options.colorEasing(currentTime, duration)
- : 1 - Math.cos(currentTime / duration * (Math.PI / 2));
- return calculateColor(startValue, byValue, posValue);
- },
- // has to take in account for color restoring;
- onComplete: function(current, valuePerc, timePerc) {
- if (originalOnComplete) {
- return originalOnComplete(
- calculateColor(endColor, endColor, 0),
+/**
+ * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed.
+ * @memberOf fabric.util
+ * @param {String} fromColor The starting color in hex or rgb(a) format.
+ * @param {String} toColor The starting color in hex or rgb(a) format.
+ * @param {Number} [duration] Duration of change (in ms).
+ * @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 {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
+ * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
+ * @returns {Function} abort function
+ */
+export function animateColor(
+ fromColor,
+ toColor,
+ duration = 500,
+ {
+ colorEasing = defaultColorEasing,
+ onComplete,
+ onChange,
+ ...restOfOptions
+ } = {}
+) {
+ const startColor = new Color(fromColor).getSource(),
+ endColor = new Color(toColor).getSource(),
+ return animate({
+ ...restOfOptions,
+ duration,
+ startValue: startColor,
+ endValue: endColor,
+ byValue: endColor,
+ easing: (currentTime, startValue, byValue, duration) =>
+ calculateColor(startValue, byValue, colorEasing(currentTime, duration)),
+ // has to take in account for color restoring;
+ onComplete: (current, valuePerc, timePerc) => onComplete?.(
+ calculateColor(endColor, endColor, 0),
+ valuePerc,
+ timePerc
+ ),
+ onChange: (current, valuePerc, timePerc) => {
+ if (onChange) {
+ if (Array.isArray(current)) {
+ return onChange(
+ calculateColor(current, current, 0),
valuePerc,
timePerc
);
}
- },
- onChange: function(current, valuePerc, timePerc) {
- if (originalOnChange) {
- if (Array.isArray(current)) {
- return originalOnChange(
- calculateColor(current, current, 0),
- valuePerc,
- timePerc
- );
- }
- originalOnChange(current, valuePerc, timePerc);
- }
+ onChange(current, valuePerc, timePerc);
}
- }));
- }
-
- fabric.util.animateColor = animateColor;
+ }
+ });
+}
-})(typeof exports !== 'undefined' ? exports : window);
diff --git a/src/util/animation_registry.ts b/src/util/animation_registry.ts
new file mode 100644
index 00000000000..215eefdc487
--- /dev/null
+++ b/src/util/animation_registry.ts
@@ -0,0 +1,81 @@
+//@ts-nocheck
+import { fabric } from '../../HEADER';
+
+/**
+ * Array holding all running animations
+ * @memberof fabric
+ * @type {AnimationContext[]}
+ */
+class RunningAnimations extends Array {
+ /**
+ * cancel all running animations at the next requestAnimFrame
+ * @returns {AnimationContext[]}
+ */
+ cancelAll(): any[] {
+ const animations = this.splice(0);
+ animations.forEach((animation) => animation.cancel());
+ return animations;
+ }
+
+ /**
+ * cancel all running animations attached to canvas at the next requestAnimFrame
+ * @param {fabric.Canvas} canvas
+ * @returns {AnimationContext[]}
+ */
+ cancelByCanvas(canvas: any) {
+ if (!canvas) {
+ return [];
+ }
+ const cancelled = this.filter(
+ (animation) => typeof animation.target === 'object' && animation.target.canvas === canvas
+ );
+ cancelled.forEach((animation) => animation.cancel());
+ return cancelled;
+ }
+
+ /**
+ * cancel all running animations for target at the next requestAnimFrame
+ * @param {*} target
+ * @returns {AnimationContext[]}
+ */
+ cancelByTarget(target) {
+ const cancelled = this.findAnimationsByTarget(target);
+ cancelled.forEach((animation) => animation.cancel());
+ return cancelled;
+ }
+
+ /**
+ *
+ * @param {CancelFunction} cancelFunc the function returned by animate
+ * @returns {number}
+ */
+ findAnimationIndex(cancelFunc) {
+ return this.indexOf(this.findAnimation(cancelFunc));
+ }
+
+ /**
+ *
+ * @param {CancelFunction} cancelFunc the function returned by animate
+ * @returns {AnimationContext | undefined} animation's options object
+ */
+ findAnimation(cancelFunc) {
+ return this.find((animation) => 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(target) {
+ if (!target) {
+ return [];
+ }
+ return this.filter((animation) => animation.target === target);
+ }
+}
+
+export const runningAnimations = new RunningAnimations();
+
+fabric.runningAnimations = runningAnimations;
+
diff --git a/src/util/dom_misc.ts b/src/util/dom_misc.ts
index 2b9f2b3ed86..cd6ac1cddf3 100644
--- a/src/util/dom_misc.ts
+++ b/src/util/dom_misc.ts
@@ -1,152 +1,72 @@
//@ts-nocheck
-(function(global) {
-
- var fabric = global.fabric, _slice = Array.prototype.slice;
-
- /**
- * Takes id and returns an element with that id (if one exists in a document)
- * @memberOf fabric.util
- * @param {String|HTMLElement} id
- * @return {HTMLElement|null}
- */
- function getById(id) {
- return typeof id === 'string' ? fabric.document.getElementById(id) : id;
- }
-
- var sliceCanConvertNodelists,
- /**
- * Converts an array-like object (e.g. arguments or NodeList) to an array
- * @memberOf fabric.util
- * @param {Object} arrayLike
- * @return {Array}
- */
- toArray = function(arrayLike) {
- return _slice.call(arrayLike, 0);
- };
-
- try {
- sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
- }
- catch (err) { }
-
- if (!sliceCanConvertNodelists) {
- toArray = function(arrayLike) {
- var arr = new Array(arrayLike.length), i = arrayLike.length;
- while (i--) {
- arr[i] = arrayLike[i];
- }
- return arr;
- };
- }
-
- /**
- * Creates specified element with specified attributes
- * @memberOf fabric.util
- * @param {String} tagName Type of an element to create
- * @param {Object} [attributes] Attributes to set on an element
- * @return {HTMLElement} Newly created element
- */
- function makeElement(tagName, attributes) {
- var el = fabric.document.createElement(tagName);
- for (var prop in attributes) {
- if (prop === 'class') {
- el.className = attributes[prop];
- }
- else if (prop === 'for') {
- el.htmlFor = attributes[prop];
- }
- else {
- el.setAttribute(prop, attributes[prop]);
- }
- }
- return el;
- }
-
- /**
- * Adds class to an element
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to add class to
- * @param {String} className Class to add to an element
- */
- function addClass(element, className) {
- if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
- element.className += (element.className ? ' ' : '') + className;
- }
- }
-
- /**
- * Wraps element with another element
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to wrap
- * @param {HTMLElement|String} wrapper Element to wrap with
- * @param {Object} [attributes] Attributes to set on a wrapper
- * @return {HTMLElement} wrapper
- */
- function wrapElement(element, wrapper, attributes) {
- if (typeof wrapper === 'string') {
- wrapper = makeElement(wrapper, attributes);
- }
- if (element.parentNode) {
- element.parentNode.replaceChild(wrapper, element);
- }
- wrapper.appendChild(element);
- return wrapper;
+/**
+ * Wraps element with another element
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to wrap
+ * @param {HTMLElement|String} wrapper Element to wrap with
+ * @param {Object} [attributes] Attributes to set on a wrapper
+ * @return {HTMLElement} wrapper
+ */
+export function wrapElement(element, wrapper) {
+ if (element.parentNode) {
+ element.parentNode.replaceChild(wrapper, element);
}
-
- /**
- * Returns element scroll offsets
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to operate on
- * @return {Object} Object with left/top values
- */
- function getScrollLeftTop(element) {
-
- var left = 0,
- top = 0,
- docElement = fabric.document.documentElement,
+ wrapper.appendChild(element);
+ return wrapper;
+}
+
+/**
+ * Returns element scroll offsets
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to operate on
+ * @return {Object} Object with left/top values
+ */
+export function getScrollLeftTop(element) {
+
+ let left = 0,
+ top = 0;
+
+ const docElement = fabric.document.documentElement,
body = fabric.document.body || {
scrollLeft: 0, scrollTop: 0
};
-
- // While loop checks (and then sets element to) .parentNode OR .host
- // to account for ShadowDOM. We still want to traverse up out of ShadowDOM,
- // but the .parentNode of a root ShadowDOM node will always be null, instead
- // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938
- while (element && (element.parentNode || element.host)) {
-
- // Set element to element parent, or 'host' in case of ShadowDOM
- element = element.parentNode || element.host;
-
- if (element === fabric.document) {
- left = body.scrollLeft || docElement.scrollLeft || 0;
- top = body.scrollTop || docElement.scrollTop || 0;
- }
- else {
- left += element.scrollLeft || 0;
- top += element.scrollTop || 0;
- }
-
- if (element.nodeType === 1 && element.style.position === 'fixed') {
- break;
- }
+ // While loop checks (and then sets element to) .parentNode OR .host
+ // to account for ShadowDOM. We still want to traverse up out of ShadowDOM,
+ // but the .parentNode of a root ShadowDOM node will always be null, instead
+ // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938
+ while (element && (element.parentNode || element.host)) {
+
+ // Set element to element parent, or 'host' in case of ShadowDOM
+ element = element.parentNode || element.host;
+
+ if (element === fabric.document) {
+ left = body.scrollLeft || docElement.scrollLeft || 0;
+ top = body.scrollTop || docElement.scrollTop || 0;
+ }
+ else {
+ left += element.scrollLeft || 0;
+ top += element.scrollTop || 0;
}
- return { left: left, top: top };
+ if (element.nodeType === 1 && element.style.position === 'fixed') {
+ break;
+ }
}
- /**
- * Returns offset for a given element
- * @function
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to get offset for
- * @return {Object} Object with "left" and "top" properties
- */
- function getElementOffset(element) {
- var docElem,
- doc = element && element.ownerDocument,
- box = { left: 0, top: 0 },
+ return { left, top };
+}
+
+/**
+ * Returns offset for a given element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to get offset for
+ * @return {Object} Object with "left" and "top" properties
+ */
+export function getElementOffset(element) {
+ let box = { left: 0, top: 0 };
+ const doc = element && element.ownerDocument,
offset = { left: 0, top: 0 },
- scrollLeftTop,
offsetAttributes = {
borderLeftWidth: 'left',
borderTopWidth: 'top',
@@ -154,148 +74,71 @@
paddingTop: 'top'
};
- if (!doc) {
- return offset;
- }
-
- for (var attr in offsetAttributes) {
- offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
- }
-
- docElem = doc.documentElement;
- if ( typeof element.getBoundingClientRect !== 'undefined' ) {
- box = element.getBoundingClientRect();
- }
-
- scrollLeftTop = getScrollLeftTop(element);
-
- return {
- left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
- top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
- };
- }
-
- /**
- * Returns style attribute value of a given element
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to get style attribute for
- * @param {String} attr Style attribute to get for element
- * @return {String} Style attribute value of the given element.
- */
- var getElementStyle;
- if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
- getElementStyle = function(element, attr) {
- var style = fabric.document.defaultView.getComputedStyle(element, null);
- return style ? style[attr] : undefined;
- };
+ if (!doc) {
+ return offset;
}
- else {
- getElementStyle = function(element, attr) {
- var value = element.style[attr];
- if (!value && element.currentStyle) {
- value = element.currentStyle[attr];
- }
- return value;
- };
+ const elemStyle = fabric.document.defaultView.getComputedStyle(element, null)
+ for (const attr in offsetAttributes) {
+ offset[offsetAttributes[attr]] += parseInt(elemStyle[attr], 10) || 0;
}
- (function () {
- var style = fabric.document.documentElement.style,
- selectProp = 'userSelect' in style
- ? 'userSelect'
- : 'MozUserSelect' in style
- ? 'MozUserSelect'
- : 'WebkitUserSelect' in style
- ? 'WebkitUserSelect'
- : 'KhtmlUserSelect' in style
- ? 'KhtmlUserSelect'
- : '';
-
- /**
- * Makes element unselectable
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to make unselectable
- * @return {HTMLElement} Element that was passed in
- */
- function makeElementUnselectable(element) {
- if (typeof element.onselectstart !== 'undefined') {
- element.onselectstart = () => false;
- }
- if (selectProp) {
- element.style[selectProp] = 'none';
- }
- else if (typeof element.unselectable === 'string') {
- element.unselectable = 'on';
- }
- return element;
- }
-
- /**
- * Makes element selectable
- * @memberOf fabric.util
- * @param {HTMLElement} element Element to make selectable
- * @return {HTMLElement} Element that was passed in
- */
- function makeElementSelectable(element) {
- if (typeof element.onselectstart !== 'undefined') {
- element.onselectstart = null;
- }
- if (selectProp) {
- element.style[selectProp] = '';
- }
- else if (typeof element.unselectable === 'string') {
- element.unselectable = '';
- }
- return element;
- }
+ const docElem = doc.documentElement;
+ if ( typeof element.getBoundingClientRect !== 'undefined' ) {
+ box = element.getBoundingClientRect();
+ }
- fabric.util.makeElementUnselectable = makeElementUnselectable;
- fabric.util.makeElementSelectable = makeElementSelectable;
- })(typeof exports !== 'undefined' ? exports : window);
+ const scrollLeftTop = getScrollLeftTop(element);
- function getNodeCanvas(element) {
- var impl = fabric.jsdomImplForWrapper(element);
- return impl._canvas || impl._image;
+ return {
+ left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
+ top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
};
-
- function cleanUpJsdomNode(element) {
- if (!fabric.isLikelyNode) {
- return;
- }
- var impl = fabric.jsdomImplForWrapper(element);
- if (impl) {
- impl._image = null;
- impl._canvas = null;
- // unsure if necessary
- impl._currentSrc = null;
- impl._attributes = null;
- impl._classList = null;
- }
+}
+
+/**
+ * Makes element unselectable
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to make unselectable
+ * @return {HTMLElement} Element that was passed in
+ */
+export function makeElementUnselectable(element) {
+ if (typeof element.onselectstart !== 'undefined') {
+ element.onselectstart = () => false;
}
-
- function setImageSmoothing(ctx, value) {
- ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled
- || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled;
- ctx.imageSmoothingEnabled = value;
+ element.style.userSelect = 'none';
+ return element;
+}
+
+/**
+ * Makes element selectable
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to make selectable
+ * @return {HTMLElement} Element that was passed in
+ */
+export function makeElementSelectable(element) {
+ if (typeof element.onselectstart !== 'undefined') {
+ element.onselectstart = null;
}
-
- /**
- * setImageSmoothing sets the context imageSmoothingEnabled property.
- * Used by canvas and by ImageObject.
- * @memberOf fabric.util
- * @since 4.0.0
- * @param {HTMLRenderingContext2D} ctx to set on
- * @param {Boolean} value true or false
- */
- fabric.util.setImageSmoothing = setImageSmoothing;
- fabric.util.getById = getById;
- fabric.util.toArray = toArray;
- fabric.util.addClass = addClass;
- fabric.util.makeElement = makeElement;
- fabric.util.wrapElement = wrapElement;
- fabric.util.getScrollLeftTop = getScrollLeftTop;
- fabric.util.getElementOffset = getElementOffset;
- fabric.util.getNodeCanvas = getNodeCanvas;
- fabric.util.cleanUpJsdomNode = cleanUpJsdomNode;
-
-})(typeof exports !== 'undefined' ? exports : window);
+ element.style.userSelect = '';
+ return element;
+}
+
+export function getNodeCanvas(element) {
+ const impl = fabric.jsdomImplForWrapper(element);
+ return impl._canvas || impl._image;
+};
+
+export function cleanUpJsdomNode(element) {
+ if (!fabric.isLikelyNode) {
+ return;
+ }
+ const impl = fabric.jsdomImplForWrapper(element);
+ if (impl) {
+ impl._image = null;
+ impl._canvas = null;
+ // unsure if necessary
+ impl._currentSrc = null;
+ impl._attributes = null;
+ impl._classList = null;
+ }
+}
diff --git a/src/util/lang_class.ts b/src/util/lang_class.ts
index 43c1067a002..27fe7839e93 100644
--- a/src/util/lang_class.ts
+++ b/src/util/lang_class.ts
@@ -1,97 +1,90 @@
//@ts-nocheck
-(function(global) {
+import { noop } from '../constants';
- var fabric = global.fabric, slice = Array.prototype.slice, emptyFunction = function() { },
- /** @ignore */
- addMethods = function(klass, source, parent) {
- for (var property in source) {
+function addMethods(klass, source, parent) {
+ for (var property in source) {
- if (property in klass.prototype &&
- typeof klass.prototype[property] === 'function' &&
- (source[property] + '').indexOf('callSuper') > -1) {
+ if (property in klass.prototype &&
+ typeof klass.prototype[property] === 'function' &&
+ (source[property] + '').indexOf('callSuper') > -1) {
- klass.prototype[property] = (function(property) {
- return function() {
+ klass.prototype[property] = (function(property) {
+ return function(...args) {
- var superclass = this.constructor.superclass;
- this.constructor.superclass = parent;
- var returnValue = source[property].apply(this, arguments);
- this.constructor.superclass = superclass;
+ var superclass = this.constructor.superclass;
+ this.constructor.superclass = parent;
+ var returnValue = source[property].call(this, ...args);
+ this.constructor.superclass = superclass;
- if (property !== 'initialize') {
- return returnValue;
- }
- };
- })(property);
+ if (property !== 'initialize') {
+ return returnValue;
}
- else {
- klass.prototype[property] = source[property];
- }
- }
- };
+ };
+ })(property);
+ }
+ else {
+ klass.prototype[property] = source[property];
+ }
+ }
+};
- function Subclass() { }
+function Subclass() { }
- function callSuper(methodName) {
- var parentMethod = null,
- _this = this;
+function callSuper(methodName, ...args) {
+ var parentMethod = null,
+ _this = this;
- // climb prototype chain to find method not equal to callee's method
- while (_this.constructor.superclass) {
- var superClassMethod = _this.constructor.superclass.prototype[methodName];
- if (_this[methodName] !== superClassMethod) {
- parentMethod = superClassMethod;
- break;
- }
- // eslint-disable-next-line
- _this = _this.constructor.superclass.prototype;
+ // climb prototype chain to find method not equal to callee's method
+ while (_this.constructor.superclass) {
+ var superClassMethod = _this.constructor.superclass.prototype[methodName];
+ if (_this[methodName] !== superClassMethod) {
+ parentMethod = superClassMethod;
+ break;
}
-
- if (!parentMethod) {
- return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this);
- }
-
- return (arguments.length > 1)
- ? parentMethod.apply(this, slice.call(arguments, 1))
- : parentMethod.call(this);
+ // eslint-disable-next-line
+ _this = _this.constructor.superclass.prototype;
}
- /**
- * Helper for creation of "classes".
- * @memberOf fabric.util
- * @param {Function} [parent] optional "Class" to inherit from
- * @param {Object} [properties] Properties shared by all instances of this class
- * (be careful modifying objects defined here as this would affect all instances)
- */
- function createClass() {
- var parent = null,
- properties = slice.call(arguments, 0);
+ if (!parentMethod) {
+ return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this);
+ }
- if (typeof properties[0] === 'function') {
- parent = properties.shift();
- }
- function klass() {
- this.initialize.apply(this, arguments);
- }
+ return parentMethod.call(this, ...args);
+}
- klass.superclass = parent;
- klass.subclasses = [];
+/**
+ * Helper for creation of "classes".
+ * @memberOf fabric.util
+ * @param {Function} [parent] optional "Class" to inherit from
+ * @param {Object} [properties] Properties shared by all instances of this class
+ * (be careful modifying objects defined here as this would affect all instances)
+ */
+export function createClass(...args) {
+ var parent = null,
+ properties = [...args];
- if (parent) {
- Subclass.prototype = parent.prototype;
- klass.prototype = new Subclass();
- parent.subclasses.push(klass);
- }
- for (var i = 0, length = properties.length; i < length; i++) {
- addMethods(klass, properties[i], parent);
- }
- if (!klass.prototype.initialize) {
- klass.prototype.initialize = emptyFunction;
- }
- klass.prototype.constructor = klass;
- klass.prototype.callSuper = callSuper;
- return klass;
+ if (typeof args[0] === 'function') {
+ parent = properties.shift();
+ }
+ function klass(...klassArgs) {
+ this.initialize.call(this, ...klassArgs);
}
- fabric.util.createClass = createClass;
-})(typeof exports !== 'undefined' ? exports : window);
+ klass.superclass = parent;
+ klass.subclasses = [];
+
+ if (parent) {
+ Subclass.prototype = parent.prototype;
+ klass.prototype = new Subclass();
+ parent.subclasses.push(klass);
+ }
+ for (var i = 0, length = properties.length; i < length; i++) {
+ addMethods(klass, properties[i], parent);
+ }
+ if (!klass.prototype.initialize) {
+ klass.prototype.initialize = noop;
+ }
+ klass.prototype.constructor = klass;
+ klass.prototype.callSuper = callSuper;
+ return klass;
+}
diff --git a/src/util/misc/index.ts b/src/util/misc/index.ts
deleted file mode 100644
index e9a90a88ffd..00000000000
--- a/src/util/misc/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { cos } from './cos';
-export { sin } from './sin';
-export * from './radiansDegreesConversion';
-export * from './vectors';
-export { rotatePoint } from './rotatePoint';
diff --git a/src/util/misc/isTransparent.ts b/src/util/misc/isTransparent.ts
new file mode 100644
index 00000000000..34cd9932421
--- /dev/null
+++ b/src/util/misc/isTransparent.ts
@@ -0,0 +1,44 @@
+/**
+ * Returns true if context has transparent pixel
+ * at specified location (taking tolerance into account)
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x x coordinate in canvasElementCoordinate, not fabric space
+ * @param {Number} y y coordinate in canvasElementCoordinate, not fabric space
+ * @param {Number} tolerance Tolerance pixels around the point, not alpha tolerance
+ * @return {boolean} true if transparent
+ */
+export const isTransparent = (ctx: CanvasRenderingContext2D, x: number, y: number, tolerance: number): boolean => {
+
+ // If tolerance is > 0 adjust start coords to take into account.
+ // If moves off Canvas fix to 0
+ if (tolerance > 0) {
+ if (x > tolerance) {
+ x -= tolerance;
+ }
+ else {
+ x = 0;
+ }
+ if (y > tolerance) {
+ y -= tolerance;
+ }
+ else {
+ y = 0;
+ }
+ }
+
+ let _isTransparent = true;
+ const { data } = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1);
+ const l = data.length;
+
+ // Split image data - for tolerance > 1, pixelDataSize = 4;
+ for (let i = 3; i < l; i += 4) {
+ const alphaChannel = data[i];
+ if (alphaChannel > 0) {
+ // Stop if colour found
+ _isTransparent = false;
+ break;
+ }
+ }
+
+ return _isTransparent;
+};
diff --git a/src/util/misc/matrix.ts b/src/util/misc/matrix.ts
index 4186db24804..4ee725f4c34 100644
--- a/src/util/misc/matrix.ts
+++ b/src/util/misc/matrix.ts
@@ -14,7 +14,7 @@ type TTranslateMatrixArgs = {
translateY?: number;
}
-type TScaleMatrixArgs = {
+export type TScaleMatrixArgs = {
scaleX?: number;
scaleY?: number;
flipX?: boolean;
diff --git a/src/util/misc/mergeClipPaths.ts b/src/util/misc/mergeClipPaths.ts
new file mode 100644
index 00000000000..3cee36fa365
--- /dev/null
+++ b/src/util/misc/mergeClipPaths.ts
@@ -0,0 +1,45 @@
+import { fabric } from '../../../HEADER';
+import { applyTransformToObject } from './objectTransforms';
+import { multiplyTransformMatrices, invertTransform } from './matrix';
+/**
+ * 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
+ */
+export const mergeClipPaths = (c1: any, c2: any) => {
+ let 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
+ applyTransformToObject(
+ b,
+ multiplyTransformMatrices(
+ invertTransform(a.calcTransformMatrix()),
+ b.calcTransformMatrix()
+ )
+ );
+ // assign the `inverted` prop to the wrapping group
+ const inverted = a.inverted && b.inverted;
+ if (inverted) {
+ // case (1)
+ a.inverted = b.inverted = false;
+ }
+ return new fabric.Group([a], { clipPath: b, inverted });
+};
diff --git a/src/util/misc/misc.ts b/src/util/misc/misc.ts
index 342a9dd0803..163e7e9df4c 100644
--- a/src/util/misc/misc.ts
+++ b/src/util/misc/misc.ts
@@ -1,6 +1,5 @@
//@ts-nocheck
import { fabric } from '../../../HEADER';
-import { Point } from '../../point.class';
import { cos } from './cos';
import { sin } from './sin';
import { rotateVector, createVector, calcAngleBetweenVectors, getHatVector, getBisector } from './vectors';
@@ -36,6 +35,7 @@ import {
addTransformToObject,
applyTransformToObject,
removeTransformFromObject,
+ sizeAfterTransform,
} from './objectTransforms';
import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints';
import {
@@ -79,233 +79,125 @@ import {
removeListener,
addListener,
} from '../dom_event';
-
- /**
- * @namespace fabric.util
- */
- fabric.util = {
- cos,
- sin,
- rotateVector,
- createVector,
- calcAngleBetweenVectors,
- getHatVector,
- getBisector,
- degreesToRadians,
- radiansToDegrees,
- rotatePoint,
- // probably we should stop exposing this from the interface
- getRandomInt,
- removeFromArray,
- projectStrokeOnPoints,
- // matrix.ts file
- transformPoint,
- invertTransform,
- composeMatrix,
- qrDecompose,
- calcDimensionsMatrix,
- calcRotateMatrix,
- multiplyTransformMatrices,
- // textStyles.ts file
- stylesFromArray,
- stylesToArray,
- hasStyleChanged,
- object: {
- clone,
- extend,
- },
- createCanvasElement,
- createImage,
- copyCanvasElement,
- toDataURL,
- toFixed,
- matrixToSVG,
- parsePreserveAspectRatioAttribute,
- groupSVGElements,
- parseUnit,
- getSvgAttributes,
- findScaleToFit,
- findScaleToCover,
- capValue,
- saveObjectTransform,
- resetObjectTransform,
- addTransformToObject,
- applyTransformToObject,
- removeTransformFromObject,
- makeBoundingBoxFromPoints,
- sendPointToPlane,
- transformPointRelativeToCanvas,
- sendObjectToPlane,
- string: {
- camelize,
- capitalize,
- escapeXml,
- graphemeSplit,
- },
- getKlass,
- loadImage,
- enlivenObjects,
- enlivenObjectEnlivables,
- array: {
- min,
- max,
- },
- pick,
- joinPath,
- parsePath,
- makePathSimpler,
- getSmoothPathFromPoints,
- getPathSegmentsInfo,
- getBoundsOfCurve,
- getPointOnPath,
- transformPath,
- getRegularPolygonPath,
- request,
- setStyle,
- isTouchEvent,
- getPointer,
- removeListener,
- addListener,
-
- /**
- * Returns true if context has transparent pixel
- * at specified location (taking tolerance into account)
- * @param {CanvasRenderingContext2D} ctx context
- * @param {Number} x x coordinate
- * @param {Number} y y coordinate
- * @param {Number} tolerance Tolerance
- */
- isTransparent: function(ctx, x, y, tolerance) {
-
- // If tolerance is > 0 adjust start coords to take into account.
- // If moves off Canvas fix to 0
- if (tolerance > 0) {
- if (x > tolerance) {
- x -= tolerance;
- }
- else {
- x = 0;
- }
- if (y > tolerance) {
- y -= tolerance;
- }
- else {
- y = 0;
- }
- }
-
- var _isTransparent = true, i, temp,
- imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1),
- l = imageData.data.length;
-
- // Split image data - for tolerance > 1, pixelDataSize = 4;
- for (i = 3; i < l; i += 4) {
- temp = imageData.data[i];
- _isTransparent = temp <= 0;
- if (_isTransparent === false) {
- break; // Stop if colour found
- }
- }
-
- imageData = null;
-
- return _isTransparent;
- },
-
- /**
- * Given current aspect ratio, determines the max width and height that can
- * respect the total allowed area for the cache.
- * @memberOf fabric.util
- * @param {Number} ar aspect ratio
- * @param {Number} maximumArea Maximum area you want to achieve
- * @return {Object.x} Limited dimensions by X
- * @return {Object.y} Limited dimensions by Y
- */
- limitDimsByArea: function(ar, maximumArea) {
- var roughWidth = Math.sqrt(maximumArea * ar),
- perfLimitSizeY = Math.floor(maximumArea / roughWidth);
- return { x: Math.floor(roughWidth), y: perfLimitSizeY };
- },
-
- /**
- * given a width and height, return the size of the bounding box
- * that can contains the box with width/height with applied transform
- * described in options.
- * Use to calculate the boxes around objects for controls.
- * @memberOf fabric.util
- * @param {Number} width
- * @param {Number} height
- * @param {Object} options
- * @param {Number} options.scaleX
- * @param {Number} options.scaleY
- * @param {Number} options.skewX
- * @param {Number} options.skewY
- * @returns {Point} size
- */
- sizeAfterTransform: function(width, height, options) {
- var dimX = width / 2, dimY = height / 2,
- points = [
- {
- x: -dimX,
- y: -dimY
- },
- {
- x: dimX,
- y: -dimY
- },
- {
- x: -dimX,
- y: dimY
- },
- {
- x: dimX,
- y: dimY
- }],
- transformMatrix = fabric.util.calcDimensionsMatrix(options),
- bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix);
- return new Point(bbox.width, 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 });
- },
- };
+import {
+ wrapElement,
+ getScrollLeftTop,
+ getElementOffset,
+ getNodeCanvas,
+ cleanUpJsdomNode,
+ makeElementUnselectable,
+ makeElementSelectable,
+} from '../dom_misc';
+import { isTransparent } from './isTransparent';
+import { mergeClipPaths } from './mergeClipPaths';
+import * as ease from '../anim_ease';
+import { animateColor } from '../animate_color';
+import {
+ animate,
+ requestAnimFrame,
+ cancelAnimFrame,
+} from '../animate';
+import { createClass } from '../lang_class';
+/**
+ * @namespace fabric.util
+ */
+fabric.util = {
+ cos,
+ sin,
+ rotateVector,
+ createVector,
+ calcAngleBetweenVectors,
+ getHatVector,
+ getBisector,
+ degreesToRadians,
+ radiansToDegrees,
+ rotatePoint,
+ // probably we should stop exposing this from the interface
+ getRandomInt,
+ removeFromArray,
+ projectStrokeOnPoints,
+ // matrix.ts file
+ transformPoint,
+ invertTransform,
+ composeMatrix,
+ qrDecompose,
+ calcDimensionsMatrix,
+ calcRotateMatrix,
+ multiplyTransformMatrices,
+ // textStyles.ts file
+ stylesFromArray,
+ stylesToArray,
+ hasStyleChanged,
+ object: {
+ clone,
+ extend,
+ },
+ createCanvasElement,
+ createImage,
+ copyCanvasElement,
+ toDataURL,
+ toFixed,
+ matrixToSVG,
+ parsePreserveAspectRatioAttribute,
+ groupSVGElements,
+ parseUnit,
+ getSvgAttributes,
+ findScaleToFit,
+ findScaleToCover,
+ capValue,
+ saveObjectTransform,
+ resetObjectTransform,
+ addTransformToObject,
+ applyTransformToObject,
+ removeTransformFromObject,
+ makeBoundingBoxFromPoints,
+ sendPointToPlane,
+ transformPointRelativeToCanvas,
+ sendObjectToPlane,
+ string: {
+ camelize,
+ capitalize,
+ escapeXml,
+ graphemeSplit,
+ },
+ getKlass,
+ loadImage,
+ enlivenObjects,
+ enlivenObjectEnlivables,
+ array: {
+ min,
+ max,
+ },
+ pick,
+ joinPath,
+ parsePath,
+ makePathSimpler,
+ getSmoothPathFromPoints,
+ getPathSegmentsInfo,
+ getBoundsOfCurve,
+ getPointOnPath,
+ transformPath,
+ getRegularPolygonPath,
+ request,
+ setStyle,
+ isTouchEvent,
+ getPointer,
+ removeListener,
+ addListener,
+ wrapElement,
+ getScrollLeftTop,
+ getElementOffset,
+ getNodeCanvas,
+ cleanUpJsdomNode,
+ makeElementUnselectable,
+ makeElementSelectable,
+ isTransparent,
+ sizeAfterTransform,
+ mergeClipPaths,
+ ease,
+ animateColor,
+ animate,
+ requestAnimFrame,
+ cancelAnimFrame,
+ createClass,
+};
diff --git a/src/util/misc/objectTransforms.ts b/src/util/misc/objectTransforms.ts
index a804c71dd3d..daf0fd4eab5 100644
--- a/src/util/misc/objectTransforms.ts
+++ b/src/util/misc/objectTransforms.ts
@@ -1,7 +1,8 @@
import { Point } from "../../point.class";
import { TMat2D } from "../../typedefs";
-import { invertTransform, multiplyTransformMatrices, qrDecompose } from "./matrix";
-import type { TComposeMatrixArgs } from './matrix';
+import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints';
+import { invertTransform, multiplyTransformMatrices, qrDecompose, calcDimensionsMatrix } from "./matrix";
+import type { TComposeMatrixArgs, TScaleMatrixArgs } from './matrix';
type FabricObject = any;
@@ -89,3 +90,31 @@ export const saveObjectTransform = (
flipY: target.flipY,
top: target.top
});
+
+/**
+ * given a width and height, return the size of the bounding box
+ * that can contains the box with width/height with applied transform
+ * described in options.
+ * Use to calculate the boxes around objects for controls.
+ * @memberOf fabric.util
+ * @param {Number} width
+ * @param {Number} height
+ * @param {Object} options
+ * @param {Number} options.scaleX
+ * @param {Number} options.scaleY
+ * @param {Number} options.skewX
+ * @param {Number} options.skewY
+ * @returns {Point} size
+ */
+export const sizeAfterTransform = (width: number, height: number, options: TScaleMatrixArgs) => {
+ const dimX = width / 2, dimY = height / 2,
+ points = [
+ new Point(-dimX, -dimY),
+ new Point(dimX, -dimY),
+ new Point(-dimX, dimY),
+ new Point(dimX, dimY),
+ ],
+ transformMatrix = calcDimensionsMatrix(options),
+ bbox = makeBoundingBoxFromPoints(points, transformMatrix);
+ return new Point(bbox.width, bbox.height);
+};
diff --git a/src/util/misc/rotatePoint.ts b/src/util/misc/rotatePoint.ts
index 4f103795ddc..f87db19a153 100644
--- a/src/util/misc/rotatePoint.ts
+++ b/src/util/misc/rotatePoint.ts
@@ -10,4 +10,4 @@ import type { TRadian } from '../../typedefs';
* @param {TRadian} radians The radians of the angle for the rotation
* @return {Point} The new rotated point
*/
-export const rotatePoint = (point: Point, origin: Point, radians: TRadian): Point => point.rotate(origin, radians);
+export const rotatePoint = (point: Point, origin: Point, radians: TRadian): Point => point.rotate(radians, origin);
diff --git a/src/util/misc/vectors.ts b/src/util/misc/vectors.ts
index 4f61c0b0794..d5751fa86c2 100644
--- a/src/util/misc/vectors.ts
+++ b/src/util/misc/vectors.ts
@@ -1,5 +1,3 @@
-import { sin } from './sin';
-import { cos } from './cos';
import { IPoint, Point } from '../../point.class';
import { TRadian } from '../../typedefs';
@@ -11,14 +9,7 @@ import { TRadian } from '../../typedefs';
* @param {Number} radians The radians of the angle for the rotation
* @return {Point} The new rotated point
*/
-export const rotateVector = (vector: Point, radians: TRadian) => {
- const sinus = sin(radians),
- cosinus = cos(radians);
- return new Point(
- vector.x * cosinus - vector.y * sinus,
- vector.x * sinus + vector.y * cosinus,
- );
-};
+export const rotateVector = (vector: Point, radians: TRadian) => vector.rotate(radians);
/**
* Creates a vetor from points represented as a point
diff --git a/test/unit/cache.js b/test/unit/cache.js
new file mode 100644
index 00000000000..f98e0c5dad4
--- /dev/null
+++ b/test/unit/cache.js
@@ -0,0 +1,30 @@
+(function() {
+ const perfLimit = fabric.config.perfLimitSizeTotal;
+ QUnit.module('Cache', {
+ beforeEach: function() {
+ fabric.config.perfLimitSizeTotal = 10000;
+ },
+ afterEach: function() {
+ fabric.config.perfLimitSizeTotal = perfLimit;
+ }
+ });
+
+ QUnit.test('Cache.limitDimsByArea', function(assert) {
+ assert.ok(typeof fabric.cache.limitDimsByArea === 'function');
+ var [x, y] = fabric.cache.limitDimsByArea(1);
+ assert.equal(x, 100);
+ assert.equal(y, 100);
+ });
+
+ QUnit.test('Cache.limitDimsByArea ar > 1', function(assert) {
+ var [x , y] = fabric.cache.limitDimsByArea(3);
+ assert.equal(x, 173);
+ assert.equal(y, 57);
+ });
+
+ QUnit.test('Cache.limitDimsByArea ar < 1', function(assert) {
+ var [x, y] = fabric.cache.limitDimsByArea(1 / 3);
+ assert.equal(x, 57);
+ assert.equal(y, 173);
+ });
+})();
diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js
index cd85ef7de7e..f8f46ec7c5f 100644
--- a/test/unit/canvas_static.js
+++ b/test/unit/canvas_static.js
@@ -127,6 +127,10 @@
return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback);
}
+ function createImageStub() {
+ return new fabric.Image(_createImageElement(), { width: 0, height: 0 });
+ }
+
function setSrc(img, src, callback) {
img.onload = callback;
img.src = src;
@@ -878,7 +882,7 @@
text = new fabric.Text('Text'),
group = new fabric.Group([text, line]),
ellipse = new fabric.Ellipse(),
- image = new fabric.Image({width: 0, height: 0}),
+ image = createImageStub(),
path2 = new fabric.Path('M 0 0 L 200 100 L 200 300 z'),
path3 = new fabric.Path('M 50 50 L 100 300 L 400 400 z'),
pathGroup = new fabric.Group([path2, path3]);
@@ -913,9 +917,9 @@
text = new fabric.Text('Text'),
group = new fabric.Group([text, line]),
ellipse = new fabric.Ellipse(),
- image = new fabric.Image({width: 0, height: 0}),
- imageBG = new fabric.Image({width: 0, height: 0}),
- imageOL = new fabric.Image({width: 0, height: 0}),
+ image = createImageStub(),
+ imageBG = createImageStub(),
+ imageOL = createImageStub(),
path2 = new fabric.Path('M 0 0 L 200 100 L 200 300 z'),
path3 = new fabric.Path('M 50 50 L 100 300 L 400 400 z'),
pathGroup = new fabric.Group([path2, path3]);
@@ -953,7 +957,7 @@
text = new fabric.Text('Text'),
group = new fabric.Group([text, line]),
ellipse = new fabric.Ellipse(),
- image = new fabric.Image({width: 0, height: 0}),
+ image = createImageStub(),
path2 = new fabric.Path('M 0 0 L 200 100 L 200 300 z'),
path3 = new fabric.Path('M 50 50 L 100 300 L 400 400 z'),
pathGroup = new fabric.Group([path2, path3]);
@@ -983,10 +987,7 @@
});
QUnit.test('toSVG with exclude from export background', function(assert) {
- var image = fabric.document.createElement('img'),
- imageBG = new fabric.Image(image, {width: 0, height: 0}),
- imageOL = new fabric.Image(image, {width: 0, height: 0});
-
+ const imageBG = createImageStub(), imageOL = createImageStub();
canvas.renderOnAddRemove = false;
canvas.backgroundImage = imageBG;
canvas.overlayImage = imageOL;
diff --git a/test/unit/parser.js b/test/unit/parser.js
index d14d072e2ea..36047d9a701 100644
--- a/test/unit/parser.js
+++ b/test/unit/parser.js
@@ -125,6 +125,22 @@
var done = assert.async();
assert.ok(typeof fabric.parseElements === 'function');
+ function makeElement(tagName, attributes) {
+ var el = fabric.document.createElement(tagName);
+ for (var prop in attributes) {
+ if (prop === 'class') {
+ el.className = attributes[prop];
+ }
+ else if (prop === 'for') {
+ el.htmlFor = attributes[prop];
+ }
+ else {
+ el.setAttribute(prop, attributes[prop]);
+ }
+ }
+ return el;
+ }
+
function getOptions(options) {
return fabric.util.object.extend(fabric.util.object.clone({
left: 10, top: 20, width: 30, height: 40
@@ -132,10 +148,10 @@
}
var elements = [
- fabric.util.makeElement('rect', getOptions()),
- fabric.util.makeElement('circle', getOptions({ r: 14 })),
- fabric.util.makeElement('path', getOptions({ d: 'M 100 100 L 300 100 L 200 300 z' })),
- fabric.util.makeElement('inexistent', getOptions())
+ makeElement('rect', getOptions()),
+ makeElement('circle', getOptions({ r: 14 })),
+ makeElement('path', getOptions({ d: 'M 100 100 L 300 100 L 200 300 z' })),
+ makeElement('inexistent', getOptions())
];
fabric.parseElements(elements, function(parsedElements) {
assert.ok(parsedElements[0] instanceof fabric.Rect);
diff --git a/test/unit/point.js b/test/unit/point.js
index fbb42e61634..0096de3d8b3 100644
--- a/test/unit/point.js
+++ b/test/unit/point.js
@@ -445,4 +445,18 @@
assert.equal(returned.y, point.y, 'y coords should be same');
});
+ QUnit.test('rotate', function(assert) {
+ var point = new fabric.Point(5, 1);
+ var rotated = point.rotate(Math.PI);
+ assert.equal(rotated.x, -5, 'rotated x');
+ assert.equal(rotated.y, -1, 'rotated y');
+ });
+
+ QUnit.test('rotate with origin point', function(assert) {
+ var point = new fabric.Point(5, 1);
+ var rotated = point.rotate(Math.PI, new fabric.Point(4, 1));
+ assert.equal(rotated.x, 3, 'rotated x around 4');
+ assert.equal(rotated.y, 1, 'rotated y around 1');
+ });
+
})();
diff --git a/test/unit/util.js b/test/unit/util.js
index 81d9c85fc17..7e3b65df963 100644
--- a/test/unit/util.js
+++ b/test/unit/util.js
@@ -273,92 +273,22 @@
// assert.ok(axisPoint instanceof YAxisPoint); <-- fails
});
- QUnit.test('fabric.util.getById', function(assert) {
- assert.ok(typeof fabric.util.getById === 'function');
-
- var el = fabric.document.createElement('div');
- el.id = 'foobarbaz';
- fabric.document.body.appendChild(el);
-
- assert.equal(el, fabric.util.getById(el));
- assert.equal(el, fabric.util.getById('foobarbaz'));
- assert.equal(null, fabric.util.getById('likely-non-existent-id'));
- });
-
- QUnit.test('fabric.util.toArray', function(assert) {
- assert.ok(typeof fabric.util.toArray === 'function');
-
- assert.deepEqual(['x', 'y'], fabric.util.toArray({ 0: 'x', 1: 'y', length: 2 }));
- assert.deepEqual([1, 3], fabric.util.toArray((function(){ return arguments; })(1, 3)));
-
- var nodelist = fabric.document.getElementsByTagName('div'),
- converted = fabric.util.toArray(nodelist);
-
- assert.ok(converted instanceof Array);
- assert.equal(nodelist.length, converted.length);
- assert.equal(nodelist[0], converted[0]);
- assert.equal(nodelist[1], converted[1]);
- });
-
- QUnit.test('fabric.util.makeElement', function(assert) {
- var makeElement = fabric.util.makeElement;
- assert.ok(typeof makeElement === 'function');
-
- var el = makeElement('div');
-
- assert.equal(el.tagName.toLowerCase(), 'div');
- assert.equal(el.nodeType, 1);
-
- el = makeElement('p', { 'class': 'blah', 'for': 'boo_hoo', 'some_random-attribute': 'woot' });
-
- assert.equal(el.tagName.toLowerCase(), 'p');
- assert.equal(el.nodeType, 1);
- assert.equal(el.className, 'blah');
- assert.equal(el.htmlFor, 'boo_hoo');
- assert.equal(el.getAttribute('some_random-attribute'), 'woot');
- });
-
- QUnit.test('fabric.util.addClass', function(assert) {
- var addClass = fabric.util.addClass;
- assert.ok(typeof addClass === 'function');
-
- var el = fabric.document.createElement('div');
- addClass(el, 'foo');
- assert.equal(el.className, 'foo');
-
- addClass(el, 'bar');
- assert.equal(el.className, 'foo bar');
-
- addClass(el, 'baz qux');
- assert.equal(el.className, 'foo bar baz qux');
-
- addClass(el, 'foo');
- assert.equal(el.className, 'foo bar baz qux');
- });
-
QUnit.test('fabric.util.wrapElement', function(assert) {
var wrapElement = fabric.util.wrapElement;
assert.ok(typeof wrapElement === 'function');
-
+ var wrapper = fabric.document.createElement('div');
var el = fabric.document.createElement('p');
- var wrapper = wrapElement(el, 'div');
-
- assert.equal(wrapper.tagName.toLowerCase(), 'div');
- assert.equal(wrapper.firstChild, el);
-
- el = fabric.document.createElement('p');
- wrapper = wrapElement(el, 'div', { 'class': 'foo' });
+ var wrapper = wrapElement(el, wrapper);
assert.equal(wrapper.tagName.toLowerCase(), 'div');
assert.equal(wrapper.firstChild, el);
- assert.equal(wrapper.className, 'foo');
var childEl = fabric.document.createElement('span');
var parentEl = fabric.document.createElement('p');
parentEl.appendChild(childEl);
- wrapper = wrapElement(childEl, 'strong');
+ wrapper = wrapElement(childEl, fabric.document.createElement('strong'));
// wrapper is now in between parent and child
assert.equal(wrapper.parentNode, parentEl);
@@ -1058,25 +988,6 @@
assert.deepEqual(matrix, fabric.iMatrix, 'default is identity matrix');
});
- QUnit.test('fabric.util.limitDimsByArea', function(assert) {
- assert.ok(typeof fabric.util.limitDimsByArea === 'function');
- var dims = fabric.util.limitDimsByArea(1, 10000);
- assert.equal(dims.x, 100);
- assert.equal(dims.y, 100);
- });
-
- QUnit.test('fabric.util.limitDimsByArea ar > 1', function(assert) {
- var dims = fabric.util.limitDimsByArea(3, 10000);
- assert.equal(dims.x, 173);
- assert.equal(dims.y, 57);
- });
-
- QUnit.test('fabric.util.limitDimsByArea ar < 1', function(assert) {
- var dims = fabric.util.limitDimsByArea(1 / 3, 10000);
- assert.equal(dims.x, 57);
- assert.equal(dims.y, 173);
- });
-
QUnit.test('fabric.util.capValue ar < 1', function(assert) {
assert.ok(typeof fabric.util.capValue === 'function');
var val = fabric.util.capValue(3, 10, 70);