// 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; } // elements have no tlist var N = tlist.numberOfItems; for (var i = 0; i < N; ++i) { var xform = tlist.getItem(i); if (xform.type === 4) { return toRad ? xform.angle * Math.PI / 180.0 : xform.angle; } } return 0.0; }; /** * Get the rotation angle of the given/selected DOM element * @param elem - Optional DOM element to get the angle for * @param {Boolean} toRad - When true returns the value in radians rather than degrees * @returns {Number} Float with the angle in degrees or radians */ var getRotationAngle = function getRotationAngle(elem, toRad) { var selected = elem || editorContext_$1.getSelectedElements()[0]; // find the rotation transform (if any) and set it var tlist = getTransformList(selected); return getRotationAngleFromTransformList(tlist, toRad); }; /** * Get the reference element associated with the given attribute value * @param {String} attrVal - The attribute value as a string * @returns Reference element */ var getRefElem = function getRefElem(attrVal) { return getElem(getUrlFromAttr(attrVal).substr(1)); }; /** * Get a DOM element by ID within the SVG root element. * @param {String} id - String with the element's new ID */ var getElem = supportsSelectors() ? function (id) { // querySelector lookup return svgroot_.querySelector('#' + id); } : supportsXpath() ? function (id) { // xpath lookup return domdoc_.evaluate('svg:svg[@id="svgroot"]//svg:*[@id="' + id + '"]', domcontainer_, function () { return NS.SVG; }, 9, null).singleNodeValue; } : function (id) { // jQuery lookup: twice as slow as xpath in FF return $$2(svgroot_).find('[id=' + id + ']')[0]; }; /** * Assigns multiple attributes to an element. * @param node - DOM element to apply new attribute values to * @param {Object} attrs - Object with attribute keys/values * @param {Number} suspendLength - Optional integer of milliseconds to suspend redraw * @param {Boolean} unitCheck - Boolean to indicate the need to use svgedit.units.setUnitAttr */ var assignAttributes = function assignAttributes(node, attrs, suspendLength, unitCheck) { for (var i in attrs) { var ns = i.substr(0, 4) === 'xml:' ? NS.XML : i.substr(0, 6) === 'xlink:' ? NS.XLINK : null; if (ns) { node.setAttributeNS(ns, i, attrs[i]); } else if (!unitCheck) { node.setAttribute(i, attrs[i]); } else { setUnitAttr(node, i, attrs[i]); } } }; /** * Remove unneeded (default) attributes, makes resulting SVG smaller * @param element - DOM element to clean up */ var cleanupElement = function cleanupElement(element) { var defaults = { 'fill-opacity': 1, 'stop-opacity': 1, opacity: 1, stroke: 'none', 'stroke-dasharray': 'none', 'stroke-linejoin': 'miter', 'stroke-linecap': 'butt', 'stroke-opacity': 1, 'stroke-width': 1, rx: 0, ry: 0 }; if (element.nodeName === 'ellipse') { // Ellipse elements requires rx and ry attributes delete defaults.rx; delete defaults.ry; } for (var attr in defaults) { var val = defaults[attr]; if (element.getAttribute(attr) === String(val)) { element.removeAttribute(attr); } } }; // round value to for snapping var snapToGrid = function snapToGrid(value) { var unit = editorContext_$1.getBaseUnit(); var stepSize = editorContext_$1.getSnappingStep(); if (unit !== 'px') { stepSize *= getTypeMap()[unit]; } value = Math.round(value / stepSize) * stepSize; return value; }; var regexEscape = function regexEscape(str, delimiter) { // From: http://phpjs.org/functions return String(str).replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&'); }; /** * Prevents default browser click behaviour on the given element * @param img - The DOM element to prevent the click on */ var preventClickDefault = function preventClickDefault(img) { $$2(img).click(function (e) { e.preventDefault(); }); }; /** * Create a clone of an element, updating its ID and its children's IDs when needed * @param {Element} el - DOM element to clone * @param {Function} getNextId - The getter of the next unique ID. * @returns {Element} */ var copyElem = function copyElem(el, getNextId) { // manually create a copy of the element var newEl = document.createElementNS(el.namespaceURI, el.nodeName); $$2.each(el.attributes, function (i, attr) { if (attr.localName !== '-moz-math-font-style') { newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value); } }); // set the copied element's new id newEl.removeAttribute('id'); newEl.id = getNextId(); // Opera's "d" value needs to be reset for Opera/Win/non-EN // Also needed for webkit (else does not keep curved segments on clone) if (isWebkit() && el.nodeName === 'path') { var fixedD = convertPath(el); newEl.setAttribute('d', fixedD); } // now create copies of all children $$2.each(el.childNodes, function (i, child) { switch (child.nodeType) { case 1: // element node newEl.append(copyElem(child, getNextId)); break; case 3: // text node newEl.textContent = child.nodeValue; break; default: break; } }); if ($$2(el).data('gsvg')) { $$2(newEl).data('gsvg', newEl.firstChild); } else if ($$2(el).data('symbol')) { var ref = $$2(el).data('symbol'); $$2(newEl).data('ref', ref).data('symbol', ref); } else if (newEl.tagName === 'image') { preventClickDefault(newEl); } return newEl; }; /* globals jQuery */ /** * Package: svgedit.contextmenu * * Licensed under the Apache License, Version 2 * * Author: Adam Bender */ // Dependencies: // 1) jQuery (for dom injection of context menus) var $$3 = jQuery; var contextMenuExtensions = {}; var hasCustomHandler = function hasCustomHandler(handlerKey) { return Boolean(contextMenuExtensions[handlerKey]); }; var getCustomHandler = function getCustomHandler(handlerKey) { return contextMenuExtensions[handlerKey].action; }; var injectExtendedContextMenuItemIntoDom = function injectExtendedContextMenuItemIntoDom(menuItem) { if (!Object.keys(contextMenuExtensions).length) { // all menuItems appear at the bottom of the menu in their own container. // if this is the first extension menu we need to add the separator. $$3('#cmenu_canvas').append("
  • "); } var shortcut = menuItem.shortcut || ''; $$3('#cmenu_canvas').append("
  • " + menuItem.label + "" + shortcut + '
  • '); }; var injectExtendedContextMenuItemsIntoDom = function injectExtendedContextMenuItemsIntoDom() { for (var menuItem in contextMenuExtensions) { injectExtendedContextMenuItemIntoDom(contextMenuExtensions[menuItem]); } }; // MIT License // From: https://github.com/uupaa/dynamic-import-polyfill/blob/master/importModule.js function toAbsoluteURL(url) { var a = document.createElement('a'); a.setAttribute('href', url); // return a.cloneNode(false).href; // -> "http://example.com/hoge.html" } function addScriptAtts(script, atts) { ['id', 'class', 'type'].forEach(function (prop) { if (prop in atts) { script[prop] = atts[prop]; } }); } // Additions by Brett var importSetGlobalDefault = function () { var _ref = asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(url, config) { return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: return _context.abrupt('return', importSetGlobal(url, _extends({}, config, { returnDefault: true }))); case 1: case 'end': return _context.stop(); } } }, _callee, this); })); return function importSetGlobalDefault(_x, _x2) { return _ref.apply(this, arguments); }; }(); var importSetGlobal = function () { var _ref3 = asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(url, _ref2) { var global = _ref2.global, returnDefault = _ref2.returnDefault; var modularVersion; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: // Todo: Replace calls to this function with `import()` when supported modularVersion = !('svgEditor' in window) || !window.svgEditor || window.svgEditor.modules !== false; if (!modularVersion) { _context2.next = 3; break; } return _context2.abrupt('return', importModule(url, undefined, { returnDefault: returnDefault })); case 3: _context2.next = 5; return importScript(url); case 5: return _context2.abrupt('return', window[global]); case 6: case 'end': return _context2.stop(); } } }, _callee2, this); })); return function importSetGlobal(_x3, _x4) { return _ref3.apply(this, arguments); }; }(); // Addition by Brett function importScript(url) { var atts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (Array.isArray(url)) { return Promise.all(url.map(function (u) { return importScript(u, atts); })); } return new Promise(function (resolve, reject) { var script = document.createElement('script'); var destructor = function destructor() { script.onerror = null; script.onload = null; script.remove(); script.src = ''; }; script.defer = 'defer'; addScriptAtts(script, atts); script.onerror = function () { reject(new Error('Failed to import: ' + url)); destructor(); }; script.onload = function () { resolve(); destructor(); }; script.src = url; document.head.append(script); }); } function importModule(url) { var atts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _ref4 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, _ref4$returnDefault = _ref4.returnDefault, returnDefault = _ref4$returnDefault === undefined ? false : _ref4$returnDefault; if (Array.isArray(url)) { return Promise.all(url.map(function (u) { return importModule(u, atts); })); } return new Promise(function (resolve, reject) { var vector = '$importModule$' + Math.random().toString(32).slice(2); var script = document.createElement('script'); var destructor = function destructor() { delete window[vector]; script.onerror = null; script.onload = null; script.remove(); URL.revokeObjectURL(script.src); script.src = ''; }; addScriptAtts(script, atts); script.defer = 'defer'; script.type = 'module'; script.onerror = function () { reject(new Error('Failed to import: ' + url)); destructor(); }; script.onload = function () { resolve(window[vector]); destructor(); }; var absURL = toAbsoluteURL(url); var loader = 'import * as m from \'' + absURL.replace(/'/g, "\\'") + '\'; window.' + vector + ' = ' + (returnDefault ? 'm.default || ' : '') + 'm;'; // export Module var blob = new Blob([loader], { type: 'text/javascript' }); script.src = URL.createObjectURL(blob); document.head.append(script); }); } /* globals jQuery */ var $$4 = jQuery; /** * This class encapsulates the concept of a layer in the drawing. It can be constructed with * an existing group element or, with three parameters, will create a new layer group element. * * Usage: * new Layer'name', group) // Use the existing group for this layer. * new Layer('name', group, svgElem) // Create a new group and add it to the DOM after group. * new Layer('name', null, svgElem) // Create a new group and add it to the DOM as the last layer. * * @param {string} name - Layer name * @param {SVGGElement|null} group - An existing SVG group element or null. * If group and no svgElem, use group for this layer. * If group and svgElem, create a new group element and insert it in the DOM after group. * If no group and svgElem, create a new group element and insert it in the DOM as the last layer. * @param {SVGGElement=} svgElem - The SVG DOM element. If defined, use this to add * a new layer to the document. */ var Layer = function () { function Layer(name, group, svgElem) { classCallCheck(this, Layer); this.name_ = name; this.group_ = svgElem ? null : group; if (svgElem) { // Create a group element with title and add it to the DOM. var svgdoc = svgElem.ownerDocument; this.group_ = svgdoc.createElementNS(NS.SVG, 'g'); var layerTitle = svgdoc.createElementNS(NS.SVG, 'title'); layerTitle.textContent = name; this.group_.append(layerTitle); if (group) { $$4(group).after(this.group_); } else { svgElem.append(this.group_); } } addLayerClass(this.group_); walkTree(this.group_, function (e) { e.setAttribute('style', 'pointer-events:inherit'); }); this.group_.setAttribute('style', svgElem ? 'pointer-events:all' : 'pointer-events:none'); } /** * Get the layer's name. * @returns {string} The layer name */ createClass(Layer, [{ key: 'getName', value: function getName() { return this.name_; } /** * Get the group element for this layer. * @returns {SVGGElement} The layer SVG group */ }, { key: 'getGroup', value: function getGroup() { return this.group_; } /** * Active this layer so it takes pointer events. */ }, { key: 'activate', value: function activate() { this.group_.setAttribute('style', 'pointer-events:all'); } /** * Deactive this layer so it does NOT take pointer events. */ }, { key: 'deactivate', value: function deactivate() { this.group_.setAttribute('style', 'pointer-events:none'); } /** * Set this layer visible or hidden based on 'visible' parameter. * @param {boolean} visible - If true, make visible; otherwise, hide it. */ }, { key: 'setVisible', value: function setVisible(visible) { var expected = visible === undefined || visible ? 'inline' : 'none'; var oldDisplay = this.group_.getAttribute('display'); if (oldDisplay !== expected) { this.group_.setAttribute('display', expected); } } /** * Is this layer visible? * @returns {boolean} True if visible. */ }, { key: 'isVisible', value: function isVisible() { return this.group_.getAttribute('display') !== 'none'; } /** * Get layer opacity. * @returns {number} Opacity value. */ }, { key: 'getOpacity', value: function getOpacity() { var opacity = this.group_.getAttribute('opacity'); if (opacity === null || opacity === undefined) { return 1; } return parseFloat(opacity); } /** * Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0, * nothing happens. * @param {number} opacity - A float value in the range 0.0-1.0 */ }, { key: 'setOpacity', value: function setOpacity(opacity) { if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) { this.group_.setAttribute('opacity', opacity); } } /** * Append children to this layer. * @param {SVGGElement} children - The children to append to this layer. */ }, { key: 'appendChildren', value: function appendChildren(children) { for (var i = 0; i < children.length; ++i) { this.group_.append(children[i]); } } }, { key: 'getTitleElement', value: function getTitleElement() { var len = this.group_.childNodes.length; for (var i = 0; i < len; ++i) { var child = this.group_.childNodes.item(i); if (child && child.tagName === 'title') { return child; } } return null; } /** * Set the name of this layer. * @param {string} name - The new name. * @param {svgedit.history.HistoryRecordingService} hrService - History recording service * @returns {string|null} The new name if changed; otherwise, null. */ }, { key: 'setName', value: function setName(name, hrService) { var previousName = this.name_; name = toXml(name); // now change the underlying title element contents var title = this.getTitleElement(); if (title) { $$4(title).empty(); title.textContent = name; this.name_ = name; if (hrService) { hrService.changeElement(title, { '#text': previousName }); } return this.name_; } return null; } /** * Remove this layer's group from the DOM. No more functions on group can be called after this. * @param {SVGGElement} children - The children to append to this layer. * @returns {SVGGElement} The layer SVG group that was just removed. */ }, { key: 'removeGroup', value: function removeGroup() { var parent = this.group_.parentNode; var group = parent.removeChild(this.group_); this.group_ = undefined; return group; } }]); return Layer; }(); /** * @property {string} CLASS_NAME - class attribute assigned to all layer groups. */ Layer.CLASS_NAME = 'layer'; /** * @property {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME */ Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)'); /** * Add class Layer.CLASS_NAME to the element (usually class='layer'). * * Parameters: * @param {SVGGElement} elem - The SVG element to update */ function addLayerClass(elem) { var classes = elem.getAttribute('class'); if (classes === null || classes === undefined || !classes.length) { elem.setAttribute('class', Layer.CLASS_NAME); } else if (!Layer.CLASS_REGEX.test(classes)) { elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME); } } /** * Package: svgedit.history * * Licensed under the MIT License * * Copyright(c) 2016 Flint O'Brien */ /** * History recording service. * * A self-contained service interface for recording history. Once injected, no other dependencies * or globals are required (example: UndoManager, command types, etc.). Easy to mock for unit tests. * Built on top of history classes in history.js. * * There is a simple start/end interface for batch commands. * * HistoryRecordingService.NO_HISTORY is a singleton that can be passed in to functions * that record history. This helps when the caller requires that no history be recorded. * * Usage: * The following will record history: insert, batch, insert. * ``` * hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); * hrService.insertElement(elem, text); // add simple command to history. * hrService.startBatchCommand('create two elements'); * hrService.changeElement(elem, attrs, text); // add to batchCommand * hrService.changeElement(elem, attrs2, text); // add to batchCommand * hrService.endBatchCommand(); // add batch command with two change commands to history. * hrService.insertElement(elem, text); // add simple command to history. * ``` * * Note that all functions return this, so commands can be chained, like so: * * ``` * hrService * .startBatchCommand('create two elements') * .insertElement(elem, text) * .changeElement(elem, attrs, text) * .endBatchCommand(); * ``` * * @param {svgedit.history.UndoManager} undoManager - The undo manager. * A value of null is valid for cases where no history recording is required. * See singleton: HistoryRecordingService.NO_HISTORY */ var HistoryRecordingService = function () { function HistoryRecordingService(undoManager) { classCallCheck(this, HistoryRecordingService); this.undoManager_ = undoManager; this.currentBatchCommand_ = null; this.batchCommandStack_ = []; } /** * Start a batch command so multiple commands can recorded as a single history command. * Requires a corresponding call to endBatchCommand. Start and end commands can be nested. * * @param {string} text - Optional string describing the batch command. * @returns {svgedit.history.HistoryRecordingService} */ createClass(HistoryRecordingService, [{ key: 'startBatchCommand', value: function startBatchCommand(text) { if (!this.undoManager_) { return this; } this.currentBatchCommand_ = new BatchCommand(text); this.batchCommandStack_.push(this.currentBatchCommand_); return this; } /** * End a batch command and add it to the history or a parent batch command. * @returns {svgedit.history.HistoryRecordingService} */ }, { key: 'endBatchCommand', value: function endBatchCommand() { if (!this.undoManager_) { return this; } if (this.currentBatchCommand_) { var batchCommand = this.currentBatchCommand_; this.batchCommandStack_.pop(); var length = this.batchCommandStack_.length; this.currentBatchCommand_ = length ? this.batchCommandStack_[length - 1] : null; this.addCommand_(batchCommand); } return this; } /** * Add a MoveElementCommand to the history or current batch command * @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 * @returns {svgedit.history.HistoryRecordingService} */ }, { key: 'moveElement', value: function moveElement(elem, oldNextSibling, oldParent, text) { if (!this.undoManager_) { return this; } this.addCommand_(new MoveElementCommand(elem, oldNextSibling, oldParent, text)); return this; } /** * Add an InsertElementCommand to the history or current batch command * @param {Element} elem - The DOM element that was added * @param {string} [text] - An optional string visible to user related to this change * @returns {svgedit.history.HistoryRecordingService} */ }, { key: 'insertElement', value: function insertElement(elem, text) { if (!this.undoManager_) { return this; } this.addCommand_(new InsertElementCommand(elem, text)); return this; } /** * Add a RemoveElementCommand to the history or current batch command * @param {Element} elem - The DOM element that was removed * @param {Element} oldNextSibling - The element's next sibling before it was removed * @param {Element} oldParent - The element's parent before it was removed * @param {string} [text] - An optional string visible to user related to this change * @returns {svgedit.history.HistoryRecordingService} */ }, { key: 'removeElement', value: function removeElement(elem, oldNextSibling, oldParent, text) { if (!this.undoManager_) { return this; } this.addCommand_(new RemoveElementCommand(elem, oldNextSibling, oldParent, text)); return this; } /** * Add a ChangeElementCommand to the history or current batch command * @param {Element} elem - The DOM element that was changed * @param {Object} 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 * @returns {svgedit.history.HistoryRecordingService} */ }, { key: 'changeElement', value: function changeElement(elem, attrs, text) { if (!this.undoManager_) { return this; } this.addCommand_(new ChangeElementCommand(elem, attrs, text)); return this; } /** * Private function to add a command to the history or current batch command. * @param cmd * @returns {svgedit.history.HistoryRecordingService} * @private */ }, { key: 'addCommand_', value: function addCommand_(cmd) { if (!this.undoManager_) { return this; } if (this.currentBatchCommand_) { this.currentBatchCommand_.addSubCommand(cmd); } else { this.undoManager_.addCommandToHistory(cmd); } } }]); return HistoryRecordingService; }(); /** * @property {HistoryRecordingService} NO_HISTORY - Singleton that can be passed to functions that record history, but the caller requires that no history be recorded. */ HistoryRecordingService.NO_HISTORY = new HistoryRecordingService(); /* globals jQuery */ var $$5 = jQuery; var visElems$1 = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(','); var RandomizeModes = { LET_DOCUMENT_DECIDE: 0, ALWAYS_RANDOMIZE: 1, NEVER_RANDOMIZE: 2 }; var randIds = RandomizeModes.LET_DOCUMENT_DECIDE; // Array with current disabled elements (for in-group editing) var disabledElems = []; /** * Get a HistoryRecordingService. * @param {svgedit.history.HistoryRecordingService=} hrService - if exists, return it instead of creating a new service. * @returns {svgedit.history.HistoryRecordingService} */ function historyRecordingService(hrService) { return hrService || new HistoryRecordingService(canvas_.undoMgr); } /** * Find the layer name in a group element. * @param group The group element to search in. * @returns {string} The layer name or empty string. */ function findLayerNameInGroup(group) { return $$5('title', group).text() || (isOpera() && group.querySelectorAll // Hack for Opera 10.60 ? $$5(group.querySelectorAll('title')).text() : ''); } /** * Given a set of names, return a new unique name. * @param {Array.} existingLayerNames - Existing layer names. * @returns {string} - The new name. */ function getNewLayerName(existingLayerNames) { var i = 1; // TODO(codedread): What about internationalization of "Layer"? while (existingLayerNames.includes('Layer ' + i)) { i++; } return 'Layer ' + i; } /** * This class encapsulates the concept of a SVG-edit drawing * @param {SVGSVGElement} svgElem - The SVG DOM Element that this JS object * encapsulates. If the svgElem has a se:nonce attribute on it, then * IDs will use the nonce as they are generated. * @param {String} [optIdPrefix=svg_] - The ID prefix to use. */ var Drawing = function () { function Drawing(svgElem, optIdPrefix) { classCallCheck(this, Drawing); if (!svgElem || !svgElem.tagName || !svgElem.namespaceURI || svgElem.tagName !== 'svg' || svgElem.namespaceURI !== NS.SVG) { throw new Error('Error: svgedit.draw.Drawing instance initialized without a element'); } /** * The SVG DOM Element that represents this drawing. * @type {SVGSVGElement} */ this.svgElem_ = svgElem; /** * The latest object number used in this drawing. * @type {number} */ this.obj_num = 0; /** * The prefix to prepend to each element id in the drawing. * @type {String} */ this.idPrefix = optIdPrefix || 'svg_'; /** * An array of released element ids to immediately reuse. * @type {Array.} */ this.releasedNums = []; /** * The z-ordered array of Layer objects. Each layer has a name * and group element. * The first layer is the one at the bottom of the rendering. * @type {Array.} */ this.all_layers = []; /** * Map of all_layers by name. * * Note: Layers are ordered, but referenced externally by name; so, we need both container * types depending on which function is called (i.e. all_layers and layer_map). * * @type {Object.} */ this.layer_map = {}; /** * The current layer being used. * @type {Layer} */ this.current_layer = null; /** * The nonce to use to uniquely identify elements across drawings. * @type {!String} */ this.nonce_ = ''; var n = this.svgElem_.getAttributeNS(NS.SE, 'nonce'); // If already set in the DOM, use the nonce throughout the document // else, if randomizeIds(true) has been called, create and set the nonce. if (!!n && randIds !== RandomizeModes.NEVER_RANDOMIZE) { this.nonce_ = n; } else if (randIds === RandomizeModes.ALWAYS_RANDOMIZE) { this.setNonce(Math.floor(Math.random() * 100001)); } } /** * @param {string} id Element ID to retrieve * @returns {Element} SVG element within the root SVGSVGElement */ createClass(Drawing, [{ key: 'getElem_', value: function getElem_(id) { if (this.svgElem_.querySelector) { // querySelector lookup return this.svgElem_.querySelector('#' + id); } // jQuery lookup: twice as slow as xpath in FF return $$5(this.svgElem_).find('[id=' + id + ']')[0]; } /** * @returns {SVGSVGElement} */ }, { key: 'getSvgElem', value: function getSvgElem() { return this.svgElem_; } /** * @returns {!string|number} The previously set nonce */ }, { key: 'getNonce', value: function getNonce() { return this.nonce_; } /** * @param {!string|number} n The nonce to set */ }, { key: 'setNonce', value: function setNonce(n) { this.svgElem_.setAttributeNS(NS.XMLNS, 'xmlns:se', NS.SE); this.svgElem_.setAttributeNS(NS.SE, 'se:nonce', n); this.nonce_ = n; } /** * Clears any previously set nonce */ }, { key: 'clearNonce', value: function clearNonce() { // We deliberately leave any se:nonce attributes alone, // we just don't use it to randomize ids. this.nonce_ = ''; } /** * Returns the latest object id as a string. * @return {String} The latest object Id. */ }, { key: 'getId', value: function getId() { return this.nonce_ ? this.idPrefix + this.nonce_ + '_' + this.obj_num : this.idPrefix + this.obj_num; } /** * Returns the next object Id as a string. * @return {String} The next object Id to use. */ }, { key: 'getNextId', value: function getNextId() { var oldObjNum = this.obj_num; var restoreOldObjNum = false; // If there are any released numbers in the release stack, // use the last one instead of the next obj_num. // We need to temporarily use obj_num as that is what getId() depends on. if (this.releasedNums.length > 0) { this.obj_num = this.releasedNums.pop(); restoreOldObjNum = true; } else { // If we are not using a released id, then increment the obj_num. this.obj_num++; } // Ensure the ID does not exist. var id = this.getId(); while (this.getElem_(id)) { if (restoreOldObjNum) { this.obj_num = oldObjNum; restoreOldObjNum = false; } this.obj_num++; id = this.getId(); } // Restore the old object number if required. if (restoreOldObjNum) { this.obj_num = oldObjNum; } return id; } /** * Releases the object Id, letting it be used as the next id in getNextId(). * This method DOES NOT remove any elements from the DOM, it is expected * that client code will do this. * @param {string} id - The id to release. * @returns {boolean} True if the id was valid to be released, false otherwise. */ }, { key: 'releaseId', value: function releaseId(id) { // confirm if this is a valid id for this Document, else return false var front = this.idPrefix + (this.nonce_ ? this.nonce_ + '_' : ''); if (typeof id !== 'string' || !id.startsWith(front)) { return false; } // extract the obj_num of this id var num = parseInt(id.substr(front.length), 10); // if we didn't get a positive number or we already released this number // then return false. if (typeof num !== 'number' || num <= 0 || this.releasedNums.includes(num)) { return false; } // push the released number into the released queue this.releasedNums.push(num); return true; } /** * Returns the number of layers in the current drawing. * @returns {integer} The number of layers in the current drawing. */ }, { key: 'getNumLayers', value: function getNumLayers() { return this.all_layers.length; } /** * Check if layer with given name already exists * @param {string} name - The layer name to check */ }, { key: 'hasLayer', value: function hasLayer(name) { return this.layer_map[name] !== undefined; } /** * Returns the name of the ith layer. If the index is out of range, an empty string is returned. * @param {integer} i - The zero-based index of the layer you are querying. * @returns {string} The name of the ith layer (or the empty string if none found) */ }, { key: 'getLayerName', value: function getLayerName(i) { return i >= 0 && i < this.getNumLayers() ? this.all_layers[i].getName() : ''; } /** * @returns {SVGGElement} The SVGGElement representing the current layer. */ }, { key: 'getCurrentLayer', value: function getCurrentLayer() { return this.current_layer ? this.current_layer.getGroup() : null; } /** * Get a layer by name. * @returns {SVGGElement} The SVGGElement representing the named layer or null. */ }, { key: 'getLayerByName', value: function getLayerByName(name) { var layer = this.layer_map[name]; return layer ? layer.getGroup() : null; } /** * Returns the name of the currently selected layer. If an error occurs, an empty string * is returned. * @returns {string} The name of the currently active layer (or the empty string if none found). */ }, { key: 'getCurrentLayerName', value: function getCurrentLayerName() { return this.current_layer ? this.current_layer.getName() : ''; } /** * Set the current layer's name. * @param {string} name - The new name. * @param {svgedit.history.HistoryRecordingService} hrService - History recording service * @returns {string|null} The new name if changed; otherwise, null. */ }, { key: 'setCurrentLayerName', value: function setCurrentLayerName(name, hrService) { var finalName = null; if (this.current_layer) { var oldName = this.current_layer.getName(); finalName = this.current_layer.setName(name, hrService); if (finalName) { delete this.layer_map[oldName]; this.layer_map[finalName] = this.current_layer; } } return finalName; } /** * Set the current layer's position. * @param {number} newpos - The zero-based index of the new position of the layer. Range should be 0 to layers-1 * @returns {Object} If the name was changed, returns {title:SVGGElement, previousName:string}; otherwise null. */ }, { key: 'setCurrentLayerPosition', value: function setCurrentLayerPosition(newpos) { var layerCount = this.getNumLayers(); if (!this.current_layer || newpos < 0 || newpos >= layerCount) { return null; } var oldpos = void 0; for (oldpos = 0; oldpos < layerCount; ++oldpos) { if (this.all_layers[oldpos] === this.current_layer) { break; } } // some unknown error condition (current_layer not in all_layers) if (oldpos === layerCount) { return null; } if (oldpos !== newpos) { // if our new position is below us, we need to insert before the node after newpos var currentGroup = this.current_layer.getGroup(); var oldNextSibling = currentGroup.nextSibling; var refGroup = null; if (newpos > oldpos) { if (newpos < layerCount - 1) { refGroup = this.all_layers[newpos + 1].getGroup(); } // if our new position is above us, we need to insert before the node at newpos } else { refGroup = this.all_layers[newpos].getGroup(); } this.svgElem_.insertBefore(currentGroup, refGroup); // Ok to replace with `refGroup.before(currentGroup);`? this.identifyLayers(); this.setCurrentLayer(this.getLayerName(newpos)); return { currentGroup: currentGroup, oldNextSibling: oldNextSibling }; } return null; } }, { key: 'mergeLayer', value: function mergeLayer(hrService) { var currentGroup = this.current_layer.getGroup(); var prevGroup = $$5(currentGroup).prev()[0]; if (!prevGroup) { return; } hrService.startBatchCommand('Merge Layer'); var layerNextSibling = currentGroup.nextSibling; hrService.removeElement(currentGroup, layerNextSibling, this.svgElem_); while (currentGroup.firstChild) { var child = currentGroup.firstChild; if (child.localName === 'title') { hrService.removeElement(child, child.nextSibling, currentGroup); child.remove(); continue; } var oldNextSibling = child.nextSibling; prevGroup.append(child); hrService.moveElement(child, oldNextSibling, currentGroup); } // Remove current layer's group this.current_layer.removeGroup(); // Remove the current layer and set the previous layer as the new current layer var index = this.all_layers.indexOf(this.current_layer); if (index > 0) { var _name = this.current_layer.getName(); this.current_layer = this.all_layers[index - 1]; this.all_layers.splice(index, 1); delete this.layer_map[_name]; } hrService.endBatchCommand(); } }, { key: 'mergeAllLayers', value: function mergeAllLayers(hrService) { // Set the current layer to the last layer. this.current_layer = this.all_layers[this.all_layers.length - 1]; hrService.startBatchCommand('Merge all Layers'); while (this.all_layers.length > 1) { this.mergeLayer(hrService); } hrService.endBatchCommand(); } /** * Sets the current layer. If the name is not a valid layer name, then this * function returns false. Otherwise it returns true. This is not an * undo-able action. * @param {string} name - The name of the layer you want to switch to. * @returns {boolean} true if the current layer was switched, otherwise false */ }, { key: 'setCurrentLayer', value: function setCurrentLayer(name) { var layer = this.layer_map[name]; if (layer) { if (this.current_layer) { this.current_layer.deactivate(); } this.current_layer = layer; this.current_layer.activate(); return true; } return false; } /** * Deletes the current layer from the drawing and then clears the selection. * This function then calls the 'changed' handler. This is an undoable action. * @returns {SVGGElement} The SVGGElement of the layer removed or null. */ }, { key: 'deleteCurrentLayer', value: function deleteCurrentLayer() { if (this.current_layer && this.getNumLayers() > 1) { var oldLayerGroup = this.current_layer.removeGroup(); this.identifyLayers(); return oldLayerGroup; } return null; } /** * Updates layer system and sets the current layer to the * top-most layer (last child of this drawing). */ }, { key: 'identifyLayers', value: function identifyLayers() { this.all_layers = []; this.layer_map = {}; var numchildren = this.svgElem_.childNodes.length; // loop through all children of SVG element var orphans = [], layernames = []; var layer = null; var childgroups = false; for (var i = 0; i < numchildren; ++i) { var child = this.svgElem_.childNodes.item(i); // for each g, find its layer name if (child && child.nodeType === 1) { if (child.tagName === 'g') { childgroups = true; var _name2 = findLayerNameInGroup(child); if (_name2) { layernames.push(_name2); layer = new Layer(_name2, child); this.all_layers.push(layer); this.layer_map[_name2] = layer; } else { // if group did not have a name, it is an orphan orphans.push(child); } } else if (visElems$1.includes(child.nodeName)) { // Child is "visible" (i.e. not a or element), so it is an orphan orphans.push(child); } } } // If orphans or no layers found, create a new layer and add all the orphans to it if (orphans.length > 0 || !childgroups) { layer = new Layer(getNewLayerName(layernames), null, this.svgElem_); layer.appendChildren(orphans); this.all_layers.push(layer); this.layer_map[name] = layer; } else { layer.activate(); } this.current_layer = layer; } /** * Creates a new top-level layer in the drawing with the given name and * makes it the current layer. * @param {string} name - The given name. If the layer name exists, a new name will be generated. * @param {svgedit.history.HistoryRecordingService} hrService - History recording service * @returns {SVGGElement} The SVGGElement of the new layer, which is * also the current layer of this drawing. */ }, { key: 'createLayer', value: function createLayer(name, hrService) { if (this.current_layer) { this.current_layer.deactivate(); } // Check for duplicate name. if (name === undefined || name === null || name === '' || this.layer_map[name]) { name = getNewLayerName(Object.keys(this.layer_map)); } // Crate new layer and add to DOM as last layer var layer = new Layer(name, null, this.svgElem_); // Like to assume hrService exists, but this is backwards compatible with old version of createLayer. if (hrService) { hrService.startBatchCommand('Create Layer'); hrService.insertElement(layer.getGroup()); hrService.endBatchCommand(); } this.all_layers.push(layer); this.layer_map[name] = layer; this.current_layer = layer; return layer.getGroup(); } /** * Creates a copy of the current layer with the given name and makes it the current layer. * @param {string} name - The given name. If the layer name exists, a new name will be generated. * @param {svgedit.history.HistoryRecordingService} hrService - History recording service * @returns {SVGGElement} The SVGGElement of the new layer, which is * also the current layer of this drawing. */ }, { key: 'cloneLayer', value: function cloneLayer(name, hrService) { if (!this.current_layer) { return null; } this.current_layer.deactivate(); // Check for duplicate name. if (name === undefined || name === null || name === '' || this.layer_map[name]) { name = getNewLayerName(Object.keys(this.layer_map)); } // Create new group and add to DOM just after current_layer var currentGroup = this.current_layer.getGroup(); var layer = new Layer(name, currentGroup, this.svgElem_); var group = layer.getGroup(); // Clone children var children = currentGroup.childNodes; for (var _index = 0; _index < children.length; _index++) { var ch = children[_index]; if (ch.localName === 'title') { continue; } group.append(this.copyElem(ch)); } if (hrService) { hrService.startBatchCommand('Duplicate Layer'); hrService.insertElement(group); hrService.endBatchCommand(); } // Update layer containers and current_layer. var index = this.all_layers.indexOf(this.current_layer); if (index >= 0) { this.all_layers.splice(index + 1, 0, layer); } else { this.all_layers.push(layer); } this.layer_map[name] = layer; this.current_layer = layer; return group; } /** * Returns whether the layer is visible. If the layer name is not valid, * then this function returns false. * @param {string} layername - The name of the layer which you want to query. * @returns {boolean} The visibility state of the layer, or false if the layer name was invalid. */ }, { key: 'getLayerVisibility', value: function getLayerVisibility(layername) { var layer = this.layer_map[layername]; return layer ? layer.isVisible() : false; } /** * Sets the visibility of the layer. If the layer name is not valid, this * function returns false, otherwise it returns true. This is an * undo-able action. * @param {string} layername - The name of the layer to change the visibility * @param {boolean} bVisible - Whether the layer should be visible * @returns {?SVGGElement} The SVGGElement representing the layer if the * layername was valid, otherwise null. */ }, { key: 'setLayerVisibility', value: function setLayerVisibility(layername, bVisible) { if (typeof bVisible !== 'boolean') { return null; } var layer = this.layer_map[layername]; if (!layer) { return null; } layer.setVisible(bVisible); return layer.getGroup(); } /** * Returns the opacity of the given layer. If the input name is not a layer, null is returned. * @param {string} layername - name of the layer on which to get the opacity * @returns {?number} The opacity value of the given layer. This will be a value between 0.0 and 1.0, or null * if layername is not a valid layer */ }, { key: 'getLayerOpacity', value: function getLayerOpacity(layername) { var layer = this.layer_map[layername]; if (!layer) { return null; } return layer.getOpacity(); } /** * Sets the opacity of the given layer. If the input name is not a layer, * nothing happens. If opacity is not a value between 0.0 and 1.0, then * nothing happens. * @param {string} layername - Name of the layer on which to set the opacity * @param {number} opacity - A float value in the range 0.0-1.0 */ }, { key: 'setLayerOpacity', value: function setLayerOpacity(layername, opacity) { if (typeof opacity !== 'number' || opacity < 0.0 || opacity > 1.0) { return; } var layer = this.layer_map[layername]; if (layer) { layer.setOpacity(opacity); } } /** * Create a clone of an element, updating its ID and its children's IDs when needed * @param {Element} el - DOM element to clone * @returns {Element} */ }, { key: 'copyElem', value: function copyElem$$1(el) { var self = this; var getNextIdClosure = function getNextIdClosure() { return self.getNextId(); }; return copyElem(el, getNextIdClosure); } }]); return Drawing; }(); /** * Called to ensure that drawings will or will not have randomized ids. * The currentDrawing will have its nonce set if it doesn't already. * @param {boolean} enableRandomization - flag indicating if documents should have randomized ids * @param {svgedit.draw.Drawing} currentDrawing */ var randomizeIds = function randomizeIds(enableRandomization, currentDrawing) { randIds = enableRandomization === false ? RandomizeModes.NEVER_RANDOMIZE : RandomizeModes.ALWAYS_RANDOMIZE; if (randIds === RandomizeModes.ALWAYS_RANDOMIZE && !currentDrawing.getNonce()) { currentDrawing.setNonce(Math.floor(Math.random() * 100001)); } else if (randIds === RandomizeModes.NEVER_RANDOMIZE && currentDrawing.getNonce()) { currentDrawing.clearNonce(); } }; // Layer API Functions /** * Group: Layers */ var canvas_ = void 0; var init$3 = function init(canvas) { canvas_ = canvas; }; // Updates layer system var identifyLayers = function identifyLayers() { leaveContext(); canvas_.getCurrentDrawing().identifyLayers(); }; /** * Creates a new top-level layer in the drawing with the given name, sets the current layer * to it, and then clears the selection. This function then calls the 'changed' handler. * This is an undoable action. * @param name - The given name * @param hrService */ var createLayer = function createLayer(name, hrService) { var newLayer = canvas_.getCurrentDrawing().createLayer(name, historyRecordingService(hrService)); canvas_.clearSelection(); canvas_.call('changed', [newLayer]); }; /** * Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents * to it, and then clears the selection. This function then calls the 'changed' handler. * This is an undoable action. * @param {string} name - The given name. If the layer name exists, a new name will be generated. * @param {svgedit.history.HistoryRecordingService} hrService - History recording service */ var cloneLayer = function cloneLayer(name, hrService) { // Clone the current layer and make the cloned layer the new current layer var newLayer = canvas_.getCurrentDrawing().cloneLayer(name, historyRecordingService(hrService)); canvas_.clearSelection(); leaveContext(); canvas_.call('changed', [newLayer]); }; /** * Deletes the current layer from the drawing and then clears the selection. This function * then calls the 'changed' handler. This is an undoable action. */ var deleteCurrentLayer = function deleteCurrentLayer() { var currentLayer = canvas_.getCurrentDrawing().getCurrentLayer(); var _currentLayer = currentLayer, nextSibling = _currentLayer.nextSibling; var parent = currentLayer.parentNode; currentLayer = canvas_.getCurrentDrawing().deleteCurrentLayer(); if (currentLayer) { var batchCmd = new BatchCommand('Delete Layer'); // store in our Undo History batchCmd.addSubCommand(new RemoveElementCommand(currentLayer, nextSibling, parent)); canvas_.addCommandToHistory(batchCmd); canvas_.clearSelection(); canvas_.call('changed', [parent]); return true; } return false; }; /** * Sets the current layer. If the name is not a valid layer name, then this function returns * false. Otherwise it returns true. This is not an undo-able action. * @param name - The name of the layer you want to switch to. * * @returns true if the current layer was switched, otherwise false */ var setCurrentLayer = function setCurrentLayer(name) { var result = canvas_.getCurrentDrawing().setCurrentLayer(toXml(name)); if (result) { canvas_.clearSelection(); } return result; }; /** * Renames the current layer. If the layer name is not valid (i.e. unique), then this function * does nothing and returns false, otherwise it returns true. This is an undo-able action. * * @param newname - the new name you want to give the current layer. This name must be unique * among all layer names. * @returns {Boolean} Whether the rename succeeded */ var renameCurrentLayer = function renameCurrentLayer(newname) { var drawing = canvas_.getCurrentDrawing(); var layer = drawing.getCurrentLayer(); if (layer) { var result = drawing.setCurrentLayerName(newname, historyRecordingService()); if (result) { canvas_.call('changed', [layer]); return true; } } return false; }; /** * Changes the position of the current layer to the new value. If the new index is not valid, * this function does nothing and returns false, otherwise it returns true. This is an * undo-able action. * @param newpos - The zero-based index of the new position of the layer. This should be between * 0 and (number of layers - 1) * * @returns {Boolean} true if the current layer position was changed, false otherwise. */ var setCurrentLayerPosition = function setCurrentLayerPosition(newpos) { var drawing = canvas_.getCurrentDrawing(); var result = drawing.setCurrentLayerPosition(newpos); if (result) { canvas_.addCommandToHistory(new MoveElementCommand(result.currentGroup, result.oldNextSibling, canvas_.getSVGContent())); return true; } return false; }; /** * Sets the visibility of the layer. If the layer name is not valid, this function return * false, otherwise it returns true. This is an undo-able action. * @param layername - The name of the layer to change the visibility * @param {Boolean} bVisible - Whether the layer should be visible * @returns {Boolean} true if the layer's visibility was set, false otherwise */ var setLayerVisibility = function setLayerVisibility(layername, bVisible) { var drawing = canvas_.getCurrentDrawing(); var prevVisibility = drawing.getLayerVisibility(layername); var layer = drawing.setLayerVisibility(layername, bVisible); if (layer) { var oldDisplay = prevVisibility ? 'inline' : 'none'; canvas_.addCommandToHistory(new ChangeElementCommand(layer, { display: oldDisplay }, 'Layer Visibility')); } else { return false; } if (layer === drawing.getCurrentLayer()) { canvas_.clearSelection(); canvas_.pathActions.clear(); } // call('changed', [selected]); return true; }; /** * Moves the selected elements to layername. If the name is not a valid layer name, then false * is returned. Otherwise it returns true. This is an undo-able action. * * @param layername - The name of the layer you want to which you want to move the selected elements * @returns {Boolean} Whether the selected elements were moved to the layer. */ var moveSelectedToLayer = function moveSelectedToLayer(layername) { // find the layer var drawing = canvas_.getCurrentDrawing(); var layer = drawing.getLayerByName(layername); if (!layer) { return false; } var batchCmd = new BatchCommand('Move Elements to Layer'); // loop for each selected element and move it var selElems = canvas_.getSelectedElements(); var i = selElems.length; while (i--) { var elem = selElems[i]; if (!elem) { continue; } var oldNextSibling = elem.nextSibling; // TODO: this is pretty brittle! var oldLayer = elem.parentNode; layer.append(elem); batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldLayer)); } canvas_.addCommandToHistory(batchCmd); return true; }; var mergeLayer = function mergeLayer(hrService) { canvas_.getCurrentDrawing().mergeLayer(historyRecordingService(hrService)); canvas_.clearSelection(); leaveContext(); canvas_.changeSvgcontent(); }; var mergeAllLayers = function mergeAllLayers(hrService) { canvas_.getCurrentDrawing().mergeAllLayers(historyRecordingService(hrService)); canvas_.clearSelection(); leaveContext(); canvas_.changeSvgcontent(); }; // Return from a group context to the regular kind, make any previously // disabled elements enabled again var leaveContext = function leaveContext() { var len = disabledElems.length; if (len) { for (var i = 0; i < len; i++) { var elem = disabledElems[i]; var orig = canvas_.elData(elem, 'orig_opac'); if (orig !== 1) { elem.setAttribute('opacity', orig); } else { elem.removeAttribute('opacity'); } elem.setAttribute('style', 'pointer-events: inherit'); } disabledElems = []; canvas_.clearSelection(true); canvas_.call('contextset', null); } canvas_.setCurrentGroup(null); }; // Set the current context (for in-group editing) var setContext = function setContext(elem) { leaveContext(); if (typeof elem === 'string') { elem = getElem(elem); } // Edit inside this group canvas_.setCurrentGroup(elem); // Disable other elements $$5(elem).parentsUntil('#svgcontent').andSelf().siblings().each(function () { var opac = this.getAttribute('opacity') || 1; // Store the original's opacity canvas_.elData(this, 'orig_opac', opac); this.setAttribute('opacity', opac * 0.33); this.setAttribute('style', 'pointer-events: none'); disabledElems.push(this); }); canvas_.clearSelection(); canvas_.call('contextset', canvas_.getCurrentGroup()); }; var draw = /*#__PURE__*/Object.freeze({ Drawing: Drawing, randomizeIds: randomizeIds, init: init$3, identifyLayers: identifyLayers, createLayer: createLayer, cloneLayer: cloneLayer, deleteCurrentLayer: deleteCurrentLayer, setCurrentLayer: setCurrentLayer, renameCurrentLayer: renameCurrentLayer, setCurrentLayerPosition: setCurrentLayerPosition, setLayerVisibility: setLayerVisibility, moveSelectedToLayer: moveSelectedToLayer, mergeLayer: mergeLayer, mergeAllLayers: mergeAllLayers, leaveContext: leaveContext, setContext: setContext, Layer: Layer }); /** * Package: svgedit.sanitize * * Licensed under the MIT License * * Copyright(c) 2010 Alexis Deveria * Copyright(c) 2010 Jeff Schiller */ var REVERSE_NS = getReverseNS(); // this defines which elements and attributes that we support var svgWhiteList_ = { // SVG Elements a: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'xlink:href', 'xlink:title'], circle: ['class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'], clipPath: ['class', 'clipPathUnits', 'id'], defs: [], style: ['type'], desc: [], ellipse: ['class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'], feGaussianBlur: ['class', 'color-interpolation-filters', 'id', 'requiredFeatures', 'stdDeviation'], filter: ['class', 'color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'id', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'], foreignObject: ['class', 'font-size', 'height', 'id', 'opacity', 'requiredFeatures', 'style', 'transform', 'width', 'x', 'y'], g: ['class', 'clip-path', 'clip-rule', 'id', 'display', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'], image: ['class', 'clip-path', 'clip-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'style', 'systemLanguage', 'transform', 'width', 'x', 'xlink:href', 'xlink:title', 'y'], line: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'x1', 'x2', 'y1', 'y2'], linearGradient: ['class', 'id', 'gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2'], marker: ['id', 'class', 'markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'systemLanguage', 'viewBox'], mask: ['class', 'height', 'id', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y'], metadata: ['class', 'id'], path: ['class', 'clip-path', 'clip-rule', 'd', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'], pattern: ['class', 'height', 'id', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y'], polygon: ['class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'class', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'], polyline: ['class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'], radialGradient: ['class', 'cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'id', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href'], rect: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'width', 'x', 'y'], stop: ['class', 'id', 'offset', 'requiredFeatures', 'stop-color', 'stop-opacity', 'style', 'systemLanguage'], svg: ['class', 'clip-path', 'clip-rule', 'filter', 'id', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'y'], switch: ['class', 'id', 'requiredFeatures', 'systemLanguage'], symbol: ['class', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'opacity', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'viewBox'], text: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'transform', 'x', 'xml:space', 'y'], textPath: ['class', 'id', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'style', 'systemLanguage', 'transform', 'xlink:href'], title: [], tspan: ['class', 'clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'textLength', 'transform', 'x', 'xml:space', 'y'], use: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'transform', 'width', 'x', 'xlink:href', 'y'], // MathML Elements annotation: ['encoding'], 'annotation-xml': ['encoding'], maction: ['actiontype', 'other', 'selection'], math: ['class', 'id', 'display', 'xmlns'], menclose: ['notation'], merror: [], mfrac: ['linethickness'], mi: ['mathvariant'], mmultiscripts: [], mn: [], mo: ['fence', 'lspace', 'maxsize', 'minsize', 'rspace', 'stretchy'], mover: [], mpadded: ['lspace', 'width', 'height', 'depth', 'voffset'], mphantom: [], mprescripts: [], mroot: [], mrow: ['xlink:href', 'xlink:type', 'xmlns:xlink'], mspace: ['depth', 'height', 'width'], msqrt: [], mstyle: ['displaystyle', 'mathbackground', 'mathcolor', 'mathvariant', 'scriptlevel'], msub: [], msubsup: [], msup: [], mtable: ['align', 'columnalign', 'columnlines', 'columnspacing', 'displaystyle', 'equalcolumns', 'equalrows', 'frame', 'rowalign', 'rowlines', 'rowspacing', 'width'], mtd: ['columnalign', 'columnspan', 'rowalign', 'rowspan'], mtext: [], mtr: ['columnalign', 'rowalign'], munder: [], munderover: [], none: [], semantics: [] }; // Produce a Namespace-aware version of svgWhitelist var svgWhiteListNS_ = {}; Object.entries(svgWhiteList_).forEach(function (_ref) { var _ref2 = slicedToArray(_ref, 2), elt = _ref2[0], atts = _ref2[1]; var attNS = {}; Object.entries(atts).forEach(function (_ref3) { var _ref4 = slicedToArray(_ref3, 2), i = _ref4[0], att = _ref4[1]; if (att.includes(':')) { var v = att.split(':'); attNS[v[1]] = NS[v[0].toUpperCase()]; } else { attNS[att] = att === 'xmlns' ? NS.XMLNS : null; } }); svgWhiteListNS_[elt] = attNS; }); /** * Sanitizes the input node and its children * It only keeps what is allowed from our whitelist defined above * @param node - The DOM element to be checked (we'll also check its children) */ var sanitizeSvg = function sanitizeSvg(node) { // Cleanup text nodes if (node.nodeType === 3) { // 3 === TEXT_NODE // Trim whitespace node.nodeValue = node.nodeValue.replace(/^\s+|\s+$/g, ''); // Remove if empty if (!node.nodeValue.length) { node.remove(); } } // We only care about element nodes. // Automatically return for all non-element nodes, such as comments, etc. if (node.nodeType !== 1) { // 1 == ELEMENT_NODE return; } var doc = node.ownerDocument; var parent = node.parentNode; // can parent ever be null here? I think the root node's parent is the document... if (!doc || !parent) { return; } var allowedAttrs = svgWhiteList_[node.nodeName]; var allowedAttrsNS = svgWhiteListNS_[node.nodeName]; // if this element is supported, sanitize it if (typeof allowedAttrs !== 'undefined') { var seAttrs = []; var i = node.attributes.length; while (i--) { // if the attribute is not in our whitelist, then remove it // could use jQuery's inArray(), but I don't know if that's any better var attr = node.attributes.item(i); var attrName = attr.nodeName; var attrLocalName = attr.localName; var attrNsURI = attr.namespaceURI; // Check that an attribute with the correct localName in the correct namespace is on // our whitelist or is a namespace declaration for one of our allowed namespaces if (!(allowedAttrsNS.hasOwnProperty(attrLocalName) && attrNsURI === allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS) && !(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) { // TODO(codedread): Programmatically add the se: attributes to the NS-aware whitelist. // Bypassing the whitelist to allow se: prefixes. // Is there a more appropriate way to do this? if (attrName.startsWith('se:') || attrName.startsWith('data-')) { seAttrs.push([attrName, attr.value]); } node.removeAttributeNS(attrNsURI, attrLocalName); } // Add spaces before negative signs where necessary if (isGecko()) { switch (attrName) { case 'transform': case 'gradientTransform': case 'patternTransform': var val = attr.value.replace(/(\d)-/g, '$1 -'); node.setAttribute(attrName, val); break; } } // For the style attribute, rewrite it in terms of XML presentational attributes if (attrName === 'style') { var props = attr.value.split(';'); var p = props.length; while (p--) { var _props$p$split = props[p].split(':'), _props$p$split2 = slicedToArray(_props$p$split, 2), name = _props$p$split2[0], _val = _props$p$split2[1]; var styleAttrName = (name || '').trim(); var styleAttrVal = (_val || '').trim(); // Now check that this attribute is supported if (allowedAttrs.includes(styleAttrName)) { node.setAttribute(styleAttrName, styleAttrVal); } } node.removeAttribute('style'); } } Object.values(seAttrs).forEach(function (_ref5) { var _ref6 = slicedToArray(_ref5, 2), att = _ref6[0], val = _ref6[1]; node.setAttributeNS(NS.SE, att, val); }); // for some elements that have a xlink:href, ensure the URI refers to a local element // (but not for links) var href = getHref(node); if (href && ['filter', 'linearGradient', 'pattern', 'radialGradient', 'textPath', 'use'].includes(node.nodeName)) { // TODO: we simply check if the first character is a #, is this bullet-proof? if (href[0] !== '#') { // remove the attribute (but keep the element) setHref(node, ''); node.removeAttributeNS(NS.XLINK, 'href'); } } // Safari crashes on a without a xlink:href, so we just remove the node here if (node.nodeName === 'use' && !getHref(node)) { node.remove(); return; } // if the element has attributes pointing to a non-local reference, // need to remove the attribute Object.values(['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke'], function (attr) { var val = node.getAttribute(attr); if (val) { val = getUrlFromAttr(val); // simply check for first character being a '#' if (val && val[0] !== '#') { node.setAttribute(attr, ''); node.removeAttribute(attr); } } }); // recurse to children i = node.childNodes.length; while (i--) { sanitizeSvg(node.childNodes.item(i)); } // else (element not supported), remove it } else { // remove all children from this node and insert them before this node // FIXME: in the case of animation elements this will hardly ever be correct var children = []; while (node.hasChildNodes()) { children.push(parent.insertBefore(node.firstChild, node)); } // remove this node from the document altogether node.remove(); // call sanitizeSvg on each of those children var _i = children.length; while (_i--) { sanitizeSvg(children[_i]); } } }; /* globals jQuery */ var $$6 = jQuery; // this is how we map paths to our preferred relative segment types var pathMap$1 = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', 'H', 'h', 'V', 'v', 'S', 's', 'T', 't']; /** * @typedef editorContext * @type {?object} * @property {function} getGridSnapping * @property {function} getDrawing */ var editorContext_$2 = null; /** * @param {editorContext} editorContext */ var init$4 = function init(editorContext) { editorContext_$2 = editorContext; }; /** * Applies coordinate changes to an element based on the given matrix * @param {Element} selected - DOM element to be changed * @param {Object} changes - Object with changes to be remapped * @param {SVGMatrix} m - Matrix object to use for remapping coordinates */ var remapElement = function remapElement(selected, changes, m) { var remap = function remap(x, y) { return transformPoint(x, y, m); }, scalew = function scalew(w) { return m.a * w; }, scaleh = function scaleh(h) { return m.d * h; }, doSnapping = editorContext_$2.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg', finishUp = function finishUp() { if (doSnapping) { for (var o in changes) { changes[o] = snapToGrid(changes[o]); } } assignAttributes(selected, changes, 1000, true); }, box = getBBox(selected); for (var i = 0; i < 2; i++) { var type = i === 0 ? 'fill' : 'stroke'; var attrVal = selected.getAttribute(type); if (attrVal && attrVal.startsWith('url(')) { if (m.a < 0 || m.d < 0) { var grad = getRefElem(attrVal); var newgrad = grad.cloneNode(true); if (m.a < 0) { // flip x var x1 = newgrad.getAttribute('x1'); var x2 = newgrad.getAttribute('x2'); newgrad.setAttribute('x1', -(x1 - 1)); newgrad.setAttribute('x2', -(x2 - 1)); } if (m.d < 0) { // flip y var y1 = newgrad.getAttribute('y1'); var y2 = newgrad.getAttribute('y2'); newgrad.setAttribute('y1', -(y1 - 1)); newgrad.setAttribute('y2', -(y2 - 1)); } newgrad.id = editorContext_$2.getDrawing().getNextId(); findDefs().append(newgrad); selected.setAttribute(type, 'url(#' + newgrad.id + ')'); } // Not really working :( // if (selected.tagName === 'path') { // reorientGrads(selected, m); // } } } var elName = selected.tagName; if (elName === 'g' || elName === 'text' || elName === 'tspan' || elName === 'use') { // if it was a translate, then just update x,y if (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && (m.e !== 0 || m.f !== 0)) { // [T][M] = [M][T'] // therefore [T'] = [M_inv][T][M] var existing = transformListToTransform(selected).matrix, tNew = matrixMultiply(existing.inverse(), m, existing); changes.x = parseFloat(changes.x) + tNew.e; changes.y = parseFloat(changes.y) + tNew.f; } else { // we just absorb all matrices into the element and don't do any remapping var chlist = getTransformList(selected); var mt = editorContext_$2.getSVGRoot().createSVGTransform(); mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m)); chlist.clear(); chlist.appendItem(mt); } } // now we have a set of changes and an applied reduced transform list // we apply the changes directly to the DOM switch (elName) { case 'foreignObject': case 'rect': case 'image': { // Allow images to be inverted (give them matrix when flipped) if (elName === 'image' && (m.a < 0 || m.d < 0)) { // Convert to matrix var _chlist = getTransformList(selected); var _mt = editorContext_$2.getSVGRoot().createSVGTransform(); _mt.setMatrix(matrixMultiply(transformListToTransform(_chlist).matrix, m)); _chlist.clear(); _chlist.appendItem(_mt); } else { var pt1 = remap(changes.x, changes.y); changes.width = scalew(changes.width); changes.height = scaleh(changes.height); changes.x = pt1.x + Math.min(0, changes.width); changes.y = pt1.y + Math.min(0, changes.height); changes.width = Math.abs(changes.width); changes.height = Math.abs(changes.height); } finishUp(); break; }case 'ellipse': { var c = remap(changes.cx, changes.cy); changes.cx = c.x; changes.cy = c.y; changes.rx = scalew(changes.rx); changes.ry = scaleh(changes.ry); changes.rx = Math.abs(changes.rx); changes.ry = Math.abs(changes.ry); finishUp(); break; }case 'circle': { var _c = remap(changes.cx, changes.cy); changes.cx = _c.x; changes.cy = _c.y; // take the minimum of the new selected box's dimensions for the new circle radius var tbox = transformBox(box.x, box.y, box.width, box.height, m); var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; changes.r = Math.min(w / 2, h / 2); if (changes.r) { changes.r = Math.abs(changes.r); } finishUp(); break; }case 'line': { var _pt = remap(changes.x1, changes.y1); var pt2 = remap(changes.x2, changes.y2); changes.x1 = _pt.x; changes.y1 = _pt.y; changes.x2 = pt2.x; changes.y2 = pt2.y; } // Fallthrough case 'text': case 'tspan': case 'use': { finishUp(); break; }case 'g': { var gsvg = $$6(selected).data('gsvg'); if (gsvg) { assignAttributes(gsvg, changes, 1000, true); } break; }case 'polyline': case 'polygon': { var len = changes.points.length; for (var _i = 0; _i < len; ++_i) { var pt = changes.points[_i]; var _remap = remap(pt.x, pt.y), x = _remap.x, y = _remap.y; changes.points[_i].x = x; changes.points[_i].y = y; } // const len = changes.points.length; var pstr = ''; for (var _i2 = 0; _i2 < len; ++_i2) { var _pt2 = changes.points[_i2]; pstr += _pt2.x + ',' + _pt2.y + ' '; } selected.setAttribute('points', pstr); break; }case 'path': { var segList = selected.pathSegList; var _len = segList.numberOfItems; changes.d = []; for (var _i3 = 0; _i3 < _len; ++_i3) { var seg = segList.getItem(_i3); changes.d[_i3] = { type: seg.pathSegType, x: seg.x, y: seg.y, x1: seg.x1, y1: seg.y1, x2: seg.x2, y2: seg.y2, r1: seg.r1, r2: seg.r2, angle: seg.angle, largeArcFlag: seg.largeArcFlag, sweepFlag: seg.sweepFlag }; } _len = changes.d.length; var firstseg = changes.d[0], currentpt = remap(firstseg.x, firstseg.y); changes.d[0].x = currentpt.x; changes.d[0].y = currentpt.y; for (var _i4 = 1; _i4 < _len; ++_i4) { var _seg = changes.d[_i4]; var _type = _seg.type; // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 // if relative, we want to scalew, scaleh if (_type % 2 === 0) { // absolute var thisx = _seg.x !== undefined ? _seg.x : currentpt.x, // for V commands thisy = _seg.y !== undefined ? _seg.y : currentpt.y; // for H commands var _pt3 = remap(thisx, thisy); var _pt4 = remap(_seg.x1, _seg.y1); var _pt5 = remap(_seg.x2, _seg.y2); _seg.x = _pt3.x; _seg.y = _pt3.y; _seg.x1 = _pt4.x; _seg.y1 = _pt4.y; _seg.x2 = _pt5.x; _seg.y2 = _pt5.y; _seg.r1 = scalew(_seg.r1); _seg.r2 = scaleh(_seg.r2); } else { // relative _seg.x = scalew(_seg.x); _seg.y = scaleh(_seg.y); _seg.x1 = scalew(_seg.x1); _seg.y1 = scaleh(_seg.y1); _seg.x2 = scalew(_seg.x2); _seg.y2 = scaleh(_seg.y2); _seg.r1 = scalew(_seg.r1); _seg.r2 = scaleh(_seg.r2); } } // for each segment var dstr = ''; _len = changes.d.length; for (var _i5 = 0; _i5 < _len; ++_i5) { var _seg2 = changes.d[_i5]; var _type2 = _seg2.type; dstr += pathMap$1[_type2]; switch (_type2) { case 13: // relative horizontal line (h) case 12: // absolute horizontal line (H) dstr += _seg2.x + ' '; break; case 15: // relative vertical line (v) case 14: // absolute vertical line (V) dstr += _seg2.y + ' '; break; case 3: // relative move (m) case 5: // relative line (l) case 19: // relative smooth quad (t) case 2: // absolute move (M) case 4: // absolute line (L) case 18: // absolute smooth quad (T) dstr += _seg2.x + ',' + _seg2.y + ' '; break; case 7: // relative cubic (c) case 6: // absolute cubic (C) dstr += _seg2.x1 + ',' + _seg2.y1 + ' ' + _seg2.x2 + ',' + _seg2.y2 + ' ' + _seg2.x + ',' + _seg2.y + ' '; break; case 9: // relative quad (q) case 8: // absolute quad (Q) dstr += _seg2.x1 + ',' + _seg2.y1 + ' ' + _seg2.x + ',' + _seg2.y + ' '; break; case 11: // relative elliptical arc (a) case 10: // absolute elliptical arc (A) dstr += _seg2.r1 + ',' + _seg2.r2 + ' ' + _seg2.angle + ' ' + +_seg2.largeArcFlag + ' ' + +_seg2.sweepFlag + ' ' + _seg2.x + ',' + _seg2.y + ' '; break; case 17: // relative smooth cubic (s) case 16: // absolute smooth cubic (S) dstr += _seg2.x2 + ',' + _seg2.y2 + ' ' + _seg2.x + ',' + _seg2.y + ' '; break; } } selected.setAttribute('d', dstr); break; } } }; /* globals jQuery */ var $$7 = jqPluginSVG(jQuery); var context_ = void 0; /** * @param editorContext */ var init$5 = function init$$1(editorContext) { context_ = editorContext; }; /** * Updates a s values based on the given translation of an element * @param attr - The clip-path attribute value with the clipPath's ID * @param tx - The translation's x value * @param ty - The translation's y value */ var updateClipPath = function updateClipPath(attr, tx, ty) { var path = getRefElem(attr).firstChild; var cpXform = getTransformList(path); var newxlate = context_.getSVGRoot().createSVGTransform(); newxlate.setTranslate(tx, ty); cpXform.appendItem(newxlate); // Update clipPath's dimensions recalculateDimensions(path); }; /** * Decides the course of action based on the element's transform list * @param selected - The DOM element to recalculate * @returns Undo command object with the resulting change */ var recalculateDimensions = function recalculateDimensions(selected) { if (selected == null) { return null; } // Firefox Issue - 1081 if (selected.nodeName === 'svg' && navigator.userAgent.includes('Firefox/20')) { return null; } var svgroot = context_.getSVGRoot(); var tlist = getTransformList(selected); // remove any unnecessary transforms if (tlist && tlist.numberOfItems > 0) { var k = tlist.numberOfItems; var noi = k; while (k--) { var xform = tlist.getItem(k); if (xform.type === 0) { tlist.removeItem(k); // remove identity matrices } else if (xform.type === 1) { if (isIdentity(xform.matrix)) { if (noi === 1) { // Overcome Chrome bug (though only when noi is 1) with // `removeItem` preventing `removeAttribute` from // subsequently working // See https://bugs.chromium.org/p/chromium/issues/detail?id=843901 selected.removeAttribute('transform'); return null; } tlist.removeItem(k); } // remove zero-degree rotations } else if (xform.type === 4) { if (xform.angle === 0) { tlist.removeItem(k); } } } // End here if all it has is a rotation if (tlist.numberOfItems === 1 && getRotationAngle(selected)) { return null; } } // if this element had no transforms, we are done if (!tlist || tlist.numberOfItems === 0) { // Chrome apparently had a bug that requires clearing the attribute first. selected.setAttribute('transform', ''); // However, this still next line currently doesn't work at all in Chrome selected.removeAttribute('transform'); // selected.transform.baseVal.clear(); // Didn't help for Chrome bug return null; } // TODO: Make this work for more than 2 if (tlist) { var mxs = []; var _k = tlist.numberOfItems; while (_k--) { var _xform = tlist.getItem(_k); if (_xform.type === 1) { mxs.push([_xform.matrix, _k]); } else if (mxs.length) { mxs = []; } } if (mxs.length === 2) { var mNew = svgroot.createSVGTransformFromMatrix(matrixMultiply(mxs[1][0], mxs[0][0])); tlist.removeItem(mxs[0][1]); tlist.removeItem(mxs[1][1]); tlist.insertItemBefore(mNew, mxs[1][1]); } // combine matrix + translate _k = tlist.numberOfItems; if (_k >= 2 && tlist.getItem(_k - 2).type === 1 && tlist.getItem(_k - 1).type === 2) { var mt = svgroot.createSVGTransform(); var m = matrixMultiply(tlist.getItem(_k - 2).matrix, tlist.getItem(_k - 1).matrix); mt.setMatrix(m); tlist.removeItem(_k - 2); tlist.removeItem(_k - 2); tlist.appendItem(mt); } } // If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned). switch (selected.tagName) { // Ignore these elements, as they can absorb the [M] case 'line': case 'polyline': case 'polygon': case 'path': break; default: if (tlist.numberOfItems === 1 && tlist.getItem(0).type === 1 || tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4) { return null; } } // Grouped SVG element var gsvg = $$7(selected).data('gsvg'); // we know we have some transforms, so set up return variable var batchCmd = new BatchCommand('Transform'); // store initial values that will be affected by reducing the transform list var changes = {}; var initial = null; var attrs = []; switch (selected.tagName) { case 'line': attrs = ['x1', 'y1', 'x2', 'y2']; break; case 'circle': attrs = ['cx', 'cy', 'r']; break; case 'ellipse': attrs = ['cx', 'cy', 'rx', 'ry']; break; case 'foreignObject': case 'rect': case 'image': attrs = ['width', 'height', 'x', 'y']; break; case 'use': case 'text': case 'tspan': attrs = ['x', 'y']; break; case 'polygon': case 'polyline': { initial = {}; initial.points = selected.getAttribute('points'); var list = selected.points; var len = list.numberOfItems; changes.points = new Array(len); for (var i = 0; i < len; ++i) { var pt = list.getItem(i); changes.points[i] = { x: pt.x, y: pt.y }; } break; }case 'path': initial = {}; initial.d = selected.getAttribute('d'); changes.d = selected.getAttribute('d'); break; } // switch on element type to get initial values if (attrs.length) { changes = $$7(selected).attr(attrs); $$7.each(changes, function (attr, val) { changes[attr] = convertToNum(attr, val); }); } else if (gsvg) { // GSVG exception changes = { x: $$7(gsvg).attr('x') || 0, y: $$7(gsvg).attr('y') || 0 }; } // if we haven't created an initial array in polygon/polyline/path, then // make a copy of initial values and include the transform if (initial == null) { initial = $$7.extend(true, {}, changes); $$7.each(initial, function (attr, val) { initial[attr] = convertToNum(attr, val); }); } // save the start transform value too initial.transform = context_.getStartTransform() || ''; var oldcenter = void 0, newcenter = void 0; // if it's a regular group, we have special processing to flatten transforms if (selected.tagName === 'g' && !gsvg || selected.tagName === 'a') { var box = getBBox(selected); oldcenter = { x: box.x + box.width / 2, y: box.y + box.height / 2 }; newcenter = transformPoint(box.x + box.width / 2, box.y + box.height / 2, transformListToTransform(tlist).matrix); var _m = svgroot.createSVGMatrix(); // temporarily strip off the rotate and save the old center var gangle = getRotationAngle(selected); if (gangle) { var a = gangle * Math.PI / 180; var s = void 0; if (Math.abs(a) > 1.0e-10) { s = Math.sin(a) / (1 - Math.cos(a)); } else { // FIXME: This blows up if the angle is exactly 0! s = 2 / a; } for (var _i = 0; _i < tlist.numberOfItems; ++_i) { var _xform2 = tlist.getItem(_i); if (_xform2.type === 4) { // extract old center through mystical arts var rm = _xform2.matrix; oldcenter.y = (s * rm.e + rm.f) / 2; oldcenter.x = (rm.e - s * rm.f) / 2; tlist.removeItem(_i); break; } } } var N = tlist.numberOfItems; var tx = 0, ty = 0, operation = 0; var firstM = void 0; if (N) { firstM = tlist.getItem(0).matrix; } var oldStartTransform = void 0; // first, if it was a scale then the second-last transform will be it if (N >= 3 && tlist.getItem(N - 2).type === 3 && tlist.getItem(N - 3).type === 2 && tlist.getItem(N - 1).type === 2) { operation = 3; // scale // if the children are unrotated, pass the scale down directly // otherwise pass the equivalent matrix() down directly var tm = tlist.getItem(N - 3).matrix, sm = tlist.getItem(N - 2).matrix, tmn = tlist.getItem(N - 1).matrix; var children = selected.childNodes; var c = children.length; while (c--) { var child = children.item(c); tx = 0; ty = 0; if (child.nodeType === 1) { var childTlist = getTransformList(child); // some children might not have a transform (, , etc) if (!childTlist) { continue; } var _m2 = transformListToTransform(childTlist).matrix; // Convert a matrix to a scale if applicable // if (hasMatrixTransform(childTlist) && childTlist.numberOfItems == 1) { // if (m.b==0 && m.c==0 && m.e==0 && m.f==0) { // childTlist.removeItem(0); // const translateOrigin = svgroot.createSVGTransform(), // scale = svgroot.createSVGTransform(), // translateBack = svgroot.createSVGTransform(); // translateOrigin.setTranslate(0, 0); // scale.setScale(m.a, m.d); // translateBack.setTranslate(0, 0); // childTlist.appendItem(translateBack); // childTlist.appendItem(scale); // childTlist.appendItem(translateOrigin); // } // } var angle = getRotationAngle(child); oldStartTransform = context_.getStartTransform(); context_.setStartTransform(child.getAttribute('transform')); if (angle || hasMatrixTransform(childTlist)) { var e2t = svgroot.createSVGTransform(); e2t.setMatrix(matrixMultiply(tm, sm, tmn, _m2)); childTlist.clear(); childTlist.appendItem(e2t); // if not rotated or skewed, push the [T][S][-T] down to the child } else { // update the transform list with translate,scale,translate // slide the [T][S][-T] from the front to the back // [T][S][-T][M] = [M][T2][S2][-T2] // (only bringing [-T] to the right of [M]) // [T][S][-T][M] = [T][S][M][-T2] // [-T2] = [M_inv][-T][M] var t2n = matrixMultiply(_m2.inverse(), tmn, _m2); // [T2] is always negative translation of [-T2] var t2 = svgroot.createSVGMatrix(); t2.e = -t2n.e; t2.f = -t2n.f; // [T][S][-T][M] = [M][T2][S2][-T2] // [S2] = [T2_inv][M_inv][T][S][-T][M][-T2_inv] var s2 = matrixMultiply(t2.inverse(), _m2.inverse(), tm, sm, tmn, _m2, t2n.inverse()); var translateOrigin = svgroot.createSVGTransform(), scale = svgroot.createSVGTransform(), translateBack = svgroot.createSVGTransform(); translateOrigin.setTranslate(t2n.e, t2n.f); scale.setScale(s2.a, s2.d); translateBack.setTranslate(t2.e, t2.f); childTlist.appendItem(translateBack); childTlist.appendItem(scale); childTlist.appendItem(translateOrigin); // logMatrix(translateBack.matrix); // logMatrix(scale.matrix); } // not rotated batchCmd.addSubCommand(recalculateDimensions(child)); // TODO: If any have this group as a parent and are // referencing this child, then we need to impose a reverse // scale on it so that when it won't get double-translated // const uses = selected.getElementsByTagNameNS(NS.SVG, 'use'); // const href = '#' + child.id; // let u = uses.length; // while (u--) { // const useElem = uses.item(u); // if (href == getHref(useElem)) { // const usexlate = svgroot.createSVGTransform(); // usexlate.setTranslate(-tx,-ty); // getTransformList(useElem).insertItemBefore(usexlate,0); // batchCmd.addSubCommand( recalculateDimensions(useElem) ); // } // } context_.setStartTransform(oldStartTransform); } // element } // for each child // Remove these transforms from group tlist.removeItem(N - 1); tlist.removeItem(N - 2); tlist.removeItem(N - 3); } else if (N >= 3 && tlist.getItem(N - 1).type === 1) { operation = 3; // scale _m = transformListToTransform(tlist).matrix; var _e2t = svgroot.createSVGTransform(); _e2t.setMatrix(_m); tlist.clear(); tlist.appendItem(_e2t); // next, check if the first transform was a translate // if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ] // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] } else if ((N === 1 || N > 1 && tlist.getItem(1).type !== 3) && tlist.getItem(0).type === 2) { operation = 2; // translate var T_M = transformListToTransform(tlist).matrix; tlist.removeItem(0); var mInv = transformListToTransform(tlist).matrix.inverse(); var M2 = matrixMultiply(mInv, T_M); tx = M2.e; ty = M2.f; if (tx !== 0 || ty !== 0) { // we pass the translates down to the individual children var _children = selected.childNodes; var _c = _children.length; var clipPathsDone = []; while (_c--) { var _child = _children.item(_c); if (_child.nodeType === 1) { // Check if child has clip-path if (_child.getAttribute('clip-path')) { // tx, ty var attr = _child.getAttribute('clip-path'); if (!clipPathsDone.includes(attr)) { updateClipPath(attr, tx, ty); clipPathsDone.push(attr); } } oldStartTransform = context_.getStartTransform(); context_.setStartTransform(_child.getAttribute('transform')); var _childTlist = getTransformList(_child); // some children might not have a transform (, , etc) if (_childTlist) { var newxlate = svgroot.createSVGTransform(); newxlate.setTranslate(tx, ty); if (_childTlist.numberOfItems) { _childTlist.insertItemBefore(newxlate, 0); } else { _childTlist.appendItem(newxlate); } batchCmd.addSubCommand(recalculateDimensions(_child)); // If any have this group as a parent and are // referencing this child, then impose a reverse translate on it // so that when it won't get double-translated var uses = selected.getElementsByTagNameNS(NS.SVG, 'use'); var href = '#' + _child.id; var u = uses.length; while (u--) { var useElem = uses.item(u); if (href === getHref(useElem)) { var usexlate = svgroot.createSVGTransform(); usexlate.setTranslate(-tx, -ty); getTransformList(useElem).insertItemBefore(usexlate, 0); batchCmd.addSubCommand(recalculateDimensions(useElem)); } } context_.setStartTransform(oldStartTransform); } } } clipPathsDone = []; context_.setStartTransform(oldStartTransform); } // else, a matrix imposition from a parent group // keep pushing it down to the children } else if (N === 1 && tlist.getItem(0).type === 1 && !gangle) { operation = 1; var _m3 = tlist.getItem(0).matrix, _children2 = selected.childNodes; var _c2 = _children2.length; while (_c2--) { var _child2 = _children2.item(_c2); if (_child2.nodeType === 1) { oldStartTransform = context_.getStartTransform(); context_.setStartTransform(_child2.getAttribute('transform')); var _childTlist2 = getTransformList(_child2); if (!_childTlist2) { continue; } var em = matrixMultiply(_m3, transformListToTransform(_childTlist2).matrix); var e2m = svgroot.createSVGTransform(); e2m.setMatrix(em); _childTlist2.clear(); _childTlist2.appendItem(e2m, 0); batchCmd.addSubCommand(recalculateDimensions(_child2)); context_.setStartTransform(oldStartTransform); // Convert stroke // TODO: Find out if this should actually happen somewhere else var sw = _child2.getAttribute('stroke-width'); if (_child2.getAttribute('stroke') !== 'none' && !isNaN(sw)) { var avg = (Math.abs(em.a) + Math.abs(em.d)) / 2; _child2.setAttribute('stroke-width', sw * avg); } } } tlist.clear(); // else it was just a rotate } else { if (gangle) { var newRot = svgroot.createSVGTransform(); newRot.setRotate(gangle, newcenter.x, newcenter.y); if (tlist.numberOfItems) { tlist.insertItemBefore(newRot, 0); } else { tlist.appendItem(newRot); } } if (tlist.numberOfItems === 0) { selected.removeAttribute('transform'); } return null; } // if it was a translate, put back the rotate at the new center if (operation === 2) { if (gangle) { newcenter = { x: oldcenter.x + firstM.e, y: oldcenter.y + firstM.f }; var _newRot = svgroot.createSVGTransform(); _newRot.setRotate(gangle, newcenter.x, newcenter.y); if (tlist.numberOfItems) { tlist.insertItemBefore(_newRot, 0); } else { tlist.appendItem(_newRot); } } // if it was a resize } else if (operation === 3) { var _m4 = transformListToTransform(tlist).matrix; var roldt = svgroot.createSVGTransform(); roldt.setRotate(gangle, oldcenter.x, oldcenter.y); var rold = roldt.matrix; var rnew = svgroot.createSVGTransform(); rnew.setRotate(gangle, newcenter.x, newcenter.y); var rnewInv = rnew.matrix.inverse(), _mInv = _m4.inverse(), extrat = matrixMultiply(_mInv, rnewInv, rold, _m4); tx = extrat.e; ty = extrat.f; if (tx !== 0 || ty !== 0) { // now push this transform down to the children // we pass the translates down to the individual children var _children3 = selected.childNodes; var _c3 = _children3.length; while (_c3--) { var _child3 = _children3.item(_c3); if (_child3.nodeType === 1) { oldStartTransform = context_.getStartTransform(); context_.setStartTransform(_child3.getAttribute('transform')); var _childTlist3 = getTransformList(_child3); var _newxlate = svgroot.createSVGTransform(); _newxlate.setTranslate(tx, ty); if (_childTlist3.numberOfItems) { _childTlist3.insertItemBefore(_newxlate, 0); } else { _childTlist3.appendItem(_newxlate); } batchCmd.addSubCommand(recalculateDimensions(_child3)); context_.setStartTransform(oldStartTransform); } } } if (gangle) { if (tlist.numberOfItems) { tlist.insertItemBefore(rnew, 0); } else { tlist.appendItem(rnew); } } } // else, it's a non-group } else { // FIXME: box might be null for some elements ( etc), need to handle this var _box = getBBox(selected); // Paths (and possbly other shapes) will have no BBox while still in , // but we still may need to recalculate them (see issue 595). // TODO: Figure out how to get BBox from these elements in case they // have a rotation transform if (!_box && selected.tagName !== 'path') return null; var _m5 = svgroot.createSVGMatrix(); // temporarily strip off the rotate and save the old center var _angle = getRotationAngle(selected); if (_angle) { oldcenter = { x: _box.x + _box.width / 2, y: _box.y + _box.height / 2 }; newcenter = transformPoint(_box.x + _box.width / 2, _box.y + _box.height / 2, transformListToTransform(tlist).matrix); var _a = _angle * Math.PI / 180; var _s = Math.abs(_a) > 1.0e-10 ? Math.sin(_a) / (1 - Math.cos(_a)) // FIXME: This blows up if the angle is exactly 0! : 2 / _a; for (var _i2 = 0; _i2 < tlist.numberOfItems; ++_i2) { var _xform3 = tlist.getItem(_i2); if (_xform3.type === 4) { // extract old center through mystical arts var _rm = _xform3.matrix; oldcenter.y = (_s * _rm.e + _rm.f) / 2; oldcenter.x = (_rm.e - _s * _rm.f) / 2; tlist.removeItem(_i2); break; } } } // 2 = translate, 3 = scale, 4 = rotate, 1 = matrix imposition var _operation = 0; var _N = tlist.numberOfItems; // Check if it has a gradient with userSpaceOnUse, in which case // adjust it by recalculating the matrix transform. // TODO: Make this work in Webkit using svgedit.transformlist.SVGTransformList if (!isWebkit()) { var fill = selected.getAttribute('fill'); if (fill && fill.startsWith('url(')) { var paint = getRefElem(fill); var type = 'pattern'; if (paint.tagName !== type) type = 'gradient'; var attrVal = paint.getAttribute(type + 'Units'); if (attrVal === 'userSpaceOnUse') { // Update the userSpaceOnUse element _m5 = transformListToTransform(tlist).matrix; var gtlist = getTransformList(paint); var gmatrix = transformListToTransform(gtlist).matrix; _m5 = matrixMultiply(_m5, gmatrix); var mStr = 'matrix(' + [_m5.a, _m5.b, _m5.c, _m5.d, _m5.e, _m5.f].join(',') + ')'; paint.setAttribute(type + 'Transform', mStr); } } } // first, if it was a scale of a non-skewed element, then the second-last // transform will be the [S] // if we had [M][T][S][T] we want to extract the matrix equivalent of // [T][S][T] and push it down to the element if (_N >= 3 && tlist.getItem(_N - 2).type === 3 && tlist.getItem(_N - 3).type === 2 && tlist.getItem(_N - 1).type === 2) { // Removed this so a with a given [T][S][T] would convert to a matrix. // Is that bad? // && selected.nodeName != 'use' _operation = 3; // scale _m5 = transformListToTransform(tlist, _N - 3, _N - 1).matrix; tlist.removeItem(_N - 1); tlist.removeItem(_N - 2); tlist.removeItem(_N - 3); // if we had [T][S][-T][M], then this was a skewed element being resized // Thus, we simply combine it all into one matrix } else if (_N === 4 && tlist.getItem(_N - 1).type === 1) { _operation = 3; // scale _m5 = transformListToTransform(tlist).matrix; var _e2t2 = svgroot.createSVGTransform(); _e2t2.setMatrix(_m5); tlist.clear(); tlist.appendItem(_e2t2); // reset the matrix so that the element is not re-mapped _m5 = svgroot.createSVGMatrix(); // if we had [R][T][S][-T][M], then this was a rotated matrix-element // if we had [T1][M] we want to transform this into [M][T2] // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2] // down to the element } else if ((_N === 1 || _N > 1 && tlist.getItem(1).type !== 3) && tlist.getItem(0).type === 2) { _operation = 2; // translate var oldxlate = tlist.getItem(0).matrix, meq = transformListToTransform(tlist, 1).matrix, meqInv = meq.inverse(); _m5 = matrixMultiply(meqInv, oldxlate, meq); tlist.removeItem(0); // else if this child now has a matrix imposition (from a parent group) // we might be able to simplify } else if (_N === 1 && tlist.getItem(0).type === 1 && !_angle) { // Remap all point-based elements _m5 = transformListToTransform(tlist).matrix; switch (selected.tagName) { case 'line': changes = $$7(selected).attr(['x1', 'y1', 'x2', 'y2']); // Fallthrough case 'polyline': case 'polygon': changes.points = selected.getAttribute('points'); if (changes.points) { var _list = selected.points; var _len = _list.numberOfItems; changes.points = new Array(_len); for (var _i3 = 0; _i3 < _len; ++_i3) { var _pt = _list.getItem(_i3); changes.points[_i3] = { x: _pt.x, y: _pt.y }; } } // Fallthrough case 'path': changes.d = selected.getAttribute('d'); _operation = 1; tlist.clear(); break; default: break; } // if it was a rotation, put the rotate back and return without a command // (this function has zero work to do for a rotate()) } else { _operation = 4; // rotation if (_angle) { var _newRot2 = svgroot.createSVGTransform(); _newRot2.setRotate(_angle, newcenter.x, newcenter.y); if (tlist.numberOfItems) { tlist.insertItemBefore(_newRot2, 0); } else { tlist.appendItem(_newRot2); } } if (tlist.numberOfItems === 0) { selected.removeAttribute('transform'); } return null; } // if it was a translate or resize, we need to remap the element and absorb the xform if (_operation === 1 || _operation === 2 || _operation === 3) { remapElement(selected, changes, _m5); } // if we are remapping // if it was a translate, put back the rotate at the new center if (_operation === 2) { if (_angle) { if (!hasMatrixTransform(tlist)) { newcenter = { x: oldcenter.x + _m5.e, y: oldcenter.y + _m5.f }; } var _newRot3 = svgroot.createSVGTransform(); _newRot3.setRotate(_angle, newcenter.x, newcenter.y); if (tlist.numberOfItems) { tlist.insertItemBefore(_newRot3, 0); } else { tlist.appendItem(_newRot3); } } // We have special processing for tspans: Tspans are not transformable // but they can have x,y coordinates (sigh). Thus, if this was a translate, // on a text element, also translate any tspan children. if (selected.tagName === 'text') { var _children4 = selected.childNodes; var _c4 = _children4.length; while (_c4--) { var _child4 = _children4.item(_c4); if (_child4.tagName === 'tspan') { var tspanChanges = { x: $$7(_child4).attr('x') || 0, y: $$7(_child4).attr('y') || 0 }; remapElement(_child4, tspanChanges, _m5); } } } // [Rold][M][T][S][-T] became [Rold][M] // we want it to be [Rnew][M][Tr] where Tr is the // translation required to re-center it // Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M] } else if (_operation === 3 && _angle) { var _m6 = transformListToTransform(tlist).matrix; var _roldt = svgroot.createSVGTransform(); _roldt.setRotate(_angle, oldcenter.x, oldcenter.y); var _rold = _roldt.matrix; var _rnew = svgroot.createSVGTransform(); _rnew.setRotate(_angle, newcenter.x, newcenter.y); var _rnewInv = _rnew.matrix.inverse(); var _mInv2 = _m6.inverse(); var _extrat = matrixMultiply(_mInv2, _rnewInv, _rold, _m6); remapElement(selected, changes, _extrat); if (_angle) { if (tlist.numberOfItems) { tlist.insertItemBefore(_rnew, 0); } else { tlist.appendItem(_rnew); } } } } // a non-group // if the transform list has been emptied, remove it if (tlist.numberOfItems === 0) { selected.removeAttribute('transform'); } batchCmd.addSubCommand(new ChangeElementCommand(selected, initial)); return batchCmd; }; /* globals jQuery */ var $$8 = jQuery; var svgFactory_ = void 0; var config_ = void 0; var selectorManager_ = void 0; // A Singleton var gripRadius = isTouch() ? 10 : 4; /** * Private class for DOM element selection boxes * @param id - integer to internally indentify the selector * @param elem - DOM element associated with this selector * @param bbox - Optional bbox to use for initialization (prevents duplicate getBBox call). */ var Selector = function () { function Selector(id, elem, bbox) { classCallCheck(this, Selector); // this is the selector's unique number this.id = id; // this holds a reference to the element for which this selector is being used this.selectedElement = elem; // this is a flag used internally to track whether the selector is being used or not this.locked = true; // this holds a reference to the element that holds all visual elements of the selector this.selectorGroup = svgFactory_.createSVGElement({ element: 'g', attr: { id: 'selectorGroup' + this.id } }); // this holds a reference to the path rect this.selectorRect = this.selectorGroup.appendChild(svgFactory_.createSVGElement({ element: 'path', attr: { id: 'selectedBox' + this.id, fill: 'none', stroke: '#22C', 'stroke-width': '1', 'stroke-dasharray': '5,5', // need to specify this so that the rect is not selectable style: 'pointer-events:none' } })); // this holds a reference to the grip coordinates for this selector this.gripCoords = { nw: null, n: null, ne: null, e: null, se: null, s: null, sw: null, w: null }; this.reset(this.selectedElement, bbox); } /** * Used to reset the id and element that the selector is attached to * @param e - DOM element associated with this selector * @param bbox - Optional bbox to use for reset (prevents duplicate getBBox call). */ createClass(Selector, [{ key: 'reset', value: function reset(e, bbox) { this.locked = true; this.selectedElement = e; this.resize(bbox); this.selectorGroup.setAttribute('display', 'inline'); } /** * Updates cursors for corner grips on rotation so arrows point the right way * @param {Number} angle - Float indicating current rotation angle in degrees */ }, { key: 'updateGripCursors', value: function updateGripCursors(angle) { var dir = void 0; var dirArr = []; var steps = Math.round(angle / 45); if (steps < 0) { steps += 8; } for (dir in selectorManager_.selectorGrips) { dirArr.push(dir); } while (steps > 0) { dirArr.push(dirArr.shift()); steps--; } var i = 0; for (dir in selectorManager_.selectorGrips) { selectorManager_.selectorGrips[dir].setAttribute('style', 'cursor:' + dirArr[i] + '-resize'); i++; } } /** * Show the resize grips of this selector * * @param {Boolean} show - Indicates whether grips should be shown or not */ }, { key: 'showGrips', value: function showGrips(show) { var bShow = show ? 'inline' : 'none'; selectorManager_.selectorGripsGroup.setAttribute('display', bShow); var elem = this.selectedElement; this.hasGrips = show; if (elem && show) { this.selectorGroup.append(selectorManager_.selectorGripsGroup); this.updateGripCursors(getRotationAngle(elem)); } } /** * Updates the selector to match the element's size * @param bbox - Optional bbox to use for resize (prevents duplicate getBBox call). */ }, { key: 'resize', value: function resize(bbox) { var selectedBox = this.selectorRect, mgr = selectorManager_, selectedGrips = mgr.selectorGrips, selected = this.selectedElement, sw = selected.getAttribute('stroke-width'), currentZoom = svgFactory_.getCurrentZoom(); var offset = 1 / currentZoom; if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) { offset += sw / 2; } var tagName = selected.tagName; if (tagName === 'text') { offset += 2 / currentZoom; } // loop and transform our bounding box until we reach our first rotation var tlist = getTransformList(selected); var m = transformListToTransform(tlist).matrix; // This should probably be handled somewhere else, but for now // it keeps the selection box correctly positioned when zoomed m.e *= currentZoom; m.f *= currentZoom; if (!bbox) { bbox = getBBox(selected); } // TODO: getBBox (previous line) already knows to call getStrokedBBox when tagName === 'g'. Remove this? // TODO: getBBox doesn't exclude 'gsvg' and calls getStrokedBBox for any 'g'. Should getBBox be updated? if (tagName === 'g' && !$$8.data(selected, 'gsvg')) { // The bbox for a group does not include stroke vals, so we // get the bbox based on its children. var strokedBbox = getStrokedBBox([selected.childNodes]); if (strokedBbox) { bbox = strokedBbox; } } // apply the transforms var l = bbox.x, t = bbox.y, w = bbox.width, h = bbox.height; bbox = { x: l, y: t, width: w, height: h }; // we need to handle temporary transforms too // if skewed, get its transformed box, then find its axis-aligned bbox // * offset *= currentZoom; var nbox = transformBox(l * currentZoom, t * currentZoom, w * currentZoom, h * currentZoom, m), aabox = nbox.aabox; var nbax = aabox.x - offset, nbay = aabox.y - offset, nbaw = aabox.width + offset * 2, nbah = aabox.height + offset * 2; // now if the shape is rotated, un-rotate it var cx = nbax + nbaw / 2, cy = nbay + nbah / 2; var angle = getRotationAngle(selected); if (angle) { var rot = svgFactory_.svgRoot().createSVGTransform(); rot.setRotate(-angle, cx, cy); var rotm = rot.matrix; nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm); nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm); nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm); nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm); // calculate the axis-aligned bbox var tl = nbox.tl; var minx = tl.x, miny = tl.y, maxx = tl.x, maxy = tl.y; var min = Math.min, max = Math.max; minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset; miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset; maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset; maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset; nbax = minx; nbay = miny; nbaw = maxx - minx; nbah = maxy - miny; } var dstr = 'M' + nbax + ',' + nbay + ' L' + (nbax + nbaw) + ',' + nbay + ' ' + (nbax + nbaw) + ',' + (nbay + nbah) + ' ' + nbax + ',' + (nbay + nbah) + 'z'; selectedBox.setAttribute('d', dstr); var xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''; this.selectorGroup.setAttribute('transform', xform); // TODO(codedread): Is this needed? // if (selected === selectedElements[0]) { this.gripCoords = { nw: [nbax, nbay], ne: [nbax + nbaw, nbay], sw: [nbax, nbay + nbah], se: [nbax + nbaw, nbay + nbah], n: [nbax + nbaw / 2, nbay], w: [nbax, nbay + nbah / 2], e: [nbax + nbaw, nbay + nbah / 2], s: [nbax + nbaw / 2, nbay + nbah] }; for (var dir in this.gripCoords) { var coords = this.gripCoords[dir]; selectedGrips[dir].setAttribute('cx', coords[0]); selectedGrips[dir].setAttribute('cy', coords[1]); } // we want to go 20 pixels in the negative transformed y direction, ignoring scale mgr.rotateGripConnector.setAttribute('x1', nbax + nbaw / 2); mgr.rotateGripConnector.setAttribute('y1', nbay); mgr.rotateGripConnector.setAttribute('x2', nbax + nbaw / 2); mgr.rotateGripConnector.setAttribute('y2', nbay - gripRadius * 5); mgr.rotateGrip.setAttribute('cx', nbax + nbaw / 2); mgr.rotateGrip.setAttribute('cy', nbay - gripRadius * 5); // } } }]); return Selector; }(); /** * */ var SelectorManager = function () { function SelectorManager() { classCallCheck(this, SelectorManager); // this will hold the element that contains all selector rects/grips this.selectorParentGroup = null; // this is a special rect that is used for multi-select this.rubberBandBox = null; // this will hold objects of type Selector (see above) this.selectors = []; // this holds a map of SVG elements to their Selector object this.selectorMap = {}; // this holds a reference to the grip elements this.selectorGrips = { nw: null, n: null, ne: null, e: null, se: null, s: null, sw: null, w: null }; this.selectorGripsGroup = null; this.rotateGripConnector = null; this.rotateGrip = null; this.initGroup(); } /** * Resets the parent selector group element */ createClass(SelectorManager, [{ key: 'initGroup', value: function initGroup() { // remove old selector parent group if it existed if (this.selectorParentGroup && this.selectorParentGroup.parentNode) { this.selectorParentGroup.remove(); } // create parent selector group and add it to svgroot this.selectorParentGroup = svgFactory_.createSVGElement({ element: 'g', attr: { id: 'selectorParentGroup' } }); this.selectorGripsGroup = svgFactory_.createSVGElement({ element: 'g', attr: { display: 'none' } }); this.selectorParentGroup.append(this.selectorGripsGroup); svgFactory_.svgRoot().append(this.selectorParentGroup); this.selectorMap = {}; this.selectors = []; this.rubberBandBox = null; // add the corner grips for (var dir in this.selectorGrips) { var grip = svgFactory_.createSVGElement({ element: 'circle', attr: { id: 'selectorGrip_resize_' + dir, fill: '#22C', r: gripRadius, style: 'cursor:' + dir + '-resize', // This expands the mouse-able area of the grips making them // easier to grab with the mouse. // This works in Opera and WebKit, but does not work in Firefox // see https://bugzilla.mozilla.org/show_bug.cgi?id=500174 'stroke-width': 2, 'pointer-events': 'all' } }); $$8.data(grip, 'dir', dir); $$8.data(grip, 'type', 'resize'); this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip); } // add rotator elems this.rotateGripConnector = this.selectorGripsGroup.appendChild(svgFactory_.createSVGElement({ element: 'line', attr: { id: 'selectorGrip_rotateconnector', stroke: '#22C', 'stroke-width': '1' } })); this.rotateGrip = this.selectorGripsGroup.appendChild(svgFactory_.createSVGElement({ element: 'circle', attr: { id: 'selectorGrip_rotate', fill: 'lime', r: gripRadius, stroke: '#22C', 'stroke-width': 2, style: 'cursor:url(' + config_.imgPath + 'rotate.png) 12 12, auto;' } })); $$8.data(this.rotateGrip, 'type', 'rotate'); if ($$8('#canvasBackground').length) { return; } var dims = config_.dimensions; var canvasbg = svgFactory_.createSVGElement({ element: 'svg', attr: { id: 'canvasBackground', width: dims[0], height: dims[1], x: 0, y: 0, overflow: isWebkit() ? 'none' : 'visible', // Chrome 7 has a problem with this when zooming out style: 'pointer-events:none' } }); var rect = svgFactory_.createSVGElement({ element: 'rect', attr: { width: '100%', height: '100%', x: 0, y: 0, 'stroke-width': 1, stroke: '#000', fill: '#FFF', style: 'pointer-events:none' } }); // Both Firefox and WebKit are too slow with this filter region (especially at higher // zoom levels) and Opera has at least one bug // if (!isOpera()) rect.setAttribute('filter', 'url(#canvashadow)'); canvasbg.append(rect); svgFactory_.svgRoot().insertBefore(canvasbg, svgFactory_.svgContent()); // Ok to replace above with `svgFactory_.svgContent().before(canvasbg);`? } /** * * @param elem - DOM element to get the selector for * @param [bbox] - Optional bbox to use for reset (prevents duplicate getBBox call). * @returns The selector based on the given element */ }, { key: 'requestSelector', value: function requestSelector(elem, bbox) { if (elem == null) { return null; } var N = this.selectors.length; // If we've already acquired one for this element, return it. if (_typeof(this.selectorMap[elem.id]) === 'object') { this.selectorMap[elem.id].locked = true; return this.selectorMap[elem.id]; } for (var i = 0; i < N; ++i) { if (this.selectors[i] && !this.selectors[i].locked) { this.selectors[i].locked = true; this.selectors[i].reset(elem, bbox); this.selectorMap[elem.id] = this.selectors[i]; return this.selectors[i]; } } // if we reached here, no available selectors were found, we create one this.selectors[N] = new Selector(N, elem, bbox); this.selectorParentGroup.append(this.selectors[N].selectorGroup); this.selectorMap[elem.id] = this.selectors[N]; return this.selectors[N]; } /** * Removes the selector of the given element (hides selection box) * * @param elem - DOM element to remove the selector for */ }, { key: 'releaseSelector', value: function releaseSelector(elem) { if (elem == null) { return; } var N = this.selectors.length, sel = this.selectorMap[elem.id]; if (!sel.locked) { // TODO(codedread): Ensure this exists in this module. console.log('WARNING! selector was released but was already unlocked'); } for (var i = 0; i < N; ++i) { if (this.selectors[i] && this.selectors[i] === sel) { delete this.selectorMap[elem.id]; sel.locked = false; sel.selectedElement = null; sel.showGrips(false); // remove from DOM and store reference in JS but only if it exists in the DOM try { sel.selectorGroup.setAttribute('display', 'none'); } catch (e) {} break; } } } /** * @returns The rubberBandBox DOM element. This is the rectangle drawn by * the user for selecting/zooming */ }, { key: 'getRubberBandBox', value: function getRubberBandBox() { if (!this.rubberBandBox) { this.rubberBandBox = this.selectorParentGroup.appendChild(svgFactory_.createSVGElement({ element: 'rect', attr: { id: 'selectorRubberBand', fill: '#22C', 'fill-opacity': 0.15, stroke: '#22C', 'stroke-width': 0.5, display: 'none', style: 'pointer-events:none' } })); } return this.rubberBandBox; } }]); return SelectorManager; }(); /** * An object that creates SVG elements for the canvas. * * interface svgedit.select.SVGFactory { * SVGElement createSVGElement(jsonMap); * SVGSVGElement svgRoot(); * SVGSVGElement svgContent(); * * Number currentZoom(); * } */ /** * Initializes this module. * * @param config - An object containing configurable parameters (imgPath) * @param svgFactory - An object implementing the SVGFactory interface (see above). */ var init$6 = function init(config, svgFactory) { config_ = config; svgFactory_ = svgFactory; selectorManager_ = new SelectorManager(); }; /** * * @returns The SelectorManager instance. */ var getSelectorManager = function getSelectorManager() { return selectorManager_; }; /* eslint-disable indent */ var $$9 = jqPluginSVG(jQuery); var MoveElementCommand$1 = MoveElementCommand, InsertElementCommand$1 = InsertElementCommand, RemoveElementCommand$1 = RemoveElementCommand, ChangeElementCommand$1 = ChangeElementCommand, BatchCommand$1 = BatchCommand, UndoManager$1 = UndoManager, HistoryEventTypes$1 = HistoryEventTypes; if (!window.console) { window.console = {}; window.console.log = function (str) {}; window.console.dir = function (str) {}; } if (window.opera) { window.console.log = function (str) { opera.postError(str); }; window.console.dir = function (str) {}; } /** * The main SvgCanvas class that manages all SVG-related functions * @param container - The container HTML element that should hold the SVG root element * @param {Object} config - An object that contains configuration data */ var SvgCanvas = function SvgCanvas(container, config) { classCallCheck(this, SvgCanvas); // Alias Namespace constants // Default configuration options var curConfig = { show_outside_canvas: true, selectNew: true, dimensions: [640, 480] }; // Update config with new one if given if (config) { $$9.extend(curConfig, config); } // Array with width/height of canvas var dimensions = curConfig.dimensions; var canvas = this; // "document" element associated with the container (same as window.document using default svg-editor.js) // NOTE: This is not actually a SVG document, but an HTML document. var svgdoc = container.ownerDocument; // This is a container for the document being edited, not the document itself. var svgroot = svgdoc.importNode(text2xml('' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '').documentElement, true); container.append(svgroot); // The actual element that represents the final output SVG element var svgcontent = svgdoc.createElementNS(NS.SVG, 'svg'); // This function resets the svgcontent element while keeping it in the DOM. var clearSvgContentElement = canvas.clearSvgContentElement = function () { $$9(svgcontent).empty(); // TODO: Clear out all other attributes first? $$9(svgcontent).attr({ id: 'svgcontent', width: dimensions[0], height: dimensions[1], x: dimensions[0], y: dimensions[1], overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden', xmlns: NS.SVG, 'xmlns:se': NS.SE, 'xmlns:xlink': NS.XLINK }).appendTo(svgroot); // TODO: make this string optional and set by the client var comment = svgdoc.createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit'); svgcontent.append(comment); }; clearSvgContentElement(); // Prefix string for element IDs var idprefix = 'svg_'; /** * Changes the ID prefix to the given value * @param {String} p - String with the new prefix */ canvas.setIdPrefix = function (p) { idprefix = p; }; // Current svgedit.draw.Drawing object // @type {svgedit.draw.Drawing} canvas.current_drawing_ = new Drawing(svgcontent, idprefix); /** * Returns the current Drawing. * @returns {svgedit.draw.Drawing} */ var getCurrentDrawing = canvas.getCurrentDrawing = function () { return canvas.current_drawing_; }; // Float displaying the current zoom level (1 = 100%, .5 = 50%, etc) var currentZoom = 1; // pointer to current group (for in-group editing) var currentGroup = null; // Object containing data for the currently selected styles var allProperties = { shape: { fill: (curConfig.initFill.color === 'none' ? '' : '#') + curConfig.initFill.color, fill_paint: null, fill_opacity: curConfig.initFill.opacity, stroke: '#' + curConfig.initStroke.color, stroke_paint: null, stroke_opacity: curConfig.initStroke.opacity, stroke_width: curConfig.initStroke.width, stroke_dasharray: 'none', stroke_linejoin: 'miter', stroke_linecap: 'butt', opacity: curConfig.initOpacity } }; allProperties.text = $$9.extend(true, {}, allProperties.shape); $$9.extend(allProperties.text, { fill: '#000000', stroke_width: curConfig.text && curConfig.text.stroke_width, font_size: curConfig.text && curConfig.text.font_size, font_family: curConfig.text && curConfig.text.font_family }); // Current shape style properties var curShape = allProperties.shape; // Array with all the currently selected elements // default size of 1 until it needs to grow bigger var selectedElements = []; var getJsonFromSvgElement = this.getJsonFromSvgElement = function (data) { // Text node if (data.nodeType === 3) return data.nodeValue; var retval = { element: data.tagName, // namespace: nsMap[data.namespaceURI], attr: {}, children: [] }; // Iterate attributes for (var i = 0, attr; attr = data.attributes[i]; i++) { retval.attr[attr.name] = attr.value; } // Iterate children for (var _i = 0, node; node = data.childNodes[_i]; _i++) { retval.children[_i] = getJsonFromSvgElement(node); } return retval; }; /** * Create a new SVG element based on the given object keys/values and add it to the current layer * The element will be ran through cleanupElement before being returned * * @param data - Object with the following keys/values: * @param {String} data.element - tag name of the SVG element to create * @param {Object} data.attr - Has key-value attributes to assign to the new element * @param {Boolean} [data.curStyles] - Indicates whether current style attributes should be applied first * @param {Array} [data.children] - Data objects to be added recursively as children * @param {String} [data.namespace="http://www.w3.org/2000/svg"] - Indicate a (non-SVG) namespace * * @returns The new element */ var addSvgElementFromJson = this.addSvgElementFromJson = function (data) { if (typeof data === 'string') return svgdoc.createTextNode(data); var shape = getElem(data.attr.id); // if shape is a path but we need to create a rect/ellipse, then remove the path var currentLayer = getCurrentDrawing().getCurrentLayer(); if (shape && data.element !== shape.tagName) { shape.remove(); shape = null; } if (!shape) { var ns = data.namespace || NS.SVG; shape = svgdoc.createElementNS(ns, data.element); if (currentLayer) { (currentGroup || currentLayer).append(shape); } } if (data.curStyles) { assignAttributes(shape, { fill: curShape.fill, stroke: curShape.stroke, 'stroke-width': curShape.stroke_width, 'stroke-dasharray': curShape.stroke_dasharray, 'stroke-linejoin': curShape.stroke_linejoin, 'stroke-linecap': curShape.stroke_linecap, 'stroke-opacity': curShape.stroke_opacity, 'fill-opacity': curShape.fill_opacity, opacity: curShape.opacity / 2, style: 'pointer-events:inherit' }, 100); } assignAttributes(shape, data.attr, 100); cleanupElement(shape); // Children if (data.children) { data.children.forEach(function (child) { shape.append(addSvgElementFromJson(child)); }); } return shape; }; canvas.getTransformList = getTransformList; canvas.matrixMultiply = matrixMultiply; canvas.hasMatrixTransform = hasMatrixTransform; canvas.transformListToTransform = transformListToTransform; // initialize from units.js // send in an object implementing the ElementContainer interface (see units.js) init({ getBaseUnit: function getBaseUnit() { return curConfig.baseUnit; }, getElement: getElem, getHeight: function getHeight() { return svgcontent.getAttribute('height') / currentZoom; }, getWidth: function getWidth() { return svgcontent.getAttribute('width') / currentZoom; }, getRoundDigits: function getRoundDigits() { return saveOptions.round_digits; } }); canvas.convertToNum = convertToNum; var getSVGContent = function getSVGContent() { return svgcontent; }; /** * @returns {Array} the array with selected DOM elements */ var getSelectedElements = this.getSelectedElems = function () { return selectedElements; }; var pathActions$$1 = pathActions; init$2({ pathActions: pathActions$$1, // Ok since not modifying getSVGContent: getSVGContent, addSvgElementFromJson: addSvgElementFromJson, getSelectedElements: getSelectedElements, getDOMDocument: function getDOMDocument() { return svgdoc; }, getDOMContainer: function getDOMContainer() { return container; }, getSVGRoot: function getSVGRoot() { return svgroot; }, // TODO: replace this mostly with a way to get the current drawing. getBaseUnit: function getBaseUnit() { return curConfig.baseUnit; }, getSnappingStep: function getSnappingStep() { return curConfig.snappingStep; } }); canvas.findDefs = findDefs; canvas.getUrlFromAttr = getUrlFromAttr; canvas.getHref = getHref; canvas.setHref = setHref; /* const getBBox = */canvas.getBBox = getBBox; canvas.getRotationAngle = getRotationAngle; canvas.getElem = getElem; canvas.getRefElem = getRefElem; canvas.assignAttributes = assignAttributes; this.cleanupElement = cleanupElement; var getGridSnapping = function getGridSnapping() { return curConfig.gridSnapping; }; init$4({ getDrawing: function getDrawing() { return getCurrentDrawing(); }, getSVGRoot: function getSVGRoot() { return svgroot; }, getGridSnapping: getGridSnapping }); this.remapElement = remapElement; init$5({ getSVGRoot: function getSVGRoot() { return svgroot; }, getStartTransform: function getStartTransform() { return startTransform; }, setStartTransform: function setStartTransform(transform) { startTransform = transform; } }); this.recalculateDimensions = recalculateDimensions; // import from sanitize.js var nsMap = getReverseNS(); canvas.sanitizeSvg = sanitizeSvg; // Implement the svgedit.history.HistoryEventHandler interface. var undoMgr = canvas.undoMgr = new UndoManager$1({ handleHistoryEvent: function handleHistoryEvent(eventType, cmd) { var EventTypes = HistoryEventTypes$1; // TODO: handle setBlurOffsets. if (eventType === EventTypes.BEFORE_UNAPPLY || eventType === EventTypes.BEFORE_APPLY) { canvas.clearSelection(); } else if (eventType === EventTypes.AFTER_APPLY || eventType === EventTypes.AFTER_UNAPPLY) { var elems = cmd.elements(); canvas.pathActions.clear(); call('changed', elems); var cmdType = cmd.type(); var isApply = eventType === EventTypes.AFTER_APPLY; if (cmdType === MoveElementCommand$1.type()) { var parent = isApply ? cmd.newParent : cmd.oldParent; if (parent === svgcontent) { identifyLayers(); } } else if (cmdType === InsertElementCommand$1.type() || cmdType === RemoveElementCommand$1.type()) { if (cmd.parent === svgcontent) { identifyLayers(); } if (cmdType === InsertElementCommand$1.type()) { if (isApply) { restoreRefElems(cmd.elem); } } else { if (!isApply) { restoreRefElems(cmd.elem); } } if (cmd.elem.tagName === 'use') { setUseData(cmd.elem); } } else if (cmdType === ChangeElementCommand$1.type()) { // if we are changing layer names, re-identify all layers if (cmd.elem.tagName === 'title' && cmd.elem.parentNode.parentNode === svgcontent) { identifyLayers(); } var values = isApply ? cmd.newValues : cmd.oldValues; // If stdDeviation was changed, update the blur. if (values.stdDeviation) { canvas.setBlurOffsets(cmd.elem.parentNode, values.stdDeviation); } // This is resolved in later versions of webkit, perhaps we should // have a featured detection for correct 'use' behavior? // —————————— // Remove & Re-add hack for Webkit (issue 775) // if (cmd.elem.tagName === 'use' && isWebkit()) { // const {elem} = cmd; // if (!elem.getAttribute('x') && !elem.getAttribute('y')) { // const parent = elem.parentNode; // const sib = elem.nextSibling; // elem.remove(); // parent.insertBefore(elem, sib); // // Ok to replace above with this? `sib.before(elem);` // } // } } } } }); var addCommandToHistory = function addCommandToHistory(cmd) { canvas.undoMgr.addCommandToHistory(cmd); }; /** * @returns The current zoom level */ var getCurrentZoom = this.getZoom = function () { return currentZoom; }; /** * This method rounds the incoming value to the nearest value based on the `currentZoom` * @param {Number} val * @returns Rounded value to nearest value based on `currentZoom` */ var round = this.round = function (val) { return parseInt(val * currentZoom, 10) / currentZoom; }; // import from select.js init$6(curConfig, { createSVGElement: function createSVGElement(jsonMap) { return canvas.addSvgElementFromJson(jsonMap); }, svgRoot: function svgRoot() { return svgroot; }, svgContent: function svgContent() { return svgcontent; }, getCurrentZoom: getCurrentZoom }); // this object manages selectors for us var selectorManager = this.selectorManager = getSelectorManager(); var getNextId = canvas.getNextId = function () { return getCurrentDrawing().getNextId(); }; var getId = canvas.getId = function () { return getCurrentDrawing().getId(); }; /** * Run the callback function associated with the given event * @param ev - String with the event name * @param arg - Argument to pass through to the callback function */ var call = function call(ev, arg) { if (events[ev]) { return events[ev](window, arg); } }; /** * Clears the selection. The 'selected' handler is then called. * @param {Boolean} [noCall] - When true does not call the "selected" handler */ var clearSelection = function clearSelection(noCall) { selectedElements.map(function (elem) { if (elem == null) return; selectorManager.releaseSelector(elem); }); selectedElements = []; if (!noCall) { call('selected', selectedElements); } }; /** * Adds a list of elements to the selection. The 'selected' handler is then called. * @param {Array} elemsToAdd - An array of DOM elements to add to the selection * @param {Boolean} showGrips - Indicates whether the resize grips should be shown */ var addToSelection = function addToSelection(elemsToAdd, showGrips) { if (!elemsToAdd.length) { return; } // find the first null in our selectedElements array var j = 0; while (j < selectedElements.length) { if (selectedElements[j] == null) { break; } ++j; } // now add each element consecutively var i = elemsToAdd.length; while (i--) { var elem = elemsToAdd[i]; if (!elem) { continue; } var bbox = getBBox(elem); if (!bbox) { continue; } if (elem.tagName === 'a' && elem.childNodes.length === 1) { // Make "a" element's child be the selected element elem = elem.firstChild; } // if it's not already there, add it if (!selectedElements.includes(elem)) { selectedElements[j] = elem; // only the first selectedBBoxes element is ever used in the codebase these days // if (j === 0) selectedBBoxes[0] = utilsGetBBox(elem); j++; var sel = selectorManager.requestSelector(elem, bbox); if (selectedElements.length > 1) { sel.showGrips(false); } } } call('selected', selectedElements); if (showGrips || selectedElements.length === 1) { selectorManager.requestSelector(selectedElements[0]).showGrips(true); } else { selectorManager.requestSelector(selectedElements[0]).showGrips(false); } // make sure the elements are in the correct order // See: https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition selectedElements.sort(function (a, b) { if (a && b && a.compareDocumentPosition) { return 3 - (b.compareDocumentPosition(a) & 6); } if (a == null) { return 1; } }); // Make sure first elements are not null while (selectedElements[0] == null) { selectedElements.shift(0); } }; var getOpacity = function getOpacity() { return curShape.opacity; }; /** * Gets the desired element from a mouse event * @param evt - Event object from the mouse event * @returns DOM element we want */ var getMouseTarget = this.getMouseTarget = function (evt) { if (evt == null) { return null; } var mouseTarget = evt.target; // if it was a , Opera and WebKit return the SVGElementInstance if (mouseTarget.correspondingUseElement) { mouseTarget = mouseTarget.correspondingUseElement; } // for foreign content, go up until we find the foreignObject // WebKit browsers set the mouse target to the svgcanvas div if ([NS.MATH, NS.HTML].includes(mouseTarget.namespaceURI) && mouseTarget.id !== 'svgcanvas') { while (mouseTarget.nodeName !== 'foreignObject') { mouseTarget = mouseTarget.parentNode; if (!mouseTarget) { return svgroot; } } } // Get the desired mouseTarget with jQuery selector-fu // If it's root-like, select the root var currentLayer = getCurrentDrawing().getCurrentLayer(); if ([svgroot, container, svgcontent, currentLayer].includes(mouseTarget)) { return svgroot; } var $target = $$9(mouseTarget); // If it's a selection grip, return the grip parent if ($target.closest('#selectorParentGroup').length) { // While we could instead have just returned mouseTarget, // this makes it easier to indentify as being a selector grip return selectorManager.selectorParentGroup; } while (mouseTarget.parentNode !== (currentGroup || currentLayer)) { mouseTarget = mouseTarget.parentNode; } // // // go up until we hit a child of a layer // while (mouseTarget.parentNode.parentNode.tagName == 'g') { // mouseTarget = mouseTarget.parentNode; // } // Webkit bubbles the mouse event all the way up to the div, so we // set the mouseTarget to the svgroot like the other browsers // if (mouseTarget.nodeName.toLowerCase() == 'div') { // mouseTarget = svgroot; // } return mouseTarget; }; canvas.pathActions = pathActions$$1; function resetD(p) { p.setAttribute('d', pathActions$$1.convertPath(p)); } init$1({ selectorManager: selectorManager, // Ok since not changing canvas: canvas, // Ok since not changing call: call, resetD: resetD, round: round, clearSelection: clearSelection, addToSelection: addToSelection, addCommandToHistory: addCommandToHistory, remapElement: remapElement, addSvgElementFromJson: addSvgElementFromJson, getGridSnapping: getGridSnapping, getOpacity: getOpacity, getSelectedElements: getSelectedElements, getContainer: function getContainer() { return container; }, setStarted: function setStarted(s) { started = s; }, getRubberBox: function getRubberBox() { return rubberBox; }, setRubberBox: function setRubberBox(rb) { rubberBox = rb; return rubberBox; }, addPtsToSelection: function addPtsToSelection(_ref) { var closedSubpath = _ref.closedSubpath, grips = _ref.grips; // TODO: Correct this: pathActions$$1.canDeleteNodes = true; pathActions$$1.closed_subpath = closedSubpath; call('pointsAdded', { closedSubpath: closedSubpath, grips: grips }); call('selected', grips); }, endChanges: function endChanges(_ref2) { var cmd = _ref2.cmd, elem = _ref2.elem; addCommandToHistory(cmd); call('changed', [elem]); }, getCurrentZoom: getCurrentZoom, getId: getId, getNextId: getNextId, getMouseTarget: getMouseTarget, getCurrentMode: function getCurrentMode() { return currentMode; }, setCurrentMode: function setCurrentMode(cm) { currentMode = cm; return currentMode; }, getDrawnPath: function getDrawnPath() { return drawnPath; }, setDrawnPath: function setDrawnPath(dp) { drawnPath = dp; return drawnPath; }, getSVGRoot: function getSVGRoot() { return svgroot; } }); // Interface strings, usually for title elements var uiStrings = {}; var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'; var refAttrs = ['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke']; var elData = $$9.data; // Animation element to change the opacity of any newly created element var opacAni = document.createElementNS(NS.SVG, 'animate'); $$9(opacAni).attr({ attributeName: 'opacity', begin: 'indefinite', dur: 1, fill: 'freeze' }).appendTo(svgroot); var restoreRefElems = function restoreRefElems(elem) { // Look for missing reference elements, restore any found var attrs = $$9(elem).attr(refAttrs); for (var o in attrs) { var val = attrs[o]; if (val && val.startsWith('url(')) { var id = getUrlFromAttr(val).substr(1); var ref = getElem(id); if (!ref) { findDefs().append(removedElements[id]); delete removedElements[id]; } } } var childs = elem.getElementsByTagName('*'); if (childs.length) { for (var i = 0, l = childs.length; i < l; i++) { restoreRefElems(childs[i]); } } }; // (function () { // TODO For Issue 208: this is a start on a thumbnail // const svgthumb = svgdoc.createElementNS(NS.SVG, 'use'); // svgthumb.setAttribute('width', '100'); // svgthumb.setAttribute('height', '100'); // setHref(svgthumb, '#svgcontent'); // svgroot.append(svgthumb); // }()); // Object to contain image data for raster images that were found encodable var encodableImages = {}, // Object with save options saveOptions = { round_digits: 5 }, // Object with IDs for imported files, to see if one was already added importIds = {}, // Current text style properties curText = allProperties.text, // Object to contain all included extensions extensions = {}, // Map of deleted reference elements removedElements = {}; var // String with image URL of last loadable image lastGoodImgUrl = curConfig.imgPath + 'logo.png', // Boolean indicating whether or not a draw action has been started started = false, // String with an element's initial transform attribute value startTransform = null, // String indicating the current editor mode currentMode = 'select', // String with the current direction in which an element is being resized currentResizeMode = 'none', // Current general properties curProperties = curShape, // Array with selected elements' Bounding box object // selectedBBoxes = new Array(1), // The DOM element that was just selected justSelected = null, // DOM element for selection rectangle drawn by the user rubberBox = null, // Array of current BBoxes, used in getIntersectionList(). curBBoxes = [], // Canvas point for the most recent right click lastClickPoint = null; // Should this return an array by default, so extension results aren't overwritten? var runExtensions = this.runExtensions = function (action, vars, returnArray) { var result = returnArray ? [] : false; $$9.each(extensions, function (name, opts) { if (opts && action in opts) { if (returnArray) { result.push(opts[action](vars)); } else { result = opts[action](vars); } } }); return result; }; /** * Add an extension to the editor * @param {String} name - String with the ID of the extension * @param {Function} extFunc - Function supplied by the extension with its data */ this.addExtension = function (name, extFunc) { var ext = void 0; if (!(name in extensions)) { // Provide private vars/funcs here. Is there a better way to do this? var argObj = $$9.extend(canvas.getPrivateMethods(), { svgroot: svgroot, svgcontent: svgcontent, nonce: getCurrentDrawing().getNonce(), selectorManager: selectorManager }); if (typeof extFunc === 'function') { ext = extFunc(argObj); } else { ext = extFunc; if (ext.callback) { ext.callback = ext.callback.bind(ext, argObj); } } extensions[name] = ext; call('extension_added', ext); } else { console.log('Cannot add extension "' + name + '", an extension by that name already exists.'); } }; /** * This method sends back an array or a NodeList full of elements that * intersect the multi-select rubber-band-box on the currentLayer only. * * We brute-force getIntersectionList for browsers that do not support it (Firefox). * * Reference: * Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421 * @param rect * @returns {Array|NodeList} Bbox elements */ var getIntersectionList = this.getIntersectionList = function (rect) { if (rubberBox == null) { return null; } var parent = currentGroup || getCurrentDrawing().getCurrentLayer(); var rubberBBox = void 0; if (!rect) { rubberBBox = rubberBox.getBBox(); var bb = svgcontent.createSVGRect(); for (var o in rubberBBox) { bb[o] = rubberBBox[o] / currentZoom; } rubberBBox = bb; } else { rubberBBox = svgcontent.createSVGRect(); rubberBBox.x = rect.x; rubberBBox.y = rect.y; rubberBBox.width = rect.width; rubberBBox.height = rect.height; } var resultList = null; if (!isIE) { if (typeof svgroot.getIntersectionList === 'function') { // Offset the bbox of the rubber box by the offset of the svgcontent element. rubberBBox.x += parseInt(svgcontent.getAttribute('x'), 10); rubberBBox.y += parseInt(svgcontent.getAttribute('y'), 10); resultList = svgroot.getIntersectionList(rubberBBox, parent); } } if (resultList == null || typeof resultList.item !== 'function') { resultList = []; if (!curBBoxes.length) { // Cache all bboxes curBBoxes = getVisibleElementsAndBBoxes(parent); } var i = curBBoxes.length; while (i--) { if (!rubberBBox.width) { continue; } if (rectsIntersect(rubberBBox, curBBoxes[i].bbox)) { resultList.push(curBBoxes[i].elem); } } } // addToSelection expects an array, but it's ok to pass a NodeList // because using square-bracket notation is allowed: // https://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html return resultList; }; this.getStrokedBBox = getStrokedBBoxDefaultVisible; this.getVisibleElements = getVisibleElements; /** * 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} An array with objects that include: * - elem - The element * - bbox - The element's BBox as retrieved from `getStrokedBBoxDefaultVisible` */ var getVisibleElementsAndBBoxes = this.getVisibleElementsAndBBoxes = function (parent) { if (!parent) { parent = $$9(svgcontent).children(); // Prevent layers from being included } var contentElems = []; $$9(parent).children().each(function (i, elem) { if (elem.getBBox) { contentElems.push({ elem: elem, bbox: getStrokedBBoxDefaultVisible([elem]) }); } }); return contentElems.reverse(); }; /** * Wrap an SVG element into a group element, mark the group as 'gsvg' * @param elem - SVG element to wrap */ var groupSvgElem = this.groupSvgElem = function (elem) { var g = document.createElementNS(NS.SVG, 'g'); elem.replaceWith(g); $$9(g).append(elem).data('gsvg', elem)[0].id = getNextId(); }; // Set scope for these functions // Object to contain editor event names and callback functions var events = {}; canvas.call = call; /** * Attaches a callback function to an event * @param {String} ev - String indicating the name of the event * @param {Function} f - The callback function to bind to the event * @returns The previous event */ canvas.bind = function (ev, f) { var old = events[ev]; events[ev] = f; return old; }; /** * Runs the SVG Document through the sanitizer and then updates its paths. * @param newDoc - The SVG DOM document */ this.prepareSvg = function (newDoc) { this.sanitizeSvg(newDoc.documentElement); // convert paths into absolute commands var paths = newDoc.getElementsByTagNameNS(NS.SVG, 'path'); for (var i = 0, len = paths.length; i < len; ++i) { var path$$1 = paths[i]; path$$1.setAttribute('d', pathActions$$1.convertPath(path$$1)); pathActions$$1.fixEnd(path$$1); } }; /** * Hack for Firefox bugs where text element features aren't updated or get * messed up. See issue 136 and issue 137. * This function clones the element and re-selects it * @todo Test for this bug on load and add it to "support" object instead of * browser sniffing * @param elem - The (text) DOM element to clone * @returns Cloned element */ var ffClone = function ffClone(elem) { if (!isGecko()) { return elem; } var clone = elem.cloneNode(true); elem.before(clone); elem.remove(); selectorManager.releaseSelector(elem); selectedElements[0] = clone; selectorManager.requestSelector(clone).showGrips(true); return clone; }; // this.each is deprecated, if any extension used this it can be recreated by doing this: // $(canvas.getRootElem()).children().each(...) // this.each = function (cb) { // $(svgroot).children().each(cb); // }; /** * Removes any old rotations if present, prepends a new rotation at the * transformed center * @param val - The new rotation angle in degrees * @param {Boolean} preventUndo - Indicates whether the action should be undoable or not */ this.setRotationAngle = function (val, preventUndo) { // ensure val is the proper type val = parseFloat(val); var elem = selectedElements[0]; var oldTransform = elem.getAttribute('transform'); var bbox = getBBox(elem); var cx = bbox.x + bbox.width / 2, cy = bbox.y + bbox.height / 2; var tlist = getTransformList(elem); // only remove the real rotational transform if present (i.e. at index=0) if (tlist.numberOfItems > 0) { var xform = tlist.getItem(0); if (xform.type === 4) { tlist.removeItem(0); } } // find Rnc and insert it if (val !== 0) { var center = transformPoint(cx, cy, transformListToTransform(tlist).matrix); var Rnc = svgroot.createSVGTransform(); Rnc.setRotate(val, center.x, center.y); if (tlist.numberOfItems) { tlist.insertItemBefore(Rnc, 0); } else { tlist.appendItem(Rnc); } } else if (tlist.numberOfItems === 0) { elem.removeAttribute('transform'); } if (!preventUndo) { // we need to undo it, then redo it so it can be undo-able! :) // TODO: figure out how to make changes to transform list undo-able cross-browser? var newTransform = elem.getAttribute('transform'); elem.setAttribute('transform', oldTransform); changeSelectedAttribute('transform', newTransform, selectedElements); call('changed', selectedElements); } // const pointGripContainer = getElem('pathpointgrip_container'); // if (elem.nodeName === 'path' && pointGripContainer) { // pathActions.setPointContainerTransform(elem.getAttribute('transform')); // } var selector = selectorManager.requestSelector(selectedElements[0]); selector.resize(); selector.updateGripCursors(val); }; // Runs recalculateDimensions on the selected elements, // adding the changes to a single batch command var recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions = function () { var text = currentResizeMode === 'none' ? 'position' : 'size'; var batchCmd = new BatchCommand$1(text); var i = selectedElements.length; while (i--) { var elem = selectedElements[i]; // if (getRotationAngle(elem) && !hasMatrixTransform(getTransformList(elem))) { continue; } var cmd = recalculateDimensions(elem); if (cmd) { batchCmd.addSubCommand(cmd); } } if (!batchCmd.isEmpty()) { addCommandToHistory(batchCmd); call('changed', selectedElements); } }; // Debug tool to easily see the current matrix in the browser's console var logMatrix = function logMatrix(m) { console.log([m.a, m.b, m.c, m.d, m.e, m.f]); }; // Root Current Transformation Matrix in user units var rootSctm = null; /** * Group: Selection */ this.clearSelection = clearSelection; // TODO: do we need to worry about selectedBBoxes here? this.addToSelection = addToSelection; /** * Selects only the given elements, shortcut for clearSelection(); addToSelection() * @param {Array} elems - an array of DOM elements to be selected */ var selectOnly = this.selectOnly = function (elems, showGrips) { clearSelection(true); addToSelection(elems, showGrips); }; // TODO: could use slice here to make this faster? // TODO: should the 'selected' handler /** * Removes elements from the selection. * @param {Array} elemsToRemove - an array of elements to remove from selection */ /* const removeFromSelection = */this.removeFromSelection = function (elemsToRemove) { if (selectedElements[0] == null) { return; } if (!elemsToRemove.length) { return; } // find every element and remove it from our array copy var j = 0; var newSelectedItems = [], len = selectedElements.length; newSelectedItems.length = len; for (var i = 0; i < len; ++i) { var elem = selectedElements[i]; if (elem) { // keep the item if (!elemsToRemove.includes(elem)) { newSelectedItems[j] = elem; j++; } else { // remove the item and its selector selectorManager.releaseSelector(elem); } } } // the copy becomes the master now selectedElements = newSelectedItems; }; // Clears the selection, then adds all elements in the current layer to the selection. this.selectAllInCurrentLayer = function () { var currentLayer = getCurrentDrawing().getCurrentLayer(); if (currentLayer) { currentMode = 'select'; selectOnly($$9(currentGroup || currentLayer).children()); } }; var drawnPath = null; // Mouse events (function () { var freehand = { minx: null, miny: null, maxx: null, maxy: null }; var THRESHOLD_DIST = 0.8, STEP_COUNT = 10; var dAttr = null, startX = null, startY = null, rStartX = null, rStartY = null, initBbox = {}, sumDistance = 0, controllPoint2 = { x: 0, y: 0 }, controllPoint1 = { x: 0, y: 0 }, start = { x: 0, y: 0 }, end = { x: 0, y: 0 }, bSpline = { x: 0, y: 0 }, nextPos = { x: 0, y: 0 }, parameter = void 0, nextParameter = void 0; var getBsplinePoint = function getBsplinePoint(t) { var spline = { x: 0, y: 0 }, p0 = controllPoint2, p1 = controllPoint1, p2 = start, p3 = end, S = 1.0 / 6.0, t2 = t * t, t3 = t2 * t; var m = [[-1, 3, -3, 1], [3, -6, 3, 0], [-3, 0, 3, 0], [1, 4, 1, 0]]; spline.x = S * ((p0.x * m[0][0] + p1.x * m[0][1] + p2.x * m[0][2] + p3.x * m[0][3]) * t3 + (p0.x * m[1][0] + p1.x * m[1][1] + p2.x * m[1][2] + p3.x * m[1][3]) * t2 + (p0.x * m[2][0] + p1.x * m[2][1] + p2.x * m[2][2] + p3.x * m[2][3]) * t + (p0.x * m[3][0] + p1.x * m[3][1] + p2.x * m[3][2] + p3.x * m[3][3])); spline.y = S * ((p0.y * m[0][0] + p1.y * m[0][1] + p2.y * m[0][2] + p3.y * m[0][3]) * t3 + (p0.y * m[1][0] + p1.y * m[1][1] + p2.y * m[1][2] + p3.y * m[1][3]) * t2 + (p0.y * m[2][0] + p1.y * m[2][1] + p2.y * m[2][2] + p3.y * m[2][3]) * t + (p0.y * m[3][0] + p1.y * m[3][1] + p2.y * m[3][2] + p3.y * m[3][3])); return { x: spline.x, y: spline.y }; }; // - when we are in a create mode, the element is added to the canvas // but the action is not recorded until mousing up // - when we are in select mode, select the element, remember the position // and do nothing else var mouseDown = function mouseDown(evt) { if (canvas.spaceKey || evt.button === 1) { return; } var rightClick = evt.button === 2; if (evt.altKey) { // duplicate when dragging canvas.cloneSelectedElements(0, 0); } rootSctm = $$9('#svgcontent g')[0].getScreenCTM().inverse(); var pt = transformPoint(evt.pageX, evt.pageY, rootSctm), mouseX = pt.x * currentZoom, mouseY = pt.y * currentZoom; evt.preventDefault(); if (rightClick) { currentMode = 'select'; lastClickPoint = pt; } // This would seem to be unnecessary... // if (!['select', 'resize'].includes(currentMode)) { // setGradient(); // } var x = mouseX / currentZoom, y = mouseY / currentZoom; var mouseTarget = getMouseTarget(evt); if (mouseTarget.tagName === 'a' && mouseTarget.childNodes.length === 1) { mouseTarget = mouseTarget.firstChild; } // realX/y ignores grid-snap value var realX = x; rStartX = startX = x; var realY = y; rStartY = startY = y; if (curConfig.gridSnapping) { x = snapToGrid(x); y = snapToGrid(y); startX = snapToGrid(startX); startY = snapToGrid(startY); } // if it is a selector grip, then it must be a single element selected, // set the mouseTarget to that and update the mode to rotate/resize if (mouseTarget === selectorManager.selectorParentGroup && selectedElements[0] != null) { var grip = evt.target; var griptype = elData(grip, 'type'); // rotating if (griptype === 'rotate') { currentMode = 'rotate'; // resizing } else if (griptype === 'resize') { currentMode = 'resize'; currentResizeMode = elData(grip, 'dir'); } mouseTarget = selectedElements[0]; } startTransform = mouseTarget.getAttribute('transform'); var i = void 0, strokeW = void 0; var tlist = getTransformList(mouseTarget); switch (currentMode) { case 'select': started = true; currentResizeMode = 'none'; if (rightClick) { started = false; } if (mouseTarget !== svgroot) { // if this element is not yet selected, clear selection and select it if (!selectedElements.includes(mouseTarget)) { // only clear selection if shift is not pressed (otherwise, add // element to selection) if (!evt.shiftKey) { // No need to do the call here as it will be done on addToSelection clearSelection(true); } addToSelection([mouseTarget]); justSelected = mouseTarget; pathActions$$1.clear(); } // else if it's a path, go into pathedit mode in mouseup if (!rightClick) { // insert a dummy transform so if the element(s) are moved it will have // a transform to use for its translate for (i = 0; i < selectedElements.length; ++i) { if (selectedElements[i] == null) { continue; } var slist = getTransformList(selectedElements[i]); if (slist.numberOfItems) { slist.insertItemBefore(svgroot.createSVGTransform(), 0); } else { slist.appendItem(svgroot.createSVGTransform()); } } } } else if (!rightClick) { clearSelection(); currentMode = 'multiselect'; if (rubberBox == null) { rubberBox = selectorManager.getRubberBandBox(); } rStartX *= currentZoom; rStartY *= currentZoom; // console.log('p',[evt.pageX, evt.pageY]); // console.log('c',[evt.clientX, evt.clientY]); // console.log('o',[evt.offsetX, evt.offsetY]); // console.log('s',[startX, startY]); assignAttributes(rubberBox, { x: rStartX, y: rStartY, width: 0, height: 0, display: 'inline' }, 100); } break; case 'zoom': started = true; if (rubberBox == null) { rubberBox = selectorManager.getRubberBandBox(); } assignAttributes(rubberBox, { x: realX * currentZoom, y: realX * currentZoom, width: 0, height: 0, display: 'inline' }, 100); break; case 'resize': started = true; startX = x; startY = y; // Getting the BBox from the selection box, since we know we // want to orient around it initBbox = getBBox($$9('#selectedBox0')[0]); var bb = {}; $$9.each(initBbox, function (key, val) { bb[key] = val / currentZoom; }); initBbox = bb; // append three dummy transforms to the tlist so that // we can translate,scale,translate in mousemove var pos = getRotationAngle(mouseTarget) ? 1 : 0; if (hasMatrixTransform(tlist)) { tlist.insertItemBefore(svgroot.createSVGTransform(), pos); tlist.insertItemBefore(svgroot.createSVGTransform(), pos); tlist.insertItemBefore(svgroot.createSVGTransform(), pos); } else { tlist.appendItem(svgroot.createSVGTransform()); tlist.appendItem(svgroot.createSVGTransform()); tlist.appendItem(svgroot.createSVGTransform()); if (supportsNonScalingStroke()) { // Handle crash for newer Chrome and Safari 6 (Mobile and Desktop): // https://code.google.com/p/svg-edit/issues/detail?id=904 // Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625 // TODO: Remove this workaround once vendor fixes the issue var iswebkit = isWebkit(); var delayedStroke = void 0; if (iswebkit) { delayedStroke = function delayedStroke(ele) { var _stroke = ele.getAttributeNS(null, 'stroke'); ele.removeAttributeNS(null, 'stroke'); // Re-apply stroke after delay. Anything higher than 1 seems to cause flicker if (_stroke !== null) setTimeout(function () { ele.setAttributeNS(null, 'stroke', _stroke); }, 0); }; } mouseTarget.style.vectorEffect = 'non-scaling-stroke'; if (iswebkit) { delayedStroke(mouseTarget); } var all = mouseTarget.getElementsByTagName('*'), len = all.length; for (i = 0; i < len; i++) { all[i].style.vectorEffect = 'non-scaling-stroke'; if (iswebkit) { delayedStroke(all[i]); } } } } break; case 'fhellipse': case 'fhrect': case 'fhpath': start.x = realX; start.y = realY; started = true; dAttr = realX + ',' + realY + ' '; strokeW = parseFloat(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; addSvgElementFromJson({ element: 'polyline', curStyles: true, attr: { points: dAttr, id: getNextId(), fill: 'none', opacity: curShape.opacity / 2, 'stroke-linecap': 'round', style: 'pointer-events:none' } }); freehand.minx = realX; freehand.maxx = realX; freehand.miny = realY; freehand.maxy = realY; break; case 'image': started = true; var newImage = addSvgElementFromJson({ element: 'image', attr: { x: x, y: y, width: 0, height: 0, id: getNextId(), opacity: curShape.opacity / 2, style: 'pointer-events:inherit' } }); setHref(newImage, lastGoodImgUrl); preventClickDefault(newImage); break; case 'square': // FIXME: once we create the rect, we lose information that this was a square // (for resizing purposes this could be important) // Fallthrough case 'rect': started = true; startX = x; startY = y; addSvgElementFromJson({ element: 'rect', curStyles: true, attr: { x: x, y: y, width: 0, height: 0, id: getNextId(), opacity: curShape.opacity / 2 } }); break; case 'line': started = true; strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; addSvgElementFromJson({ element: 'line', curStyles: true, attr: { x1: x, y1: y, x2: x, y2: y, id: getNextId(), stroke: curShape.stroke, 'stroke-width': strokeW, 'stroke-dasharray': curShape.stroke_dasharray, 'stroke-linejoin': curShape.stroke_linejoin, 'stroke-linecap': curShape.stroke_linecap, 'stroke-opacity': curShape.stroke_opacity, fill: 'none', opacity: curShape.opacity / 2, style: 'pointer-events:none' } }); break; case 'circle': started = true; addSvgElementFromJson({ element: 'circle', curStyles: true, attr: { cx: x, cy: y, r: 0, id: getNextId(), opacity: curShape.opacity / 2 } }); break; case 'ellipse': started = true; addSvgElementFromJson({ element: 'ellipse', curStyles: true, attr: { cx: x, cy: y, rx: 0, ry: 0, id: getNextId(), opacity: curShape.opacity / 2 } }); break; case 'text': started = true; /* const newText = */addSvgElementFromJson({ element: 'text', curStyles: true, attr: { x: x, y: y, id: getNextId(), fill: curText.fill, 'stroke-width': curText.stroke_width, 'font-size': curText.font_size, 'font-family': curText.font_family, 'text-anchor': 'middle', 'xml:space': 'preserve', opacity: curShape.opacity } }); // newText.textContent = 'text'; break; case 'path': // Fall through case 'pathedit': startX *= currentZoom; startY *= currentZoom; pathActions$$1.mouseDown(evt, mouseTarget, startX, startY); started = true; break; case 'textedit': startX *= currentZoom; startY *= currentZoom; textActions.mouseDown(evt, mouseTarget, startX, startY); started = true; break; case 'rotate': started = true; // we are starting an undoable change (a drag-rotation) canvas.undoMgr.beginUndoableChange('transform', selectedElements); break; default: // This could occur in an extension break; } var extResult = runExtensions('mouseDown', { event: evt, start_x: startX, start_y: startY, selectedElements: selectedElements }, true); $$9.each(extResult, function (i, r) { if (r && r.started) { started = true; } }); }; // in this function we do not record any state changes yet (but we do update // any elements that are still being created, moved or resized on the canvas) var mouseMove = function mouseMove(evt) { if (!started) { return; } if (evt.button === 1 || canvas.spaceKey) { return; } var i = void 0, xya = void 0, c = void 0, cx = void 0, cy = void 0, dx = void 0, dy = void 0, len = void 0, angle = void 0, box = void 0, selected = selectedElements[0]; var pt = transformPoint(evt.pageX, evt.pageY, rootSctm), mouseX = pt.x * currentZoom, mouseY = pt.y * currentZoom, shape = getElem(getId()); var realX = mouseX / currentZoom; var x = realX; var realY = mouseY / currentZoom; var y = realY; if (curConfig.gridSnapping) { x = snapToGrid(x); y = snapToGrid(y); } evt.preventDefault(); var tlist = void 0; switch (currentMode) { case 'select': { // we temporarily use a translate on the element(s) being dragged // this transform is removed upon mousing up and the element is // relocated to the new location if (selectedElements[0] !== null) { dx = x - startX; dy = y - startY; if (curConfig.gridSnapping) { dx = snapToGrid(dx); dy = snapToGrid(dy); } if (evt.shiftKey) { xya = snapToAngle(startX, startY, x, y); var _xya = xya; x = _xya.x; y = _xya.y; } if (dx !== 0 || dy !== 0) { len = selectedElements.length; for (i = 0; i < len; ++i) { selected = selectedElements[i]; if (selected == null) { break; } // if (i === 0) { // const box = utilsGetBBox(selected); // selectedBBoxes[i].x = box.x + dx; // selectedBBoxes[i].y = box.y + dy; // } // update the dummy transform in our transform list // to be a translate var xform = svgroot.createSVGTransform(); tlist = getTransformList(selected); // Note that if Webkit and there's no ID for this // element, the dummy transform may have gotten lost. // This results in unexpected behaviour xform.setTranslate(dx, dy); if (tlist.numberOfItems) { tlist.replaceItem(xform, 0); } else { tlist.appendItem(xform); } // update our internal bbox that we're tracking while dragging selectorManager.requestSelector(selected).resize(); } call('transition', selectedElements); } } break; }case 'multiselect': { realX *= currentZoom; realY *= currentZoom; assignAttributes(rubberBox, { x: Math.min(rStartX, realX), y: Math.min(rStartY, realY), width: Math.abs(realX - rStartX), height: Math.abs(realY - rStartY) }, 100); // for each selected: // - if newList contains selected, do nothing // - if newList doesn't contain selected, remove it from selected // - for any newList that was not in selectedElements, add it to selected var elemsToRemove = selectedElements.slice(), elemsToAdd = [], newList = getIntersectionList(); // For every element in the intersection, add if not present in selectedElements. len = newList.length; for (i = 0; i < len; ++i) { var intElem = newList[i]; // Found an element that was not selected before, so we should add it. if (!selectedElements.includes(intElem)) { elemsToAdd.push(intElem); } // Found an element that was already selected, so we shouldn't remove it. var foundInd = elemsToRemove.indexOf(intElem); if (foundInd !== -1) { elemsToRemove.splice(foundInd, 1); } } if (elemsToRemove.length > 0) { canvas.removeFromSelection(elemsToRemove); } if (elemsToAdd.length > 0) { canvas.addToSelection(elemsToAdd); } break; }case 'resize': { // we track the resize bounding box and translate/scale the selected element // while the mouse is down, when mouse goes up, we use this to recalculate // the shape's coordinates tlist = getTransformList(selected); var hasMatrix = hasMatrixTransform(tlist); box = hasMatrix ? initBbox : getBBox(selected); var left = box.x, top = box.y, _box = box, width = _box.width, height = _box.height; dx = x - startX; dy = y - startY; if (curConfig.gridSnapping) { dx = snapToGrid(dx); dy = snapToGrid(dy); height = snapToGrid(height); width = snapToGrid(width); } // if rotated, adjust the dx,dy values angle = getRotationAngle(selected); if (angle) { var r = Math.sqrt(dx * dx + dy * dy), theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0; dx = r * Math.cos(theta); dy = r * Math.sin(theta); } // if not stretching in y direction, set dy to 0 // if not stretching in x direction, set dx to 0 if (!currentResizeMode.includes('n') && !currentResizeMode.includes('s')) { dy = 0; } if (!currentResizeMode.includes('e') && !currentResizeMode.includes('w')) { dx = 0; } var // ts = null, tx = 0, ty = 0, sy = height ? (height + dy) / height : 1, sx = width ? (width + dx) / width : 1; // if we are dragging on the north side, then adjust the scale factor and ty if (currentResizeMode.includes('n')) { sy = height ? (height - dy) / height : 1; ty = height; } // if we dragging on the east side, then adjust the scale factor and tx if (currentResizeMode.includes('w')) { sx = width ? (width - dx) / width : 1; tx = width; } // update the transform list with translate,scale,translate var translateOrigin = svgroot.createSVGTransform(), scale = svgroot.createSVGTransform(), translateBack = svgroot.createSVGTransform(); if (curConfig.gridSnapping) { left = snapToGrid(left); tx = snapToGrid(tx); top = snapToGrid(top); ty = snapToGrid(ty); } translateOrigin.setTranslate(-(left + tx), -(top + ty)); if (evt.shiftKey) { if (sx === 1) { sx = sy; } else { sy = sx; } } scale.setScale(sx, sy); translateBack.setTranslate(left + tx, top + ty); if (hasMatrix) { var diff = angle ? 1 : 0; tlist.replaceItem(translateOrigin, 2 + diff); tlist.replaceItem(scale, 1 + diff); tlist.replaceItem(translateBack, Number(diff)); } else { var N = tlist.numberOfItems; tlist.replaceItem(translateBack, N - 3); tlist.replaceItem(scale, N - 2); tlist.replaceItem(translateOrigin, N - 1); } selectorManager.requestSelector(selected).resize(); call('transition', selectedElements); break; }case 'zoom': { realX *= currentZoom; realY *= currentZoom; assignAttributes(rubberBox, { x: Math.min(rStartX * currentZoom, realX), y: Math.min(rStartY * currentZoom, realY), width: Math.abs(realX - rStartX * currentZoom), height: Math.abs(realY - rStartY * currentZoom) }, 100); break; }case 'text': { assignAttributes(shape, { x: x, y: y }, 1000); break; }case 'line': { if (curConfig.gridSnapping) { x = snapToGrid(x); y = snapToGrid(y); } var x2 = x; var y2 = y; if (evt.shiftKey) { xya = snapToAngle(startX, startY, x2, y2); x2 = xya.x; y2 = xya.y; } shape.setAttributeNS(null, 'x2', x2); shape.setAttributeNS(null, 'y2', y2); break; }case 'foreignObject': // fall through case 'square': // fall through case 'rect': // fall through case 'image': { var square = currentMode === 'square' || evt.shiftKey; var w = Math.abs(x - startX), h = Math.abs(y - startY); var newX = void 0, newY = void 0; if (square) { w = h = Math.max(w, h); newX = startX < x ? startX : startX - w; newY = startY < y ? startY : startY - h; } else { newX = Math.min(startX, x); newY = Math.min(startY, y); } if (curConfig.gridSnapping) { w = snapToGrid(w); h = snapToGrid(h); newX = snapToGrid(newX); newY = snapToGrid(newY); } assignAttributes(shape, { width: w, height: h, x: newX, y: newY }, 1000); break; }case 'circle': { c = $$9(shape).attr(['cx', 'cy']); var _c = c; cx = _c.cx; cy = _c.cy; var rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); if (curConfig.gridSnapping) { rad = snapToGrid(rad); } shape.setAttributeNS(null, 'r', rad); break; }case 'ellipse': { c = $$9(shape).attr(['cx', 'cy']); var _c2 = c; cx = _c2.cx; cy = _c2.cy; if (curConfig.gridSnapping) { x = snapToGrid(x); cx = snapToGrid(cx); y = snapToGrid(y); cy = snapToGrid(cy); } shape.setAttributeNS(null, 'rx', Math.abs(x - cx)); var ry = Math.abs(evt.shiftKey ? x - cx : y - cy); shape.setAttributeNS(null, 'ry', ry); break; } case 'fhellipse': case 'fhrect': { freehand.minx = Math.min(realX, freehand.minx); freehand.maxx = Math.max(realX, freehand.maxx); freehand.miny = Math.min(realY, freehand.miny); freehand.maxy = Math.max(realY, freehand.maxy); } // Fallthrough case 'fhpath': { // dAttr += + realX + ',' + realY + ' '; // shape.setAttributeNS(null, 'points', dAttr); end.x = realX;end.y = realY; if (controllPoint2.x && controllPoint2.y) { for (i = 0; i < STEP_COUNT - 1; i++) { parameter = i / STEP_COUNT; nextParameter = (i + 1) / STEP_COUNT; bSpline = getBsplinePoint(nextParameter); nextPos = bSpline; bSpline = getBsplinePoint(parameter); sumDistance += Math.sqrt((nextPos.x - bSpline.x) * (nextPos.x - bSpline.x) + (nextPos.y - bSpline.y) * (nextPos.y - bSpline.y)); if (sumDistance > THRESHOLD_DIST) { dAttr += +bSpline.x + ',' + bSpline.y + ' '; shape.setAttributeNS(null, 'points', dAttr); sumDistance -= THRESHOLD_DIST; } } } controllPoint2 = { x: controllPoint1.x, y: controllPoint1.y }; controllPoint1 = { x: start.x, y: start.y }; start = { x: end.x, y: end.y }; break; // update path stretch line coordinates }case 'path': // fall through case 'pathedit': { x *= currentZoom; y *= currentZoom; if (curConfig.gridSnapping) { x = snapToGrid(x); y = snapToGrid(y); startX = snapToGrid(startX); startY = snapToGrid(startY); } if (evt.shiftKey) { var path$$1 = path; var x1 = void 0, y1 = void 0; if (path$$1) { x1 = path$$1.dragging ? path$$1.dragging[0] : startX; y1 = path$$1.dragging ? path$$1.dragging[1] : startY; } else { x1 = startX; y1 = startY; } xya = snapToAngle(x1, y1, x, y); var _xya2 = xya; x = _xya2.x; y = _xya2.y; } if (rubberBox && rubberBox.getAttribute('display') !== 'none') { realX *= currentZoom; realY *= currentZoom; assignAttributes(rubberBox, { x: Math.min(rStartX * currentZoom, realX), y: Math.min(rStartY * currentZoom, realY), width: Math.abs(realX - rStartX * currentZoom), height: Math.abs(realY - rStartY * currentZoom) }, 100); } pathActions$$1.mouseMove(x, y); break; }case 'textedit': { x *= currentZoom; y *= currentZoom; // if (rubberBox && rubberBox.getAttribute('display') !== 'none') { // assignAttributes(rubberBox, { // x: Math.min(startX, x), // y: Math.min(startY, y), // width: Math.abs(x - startX), // height: Math.abs(y - startY) // }, 100); // } textActions.mouseMove(mouseX, mouseY); break; }case 'rotate': { box = getBBox(selected); cx = box.x + box.width / 2; cy = box.y + box.height / 2; var m = getMatrix(selected), center = transformPoint(cx, cy, m); cx = center.x; cy = center.y; angle = (Math.atan2(cy - y, cx - x) * (180 / Math.PI) - 90) % 360; if (curConfig.gridSnapping) { angle = snapToGrid(angle); } if (evt.shiftKey) { // restrict rotations to nice angles (WRS) var snap = 45; angle = Math.round(angle / snap) * snap; } canvas.setRotationAngle(angle < -180 ? 360 + angle : angle, true); call('transition', selectedElements); break; }default: break; } runExtensions('mouseMove', { event: evt, mouse_x: mouseX, mouse_y: mouseY, selected: selected }); }; // mouseMove() // - in create mode, the element's opacity is set properly, we create an InsertElementCommand // and store it on the Undo stack // - in move/resize mode, the element's attributes which were affected by the move/resize are // identified, a ChangeElementCommand is created and stored on the stack for those attrs // this is done in when we recalculate the selected dimensions() var mouseUp = function mouseUp(evt) { if (evt.button === 2) { return; } var tempJustSelected = justSelected; justSelected = null; if (!started) { return; } var pt = transformPoint(evt.pageX, evt.pageY, rootSctm), mouseX = pt.x * currentZoom, mouseY = pt.y * currentZoom, x = mouseX / currentZoom, y = mouseY / currentZoom; var element = getElem(getId()); var keep = false; var realX = x; var realY = y; started = false; var attrs = void 0, t = void 0; switch (currentMode) { // intentionally fall-through to select here case 'resize': case 'multiselect': if (rubberBox != null) { rubberBox.setAttribute('display', 'none'); curBBoxes = []; } currentMode = 'select'; // Fallthrough case 'select': if (selectedElements[0] != null) { // if we only have one selected element if (selectedElements[1] == null) { // set our current stroke/fill properties to the element's var selected = selectedElements[0]; switch (selected.tagName) { case 'g': case 'use': case 'image': case 'foreignObject': break; default: curProperties.fill = selected.getAttribute('fill'); curProperties.fill_opacity = selected.getAttribute('fill-opacity'); curProperties.stroke = selected.getAttribute('stroke'); curProperties.stroke_opacity = selected.getAttribute('stroke-opacity'); curProperties.stroke_width = selected.getAttribute('stroke-width'); curProperties.stroke_dasharray = selected.getAttribute('stroke-dasharray'); curProperties.stroke_linejoin = selected.getAttribute('stroke-linejoin'); curProperties.stroke_linecap = selected.getAttribute('stroke-linecap'); } if (selected.tagName === 'text') { curText.font_size = selected.getAttribute('font-size'); curText.font_family = selected.getAttribute('font-family'); } selectorManager.requestSelector(selected).showGrips(true); // This shouldn't be necessary as it was done on mouseDown... // call('selected', [selected]); } // always recalculate dimensions to strip off stray identity transforms recalculateAllSelectedDimensions(); // if it was being dragged/resized if (realX !== rStartX || realY !== rStartY) { var len = selectedElements.length; for (var i = 0; i < len; ++i) { if (selectedElements[i] == null) { break; } if (!selectedElements[i].firstChild) { // Not needed for groups (incorrectly resizes elems), possibly not needed at all? selectorManager.requestSelector(selectedElements[i]).resize(); } } // no change in position/size, so maybe we should move to pathedit } else { t = evt.target; if (selectedElements[0].nodeName === 'path' && selectedElements[1] == null) { pathActions$$1.select(selectedElements[0]); // if it was a path // else, if it was selected and this is a shift-click, remove it from selection } else if (evt.shiftKey) { if (tempJustSelected !== t) { canvas.removeFromSelection([t]); } } } // no change in mouse position // Remove non-scaling stroke if (supportsNonScalingStroke()) { var elem = selectedElements[0]; if (elem) { elem.removeAttribute('style'); walkTree(elem, function (elem) { elem.removeAttribute('style'); }); } } } return; case 'zoom': if (rubberBox != null) { rubberBox.setAttribute('display', 'none'); } var factor = evt.shiftKey ? 0.5 : 2; call('zoomed', { x: Math.min(rStartX, realX), y: Math.min(rStartY, realY), width: Math.abs(realX - rStartX), height: Math.abs(realY - rStartY), factor: factor }); return; case 'fhpath': // Check that the path contains at least 2 points; a degenerate one-point path // causes problems. // Webkit ignores how we set the points attribute with commas and uses space // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870 sumDistance = 0; controllPoint2 = { x: 0, y: 0 }; controllPoint1 = { x: 0, y: 0 }; start = { x: 0, y: 0 }; end = { x: 0, y: 0 }; var coords = element.getAttribute('points'); var commaIndex = coords.indexOf(','); if (commaIndex >= 0) { keep = coords.indexOf(',', commaIndex + 1) >= 0; } else { keep = coords.indexOf(' ', coords.indexOf(' ') + 1) >= 0; } if (keep) { element = pathActions$$1.smoothPolylineIntoPath(element); } break; case 'line': attrs = $$9(element).attr(['x1', 'x2', 'y1', 'y2']); keep = attrs.x1 !== attrs.x2 || attrs.y1 !== attrs.y2; break; case 'foreignObject': case 'square': case 'rect': case 'image': attrs = $$9(element).attr(['width', 'height']); // Image should be kept regardless of size (use inherit dimensions later) keep = attrs.width !== '0' || attrs.height !== '0' || currentMode === 'image'; break; case 'circle': keep = element.getAttribute('r') !== '0'; break; case 'ellipse': attrs = $$9(element).attr(['rx', 'ry']); keep = attrs.rx != null || attrs.ry != null; break; case 'fhellipse': if (freehand.maxx - freehand.minx > 0 && freehand.maxy - freehand.miny > 0) { element = addSvgElementFromJson({ element: 'ellipse', curStyles: true, attr: { cx: (freehand.minx + freehand.maxx) / 2, cy: (freehand.miny + freehand.maxy) / 2, rx: (freehand.maxx - freehand.minx) / 2, ry: (freehand.maxy - freehand.miny) / 2, id: getId() } }); call('changed', [element]); keep = true; } break; case 'fhrect': if (freehand.maxx - freehand.minx > 0 && freehand.maxy - freehand.miny > 0) { element = addSvgElementFromJson({ element: 'rect', curStyles: true, attr: { x: freehand.minx, y: freehand.miny, width: freehand.maxx - freehand.minx, height: freehand.maxy - freehand.miny, id: getId() } }); call('changed', [element]); keep = true; } break; case 'text': keep = true; selectOnly([element]); textActions.start(element); break; case 'path': // set element to null here so that it is not removed nor finalized element = null; // continue to be set to true so that mouseMove happens started = true; var res = pathActions$$1.mouseUp(evt, element, mouseX, mouseY); element = res.element; keep = res.keep; break; case 'pathedit': keep = true; element = null; pathActions$$1.mouseUp(evt); break; case 'textedit': keep = false; element = null; textActions.mouseUp(evt, mouseX, mouseY); break; case 'rotate': keep = true; element = null; currentMode = 'select'; var batchCmd = canvas.undoMgr.finishUndoableChange(); if (!batchCmd.isEmpty()) { addCommandToHistory(batchCmd); } // perform recalculation to weed out any stray identity transforms that might get stuck recalculateAllSelectedDimensions(); call('changed', selectedElements); break; default: // This could occur in an extension break; } var extResult = runExtensions('mouseUp', { event: evt, mouse_x: mouseX, mouse_y: mouseY }, true); $$9.each(extResult, function (i, r) { if (r) { keep = r.keep || keep; element = r.element; started = r.started || started; } }); if (!keep && element != null) { getCurrentDrawing().releaseId(getId()); element.remove(); element = null; t = evt.target; // if this element is in a group, go up until we reach the top-level group // just below the layer groups // TODO: once we implement links, we also would have to check for elements while (t && t.parentNode && t.parentNode.parentNode && t.parentNode.parentNode.tagName === 'g') { t = t.parentNode; } // if we are not in the middle of creating a path, and we've clicked on some shape, // then go to Select mode. // WebKit returns
    when the canvas is clicked, Firefox/Opera return if ((currentMode !== 'path' || !drawnPath) && t && t.parentNode && t.parentNode.id !== 'selectorParentGroup' && t.id !== 'svgcanvas' && t.id !== 'svgroot') { // switch into "select" mode if we've clicked on an element canvas.setMode('select'); selectOnly([t], true); } } else if (element != null) { canvas.addedNew = true; var aniDur = 0.2; var cAni = void 0; if (opacAni.beginElement && parseFloat(element.getAttribute('opacity')) !== curShape.opacity) { cAni = $$9(opacAni).clone().attr({ to: curShape.opacity, dur: aniDur }).appendTo(element); try { // Fails in FF4 on foreignObject cAni[0].beginElement(); } catch (e) {} } else { aniDur = 0; } // Ideally this would be done on the endEvent of the animation, // but that doesn't seem to be supported in Webkit setTimeout(function () { if (cAni) { cAni.remove(); } element.setAttribute('opacity', curShape.opacity); element.setAttribute('style', 'pointer-events:inherit'); cleanupElement(element); if (currentMode === 'path') { pathActions$$1.toEditMode(element); } else if (curConfig.selectNew) { selectOnly([element], true); } // we create the insert command that is stored on the stack // undo means to call cmd.unapply(), redo means to call cmd.apply() addCommandToHistory(new InsertElementCommand$1(element)); call('changed', [element]); }, aniDur * 1000); } startTransform = null; }; var dblClick = function dblClick(evt) { var evtTarget = evt.target; var parent = evtTarget.parentNode; // Do nothing if already in current group if (parent === currentGroup) { return; } var mouseTarget = getMouseTarget(evt); var _mouseTarget = mouseTarget, tagName = _mouseTarget.tagName; if (tagName === 'text' && currentMode !== 'textedit') { var pt = transformPoint(evt.pageX, evt.pageY, rootSctm); textActions.select(mouseTarget, pt.x, pt.y); } if ((tagName === 'g' || tagName === 'a') && getRotationAngle(mouseTarget)) { // TODO: Allow method of in-group editing without having to do // this (similar to editing rotated paths) // Ungroup and regroup pushGroupProperties(mouseTarget); mouseTarget = selectedElements[0]; clearSelection(true); } // Reset context if (currentGroup) { leaveContext(); } if (parent.tagName !== 'g' && parent.tagName !== 'a' || parent === getCurrentDrawing().getCurrentLayer() || mouseTarget === selectorManager.selectorParentGroup) { // Escape from in-group edit return; } setContext(mouseTarget); }; // prevent links from being followed in the canvas var handleLinkInCanvas = function handleLinkInCanvas(e) { e.preventDefault(); return false; }; // Added mouseup to the container here. // TODO(codedread): Figure out why after the Closure compiler, the window mouseup is ignored. $$9(container).mousedown(mouseDown).mousemove(mouseMove).click(handleLinkInCanvas).dblclick(dblClick).mouseup(mouseUp); // $(window).mouseup(mouseUp); // TODO(rafaelcastrocouto): User preference for shift key and zoom factor $$9(container).bind('mousewheel DOMMouseScroll', function (e) { if (!e.shiftKey) { return; } e.preventDefault(); var evt = e.originalEvent; rootSctm = $$9('#svgcontent g')[0].getScreenCTM().inverse(); var workarea = $$9('#workarea'); var scrbar = 15; var rulerwidth = curConfig.showRulers ? 16 : 0; // mouse relative to content area in content pixels var pt = transformPoint(evt.pageX, evt.pageY, rootSctm); // full work area width in screen pixels var editorFullW = workarea.width(); var editorFullH = workarea.height(); // work area width minus scroll and ruler in screen pixels var editorW = editorFullW - scrbar - rulerwidth; var editorH = editorFullH - scrbar - rulerwidth; // work area width in content pixels var workareaViewW = editorW * rootSctm.a; var workareaViewH = editorH * rootSctm.d; // content offset from canvas in screen pixels var wOffset = workarea.offset(); var wOffsetLeft = wOffset['left'] + rulerwidth; var wOffsetTop = wOffset['top'] + rulerwidth; var delta = evt.wheelDelta ? evt.wheelDelta : evt.detail ? -evt.detail : 0; if (!delta) { return; } var factor = Math.max(3 / 4, Math.min(4 / 3, delta)); var wZoom = void 0, hZoom = void 0; if (factor > 1) { wZoom = Math.ceil(editorW / workareaViewW * factor * 100) / 100; hZoom = Math.ceil(editorH / workareaViewH * factor * 100) / 100; } else { wZoom = Math.floor(editorW / workareaViewW * factor * 100) / 100; hZoom = Math.floor(editorH / workareaViewH * factor * 100) / 100; } var zoomlevel = Math.min(wZoom, hZoom); zoomlevel = Math.min(10, Math.max(0.01, zoomlevel)); if (zoomlevel === currentZoom) { return; } factor = zoomlevel / currentZoom; // top left of workarea in content pixels before zoom var topLeftOld = transformPoint(wOffsetLeft, wOffsetTop, rootSctm); // top left of workarea in content pixels after zoom var topLeftNew = { x: pt.x - (pt.x - topLeftOld.x) / factor, y: pt.y - (pt.y - topLeftOld.y) / factor }; // top left of workarea in canvas pixels relative to content after zoom var topLeftNewCanvas = { x: topLeftNew.x * zoomlevel, y: topLeftNew.y * zoomlevel }; // new center in canvas pixels var newCtr = { x: topLeftNewCanvas.x - rulerwidth + editorFullW / 2, y: topLeftNewCanvas.y - rulerwidth + editorFullH / 2 }; canvas.setZoom(zoomlevel); $$9('#zoom').val((zoomlevel * 100).toFixed(1)); call('updateCanvas', { center: false, newCtr: newCtr }); call('zoomDone'); }); })(); /** * Group: Text edit functions * Functions relating to editing text elements */ var textActions = canvas.textActions = function () { var curtext = void 0; var textinput = void 0; var cursor = void 0; var selblock = void 0; var blinker = void 0; var chardata = []; var textbb = void 0; // , transbb; var matrix = void 0; var lastX = void 0, lastY = void 0; var allowDbl = void 0; function setCursor(index) { var empty = textinput.value === ''; $$9(textinput).focus(); if (!arguments.length) { if (empty) { index = 0; } else { if (textinput.selectionEnd !== textinput.selectionStart) { return; } index = textinput.selectionEnd; } } var charbb = chardata[index]; if (!empty) { textinput.setSelectionRange(index, index); } cursor = getElem('text_cursor'); if (!cursor) { cursor = document.createElementNS(NS.SVG, 'line'); assignAttributes(cursor, { id: 'text_cursor', stroke: '#333', 'stroke-width': 1 }); cursor = getElem('selectorParentGroup').appendChild(cursor); } if (!blinker) { blinker = setInterval(function () { var show = cursor.getAttribute('display') === 'none'; cursor.setAttribute('display', show ? 'inline' : 'none'); }, 600); } var startPt = ptToScreen(charbb.x, textbb.y); var endPt = ptToScreen(charbb.x, textbb.y + textbb.height); assignAttributes(cursor, { x1: startPt.x, y1: startPt.y, x2: endPt.x, y2: endPt.y, visibility: 'visible', display: 'inline' }); if (selblock) { selblock.setAttribute('d', ''); } } function setSelection(start, end, skipInput) { if (start === end) { setCursor(end); return; } if (!skipInput) { textinput.setSelectionRange(start, end); } selblock = getElem('text_selectblock'); if (!selblock) { selblock = document.createElementNS(NS.SVG, 'path'); assignAttributes(selblock, { id: 'text_selectblock', fill: 'green', opacity: 0.5, style: 'pointer-events:none' }); getElem('selectorParentGroup').append(selblock); } var startbb = chardata[start]; var endbb = chardata[end]; cursor.setAttribute('visibility', 'hidden'); var tl = ptToScreen(startbb.x, textbb.y), tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y), bl = ptToScreen(startbb.x, textbb.y + textbb.height), br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height); var dstr = 'M' + tl.x + ',' + tl.y + ' L' + tr.x + ',' + tr.y + ' ' + br.x + ',' + br.y + ' ' + bl.x + ',' + bl.y + 'z'; assignAttributes(selblock, { d: dstr, display: 'inline' }); } function getIndexFromPoint(mouseX, mouseY) { // Position cursor here var pt = svgroot.createSVGPoint(); pt.x = mouseX; pt.y = mouseY; // No content, so return 0 if (chardata.length === 1) { return 0; } // Determine if cursor should be on left or right of character var charpos = curtext.getCharNumAtPosition(pt); if (charpos < 0) { // Out of text range, look at mouse coords charpos = chardata.length - 2; if (mouseX <= chardata[0].x) { charpos = 0; } } else if (charpos >= chardata.length - 2) { charpos = chardata.length - 2; } var charbb = chardata[charpos]; var mid = charbb.x + charbb.width / 2; if (mouseX > mid) { charpos++; } return charpos; } function setCursorFromPoint(mouseX, mouseY) { setCursor(getIndexFromPoint(mouseX, mouseY)); } function setEndSelectionFromPoint(x, y, apply) { var i1 = textinput.selectionStart; var i2 = getIndexFromPoint(x, y); var start = Math.min(i1, i2); var end = Math.max(i1, i2); setSelection(start, end, !apply); } function screenToPt(xIn, yIn) { var out = { x: xIn, y: yIn }; out.x /= currentZoom; out.y /= currentZoom; if (matrix) { var pt = transformPoint(out.x, out.y, matrix.inverse()); out.x = pt.x; out.y = pt.y; } return out; } function ptToScreen(xIn, yIn) { var out = { x: xIn, y: yIn }; if (matrix) { var pt = transformPoint(out.x, out.y, matrix); out.x = pt.x; out.y = pt.y; } out.x *= currentZoom; out.y *= currentZoom; return out; } /* // Not currently in use function hideCursor () { if (cursor) { cursor.setAttribute('visibility', 'hidden'); } } */ function selectAll(evt) { setSelection(0, curtext.textContent.length); $$9(this).unbind(evt); } function selectWord(evt) { if (!allowDbl || !curtext) { return; } var ept = transformPoint(evt.pageX, evt.pageY, rootSctm), mouseX = ept.x * currentZoom, mouseY = ept.y * currentZoom; var pt = screenToPt(mouseX, mouseY); var index = getIndexFromPoint(pt.x, pt.y); var str = curtext.textContent; var first = str.substr(0, index).replace(/[a-z0-9]+$/i, '').length; var m = str.substr(index).match(/^[a-z0-9]+/i); var last = (m ? m[0].length : 0) + index; setSelection(first, last); // Set tripleclick $$9(evt.target).click(selectAll); setTimeout(function () { $$9(evt.target).unbind('click', selectAll); }, 300); } return { select: function select(target, x, y) { curtext = target; textActions.toEditMode(x, y); }, start: function start(elem) { curtext = elem; textActions.toEditMode(); }, mouseDown: function mouseDown(evt, mouseTarget, startX, startY) { var pt = screenToPt(startX, startY); textinput.focus(); setCursorFromPoint(pt.x, pt.y); lastX = startX; lastY = startY; // TODO: Find way to block native selection }, mouseMove: function mouseMove(mouseX, mouseY) { var pt = screenToPt(mouseX, mouseY); setEndSelectionFromPoint(pt.x, pt.y); }, mouseUp: function mouseUp(evt, mouseX, mouseY) { var pt = screenToPt(mouseX, mouseY); setEndSelectionFromPoint(pt.x, pt.y, true); // TODO: Find a way to make this work: Use transformed BBox instead of evt.target // if (lastX === mouseX && lastY === mouseY // && !rectsIntersect(transbb, {x: pt.x, y: pt.y, width: 0, height: 0})) { // textActions.toSelectMode(true); // } if (evt.target !== curtext && mouseX < lastX + 2 && mouseX > lastX - 2 && mouseY < lastY + 2 && mouseY > lastY - 2) { textActions.toSelectMode(true); } }, setCursor: setCursor, toEditMode: function toEditMode(x, y) { allowDbl = false; currentMode = 'textedit'; selectorManager.requestSelector(curtext).showGrips(false); // Make selector group accept clicks /* const selector = */selectorManager.requestSelector(curtext); // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used // const sel = selector.selectorRect; textActions.init(); $$9(curtext).css('cursor', 'text'); // if (supportsEditableText()) { // curtext.setAttribute('editable', 'simple'); // return; // } if (!arguments.length) { setCursor(); } else { var pt = screenToPt(x, y); setCursorFromPoint(pt.x, pt.y); } setTimeout(function () { allowDbl = true; }, 300); }, toSelectMode: function toSelectMode(selectElem) { currentMode = 'select'; clearInterval(blinker); blinker = null; if (selblock) { $$9(selblock).attr('display', 'none'); } if (cursor) { $$9(cursor).attr('visibility', 'hidden'); } $$9(curtext).css('cursor', 'move'); if (selectElem) { clearSelection(); $$9(curtext).css('cursor', 'move'); call('selected', [curtext]); addToSelection([curtext], true); } if (curtext && !curtext.textContent.length) { // No content, so delete canvas.deleteSelectedElements(); } $$9(textinput).blur(); curtext = false; // if (supportsEditableText()) { // curtext.removeAttribute('editable'); // } }, setInputElem: function setInputElem(elem) { textinput = elem; // $(textinput).blur(hideCursor); }, clear: function clear() { if (currentMode === 'textedit') { textActions.toSelectMode(); } }, init: function init$$1(inputElem) { if (!curtext) { return; } var i = void 0, end = void 0; // if (supportsEditableText()) { // curtext.select(); // return; // } if (!curtext.parentNode) { // Result of the ffClone, need to get correct element curtext = selectedElements[0]; selectorManager.requestSelector(curtext).showGrips(false); } var str = curtext.textContent; var len = str.length; var xform = curtext.getAttribute('transform'); textbb = getBBox(curtext); matrix = xform ? getMatrix(curtext) : null; chardata = []; chardata.length = len; textinput.focus(); $$9(curtext).unbind('dblclick', selectWord).dblclick(selectWord); if (!len) { end = { x: textbb.x + textbb.width / 2, width: 0 }; } for (i = 0; i < len; i++) { var start = curtext.getStartPositionOfChar(i); end = curtext.getEndPositionOfChar(i); if (!supportsGoodTextCharPos()) { var offset = canvas.contentW * currentZoom; start.x -= offset; end.x -= offset; start.x /= currentZoom; end.x /= currentZoom; } // Get a "bbox" equivalent for each character. Uses the // bbox data of the actual text for y, height purposes // TODO: Decide if y, width and height are actually necessary chardata[i] = { x: start.x, y: textbb.y, // start.y? width: end.x - start.x, height: textbb.height }; } // Add a last bbox for cursor at end of text chardata.push({ x: end.x, width: 0 }); setSelection(textinput.selectionStart, textinput.selectionEnd, true); } }; }(); /** * Group: Serialization */ /** * Looks at DOM elements inside the to see if they are referred to, * removes them from the DOM if they are not. * @returns The amount of elements that were removed */ var removeUnusedDefElems = this.removeUnusedDefElems = function () { var defs = svgcontent.getElementsByTagNameNS(NS.SVG, 'defs'); if (!defs || !defs.length) { return 0; } // if (!defs.firstChild) { return; } var defelemUses = []; var numRemoved = 0; var attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end']; var alen = attrs.length; var allEls = svgcontent.getElementsByTagNameNS(NS.SVG, '*'); var allLen = allEls.length; var i = void 0, j = void 0; for (i = 0; i < allLen; i++) { var el = allEls[i]; for (j = 0; j < alen; j++) { var ref = getUrlFromAttr(el.getAttribute(attrs[j])); if (ref) { defelemUses.push(ref.substr(1)); } } // gradients can refer to other gradients var href = getHref(el); if (href && href.startsWith('#')) { defelemUses.push(href.substr(1)); } } var defelems = $$9(defs).find('linearGradient, radialGradient, filter, marker, svg, symbol'); i = defelems.length; while (i--) { var defelem = defelems[i]; var id = defelem.id; if (!defelemUses.includes(id)) { // Not found, so remove (but remember) removedElements[id] = defelem; defelem.remove(); numRemoved++; } } return numRemoved; }; /** * Main function to set up the SVG content for output * @returns {String} The SVG image for output */ this.svgCanvasToString = function () { // keep calling it until there are none to remove while (removeUnusedDefElems() > 0) {} pathActions$$1.clear(true); // Keep SVG-Edit comment on top $$9.each(svgcontent.childNodes, function (i, node) { if (i && node.nodeType === 8 && node.data.includes('Created with')) { svgcontent.firstChild.before(node); } }); // Move out of in-group editing mode if (currentGroup) { leaveContext(); selectOnly([currentGroup]); } var nakedSvgs = []; // Unwrap gsvg if it has no special attributes (only id and style) $$9(svgcontent).find('g:data(gsvg)').each(function () { var attrs = this.attributes; var len = attrs.length; for (var i = 0; i < len; i++) { if (attrs[i].nodeName === 'id' || attrs[i].nodeName === 'style') { len--; } } // No significant attributes, so ungroup if (len <= 0) { var svg = this.firstChild; nakedSvgs.push(svg); $$9(this).replaceWith(svg); } }); var output = this.svgToString(svgcontent, 0); // Rewrap gsvg if (nakedSvgs.length) { $$9(nakedSvgs).each(function () { groupSvgElem(this); }); } return output; }; /** * Sub function ran on each SVG element to convert it to a string as desired * @param elem - The SVG element to convert * @param {Number} indent - Integer with the amount of spaces to indent this tag * @returns {String} The given element as an SVG tag */ this.svgToString = function (elem, indent) { var out = []; var unit = curConfig.baseUnit; var unitRe = new RegExp('^-?[\\d\\.]+' + unit + '$'); if (elem) { cleanupElement(elem); var attrs = Array.from(elem.attributes); var i = void 0; var childs = elem.childNodes; attrs.sort(function (a, b) { return a.name > b.name ? -1 : 1; }); for (i = 0; i < indent; i++) { out.push(' '); } out.push('<');out.push(elem.nodeName); if (elem.id === 'svgcontent') { // Process root element separately var res = getResolution(); var vb = ''; // TODO: Allow this by dividing all values by current baseVal // Note that this also means we should properly deal with this on import // if (curConfig.baseUnit !== 'px') { // const unit = curConfig.baseUnit; // const unitM = getTypeMap()[unit]; // res.w = shortFloat(res.w / unitM); // res.h = shortFloat(res.h / unitM); // vb = ' viewBox="' + [0, 0, res.w, res.h].join(' ') + '"'; // res.w += unit; // res.h += unit; // } if (unit !== 'px') { res.w = convertUnit(res.w, unit) + unit; res.h = convertUnit(res.h, unit) + unit; } out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="' + NS.SVG + '"'); var nsuris = {}; // Check elements for namespaces, add if found $$9(elem).find('*').andSelf().each(function () { // const el = this; // for some elements have no attribute var uri = this.namespaceURI; if (uri && !nsuris[uri] && nsMap[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml') { nsuris[uri] = true; out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"'); } $$9.each(this.attributes, function (i, attr) { var uri = attr.namespaceURI; if (uri && !nsuris[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml') { nsuris[uri] = true; out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"'); } }); }); i = attrs.length; var attrNames = ['width', 'height', 'xmlns', 'x', 'y', 'viewBox', 'id', 'overflow']; while (i--) { var attr = attrs[i]; var attrVal = toXml(attr.value); // Namespaces have already been dealt with, so skip if (attr.nodeName.startsWith('xmlns:')) { continue; } // only serialize attributes we don't use internally if (attrVal !== '' && !attrNames.includes(attr.localName)) { if (!attr.namespaceURI || nsMap[attr.namespaceURI]) { out.push(' '); out.push(attr.nodeName);out.push('="'); out.push(attrVal);out.push('"'); } } } } else { // Skip empty defs if (elem.nodeName === 'defs' && !elem.firstChild) { return; } var mozAttrs = ['-moz-math-font-style', '_moz-math-font-style']; for (i = attrs.length - 1; i >= 0; i--) { var _attr = attrs[i]; var _attrVal = toXml(_attr.value); // remove bogus attributes added by Gecko if (mozAttrs.includes(_attr.localName)) { continue; } if (_attrVal !== '') { if (_attrVal.startsWith('pointer-events')) { continue; } if (_attr.localName === 'class' && _attrVal.startsWith('se_')) { continue; } out.push(' '); if (_attr.localName === 'd') { _attrVal = pathActions$$1.convertPath(elem, true); } if (!isNaN(_attrVal)) { _attrVal = shortFloat(_attrVal); } else if (unitRe.test(_attrVal)) { _attrVal = shortFloat(_attrVal) + unit; } // Embed images when saving if (saveOptions.apply && elem.nodeName === 'image' && _attr.localName === 'href' && saveOptions.images && saveOptions.images === 'embed') { var img = encodableImages[_attrVal]; if (img) { _attrVal = img; } } // map various namespaces to our fixed namespace prefixes // (the default xmlns attribute itself does not get a prefix) if (!_attr.namespaceURI || _attr.namespaceURI === NS.SVG || nsMap[_attr.namespaceURI]) { out.push(_attr.nodeName);out.push('="'); out.push(_attrVal);out.push('"'); } } } } if (elem.hasChildNodes()) { out.push('>'); indent++; var bOneLine = false; for (i = 0; i < childs.length; i++) { var child = childs.item(i); switch (child.nodeType) { case 1: // element node out.push('\n'); out.push(this.svgToString(childs.item(i), indent)); break; case 3: // text node var str = child.nodeValue.replace(/^\s+|\s+$/g, ''); if (str !== '') { bOneLine = true; out.push(String(toXml(str))); } break; case 4: // cdata node out.push('\n'); out.push(new Array(indent + 1).join(' ')); out.push(''); out.push(child.nodeValue); out.push(''); break; case 8: // comment out.push('\n'); out.push(new Array(indent + 1).join(' ')); out.push(''); break; } // switch on node type } indent--; if (!bOneLine) { out.push('\n'); for (i = 0; i < indent; i++) { out.push(' '); } } out.push(''); } else { out.push('/>'); } } return out.join(''); }; // end svgToString() /** * Converts a given image file to a data URL when possible, then runs a given callback * @param {String} val - String with the path/URL of the image * @param {Function} callback - Optional function to run when image data is found, supplies the * result (data URL or false) as first parameter. */ this.embedImage = function (val, callback) { // load in the image and once it's loaded, get the dimensions $$9(new Image()).load(function () { // create a canvas the same size as the raster image var canvas = document.createElement('canvas'); canvas.width = this.width; canvas.height = this.height; // load the raster image into the canvas canvas.getContext('2d').drawImage(this, 0, 0); // retrieve the data: URL try { var urldata = ';svgedit_url=' + encodeURIComponent(val); urldata = canvas.toDataURL().replace(';base64', urldata + ';base64'); encodableImages[val] = urldata; } catch (e) { encodableImages[val] = false; } lastGoodImgUrl = val; if (callback) { callback(encodableImages[val]); } }).attr('src', val); }; /** * Sets a given URL to be a "last good image" URL */ this.setGoodImage = function (val) { lastGoodImgUrl = val; }; /** * */ this.open = function () { // Nothing by default, handled by optional widget/extension }; /** * Serializes the current drawing into SVG XML text and returns it to the 'saved' handler. * This function also includes the XML prolog. Clients of the SvgCanvas bind their save * function to the 'saved' event. */ this.save = function (opts) { // remove the selected outline before serializing clearSelection(); // Update save options if provided if (opts) { $$9.extend(saveOptions, opts); } saveOptions.apply = true; // no need for doctype, see https://jwatt.org/svg/authoring/#doctype-declaration var str = this.svgCanvasToString(); call('saved', str); }; /** * Codes only is useful for locale-independent detection */ function getIssues() { // remove the selected outline before serializing clearSelection(); // Check for known CanVG issues var issues = []; var issueCodes = []; // Selector and notice var issueList = { feGaussianBlur: uiStrings.exportNoBlur, foreignObject: uiStrings.exportNoforeignObject, '[stroke-dasharray]': uiStrings.exportNoDashArray }; var content = $$9(svgcontent); // Add font/text check if Canvas Text API is not implemented if (!('font' in $$9('')[0].getContext('2d'))) { issueList.text = uiStrings.exportNoText; } $$9.each(issueList, function (sel, descr) { if (content.find(sel).length) { issueCodes.push(sel); issues.push(descr); } }); return { issues: issues, issueCodes: issueCodes }; } var canvg = void 0; /** * Generates a Data URL based on the current image, then calls "exported" * with an object including the string, image information, and any issues found * @param {String} [imgType="PNG"] * @param {Number} [quality] Between 0 and 1 * @param {String} [exportWindowName] * @param {Function} [cb] * @returns {Promise} */ this.rasterExport = function (imgType, quality, exportWindowName, cb) { var _this = this; var mimeType = 'image/' + imgType.toLowerCase(); var _getIssues = getIssues(), issues = _getIssues.issues, issueCodes = _getIssues.issueCodes; var svg = this.svgCanvasToString(); return new Promise(function () { var _ref3 = asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(resolve, reject) { var _ref4, type, c, dataURLType, datauri, bloburl, done; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: done = function done() { var obj = { datauri: datauri, bloburl: bloburl, svg: svg, issues: issues, issueCodes: issueCodes, type: imgType, mimeType: mimeType, quality: quality, exportWindowName: exportWindowName }; call('exported', obj); if (cb) { cb(obj); } resolve(obj); }; if (canvg) { _context.next = 6; break; } _context.next = 4; return importSetGlobal(curConfig.canvgPath + 'canvg.js', { global: 'canvg' }); case 4: _ref4 = _context.sent; canvg = _ref4.canvg; case 6: type = imgType || 'PNG'; if (!$$9('#export_canvas').length) { $$9('', { id: 'export_canvas' }).hide().appendTo('body'); } c = $$9('#export_canvas')[0]; c.width = canvas.contentW; c.height = canvas.contentH; _context.next = 13; return canvg(c, svg); case 13: dataURLType = (type === 'ICO' ? 'BMP' : type).toLowerCase(); datauri = quality ? c.toDataURL('image/' + dataURLType, quality) : c.toDataURL('image/' + dataURLType); bloburl = void 0; if (!c.toBlob) { _context.next = 19; break; } c.toBlob(function (blob) { bloburl = createObjectURL(blob); done(); }, mimeType, quality); return _context.abrupt('return'); case 19: bloburl = dataURLToObjectURL(datauri); done(); case 21: case 'end': return _context.stop(); } } }, _callee, _this); })); return function (_x, _x2) { return _ref3.apply(this, arguments); }; }()); }; /** * @param {String} exportWindowName * @param outputType Needed? * @param {Function} cb * @returns {Promise} */ this.exportPDF = function (exportWindowName, outputType, cb) { var _this2 = this; var that = this; return new Promise(function () { var _ref5 = asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(resolve, reject) { var modularVersion, res, orientation, unit, doc, docTitle, _getIssues2, issues, issueCodes, svg, obj, method; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (window.jsPDF) { _context2.next = 6; break; } _context2.next = 3; return importScript([ // We do not currently have these paths configurable as they are // currently global-only, so not Rolled-up 'jspdf/underscore-min.js', 'jspdf/jspdf.min.js']); case 3: modularVersion = !('svgEditor' in window) || !window.svgEditor || window.svgEditor.modules !== false; // Todo: Switch to `import()` when widely supported and available (also allow customization of path) _context2.next = 6; return importScript(curConfig.jspdfPath + 'jspdf.plugin.svgToPdf.js', { type: modularVersion ? 'module' : 'text/javascript' }); case 6: res = getResolution(); orientation = res.w > res.h ? 'landscape' : 'portrait'; unit = 'pt'; // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes doc = jsPDF({ orientation: orientation, unit: unit, format: [res.w, res.h] // , compressPdf: true }); // Todo: Give options to use predefined jsPDF formats like "a4", etc. from pull-down (with option to keep customizable) docTitle = getDocumentTitle(); doc.setProperties({ title: docTitle /* , subject: '', author: '', keywords: '', creator: '' */ }); _getIssues2 = getIssues(), issues = _getIssues2.issues, issueCodes = _getIssues2.issueCodes; svg = that.svgCanvasToString(); doc.addSVG(svg, 0, 0); // doc.output('save'); // Works to open in a new // window; todo: configure this and other export // options to optionally work in this manner as // opposed to opening a new tab obj = { svg: svg, issues: issues, issueCodes: issueCodes, exportWindowName: exportWindowName }; method = outputType || 'dataurlstring'; obj[method] = doc.output(method); if (cb) { cb(obj); } resolve(obj); call('exportedPDF', obj); case 21: case 'end': return _context2.stop(); } } }, _callee2, _this2); })); return function (_x3, _x4) { return _ref5.apply(this, arguments); }; }()); }; /** * Returns the current drawing as raw SVG XML text. * @returns The current drawing as raw SVG XML text. */ this.getSvgString = function () { saveOptions.apply = false; return this.svgCanvasToString(); }; /** * This function determines whether to use a nonce in the prefix, when * generating IDs for future documents in SVG-Edit. * @param {Boolean} [enableRandomization] If true, adds a nonce to the prefix. Thus * svgCanvas.randomizeIds() <==> svgCanvas.randomizeIds(true) * * if you're controlling SVG-Edit externally, and want randomized IDs, call * this BEFORE calling svgCanvas.setSvgString */ this.randomizeIds = function (enableRandomization) { if (arguments.length > 0 && enableRandomization === false) { randomizeIds(false, getCurrentDrawing()); } else { randomizeIds(true, getCurrentDrawing()); } }; /** * Ensure each element has a unique ID * @param g - The parent element of the tree to give unique IDs */ var uniquifyElems = this.uniquifyElems = function (g) { var ids = {}; // TODO: Handle markers and connectors. These are not yet re-identified properly // as their referring elements do not get remapped. // // // // // Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute // Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute var refElems = ['filter', 'linearGradient', 'pattern', 'radialGradient', 'symbol', 'textPath', 'use']; walkTree(g, function (n) { // if it's an element node if (n.nodeType === 1) { // and the element has an ID if (n.id) { // and we haven't tracked this ID yet if (!(n.id in ids)) { // add this id to our map ids[n.id] = { elem: null, attrs: [], hrefs: [] }; } ids[n.id].elem = n; } // now search for all attributes on this element that might refer // to other elements $$9.each(refAttrs, function (i, attr) { var attrnode = n.getAttributeNode(attr); if (attrnode) { // the incoming file has been sanitized, so we should be able to safely just strip off the leading # var url = getUrlFromAttr(attrnode.value), refid = url ? url.substr(1) : null; if (refid) { if (!(refid in ids)) { // add this id to our map ids[refid] = { elem: null, attrs: [], hrefs: [] }; } ids[refid].attrs.push(attrnode); } } }); // check xlink:href now var href = getHref(n); // TODO: what if an or element refers to an element internally? if (href && refElems.includes(n.nodeName)) { var refid = href.substr(1); if (refid) { if (!(refid in ids)) { // add this id to our map ids[refid] = { elem: null, attrs: [], hrefs: [] }; } ids[refid].hrefs.push(n); } } } }); // in ids, we now have a map of ids, elements and attributes, let's re-identify for (var oldid in ids) { if (!oldid) { continue; } var elem = ids[oldid].elem; if (elem) { var newid = getNextId(); // assign element its new id elem.id = newid; // remap all url() attributes var attrs = ids[oldid].attrs; var j = attrs.length; while (j--) { var attr = attrs[j]; attr.ownerElement.setAttribute(attr.name, 'url(#' + newid + ')'); } // remap all href attributes var hreffers = ids[oldid].hrefs; var k = hreffers.length; while (k--) { var hreffer = hreffers[k]; setHref(hreffer, '#' + newid); } } } }; /** * Assigns reference data for each use element */ var setUseData = this.setUseData = function (parent) { var elems = $$9(parent); if (parent.tagName !== 'use') { elems = elems.find('use'); } elems.each(function () { var id = getHref(this).substr(1); var refElem = getElem(id); if (!refElem) { return; } $$9(this).data('ref', refElem); if (refElem.tagName === 'symbol' || refElem.tagName === 'svg') { $$9(this).data('symbol', refElem).data('ref', refElem); } }); }; /** * Converts gradients from userSpaceOnUse to objectBoundingBox * @param elem */ var convertGradients = this.convertGradients = function (elem) { var elems = $$9(elem).find('linearGradient, radialGradient'); if (!elems.length && isWebkit()) { // Bug in webkit prevents regular *Gradient selector search elems = $$9(elem).find('*').filter(function () { return this.tagName.includes('Gradient'); }); } elems.each(function () { var grad = this; if ($$9(grad).attr('gradientUnits') === 'userSpaceOnUse') { // TODO: Support more than one element with this ref by duplicating parent grad var _elems = $$9(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]'); if (!_elems.length) { return; } // get object's bounding box var bb = getBBox(_elems[0]); // This will occur if the element is inside a or a , // in which we shouldn't need to convert anyway. if (!bb) { return; } if (grad.tagName === 'linearGradient') { var gCoords = $$9(grad).attr(['x1', 'y1', 'x2', 'y2']); // If has transform, convert var tlist = grad.gradientTransform.baseVal; if (tlist && tlist.numberOfItems > 0) { var m = transformListToTransform(tlist).matrix; var pt1 = transformPoint(gCoords.x1, gCoords.y1, m); var pt2 = transformPoint(gCoords.x2, gCoords.y2, m); gCoords.x1 = pt1.x; gCoords.y1 = pt1.y; gCoords.x2 = pt2.x; gCoords.y2 = pt2.y; grad.removeAttribute('gradientTransform'); } $$9(grad).attr({ x1: (gCoords.x1 - bb.x) / bb.width, y1: (gCoords.y1 - bb.y) / bb.height, x2: (gCoords.x2 - bb.x) / bb.width, y2: (gCoords.y2 - bb.y) / bb.height }); grad.removeAttribute('gradientUnits'); } // else { // Note: radialGradient elements cannot be easily converted // because userSpaceOnUse will keep circular gradients, while // objectBoundingBox will x/y scale the gradient according to // its bbox. // // For now we'll do nothing, though we should probably have // the gradient be updated as the element is moved, as // inkscape/illustrator do. // // const gCoords = $(grad).attr(['cx', 'cy', 'r']); // // $(grad).attr({ // cx: (gCoords.cx - bb.x) / bb.width, // cy: (gCoords.cy - bb.y) / bb.height, // r: gCoords.r // }); // // grad.removeAttribute('gradientUnits'); // } } }); }; /** * Converts selected/given or child SVG element to a group * @param elem */ var convertToGroup = this.convertToGroup = function (elem) { if (!elem) { elem = selectedElements[0]; } var $elem = $$9(elem); var batchCmd = new BatchCommand$1(); var ts = void 0; if ($elem.data('gsvg')) { // Use the gsvg as the new group var svg = elem.firstChild; var pt = $$9(svg).attr(['x', 'y']); $$9(elem.firstChild.firstChild).unwrap(); $$9(elem).removeData('gsvg'); var tlist = getTransformList(elem); var xform = svgroot.createSVGTransform(); xform.setTranslate(pt.x, pt.y); tlist.appendItem(xform); recalculateDimensions(elem); call('selected', [elem]); } else if ($elem.data('symbol')) { elem = $elem.data('symbol'); ts = $elem.attr('transform'); var pos = $elem.attr(['x', 'y']); var vb = elem.getAttribute('viewBox'); if (vb) { var nums = vb.split(' '); pos.x -= +nums[0]; pos.y -= +nums[1]; } // Not ideal, but works ts += ' translate(' + (pos.x || 0) + ',' + (pos.y || 0) + ')'; var prev = $elem.prev(); // Remove element batchCmd.addSubCommand(new RemoveElementCommand$1($elem[0], $elem[0].nextSibling, $elem[0].parentNode)); $elem.remove(); // See if other elements reference this symbol var hasMore = $$9(svgcontent).find('use:data(symbol)').length; var g = svgdoc.createElementNS(NS.SVG, 'g'); var childs = elem.childNodes; var i = void 0; for (i = 0; i < childs.length; i++) { g.append(childs[i].cloneNode(true)); } // Duplicate the gradients for Gecko, since they weren't included in the if (isGecko()) { var dupeGrads = $$9(findDefs()).children('linearGradient,radialGradient,pattern').clone(); $$9(g).append(dupeGrads); } if (ts) { g.setAttribute('transform', ts); } var parent = elem.parentNode; uniquifyElems(g); // Put the dupe gradients back into (after uniquifying them) if (isGecko()) { $$9(findDefs()).append($$9(g).find('linearGradient,radialGradient,pattern')); } // now give the g itself a new id g.id = getNextId(); prev.after(g); if (parent) { if (!hasMore) { // remove symbol/svg element var _elem = elem, nextSibling = _elem.nextSibling; elem.remove(); batchCmd.addSubCommand(new RemoveElementCommand$1(elem, nextSibling, parent)); } batchCmd.addSubCommand(new InsertElementCommand$1(g)); } setUseData(g); if (isGecko()) { convertGradients(findDefs()); } else { convertGradients(g); } // recalculate dimensions on the top-level children so that unnecessary transforms // are removed walkTreePost(g, function (n) { try { recalculateDimensions(n); } catch (e) { console.log(e); } }); // Give ID for any visible element missing one $$9(g).find(visElems).each(function () { if (!this.id) { this.id = getNextId(); } }); selectOnly([g]); var cm = pushGroupProperties(g, true); if (cm) { batchCmd.addSubCommand(cm); } addCommandToHistory(batchCmd); } else { console.log('Unexpected element to ungroup:', elem); } }; /** * This function sets the current drawing as the input SVG XML. * @param {String} xmlString - The SVG as XML text. * @param {Boolean} [preventUndo=false] - Indicates if we want to do the * changes without adding them to the undo stack - e.g. for initializing a * drawing on page load. * @returns {Boolean} This function returns false if the set was * unsuccessful, true otherwise. */ this.setSvgString = function (xmlString, preventUndo) { try { // convert string into XML document var newDoc = text2xml(xmlString); if (newDoc.firstElementChild && newDoc.firstElementChild.namespaceURI !== NS.SVG) { return false; } this.prepareSvg(newDoc); var batchCmd = new BatchCommand$1('Change Source'); // remove old svg document var _svgcontent = svgcontent, nextSibling = _svgcontent.nextSibling; var oldzoom = svgroot.removeChild(svgcontent); batchCmd.addSubCommand(new RemoveElementCommand$1(oldzoom, nextSibling, svgroot)); // set new svg document // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode() if (svgdoc.adoptNode) { svgcontent = svgdoc.adoptNode(newDoc.documentElement); } else { svgcontent = svgdoc.importNode(newDoc.documentElement, true); } svgroot.append(svgcontent); var content = $$9(svgcontent); canvas.current_drawing_ = new Drawing(svgcontent, idprefix); // retrieve or set the nonce var nonce = getCurrentDrawing().getNonce(); if (nonce) { call('setnonce', nonce); } else { call('unsetnonce'); } // change image href vals if possible content.find('image').each(function () { var image = this; preventClickDefault(image); var val = getHref(this); if (val) { if (val.startsWith('data:')) { // Check if an SVG-edit data URI var m = val.match(/svgedit_url=(.*?);/); if (m) { var url = decodeURIComponent(m[1]); $$9(new Image()).load(function () { image.setAttributeNS(NS.XLINK, 'xlink:href', url); }).attr('src', url); } } // Add to encodableImages if it loads canvas.embedImage(val); } }); // Wrap child SVGs in group elements content.find('svg').each(function () { // Skip if it's in a if ($$9(this).closest('defs').length) { return; } uniquifyElems(this); // Check if it already has a gsvg group var pa = this.parentNode; if (pa.childNodes.length === 1 && pa.nodeName === 'g') { $$9(pa).data('gsvg', this); pa.id = pa.id || getNextId(); } else { groupSvgElem(this); } }); // For Firefox: Put all paint elems in defs if (isGecko()) { content.find('linearGradient, radialGradient, pattern').appendTo(findDefs()); } // Set ref element for elements // TODO: This should also be done if the object is re-added through "redo" setUseData(content); convertGradients(content[0]); var attrs = { id: 'svgcontent', overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden' }; var percs = false; // determine proper size if (content.attr('viewBox')) { var vb = content.attr('viewBox').split(' '); attrs.width = vb[2]; attrs.height = vb[3]; // handle content that doesn't have a viewBox } else { $$9.each(['width', 'height'], function (i, dim) { // Set to 100 if not given var val = content.attr(dim) || '100%'; if (String(val).substr(-1) === '%') { // Use user units if percentage given percs = true; } else { attrs[dim] = convertToNum(dim, val); } }); } // identify layers identifyLayers(); // Give ID for any visible layer children missing one content.children().find(visElems).each(function () { if (!this.id) { this.id = getNextId(); } }); // Percentage width/height, so let's base it on visible elements if (percs) { var bb = getStrokedBBoxDefaultVisible(); attrs.width = bb.width + bb.x; attrs.height = bb.height + bb.y; } // Just in case negative numbers are given or // result from the percs calculation if (attrs.width <= 0) { attrs.width = 100; } if (attrs.height <= 0) { attrs.height = 100; } content.attr(attrs); this.contentW = attrs.width; this.contentH = attrs.height; batchCmd.addSubCommand(new InsertElementCommand$1(svgcontent)); // update root to the correct size var changes = content.attr(['width', 'height']); batchCmd.addSubCommand(new ChangeElementCommand$1(svgroot, changes)); // reset zoom currentZoom = 1; // reset transform lists resetListMap(); clearSelection(); clearData(); svgroot.append(selectorManager.selectorParentGroup); if (!preventUndo) addCommandToHistory(batchCmd); call('changed', [svgcontent]); } catch (e) { console.log(e); return false; } return true; }; /** * This function imports the input SVG XML as a <symbol> in the <defs>, then adds a * <use> to the current layer. * @param {String} xmlString - The SVG as XML text. * @returns This function returns null if the import was unsuccessful, or the element otherwise. * @todo * - properly handle if namespace is introduced by imported content (must add to svgcontent * and update all prefixes in the imported node) * - properly handle recalculating dimensions, recalculateDimensions() doesn't handle * arbitrary transform lists, but makes some assumptions about how the transform list * was obtained * - import should happen in top-left of current zoomed viewport */ this.importSvgString = function (xmlString) { var j = void 0, ts = void 0, useEl = void 0; try { // Get unique ID var uid = encode64(xmlString.length + xmlString).substr(0, 32); var useExisting = false; // Look for symbol and make sure symbol exists in image if (importIds[uid]) { if ($$9(importIds[uid].symbol).parents('#svgroot').length) { useExisting = true; } } var batchCmd = new BatchCommand$1('Import Image'); var symbol = void 0; if (useExisting) { symbol = importIds[uid].symbol; ts = importIds[uid].xform; } else { // convert string into XML document var newDoc = text2xml(xmlString); this.prepareSvg(newDoc); // import new svg document into our document var svg = void 0; // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode() if (svgdoc.adoptNode) { svg = svgdoc.adoptNode(newDoc.documentElement); } else { svg = svgdoc.importNode(newDoc.documentElement, true); } uniquifyElems(svg); var innerw = convertToNum('width', svg.getAttribute('width')), innerh = convertToNum('height', svg.getAttribute('height')), innervb = svg.getAttribute('viewBox'), // if no explicit viewbox, create one out of the width and height vb = innervb ? innervb.split(' ') : [0, 0, innerw, innerh]; for (j = 0; j < 4; ++j) { vb[j] = +vb[j]; } // TODO: properly handle preserveAspectRatio var // canvasw = +svgcontent.getAttribute('width'), canvash = +svgcontent.getAttribute('height'); // imported content should be 1/3 of the canvas on its largest dimension if (innerh > innerw) { ts = 'scale(' + canvash / 3 / vb[3] + ')'; } else { ts = 'scale(' + canvash / 3 / vb[2] + ')'; } // Hack to make recalculateDimensions understand how to scale ts = 'translate(0) ' + ts + ' translate(0)'; symbol = svgdoc.createElementNS(NS.SVG, 'symbol'); var defs = findDefs(); if (isGecko()) { // Move all gradients into root for Firefox, workaround for this bug: // https://bugzilla.mozilla.org/show_bug.cgi?id=353575 // TODO: Make this properly undo-able. $$9(svg).find('linearGradient, radialGradient, pattern').appendTo(defs); } while (svg.firstChild) { var first = svg.firstChild; symbol.append(first); } var attrs = svg.attributes; for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; symbol.setAttribute(attr.nodeName, attr.value); } symbol.id = getNextId(); // Store data importIds[uid] = { symbol: symbol, xform: ts }; findDefs().append(symbol); batchCmd.addSubCommand(new InsertElementCommand$1(symbol)); } useEl = svgdoc.createElementNS(NS.SVG, 'use'); useEl.id = getNextId(); setHref(useEl, '#' + symbol.id); (currentGroup || getCurrentDrawing().getCurrentLayer()).append(useEl); batchCmd.addSubCommand(new InsertElementCommand$1(useEl)); clearSelection(); useEl.setAttribute('transform', ts); recalculateDimensions(useEl); $$9(useEl).data('symbol', symbol).data('ref', symbol); addToSelection([useEl]); // TODO: Find way to add this in a recalculateDimensions-parsable way // if (vb[0] !== 0 || vb[1] !== 0) { // ts = 'translate(' + (-vb[0]) + ',' + (-vb[1]) + ') ' + ts; // } addCommandToHistory(batchCmd); call('changed', [svgcontent]); } catch (e) { console.log(e); return null; } // we want to return the element so we can automatically select it return useEl; }; // Could deprecate, but besides external uses, their usage makes clear that // canvas is a dependency for all of these ['identifyLayers', 'createLayer', 'cloneLayer', 'deleteCurrentLayer', 'setCurrentLayer', 'renameCurrentLayer', 'setCurrentLayerPosition', 'setLayerVisibility', 'moveSelectedToLayer', 'mergeLayer', 'mergeAllLayers', 'leaveContext', 'setContext'].forEach(function (prop) { canvas[prop] = draw[prop]; }); init$3({ pathActions: pathActions$$1, getCurrentGroup: function getCurrentGroup() { return currentGroup; }, setCurrentGroup: function setCurrentGroup(cg) { currentGroup = cg; }, getSelectedElements: getSelectedElements, getSVGContent: getSVGContent, undoMgr: undoMgr, elData: elData, getCurrentDrawing: getCurrentDrawing, clearSelection: clearSelection, call: call, addCommandToHistory: addCommandToHistory, changeSvgcontent: function changeSvgcontent() { call('changed', [svgcontent]); } }); /** * Group: Document functions */ /** * Clears the current document. This is not an undoable action. */ this.clear = function () { pathActions$$1.clear(); clearSelection(); // clear the svgcontent node canvas.clearSvgContentElement(); // create new document canvas.current_drawing_ = new Drawing(svgcontent); // create empty first layer canvas.createLayer('Layer 1'); // clear the undo stack canvas.undoMgr.resetUndoStack(); // reset the selector manager selectorManager.initGroup(); // reset the rubber band box rubberBox = selectorManager.getRubberBandBox(); call('cleared'); }; /** * Alias function */ this.linkControlPoints = pathActions$$1.linkControlPoints; /** * @returns The content DOM element */ this.getContentElem = function () { return svgcontent; }; /** * @returns The root DOM element */ this.getRootElem = function () { return svgroot; }; /** * @returns {Object} The current dimensions and zoom level in an object */ var getResolution = this.getResolution = function () { // const vb = svgcontent.getAttribute('viewBox').split(' '); // return {w:vb[2], h:vb[3], zoom: currentZoom}; var w = svgcontent.getAttribute('width') / currentZoom; var h = svgcontent.getAttribute('height') / currentZoom; return { w: w, h: h, zoom: currentZoom }; }; /** * @returns The current snap to grid setting */ this.getSnapToGrid = function () { return curConfig.gridSnapping; }; /** * @returns {String} A string which describes the revision number of SvgCanvas. */ this.getVersion = function () { return 'svgcanvas.js ($Rev$)'; }; /** * Update interface strings with given values * @param strs - Object with strings (see locales file) */ this.setUiStrings = function (strs) { Object.assign(uiStrings, strs.notification); setUiStrings(strs); }; /** * Update configuration options with given values * @param {Object} opts - Object with options (see curConfig for examples) */ this.setConfig = function (opts) { Object.assign(curConfig, opts); }; /** * @param elem * @returns {String|undefined} the current group/SVG's title contents */ this.getTitle = function (elem) { elem = elem || selectedElements[0]; if (!elem) { return; } elem = $$9(elem).data('gsvg') || $$9(elem).data('symbol') || elem; var childs = elem.childNodes; for (var i = 0; i < childs.length; i++) { if (childs[i].nodeName === 'title') { return childs[i].textContent; } } return ''; }; /** * Sets the group/SVG's title content * @param val * @todo Combine this with `setDocumentTitle` */ this.setGroupTitle = function (val) { var elem = selectedElements[0]; elem = $$9(elem).data('gsvg') || elem; var ts = $$9(elem).children('title'); var batchCmd = new BatchCommand$1('Set Label'); var title = void 0; if (!val.length) { // Remove title element var tsNextSibling = ts.nextSibling; batchCmd.addSubCommand(new RemoveElementCommand$1(ts[0], tsNextSibling, elem)); ts.remove(); } else if (ts.length) { // Change title contents title = ts[0]; batchCmd.addSubCommand(new ChangeElementCommand$1(title, { '#text': title.textContent })); title.textContent = val; } else { // Add title element title = svgdoc.createElementNS(NS.SVG, 'title'); title.textContent = val; $$9(elem).prepend(title); batchCmd.addSubCommand(new InsertElementCommand$1(title)); } addCommandToHistory(batchCmd); }; /** * @returns {String|undefined} The current document title or an empty string if not found */ var getDocumentTitle = this.getDocumentTitle = function () { return canvas.getTitle(svgcontent); }; /** * Adds/updates a title element for the document with the given name. * This is an undoable action * @param {String} newtitle - String with the new title */ this.setDocumentTitle = function (newtitle) { var childs = svgcontent.childNodes; var docTitle = false, oldTitle = ''; var batchCmd = new BatchCommand$1('Change Image Title'); for (var i = 0; i < childs.length; i++) { if (childs[i].nodeName === 'title') { docTitle = childs[i]; oldTitle = docTitle.textContent; break; } } if (!docTitle) { docTitle = svgdoc.createElementNS(NS.SVG, 'title'); svgcontent.insertBefore(docTitle, svgcontent.firstChild); // svgcontent.firstChild.before(docTitle); // Ok to replace above with this? } if (newtitle.length) { docTitle.textContent = newtitle; } else { // No title given, so element is not necessary docTitle.remove(); } batchCmd.addSubCommand(new ChangeElementCommand$1(docTitle, { '#text': oldTitle })); addCommandToHistory(batchCmd); }; /** * Returns the editor's namespace URL, optionally adds it to root element * @param {Boolean} add - Indicates whether or not to add the namespace value * @returns {String} The editor's namespace URL */ this.getEditorNS = function (add) { if (add) { svgcontent.setAttribute('xmlns:se', NS.SE); } return NS.SE; }; /** * Changes the document's dimensions to the given size * @param x - Number with the width of the new dimensions in user units. * Can also be the string "fit" to indicate "fit to content" * @param y - Number with the height of the new dimensions in user units. * @returns {Boolean} Indicates if resolution change was succesful. * It will fail on "fit to content" option with no content to fit to. */ this.setResolution = function (x, y) { var res = getResolution(); var w = res.w, h = res.h; var batchCmd = void 0; if (x === 'fit') { // Get bounding box var bbox = getStrokedBBoxDefaultVisible(); if (bbox) { batchCmd = new BatchCommand$1('Fit Canvas to Content'); var visEls = getVisibleElements(); addToSelection(visEls); var dx = [], dy = []; $$9.each(visEls, function (i, item) { dx.push(bbox.x * -1); dy.push(bbox.y * -1); }); var cmd = canvas.moveSelectedElements(dx, dy, true); batchCmd.addSubCommand(cmd); clearSelection(); x = Math.round(bbox.width); y = Math.round(bbox.height); } else { return false; } } if (x !== w || y !== h) { if (!batchCmd) { batchCmd = new BatchCommand$1('Change Image Dimensions'); } x = convertToNum('width', x); y = convertToNum('height', y); svgcontent.setAttribute('width', x); svgcontent.setAttribute('height', y); this.contentW = x; this.contentH = y; batchCmd.addSubCommand(new ChangeElementCommand$1(svgcontent, { width: w, height: h })); svgcontent.setAttribute('viewBox', [0, 0, x / currentZoom, y / currentZoom].join(' ')); batchCmd.addSubCommand(new ChangeElementCommand$1(svgcontent, { viewBox: ['0 0', w, h].join(' ') })); addCommandToHistory(batchCmd); call('changed', [svgcontent]); } return true; }; /** * @returns An object with x, y values indicating the svgcontent element's * position in the editor's canvas. */ this.getOffset = function () { return $$9(svgcontent).attr(['x', 'y']); }; /** * Sets the zoom level on the canvas-side based on the given value * @param val - Bounding box object to zoom to or string indicating zoom option * @param {Number} editorW - Integer with the editor's workarea box's width * @param {Number} editorH - Integer with the editor's workarea box's height * @returns {Object|undefined} */ this.setBBoxZoom = function (val, editorW, editorH) { var spacer = 0.85; var bb = void 0; var calcZoom = function calcZoom(bb) { if (!bb) { return false; } var wZoom = Math.round(editorW / bb.width * 100 * spacer) / 100; var hZoom = Math.round(editorH / bb.height * 100 * spacer) / 100; var zoom = Math.min(wZoom, hZoom); canvas.setZoom(zoom); return { zoom: zoom, bbox: bb }; }; if ((typeof val === 'undefined' ? 'undefined' : _typeof(val)) === 'object') { bb = val; if (bb.width === 0 || bb.height === 0) { var newzoom = bb.zoom ? bb.zoom : currentZoom * bb.factor; canvas.setZoom(newzoom); return { zoom: currentZoom, bbox: bb }; } return calcZoom(bb); } switch (val) { case 'selection': if (!selectedElements[0]) { return; } var selectedElems = $$9.map(selectedElements, function (n) { if (n) { return n; } }); bb = getStrokedBBoxDefaultVisible(selectedElems); break; case 'canvas': var res = getResolution(); spacer = 0.95; bb = { width: res.w, height: res.h, x: 0, y: 0 }; break; case 'content': bb = getStrokedBBoxDefaultVisible(); break; case 'layer': bb = getStrokedBBoxDefaultVisible(getVisibleElements(getCurrentDrawing().getCurrentLayer())); break; default: return; } return calcZoom(bb); }; /** * Sets the zoom to the given level * @param {Number} zoomlevel - Float indicating the zoom level to change to */ this.setZoom = function (zoomlevel) { var res = getResolution(); svgcontent.setAttribute('viewBox', '0 0 ' + res.w / zoomlevel + ' ' + res.h / zoomlevel); currentZoom = zoomlevel; $$9.each(selectedElements, function (i, elem) { if (!elem) { return; } selectorManager.requestSelector(elem).resize(); }); pathActions$$1.zoomChange(); runExtensions('zoomChanged', zoomlevel); }; /** * @returns {String} The current editor mode string */ this.getMode = function () { return currentMode; }; /** * Sets the editor's mode to the given string * @param {String} name - String with the new mode to change to */ this.setMode = function (name) { pathActions$$1.clear(true); textActions.clear(); curProperties = selectedElements[0] && selectedElements[0].nodeName === 'text' ? curText : curShape; currentMode = name; }; /** * Group: Element Styling */ /** * @returns The current fill/stroke option */ this.getColor = function (type) { return curProperties[type]; }; /** * Change the current stroke/fill color/gradient value * @param {String} type - String indicating fill or stroke * @param val - The value to set the stroke attribute to * @param {Boolean} preventUndo - Boolean indicating whether or not this should be and undoable option */ this.setColor = function (type, val, preventUndo) { curShape[type] = val; curProperties[type + '_paint'] = { type: 'solidColor' }; var elems = []; function addNonG(e) { if (e.nodeName !== 'g') { elems.push(e); } } var i = selectedElements.length; while (i--) { var elem = selectedElements[i]; if (elem) { if (elem.tagName === 'g') { walkTree(elem, addNonG); } else { if (type === 'fill') { if (elem.tagName !== 'polyline' && elem.tagName !== 'line') { elems.push(elem); } } else { elems.push(elem); } } } } if (elems.length > 0) { if (!preventUndo) { changeSelectedAttribute(type, val, elems); call('changed', elems); } else { changeSelectedAttributeNoUndo(type, val, elems); } } }; // Apply the current gradient to selected element's fill or stroke // // Parameters // type - String indicating "fill" or "stroke" to apply to an element var setGradient = this.setGradient = function (type) { if (!curProperties[type + '_paint'] || curProperties[type + '_paint'].type === 'solidColor') { return; } var grad = canvas[type + 'Grad']; // find out if there is a duplicate gradient already in the defs var duplicateGrad = findDuplicateGradient(grad); var defs = findDefs(); // no duplicate found, so import gradient into defs if (!duplicateGrad) { // const origGrad = grad; grad = defs.appendChild(svgdoc.importNode(grad, true)); // get next id and set it on the grad grad.id = getNextId(); } else { // use existing gradient grad = duplicateGrad; } canvas.setColor(type, 'url(#' + grad.id + ')'); }; /** * Check if exact gradient already exists * @param grad - The gradient DOM element to compare to others * @returns The existing gradient if found, null if not */ var findDuplicateGradient = function findDuplicateGradient(grad) { var defs = findDefs(); var existingGrads = $$9(defs).find('linearGradient, radialGradient'); var i = existingGrads.length; var radAttrs = ['r', 'cx', 'cy', 'fx', 'fy']; while (i--) { var og = existingGrads[i]; if (grad.tagName === 'linearGradient') { if (grad.getAttribute('x1') !== og.getAttribute('x1') || grad.getAttribute('y1') !== og.getAttribute('y1') || grad.getAttribute('x2') !== og.getAttribute('x2') || grad.getAttribute('y2') !== og.getAttribute('y2')) { continue; } } else { var _ret = function () { var gradAttrs = $$9(grad).attr(radAttrs); var ogAttrs = $$9(og).attr(radAttrs); var diff = false; $$9.each(radAttrs, function (i, attr) { if (gradAttrs[attr] !== ogAttrs[attr]) { diff = true; } }); if (diff) { return 'continue'; } }(); if (_ret === 'continue') continue; } // else could be a duplicate, iterate through stops var stops = grad.getElementsByTagNameNS(NS.SVG, 'stop'); var ostops = og.getElementsByTagNameNS(NS.SVG, 'stop'); if (stops.length !== ostops.length) { continue; } var j = stops.length; while (j--) { var stop = stops[j]; var ostop = ostops[j]; if (stop.getAttribute('offset') !== ostop.getAttribute('offset') || stop.getAttribute('stop-opacity') !== ostop.getAttribute('stop-opacity') || stop.getAttribute('stop-color') !== ostop.getAttribute('stop-color')) { break; } } if (j === -1) { return og; } } // for each gradient in defs return null; }; /** * Set a color/gradient to a fill/stroke * @param {"fill"|"stroke"} type - String with "fill" or "stroke" * @param paint - The jGraduate paint object to apply */ this.setPaint = function (type, paint) { // make a copy var p = new $$9.jGraduate.Paint(paint); this.setPaintOpacity(type, p.alpha / 100, true); // now set the current paint object curProperties[type + '_paint'] = p; switch (p.type) { case 'solidColor': this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none'); break; case 'linearGradient': case 'radialGradient': canvas[type + 'Grad'] = p[p.type]; setGradient(type); break; } }; // alias this.setStrokePaint = function (paint) { this.setPaint('stroke', paint); }; this.setFillPaint = function (paint) { this.setPaint('fill', paint); }; /** * @returns The current stroke-width value */ this.getStrokeWidth = function () { return curProperties.stroke_width; }; /** * Sets the stroke width for the current selected elements * When attempting to set a line's width to 0, this changes it to 1 instead * @param {Number} val - A Float indicating the new stroke width value */ this.setStrokeWidth = function (val) { if (val === 0 && ['line', 'path'].includes(currentMode)) { canvas.setStrokeWidth(1); return; } curProperties.stroke_width = val; var elems = []; function addNonG(e) { if (e.nodeName !== 'g') { elems.push(e); } } var i = selectedElements.length; while (i--) { var elem = selectedElements[i]; if (elem) { if (elem.tagName === 'g') { walkTree(elem, addNonG); } else { elems.push(elem); } } } if (elems.length > 0) { changeSelectedAttribute('stroke-width', val, elems); call('changed', selectedElements); } }; /** * Set the given stroke-related attribute the given value for selected elements * @param {String} attr - String with the attribute name * @param {String|Number} val - String or number with the attribute value */ this.setStrokeAttr = function (attr, val) { curShape[attr.replace('-', '_')] = val; var elems = []; var i = selectedElements.length; while (i--) { var elem = selectedElements[i]; if (elem) { if (elem.tagName === 'g') { walkTree(elem, function (e) { if (e.nodeName !== 'g') { elems.push(e); } }); } else { elems.push(elem); } } } if (elems.length > 0) { changeSelectedAttribute(attr, val, elems); call('changed', selectedElements); } }; /** * @returns current style options */ this.getStyle = function () { return curShape; }; /** * @returns the current opacity */ this.getOpacity = getOpacity; /** * Sets the given opacity to the current selected elements * @param val */ this.setOpacity = function (val) { curShape.opacity = val; changeSelectedAttribute('opacity', val); }; /** * @returns the current fill opacity */ this.getFillOpacity = function () { return curShape.fill_opacity; }; /** * @returns the current stroke opacity */ this.getStrokeOpacity = function () { return curShape.stroke_opacity; }; /** * Sets the current fill/stroke opacity * @param {String} type - String with "fill" or "stroke" * @param {Number} val - Float with the new opacity value * @param {Boolean} preventUndo - Indicates whether or not this should be an undoable action */ this.setPaintOpacity = function (type, val, preventUndo) { curShape[type + '_opacity'] = val; if (!preventUndo) { changeSelectedAttribute(type + '-opacity', val); } else { changeSelectedAttributeNoUndo(type + '-opacity', val); } }; /** * Gets the current fill/stroke opacity * @param {"fill"|"stroke"} type - String with "fill" or "stroke" * @returns Fill/stroke opacity */ this.getPaintOpacity = function (type) { return type === 'fill' ? this.getFillOpacity() : this.getStrokeOpacity(); }; /** * Gets the stdDeviation blur value of the given element * @param elem - The element to check the blur value for * @returns stdDeviation blur attribute value */ this.getBlur = function (elem) { var val = 0; // const elem = selectedElements[0]; if (elem) { var filterUrl = elem.getAttribute('filter'); if (filterUrl) { var blur = getElem(elem.id + '_blur'); if (blur) { val = blur.firstChild.getAttribute('stdDeviation'); } } } return val; }; (function () { var curCommand = null; var filter = null; var filterHidden = false; /** * Sets the stdDeviation blur value on the selected element without being undoable * @param val - The new stdDeviation value */ canvas.setBlurNoUndo = function (val) { if (!filter) { canvas.setBlur(val); return; } if (val === 0) { // Don't change the StdDev, as that will hide the element. // Instead, just remove the value for "filter" changeSelectedAttributeNoUndo('filter', ''); filterHidden = true; } else { var elem = selectedElements[0]; if (filterHidden) { changeSelectedAttributeNoUndo('filter', 'url(#' + elem.id + '_blur)'); } if (isWebkit()) { console.log('e', elem); elem.removeAttribute('filter'); elem.setAttribute('filter', 'url(#' + elem.id + '_blur)'); } changeSelectedAttributeNoUndo('stdDeviation', val, [filter.firstChild]); canvas.setBlurOffsets(filter, val); } }; function finishChange() { var bCmd = canvas.undoMgr.finishUndoableChange(); curCommand.addSubCommand(bCmd); addCommandToHistory(curCommand); curCommand = null; filter = null; } /** * Sets the x, y, with, height values of the filter element in order to * make the blur not be clipped. Removes them if not neeeded * @param filter - The filter DOM element to update * @param stdDev - The standard deviation value on which to base the offset size */ canvas.setBlurOffsets = function (filter, stdDev) { if (stdDev > 3) { // TODO: Create algorithm here where size is based on expected blur assignAttributes(filter, { x: '-50%', y: '-50%', width: '200%', height: '200%' }, 100); } else { // Removing these attributes hides text in Chrome (see Issue 579) if (!isWebkit()) { filter.removeAttribute('x'); filter.removeAttribute('y'); filter.removeAttribute('width'); filter.removeAttribute('height'); } } }; /** * Adds/updates the blur filter to the selected element * @param {Number} val - Float with the new stdDeviation blur value * @param {Boolean} complete - Boolean indicating whether or not the action should be completed (to add to the undo manager) */ canvas.setBlur = function (val, complete) { if (curCommand) { finishChange(); return; } // Looks for associated blur, creates one if not found var elem = selectedElements[0]; var elemId = elem.id; filter = getElem(elemId + '_blur'); val -= 0; var batchCmd = new BatchCommand$1(); // Blur found! if (filter) { if (val === 0) { filter = null; } } else { // Not found, so create var newblur = addSvgElementFromJson({ element: 'feGaussianBlur', attr: { in: 'SourceGraphic', stdDeviation: val } }); filter = addSvgElementFromJson({ element: 'filter', attr: { id: elemId + '_blur' } }); filter.append(newblur); findDefs().append(filter); batchCmd.addSubCommand(new InsertElementCommand$1(filter)); } var changes = { filter: elem.getAttribute('filter') }; if (val === 0) { elem.removeAttribute('filter'); batchCmd.addSubCommand(new ChangeElementCommand$1(elem, changes)); return; } changeSelectedAttribute('filter', 'url(#' + elemId + '_blur)'); batchCmd.addSubCommand(new ChangeElementCommand$1(elem, changes)); canvas.setBlurOffsets(filter, val); curCommand = batchCmd; canvas.undoMgr.beginUndoableChange('stdDeviation', [filter ? filter.firstChild : null]); if (complete) { canvas.setBlurNoUndo(val); finishChange(); } }; })(); /** * Check whether selected element is bold or not * @returns {Boolean} Indicates whether or not element is bold */ this.getBold = function () { // should only have one element selected var selected = selectedElements[0]; if (selected != null && selected.tagName === 'text' && selectedElements[1] == null) { return selected.getAttribute('font-weight') === 'bold'; } return false; }; /** * Make the selected element bold or normal * @param {Boolean} b - Indicates bold (true) or normal (false) */ this.setBold = function (b) { var selected = selectedElements[0]; if (selected != null && selected.tagName === 'text' && selectedElements[1] == null) { changeSelectedAttribute('font-weight', b ? 'bold' : 'normal'); } if (!selectedElements[0].textContent) { textActions.setCursor(); } }; /** * Check whether selected element is italic or not * @returns {Boolean} Indicates whether or not element is italic */ this.getItalic = function () { var selected = selectedElements[0]; if (selected != null && selected.tagName === 'text' && selectedElements[1] == null) { return selected.getAttribute('font-style') === 'italic'; } return false; }; /** * Make the selected element italic or normal * @param {Boolean} b - Indicates italic (true) or normal (false) */ this.setItalic = function (i) { var selected = selectedElements[0]; if (selected != null && selected.tagName === 'text' && selectedElements[1] == null) { changeSelectedAttribute('font-style', i ? 'italic' : 'normal'); } if (!selectedElements[0].textContent) { textActions.setCursor(); } }; /** * @returns The current font family */ this.getFontFamily = function () { return curText.font_family; }; /** * Set the new font family * @param {String} val - String with the new font family */ this.setFontFamily = function (val) { curText.font_family = val; changeSelectedAttribute('font-family', val); if (selectedElements[0] && !selectedElements[0].textContent) { textActions.setCursor(); } }; /** * Set the new font color * @param {String} val - String with the new font color */ this.setFontColor = function (val) { curText.fill = val; changeSelectedAttribute('fill', val); }; /** * @returns The current font color */ this.getFontColor = function () { return curText.fill; }; /** * Returns the current font size */ this.getFontSize = function () { return curText.font_size; }; /** * Applies the given font size to the selected element * @param {Number} val - Float with the new font size */ this.setFontSize = function (val) { curText.font_size = val; changeSelectedAttribute('font-size', val); if (!selectedElements[0].textContent) { textActions.setCursor(); } }; /** * @returns The current text (textContent) of the selected element */ this.getText = function () { var selected = selectedElements[0]; if (selected == null) { return ''; } return selected.textContent; }; /** * Updates the text element with the given string * @param {String} val - String with the new text */ this.setTextContent = function (val) { changeSelectedAttribute('#text', val); textActions.init(val); textActions.setCursor(); }; /** * Sets the new image URL for the selected image element. Updates its size if * a new URL is given * @param {String} val - String with the image URL/path */ this.setImageURL = function (val) { var elem = selectedElements[0]; if (!elem) { return; } var attrs = $$9(elem).attr(['width', 'height']); var setsize = !attrs.width || !attrs.height; var curHref = getHref(elem); // Do nothing if no URL change or size change if (curHref !== val) { setsize = true; } else if (!setsize) { return; } var batchCmd = new BatchCommand$1('Change Image URL'); setHref(elem, val); batchCmd.addSubCommand(new ChangeElementCommand$1(elem, { '#href': curHref })); if (setsize) { $$9(new Image()).load(function () { var changes = $$9(elem).attr(['width', 'height']); $$9(elem).attr({ width: this.width, height: this.height }); selectorManager.requestSelector(elem).resize(); batchCmd.addSubCommand(new ChangeElementCommand$1(elem, changes)); addCommandToHistory(batchCmd); call('changed', [elem]); }).attr('src', val); } else { addCommandToHistory(batchCmd); } }; /** * Sets the new link URL for the selected anchor element. * @param {String} val - String with the link URL/path */ this.setLinkURL = function (val) { var elem = selectedElements[0]; if (!elem) { return; } if (elem.tagName !== 'a') { // See if parent is an anchor var parentsA = $$9(elem).parents('a'); if (parentsA.length) { elem = parentsA[0]; } else { return; } } var curHref = getHref(elem); if (curHref === val) { return; } var batchCmd = new BatchCommand$1('Change Link URL'); setHref(elem, val); batchCmd.addSubCommand(new ChangeElementCommand$1(elem, { '#href': curHref })); addCommandToHistory(batchCmd); }; /** * Sets the rx & ry values to the selected rect element to change its corner radius * @param val - The new radius */ this.setRectRadius = function (val) { var selected = selectedElements[0]; if (selected != null && selected.tagName === 'rect') { var r = selected.getAttribute('rx'); if (r !== String(val)) { selected.setAttribute('rx', val); selected.setAttribute('ry', val); addCommandToHistory(new ChangeElementCommand$1(selected, { rx: r, ry: r }, 'Radius')); call('changed', [selected]); } } }; /** * Wraps the selected element(s) in an anchor element or converts group to one * @param url */ this.makeHyperlink = function (url) { canvas.groupSelectedElements('a', url); // TODO: If element is a single "g", convert to "a" // if (selectedElements.length > 1 && selectedElements[1]) { }; /** * */ this.removeHyperlink = function () { canvas.ungroupSelectedElement(); }; /** * Group: Element manipulation */ /** * Sets the new segment type to the selected segment(s). * @param {Number} newType - Integer with the new segment type * See https://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg for list */ this.setSegType = function (newType) { pathActions$$1.setSegType(newType); }; /** * @todo (codedread): Remove the getBBox argument and split this function into two. * Convert selected element to a path, or get the BBox of an element-as-path * @param elem - The DOM element to be converted * @param getBBox - Boolean on whether or not to only return the path's BBox * @returns If the getBBox flag is true, the resulting path's bounding box object. * Otherwise the resulting path element is returned. */ this.convertToPath = function (elem, getBBox$$1) { if (elem == null) { var elems = selectedElements; $$9.each(elems, function (i, elem) { if (elem) { canvas.convertToPath(elem); } }); return; } if (getBBox$$1) { return getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions$$1); } else { // TODO: Why is this applying attributes from curShape, then inside utilities.convertToPath it's pulling addition attributes from elem? // TODO: If convertToPath is called with one elem, curShape and elem are probably the same; but calling with multiple is a bug or cool feature. var attrs = { fill: curShape.fill, 'fill-opacity': curShape.fill_opacity, stroke: curShape.stroke, 'stroke-width': curShape.stroke_width, 'stroke-dasharray': curShape.stroke_dasharray, 'stroke-linejoin': curShape.stroke_linejoin, 'stroke-linecap': curShape.stroke_linecap, 'stroke-opacity': curShape.stroke_opacity, opacity: curShape.opacity, visibility: 'hidden' }; return convertToPath(elem, attrs, addSvgElementFromJson, pathActions$$1, clearSelection, addToSelection, history, addCommandToHistory); } }; /** * This function makes the changes to the elements. It does not add the change * to the history stack. * @param {String} attr - Attribute name * @param {String|Number} newValue - String or number with the new attribute value * @param elems - The DOM elements to apply the change to */ var changeSelectedAttributeNoUndo = function changeSelectedAttributeNoUndo(attr, newValue, elems) { if (currentMode === 'pathedit') { // Editing node pathActions$$1.moveNode(attr, newValue); } elems = elems || selectedElements; var i = elems.length; var noXYElems = ['g', 'polyline', 'path']; var goodGAttrs = ['transform', 'opacity', 'filter']; var _loop = function _loop() { var elem = elems[i]; if (elem == null) { return 'continue'; } // Set x,y vals on elements that don't have them if ((attr === 'x' || attr === 'y') && noXYElems.includes(elem.tagName)) { var bbox = getStrokedBBoxDefaultVisible([elem]); var diffX = attr === 'x' ? newValue - bbox.x : 0; var diffY = attr === 'y' ? newValue - bbox.y : 0; canvas.moveSelectedElements(diffX * currentZoom, diffY * currentZoom, true); return 'continue'; } // only allow the transform/opacity/filter attribute to change on elements, slightly hacky // TODO: FIXME: This doesn't seem right. Where's the body of this if statement? if (elem.tagName === 'g' && goodGAttrs.includes(attr)) ; var oldval = attr === '#text' ? elem.textContent : elem.getAttribute(attr); if (oldval == null) { oldval = ''; } if (oldval !== String(newValue)) { if (attr === '#text') { // const oldW = utilsGetBBox(elem).width; elem.textContent = newValue; // FF bug occurs on on rotated elements if (/rotate/.test(elem.getAttribute('transform'))) { elem = ffClone(elem); } // Hoped to solve the issue of moving text with text-anchor="start", // but this doesn't actually fix it. Hopefully on the right track, though. -Fyrd // const box = getBBox(elem), left = box.x, top = box.y, {width, height} = box, // dx = width - oldW, dy = 0; // const angle = getRotationAngle(elem, true); // if (angle) { // const r = Math.sqrt(dx * dx + dy * dy); // const theta = Math.atan2(dy, dx) - angle; // dx = r * Math.cos(theta); // dy = r * Math.sin(theta); // // elem.setAttribute('x', elem.getAttribute('x') - dx); // elem.setAttribute('y', elem.getAttribute('y') - dy); // } } else if (attr === '#href') { setHref(elem, newValue); } else { elem.setAttribute(attr, newValue); } // Go into "select" mode for text changes // NOTE: Important that this happens AFTER elem.setAttribute() or else attributes like // font-size can get reset to their old value, ultimately by svgEditor.updateContextPanel(), // after calling textActions.toSelectMode() below if (currentMode === 'textedit' && attr !== '#text' && elem.textContent.length) { textActions.toSelectMode(elem); } // if (i === 0) { // selectedBBoxes[0] = utilsGetBBox(elem); // } // Use the Firefox ffClone hack for text elements with gradients or // where other text attributes are changed. if (isGecko() && elem.nodeName === 'text' && /rotate/.test(elem.getAttribute('transform'))) { if (String(newValue).startsWith('url') || ['font-size', 'font-family', 'x', 'y'].includes(attr) && elem.textContent) { elem = ffClone(elem); } } // Timeout needed for Opera & Firefox // codedread: it is now possible for this function to be called with elements // that are not in the selectedElements array, we need to only request a // selector if the element is in that array if (selectedElements.includes(elem)) { setTimeout(function () { // Due to element replacement, this element may no longer // be part of the DOM if (!elem.parentNode) { return; } selectorManager.requestSelector(elem).resize(); }, 0); } // if this element was rotated, and we changed the position of this element // we need to update the rotational transform attribute var angle = getRotationAngle(elem); if (angle !== 0 && attr !== 'transform') { var tlist = getTransformList(elem); var n = tlist.numberOfItems; while (n--) { var xform = tlist.getItem(n); if (xform.type === 4) { // remove old rotate tlist.removeItem(n); var box = getBBox(elem); var center = transformPoint(box.x + box.width / 2, box.y + box.height / 2, transformListToTransform(tlist).matrix); var cx = center.x, cy = center.y; var newrot = svgroot.createSVGTransform(); newrot.setRotate(angle, cx, cy); tlist.insertItemBefore(newrot, n); break; } } } } // if oldValue != newValue }; while (i--) { var _ret2 = _loop(); if (_ret2 === 'continue') continue; } // for each elem }; /** * Change the given/selected element and add the original value to the history stack * If you want to change all selectedElements, ignore the elems argument. * If you want to change only a subset of selectedElements, then send the * subset to this function in the elems argument. * @param {String} attr - String with the attribute name * @param {String|Number} newValue - String or number with the new attribute value * @param elems - The DOM elements to apply the change to */ var changeSelectedAttribute = this.changeSelectedAttribute = function (attr, val, elems) { elems = elems || selectedElements; canvas.undoMgr.beginUndoableChange(attr, elems); // const i = elems.length; changeSelectedAttributeNoUndo(attr, val, elems); var batchCmd = canvas.undoMgr.finishUndoableChange(); if (!batchCmd.isEmpty()) { addCommandToHistory(batchCmd); } }; // Removes all selected elements from the DOM and adds the change to the // history stack this.deleteSelectedElements = function () { var batchCmd = new BatchCommand$1('Delete Elements'); var len = selectedElements.length; var selectedCopy = []; // selectedElements is being deleted for (var i = 0; i < len; ++i) { var selected = selectedElements[i]; if (selected == null) { break; } var parent = selected.parentNode; var t = selected; // this will unselect the element and remove the selectedOutline selectorManager.releaseSelector(t); // Remove the path if present. removePath_(t.id); // Get the parent if it's a single-child anchor if (parent.tagName === 'a' && parent.childNodes.length === 1) { t = parent; parent = parent.parentNode; } var _t = t, nextSibling = _t.nextSibling; var _elem2 = parent.removeChild(t); selectedCopy.push(selected); // for the copy batchCmd.addSubCommand(new RemoveElementCommand$1(_elem2, nextSibling, parent)); } selectedElements = []; if (!batchCmd.isEmpty()) { addCommandToHistory(batchCmd); } call('changed', selectedCopy); clearSelection(); }; /** * Removes all selected elements from the DOM and adds the change to the * history stack. Remembers removed elements on the clipboard */ this.cutSelectedElements = function () { canvas.copySelectedElements(); canvas.deleteSelectedElements(); }; /** * Remembers the current selected elements on the clipboard */ this.copySelectedElements = function () { localStorage.setItem('svgedit_clipboard', JSON.stringify(selectedElements.map(function (x) { return getJsonFromSvgElement(x); }))); $$9('#cmenu_canvas').enableContextMenuItems('#paste,#paste_in_place'); }; /** * @param {"in_place"|"point"|undefined} type * @param {Number|undefined} x Expected if type is "point" * @param {Number|undefined} y Expected if type is "point" */ this.pasteElements = function (type, x, y) { var cb = JSON.parse(localStorage.getItem('svgedit_clipboard')); var len = cb.length; if (!len) { return; } var pasted = []; var batchCmd = new BatchCommand$1('Paste elements'); // const drawing = getCurrentDrawing(); var changedIDs = {}; // Recursively replace IDs and record the changes function checkIDs(elem) { if (elem.attr && elem.attr.id) { changedIDs[elem.attr.id] = getNextId(); elem.attr.id = changedIDs[elem.attr.id]; } if (elem.children) elem.children.forEach(checkIDs); } cb.forEach(checkIDs); // Give extensions like the connector extension a chance to reflect new IDs and remove invalid elements runExtensions('IDsUpdated', { elems: cb, changes: changedIDs }, true).forEach(function (extChanges) { if (!extChanges || !('remove' in extChanges)) return; extChanges.remove.forEach(function (removeID) { cb = cb.filter(function (cbItem) { return cbItem.attr.id !== removeID; }); }); }); // Move elements to lastClickPoint while (len--) { var _elem3 = cb[len]; if (!_elem3) { continue; } var copy = addSvgElementFromJson(_elem3); pasted.push(copy); batchCmd.addSubCommand(new InsertElementCommand$1(copy)); restoreRefElems(copy); } selectOnly(pasted); if (type !== 'in_place') { var ctrX = void 0, ctrY = void 0; if (!type) { ctrX = lastClickPoint.x; ctrY = lastClickPoint.y; } else if (type === 'point') { ctrX = x; ctrY = y; } var bbox = getStrokedBBoxDefaultVisible(pasted); var cx = ctrX - (bbox.x + bbox.width / 2), cy = ctrY - (bbox.y + bbox.height / 2), dx = [], dy = []; $$9.each(pasted, function (i, item) { dx.push(cx); dy.push(cy); }); var cmd = canvas.moveSelectedElements(dx, dy, false); if (cmd) batchCmd.addSubCommand(cmd); } addCommandToHistory(batchCmd); call('changed', pasted); }; /** * Wraps all the selected elements in a group (g) element * @param type - type of element to group into, defaults to <g> */ this.groupSelectedElements = function (type, urlArg) { if (!type) { type = 'g'; } var cmdStr = ''; var url = void 0; switch (type) { case 'a': { cmdStr = 'Make hyperlink'; url = ''; if (arguments.length > 1) { url = urlArg; } break; }default: { type = 'g'; cmdStr = 'Group Elements'; break; } } var batchCmd = new BatchCommand$1(cmdStr); // create and insert the group element var g = addSvgElementFromJson({ element: type, attr: { id: getNextId() } }); if (type === 'a') { setHref(g, url); } batchCmd.addSubCommand(new InsertElementCommand$1(g)); // now move all children into the group var i = selectedElements.length; while (i--) { var _elem4 = selectedElements[i]; if (_elem4 == null) { continue; } if (_elem4.parentNode.tagName === 'a' && _elem4.parentNode.childNodes.length === 1) { _elem4 = _elem4.parentNode; } var oldNextSibling = _elem4.nextSibling; var oldParent = _elem4.parentNode; g.append(_elem4); batchCmd.addSubCommand(new MoveElementCommand$1(_elem4, oldNextSibling, oldParent)); } if (!batchCmd.isEmpty()) { addCommandToHistory(batchCmd); } // update selection selectOnly([g], true); }; // Pushes all appropriate parent group properties down to its children, then // removes them from the group var pushGroupProperties = this.pushGroupProperties = function (g, undoable) { var children = g.childNodes; var len = children.length; var xform = g.getAttribute('transform'); var glist = getTransformList(g); var m = transformListToTransform(glist).matrix; var batchCmd = new BatchCommand$1('Push group properties'); // TODO: get all fill/stroke properties from the group that we are about to destroy // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset", // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", // "stroke-width" // and then for each child, if they do not have the attribute (or the value is 'inherit') // then set the child's attribute var gangle = getRotationAngle(g); var gattrs = $$9(g).attr(['filter', 'opacity']); var gfilter = void 0, gblur = void 0, changes = void 0; var drawing = getCurrentDrawing(); for (var i = 0; i < len; i++) { var _elem5 = children[i]; if (_elem5.nodeType !== 1) { continue; } if (gattrs.opacity !== null && gattrs.opacity !== 1) { // const c_opac = elem.getAttribute('opacity') || 1; var newOpac = Math.round((_elem5.getAttribute('opacity') || 1) * gattrs.opacity * 100) / 100; changeSelectedAttribute('opacity', newOpac, [_elem5]); } if (gattrs.filter) { var cblur = this.getBlur(_elem5); var origCblur = cblur; if (!gblur) { gblur = this.getBlur(g); } if (cblur) { // Is this formula correct? cblur = Number(gblur) + Number(cblur); } else if (cblur === 0) { cblur = gblur; } // If child has no current filter, get group's filter or clone it. if (!origCblur) { // Set group's filter to use first child's ID if (!gfilter) { gfilter = getRefElem(gattrs.filter); } else { // Clone the group's filter gfilter = drawing.copyElem(gfilter); findDefs().append(gfilter); } } else { gfilter = getRefElem(_elem5.getAttribute('filter')); } // Change this in future for different filters var suffix = gfilter.firstChild.tagName === 'feGaussianBlur' ? 'blur' : 'filter'; gfilter.id = _elem5.id + '_' + suffix; changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [_elem5]); // Update blur value if (cblur) { changeSelectedAttribute('stdDeviation', cblur, [gfilter.firstChild]); canvas.setBlurOffsets(gfilter, cblur); } } var chtlist = getTransformList(_elem5); // Don't process gradient transforms if (_elem5.tagName.includes('Gradient')) { chtlist = null; } // Hopefully not a problem to add this. Necessary for elements like if (!chtlist) { continue; } // Apparently can get get a transformlist, but we don't want it to have one! if (_elem5.tagName === 'defs') { continue; } if (glist.numberOfItems) { // TODO: if the group's transform is just a rotate, we can always transfer the // rotate() down to the children (collapsing consecutive rotates and factoring // out any translates) if (gangle && glist.numberOfItems === 1) { // [Rg] [Rc] [Mc] // we want [Tr] [Rc2] [Mc] where: // - [Rc2] is at the child's current center but has the // sum of the group and child's rotation angles // - [Tr] is the equivalent translation that this child // undergoes if the group wasn't there // [Tr] = [Rg] [Rc] [Rc2_inv] // get group's rotation matrix (Rg) var rgm = glist.getItem(0).matrix; // get child's rotation matrix (Rc) var rcm = svgroot.createSVGMatrix(); var cangle = getRotationAngle(_elem5); if (cangle) { rcm = chtlist.getItem(0).matrix; } // get child's old center of rotation var cbox = getBBox(_elem5); var ceqm = transformListToTransform(chtlist).matrix; var coldc = transformPoint(cbox.x + cbox.width / 2, cbox.y + cbox.height / 2, ceqm); // sum group and child's angles var sangle = gangle + cangle; // get child's rotation at the old center (Rc2_inv) var r2 = svgroot.createSVGTransform(); r2.setRotate(sangle, coldc.x, coldc.y); // calculate equivalent translate var trm = matrixMultiply(rgm, rcm, r2.matrix.inverse()); // set up tlist if (cangle) { chtlist.removeItem(0); } if (sangle) { if (chtlist.numberOfItems) { chtlist.insertItemBefore(r2, 0); } else { chtlist.appendItem(r2); } } if (trm.e || trm.f) { var tr = svgroot.createSVGTransform(); tr.setTranslate(trm.e, trm.f); if (chtlist.numberOfItems) { chtlist.insertItemBefore(tr, 0); } else { chtlist.appendItem(tr); } } } else { // more complicated than just a rotate // transfer the group's transform down to each child and then // call recalculateDimensions() var oldxform = _elem5.getAttribute('transform'); changes = {}; changes.transform = oldxform || ''; var newxform = svgroot.createSVGTransform(); // [ gm ] [ chm ] = [ chm ] [ gm' ] // [ gm' ] = [ chmInv ] [ gm ] [ chm ] var chm = transformListToTransform(chtlist).matrix, chmInv = chm.inverse(); var gm = matrixMultiply(chmInv, m, chm); newxform.setMatrix(gm); chtlist.appendItem(newxform); } var cmd = recalculateDimensions(_elem5); if (cmd) { batchCmd.addSubCommand(cmd); } } } // remove transform and make it undo-able if (xform) { changes = {}; changes.transform = xform; g.setAttribute('transform', ''); g.removeAttribute('transform'); batchCmd.addSubCommand(new ChangeElementCommand$1(g, changes)); } if (undoable && !batchCmd.isEmpty()) { return batchCmd; } }; /** * Unwraps all the elements in a selected group (g) element. This requires * significant recalculations to apply group's transforms, etc to its children */ this.ungroupSelectedElement = function () { var g = selectedElements[0]; if (!g) { return; } if ($$9(g).data('gsvg') || $$9(g).data('symbol')) { // Is svg, so actually convert to group convertToGroup(g); return; } if (g.tagName === 'use') { // Somehow doesn't have data set, so retrieve var symbol = getElem(getHref(g).substr(1)); $$9(g).data('symbol', symbol).data('ref', symbol); convertToGroup(g); return; } var parentsA = $$9(g).parents('a'); if (parentsA.length) { g = parentsA[0]; } // Look for parent "a" if (g.tagName === 'g' || g.tagName === 'a') { var batchCmd = new BatchCommand$1('Ungroup Elements'); var cmd = pushGroupProperties(g, true); if (cmd) { batchCmd.addSubCommand(cmd); } var parent = g.parentNode; var anchor = g.nextSibling; var children = new Array(g.childNodes.length); var i = 0; while (g.firstChild) { var _elem6 = g.firstChild; var oldNextSibling = _elem6.nextSibling; var oldParent = _elem6.parentNode; // Remove child title elements if (_elem6.tagName === 'title') { var _elem7 = _elem6, nextSibling = _elem7.nextSibling; batchCmd.addSubCommand(new RemoveElementCommand$1(_elem6, nextSibling, oldParent)); _elem6.remove(); continue; } children[i++] = _elem6 = parent.insertBefore(_elem6, anchor); batchCmd.addSubCommand(new MoveElementCommand$1(_elem6, oldNextSibling, oldParent)); } // remove the group from the selection clearSelection(); // delete the group element (but make undo-able) var gNextSibling = g.nextSibling; g = parent.removeChild(g); batchCmd.addSubCommand(new RemoveElementCommand$1(g, gNextSibling, parent)); if (!batchCmd.isEmpty()) { addCommandToHistory(batchCmd); } // update selection addToSelection(children); } }; /** * Repositions the selected element to the bottom in the DOM to appear on top of * other elements */ this.moveToTopSelectedElement = function () { var selected = selectedElements[0]; if (selected != null) { var t = selected; var oldParent = t.parentNode; var oldNextSibling = t.nextSibling; t = t.parentNode.appendChild(t); // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { addCommandToHistory(new MoveElementCommand$1(t, oldNextSibling, oldParent, 'top')); call('changed', [t]); } } }; /** * Repositions the selected element to the top in the DOM to appear under * other elements */ this.moveToBottomSelectedElement = function () { var selected = selectedElements[0]; if (selected != null) { var t = selected; var oldParent = t.parentNode; var oldNextSibling = t.nextSibling; var firstChild = t.parentNode.firstChild; if (firstChild.tagName === 'title') { firstChild = firstChild.nextSibling; } // This can probably be removed, as the defs should not ever apppear // inside a layer group if (firstChild.tagName === 'defs') { firstChild = firstChild.nextSibling; } t = t.parentNode.insertBefore(t, firstChild); // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { addCommandToHistory(new MoveElementCommand$1(t, oldNextSibling, oldParent, 'bottom')); call('changed', [t]); } } }; /** * Moves the select element up or down the stack, based on the visibly * intersecting elements * @param {"Up"|"Down"} dir - String that's either 'Up' or 'Down' */ this.moveUpDownSelected = function (dir) { var selected = selectedElements[0]; if (!selected) { return; } curBBoxes = []; var closest = void 0, foundCur = void 0; // jQuery sorts this list var list = $$9(getIntersectionList(getStrokedBBoxDefaultVisible([selected]))).toArray(); if (dir === 'Down') { list.reverse(); } $$9.each(list, function () { if (!foundCur) { if (this === selected) { foundCur = true; } return; } closest = this; return false; }); if (!closest) { return; } var t = selected; var oldParent = t.parentNode; var oldNextSibling = t.nextSibling; $$9(closest)[dir === 'Down' ? 'before' : 'after'](t); // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { addCommandToHistory(new MoveElementCommand$1(t, oldNextSibling, oldParent, 'Move ' + dir)); call('changed', [t]); } }; /** * Moves selected elements on the X/Y axis * @param {Number} dx - Float with the distance to move on the x-axis * @param {Number} dy - Float with the distance to move on the y-axis * @param {Boolean} undoable - Boolean indicating whether or not the action should be undoable * @returns Batch command for the move */ this.moveSelectedElements = function (dx, dy, undoable) { // if undoable is not sent, default to true // if single values, scale them to the zoom if (dx.constructor !== Array) { dx /= currentZoom; dy /= currentZoom; } undoable = undoable || true; var batchCmd = new BatchCommand$1('position'); var i = selectedElements.length; while (i--) { var selected = selectedElements[i]; if (selected != null) { // if (i === 0) { // selectedBBoxes[0] = utilsGetBBox(selected); // } // const b = {}; // for (const j in selectedBBoxes[i]) b[j] = selectedBBoxes[i][j]; // selectedBBoxes[i] = b; var xform = svgroot.createSVGTransform(); var tlist = getTransformList(selected); // dx and dy could be arrays if (dx.constructor === Array) { // if (i === 0) { // selectedBBoxes[0].x += dx[0]; // selectedBBoxes[0].y += dy[0]; // } xform.setTranslate(dx[i], dy[i]); } else { // if (i === 0) { // selectedBBoxes[0].x += dx; // selectedBBoxes[0].y += dy; // } xform.setTranslate(dx, dy); } if (tlist.numberOfItems) { tlist.insertItemBefore(xform, 0); } else { tlist.appendItem(xform); } var cmd = recalculateDimensions(selected); if (cmd) { batchCmd.addSubCommand(cmd); } selectorManager.requestSelector(selected).resize(); } } if (!batchCmd.isEmpty()) { if (undoable) { addCommandToHistory(batchCmd); } call('changed', selectedElements); return batchCmd; } }; /** * Create deep DOM copies (clones) of all selected elements and move them slightly * from their originals */ this.cloneSelectedElements = function (x, y) { var i = void 0, elem = void 0; var batchCmd = new BatchCommand$1('Clone Elements'); // find all the elements selected (stop at first null) var len = selectedElements.length; function sortfunction(a, b) { return $$9(b).index() - $$9(a).index(); // causes an array to be sorted numerically and ascending } selectedElements.sort(sortfunction); for (i = 0; i < len; ++i) { elem = selectedElements[i]; if (elem == null) { break; } } // use slice to quickly get the subset of elements we need var copiedElements = selectedElements.slice(0, i); this.clearSelection(true); // note that we loop in the reverse way because of the way elements are added // to the selectedElements array (top-first) var drawing = getCurrentDrawing(); i = copiedElements.length; while (i--) { // clone each element and replace it within copiedElements elem = copiedElements[i] = drawing.copyElem(copiedElements[i]); (currentGroup || drawing.getCurrentLayer()).append(elem); batchCmd.addSubCommand(new InsertElementCommand$1(elem)); } if (!batchCmd.isEmpty()) { addToSelection(copiedElements.reverse()); // Need to reverse for correct selection-adding this.moveSelectedElements(x, y, false); addCommandToHistory(batchCmd); } }; /** * Aligns selected elements * @param {String} type - String with single character indicating the alignment type * @param {"selected"|"largest"|"smallest"|"page"} relativeTo */ this.alignSelectedElements = function (type, relativeTo) { var bboxes = []; // angles = []; var len = selectedElements.length; if (!len) { return; } var minx = Number.MAX_VALUE, maxx = Number.MIN_VALUE, miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE; var curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE; for (var i = 0; i < len; ++i) { if (selectedElements[i] == null) { break; } var _elem8 = selectedElements[i]; bboxes[i] = getStrokedBBoxDefaultVisible([_elem8]); // now bbox is axis-aligned and handles rotation switch (relativeTo) { case 'smallest': if ((type === 'l' || type === 'c' || type === 'r') && (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width) || (type === 't' || type === 'm' || type === 'b') && (curheight === Number.MIN_VALUE || curheight > bboxes[i].height)) { minx = bboxes[i].x; miny = bboxes[i].y; maxx = bboxes[i].x + bboxes[i].width; maxy = bboxes[i].y + bboxes[i].height; curwidth = bboxes[i].width; curheight = bboxes[i].height; } break; case 'largest': if ((type === 'l' || type === 'c' || type === 'r') && (curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width) || (type === 't' || type === 'm' || type === 'b') && (curheight === Number.MIN_VALUE || curheight < bboxes[i].height)) { minx = bboxes[i].x; miny = bboxes[i].y; maxx = bboxes[i].x + bboxes[i].width; maxy = bboxes[i].y + bboxes[i].height; curwidth = bboxes[i].width; curheight = bboxes[i].height; } break; default: // 'selected' if (bboxes[i].x < minx) { minx = bboxes[i].x; } if (bboxes[i].y < miny) { miny = bboxes[i].y; } if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width; } if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height; } break; } } // loop for each element to find the bbox and adjust min/max if (relativeTo === 'page') { minx = 0; miny = 0; maxx = canvas.contentW; maxy = canvas.contentH; } var dx = new Array(len); var dy = new Array(len); for (var _i2 = 0; _i2 < len; ++_i2) { if (selectedElements[_i2] == null) { break; } // const elem = selectedElements[i]; var bbox = bboxes[_i2]; dx[_i2] = 0; dy[_i2] = 0; switch (type) { case 'l': // left (horizontal) dx[_i2] = minx - bbox.x; break; case 'c': // center (horizontal) dx[_i2] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2); break; case 'r': // right (horizontal) dx[_i2] = maxx - (bbox.x + bbox.width); break; case 't': // top (vertical) dy[_i2] = miny - bbox.y; break; case 'm': // middle (vertical) dy[_i2] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2); break; case 'b': // bottom (vertical) dy[_i2] = maxy - (bbox.y + bbox.height); break; } } this.moveSelectedElements(dx, dy); }; /** * Group: Additional editor tools */ this.contentW = getResolution().w; this.contentH = getResolution().h; /** * Updates the editor canvas width/height/position after a zoom has occurred * @param {Number} w - Float with the new width * @param {Number} h - Float with the new height * @returns Object with the following values: * - x - The canvas' new x coordinate * - y - The canvas' new y coordinate * - oldX - The canvas' old x coordinate * - oldY - The canvas' old y coordinate * - d_x - The x position difference * - d_y - The y position difference */ this.updateCanvas = function (w, h) { svgroot.setAttribute('width', w); svgroot.setAttribute('height', h); var bg = $$9('#canvasBackground')[0]; var oldX = svgcontent.getAttribute('x'); var oldY = svgcontent.getAttribute('y'); var x = w / 2 - this.contentW * currentZoom / 2; var y = h / 2 - this.contentH * currentZoom / 2; assignAttributes(svgcontent, { width: this.contentW * currentZoom, height: this.contentH * currentZoom, x: x, y: y, viewBox: '0 0 ' + this.contentW + ' ' + this.contentH }); assignAttributes(bg, { width: svgcontent.getAttribute('width'), height: svgcontent.getAttribute('height'), x: x, y: y }); var bgImg = getElem('background_image'); if (bgImg) { assignAttributes(bgImg, { width: '100%', height: '100%' }); } selectorManager.selectorParentGroup.setAttribute('transform', 'translate(' + x + ',' + y + ')'); runExtensions('canvasUpdated', { new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY }); return { x: x, y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY }; }; /** * Set the background of the editor (NOT the actual document) * @param {String} color - String with fill color to apply * @param url - URL or path to image to use */ this.setBackground = function (color, url) { var bg = getElem('canvasBackground'); var border = $$9(bg).find('rect')[0]; var bgImg = getElem('background_image'); border.setAttribute('fill', color); if (url) { if (!bgImg) { bgImg = svgdoc.createElementNS(NS.SVG, 'image'); assignAttributes(bgImg, { id: 'background_image', width: '100%', height: '100%', preserveAspectRatio: 'xMinYMin', style: 'pointer-events:none' }); } setHref(bgImg, url); bg.append(bgImg); } else if (bgImg) { bgImg.remove(); } }; /** * Select the next/previous element within the current layer * @param {Boolean} next - true = next and false = previous element */ this.cycleElement = function (next) { var num = void 0; var curElem = selectedElements[0]; var elem = false; var allElems = getVisibleElements(currentGroup || getCurrentDrawing().getCurrentLayer()); if (!allElems.length) { return; } if (curElem == null) { num = next ? allElems.length - 1 : 0; elem = allElems[num]; } else { var i = allElems.length; while (i--) { if (allElems[i] === curElem) { num = next ? i - 1 : i + 1; if (num >= allElems.length) { num = 0; } else if (num < 0) { num = allElems.length - 1; } elem = allElems[num]; break; } } } selectOnly([elem], true); call('selected', selectedElements); }; this.clear(); /** * @deprecated getPrivateMethods * Since all methods are/should be public somehow, this function should be removed; * we might require `import` in place of this in the future once ES6 Modules * widespread * Being able to access private methods publicly seems wrong somehow, * but currently appears to be the best way to allow testing and provide * access to them to plugins. */ this.getPrivateMethods = function () { var obj = { addCommandToHistory: addCommandToHistory, setGradient: setGradient, addSvgElementFromJson: addSvgElementFromJson, assignAttributes: assignAttributes, BatchCommand: BatchCommand$1, call: call, ChangeElementCommand: ChangeElementCommand$1, copyElem: function copyElem$$1(elem) { return getCurrentDrawing().copyElem(elem); }, decode64: decode64, encode64: encode64, ffClone: ffClone, findDefs: findDefs, findDuplicateGradient: findDuplicateGradient, getElem: getElem, getId: getId, getIntersectionList: getIntersectionList, getMouseTarget: getMouseTarget, getNextId: getNextId, getPathBBox: getPathBBox, getTypeMap: getTypeMap, getUrlFromAttr: getUrlFromAttr, hasMatrixTransform: hasMatrixTransform, identifyLayers: identifyLayers, InsertElementCommand: InsertElementCommand$1, isChrome: isChrome, isIdentity: isIdentity, isIE: isIE, logMatrix: logMatrix, matrixMultiply: matrixMultiply, MoveElementCommand: MoveElementCommand$1, NS: NS, preventClickDefault: preventClickDefault, recalculateAllSelectedDimensions: recalculateAllSelectedDimensions, recalculateDimensions: recalculateDimensions, remapElement: remapElement, RemoveElementCommand: RemoveElementCommand$1, removeUnusedDefElems: removeUnusedDefElems, round: round, runExtensions: runExtensions, sanitizeSvg: sanitizeSvg, SVGEditTransformList: SVGTransformList, text2xml: text2xml, toString: toString, transformBox: transformBox, transformListToTransform: transformListToTransform, transformPoint: transformPoint, walkTree: walkTree }; return obj; }; } // End constructor ; // End class // Todo: Update: https://github.com/jeresig/jquery.hotkeys /* * jQuery Hotkeys Plugin * Copyright 2010, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * * http://github.com/jeresig/jquery.hotkeys * * Based upon the plugin by Tzury Bar Yochay: * http://github.com/tzuryby/hotkeys * * Original idea by: * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ */ function jqPluginJSHotkeys (b) { b.hotkeys = { version: "0.8", specialKeys: { 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta", 219: "[", 221: "]" }, shiftNums: { "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": '"', ",": "<", ".": ">", "/": "?", "\\": "|" } };function a(d) { if (typeof d.data !== "string") { return; }var c = d.handler, e = d.data.toLowerCase().split(" ");d.handler = function (n) { if (this !== n.target && (/textarea|select/i.test(n.target.nodeName) || n.target.type === "text")) { return; }var h = n.type !== "keypress" && b.hotkeys.specialKeys[n.which], o = String.fromCharCode(n.which).toLowerCase(), m = "", g = {};if (n.altKey && h !== "alt") { m += "alt+"; }if (n.ctrlKey && h !== "ctrl") { m += "ctrl+"; }if (n.metaKey && !n.ctrlKey && h !== "meta") { m += "meta+"; }if (n.shiftKey && h !== "shift") { m += "shift+"; }if (h) { g[m + h] = true; } else { g[m + o] = true;g[m + b.hotkeys.shiftNums[o]] = true;if (m === "shift+") { g[b.hotkeys.shiftNums[o]] = true; } }for (var j = 0, f = e.length; j < f; j++) { if (g[e[j]]) { return c.apply(this, arguments); } } }; }b.each(["keydown", "keyup", "keypress"], function () { b.event.special[this] = { add: a }; }); return b; } /* * Todo: Update to latest at https://github.com/cowboy/jquery-bbq ? * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 * http://benalman.com/projects/jquery-bbq-plugin/ * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ */ // For sake of modules, added this wrapping export and changed `this` to `window` function jqPluginBBQ (jQuery) { (function ($, p) { var i, m = Array.prototype.slice, r = decodeURIComponent, a = $.param, c, l, v, b = $.bbq = $.bbq || {}, q, u, j, e = $.event.special, d = "hashchange", A = "querystring", D = "fragment", y = "elemUrlAttr", g = "location", k = "href", t = "src", x = /^.*\?|#.*$/g, w = /^.*\#/, h, C = {};function E(F) { return typeof F === "string"; }function B(G) { var F = m.call(arguments, 1);return function () { return G.apply(this, F.concat(m.call(arguments))); }; }function n(F) { return F.replace(/^[^#]*#?(.*)$/, "$1"); }function o(F) { return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/, "$1"); }function f(H, M, F, I, G) { var O, L, K, N, J;if (I !== i) { K = F.match(H ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/);J = K[3] || "";if (G === 2 && E(I)) { L = I.replace(H ? w : x, ""); } else { N = l(K[2]);I = E(I) ? l[H ? D : A](I) : I;L = G === 2 ? I : G === 1 ? $.extend({}, I, N) : $.extend({}, N, I);L = a(L);if (H) { L = L.replace(h, r); } }O = K[1] + (H ? "#" : L || !K[1] ? "?" : "") + L + J; } else { O = M(F !== i ? F : p[g][k]); }return O; }a[A] = B(f, 0, o);a[D] = c = B(f, 1, n);c.noEscape = function (G) { G = G || "";var F = $.map(G.split(""), encodeURIComponent);h = new RegExp(F.join("|"), "g"); };c.noEscape(",/");$.deparam = l = function l(I, F) { var H = {}, G = { "true": !0, "false": !1, "null": null };$.each(I.replace(/\+/g, " ").split("&"), function (L, Q) { var K = Q.split("="), P = r(K[0]), J, O = H, M = 0, R = P.split("]["), N = R.length - 1;if (/\[/.test(R[0]) && /\]$/.test(R[N])) { R[N] = R[N].replace(/\]$/, "");R = R.shift().split("[").concat(R);N = R.length - 1; } else { N = 0; }if (K.length === 2) { J = r(K[1]);if (F) { J = J && !isNaN(J) ? +J : J === "undefined" ? i : G[J] !== i ? G[J] : J; }if (N) { for (; M <= N; M++) { P = R[M] === "" ? O.length : R[M];O = O[P] = M < N ? O[P] || (R[M + 1] && isNaN(R[M + 1]) ? {} : []) : J; } } else { if ($.isArray(H[P])) { H[P].push(J); } else { if (H[P] !== i) { H[P] = [H[P], J]; } else { H[P] = J; } } } } else { if (P) { H[P] = F ? i : ""; } } });return H; };function z(H, F, G) { if (F === i || typeof F === "boolean") { G = F;F = a[H ? D : A](); } else { F = E(F) ? F.replace(H ? w : x, "") : F; }return l(F, G); }l[A] = B(z, 0);l[D] = v = B(z, 1);$[y] || ($[y] = function (F) { return $.extend(C, F); })({ a: k, base: k, iframe: t, img: t, input: t, form: "action", link: k, script: t });j = $[y];function s(I, G, H, F) { if (!E(H) && (typeof H === "undefined" ? "undefined" : _typeof(H)) !== "object") { F = H;H = G;G = i; }return this.each(function () { var L = $(this), J = G || j()[(this.nodeName || "").toLowerCase()] || "", K = J && L.attr(J) || "";L.attr(J, a[I](K, H, F)); }); }$.fn[A] = B(s, A);$.fn[D] = B(s, D);b.pushState = q = function q(I, F) { if (E(I) && /^#/.test(I) && F === i) { F = 2; }var H = I !== i, G = c(p[g][k], H ? I : {}, H ? F : 2);p[g][k] = G + (/#/.test(G) ? "" : "#"); };b.getState = u = function u(F, G) { return F === i || typeof F === "boolean" ? v(F) : v(G)[F]; };b.removeState = function (F) { var G = {};if (F !== i) { G = u();$.each($.isArray(F) ? F : arguments, function (I, H) { delete G[H]; }); }q(G, 2); };e[d] = $.extend(e[d], { add: function add(F) { var H;function G(J) { var I = J[D] = c();J.getState = function (K, L) { return K === i || typeof K === "boolean" ? l(I, K) : l(I, L)[K]; };H.apply(this, arguments); }if ($.isFunction(F)) { H = F;return G; } else { H = F.handler;F.handler = G; } } }); })(jQuery, window); /* * jQuery hashchange event - v1.2 - 2/11/2010 * http://benalman.com/projects/jquery-hashchange-plugin/ * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ */ (function ($, i, b) { var j, k = $.event.special, c = "location", d = "hashchange", l = "href", f = $.browser, g = document.documentMode, h = f.msie && (g === b || g < 8), e = "on" + d in i && !h;function a(m) { m = m || i[c][l];return m.replace(/^[^#]*#?(.*)$/, "$1"); }$[d + "Delay"] = 100;k[d] = $.extend(k[d], { setup: function setup() { if (e) { return false; }$(j.start); }, teardown: function teardown() { if (e) { return false; }$(j.stop); } });j = function () { var m = {}, r, n, o, q;function p() { o = q = function q(s) { return s; };if (h) { n = $('').hide().insertAfter("body")[0].contentWindow;q = function q() { return a(n.document[c][l]); };o = function o(u, s) { if (u !== s) { var t = n.document;t.open().close();t[c].hash = "#" + u; } };o(a()); } }m.start = function () { if (r) { return; }var t = a();o || p();(function s() { var v = a(), u = q(t);if (v !== t) { o(t = v, u);$(i).trigger(d); } else { if (u !== t) { i[c][l] = i[c][l].replace(/#.*/, "") + "#" + u; } }r = setTimeout(s, $[d + "Delay"]); })(); };m.stop = function () { if (!n) { r && clearTimeout(r);r = 0; } };return m; }(); })(jQuery, window); return jQuery; } /* * SVG Icon Loader 2.0 * * jQuery Plugin for loading SVG icons from a single file * * Copyright (c) 2009 Alexis Deveria * http://a.deveria.com * * MIT License How to use: 1. Create the SVG master file that includes all icons: The master SVG icon-containing file is an SVG file that contains elements. Each element should contain the markup of an SVG icon. The element has an ID that should correspond with the ID of the HTML element used on the page that should contain or optionally be replaced by the icon. Additionally, one empty element should be added at the end with id "svg_eof". 2. Optionally create fallback raster images for each SVG icon. 3. Include the jQuery and the SVG Icon Loader scripts on your page. 4. Run $.svgIcons() when the document is ready: $.svgIcons( file [string], options [object literal]); File is the location of a local SVG or SVGz file. All options are optional and can include: - 'w (number)': The icon widths - 'h (number)': The icon heights - 'fallback (object literal)': List of raster images with each key being the SVG icon ID to replace, and the value the image file name. - 'fallback_path (string)': The path to use for all images listed under "fallback" - 'replace (boolean)': If set to true, HTML elements will be replaced by, rather than include the SVG icon. - 'placement (object literal)': List with selectors for keys and SVG icon ids as values. This provides a custom method of adding icons. - 'resize (object literal)': List with selectors for keys and numbers as values. This allows an easy way to resize specific icons. - 'callback (function)': A function to call when all icons have been loaded. Includes an object literal as its argument with as keys all icon IDs and the icon as a jQuery object as its value. - 'id_match (boolean)': Automatically attempt to match SVG icon ids with corresponding HTML id (default: true) - 'no_img (boolean)': Prevent attempting to convert the icon into an element (may be faster, help for browser consistency) - 'svgz (boolean)': Indicate that the file is an SVGZ file, and thus not to parse as XML. SVGZ files add compression benefits, but getting data from them fails in Firefox 2 and older. 5. To access an icon at a later point without using the callback, use this: $.getSvgIcon(id (string)); This will return the icon (as jQuery object) with a given ID. 6. To resize icons at a later point without using the callback, use this: $.resizeSvgIcons(resizeOptions) (use the same way as the "resize" parameter) Example usage #1: $(function() { $.svgIcons('my_icon_set.svg'); // The SVG file that contains all icons // No options have been set, so all icons will automatically be inserted // into HTML elements that match the same IDs. }); Example usage #2: $(function() { $.svgIcons('my_icon_set.svg', { // The SVG file that contains all icons callback (icons) { // Custom callback function that sets click // events for each icon $.each(icons, function(id, icon) { icon.click(function() { alert('You clicked on the icon with id ' + id); }); }); } }); //The SVG file that contains all icons }); Example usage #3: $(function() { $.svgIcons('my_icon_set.svgz', { // The SVGZ file that contains all icons w: 32, // All icons will be 32px wide h: 32, // All icons will be 32px high fallback_path: 'icons/', // All fallback files can be found here fallback: { '#open_icon': 'open.png', // The "open.png" will be appended to the // HTML element with ID "open_icon" '#close_icon': 'close.png', '#save_icon': 'save.png' }, placement: {'.open_icon','open'}, // The "open" icon will be added // to all elements with class "open_icon" resize () { '#save_icon .svg_icon': 64 // The "save" icon will be resized to 64 x 64px }, callback (icons) { // Sets background color for "close" icon icons['close'].css('background','red'); }, svgz: true // Indicates that an SVGZ file is being used }) }); */ function jqPluginSVGIcons ($) { var svgIcons = {}; var fixIDs = void 0; $.svgIcons = function (file, opts) { var svgns = 'http://www.w3.org/2000/svg', xlinkns = 'http://www.w3.org/1999/xlink', iconW = opts.w || 24, iconH = opts.h || 24; var elems = void 0, svgdoc = void 0, testImg = void 0, iconsMade = false, dataLoaded = false, loadAttempts = 0; var isOpera = !!window.opera, // ua = navigator.userAgent, // isSafari = (ua.includes('Safari/') && !ua.includes('Chrome/')), dataPre = 'data:image/svg+xml;charset=utf-8;base64,'; var dataEl = void 0; if (opts.svgz) { dataEl = $('').appendTo('body').hide(); try { svgdoc = dataEl[0].contentDocument; dataEl.load(getIcons); getIcons(0, true); // Opera will not run "load" event if file is already cached } catch (err1) { useFallback(); } } else { var parser = new DOMParser(); $.ajax({ url: file, dataType: 'string', success: function success(data) { if (!data) { $(useFallback); return; } svgdoc = parser.parseFromString(data, 'text/xml'); $(function () { getIcons('ajax'); }); }, error: function error(err) { // TODO: Fix Opera widget icon bug if (window.opera) { $(function () { useFallback(); }); } else { if (err.responseText) { svgdoc = parser.parseFromString(err.responseText, 'text/xml'); if (!svgdoc.childNodes.length) { $(useFallback); } $(function () { getIcons('ajax'); }); } else { $(useFallback); } } } }); } function getIcons(evt, noWait) { if (evt !== 'ajax') { if (dataLoaded) return; // Webkit sometimes says svgdoc is undefined, other times // it fails to load all nodes. Thus we must make sure the "eof" // element is loaded. svgdoc = dataEl[0].contentDocument; // Needed again for Webkit var isReady = svgdoc && svgdoc.getElementById('svg_eof'); if (!isReady && !(noWait && isReady)) { loadAttempts++; if (loadAttempts < 50) { setTimeout(getIcons, 20); } else { useFallback(); dataLoaded = true; } return; } dataLoaded = true; } elems = $(svgdoc.firstChild).children(); // .getElementsByTagName('foreignContent'); if (!opts.no_img) { var testSrc = dataPre + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzUiIGhlaWdodD0iMjc1Ij48L3N2Zz4%3D'; testImg = $(new Image()).attr({ src: testSrc, width: 0, height: 0 }).appendTo('body').load(function () { // Safari 4 crashes, Opera and Chrome don't makeIcons(true); }).error(function () { makeIcons(); }); } else { setTimeout(function () { if (!iconsMade) makeIcons(); }, 500); } } var setIcon = function setIcon(target, icon, id, setID) { if (isOpera) icon.css('visibility', 'hidden'); if (opts.replace) { if (setID) icon.attr('id', id); var cl = target.attr('class'); if (cl) icon.attr('class', 'svg_icon ' + cl); target.replaceWith(icon); } else { target.append(icon); } if (isOpera) { setTimeout(function () { icon.removeAttr('style'); }, 1); } }; var holder = void 0; var addIcon = function addIcon(icon, id) { if (opts.id_match === undefined || opts.id_match !== false) { setIcon(holder, icon, id, true); } svgIcons[id] = icon; }; function makeIcons(toImage, fallback) { if (iconsMade) return; if (opts.no_img) toImage = false; var tempHolder = void 0; if (toImage) { tempHolder = $(document.createElement('div')); tempHolder.hide().appendTo('body'); } if (fallback) { var path = opts.fallback_path || ''; $.each(fallback, function (id, imgsrc) { holder = $('#' + id); var icon = $(new Image()).attr({ class: 'svg_icon', src: path + imgsrc, width: iconW, height: iconH, alt: 'icon' }); addIcon(icon, id); }); } else { var len = elems.length; for (var i = 0; i < len; i++) { var elem = elems[i]; var id = elem.id; if (id === 'svg_eof') break; holder = $('#' + id); var svgroot = document.createElementNS(svgns, 'svg'); // Per https://www.w3.org/TR/xml-names11/#defaulting, the namespace for // attributes should have no value. svgroot.setAttributeNS(null, 'viewBox', [0, 0, iconW, iconH].join(' ')); var svg = elem.getElementsByTagNameNS(svgns, 'svg')[0]; // Make flexible by converting width/height to viewBox var w = svg.getAttribute('width'); var h = svg.getAttribute('height'); svg.removeAttribute('width'); svg.removeAttribute('height'); var vb = svg.getAttribute('viewBox'); if (!vb) { svg.setAttribute('viewBox', [0, 0, w, h].join(' ')); } // Not using jQuery to be a bit faster svgroot.setAttribute('xmlns', svgns); svgroot.setAttribute('width', iconW); svgroot.setAttribute('height', iconH); svgroot.setAttribute('xmlns:xlink', xlinkns); svgroot.setAttribute('class', 'svg_icon'); // Without cloning, Firefox will make another GET request. // With cloning, causes issue in Opera/Win/Non-EN if (!isOpera) svg = svg.cloneNode(true); svgroot.append(svg); var icon = void 0; if (toImage) { tempHolder.empty().append(svgroot); var str = dataPre + encode64(unescape(encodeURIComponent(new XMLSerializer().serializeToString(svgroot)))); icon = $(new Image()).attr({ class: 'svg_icon', src: str }); } else { icon = fixIDs($(svgroot), i); } addIcon(icon, id); } } if (opts.placement) { $.each(opts.placement, function (sel, id) { if (!svgIcons[id]) return; $(sel).each(function (i) { var copy = svgIcons[id].clone(); if (i > 0 && !toImage) copy = fixIDs(copy, i, true); setIcon($(this), copy, id); }); }); } if (!fallback) { if (toImage) tempHolder.remove(); if (dataEl) dataEl.remove(); if (testImg) testImg.remove(); } if (opts.resize) $.resizeSvgIcons(opts.resize); iconsMade = true; if (opts.callback) opts.callback(svgIcons); } fixIDs = function fixIDs(svgEl, svgNum, force) { var defs = svgEl.find('defs'); if (!defs.length) return svgEl; var idElems = void 0; if (isOpera) { idElems = defs.find('*').filter(function () { return !!this.id; }); } else { idElems = defs.find('[id]'); } var allElems = svgEl[0].getElementsByTagName('*'), len = allElems.length; idElems.each(function (i) { var id = this.id; /* const noDupes = ($(svgdoc).find('#' + id).length <= 1); if (isOpera) noDupes = false; // Opera didn't clone svgEl, so not reliable if(!force && noDupes) return; */ var newId = 'x' + id + svgNum + i; this.id = newId; var oldVal = 'url(#' + id + ')'; var newVal = 'url(#' + newId + ')'; // Selector method, possibly faster but fails in Opera / jQuery 1.4.3 // svgEl.find('[fill="url(#' + id + ')"]').each(function() { // this.setAttribute('fill', 'url(#' + newId + ')'); // }).end().find('[stroke="url(#' + id + ')"]').each(function() { // this.setAttribute('stroke', 'url(#' + newId + ')'); // }).end().find('use').each(function() { // if(this.getAttribute('xlink:href') == '#' + id) { // this.setAttributeNS(xlinkns,'href','#' + newId); // } // }).end().find('[filter="url(#' + id + ')"]').each(function() { // this.setAttribute('filter', 'url(#' + newId + ')'); // }); for (i = 0; i < len; i++) { var elem = allElems[i]; if (elem.getAttribute('fill') === oldVal) { elem.setAttribute('fill', newVal); } if (elem.getAttribute('stroke') === oldVal) { elem.setAttribute('stroke', newVal); } if (elem.getAttribute('filter') === oldVal) { elem.setAttribute('filter', newVal); } } }); return svgEl; }; function useFallback() { if (file.includes('.svgz')) { var regFile = file.replace('.svgz', '.svg'); if (window.console) { console.log('.svgz failed, trying with .svg'); } $.svgIcons(regFile, opts); } else if (opts.fallback) { makeIcons(false, opts.fallback); } } function encode64(input) { // base64 strings are 4/3 larger than the original string if (window.btoa) return window.btoa(input); var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var output = new Array(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(''); } }; $.getSvgIcon = function (id, uniqueClone) { var icon = svgIcons[id]; if (uniqueClone && icon) { icon = fixIDs(icon, 0, true).clone(true); } return icon; }; $.resizeSvgIcons = function (obj) { // FF2 and older don't detect .svg_icon, so we change it detect svg elems instead var changeSel = !$('.svg_icon:first').length; $.each(obj, function (sel, size) { var arr = Array.isArray(size); var w = arr ? size[0] : size, h = arr ? size[1] : size; if (changeSel) { sel = sel.replace(/\.svg_icon/g, 'svg'); } $(sel).each(function () { this.setAttribute('width', w); this.setAttribute('height', h); if (window.opera && window.widget) { this.parentNode.style.width = w + 'px'; this.parentNode.style.height = h + 'px'; } }); }); }; return $; } /** * jGraduate 0.4 * * jQuery Plugin for a gradient picker * * Copyright (c) 2010 Jeff Schiller * http://blog.codedread.com/ * Copyright (c) 2010 Alexis Deveria * http://a.deveria.com/ * * Apache 2 License jGraduate(options, okCallback, cancelCallback) where options is an object literal: { window: { title: 'Pick the start color and opacity for the gradient' }, images: { clientPath: 'images/' }, paint: a Paint object, newstop: String of value "same", "inverse", "black" or "white" OR object with one or both values {color: #Hex color, opac: number 0-1} } - the Paint object is: Paint { type: String, // one of "none", "solidColor", "linearGradient", "radialGradient" alpha: Number representing opacity (0-100), solidColor: String representing #RRGGBB hex of color, linearGradient: object of interface SVGLinearGradientElement, radialGradient: object of interface SVGRadialGradientElement, } $.jGraduate.Paint() -> constructs a 'none' color $.jGraduate.Paint({copy: o}) -> creates a copy of the paint o $.jGraduate.Paint({hex: '#rrggbb'}) -> creates a solid color paint with hex = "#rrggbb" $.jGraduate.Paint({linearGradient: o, a: 50}) -> creates a linear gradient paint with opacity=0.5 $.jGraduate.Paint({radialGradient: o, a: 7}) -> creates a radial gradient paint with opacity=0.07 $.jGraduate.Paint({hex: '#rrggbb', linearGradient: o}) -> throws an exception? - picker accepts the following object as input: { okCallback: function to call when Ok is pressed cancelCallback: function to call when Cancel is pressed paint: object describing the paint to display initially, if not set, then default to opaque white } - okCallback receives a Paint object * */ var ns = { svg: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink' }; if (!window.console) { window.console = { log: function log(str) {}, dir: function dir(str) {} }; } function jqPluginJGraduate ($) { if (!$.loadingStylesheets) { $.loadingStylesheets = []; } var stylesheet = 'jgraduate/css/jgraduate.css'; if (!$.loadingStylesheets.includes(stylesheet)) { $.loadingStylesheets.push(stylesheet); } $.jGraduate = { Paint: function Paint(opt) { var options = opt || {}; this.alpha = isNaN(options.alpha) ? 100 : options.alpha; // copy paint object if (options.copy) { this.type = options.copy.type; this.alpha = options.copy.alpha; this.solidColor = null; this.linearGradient = null; this.radialGradient = null; switch (this.type) { case 'none': break; case 'solidColor': this.solidColor = options.copy.solidColor; break; case 'linearGradient': this.linearGradient = options.copy.linearGradient.cloneNode(true); break; case 'radialGradient': this.radialGradient = options.copy.radialGradient.cloneNode(true); break; } // create linear gradient paint } else if (options.linearGradient) { this.type = 'linearGradient'; this.solidColor = null; this.radialGradient = null; this.linearGradient = options.linearGradient.cloneNode(true); // create linear gradient paint } else if (options.radialGradient) { this.type = 'radialGradient'; this.solidColor = null; this.linearGradient = null; this.radialGradient = options.radialGradient.cloneNode(true); // create solid color paint } else if (options.solidColor) { this.type = 'solidColor'; this.solidColor = options.solidColor; // create empty paint } else { this.type = 'none'; this.solidColor = null; this.linearGradient = null; this.radialGradient = null; } } }; $.fn.jGraduateDefaults = { paint: new $.jGraduate.Paint(), window: { pickerTitle: 'Drag markers to pick a paint' }, images: { clientPath: 'images/' }, newstop: 'inverse' // same, inverse, black, white }; var isGecko = navigator.userAgent.includes('Gecko/'); function setAttrs(elem, attrs) { if (isGecko) { for (var aname in attrs) { elem.setAttribute(aname, attrs[aname]); } } else { for (var _aname in attrs) { var val = attrs[_aname], prop = elem[_aname]; if (prop && prop.constructor === 'SVGLength') { prop.baseVal.value = val; } else { elem.setAttribute(_aname, val); } } } } function mkElem(name, attrs, newparent) { var elem = document.createElementNS(ns.svg, name); setAttrs(elem, attrs); if (newparent) { newparent.append(elem); } return elem; } $.fn.jGraduate = function (options) { var $arguments = arguments; return this.each(function () { var $this = $(this), $settings = $.extend(true, {}, $.fn.jGraduateDefaults, options), id = $this.attr('id'), idref = '#' + $this.attr('id') + ' '; if (!idref) { alert('Container element must have an id attribute to maintain unique id strings for sub-elements.'); return; } var okClicked = function okClicked() { switch ($this.paint.type) { case 'radialGradient': $this.paint.linearGradient = null; break; case 'linearGradient': $this.paint.radialGradient = null; break; case 'solidColor': $this.paint.radialGradient = $this.paint.linearGradient = null; break; } typeof $this.okCallback === 'function' && $this.okCallback($this.paint); $this.hide(); }; var cancelClicked = function cancelClicked() { typeof $this.cancelCallback === 'function' && $this.cancelCallback(); $this.hide(); }; $.extend(true, $this, { // public properties, methods, and callbacks // make a copy of the incoming paint paint: new $.jGraduate.Paint({ copy: $settings.paint }), okCallback: typeof $arguments[1] === 'function' && $arguments[1] || null, cancelCallback: typeof $arguments[2] === 'function' && $arguments[2] || null }); var // pos = $this.position(), color = null; var $win = $(window); if ($this.paint.type === 'none') { $this.paint = $.jGraduate.Paint({ solidColor: 'ffffff' }); } $this.addClass('jGraduate_Picker'); $this.html('
      ' + '
    • Solid Color
    • ' + '
    • Linear Gradient
    • ' + '
    • Radial Gradient
    • ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    '); var colPicker = $(idref + '> .jGraduate_colPick'); var gradPicker = $(idref + '> .jGraduate_gradPick'); gradPicker.html('
    ' + '

    ' + $settings.window.pickerTitle + '

    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '' + '' + '' + '' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '' + '' + '' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '' + '' + '' + '' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '
    ' + '' + '' + '' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '' + '
    ' + '' + '
    ' + '
    ' + '' + '
    ' + '' + '
    ' + '' + '
    ' + '
    ' + '' + '
    ' + '' + '
    ' + '' + '
    ' + '
    ' + '' + '
    ' + '' + '
    ' + '' + '
    ' + '
    ' + '
    ' + '' + '' + '
    '); // -------------- // Set up all the SVG elements (the gradient, stops and rectangle) var MAX = 256, MARGINX = 0, MARGINY = 0, // STOP_RADIUS = 15 / 2, SIZEX = MAX - 2 * MARGINX, SIZEY = MAX - 2 * MARGINY; var attrInput = {}; var SLIDERW = 145; $('.jGraduate_SliderBar').width(SLIDERW); var container = $('#' + id + '_jGraduate_GradContainer')[0]; var svg = mkElem('svg', { id: id + '_jgraduate_svg', width: MAX, height: MAX, xmlns: ns.svg }, container); // This wasn't working as designed // let curType; // curType = curType || $this.paint.type; // if we are sent a gradient, import it var curType = $this.paint.type; var grad = $this.paint[curType]; var curGradient = grad; var gradalpha = $this.paint.alpha; var isSolid = curType === 'solidColor'; // Make any missing gradients switch (curType) { case 'solidColor': // fall through case 'linearGradient': if (!isSolid) { curGradient.id = id + '_lg_jgraduate_grad'; grad = curGradient = svg.appendChild(curGradient); // .cloneNode(true)); } mkElem('radialGradient', { id: id + '_rg_jgraduate_grad' }, svg); if (curType === 'linearGradient') { break; } // fall through case 'radialGradient': if (!isSolid) { curGradient.id = id + '_rg_jgraduate_grad'; grad = curGradient = svg.appendChild(curGradient); // .cloneNode(true)); } mkElem('linearGradient', { id: id + '_lg_jgraduate_grad' }, svg); } var stopGroup = void 0; // eslint-disable-line prefer-const if (isSolid) { grad = curGradient = $('#' + id + '_lg_jgraduate_grad')[0]; color = $this.paint[curType]; mkStop(0, '#' + color, 1); var type = _typeof($settings.newstop); if (type === 'string') { switch ($settings.newstop) { case 'same': mkStop(1, '#' + color, 1); break; case 'inverse': // Invert current color for second stop var inverted = ''; for (var i = 0; i < 6; i += 2) { // const ch = color.substr(i, 2); var inv = (255 - parseInt(color.substr(i, 2), 16)).toString(16); if (inv.length < 2) inv = 0 + inv; inverted += inv; } mkStop(1, '#' + inverted, 1); break; case 'white': mkStop(1, '#ffffff', 1); break; case 'black': mkStop(1, '#000000', 1); break; } } else if (type === 'object') { var opac = 'opac' in $settings.newstop ? $settings.newstop.opac : 1; mkStop(1, $settings.newstop.color || '#' + color, opac); } } var x1 = parseFloat(grad.getAttribute('x1') || 0.0), y1 = parseFloat(grad.getAttribute('y1') || 0.0), x2 = parseFloat(grad.getAttribute('x2') || 1.0), y2 = parseFloat(grad.getAttribute('y2') || 0.0); var cx = parseFloat(grad.getAttribute('cx') || 0.5), cy = parseFloat(grad.getAttribute('cy') || 0.5), fx = parseFloat(grad.getAttribute('fx') || cx), fy = parseFloat(grad.getAttribute('fy') || cy); var previewRect = mkElem('rect', { id: id + '_jgraduate_rect', x: MARGINX, y: MARGINY, width: SIZEX, height: SIZEY, fill: 'url(#' + id + '_jgraduate_grad)', 'fill-opacity': gradalpha / 100 }, svg); // stop visuals created here var beginCoord = $('
    ').attr({ class: 'grad_coord jGraduate_lg_field', title: 'Begin Stop' }).text(1).css({ top: y1 * MAX, left: x1 * MAX }).data('coord', 'start').appendTo(container); var endCoord = beginCoord.clone().text(2).css({ top: y2 * MAX, left: x2 * MAX }).attr('title', 'End stop').data('coord', 'end').appendTo(container); var centerCoord = $('
    ').attr({ class: 'grad_coord jGraduate_rg_field', title: 'Center stop' }).text('C').css({ top: cy * MAX, left: cx * MAX }).data('coord', 'center').appendTo(container); var focusCoord = centerCoord.clone().text('F').css({ top: fy * MAX, left: fx * MAX, display: 'none' }).attr('title', 'Focus point').data('coord', 'focus').appendTo(container); focusCoord[0].id = id + '_jGraduate_focusCoord'; // const coords = $(idref + ' .grad_coord'); // $(container).hover(function () { // coords.animate({ // opacity: 1 // }, 500); // }, function () { // coords.animate({ // opacity: .2 // }, 500); // }); var showFocus = void 0; $.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function (i, attr) { var isRadial = isNaN(attr[1]); var attrval = curGradient.getAttribute(attr); if (!attrval) { // Set defaults if (isRadial) { // For radial points attrval = '0.5'; } else { // Only x2 is 1 attrval = attr === 'x2' ? '1.0' : '0.0'; } } attrInput[attr] = $('#' + id + '_jGraduate_' + attr).val(attrval).change(function () { // TODO: Support values < 0 and > 1 (zoomable preview?) if (isNaN(parseFloat(this.value)) || this.value < 0) { this.value = 0.0; } else if (this.value > 1) { this.value = 1.0; } if (!(attr[0] === 'f' && !showFocus)) { if (isRadial && curType === 'radialGradient' || !isRadial && curType === 'linearGradient') { curGradient.setAttribute(attr, this.value); } } var $elem = isRadial ? attr[0] === 'c' ? centerCoord : focusCoord : attr[1] === '1' ? beginCoord : endCoord; var cssName = attr.includes('x') ? 'left' : 'top'; $elem.css(cssName, this.value * MAX); }).change(); }); function mkStop(n, color, opac, sel, stopElem) { var stop = stopElem || mkElem('stop', { 'stop-color': color, 'stop-opacity': opac, offset: n }, curGradient); if (stopElem) { color = stopElem.getAttribute('stop-color'); opac = stopElem.getAttribute('stop-opacity'); n = stopElem.getAttribute('offset'); } else { curGradient.append(stop); } if (opac === null) opac = 1; var pickerD = 'M-6.2,0.9c3.6-4,6.7-4.3,6.7-12.4c-0.2,7.9,3.1,8.8,6.5,12.4c3.5,3.8,2.9,9.6,0,12.3c-3.1,2.8-10.4,2.7-13.2,0C-9.6,9.9-9.4,4.4-6.2,0.9z'; var pathbg = mkElem('path', { d: pickerD, fill: 'url(#jGraduate_trans)', transform: 'translate(' + (10 + n * MAX) + ', 26)' }, stopGroup); var path = mkElem('path', { d: pickerD, fill: color, 'fill-opacity': opac, transform: 'translate(' + (10 + n * MAX) + ', 26)', stroke: '#000', 'stroke-width': 1.5 }, stopGroup); $(path).mousedown(function (e) { selectStop(this); drag = curStop; $win.mousemove(dragColor).mouseup(remDrags); stopOffset = stopMakerDiv.offset(); e.preventDefault(); return false; }).data('stop', stop).data('bg', pathbg).dblclick(function () { $('div.jGraduate_LightBox').show(); var colorhandle = this; var stopOpacity = +stop.getAttribute('stop-opacity') || 1; var stopColor = stop.getAttribute('stop-color') || 1; var thisAlpha = (parseFloat(stopOpacity) * 255).toString(16); while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } color = stopColor.substr(1) + thisAlpha; $('#' + id + '_jGraduate_stopPicker').css({ left: 100, bottom: 15 }).jPicker({ window: { title: 'Pick the start color and opacity for the gradient' }, images: { clientPath: $settings.images.clientPath }, color: { active: color, alphaSupport: true } }, function (color, arg2) { stopColor = color.val('hex') ? '#' + color.val('hex') : 'none'; stopOpacity = color.val('a') !== null ? color.val('a') / 256 : 1; colorhandle.setAttribute('fill', stopColor); colorhandle.setAttribute('fill-opacity', stopOpacity); stop.setAttribute('stop-color', stopColor); stop.setAttribute('stop-opacity', stopOpacity); $('div.jGraduate_LightBox').hide(); $('#' + id + '_jGraduate_stopPicker').hide(); }, null, function () { $('div.jGraduate_LightBox').hide(); $('#' + id + '_jGraduate_stopPicker').hide(); }); }); $(curGradient).find('stop').each(function () { var curS = $(this); if (+this.getAttribute('offset') > n) { if (!color) { var newcolor = this.getAttribute('stop-color'); var newopac = this.getAttribute('stop-opacity'); stop.setAttribute('stop-color', newcolor); path.setAttribute('fill', newcolor); stop.setAttribute('stop-opacity', newopac === null ? 1 : newopac); path.setAttribute('fill-opacity', newopac === null ? 1 : newopac); } curS.before(stop); return false; } }); if (sel) selectStop(path); return stop; } function remStop() { delStop.setAttribute('display', 'none'); var path = $(curStop); var stop = path.data('stop'); var bg = path.data('bg'); $([curStop, stop, bg]).remove(); } var stopMakerDiv = $('#' + id + '_jGraduate_StopSlider'); var stops = void 0, curStop = void 0, drag = void 0; var delStop = mkElem('path', { d: 'm9.75,-6l-19.5,19.5m0,-19.5l19.5,19.5', fill: 'none', stroke: '#D00', 'stroke-width': 5, display: 'none' }, undefined); // stopMakerSVG); function selectStop(item) { if (curStop) curStop.setAttribute('stroke', '#000'); item.setAttribute('stroke', 'blue'); curStop = item; curStop.parentNode.append(curStop); // stops = $('stop'); // opac_select.val(curStop.attr('fill-opacity') || 1); // root.append(delStop); } var stopOffset = void 0; function remDrags() { $win.unbind('mousemove', dragColor); if (delStop.getAttribute('display') !== 'none') { remStop(); } drag = null; } var scaleX = 1, scaleY = 1, angle = 0; var cX = cx; var cY = cy; function xform() { var rot = angle ? 'rotate(' + angle + ',' + cX + ',' + cY + ') ' : ''; if (scaleX === 1 && scaleY === 1) { curGradient.removeAttribute('gradientTransform'); // $('#ang').addClass('dis'); } else { var x = -cX * (scaleX - 1); var y = -cY * (scaleY - 1); curGradient.setAttribute('gradientTransform', rot + 'translate(' + x + ',' + y + ') scale(' + scaleX + ',' + scaleY + ')'); // $('#ang').removeClass('dis'); } } function dragColor(evt) { var x = evt.pageX - stopOffset.left; var y = evt.pageY - stopOffset.top; x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x; var xfStr = 'translate(' + x + ', 26)'; if (y < -60 || y > 130) { delStop.setAttribute('display', 'block'); delStop.setAttribute('transform', xfStr); } else { delStop.setAttribute('display', 'none'); } drag.setAttribute('transform', xfStr); $.data(drag, 'bg').setAttribute('transform', xfStr); var stop = $.data(drag, 'stop'); var sX = (x - 10) / MAX; stop.setAttribute('offset', sX); var last = 0; $(curGradient).find('stop').each(function (i) { var cur = this.getAttribute('offset'); var t = $(this); if (cur < last) { t.prev().before(t); stops = $(curGradient).find('stop'); } last = cur; }); } var stopMakerSVG = mkElem('svg', { width: '100%', height: 45 }, stopMakerDiv[0]); var transPattern = mkElem('pattern', { width: 16, height: 16, patternUnits: 'userSpaceOnUse', id: 'jGraduate_trans' }, stopMakerSVG); var transImg = mkElem('image', { width: 16, height: 16 }, transPattern); var bgImage = $settings.images.clientPath + 'map-opacity.png'; transImg.setAttributeNS(ns.xlink, 'xlink:href', bgImage); $(stopMakerSVG).click(function (evt) { stopOffset = stopMakerDiv.offset(); var target = evt.target; if (target.tagName === 'path') return; var x = evt.pageX - stopOffset.left - 8; x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x; mkStop(x / MAX, 0, 0, true); evt.stopPropagation(); }); $(stopMakerSVG).mouseover(function () { stopMakerSVG.append(delStop); }); stopGroup = mkElem('g', {}, stopMakerSVG); mkElem('line', { x1: 10, y1: 15, x2: MAX + 10, y2: 15, 'stroke-width': 2, stroke: '#000' }, stopMakerSVG); var spreadMethodOpt = gradPicker.find('.jGraduate_spreadMethod').change(function () { curGradient.setAttribute('spreadMethod', $(this).val()); }); // handle dragging the stop around the swatch var draggingCoord = null; var onCoordDrag = function onCoordDrag(evt) { var x = evt.pageX - offset.left; var y = evt.pageY - offset.top; // clamp stop to the swatch x = x < 0 ? 0 : x > MAX ? MAX : x; y = y < 0 ? 0 : y > MAX ? MAX : y; draggingCoord.css('left', x).css('top', y); // calculate stop offset var fracx = x / SIZEX; var fracy = y / SIZEY; var type = draggingCoord.data('coord'); var grad = curGradient; switch (type) { case 'start': attrInput.x1.val(fracx); attrInput.y1.val(fracy); grad.setAttribute('x1', fracx); grad.setAttribute('y1', fracy); break; case 'end': attrInput.x2.val(fracx); attrInput.y2.val(fracy); grad.setAttribute('x2', fracx); grad.setAttribute('y2', fracy); break; case 'center': attrInput.cx.val(fracx); attrInput.cy.val(fracy); grad.setAttribute('cx', fracx); grad.setAttribute('cy', fracy); cX = fracx; cY = fracy; xform(); break; case 'focus': attrInput.fx.val(fracx); attrInput.fy.val(fracy); grad.setAttribute('fx', fracx); grad.setAttribute('fy', fracy); xform(); } evt.preventDefault(); }; var onCoordUp = function onCoordUp() { draggingCoord = null; $win.unbind('mousemove', onCoordDrag).unbind('mouseup', onCoordUp); }; // Linear gradient // (function () { stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); var numstops = stops.length; // if there are not at least two stops, then if (numstops < 2) { while (numstops < 2) { curGradient.append(document.createElementNS(ns.svg, 'stop')); ++numstops; } stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); } for (var _i = 0; _i < numstops; _i++) { mkStop(0, 0, 0, 0, stops[_i]); } spreadMethodOpt.val(curGradient.getAttribute('spreadMethod') || 'pad'); var offset = void 0; // No match, so show focus point showFocus = false; previewRect.setAttribute('fill-opacity', gradalpha / 100); $('#' + id + ' div.grad_coord').mousedown(function (evt) { evt.preventDefault(); draggingCoord = $(this); // const sPos = draggingCoord.offset(); offset = draggingCoord.parent().offset(); $win.mousemove(onCoordDrag).mouseup(onCoordUp); }); // bind GUI elements $('#' + id + '_jGraduate_Ok').bind('click', function () { $this.paint.type = curType; $this.paint[curType] = curGradient.cloneNode(true); $this.paint.solidColor = null; okClicked(); }); $('#' + id + '_jGraduate_Cancel').bind('click', function (paint) { cancelClicked(); }); if (curType === 'radialGradient') { if (showFocus) { focusCoord.show(); } else { focusCoord.hide(); attrInput.fx.val(''); attrInput.fy.val(''); } } $('#' + id + '_jGraduate_match_ctr')[0].checked = !showFocus; var lastfx = void 0, lastfy = void 0; $('#' + id + '_jGraduate_match_ctr').change(function () { showFocus = !this.checked; focusCoord.toggle(showFocus); attrInput.fx.val(''); attrInput.fy.val(''); var grad = curGradient; if (!showFocus) { lastfx = grad.getAttribute('fx'); lastfy = grad.getAttribute('fy'); grad.removeAttribute('fx'); grad.removeAttribute('fy'); } else { var _fx = lastfx || 0.5; var _fy = lastfy || 0.5; grad.setAttribute('fx', _fx); grad.setAttribute('fy', _fy); attrInput.fx.val(_fx); attrInput.fy.val(_fy); } }); stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); numstops = stops.length; // if there are not at least two stops, then if (numstops < 2) { while (numstops < 2) { curGradient.append(document.createElementNS(ns.svg, 'stop')); ++numstops; } stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); } var slider = void 0; var setSlider = function setSlider(e) { var _slider = slider, offset = _slider.offset; var div = slider.parent; var x = e.pageX - offset.left - parseInt(div.css('border-left-width')); if (x > SLIDERW) x = SLIDERW; if (x <= 0) x = 0; var posx = x - 5; x /= SLIDERW; switch (slider.type) { case 'radius': x = Math.pow(x * 2, 2.5); if (x > 0.98 && x < 1.02) x = 1; if (x <= 0.01) x = 0.01; curGradient.setAttribute('r', x); break; case 'opacity': $this.paint.alpha = parseInt(x * 100); previewRect.setAttribute('fill-opacity', x); break; case 'ellip': scaleX = 1; scaleY = 1; if (x < 0.5) { x /= 0.5; // 0.001 scaleX = x <= 0 ? 0.01 : x; } else if (x > 0.5) { x /= 0.5; // 2 x = 2 - x; scaleY = x <= 0 ? 0.01 : x; } xform(); x -= 1; if (scaleY === x + 1) { x = Math.abs(x); } break; case 'angle': x = x - 0.5; angle = x *= 180; xform(); x /= 100; break; } slider.elem.css({ 'margin-left': posx }); x = Math.round(x * 100); slider.input.val(x); }; var ellipVal = 0, angleVal = 0; if (curType === 'radialGradient') { var tlist = curGradient.gradientTransform.baseVal; if (tlist.numberOfItems === 2) { var t = tlist.getItem(0); var s = tlist.getItem(1); if (t.type === 2 && s.type === 3) { var m = s.matrix; if (m.a !== 1) { ellipVal = Math.round(-(1 - m.a) * 100); } else if (m.d !== 1) { ellipVal = Math.round((1 - m.d) * 100); } } } else if (tlist.numberOfItems === 3) { // Assume [R][T][S] var r = tlist.getItem(0); var _t = tlist.getItem(1); var _s = tlist.getItem(2); if (r.type === 4 && _t.type === 2 && _s.type === 3) { angleVal = Math.round(r.angle); var _m = _s.matrix; if (_m.a !== 1) { ellipVal = Math.round(-(1 - _m.a) * 100); } else if (_m.d !== 1) { ellipVal = Math.round((1 - _m.d) * 100); } } } } var sliders = { radius: { handle: '#' + id + '_jGraduate_RadiusArrows', input: '#' + id + '_jGraduate_RadiusInput', val: (curGradient.getAttribute('r') || 0.5) * 100 }, opacity: { handle: '#' + id + '_jGraduate_OpacArrows', input: '#' + id + '_jGraduate_OpacInput', val: $this.paint.alpha || 100 }, ellip: { handle: '#' + id + '_jGraduate_EllipArrows', input: '#' + id + '_jGraduate_EllipInput', val: ellipVal }, angle: { handle: '#' + id + '_jGraduate_AngleArrows', input: '#' + id + '_jGraduate_AngleInput', val: angleVal } }; $.each(sliders, function (type, data) { var handle = $(data.handle); handle.mousedown(function (evt) { var parent = handle.parent(); slider = { type: type, elem: handle, input: $(data.input), parent: parent, offset: parent.offset() }; $win.mousemove(dragSlider).mouseup(stopSlider); evt.preventDefault(); }); $(data.input).val(data.val).change(function () { var isRad = curType === 'radialGradient'; var val = +this.value; var xpos = 0; switch (type) { case 'radius': if (isRad) curGradient.setAttribute('r', val / 100); xpos = Math.pow(val / 100, 1 / 2.5) / 2 * SLIDERW; break; case 'opacity': $this.paint.alpha = val; previewRect.setAttribute('fill-opacity', val / 100); xpos = val * (SLIDERW / 100); break; case 'ellip': scaleX = scaleY = 1; if (val === 0) { xpos = SLIDERW * 0.5; break; } if (val > 99.5) val = 99.5; if (val > 0) { scaleY = 1 - val / 100; } else { scaleX = -(val / 100) - 1; } xpos = SLIDERW * ((val + 100) / 2) / 100; if (isRad) xform(); break; case 'angle': angle = val; xpos = angle / 180; xpos += 0.5; xpos *= SLIDERW; if (isRad) xform(); } if (xpos > SLIDERW) { xpos = SLIDERW; } else if (xpos < 0) { xpos = 0; } handle.css({ 'margin-left': xpos - 5 }); }).change(); }); var dragSlider = function dragSlider(evt) { setSlider(evt); evt.preventDefault(); }; var stopSlider = function stopSlider(evt) { $win.unbind('mousemove', dragSlider).unbind('mouseup', stopSlider); slider = null; }; // -------------- var thisAlpha = ($this.paint.alpha * 255 / 100).toString(16); while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } thisAlpha = thisAlpha.split('.')[0]; color = $this.paint.solidColor === 'none' ? '' : $this.paint.solidColor + thisAlpha; if (!isSolid) { color = stops[0].getAttribute('stop-color'); } // This should be done somewhere else, probably $.extend($.fn.jPicker.defaults.window, { alphaSupport: true, effects: { type: 'show', speed: 0 } }); colPicker.jPicker({ window: { title: $settings.window.pickerTitle }, images: { clientPath: $settings.images.clientPath }, color: { active: color, alphaSupport: true } }, function (color) { $this.paint.type = 'solidColor'; $this.paint.alpha = color.val('ahex') ? Math.round(color.val('a') / 255 * 100) : 100; $this.paint.solidColor = color.val('hex') ? color.val('hex') : 'none'; $this.paint.radialGradient = null; okClicked(); }, null, function () { cancelClicked(); }); var tabs = $(idref + ' .jGraduate_tabs li'); tabs.click(function () { tabs.removeClass('jGraduate_tab_current'); $(this).addClass('jGraduate_tab_current'); $(idref + ' > div').hide(); var type = $(this).attr('data-type'); /* const container = */$(idref + ' .jGraduate_gradPick').show(); if (type === 'rg' || type === 'lg') { // Show/hide appropriate fields $('.jGraduate_' + type + '_field').show(); $('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field').hide(); $('#' + id + '_jgraduate_rect')[0].setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)'); // Copy stops curType = type === 'lg' ? 'linearGradient' : 'radialGradient'; $('#' + id + '_jGraduate_OpacInput').val($this.paint.alpha).change(); var newGrad = $('#' + id + '_' + type + '_jgraduate_grad')[0]; if (curGradient !== newGrad) { var curStops = $(curGradient).find('stop'); $(newGrad).empty().append(curStops); curGradient = newGrad; var sm = spreadMethodOpt.val(); curGradient.setAttribute('spreadMethod', sm); } showFocus = type === 'rg' && curGradient.getAttribute('fx') != null && !(cx === fx && cy === fy); $('#' + id + '_jGraduate_focusCoord').toggle(showFocus); if (showFocus) { $('#' + id + '_jGraduate_match_ctr')[0].checked = false; } } else { $(idref + ' .jGraduate_gradPick').hide(); $(idref + ' .jGraduate_colPick').show(); } }); $(idref + ' > div').hide(); tabs.removeClass('jGraduate_tab_current'); var tab = void 0; switch ($this.paint.type) { case 'linearGradient': tab = $(idref + ' .jGraduate_tab_lingrad'); break; case 'radialGradient': tab = $(idref + ' .jGraduate_tab_radgrad'); break; default: tab = $(idref + ' .jGraduate_tab_color'); break; } $this.show(); // jPicker will try to show after a 0ms timeout, so need to fire this after that setTimeout(function () { tab.addClass('jGraduate_tab_current').click(); }, 10); }); }; return $; } /* SpinButton control * * Adds bells and whistles to any ordinary textbox to * make it look and feel like a SpinButton Control. * * Originally written by George Adamson, Software Unity (george.jquery@softwareunity.com) August 2006. * - Added min/max options * - Added step size option * - Added bigStep (page up/down) option * * Modifications made by Mark Gibson, (mgibson@designlinks.net) September 2006: * - Converted to jQuery plugin * - Allow limited or unlimited min/max values * - Allow custom class names, and add class to input element * - Removed global vars * - Reset (to original or through config) when invalid value entered * - Repeat whilst holding mouse button down (with initial pause, like keyboard repeat) * - Support mouse wheel in Firefox * - Fix double click in IE * - Refactored some code and renamed some vars * * Modifications by Jeff Schiller, June 2009: * - provide callback function for when the value changes based on the following * https://www.mail-archive.com/jquery-en@googlegroups.com/msg36070.html * Modifications by Jeff Schiller, July 2009: * - improve styling for widget in Opera * - consistent key-repeat handling cross-browser * Modifications by Alexis Deveria, October 2009: * - provide "stepfunc" callback option to allow custom function to run when changing a value * - Made adjustValue(0) only run on certain keyup events, not all. * * Tested in IE6, Opera9, Firefox 1.5 * v1.0 11 Aug 2006 - George Adamson - First release * v1.1 Aug 2006 - George Adamson - Minor enhancements * v1.2 27 Sep 2006 - Mark Gibson - Major enhancements * v1.3a 28 Sep 2006 - George Adamson - Minor enhancements * v1.4 18 Jun 2009 - Jeff Schiller - Added callback function * v1.5 06 Jul 2009 - Jeff Schiller - Fixes for Opera. * v1.6 13 Oct 2009 - Alexis Deveria - Added stepfunc function * v1.7 21 Oct 2009 - Alexis Deveria - Minor fixes * Fast-repeat for keys and live updating as you type. * v1.8 12 Jan 2010 - Benjamin Thomas - Fixes for mouseout behavior. * Added smallStep * ? 20 May 2018 - Brett Zamir - Avoid SVGEdit dependency via `stateObj` config; convert to ES6 module Sample usage: // Create group of settings to initialise spinbutton(s). (Optional) const myOptions = { min: 0, // Set lower limit. max: 100, // Set upper limit. step: 1, // Set increment size. smallStep: 0.5, // Set shift-click increment size. stateObj: {tool_scale: 1}, // Object to allow passing in live-updating scale spinClass: mySpinBtnClass, // CSS class to style the spinbutton. (Class also specifies url of the up/down button image.) upClass: mySpinUpClass, // CSS class for style when mouse over up button. downClass: mySpinDnClass // CSS class for style when mouse over down button. }; $(function () { // Initialise INPUT element(s) as SpinButtons: (passing options if desired) $("#myInputElement").SpinButton(myOptions); }); */ function jqPluginSpinBtn ($) { if (!$.loadingStylesheets) { $.loadingStylesheets = []; } var stylesheet = 'spinbtn/JQuerySpinBtn.css'; if (!$.loadingStylesheets.includes(stylesheet)) { $.loadingStylesheets.push(stylesheet); } $.fn.SpinButton = function (cfg) { cfg = cfg || {}; function coord(el, prop) { var b = document.body; var c = el[prop]; while ((el = el.offsetParent) && el !== b) { if (!$.browser.msie || el.currentStyle.position !== 'relative') { c += el[prop]; } } return c; } return this.each(function () { this.repeating = false; // Apply specified options or defaults: // (Ought to refactor this some day to use $.extend() instead) this.spinCfg = { // min: cfg.min ? Number(cfg.min) : null, // max: cfg.max ? Number(cfg.max) : null, min: !isNaN(parseFloat(cfg.min)) ? Number(cfg.min) : null, // Fixes bug with min:0 max: !isNaN(parseFloat(cfg.max)) ? Number(cfg.max) : null, step: cfg.step ? Number(cfg.step) : 1, stepfunc: cfg.stepfunc || false, page: cfg.page ? Number(cfg.page) : 10, upClass: cfg.upClass || 'up', downClass: cfg.downClass || 'down', reset: cfg.reset || this.value, delay: cfg.delay ? Number(cfg.delay) : 500, interval: cfg.interval ? Number(cfg.interval) : 100, _btn_width: 20, _direction: null, _delay: null, _repeat: null, callback: cfg.callback || null }; // if a smallStep isn't supplied, use half the regular step this.spinCfg.smallStep = cfg.smallStep || this.spinCfg.step / 2; this.adjustValue = function (i) { var v = void 0; if (isNaN(this.value)) { v = this.spinCfg.reset; } else if (typeof this.spinCfg.stepfunc === 'function') { v = this.spinCfg.stepfunc(this, i); } else { // weirdest JavaScript bug ever: 5.1 + 0.1 = 5.199999999 v = Number((Number(this.value) + Number(i)).toFixed(5)); } if (this.spinCfg.min !== null) { v = Math.max(v, this.spinCfg.min); } if (this.spinCfg.max !== null) { v = Math.min(v, this.spinCfg.max); } this.value = v; if (typeof this.spinCfg.callback === 'function') { this.spinCfg.callback(this); } }; $(this).addClass(cfg.spinClass || 'spin-button').mousemove(function (e) { // Determine which button mouse is over, or not (spin direction): var x = e.pageX || e.x; var y = e.pageY || e.y; var el = e.target; var scale = cfg.stateObj.tool_scale || 1; var height = $(el).height() / 2; var direction = x > coord(el, 'offsetLeft') + el.offsetWidth * scale - this.spinCfg._btn_width ? y < coord(el, 'offsetTop') + height * scale ? 1 : -1 : 0; if (direction !== this.spinCfg._direction) { // Style up/down buttons: switch (direction) { case 1: // Up arrow: $(this).removeClass(this.spinCfg.downClass).addClass(this.spinCfg.upClass); break; case -1: // Down arrow: $(this).removeClass(this.spinCfg.upClass).addClass(this.spinCfg.downClass); break; default: // Mouse is elsewhere in the textbox $(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass); } // Set spin direction: this.spinCfg._direction = direction; } }).mouseout(function () { // Reset up/down buttons to their normal appearance when mouse moves away: $(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass); this.spinCfg._direction = null; window.clearInterval(this.spinCfg._repeat); window.clearTimeout(this.spinCfg._delay); }).mousedown(function (e) { var _this = this; if (e.button === 0 && this.spinCfg._direction !== 0) { // Respond to click on one of the buttons: var stepSize = e.shiftKey ? this.spinCfg.smallStep : this.spinCfg.step; var adjust = function adjust() { _this.adjustValue(_this.spinCfg._direction * stepSize); }; adjust(); // Initial delay before repeating adjustment this.spinCfg._delay = window.setTimeout(function () { adjust(); // Repeat adjust at regular intervals _this.spinCfg._repeat = window.setInterval(adjust, _this.spinCfg.interval); }, this.spinCfg.delay); } }).mouseup(function (e) { // Cancel repeating adjustment window.clearInterval(this.spinCfg._repeat); window.clearTimeout(this.spinCfg._delay); }).dblclick(function (e) { if ($.browser.msie) { this.adjustValue(this.spinCfg._direction * this.spinCfg.step); } }).keydown(function (e) { // Respond to up/down arrow keys. switch (e.keyCode) { case 38: this.adjustValue(this.spinCfg.step);break; // Up case 40: this.adjustValue(-this.spinCfg.step);break; // Down case 33: this.adjustValue(this.spinCfg.page);break; // PageUp case 34: this.adjustValue(-this.spinCfg.page);break; // PageDown } }) /* http://unixpapa.com/js/key.html describes the current state-of-affairs for key repeat events: - Safari 3.1 changed their model so that keydown is reliably repeated going forward - Firefox and Opera still only repeat the keypress event, not the keydown */ .keypress(function (e) { if (this.repeating) { // Respond to up/down arrow keys. switch (e.keyCode) { case 38: this.adjustValue(this.spinCfg.step);break; // Up case 40: this.adjustValue(-this.spinCfg.step);break; // Down case 33: this.adjustValue(this.spinCfg.page);break; // PageUp case 34: this.adjustValue(-this.spinCfg.page);break; // PageDown } // we always ignore the first keypress event (use the keydown instead) } else { this.repeating = true; } }) // clear the 'repeating' flag .keyup(function (e) { this.repeating = false; switch (e.keyCode) { case 38: // Up case 40: // Down case 33: // PageUp case 34: // PageDown case 13: this.adjustValue(0);break; // Enter/Return } }).bind('mousewheel', function (e) { // Respond to mouse wheel in IE. (It returns up/dn motion in multiples of 120) if (e.wheelDelta >= 120) { this.adjustValue(this.spinCfg.step); } else if (e.wheelDelta <= -120) { this.adjustValue(-this.spinCfg.step); } e.preventDefault(); }).change(function (e) { this.adjustValue(0); }); if (this.addEventListener) { // Respond to mouse wheel in Firefox this.addEventListener('DOMMouseScroll', function (e) { if (e.detail > 0) { this.adjustValue(-this.spinCfg.step); } else if (e.detail < 0) { this.adjustValue(this.spinCfg.step); } e.preventDefault(); }, false); } }); }; return $; } // Todo: Update to latest version and adapt (and needs jQuery update as well): https://github.com/swisnl/jQuery-contextMenu function jqPluginContextMenu ($) { var win = $(window); var doc = $(document); $.extend($.fn, { contextMenu: function contextMenu(o, callback) { // Defaults if (o.menu === undefined) return false; if (o.inSpeed === undefined) o.inSpeed = 150; if (o.outSpeed === undefined) o.outSpeed = 75; // 0 needs to be -1 for expected results (no fade) if (o.inSpeed === 0) o.inSpeed = -1; if (o.outSpeed === 0) o.outSpeed = -1; // Loop each context menu $(this).each(function () { var el = $(this); var offset = $(el).offset(); var menu = $('#' + o.menu); // Add contextMenu class menu.addClass('contextMenu'); // Simulate a true right click $(this).bind('mousedown', function (e) { var evt = e; $(this).mouseup(function (e) { var srcElement = $(this); srcElement.unbind('mouseup'); if (evt.button === 2 || o.allowLeft || evt.ctrlKey && isMac()) { e.stopPropagation(); // Hide context menus that may be showing $('.contextMenu').hide(); // Get this context menu if (el.hasClass('disabled')) return false; // Detect mouse position var x = e.pageX, y = e.pageY; var xOff = win.width() - menu.width(), yOff = win.height() - menu.height(); if (x > xOff - 15) x = xOff - 15; if (y > yOff - 30) y = yOff - 30; // 30 is needed to prevent scrollbars in FF // Show the menu doc.unbind('click'); menu.css({ top: y, left: x }).fadeIn(o.inSpeed); // Hover events menu.find('A').mouseover(function () { menu.find('LI.hover').removeClass('hover'); $(this).parent().addClass('hover'); }).mouseout(function () { menu.find('LI.hover').removeClass('hover'); }); // Keyboard doc.keypress(function (e) { switch (e.keyCode) { case 38: // up if (!menu.find('LI.hover').length) { menu.find('LI:last').addClass('hover'); } else { menu.find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover'); if (!menu.find('LI.hover').length) menu.find('LI:last').addClass('hover'); } break; case 40: // down if (!menu.find('LI.hover').length) { menu.find('LI:first').addClass('hover'); } else { menu.find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover'); if (!menu.find('LI.hover').length) menu.find('LI:first').addClass('hover'); } break; case 13: // enter menu.find('LI.hover A').trigger('click'); break; case 27: // esc doc.trigger('click'); break; } }); // When items are selected menu.find('A').unbind('mouseup'); menu.find('LI:not(.disabled) A').mouseup(function () { doc.unbind('click').unbind('keypress'); $('.contextMenu').hide(); // Callback if (callback) callback($(this).attr('href').substr(1), $(srcElement), { x: x - offset.left, y: y - offset.top, docX: x, docY: y }); return false; }); // Hide bindings setTimeout(function () { // Delay for Mozilla doc.click(function () { doc.unbind('click').unbind('keypress'); menu.fadeOut(o.outSpeed); return false; }); }, 0); } }); }); // Disable text selection if ($.browser.mozilla) { $('#' + o.menu).each(function () { $(this).css({ MozUserSelect: 'none' }); }); } else if ($.browser.msie) { $('#' + o.menu).each(function () { $(this).bind('selectstart.disableTextSelect', function () { return false; }); }); } else { $('#' + o.menu).each(function () { $(this).bind('mousedown.disableTextSelect', function () { return false; }); }); } // Disable browser context menu (requires both selectors to work in IE/Safari + FF/Chrome) $(el).add($('UL.contextMenu')).bind('contextmenu', function () { return false; }); }); return $(this); }, // Disable context menu items on the fly disableContextMenuItems: function disableContextMenuItems(o) { if (o === undefined) { // Disable all $(this).find('LI').addClass('disabled'); return $(this); } $(this).each(function () { if (o !== undefined) { var d = o.split(','); for (var i = 0; i < d.length; i++) { $(this).find('A[href="' + d[i] + '"]').parent().addClass('disabled'); } } }); return $(this); }, // Enable context menu items on the fly enableContextMenuItems: function enableContextMenuItems(o) { if (o === undefined) { // Enable all $(this).find('LI.disabled').removeClass('disabled'); return $(this); } $(this).each(function () { if (o !== undefined) { var d = o.split(','); for (var i = 0; i < d.length; i++) { $(this).find('A[href="' + d[i] + '"]').parent().removeClass('disabled'); } } }); return $(this); }, // Disable context menu(s) disableContextMenu: function disableContextMenu() { $(this).each(function () { $(this).addClass('disabled'); }); return $(this); }, // Enable context menu(s) enableContextMenu: function enableContextMenu() { $(this).each(function () { $(this).removeClass('disabled'); }); return $(this); }, // Destroy context menu(s) destroyContextMenu: function destroyContextMenu() { // Destroy specified context menus $(this).each(function () { // Disable action $(this).unbind('mousedown').unbind('mouseup'); }); return $(this); } }); return $; } /* * jPicker (Adapted from version 1.1.6) * * jQuery Plugin for Photoshop style color picker * * Copyright (c) 2010 Christopher T. Tillman * Digital Magic Productions, Inc. (http://www.digitalmagicpro.com/) * MIT style license, FREE to use, alter, copy, sell, and especially ENHANCE * * Painstakingly ported from John Dyers' excellent work on his own color picker based on the Prototype framework. * * John Dyers' website: (http://johndyer.name) * Color Picker page: (http://johndyer.name/post/2007/09/PhotoShop-like-JavaScript-Color-Picker.aspx) * */ Math.precision = function (value, precision) { if (precision === undefined) precision = 0; return Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision); }; var jPicker = function jPicker($) { if (!$.loadingStylesheets) { $.loadingStylesheets = []; } var stylesheet = 'jgraduate/css/jPicker.css'; if (!$.loadingStylesheets.includes(stylesheet)) { $.loadingStylesheets.push(stylesheet); } /** * Encapsulate slider functionality for the ColorMap and ColorBar - * could be useful to use a jQuery UI draggable for this with certain extensions */ function Slider(bar, options) { var $this = this; function fireChangeEvents(context) { for (var i = 0; i < changeEvents.length; i++) { changeEvents[i].call($this, $this, context); } } // bind the mousedown to the bar not the arrow for quick snapping to the clicked location function mouseDown(e) { var off = bar.offset(); offset = { l: off.left | 0, t: off.top | 0 }; clearTimeout(timeout); // using setTimeout for visual updates - once the style is updated the browser will re-render internally allowing the next Javascript to run timeout = setTimeout(function () { setValuesFromMousePosition.call($this, e); }, 0); // Bind mousemove and mouseup event to the document so it responds when dragged of of the bar - we will unbind these when on mouseup to save processing $(document).bind('mousemove', mouseMove).bind('mouseup', mouseUp); e.preventDefault(); // don't try to select anything or drag the image to the desktop } // set the values as the mouse moves function mouseMove(e) { clearTimeout(timeout); timeout = setTimeout(function () { setValuesFromMousePosition.call($this, e); }, 0); e.stopPropagation(); e.preventDefault(); return false; } // unbind the document events - they aren't needed when not dragging function mouseUp(e) { $(document).unbind('mouseup', mouseUp).unbind('mousemove', mouseMove); e.stopPropagation(); e.preventDefault(); return false; } // calculate mouse position and set value within the current range function setValuesFromMousePosition(e) { var barW = bar.w, // local copies for YUI compressor barH = bar.h; var locX = e.pageX - offset.l, locY = e.pageY - offset.t; // keep the arrow within the bounds of the bar if (locX < 0) locX = 0;else if (locX > barW) locX = barW; if (locY < 0) locY = 0;else if (locY > barH) locY = barH; val.call($this, 'xy', { x: locX / barW * rangeX + minX, y: locY / barH * rangeY + minY }); } function draw() { var barW = bar.w, barH = bar.h, arrowW = arrow.w, arrowH = arrow.h; var arrowOffsetX = 0, arrowOffsetY = 0; setTimeout(function () { if (rangeX > 0) { // range is greater than zero // constrain to bounds if (x === maxX) arrowOffsetX = barW;else arrowOffsetX = x / rangeX * barW | 0; } if (rangeY > 0) { // range is greater than zero // constrain to bounds if (y === maxY) arrowOffsetY = barH;else arrowOffsetY = y / rangeY * barH | 0; } // if arrow width is greater than bar width, center arrow and prevent horizontal dragging if (arrowW >= barW) arrowOffsetX = (barW >> 1) - (arrowW >> 1); // number >> 1 - superfast bitwise divide by two and truncate (move bits over one bit discarding lowest) else arrowOffsetX -= arrowW >> 1; // if arrow height is greater than bar height, center arrow and prevent vertical dragging if (arrowH >= barH) arrowOffsetY = (barH >> 1) - (arrowH >> 1);else arrowOffsetY -= arrowH >> 1; // set the arrow position based on these offsets arrow.css({ left: arrowOffsetX + 'px', top: arrowOffsetY + 'px' }); }, 0); } function val(name, value, context) { var set$$1 = value !== undefined; if (!set$$1) { if (name === undefined || name == null) name = 'xy'; switch (name.toLowerCase()) { case 'x': return x; case 'y': return y; case 'xy': default: return { x: x, y: y }; } } if (context != null && context === $this) return; var changed = false; var newX = void 0, newY = void 0; if (name == null) name = 'xy'; switch (name.toLowerCase()) { case 'x': newX = value && (value.x && value.x | 0 || value | 0) || 0; break; case 'y': newY = value && (value.y && value.y | 0 || value | 0) || 0; break; case 'xy': default: newX = value && value.x && value.x | 0 || 0; newY = value && value.y && value.y | 0 || 0; break; } if (newX != null) { if (newX < minX) newX = minX;else if (newX > maxX) newX = maxX; if (x !== newX) { x = newX; changed = true; } } if (newY != null) { if (newY < minY) newY = minY;else if (newY > maxY) newY = maxY; if (y !== newY) { y = newY; changed = true; } } changed && fireChangeEvents.call($this, context || $this); } function range(name, value) { var set$$1 = value !== undefined; if (!set$$1) { if (name === undefined || name == null) name = 'all'; switch (name.toLowerCase()) { case 'minx': return minX; case 'maxx': return maxX; case 'rangex': return { minX: minX, maxX: maxX, rangeX: rangeX }; case 'miny': return minY; case 'maxy': return maxY; case 'rangey': return { minY: minY, maxY: maxY, rangeY: rangeY }; case 'all': default: return { minX: minX, maxX: maxX, rangeX: rangeX, minY: minY, maxY: maxY, rangeY: rangeY }; } } var // changed = false, newMinX = void 0, newMaxX = void 0, newMinY = void 0, newMaxY = void 0; if (name == null) name = 'all'; switch (name.toLowerCase()) { case 'minx': newMinX = value && (value.minX && value.minX | 0 || value | 0) || 0; break; case 'maxx': newMaxX = value && (value.maxX && value.maxX | 0 || value | 0) || 0; break; case 'rangex': newMinX = value && value.minX && value.minX | 0 || 0; newMaxX = value && value.maxX && value.maxX | 0 || 0; break; case 'miny': newMinY = value && (value.minY && value.minY | 0 || value | 0) || 0; break; case 'maxy': newMaxY = value && (value.maxY && value.maxY | 0 || value | 0) || 0; break; case 'rangey': newMinY = value && value.minY && value.minY | 0 || 0; newMaxY = value && value.maxY && value.maxY | 0 || 0; break; case 'all': default: newMinX = value && value.minX && value.minX | 0 || 0; newMaxX = value && value.maxX && value.maxX | 0 || 0; newMinY = value && value.minY && value.minY | 0 || 0; newMaxY = value && value.maxY && value.maxY | 0 || 0; break; } if (newMinX != null && minX !== newMinX) { minX = newMinX; rangeX = maxX - minX; } if (newMaxX != null && maxX !== newMaxX) { maxX = newMaxX; rangeX = maxX - minX; } if (newMinY != null && minY !== newMinY) { minY = newMinY; rangeY = maxY - minY; } if (newMaxY != null && maxY !== newMaxY) { maxY = newMaxY; rangeY = maxY - minY; } } function bind(callback) { if (typeof callback === 'function') changeEvents.push(callback); } function unbind(callback) { if (typeof callback !== 'function') return; var i = void 0; while (i = changeEvents.includes(callback)) { changeEvents.splice(i, 1); } } function destroy() { // unbind all possible events and null objects $(document).unbind('mouseup', mouseUp).unbind('mousemove', mouseMove); bar.unbind('mousedown', mouseDown); bar = null; arrow = null; changeEvents = null; } var offset = void 0, timeout = void 0, x = 0, y = 0, minX = 0, maxX = 100, rangeX = 100, minY = 0, maxY = 100, rangeY = 100, arrow = bar.find('img:first'), // the arrow image to drag changeEvents = []; $.extend(true, $this, // public properties, methods, and event bindings - these we need to access from other controls { val: val, range: range, bind: bind, unbind: unbind, destroy: destroy }); // initialize this control arrow.src = options.arrow && options.arrow.image; arrow.w = options.arrow && options.arrow.width || arrow.width(); arrow.h = options.arrow && options.arrow.height || arrow.height(); bar.w = options.map && options.map.width || bar.width(); bar.h = options.map && options.map.height || bar.height(); // bind mousedown event bar.bind('mousedown', mouseDown); bind.call($this, draw); } // controls for all the input elements for the typing in color values function ColorValuePicker(picker, color, bindedHex, alphaPrecision) { var $this = this; // private properties and methods var inputs = picker.find('td.Text input'); // input box key down - use arrows to alter color function keyDown(e) { if (e.target.value === '' && e.target !== hex.get(0) && (bindedHex != null && e.target !== bindedHex.get(0) || bindedHex == null)) return; if (!validateKey(e)) return e; switch (e.target) { case red.get(0): switch (e.keyCode) { case 38: red.val(setValueInRange.call($this, (red.val() << 0) + 1, 0, 255)); color.val('r', red.val(), e.target); return false; case 40: red.val(setValueInRange.call($this, (red.val() << 0) - 1, 0, 255)); color.val('r', red.val(), e.target); return false; } break; case green.get(0): switch (e.keyCode) { case 38: green.val(setValueInRange.call($this, (green.val() << 0) + 1, 0, 255)); color.val('g', green.val(), e.target); return false; case 40: green.val(setValueInRange.call($this, (green.val() << 0) - 1, 0, 255)); color.val('g', green.val(), e.target); return false; } break; case blue.get(0): switch (e.keyCode) { case 38: blue.val(setValueInRange.call($this, (blue.val() << 0) + 1, 0, 255)); color.val('b', blue.val(), e.target); return false; case 40: blue.val(setValueInRange.call($this, (blue.val() << 0) - 1, 0, 255)); color.val('b', blue.val(), e.target); return false; } break; case alpha && alpha.get(0): switch (e.keyCode) { case 38: alpha.val(setValueInRange.call($this, parseFloat(alpha.val()) + 1, 0, 100)); color.val('a', Math.precision(alpha.val() * 255 / 100, alphaPrecision), e.target); return false; case 40: alpha.val(setValueInRange.call($this, parseFloat(alpha.val()) - 1, 0, 100)); color.val('a', Math.precision(alpha.val() * 255 / 100, alphaPrecision), e.target); return false; } break; case hue.get(0): switch (e.keyCode) { case 38: hue.val(setValueInRange.call($this, (hue.val() << 0) + 1, 0, 360)); color.val('h', hue.val(), e.target); return false; case 40: hue.val(setValueInRange.call($this, (hue.val() << 0) - 1, 0, 360)); color.val('h', hue.val(), e.target); return false; } break; case saturation.get(0): switch (e.keyCode) { case 38: saturation.val(setValueInRange.call($this, (saturation.val() << 0) + 1, 0, 100)); color.val('s', saturation.val(), e.target); return false; case 40: saturation.val(setValueInRange.call($this, (saturation.val() << 0) - 1, 0, 100)); color.val('s', saturation.val(), e.target); return false; } break; case value.get(0): switch (e.keyCode) { case 38: value.val(setValueInRange.call($this, (value.val() << 0) + 1, 0, 100)); color.val('v', value.val(), e.target); return false; case 40: value.val(setValueInRange.call($this, (value.val() << 0) - 1, 0, 100)); color.val('v', value.val(), e.target); return false; } break; } } // input box key up - validate value and set color function keyUp(e) { if (e.target.value === '' && e.target !== hex.get(0) && (bindedHex != null && e.target !== bindedHex.get(0) || bindedHex == null)) return; if (!validateKey(e)) return e; switch (e.target) { case red.get(0): red.val(setValueInRange.call($this, red.val(), 0, 255)); color.val('r', red.val(), e.target); break; case green.get(0): green.val(setValueInRange.call($this, green.val(), 0, 255)); color.val('g', green.val(), e.target); break; case blue.get(0): blue.val(setValueInRange.call($this, blue.val(), 0, 255)); color.val('b', blue.val(), e.target); break; case alpha && alpha.get(0): alpha.val(setValueInRange.call($this, alpha.val(), 0, 100)); color.val('a', Math.precision(alpha.val() * 255 / 100, alphaPrecision), e.target); break; case hue.get(0): hue.val(setValueInRange.call($this, hue.val(), 0, 360)); color.val('h', hue.val(), e.target); break; case saturation.get(0): saturation.val(setValueInRange.call($this, saturation.val(), 0, 100)); color.val('s', saturation.val(), e.target); break; case value.get(0): value.val(setValueInRange.call($this, value.val(), 0, 100)); color.val('v', value.val(), e.target); break; case hex.get(0): hex.val(hex.val().replace(/[^a-fA-F0-9]/g, '').toLowerCase().substring(0, 6)); bindedHex && bindedHex.val(hex.val()); color.val('hex', hex.val() !== '' ? hex.val() : null, e.target); break; case bindedHex && bindedHex.get(0): bindedHex.val(bindedHex.val().replace(/[^a-fA-F0-9]/g, '').toLowerCase().substring(0, 6)); hex.val(bindedHex.val()); color.val('hex', bindedHex.val() !== '' ? bindedHex.val() : null, e.target); break; case ahex && ahex.get(0): ahex.val(ahex.val().replace(/[^a-fA-F0-9]/g, '').toLowerCase().substring(0, 2)); color.val('a', ahex.val() != null ? parseInt(ahex.val(), 16) : null, e.target); break; } } // input box blur - reset to original if value empty function blur(e) { if (color.val() != null) { switch (e.target) { case red.get(0): red.val(color.val('r'));break; case green.get(0): green.val(color.val('g'));break; case blue.get(0): blue.val(color.val('b'));break; case alpha && alpha.get(0): alpha.val(Math.precision(color.val('a') * 100 / 255, alphaPrecision));break; case hue.get(0): hue.val(color.val('h'));break; case saturation.get(0): saturation.val(color.val('s'));break; case value.get(0): value.val(color.val('v'));break; case hex.get(0): case bindedHex && bindedHex.get(0): hex.val(color.val('hex')); bindedHex && bindedHex.val(color.val('hex')); break; case ahex && ahex.get(0): ahex.val(color.val('ahex').substring(6));break; } } } function validateKey(e) { switch (e.keyCode) { case 9: case 16: case 29: case 37: case 39: return false; case 'c'.charCodeAt(): case 'v'.charCodeAt(): if (e.ctrlKey) return false; } return true; } // constrain value within range function setValueInRange(value, min, max) { if (value === '' || isNaN(value)) return min; if (value > max) return max; if (value < min) return min; return value; } function colorChanged(ui, context) { var all = ui.val('all'); if (context !== red.get(0)) red.val(all != null ? all.r : ''); if (context !== green.get(0)) green.val(all != null ? all.g : ''); if (context !== blue.get(0)) blue.val(all != null ? all.b : ''); if (alpha && context !== alpha.get(0)) alpha.val(all != null ? Math.precision(all.a * 100 / 255, alphaPrecision) : ''); if (context !== hue.get(0)) hue.val(all != null ? all.h : ''); if (context !== saturation.get(0)) saturation.val(all != null ? all.s : ''); if (context !== value.get(0)) value.val(all != null ? all.v : ''); if (context !== hex.get(0) && (bindedHex && context !== bindedHex.get(0) || !bindedHex)) hex.val(all != null ? all.hex : ''); if (bindedHex && context !== bindedHex.get(0) && context !== hex.get(0)) bindedHex.val(all != null ? all.hex : ''); if (ahex && context !== ahex.get(0)) ahex.val(all != null ? all.ahex.substring(6) : ''); } function destroy() { // unbind all events and null objects red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).add(hex).add(bindedHex).add(ahex).unbind('keyup', keyUp).unbind('blur', blur); red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).unbind('keydown', keyDown); color.unbind(colorChanged); red = null; green = null; blue = null; alpha = null; hue = null; saturation = null; value = null; hex = null; ahex = null; } var red = inputs.eq(3), green = inputs.eq(4), blue = inputs.eq(5), alpha = inputs.length > 7 ? inputs.eq(6) : null, hue = inputs.eq(0), saturation = inputs.eq(1), value = inputs.eq(2), hex = inputs.eq(inputs.length > 7 ? 7 : 6), ahex = inputs.length > 7 ? inputs.eq(8) : null; $.extend(true, $this, { // public properties and methods destroy: destroy }); red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).add(hex).add(bindedHex).add(ahex).bind('keyup', keyUp).bind('blur', blur); red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).bind('keydown', keyDown); color.bind(colorChanged); } $.jPicker = { List: [], // array holding references to each active instance of the control // color object - we will be able to assign by any color space type or retrieve any color space info // we want this public so we can optionally assign new color objects to initial values using inputs other than a string hex value (also supported) Color: function Color(init) { classCallCheck(this, Color); var $this = this; function fireChangeEvents(context) { for (var i = 0; i < changeEvents.length; i++) { changeEvents[i].call($this, $this, context); } } function val(name, value, context) { // Kind of ugly var set$$1 = Boolean(value); if (set$$1 && value.ahex === '') value.ahex = '00000000'; if (!set$$1) { if (name === undefined || name == null || name === '') name = 'all'; if (r == null) return null; switch (name.toLowerCase()) { case 'ahex': return ColorMethods.rgbaToHex({ r: r, g: g, b: b, a: a }); case 'hex': return val('ahex').substring(0, 6); case 'all': return { r: r, g: g, b: b, a: a, h: h, s: s, v: v, hex: val.call($this, 'hex'), ahex: val.call($this, 'ahex') }; default: var ret = {}; for (var i = 0; i < name.length; i++) { switch (name.charAt(i)) { case 'r': if (name.length === 1) ret = r;else ret.r = r; break; case 'g': if (name.length === 1) ret = g;else ret.g = g; break; case 'b': if (name.length === 1) ret = b;else ret.b = b; break; case 'a': if (name.length === 1) ret = a;else ret.a = a; break; case 'h': if (name.length === 1) ret = h;else ret.h = h; break; case 's': if (name.length === 1) ret = s;else ret.s = s; break; case 'v': if (name.length === 1) ret = v;else ret.v = v; break; } } return !name.length ? val.call($this, 'all') : ret; } } if (context != null && context === $this) return; if (name == null) name = ''; var changed = false; if (value == null) { if (r != null) { r = null; changed = true; } if (g != null) { g = null; changed = true; } if (b != null) { b = null; changed = true; } if (a != null) { a = null; changed = true; } if (h != null) { h = null; changed = true; } if (s != null) { s = null; changed = true; } if (v != null) { v = null; changed = true; } changed && fireChangeEvents.call($this, context || $this); return; } switch (name.toLowerCase()) { case 'ahex': case 'hex': var _ret = ColorMethods.hexToRgba(value && (value.ahex || value.hex) || value || 'none'); val.call($this, 'rgba', { r: _ret.r, g: _ret.g, b: _ret.b, a: name === 'ahex' ? _ret.a : a != null ? a : 255 }, context); break; default: if (value && (value.ahex != null || value.hex != null)) { val.call($this, 'ahex', value.ahex || value.hex || '00000000', context); return; } var newV = {}; var rgb = false, hsv = false; if (value.r !== undefined && !name.includes('r')) name += 'r'; if (value.g !== undefined && !name.includes('g')) name += 'g'; if (value.b !== undefined && !name.includes('b')) name += 'b'; if (value.a !== undefined && !name.includes('a')) name += 'a'; if (value.h !== undefined && !name.includes('h')) name += 'h'; if (value.s !== undefined && !name.includes('s')) name += 's'; if (value.v !== undefined && !name.includes('v')) name += 'v'; for (var _i = 0; _i < name.length; _i++) { switch (name.charAt(_i)) { case 'r': if (hsv) continue; rgb = true; newV.r = value && value.r && value.r | 0 || value && value | 0 || 0; if (newV.r < 0) newV.r = 0;else if (newV.r > 255) newV.r = 255; if (r !== newV.r) { r = newV.r; changed = true; } break; case 'g': if (hsv) continue; rgb = true; newV.g = value && value.g && value.g | 0 || value && value | 0 || 0; if (newV.g < 0) newV.g = 0;else if (newV.g > 255) newV.g = 255; if (g !== newV.g) { g = newV.g; changed = true; } break; case 'b': if (hsv) continue; rgb = true; newV.b = value && value.b && value.b | 0 || value && value | 0 || 0; if (newV.b < 0) newV.b = 0;else if (newV.b > 255) newV.b = 255; if (b !== newV.b) { b = newV.b; changed = true; } break; case 'a': newV.a = value && value.a != null ? value.a | 0 : value != null ? value | 0 : 255; if (newV.a < 0) newV.a = 0;else if (newV.a > 255) newV.a = 255; if (a !== newV.a) { a = newV.a; changed = true; } break; case 'h': if (rgb) continue; hsv = true; newV.h = value && value.h && value.h | 0 || value && value | 0 || 0; if (newV.h < 0) newV.h = 0;else if (newV.h > 360) newV.h = 360; if (h !== newV.h) { h = newV.h; changed = true; } break; case 's': if (rgb) continue; hsv = true; newV.s = value && value.s != null ? value.s | 0 : value != null ? value | 0 : 100; if (newV.s < 0) newV.s = 0;else if (newV.s > 100) newV.s = 100; if (s !== newV.s) { s = newV.s; changed = true; } break; case 'v': if (rgb) continue; hsv = true; newV.v = value && value.v != null ? value.v | 0 : value != null ? value | 0 : 100; if (newV.v < 0) newV.v = 0;else if (newV.v > 100) newV.v = 100; if (v !== newV.v) { v = newV.v; changed = true; } break; } } if (changed) { if (rgb) { r = r || 0; g = g || 0; b = b || 0; var _ret2 = ColorMethods.rgbToHsv({ r: r, g: g, b: b }); h = _ret2.h; s = _ret2.s; v = _ret2.v; } else if (hsv) { h = h || 0; s = s != null ? s : 100; v = v != null ? v : 100; var _ret3 = ColorMethods.hsvToRgb({ h: h, s: s, v: v }); r = _ret3.r; g = _ret3.g; b = _ret3.b; } a = a != null ? a : 255; fireChangeEvents.call($this, context || $this); } break; } } function bind(callback) { if (typeof callback === 'function') changeEvents.push(callback); } function unbind(callback) { if (typeof callback !== 'function') return; var i = void 0; while (i = changeEvents.includes(callback)) { changeEvents.splice(i, 1); } } function destroy() { changeEvents = null; } var r = void 0, g = void 0, b = void 0, a = void 0, h = void 0, s = void 0, v = void 0, changeEvents = []; $.extend(true, $this, { // public properties and methods val: val, bind: bind, unbind: unbind, destroy: destroy }); if (init) { if (init.ahex != null) { val('ahex', init); } else if (init.hex != null) { val((init.a != null ? 'a' : '') + 'hex', init.a != null ? { ahex: init.hex + ColorMethods.intToHex(init.a) } : init); } else if (init.r != null && init.g != null && init.b != null) { val('rgb' + (init.a != null ? 'a' : ''), init); } else if (init.h != null && init.s != null && init.v != null) { val('hsv' + (init.a != null ? 'a' : ''), init); } } }, // color conversion methods - make public to give use to external scripts ColorMethods: { hexToRgba: function hexToRgba(hex) { if (hex === '' || hex === 'none') return { r: null, g: null, b: null, a: null }; hex = this.validateHex(hex); var r = '00', g = '00', b = '00', a = '255'; if (hex.length === 6) hex += 'ff'; if (hex.length > 6) { r = hex.substring(0, 2); g = hex.substring(2, 4); b = hex.substring(4, 6); a = hex.substring(6, hex.length); } else { if (hex.length > 4) { r = hex.substring(4, hex.length); hex = hex.substring(0, 4); } if (hex.length > 2) { g = hex.substring(2, hex.length); hex = hex.substring(0, 2); } if (hex.length > 0) b = hex.substring(0, hex.length); } return { r: this.hexToInt(r), g: this.hexToInt(g), b: this.hexToInt(b), a: this.hexToInt(a) }; }, validateHex: function validateHex(hex) { // if (typeof hex === 'object') return ''; hex = hex.toLowerCase().replace(/[^a-f0-9]/g, ''); if (hex.length > 8) hex = hex.substring(0, 8); return hex; }, rgbaToHex: function rgbaToHex(rgba) { return this.intToHex(rgba.r) + this.intToHex(rgba.g) + this.intToHex(rgba.b) + this.intToHex(rgba.a); }, intToHex: function intToHex(dec) { var result = (dec | 0).toString(16); if (result.length === 1) result = '0' + result; return result.toLowerCase(); }, hexToInt: function hexToInt(hex) { return parseInt(hex, 16); }, rgbToHsv: function rgbToHsv(rgb) { var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255, hsv = { h: 0, s: 0, v: 0 }; var min = 0, max = 0; if (r >= g && r >= b) { max = r; min = g > b ? b : g; } else if (g >= b && g >= r) { max = g; min = r > b ? b : r; } else { max = b; min = g > r ? r : g; } hsv.v = max; hsv.s = max ? (max - min) / max : 0; var delta = void 0; if (!hsv.s) hsv.h = 0;else { delta = max - min; if (r === max) hsv.h = (g - b) / delta;else if (g === max) hsv.h = 2 + (b - r) / delta;else hsv.h = 4 + (r - g) / delta; hsv.h = parseInt(hsv.h * 60); if (hsv.h < 0) hsv.h += 360; } hsv.s = hsv.s * 100 | 0; hsv.v = hsv.v * 100 | 0; return hsv; }, hsvToRgb: function hsvToRgb(hsv) { var rgb = { r: 0, g: 0, b: 0, a: 100 }; var h = hsv.h, s = hsv.s, v = hsv.v; if (s === 0) { if (v === 0) rgb.r = rgb.g = rgb.b = 0;else rgb.r = rgb.g = rgb.b = v * 255 / 100 | 0; } else { if (h === 360) h = 0; h /= 60; s = s / 100; v = v / 100; var i = h | 0, f = h - i, p = v * (1 - s), q = v * (1 - s * f), t = v * (1 - s * (1 - f)); switch (i) { case 0: rgb.r = v; rgb.g = t; rgb.b = p; break; case 1: rgb.r = q; rgb.g = v; rgb.b = p; break; case 2: rgb.r = p; rgb.g = v; rgb.b = t; break; case 3: rgb.r = p; rgb.g = q; rgb.b = v; break; case 4: rgb.r = t; rgb.g = p; rgb.b = v; break; case 5: rgb.r = v; rgb.g = p; rgb.b = q; break; } rgb.r = rgb.r * 255 | 0; rgb.g = rgb.g * 255 | 0; rgb.b = rgb.b * 255 | 0; } return rgb; } } }; var _$$jPicker = $.jPicker, Color = _$$jPicker.Color, List = _$$jPicker.List, ColorMethods = _$$jPicker.ColorMethods; // local copies for YUI compressor $.fn.jPicker = function (options) { var $arguments = arguments; return this.each(function () { var $this = this, settings = $.extend(true, {}, $.fn.jPicker.defaults, options); // local copies for YUI compressor if ($($this).get(0).nodeName.toLowerCase() === 'input') { // Add color picker icon if binding to an input element and bind the events to the input $.extend(true, settings, { window: { bindToInput: true, expandable: true, input: $($this) } }); if ($($this).val() === '') { settings.color.active = new Color({ hex: null }); settings.color.current = new Color({ hex: null }); } else if (ColorMethods.validateHex($($this).val())) { settings.color.active = new Color({ hex: $($this).val(), a: settings.color.active.val('a') }); settings.color.current = new Color({ hex: $($this).val(), a: settings.color.active.val('a') }); } } if (settings.window.expandable) { $($this).after('    '); } else { settings.window.liveUpdate = false; // Basic control binding for inline use - You will need to override the liveCallback or commitCallback function to retrieve results } var isLessThanIE7 = parseFloat(navigator.appVersion.split('MSIE')[1]) < 7 && document.body.filters; // needed to run the AlphaImageLoader function for IE6 // set color mode and update visuals for the new color mode function setColorMode(colorMode) { var active = color.active, hex = active.val('hex'); var rgbMap = void 0, rgbBar = void 0; settings.color.mode = colorMode; switch (colorMode) { case 'h': setTimeout(function () { setBG.call($this, colorMapDiv, 'transparent'); setImgLoc.call($this, colorMapL1, 0); setAlpha.call($this, colorMapL1, 100); setImgLoc.call($this, colorMapL2, 260); setAlpha.call($this, colorMapL2, 100); setBG.call($this, colorBarDiv, 'transparent'); setImgLoc.call($this, colorBarL1, 0); setAlpha.call($this, colorBarL1, 100); setImgLoc.call($this, colorBarL2, 260); setAlpha.call($this, colorBarL2, 100); setImgLoc.call($this, colorBarL3, 260); setAlpha.call($this, colorBarL3, 100); setImgLoc.call($this, colorBarL4, 260); setAlpha.call($this, colorBarL4, 100); setImgLoc.call($this, colorBarL6, 260); setAlpha.call($this, colorBarL6, 100); }, 0); colorMap.range('all', { minX: 0, maxX: 100, minY: 0, maxY: 100 }); colorBar.range('rangeY', { minY: 0, maxY: 360 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('s'), y: 100 - active.val('v') }, colorMap); colorBar.val('y', 360 - active.val('h'), colorBar); break; case 's': setTimeout(function () { setBG.call($this, colorMapDiv, 'transparent'); setImgLoc.call($this, colorMapL1, -260); setImgLoc.call($this, colorMapL2, -520); setImgLoc.call($this, colorBarL1, -260); setImgLoc.call($this, colorBarL2, -520); setImgLoc.call($this, colorBarL6, 260); setAlpha.call($this, colorBarL6, 100); }, 0); colorMap.range('all', { minX: 0, maxX: 360, minY: 0, maxY: 100 }); colorBar.range('rangeY', { minY: 0, maxY: 100 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('h'), y: 100 - active.val('v') }, colorMap); colorBar.val('y', 100 - active.val('s'), colorBar); break; case 'v': setTimeout(function () { setBG.call($this, colorMapDiv, '000000'); setImgLoc.call($this, colorMapL1, -780); setImgLoc.call($this, colorMapL2, 260); setBG.call($this, colorBarDiv, hex); setImgLoc.call($this, colorBarL1, -520); setImgLoc.call($this, colorBarL2, 260); setAlpha.call($this, colorBarL2, 100); setImgLoc.call($this, colorBarL6, 260); setAlpha.call($this, colorBarL6, 100); }, 0); colorMap.range('all', { minX: 0, maxX: 360, minY: 0, maxY: 100 }); colorBar.range('rangeY', { minY: 0, maxY: 100 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('h'), y: 100 - active.val('s') }, colorMap); colorBar.val('y', 100 - active.val('v'), colorBar); break; case 'r': rgbMap = -1040; rgbBar = -780; colorMap.range('all', { minX: 0, maxX: 255, minY: 0, maxY: 255 }); colorBar.range('rangeY', { minY: 0, maxY: 255 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('b'), y: 255 - active.val('g') }, colorMap); colorBar.val('y', 255 - active.val('r'), colorBar); break; case 'g': rgbMap = -1560; rgbBar = -1820; colorMap.range('all', { minX: 0, maxX: 255, minY: 0, maxY: 255 }); colorBar.range('rangeY', { minY: 0, maxY: 255 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('b'), y: 255 - active.val('r') }, colorMap); colorBar.val('y', 255 - active.val('g'), colorBar); break; case 'b': rgbMap = -2080; rgbBar = -2860; colorMap.range('all', { minX: 0, maxX: 255, minY: 0, maxY: 255 }); colorBar.range('rangeY', { minY: 0, maxY: 255 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('r'), y: 255 - active.val('g') }, colorMap); colorBar.val('y', 255 - active.val('b'), colorBar); break; case 'a': setTimeout(function () { setBG.call($this, colorMapDiv, 'transparent'); setImgLoc.call($this, colorMapL1, -260); setImgLoc.call($this, colorMapL2, -520); setImgLoc.call($this, colorBarL1, 260); setImgLoc.call($this, colorBarL2, 260); setAlpha.call($this, colorBarL2, 100); setImgLoc.call($this, colorBarL6, 0); setAlpha.call($this, colorBarL6, 100); }, 0); colorMap.range('all', { minX: 0, maxX: 360, minY: 0, maxY: 100 }); colorBar.range('rangeY', { minY: 0, maxY: 255 }); if (active.val('ahex') == null) break; colorMap.val('xy', { x: active.val('h'), y: 100 - active.val('v') }, colorMap); colorBar.val('y', 255 - active.val('a'), colorBar); break; default: throw new Error('Invalid Mode'); } switch (colorMode) { case 'h': break; case 's': case 'v': case 'a': setTimeout(function () { setAlpha.call($this, colorMapL1, 100); setAlpha.call($this, colorBarL1, 100); setImgLoc.call($this, colorBarL3, 260); setAlpha.call($this, colorBarL3, 100); setImgLoc.call($this, colorBarL4, 260); setAlpha.call($this, colorBarL4, 100); }, 0); break; case 'r': case 'g': case 'b': setTimeout(function () { setBG.call($this, colorMapDiv, 'transparent'); setBG.call($this, colorBarDiv, 'transparent'); setAlpha.call($this, colorBarL1, 100); setAlpha.call($this, colorMapL1, 100); setImgLoc.call($this, colorMapL1, rgbMap); setImgLoc.call($this, colorMapL2, rgbMap - 260); setImgLoc.call($this, colorBarL1, rgbBar - 780); setImgLoc.call($this, colorBarL2, rgbBar - 520); setImgLoc.call($this, colorBarL3, rgbBar); setImgLoc.call($this, colorBarL4, rgbBar - 260); setImgLoc.call($this, colorBarL6, 260); setAlpha.call($this, colorBarL6, 100); }, 0); break; } if (active.val('ahex') == null) return; activeColorChanged.call($this, active); } // Update color when user changes text values function activeColorChanged(ui, context) { if (context == null || context !== colorBar && context !== colorMap) positionMapAndBarArrows.call($this, ui, context); setTimeout(function () { updatePreview.call($this, ui); updateMapVisuals.call($this, ui); updateBarVisuals.call($this, ui); }, 0); } // user has dragged the ColorMap pointer function mapValueChanged(ui, context) { var active = color.active; if (context !== colorMap && active.val() == null) return; var xy = ui.val('all'); switch (settings.color.mode) { case 'h': active.val('sv', { s: xy.x, v: 100 - xy.y }, context); break; case 's': case 'a': active.val('hv', { h: xy.x, v: 100 - xy.y }, context); break; case 'v': active.val('hs', { h: xy.x, s: 100 - xy.y }, context); break; case 'r': active.val('gb', { g: 255 - xy.y, b: xy.x }, context); break; case 'g': active.val('rb', { r: 255 - xy.y, b: xy.x }, context); break; case 'b': active.val('rg', { r: xy.x, g: 255 - xy.y }, context); break; } } // user has dragged the ColorBar slider function colorBarValueChanged(ui, context) { var active = color.active; if (context !== colorBar && active.val() == null) return; switch (settings.color.mode) { case 'h': active.val('h', { h: 360 - ui.val('y') }, context); break; case 's': active.val('s', { s: 100 - ui.val('y') }, context); break; case 'v': active.val('v', { v: 100 - ui.val('y') }, context); break; case 'r': active.val('r', { r: 255 - ui.val('y') }, context); break; case 'g': active.val('g', { g: 255 - ui.val('y') }, context); break; case 'b': active.val('b', { b: 255 - ui.val('y') }, context); break; case 'a': active.val('a', 255 - ui.val('y'), context); break; } } // position map and bar arrows to match current color function positionMapAndBarArrows(ui, context) { if (context !== colorMap) { switch (settings.color.mode) { case 'h': var sv = ui.val('sv'); colorMap.val('xy', { x: sv != null ? sv.s : 100, y: 100 - (sv != null ? sv.v : 100) }, context); break; case 's': case 'a': var hv = ui.val('hv'); colorMap.val('xy', { x: hv && hv.h || 0, y: 100 - (hv != null ? hv.v : 100) }, context); break; case 'v': var hs = ui.val('hs'); colorMap.val('xy', { x: hs && hs.h || 0, y: 100 - (hs != null ? hs.s : 100) }, context); break; case 'r': var bg = ui.val('bg'); colorMap.val('xy', { x: bg && bg.b || 0, y: 255 - (bg && bg.g || 0) }, context); break; case 'g': var br = ui.val('br'); colorMap.val('xy', { x: br && br.b || 0, y: 255 - (br && br.r || 0) }, context); break; case 'b': var rg = ui.val('rg'); colorMap.val('xy', { x: rg && rg.r || 0, y: 255 - (rg && rg.g || 0) }, context); break; } } if (context !== colorBar) { switch (settings.color.mode) { case 'h': colorBar.val('y', 360 - (ui.val('h') || 0), context); break; case 's': var _s = ui.val('s'); colorBar.val('y', 100 - (_s != null ? _s : 100), context); break; case 'v': var _v = ui.val('v'); colorBar.val('y', 100 - (_v != null ? _v : 100), context); break; case 'r': colorBar.val('y', 255 - (ui.val('r') || 0), context); break; case 'g': colorBar.val('y', 255 - (ui.val('g') || 0), context); break; case 'b': colorBar.val('y', 255 - (ui.val('b') || 0), context); break; case 'a': var _a = ui.val('a'); colorBar.val('y', 255 - (_a != null ? _a : 255), context); break; } } } function updatePreview(ui) { try { var all = ui.val('all'); activePreview.css({ backgroundColor: all && '#' + all.hex || 'transparent' }); setAlpha.call($this, activePreview, all && Math.precision(all.a * 100 / 255, 4) || 0); } catch (e) {} } function updateMapVisuals(ui) { switch (settings.color.mode) { case 'h': setBG.call($this, colorMapDiv, new Color({ h: ui.val('h') || 0, s: 100, v: 100 }).val('hex')); break; case 's': case 'a': var _s2 = ui.val('s'); setAlpha.call($this, colorMapL2, 100 - (_s2 != null ? _s2 : 100)); break; case 'v': var _v2 = ui.val('v'); setAlpha.call($this, colorMapL1, _v2 != null ? _v2 : 100); break; case 'r': setAlpha.call($this, colorMapL2, Math.precision((ui.val('r') || 0) / 255 * 100, 4)); break; case 'g': setAlpha.call($this, colorMapL2, Math.precision((ui.val('g') || 0) / 255 * 100, 4)); break; case 'b': setAlpha.call($this, colorMapL2, Math.precision((ui.val('b') || 0) / 255 * 100)); break; } var a = ui.val('a'); setAlpha.call($this, colorMapL3, Math.precision((255 - (a || 0)) * 100 / 255, 4)); } function updateBarVisuals(ui) { switch (settings.color.mode) { case 'h': var _a2 = ui.val('a'); setAlpha.call($this, colorBarL5, Math.precision((255 - (_a2 || 0)) * 100 / 255, 4)); break; case 's': var hva = ui.val('hva'), saturatedColor = new Color({ h: hva && hva.h || 0, s: 100, v: hva != null ? hva.v : 100 }); setBG.call($this, colorBarDiv, saturatedColor.val('hex')); setAlpha.call($this, colorBarL2, 100 - (hva != null ? hva.v : 100)); setAlpha.call($this, colorBarL5, Math.precision((255 - (hva && hva.a || 0)) * 100 / 255, 4)); break; case 'v': var hsa = ui.val('hsa'), valueColor = new Color({ h: hsa && hsa.h || 0, s: hsa != null ? hsa.s : 100, v: 100 }); setBG.call($this, colorBarDiv, valueColor.val('hex')); setAlpha.call($this, colorBarL5, Math.precision((255 - (hsa && hsa.a || 0)) * 100 / 255, 4)); break; case 'r': case 'g': case 'b': var rgba = ui.val('rgba'); var hValue = 0, vValue = 0; if (settings.color.mode === 'r') { hValue = rgba && rgba.b || 0; vValue = rgba && rgba.g || 0; } else if (settings.color.mode === 'g') { hValue = rgba && rgba.b || 0; vValue = rgba && rgba.r || 0; } else if (settings.color.mode === 'b') { hValue = rgba && rgba.r || 0; vValue = rgba && rgba.g || 0; } var middle = vValue > hValue ? hValue : vValue; setAlpha.call($this, colorBarL2, hValue > vValue ? Math.precision((hValue - vValue) / (255 - vValue) * 100, 4) : 0); setAlpha.call($this, colorBarL3, vValue > hValue ? Math.precision((vValue - hValue) / (255 - hValue) * 100, 4) : 0); setAlpha.call($this, colorBarL4, Math.precision(middle / 255 * 100, 4)); setAlpha.call($this, colorBarL5, Math.precision((255 - (rgba && rgba.a || 0)) * 100 / 255, 4)); break; case 'a': { var _a3 = ui.val('a'); setBG.call($this, colorBarDiv, ui.val('hex') || '000000'); setAlpha.call($this, colorBarL5, _a3 != null ? 0 : 100); setAlpha.call($this, colorBarL6, _a3 != null ? 100 : 0); break; } } } function setBG(el, c) { el.css({ backgroundColor: c && c.length === 6 && '#' + c || 'transparent' }); } function setImg(img, src) { if (isLessThanIE7 && (src.includes('AlphaBar.png') || src.includes('Bars.png') || src.includes('Maps.png'))) { img.attr('pngSrc', src); img.css({ backgroundImage: 'none', filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + '\', sizingMethod=\'scale\')' }); } else img.css({ backgroundImage: 'url(\'' + src + '\')' }); } function setImgLoc(img, y) { img.css({ top: y + 'px' }); } function setAlpha(obj, alpha) { obj.css({ visibility: alpha > 0 ? 'visible' : 'hidden' }); if (alpha > 0 && alpha < 100) { if (isLessThanIE7) { var src = obj.attr('pngSrc'); if (src != null && (src.includes('AlphaBar.png') || src.includes('Bars.png') || src.includes('Maps.png'))) { obj.css({ filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + '\', sizingMethod=\'scale\') progid:DXImageTransform.Microsoft.Alpha(opacity=' + alpha + ')' }); } else obj.css({ opacity: Math.precision(alpha / 100, 4) }); } else obj.css({ opacity: Math.precision(alpha / 100, 4) }); } else if (alpha === 0 || alpha === 100) { if (isLessThanIE7) { var _src = obj.attr('pngSrc'); if (_src != null && (_src.includes('AlphaBar.png') || _src.includes('Bars.png') || _src.includes('Maps.png'))) { obj.css({ filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + _src + '\', sizingMethod=\'scale\')' }); } else obj.css({ opacity: '' }); } else obj.css({ opacity: '' }); } } // revert color to original color when opened function revertColor() { color.active.val('ahex', color.current.val('ahex')); } // commit the color changes function commitColor() { color.current.val('ahex', color.active.val('ahex')); } function radioClicked(e) { $(this).parents('tbody:first').find('input:radio[value!="' + e.target.value + '"]').removeAttr('checked'); setColorMode.call($this, e.target.value); } function currentClicked() { revertColor.call($this); } function cancelClicked() { revertColor.call($this); settings.window.expandable && hide.call($this); typeof cancelCallback === 'function' && cancelCallback.call($this, color.active, cancelButton); } function okClicked() { commitColor.call($this); settings.window.expandable && hide.call($this); typeof commitCallback === 'function' && commitCallback.call($this, color.active, okButton); } function iconImageClicked() { show.call($this); } function currentColorChanged(ui, context) { var hex = ui.val('hex'); currentPreview.css({ backgroundColor: hex && '#' + hex || 'transparent' }); setAlpha.call($this, currentPreview, Math.precision((ui.val('a') || 0) * 100 / 255, 4)); } function expandableColorChanged(ui, context) { var hex = ui.val('hex'); var va = ui.val('va'); iconColor.css({ backgroundColor: hex && '#' + hex || 'transparent' }); setAlpha.call($this, iconAlpha, Math.precision((255 - (va && va.a || 0)) * 100 / 255, 4)); if (settings.window.bindToInput && settings.window.updateInputColor) { settings.window.input.css({ backgroundColor: hex && '#' + hex || 'transparent', color: va == null || va.v > 75 ? '#000000' : '#ffffff' }); } } function moveBarMouseDown(e) { // const {element} = settings.window, // local copies for YUI compressor // {page} = settings.window; elementStartX = parseInt(container.css('left')); elementStartY = parseInt(container.css('top')); pageStartX = e.pageX; pageStartY = e.pageY; // bind events to document to move window - we will unbind these on mouseup $(document).bind('mousemove', documentMouseMove).bind('mouseup', documentMouseUp); e.preventDefault(); // prevent attempted dragging of the column } function documentMouseMove(e) { container.css({ left: elementStartX - (pageStartX - e.pageX) + 'px', top: elementStartY - (pageStartY - e.pageY) + 'px' }); if (settings.window.expandable && !$.support.boxModel) container.prev().css({ left: container.css('left'), top: container.css('top') }); e.stopPropagation(); e.preventDefault(); return false; } function documentMouseUp(e) { $(document).unbind('mousemove', documentMouseMove).unbind('mouseup', documentMouseUp); e.stopPropagation(); e.preventDefault(); return false; } function quickPickClicked(e) { e.preventDefault(); e.stopPropagation(); color.active.val('ahex', $(this).attr('title') || null, e.target); return false; } function show() { color.current.val('ahex', color.active.val('ahex')); function attachIFrame() { if (!settings.window.expandable || $.support.boxModel) return; var table = container.find('table:first'); container.before('