sg-vhuman
Version:
简单的数字人虚拟形象组件
1,238 lines (1,237 loc) • 801 kB
JavaScript
import { ref as vt, onMounted as Pt, withDirectives as Et, createElementBlock as Ct, openBlock as St, normalizeStyle as kt, createElementVNode as bt, createCommentVNode as Dt, vShow as At } from "vue";
class Mt {
constructor() {
this.peerConnection = null, this.remoteStream = null;
}
/**
* 初始化 WebRTC 连接
* @param {Object} configuration - WebRTC 配置项,包含 ICE 服务器等信息
* @returns {Promise<boolean>} 初始化是否成功
*/
async initializeConnection(f = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" }
]
}) {
try {
return this.peerConnection = new RTCPeerConnection(f), this.peerConnection.addTransceiver("video", { direction: "recvonly" }), this.peerConnection.addTransceiver("audio", { direction: "recvonly" }), this.setupPeerConnectionListeners(), !0;
} catch (st) {
return console.error("Failed to initialize WebRTC connection:", st), !1;
}
}
/**
* 设置 WebRTC 连接的事件监听器
* 包括 ICE 候选者和远程流接收的处理
*/
setupPeerConnectionListeners() {
this.peerConnection.onicecandidate = (f) => {
f.candidate && this.onIceCandidate(f.candidate);
}, this.peerConnection.ontrack = (f) => {
this.remoteStream = f.streams[0], this.onRemoteStreamReceived(this.remoteStream);
};
}
/**
* 创建并设置本地 SDP offer
* @returns {Promise<RTCSessionDescription>} 本地 SDP 描述
*/
async createOffer() {
try {
const f = await this.peerConnection.createOffer();
return await this.peerConnection.setLocalDescription(f), this.peerConnection.localDescription;
} catch (f) {
throw console.error("Error creating offer:", f), f;
}
}
/**
* 处理远程 SDP answer
* @param {RTCSessionDescriptionInit} answer - 远程 SDP 答复
*/
async handleAnswer(f) {
try {
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(f));
} catch (st) {
throw console.error("Error handling answer:", st), st;
}
}
// 回调函数接口
onIceCandidate(f) {
}
onRemoteStreamReceived(f) {
}
/**
* 关闭并清理 WebRTC 连接
*/
closeConnection() {
this.peerConnection && this.peerConnection.close(), this.remoteStream = null, this.peerConnection = null;
}
}
function Ft(q) {
if (Object.prototype.hasOwnProperty.call(q, "__esModule")) return q;
var f = q.default;
if (typeof f == "function") {
var st = function rt() {
return this instanceof rt ? Reflect.construct(f, arguments, this.constructor) : f.apply(this, arguments);
};
st.prototype = f.prototype;
} else st = {};
return Object.defineProperty(st, "__esModule", { value: !0 }), Object.keys(q).forEach(function(rt) {
var at = Object.getOwnPropertyDescriptor(q, rt);
Object.defineProperty(st, rt, at.get ? at : {
enumerable: !0,
get: function() {
return q[rt];
}
});
}), st;
}
var pt = {};
const Lt = {}, It = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
default: Lt
}, Symbol.toStringTag, { value: "Module" })), _t = /* @__PURE__ */ Ft(It);
var xt;
function jt() {
return xt || (xt = 1, function(q) {
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
var f = f || { version: "5.3.0" };
if (q.fabric = f, typeof document < "u" && typeof window < "u")
document instanceof (typeof HTMLDocument < "u" ? HTMLDocument : Document) ? f.document = document : f.document = document.implementation.createHTMLDocument(""), f.window = window;
else {
var st = _t, rt = new st.JSDOM(
decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E"),
{
features: {
FetchExternalResources: ["img"]
},
resources: "usable"
}
).window;
f.document = rt.document, f.jsdomImplForWrapper = _t.implForWrapper, f.nodeCanvas = _t.Canvas, f.window = rt, DOMParser = f.window.DOMParser;
}
f.isTouchSupported = "ontouchstart" in f.window || "ontouchstart" in f.document || f.window && f.window.navigator && f.window.navigator.maxTouchPoints > 0, f.isLikelyNode = typeof Buffer < "u" && typeof window > "u", f.SHARED_ATTRIBUTES = [
"display",
"transform",
"fill",
"fill-opacity",
"fill-rule",
"opacity",
"stroke",
"stroke-dasharray",
"stroke-linecap",
"stroke-dashoffset",
"stroke-linejoin",
"stroke-miterlimit",
"stroke-opacity",
"stroke-width",
"id",
"paint-order",
"vector-effect",
"instantiated_by_use",
"clip-path"
], f.DPI = 96, f.reNum = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:[eE][-+]?\\d+)?)", f.commaWsp = "(?:\\s+,?\\s*|,\\s*)", f.rePathCommand = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:[eE][-+]?\d+)?)/ig, f.reNonWord = /[ \n\.,;!\?\-]/, f.fontPaths = {}, f.iMatrix = [1, 0, 0, 1, 0, 0], f.svgNS = "http://www.w3.org/2000/svg", f.perfLimitSizeTotal = 2097152, f.maxCacheSideLimit = 4096, f.minCacheSideLimit = 256, f.charWidthsCache = {}, f.textureSize = 2048, f.disableStyleCopyPaste = !1, f.enableGLFiltering = !0, f.devicePixelRatio = f.window.devicePixelRatio || f.window.webkitDevicePixelRatio || f.window.mozDevicePixelRatio || 1, f.browserShadowBlurConstant = 1, f.arcToSegmentsCache = {}, f.boundsOfCurveCache = {}, f.cachesBoundsOfCurve = !0, f.forceGLPutImageData = !1, f.initFilterBackend = function() {
if (f.enableGLFiltering && f.isWebglSupported && f.isWebglSupported(f.textureSize))
return console.log("max texture size: " + f.maxTextureSize), new f.WebglFilterBackend({ tileSize: f.textureSize });
if (f.Canvas2dFilterBackend)
return new f.Canvas2dFilterBackend();
}, typeof document < "u" && typeof window < "u" && (window.fabric = f), function() {
function c(t, n) {
if (this.__eventListeners[t]) {
var a = this.__eventListeners[t];
n ? a[a.indexOf(n)] = !1 : f.util.array.fill(a, !1);
}
}
function s(t, n) {
if (this.__eventListeners || (this.__eventListeners = {}), arguments.length === 1)
for (var a in t)
this.on(a, t[a]);
else
this.__eventListeners[t] || (this.__eventListeners[t] = []), this.__eventListeners[t].push(n);
return this;
}
function h(t, n) {
var a = (function() {
n.apply(this, arguments), this.off(t, a);
}).bind(this);
this.on(t, a);
}
function o(t, n) {
if (arguments.length === 1)
for (var a in t)
h.call(this, a, t[a]);
else
h.call(this, t, n);
return this;
}
function e(t, n) {
if (!this.__eventListeners)
return this;
if (arguments.length === 0)
for (t in this.__eventListeners)
c.call(this, t);
else if (arguments.length === 1 && typeof arguments[0] == "object")
for (var a in t)
c.call(this, a, t[a]);
else
c.call(this, t, n);
return this;
}
function r(t, n) {
if (!this.__eventListeners)
return this;
var a = this.__eventListeners[t];
if (!a)
return this;
for (var i = 0, l = a.length; i < l; i++)
a[i] && a[i].call(this, n || {});
return this.__eventListeners[t] = a.filter(function(u) {
return u !== !1;
}), this;
}
f.Observable = {
fire: r,
on: s,
once: o,
off: e
};
}(), f.Collection = {
_objects: [],
/**
* Adds objects to collection, Canvas or Group, then renders canvas
* (if `renderOnAddRemove` is not `false`).
* in case of Group no changes to bounding box are made.
* Objects should be instances of (or inherit from) fabric.Object
* Use of this function is highly discouraged for groups.
* you can add a bunch of objects with the add method but then you NEED
* to run a addWithUpdate call for the Group class or position/bbox will be wrong.
* @param {...fabric.Object} object Zero or more fabric instances
* @return {Self} thisArg
* @chainable
*/
add: function() {
if (this._objects.push.apply(this._objects, arguments), this._onObjectAdded)
for (var c = 0, s = arguments.length; c < s; c++)
this._onObjectAdded(arguments[c]);
return this.renderOnAddRemove && this.requestRenderAll(), this;
},
/**
* Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
* An object should be an instance of (or inherit from) fabric.Object
* Use of this function is highly discouraged for groups.
* you can add a bunch of objects with the insertAt method but then you NEED
* to run a addWithUpdate call for the Group class or position/bbox will be wrong.
* @param {Object} object Object to insert
* @param {Number} index Index to insert object at
* @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
* @return {Self} thisArg
* @chainable
*/
insertAt: function(c, s, h) {
var o = this._objects;
return h ? o[s] = c : o.splice(s, 0, c), this._onObjectAdded && this._onObjectAdded(c), this.renderOnAddRemove && this.requestRenderAll(), this;
},
/**
* Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
* @param {...fabric.Object} object Zero or more fabric instances
* @return {Self} thisArg
* @chainable
*/
remove: function() {
for (var c = this._objects, s, h = !1, o = 0, e = arguments.length; o < e; o++)
s = c.indexOf(arguments[o]), s !== -1 && (h = !0, c.splice(s, 1), this._onObjectRemoved && this._onObjectRemoved(arguments[o]));
return this.renderOnAddRemove && h && this.requestRenderAll(), this;
},
/**
* Executes given function for each object in this group
* @param {Function} callback
* Callback invoked with current object as first argument,
* index - as second and an array of all objects - as third.
* Callback is invoked in a context of Global Object (e.g. `window`)
* when no `context` argument is given
*
* @param {Object} context Context (aka thisObject)
* @return {Self} thisArg
* @chainable
*/
forEachObject: function(c, s) {
for (var h = this.getObjects(), o = 0, e = h.length; o < e; o++)
c.call(s, h[o], o, h);
return this;
},
/**
* Returns an array of children objects of this instance
* Type parameter introduced in 1.3.10
* since 2.3.5 this method return always a COPY of the array;
* @param {String} [type] When specified, only objects of this type are returned
* @return {Array}
*/
getObjects: function(c) {
return typeof c > "u" ? this._objects.concat() : this._objects.filter(function(s) {
return s.type === c;
});
},
/**
* Returns object at specified index
* @param {Number} index
* @return {Self} thisArg
*/
item: function(c) {
return this._objects[c];
},
/**
* Returns true if collection contains no objects
* @return {Boolean} true if collection is empty
*/
isEmpty: function() {
return this._objects.length === 0;
},
/**
* Returns a size of a collection (i.e: length of an array containing its objects)
* @return {Number} Collection size
*/
size: function() {
return this._objects.length;
},
/**
* Returns true if collection contains an object
* @param {Object} object Object to check against
* @param {Boolean} [deep=false] `true` to check all descendants, `false` to check only `_objects`
* @return {Boolean} `true` if collection contains an object
*/
contains: function(c, s) {
return this._objects.indexOf(c) > -1 ? !0 : s ? this._objects.some(function(h) {
return typeof h.contains == "function" && h.contains(c, !0);
}) : !1;
},
/**
* Returns number representation of a collection complexity
* @return {Number} complexity
*/
complexity: function() {
return this._objects.reduce(function(c, s) {
return c += s.complexity ? s.complexity() : 0, c;
}, 0);
}
}, f.CommonMethods = {
/**
* Sets object's properties from options
* @param {Object} [options] Options object
*/
_setOptions: function(c) {
for (var s in c)
this.set(s, c[s]);
},
/**
* @private
* @param {Object} [filler] Options object
* @param {String} [property] property to set the Gradient to
*/
_initGradient: function(c, s) {
c && c.colorStops && !(c instanceof f.Gradient) && this.set(s, new f.Gradient(c));
},
/**
* @private
* @param {Object} [filler] Options object
* @param {String} [property] property to set the Pattern to
* @param {Function} [callback] callback to invoke after pattern load
*/
_initPattern: function(c, s, h) {
c && c.source && !(c instanceof f.Pattern) ? this.set(s, new f.Pattern(c, h)) : h && h();
},
/**
* @private
*/
_setObject: function(c) {
for (var s in c)
this._set(s, c[s]);
},
/**
* Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
* @param {String|Object} key Property name or object (if object, iterate over the object properties)
* @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
* @return {fabric.Object} thisArg
* @chainable
*/
set: function(c, s) {
return typeof c == "object" ? this._setObject(c) : this._set(c, s), this;
},
_set: function(c, s) {
this[c] = s;
},
/**
* Toggles specified property from `true` to `false` or from `false` to `true`
* @param {String} property Property to toggle
* @return {fabric.Object} thisArg
* @chainable
*/
toggle: function(c) {
var s = this.get(c);
return typeof s == "boolean" && this.set(c, !s), this;
},
/**
* Basic getter
* @param {String} property Property name
* @return {*} value of a property
*/
get: function(c) {
return this[c];
}
}, function(c) {
var s = Math.sqrt, h = Math.atan2, o = Math.pow, e = Math.PI / 180, r = Math.PI / 2;
f.util = {
/**
* Calculate the cos of an angle, avoiding returning floats for known results
* @static
* @memberOf fabric.util
* @param {Number} angle the angle in radians or in degree
* @return {Number}
*/
cos: function(t) {
if (t === 0)
return 1;
t < 0 && (t = -t);
var n = t / r;
switch (n) {
case 1:
case 3:
return 0;
case 2:
return -1;
}
return Math.cos(t);
},
/**
* Calculate the sin of an angle, avoiding returning floats for known results
* @static
* @memberOf fabric.util
* @param {Number} angle the angle in radians or in degree
* @return {Number}
*/
sin: function(t) {
if (t === 0)
return 0;
var n = t / r, a = 1;
switch (t < 0 && (a = -1), n) {
case 1:
return a;
case 2:
return 0;
case 3:
return -a;
}
return Math.sin(t);
},
/**
* Removes value from an array.
* Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
* @static
* @memberOf fabric.util
* @param {Array} array
* @param {*} value
* @return {Array} original array
*/
removeFromArray: function(t, n) {
var a = t.indexOf(n);
return a !== -1 && t.splice(a, 1), t;
},
/**
* Returns random number between 2 specified ones.
* @static
* @memberOf fabric.util
* @param {Number} min lower limit
* @param {Number} max upper limit
* @return {Number} random value (between min and max)
*/
getRandomInt: function(t, n) {
return Math.floor(Math.random() * (n - t + 1)) + t;
},
/**
* Transforms degrees to radians.
* @static
* @memberOf fabric.util
* @param {Number} degrees value in degrees
* @return {Number} value in radians
*/
degreesToRadians: function(t) {
return t * e;
},
/**
* Transforms radians to degrees.
* @static
* @memberOf fabric.util
* @param {Number} radians value in radians
* @return {Number} value in degrees
*/
radiansToDegrees: function(t) {
return t / e;
},
/**
* Rotates `point` around `origin` with `radians`
* @static
* @memberOf fabric.util
* @param {fabric.Point} point The point to rotate
* @param {fabric.Point} origin The origin of the rotation
* @param {Number} radians The radians of the angle for the rotation
* @return {fabric.Point} The new rotated point
*/
rotatePoint: function(t, n, a) {
var i = new f.Point(t.x - n.x, t.y - n.y), l = f.util.rotateVector(i, a);
return new f.Point(l.x, l.y).addEquals(n);
},
/**
* Rotates `vector` with `radians`
* @static
* @memberOf fabric.util
* @param {Object} vector The vector to rotate (x and y)
* @param {Number} radians The radians of the angle for the rotation
* @return {Object} The new rotated point
*/
rotateVector: function(t, n) {
var a = f.util.sin(n), i = f.util.cos(n), l = t.x * i - t.y * a, u = t.x * a + t.y * i;
return {
x: l,
y: u
};
},
/**
* Creates a vetor from points represented as a point
* @static
* @memberOf fabric.util
*
* @typedef {Object} Point
* @property {number} x
* @property {number} y
*
* @param {Point} from
* @param {Point} to
* @returns {Point} vector
*/
createVector: function(t, n) {
return new f.Point(n.x - t.x, n.y - t.y);
},
/**
* Calculates angle between 2 vectors using dot product
* @static
* @memberOf fabric.util
* @param {Point} a
* @param {Point} b
* @returns the angle in radian between the vectors
*/
calcAngleBetweenVectors: function(t, n) {
return Math.acos((t.x * n.x + t.y * n.y) / (Math.hypot(t.x, t.y) * Math.hypot(n.x, n.y)));
},
/**
* @static
* @memberOf fabric.util
* @param {Point} v
* @returns {Point} vector representing the unit vector of pointing to the direction of `v`
*/
getHatVector: function(t) {
return new f.Point(t.x, t.y).multiply(1 / Math.hypot(t.x, t.y));
},
/**
* @static
* @memberOf fabric.util
* @param {Point} A
* @param {Point} B
* @param {Point} C
* @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle
*/
getBisector: function(t, n, a) {
var i = f.util.createVector(t, n), l = f.util.createVector(t, a), u = f.util.calcAngleBetweenVectors(i, l), d = f.util.calcAngleBetweenVectors(f.util.rotateVector(i, u), l), g = u * (d === 0 ? 1 : -1) / 2;
return {
vector: f.util.getHatVector(f.util.rotateVector(i, g)),
angle: u
};
},
/**
* Project stroke width on points returning 2 projections for each point as follows:
* - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke.
* - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector.
* - `round`: same as `bevel`
* Used to calculate object's bounding box
* @static
* @memberOf fabric.util
* @param {Point[]} points
* @param {Object} options
* @param {number} options.strokeWidth
* @param {'miter'|'bevel'|'round'} options.strokeLineJoin
* @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit
* @param {boolean} options.strokeUniform
* @param {number} options.scaleX
* @param {number} options.scaleY
* @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points
* @returns {fabric.Point[]} array of size 2n/4n of all suspected points
*/
projectStrokeOnPoints: function(t, n, a) {
var i = [], l = n.strokeWidth / 2, u = n.strokeUniform ? new f.Point(1 / n.scaleX, 1 / n.scaleY) : new f.Point(1, 1), d = function(g) {
var m = l / Math.hypot(g.x, g.y);
return new f.Point(g.x * m * u.x, g.y * m * u.y);
};
return t.length <= 1 || t.forEach(function(g, m) {
var v = new f.Point(g.x, g.y), y, w;
m === 0 ? (w = t[m + 1], y = a ? d(f.util.createVector(w, v)).addEquals(v) : t[t.length - 1]) : m === t.length - 1 ? (y = t[m - 1], w = a ? d(f.util.createVector(y, v)).addEquals(v) : t[0]) : (y = t[m - 1], w = t[m + 1]);
var M = f.util.getBisector(v, y, w), X = M.vector, W = M.angle, G, N;
if (n.strokeLineJoin === "miter" && (G = -l / Math.sin(W / 2), N = new f.Point(
X.x * G * u.x,
X.y * G * u.y
), Math.hypot(N.x, N.y) / l <= n.strokeMiterLimit)) {
i.push(v.add(N)), i.push(v.subtract(N));
return;
}
G = -l * Math.SQRT2, N = new f.Point(
X.x * G * u.x,
X.y * G * u.y
), i.push(v.add(N)), i.push(v.subtract(N));
}), i;
},
/**
* Apply transform t to point p
* @static
* @memberOf fabric.util
* @param {fabric.Point} p The point to transform
* @param {Array} t The transform
* @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
* @return {fabric.Point} The transformed point
*/
transformPoint: function(t, n, a) {
return a ? new f.Point(
n[0] * t.x + n[2] * t.y,
n[1] * t.x + n[3] * t.y
) : new f.Point(
n[0] * t.x + n[2] * t.y + n[4],
n[1] * t.x + n[3] * t.y + n[5]
);
},
/**
* Returns coordinates of points's bounding rectangle (left, top, width, height)
* @param {Array} points 4 points array
* @param {Array} [transform] an array of 6 numbers representing a 2x3 transform matrix
* @return {Object} Object with left, top, width, height properties
*/
makeBoundingBoxFromPoints: function(t, n) {
if (n)
for (var a = 0; a < t.length; a++)
t[a] = f.util.transformPoint(t[a], n);
var i = [t[0].x, t[1].x, t[2].x, t[3].x], l = f.util.array.min(i), u = f.util.array.max(i), d = u - l, g = [t[0].y, t[1].y, t[2].y, t[3].y], m = f.util.array.min(g), v = f.util.array.max(g), y = v - m;
return {
left: l,
top: m,
width: d,
height: y
};
},
/**
* Invert transformation t
* @static
* @memberOf fabric.util
* @param {Array} t The transform
* @return {Array} The inverted transform
*/
invertTransform: function(t) {
var n = 1 / (t[0] * t[3] - t[1] * t[2]), a = [n * t[3], -n * t[1], -n * t[2], n * t[0]], i = f.util.transformPoint({ x: t[4], y: t[5] }, a, !0);
return a[4] = -i.x, a[5] = -i.y, a;
},
/**
* A wrapper around Number#toFixed, which contrary to native method returns number, not string.
* @static
* @memberOf fabric.util
* @param {Number|String} number number to operate on
* @param {Number} fractionDigits number of fraction digits to "leave"
* @return {Number}
*/
toFixed: function(t, n) {
return parseFloat(Number(t).toFixed(n));
},
/**
* Converts from attribute value to pixel value if applicable.
* Returns converted pixels or original value not converted.
* @param {Number|String} value number to operate on
* @param {Number} fontSize
* @return {Number|String}
*/
parseUnit: function(t, n) {
var a = /\D{0,2}$/.exec(t), i = parseFloat(t);
switch (n || (n = f.Text.DEFAULT_SVG_FONT_SIZE), a[0]) {
case "mm":
return i * f.DPI / 25.4;
case "cm":
return i * f.DPI / 2.54;
case "in":
return i * f.DPI;
case "pt":
return i * f.DPI / 72;
// or * 4 / 3
case "pc":
return i * f.DPI / 72 * 12;
// or * 16
case "em":
return i * n;
default:
return i;
}
},
/**
* Function which always returns `false`.
* @static
* @memberOf fabric.util
* @return {Boolean}
*/
falseFunction: function() {
return !1;
},
/**
* Returns klass "Class" object of given namespace
* @memberOf fabric.util
* @param {String} type Type of object (eg. 'circle')
* @param {String} namespace Namespace to get klass "Class" object from
* @return {Object} klass "Class"
*/
getKlass: function(t, n) {
return t = f.util.string.camelize(t.charAt(0).toUpperCase() + t.slice(1)), f.util.resolveNamespace(n)[t];
},
/**
* Returns array of attributes for given svg that fabric parses
* @memberOf fabric.util
* @param {String} type Type of svg element (eg. 'circle')
* @return {Array} string names of supported attributes
*/
getSvgAttributes: function(t) {
var n = [
"instantiated_by_use",
"style",
"id",
"class"
];
switch (t) {
case "linearGradient":
n = n.concat(["x1", "y1", "x2", "y2", "gradientUnits", "gradientTransform"]);
break;
case "radialGradient":
n = n.concat(["gradientUnits", "gradientTransform", "cx", "cy", "r", "fx", "fy", "fr"]);
break;
case "stop":
n = n.concat(["offset", "stop-color", "stop-opacity"]);
break;
}
return n;
},
/**
* Returns object of given namespace
* @memberOf fabric.util
* @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
* @return {Object} Object for given namespace (default fabric)
*/
resolveNamespace: function(t) {
if (!t)
return f;
var n = t.split("."), a = n.length, i, l = c || f.window;
for (i = 0; i < a; ++i)
l = l[n[i]];
return l;
},
/**
* Loads image element from given url and passes it to a callback
* @memberOf fabric.util
* @param {String} url URL representing an image
* @param {Function} callback Callback; invoked with loaded image
* @param {*} [context] Context to invoke callback in
* @param {Object} [crossOrigin] crossOrigin value to set image element to
*/
loadImage: function(t, n, a, i) {
if (!t) {
n && n.call(a, t);
return;
}
var l = f.util.createImage(), u = function() {
n && n.call(a, l, !1), l = l.onload = l.onerror = null;
};
l.onload = u, l.onerror = function() {
f.log("Error loading " + l.src), n && n.call(a, null, !0), l = l.onload = l.onerror = null;
}, t.indexOf("data") !== 0 && i !== void 0 && i !== null && (l.crossOrigin = i), t.substring(0, 14) === "data:image/svg" && (l.onload = null, f.util.loadImageInDom(l, u)), l.src = t;
},
/**
* Attaches SVG image with data: URL to the dom
* @memberOf fabric.util
* @param {Object} img Image object with data:image/svg src
* @param {Function} callback Callback; invoked with loaded image
* @return {Object} DOM element (div containing the SVG image)
*/
loadImageInDom: function(t, n) {
var a = f.document.createElement("div");
a.style.width = a.style.height = "1px", a.style.left = a.style.top = "-100%", a.style.position = "absolute", a.appendChild(t), f.document.querySelector("body").appendChild(a), t.onload = function() {
n(), a.parentNode.removeChild(a), a = null;
};
},
/**
* Creates corresponding fabric instances from their object representations
* @static
* @memberOf fabric.util
* @param {Array} objects Objects to enliven
* @param {Function} callback Callback to invoke when all objects are created
* @param {String} namespace Namespace to get klass "Class" object from
* @param {Function} reviver Method for further parsing of object elements,
* called after each fabric object created.
*/
enlivenObjects: function(t, n, a, i) {
t = t || [];
var l = [], u = 0, d = t.length;
function g() {
++u === d && n && n(l.filter(function(m) {
return m;
}));
}
if (!d) {
n && n(l);
return;
}
t.forEach(function(m, v) {
if (!m || !m.type) {
g();
return;
}
var y = f.util.getKlass(m.type, a);
y.fromObject(m, function(w, M) {
M || (l[v] = w), i && i(m, w, M), g();
});
});
},
/**
* Creates corresponding fabric instances residing in an object, e.g. `clipPath`
* @see {@link fabric.Object.ENLIVEN_PROPS}
* @param {Object} object
* @param {Object} [context] assign enlived props to this object (pass null to skip this)
* @param {(objects:fabric.Object[]) => void} callback
*/
enlivenObjectEnlivables: function(t, n, a) {
var i = f.Object.ENLIVEN_PROPS.filter(function(l) {
return !!t[l];
});
f.util.enlivenObjects(i.map(function(l) {
return t[l];
}), function(l) {
var u = {};
i.forEach(function(d, g) {
u[d] = l[g], n && (n[d] = l[g]);
}), a && a(u);
});
},
/**
* Create and wait for loading of patterns
* @static
* @memberOf fabric.util
* @param {Array} patterns Objects to enliven
* @param {Function} callback Callback to invoke when all objects are created
* called after each fabric object created.
*/
enlivenPatterns: function(t, n) {
t = t || [];
function a() {
++l === u && n && n(i);
}
var i = [], l = 0, u = t.length;
if (!u) {
n && n(i);
return;
}
t.forEach(function(d, g) {
d && d.source ? new f.Pattern(d, function(m) {
i[g] = m, a();
}) : (i[g] = d, a());
});
},
/**
* Groups SVG elements (usually those retrieved from SVG document)
* @static
* @memberOf fabric.util
* @param {Array} elements SVG elements to group
* @param {Object} [options] Options object
* @param {String} path Value to set sourcePath to
* @return {fabric.Object|fabric.Group}
*/
groupSVGElements: function(t, n, a) {
var i;
return t && t.length === 1 ? (typeof a < "u" && (t[0].sourcePath = a), t[0]) : (n && (n.width && n.height ? n.centerPoint = {
x: n.width / 2,
y: n.height / 2
} : (delete n.width, delete n.height)), i = new f.Group(t, n), typeof a < "u" && (i.sourcePath = a), i);
},
/**
* Populates an object with properties of another object
* @static
* @memberOf fabric.util
* @param {Object} source Source object
* @param {Object} destination Destination object
* @return {Array} properties Properties names to include
*/
populateWithProperties: function(t, n, a) {
if (a && Array.isArray(a))
for (var i = 0, l = a.length; i < l; i++)
a[i] in t && (n[a[i]] = t[a[i]]);
},
/**
* Creates canvas element
* @static
* @memberOf fabric.util
* @return {CanvasElement} initialized canvas element
*/
createCanvasElement: function() {
return f.document.createElement("canvas");
},
/**
* Creates a canvas element that is a copy of another and is also painted
* @param {CanvasElement} canvas to copy size and content of
* @static
* @memberOf fabric.util
* @return {CanvasElement} initialized canvas element
*/
copyCanvasElement: function(t) {
var n = f.util.createCanvasElement();
return n.width = t.width, n.height = t.height, n.getContext("2d").drawImage(t, 0, 0), n;
},
/**
* since 2.6.0 moved from canvas instance to utility.
* @param {CanvasElement} canvasEl to copy size and content of
* @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too
* @param {Number} quality <= 1 and > 0
* @static
* @memberOf fabric.util
* @return {String} data url
*/
toDataURL: function(t, n, a) {
return t.toDataURL("image/" + n, a);
},
/**
* Creates image element (works on client and node)
* @static
* @memberOf fabric.util
* @return {HTMLImageElement} HTML image element
*/
createImage: function() {
return f.document.createElement("img");
},
/**
* Multiply matrix A by matrix B to nest transformations
* @static
* @memberOf fabric.util
* @param {Array} a First transformMatrix
* @param {Array} b Second transformMatrix
* @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices
* @return {Array} The product of the two transform matrices
*/
multiplyTransformMatrices: function(t, n, a) {
return [
t[0] * n[0] + t[2] * n[1],
t[1] * n[0] + t[3] * n[1],
t[0] * n[2] + t[2] * n[3],
t[1] * n[2] + t[3] * n[3],
a ? 0 : t[0] * n[4] + t[2] * n[5] + t[4],
a ? 0 : t[1] * n[4] + t[3] * n[5] + t[5]
];
},
/**
* Decomposes standard 2x3 matrix into transform components
* @static
* @memberOf fabric.util
* @param {Array} a transformMatrix
* @return {Object} Components of transform
*/
qrDecompose: function(t) {
var n = h(t[1], t[0]), a = o(t[0], 2) + o(t[1], 2), i = s(a), l = (t[0] * t[3] - t[2] * t[1]) / i, u = h(t[0] * t[2] + t[1] * t[3], a);
return {
angle: n / e,
scaleX: i,
scaleY: l,
skewX: u / e,
skewY: 0,
translateX: t[4],
translateY: t[5]
};
},
/**
* Returns a transform matrix starting from an object of the same kind of
* the one returned from qrDecompose, useful also if you want to calculate some
* transformations from an object that is not enlived yet
* @static
* @memberOf fabric.util
* @param {Object} options
* @param {Number} [options.angle] angle in degrees
* @return {Number[]} transform matrix
*/
calcRotateMatrix: function(t) {
if (!t.angle)
return f.iMatrix.concat();
var n = f.util.degreesToRadians(t.angle), a = f.util.cos(n), i = f.util.sin(n);
return [a, i, -i, a, 0, 0];
},
/**
* Returns a transform matrix starting from an object of the same kind of
* the one returned from qrDecompose, useful also if you want to calculate some
* transformations from an object that is not enlived yet.
* is called DimensionsTransformMatrix because those properties are the one that influence
* the size of the resulting box of the object.
* @static
* @memberOf fabric.util
* @param {Object} options
* @param {Number} [options.scaleX]
* @param {Number} [options.scaleY]
* @param {Boolean} [options.flipX]
* @param {Boolean} [options.flipY]
* @param {Number} [options.skewX]
* @param {Number} [options.skewY]
* @return {Number[]} transform matrix
*/
calcDimensionsMatrix: function(t) {
var n = typeof t.scaleX > "u" ? 1 : t.scaleX, a = typeof t.scaleY > "u" ? 1 : t.scaleY, i = [
t.flipX ? -n : n,
0,
0,
t.flipY ? -a : a,
0,
0
], l = f.util.multiplyTransformMatrices, u = f.util.degreesToRadians;
return t.skewX && (i = l(
i,
[1, 0, Math.tan(u(t.skewX)), 1],
!0
)), t.skewY && (i = l(
i,
[1, Math.tan(u(t.skewY)), 0, 1],
!0
)), i;
},
/**
* Returns a transform matrix starting from an object of the same kind of
* the one returned from qrDecompose, useful also if you want to calculate some
* transformations from an object that is not enlived yet
* @static
* @memberOf fabric.util
* @param {Object} options
* @param {Number} [options.angle]
* @param {Number} [options.scaleX]
* @param {Number} [options.scaleY]
* @param {Boolean} [options.flipX]
* @param {Boolean} [options.flipY]
* @param {Number} [options.skewX]
* @param {Number} [options.skewX]
* @param {Number} [options.translateX]
* @param {Number} [options.translateY]
* @return {Number[]} transform matrix
*/
composeMatrix: function(t) {
var n = [1, 0, 0, 1, t.translateX || 0, t.translateY || 0], a = f.util.multiplyTransformMatrices;
return t.angle && (n = a(n, f.util.calcRotateMatrix(t))), (t.scaleX !== 1 || t.scaleY !== 1 || t.skewX || t.skewY || t.flipX || t.flipY) && (n = a(n, f.util.calcDimensionsMatrix(t))), n;
},
/**
* reset an object transform state to neutral. Top and left are not accounted for
* @static
* @memberOf fabric.util
* @param {fabric.Object} target object to transform
*/
resetObjectTransform: function(t) {
t.scaleX = 1, t.scaleY = 1, t.skewX = 0, t.skewY = 0, t.flipX = !1, t.flipY = !1, t.rotate(0);
},
/**
* Extract Object transform values
* @static
* @memberOf fabric.util
* @param {fabric.Object} target object to read from
* @return {Object} Components of transform
*/
saveObjectTransform: function(t) {
return {
scaleX: t.scaleX,
scaleY: t.scaleY,
skewX: t.skewX,
skewY: t.skewY,
angle: t.angle,
left: t.left,
flipX: t.flipX,
flipY: t.flipY,
top: t.top
};
},
/**
* 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(t, n, a, i) {
i > 0 && (n > i ? n -= i : n = 0, a > i ? a -= i : a = 0);
var l = !0, u, d, g = t.getImageData(n, a, i * 2 || 1, i * 2 || 1), m = g.data.length;
for (u = 3; u < m && (d = g.data[u], l = d <= 0, l !== !1); u += 4)
;
return g = null, l;
},
/**
* Parse preserveAspectRatio attribute from element
* @param {string} attribute to be parsed
* @return {Object} an object containing align and meetOrSlice attribute
*/
parsePreserveAspectRatioAttribute: function(t) {
var n = "meet", a = "Mid", i = "Mid", l = t.split(" "), u;
return l && l.length && (n = l.pop(), n !== "meet" && n !== "slice" ? (u = n, n = "meet") : l.length && (u = l.pop())), a = u !== "none" ? u.slice(1, 4) : "none", i = u !== "none" ? u.slice(5, 8) : "none", {
meetOrSlice: n,
alignX: a,
alignY: i
};
},
/**
* Clear char widths cache for the given font family or all the cache if no
* fontFamily is specified.
* Use it if you know you are loading fonts in a lazy way and you are not waiting
* for custom fonts to load properly when adding text objects to the canvas.
* If a text object is added when its own font is not loaded yet, you will get wrong
* measurement and so wrong bounding boxes.
* After the font cache is cleared, either change the textObject text content or call
* initDimensions() to trigger a recalculation
* @memberOf fabric.util
* @param {String} [fontFamily] font family to clear
*/
clearFabricFontCache: function(t) {
t = (t || "").toLowerCase(), t ? f.charWidthsCache[t] && delete f.charWidthsCache[t] : f.charWidthsCache = {};
},
/**
* 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(t, n) {
var a = Math.sqrt(n * t), i = Math.floor(n / a);
return { x: Math.floor(a), y: i };
},
capValue: function(t, n, a) {
return Math.max(t, Math.min(n, a));
},
/**
* Finds the scale for the object source to fit inside the object destination,
* keeping aspect ratio intact.
* respect the total allowed area for the cache.
* @memberOf fabric.util
* @param {Object | fabric.Object} source
* @param {Number} source.height natural unscaled height of the object
* @param {Number} source.width natural unscaled width of the object
* @param {Object | fabric.Object} destination
* @param {Number} destination.height natural unscaled height of the object
* @param {Number} destination.width natural unscaled width of the object
* @return {Number} scale factor to apply to source to fit into destination
*/
findScaleToFit: function(t, n) {
return Math.min(n.width / t.width, n.height / t.height);
},
/**
* Finds the scale for the object source to cover entirely the object destination,
* keeping aspect ratio intact.
* respect the total allowed area for the cache.
* @memberOf fabric.util
* @param {Object | fabric.Object} source
* @param {Number} source.height natural unscaled height of the object
* @param {Number} source.width natural unscaled width of the object
* @param {Object | fabric.Object} destination
* @param {Number} destination.height natural unscaled height of the object
* @param {Number} destination.width natural unscaled width of the object
* @return {Number} scale factor to apply to source to cover destination
*/
findScaleToCover: function(t, n) {
return Math.max(n.width / t.width, n.height / t.height);
},
/**
* given an array of 6 number returns something like `"matrix(...numbers)"`
* @memberOf fabric.util
* @param {Array} transform an array with 6 numbers
* @return {String} transform matrix for svg
* @return {Object.y} Limited dimensions by Y
*/
matrixToSVG: function(t) {
return "matrix(" + t.map(function(n) {
return f.util.toFixed(n, f.Object.NUM_FRACTION_DIGITS);
}).join(" ") + ")";
},
/**
* given an object and a transform, apply the inverse transform to the object,
* this is equivalent to remove from that object that transformation, so that
* added in a space with the removed transform, the object will be the same as before.
* Removing from an object a transform that scale by 2 is like scaling it by 1/2.
* Removing from an object a transfrom that rotate by 30deg is like rotating by 30deg
* in the opposite direction.
* This util is used to add objects inside transformed groups or nested groups.
* @memberOf fabric.util
* @param {fabric.Object} object the object you want to transform
* @param {Array} transform the destination transform
*/
removeTransformFromObject: function(t, n) {
var a = f.util.invertTransform(n), i = f.util.multiplyTransformMatrices(a, t.calcOwnMatrix());
f.util.applyTransformToObject(t, i);
},
/**
* given an object and a transform, apply the transform to the object.
* this is equivalent to change the space where the object is drawn.
* Adding to an object a transform that scale by 2 is like scaling it by 2.
* This is used when removing an object from an active selection for example.
* @memberOf fabric.util
* @param {fabric.Object} object the object you want to transform
* @param {Array} transform the destination transform
*/
addTransformToObject: function(t, n) {
f.util.applyTransformToObject(
t,
f.util.multiplyTransformMatrices(n, t.calcOwnMatrix())
);
},
/**
* discard an object transform state and apply the one from the matrix.
* @memberOf fabric.util
* @param {fabric.Object} object the object you want to transform
* @param {Array} transform the destination transform
*/
applyTransformToObject: function(t, n) {
var a = f.util.qrDecompose(n), i = new f.Point(a.translateX, a.translateY);
t.flipX = !1, t.flipY = !1, t.set("scaleX", a.scaleX), t.set("scaleY", a.scaleY), t.skewX = a.skewX, t.skewY = a.skewY, t.angle = a.angle, t.setPositionByOrigin(i, "center", "center");
},
/**
* 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
* @return {Object.x} width of containing
* @return {Object.y} height of containing
*/
sizeAfterTransform: function(t, n, a) {
var i = t / 2, l = n / 2, u = [
{
x: -i,
y: -l
},
{
x: i,
y: -l
},
{
x: -i,
y: l
},
{
x: i,
y: l
}
], d = f.util.calcDimensionsMatrix(a), g = f.util.makeBoundingBoxFromPoints(u, d);
return {
x: g.width,
y: g.height
};
},
/**