// http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
function touchHandler(event) {
var touches = event.changedTouches,
first = touches[0];
var type = '';
switch (event.type) {
case 'touchstart':
type = 'mousedown';break;
case 'touchmove':
type = 'mousemove';break;
case 'touchend':
type = 'mouseup';break;
default:
return;
}
// initMouseEvent(type, canBubble, cancelable, view, clickCount,
// screenX, screenY, clientX, clientY, ctrlKey,
// altKey, shiftKey, metaKey, button, relatedTarget);
var simulatedEvent = document.createEvent('MouseEvent');
simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0 /* left */, null);
if (touches.length < 2) {
first.target.dispatchEvent(simulatedEvent);
event.preventDefault();
}
}
document.addEventListener('touchstart', touchHandler, true);
document.addEventListener('touchmove', touchHandler, true);
document.addEventListener('touchend', touchHandler, true);
document.addEventListener('touchcancel', touchHandler, true);
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var asyncToGenerator = function (fn) {
return function () {
var gen = fn.apply(this, arguments);
return new Promise(function (resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function (value) {
step("next", value);
}, function (err) {
step("throw", err);
});
}
}
return step("next");
});
};
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
var toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
/**
*
* Licensed under the MIT License
*/
/**
* Common namepaces constants in alpha order
*/
var NS = {
HTML: 'http://www.w3.org/1999/xhtml',
MATH: 'http://www.w3.org/1998/Math/MathML',
SE: 'http://svg-edit.googlecode.com',
SVG: 'http://www.w3.org/2000/svg',
XLINK: 'http://www.w3.org/1999/xlink',
XML: 'http://www.w3.org/XML/1998/namespace',
XMLNS: 'http://www.w3.org/2000/xmlns/' // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
};
/**
* @returns The NS with key values switched and lowercase
*/
var getReverseNS = function getReverseNS() {
var reverseNS = {};
Object.entries(NS).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
name = _ref2[0],
URI = _ref2[1];
reverseNS[URI] = name.toLowerCase();
});
return reverseNS;
};
// SVGPathSeg API polyfill
// https://github.com/progers/pathseg
//
// This is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
// SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
// changes which were implemented in Firefox 43 and Chrome 46.
(function () {
if (!('SVGPathSeg' in window)) {
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
var _SVGPathSeg = function () {
function _SVGPathSeg(type, typeAsLetter, owningPathSegList) {
classCallCheck(this, _SVGPathSeg);
this.pathSegType = type;
this.pathSegTypeAsLetter = typeAsLetter;
this._owningPathSegList = owningPathSegList;
}
// Notify owning PathSegList on any changes so they can be synchronized back to the path element.
createClass(_SVGPathSeg, [{
key: '_segmentChanged',
value: function _segmentChanged() {
if (this._owningPathSegList) {
this._owningPathSegList.segmentChanged(this);
}
}
}]);
return _SVGPathSeg;
}();
_SVGPathSeg.prototype.classname = 'SVGPathSeg';
_SVGPathSeg.PATHSEG_UNKNOWN = 0;
_SVGPathSeg.PATHSEG_CLOSEPATH = 1;
_SVGPathSeg.PATHSEG_MOVETO_ABS = 2;
_SVGPathSeg.PATHSEG_MOVETO_REL = 3;
_SVGPathSeg.PATHSEG_LINETO_ABS = 4;
_SVGPathSeg.PATHSEG_LINETO_REL = 5;
_SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS = 6;
_SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL = 7;
_SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS = 8;
_SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL = 9;
_SVGPathSeg.PATHSEG_ARC_ABS = 10;
_SVGPathSeg.PATHSEG_ARC_REL = 11;
_SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS = 12;
_SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL = 13;
_SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS = 14;
_SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL = 15;
_SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16;
_SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17;
_SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18;
_SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19;
var _SVGPathSegClosePath = function (_SVGPathSeg2) {
inherits(_SVGPathSegClosePath, _SVGPathSeg2);
function _SVGPathSegClosePath(owningPathSegList) {
classCallCheck(this, _SVGPathSegClosePath);
return possibleConstructorReturn(this, (_SVGPathSegClosePath.__proto__ || Object.getPrototypeOf(_SVGPathSegClosePath)).call(this, _SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList));
}
createClass(_SVGPathSegClosePath, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegClosePath]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegClosePath(undefined);
}
}]);
return _SVGPathSegClosePath;
}(_SVGPathSeg);
var _SVGPathSegMovetoAbs = function (_SVGPathSeg3) {
inherits(_SVGPathSegMovetoAbs, _SVGPathSeg3);
function _SVGPathSegMovetoAbs(owningPathSegList, x, y) {
classCallCheck(this, _SVGPathSegMovetoAbs);
var _this2 = possibleConstructorReturn(this, (_SVGPathSegMovetoAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegMovetoAbs)).call(this, _SVGPathSeg.PATHSEG_MOVETO_ABS, 'M', owningPathSegList));
_this2._x = x;
_this2._y = y;
return _this2;
}
createClass(_SVGPathSegMovetoAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegMovetoAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegMovetoAbs(undefined, this._x, this._y);
}
}]);
return _SVGPathSegMovetoAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegMovetoAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true
},
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true
}
});
var _SVGPathSegMovetoRel = function (_SVGPathSeg4) {
inherits(_SVGPathSegMovetoRel, _SVGPathSeg4);
function _SVGPathSegMovetoRel(owningPathSegList, x, y) {
classCallCheck(this, _SVGPathSegMovetoRel);
var _this3 = possibleConstructorReturn(this, (_SVGPathSegMovetoRel.__proto__ || Object.getPrototypeOf(_SVGPathSegMovetoRel)).call(this, _SVGPathSeg.PATHSEG_MOVETO_REL, 'm', owningPathSegList));
_this3._x = x;
_this3._y = y;
return _this3;
}
createClass(_SVGPathSegMovetoRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegMovetoRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegMovetoRel(undefined, this._x, this._y);
}
}]);
return _SVGPathSegMovetoRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegMovetoRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegLinetoAbs = function (_SVGPathSeg5) {
inherits(_SVGPathSegLinetoAbs, _SVGPathSeg5);
function _SVGPathSegLinetoAbs(owningPathSegList, x, y) {
classCallCheck(this, _SVGPathSegLinetoAbs);
var _this4 = possibleConstructorReturn(this, (_SVGPathSegLinetoAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegLinetoAbs)).call(this, _SVGPathSeg.PATHSEG_LINETO_ABS, 'L', owningPathSegList));
_this4._x = x;
_this4._y = y;
return _this4;
}
createClass(_SVGPathSegLinetoAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegLinetoAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegLinetoAbs(undefined, this._x, this._y);
}
}]);
return _SVGPathSegLinetoAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegLinetoAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegLinetoRel = function (_SVGPathSeg6) {
inherits(_SVGPathSegLinetoRel, _SVGPathSeg6);
function _SVGPathSegLinetoRel(owningPathSegList, x, y) {
classCallCheck(this, _SVGPathSegLinetoRel);
var _this5 = possibleConstructorReturn(this, (_SVGPathSegLinetoRel.__proto__ || Object.getPrototypeOf(_SVGPathSegLinetoRel)).call(this, _SVGPathSeg.PATHSEG_LINETO_REL, 'l', owningPathSegList));
_this5._x = x;
_this5._y = y;
return _this5;
}
createClass(_SVGPathSegLinetoRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegLinetoRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegLinetoRel(undefined, this._x, this._y);
}
}]);
return _SVGPathSegLinetoRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegLinetoRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoCubicAbs = function (_SVGPathSeg7) {
inherits(_SVGPathSegCurvetoCubicAbs, _SVGPathSeg7);
function _SVGPathSegCurvetoCubicAbs(owningPathSegList, x, y, x1, y1, x2, y2) {
classCallCheck(this, _SVGPathSegCurvetoCubicAbs);
var _this6 = possibleConstructorReturn(this, (_SVGPathSegCurvetoCubicAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoCubicAbs)).call(this, _SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, 'C', owningPathSegList));
_this6._x = x;
_this6._y = y;
_this6._x1 = x1;
_this6._y1 = y1;
_this6._x2 = x2;
_this6._y2 = y2;
return _this6;
}
createClass(_SVGPathSegCurvetoCubicAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoCubicAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2);
}
}]);
return _SVGPathSegCurvetoCubicAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoCubicAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
x1: {
get: function get$$1() {
return this._x1;
},
set: function set$$1(x1) {
this._x1 = x1;this._segmentChanged();
},
enumerable: true },
y1: {
get: function get$$1() {
return this._y1;
},
set: function set$$1(y1) {
this._y1 = y1;this._segmentChanged();
},
enumerable: true },
x2: {
get: function get$$1() {
return this._x2;
},
set: function set$$1(x2) {
this._x2 = x2;this._segmentChanged();
},
enumerable: true },
y2: {
get: function get$$1() {
return this._y2;
},
set: function set$$1(y2) {
this._y2 = y2;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoCubicRel = function (_SVGPathSeg8) {
inherits(_SVGPathSegCurvetoCubicRel, _SVGPathSeg8);
function _SVGPathSegCurvetoCubicRel(owningPathSegList, x, y, x1, y1, x2, y2) {
classCallCheck(this, _SVGPathSegCurvetoCubicRel);
var _this7 = possibleConstructorReturn(this, (_SVGPathSegCurvetoCubicRel.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoCubicRel)).call(this, _SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, 'c', owningPathSegList));
_this7._x = x;
_this7._y = y;
_this7._x1 = x1;
_this7._y1 = y1;
_this7._x2 = x2;
_this7._y2 = y2;
return _this7;
}
createClass(_SVGPathSegCurvetoCubicRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoCubicRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2);
}
}]);
return _SVGPathSegCurvetoCubicRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoCubicRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
x1: {
get: function get$$1() {
return this._x1;
},
set: function set$$1(x1) {
this._x1 = x1;this._segmentChanged();
},
enumerable: true },
y1: {
get: function get$$1() {
return this._y1;
},
set: function set$$1(y1) {
this._y1 = y1;this._segmentChanged();
},
enumerable: true },
x2: {
get: function get$$1() {
return this._x2;
},
set: function set$$1(x2) {
this._x2 = x2;this._segmentChanged();
},
enumerable: true },
y2: {
get: function get$$1() {
return this._y2;
},
set: function set$$1(y2) {
this._y2 = y2;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoQuadraticAbs = function (_SVGPathSeg9) {
inherits(_SVGPathSegCurvetoQuadraticAbs, _SVGPathSeg9);
function _SVGPathSegCurvetoQuadraticAbs(owningPathSegList, x, y, x1, y1) {
classCallCheck(this, _SVGPathSegCurvetoQuadraticAbs);
var _this8 = possibleConstructorReturn(this, (_SVGPathSegCurvetoQuadraticAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoQuadraticAbs)).call(this, _SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, 'Q', owningPathSegList));
_this8._x = x;
_this8._y = y;
_this8._x1 = x1;
_this8._y1 = y1;
return _this8;
}
createClass(_SVGPathSegCurvetoQuadraticAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoQuadraticAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1);
}
}]);
return _SVGPathSegCurvetoQuadraticAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoQuadraticAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
x1: {
get: function get$$1() {
return this._x1;
},
set: function set$$1(x1) {
this._x1 = x1;this._segmentChanged();
},
enumerable: true },
y1: {
get: function get$$1() {
return this._y1;
},
set: function set$$1(y1) {
this._y1 = y1;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoQuadraticRel = function (_SVGPathSeg10) {
inherits(_SVGPathSegCurvetoQuadraticRel, _SVGPathSeg10);
function _SVGPathSegCurvetoQuadraticRel(owningPathSegList, x, y, x1, y1) {
classCallCheck(this, _SVGPathSegCurvetoQuadraticRel);
var _this9 = possibleConstructorReturn(this, (_SVGPathSegCurvetoQuadraticRel.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoQuadraticRel)).call(this, _SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, 'q', owningPathSegList));
_this9._x = x;
_this9._y = y;
_this9._x1 = x1;
_this9._y1 = y1;
return _this9;
}
createClass(_SVGPathSegCurvetoQuadraticRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoQuadraticRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1);
}
}]);
return _SVGPathSegCurvetoQuadraticRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoQuadraticRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
x1: {
get: function get$$1() {
return this._x1;
},
set: function set$$1(x1) {
this._x1 = x1;this._segmentChanged();
},
enumerable: true },
y1: {
get: function get$$1() {
return this._y1;
},
set: function set$$1(y1) {
this._y1 = y1;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegArcAbs = function (_SVGPathSeg11) {
inherits(_SVGPathSegArcAbs, _SVGPathSeg11);
function _SVGPathSegArcAbs(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
classCallCheck(this, _SVGPathSegArcAbs);
var _this10 = possibleConstructorReturn(this, (_SVGPathSegArcAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegArcAbs)).call(this, _SVGPathSeg.PATHSEG_ARC_ABS, 'A', owningPathSegList));
_this10._x = x;
_this10._y = y;
_this10._r1 = r1;
_this10._r2 = r2;
_this10._angle = angle;
_this10._largeArcFlag = largeArcFlag;
_this10._sweepFlag = sweepFlag;
return _this10;
}
createClass(_SVGPathSegArcAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegArcAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._r1 + ' ' + this._r2 + ' ' + this._angle + ' ' + (this._largeArcFlag ? '1' : '0') + ' ' + (this._sweepFlag ? '1' : '0') + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag);
}
}]);
return _SVGPathSegArcAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegArcAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
r1: {
get: function get$$1() {
return this._r1;
},
set: function set$$1(r1) {
this._r1 = r1;this._segmentChanged();
},
enumerable: true },
r2: {
get: function get$$1() {
return this._r2;
},
set: function set$$1(r2) {
this._r2 = r2;this._segmentChanged();
},
enumerable: true },
angle: {
get: function get$$1() {
return this._angle;
},
set: function set$$1(angle) {
this._angle = angle;this._segmentChanged();
},
enumerable: true },
largeArcFlag: {
get: function get$$1() {
return this._largeArcFlag;
},
set: function set$$1(largeArcFlag) {
this._largeArcFlag = largeArcFlag;this._segmentChanged();
},
enumerable: true },
sweepFlag: {
get: function get$$1() {
return this._sweepFlag;
},
set: function set$$1(sweepFlag) {
this._sweepFlag = sweepFlag;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegArcRel = function (_SVGPathSeg12) {
inherits(_SVGPathSegArcRel, _SVGPathSeg12);
function _SVGPathSegArcRel(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
classCallCheck(this, _SVGPathSegArcRel);
var _this11 = possibleConstructorReturn(this, (_SVGPathSegArcRel.__proto__ || Object.getPrototypeOf(_SVGPathSegArcRel)).call(this, _SVGPathSeg.PATHSEG_ARC_REL, 'a', owningPathSegList));
_this11._x = x;
_this11._y = y;
_this11._r1 = r1;
_this11._r2 = r2;
_this11._angle = angle;
_this11._largeArcFlag = largeArcFlag;
_this11._sweepFlag = sweepFlag;
return _this11;
}
createClass(_SVGPathSegArcRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegArcRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._r1 + ' ' + this._r2 + ' ' + this._angle + ' ' + (this._largeArcFlag ? '1' : '0') + ' ' + (this._sweepFlag ? '1' : '0') + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag);
}
}]);
return _SVGPathSegArcRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegArcRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
r1: {
get: function get$$1() {
return this._r1;
},
set: function set$$1(r1) {
this._r1 = r1;this._segmentChanged();
},
enumerable: true },
r2: {
get: function get$$1() {
return this._r2;
},
set: function set$$1(r2) {
this._r2 = r2;this._segmentChanged();
},
enumerable: true },
angle: {
get: function get$$1() {
return this._angle;
},
set: function set$$1(angle) {
this._angle = angle;this._segmentChanged();
},
enumerable: true },
largeArcFlag: {
get: function get$$1() {
return this._largeArcFlag;
},
set: function set$$1(largeArcFlag) {
this._largeArcFlag = largeArcFlag;this._segmentChanged();
},
enumerable: true },
sweepFlag: {
get: function get$$1() {
return this._sweepFlag;
},
set: function set$$1(sweepFlag) {
this._sweepFlag = sweepFlag;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegLinetoHorizontalAbs = function (_SVGPathSeg13) {
inherits(_SVGPathSegLinetoHorizontalAbs, _SVGPathSeg13);
function _SVGPathSegLinetoHorizontalAbs(owningPathSegList, x) {
classCallCheck(this, _SVGPathSegLinetoHorizontalAbs);
var _this12 = possibleConstructorReturn(this, (_SVGPathSegLinetoHorizontalAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegLinetoHorizontalAbs)).call(this, _SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, 'H', owningPathSegList));
_this12._x = x;
return _this12;
}
createClass(_SVGPathSegLinetoHorizontalAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegLinetoHorizontalAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegLinetoHorizontalAbs(undefined, this._x);
}
}]);
return _SVGPathSegLinetoHorizontalAbs;
}(_SVGPathSeg);
Object.defineProperty(_SVGPathSegLinetoHorizontalAbs.prototype, 'x', {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true });
var _SVGPathSegLinetoHorizontalRel = function (_SVGPathSeg14) {
inherits(_SVGPathSegLinetoHorizontalRel, _SVGPathSeg14);
function _SVGPathSegLinetoHorizontalRel(owningPathSegList, x) {
classCallCheck(this, _SVGPathSegLinetoHorizontalRel);
var _this13 = possibleConstructorReturn(this, (_SVGPathSegLinetoHorizontalRel.__proto__ || Object.getPrototypeOf(_SVGPathSegLinetoHorizontalRel)).call(this, _SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, 'h', owningPathSegList));
_this13._x = x;
return _this13;
}
createClass(_SVGPathSegLinetoHorizontalRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegLinetoHorizontalRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegLinetoHorizontalRel(undefined, this._x);
}
}]);
return _SVGPathSegLinetoHorizontalRel;
}(_SVGPathSeg);
Object.defineProperty(_SVGPathSegLinetoHorizontalRel.prototype, 'x', {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true });
var _SVGPathSegLinetoVerticalAbs = function (_SVGPathSeg15) {
inherits(_SVGPathSegLinetoVerticalAbs, _SVGPathSeg15);
function _SVGPathSegLinetoVerticalAbs(owningPathSegList, y) {
classCallCheck(this, _SVGPathSegLinetoVerticalAbs);
var _this14 = possibleConstructorReturn(this, (_SVGPathSegLinetoVerticalAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegLinetoVerticalAbs)).call(this, _SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, 'V', owningPathSegList));
_this14._y = y;
return _this14;
}
createClass(_SVGPathSegLinetoVerticalAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegLinetoVerticalAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegLinetoVerticalAbs(undefined, this._y);
}
}]);
return _SVGPathSegLinetoVerticalAbs;
}(_SVGPathSeg);
Object.defineProperty(_SVGPathSegLinetoVerticalAbs.prototype, 'y', {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true });
var _SVGPathSegLinetoVerticalRel = function (_SVGPathSeg16) {
inherits(_SVGPathSegLinetoVerticalRel, _SVGPathSeg16);
function _SVGPathSegLinetoVerticalRel(owningPathSegList, y) {
classCallCheck(this, _SVGPathSegLinetoVerticalRel);
var _this15 = possibleConstructorReturn(this, (_SVGPathSegLinetoVerticalRel.__proto__ || Object.getPrototypeOf(_SVGPathSegLinetoVerticalRel)).call(this, _SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, 'v', owningPathSegList));
_this15._y = y;
return _this15;
}
createClass(_SVGPathSegLinetoVerticalRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegLinetoVerticalRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegLinetoVerticalRel(undefined, this._y);
}
}]);
return _SVGPathSegLinetoVerticalRel;
}(_SVGPathSeg);
Object.defineProperty(_SVGPathSegLinetoVerticalRel.prototype, 'y', {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true });
var _SVGPathSegCurvetoCubicSmoothAbs = function (_SVGPathSeg17) {
inherits(_SVGPathSegCurvetoCubicSmoothAbs, _SVGPathSeg17);
function _SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, x, y, x2, y2) {
classCallCheck(this, _SVGPathSegCurvetoCubicSmoothAbs);
var _this16 = possibleConstructorReturn(this, (_SVGPathSegCurvetoCubicSmoothAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoCubicSmoothAbs)).call(this, _SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, 'S', owningPathSegList));
_this16._x = x;
_this16._y = y;
_this16._x2 = x2;
_this16._y2 = y2;
return _this16;
}
createClass(_SVGPathSegCurvetoCubicSmoothAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoCubicSmoothAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2);
}
}]);
return _SVGPathSegCurvetoCubicSmoothAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoCubicSmoothAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
x2: {
get: function get$$1() {
return this._x2;
},
set: function set$$1(x2) {
this._x2 = x2;this._segmentChanged();
},
enumerable: true },
y2: {
get: function get$$1() {
return this._y2;
},
set: function set$$1(y2) {
this._y2 = y2;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoCubicSmoothRel = function (_SVGPathSeg18) {
inherits(_SVGPathSegCurvetoCubicSmoothRel, _SVGPathSeg18);
function _SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, x, y, x2, y2) {
classCallCheck(this, _SVGPathSegCurvetoCubicSmoothRel);
var _this17 = possibleConstructorReturn(this, (_SVGPathSegCurvetoCubicSmoothRel.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoCubicSmoothRel)).call(this, _SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, 's', owningPathSegList));
_this17._x = x;
_this17._y = y;
_this17._x2 = x2;
_this17._y2 = y2;
return _this17;
}
createClass(_SVGPathSegCurvetoCubicSmoothRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoCubicSmoothRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2);
}
}]);
return _SVGPathSegCurvetoCubicSmoothRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoCubicSmoothRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true },
x2: {
get: function get$$1() {
return this._x2;
},
set: function set$$1(x2) {
this._x2 = x2;this._segmentChanged();
},
enumerable: true },
y2: {
get: function get$$1() {
return this._y2;
},
set: function set$$1(y2) {
this._y2 = y2;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoQuadraticSmoothAbs = function (_SVGPathSeg19) {
inherits(_SVGPathSegCurvetoQuadraticSmoothAbs, _SVGPathSeg19);
function _SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, x, y) {
classCallCheck(this, _SVGPathSegCurvetoQuadraticSmoothAbs);
var _this18 = possibleConstructorReturn(this, (_SVGPathSegCurvetoQuadraticSmoothAbs.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoQuadraticSmoothAbs)).call(this, _SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, 'T', owningPathSegList));
_this18._x = x;
_this18._y = y;
return _this18;
}
createClass(_SVGPathSegCurvetoQuadraticSmoothAbs, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoQuadraticSmoothAbs]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y);
}
}]);
return _SVGPathSegCurvetoQuadraticSmoothAbs;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoQuadraticSmoothAbs.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true }
});
var _SVGPathSegCurvetoQuadraticSmoothRel = function (_SVGPathSeg20) {
inherits(_SVGPathSegCurvetoQuadraticSmoothRel, _SVGPathSeg20);
function _SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, x, y) {
classCallCheck(this, _SVGPathSegCurvetoQuadraticSmoothRel);
var _this19 = possibleConstructorReturn(this, (_SVGPathSegCurvetoQuadraticSmoothRel.__proto__ || Object.getPrototypeOf(_SVGPathSegCurvetoQuadraticSmoothRel)).call(this, _SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, 't', owningPathSegList));
_this19._x = x;
_this19._y = y;
return _this19;
}
createClass(_SVGPathSegCurvetoQuadraticSmoothRel, [{
key: 'toString',
value: function toString() {
return '[object SVGPathSegCurvetoQuadraticSmoothRel]';
}
}, {
key: '_asPathString',
value: function _asPathString() {
return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y;
}
}, {
key: 'clone',
value: function clone() {
return new _SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y);
}
}]);
return _SVGPathSegCurvetoQuadraticSmoothRel;
}(_SVGPathSeg);
Object.defineProperties(_SVGPathSegCurvetoQuadraticSmoothRel.prototype, {
x: {
get: function get$$1() {
return this._x;
},
set: function set$$1(x) {
this._x = x;this._segmentChanged();
},
enumerable: true },
y: {
get: function get$$1() {
return this._y;
},
set: function set$$1(y) {
this._y = y;this._segmentChanged();
},
enumerable: true }
});
// Add createSVGPathSeg* functions to SVGPathElement.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathElement.
SVGPathElement.prototype.createSVGPathSegClosePath = function () {
return new _SVGPathSegClosePath(undefined);
};
SVGPathElement.prototype.createSVGPathSegMovetoAbs = function (x, y) {
return new _SVGPathSegMovetoAbs(undefined, x, y);
};
SVGPathElement.prototype.createSVGPathSegMovetoRel = function (x, y) {
return new _SVGPathSegMovetoRel(undefined, x, y);
};
SVGPathElement.prototype.createSVGPathSegLinetoAbs = function (x, y) {
return new _SVGPathSegLinetoAbs(undefined, x, y);
};
SVGPathElement.prototype.createSVGPathSegLinetoRel = function (x, y) {
return new _SVGPathSegLinetoRel(undefined, x, y);
};
SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function (x, y, x1, y1, x2, y2) {
return new _SVGPathSegCurvetoCubicAbs(undefined, x, y, x1, y1, x2, y2);
};
SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function (x, y, x1, y1, x2, y2) {
return new _SVGPathSegCurvetoCubicRel(undefined, x, y, x1, y1, x2, y2);
};
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function (x, y, x1, y1) {
return new _SVGPathSegCurvetoQuadraticAbs(undefined, x, y, x1, y1);
};
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function (x, y, x1, y1) {
return new _SVGPathSegCurvetoQuadraticRel(undefined, x, y, x1, y1);
};
SVGPathElement.prototype.createSVGPathSegArcAbs = function (x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
return new _SVGPathSegArcAbs(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
};
SVGPathElement.prototype.createSVGPathSegArcRel = function (x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
return new _SVGPathSegArcRel(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
};
SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function (x) {
return new _SVGPathSegLinetoHorizontalAbs(undefined, x);
};
SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function (x) {
return new _SVGPathSegLinetoHorizontalRel(undefined, x);
};
SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function (y) {
return new _SVGPathSegLinetoVerticalAbs(undefined, y);
};
SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function (y) {
return new _SVGPathSegLinetoVerticalRel(undefined, y);
};
SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function (x, y, x2, y2) {
return new _SVGPathSegCurvetoCubicSmoothAbs(undefined, x, y, x2, y2);
};
SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function (x, y, x2, y2) {
return new _SVGPathSegCurvetoCubicSmoothRel(undefined, x, y, x2, y2);
};
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function (x, y) {
return new _SVGPathSegCurvetoQuadraticSmoothAbs(undefined, x, y);
};
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function (x, y) {
return new _SVGPathSegCurvetoQuadraticSmoothRel(undefined, x, y);
};
if (!('getPathSegAtLength' in SVGPathElement.prototype)) {
// Add getPathSegAtLength to SVGPathElement.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-__svg__SVGPathElement__getPathSegAtLength
// This polyfill requires SVGPathElement.getTotalLength to implement the distance-along-a-path algorithm.
SVGPathElement.prototype.getPathSegAtLength = function (distance) {
if (distance === undefined || !isFinite(distance)) {
throw new Error('Invalid arguments.');
}
var measurementElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
measurementElement.setAttribute('d', this.getAttribute('d'));
var lastPathSegment = measurementElement.pathSegList.numberOfItems - 1;
// If the path is empty, return 0.
if (lastPathSegment <= 0) {
return 0;
}
do {
measurementElement.pathSegList.removeItem(lastPathSegment);
if (distance > measurementElement.getTotalLength()) {
break;
}
lastPathSegment--;
} while (lastPathSegment > 0);
return lastPathSegment;
};
}
window.SVGPathSeg = _SVGPathSeg;
window.SVGPathSegClosePath = _SVGPathSegClosePath;
window.SVGPathSegMovetoAbs = _SVGPathSegMovetoAbs;
window.SVGPathSegMovetoRel = _SVGPathSegMovetoRel;
window.SVGPathSegLinetoAbs = _SVGPathSegLinetoAbs;
window.SVGPathSegLinetoRel = _SVGPathSegLinetoRel;
window.SVGPathSegCurvetoCubicAbs = _SVGPathSegCurvetoCubicAbs;
window.SVGPathSegCurvetoCubicRel = _SVGPathSegCurvetoCubicRel;
window.SVGPathSegCurvetoQuadraticAbs = _SVGPathSegCurvetoQuadraticAbs;
window.SVGPathSegCurvetoQuadraticRel = _SVGPathSegCurvetoQuadraticRel;
window.SVGPathSegArcAbs = _SVGPathSegArcAbs;
window.SVGPathSegArcRel = _SVGPathSegArcRel;
window.SVGPathSegLinetoHorizontalAbs = _SVGPathSegLinetoHorizontalAbs;
window.SVGPathSegLinetoHorizontalRel = _SVGPathSegLinetoHorizontalRel;
window.SVGPathSegLinetoVerticalAbs = _SVGPathSegLinetoVerticalAbs;
window.SVGPathSegLinetoVerticalRel = _SVGPathSegLinetoVerticalRel;
window.SVGPathSegCurvetoCubicSmoothAbs = _SVGPathSegCurvetoCubicSmoothAbs;
window.SVGPathSegCurvetoCubicSmoothRel = _SVGPathSegCurvetoCubicSmoothRel;
window.SVGPathSegCurvetoQuadraticSmoothAbs = _SVGPathSegCurvetoQuadraticSmoothAbs;
window.SVGPathSegCurvetoQuadraticSmoothRel = _SVGPathSegCurvetoQuadraticSmoothRel;
}
// Checking for SVGPathSegList in window checks for the case of an implementation without the
// SVGPathSegList API.
// The second check for appendItem is specific to Firefox 59+ which removed only parts of the
// SVGPathSegList API (e.g., appendItem). In this case we need to re-implement the entire API
// so the polyfill data (i.e., _list) is used throughout.
if (!('SVGPathSegList' in window) || !('appendItem' in SVGPathSegList.prototype)) {
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList
var _SVGPathSegList = function () {
function _SVGPathSegList(pathElement) {
classCallCheck(this, _SVGPathSegList);
this._pathElement = pathElement;
this._list = this._parsePath(this._pathElement.getAttribute('d'));
// Use a MutationObserver to catch changes to the path's "d" attribute.
this._mutationObserverConfig = { attributes: true, attributeFilter: ['d'] };
this._pathElementMutationObserver = new MutationObserver(this._updateListFromPathMutations.bind(this));
this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
}
// Process any pending mutations to the path element and update the list as needed.
// This should be the first call of all public functions and is needed because
// MutationObservers are not synchronous so we can have pending asynchronous mutations.
createClass(_SVGPathSegList, [{
key: '_checkPathSynchronizedToList',
value: function _checkPathSynchronizedToList() {
this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());
}
}, {
key: '_updateListFromPathMutations',
value: function _updateListFromPathMutations(mutationRecords) {
if (!this._pathElement) {
return;
}
var hasPathMutations = false;
mutationRecords.forEach(function (record) {
if (record.attributeName === 'd') {
hasPathMutations = true;
}
});
if (hasPathMutations) {
this._list = this._parsePath(this._pathElement.getAttribute('d'));
}
}
// Serialize the list and update the path's 'd' attribute.
}, {
key: '_writeListToPath',
value: function _writeListToPath() {
this._pathElementMutationObserver.disconnect();
this._pathElement.setAttribute('d', _SVGPathSegList._pathSegArrayAsString(this._list));
this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
}
// When a path segment changes the list needs to be synchronized back to the path element.
}, {
key: 'segmentChanged',
value: function segmentChanged(pathSeg) {
this._writeListToPath();
}
}, {
key: 'clear',
value: function clear() {
this._checkPathSynchronizedToList();
this._list.forEach(function (pathSeg) {
pathSeg._owningPathSegList = null;
});
this._list = [];
this._writeListToPath();
}
}, {
key: 'initialize',
value: function initialize(newItem) {
this._checkPathSynchronizedToList();
this._list = [newItem];
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
}, {
key: '_checkValidIndex',
value: function _checkValidIndex(index) {
if (isNaN(index) || index < 0 || index >= this.numberOfItems) {
throw new Error('INDEX_SIZE_ERR');
}
}
}, {
key: 'getItem',
value: function getItem(index) {
this._checkPathSynchronizedToList();
this._checkValidIndex(index);
return this._list[index];
}
}, {
key: 'insertItemBefore',
value: function insertItemBefore(newItem, index) {
this._checkPathSynchronizedToList();
// Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
if (index > this.numberOfItems) {
index = this.numberOfItems;
}
if (newItem._owningPathSegList) {
// SVG2 spec says to make a copy.
newItem = newItem.clone();
}
this._list.splice(index, 0, newItem);
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
}, {
key: 'replaceItem',
value: function replaceItem(newItem, index) {
this._checkPathSynchronizedToList();
if (newItem._owningPathSegList) {
// SVG2 spec says to make a copy.
newItem = newItem.clone();
}
this._checkValidIndex(index);
this._list[index] = newItem;
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
}, {
key: 'removeItem',
value: function removeItem(index) {
this._checkPathSynchronizedToList();
this._checkValidIndex(index);
var item = this._list[index];
this._list.splice(index, 1);
this._writeListToPath();
return item;
}
}, {
key: 'appendItem',
value: function appendItem(newItem) {
this._checkPathSynchronizedToList();
if (newItem._owningPathSegList) {
// SVG2 spec says to make a copy.
newItem = newItem.clone();
}
this._list.push(newItem);
newItem._owningPathSegList = this;
// TODO: Optimize this to just append to the existing attribute.
this._writeListToPath();
return newItem;
}
// This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp.
}, {
key: '_parsePath',
value: function _parsePath(string) {
if (!string || !string.length) {
return [];
}
var owningPathSegList = this;
var Builder = function () {
function Builder() {
classCallCheck(this, Builder);
this.pathSegList = [];
}
createClass(Builder, [{
key: 'appendSegment',
value: function appendSegment(pathSeg) {
this.pathSegList.push(pathSeg);
}
}]);
return Builder;
}();
var Source = function () {
function Source(string) {
classCallCheck(this, Source);
this._string = string;
this._currentIndex = 0;
this._endIndex = this._string.length;
this._previousCommand = SVGPathSeg.PATHSEG_UNKNOWN;
this._skipOptionalSpaces();
}
createClass(Source, [{
key: '_isCurrentSpace',
value: function _isCurrentSpace() {
var character = this._string[this._currentIndex];
return character <= ' ' && (character === ' ' || character === '\n' || character === '\t' || character === '\r' || character === '\f');
}
}, {
key: '_skipOptionalSpaces',
value: function _skipOptionalSpaces() {
while (this._currentIndex < this._endIndex && this._isCurrentSpace()) {
this._currentIndex++;
}
return this._currentIndex < this._endIndex;
}
}, {
key: '_skipOptionalSpacesOrDelimiter',
value: function _skipOptionalSpacesOrDelimiter() {
if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) !== ',') {
return false;
}
if (this._skipOptionalSpaces()) {
if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === ',') {
this._currentIndex++;
this._skipOptionalSpaces();
}
}
return this._currentIndex < this._endIndex;
}
}, {
key: 'hasMoreData',
value: function hasMoreData() {
return this._currentIndex < this._endIndex;
}
}, {
key: 'peekSegmentType',
value: function peekSegmentType() {
var lookahead = this._string[this._currentIndex];
return this._pathSegTypeFromChar(lookahead);
}
}, {
key: '_pathSegTypeFromChar',
value: function _pathSegTypeFromChar(lookahead) {
switch (lookahead) {
case 'Z':
case 'z':
return SVGPathSeg.PATHSEG_CLOSEPATH;
case 'M':
return SVGPathSeg.PATHSEG_MOVETO_ABS;
case 'm':
return SVGPathSeg.PATHSEG_MOVETO_REL;
case 'L':
return SVGPathSeg.PATHSEG_LINETO_ABS;
case 'l':
return SVGPathSeg.PATHSEG_LINETO_REL;
case 'C':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
case 'c':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
case 'Q':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
case 'q':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
case 'A':
return SVGPathSeg.PATHSEG_ARC_ABS;
case 'a':
return SVGPathSeg.PATHSEG_ARC_REL;
case 'H':
return SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
case 'h':
return SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
case 'V':
return SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
case 'v':
return SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
case 'S':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
case 's':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
case 'T':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
case 't':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
default:
return SVGPathSeg.PATHSEG_UNKNOWN;
}
}
}, {
key: '_nextCommandHelper',
value: function _nextCommandHelper(lookahead, previousCommand) {
// Check for remaining coordinates in the current command.
if ((lookahead === '+' || lookahead === '-' || lookahead === '.' || lookahead >= '0' && lookahead <= '9') && previousCommand !== SVGPathSeg.PATHSEG_CLOSEPATH) {
if (previousCommand === SVGPathSeg.PATHSEG_MOVETO_ABS) {
return SVGPathSeg.PATHSEG_LINETO_ABS;
}
if (previousCommand === SVGPathSeg.PATHSEG_MOVETO_REL) {
return SVGPathSeg.PATHSEG_LINETO_REL;
}
return previousCommand;
}
return SVGPathSeg.PATHSEG_UNKNOWN;
}
}, {
key: 'initialCommandIsMoveTo',
value: function initialCommandIsMoveTo() {
// If the path is empty it is still valid, so return true.
if (!this.hasMoreData()) {
return true;
}
var command = this.peekSegmentType();
// Path must start with moveTo.
return command === SVGPathSeg.PATHSEG_MOVETO_ABS || command === SVGPathSeg.PATHSEG_MOVETO_REL;
}
// Parse a number from an SVG path. This very closely follows genericParseNumber(...) from Source/core/svg/SVGParserUtilities.cpp.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
}, {
key: '_parseNumber',
value: function _parseNumber() {
var exponent = 0;
var integer = 0;
var frac = 1;
var decimal = 0;
var sign = 1;
var expsign = 1;
var startIndex = this._currentIndex;
this._skipOptionalSpaces();
// Read the sign.
if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === '+') {
this._currentIndex++;
} else if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === '-') {
this._currentIndex++;
sign = -1;
}
if (this._currentIndex === this._endIndex || (this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') && this._string.charAt(this._currentIndex) !== '.') {
// The first character of a number must be one of [0-9+-.].
return undefined;
}
// Read the integer part, build right-to-left.
var startIntPartIndex = this._currentIndex;
while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
this._currentIndex++; // Advance to first non-digit.
}
if (this._currentIndex !== startIntPartIndex) {
var scanIntPartIndex = this._currentIndex - 1;
var multiplier = 1;
while (scanIntPartIndex >= startIntPartIndex) {
integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
multiplier *= 10;
}
}
// Read the decimals.
if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === '.') {
this._currentIndex++;
// There must be a least one digit following the .
if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') {
return undefined;
}
while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
frac *= 10;
decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
this._currentIndex += 1;
}
}
// Read the exponent part.
if (this._currentIndex !== startIndex && this._currentIndex + 1 < this._endIndex && (this._string.charAt(this._currentIndex) === 'e' || this._string.charAt(this._currentIndex) === 'E') && this._string.charAt(this._currentIndex + 1) !== 'x' && this._string.charAt(this._currentIndex + 1) !== 'm') {
this._currentIndex++;
// Read the sign of the exponent.
if (this._string.charAt(this._currentIndex) === '+') {
this._currentIndex++;
} else if (this._string.charAt(this._currentIndex) === '-') {
this._currentIndex++;
expsign = -1;
}
// There must be an exponent.
if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') {
return undefined;
}
while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
exponent *= 10;
exponent += this._string.charAt(this._currentIndex) - '0';
this._currentIndex++;
}
}
var number = integer + decimal;
number *= sign;
if (exponent) {
number *= Math.pow(10, expsign * exponent);
}
if (startIndex === this._currentIndex) {
return undefined;
}
this._skipOptionalSpacesOrDelimiter();
return number;
}
}, {
key: '_parseArcFlag',
value: function _parseArcFlag() {
if (this._currentIndex >= this._endIndex) {
return undefined;
}
var flag = false;
var flagChar = this._string.charAt(this._currentIndex++);
if (flagChar === '0') {
flag = false;
} else if (flagChar === '1') {
flag = true;
} else {
return undefined;
}
this._skipOptionalSpacesOrDelimiter();
return flag;
}
}, {
key: 'parseSegment',
value: function parseSegment() {
var lookahead = this._string[this._currentIndex];
var command = this._pathSegTypeFromChar(lookahead);
if (command === SVGPathSeg.PATHSEG_UNKNOWN) {
// Possibly an implicit command. Not allowed if this is the first command.
if (this._previousCommand === SVGPathSeg.PATHSEG_UNKNOWN) {
return null;
}
command = this._nextCommandHelper(lookahead, this._previousCommand);
if (command === SVGPathSeg.PATHSEG_UNKNOWN) {
return null;
}
} else {
this._currentIndex++;
}
this._previousCommand = command;
switch (command) {
case SVGPathSeg.PATHSEG_MOVETO_REL:
return new SVGPathSegMovetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_MOVETO_ABS:
return new SVGPathSegMovetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_REL:
return new SVGPathSegLinetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_ABS:
return new SVGPathSegLinetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
return new SVGPathSegLinetoHorizontalRel(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
return new SVGPathSegLinetoHorizontalAbs(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
return new SVGPathSegLinetoVerticalRel(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
return new SVGPathSegLinetoVerticalAbs(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_CLOSEPATH:
this._skipOptionalSpaces();
return new SVGPathSegClosePath(owningPathSegList);
case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
{
var _points = { x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegCurvetoCubicRel(owningPathSegList, _points.x, _points.y, _points.x1, _points.y1, _points.x2, _points.y2);
}case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
{
var _points2 = { x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegCurvetoCubicAbs(owningPathSegList, _points2.x, _points2.y, _points2.x1, _points2.y1, _points2.x2, _points2.y2);
}case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
{
var _points3 = { x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, _points3.x, _points3.y, _points3.x2, _points3.y2);
}case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
{
var _points4 = { x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, _points4.x, _points4.y, _points4.x2, _points4.y2);
}case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
{
var _points5 = { x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegCurvetoQuadraticRel(owningPathSegList, _points5.x, _points5.y, _points5.x1, _points5.y1);
}case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
var points = { x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1);
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_ARC_REL:
{
var _points6 = { x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegArcRel(owningPathSegList, _points6.x, _points6.y, _points6.x1, _points6.y1, _points6.arcAngle, _points6.arcLarge, _points6.arcSweep);
}case SVGPathSeg.PATHSEG_ARC_ABS:
{
var _points7 = { x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber() };
return new SVGPathSegArcAbs(owningPathSegList, _points7.x, _points7.y, _points7.x1, _points7.y1, _points7.arcAngle, _points7.arcLarge, _points7.arcSweep);
}default:
throw new Error('Unknown path seg type.');
}
}
}]);
return Source;
}();
var builder = new Builder();
var source = new Source(string);
if (!source.initialCommandIsMoveTo()) {
return [];
}
while (source.hasMoreData()) {
var pathSeg = source.parseSegment();
if (!pathSeg) {
return [];
}
builder.appendSegment(pathSeg);
}
return builder.pathSegList;
}
}]);
return _SVGPathSegList;
}();
_SVGPathSegList.prototype.classname = 'SVGPathSegList';
Object.defineProperty(_SVGPathSegList.prototype, 'numberOfItems', {
get: function get$$1() {
this._checkPathSynchronizedToList();
return this._list.length;
},
enumerable: true
});
_SVGPathSegList._pathSegArrayAsString = function (pathSegArray) {
var string = '';
var first = true;
pathSegArray.forEach(function (pathSeg) {
if (first) {
first = false;
string += pathSeg._asPathString();
} else {
string += ' ' + pathSeg._asPathString();
}
});
return string;
};
// Add the pathSegList accessors to SVGPathElement.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData
Object.defineProperties(SVGPathElement.prototype, {
pathSegList: {
get: function get$$1() {
if (!this._pathSegList) {
this._pathSegList = new _SVGPathSegList(this);
}
return this._pathSegList;
},
enumerable: true
},
// FIXME: The following are not implemented and simply return SVGPathElement.pathSegList.
normalizedPathSegList: {
get: function get$$1() {
return this.pathSegList;
},
enumerable: true },
animatedPathSegList: {
get: function get$$1() {
return this.pathSegList;
},
enumerable: true },
animatedNormalizedPathSegList: {
get: function get$$1() {
return this.pathSegList;
},
enumerable: true }
});
window.SVGPathSegList = _SVGPathSegList;
}
})();
/* globals jQuery */
var $ = jQuery;
var supportsSvg_ = function () {
return !!document.createElementNS && !!document.createElementNS(NS.SVG, 'svg').createSVGRect;
}();
var _navigator = navigator,
userAgent = _navigator.userAgent;
var svg = document.createElementNS(NS.SVG, 'svg');
// Note: Browser sniffing should only be used if no other detection method is possible
var isOpera_ = !!window.opera;
var isWebkit_ = userAgent.includes('AppleWebKit');
var isGecko_ = userAgent.includes('Gecko/');
var isIE_ = userAgent.includes('MSIE');
var isChrome_ = userAgent.includes('Chrome/');
var isWindows_ = userAgent.includes('Windows');
var isMac_ = userAgent.includes('Macintosh');
var isTouch_ = 'ontouchstart' in window;
var supportsSelectors_ = function () {
return !!svg.querySelector;
}();
var supportsXpath_ = function () {
return !!document.evaluate;
}();
// segList functions (for FF1.5 and 2.0)
var supportsPathReplaceItem_ = function () {
var path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 10,10');
var seglist = path.pathSegList;
var seg = path.createSVGPathSegLinetoAbs(5, 5);
try {
seglist.replaceItem(seg, 1);
return true;
} catch (err) {}
return false;
}();
var supportsPathInsertItemBefore_ = function () {
var path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 10,10');
var seglist = path.pathSegList;
var seg = path.createSVGPathSegLinetoAbs(5, 5);
try {
seglist.insertItemBefore(seg, 1);
return true;
} catch (err) {}
return false;
}();
// text character positioning (for IE9)
var supportsGoodTextCharPos_ = function () {
var svgroot = document.createElementNS(NS.SVG, 'svg');
var svgcontent = document.createElementNS(NS.SVG, 'svg');
document.documentElement.append(svgroot);
svgcontent.setAttribute('x', 5);
svgroot.append(svgcontent);
var text = document.createElementNS(NS.SVG, 'text');
text.textContent = 'a';
svgcontent.append(text);
var pos = text.getStartPositionOfChar(0).x;
svgroot.remove();
return pos === 0;
}();
var supportsPathBBox_ = function () {
var svgcontent = document.createElementNS(NS.SVG, 'svg');
document.documentElement.append(svgcontent);
var path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 C0,0 10,10 10,0');
svgcontent.append(path);
var bbox = path.getBBox();
svgcontent.remove();
return bbox.height > 4 && bbox.height < 5;
}();
// Support for correct bbox sizing on groups with horizontal/vertical lines
var supportsHVLineContainerBBox_ = function () {
var svgcontent = document.createElementNS(NS.SVG, 'svg');
document.documentElement.append(svgcontent);
var path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 10,0');
var path2 = document.createElementNS(NS.SVG, 'path');
path2.setAttribute('d', 'M5,0 15,0');
var g = document.createElementNS(NS.SVG, 'g');
g.append(path, path2);
svgcontent.append(g);
var bbox = g.getBBox();
svgcontent.remove();
// Webkit gives 0, FF gives 10, Opera (correctly) gives 15
return bbox.width === 15;
}();
var supportsGoodDecimals_ = function () {
// Correct decimals on clone attributes (Opera < 10.5/win/non-en)
var rect = document.createElementNS(NS.SVG, 'rect');
rect.setAttribute('x', 0.1);
var crect = rect.cloneNode(false);
var retValue = !crect.getAttribute('x').includes(',');
if (!retValue) {
// Todo: i18nize or remove
$.alert('NOTE: This version of Opera is known to contain bugs in SVG-edit.\n' + 'Please upgrade to the latest version in which the problems have been fixed.');
}
return retValue;
}();
var supportsNonScalingStroke_ = function () {
var rect = document.createElementNS(NS.SVG, 'rect');
rect.setAttribute('style', 'vector-effect:non-scaling-stroke');
return rect.style.vectorEffect === 'non-scaling-stroke';
}();
var supportsNativeSVGTransformLists_ = function () {
var rect = document.createElementNS(NS.SVG, 'rect');
var rxform = rect.transform.baseVal;
var t1 = svg.createSVGTransform();
rxform.appendItem(t1);
var r1 = rxform.getItem(0);
// Todo: Do frame-independent instance checking
return r1 instanceof SVGTransform && t1 instanceof SVGTransform && r1.type === t1.type && r1.angle === t1.angle && r1.matrix.a === t1.matrix.a && r1.matrix.b === t1.matrix.b && r1.matrix.c === t1.matrix.c && r1.matrix.d === t1.matrix.d && r1.matrix.e === t1.matrix.e && r1.matrix.f === t1.matrix.f;
}();
// Public API
var isOpera = function isOpera() {
return isOpera_;
};
var isWebkit = function isWebkit() {
return isWebkit_;
};
var isGecko = function isGecko() {
return isGecko_;
};
var isIE = function isIE() {
return isIE_;
};
var isChrome = function isChrome() {
return isChrome_;
};
var isMac = function isMac() {
return isMac_;
};
var isTouch = function isTouch() {
return isTouch_;
};
var supportsSelectors = function supportsSelectors() {
return supportsSelectors_;
};
var supportsXpath = function supportsXpath() {
return supportsXpath_;
};
var supportsPathReplaceItem = function supportsPathReplaceItem() {
return supportsPathReplaceItem_;
};
var supportsPathInsertItemBefore = function supportsPathInsertItemBefore() {
return supportsPathInsertItemBefore_;
};
var supportsPathBBox = function supportsPathBBox() {
return supportsPathBBox_;
};
var supportsHVLineContainerBBox = function supportsHVLineContainerBBox() {
return supportsHVLineContainerBBox_;
};
var supportsGoodTextCharPos = function supportsGoodTextCharPos() {
return supportsGoodTextCharPos_;
};
var supportsNonScalingStroke = function supportsNonScalingStroke() {
return supportsNonScalingStroke_;
};
var supportsNativeTransformLists = function supportsNativeTransformLists() {
return supportsNativeSVGTransformLists_;
};
/**
* jQuery module to work with SVG.
*
* Licensed under the MIT License
*
*/
// This fixes $(...).attr() to work as expected with SVG elements.
// Does not currently use *AttributeNS() since we rarely need that.
// See https://api.jquery.com/attr/ for basic documentation of .attr()
// Additional functionality:
// - When getting attributes, a string that's a number is returned as type number.
// - If an array is supplied as the first parameter, multiple values are returned
// as an object with values for each given attribute
function jqPluginSVG ($) {
var proxied = $.fn.attr,
svgns = 'http://www.w3.org/2000/svg';
$.fn.attr = function (key, value) {
var len = this.length;
if (!len) {
return proxied.apply(this, arguments);
}
for (var i = 0; i < len; ++i) {
var elem = this[i];
// set/get SVG attribute
if (elem.namespaceURI === svgns) {
// Setting attribute
if (value !== undefined) {
elem.setAttribute(key, value);
} else if (Array.isArray(key)) {
// Getting attributes from array
var obj = {};
var j = key.length;
while (j--) {
var aname = key[j];
var attr = elem.getAttribute(aname);
// This returns a number when appropriate
if (attr || attr === '0') {
attr = isNaN(attr) ? attr : attr - 0;
}
obj[aname] = attr;
}
return obj;
}
if ((typeof key === 'undefined' ? 'undefined' : _typeof(key)) === 'object') {
// Setting attributes from object
for (var v in key) {
elem.setAttribute(v, key[v]);
}
// Getting attribute
} else {
var _attr = elem.getAttribute(key);
if (_attr || _attr === '0') {
_attr = isNaN(_attr) ? _attr : _attr - 0;
}
return _attr;
}
} else {
return proxied.apply(this, arguments);
}
}
return this;
};
return $;
}
/**
* SVGTransformList
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
var svgroot = document.createElementNS(NS.SVG, 'svg');
// Helper function.
function transformToString(xform) {
var m = xform.matrix;
var text = '';
switch (xform.type) {
case 1:
// MATRIX
text = 'matrix(' + [m.a, m.b, m.c, m.d, m.e, m.f].join(',') + ')';
break;
case 2:
// TRANSLATE
text = 'translate(' + m.e + ',' + m.f + ')';
break;
case 3:
// SCALE
if (m.a === m.d) {
text = 'scale(' + m.a + ')';
} else {
text = 'scale(' + m.a + ',' + m.d + ')';
}
break;
case 4:
{
// ROTATE
var cx = 0;
var cy = 0;
// this prevents divide by zero
if (xform.angle !== 0) {
var K = 1 - m.a;
cy = (K * m.f + m.b * m.e) / (K * K + m.b * m.b);
cx = (m.e - m.b * cy) / K;
}
text = 'rotate(' + xform.angle + ' ' + cx + ',' + cy + ')';
break;
}
}
return text;
}
/**
* Map of SVGTransformList objects.
*/
var listMap_ = {};
// **************************************************************************************
// SVGTransformList implementation for Webkit
// These methods do not currently raise any exceptions.
// These methods also do not check that transforms are being inserted. This is basically
// implementing as much of SVGTransformList that we need to get the job done.
//
// interface SVGEditTransformList {
// attribute unsigned long numberOfItems;
// void clear ( )
// SVGTransform initialize ( in SVGTransform newItem )
// SVGTransform getItem ( in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform insertItemBefore ( in SVGTransform newItem, in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform replaceItem ( in SVGTransform newItem, in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform removeItem ( in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform appendItem ( in SVGTransform newItem )
// NOT IMPLEMENTED: SVGTransform createSVGTransformFromMatrix ( in SVGMatrix matrix );
// NOT IMPLEMENTED: SVGTransform consolidate ( );
// }
// **************************************************************************************
var SVGTransformList = function SVGTransformList(elem) {
classCallCheck(this, SVGTransformList);
this._elem = elem || null;
this._xforms = [];
// TODO: how do we capture the undo-ability in the changed transform list?
this._update = function () {
var tstr = '';
/* const concatMatrix = */svgroot.createSVGMatrix();
for (var i = 0; i < this.numberOfItems; ++i) {
var xform = this._list.getItem(i);
tstr += transformToString(xform) + ' ';
}
this._elem.setAttribute('transform', tstr);
};
this._list = this;
this._init = function () {
var _this = this;
// Transform attribute parser
var str = this._elem.getAttribute('transform');
if (!str) {
return;
}
// TODO: Add skew support in future
var re = /\s*((scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/;
var m = true;
while (m) {
m = str.match(re);
str = str.replace(re, '');
if (m && m[1]) {
(function () {
var x = m[1];
var bits = x.split(/\s*\(/);
var name = bits[0];
var valBits = bits[1].match(/\s*(.*?)\s*\)/);
valBits[1] = valBits[1].replace(/(\d)-/g, '$1 -');
var valArr = valBits[1].split(/[, ]+/);
var letters = 'abcdef'.split('');
var mtx = svgroot.createSVGMatrix();
Object.values(valArr).forEach(function (item, i) {
valArr[i] = parseFloat(item);
if (name === 'matrix') {
mtx[letters[i]] = valArr[i];
}
});
var xform = svgroot.createSVGTransform();
var fname = 'set' + name.charAt(0).toUpperCase() + name.slice(1);
var values = name === 'matrix' ? [mtx] : valArr;
if (name === 'scale' && values.length === 1) {
values.push(values[0]);
} else if (name === 'translate' && values.length === 1) {
values.push(0);
} else if (name === 'rotate' && values.length === 1) {
values.push(0, 0);
}
xform[fname].apply(xform, values);
_this._list.appendItem(xform);
})();
}
}
};
this._removeFromOtherLists = function (item) {
if (item) {
// Check if this transform is already in a transformlist, and
// remove it if so.
var found = false;
for (var id in listMap_) {
var tl = listMap_[id];
for (var i = 0, len = tl._xforms.length; i < len; ++i) {
if (tl._xforms[i] === item) {
found = true;
tl.removeItem(i);
break;
}
}
if (found) {
break;
}
}
}
};
this.numberOfItems = 0;
this.clear = function () {
this.numberOfItems = 0;
this._xforms = [];
};
this.initialize = function (newItem) {
this.numberOfItems = 1;
this._removeFromOtherLists(newItem);
this._xforms = [newItem];
};
this.getItem = function (index) {
if (index < this.numberOfItems && index >= 0) {
return this._xforms[index];
}
var err = new Error('DOMException with code=INDEX_SIZE_ERR');
err.code = 1;
throw err;
};
this.insertItemBefore = function (newItem, index) {
var retValue = null;
if (index >= 0) {
if (index < this.numberOfItems) {
this._removeFromOtherLists(newItem);
var newxforms = new Array(this.numberOfItems + 1);
// TODO: use array copying and slicing
var i = void 0;
for (i = 0; i < index; ++i) {
newxforms[i] = this._xforms[i];
}
newxforms[i] = newItem;
for (var j = i + 1; i < this.numberOfItems; ++j, ++i) {
newxforms[j] = this._xforms[i];
}
this.numberOfItems++;
this._xforms = newxforms;
retValue = newItem;
this._list._update();
} else {
retValue = this._list.appendItem(newItem);
}
}
return retValue;
};
this.replaceItem = function (newItem, index) {
var retValue = null;
if (index < this.numberOfItems && index >= 0) {
this._removeFromOtherLists(newItem);
this._xforms[index] = newItem;
retValue = newItem;
this._list._update();
}
return retValue;
};
this.removeItem = function (index) {
if (index < this.numberOfItems && index >= 0) {
var retValue = this._xforms[index];
var newxforms = new Array(this.numberOfItems - 1);
var i = void 0;
for (i = 0; i < index; ++i) {
newxforms[i] = this._xforms[i];
}
for (var j = i; j < this.numberOfItems - 1; ++j, ++i) {
newxforms[j] = this._xforms[i + 1];
}
this.numberOfItems--;
this._xforms = newxforms;
this._list._update();
return retValue;
}
var err = new Error('DOMException with code=INDEX_SIZE_ERR');
err.code = 1;
throw err;
};
this.appendItem = function (newItem) {
this._removeFromOtherLists(newItem);
this._xforms.push(newItem);
this.numberOfItems++;
this._list._update();
return newItem;
};
};
var resetListMap = function resetListMap() {
listMap_ = {};
};
/**
* Removes transforms of the given element from the map.
* Parameters:
* elem - a DOM Element
*/
var removeElementFromListMap = function removeElementFromListMap(elem) {
if (elem.id && listMap_[elem.id]) {
delete listMap_[elem.id];
}
};
/**
* Returns an object that behaves like a SVGTransformList for the given DOM element
* @param elem - DOM element to get a transformlist from
*/
var getTransformList = function getTransformList(elem) {
if (!supportsNativeTransformLists()) {
var id = elem.id || 'temp';
var t = listMap_[id];
if (!t || id === 'temp') {
listMap_[id] = new SVGTransformList(elem);
listMap_[id]._init();
t = listMap_[id];
}
return t;
}
if (elem.transform) {
return elem.transform.baseVal;
}
if (elem.gradientTransform) {
return elem.gradientTransform.baseVal;
}
if (elem.patternTransform) {
return elem.patternTransform.baseVal;
}
return null;
};
/**
* Package: svgedit.units
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
var wAttrs = ['x', 'x1', 'cx', 'rx', 'width'];
var hAttrs = ['y', 'y1', 'cy', 'ry', 'height'];
var unitAttrs = ['r', 'radius'].concat(wAttrs, hAttrs);
// unused
/*
const unitNumMap = {
'%': 2,
em: 3,
ex: 4,
px: 5,
cm: 6,
mm: 7,
in: 8,
pt: 9,
pc: 10
};
*/
// Container of elements.
var elementContainer_ = void 0;
/**
* Stores mapping of unit type to user coordinates.
*/
var typeMap_ = {};
/**
* ElementContainer interface
*
* function getBaseUnit() - Returns a string of the base unit type of the container ('em')
* function getElement() - Returns an element in the container given an id
* function getHeight() - Returns the container's height
* function getWidth() - Returns the container's width
* function getRoundDigits() - Returns the number of digits number should be rounded to
*/
/**
* Initializes this module.
*
* @param elementContainer - An object implementing the ElementContainer interface.
*/
var init = function init(elementContainer) {
elementContainer_ = elementContainer;
// Get correct em/ex values by creating a temporary SVG.
var svg = document.createElementNS(NS.SVG, 'svg');
document.body.append(svg);
var rect = document.createElementNS(NS.SVG, 'rect');
rect.setAttribute('width', '1em');
rect.setAttribute('height', '1ex');
rect.setAttribute('x', '1in');
svg.append(rect);
var bb = rect.getBBox();
svg.remove();
var inch = bb.x;
typeMap_ = {
em: bb.width,
ex: bb.height,
in: inch,
cm: inch / 2.54,
mm: inch / 25.4,
pt: inch / 72,
pc: inch / 6,
px: 1,
'%': 0
};
};
/**
* Group: Unit conversion functions
*/
/**
* @returns The unit object with values for each unit
*/
var getTypeMap = function getTypeMap() {
return typeMap_;
};
/**
* Rounds a given value to a float with number of digits defined in save_options
*
* @param val - The value as a String, Number or Array of two numbers to be rounded
*
* @returns
* If a string/number was given, returns a Float. If an array, return a string
* with comma-separated floats
*/
var shortFloat = function shortFloat(val) {
var digits = elementContainer_.getRoundDigits();
if (!isNaN(val)) {
// Note that + converts to Number
return +(+val).toFixed(digits);
}
if (Array.isArray(val)) {
return shortFloat(val[0]) + ',' + shortFloat(val[1]);
}
return parseFloat(val).toFixed(digits) - 0;
};
/**
* Converts the number to given unit or baseUnit
* @returns {number}
*/
var convertUnit = function convertUnit(val, unit) {
unit = unit || elementContainer_.getBaseUnit();
// baseVal.convertToSpecifiedUnits(unitNumMap[unit]);
// const val = baseVal.valueInSpecifiedUnits;
// baseVal.convertToSpecifiedUnits(1);
return shortFloat(val / typeMap_[unit]);
};
/**
* Sets an element's attribute based on the unit in its current value.
*
* @param elem - DOM element to be changed
* @param attr - String with the name of the attribute associated with the value
* @param val - String with the attribute value to convert
*/
var setUnitAttr = function setUnitAttr(elem, attr, val) {
// if (!isNaN(val)) {
// New value is a number, so check currently used unit
// const oldVal = elem.getAttribute(attr);
// Enable this for alternate mode
// if (oldVal !== null && (isNaN(oldVal) || elementContainer_.getBaseUnit() !== 'px')) {
// // Old value was a number, so get unit, then convert
// let unit;
// if (oldVal.substr(-1) === '%') {
// const res = getResolution();
// unit = '%';
// val *= 100;
// if (wAttrs.includes(attr)) {
// val = val / res.w;
// } else if (hAttrs.includes(attr)) {
// val = val / res.h;
// } else {
// return val / Math.sqrt((res.w*res.w) + (res.h*res.h))/Math.sqrt(2);
// }
// } else {
// if (elementContainer_.getBaseUnit() !== 'px') {
// unit = elementContainer_.getBaseUnit();
// } else {
// unit = oldVal.substr(-2);
// }
// val = val / typeMap_[unit];
// }
//
// val += unit;
// }
// }
elem.setAttribute(attr, val);
};
/**
* Converts given values to numbers. Attributes must be supplied in
* case a percentage is given
*
* @param attr - String with the name of the attribute associated with the value
* @param val - String with the attribute value to convert
*/
var convertToNum = function convertToNum(attr, val) {
// Return a number if that's what it already is
if (!isNaN(val)) {
return val - 0;
}
if (val.substr(-1) === '%') {
// Deal with percentage, depends on attribute
var _num = val.substr(0, val.length - 1) / 100;
var width = elementContainer_.getWidth();
var height = elementContainer_.getHeight();
if (wAttrs.includes(attr)) {
return _num * width;
}
if (hAttrs.includes(attr)) {
return _num * height;
}
return _num * Math.sqrt(width * width + height * height) / Math.sqrt(2);
}
var unit = val.substr(-2);
var num = val.substr(0, val.length - 2);
// Note that this multiplication turns the string into a number
return num * typeMap_[unit];
};
/**
* Check if an attribute's value is in a valid format
* @param attr - String with the name of the attribute associated with the value
* @param val - String with the attribute value to check
*/
var isValidUnit = function isValidUnit(attr, val, selectedElement) {
var valid = false;
if (unitAttrs.includes(attr)) {
// True if it's just a number
if (!isNaN(val)) {
valid = true;
} else {
// Not a number, check if it has a valid unit
val = val.toLowerCase();
Object.keys(typeMap_).forEach(function (unit) {
if (valid) {
return;
}
var re = new RegExp('^-?[\\d\\.]+' + unit + '$');
if (re.test(val)) {
valid = true;
}
});
}
} else if (attr === 'id') {
// if we're trying to change the id, make sure it's not already present in the doc
// and the id value is valid.
var result = false;
// because getElem() can throw an exception in the case of an invalid id
// (according to https://www.w3.org/TR/xml-id/ IDs must be a NCName)
// we wrap it in an exception and only return true if the ID was valid and
// not already present
try {
var elem = elementContainer_.getElement(val);
result = elem == null || elem === selectedElement;
} catch (e) {}
return result;
}
valid = true;
return valid;
};
/**
* Package: svedit.history
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Jeff Schiller
*/
/**
* Group: Undo/Redo history management
*/
var HistoryEventTypes = {
BEFORE_APPLY: 'before_apply',
AFTER_APPLY: 'after_apply',
BEFORE_UNAPPLY: 'before_unapply',
AFTER_UNAPPLY: 'after_unapply'
};
// const removedElements = {};
/**
* An interface that all command objects must implement.
* @typedef {Object} svgedit.history.HistoryCommand
* void apply(svgedit.history.HistoryEventHandler);
* void unapply(svgedit.history.HistoryEventHandler);
* Element[] elements();
* String getText();
*
* static String type();
* }
*
* Interface: svgedit.history.HistoryEventHandler
* An interface for objects that will handle history events.
*
* interface svgedit.history.HistoryEventHandler {
* void handleHistoryEvent(eventType, command);
* }
*
* eventType is a string conforming to one of the HistoryEvent types.
* command is an object fulfilling the HistoryCommand interface.
*/
/**
* @class svgedit.history.MoveElementCommand
* @implements svgedit.history.HistoryCommand
* History command for an element that had its DOM position changed
* @param {Element} elem - The DOM element that was moved
* @param {Element} oldNextSibling - The element's next sibling before it was moved
* @param {Element} oldParent - The element's parent before it was moved
* @param {string} [text] - An optional string visible to user related to this change
*/
var MoveElementCommand = function () {
function MoveElementCommand(elem, oldNextSibling, oldParent, text) {
classCallCheck(this, MoveElementCommand);
this.elem = elem;
this.text = text ? 'Move ' + elem.tagName + ' to ' + text : 'Move ' + elem.tagName;
this.oldNextSibling = oldNextSibling;
this.oldParent = oldParent;
this.newNextSibling = elem.nextSibling;
this.newParent = elem.parentNode;
}
createClass(MoveElementCommand, [{
key: 'getText',
value: function getText() {
return this.text;
}
}, {
key: 'type',
value: function type() {
return 'svgedit.history.MoveElementCommand';
}
/**
* Re-positions the element
* @param {{handleHistoryEvent: function}} handler
*/
}, {
key: 'apply',
value: function apply(handler) {
// TODO(codedread): Refactor this common event code into a base HistoryCommand class.
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this);
}
this.elem = this.newParent.insertBefore(this.elem, this.newNextSibling);
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this);
}
}
/**
* Positions the element back to its original location
* @param {{handleHistoryEvent: function}} handler
*/
}, {
key: 'unapply',
value: function unapply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this);
}
this.elem = this.oldParent.insertBefore(this.elem, this.oldNextSibling);
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this);
}
}
/**
* @returns {Array} Array with element associated with this command
*/
}, {
key: 'elements',
value: function elements() {
return [this.elem];
}
}]);
return MoveElementCommand;
}();
MoveElementCommand.type = MoveElementCommand.prototype.type;
/**
* @implements svgedit.history.HistoryCommand
* History command for an element that was added to the DOM
*
* @param elem - The newly added DOM element
* @param text - An optional string visible to user related to this change
*/
var InsertElementCommand = function () {
function InsertElementCommand(elem, text) {
classCallCheck(this, InsertElementCommand);
this.elem = elem;
this.text = text || 'Create ' + elem.tagName;
this.parent = elem.parentNode;
this.nextSibling = this.elem.nextSibling;
}
createClass(InsertElementCommand, [{
key: 'type',
value: function type() {
return 'svgedit.history.InsertElementCommand';
}
}, {
key: 'getText',
value: function getText() {
return this.text;
}
// Re-Inserts the new element
}, {
key: 'apply',
value: function apply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this);
}
this.elem = this.parent.insertBefore(this.elem, this.nextSibling);
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this);
}
}
// Removes the element
}, {
key: 'unapply',
value: function unapply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this);
}
this.parent = this.elem.parentNode;
this.elem = this.elem.parentNode.removeChild(this.elem);
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this);
}
}
/**
* @returns {Array} Array with element associated with this command
*/
}, {
key: 'elements',
value: function elements() {
return [this.elem];
}
}]);
return InsertElementCommand;
}();
InsertElementCommand.type = InsertElementCommand.prototype.type;
/**
* @implements svgedit.history.HistoryCommand
* History command for an element removed from the DOM
* @param elem - The removed DOM element
* @param oldNextSibling - The DOM element's nextSibling when it was in the DOM
* @param oldParent - The DOM element's parent
* @param {String} [text] - An optional string visible to user related to this change
*/
var RemoveElementCommand = function () {
function RemoveElementCommand(elem, oldNextSibling, oldParent, text) {
classCallCheck(this, RemoveElementCommand);
this.elem = elem;
this.text = text || 'Delete ' + elem.tagName;
this.nextSibling = oldNextSibling;
this.parent = oldParent;
// special hack for webkit: remove this element's entry in the svgTransformLists map
removeElementFromListMap(elem);
}
createClass(RemoveElementCommand, [{
key: 'type',
value: function type() {
return 'svgedit.history.RemoveElementCommand';
}
}, {
key: 'getText',
value: function getText() {
return this.text;
}
// Re-removes the new element
}, {
key: 'apply',
value: function apply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this);
}
removeElementFromListMap(this.elem);
this.parent = this.elem.parentNode;
this.elem = this.parent.removeChild(this.elem);
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this);
}
}
// Re-adds the new element
}, {
key: 'unapply',
value: function unapply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this);
}
removeElementFromListMap(this.elem);
if (this.nextSibling == null) {
if (window.console) {
console.log('Error: reference element was lost');
}
}
this.parent.insertBefore(this.elem, this.nextSibling); // Don't use `before` or `prepend` as `this.nextSibling` may be `null`
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this);
}
}
/**
* @returns {Array} Array with element associated with this command
*/
}, {
key: 'elements',
value: function elements() {
return [this.elem];
}
}]);
return RemoveElementCommand;
}();
RemoveElementCommand.type = RemoveElementCommand.prototype.type;
/**
* @implements svgedit.history.HistoryCommand
* History command to make a change to an element.
* Usually an attribute change, but can also be textcontent.
* @param elem - The DOM element that was changed
* @param attrs - An object with the attributes to be changed and the values they had *before* the change
* @param {String} text - An optional string visible to user related to this change
*/
var ChangeElementCommand = function () {
function ChangeElementCommand(elem, attrs, text) {
classCallCheck(this, ChangeElementCommand);
this.elem = elem;
this.text = text ? 'Change ' + elem.tagName + ' ' + text : 'Change ' + elem.tagName;
this.newValues = {};
this.oldValues = attrs;
for (var attr in attrs) {
if (attr === '#text') {
this.newValues[attr] = elem.textContent;
} else if (attr === '#href') {
this.newValues[attr] = getHref(elem);
} else {
this.newValues[attr] = elem.getAttribute(attr);
}
}
}
createClass(ChangeElementCommand, [{
key: 'type',
value: function type() {
return 'svgedit.history.ChangeElementCommand';
}
}, {
key: 'getText',
value: function getText() {
return this.text;
}
// Performs the stored change action
}, {
key: 'apply',
value: function apply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this);
}
var bChangedTransform = false;
for (var attr in this.newValues) {
if (this.newValues[attr]) {
if (attr === '#text') {
this.elem.textContent = this.newValues[attr];
} else if (attr === '#href') {
setHref(this.elem, this.newValues[attr]);
} else {
this.elem.setAttribute(attr, this.newValues[attr]);
}
} else {
if (attr === '#text') {
this.elem.textContent = '';
} else {
this.elem.setAttribute(attr, '');
this.elem.removeAttribute(attr);
}
}
if (attr === 'transform') {
bChangedTransform = true;
}
}
// relocate rotational transform, if necessary
if (!bChangedTransform) {
var angle = getRotationAngle(this.elem);
if (angle) {
var bbox = this.elem.getBBox();
var cx = bbox.x + bbox.width / 2,
cy = bbox.y + bbox.height / 2;
var rotate = ['rotate(', angle, ' ', cx, ',', cy, ')'].join('');
if (rotate !== this.elem.getAttribute('transform')) {
this.elem.setAttribute('transform', rotate);
}
}
}
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this);
}
return true;
}
// Reverses the stored change action
}, {
key: 'unapply',
value: function unapply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this);
}
var bChangedTransform = false;
for (var attr in this.oldValues) {
if (this.oldValues[attr]) {
if (attr === '#text') {
this.elem.textContent = this.oldValues[attr];
} else if (attr === '#href') {
setHref(this.elem, this.oldValues[attr]);
} else {
this.elem.setAttribute(attr, this.oldValues[attr]);
}
} else {
if (attr === '#text') {
this.elem.textContent = '';
} else {
this.elem.removeAttribute(attr);
}
}
if (attr === 'transform') {
bChangedTransform = true;
}
}
// relocate rotational transform, if necessary
if (!bChangedTransform) {
var angle = getRotationAngle(this.elem);
if (angle) {
var bbox = this.elem.getBBox();
var cx = bbox.x + bbox.width / 2,
cy = bbox.y + bbox.height / 2;
var rotate = ['rotate(', angle, ' ', cx, ',', cy, ')'].join('');
if (rotate !== this.elem.getAttribute('transform')) {
this.elem.setAttribute('transform', rotate);
}
}
}
// Remove transformlist to prevent confusion that causes bugs like 575.
removeElementFromListMap(this.elem);
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this);
}
return true;
}
/**
* @returns {Array} Array with element associated with this command
*/
}, {
key: 'elements',
value: function elements() {
return [this.elem];
}
}]);
return ChangeElementCommand;
}();
ChangeElementCommand.type = ChangeElementCommand.prototype.type;
// TODO: create a 'typing' command object that tracks changes in text
// if a new Typing command is created and the top command on the stack is also a Typing
// and they both affect the same element, then collapse the two commands into one
/**
* @implements svgedit.history.HistoryCommand
* History command that can contain/execute multiple other commands
* @param {String} [text] - An optional string visible to user related to this change
*/
var BatchCommand = function () {
function BatchCommand(text) {
classCallCheck(this, BatchCommand);
this.text = text || 'Batch Command';
this.stack = [];
}
createClass(BatchCommand, [{
key: 'type',
value: function type() {
return 'svgedit.history.BatchCommand';
}
}, {
key: 'getText',
value: function getText() {
return this.text;
}
// Runs "apply" on all subcommands
}, {
key: 'apply',
value: function apply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this);
}
var len = this.stack.length;
for (var i = 0; i < len; ++i) {
this.stack[i].apply(handler);
}
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this);
}
}
// Runs "unapply" on all subcommands
}, {
key: 'unapply',
value: function unapply(handler) {
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this);
}
for (var i = this.stack.length - 1; i >= 0; i--) {
this.stack[i].unapply(handler);
}
if (handler) {
handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this);
}
}
// Iterate through all our subcommands and returns all the elements we are changing
}, {
key: 'elements',
value: function elements() {
var elems = [];
var cmd = this.stack.length;
while (cmd--) {
var thisElems = this.stack[cmd].elements();
var elem = thisElems.length;
while (elem--) {
if (!elems.includes(thisElems[elem])) {
elems.push(thisElems[elem]);
}
}
}
return elems;
}
/**
* Adds a given command to the history stack
* @param cmd - The undo command object to add
*/
}, {
key: 'addSubCommand',
value: function addSubCommand(cmd) {
this.stack.push(cmd);
}
/**
* @returns {Boolean} Indicates whether or not the batch command is empty
*/
}, {
key: 'isEmpty',
value: function isEmpty() {
return !this.stack.length;
}
}]);
return BatchCommand;
}();
BatchCommand.type = BatchCommand.prototype.type;
/**
* @param historyEventHandler - an object that conforms to the HistoryEventHandler interface
* (see above)
*/
var UndoManager = function () {
function UndoManager(historyEventHandler) {
classCallCheck(this, UndoManager);
this.handler_ = historyEventHandler || null;
this.undoStackPointer = 0;
this.undoStack = [];
// this is the stack that stores the original values, the elements and
// the attribute name for begin/finish
this.undoChangeStackPointer = -1;
this.undoableChangeStack = [];
}
// Resets the undo stack, effectively clearing the undo/redo history
createClass(UndoManager, [{
key: 'resetUndoStack',
value: function resetUndoStack() {
this.undoStack = [];
this.undoStackPointer = 0;
}
/**
* @returns {Number} Integer with the current size of the undo history stack
*/
}, {
key: 'getUndoStackSize',
value: function getUndoStackSize() {
return this.undoStackPointer;
}
/**
* @returns {Number} Integer with the current size of the redo history stack
*/
}, {
key: 'getRedoStackSize',
value: function getRedoStackSize() {
return this.undoStack.length - this.undoStackPointer;
}
/**
* @returns {String} String associated with the next undo command
*/
}, {
key: 'getNextUndoCommandText',
value: function getNextUndoCommandText() {
return this.undoStackPointer > 0 ? this.undoStack[this.undoStackPointer - 1].getText() : '';
}
/**
* @returns {String} String associated with the next redo command
*/
}, {
key: 'getNextRedoCommandText',
value: function getNextRedoCommandText() {
return this.undoStackPointer < this.undoStack.length ? this.undoStack[this.undoStackPointer].getText() : '';
}
// Performs an undo step
}, {
key: 'undo',
value: function undo() {
if (this.undoStackPointer > 0) {
var cmd = this.undoStack[--this.undoStackPointer];
cmd.unapply(this.handler_);
}
}
// Performs a redo step
}, {
key: 'redo',
value: function redo() {
if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
var cmd = this.undoStack[this.undoStackPointer++];
cmd.apply(this.handler_);
}
}
/**
* Adds a command object to the undo history stack
* @param cmd - The command object to add
*/
}, {
key: 'addCommandToHistory',
value: function addCommandToHistory(cmd) {
// FIXME: we MUST compress consecutive text changes to the same element
// (right now each keystroke is saved as a separate command that includes the
// entire text contents of the text element)
// TODO: consider limiting the history that we store here (need to do some slicing)
// if our stack pointer is not at the end, then we have to remove
// all commands after the pointer and insert the new command
if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
this.undoStack = this.undoStack.splice(0, this.undoStackPointer);
}
this.undoStack.push(cmd);
this.undoStackPointer = this.undoStack.length;
}
/**
* This function tells the canvas to remember the old values of the
* attrName attribute for each element sent in. The elements and values
* are stored on a stack, so the next call to finishUndoableChange() will
* pop the elements and old values off the stack, gets the current values
* from the DOM and uses all of these to construct the undo-able command.
* @param attrName - The name of the attribute being changed
* @param elems - Array of DOM elements being changed
*/
}, {
key: 'beginUndoableChange',
value: function beginUndoableChange(attrName, elems) {
var p = ++this.undoChangeStackPointer;
var i = elems.length;
var oldValues = new Array(i),
elements = new Array(i);
while (i--) {
var elem = elems[i];
if (elem == null) {
continue;
}
elements[i] = elem;
oldValues[i] = elem.getAttribute(attrName);
}
this.undoableChangeStack[p] = {
attrName: attrName,
oldValues: oldValues,
elements: elements
};
}
/**
* This function returns a BatchCommand object which summarizes the
* change since beginUndoableChange was called. The command can then
* be added to the command history
* @returns Batch command object with resulting changes
*/
}, {
key: 'finishUndoableChange',
value: function finishUndoableChange() {
var p = this.undoChangeStackPointer--;
var changeset = this.undoableChangeStack[p];
var attrName = changeset.attrName;
var batchCmd = new BatchCommand('Change ' + attrName);
var i = changeset.elements.length;
while (i--) {
var elem = changeset.elements[i];
if (elem == null) {
continue;
}
var changes = {};
changes[attrName] = changeset.oldValues[i];
if (changes[attrName] !== elem.getAttribute(attrName)) {
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes, attrName));
}
}
this.undoableChangeStack[p] = null;
return batchCmd;
}
}]);
return UndoManager;
}();
var history = /*#__PURE__*/Object.freeze({
HistoryEventTypes: HistoryEventTypes,
MoveElementCommand: MoveElementCommand,
InsertElementCommand: InsertElementCommand,
RemoveElementCommand: RemoveElementCommand,
ChangeElementCommand: ChangeElementCommand,
BatchCommand: BatchCommand,
UndoManager: UndoManager
});
/**
* Package: svedit.math
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Constants
var NEAR_ZERO = 1e-14;
// Throw away SVGSVGElement used for creating matrices/transforms.
var svg$1 = document.createElementNS(NS.SVG, 'svg');
/**
* A (hopefully) quicker function to transform a point by a matrix
* (this function avoids any DOM calls and just does the math)
* @param {number} x - Float representing the x coordinate
* @param {number} y - Float representing the y coordinate
* @param {SVGMatrix} m - Matrix object to transform the point with
* @returns {Object} An x, y object representing the transformed point
*/
var transformPoint = function transformPoint(x, y, m) {
return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f };
};
/**
* Helper function to check if the matrix performs no actual transform
* (i.e. exists for identity purposes)
* @param {SVGMatrix} m - The matrix object to check
* @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
*/
var isIdentity = function isIdentity(m) {
return m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0;
};
/**
* This function tries to return a SVGMatrix that is the multiplication m1*m2.
* We also round to zero when it's near zero
* @param {...SVGMatrix} args - Matrix objects to multiply
* @returns {SVGMatrix} The matrix object resulting from the calculation
*/
var matrixMultiply = function matrixMultiply() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var m = args.reduceRight(function (prev, m1) {
return m1.multiply(prev);
});
if (Math.abs(m.a) < NEAR_ZERO) {
m.a = 0;
}
if (Math.abs(m.b) < NEAR_ZERO) {
m.b = 0;
}
if (Math.abs(m.c) < NEAR_ZERO) {
m.c = 0;
}
if (Math.abs(m.d) < NEAR_ZERO) {
m.d = 0;
}
if (Math.abs(m.e) < NEAR_ZERO) {
m.e = 0;
}
if (Math.abs(m.f) < NEAR_ZERO) {
m.f = 0;
}
return m;
};
/**
* See if the given transformlist includes a non-indentity matrix transform
* @param {Object} [tlist] - The transformlist to check
* @returns {boolean} Whether or not a matrix transform was found
*/
var hasMatrixTransform = function hasMatrixTransform(tlist) {
if (!tlist) {
return false;
}
var num = tlist.numberOfItems;
while (num--) {
var xform = tlist.getItem(num);
if (xform.type === 1 && !isIdentity(xform.matrix)) {
return true;
}
}
return false;
};
/**
* Transforms a rectangle based on the given matrix
* @param {number} l - Float with the box's left coordinate
* @param {number} t - Float with the box's top coordinate
* @param {number} w - Float with the box width
* @param {number} h - Float with the box height
* @param {SVGMatrix} m - Matrix object to transform the box by
* @returns {Object} An object with the following values:
* tl - The top left coordinate (x,y object)
* tr - The top right coordinate (x,y object)
* bl - The bottom left coordinate (x,y object)
* br - The bottom right coordinate (x,y object)
* aabox - Object with the following values:
* x - Float with the axis-aligned x coordinate
* y - Float with the axis-aligned y coordinate
* width - Float with the axis-aligned width coordinate
* height - Float with the axis-aligned height coordinate
*/
var transformBox = function transformBox(l, t, w, h, m) {
var tl = transformPoint(l, t, m),
tr = transformPoint(l + w, t, m),
bl = transformPoint(l, t + h, m),
br = transformPoint(l + w, t + h, m),
minx = Math.min(tl.x, tr.x, bl.x, br.x),
maxx = Math.max(tl.x, tr.x, bl.x, br.x),
miny = Math.min(tl.y, tr.y, bl.y, br.y),
maxy = Math.max(tl.y, tr.y, bl.y, br.y);
return {
tl: tl,
tr: tr,
bl: bl,
br: br,
aabox: {
x: minx,
y: miny,
width: maxx - minx,
height: maxy - miny
}
};
};
/**
* This returns a single matrix Transform for a given Transform List
* (this is the equivalent of SVGTransformList.consolidate() but unlike
* that method, this one does not modify the actual SVGTransformList)
* This function is very liberal with its min, max arguments
* @param {Object} tlist - The transformlist object
* @param {integer} [min=0] - Optional integer indicating start transform position
* @param {integer} [max] - Optional integer indicating end transform position;
* defaults to one less than the tlist's numberOfItems
* @returns {Object} A single matrix transform object
*/
var transformListToTransform = function transformListToTransform(tlist, min, max) {
if (tlist == null) {
// Or should tlist = null have been prevented before this?
return svg$1.createSVGTransformFromMatrix(svg$1.createSVGMatrix());
}
min = min || 0;
max = max || tlist.numberOfItems - 1;
min = parseInt(min, 10);
max = parseInt(max, 10);
if (min > max) {
var temp = max;max = min;min = temp;
}
var m = svg$1.createSVGMatrix();
for (var i = min; i <= max; ++i) {
// if our indices are out of range, just use a harmless identity matrix
var mtom = i >= 0 && i < tlist.numberOfItems ? tlist.getItem(i).matrix : svg$1.createSVGMatrix();
m = matrixMultiply(m, mtom);
}
return svg$1.createSVGTransformFromMatrix(m);
};
/**
* Get the matrix object for a given element
* @param {Element} elem - The DOM element to check
* @returns {SVGMatrix} The matrix object associated with the element's transformlist
*/
var getMatrix = function getMatrix(elem) {
var tlist = getTransformList(elem);
return transformListToTransform(tlist).matrix;
};
/**
* Returns a 45 degree angle coordinate associated with the two given
* coordinates
* @param {number} x1 - First coordinate's x value
* @param {number} x2 - Second coordinate's x value
* @param {number} y1 - First coordinate's y value
* @param {number} y2 - Second coordinate's y value
* @returns {AngleCoord45}
*/
var snapToAngle = function snapToAngle(x1, y1, x2, y2) {
var snap = Math.PI / 4; // 45 degrees
var dx = x2 - x1;
var dy = y2 - y1;
var angle = Math.atan2(dy, dx);
var dist = Math.sqrt(dx * dx + dy * dy);
var snapangle = Math.round(angle / snap) * snap;
return {
x: x1 + dist * Math.cos(snapangle),
y: y1 + dist * Math.sin(snapangle),
a: snapangle
};
};
/**
* Check if two rectangles (BBoxes objects) intersect each other
* @param {SVGRect} r1 - The first BBox-like object
* @param {SVGRect} r2 - The second BBox-like object
* @returns {boolean} True if rectangles intersect
*/
var rectsIntersect = function rectsIntersect(r1, r2) {
return r2.x < r1.x + r1.width && r2.x + r2.width > r1.x && r2.y < r1.y + r1.height && r2.y + r2.height > r1.y;
};
/* globals jQuery */
var $$1 = jQuery;
var segData = {
2: ['x', 'y'],
4: ['x', 'y'],
6: ['x', 'y', 'x1', 'y1', 'x2', 'y2'],
8: ['x', 'y', 'x1', 'y1'],
10: ['x', 'y', 'r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag'],
12: ['x'],
14: ['y'],
16: ['x', 'y', 'x2', 'y2'],
18: ['x', 'y']
};
var uiStrings = {};
var setUiStrings = function setUiStrings(strs) {
Object.assign(uiStrings, strs.ui);
};
var pathFuncs = [];
var linkControlPts = true;
// Stores references to paths via IDs.
// TODO: Make this cross-document happy.
var pathData = {};
var setLinkControlPoints = function setLinkControlPoints(lcp) {
linkControlPts = lcp;
};
var path = null;
var editorContext_ = null;
var init$1 = function init$$1(editorContext) {
editorContext_ = editorContext;
pathFuncs = [0, 'ClosePath'];
var pathFuncsStrs = ['Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc', 'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth'];
$$1.each(pathFuncsStrs, function (i, s) {
pathFuncs.push(s + 'Abs');
pathFuncs.push(s + 'Rel');
});
};
var insertItemBefore = function insertItemBefore(elem, newseg, index) {
// Support insertItemBefore on paths for FF2
var list = elem.pathSegList;
if (supportsPathInsertItemBefore()) {
list.insertItemBefore(newseg, index);
return;
}
var len = list.numberOfItems;
var arr = [];
for (var i = 0; i < len; i++) {
var curSeg = list.getItem(i);
arr.push(curSeg);
}
list.clear();
for (var _i = 0; _i < len; _i++) {
if (_i === index) {
// index + 1
list.appendItem(newseg);
}
list.appendItem(arr[_i]);
}
};
// TODO: See if this should just live in replacePathSeg
var ptObjToArr = function ptObjToArr(type, segItem) {
var arr = segData[type],
len = arr.length;
var out = [];
for (var i = 0; i < len; i++) {
out[i] = segItem[arr[i]];
}
return out;
};
var getGripPt = function getGripPt(seg, altPt) {
var path = seg.path;
var out = {
x: altPt ? altPt.x : seg.item.x,
y: altPt ? altPt.y : seg.item.y
};
if (path.matrix) {
var pt = transformPoint(out.x, out.y, path.matrix);
out = pt;
}
var currentZoom = editorContext_.getCurrentZoom();
out.x *= currentZoom;
out.y *= currentZoom;
return out;
};
var getPointFromGrip = function getPointFromGrip(pt, path) {
var out = {
x: pt.x,
y: pt.y
};
if (path.matrix) {
pt = transformPoint(out.x, out.y, path.imatrix);
out.x = pt.x;
out.y = pt.y;
}
var currentZoom = editorContext_.getCurrentZoom();
out.x /= currentZoom;
out.y /= currentZoom;
return out;
};
/**
* Requires prior call to `setUiStrings` if `xlink:title`
* to be set on the grip
*/
var addPointGrip = function addPointGrip(index, x, y) {
// create the container of all the point grips
var pointGripContainer = getGripContainer();
var pointGrip = getElem('pathpointgrip_' + index);
// create it
if (!pointGrip) {
pointGrip = document.createElementNS(NS.SVG, 'circle');
var atts = {
id: 'pathpointgrip_' + index,
display: 'none',
r: 4,
fill: '#0FF',
stroke: '#00F',
'stroke-width': 2,
cursor: 'move',
style: 'pointer-events:all'
};
if ('pathNodeTooltip' in uiStrings) {
// May be empty if running path.js without svg-editor
atts['xlink:title'] = uiStrings.pathNodeTooltip;
}
assignAttributes(pointGrip, atts);
pointGrip = pointGripContainer.appendChild(pointGrip);
var grip = $$1('#pathpointgrip_' + index);
grip.dblclick(function () {
if (path) {
path.setSegType();
}
});
}
if (x && y) {
// set up the point grip element and display it
assignAttributes(pointGrip, {
cx: x,
cy: y,
display: 'inline'
});
}
return pointGrip;
};
var getGripContainer = function getGripContainer() {
var c = getElem('pathpointgrip_container');
if (!c) {
var parent = getElem('selectorParentGroup');
c = parent.appendChild(document.createElementNS(NS.SVG, 'g'));
c.id = 'pathpointgrip_container';
}
return c;
};
/**
* Requires prior call to `setUiStrings` if `xlink:title`
* to be set on the grip
*/
var addCtrlGrip = function addCtrlGrip(id) {
var pointGrip = getElem('ctrlpointgrip_' + id);
if (pointGrip) {
return pointGrip;
}
pointGrip = document.createElementNS(NS.SVG, 'circle');
var atts = {
id: 'ctrlpointgrip_' + id,
display: 'none',
r: 4,
fill: '#0FF',
stroke: '#55F',
'stroke-width': 1,
cursor: 'move',
style: 'pointer-events:all'
};
if ('pathCtrlPtTooltip' in uiStrings) {
// May be empty if running path.js without svg-editor
atts['xlink:title'] = uiStrings.pathCtrlPtTooltip;
}
assignAttributes(pointGrip, atts);
getGripContainer().append(pointGrip);
return pointGrip;
};
var getCtrlLine = function getCtrlLine(id) {
var ctrlLine = getElem('ctrlLine_' + id);
if (ctrlLine) {
return ctrlLine;
}
ctrlLine = document.createElementNS(NS.SVG, 'line');
assignAttributes(ctrlLine, {
id: 'ctrlLine_' + id,
stroke: '#555',
'stroke-width': 1,
style: 'pointer-events:none'
});
getGripContainer().append(ctrlLine);
return ctrlLine;
};
var getPointGrip = function getPointGrip(seg, update) {
var index = seg.index;
var pointGrip = addPointGrip(index);
if (update) {
var pt = getGripPt(seg);
assignAttributes(pointGrip, {
cx: pt.x,
cy: pt.y,
display: 'inline'
});
}
return pointGrip;
};
var getControlPoints = function getControlPoints(seg) {
var item = seg.item,
index = seg.index;
if (!('x1' in item) || !('x2' in item)) {
return null;
}
var cpt = {};
/* const pointGripContainer = */getGripContainer();
// Note that this is intentionally not seg.prev.item
var prev = path.segs[index - 1].item;
var segItems = [prev, item];
for (var i = 1; i < 3; i++) {
var id = index + 'c' + i;
var ctrlLine = cpt['c' + i + '_line'] = getCtrlLine(id);
var pt = getGripPt(seg, { x: item['x' + i], y: item['y' + i] });
var gpt = getGripPt(seg, { x: segItems[i - 1].x, y: segItems[i - 1].y });
assignAttributes(ctrlLine, {
x1: pt.x,
y1: pt.y,
x2: gpt.x,
y2: gpt.y,
display: 'inline'
});
cpt['c' + i + '_line'] = ctrlLine;
// create it
var pointGrip = cpt['c' + i] = addCtrlGrip(id);
assignAttributes(pointGrip, {
cx: pt.x,
cy: pt.y,
display: 'inline'
});
cpt['c' + i] = pointGrip;
}
return cpt;
};
// This replaces the segment at the given index. Type is given as number.
var replacePathSeg = function replacePathSeg(type, index, pts, elem) {
var pth = elem || path.elem;
var func = 'createSVGPathSeg' + pathFuncs[type];
var seg = pth[func].apply(pth, pts);
if (supportsPathReplaceItem()) {
pth.pathSegList.replaceItem(seg, index);
} else {
var segList = pth.pathSegList;
var len = segList.numberOfItems;
var arr = [];
for (var i = 0; i < len; i++) {
var curSeg = segList.getItem(i);
arr.push(curSeg);
}
segList.clear();
for (var _i2 = 0; _i2 < len; _i2++) {
if (_i2 === index) {
segList.appendItem(seg);
} else {
segList.appendItem(arr[_i2]);
}
}
}
};
var getSegSelector = function getSegSelector(seg, update) {
var index = seg.index;
var segLine = getElem('segline_' + index);
if (!segLine) {
var pointGripContainer = getGripContainer();
// create segline
segLine = document.createElementNS(NS.SVG, 'path');
assignAttributes(segLine, {
id: 'segline_' + index,
display: 'none',
fill: 'none',
stroke: '#0FF',
'stroke-width': 2,
style: 'pointer-events:none',
d: 'M0,0 0,0'
});
pointGripContainer.append(segLine);
}
if (update) {
var prev = seg.prev;
if (!prev) {
segLine.setAttribute('display', 'none');
return segLine;
}
var pt = getGripPt(prev);
// Set start point
replacePathSeg(2, 0, [pt.x, pt.y], segLine);
var pts = ptObjToArr(seg.type, seg.item, true);
for (var i = 0; i < pts.length; i += 2) {
var _pt = getGripPt(seg, { x: pts[i], y: pts[i + 1] });
pts[i] = _pt.x;
pts[i + 1] = _pt.y;
}
replacePathSeg(seg.type, 1, pts, segLine);
}
return segLine;
};
/**
* Takes three points and creates a smoother line based on them
* @param ct1 - Object with x and y values (first control point)
* @param ct2 - Object with x and y values (second control point)
* @param pt - Object with x and y values (third point)
* @returns Array of two "smoothed" point objects
*/
var smoothControlPoints = function smoothControlPoints(ct1, ct2, pt) {
// each point must not be the origin
var x1 = ct1.x - pt.x,
y1 = ct1.y - pt.y,
x2 = ct2.x - pt.x,
y2 = ct2.y - pt.y;
if ((x1 !== 0 || y1 !== 0) && (x2 !== 0 || y2 !== 0)) {
var r1 = Math.sqrt(x1 * x1 + y1 * y1),
r2 = Math.sqrt(x2 * x2 + y2 * y2),
nct1 = editorContext_.getSVGRoot().createSVGPoint(),
nct2 = editorContext_.getSVGRoot().createSVGPoint();
var anglea = Math.atan2(y1, x1),
angleb = Math.atan2(y2, x2);
if (anglea < 0) {
anglea += 2 * Math.PI;
}
if (angleb < 0) {
angleb += 2 * Math.PI;
}
var angleBetween = Math.abs(anglea - angleb),
angleDiff = Math.abs(Math.PI - angleBetween) / 2;
var newAnglea = void 0,
newAngleb = void 0;
if (anglea - angleb > 0) {
newAnglea = angleBetween < Math.PI ? anglea + angleDiff : anglea - angleDiff;
newAngleb = angleBetween < Math.PI ? angleb - angleDiff : angleb + angleDiff;
} else {
newAnglea = angleBetween < Math.PI ? anglea - angleDiff : anglea + angleDiff;
newAngleb = angleBetween < Math.PI ? angleb + angleDiff : angleb - angleDiff;
}
// rotate the points
nct1.x = r1 * Math.cos(newAnglea) + pt.x;
nct1.y = r1 * Math.sin(newAnglea) + pt.y;
nct2.x = r2 * Math.cos(newAngleb) + pt.x;
nct2.y = r2 * Math.sin(newAngleb) + pt.y;
return [nct1, nct2];
}
return undefined;
};
var Segment = function () {
function Segment(index, item) {
classCallCheck(this, Segment);
this.selected = false;
this.index = index;
this.item = item;
this.type = item.pathSegType;
this.ctrlpts = [];
this.ptgrip = null;
this.segsel = null;
}
createClass(Segment, [{
key: 'showCtrlPts',
value: function showCtrlPts(y) {
for (var i in this.ctrlpts) {
if (this.ctrlpts.hasOwnProperty(i)) {
this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none');
}
}
}
}, {
key: 'selectCtrls',
value: function selectCtrls(y) {
$$1('#ctrlpointgrip_' + this.index + 'c1, #ctrlpointgrip_' + this.index + 'c2').attr('fill', y ? '#0FF' : '#EEE');
}
}, {
key: 'show',
value: function show(y) {
if (this.ptgrip) {
this.ptgrip.setAttribute('display', y ? 'inline' : 'none');
this.segsel.setAttribute('display', y ? 'inline' : 'none');
// Show/hide all control points if available
this.showCtrlPts(y);
}
}
}, {
key: 'select',
value: function select(y) {
if (this.ptgrip) {
this.ptgrip.setAttribute('stroke', y ? '#0FF' : '#00F');
this.segsel.setAttribute('display', y ? 'inline' : 'none');
if (this.ctrlpts) {
this.selectCtrls(y);
}
this.selected = y;
}
}
}, {
key: 'addGrip',
value: function addGrip() {
this.ptgrip = getPointGrip(this, true);
this.ctrlpts = getControlPoints(this, true);
this.segsel = getSegSelector(this, true);
}
}, {
key: 'update',
value: function update(full) {
if (this.ptgrip) {
var pt = getGripPt(this);
assignAttributes(this.ptgrip, {
cx: pt.x,
cy: pt.y
});
getSegSelector(this, true);
if (this.ctrlpts) {
if (full) {
this.item = path.elem.pathSegList.getItem(this.index);
this.type = this.item.pathSegType;
}
getControlPoints(this);
}
// this.segsel.setAttribute('display', y ? 'inline' : 'none');
}
}
}, {
key: 'move',
value: function move(dx, dy) {
var item = this.item;
var curPts = this.ctrlpts ? [item.x += dx, item.y += dy, item.x1, item.y1, item.x2 += dx, item.y2 += dy] : [item.x += dx, item.y += dy];
replacePathSeg(this.type, this.index, curPts);
if (this.next && this.next.ctrlpts) {
var next = this.next.item;
var nextPts = [next.x, next.y, next.x1 += dx, next.y1 += dy, next.x2, next.y2];
replacePathSeg(this.next.type, this.next.index, nextPts);
}
if (this.mate) {
// The last point of a closed subpath has a 'mate',
// which is the 'M' segment of the subpath
var _item = this.mate.item;
var pts = [_item.x += dx, _item.y += dy];
replacePathSeg(this.mate.type, this.mate.index, pts);
// Has no grip, so does not need 'updating'?
}
this.update(true);
if (this.next) {
this.next.update(true);
}
}
}, {
key: 'setLinked',
value: function setLinked(num) {
var seg = void 0,
anum = void 0,
pt = void 0;
if (num === 2) {
anum = 1;
seg = this.next;
if (!seg) {
return;
}
pt = this.item;
} else {
anum = 2;
seg = this.prev;
if (!seg) {
return;
}
pt = seg.item;
}
var _seg = seg,
item = _seg.item;
item['x' + anum] = pt.x + (pt.x - this.item['x' + num]);
item['y' + anum] = pt.y + (pt.y - this.item['y' + num]);
var pts = [item.x, item.y, item.x1, item.y1, item.x2, item.y2];
replacePathSeg(seg.type, seg.index, pts);
seg.update(true);
}
}, {
key: 'moveCtrl',
value: function moveCtrl(num, dx, dy) {
var item = this.item;
item['x' + num] += dx;
item['y' + num] += dy;
var pts = [item.x, item.y, item.x1, item.y1, item.x2, item.y2];
replacePathSeg(this.type, this.index, pts);
this.update(true);
}
}, {
key: 'setType',
value: function setType(newType, pts) {
replacePathSeg(newType, this.index, pts);
this.type = newType;
this.item = path.elem.pathSegList.getItem(this.index);
this.showCtrlPts(newType === 6);
this.ctrlpts = getControlPoints(this);
this.update(true);
}
}]);
return Segment;
}();
var Path = function () {
function Path(elem) {
classCallCheck(this, Path);
if (!elem || elem.tagName !== 'path') {
throw new Error('svgedit.path.Path constructed without a element');
}
this.elem = elem;
this.segs = [];
this.selected_pts = [];
path = this;
this.init();
}
// Reset path data
createClass(Path, [{
key: 'init',
value: function init$$1() {
// Hide all grips, etc
// fixed, needed to work on all found elements, not just first
$$1(getGripContainer()).find('*').each(function () {
$$1(this).attr('display', 'none');
});
var segList = this.elem.pathSegList;
var len = segList.numberOfItems;
this.segs = [];
this.selected_pts = [];
this.first_seg = null;
// Set up segs array
for (var i = 0; i < len; i++) {
var item = segList.getItem(i);
var segment = new Segment(i, item);
segment.path = this;
this.segs.push(segment);
}
var segs = this.segs;
var startI = null;
for (var _i3 = 0; _i3 < len; _i3++) {
var seg = segs[_i3];
var nextSeg = _i3 + 1 >= len ? null : segs[_i3 + 1];
var prevSeg = _i3 - 1 < 0 ? null : segs[_i3 - 1];
if (seg.type === 2) {
if (prevSeg && prevSeg.type !== 1) {
// New sub-path, last one is open,
// so add a grip to last sub-path's first point
var startSeg = segs[startI];
startSeg.next = segs[startI + 1];
startSeg.next.prev = startSeg;
startSeg.addGrip();
}
// Remember that this is a starter seg
startI = _i3;
} else if (nextSeg && nextSeg.type === 1) {
// This is the last real segment of a closed sub-path
// Next is first seg after "M"
seg.next = segs[startI + 1];
// First seg after "M"'s prev is this
seg.next.prev = seg;
seg.mate = segs[startI];
seg.addGrip();
if (this.first_seg == null) {
this.first_seg = seg;
}
} else if (!nextSeg) {
if (seg.type !== 1) {
// Last seg, doesn't close so add a grip
// to last sub-path's first point
var _startSeg = segs[startI];
_startSeg.next = segs[startI + 1];
_startSeg.next.prev = _startSeg;
_startSeg.addGrip();
seg.addGrip();
if (!this.first_seg) {
// Open path, so set first as real first and add grip
this.first_seg = segs[startI];
}
}
} else if (seg.type !== 1) {
// Regular segment, so add grip and its "next"
seg.addGrip();
// Don't set its "next" if it's an "M"
if (nextSeg && nextSeg.type !== 2) {
seg.next = nextSeg;
seg.next.prev = seg;
}
}
}
return this;
}
}, {
key: 'eachSeg',
value: function eachSeg(fn) {
var len = this.segs.length;
for (var i = 0; i < len; i++) {
var ret = fn.call(this.segs[i], i);
if (ret === false) {
break;
}
}
}
}, {
key: 'addSeg',
value: function addSeg(index) {
// Adds a new segment
var seg = this.segs[index];
if (!seg.prev) {
return;
}
var prev = seg.prev;
var newseg = void 0,
newX = void 0,
newY = void 0;
switch (seg.item.pathSegType) {
case 4:
{
newX = (seg.item.x + prev.item.x) / 2;
newY = (seg.item.y + prev.item.y) / 2;
newseg = this.elem.createSVGPathSegLinetoAbs(newX, newY);
break;
}case 6:
{
// make it a curved segment to preserve the shape (WRS)
// https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm#Geometric_interpretation
var p0x = (prev.item.x + seg.item.x1) / 2;
var p1x = (seg.item.x1 + seg.item.x2) / 2;
var p2x = (seg.item.x2 + seg.item.x) / 2;
var p01x = (p0x + p1x) / 2;
var p12x = (p1x + p2x) / 2;
newX = (p01x + p12x) / 2;
var p0y = (prev.item.y + seg.item.y1) / 2;
var p1y = (seg.item.y1 + seg.item.y2) / 2;
var p2y = (seg.item.y2 + seg.item.y) / 2;
var p01y = (p0y + p1y) / 2;
var p12y = (p1y + p2y) / 2;
newY = (p01y + p12y) / 2;
newseg = this.elem.createSVGPathSegCurvetoCubicAbs(newX, newY, p0x, p0y, p01x, p01y);
var pts = [seg.item.x, seg.item.y, p12x, p12y, p2x, p2y];
replacePathSeg(seg.type, index, pts);
break;
}
}
insertItemBefore(this.elem, newseg, index);
}
}, {
key: 'deleteSeg',
value: function deleteSeg(index) {
var seg = this.segs[index];
var list = this.elem.pathSegList;
seg.show(false);
var next = seg.next;
if (seg.mate) {
// Make the next point be the "M" point
var pt = [next.item.x, next.item.y];
replacePathSeg(2, next.index, pt);
// Reposition last node
replacePathSeg(4, seg.index, pt);
list.removeItem(seg.mate.index);
} else if (!seg.prev) {
// First node of open path, make next point the M
// const {item} = seg;
var _pt2 = [next.item.x, next.item.y];
replacePathSeg(2, seg.next.index, _pt2);
list.removeItem(index);
} else {
list.removeItem(index);
}
}
}, {
key: 'subpathIsClosed',
value: function subpathIsClosed(index) {
var closed = false;
// Check if subpath is already open
path.eachSeg(function (i) {
if (i <= index) {
return true;
}
if (this.type === 2) {
// Found M first, so open
return false;
}
if (this.type === 1) {
// Found Z first, so closed
closed = true;
return false;
}
});
return closed;
}
}, {
key: 'removePtFromSelection',
value: function removePtFromSelection(index) {
var pos = this.selected_pts.indexOf(index);
if (pos === -1) {
return;
}
this.segs[index].select(false);
this.selected_pts.splice(pos, 1);
}
}, {
key: 'clearSelection',
value: function clearSelection() {
this.eachSeg(function () {
// 'this' is the segment here
this.select(false);
});
this.selected_pts = [];
}
}, {
key: 'storeD',
value: function storeD() {
this.last_d = this.elem.getAttribute('d');
}
}, {
key: 'show',
value: function show(y) {
// Shows this path's segment grips
this.eachSeg(function () {
// 'this' is the segment here
this.show(y);
});
if (y) {
this.selectPt(this.first_seg.index);
}
return this;
}
// Move selected points
}, {
key: 'movePts',
value: function movePts(dx, dy) {
var i = this.selected_pts.length;
while (i--) {
var seg = this.segs[this.selected_pts[i]];
seg.move(dx, dy);
}
}
}, {
key: 'moveCtrl',
value: function moveCtrl(dx, dy) {
var seg = this.segs[this.selected_pts[0]];
seg.moveCtrl(this.dragctrl, dx, dy);
if (linkControlPts) {
seg.setLinked(this.dragctrl);
}
}
}, {
key: 'setSegType',
value: function setSegType(newType) {
this.storeD();
var i = this.selected_pts.length;
var text = void 0;
while (i--) {
var selPt = this.selected_pts[i];
// Selected seg
var cur = this.segs[selPt];
var prev = cur.prev;
if (!prev) {
continue;
}
if (!newType) {
// double-click, so just toggle
text = 'Toggle Path Segment Type';
// Toggle segment to curve/straight line
var oldType = cur.type;
newType = oldType === 6 ? 4 : 6;
}
newType = Number(newType);
var curX = cur.item.x;
var curY = cur.item.y;
var prevX = prev.item.x;
var prevY = prev.item.y;
var points = void 0;
switch (newType) {
case 6:
{
if (cur.olditem) {
var old = cur.olditem;
points = [curX, curY, old.x1, old.y1, old.x2, old.y2];
} else {
var diffX = curX - prevX;
var diffY = curY - prevY;
// get control points from straight line segment
/*
const ct1x = (prevX + (diffY/2));
const ct1y = (prevY - (diffX/2));
const ct2x = (curX + (diffY/2));
const ct2y = (curY - (diffX/2));
*/
// create control points on the line to preserve the shape (WRS)
var ct1x = prevX + diffX / 3;
var ct1y = prevY + diffY / 3;
var ct2x = curX - diffX / 3;
var ct2y = curY - diffY / 3;
points = [curX, curY, ct1x, ct1y, ct2x, ct2y];
}
break;
}case 4:
{
points = [curX, curY];
// Store original prevve segment nums
cur.olditem = cur.item;
break;
}
}
cur.setType(newType, points);
}
path.endChanges(text);
}
}, {
key: 'selectPt',
value: function selectPt(pt, ctrlNum) {
this.clearSelection();
if (pt == null) {
this.eachSeg(function (i) {
// 'this' is the segment here.
if (this.prev) {
pt = i;
}
});
}
this.addPtsToSelection(pt);
if (ctrlNum) {
this.dragctrl = ctrlNum;
if (linkControlPts) {
this.segs[pt].setLinked(ctrlNum);
}
}
}
// Update position of all points
}, {
key: 'update',
value: function update() {
var elem = this.elem;
if (getRotationAngle(elem)) {
this.matrix = getMatrix(elem);
this.imatrix = this.matrix.inverse();
} else {
this.matrix = null;
this.imatrix = null;
}
this.eachSeg(function (i) {
this.item = elem.pathSegList.getItem(i);
this.update();
});
return this;
}
}, {
key: 'endChanges',
value: function endChanges(text) {
if (isWebkit()) {
editorContext_.resetD(this.elem);
}
var cmd = new ChangeElementCommand(this.elem, { d: this.last_d }, text);
editorContext_.endChanges({ cmd: cmd, elem: this.elem });
}
}, {
key: 'addPtsToSelection',
value: function addPtsToSelection(indexes) {
if (!Array.isArray(indexes)) {
indexes = [indexes];
}
for (var _i4 = 0; _i4 < indexes.length; _i4++) {
var index = indexes[_i4];
var seg = this.segs[index];
if (seg.ptgrip) {
if (!this.selected_pts.includes(index) && index >= 0) {
this.selected_pts.push(index);
}
}
}
this.selected_pts.sort();
var i = this.selected_pts.length;
var grips = [];
grips.length = i;
// Loop through points to be selected and highlight each
while (i--) {
var pt = this.selected_pts[i];
var _seg2 = this.segs[pt];
_seg2.select(true);
grips[i] = _seg2.ptgrip;
}
var closedSubpath = this.subpathIsClosed(this.selected_pts[0]);
editorContext_.addPtsToSelection({ grips: grips, closedSubpath: closedSubpath });
}
}]);
return Path;
}();
var getPath_ = function getPath_(elem) {
var p = pathData[elem.id];
if (!p) {
p = pathData[elem.id] = new Path(elem);
}
return p;
};
var removePath_ = function removePath_(id) {
if (id in pathData) {
delete pathData[id];
}
};
var newcx = void 0,
newcy = void 0,
oldcx = void 0,
oldcy = void 0,
angle = void 0;
var getRotVals = function getRotVals(x, y) {
var dx = x - oldcx;
var dy = y - oldcy;
// rotate the point around the old center
var r = Math.sqrt(dx * dx + dy * dy);
var theta = Math.atan2(dy, dx) + angle;
dx = r * Math.cos(theta) + oldcx;
dy = r * Math.sin(theta) + oldcy;
// dx,dy should now hold the actual coordinates of each
// point after being rotated
// now we want to rotate them around the new center in the reverse direction
dx -= newcx;
dy -= newcy;
r = Math.sqrt(dx * dx + dy * dy);
theta = Math.atan2(dy, dx) - angle;
return { x: r * Math.cos(theta) + newcx,
y: r * Math.sin(theta) + newcy };
};
// If the path was rotated, we must now pay the piper:
// Every path point must be rotated into the rotated coordinate system of
// its old center, then determine the new center, then rotate it back
// This is because we want the path to remember its rotation
// TODO: This is still using ye olde transform methods, can probably
// be optimized or even taken care of by `recalculateDimensions`
var recalcRotatedPath = function recalcRotatedPath() {
var currentPath = path.elem;
angle = getRotationAngle(currentPath, true);
if (!angle) {
return;
}
// selectedBBoxes[0] = path.oldbbox;
var oldbox = path.oldbbox; // selectedBBoxes[0],
oldcx = oldbox.x + oldbox.width / 2;
oldcy = oldbox.y + oldbox.height / 2;
var box = getBBox(currentPath);
newcx = box.x + box.width / 2;
newcy = box.y + box.height / 2;
// un-rotate the new center to the proper position
var dx = newcx - oldcx,
dy = newcy - oldcy,
r = Math.sqrt(dx * dx + dy * dy),
theta = Math.atan2(dy, dx) + angle;
newcx = r * Math.cos(theta) + oldcx;
newcy = r * Math.sin(theta) + oldcy;
var list = currentPath.pathSegList;
var i = list.numberOfItems;
while (i) {
i -= 1;
var seg = list.getItem(i),
type = seg.pathSegType;
if (type === 1) {
continue;
}
var rvals = getRotVals(seg.x, seg.y),
points = [rvals.x, rvals.y];
if (seg.x1 != null && seg.x2 != null) {
var cVals1 = getRotVals(seg.x1, seg.y1);
var cVals2 = getRotVals(seg.x2, seg.y2);
points.splice(points.length, 0, cVals1.x, cVals1.y, cVals2.x, cVals2.y);
}
replacePathSeg(type, i, points);
} // loop for each point
box = getBBox(currentPath);
// selectedBBoxes[0].x = box.x; selectedBBoxes[0].y = box.y;
// selectedBBoxes[0].width = box.width; selectedBBoxes[0].height = box.height;
// now we must set the new transform to be rotated around the new center
var Rnc = editorContext_.getSVGRoot().createSVGTransform(),
tlist = getTransformList(currentPath);
Rnc.setRotate(angle * 180.0 / Math.PI, newcx, newcy);
tlist.replaceItem(Rnc, 0);
};
// ====================================
// Public API starts here
var clearData = function clearData() {
pathData = {};
};
// Making public for mocking
var reorientGrads = function reorientGrads(elem, m) {
var bb = getBBox(elem);
for (var i = 0; i < 2; i++) {
var type = i === 0 ? 'fill' : 'stroke';
var attrVal = elem.getAttribute(type);
if (attrVal && attrVal.startsWith('url(')) {
var grad = getRefElem(attrVal);
if (grad.tagName === 'linearGradient') {
var x1 = grad.getAttribute('x1') || 0;
var y1 = grad.getAttribute('y1') || 0;
var x2 = grad.getAttribute('x2') || 1;
var y2 = grad.getAttribute('y2') || 0;
// Convert to USOU points
x1 = bb.width * x1 + bb.x;
y1 = bb.height * y1 + bb.y;
x2 = bb.width * x2 + bb.x;
y2 = bb.height * y2 + bb.y;
// Transform those points
var pt1 = transformPoint(x1, y1, m);
var pt2 = transformPoint(x2, y2, m);
// Convert back to BB points
var gCoords = {};
gCoords.x1 = (pt1.x - bb.x) / bb.width;
gCoords.y1 = (pt1.y - bb.y) / bb.height;
gCoords.x2 = (pt2.x - bb.x) / bb.width;
gCoords.y2 = (pt2.y - bb.y) / bb.height;
var newgrad = grad.cloneNode(true);
$$1(newgrad).attr(gCoords);
newgrad.id = editorContext_.getNextId();
findDefs().append(newgrad);
elem.setAttribute(type, 'url(#' + newgrad.id + ')');
}
}
}
};
// this is how we map paths to our preferred relative segment types
var pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', 'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
/**
* TODO: move to pathActions.js
* Convert a path to one with only absolute or relative values
* @param {Object} path - the path to convert
* @param {boolean} toRel - true of convert to relative
* @returns {string}
*/
var convertPath = function convertPath(path, toRel) {
var segList = path.pathSegList;
var len = segList.numberOfItems;
var curx = 0,
cury = 0;
var d = '';
var lastM = null;
for (var i = 0; i < len; ++i) {
var seg = segList.getItem(i);
// if these properties are not in the segment, set them to zero
var x = seg.x || 0,
y = seg.y || 0,
x1 = seg.x1 || 0,
y1 = seg.y1 || 0,
x2 = seg.x2 || 0,
y2 = seg.y2 || 0;
var type = seg.pathSegType;
var letter = pathMap[type]['to' + (toRel ? 'Lower' : 'Upper') + 'Case']();
switch (type) {
case 1:
// z,Z closepath (Z/z)
d += 'z';
if (lastM && !toRel) {
curx = lastM[0];
cury = lastM[1];
}
break;
case 12:
// absolute horizontal line (H)
x -= curx;
// Fallthrough
case 13:
// relative horizontal line (h)
if (toRel) {
curx += x;
letter = 'l';
} else {
x += curx;
curx = x;
letter = 'L';
}
// Convert to "line" for easier editing
d += pathDSegment(letter, [[x, cury]]);
break;
case 14:
// absolute vertical line (V)
y -= cury;
// Fallthrough
case 15:
// relative vertical line (v)
if (toRel) {
cury += y;
letter = 'l';
} else {
y += cury;
cury = y;
letter = 'L';
}
// Convert to "line" for easier editing
d += pathDSegment(letter, [[curx, y]]);
break;
case 2: // absolute move (M)
case 4: // absolute line (L)
case 18:
// absolute smooth quad (T)
x -= curx;
y -= cury;
// Fallthrough
case 5: // relative line (l)
case 3: // relative move (m)
case 19:
// relative smooth quad (t)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;
y += cury;
curx = x;
cury = y;
}
if (type === 2 || type === 3) {
lastM = [curx, cury];
}
d += pathDSegment(letter, [[x, y]]);
break;
case 6:
// absolute cubic (C)
x -= curx;x1 -= curx;x2 -= curx;
y -= cury;y1 -= cury;y2 -= cury;
// Fallthrough
case 7:
// relative cubic (c)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;x1 += curx;x2 += curx;
y += cury;y1 += cury;y2 += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [[x1, y1], [x2, y2], [x, y]]);
break;
case 8:
// absolute quad (Q)
x -= curx;x1 -= curx;
y -= cury;y1 -= cury;
// Fallthrough
case 9:
// relative quad (q)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;x1 += curx;
y += cury;y1 += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [[x1, y1], [x, y]]);
break;
case 10:
// absolute elliptical arc (A)
x -= curx;
y -= cury;
// Fallthrough
case 11:
// relative elliptical arc (a)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;
y += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [[seg.r1, seg.r2]], [seg.angle, seg.largeArcFlag ? 1 : 0, seg.sweepFlag ? 1 : 0], [x, y]);
break;
case 16:
// absolute smooth cubic (S)
x -= curx;x2 -= curx;
y -= cury;y2 -= cury;
// Fallthrough
case 17:
// relative smooth cubic (s)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;x2 += curx;
y += cury;y2 += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [[x2, y2], [x, y]]);
break;
} // switch on path segment type
} // for each segment
return d;
};
/**
* TODO: refactor callers in convertPath to use getPathDFromSegments instead of this function.
* Legacy code refactored from svgcanvas.pathActions.convertPath
* @param letter - path segment command
* @param {Array.>} points - x,y points.
* @param {Array.>=} morePoints - x,y points
* @param {Array.=}lastPoint - x,y point
* @returns {string}
*/
function pathDSegment(letter, points, morePoints, lastPoint) {
$$1.each(points, function (i, pnt) {
points[i] = shortFloat(pnt);
});
var segment = letter + points.join(' ');
if (morePoints) {
segment += ' ' + morePoints.join(' ');
}
if (lastPoint) {
segment += ' ' + shortFloat(lastPoint);
}
return segment;
}
/**
* Group: Path edit functions
* Functions relating to editing path elements
*/
var pathActions = function () {
var subpath = false;
var newPoint = void 0,
firstCtrl = void 0;
var currentPath = null;
var hasMoved = false;
// No `editorContext_` yet but should be ok as is `null` by default
// editorContext_.setDrawnPath(null);
// This function converts a polyline (created by the fh_path tool) into
// a path element and coverts every three line segments into a single bezier
// curve in an attempt to smooth out the free-hand
var smoothPolylineIntoPath = function smoothPolylineIntoPath(element) {
var i = void 0;
var _element = element,
points = _element.points;
var N = points.numberOfItems;
if (N >= 4) {
// loop through every 3 points and convert to a cubic bezier curve segment
//
// NOTE: this is cheating, it means that every 3 points has the potential to
// be a corner instead of treating each point in an equal manner. In general,
// this technique does not look that good.
//
// I am open to better ideas!
//
// Reading:
// - http://www.efg2.com/Lab/Graphics/Jean-YvesQueinecBezierCurves.htm
// - https://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
// - https://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
// - https://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
var curpos = points.getItem(0),
prevCtlPt = null;
var d = [];
d.push(['M', curpos.x, ',', curpos.y, ' C'].join(''));
for (i = 1; i <= N - 4; i += 3) {
var ct1 = points.getItem(i);
var ct2 = points.getItem(i + 1);
var end = points.getItem(i + 2);
// if the previous segment had a control point, we want to smooth out
// the control points on both sides
if (prevCtlPt) {
var newpts = smoothControlPoints(prevCtlPt, ct1, curpos);
if (newpts && newpts.length === 2) {
var prevArr = d[d.length - 1].split(',');
prevArr[2] = newpts[0].x;
prevArr[3] = newpts[0].y;
d[d.length - 1] = prevArr.join(',');
ct1 = newpts[1];
}
}
d.push([ct1.x, ct1.y, ct2.x, ct2.y, end.x, end.y].join(','));
curpos = end;
prevCtlPt = ct2;
}
// handle remaining line segments
d.push('L');
while (i < N) {
var pt = points.getItem(i);
d.push([pt.x, pt.y].join(','));
i++;
}
d = d.join(' ');
// create new path element
element = editorContext_.addSvgElementFromJson({
element: 'path',
curStyles: true,
attr: {
id: editorContext_.getId(),
d: d,
fill: 'none'
}
});
// No need to call "changed", as this is already done under mouseUp
}
return element;
};
return {
mouseDown: function mouseDown(evt, mouseTarget, startX, startY) {
var id = void 0;
if (editorContext_.getCurrentMode() === 'path') {
var mouseX = startX; // Was this meant to work with the other `mouseX`? (was defined globally so adding `let` to at least avoid a global)
var mouseY = startY; // Was this meant to work with the other `mouseY`? (was defined globally so adding `let` to at least avoid a global)
var currentZoom = editorContext_.getCurrentZoom();
var x = mouseX / currentZoom,
y = mouseY / currentZoom,
stretchy = getElem('path_stretch_line');
newPoint = [x, y];
if (editorContext_.getGridSnapping()) {
x = snapToGrid(x);
y = snapToGrid(y);
mouseX = snapToGrid(mouseX);
mouseY = snapToGrid(mouseY);
}
if (!stretchy) {
stretchy = document.createElementNS(NS.SVG, 'path');
assignAttributes(stretchy, {
id: 'path_stretch_line',
stroke: '#22C',
'stroke-width': '0.5',
fill: 'none'
});
stretchy = getElem('selectorParentGroup').appendChild(stretchy);
}
stretchy.setAttribute('display', 'inline');
var keep = null;
var index = void 0;
// if pts array is empty, create path element with M at current point
var drawnPath = editorContext_.getDrawnPath();
if (!drawnPath) {
var dAttr = 'M' + x + ',' + y + ' '; // Was this meant to work with the other `dAttr`? (was defined globally so adding `var` to at least avoid a global)
drawnPath = editorContext_.setDrawnPath(editorContext_.addSvgElementFromJson({
element: 'path',
curStyles: true,
attr: {
d: dAttr,
id: editorContext_.getNextId(),
opacity: editorContext_.getOpacity() / 2
}
}));
// set stretchy line to first point
stretchy.setAttribute('d', ['M', mouseX, mouseY, mouseX, mouseY].join(' '));
index = subpath ? path.segs.length : 0;
addPointGrip(index, mouseX, mouseY);
} else {
// determine if we clicked on an existing point
var seglist = drawnPath.pathSegList;
var i = seglist.numberOfItems;
var FUZZ = 6 / currentZoom;
var clickOnPoint = false;
while (i) {
i--;
var item = seglist.getItem(i);
var px = item.x,
py = item.y;
// found a matching point
if (x >= px - FUZZ && x <= px + FUZZ && y >= py - FUZZ && y <= py + FUZZ) {
clickOnPoint = true;
break;
}
}
// get path element that we are in the process of creating
id = editorContext_.getId();
// Remove previous path object if previously created
removePath_(id);
var newpath = getElem(id);
var newseg = void 0;
var sSeg = void 0;
var len = seglist.numberOfItems;
// if we clicked on an existing point, then we are done this path, commit it
// (i, i+1) are the x,y that were clicked on
if (clickOnPoint) {
// if clicked on any other point but the first OR
// the first point was clicked on and there are less than 3 points
// then leave the path open
// otherwise, close the path
if (i <= 1 && len >= 2) {
// Create end segment
var absX = seglist.getItem(0).x;
var absY = seglist.getItem(0).y;
sSeg = stretchy.pathSegList.getItem(1);
if (sSeg.pathSegType === 4) {
newseg = drawnPath.createSVGPathSegLinetoAbs(absX, absY);
} else {
newseg = drawnPath.createSVGPathSegCurvetoCubicAbs(absX, absY, sSeg.x1 / currentZoom, sSeg.y1 / currentZoom, absX, absY);
}
var endseg = drawnPath.createSVGPathSegClosePath();
seglist.appendItem(newseg);
seglist.appendItem(endseg);
} else if (len < 3) {
keep = false;
return keep;
}
$$1(stretchy).remove();
// This will signal to commit the path
// const element = newpath; // Other event handlers define own `element`, so this was probably not meant to interact with them or one which shares state (as there were none); I therefore adding a missing `var` to avoid a global
drawnPath = editorContext_.setDrawnPath(null);
editorContext_.setStarted(false);
if (subpath) {
if (path.matrix) {
editorContext_.remapElement(newpath, {}, path.matrix.inverse());
}
var newD = newpath.getAttribute('d');
var origD = $$1(path.elem).attr('d');
$$1(path.elem).attr('d', origD + newD);
$$1(newpath).remove();
if (path.matrix) {
recalcRotatedPath();
}
init$1();
pathActions.toEditMode(path.elem);
path.selectPt();
return false;
}
// else, create a new point, update path element
} else {
// Checks if current target or parents are #svgcontent
if (!$$1.contains(editorContext_.getContainer(), editorContext_.getMouseTarget(evt))) {
// Clicked outside canvas, so don't make point
console.log('Clicked outside canvas');
return false;
}
var num = drawnPath.pathSegList.numberOfItems;
var last = drawnPath.pathSegList.getItem(num - 1);
var lastx = last.x,
lasty = last.y;
if (evt.shiftKey) {
var xya = snapToAngle(lastx, lasty, x, y);
x = xya.x;
y = xya.y;
}
// Use the segment defined by stretchy
sSeg = stretchy.pathSegList.getItem(1);
if (sSeg.pathSegType === 4) {
newseg = drawnPath.createSVGPathSegLinetoAbs(editorContext_.round(x), editorContext_.round(y));
} else {
newseg = drawnPath.createSVGPathSegCurvetoCubicAbs(editorContext_.round(x), editorContext_.round(y), sSeg.x1 / currentZoom, sSeg.y1 / currentZoom, sSeg.x2 / currentZoom, sSeg.y2 / currentZoom);
}
drawnPath.pathSegList.appendItem(newseg);
x *= currentZoom;
y *= currentZoom;
// set stretchy line to latest point
stretchy.setAttribute('d', ['M', x, y, x, y].join(' '));
index = num;
if (subpath) {
index += path.segs.length;
}
addPointGrip(index, x, y);
}
// keep = true;
}
return;
}
// TODO: Make sure currentPath isn't null at this point
if (!path) {
return;
}
path.storeD();
id = evt.target.id;
var curPt = void 0;
if (id.substr(0, 14) === 'pathpointgrip_') {
// Select this point
curPt = path.cur_pt = parseInt(id.substr(14), 10);
path.dragging = [startX, startY];
var seg = path.segs[curPt];
// only clear selection if shift is not pressed (otherwise, add
// node to selection)
if (!evt.shiftKey) {
if (path.selected_pts.length <= 1 || !seg.selected) {
path.clearSelection();
}
path.addPtsToSelection(curPt);
} else if (seg.selected) {
path.removePtFromSelection(curPt);
} else {
path.addPtsToSelection(curPt);
}
} else if (id.startsWith('ctrlpointgrip_')) {
path.dragging = [startX, startY];
var parts = id.split('_')[1].split('c');
curPt = Number(parts[0]);
var ctrlNum = Number(parts[1]);
path.selectPt(curPt, ctrlNum);
}
// Start selection box
if (!path.dragging) {
var rubberBox = editorContext_.getRubberBox();
if (rubberBox == null) {
rubberBox = editorContext_.setRubberBox(editorContext_.selectorManager.getRubberBandBox());
}
var _currentZoom = editorContext_.getCurrentZoom();
assignAttributes(rubberBox, {
x: startX * _currentZoom,
y: startY * _currentZoom,
width: 0,
height: 0,
display: 'inline'
}, 100);
}
},
mouseMove: function mouseMove(mouseX, mouseY) {
var currentZoom = editorContext_.getCurrentZoom();
hasMoved = true;
var drawnPath = editorContext_.getDrawnPath();
if (editorContext_.getCurrentMode() === 'path') {
if (!drawnPath) {
return;
}
var seglist = drawnPath.pathSegList;
var index = seglist.numberOfItems - 1;
if (newPoint) {
// First point
// if (!index) { return; }
// Set control points
var pointGrip1 = addCtrlGrip('1c1');
var pointGrip2 = addCtrlGrip('0c2');
// dragging pointGrip1
pointGrip1.setAttribute('cx', mouseX);
pointGrip1.setAttribute('cy', mouseY);
pointGrip1.setAttribute('display', 'inline');
var ptX = newPoint[0];
var ptY = newPoint[1];
// set curve
// const seg = seglist.getItem(index);
var curX = mouseX / currentZoom;
var curY = mouseY / currentZoom;
var altX = ptX + (ptX - curX);
var altY = ptY + (ptY - curY);
pointGrip2.setAttribute('cx', altX * currentZoom);
pointGrip2.setAttribute('cy', altY * currentZoom);
pointGrip2.setAttribute('display', 'inline');
var ctrlLine = getCtrlLine(1);
assignAttributes(ctrlLine, {
x1: mouseX,
y1: mouseY,
x2: altX * currentZoom,
y2: altY * currentZoom,
display: 'inline'
});
if (index === 0) {
firstCtrl = [mouseX, mouseY];
} else {
var last = seglist.getItem(index - 1);
var lastX = last.x;
var lastY = last.y;
if (last.pathSegType === 6) {
lastX += lastX - last.x2;
lastY += lastY - last.y2;
} else if (firstCtrl) {
lastX = firstCtrl[0] / currentZoom;
lastY = firstCtrl[1] / currentZoom;
}
replacePathSeg(6, index, [ptX, ptY, lastX, lastY, altX, altY], drawnPath);
}
} else {
var stretchy = getElem('path_stretch_line');
if (stretchy) {
var prev = seglist.getItem(index);
if (prev.pathSegType === 6) {
var prevX = prev.x + (prev.x - prev.x2);
var prevY = prev.y + (prev.y - prev.y2);
replacePathSeg(6, 1, [mouseX, mouseY, prevX * currentZoom, prevY * currentZoom, mouseX, mouseY], stretchy);
} else if (firstCtrl) {
replacePathSeg(6, 1, [mouseX, mouseY, firstCtrl[0], firstCtrl[1], mouseX, mouseY], stretchy);
} else {
replacePathSeg(4, 1, [mouseX, mouseY], stretchy);
}
}
}
return;
}
// if we are dragging a point, let's move it
if (path.dragging) {
var pt = getPointFromGrip({
x: path.dragging[0],
y: path.dragging[1]
}, path);
var mpt = getPointFromGrip({
x: mouseX,
y: mouseY
}, path);
var diffX = mpt.x - pt.x;
var diffY = mpt.y - pt.y;
path.dragging = [mouseX, mouseY];
if (path.dragctrl) {
path.moveCtrl(diffX, diffY);
} else {
path.movePts(diffX, diffY);
}
} else {
path.selected_pts = [];
path.eachSeg(function (i) {
var seg = this;
if (!seg.next && !seg.prev) {
return;
}
// const {item} = seg;
var rubberBox = editorContext_.getRubberBox();
var rbb = rubberBox.getBBox();
var pt = getGripPt(seg);
var ptBb = {
x: pt.x,
y: pt.y,
width: 0,
height: 0
};
var sel = rectsIntersect(rbb, ptBb);
this.select(sel);
// Note that addPtsToSelection is not being run
if (sel) {
path.selected_pts.push(seg.index);
}
});
}
},
mouseUp: function mouseUp(evt, element, mouseX, mouseY) {
var drawnPath = editorContext_.getDrawnPath();
// Create mode
if (editorContext_.getCurrentMode() === 'path') {
newPoint = null;
if (!drawnPath) {
element = getElem(editorContext_.getId());
editorContext_.setStarted(false);
firstCtrl = null;
}
return {
keep: true,
element: element
};
}
// Edit mode
var rubberBox = editorContext_.getRubberBox();
if (path.dragging) {
var lastPt = path.cur_pt;
path.dragging = false;
path.dragctrl = false;
path.update();
if (hasMoved) {
path.endChanges('Move path point(s)');
}
if (!evt.shiftKey && !hasMoved) {
path.selectPt(lastPt);
}
} else if (rubberBox && rubberBox.getAttribute('display') !== 'none') {
// Done with multi-node-select
rubberBox.setAttribute('display', 'none');
if (rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) {
pathActions.toSelectMode(evt.target);
}
// else, move back to select mode
} else {
pathActions.toSelectMode(evt.target);
}
hasMoved = false;
},
toEditMode: function toEditMode(element) {
path = getPath_(element);
editorContext_.setCurrentMode('pathedit');
editorContext_.clearSelection();
path.show(true).update();
path.oldbbox = getBBox(path.elem);
subpath = false;
},
toSelectMode: function toSelectMode(elem) {
var selPath = elem === path.elem;
editorContext_.setCurrentMode('select');
path.show(false);
currentPath = false;
editorContext_.clearSelection();
if (path.matrix) {
// Rotated, so may need to re-calculate the center
recalcRotatedPath();
}
if (selPath) {
editorContext_.call('selected', [elem]);
editorContext_.addToSelection([elem], true);
}
},
addSubPath: function addSubPath(on) {
if (on) {
// Internally we go into "path" mode, but in the UI it will
// still appear as if in "pathedit" mode.
editorContext_.setCurrentMode('path');
subpath = true;
} else {
pathActions.clear(true);
pathActions.toEditMode(path.elem);
}
},
select: function select(target) {
if (currentPath === target) {
pathActions.toEditMode(target);
editorContext_.setCurrentMode('pathedit');
// going into pathedit mode
} else {
currentPath = target;
}
},
reorient: function reorient() {
var elem = editorContext_.getSelectedElements()[0];
if (!elem) {
return;
}
var angle = getRotationAngle(elem);
if (angle === 0) {
return;
}
var batchCmd = new BatchCommand('Reorient path');
var changes = {
d: elem.getAttribute('d'),
transform: elem.getAttribute('transform')
};
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
editorContext_.clearSelection();
this.resetOrientation(elem);
editorContext_.addCommandToHistory(batchCmd);
// Set matrix to null
getPath_(elem).show(false).matrix = null;
this.clear();
editorContext_.addToSelection([elem], true);
editorContext_.call('changed', editorContext_.getSelectedElements());
},
clear: function clear(remove) {
var drawnPath = editorContext_.getDrawnPath();
currentPath = null;
if (drawnPath) {
var elem = getElem(editorContext_.getId());
$$1(getElem('path_stretch_line')).remove();
$$1(elem).remove();
$$1(getElem('pathpointgrip_container')).find('*').attr('display', 'none');
firstCtrl = null;
editorContext_.setDrawnPath(null);
editorContext_.setStarted(false);
} else if (editorContext_.getCurrentMode() === 'pathedit') {
this.toSelectMode();
}
if (path) {
path.init().show(false);
}
},
resetOrientation: function resetOrientation(pth) {
if (pth == null || pth.nodeName !== 'path') {
return false;
}
var tlist = getTransformList(pth);
var m = transformListToTransform(tlist).matrix;
tlist.clear();
pth.removeAttribute('transform');
var segList = pth.pathSegList;
// Opera/win/non-EN throws an error here.
// TODO: Find out why!
// Presumed fixed in Opera 10.5, so commented out for now
// try {
var len = segList.numberOfItems;
// } catch(err) {
// const fixed_d = pathActions.convertPath(pth);
// pth.setAttribute('d', fixed_d);
// segList = pth.pathSegList;
// const len = segList.numberOfItems;
// }
// let lastX, lastY;
var _loop = function _loop(i) {
var seg = segList.getItem(i);
var type = seg.pathSegType;
if (type === 1) {
return 'continue';
}
var pts = [];
$$1.each(['', 1, 2], function (j, n) {
var x = seg['x' + n],
y = seg['y' + n];
if (x !== undefined && y !== undefined) {
var pt = transformPoint(x, y, m);
pts.splice(pts.length, 0, pt.x, pt.y);
}
});
replacePathSeg(type, i, pts, pth);
};
for (var i = 0; i < len; ++i) {
var _ret = _loop(i);
if (_ret === 'continue') continue;
}
reorientGrads(pth, m);
},
zoomChange: function zoomChange() {
if (editorContext_.getCurrentMode() === 'pathedit') {
path.update();
}
},
getNodePoint: function getNodePoint() {
var selPt = path.selected_pts.length ? path.selected_pts[0] : 1;
var seg = path.segs[selPt];
return {
x: seg.item.x,
y: seg.item.y,
type: seg.type
};
},
linkControlPoints: function linkControlPoints(linkPoints) {
setLinkControlPoints(linkPoints);
},
clonePathNode: function clonePathNode() {
path.storeD();
var selPts = path.selected_pts;
// const {segs} = path;
var i = selPts.length;
var nums = [];
while (i--) {
var pt = selPts[i];
path.addSeg(pt);
nums.push(pt + i);
nums.push(pt + i + 1);
}
path.init().addPtsToSelection(nums);
path.endChanges('Clone path node(s)');
},
opencloseSubPath: function opencloseSubPath() {
var selPts = path.selected_pts;
// Only allow one selected node for now
if (selPts.length !== 1) {
return;
}
var _path = path,
elem = _path.elem;
var list = elem.pathSegList;
// const len = list.numberOfItems;
var index = selPts[0];
var openPt = null;
var startItem = null;
// Check if subpath is already open
path.eachSeg(function (i) {
if (this.type === 2 && i <= index) {
startItem = this.item;
}
if (i <= index) {
return true;
}
if (this.type === 2) {
// Found M first, so open
openPt = i;
return false;
}
if (this.type === 1) {
// Found Z first, so closed
openPt = false;
return false;
}
});
if (openPt == null) {
// Single path, so close last seg
openPt = path.segs.length - 1;
}
if (openPt !== false) {
// Close this path
// Create a line going to the previous "M"
var newseg = elem.createSVGPathSegLinetoAbs(startItem.x, startItem.y);
var closer = elem.createSVGPathSegClosePath();
if (openPt === path.segs.length - 1) {
list.appendItem(newseg);
list.appendItem(closer);
} else {
insertItemBefore(elem, closer, openPt);
insertItemBefore(elem, newseg, openPt);
}
path.init().selectPt(openPt + 1);
return;
}
// M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
// M 2,2 L 3,3 L 1,1
// M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
// M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
var seg = path.segs[index];
if (seg.mate) {
list.removeItem(index); // Removes last "L"
list.removeItem(index); // Removes the "Z"
path.init().selectPt(index - 1);
return;
}
var lastM = void 0,
zSeg = void 0;
// Find this sub-path's closing point and remove
for (var i = 0; i < list.numberOfItems; i++) {
var item = list.getItem(i);
if (item.pathSegType === 2) {
// Find the preceding M
lastM = i;
} else if (i === index) {
// Remove it
list.removeItem(lastM);
// index--;
} else if (item.pathSegType === 1 && index < i) {
// Remove the closing seg of this subpath
zSeg = i - 1;
list.removeItem(i);
break;
}
}
var num = index - lastM - 1;
while (num--) {
insertItemBefore(elem, list.getItem(lastM), zSeg);
}
var pt = list.getItem(lastM);
// Make this point the new "M"
replacePathSeg(2, lastM, [pt.x, pt.y]);
// i = index; // i is local here, so has no effect; what was the intent for this?
path.init().selectPt(0);
},
deletePathNode: function deletePathNode() {
if (!pathActions.canDeleteNodes) {
return;
}
path.storeD();
var selPts = path.selected_pts;
var i = selPts.length;
while (i--) {
var pt = selPts[i];
path.deleteSeg(pt);
}
// Cleanup
var cleanup = function cleanup() {
var segList = path.elem.pathSegList;
var len = segList.numberOfItems;
var remItems = function remItems(pos, count) {
while (count--) {
segList.removeItem(pos);
}
};
if (len <= 1) {
return true;
}
while (len--) {
var item = segList.getItem(len);
if (item.pathSegType === 1) {
var prev = segList.getItem(len - 1);
var nprev = segList.getItem(len - 2);
if (prev.pathSegType === 2) {
remItems(len - 1, 2);
cleanup();
break;
} else if (nprev.pathSegType === 2) {
remItems(len - 2, 3);
cleanup();
break;
}
} else if (item.pathSegType === 2) {
if (len > 0) {
var prevType = segList.getItem(len - 1).pathSegType;
// Path has M M
if (prevType === 2) {
remItems(len - 1, 1);
cleanup();
break;
// Entire path ends with Z M
} else if (prevType === 1 && segList.numberOfItems - 1 === len) {
remItems(len, 1);
cleanup();
break;
}
}
}
}
return false;
};
cleanup();
// Completely delete a path with 1 or 0 segments
if (path.elem.pathSegList.numberOfItems <= 1) {
pathActions.toSelectMode(path.elem);
editorContext_.canvas.deleteSelectedElements();
return;
}
path.init();
path.clearSelection();
// TODO: Find right way to select point now
// path.selectPt(selPt);
if (window.opera) {
// Opera repaints incorrectly
var cp = $$1(path.elem);
cp.attr('d', cp.attr('d'));
}
path.endChanges('Delete path node(s)');
},
smoothPolylineIntoPath: smoothPolylineIntoPath,
setSegType: function setSegType(v) {
path.setSegType(v);
},
moveNode: function moveNode(attr, newValue) {
var selPts = path.selected_pts;
if (!selPts.length) {
return;
}
path.storeD();
// Get first selected point
var seg = path.segs[selPts[0]];
var diff = { x: 0, y: 0 };
diff[attr] = newValue - seg.item[attr];
seg.move(diff.x, diff.y);
path.endChanges('Move path point');
},
fixEnd: function fixEnd(elem) {
// Adds an extra segment if the last seg before a Z doesn't end
// at its M point
// M0,0 L0,100 L100,100 z
var segList = elem.pathSegList;
var len = segList.numberOfItems;
var lastM = void 0;
for (var i = 0; i < len; ++i) {
var item = segList.getItem(i);
if (item.pathSegType === 2) {
lastM = item;
}
if (item.pathSegType === 1) {
var prev = segList.getItem(i - 1);
if (prev.x !== lastM.x || prev.y !== lastM.y) {
// Add an L segment here
var newseg = elem.createSVGPathSegLinetoAbs(lastM.x, lastM.y);
insertItemBefore(elem, newseg, i);
// Can this be done better?
pathActions.fixEnd(elem);
break;
}
}
}
if (isWebkit()) {
editorContext_.resetD(elem);
}
},
// Convert a path to one with only absolute or relative values
convertPath: convertPath
};
}();
/* globals jQuery */
// Constants
var $$2 = jqPluginSVG(jQuery);
// String used to encode base64.
var KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
// Much faster than running getBBox() every time
var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
var visElemsArr = visElems.split(',');
// const hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
var editorContext_$1 = null;
var domdoc_ = null;
var domcontainer_ = null;
var svgroot_ = null;
var init$2 = function init$$1(editorContext) {
editorContext_$1 = editorContext;
domdoc_ = editorContext.getDOMDocument();
domcontainer_ = editorContext.getDOMContainer();
svgroot_ = editorContext.getSVGRoot();
};
/**
* Converts characters in a string to XML-friendly entities.
* @example: '&' becomes '&'
* @param str - The string to be converted
* @returns {String} The converted string
*/
var toXml = function toXml(str) {
// ' is ok in XML, but not HTML
// > does not normally need escaping, though it can if within a CDATA expression (and preceded by "]]")
return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/, ''');
};
// This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
// schiller: Removed string concatenation in favour of Array.join() optimization,
// also precalculate the size of the array needed.
// Converts a string to base64
var encode64 = function encode64(input) {
// base64 strings are 4/3 larger than the original string
input = encodeUTF8(input); // convert non-ASCII characters
// input = convertToXMLReferences(input);
if (window.btoa) {
return window.btoa(input); // Use native if available
}
var output = [];
output.length = Math.floor((input.length + 2) / 3) * 4;
var i = 0,
p = 0;
do {
var chr1 = input.charCodeAt(i++);
var chr2 = input.charCodeAt(i++);
var chr3 = input.charCodeAt(i++);
var enc1 = chr1 >> 2;
var enc2 = (chr1 & 3) << 4 | chr2 >> 4;
var enc3 = (chr2 & 15) << 2 | chr3 >> 6;
var enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output[p++] = KEYSTR.charAt(enc1);
output[p++] = KEYSTR.charAt(enc2);
output[p++] = KEYSTR.charAt(enc3);
output[p++] = KEYSTR.charAt(enc4);
} while (i < input.length);
return output.join('');
};
// Converts a string from base64
var decode64 = function decode64(input) {
if (window.atob) {
return decodeUTF8(window.atob(input));
}
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input.replace(/[^A-Za-z0-9+/=]/g, '');
var output = '';
var i = 0;
do {
var enc1 = KEYSTR.indexOf(input.charAt(i++));
var enc2 = KEYSTR.indexOf(input.charAt(i++));
var enc3 = KEYSTR.indexOf(input.charAt(i++));
var enc4 = KEYSTR.indexOf(input.charAt(i++));
var chr1 = enc1 << 2 | enc2 >> 4;
var chr2 = (enc2 & 15) << 4 | enc3 >> 2;
var chr3 = (enc3 & 3) << 6 | enc4;
output += String.fromCharCode(chr1);
if (enc3 !== 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 !== 64) {
output = output + String.fromCharCode(chr3);
}
} while (i < input.length);
return decodeUTF8(output);
};
var decodeUTF8 = function decodeUTF8(argString) {
return decodeURIComponent(escape(argString));
};
// codedread:does not seem to work with webkit-based browsers on OSX // Brettz9: please test again as function upgraded
var encodeUTF8 = function encodeUTF8(argString) {
return unescape(encodeURIComponent(argString));
};
/**
* convert dataURL to object URL
* @param {string} dataurl
* @return {string} object URL or empty string
*/
var dataURLToObjectURL = function dataURLToObjectURL(dataurl) {
if (typeof Uint8Array === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL) {
return '';
}
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
var blob = new Blob([u8arr], { type: mime });
return URL.createObjectURL(blob);
};
/**
* get object URL for a blob object
* @param {Blob} blob A Blob object or File object
* @return {string} object URL or empty string
*/
var createObjectURL = function createObjectURL(blob) {
if (!blob || typeof URL === 'undefined' || !URL.createObjectURL) {
return '';
}
return URL.createObjectURL(blob);
};
/**
* @property {string} blankPageObjectURL
*/
var blankPageObjectURL = function () {
if (typeof Blob === 'undefined') {
return '';
}
var blob = new Blob(['SVG-edit '], { type: 'text/html' });
return createObjectURL(blob);
}();
// Cross-browser compatible method of converting a string to an XML tree
// found this function here: http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
var text2xml = function text2xml(sXML) {
if (sXML.includes('
* -
* -
* @param attrVal - The attribute value as a string
* @returns {String} String with just the URL, like "someFile.svg#foo"
*/
var getUrlFromAttr = function getUrlFromAttr(attrVal) {
if (attrVal) {
// url('#somegrad')
if (attrVal.startsWith('url("')) {
return attrVal.substring(5, attrVal.indexOf('"', 6));
}
// url('#somegrad')
if (attrVal.startsWith("url('")) {
return attrVal.substring(5, attrVal.indexOf("'", 6));
}
if (attrVal.startsWith('url(')) {
return attrVal.substring(4, attrVal.indexOf(')'));
}
}
return null;
};
/**
* @returns The given element's xlink:href value
*/
var getHref = function getHref(elem) {
return elem.getAttributeNS(NS.XLINK, 'href');
};
/**
* Sets the given element's xlink:href value
* @param elem
* @param {String} val
*/
var setHref = function setHref(elem, val) {
elem.setAttributeNS(NS.XLINK, 'xlink:href', val);
};
/**
* @returns The document's <defs> element, create it first if necessary
*/
var findDefs = function findDefs() {
var svgElement = editorContext_$1.getSVGContent();
var defs = svgElement.getElementsByTagNameNS(NS.SVG, 'defs');
if (defs.length > 0) {
defs = defs[0];
} else {
defs = svgElement.ownerDocument.createElementNS(NS.SVG, 'defs');
if (svgElement.firstChild) {
// first child is a comment, so call nextSibling
svgElement.insertBefore(defs, svgElement.firstChild.nextSibling);
// svgElement.firstChild.nextSibling.before(defs); // Not safe
} else {
svgElement.append(defs);
}
}
return defs;
};
// TODO(codedread): Consider moving the next to functions to bbox.js
/**
* Get correct BBox for a path in Webkit
* Converted from code found here:
* http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
* @param path - The path DOM element to get the BBox for
* @returns A BBox-like object
*/
var getPathBBox = function getPathBBox(path$$1) {
var seglist = path$$1.pathSegList;
var tot = seglist.numberOfItems;
var bounds = [[], []];
var start = seglist.getItem(0);
var P0 = [start.x, start.y];
for (var i = 0; i < tot; i++) {
var seg = seglist.getItem(i);
if (seg.x === undefined) {
continue;
}
// Add actual points to limits
bounds[0].push(P0[0]);
bounds[1].push(P0[1]);
if (seg.x1) {
(function () {
var P1 = [seg.x1, seg.y1],
P2 = [seg.x2, seg.y2],
P3 = [seg.x, seg.y];
var _loop = function _loop(j) {
var calc = function calc(t) {
return Math.pow(1 - t, 3) * P0[j] + 3 * Math.pow(1 - t, 2) * t * P1[j] + 3 * (1 - t) * Math.pow(t, 2) * P2[j] + Math.pow(t, 3) * P3[j];
};
var b = 6 * P0[j] - 12 * P1[j] + 6 * P2[j];
var a = -3 * P0[j] + 9 * P1[j] - 9 * P2[j] + 3 * P3[j];
var c = 3 * P1[j] - 3 * P0[j];
if (a === 0) {
if (b === 0) {
return 'continue';
}
var t = -c / b;
if (t > 0 && t < 1) {
bounds[j].push(calc(t));
}
return 'continue';
}
var b2ac = Math.pow(b, 2) - 4 * c * a;
if (b2ac < 0) {
return 'continue';
}
var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
if (t1 > 0 && t1 < 1) {
bounds[j].push(calc(t1));
}
var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
if (t2 > 0 && t2 < 1) {
bounds[j].push(calc(t2));
}
};
for (var j = 0; j < 2; j++) {
var _ret2 = _loop(j);
if (_ret2 === 'continue') continue;
}
P0 = P3;
})();
} else {
bounds[0].push(seg.x);
bounds[1].push(seg.y);
}
}
var x = Math.min.apply(null, bounds[0]);
var w = Math.max.apply(null, bounds[0]) - x;
var y = Math.min.apply(null, bounds[1]);
var h = Math.max.apply(null, bounds[1]) - y;
return {
x: x,
y: y,
width: w,
height: h
};
};
/**
* Get the given/selected element's bounding box object, checking for
* horizontal/vertical lines (see issue 717)
* Note that performance is currently terrible, so some way to improve would
* be great.
* @param selected - Container or <use> DOM element
* @returns Bounding box object
*/
function groupBBFix(selected) {
if (supportsHVLineContainerBBox()) {
try {
return selected.getBBox();
} catch (e) {}
}
var ref = $$2.data(selected, 'ref');
var matched = null;
var ret = void 0,
copy = void 0;
if (ref) {
copy = $$2(ref).children().clone().attr('visibility', 'hidden');
$$2(svgroot_).append(copy);
matched = copy.filter('line, path');
} else {
matched = $$2(selected).find('line, path');
}
var issue = false;
if (matched.length) {
matched.each(function () {
var bb = this.getBBox();
if (!bb.width || !bb.height) {
issue = true;
}
});
if (issue) {
var elems = ref ? copy : $$2(selected).children();
ret = getStrokedBBox(elems); // getStrokedBBox defined in svgcanvas
} else {
ret = selected.getBBox();
}
} else {
ret = selected.getBBox();
}
if (ref) {
copy.remove();
}
return ret;
}
/**
* Get the given/selected element's bounding box object, convert it to be more
* usable when necessary
* @param elem - Optional DOM element to get the BBox for
* @returns Bounding box object
*/
var getBBox = function getBBox(elem) {
var selected = elem || editorContext_$1.geSelectedElements()[0];
if (elem.nodeType !== 1) {
return null;
}
var elname = selected.nodeName;
var ret = null;
switch (elname) {
case 'text':
if (selected.textContent === '') {
selected.textContent = 'a'; // Some character needed for the selector to use.
ret = selected.getBBox();
selected.textContent = '';
} else {
if (selected.getBBox) {
ret = selected.getBBox();
}
}
break;
case 'path':
if (!supportsPathBBox()) {
ret = getPathBBox(selected);
} else {
if (selected.getBBox) {
ret = selected.getBBox();
}
}
break;
case 'g':
case 'a':
ret = groupBBFix(selected);
break;
default:
if (elname === 'use') {
ret = groupBBFix(selected, true);
}
if (elname === 'use' || elname === 'foreignObject' && isWebkit()) {
if (!ret) {
ret = selected.getBBox();
}
// This is resolved in later versions of webkit, perhaps we should
// have a featured detection for correct 'use' behavior?
// ——————————
if (!isWebkit()) {
var bb = {};
bb.width = ret.width;
bb.height = ret.height;
bb.x = ret.x + parseFloat(selected.getAttribute('x') || 0);
bb.y = ret.y + parseFloat(selected.getAttribute('y') || 0);
ret = bb;
}
} else if (visElemsArr.includes(elname)) {
if (selected) {
try {
ret = selected.getBBox();
} catch (err) {
// tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268
// Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835
var extent = selected.getExtentOfChar(0); // pos+dimensions of the first glyph
var width = selected.getComputedTextLength(); // width of the tspan
ret = {
x: extent.x,
y: extent.y,
width: width,
height: extent.height
};
}
} else {
// Check if element is child of a foreignObject
var fo = $$2(selected).closest('foreignObject');
if (fo.length) {
if (fo[0].getBBox) {
ret = fo[0].getBBox();
}
}
}
}
}
if (ret) {
ret = bboxToObj(ret);
}
// get the bounding box from the DOM (which is in that element's coordinate system)
return ret;
};
/**
* Create a path 'd' attribute from path segments.
* Each segment is an array of the form: [singleChar, [x,y, x,y, ...]]
* @param pathSegments - An array of path segments to be converted
* @returns The converted path d attribute.
*/
var getPathDFromSegments = function getPathDFromSegments(pathSegments) {
var d = '';
$$2.each(pathSegments, function (j, seg) {
var pts = seg[1];
d += seg[0];
for (var i = 0; i < pts.length; i += 2) {
d += pts[i] + ',' + pts[i + 1] + ' ';
}
});
return d;
};
/**
* Make a path 'd' attribute from a simple SVG element shape.
* @param elem - The element to be converted
* @returns The path d attribute or `undefined` if the element type is unknown.
*/
var getPathDFromElement = function getPathDFromElement(elem) {
// Possibly the cubed root of 6, but 1.81 works best
var num = 1.81;
var d = void 0,
a = void 0,
rx = void 0,
ry = void 0;
switch (elem.tagName) {
case 'ellipse':
case 'circle':
a = $$2(elem).attr(['rx', 'ry', 'cx', 'cy']);
var _a = a,
cx = _a.cx,
cy = _a.cy;
var _a2 = a;
rx = _a2.rx;
ry = _a2.ry;
if (elem.tagName === 'circle') {
rx = ry = $$2(elem).attr('r');
}
d = getPathDFromSegments([['M', [cx - rx, cy]], ['C', [cx - rx, cy - ry / num, cx - rx / num, cy - ry, cx, cy - ry]], ['C', [cx + rx / num, cy - ry, cx + rx, cy - ry / num, cx + rx, cy]], ['C', [cx + rx, cy + ry / num, cx + rx / num, cy + ry, cx, cy + ry]], ['C', [cx - rx / num, cy + ry, cx - rx, cy + ry / num, cx - rx, cy]], ['Z', []]]);
break;
case 'path':
d = elem.getAttribute('d');
break;
case 'line':
a = $$2(elem).attr(['x1', 'y1', 'x2', 'y2']);
d = 'M' + a.x1 + ',' + a.y1 + 'L' + a.x2 + ',' + a.y2;
break;
case 'polyline':
d = 'M' + elem.getAttribute('points');
break;
case 'polygon':
d = 'M' + elem.getAttribute('points') + ' Z';
break;
case 'rect':
var r = $$2(elem).attr(['rx', 'ry']);
rx = r.rx;
ry = r.ry;
var _b = elem.getBBox();
var x = _b.x,
y = _b.y,
w = _b.width,
h = _b.height;
num = 4 - num; // Why? Because!
if (!rx && !ry) {
// Regular rect
d = getPathDFromSegments([['M', [x, y]], ['L', [x + w, y]], ['L', [x + w, y + h]], ['L', [x, y + h]], ['L', [x, y]], ['Z', []]]);
} else {
d = getPathDFromSegments([['M', [x, y + ry]], ['C', [x, y + ry / num, x + rx / num, y, x + rx, y]], ['L', [x + w - rx, y]], ['C', [x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry]], ['L', [x + w, y + h - ry]], ['C', [x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h]], ['L', [x + rx, y + h]], ['C', [x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry]], ['L', [x, y + ry]], ['Z', []]]);
}
break;
default:
break;
}
return d;
};
/**
* Get a set of attributes from an element that is useful for convertToPath.
* @param elem - The element to be probed
* @returns {Object} An object with attributes.
*/
var getExtraAttributesForConvertToPath = function getExtraAttributesForConvertToPath(elem) {
var attrs = {};
// TODO: make this list global so that we can properly maintain it
// TODO: what about @transform, @clip-rule, @fill-rule, etc?
$$2.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function () {
var a = elem.getAttribute(this);
if (a) {
attrs[this] = a;
}
});
return attrs;
};
/**
* Get the BBox of an element-as-path
* @param elem - The DOM element to be probed
* @param addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
* @param pathActions - If a transform exists, `pathActions.resetOrientation()` is used. See: canvas.pathActions.
* @returns The resulting path's bounding box object.
*/
var getBBoxOfElementAsPath = function getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions$$1) {
var path$$1 = addSvgElementFromJson({
element: 'path',
attr: getExtraAttributesForConvertToPath(elem)
});
var eltrans = elem.getAttribute('transform');
if (eltrans) {
path$$1.setAttribute('transform', eltrans);
}
var parent = elem.parentNode;
if (elem.nextSibling) {
elem.before(path$$1);
} else {
parent.append(path$$1);
}
var d = getPathDFromElement(elem);
if (d) {
path$$1.setAttribute('d', d);
} else {
path$$1.remove();
}
// Get the correct BBox of the new path, then discard it
pathActions$$1.resetOrientation(path$$1);
var bb = false;
try {
bb = path$$1.getBBox();
} catch (e) {
// Firefox fails
}
path$$1.remove();
return bb;
};
/**
* Convert selected element to a path.
* @param elem - The DOM element to be converted
* @param attrs - Apply attributes to new path. see canvas.convertToPath
* @param addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
* @param pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
* @param clearSelection - see canvas.clearSelection
* @param addToSelection - see canvas.addToSelection
* @param history - see svgedit.history
* @param addCommandToHistory - see canvas.addCommandToHistory
* @returns The converted path element or null if the DOM element was not recognized.
*/
var convertToPath = function convertToPath(elem, attrs, addSvgElementFromJson, pathActions$$1, clearSelection, addToSelection, history, addCommandToHistory) {
var batchCmd = new history.BatchCommand('Convert element to Path');
// Any attribute on the element not covered by the passed-in attributes
attrs = $$2.extend({}, attrs, getExtraAttributesForConvertToPath(elem));
var path$$1 = addSvgElementFromJson({
element: 'path',
attr: attrs
});
var eltrans = elem.getAttribute('transform');
if (eltrans) {
path$$1.setAttribute('transform', eltrans);
}
var id = elem.id;
var parent = elem.parentNode;
if (elem.nextSibling) {
elem.before(path$$1);
} else {
parent.append(path$$1);
}
var d = getPathDFromElement(elem);
if (d) {
path$$1.setAttribute('d', d);
// Replace the current element with the converted one
// Reorient if it has a matrix
if (eltrans) {
var tlist = getTransformList(path$$1);
if (hasMatrixTransform(tlist)) {
pathActions$$1.resetOrientation(path$$1);
}
}
var nextSibling = elem.nextSibling;
batchCmd.addSubCommand(new history.RemoveElementCommand(elem, nextSibling, parent));
batchCmd.addSubCommand(new history.InsertElementCommand(path$$1));
clearSelection();
elem.remove();
path$$1.setAttribute('id', id);
path$$1.removeAttribute('visibility');
addToSelection([path$$1], true);
addCommandToHistory(batchCmd);
return path$$1;
} else {
// the elem.tagName was not recognized, so no "d" attribute. Remove it, so we've haven't changed anything.
path$$1.remove();
return null;
}
};
/**
* Can the bbox be optimized over the native getBBox? The optimized bbox is the same as the native getBBox when
* the rotation angle is a multiple of 90 degrees and there are no complex transforms.
* Getting an optimized bbox can be dramatically slower, so we want to make sure it's worth it.
*
* The best example for this is a circle rotate 45 degrees. The circle doesn't get wider or taller when rotated
* about it's center.
*
* The standard, unoptimized technique gets the native bbox of the circle, rotates the box 45 degrees, uses
* that width and height, and applies any transforms to get the final bbox. This means the calculated bbox
* is much wider than the original circle. If the angle had been 0, 90, 180, etc. both techniques render the
* same bbox.
*
* The optimization is not needed if the rotation is a multiple 90 degrees. The default technique is to call
* getBBox then apply the angle and any transforms.
*
* @param angle - The rotation angle in degrees
* @param {Boolean} hasMatrixTransform - True if there is a matrix transform
* @returns {Boolean} True if the bbox can be optimized.
*/
function bBoxCanBeOptimizedOverNativeGetBBox(angle, hasMatrixTransform$$1) {
var angleModulo90 = angle % 90;
var closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99;
var closeTo0 = angleModulo90 > -0.001 && angleModulo90 < 0.001;
return hasMatrixTransform$$1 || !(closeTo0 || closeTo90);
}
/**
* Get bounding box that includes any transforms.
* @param elem - The DOM element to be converted
* @param addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
* @param pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
* @returns A single bounding box object
*/
var getBBoxWithTransform = function getBBoxWithTransform(elem, addSvgElementFromJson, pathActions$$1) {
// TODO: Fix issue with rotated groups. Currently they work
// fine in FF, but not in other browsers (same problem mentioned
// in Issue 339 comment #2).
var bb = getBBox(elem);
if (!bb) {
return null;
}
var tlist = getTransformList(elem);
var angle = getRotationAngleFromTransformList(tlist);
var hasMatrixXForm = hasMatrixTransform(tlist);
if (angle || hasMatrixXForm) {
var goodBb = false;
if (bBoxCanBeOptimizedOverNativeGetBBox(angle, hasMatrixXForm)) {
// Get the BBox from the raw path for these elements
// TODO: why ellipse and not circle
var elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon'];
if (elemNames.includes(elem.tagName)) {
bb = goodBb = getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions$$1);
} else if (elem.tagName === 'rect') {
// Look for radius
var rx = elem.getAttribute('rx');
var ry = elem.getAttribute('ry');
if (rx || ry) {
bb = goodBb = getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions$$1);
}
}
}
if (!goodBb) {
var _transformListToTrans = transformListToTransform(tlist),
matrix = _transformListToTrans.matrix;
bb = transformBox(bb.x, bb.y, bb.width, bb.height, matrix).aabox;
// Old technique that was exceedingly slow with large documents.
//
// Accurate way to get BBox of rotated element in Firefox:
// Put element in group and get its BBox
//
// Must use clone else FF freaks out
// const clone = elem.cloneNode(true);
// const g = document.createElementNS(NS.SVG, 'g');
// const parent = elem.parentNode;
// parent.append(g);
// g.append(clone);
// const bb2 = bboxToObj(g.getBBox());
// g.remove();
}
}
return bb;
};
// TODO: This is problematic with large stroke-width and, for example, a single horizontal line. The calculated BBox extends way beyond left and right sides.
function getStrokeOffsetForBBox(elem) {
var sw = elem.getAttribute('stroke-width');
return !isNaN(sw) && elem.getAttribute('stroke') !== 'none' ? sw / 2 : 0;
}
/**
* Get the bounding box for one or more stroked and/or transformed elements
* @param elems - Array with DOM elements to check
* @param addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
* @param pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
* @returns A single bounding box object
*/
var getStrokedBBox = function getStrokedBBox(elems, addSvgElementFromJson, pathActions$$1) {
if (!elems || !elems.length) {
return false;
}
var fullBb = void 0;
$$2.each(elems, function () {
if (fullBb) {
return;
}
if (!this.parentNode) {
return;
}
fullBb = getBBoxWithTransform(this, addSvgElementFromJson, pathActions$$1);
});
// This shouldn't ever happen...
if (fullBb === undefined) {
return null;
}
// fullBb doesn't include the stoke, so this does no good!
// if (elems.length == 1) return fullBb;
var maxX = fullBb.x + fullBb.width;
var maxY = fullBb.y + fullBb.height;
var minX = fullBb.x;
var minY = fullBb.y;
// If only one elem, don't call the potentially slow getBBoxWithTransform method again.
if (elems.length === 1) {
var offset = getStrokeOffsetForBBox(elems[0]);
minX -= offset;
minY -= offset;
maxX += offset;
maxY += offset;
} else {
$$2.each(elems, function (i, elem) {
var curBb = getBBoxWithTransform(elem, addSvgElementFromJson, pathActions$$1);
if (curBb) {
var _offset = getStrokeOffsetForBBox(elem);
minX = Math.min(minX, curBb.x - _offset);
minY = Math.min(minY, curBb.y - _offset);
// TODO: The old code had this test for max, but not min. I suspect this test should be for both min and max
if (elem.nodeType === 1) {
maxX = Math.max(maxX, curBb.x + curBb.width + _offset);
maxY = Math.max(maxY, curBb.y + curBb.height + _offset);
}
}
});
}
fullBb.x = minX;
fullBb.y = minY;
fullBb.width = maxX - minX;
fullBb.height = maxY - minY;
return fullBb;
};
/**
* Get all elements that have a BBox (excludes `<defs>`, `<title>`, etc).
* Note that 0-opacity, off-screen etc elements are still considered "visible"
* for this function
* @param parent - The parent DOM element to search within
* @returns {Array} All "visible" elements.
*/
var getVisibleElements = function getVisibleElements(parent) {
if (!parent) {
parent = $$2(editorContext_$1.getSVGContent()).children(); // Prevent layers from being included
}
var contentElems = [];
$$2(parent).children().each(function (i, elem) {
if (elem.getBBox) {
contentElems.push(elem);
}
});
return contentElems.reverse();
};
/**
* Get the bounding box for one or more stroked and/or transformed elements
* @param elems - Array with DOM elements to check
* @returns A single bounding box object
*/
var getStrokedBBoxDefaultVisible = function getStrokedBBoxDefaultVisible(elems) {
if (!elems) {
elems = getVisibleElements();
}
return getStrokedBBox(elems, editorContext_$1.addSvgElementFromJson, editorContext_$1.pathActions);
};
/**
* Get the rotation angle of the given transform list.
* @param tlist - List of transforms
* @param {Boolean} toRad - When true returns the value in radians rather than degrees
* @returns {Number} Float with the angle in degrees or radians
*/
var getRotationAngleFromTransformList = function getRotationAngleFromTransformList(tlist, toRad) {
if (!tlist) {
return 0;
} //