websch/dist/all.js

23964 lines
950 KiB
JavaScript
Raw Normal View History

2020-08-02 10:26:22 +00:00
/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;
// 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() { "use strict";
if (!("SVGPathSeg" in window)) {
// Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
window.SVGPathSeg = function(type, typeAsLetter, owningPathSegList) {
this.pathSegType = type;
this.pathSegTypeAsLetter = typeAsLetter;
this._owningPathSegList = owningPathSegList;
}
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;
// Notify owning PathSegList on any changes so they can be synchronized back to the path element.
SVGPathSeg.prototype._segmentChanged = function() {
if (this._owningPathSegList)
this._owningPathSegList.segmentChanged(this);
}
window.SVGPathSegClosePath = function(owningPathSegList) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CLOSEPATH, "z", owningPathSegList);
}
SVGPathSegClosePath.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegClosePath.prototype.toString = function() { return "[object SVGPathSegClosePath]"; }
SVGPathSegClosePath.prototype._asPathString = function() { return this.pathSegTypeAsLetter; }
SVGPathSegClosePath.prototype.clone = function() { return new SVGPathSegClosePath(undefined); }
window.SVGPathSegMovetoAbs = function(owningPathSegList, x, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_MOVETO_ABS, "M", owningPathSegList);
this._x = x;
this._y = y;
}
SVGPathSegMovetoAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegMovetoAbs.prototype.toString = function() { return "[object SVGPathSegMovetoAbs]"; }
SVGPathSegMovetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; }
SVGPathSegMovetoAbs.prototype.clone = function() { return new SVGPathSegMovetoAbs(undefined, this._x, this._y); }
Object.defineProperty(SVGPathSegMovetoAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegMovetoAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegMovetoRel = function(owningPathSegList, x, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_MOVETO_REL, "m", owningPathSegList);
this._x = x;
this._y = y;
}
SVGPathSegMovetoRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegMovetoRel.prototype.toString = function() { return "[object SVGPathSegMovetoRel]"; }
SVGPathSegMovetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; }
SVGPathSegMovetoRel.prototype.clone = function() { return new SVGPathSegMovetoRel(undefined, this._x, this._y); }
Object.defineProperty(SVGPathSegMovetoRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegMovetoRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegLinetoAbs = function(owningPathSegList, x, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_ABS, "L", owningPathSegList);
this._x = x;
this._y = y;
}
SVGPathSegLinetoAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegLinetoAbs.prototype.toString = function() { return "[object SVGPathSegLinetoAbs]"; }
SVGPathSegLinetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; }
SVGPathSegLinetoAbs.prototype.clone = function() { return new SVGPathSegLinetoAbs(undefined, this._x, this._y); }
Object.defineProperty(SVGPathSegLinetoAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegLinetoAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegLinetoRel = function(owningPathSegList, x, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_REL, "l", owningPathSegList);
this._x = x;
this._y = y;
}
SVGPathSegLinetoRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegLinetoRel.prototype.toString = function() { return "[object SVGPathSegLinetoRel]"; }
SVGPathSegLinetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; }
SVGPathSegLinetoRel.prototype.clone = function() { return new SVGPathSegLinetoRel(undefined, this._x, this._y); }
Object.defineProperty(SVGPathSegLinetoRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegLinetoRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoCubicAbs = function(owningPathSegList, x, y, x1, y1, x2, y2) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, "C", owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
this._x2 = x2;
this._y2 = y2;
}
SVGPathSegCurvetoCubicAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoCubicAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicAbs]"; }
SVGPathSegCurvetoCubicAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; }
SVGPathSegCurvetoCubicAbs.prototype.clone = function() { return new SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoCubicRel = function(owningPathSegList, x, y, x1, y1, x2, y2) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, "c", owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
this._x2 = x2;
this._y2 = y2;
}
SVGPathSegCurvetoCubicRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoCubicRel.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicRel]"; }
SVGPathSegCurvetoCubicRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; }
SVGPathSegCurvetoCubicRel.prototype.clone = function() { return new SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoQuadraticAbs = function(owningPathSegList, x, y, x1, y1) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, "Q", owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
}
SVGPathSegCurvetoQuadraticAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoQuadraticAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticAbs]"; }
SVGPathSegCurvetoQuadraticAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x + " " + this._y; }
SVGPathSegCurvetoQuadraticAbs.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1); }
Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoQuadraticRel = function(owningPathSegList, x, y, x1, y1) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, "q", owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
}
SVGPathSegCurvetoQuadraticRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoQuadraticRel.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticRel]"; }
SVGPathSegCurvetoQuadraticRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x + " " + this._y; }
SVGPathSegCurvetoQuadraticRel.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1); }
Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegArcAbs = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_ARC_ABS, "A", owningPathSegList);
this._x = x;
this._y = y;
this._r1 = r1;
this._r2 = r2;
this._angle = angle;
this._largeArcFlag = largeArcFlag;
this._sweepFlag = sweepFlag;
}
SVGPathSegArcAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegArcAbs.prototype.toString = function() { return "[object SVGPathSegArcAbs]"; }
SVGPathSegArcAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._r1 + " " + this._r2 + " " + this._angle + " " + (this._largeArcFlag ? "1" : "0") + " " + (this._sweepFlag ? "1" : "0") + " " + this._x + " " + this._y; }
SVGPathSegArcAbs.prototype.clone = function() { return new SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
Object.defineProperty(SVGPathSegArcAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcAbs.prototype, "r1", { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcAbs.prototype, "r2", { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcAbs.prototype, "angle", { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcAbs.prototype, "largeArcFlag", { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcAbs.prototype, "sweepFlag", { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegArcRel = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_ARC_REL, "a", owningPathSegList);
this._x = x;
this._y = y;
this._r1 = r1;
this._r2 = r2;
this._angle = angle;
this._largeArcFlag = largeArcFlag;
this._sweepFlag = sweepFlag;
}
SVGPathSegArcRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegArcRel.prototype.toString = function() { return "[object SVGPathSegArcRel]"; }
SVGPathSegArcRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._r1 + " " + this._r2 + " " + this._angle + " " + (this._largeArcFlag ? "1" : "0") + " " + (this._sweepFlag ? "1" : "0") + " " + this._x + " " + this._y; }
SVGPathSegArcRel.prototype.clone = function() { return new SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
Object.defineProperty(SVGPathSegArcRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcRel.prototype, "r1", { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcRel.prototype, "r2", { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcRel.prototype, "angle", { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcRel.prototype, "largeArcFlag", { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegArcRel.prototype, "sweepFlag", { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegLinetoHorizontalAbs = function(owningPathSegList, x) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, "H", owningPathSegList);
this._x = x;
}
SVGPathSegLinetoHorizontalAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegLinetoHorizontalAbs.prototype.toString = function() { return "[object SVGPathSegLinetoHorizontalAbs]"; }
SVGPathSegLinetoHorizontalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x; }
SVGPathSegLinetoHorizontalAbs.prototype.clone = function() { return new SVGPathSegLinetoHorizontalAbs(undefined, this._x); }
Object.defineProperty(SVGPathSegLinetoHorizontalAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegLinetoHorizontalRel = function(owningPathSegList, x) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, "h", owningPathSegList);
this._x = x;
}
SVGPathSegLinetoHorizontalRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegLinetoHorizontalRel.prototype.toString = function() { return "[object SVGPathSegLinetoHorizontalRel]"; }
SVGPathSegLinetoHorizontalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x; }
SVGPathSegLinetoHorizontalRel.prototype.clone = function() { return new SVGPathSegLinetoHorizontalRel(undefined, this._x); }
Object.defineProperty(SVGPathSegLinetoHorizontalRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegLinetoVerticalAbs = function(owningPathSegList, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, "V", owningPathSegList);
this._y = y;
}
SVGPathSegLinetoVerticalAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegLinetoVerticalAbs.prototype.toString = function() { return "[object SVGPathSegLinetoVerticalAbs]"; }
SVGPathSegLinetoVerticalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._y; }
SVGPathSegLinetoVerticalAbs.prototype.clone = function() { return new SVGPathSegLinetoVerticalAbs(undefined, this._y); }
Object.defineProperty(SVGPathSegLinetoVerticalAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegLinetoVerticalRel = function(owningPathSegList, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, "v", owningPathSegList);
this._y = y;
}
SVGPathSegLinetoVerticalRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegLinetoVerticalRel.prototype.toString = function() { return "[object SVGPathSegLinetoVerticalRel]"; }
SVGPathSegLinetoVerticalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._y; }
SVGPathSegLinetoVerticalRel.prototype.clone = function() { return new SVGPathSegLinetoVerticalRel(undefined, this._y); }
Object.defineProperty(SVGPathSegLinetoVerticalRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoCubicSmoothAbs = function(owningPathSegList, x, y, x2, y2) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, "S", owningPathSegList);
this._x = x;
this._y = y;
this._x2 = x2;
this._y2 = y2;
}
SVGPathSegCurvetoCubicSmoothAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoCubicSmoothAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicSmoothAbs]"; }
SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; }
SVGPathSegCurvetoCubicSmoothAbs.prototype.clone = function() { return new SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2); }
Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoCubicSmoothRel = function(owningPathSegList, x, y, x2, y2) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, "s", owningPathSegList);
this._x = x;
this._y = y;
this._x2 = x2;
this._y2 = y2;
}
SVGPathSegCurvetoCubicSmoothRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoCubicSmoothRel.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicSmoothRel]"; }
SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; }
SVGPathSegCurvetoCubicSmoothRel.prototype.clone = function() { return new SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2); }
Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoQuadraticSmoothAbs = function(owningPathSegList, x, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, "T", owningPathSegList);
this._x = x;
this._y = y;
}
SVGPathSegCurvetoQuadraticSmoothAbs.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticSmoothAbs]"; }
SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; }
SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y); }
Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
window.SVGPathSegCurvetoQuadraticSmoothRel = function(owningPathSegList, x, y) {
SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, "t", owningPathSegList);
this._x = x;
this._y = y;
}
SVGPathSegCurvetoQuadraticSmoothRel.prototype = Object.create(SVGPathSeg.prototype);
SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticSmoothRel]"; }
SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; }
SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y); }
Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
// Add createSVGPathSeg* functions to SVGPathElement.
// Spec: http://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 (!("SVGPathSegList" in window)) {
// Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList
window.SVGPathSegList = function(pathElement) {
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);
}
Object.defineProperty(SVGPathSegList.prototype, "numberOfItems", {
get: function() {
this._checkPathSynchronizedToList();
return this._list.length;
},
enumerable: true
});
// Add the pathSegList accessors to SVGPathElement.
// Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData
Object.defineProperty(SVGPathElement.prototype, "pathSegList", {
get: function() {
if (!this._pathSegList)
this._pathSegList = new SVGPathSegList(this);
return this._pathSegList;
},
enumerable: true
});
// FIXME: The following are not implemented and simply return SVGPathElement.pathSegList.
Object.defineProperty(SVGPathElement.prototype, "normalizedPathSegList", { get: function() { return this.pathSegList; }, enumerable: true });
Object.defineProperty(SVGPathElement.prototype, "animatedPathSegList", { get: function() { return this.pathSegList; }, enumerable: true });
Object.defineProperty(SVGPathElement.prototype, "animatedNormalizedPathSegList", { get: function() { return this.pathSegList; }, enumerable: true });
// 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.
SVGPathSegList.prototype._checkPathSynchronizedToList = function() {
this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());
}
SVGPathSegList.prototype._updateListFromPathMutations = function(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.
SVGPathSegList.prototype._writeListToPath = function() {
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.
SVGPathSegList.prototype.segmentChanged = function(pathSeg) {
this._writeListToPath();
}
SVGPathSegList.prototype.clear = function() {
this._checkPathSynchronizedToList();
this._list.forEach(function(pathSeg) {
pathSeg._owningPathSegList = null;
});
this._list = [];
this._writeListToPath();
}
SVGPathSegList.prototype.initialize = function(newItem) {
this._checkPathSynchronizedToList();
this._list = [newItem];
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
SVGPathSegList.prototype._checkValidIndex = function(index) {
if (isNaN(index) || index < 0 || index >= this.numberOfItems)
throw "INDEX_SIZE_ERR";
}
SVGPathSegList.prototype.getItem = function(index) {
this._checkPathSynchronizedToList();
this._checkValidIndex(index);
return this._list[index];
}
SVGPathSegList.prototype.insertItemBefore = function(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;
}
SVGPathSegList.prototype.replaceItem = function(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;
}
SVGPathSegList.prototype.removeItem = function(index) {
this._checkPathSynchronizedToList();
this._checkValidIndex(index);
var item = this._list[index];
this._list.splice(index, 1);
this._writeListToPath();
return item;
}
SVGPathSegList.prototype.appendItem = function(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;
}
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;
}
// This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp.
SVGPathSegList.prototype._parsePath = function(string) {
if (!string || string.length == 0)
return [];
var owningPathSegList = this;
var Builder = function() {
this.pathSegList = [];
}
Builder.prototype.appendSegment = function(pathSeg) {
this.pathSegList.push(pathSeg);
}
var Source = function(string) {
this._string = string;
this._currentIndex = 0;
this._endIndex = this._string.length;
this._previousCommand = SVGPathSeg.PATHSEG_UNKNOWN;
this._skipOptionalSpaces();
}
Source.prototype._isCurrentSpace = function() {
var character = this._string[this._currentIndex];
return character <= " " && (character == " " || character == "\n" || character == "\t" || character == "\r" || character == "\f");
}
Source.prototype._skipOptionalSpaces = function() {
while (this._currentIndex < this._endIndex && this._isCurrentSpace())
this._currentIndex++;
return this._currentIndex < this._endIndex;
}
Source.prototype._skipOptionalSpacesOrDelimiter = function() {
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;
}
Source.prototype.hasMoreData = function() {
return this._currentIndex < this._endIndex;
}
Source.prototype.peekSegmentType = function() {
var lookahead = this._string[this._currentIndex];
return this._pathSegTypeFromChar(lookahead);
}
Source.prototype._pathSegTypeFromChar = function(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;
}
}
Source.prototype._nextCommandHelper = function(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;
}
Source.prototype.initialCommandIsMoveTo = function() {
// 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: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
Source.prototype._parseNumber = function() {
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")
decimal += (this._string.charAt(this._currentIndex++) - "0") * (frac *= 0.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;
}
Source.prototype._parseArcFlag = function() {
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;
}
Source.prototype.parseSegment = function() {
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 points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, points.x, points.y, points.x2, points.y2);
case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, points.x, points.y, points.x2, points.y2);
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.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 points = {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, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
case SVGPathSeg.PATHSEG_ARC_ABS:
var points = {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, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
default:
throw "Unknown path seg type."
}
}
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;
}
}
}());
(function() {
document.addEventListener("touchstart", touchHandler, true);
document.addEventListener("touchmove", touchHandler, true);
document.addEventListener("touchend", touchHandler, true);
document.addEventListener("touchcancel", touchHandler, true);
function touchHandler(event) {
var touches = event.changedTouches,
first = touches[0],
type = "";
switch(event.type) {
case "touchstart": type="mousedown"; break;
case "touchmove": type="mousemove"; break;
case "touchend": type="mouseup"; break;
default: return;
}
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();
}
}
})();
/*
* 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(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(),k,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}})})(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
*
* Apache 2 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
<g> elements. Each <g> element should contain the markup of an SVG
icon. The <g> 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 <img>
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: function(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: function() {
'#save_icon .svg_icon': 64 // The "save" icon will be resized to 64 x 64px
},
callback: function(icons) { // Sets background color for "close" icon
icons['close'].css('background','red');
},
svgz: true // Indicates that an SVGZ file is being used
})
});
*/
(function($) {
var svg_icons = {}, fixIDs;
$.svgIcons = function(file, opts) {
var svgns = "http://www.w3.org/2000/svg",
xlinkns = "http://www.w3.org/1999/xlink",
icon_w = opts.w?opts.w : 24,
icon_h = opts.h?opts.h : 24,
elems, svgdoc, testImg,
icons_made = false, data_loaded = false, load_attempts = 0,
ua = navigator.userAgent, isOpera = !!window.opera, isSafari = (ua.indexOf('Safari/') > -1 && ua.indexOf('Chrome/')==-1),
data_pre = 'data:image/svg+xml;charset=utf-8;base64,';
if(opts.svgz) {
var data_el = $('<object data="' + file + '" type=image/svg+xml>').appendTo('body').hide();
try {
svgdoc = data_el[0].contentDocument;
data_el.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(data) {
if(!data) {
$(useFallback);
return;
}
svgdoc = parser.parseFromString(data, "text/xml");
$(function() {
getIcons('ajax');
});
},
error: function(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, no_wait) {
if(evt !== 'ajax') {
if(data_loaded) 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 = data_el[0].contentDocument; // Needed again for Webkit
var isReady = (svgdoc && svgdoc.getElementById('svg_eof'));
if(!isReady && !(no_wait && isReady)) {
load_attempts++;
if(load_attempts < 50) {
setTimeout(getIcons, 20);
} else {
useFallback();
data_loaded = true;
}
return;
}
data_loaded = true;
}
elems = $(svgdoc.firstChild).children(); //.getElementsByTagName('foreignContent');
if(!opts.no_img) {
var testSrc = data_pre + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzUiIGhlaWdodD0iMjc1Ij48L3N2Zz4%3D';
testImg = $(new Image()).attr({
src: testSrc,
width: 0,
height: 0
}).appendTo('body')
.on("load", function () {
// Safari 4 crashes, Opera and Chrome don't
makeIcons(true);
});
} else {
setTimeout(function() {
if(!icons_made) makeIcons();
},500);
}
}
var setIcon = function(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 addIcon = function(icon, id) {
if(opts.id_match === undefined || opts.id_match !== false) {
setIcon(holder, icon, id, true);
}
svg_icons[id] = icon;
}
function makeIcons(toImage, fallback) {
if(icons_made) return;
if(opts.no_img) toImage = false;
var holder;
if(toImage) {
var temp_holder = $(document.createElement('div'));
temp_holder.hide().appendTo('body');
}
if(fallback) {
var path = opts.fallback_path?opts.fallback_path:'';
$.each(fallback, function(id, imgsrc) {
holder = $('#' + id);
var icon = $(new Image())
.attr({
'class':'svg_icon',
src: path + imgsrc,
'width': icon_w,
'height': icon_h,
'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 svg = elem.getElementsByTagNameNS(svgns, 'svg')[0];
var svgroot = document.createElementNS(svgns, "svg");
svgroot.setAttributeNS(svgns, 'viewBox', [0,0,icon_w,icon_h].join(' '));
// 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', icon_w);
svgroot.setAttribute('height', icon_h);
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.appendChild(svg);
if(toImage) {
// Without cloning, Safari will crash
// With cloning, causes issue in Opera/Win/Non-EN
var svgcontent = isOpera?svgroot:svgroot.cloneNode(true);
temp_holder.empty().append(svgroot);
var str = data_pre + encode64(temp_holder.html());
var icon = $(new Image())
.attr({'class':'svg_icon', src:str});
} else {
var icon = fixIDs($(svgroot), i);
}
addIcon(icon, id);
}
}
if(opts.placement) {
$.each(opts.placement, function(sel, id) {
if(!svg_icons[id]) return;
$(sel).each(function(i) {
var copy = svg_icons[id].clone();
if(i > 0 && !toImage) copy = fixIDs(copy, i, true);
setIcon($(this), copy, id);
})
});
}
if(!fallback) {
if(toImage) temp_holder.remove();
if(data_el) data_el.remove();
if(testImg) testImg.remove();
}
if(opts.resize) $.resizeSvgIcons(opts.resize);
icons_made = true;
if(opts.callback) opts.callback(svg_icons);
}
fixIDs = function(svg_el, svg_num, force) {
var defs = svg_el.find('defs');
if(!defs.length) return svg_el;
if(isOpera) {
var id_elems = defs.find('*').filter(function() {
return !!this.id;
});
} else {
var id_elems = defs.find('[id]');
}
var all_elems = svg_el[0].getElementsByTagName('*'), len = all_elems.length;
id_elems.each(function(i) {
var id = this.id;
var no_dupes = ($(svgdoc).find('#' + id).length <= 1);
if(isOpera) no_dupes = false; // Opera didn't clone svg_el, so not reliable
// if(!force && no_dupes) return;
var new_id = 'x' + id + svg_num + i;
this.id = new_id;
var old_val = 'url(#' + id + ')';
var new_val = 'url(#' + new_id + ')';
for(var i = 0; i < len; i++) {
var elem = all_elems[i];
if(elem.getAttribute('fill') === old_val) {
elem.setAttribute('fill', new_val);
}
if(elem.getAttribute('stroke') === old_val) {
elem.setAttribute('stroke', new_val);
}
if(elem.getAttribute('filter') === old_val) {
elem.setAttribute('filter', new_val);
}
}
});
return svg_el;
}
function useFallback() {
if(file.indexOf('.svgz') != -1) {
var reg_file = file.replace('.svgz','.svg');
if(window.console) {
console.log('.svgz failed, trying with .svg');
}
$.svgIcons(reg_file, 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 chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0, p = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
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 = svg_icons[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 change_sel = !$('.svg_icon:first').length;
$.each(obj, function(sel, size) {
var arr = $.isArray(size);
var w = arr?size[0]:size,
h = arr?size[1]:size;
if(change_sel) {
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';
}
});
});
}
})(jQuery);
/*
* 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
*
*/
(function() {
var ns = { svg: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink' };
if(!window.console) {
window.console = new function() {
this.log = function(str) {};
this.dir = function(str) {};
};
}
$.jGraduate = {
Paint:
function(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;
}
}
};
jQuery.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.indexOf('Gecko/') >= 0;
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.appendChild(elem);
return elem;
}
jQuery.fn.jGraduate =
function(options) {
var $arguments = arguments;
return this.each( function() {
var $this = $(this), $settings = $.extend(true, {}, jQuery.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() {
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;
}
$.isFunction($this.okCallback) && $this.okCallback($this.paint);
$this.hide();
},
cancelClicked = function() {
$.isFunction($this.cancelCallback) && $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: $.isFunction($arguments[1]) && $arguments[1] || null,
cancelCallback: $.isFunction($arguments[2]) && $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('<ul class="jGraduate_tabs">' +
'<li class="jGraduate_tab_color jGraduate_tab_current" data-type="col">Solid Color</li>' +
'<li class="jGraduate_tab_lingrad" data-type="lg">Linear Gradient</li>' +
'<li class="jGraduate_tab_radgrad" data-type="rg">Radial Gradient</li>' +
'</ul>' +
'<div class="jGraduate_colPick"></div>' +
'<div class="jGraduate_gradPick"></div>' +
'<div class="jGraduate_LightBox"></div>' +
'<div id="' + id + '_jGraduate_stopPicker" class="jGraduate_stopPicker"></div>'
);
var colPicker = $(idref + '> .jGraduate_colPick');
var gradPicker = $(idref + '> .jGraduate_gradPick');
gradPicker.html(
'<div id="' + id + '_jGraduate_Swatch" class="jGraduate_Swatch">' +
'<h2 class="jGraduate_Title">' + $settings.window.pickerTitle + '</h2>' +
'<div id="' + id + '_jGraduate_GradContainer" class="jGraduate_GradContainer"></div>' +
'<div id="' + id + '_jGraduate_StopSlider" class="jGraduate_StopSlider"></div>' +
'</div>' +
'<div class="jGraduate_Form jGraduate_Points jGraduate_lg_field">' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">Begin Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_x1" size="3" title="Enter starting x value between 0.0 and 1.0"/>' +
'<label> y:</label>' +
'<input type="text" id="' + id + '_jGraduate_y1" size="3" title="Enter starting y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">End Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_x2" size="3" title="Enter ending x value between 0.0 and 1.0"/>' +
'<label> y:</label>' +
'<input type="text" id="' + id + '_jGraduate_y2" size="3" title="Enter ending y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="jGraduate_Form jGraduate_Points jGraduate_rg_field">' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">Center Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_cx" size="3" title="Enter x value between 0.0 and 1.0"/>' +
'<label> y:</label>' +
'<input type="text" id="' + id + '_jGraduate_cy" size="3" title="Enter y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">Focal Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>Match center: <input type="checkbox" checked="checked" id="' + id + '_jGraduate_match_ctr"/></label><br/>' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_fx" size="3" title="Enter x value between 0.0 and 1.0"/>' +
'<label> y:</label>' +
'<input type="text" id="' + id + '_jGraduate_fy" size="3" title="Enter y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="jGraduate_StopSection jGraduate_SpreadMethod">' +
'<label class="jGraduate_Form_Heading">Spread method</label>' +
'<div class="jGraduate_Form_Section">' +
'<select class="jGraduate_spreadMethod">' +
'<option value=pad selected>Pad</option>' +
'<option value=reflect>Reflect</option>' +
'<option value=repeat>Repeat</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="jGraduate_Form">' +
'<div class="jGraduate_Slider jGraduate_RadiusField jGraduate_rg_field">' +
'<label class="prelabel">Radius:</label>' +
'<div id="' + id + '_jGraduate_Radius" class="jGraduate_SliderBar jGraduate_Radius" title="Click to set radius">' +
'<img id="' + id + '_jGraduate_RadiusArrows" class="jGraduate_RadiusArrows" src="' + $settings.images.clientPath + 'rangearrows2.svg" width="9" height="20">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_RadiusInput" size="3" value="100"/>%</label>' +
'</div>' +
'<div class="jGraduate_Slider jGraduate_EllipField jGraduate_rg_field">' +
'<label class="prelabel">Ellip:</label>' +
'<div id="' + id + '_jGraduate_Ellip" class="jGraduate_SliderBar jGraduate_Ellip" title="Click to set Ellip">' +
'<img id="' + id + '_jGraduate_EllipArrows" class="jGraduate_EllipArrows" src="' + $settings.images.clientPath + 'rangearrows2.svg" width="9" height="20">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_EllipInput" size="3" value="0"/>%</label>' +
'</div>' +
'<div class="jGraduate_Slider jGraduate_AngleField jGraduate_rg_field">' +
'<label class="prelabel">Angle:</label>' +
'<div id="' + id + '_jGraduate_Angle" class="jGraduate_SliderBar jGraduate_Angle" title="Click to set Angle">' +
'<img id="' + id + '_jGraduate_AngleArrows" class="jGraduate_AngleArrows" src="' + $settings.images.clientPath + 'rangearrows2.svg" width="9" height="20">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_AngleInput" size="3" value="0"/>º&nbsp;</label>' +
'</div>' +
'<div class="jGraduate_Slider jGraduate_OpacField">' +
'<label class="prelabel">Opac:</label>' +
'<div id="' + id + '_jGraduate_Opac" class="jGraduate_SliderBar jGraduate_Opac" title="Click to set Opac">' +
'<img id="' + id + '_jGraduate_OpacArrows" class="jGraduate_OpacArrows" src="' + $settings.images.clientPath + 'rangearrows2.svg" width="9" height="20">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_OpacInput" size="3" value="100"/>%</label>' +
'</div>' +
'</div>' +
'<div class="jGraduate_OkCancel">' +
'<input type="button" id="' + id + '_jGraduate_Ok" class="jGraduate_Ok" value="OK"/>' +
'<input type="button" id="' + id + '_jGraduate_Cancel" class="jGraduate_Cancel" value="Cancel"/>' +
'</div>');
// --------------
// 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 curType, curGradient, previewRect;
var attr_input = {};
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);
// if we are sent a gradient, import it
curType = curType || $this.paint.type;
var grad = curGradient = $this.paint[curType];
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;
case "radialGradient":
if(!isSolid) {
curGradient.id = id+'_rg_jgraduate_grad';
grad = curGradient = svg.appendChild(curGradient);//.cloneNode(true));
}
mkElem('linearGradient', {
id: id + '_lg_jgraduate_grad',
x1: 0,
y1: 0,
x2: 1,
y2: 0
}, svg);
}
if(isSolid) {
grad = curGradient = $('#' + id + '_lg_jgraduate_grad')[0];
var 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 = '';
if (color.length === 3) {
color = color.split("").map(function(d){return d + "" + d}).join("");
}
for(var i = 0; i < 6; i += 2) {
var 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 = $('<div/>').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 = $('<div/>').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';
var coords = $(idref + ' .grad_coord');
$.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function(i, attr) {
var attrval = curGradient.getAttribute(attr);
var isRadial = isNaN(attr[1]);
if(!attrval) {
// Set defaults
if(isRadial) {
// For radial points
attrval = "0.5";
} else {
// Only x2 is 1
attrval = attr === 'x2' ? "1.0" : "0.0";
}
}
attr_input[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);
}
}
if(isRadial) {
var $elem = attr[0] === "c" ? centerCoord : focusCoord;
} else {
var $elem = attr[1] === "1" ? beginCoord : endCoord;
}
var cssName = attr.indexOf('x') >= 0 ? 'left' : 'top';
$elem.css(cssName, this.value * MAX);
}).change();
});
function mkStop(n, color, opac, sel, stop_elem) {
var stop = stop_elem || mkElem('stop',{'stop-color':color,'stop-opacity':opac,offset:n}, curGradient);
if(stop_elem) {
color = stop_elem.getAttribute('stop-color');
opac = stop_elem.getAttribute('stop-opacity');
n = stop_elem.getAttribute('offset');
} else {
curGradient.appendChild(stop);
}
if(opac === null) opac = 1;
var picker_d = '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: picker_d,
fill: 'url(#jGraduate_trans)',
transform: 'translate(' + (10 + n * MAX) + ', 26)'
}, stopGroup);
var path = mkElem('path',{
d: picker_d,
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 = cur_stop;
$win.mousemove(dragColor).mouseup(remDrags);
stop_offset = 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 cur_s = $(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);
}
cur_s.before(stop);
return false;
}
});
if(sel) selectStop(path);
return stop;
}
function remStop() {
delStop.setAttribute('display', 'none');
var path = $(cur_stop);
var stop = path.data('stop');
var bg = path.data('bg');
$([cur_stop, stop, bg]).remove();
}
var stops, stopGroup;
var stopMakerDiv = $('#' + id + '_jGraduate_StopSlider');
var cur_stop, stopGroup, stopMakerSVG, drag;
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'
}, stopMakerSVG);
function selectStop(item) {
if(cur_stop) cur_stop.setAttribute('stroke', '#000');
item.setAttribute('stroke', 'blue');
cur_stop = item;
cur_stop.parentNode.appendChild(cur_stop);
// stops = $('stop');
// opac_select.val(cur_stop.attr('fill-opacity') || 1);
// root.append(delStop);
}
var stop_offset;
function remDrags() {
$win.unbind('mousemove', dragColor);
if(delStop.getAttribute('display') !== 'none') {
remStop();
}
drag = null;
}
var scale_x = 1, scale_y = 1, angle = 0;
var c_x = cx;
var c_y = cy;
function xform() {
var rot = angle?'rotate(' + angle + ',' + c_x + ',' + c_y + ') ':'';
if(scale_x === 1 && scale_y === 1) {
curGradient.removeAttribute('gradientTransform');
// $('#ang').addClass('dis');
} else {
var x = -c_x * (scale_x-1);
var y = -c_y * (scale_y-1);
curGradient.setAttribute('gradientTransform', rot + 'translate(' + x + ',' + y + ') scale(' + scale_x + ',' + scale_y + ')');
// $('#ang').removeClass('dis');
}
}
function dragColor(evt) {
var x = evt.pageX - stop_offset.left;
var y = evt.pageY - stop_offset.top;
x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10: x;
var xf_str = 'translate(' + x + ', 26)';
if(y < -60 || y > 130) {
delStop.setAttribute('display', 'block');
delStop.setAttribute('transform', xf_str);
} else {
delStop.setAttribute('display', 'none');
}
drag.setAttribute('transform', xf_str);
$.data(drag, 'bg').setAttribute('transform', xf_str);
var stop = $.data(drag, 'stop');
var s_x = (x - 10) / MAX;
stop.setAttribute('offset', s_x);
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;
});
}
stopMakerSVG = mkElem('svg', {
width: '100%',
height: 45
}, stopMakerDiv[0]);
var trans_pattern = mkElem('pattern', {
width: 16,
height: 16,
patternUnits: 'userSpaceOnUse',
id: 'jGraduate_trans'
}, stopMakerSVG);
var trans_img = mkElem('image', {
width: 16,
height: 16
}, trans_pattern);
var bg_image = $settings.images.clientPath + 'map-opacity.png';
trans_img.setAttributeNS(ns.xlink, 'xlink:href', bg_image);
$(stopMakerSVG).on("click touchstart", function(evt) {
stop_offset = stopMakerDiv.offset();
var target = evt.target;
if(target.tagName === 'path') return;
var x = evt.pageX - stop_offset.left - 8;
x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10: x;
mkStop(x / MAX, 0, 0, true);
evt.stopPropagation();
});
$(stopMakerSVG).mouseover(function() {
stopMakerSVG.appendChild(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(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':
attr_input.x1.val(fracx);
attr_input.y1.val(fracy);
grad.setAttribute('x1', fracx);
grad.setAttribute('y1', fracy);
break;
case 'end':
attr_input.x2.val(fracx);
attr_input.y2.val(fracy);
grad.setAttribute('x2', fracx);
grad.setAttribute('y2', fracy);
break;
case 'center':
attr_input.cx.val(fracx);
attr_input.cy.val(fracy);
grad.setAttribute('cx', fracx);
grad.setAttribute('cy', fracy);
c_x = fracx;
c_y = fracy;
xform();
break;
case 'focus':
attr_input.fx.val(fracx);
attr_input.fy.val(fracy);
grad.setAttribute('fx', fracx);
grad.setAttribute('fy', fracy);
xform();
}
evt.preventDefault();
}
var onCoordUp = function() {
draggingCoord = null;
$win.unbind('mousemove', onCoordDrag).unbind('mouseup', onCoordUp);
}
// Linear gradient
// (function() {
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
// if there are not at least two stops, then
if (numstops < 2) {
while (numstops < 2) {
curGradient.appendChild( document.createElementNS(ns.svg, 'stop') );
++numstops;
}
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
}
var numstops = stops.length;
for(var i = 0; i < numstops; i++) {
mkStop(0, 0, 0, 0, stops[i]);
}
spreadMethodOpt.val(curGradient.getAttribute('spreadMethod') || 'pad');
var offset;
// No match, so show focus point
var showFocus = false;
previewRect.setAttribute('fill-opacity', gradalpha/100);
$('#' + id + ' div.grad_coord').mousedown(function(evt) {
evt.preventDefault();
draggingCoord = $(this);
var s_pos = draggingCoord.offset();
offset = draggingCoord.parent().offset();
$win.mousemove(onCoordDrag).mouseup(onCoordUp);
});
// bind GUI elements
$('#'+id+'_jGraduate_Ok').bind('click touchstart', function() {
$this.paint.type = curType;
$this.paint[curType] = curGradient.cloneNode(true);;
$this.paint.solidColor = null;
okClicked();
});
$('#'+id+'_jGraduate_Cancel').bind('click touchstart', function(paint) {
cancelClicked();
});
if(curType === 'radialGradient') {
if(showFocus) {
focusCoord.show();
} else {
focusCoord.hide();
attr_input.fx.val("");
attr_input.fy.val("");
}
}
$("#" + id + "_jGraduate_match_ctr")[0].checked = !showFocus;
var lastfx, lastfy;
$("#" + id + "_jGraduate_match_ctr").change(function() {
showFocus = !this.checked;
focusCoord.toggle(showFocus);
attr_input.fx.val('');
attr_input.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 || .5;
var fy = lastfy || .5;
grad.setAttribute('fx', fx);
grad.setAttribute('fy', fy);
attr_input.fx.val(fx);
attr_input.fy.val(fy);
}
});
var 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.appendChild( document.createElementNS(ns.svg, 'stop') );
++numstops;
}
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
}
var slider;
var setSlider = function(e) {
var 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 > .98 && x < 1.02) x = 1;
if (x <= .01) x = .01;
curGradient.setAttribute('r', x);
break;
case 'opacity':
$this.paint.alpha = parseInt(x*100);
previewRect.setAttribute('fill-opacity', x);
break;
case 'ellip':
scale_x = 1, scale_y = 1;
if(x < .5) {
x /= .5; // 0.001
scale_x = x <= 0 ? .01 : x;
} else if(x > .5) {
x /= .5; // 2
x = 2 - x;
scale_y = x <= 0 ? .01 : x;
}
xform();
x -= 1;
if(scale_y === x + 1) {
x = Math.abs(x);
}
break;
case 'angle':
x = x - .5;
angle = x *= 180;
xform();
x /= 100;
break;
}
slider.elem.css({'margin-left':posx});
x = Math.round(x*100);
slider.input.val(x);
};
var ellip_val = 0, angle_val = 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) {
ellip_val = Math.round(-(1 - m.a) * 100);
} else if(m.d !== 1) {
ellip_val = 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) {
angle_val = Math.round(r.angle);
var m = s.matrix;
if(m.a !== 1) {
ellip_val = Math.round(-(1 - m.a) * 100);
} else if(m.d !== 1) {
ellip_val = Math.round((1 - m.d) * 100);
}
}
}
}
var sliders = {
radius: {
handle: '#' + id + '_jGraduate_RadiusArrows',
input: '#' + id + '_jGraduate_RadiusInput',
val: (curGradient.getAttribute('r') || .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: ellip_val
},
angle: {
handle: '#' + id + '_jGraduate_AngleArrows',
input: '#' + id + '_jGraduate_AngleInput',
val: angle_val
}
}
$.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 val = +this.value;
var xpos = 0;
var isRad = curType === 'radialGradient';
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':
scale_x = scale_y = 1;
if(val === 0) {
xpos = SLIDERW * .5;
break;
}
if(val > 99.5) val = 99.5;
if(val > 0) {
scale_y = 1 - (val / 100);
} else {
scale_x = - (val / 100) - 1;
}
xpos = SLIDERW * ((val + 100) / 2) / 100;
if(isRad) xform();
break;
case 'angle':
angle = val;
xpos = angle / 180;
xpos += .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(evt) {
setSlider(evt);
evt.preventDefault();
};
var stopSlider = function(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.on("click touchstart", function() {
tabs.removeClass('jGraduate_tab_current');
$(this).addClass('jGraduate_tab_current');
$(idref + " > div").hide();
var type = $(this).attr('data-type');
var 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 cur_stops = $(curGradient).find('stop');
$(newGrad).empty().append(cur_stops);
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;
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);
});
};
})();
// jQuery Context Menu Plugin
//
// Version 1.01
//
// Cory S.N. LaViska
// A Beautiful Site (http://abeautifulsite.net/)
// Modified by Alexis Deveria
//
// More info: http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/
//
// Terms of Use
//
// This plugin is dual-licensed under the GNU General Public License
// and the MIT License and is copyright A Beautiful Site, LLC.
//
if(jQuery)( function() {
var win = $(window);
var doc = $(document);
$.extend($.fn, {
contextMenu: function(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).on("mouseup", function(e) {
var srcElement = $(this);
srcElement.unbind('mouseup');
$(".contextMenu").hide();
if( evt.button === 2 || o.allowLeft || (evt.ctrlKey && svgedit.browser.isMac()) ) {
if (!svgedit.browser.isTouch()) open_context_menu(e, evt, srcElement);
}
});
});
if (svgedit.browser.isTouch()) {
$(this).bind("taphold", function(e){
var srcElement = $(this);
srcElement.unbind('mouseup');
open_context_menu(e,e, srcElement);
})
}
var open_context_menu = function(e, evt, srcElement) {
if (typeof evt == 'undefined') evt = e;
e.stopPropagation();
// Get this context menu
if( el.hasClass('disabled') || evt.altKey ) return false;
// Detect mouse position
var d = {}, x = e.pageX, y = e.pageY;
if (svgedit.browser.isTouch()) var d = {}, x = e.originalEvent.touches[0].pageX, y = e.originalEvent.touches[0].pageY;
var x_off = win.width() - menu.width(),
y_off = win.height() - menu.height();
if(x > x_off - 15) x = x_off-15;
if(y > y_off - 30) y = y_off-30; // 30 is needed to prevent scrollbars in FF
if(svgedit.browser.isTouch())
y = y - (menu.height()/2)
// 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 == 0 ) {
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 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(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(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() {
$(this).each( function() {
$(this).addClass('disabled');
});
return( $(this) );
},
// Enable context menu(s)
enableContextMenu: function() {
$(this).each( function() {
$(this).removeClass('disabled');
});
return( $(this) );
},
// Destroy context menu(s)
destroyContextMenu: function() {
// Destroy specified context menus
$(this).each( function() {
// Disable action
$(this).unbind('mousedown').unbind('mouseup');
});
return( $(this) );
}
});
})(jQuery);
/*!
* jQuery UI 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*/(function(a,b){function d(b){return!a(b).parents().addBack().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;if(!b.href||!g||f.nodeName.toLowerCase()!=="map")return!1;h=a("img[usemap=#"+g+"]")[0];return!!h&&d(h)}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}a.ui=a.ui||{};a.ui.version||(a.extend(a.ui,{version:"1.8.17",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)});return c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){if(c===b)return g["inner"+d].call(this);return this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){if(typeof b!="number")return g["outer"+d].call(this,b);return this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!!d&&!!a.element[0].parentNode)for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;if(b[d]>0)return!0;b[d]=1,e=b[d]>0,b[d]=0;return e},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverA
* jQuery UI Widget 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Widget
*/(function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}});return d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][f]=function(c){return!!a.data(c,b)},a[e]=a[e]||{},a[e][b]=function(a,b){arguments.length&&this._createWidget(a,b)};var g=new c;g.options=a.extend(!0,{},g.options),a[e][b].prototype=a.extend(!0,g,{namespace:e,widgetName:b,widgetEventPrefix:a[e][b].prototype.widgetEventPrefix||b,widgetBaseClass:f},d),a.widget.bridge(b,a[e][b])},a.widget.bridge=function(c,d){a.fn[c]=function(e){var f=typeof e=="string",g=Array.prototype.slice.call(arguments,1),h=this;e=!f&&g.length?a.extend.apply(null,[!0,e].concat(g)):e;if(f&&e.charAt(0)==="_")return h;f?this.each(function(){var d=a.data(this,c),f=d&&a.isFunction(d[e])?d[e].apply(d,g):d;if(f!==d&&f!==b){h=f;return!1}}):this.each(function(){var b=a.data(this,c);b?b.option(e||{})._init():a.data(this,c,new d(e,this))});return h}},a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)},a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:!1},_createWidget:function(b,c){a.data(c,this.widgetName,this),this.element=a(c),this.options=a.extend(!0,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()}),this._create(),this._trigger("create"),this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName),this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled "+"ui-state-disabled")},widget:function(){return this.element},option:function(c,d){var e=c;if(arguments.length===0)return a.extend({},this.options);if(typeof c=="string"){if(d===b)return this.options[c];e={},e[c]=d}this._setOptions(e);return this},_setOptions:function(b){var c=this;a.each(b,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b,a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled"+" "+"ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);this.element.trigger(c,d);return!(a.isFunction(g)&&g.call(this.element[0],c,d)===!1||c.isDefaultPrevented())}}})(jQuery);/*!
* jQuery UI Mouse 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Mouse
*
* Depends:
* jquery.ui.widget.js
*/(function(a,b){var c=!1;a(document).mouseup(function(a){c=!1}),a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var b=this;this.element.bind("mousedown."+this.widgetName,function(a){return b._mouseDown(a)}).bind("click."+this.widgetName,function(c){if(!0===a.data(c.target,b.widgetName+".preventClickEvent")){a.removeData(c.target,b.widgetName+".preventClickEvent"),c.stopImmediatePropagation();return!1}}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(b){if(!c){this._mouseStarted&&this._mouseUp(b),this._mouseDownEvent=b;var d=this,e=b.which==1,f=typeof this.options.cancel=="string"&&b.target.nodeName?a(b.target).closest(this.options.cancel).length:!1;if(!e||f||!this._mouseCapture(b))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){d.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)){this._mouseStarted=this._mouseStart(b)!==!1;if(!this._mouseStarted){b.preventDefault();return!0}}!0===a.data(b.target,this.widgetName+".preventClickEvent")&&a.removeData(b.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(a){return d._mouseMove(a)},this._mouseUpDelegate=function(a){return d._mouseUp(a)},a(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),b.preventDefault(),c=!0;return!0}},_mouseMove:function(b){if(a.browser.msie&&!(document.documentMode>=9)&&!b.button)return this._mouseUp(b);if(this._mouseStarted){this._mouseDrag(b);return b.preventDefault()}this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b));return!this._mouseStarted},_mouseUp:function(b){a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b));return!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);/*
* jQuery UI Draggable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Draggables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!!this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy();return this}},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle"))return!1;this.handle=this._getHandle(b);if(!this.handle)return!1;c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")});return!0},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment();if(this._trigger("start",b)===!1){this._clear();return!1}this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.helper.addClass("ui-draggable-dragging"),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b);return!0},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1){this._mouseUp({});return!1}this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";a.ui.ddmanager&&a.ui.ddmanager.drag(this,b);return!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var d=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){d._trigger("stop",b)!==!1&&d._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b);return a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;a(this.options.handle,this.element).find("*").addBack().each(functi
* jQuery UI Slider 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Slider
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/(function(a,b){var c=5;a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var b=this,d=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",g=d.values&&d.values.length||1,h=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(d.disabled?" ui-slider-disabled ui-disabled":"")),this.range=a([]),d.range&&(d.range===!0&&(d.values||(d.values=[this._valueMin(),this._valueMin()]),d.values.length&&d.values.length!==2&&(d.values=[d.values[0],d.values[0]])),this.range=a("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;i<g;i+=1)h.push(f);this.handles=e.add(a(h.join("")).appendTo(b.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(a){a.preventDefault()}).hover(function(){d.disabled||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){d.disabled?a(this).blur():(a(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),a(this).addClass("ui-state-focus"))}).blur(function(){a(this).removeClass("ui-state-focus")}),this.handles.each(function(b){a(this).data("index.ui-slider-handle",b)}),this.handles.keydown(function(d){var e=!0,f=a(this).data("index.ui-slider-handle"),g,h,i,j;if(!b.options.disabled){switch(d.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.PAGE_UP:case a.ui.keyCode.PAGE_DOWN:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:e=!1;if(!b._keySliding){b._keySliding=!0,a(this).addClass("ui-state-active"),g=b._start(d,f);if(g===!1)return}}j=b.options.step,b.options.values&&b.options.values.length?h=i=b.values(f):h=i=b.value();switch(d.keyCode){case a.ui.keyCode.HOME:i=b._valueMin();break;case a.ui.keyCode.END:i=b._valueMax();break;case a.ui.keyCode.PAGE_UP:i=b._trimAlignValue(h+(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.PAGE_DOWN:i=b._trimAlignValue(h-(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(h===b._valueMax())return;i=b._trimAlignValue(h+j);break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(h===b._valueMin())return;i=b._trimAlignValue(h-j)}b._slide(d,f,i);return e}}).keyup(function(c){var d=a(this).data("index.ui-slider-handle");b._keySliding&&(b._keySliding=!1,b._stop(c,d),b._change(c,d),a(this).removeClass("ui-state-active"))}),this._refreshValue(),this._animateOff=!1},destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"),this._mouseDestroy();return this},_mouseCapture:function(b){var c=this.options,d,e,f,g,h,i,j,k,l;if(c.disabled)return!1;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),d={x:b.pageX,y:b.pageY},e=this._normValueFromMouse(d),f=this._valueMax()-this._valueMin()+1,h=this,this.handles.each(function(b){var c=Math.abs(e-h.values(b));f>c&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i);if(j===!1)return!1;this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this
/**
* Package: svgedit.browser
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Jeff Schiller
* Copyright(c) 2010 Alexis Deveria
*/
// Dependencies:
// 1) jQuery (for $.alert())
var svgedit = svgedit || {};
(function() {
if (!svgedit.browser) {
svgedit.browser = {};
}
var supportsSvg_ = (function() {
return !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect;
})();
svgedit.browser.supportsSvg = function() { return supportsSvg_; }
if(!svgedit.browser.supportsSvg()) {
window.location = "browser-not-supported.html";
}
else{
var svgns = 'http://www.w3.org/2000/svg';
var userAgent = navigator.userAgent;
var svg = document.createElementNS(svgns, 'svg');
// Note: Browser sniffing should only be used if no other detection method is possible
var isOpera_ = !!window.opera;
var isWebkit_ = userAgent.indexOf("AppleWebKit") >= 0;
var isGecko_ = userAgent.indexOf('Gecko/') >= 0;
var isIE_ = userAgent.indexOf('MSIE') >= 0;
var isChrome_ = userAgent.indexOf('Chrome/') >= 0;
var isWindows_ = userAgent.indexOf('Windows') >= 0;
var isMac_ = userAgent.indexOf('Macintosh') >= 0;
var isTouch_ = 'ontouchstart' in window;
var supportsSelectors_ = (function() {
return !!svg.querySelector;
})();
var supportsXpath_ = (function() {
return !!document.evaluate;
})();
// text character positioning (for IE9)
var supportsGoodTextCharPos_ = (function() {
var retValue = false;
var svgroot = document.createElementNS(svgns, 'svg');
var svgcontent = document.createElementNS(svgns, 'svg');
document.documentElement.appendChild(svgroot);
svgcontent.setAttribute('x', 5);
svgroot.appendChild(svgcontent);
var text = document.createElementNS(svgns,'text');
text.textContent = 'a';
svgcontent.appendChild(text);
var pos = text.getStartPositionOfChar(0)
pos = pos.x; //if you put it on one line it fails when compiled
document.documentElement.removeChild(svgroot);
return (pos === 0);
})();
var supportsPathBBox_ = (function() {
var svgcontent = document.createElementNS(svgns, 'svg');
document.documentElement.appendChild(svgcontent);
var path = document.createElementNS(svgns, 'path');
path.setAttribute('d','M0,0 C0,0 10,10 10,0');
svgcontent.appendChild(path);
var bbox = path.getBBox();
document.documentElement.removeChild(svgcontent);
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(svgns, 'svg');
document.documentElement.appendChild(svgcontent);
var path = document.createElementNS(svgns, 'path');
path.setAttribute('d','M0,0 10,0');
var path2 = document.createElementNS(svgns, 'path');
path2.setAttribute('d','M5,0 15,0');
var g = document.createElementNS(svgns, 'g');
g.appendChild(path);
g.appendChild(path2);
svgcontent.appendChild(g);
var bbox = g.getBBox();
document.documentElement.removeChild(svgcontent);
// Webkit gives 0, FF gives 10, Opera (correctly) gives 15
return (bbox.width == 15);
})();
var supportsEditableText_ = (function() {
// TODO: Find better way to check support for this
return isOpera_;
})();
var supportsGoodDecimals_ = (function() {
// Correct decimals on clone attributes (Opera < 10.5/win/non-en)
var rect = document.createElementNS(svgns, 'rect');
rect.setAttribute('x',.1);
var crect = rect.cloneNode(false);
var retValue = (crect.getAttribute('x').indexOf(',') == -1);
if(!retValue) {
$.alert("NOTE: This version of Opera is known to contain bugs in SVG-edit.\n\
Please upgrade to the <a href='http://opera.com'>latest version</a> in which the problems have been fixed.");
}
return retValue;
})();
var supportsNonScalingStroke_ = (function() {
var rect = document.createElementNS(svgns, 'rect');
rect.setAttribute('style','vector-effect:non-scaling-stroke');
return rect.style.vectorEffect === 'non-scaling-stroke';
})();
var supportsNativeSVGTransformLists_ = (function() {
var rect = document.createElementNS(svgns, 'rect');
var rxform = rect.transform.baseVal;
var t1 = svg.createSVGTransform();
rxform.appendItem(t1);
return rxform.getItem(0) == t1;
})();
var supportsBlobs_ = (function() {
if (typeof Blob != 'function') return false;
// check if download is supported
var svg = new Blob(
["<svg xmlns='http://www.w3.org/2000/svg'></svg>"],
{type: "image/svg+xml;charset=utf-8"}
);
var img = new Image();
var support = false;
img.onload = function() { svgedit.browser.supportsBlobs = function() {return true} };
img.onerror = function() { svgedit.browser.supportsBlobs = function() {return false} };
img.src = URL.createObjectURL(svg);
return false;
})();
// Public API
svgedit.browser.isOpera = function() { return isOpera_; }
svgedit.browser.isWebkit = function() { return isWebkit_; }
svgedit.browser.isGecko = function() { return isGecko_; }
svgedit.browser.isIE = function() { return isIE_; }
svgedit.browser.isChrome = function() { return isChrome_; }
svgedit.browser.isWindows = function() { return isWindows_; }
svgedit.browser.isMac = function() { return isMac_; }
svgedit.browser.isTouch = function() { return isTouch_; }
svgedit.browser.supportsSelectors = function() { return supportsSelectors_; }
svgedit.browser.supportsXpath = function() { return supportsXpath_; }
svgedit.browser.supportsPathBBox = function() { return supportsPathBBox_; }
svgedit.browser.supportsHVLineContainerBBox = function() { return supportsHVLineContainerBBox_; }
svgedit.browser.supportsGoodTextCharPos = function() { return supportsGoodTextCharPos_; }
svgedit.browser.supportsEditableText = function() { return supportsEditableText_; }
svgedit.browser.supportsGoodDecimals = function() { return supportsGoodDecimals_; }
svgedit.browser.supportsNonScalingStroke = function() { return supportsNonScalingStroke_; }
svgedit.browser.supportsNativeTransformLists = function() { return supportsNativeSVGTransformLists_; }
svgedit.browser.supportsBlobs = function() {return supportsBlobs_; }
}
})();
/**
* SVGTransformList
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) browser.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.transformlist) {
svgedit.transformlist = {};
}
var svgroot = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
// Helper function.
function transformToString(xform) {
var m = xform.matrix,
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, 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 ( );
// }
// **************************************************************************************
svgedit.transformlist.SVGTransformList = function(elem) {
this._elem = elem || null;
this._xforms = [];
// TODO: how do we capture the undo-ability in the changed transform list?
this._update = function() {
var tstr = "";
var 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() {
// 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 arr = [];
var m = true;
while(m) {
m = str.match(re);
str = str.replace(re,'');
if(m && m[1]) {
var x = m[1];
var bits = x.split(/\s*\(/);
var name = bits[0];
var val_bits = bits[1].match(/\s*(.*?)\s*\)/);
val_bits[1] = val_bits[1].replace(/(\d)-/g, "$1 -");
var val_arr = val_bits[1].split(/[, ]+/);
var letters = 'abcdef'.split('');
var mtx = svgroot.createSVGMatrix();
$.each(val_arr, function(i, item) {
val_arr[i] = parseFloat(item);
if(name == 'matrix') {
mtx[letters[i]] = val_arr[i];
}
});
var xform = svgroot.createSVGTransform();
var fname = 'set' + name.charAt(0).toUpperCase() + name.slice(1);
var values = name=='matrix'?[mtx]:val_arr;
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);
values.push(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];
}
throw {code: 1}; // DOMException with code=INDEX_SIZE_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
for ( var 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);
for (var 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;
} else {
throw {code: 1}; // DOMException with code=INDEX_SIZE_ERR
}
};
this.appendItem = function(newItem) {
this._removeFromOtherLists(newItem);
this._xforms.push(newItem);
this.numberOfItems++;
this._list._update();
return newItem;
};
};
svgedit.transformlist.resetListMap = function() {
listMap_ = {};
};
/**
* Removes transforms of the given element from the map.
* Parameters:
* elem - a DOM Element
*/
svgedit.transformlist.removeElementFromListMap = function(elem) {
if (elem.id && listMap_[elem.id]) {
delete listMap_[elem.id];
}
};
// Function: getTransformList
// Returns an object that behaves like a SVGTransformList for the given DOM element
//
// Parameters:
// elem - DOM element to get a transformlist from
svgedit.transformlist.getTransformList = function(elem) {
if (!svgedit.browser.supportsNativeTransformLists()) {
var id = elem.id;
if(!id) {
// Get unique ID for temporary element
id = 'temp';
}
var t = listMap_[id];
if (!t || id == 'temp') {
listMap_[id] = new svgedit.transformlist.SVGTransformList(elem);
listMap_[id]._init();
t = listMap_[id];
}
return t;
}
else if (elem.transform) {
return elem.transform.baseVal;
}
else if (elem.gradientTransform) {
return elem.gradientTransform.baseVal;
}
else if (elem.patternTransform) {
return elem.patternTransform.baseVal;
}
return null;
};
})();
/**
* Package: svedit.math
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// None.
var svgedit = svgedit || {};
(function() {
if (!svgedit.math) {
svgedit.math = {};
}
// Constants
var NEAR_ZERO = 1e-14;
// Throw away SVGSVGElement used for creating matrices/transforms.
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
// Function: svgedit.math.transformPoint
// A (hopefully) quicker function to transform a point by a matrix
// (this function avoids any DOM calls and just does the math)
//
// Parameters:
// x - Float representing the x coordinate
// y - Float representing the y coordinate
// m - Matrix object to transform the point with
// Returns a x,y object representing the transformed point
svgedit.math.transformPoint = function(x, y, m) {
return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f};
};
// Function: svgedit.math.isIdentity
// Helper function to check if the matrix performs no actual transform
// (i.e. exists for identity purposes)
//
// Parameters:
// m - The matrix object to check
//
// Returns:
// Boolean indicating whether or not the matrix is 1,0,0,1,0,0
svgedit.math.isIdentity = function(m) {
return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0);
};
// Function: svgedit.math.matrixMultiply
// This function tries to return a SVGMatrix that is the multiplication m1*m2.
// We also round to zero when it's near zero
//
// Parameters:
// >= 2 Matrix objects to multiply
//
// Returns:
// The matrix object resulting from the calculation
svgedit.math.matrixMultiply = function() {
var args = arguments, i = args.length, m = args[i-1];
while(i-- > 1) {
var m1 = args[i-1];
m = m1.multiply(m);
}
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;
};
// Function: svgedit.math.hasMatrixTransform
// See if the given transformlist includes a non-indentity matrix transform
//
// Parameters:
// tlist - The transformlist to check
//
// Returns:
// Boolean on whether or not a matrix transform was found
svgedit.math.hasMatrixTransform = function(tlist) {
if(!tlist) return false;
var num = tlist.numberOfItems;
while (num--) {
var xform = tlist.getItem(num);
if (xform.type == 1 && !svgedit.math.isIdentity(xform.matrix)) return true;
}
return false;
};
// Function: svgedit.math.transformBox
// Transforms a rectangle based on the given matrix
//
// Parameters:
// l - Float with the box's left coordinate
// t - Float with the box's top coordinate
// w - Float with the box width
// h - Float with the box height
// m - Matrix object to transform the box by
//
// Returns:
// 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:
// * Float with the axis-aligned x coordinate
// * Float with the axis-aligned y coordinate
// * Float with the axis-aligned width coordinate
// * Float with the axis-aligned height coordinate
svgedit.math.transformBox = function(l, t, w, h, m) {
var topleft = {x:l,y:t},
topright = {x:(l+w),y:t},
botright = {x:(l+w),y:(t+h)},
botleft = {x:l,y:(t+h)};
var transformPoint = svgedit.math.transformPoint;
topleft = transformPoint( topleft.x, topleft.y, m );
var minx = topleft.x,
maxx = topleft.x,
miny = topleft.y,
maxy = topleft.y;
topright = transformPoint( topright.x, topright.y, m );
minx = Math.min(minx, topright.x);
maxx = Math.max(maxx, topright.x);
miny = Math.min(miny, topright.y);
maxy = Math.max(maxy, topright.y);
botleft = transformPoint( botleft.x, botleft.y, m);
minx = Math.min(minx, botleft.x);
maxx = Math.max(maxx, botleft.x);
miny = Math.min(miny, botleft.y);
maxy = Math.max(maxy, botleft.y);
botright = transformPoint( botright.x, botright.y, m );
minx = Math.min(minx, botright.x);
maxx = Math.max(maxx, botright.x);
miny = Math.min(miny, botright.y);
maxy = Math.max(maxy, botright.y);
return {tl:topleft, tr:topright, bl:botleft, br:botright,
aabox: {x:minx, y:miny, width:(maxx-minx), height:(maxy-miny)} };
};
// Function: svgedit.math.transformListToTransform
// 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
//
// Parameters:
// tlist - The transformlist object
// min - Optional integer indicating start transform position
// max - Optional integer indicating end transform position
//
// Returns:
// A single matrix transform object
svgedit.math.transformListToTransform = function(tlist, min, max) {
if(tlist == null) {
// Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
}
var min = min == undefined ? 0 : min;
var max = max == undefined ? (tlist.numberOfItems-1) : max;
min = parseInt(min);
max = parseInt(max);
if (min > max) { var temp = max; max = min; min = temp; }
var m = svg.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.createSVGMatrix());
m = svgedit.math.matrixMultiply(m, mtom);
}
return svg.createSVGTransformFromMatrix(m);
};
// Function: svgedit.math.getMatrix
// Get the matrix object for a given element
//
// Parameters:
// elem - The DOM element to check
//
// Returns:
// The matrix object associated with the element's transformlist
svgedit.math.getMatrix = function(elem) {
var tlist = svgedit.transformlist.getTransformList(elem);
return svgedit.math.transformListToTransform(tlist).matrix;
};
// Function: svgedit.math.snapToAngle
// Returns a 45 degree angle coordinate associated with the two given
// coordinates
//
// Parameters:
// x1 - First coordinate's x value
// x2 - Second coordinate's x value
// y1 - First coordinate's y value
// y2 - Second coordinate's y value
//
// Returns:
// Object with the following values:
// x - The angle-snapped x value
// y - The angle-snapped y value
// snapangle - The angle at which to snap
svgedit.math.snapToAngle = function(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;
var x = x1 + dist*Math.cos(snapangle);
var y = y1 + dist*Math.sin(snapangle);
//console.log(x1,y1,x2,y2,x,y,angle)
return {x:x, y:y, a:snapangle};
};
// Function: rectsIntersect
// Check if two rectangles (BBoxes objects) intersect each other
//
// Paramaters:
// r1 - The first BBox-like object
// r2 - The second BBox-like object
//
// Returns:
// Boolean that's true if rectangles intersect
svgedit.math.rectsIntersect = function(r1, r2) {
if (!r1 || !r2) return false;
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;
};
})();
/**
* Package: svgedit.units
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
var svgedit = svgedit || {};
(function() {
if (!svgedit.units) {
svgedit.units = {};
}
var w_attrs = ['x', 'x1', 'cx', 'rx', 'width'];
var h_attrs = ['y', 'y1', 'cy', 'ry', 'height'];
var unit_attrs = $.merge(['r','radius'], w_attrs);
var unitNumMap = {
'%': 2,
'em': 3,
'ex': 4,
'px': 5,
'cm': 6,
'mm': 7,
'in': 8,
'pt': 9,
'pc': 10
};
$.merge(unit_attrs, h_attrs);
// Container of elements.
var elementContainer_;
/**
* Stores mapping of unit type to user coordinates.
*/
var typeMap_ = {px: 1};
/**
* 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
*/
/**
* Function: svgedit.units.init()
* Initializes this module.
*
* Parameters:
* elementContainer - an object implementing the ElementContainer interface.
*/
svgedit.units.init = function(elementContainer) {
elementContainer_ = elementContainer;
var svgns = 'http://www.w3.org/2000/svg';
// Get correct em/ex values by creating a temporary SVG.
var svg = document.createElementNS(svgns, 'svg');
document.body.appendChild(svg);
var rect = document.createElementNS(svgns,'rect');
rect.setAttribute('width',"1em");
rect.setAttribute('height',"1ex");
rect.setAttribute('x',"1in");
svg.appendChild(rect);
var bb = rect.getBBox();
document.body.removeChild(svg);
var inch = bb.x;
typeMap_['em'] = bb.width;
typeMap_['ex'] = bb.height;
typeMap_['in'] = inch;
typeMap_['cm'] = inch / 2.54;
typeMap_['mm'] = inch / 25.4;
typeMap_['pt'] = inch / 72;
typeMap_['pc'] = inch / 6;
typeMap_['%'] = 0;
};
// Group: Unit conversion functions
// Function: svgedit.units.getTypeMap
// Returns the unit object with values for each unit
svgedit.units.getTypeMap = function() {
return typeMap_;
};
// Function: svgedit.units.shortFloat
// Rounds a given value to a float with number of digits defined in save_options
//
// Parameters:
// 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-seperated floats
svgedit.units.shortFloat = function(val) {
var digits = elementContainer_.getRoundDigits();
if(!isNaN(val)) {
// Note that + converts to Number
return +((+val).toFixed(digits));
} else if($.isArray(val)) {
return svgedit.units.shortFloat(val[0]) + ',' + svgedit.units.shortFloat(val[1]);
}
return parseFloat(val).toFixed(digits) - 0;
};
// Function: svgedit.units.convertUnit
// Converts the number to given unit or baseUnit
svgedit.units.convertUnit = function(val, unit) {
unit = unit || elementContainer_.getBaseUnit();
// baseVal.convertToSpecifiedUnits(unitNumMap[unit]);
// var val = baseVal.valueInSpecifiedUnits;
// baseVal.convertToSpecifiedUnits(1);
2020-08-02 11:03:04 +00:00
return svgedit.units.shortFloat(val / typeMap_[unit]);
2020-08-02 10:26:22 +00:00
};
// Function: svgedit.units.setUnitAttr
// Sets an element's attribute based on the unit in its current value.
//
// Parameters:
// elem - DOM element to be changed
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to convert
svgedit.units.setUnitAttr = function(elem, attr, val) {
if(!isNaN(val)) {
// New value is a number, so check currently used unit
var old_val = elem.getAttribute(attr);
// Enable this for alternate mode
// if(old_val !== null && (isNaN(old_val) || elementContainer_.getBaseUnit() !== 'px')) {
// // Old value was a number, so get unit, then convert
// var unit;
// if(old_val.substr(-1) === '%') {
// var res = getResolution();
// unit = '%';
// val *= 100;
// if(w_attrs.indexOf(attr) >= 0) {
// val = val / res.w;
// } else if(h_attrs.indexOf(attr) >= 0) {
// 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 = old_val.substr(-2);
// }
// val = val / typeMap_[unit];
// }
//
// val += unit;
// }
}
elem.setAttribute(attr, val);
};
var attrsToConvert = {
"line": ['x1', 'x2', 'y1', 'y2'],
"circle": ['cx', 'cy', 'r'],
"ellipse": ['cx', 'cy', 'rx', 'ry'],
"foreignObject": ['x', 'y', 'width', 'height'],
"rect": ['x', 'y', 'width', 'height'],
"image": ['x', 'y', 'width', 'height'],
"use": ['x', 'y', 'width', 'height'],
"text": ['x', 'y']
};
// Function: svgedit.units.convertAttrs
// Converts all applicable attributes to the configured baseUnit
//
// Parameters:
// element - a DOM element whose attributes should be converted
svgedit.units.convertAttrs = function(element) {
var elName = element.tagName;
var unit = elementContainer_.getBaseUnit();
var attrs = attrsToConvert[elName];
if(!attrs) return;
var len = attrs.length
for(var i = 0; i < len; i++) {
var attr = attrs[i];
var cur = element.getAttribute(attr);
if(cur) {
if(!isNaN(cur)) {
element.setAttribute(attr, (cur / typeMap_[unit]) + unit);
} else {
// Convert existing?
}
}
}
};
// Function: svgedit.units.convertToNum
// Converts given values to numbers. Attributes must be supplied in
// case a percentage is given
//
// Parameters:
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to convert
svgedit.units.convertToNum = function(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(w_attrs.indexOf(attr) >= 0) {
return num * width;
} else if(h_attrs.indexOf(attr) >= 0) {
return num * height;
} else {
return num * Math.sqrt((width*width) + (height*height))/Math.sqrt(2);
}
} else {
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];
}
};
// Function: svgedit.units.isValidUnit
// Check if an attribute's value is in a valid format
//
// Parameters:
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to check
svgedit.units.isValidUnit = function(attr, val, selectedElement) {
var valid = false;
if(unit_attrs.indexOf(attr) >= 0) {
// 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();
$.each(typeMap_, 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 http://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;
} else {
valid = true;
}
return valid;
};
})();
/**
* Package: svgedit.utilities
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) svgtransformlist.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.utilities) {
svgedit.utilities = {};
}
// Constants
// String used to encode base64.
var KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var SVGNS = 'http://www.w3.org/2000/svg';
var XLINKNS = 'http://www.w3.org/1999/xlink';
var XMLNS = "http://www.w3.org/XML/1998/namespace";
// 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 visElems_arr = visElems.split(',');
//var hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
var editorContext_ = null;
var domdoc_ = null;
var domcontainer_ = null;
var svgroot_ = null;
svgedit.utilities.init = function(editorContext) {
editorContext_ = editorContext;
domdoc_ = editorContext.getDOMDocument();
domcontainer_ = editorContext.getDOMContainer();
svgroot_ = editorContext.getSVGRoot();
};
// Function: svgedit.utilities.toXml
// Converts characters in a string to XML-friendly entities.
//
// Example: "&" becomes "&amp;"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.utilities.toXml = function(str) {
return $('<p/>').text(str).html();
};
// Function: svgedit.utilities.fromXml
// Converts XML entities in a string to single characters.
// Example: "&amp;" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.utilities.fromXml = function(str) {
return $('<p/>').html(str).text();
};
// 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.
// Function: svgedit.utilities.encode64
// Converts a string to base64
svgedit.utilities.encode64 = function(input) {
// base64 strings are 4/3 larger than the original string
// input = svgedit.utilities.encodeUTF8(input); // convert non-ASCII characters
input = svgedit.utilities.convertToXMLReferences(input);
if(window.btoa) return window.btoa(input); // Use native if available
var output = new Array( Math.floor( (input.length + 2) / 3 ) * 4 );
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0, p = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
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('');
};
// Function: svgedit.utilities.decode64
// Converts a string from base64
svgedit.utilities.decode64 = function(input) {
if(window.atob) return window.atob(input);
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = KEYSTR.indexOf(input.charAt(i++));
enc2 = KEYSTR.indexOf(input.charAt(i++));
enc3 = KEYSTR.indexOf(input.charAt(i++));
enc4 = KEYSTR.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);
return unescape(output);
};
svgedit.utilities.dataURItoBlob = function(dataURI, mimeStr) {
var byteStr;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteStr = atob(dataURI.split(',')[1]);
else
byteStr = unescape(dataURI.split(',')[1]);
var mimeStr = dataURI.split(',')[0].split(':')[1].split(';')[0];
var arr= new Uint8Array(byteStr.length);
for (var i = 0; i < byteStr.length; i++) {
arr[i] = byteStr.charCodeAt(i);
}
return new Blob([arr], {type:mimeStr});
}
// Currently not being used, so commented out for now
// based on http://phpjs.org/functions/utf8_encode:577
// codedread:does not seem to work with webkit-based browsers on OSX
// "encodeUTF8": function(input) {
// //return unescape(encodeURIComponent(input)); //may or may not work
// var output = '';
// for (var n = 0; n < input.length; n++){
// var c = input.charCodeAt(n);
// if (c < 128) {
// output += input[n];
// }
// else if (c > 127) {
// if (c < 2048){
// output += String.fromCharCode((c >> 6) | 192);
// }
// else {
// output += String.fromCharCode((c >> 12) | 224) + String.fromCharCode((c >> 6) & 63 | 128);
// }
// output += String.fromCharCode((c & 63) | 128);
// }
// }
// return output;
// },
// Function: svgedit.utilities.convertToXMLReferences
// Converts a string to use XML references
svgedit.utilities.convertToXMLReferences = function(input) {
var output = '';
for (var n = 0; n < input.length; n++){
var c = input.charCodeAt(n);
if (c < 128) {
output += input[n];
} else if(c > 127) {
output += ("&#" + c + ";");
}
}
return output;
};
// Function: svgedit.utilities.text2xml
// 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
svgedit.utilities.text2xml = function(sXML) {
if(sXML.indexOf('<svg:svg') >= 0) {
sXML = sXML.replace(/<(\/?)svg:/g, '<$1').replace('xmlns:svg', 'xmlns');
}
var out;
try{
var dXML = (window.DOMParser)?new DOMParser():new ActiveXObject("Microsoft.XMLDOM");
dXML.async = false;
} catch(e){
throw new Error("XML Parser could not be instantiated");
};
try{
if(dXML.loadXML) out = (dXML.loadXML(sXML))?dXML:false;
else out = dXML.parseFromString(sXML, "text/xml");
}
catch(e){ throw new Error("Error parsing XML string"); };
return out;
};
// Function: svgedit.utilities.bboxToObj
// Converts a SVGRect into an object.
//
// Parameters:
// bbox - a SVGRect
//
// Returns:
// An object with properties names x, y, width, height.
svgedit.utilities.bboxToObj = function(bbox) {
return {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height
}
};
// Function: svgedit.utilities.walkTree
// Walks the tree and executes the callback on each element in a top-down fashion
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.utilities.walkTree = function(elem, cbFn){
if (elem && elem.nodeType == 1) {
cbFn(elem);
var i = elem.childNodes.length;
while (i--) {
svgedit.utilities.walkTree(elem.childNodes.item(i), cbFn);
}
}
};
// Function: svgedit.utilities.walkTreePost
// Walks the tree and executes the callback on each element in a depth-first fashion
// TODO: FIXME: Shouldn't this be calling walkTreePost?
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.utilities.walkTreePost = function(elem, cbFn) {
if (elem && elem.nodeType == 1) {
var i = elem.childNodes.length;
while (i--) {
svgedit.utilities.walkTree(elem.childNodes.item(i), cbFn);
}
cbFn(elem);
}
};
// Function: svgedit.utilities.getUrlFromAttr
// Extracts the URL from the url(...) syntax of some attributes.
// Three variants:
// * <circle fill="url(someFile.svg#foo)" />
// * <circle fill="url('someFile.svg#foo')" />
// * <circle fill='url("someFile.svg#foo")' />
//
// Parameters:
// attrVal - The attribute value as a string
//
// Returns:
// String with just the URL, like someFile.svg#foo
svgedit.utilities.getUrlFromAttr = function(attrVal) {
if (attrVal) {
// url("#somegrad")
if (attrVal.indexOf('url("') === 0) {
return attrVal.substring(5,attrVal.indexOf('"',6));
}
// url('#somegrad')
else if (attrVal.indexOf("url('") === 0) {
return attrVal.substring(5,attrVal.indexOf("'",6));
}
else if (attrVal.indexOf("url(") === 0) {
return attrVal.substring(4,attrVal.indexOf(')'));
}
}
return null;
};
// Function: svgedit.utilities.getHref
// Returns the given element's xlink:href value
svgedit.utilities.getHref = function(elem) {
if (elem) return elem.getAttributeNS(XLINKNS, "href");
}
// Function: svgedit.utilities.setHref
// Sets the given element's xlink:href value
svgedit.utilities.setHref = function(elem, val) {
elem.setAttributeNS(XLINKNS, "xlink:href", val);
}
// Function: findDefs
// Parameters:
// svgElement - The <svg> element.
//
// Returns:
// The document's <defs> element, create it first if necessary
svgedit.utilities.findDefs = function(svgElement) {
var svgElement = editorContext_.getSVGContent().documentElement;
var defs = svgElement.getElementsByTagNameNS(SVGNS, "defs");
if (defs.length > 0) {
defs = defs[0];
}
else {
// first child is a comment, so call nextSibling
defs = svgElement.insertBefore( svgElement.ownerDocument.createElementNS(SVGNS, "defs" ), svgElement.firstChild.nextSibling);
}
return defs;
};
// TODO(codedread): Consider moving the next to functions to bbox.js
// Function: svgedit.utilities.getPathBBox
// 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
//
// Parameters:
// path - The path DOM element to get the BBox for
//
// Returns:
// A BBox-like object
svgedit.utilities.getPathBBox = function(path) {
var seglist = path.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(typeof seg.x == 'undefined') continue;
// Add actual points to limits
bounds[0].push(P0[0]);
bounds[1].push(P0[1]);
if(seg.x1) {
var P1 = [seg.x1, seg.y1],
P2 = [seg.x2, seg.y2],
P3 = [seg.x, seg.y];
for(var j=0; j < 2; j++) {
var calc = function(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) {
continue;
}
var t = -c / b;
if(0 < t && t < 1) {
bounds[j].push(calc(t));
}
continue;
}
var b2ac = Math.pow(b,2) - 4 * c * a;
if(b2ac < 0) continue;
var t1 = (-b + Math.sqrt(b2ac))/(2 * a);
if(0 < t1 && t1 < 1) bounds[j].push(calc(t1));
var t2 = (-b - Math.sqrt(b2ac))/(2 * a);
if(0 < t2 && t2 < 1) bounds[j].push(calc(t2));
}
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
};
};
// Function: groupBBFix
// 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.
//
// Parameters:
// selected - Container or <use> DOM element
function groupBBFix(selected) {
if(svgedit.browser.supportsHVLineContainerBBox()) {
try { return selected.getBBox();} catch(e){}
}
var ref = $.data(selected, 'ref');
var matched = null;
if(ref) {
var copy = $(ref).children().clone().attr('visibility', 'hidden');
$(svgroot_).append(copy);
matched = copy.filter('line, path');
} else {
matched = $(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 : $(selected).children();
ret = getStrokedBBox(elems);
} else {
ret = selected.getBBox();
}
} else {
ret = selected.getBBox();
}
if(ref) {
copy.remove();
}
return ret;
}
// Function: svgedit.utilities.getBBox
// Get the given/selected element's bounding box object, convert it to be more
// usable when necessary
//
// Parameters:
// elem - Optional DOM element to get the BBox for
svgedit.utilities.getBBox = function(elem) {
var selected = elem || editorContext_.getSelectedElements()[0];
if (elem.nodeType != 1) return null;
var ret = null;
var elname = selected.nodeName;
switch ( elname ) {
case 'text':
if(selected.textContent === '') {
selected.textContent = 'a'; // Some character needed for the selector to use.
ret = selected.getBBox();
selected.textContent = '';
} else {
try { ret = selected.getBBox();} catch(e){}
}
break;
case 'path':
if(!svgedit.browser.supportsPathBBox()) {
ret = svgedit.utilities.getPathBBox(selected);
} else {
try { ret = selected.getBBox();} catch(e){}
}
break;
case 'g':
case 'a':
ret = groupBBFix(selected);
break;
default:
if(elname === 'use') {
ret = groupBBFix(selected, true);
}
if(elname === 'use') {
if(!ret) ret = selected.getBBox();
//if(!svgedit.browser.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(~visElems_arr.indexOf(elname)) {
try { ret = selected.getBBox();}
catch(e) {
// Check if element is child of a foreignObject
var fo = $(selected).closest("foreignObject");
if(fo.length) {
try {
ret = fo[0].getBBox();
} catch(e) {
ret = null;
}
} else {
ret = null;
}
}
}
}
if(ret) {
ret = svgedit.utilities.bboxToObj(ret);
}
// get the bounding box from the DOM (which is in that element's coordinate system)
return ret;
};
// Function: svgedit.utilities.getRotationAngle
// Get the rotation angle of the given/selected DOM element
//
// Parameters:
// elem - Optional DOM element to get the angle for
// to_rad - Boolean that when true returns the value in radians rather than degrees
//
// Returns:
// Float with the angle in degrees or radians
svgedit.utilities.getRotationAngle = function(elem, to_rad) {
var selected = elem || editorContext_.getSelectedElements()[0];
// find the rotation transform (if any) and set it
var tlist = svgedit.transformlist.getTransformList(selected);
if(!tlist) return 0; // <svg> 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 to_rad ? xform.angle * Math.PI / 180.0 : xform.angle;
}
}
return 0.0;
};
// Function: getElem
// Get a DOM element by ID within the SVG root element.
//
// Parameters:
// id - String with the element's new ID
if (svgedit.browser.supportsSelectors()) {
svgedit.utilities.getElem = function(id) {
// querySelector lookup
return svgroot_.querySelector('#'+id);
};
} else if (svgedit.browser.supportsXpath()) {
svgedit.utilities.getElem = function(id) {
// xpath lookup
return domdoc_.evaluate(
'svg:svg[@id="svgroot"]//svg:*[@id="'+id+'"]',
domcontainer_,
function() { return "http://www.w3.org/2000/svg"; },
9,
null).singleNodeValue;
};
} else {
svgedit.utilities.getElem = function(id) {
// jQuery lookup: twice as slow as xpath in FF
return $(svgroot_).find('[id=' + id + ']')[0];
};
}
// Function: assignAttributes
// Assigns multiple attributes to an element.
//
// Parameters:
// node - DOM element to apply new attribute values to
// attrs - Object with attribute keys/values
// suspendLength - Optional integer of milliseconds to suspend redraw
// unitCheck - Boolean to indicate the need to use svgedit.units.setUnitAttr
svgedit.utilities.assignAttributes = function(node, attrs, suspendLength, unitCheck) {
for (var i in attrs) {
var ns = (i.substr(0,4) === "xml:" ? XMLNS :
i.substr(0,6) === "xlink:" ? XLINKNS : null);
if(ns) {
node.setAttributeNS(ns, i, attrs[i]);
} else if(!unitCheck) {
node.setAttribute(i, attrs[i]);
} else {
svgedit.units.setUnitAttr(node, i, attrs[i]);
}
}
};
// Function: cleanupElement
// Remove unneeded (default) attributes, makes resulting SVG smaller
//
// Parameters:
// element - DOM element to clean up
svgedit.utilities.cleanupElement = function(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
}
for(var attr in defaults) {
var val = defaults[attr];
if(element.getAttribute(attr) == val) {
element.removeAttribute(attr);
}
}
};
})();
/**
* Package: svgedit.sanitize
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) browser.js
// 2) svgutils.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.sanitize) {
svgedit.sanitize = {};
}
// Namespace constants
var svgns = "http://www.w3.org/2000/svg",
xlinkns = "http://www.w3.org/1999/xlink",
xmlns = "http://www.w3.org/XML/1998/namespace",
xmlnsns = "http://www.w3.org/2000/xmlns/", // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
se_ns = "http://svg-edit.googlecode.com",
htmlns = "http://www.w3.org/1999/xhtml",
mathns = "http://www.w3.org/1998/Math/MathML";
// map namespace URIs to prefixes
var nsMap_ = {};
nsMap_[xlinkns] = 'xlink';
nsMap_[xmlns] = 'xml';
nsMap_[xmlnsns] = 'xmlns';
nsMap_[se_ns] = 'se';
nsMap_[htmlns] = 'xhtml';
nsMap_[mathns] = 'mathml';
// map prefixes to namespace URIs
var nsRevMap_ = {};
$.each(nsMap_, function(key,value){
nsRevMap_[value] = key;
});
// 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", "data-locked"],
"image": ["class", "clip-path", "clip-rule", "filter", "height", "id", "mask", "opacity", "requiredFeatures", "style", "systemLanguage", "transform", "width", "x", "xlink:href", "xlink:title", "y"],
"line": ["shape-rendering", "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": ["shape-rendering", "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_ = {};
$.each(svgWhiteList_, function(elt,atts){
var attNS = {};
$.each(atts, function(i, att){
if (att.indexOf(':') >= 0) {
var v = att.split(':');
attNS[v[1]] = nsRevMap_[v[0]];
} else {
attNS[att] = att == 'xmlns' ? xmlnsns : null;
}
});
svgWhiteListNS_[elt] = attNS;
});
// temporarily expose these
svgedit.sanitize.getNSMap = function() { return nsMap_; }
// Function: svgedit.sanitize.sanitizeSvg
// Sanitizes the input node and its children
// It only keeps what is allowed from our whitelist defined above
//
// Parameters:
// node - The DOM element to be checked, will also check its children
svgedit.sanitize.sanitizeSvg = function(node) {
// we only care about element nodes
// automatically return for all comment, etc nodes
// for text, we do a whitespace trim
if (node.nodeType == 3) {
node.nodeValue = node.nodeValue.replace(/^\s+|\s+$/g, "");
// Remove empty text nodes
if(!node.nodeValue.length) node.parentNode.removeChild(node);
}
if (node.nodeType != 1) 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 allowed
if (allowedAttrs != undefined) {
var se_attrs = [];
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 != xmlnsns) &&
!(attrNsURI == xmlnsns && nsMap_[attr.nodeValue]) )
{
// 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.indexOf('se:') == 0) {
se_attrs.push([attrName, attr.nodeValue]);
}
node.removeAttributeNS(attrNsURI, attrLocalName);
}
// Add spaces before negative signs where necessary
if(svgedit.browser.isGecko()) {
switch ( attrName ) {
case "transform":
case "gradientTransform":
case "patternTransform":
var val = attr.nodeValue.replace(/(\d)-/g, "$1 -");
node.setAttribute(attrName, val);
}
}
// for the style attribute, rewrite it in terms of XML presentational attributes
if (attrName == "style") {
var props = attr.nodeValue.split(";"),
p = props.length;
while(p--) {
var nv = props[p].split(":");
// now check that this attribute is supported
if (allowedAttrs.indexOf(nv[0]) >= 0) {
node.setAttribute(nv[0],nv[1]);
}
}
node.removeAttribute('style');
}
}
$.each(se_attrs, function(i, attr) {
node.setAttributeNS(se_ns, attr[0], attr[1]);
});
// for some elements that have a xlink:href, ensure the URI refers to a local element
// (but not for links)
var href = svgedit.utilities.getHref(node);
if(href &&
["filter", "linearGradient", "pattern",
"radialGradient", "textPath", "use"].indexOf(node.nodeName) >= 0)
{
// TODO: we simply check if the first character is a #, is this bullet-proof?
if (href[0] != "#") {
// remove the attribute (but keep the element)
svgedit.utilities.setHref(node, "");
node.removeAttributeNS(xlinkns, "href");
}
}
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
if (node.nodeName == "use" && !svgedit.utilities.getHref(node)) {
parent.removeChild(node);
return;
}
// if the element has attributes pointing to a non-local reference,
// need to remove the attribute
$.each(["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"],function(i,attr) {
var val = node.getAttribute(attr);
if (val) {
val = svgedit.utilities.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--) { svgedit.sanitize.sanitizeSvg(node.childNodes.item(i)); }
}
// else, remove this element
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
parent.removeChild(node);
// call sanitizeSvg on each of those children
var i = children.length;
while (i--) { svgedit.sanitize.sanitizeSvg(children[i]); }
}
};
})();
/**
* Package: svedit.history
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
// 2) svgtransformlist.js
// 3) svgutils.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.history) {
svgedit.history = {};
}
// Group: Undo/Redo history management
svgedit.history.HistoryEventTypes = {
BEFORE_APPLY: 'before_apply',
AFTER_APPLY: 'after_apply',
BEFORE_UNAPPLY: 'before_unapply',
AFTER_UNAPPLY: 'after_unapply'
};
var removedElements = {};
/**
* Interface: svgedit.history.HistoryCommand
* An interface that all command objects must implement.
*
* interface 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
//
// Parameters:
// elem - The DOM element that was moved
// oldNextSibling - The element's next sibling before it was moved
// oldParent - The element's parent before it was moved
// text - An optional string visible to user related to this change
svgedit.history.MoveElementCommand = function(elem, oldNextSibling, oldParent, text) {
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;
};
svgedit.history.MoveElementCommand.type = function() { return 'svgedit.history.MoveElementCommand'; }
svgedit.history.MoveElementCommand.prototype.type = svgedit.history.MoveElementCommand.type;
// Function: svgedit.history.MoveElementCommand.getText
svgedit.history.MoveElementCommand.prototype.getText = function() {
return this.text;
};
// Function: svgedit.history.MoveElementCommand.apply
// Re-positions the element
svgedit.history.MoveElementCommand.prototype.apply = function(handler) {
// TODO(codedread): Refactor this common event code into a base HistoryCommand class.
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
}
this.elem = this.newParent.insertBefore(this.elem, this.newNextSibling);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
}
};
// Function: svgedit.history.MoveElementCommand.unapply
// Positions the element back to its original location
svgedit.history.MoveElementCommand.prototype.unapply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
}
this.elem = this.oldParent.insertBefore(this.elem, this.oldNextSibling);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
}
};
// Function: svgedit.history.MoveElementCommand.elements
// Returns array with element associated with this command
svgedit.history.MoveElementCommand.prototype.elements = function() {
return [this.elem];
};
// Class: svgedit.history.InsertElementCommand
// implements svgedit.history.HistoryCommand
// History command for an element that was added to the DOM
//
// Parameters:
// elem - The newly added DOM element
// text - An optional string visible to user related to this change
svgedit.history.InsertElementCommand = function(elem, text) {
this.elem = elem;
this.text = text || ("Create " + elem.tagName);
this.parent = elem.parentNode;
this.nextSibling = this.elem.nextSibling;
};
svgedit.history.InsertElementCommand.type = function() { return 'svgedit.history.InsertElementCommand'; }
svgedit.history.InsertElementCommand.prototype.type = svgedit.history.InsertElementCommand.type;
// Function: svgedit.history.InsertElementCommand.getText
svgedit.history.InsertElementCommand.prototype.getText = function() {
return this.text;
};
// Function: svgedit.history.InsertElementCommand.apply
// Re-Inserts the new element
svgedit.history.InsertElementCommand.prototype.apply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
}
this.elem = this.parent.insertBefore(this.elem, this.nextSibling);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
}
};
// Function: svgedit.history.InsertElementCommand.unapply
// Removes the element
svgedit.history.InsertElementCommand.prototype.unapply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
}
this.parent = this.elem.parentNode;
this.elem = this.elem.parentNode.removeChild(this.elem);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
}
};
// Function: svgedit.history.InsertElementCommand.elements
// Returns array with element associated with this command
svgedit.history.InsertElementCommand.prototype.elements = function() {
return [this.elem];
};
// Class: svgedit.history.RemoveElementCommand
// implements svgedit.history.HistoryCommand
// History command for an element removed from the DOM
//
// Parameters:
// elem - The removed DOM element
// oldNextSibling - the DOM element's nextSibling when it was in the DOM
// oldParent - The DOM element's parent
// text - An optional string visible to user related to this change
svgedit.history.RemoveElementCommand = function(elem, oldNextSibling, oldParent, text) {
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
svgedit.transformlist.removeElementFromListMap(elem);
};
svgedit.history.RemoveElementCommand.type = function() { return 'svgedit.history.RemoveElementCommand'; }
svgedit.history.RemoveElementCommand.prototype.type = svgedit.history.RemoveElementCommand.type;
// Function: svgedit.history.RemoveElementCommand.getText
svgedit.history.RemoveElementCommand.prototype.getText = function() {
return this.text;
};
// Function: RemoveElementCommand.apply
// Re-removes the new element
svgedit.history.RemoveElementCommand.prototype.apply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
}
svgedit.transformlist.removeElementFromListMap(this.elem);
this.parent = this.elem.parentNode;
this.elem = this.parent.removeChild(this.elem);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
}
};
// Function: RemoveElementCommand.unapply
// Re-adds the new element
svgedit.history.RemoveElementCommand.prototype.unapply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
}
svgedit.transformlist.removeElementFromListMap(this.elem);
if(this.nextSibling == null) {
if(window.console) console.log('Error: reference element was lost');
}
this.parent.insertBefore(this.elem, this.nextSibling);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
}
};
// Function: RemoveElementCommand.elements
// Returns array with element associated with this command
svgedit.history.RemoveElementCommand.prototype.elements = function() {
return [this.elem];
};
// Class: svgedit.history.ChangeElementCommand
// implements svgedit.history.HistoryCommand
// History command to make a change to an element.
// Usually an attribute change, but can also be textcontent.
//
// Parameters:
// elem - The DOM element that was changed
// attrs - An object with the attributes to be changed and the values they had *before* the change
// text - An optional string visible to user related to this change
svgedit.history.ChangeElementCommand = function(elem, attrs, text) {
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] = svgedit.utilities.getHref(elem);
else this.newValues[attr] = elem.getAttribute(attr);
}
};
svgedit.history.ChangeElementCommand.type = function() { return 'svgedit.history.ChangeElementCommand'; }
svgedit.history.ChangeElementCommand.prototype.type = svgedit.history.ChangeElementCommand.type;
// Function: svgedit.history.ChangeElementCommand.getText
svgedit.history.ChangeElementCommand.prototype.getText = function() {
return this.text;
};
// Function: svgedit.history.ChangeElementCommand.apply
// Performs the stored change action
svgedit.history.ChangeElementCommand.prototype.apply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.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") svgedit.utilities.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 = svgedit.utilities.getRotationAngle(this.elem);
if (angle) {
var bbox = elem.getBBox();
var cx = bbox.x + bbox.width/2,
cy = bbox.y + bbox.height/2;
var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
if (rotate != elem.getAttribute("transform")) {
elem.setAttribute("transform", rotate);
}
}
}
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
}
return true;
};
// Function: svgedit.history.ChangeElementCommand.unapply
// Reverses the stored change action
svgedit.history.ChangeElementCommand.prototype.unapply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.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") svgedit.utilities.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 = svgedit.utilities.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.
svgedit.transformlist.removeElementFromListMap(this.elem);
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
}
return true;
};
// Function: ChangeElementCommand.elements
// Returns array with element associated with this command
svgedit.history.ChangeElementCommand.prototype.elements = function() {
return [this.elem];
};
// 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
// Class: svgedit.history.BatchCommand
// implements svgedit.history.HistoryCommand
// History command that can contain/execute multiple other commands
//
// Parameters:
// text - An optional string visible to user related to this change
svgedit.history.BatchCommand = function(text) {
this.text = text || "Batch Command";
this.stack = [];
};
svgedit.history.BatchCommand.type = function() { return 'svgedit.history.BatchCommand'; }
svgedit.history.BatchCommand.prototype.type = svgedit.history.BatchCommand.type;
// Function: svgedit.history.BatchCommand.getText
svgedit.history.BatchCommand.prototype.getText = function() {
return this.text;
};
// Function: svgedit.history.BatchCommand.apply
// Runs "apply" on all subcommands
svgedit.history.BatchCommand.prototype.apply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.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(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
}
};
// Function: svgedit.history.BatchCommand.unapply
// Runs "unapply" on all subcommands
svgedit.history.BatchCommand.prototype.unapply = function(handler) {
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
}
for (var i = this.stack.length-1; i >= 0; i--) {
this.stack[i].unapply(handler);
}
if (handler) {
handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
}
};
// Function: svgedit.history.BatchCommand.elements
// Iterate through all our subcommands and returns all the elements we are changing
svgedit.history.BatchCommand.prototype.elements = function() {
var elems = [];
var cmd = this.stack.length;
while (cmd--) {
var thisElems = this.stack[cmd].elements();
var elem = thisElems.length;
while (elem--) {
if (elems.indexOf(thisElems[elem]) == -1) elems.push(thisElems[elem]);
}
}
return elems;
};
// Function: svgedit.history.BatchCommand.addSubCommand
// Adds a given command to the history stack
//
// Parameters:
// cmd - The undo command object to add
svgedit.history.BatchCommand.prototype.addSubCommand = function(cmd) {
this.stack.push(cmd);
};
// Function: svgedit.history.BatchCommand.isEmpty
// Returns a boolean indicating whether or not the batch command is empty
svgedit.history.BatchCommand.prototype.isEmpty = function() {
return this.stack.length == 0;
};
// Class: svgedit.history.UndoManager
// Parameters:
// historyEventHandler - an object that conforms to the HistoryEventHandler interface
// (see above)
svgedit.history.UndoManager = function(historyEventHandler) {
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 = [];
};
// Function: svgedit.history.UndoManager.resetUndoStack
// Resets the undo stack, effectively clearing the undo/redo history
svgedit.history.UndoManager.prototype.resetUndoStack = function() {
this.undoStack = [];
this.undoStackPointer = 0;
};
// Function: svgedit.history.UndoManager.getUndoStackSize
// Returns:
// Integer with the current size of the undo history stack
svgedit.history.UndoManager.prototype.getUndoStackSize = function() {
return this.undoStackPointer;
};
// Function: svgedit.history.UndoManager.getRedoStackSize
// Returns:
// Integer with the current size of the redo history stack
svgedit.history.UndoManager.prototype.getRedoStackSize = function() {
return this.undoStack.length - this.undoStackPointer;
};
// Function: svgedit.history.UndoManager.getNextUndoCommandText
// Returns:
// String associated with the next undo command
svgedit.history.UndoManager.prototype.getNextUndoCommandText = function() {
return this.undoStackPointer > 0 ? this.undoStack[this.undoStackPointer-1].getText() : "";
};
// Function: svgedit.history.UndoManager.getNextRedoCommandText
// Returns:
// String associated with the next redo command
svgedit.history.UndoManager.prototype.getNextRedoCommandText = function() {
return this.undoStackPointer < this.undoStack.length ? this.undoStack[this.undoStackPointer].getText() : "";
};
// Function: svgedit.history.UndoManager.undo
// Performs an undo step
svgedit.history.UndoManager.prototype.undo = function() {
if (this.undoStackPointer > 0) {
var cmd = this.undoStack[--this.undoStackPointer];
cmd.unapply(this.handler_);
}
};
// Function: svgedit.history.UndoManager.redo
// Performs a redo step
svgedit.history.UndoManager.prototype.redo = function() {
if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
var cmd = this.undoStack[this.undoStackPointer++];
cmd.apply(this.handler_);
}
};
// Function: svgedit.history.UndoManager.addCommandToHistory
// Adds a command object to the undo history stack
//
// Parameters:
// cmd - The command object to add
svgedit.history.UndoManager.prototype.addCommandToHistory = function(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;
};
// Function: svgedit.history.UndoManager.beginUndoableChange
// 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.
//
// Parameters:
// attrName - The name of the attribute being changed
// elems - Array of DOM elements being changed
svgedit.history.UndoManager.prototype.beginUndoableChange = function(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};
};
// Function: svgedit.history.UndoManager.finishUndoableChange
// 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
svgedit.history.UndoManager.prototype.finishUndoableChange = function() {
var p = this.undoChangeStackPointer--;
var changeset = this.undoableChangeStack[p];
var i = changeset['elements'].length;
var attrName = changeset['attrName'];
var batchCmd = new svgedit.history.BatchCommand("Change " + attrName);
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 svgedit.history.ChangeElementCommand(elem, changes, attrName));
}
}
this.undoableChangeStack[p] = null;
return batchCmd;
};
})();
/**
* Package: svedit.select
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) math.js
// 4) svgutils.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.select) {
svgedit.select = {};
}
var svgFactory_;
var config_;
var selectorManager_; // A Singleton
// Class: svgedit.select.Selector
// Private class for DOM element selection boxes
//
// Parameters:
// id - integer to internally indentify the selector
// elem - DOM element associated with this selector
svgedit.select.Selector = function(id, elem) {
// 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 <g> 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': '#4F80FF',
'stroke-width': '1',
'shape-rendering': 'crispEdges',
'style': 'pointer-events:none'
}
})
);
if (svgedit.browser.isTouch()) {
this.selectorRect.setAttribute("stroke-opacity", 0.3);
}
// 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);
};
// Function: svgedit.select.Selector.reset
// Used to reset the id and element that the selector is attached to
//
// Parameters:
// e - DOM element associated with this selector
svgedit.select.Selector.prototype.reset = function(e) {
this.locked = true;
this.selectedElement = e;
this.resize();
this.selectorGroup.setAttribute('display', 'inline');
};
// Function: svgedit.select.Selector.updateGripCursors
// Updates cursors for corner grips on rotation so arrows point the right way
//
// Parameters:
// angle - Float indicating current rotation angle in degrees
svgedit.select.Selector.prototype.updateGripCursors = function(angle) {
var dir_arr = [];
var steps = Math.round(angle / 45);
if(steps < 0) steps += 8;
for (var dir in selectorManager_.selectorGrips) {
dir_arr.push(dir);
}
while(steps > 0) {
dir_arr.push(dir_arr.shift());
steps--;
}
var i = 0;
for (var dir in selectorManager_.selectorGrips) {
selectorManager_.selectorGrips[dir].setAttribute('style', ('cursor:' + dir_arr[i] + '-resize'));
i++;
};
};
// Function: svgedit.select.Selector.showGrips
// Show the resize grips of this selector
//
// Parameters:
// show - boolean indicating whether grips should be shown or not
svgedit.select.Selector.prototype.showGrips = function(show) {
var bShow = show ? 'inline' : 'none';
selectorManager_.selectorGripsGroup.setAttribute('display', bShow);
var elem = this.selectedElement;
this.hasGrips = show;
if(elem && show) {
this.selectorGroup.appendChild(selectorManager_.selectorGripsGroup);
this.updateGripCursors(svgedit.utilities.getRotationAngle(elem));
}
};
// Function: svgedit.select.Selector.resize
// Updates the selector to match the element's size
svgedit.select.Selector.prototype.resize = function() {
var selectedBox = this.selectorRect,
mgr = selectorManager_,
selectedGrips = mgr.selectorGrips,
selected = this.selectedElement,
sw = selected.getAttribute('stroke-width'),
current_zoom = svgFactory_.currentZoom();
var offset = 1/current_zoom;
if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
offset += (sw/2);
}
var tagName = selected.tagName;
if (tagName === 'text') {
offset += 2/current_zoom;
}
// loop and transform our bounding box until we reach our first rotation
var tlist = svgedit.transformlist.getTransformList(selected);
var m = svgedit.math.transformListToTransform(tlist).matrix;
// This should probably be handled somewhere else, but for now
// it keeps the selection box correctly positioned when zoomed
m.e *= current_zoom;
m.f *= current_zoom;
var bbox = svgedit.utilities.getBBox(selected);
if(tagName === 'g' && !$.data(selected, 'gsvg')) {
// The bbox for a group does not include stroke vals, so we
// get the bbox based on its children.
var stroked_bbox = svgFactory_.getStrokedBBox(selected.childNodes);
if(stroked_bbox) {
bbox = stroked_bbox;
}
}
// 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 *= current_zoom;
var nbox = svgedit.math.transformBox(l*current_zoom, t*current_zoom, w*current_zoom, h*current_zoom, m),
aabox = nbox.aabox,
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 = svgedit.utilities.getRotationAngle(selected);
if (angle) {
var rot = svgFactory_.svgRoot().createSVGTransform();
rot.setRotate(-angle,cx,cy);
var rotm = rot.matrix;
nbox.tl = svgedit.math.transformPoint(nbox.tl.x,nbox.tl.y,rotm);
nbox.tr = svgedit.math.transformPoint(nbox.tr.x,nbox.tr.y,rotm);
nbox.bl = svgedit.math.transformPoint(nbox.bl.x,nbox.bl.y,rotm);
nbox.br = svgedit.math.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);
if(svgedit.browser.isTouch()) {
nbax -= 15.75;
nbay -= 15.75;
}
else {
nbax -= 4;
nbay -= 4;
}
this.gripCoords = {
'nw': [nbax, nbay].map(Math.round),
'ne': [nbax+nbaw, nbay].map(Math.round),
'sw': [nbax, nbay+nbah].map(Math.round),
'se': [nbax+nbaw, nbay+nbah].map(Math.round),
'n': [nbax + (nbaw)/2, nbay].map(Math.round),
'w': [nbax, nbay + (nbah)/2].map(Math.round),
'e': [nbax + nbaw, nbay + (nbah)/2].map(Math.round),
's': [nbax + (nbaw)/2, nbay + nbah].map(Math.round)
};
for(var dir in this.gripCoords) {
var coords = this.gripCoords[dir];
selectedGrips[dir].setAttribute('x', coords[0]);
selectedGrips[dir].setAttribute('y', coords[1]);
};
this.rotateCoords = {
'nw': [nbax, nbay],
'ne': [nbax+nbaw+8, nbay],
'sw': [nbax, nbay+nbah+8],
'se': [nbax+nbaw+8, nbay+nbah+8]
};
for(var dir in this.rotateCoords) {
var coords = this.rotateCoords[dir];
mgr.rotateGrips[dir].setAttribute('cx', coords[0]);
mgr.rotateGrips[dir].setAttribute('cy', coords[1]);
}
};
// Class: svgedit.select.SelectorManager
svgedit.select.SelectorManager = function() {
// this will hold the <g> 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 svgedit.select.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.rotateGrips = {
'nw': null,
'ne': null,
'se': null,
'sw': null
};
this.initGroup();
};
// Function: svgedit.select.SelectorManager.initGroup
// Resets the parent selector group element
svgedit.select.SelectorManager.prototype.initGroup = function() {
// remove old selector parent group if it existed
if (this.selectorParentGroup && this.selectorParentGroup.parentNode) {
this.selectorParentGroup.parentNode.removeChild(this.selectorParentGroup);
}
// 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.appendChild(this.selectorGripsGroup);
svgFactory_.svgRoot().appendChild(this.selectorParentGroup);
this.selectorMap = {};
this.selectors = [];
this.rubberBandBox = null;
for (var dir in this.rotateGrips) {
var grip = svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': 'selectorGrip_rotate_' + dir,
'fill': '#000',
'r': 8,
'stroke': '#000',
"fill-opacity": 0,
"stroke-opacity": 0,
'stroke-width': 0,
'style': 'cursor:url(' + config_.imgPath + 'rotate.png) 12 12, auto;'
}
})
$.data(grip, 'dir', dir);
$.data(grip, 'type', 'rotate');
this.rotateGrips[dir] = this.selectorGripsGroup.appendChild(grip);
}
// add the corner grips
for (var dir in this.selectorGrips) {
var grip = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'width': 8,
'height': 8,
'fill': "#4F80FF",
'stroke': "rgba(0,0,0,0)",
'stroke-width': 1,
'style': ('cursor:' + dir + '-resize'),
'pointer-events': 'all'
}
});
if (svgedit.browser.isTouch()) {
grip.setAttribute("width", 30.5)
grip.setAttribute("height", 30.5)
grip.setAttribute("fill-opacity", 0.3)
}
$.data(grip, 'dir', dir);
$.data(grip, 'type', 'resize');
this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip);
}
if($('#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': (svgedit.browser.isWebkit() ? 'none' : 'visible'), // Chrome 7 has a problem with this when zooming out
'style': 'pointer-events:none'
}
});
var defs = svgFactory_.createSVGElement({
'element': 'defs',
'attr': {
'id': 'placeholder_defs'
}
})
var pattern = svgFactory_.createSVGElement({
'element': 'pattern',
'attr': {
'id': 'checkerPattern',
'patternUnits': 'userSpaceOnUse',
'x': 0,
'y': 0,
'width': 20,
'height': 20,
'viewBox': '0 0 10 10'
}
})
var pattern_bg = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'x': 0,
'y': 0,
'width': 10,
'height': 10,
'fill': "#fff"
}
})
var pattern_square1 = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'x': 0,
'y': 0,
'width': 5,
'height': 5,
'fill': "#eee"
}
})
var pattern_square2 = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'x': 5,
'y': 5,
'width': 5,
'height': 5,
'fill': "#eee"
}
})
var rect = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'width': '100%',
'height': '100%',
'x': 0,
'y': 0,
'stroke-width': 1,
'stroke': '#000',
'fill': 'url(#checkerPattern)',
'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 (!svgedit.browser.isOpera()) rect.setAttribute('filter', 'url(#canvashadow)');
canvasbg.appendChild(defs);
defs.appendChild(pattern);
pattern.appendChild(pattern_bg);
pattern.appendChild(pattern_square1);
pattern.appendChild(pattern_square2);
canvasbg.appendChild(rect);
svgFactory_.svgRoot().insertBefore(canvasbg, svgFactory_.svgContent());
};
// Function: svgedit.select.SelectorManager.requestSelector
// Returns the selector based on the given element
//
// Parameters:
// elem - DOM element to get the selector for
svgedit.select.SelectorManager.prototype.requestSelector = function(elem) {
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);
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 svgedit.select.Selector(N, elem);
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
this.selectorMap[elem.id] = this.selectors[N];
return this.selectors[N];
};
// Function: svgedit.select.SelectorManager.releaseSelector
// Removes the selector of the given element (hides selection box)
//
// Parameters:
// elem - DOM element to remove the selector for
svgedit.select.SelectorManager.prototype.releaseSelector = function(elem) {
if (elem == null) return;
var N = this.selectors.length,
sel = this.selectorMap[elem.id];
for (var i = 0; i < N; ++i) {
if (this.selectors[i] && this.selectors[i] == sel) {
if (sel.locked == false) {
// TODO(codedread): Ensure this exists in this module.
console.log('WARNING! selector was released but was already unlocked');
}
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;
}
}
};
// Function: svgedit.select.SelectorManager.getRubberBandBox
// Returns the rubberBandBox DOM element. This is the rectangle drawn by the user for selecting/zooming
svgedit.select.SelectorManager.prototype.getRubberBandBox = function() {
if (!this.rubberBandBox) {
this.rubberBandBox = this.selectorParentGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'id': 'selectorRubberBand',
'fill': 'none',
'stroke': '#666',
'stroke-width': 1,
'stroke-dasharray': '3,2',
'display': 'none',
'style': 'pointer-events:none'
}
})
);
}
return this.rubberBandBox;
};
/**
* Interface: svgedit.select.SVGFactory
* An object that creates SVG elements for the canvas.
*
* interface svgedit.select.SVGFactory {
* SVGElement createSVGElement(jsonMap);
* SVGSVGElement svgRoot();
* SVGSVGElement svgContent();
*
* Number currentZoom();
* Object getStrokedBBox(Element[]); // TODO(codedread): Remove when getStrokedBBox() has been put into svgutils.js
* }
*/
/**
* Function: svgedit.select.init()
* Initializes this module.
*
* Parameters:
* config - an object containing configurable parameters (imgPath)
* svgFactory - an object implementing the SVGFactory interface (see above).
*/
svgedit.select.init = function(config, svgFactory) {
config_ = config;
svgFactory_ = svgFactory;
selectorManager_ = new svgedit.select.SelectorManager();
//for hovering elements
svgFactory_.createSVGElement({
'element': 'g',
'attr': {
'id': 'hover_group'
}
})
};
/**
* Function: svgedit.select.getSelectorManager
*
* Returns:
* The SelectorManager instance.
*/
svgedit.select.getSelectorManager = function() {
return selectorManager_;
};
})();
/**
* Package: svgedit.draw
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2011 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) svgutils.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.draw) {
svgedit.draw = {};
}
var svg_ns = "http://www.w3.org/2000/svg";
var se_ns = "http://svg-edit.googlecode.com";
var xmlns_ns = "http://www.w3.org/2000/xmlns/";
var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
var visElems_arr = visElems.split(',');
var RandomizeModes = {
LET_DOCUMENT_DECIDE: 0,
ALWAYS_RANDOMIZE: 1,
NEVER_RANDOMIZE: 2
};
var randomize_ids = RandomizeModes.LET_DOCUMENT_DECIDE;
/**
* This class encapsulates the concept of a layer in the drawing
* @param name {String} Layer name
* @param child {SVGGElement} Layer SVG group.
*/
svgedit.draw.Layer = function(name, group) {
this.name_ = name;
this.group_ = group;
};
svgedit.draw.Layer.prototype.getName = function() {
return this.name_;
};
svgedit.draw.Layer.prototype.getGroup = function() {
return this.group_;
};
// Called to ensure that drawings will or will not have randomized ids.
// The current_drawing will have its nonce set if it doesn't already.
//
// Params:
// enableRandomization - flag indicating if documents should have randomized ids
svgedit.draw.randomizeIds = function(enableRandomization, current_drawing) {
randomize_ids = enableRandomization == false ?
RandomizeModes.NEVER_RANDOMIZE :
RandomizeModes.ALWAYS_RANDOMIZE;
if (randomize_ids == RandomizeModes.ALWAYS_RANDOMIZE && !current_drawing.getNonce()) {
current_drawing.setNonce(Math.floor(Math.random() * 100001));
} else if (randomize_ids == RandomizeModes.NEVER_RANDOMIZE && current_drawing.getNonce()) {
current_drawing.clearNonce();
}
};
/**
* This class encapsulates the concept of a SVG-edit drawing
*
* @param svgElem {SVGSVGElement} 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 opt_idPrefix {String} The ID prefix to use. Defaults to "svg_"
* if not specified.
*/
svgedit.draw.Drawing = function(svgElem, opt_idPrefix) {
if (!svgElem || !svgElem.tagName || !svgElem.namespaceURI ||
svgElem.tagName != 'svg' || svgElem.namespaceURI != svg_ns) {
throw "Error: svgedit.draw.Drawing instance initialized without a <svg> 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 = opt_idPrefix || "svg_";
/**
* An array of released element ids to immediately reuse.
* @type {Array.<number>}
*/
this.releasedNums = [];
/**
* The z-ordered array of tuples containing layer names and <g> elements.
* The first layer is the one at the bottom of the rendering.
* TODO: Turn this into an Array.<Layer>
* @type {Array.<Array.<String, SVGGElement>>}
*/
this.all_layers = [];
/**
* The current layer being used.
* TODO: Make this a {Layer}.
* @type {SVGGElement}
*/
this.current_layer = null;
/**
* The nonce to use to uniquely identify elements across drawings.
* @type {!String}
*/
this.nonce_ = "";
var n = this.svgElem_.getAttributeNS(se_ns, '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 && randomize_ids != RandomizeModes.NEVER_RANDOMIZE) {
this.nonce_ = n;
} else if (randomize_ids == RandomizeModes.ALWAYS_RANDOMIZE) {
this.setNonce(Math.floor(Math.random() * 100001));
}
};
svgedit.draw.Drawing.prototype.getElem_ = function(id) {
if(this.svgElem_.querySelector) {
// querySelector lookup
return this.svgElem_.querySelector('#'+id);
} else {
// jQuery lookup: twice as slow as xpath in FF
return $(this.svgElem_).find('[id=' + id + ']')[0];
}
};
svgedit.draw.Drawing.prototype.getSvgElem = function() {
return this.svgElem_;
};
svgedit.draw.Drawing.prototype.getNonce = function() {
return this.nonce_;
};
svgedit.draw.Drawing.prototype.setNonce = function(n) {
this.svgElem_.setAttributeNS(xmlns_ns, 'xmlns:se', se_ns);
this.svgElem_.setAttributeNS(se_ns, 'se:nonce', n);
this.nonce_ = n;
};
svgedit.draw.Drawing.prototype.clearNonce = function() {
// 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.
*/
svgedit.draw.Drawing.prototype.getId = function() {
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.
*/
svgedit.draw.Drawing.prototype.getNextId = function() {
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;
};
// Function: svgedit.draw.Drawing.releaseId
// 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.
//
// Parameters:
// id - The id to release.
//
// Returns:
// True if the id was valid to be released, false otherwise.
svgedit.draw.Drawing.prototype.releaseId = function(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 != typeof '' || id.indexOf(front) != 0) {
return false;
}
// extract the obj_num of this id
var num = parseInt(id.substr(front.length));
// if we didn't get a positive number or we already released this number
// then return false.
if (typeof num != typeof 1 || num <= 0 || this.releasedNums.indexOf(num) != -1) {
return false;
}
// push the released number into the released queue
this.releasedNums.push(num);
return true;
};
// Function: svgedit.draw.Drawing.getNumLayers
// Returns the number of layers in the current drawing.
//
// Returns:
// The number of layers in the current drawing.
svgedit.draw.Drawing.prototype.getNumLayers = function() {
return this.all_layers.length;
};
// Function: svgedit.draw.Drawing.hasLayer
// Check if layer with given name already exists
svgedit.draw.Drawing.prototype.hasLayer = function(name) {
for(var i = 0; i < this.getNumLayers(); i++) {
if(this.all_layers[i][0] == name) return true;
}
return false;
};
// Function: svgedit.draw.Drawing.getLayerName
// Returns the name of the ith layer. If the index is out of range, an empty string is returned.
//
// Parameters:
// i - the zero-based index of the layer you are querying.
//
// Returns:
// The name of the ith layer
svgedit.draw.Drawing.prototype.getLayerName = function(i) {
if (i >= 0 && i < this.getNumLayers()) {
return this.all_layers[i][0];
}
return "";
};
// Function: svgedit.draw.Drawing.getCurrentLayer
// Returns:
// The SVGGElement representing the current layer.
svgedit.draw.Drawing.prototype.getCurrentLayer = function() {
return this.current_layer;
};
// Function: getCurrentLayerName
// Returns the name of the currently selected layer. If an error occurs, an empty string
// is returned.
//
// Returns:
// The name of the currently active layer.
svgedit.draw.Drawing.prototype.getCurrentLayerName = function() {
for (var i = 0; i < this.getNumLayers(); ++i) {
if (this.all_layers[i][1] == this.current_layer) {
return this.getLayerName(i);
}
}
return "";
};
// Function: setCurrentLayer
// 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.
//
// Parameters:
// name - the name of the layer you want to switch to.
//
// Returns:
// true if the current layer was switched, otherwise false
svgedit.draw.Drawing.prototype.setCurrentLayer = function(name) {
for (var i = 0; i < this.getNumLayers(); ++i) {
if (name == this.getLayerName(i)) {
if (this.current_layer != this.all_layers[i][1]) {
this.current_layer.setAttribute("style", "pointer-events:none");
this.current_layer = this.all_layers[i][1];
this.current_layer.setAttribute("style", "pointer-events:all");
}
return true;
}
}
return false;
};
// Function: svgedit.draw.Drawing.deleteCurrentLayer
// 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:
// The SVGGElement of the layer removed or null.
svgedit.draw.Drawing.prototype.deleteCurrentLayer = function() {
if (this.current_layer && this.getNumLayers() > 1) {
// actually delete from the DOM and return it
var parent = this.current_layer.parentNode;
var nextSibling = this.current_layer.nextSibling;
var oldLayerGroup = parent.removeChild(this.current_layer);
this.identifyLayers();
return oldLayerGroup;
}
return null;
};
// Function: svgedit.draw.Drawing.identifyLayers
// Updates layer system and sets the current layer to the
// top-most layer (last <g> child of this drawing).
svgedit.draw.Drawing.prototype.identifyLayers = function() {
this.all_layers = [];
var numchildren = this.svgElem_.childNodes.length;
// loop through all children of SVG element
var orphans = [], layernames = [];
var a_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 name = $("title",child).text();
// Hack for Opera 10.60
if(!name && svgedit.browser.isOpera() && child.querySelectorAll) {
name = $(child.querySelectorAll('title')).text();
}
// store layer and name in global variable
if (name) {
layernames.push(name);
this.all_layers.push( [name,child] );
a_layer = child;
svgedit.utilities.walkTree(child, function(e){e.setAttribute("style", "pointer-events:inherit");});
a_layer.setAttribute("style", "pointer-events:none");
}
// if group did not have a name, it is an orphan
else {
orphans.push(child);
}
}
// if child has is "visible" (i.e. not a <title> or <defs> element), then it is an orphan
else if(~visElems_arr.indexOf(child.nodeName)) {
var bb = svgedit.utilities.getBBox(child);
orphans.push(child);
}
}
}
// create a new layer and add all the orphans to it
var svgdoc = this.svgElem_.ownerDocument;
if (orphans.length > 0 || !childgroups) {
var i = 1;
// TODO(codedread): What about internationalization of "Layer"?
while (layernames.indexOf(("Layer " + i)) >= 0) { i++; }
var newname = "Layer " + i;
a_layer = svgdoc.createElementNS(svg_ns, "g");
var layer_title = svgdoc.createElementNS(svg_ns, "title");
layer_title.textContent = newname;
a_layer.appendChild(layer_title);
for (var j = 0; j < orphans.length; ++j) {
a_layer.appendChild(orphans[j]);
}
this.svgElem_.appendChild(a_layer);
this.all_layers.push( [newname, a_layer] );
}
svgedit.utilities.walkTree(a_layer, function(e){e.setAttribute("style","pointer-events:inherit");});
if (a_layer.getAttribute("data-locked") === "true") {
this.current_layer = this.all_layers.slice(-2)[0][1]
}
else {
this.current_layer = a_layer
}
this.current_layer.setAttribute("style","pointer-events:all");
};
// Function: svgedit.draw.Drawing.createLayer
// Creates a new top-level layer in the drawing with the given name and
// sets the current layer to it.
//
// Parameters:
// name - The given name
//
// Returns:
// The SVGGElement of the new layer, which is also the current layer
// of this drawing.
svgedit.draw.Drawing.prototype.createLayer = function(name) {
var svgdoc = this.svgElem_.ownerDocument;
var new_layer = svgdoc.createElementNS(svg_ns, "g");
var layer_title = svgdoc.createElementNS(svg_ns, "title");
layer_title.textContent = name;
new_layer.appendChild(layer_title);
this.svgElem_.appendChild(new_layer);
this.identifyLayers();
return new_layer;
};
// Function: svgedit.draw.Drawing.getLayerVisibility
// Returns whether the layer is visible. If the layer name is not valid, then this function
// returns false.
//
// Parameters:
// layername - the name of the layer which you want to query.
//
// Returns:
// The visibility state of the layer, or false if the layer name was invalid.
svgedit.draw.Drawing.prototype.getLayerVisibility = function(layername) {
// find the layer
var layer = null;
for (var i = 0; i < this.getNumLayers(); ++i) {
if (this.getLayerName(i) == layername) {
layer = this.all_layers[i][1];
break;
}
}
if (!layer) return false;
return (layer.getAttribute('display') != 'none');
};
// Function: svgedit.draw.Drawing.setLayerVisibility
// 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.
//
// Parameters:
// layername - the name of the layer to change the visibility
// bVisible - true/false, whether the layer should be visible
//
// Returns:
// The SVGGElement representing the layer if the layername was valid, otherwise null.
svgedit.draw.Drawing.prototype.setLayerVisibility = function(layername, bVisible) {
if (typeof bVisible != typeof true) {
return null;
}
// find the layer
var layer = null;
for (var i = 0; i < this.getNumLayers(); ++i) {
if (this.getLayerName(i) == layername) {
layer = this.all_layers[i][1];
break;
}
}
if (!layer) return null;
var oldDisplay = layer.getAttribute("display");
if (!oldDisplay) oldDisplay = "inline";
layer.setAttribute("display", bVisible ? "inline" : "none");
return layer;
};
// Function: svgedit.draw.Drawing.getLayerOpacity
// Returns the opacity of the given layer. If the input name is not a layer, null is returned.
//
// Parameters:
// layername - name of the layer on which to get the opacity
//
// Returns:
// 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
svgedit.draw.Drawing.prototype.getLayerOpacity = function(layername) {
for (var i = 0; i < this.getNumLayers(); ++i) {
if (this.getLayerName(i) == layername) {
var g = this.all_layers[i][1];
var opacity = g.getAttribute('opacity');
if (!opacity) {
opacity = '1.0';
}
return parseFloat(opacity);
}
}
return null;
};
// Function: svgedit.draw.Drawing.setLayerOpacity
// 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.
//
// Parameters:
// layername - name of the layer on which to set the opacity
// opacity - a float value in the range 0.0-1.0
svgedit.draw.Drawing.prototype.setLayerOpacity = function(layername, opacity) {
if (typeof opacity != typeof 1.0 || opacity < 0.0 || opacity > 1.0) {
return;
}
for (var i = 0; i < this.getNumLayers(); ++i) {
if (this.getLayerName(i) == layername) {
var g = this.all_layers[i][1];
g.setAttribute("opacity", opacity);
break;
}
}
};
})();
/**
* Package: svgedit.path
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2011 Alexis Deveria
* Copyright(c) 2011 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) math.js
// 4) svgutils.js
var svgedit = svgedit || {};
(function() {
if (!svgedit.path) {
svgedit.path = {};
}
var svgns = "http://www.w3.org/2000/svg";
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 pathFuncs = [];
var link_control_pts = false;
// Stores references to paths via IDs.
// TODO: Make this cross-document happy.
var pathData = {};
svgedit.path.setLinkControlPoints = function(lcp) {
link_control_pts = lcp;
};
svgedit.path.path = null;
var editorContext_ = null;
svgedit.path.init = function(editorContext) {
editorContext_ = editorContext;
pathFuncs = [0,'ClosePath'];
var pathFuncsStrs = ['Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc',
'LinetoHorizontal', 'LinetoVertical','CurvetoCubicSmooth','CurvetoQuadraticSmooth'];
$.each(pathFuncsStrs, function(i,s) {
pathFuncs.push(s+'Abs');
pathFuncs.push(s+'Rel');
});
};
svgedit.path.insertItemBefore = function(elem, newseg, index) {
var list = elem.pathSegList;
list.insertItemBefore(newseg, index);
};
// TODO: See if this should just live in replacePathSeg
svgedit.path.ptObjToArr = function(type, seg_item) {
var arr = segData[type], len = arr.length;
var out = Array(len);
for(var i=0; i<len; i++) {
out[i] = seg_item[arr[i]];
}
return out;
};
svgedit.path.getGripPt = function(seg, alt_pt) {
var out = {
x: alt_pt? alt_pt.x : seg.item.x,
y: alt_pt? alt_pt.y : seg.item.y
}, path = seg.path;
if(path.matrix) {
var pt = svgedit.math.transformPoint(out.x, out.y, path.matrix);
out = pt;
}
out.x *= editorContext_.getCurrentZoom();
out.y *= editorContext_.getCurrentZoom();
return out;
};
svgedit.path.getPointFromGrip = function(pt, path) {
var out = {
x: pt.x,
y: pt.y
}
if(path.matrix) {
var pt = svgedit.math.transformPoint(out.x, out.y, path.imatrix);
out.x = pt.x;
out.y = pt.y;
}
out.x /= editorContext_.getCurrentZoom();
out.y /= editorContext_.getCurrentZoom();
return out;
};
svgedit.path.addPointGrip = function(index, x, y) {
// create the container of all the point grips
var pointGripContainer = svgedit.path.getGripContainer();
var pointGrip = svgedit.utilities.getElem("pathpointgrip_"+index);
// create it
if (!pointGrip) {
pointGrip = document.createElementNS(svgns, "rect");
svgedit.utilities.assignAttributes(pointGrip, {
'id': "pathpointgrip_" + index,
'display': "none",
'width': svgedit.browser.isTouch() ? 30 : 5,
'height': svgedit.browser.isTouch() ? 30 : 5,
'fill': "#fff",
'stroke': "#4F80FF",
'shape-rendering': "crispEdges",
'stroke-width': 1,
'cursor': 'move',
'style': 'pointer-events:all',
'xlink:title': "Drag node to move it. Double-click node to change segment type"
});
pointGrip = pointGripContainer.appendChild(pointGrip);
var grip = $('#pathpointgrip_'+index);
grip.dblclick(function() {
if(svgedit.path.path) svgedit.path.path.setSegType();
});
}
if(x && y) {
// set up the point grip element and display it
svgedit.utilities.assignAttributes(pointGrip, {
'x': x-(svgedit.browser.isTouch() ? 15 : 2.5),
'y': y-(svgedit.browser.isTouch() ? 15 : 2.5),
'display': "inline"
});
}
return pointGrip;
};
svgedit.path.getGripContainer = function() {
var c = svgedit.utilities.getElem("pathpointgrip_container");
if (!c) {
var parent = svgedit.utilities.getElem("selectorParentGroup");
c = parent.appendChild(document.createElementNS(svgns, "g"));
c.id = "pathpointgrip_container";
}
return c;
};
svgedit.path.addCtrlGrip = function(id) {
var pointGrip = svgedit.utilities.getElem("ctrlpointgrip_"+id);
if(pointGrip) return pointGrip;
pointGrip = document.createElementNS(svgns, "circle");
svgedit.utilities.assignAttributes(pointGrip, {
'id': "ctrlpointgrip_" + id,
'display': "none",
'r': svgedit.browser.isTouch() ? 15 : 3,
'fill': "#4F80FF",
'stroke': '#4F80FF',
'stroke-opacity': 0,
'stroke-width': '3',
'cursor': 'move',
'style': 'pointer-events:all',
'xlink:title': "Drag control point to adjust curve properties"
});
svgedit.path.getGripContainer().appendChild(pointGrip);
return pointGrip;
};
svgedit.path.getCtrlLine = function(id) {
var ctrlLine = svgedit.utilities.getElem("ctrlLine_"+id);
if(ctrlLine) return ctrlLine;
ctrlLine = document.createElementNS(svgns, "line");
svgedit.utilities.assignAttributes(ctrlLine, {
'id': "ctrlLine_"+id,
'stroke': "#4F80FF",
'stroke-width': 1,
"style": "pointer-events:none"
});
svgedit.path.getGripContainer().appendChild(ctrlLine);
return ctrlLine;
};
svgedit.path.getPointGrip = function(seg, update) {
var index = seg.index;
var pointGrip = svgedit.path.addPointGrip(index);
if(update) {
var pt = svgedit.path.getGripPt(seg);
svgedit.utilities.assignAttributes(pointGrip, {
'x': pt.x-(svgedit.browser.isTouch() ? 15 : 2.5),
'y': pt.y-(svgedit.browser.isTouch() ? 15 : 2.5),
'display': "inline"
});
}
return pointGrip;
};
svgedit.path.getControlPoints = function(seg) {
var item = seg.item;
var index = seg.index;
if(!item || !("x1" in item) || !("x2" in item)) return null;
var cpt = {};
var pointGripContainer = svgedit.path.getGripContainer();
// Note that this is intentionally not seg.prev.item
var prev = svgedit.path.path.segs[index-1].item;
var seg_items = [prev, item];
for(var i=1; i<3; i++) {
var id = index + 'c' + i;
var ctrlLine = cpt['c' + i + '_line'] = svgedit.path.getCtrlLine(id);
var pt = svgedit.path.getGripPt(seg, {x:item['x' + i], y:item['y' + i]});
var gpt = svgedit.path.getGripPt(seg, {x:seg_items[i-1].x, y:seg_items[i-1].y});
svgedit.utilities.assignAttributes(ctrlLine, {
'x1': pt.x,
'y1': pt.y,
'x2': gpt.x,
'y2': gpt.y,
'display': "inline"
});
cpt['c' + i + '_line'] = ctrlLine;
// create it
pointGrip = cpt['c' + i] = svgedit.path.addCtrlGrip(id);
svgedit.utilities.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.
svgedit.path.replacePathSeg = function(type, index, pts, elem) {
var path = elem || svgedit.path.path.elem;
var func = 'createSVGPathSeg' + pathFuncs[type];
var seg = path[func].apply(path, pts);
path.pathSegList.replaceItem(seg, index);
};
svgedit.path.getSegSelector = function(seg, update) {
var index = seg.index;
var segLine = svgedit.utilities.getElem("segline_" + index);
if(!segLine) {
var pointGripContainer = svgedit.path.getGripContainer();
// create segline
segLine = document.createElementNS(svgns, "path");
svgedit.utilities.assignAttributes(segLine, {
'id': "segline_" + index,
'display': 'none',
'fill': "none",
'stroke': "#0ff",
'stroke-opacity': 1,
"shape-rendering": "crispEdges",
'stroke-width': 2,
'style':'pointer-events:none',
'd': 'M0,0 0,0'
});
pointGripContainer.appendChild(segLine);
}
if(update) {
var prev = seg.prev;
if(!prev) {
segLine.setAttribute("display", "none");
return segLine;
}
var pt = svgedit.path.getGripPt(prev);
// Set start point
svgedit.path.replacePathSeg(2, 0, [pt.x, pt.y], segLine);
var pts = svgedit.path.ptObjToArr(seg.type, seg.item, true);
for(var i=0; i < pts.length; i+=2) {
var pt = svgedit.path.getGripPt(seg, {x:pts[i], y:pts[i+1]});
pts[i] = pt.x;
pts[i+1] = pt.y;
}
svgedit.path.replacePathSeg(seg.type, 1, pts, segLine);
}
return segLine;
};
// Function: smoothControlPoints
// Takes three points and creates a smoother line based on them
//
// Parameters:
// ct1 - Object with x and y values (first control point)
// ct2 - Object with x and y values (second control point)
// pt - Object with x and y values (third point)
//
// Returns:
// Array of two "smoothed" point objects
svgedit.path.smoothControlPoints = this.smoothControlPoints = function(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 anglea = Math.atan2(y1,x1),
angleb = Math.atan2(y2,x2),
r1 = Math.sqrt(x1*x1+y1*y1),
r2 = Math.sqrt(x2*x2+y2*y2),
nct1 = editorContext_.getSVGRoot().createSVGPoint(),
nct2 = editorContext_.getSVGRoot().createSVGPoint();
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 new_anglea, new_angleb;
if (anglea - angleb > 0) {
new_anglea = angleBetween < Math.PI ? (anglea + angleDiff) : (anglea - angleDiff);
new_angleb = angleBetween < Math.PI ? (angleb - angleDiff) : (angleb + angleDiff);
}
else {
new_anglea = angleBetween < Math.PI ? (anglea - angleDiff) : (anglea + angleDiff);
new_angleb = angleBetween < Math.PI ? (angleb + angleDiff) : (angleb - angleDiff);
}
// rotate the points
nct1.x = r1 * Math.cos(new_anglea) + pt.x;
nct1.y = r1 * Math.sin(new_anglea) + pt.y;
nct2.x = r2 * Math.cos(new_angleb) + pt.x;
nct2.y = r2 * Math.sin(new_angleb) + pt.y;
return [nct1, nct2];
}
return undefined;
};
svgedit.path.Segment = function(index, item) {
this.selected = false;
this.index = index;
this.item = item;
this.type = item.pathSegType;
this.ctrlpts = [];
this.ptgrip = null;
this.segsel = null;
};
svgedit.path.Segment.prototype.showCtrlPts = function(y) {
for (var i in this.ctrlpts) {
this.ctrlpts[i].setAttribute("display", y ? "inline" : "none");
}
};
svgedit.path.Segment.prototype.selectCtrls = function(y) {
$('#ctrlpointgrip_' + this.index + 'c1, #ctrlpointgrip_' + this.index + 'c2').
attr('fill', '#4F80FF');
};
svgedit.path.Segment.prototype.show = function(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);
}
};
svgedit.path.Segment.prototype.select = function(y) {
if(this.ptgrip) {
this.ptgrip.setAttribute("stroke", y ? "#4F80FF" : "#4F80FF");
this.ptgrip.setAttribute("fill", y ? "#4F80FF" : "#fff");
this.segsel.setAttribute("display", y ? "inline" : "none");
if(this.ctrlpts) {
this.selectCtrls(y);
}
this.selected = y;
}
};
svgedit.path.Segment.prototype.addGrip = function() {
this.ptgrip = svgedit.path.getPointGrip(this, true);
this.ctrlpts = svgedit.path.getControlPoints(this, true);
this.segsel = svgedit.path.getSegSelector(this, true);
};
svgedit.path.Segment.prototype.update = function(full) {
if(this.ptgrip) {
var pt = svgedit.path.getGripPt(this);
var reposition = (svgedit.browser.isTouch() ? 15 : 2.5)
var properties = (this.ptgrip.nodeName == "rect") ? {'x': pt.x-reposition, 'y': pt.y-reposition} : {'cx': pt.x, 'cy': pt.y};
svgedit.utilities.assignAttributes(this.ptgrip, properties);
svgedit.path.getSegSelector(this, true);
if(this.ctrlpts) {
if(full) {
this.item = svgedit.path.path.elem.pathSegList.getItem(this.index);
this.type = this.item.pathSegType;
}
svgedit.path.getControlPoints(this);
}
// this.segsel.setAttribute("display", y?"inline":"none");
}
};
svgedit.path.Segment.prototype.move = function(dx, dy) {
var item = this.item;
// fix for path tool dom breakage, amending item does bad things now, so we take a copy and use that. Can probably improve to just take a shallow copy of object
var cloneItem = $.extend({}, item);
var cur_pts = (this.ctrlpts)
? [cloneItem.x += dx, cloneItem.y += dy,
cloneItem.x1, cloneItem.y1,
cloneItem.x2 += dx, cloneItem.y2 += dy]
: [cloneItem.x += dx, cloneItem.y += dy];
svgedit.path.replacePathSeg(this.type, this.index, cur_pts);
if(this.next && this.next.ctrlpts) {
var next = this.next.item;
var next_pts = [next.x, next.y,
next.x1 += dx, next.y1 += dy, next.x2, next.y2];
svgedit.path.replacePathSeg(this.next.type, this.next.index, next_pts);
}
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];
svgedit.path.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);
};
svgedit.path.Segment.prototype.setLinked = function(num) {
var seg, anum, pt;
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 item = seg.item;
var cloneItem = $.extend({}, item);
cloneItem['x' + anum ] = pt.x + (pt.x - this.item['x' + num]);
cloneItem['y' + anum ] = pt.y + (pt.y - this.item['y' + num]);
var pts = [
cloneItem.x, cloneItem.y,
cloneItem.x1, cloneItem.y1,
cloneItem.x2, cloneItem.y2
];
svgedit.path.replacePathSeg(seg.type, seg.index, pts);
seg.update(true);
};
svgedit.path.Segment.prototype.moveCtrl = function(num, dx, dy) {
var item = $.extend({}, this.item);
item['x' + num] += dx;
item['y' + num] += dy;
var pts = [item.x,item.y,
item.x1,item.y1, item.x2,item.y2];
svgedit.path.replacePathSeg(this.type, this.index, pts);
this.update(true);
};
svgedit.path.Segment.prototype.setType = function(new_type, pts) {
svgedit.path.replacePathSeg(new_type, this.index, pts);
this.type = new_type;
this.item = svgedit.path.path.elem.pathSegList.getItem(this.index);
this.showCtrlPts(new_type === 6);
this.ctrlpts = svgedit.path.getControlPoints(this);
this.update(true);
};
svgedit.path.Path = function(elem) {
if(!elem || elem.tagName !== "path") {
throw "svgedit.path.Path constructed without a <path> element";
}
this.elem = elem;
this.segs = [];
this.selected_pts = [];
svgedit.path.path = this;
this.init();
};
// Reset path data
svgedit.path.Path.prototype.init = function() {
// Hide all grips, etc
$(svgedit.path.getGripContainer()).find("*").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 svgedit.path.Segment(i, item);
segment.path = this;
this.segs.push(segment);
}
var segs = this.segs;
var start_i = null;
for(var i=0; i < len; i++) {
var seg = segs[i];
var next_seg = (i+1) >= len ? null : segs[i+1];
var prev_seg = (i-1) < 0 ? null : segs[i-1];
if(seg.type === 2) {
if(prev_seg && prev_seg.type !== 1) {
// New sub-path, last one is open,
// so add a grip to last sub-path's first point
var start_seg = segs[start_i];
start_seg.next = segs[start_i+1];
start_seg.next.prev = start_seg;
start_seg.addGrip();
}
// Remember that this is a starter seg
start_i = i;
} else if(next_seg && next_seg.type === 1) {
// This is the last real segment of a closed sub-path
// Next is first seg after "M"
seg.next = segs[start_i+1];
// First seg after "M"'s prev is this
seg.next.prev = seg;
seg.mate = segs[start_i];
seg.addGrip();
if(this.first_seg == null) {
this.first_seg = seg;
}
} else if(!next_seg) {
if(seg.type !== 1) {
// Last seg, doesn't close so add a grip
// to last sub-path's first point
var start_seg = segs[start_i];
start_seg.next = segs[start_i+1];
start_seg.next.prev = start_seg;
start_seg.addGrip();
seg.addGrip();
if(!this.first_seg) {
// Open path, so set first as real first and add grip
this.first_seg = segs[start_i];
}
}
} 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(next_seg && next_seg.type !== 2) {
seg.next = next_seg;
seg.next.prev = seg;
}
}
}
return this;
};
svgedit.path.Path.prototype.eachSeg = function(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;
}
};
svgedit.path.Path.prototype.addSeg = function(index) {
// Adds a new segment
var seg = this.segs[index];
if(!seg.prev) return;
var prev = seg.prev;
var newseg;
switch(seg.item.pathSegType) {
case 4:
var new_x = (seg.item.x + prev.item.x) / 2;
var new_y = (seg.item.y + prev.item.y) / 2;
newseg = this.elem.createSVGPathSegLinetoAbs(new_x, new_y);
break;
case 6: //make it a curved segment to preserve the shape (WRS)
// http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm#Geometric_interpretation
var p0_x = (prev.item.x + seg.item.x1)/2;
var p1_x = (seg.item.x1 + seg.item.x2)/2;
var p2_x = (seg.item.x2 + seg.item.x)/2;
var p01_x = (p0_x + p1_x)/2;
var p12_x = (p1_x + p2_x)/2;
var new_x = (p01_x + p12_x)/2;
var p0_y = (prev.item.y + seg.item.y1)/2;
var p1_y = (seg.item.y1 + seg.item.y2)/2;
var p2_y = (seg.item.y2 + seg.item.y)/2;
var p01_y = (p0_y + p1_y)/2;
var p12_y = (p1_y + p2_y)/2;
var new_y = (p01_y + p12_y)/2;
newseg = this.elem.createSVGPathSegCurvetoCubicAbs(new_x,new_y, p0_x,p0_y, p01_x,p01_y);
var pts = [seg.item.x,seg.item.y,p12_x,p12_y,p2_x,p2_y];
svgedit.path.replacePathSeg(seg.type,index,pts);
break;
}
svgedit.path.insertItemBefore(this.elem, newseg, index);
};
svgedit.path.Path.prototype.deleteSeg = function(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];
svgedit.path.replacePathSeg(2, next.index, pt);
// Reposition last node
svgedit.path.replacePathSeg(4, seg.index, pt);
list.removeItem(seg.mate.index);
} else if(!seg.prev) {
// First node of open path, make next point the M
var item = seg.item;
var pt = [next.item.x, next.item.y];
svgedit.path.replacePathSeg(2, seg.next.index, pt);
list.removeItem(index);
} else {
list.removeItem(index);
}
};
svgedit.path.Path.prototype.subpathIsClosed = function(index) {
var closed = false;
// Check if subpath is already open
svgedit.path.path.eachSeg(function(i) {
if(i <= index) return true;
if(this.type === 2) {
// Found M first, so open
return false;
} else if(this.type === 1) {
// Found Z first, so closed
closed = true;
return false;
}
});
return closed;
};
svgedit.path.Path.prototype.removePtFromSelection = function(index) {
var pos = this.selected_pts.indexOf(index);
if(pos == -1) {
return;
}
this.segs[index].select(false);
this.selected_pts.splice(pos, 1);
};
svgedit.path.Path.prototype.clearSelection = function() {
this.eachSeg(function(i) {
// 'this' is the segment here
this.select(false);
});
this.selected_pts = [];
};
svgedit.path.Path.prototype.storeD = function() {
this.last_d = this.elem.getAttribute('d');
};
svgedit.path.Path.prototype.show = function(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
svgedit.path.Path.prototype.movePts = function(d_x, d_y) {
var i = this.selected_pts.length;
while(i--) {
var seg = this.segs[this.selected_pts[i]];
seg.move(d_x, d_y);
}
};
svgedit.path.Path.prototype.moveCtrl = function(d_x, d_y) {
var seg = this.segs[this.selected_pts[0]];
seg.moveCtrl(this.dragctrl, d_x, d_y);
if(link_control_pts) {
seg.setLinked(this.dragctrl);
}
};
svgedit.path.Path.prototype.setSegType = function(new_type) {
this.storeD();
var i = this.selected_pts.length;
var text;
while(i--) {
var sel_pt = this.selected_pts[i];
// Selected seg
var cur = this.segs[sel_pt];
var prev = cur.prev;
if(!prev) continue;
if(!new_type) { // double-click, so just toggle
text = "Toggle Path Segment Type";
// Toggle segment to curve/straight line
var old_type = cur.type;
new_type = (old_type == 6) ? 4 : 6;
}
new_type = new_type-0;
var cur_x = cur.item.x;
var cur_y = cur.item.y;
var prev_x = prev.item.x;
var prev_y = prev.item.y;
var points;
switch ( new_type ) {
case 6:
if(cur.olditem) {
var old = cur.olditem;
points = [cur_x,cur_y, old.x1,old.y1, old.x2,old.y2];
} else {
var diff_x = cur_x - prev_x;
var diff_y = cur_y - prev_y;
// get control points from straight line segment
/*
var ct1_x = (prev_x + (diff_y/2));
var ct1_y = (prev_y - (diff_x/2));
var ct2_x = (cur_x + (diff_y/2));
var ct2_y = (cur_y - (diff_x/2));
*/
//create control points on the line to preserve the shape (WRS)
var ct1_x = (prev_x + (diff_x/3));
var ct1_y = (prev_y + (diff_y/3));
var ct2_x = (cur_x - (diff_x/3));
var ct2_y = (cur_y - (diff_y/3));
points = [cur_x,cur_y, ct1_x,ct1_y, ct2_x,ct2_y];
}
break;
case 4:
points = [cur_x,cur_y];
// Store original prevve segment nums
cur.olditem = cur.item;
break;
}
cur.setType(new_type, points);
}
svgedit.path.path.endChanges(text);
};
svgedit.path.Path.prototype.selectPt = function(pt, ctrl_num) {
this.clearSelection();
if(pt == null) {
this.eachSeg(function(i) {
// 'this' is the segment here.
if(this.prev) {
pt = i;
}
});
}
this.addPtsToSelection(pt);
if(ctrl_num) {
this.dragctrl = ctrl_num;
if(link_control_pts) {
this.segs[pt].setLinked(ctrl_num);
}
}
};
// Update position of all points
svgedit.path.Path.prototype.update = function() {
var elem = this.elem;
if(svgedit.utilities.getRotationAngle(elem)) {
this.matrix = svgedit.math.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;
};
svgedit.path.getPath_ = function(elem) {
var p = pathData[elem.id];
if(!p) p = pathData[elem.id] = new svgedit.path.Path(elem);
return p;
};
svgedit.path.removePath_ = function(id) {
if(id in pathData) delete pathData[id];
};
var getRotVals = function(x, y, oldcx, oldcy, newcx, newcy, angle) {
dx = x - oldcx;
dy = y - oldcy;
// rotate the point around the old center
r = Math.sqrt(dx*dx + dy*dy);
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)/1,
'y':(r * Math.sin(theta) + newcy)/1};
};
// 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
svgedit.path.recalcRotatedPath = function() {
var current_path = svgedit.path.path.elem;
var angle = svgedit.utilities.getRotationAngle(current_path, true);
if(!angle) return;
// selectedBBoxes[0] = svgedit.path.path.oldbbox;
var box = svgedit.utilities.getBBox(current_path),
oldbox = svgedit.path.path.oldbbox,//selectedBBoxes[0],
oldcx = oldbox.x + oldbox.width/2,
oldcy = oldbox.y + oldbox.height/2,
newcx = box.x + box.width/2,
newcy = box.y + box.height/2,
// un-rotate the new center to the proper position
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 = current_path.pathSegList,
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, oldcx, oldcy, newcx, newcy, angle),
points = [rvals.x, rvals.y];
if(seg.x1 != null && seg.x2 != null) {
c_vals1 = getRotVals(seg.x1, seg.y1, oldcx, oldcy, newcx, newcy, angle);
c_vals2 = getRotVals(seg.x2, seg.y2, oldcx, oldcy, newcx, newcy, angle);
points.splice(points.length, 0, c_vals1.x , c_vals1.y, c_vals2.x, c_vals2.y);
}
svgedit.path.replacePathSeg(type, i, points);
} // loop for each point
box = svgedit.utilities.getBBox(current_path);
// 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 R_nc = svgroot.createSVGTransform(),
tlist = svgedit.transformlist.getTransformList(current_path);
R_nc.setRotate((angle * 180.0 / Math.PI), newcx, newcy);
tlist.replaceItem(R_nc,0);
};
// ====================================
// Public API starts here
svgedit.path.clearData = function() {
pathData = {};
};
})();
// This sets up alternative dialog boxes. They mostly work the same way as
// their UI counterparts, expect instead of returning the result, a callback
// needs to be included that returns the result as its first parameter.
// In the future we may want to add additional types of dialog boxes, since
// they should be easy to handle this way.
(function() {
$('#dialog_container').draggable({cancel:'#dialog_content, #dialog_buttons *', containment: 'window'});
var box = $('#dialog_box'), btn_holder = $('#dialog_buttons');
var dbox = function(type, msg, callback, defText) {
$('#dialog_content').html('<p>'+msg.replace(/\n/g,'</p><p>')+'</p>')
.toggleClass('prompt',(type=='prompt'));
btn_holder.empty();
var ok = $('<input type="button" value="OK">').appendTo(btn_holder);
if(type != 'alert') {
$('<input type="button" value="Cancel">')
.appendTo(btn_holder)
.on("click touchstart", function() { box.hide();callback(false)});
}
if(type == 'prompt') {
var input = $('<input type="text">').prependTo(btn_holder);
input.val(defText || '');
input.bind('keydown', 'return', function() {ok.trigger("click touchstart");});
}
if(type == 'process') {
ok.hide();
}
box.show();
ok.on("click touchstart", function() {
box.hide();
var resp = (type == 'prompt')?input.val():true;
if(callback) callback(resp);
}).focus();
if(type == 'prompt') input.focus();
}
$.alert = function(msg, cb) { dbox('alert', msg, cb);};
$.confirm = function(msg, cb) { dbox('confirm', msg, cb);};
$.process_cancel = function(msg, cb) { dbox('process', msg, cb);};
$.prompt = function(msg, txt, cb) { dbox('prompt', msg, cb, txt);};
}());
/*
* svgcanvas.js
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Pavol Rusnak
* Copyright(c) 2010 Jeff Schiller
*
*/
// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) svgtransformlist.js
// 4) math.js
// 5) units.js
// 6) svgutils.js
// 7) sanitize.js
// 8) history.js
// 9) select.js
// 10) draw.js
// 11) path.js
/*jslint browser: true*/
(function() {
// This fixes $(...).attr() to work as expected with SVG elements.
// Does not currently use *AttributeNS() since we rarely need that.
// See http://api.jquery.com/attr/ for basic documentation of .attr()
// Additional functionality:
// - When getting attributes, a string that's a number is return as type number.
// - If an array is supplied as first parameter, multiple values are returned
// as an object with values for each given attributes
var proxied = jQuery.fn.attr, svgns = "http://www.w3.org/2000/svg";
jQuery.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($.isArray(key)) {
// Getting attributes from array
var j = key.length, obj = {};
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;
} else if(typeof key === "object") {
// Setting attributes form 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;
};
}());
// Class: SvgCanvas
// The main SvgCanvas class that manages all SVG-related functions
//
// Parameters:
// container - The container HTML element that should hold the SVG root element
// config - An object that contains configuration data
$.SvgCanvas = function(container, config)
{
// Namespace constants
var svgns = "http://www.w3.org/2000/svg",
xlinkns = "http://www.w3.org/1999/xlink",
xmlns = "http://www.w3.org/XML/1998/namespace",
xmlnsns = "http://www.w3.org/2000/xmlns/", // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
se_ns = "http://svg-edit.googlecode.com",
htmlns = "http://www.w3.org/1999/xhtml",
mathns = "http://www.w3.org/1998/Math/MathML";
// Default configuration options
var curConfig = {
show_outside_canvas: true,
selectNew: true,
dimensions: [640, 480]
};
// Update config with new one if given
if(config) {
$.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 a HTML document.
var svgdoc = container.ownerDocument;
// This is a container for the document being edited, not the document itself.
var svgroot = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgroot.setAttribute("width", dimensions[0]);
svgroot.setAttribute("height", dimensions[1]);
svgroot.id = "svgroot";
svgroot.setAttribute("xlinkns", xlinkns);
container.appendChild(svgroot);
// The actual element that represents the final output SVG element
var svgcontent = svgdoc.createElementNS(svgns, "svg");
// This function resets the svgcontent element while keeping it in the DOM.
var clearSvgContentElement = canvas.clearSvgContentElement = function() {
while (svgcontent.firstChild) { svgcontent.removeChild(svgcontent.firstChild); }
// TODO: Clear out all other attributes first?
$(svgcontent).attr({
id: 'svgcontent',
width: dimensions[0],
height: dimensions[1],
x: dimensions[0],
y: dimensions[1],
overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden',
xmlns: svgns,
"xmlns:se": se_ns,
"xmlns:xlink": xlinkns
}).appendTo(svgroot);
// TODO: make this string optional and set by the client
var comment = svgdoc.createComment(" Created with Method Draw - http://github.com/duopixel/Method-Draw/ ");
svgcontent.appendChild(comment);
};
clearSvgContentElement();
// Prefix string for element IDs
var idprefix = "svg_";
// Function: setIdPrefix
// Changes the ID prefix to the given value
//
// Parameters:
// 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 svgedit.draw.Drawing(svgcontent, idprefix);
// Function: getCurrentDrawing
// Returns the current Drawing.
// @return {svgedit.draw.Drawing}
var getCurrentDrawing = canvas.getCurrentDrawing = function() {
return canvas.current_drawing_;
};
// Float displaying the current zoom level (1 = 100%, .5 = 50%, etc)
var current_zoom = 1;
// pointer to current group (for in-group editing)
var current_group = null;
// Object containing data for the currently selected styles
var all_properties = {
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',
opacity: curConfig.initOpacity
}
};
all_properties.text = $.extend(true, {}, all_properties.shape);
$.extend(all_properties.text, {
fill: "#000000",
stroke_width: 0,
font_size: 24,
font_family: 'Helvetica, Arial, sans-serif'
});
// Current shape style properties
var cur_shape = all_properties.shape;
// Array with all the currently selected elements
// default size of 1 until it needs to grow bigger
var selectedElements = new Array(1);
// Function: addSvgElementFromJson
// 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
//
// Parameters:
// data - Object with the following keys/values:
// * element - tag name of the SVG element to create
// * attr - Object with attributes key-values to assign to the new element
// * curStyles - Boolean indicating that current style attributes should be applied first
//
// Returns: The new element
var addSvgElementFromJson = this.addSvgElementFromJson = function(data) {
var shape = svgedit.utilities.getElem(data.attr.id);
// if shape is a path but we need to create a rect/ellipse, then remove the path
var current_layer = getCurrentDrawing().getCurrentLayer();
if (shape && data.element != shape.tagName) {
current_layer.removeChild(shape);
shape = null;
}
if (!shape) {
shape = svgdoc.createElementNS(svgns, data.element);
if (current_layer) {
(current_group || current_layer).appendChild(shape);
}
}
if(data.curStyles) {
svgedit.utilities.assignAttributes(shape, {
"fill": cur_shape.fill,
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_dasharray,
"stroke-opacity": cur_shape.stroke_opacity,
"fill-opacity": cur_shape.fill_opacity,
"opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
}, 100);
}
svgedit.utilities.assignAttributes(shape, data.attr, 100);
svgedit.utilities.cleanupElement(shape);
return shape;
};
// import svgtransformlist.js
var getTransformList = canvas.getTransformList = svgedit.transformlist.getTransformList;
// import from math.js.
var transformPoint = svgedit.math.transformPoint;
var matrixMultiply = canvas.matrixMultiply = svgedit.math.matrixMultiply;
var hasMatrixTransform = canvas.hasMatrixTransform = svgedit.math.hasMatrixTransform;
var transformListToTransform = canvas.transformListToTransform = svgedit.math.transformListToTransform;
var snapToAngle = svgedit.math.snapToAngle;
var getMatrix = svgedit.math.getMatrix;
// initialize from units.js
// send in an object implementing the ElementContainer interface (see units.js)
svgedit.units.init({
getBaseUnit: function() { return curConfig.baseUnit; },
getElement: svgedit.utilities.getElem,
getHeight: function() { return svgcontent.getAttribute("height")/current_zoom; },
getWidth: function() { return svgcontent.getAttribute("width")/current_zoom; },
getRoundDigits: function() { return save_options.round_digits; }
});
// import from units.js
var convertToNum = canvas.convertToNum = svgedit.units.convertToNum;
// import from svgutils.js
svgedit.utilities.init({
getDOMDocument: function() { return svgdoc; },
getDOMContainer: function() { return container; },
getSVGRoot: function() { return svgroot; },
// TODO: replace this mostly with a way to get the current drawing.
getSelectedElements: function() { return selectedElements; },
getSVGContent: function() { return svgcontent; }
});
var getUrlFromAttr = canvas.getUrlFromAttr = svgedit.utilities.getUrlFromAttr;
var getHref = canvas.getHref = svgedit.utilities.getHref;
var setHref = canvas.setHref = svgedit.utilities.setHref;
var getPathBBox = svgedit.utilities.getPathBBox;
var getBBox = canvas.getBBox = svgedit.utilities.getBBox;
var getRotationAngle = canvas.getRotationAngle = svgedit.utilities.getRotationAngle;
var getElem = canvas.getElem = svgedit.utilities.getElem;
var assignAttributes = canvas.assignAttributes = svgedit.utilities.assignAttributes;
var cleanupElement = this.cleanupElement = svgedit.utilities.cleanupElement;
// import from sanitize.js
var nsMap = svgedit.sanitize.getNSMap();
var sanitizeSvg = canvas.sanitizeSvg = svgedit.sanitize.sanitizeSvg;
// import from history.js
var MoveElementCommand = svgedit.history.MoveElementCommand;
var InsertElementCommand = svgedit.history.InsertElementCommand;
var RemoveElementCommand = svgedit.history.RemoveElementCommand;
var ChangeElementCommand = svgedit.history.ChangeElementCommand;
var BatchCommand = svgedit.history.BatchCommand;
// Implement the svgedit.history.HistoryEventHandler interface.
canvas.undoMgr = new svgedit.history.UndoManager({
handleHistoryEvent: function(eventType, cmd) {
var EventTypes = svgedit.history.HistoryEventTypes;
// 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.type()) {
var parent = isApply ? cmd.newParent : cmd.oldParent;
if (parent == svgcontent) {
canvas.identifyLayers();
}
} else if (cmdType == InsertElementCommand.type() ||
cmdType == RemoveElementCommand.type()) {
if (cmd.parent == svgcontent) {
canvas.identifyLayers();
}
if (cmdType == InsertElementCommand.type()) {
if (isApply) restoreRefElems(cmd.elem);
} else {
if (!isApply) restoreRefElems(cmd.elem);
}
if(cmd.elem.tagName === 'use') {
setUseData(cmd.elem);
}
} else if (cmdType == ChangeElementCommand.type()) {
// if we are changing layer names, re-identify all layers
if (cmd.elem.tagName == "title" && cmd.elem.parentNode.parentNode == svgcontent) {
canvas.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"]);
}
// Remove & Re-add hack for Webkit (issue 775)
//if(cmd.elem.tagName === 'use' && svgedit.browser.isWebkit()) {
// var elem = cmd.elem;
// if(!elem.getAttribute('x') && !elem.getAttribute('y')) {
// var parent = elem.parentNode;
// var sib = elem.nextSibling;
// parent.removeChild(elem);
// parent.insertBefore(elem, sib);
// }
//}
}
}
}
});
var addCommandToHistory = function(cmd) {
canvas.undoMgr.addCommandToHistory(cmd);
};
// import from select.js
svgedit.select.init(curConfig, {
createSVGElement: function(jsonMap) { return canvas.addSvgElementFromJson(jsonMap); },
svgRoot: function() { return svgroot; },
svgContent: function() { return svgcontent; },
currentZoom: function() { return current_zoom; },
// TODO(codedread): Remove when getStrokedBBox() has been put into svgutils.js.
getStrokedBBox: function(elems) { return canvas.getStrokedBBox([elems]); }
});
// this object manages selectors for us
var selectorManager = this.selectorManager = svgedit.select.getSelectorManager();
// this object manages selectors for us
// Import from path.js
svgedit.path.init({
getCurrentZoom: function() { return current_zoom; },
getSVGRoot: function() { return svgroot; }
});
// Function: snapToGrid
// round value to for snapping
// NOTE: This function did not move to svgutils.js since it depends on curConfig.
svgedit.utilities.snapToGrid = function(value){
var stepSize = curConfig.snappingStep;
var unit = curConfig.baseUnit;
if(unit !== "px") {
stepSize *= svgedit.units.getTypeMap()[unit];
}
value = Math.round(value/stepSize)*stepSize;
return value;
};
var snapToGrid = svgedit.utilities.snapToGrid;
var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
var ref_attrs = ["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"];
var elData = $.data;
// Animation element to change the opacity of any newly created element
var opac_ani = false; //document.createElementNS(svgns, 'animate');
//$(opac_ani).attr({
// attributeName: 'opacity',
// begin: 'indefinite',
// dur: 0,
// fill: 'freeze'
//}).appendTo(svgroot);
var restoreRefElems = function(elem) {
// Look for missing reference elements, restore any found
var attrs = $(elem).attr(ref_attrs);
for(var o in attrs) {
var val = attrs[o];
if (val && val.indexOf('url(') === 0) {
var id = getUrlFromAttr(val).substr(1);
var ref = getElem(id);
if(!ref) {
findDefs().appendChild(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
// var svgthumb = svgdoc.createElementNS(svgns, "use");
// svgthumb.setAttribute('width', '100');
// svgthumb.setAttribute('height', '100');
// svgedit.utilities.setHref(svgthumb, '#svgcontent');
// svgroot.appendChild(svgthumb);
})();
// Object to contain image data for raster images that were found encodable
var encodableImages = {},
// String with image URL of last loadable image
last_good_img_url = curConfig.imgPath + 'logo.png',
// Array with current disabled elements (for in-group editing)
disabled_elems = [],
// Object with save options
save_options = {round_digits: 5},
// Boolean indicating whether or not a draw action has been started
started = false,
// String with an element's initial transform attribute value
start_transform = null,
// String indicating the current editor mode
current_mode = "select",
// String with the current direction in which an element is being resized
current_resize_mode = "none",
// Object with IDs for imported files, to see if one was already added
import_ids = {};
// Current text style properties
var cur_text = all_properties.text,
// Current general properties
cur_properties = cur_shape,
// 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 (still needed?)
curBBoxes = [],
// Object to contain all included extensions
extensions = {},
// Canvas point for the most recent right click
lastClickPoint = null,
// Map of deleted reference elements
removedElements = {}
// Clipboard for cut, copy&pasted elements
canvas.clipBoard = [];
// Should this return an array by default, so extension results aren't overwritten?
var runExtensions = this.runExtensions = function(action, vars, returnArray) {
var result = false;
if(returnArray) result = [];
$.each(extensions, function(name, opts) {
if(action in opts) {
if(returnArray) {
result.push(opts[action](vars))
} else {
result = opts[action](vars);
}
}
});
return result;
}
// Function: addExtension
// Add an extension to the editor
//
// Parameters:
// name - String with the ID of the extension
// ext_func - Function supplied by the extension with its data
this.addExtension = function(name, ext_func) {
if(!(name in extensions)) {
// Provide private vars/funcs here. Is there a better way to do this?
if($.isFunction(ext_func)) {
var ext = ext_func($.extend(canvas.getPrivateMethods(), {
svgroot: svgroot,
svgcontent: svgcontent,
nonce: getCurrentDrawing().getNonce(),
selectorManager: selectorManager
}));
} else {
var ext = ext_func;
}
extensions[name] = ext;
call("extension_added", ext);
} else {
console.log('Cannot add extension "' + name + '", an extension by that name already exists"');
}
};
// This method rounds the incoming value to the nearest value based on the current_zoom
var round = this.round = function(val) {
return parseInt(val*current_zoom)/current_zoom;
};
// This method sends back an array or a NodeList full of elements that
// intersect the multi-select rubber-band-box on the current_layer only.
//
// Since the only browser that supports the SVG DOM getIntersectionList is Opera,
// we need to provide an implementation here. We brute-force it for now.
//
// Reference:
// Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421
// Webkit does not implement getIntersectionList(), see https://bugs.webkit.org/show_bug.cgi?id=11274
var getIntersectionList = this.getIntersectionList = function(rect) {
if (rubberBox == null) { return null; }
var parent = current_group || getCurrentDrawing().getCurrentLayer();
if(!curBBoxes.length) {
// Cache all bboxes
curBBoxes = getVisibleElementsAndBBoxes(parent);
}
var resultList = null;
try {
resultList = parent.getIntersectionList(rect, null);
} catch(e) { }
if (resultList == null || typeof(resultList.item) != "function") {
resultList = [];
if(!rect) {
var rubberBBox = rubberBox.getBBox();
var bb = {};
for(var o in rubberBBox) {
bb[o] = rubberBBox[o] / current_zoom;
}
rubberBBox = bb;
} else {
var rubberBBox = rect;
}
var i = curBBoxes.length;
while (i--) {
if(!rubberBBox.width || !rubberBBox.width) continue;
if (svgedit.math.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:
// http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
return resultList;
};
// TODO(codedread): Migrate this into svgutils.js
// Function: getStrokedBBox
// Get the bounding box for one or more stroked and/or transformed elements
//
// Parameters:
// elems - Array with DOM elements to check
//
// Returns:
// A single bounding box object
getStrokedBBox = this.getStrokedBBox = function(elems) {
if(!elems) elems = getVisibleElements();
if(!elems.length) return false;
// Make sure the expected BBox is returned if the element is a group
var getCheckedBBox = function(elem) {
try {
// 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 = svgedit.utilities.getBBox(elem);
var angle = svgedit.utilities.getRotationAngle(elem);
if ((angle && angle % 90) ||
svgedit.math.hasMatrixTransform(svgedit.transformlist.getTransformList(elem))) {
// Accurate way to get BBox of rotated element in Firefox:
// Put element in group and get its BBox
var good_bb = false;
// Get the BBox from the raw path for these elements
var elemNames = ['ellipse','path','line','polyline','polygon'];
if(elemNames.indexOf(elem.tagName) >= 0) {
bb = good_bb = canvas.convertToPath(elem, true);
} else if(elem.tagName == 'rect') {
// Look for radius
var rx = elem.getAttribute('rx');
var ry = elem.getAttribute('ry');
if(rx || ry) {
bb = good_bb = canvas.convertToPath(elem, true);
}
}
if(!good_bb) {
// Must use clone else FF freaks out
var clone = elem.cloneNode(true);
var g = document.createElementNS(svgns, "g");
var parent = elem.parentNode;
parent.appendChild(g);
g.appendChild(clone);
bb = svgedit.utilities.bboxToObj(g.getBBox());
parent.removeChild(g);
}
// Old method: Works by giving the rotated BBox,
// this is (unfortunately) what Opera and Safari do
// natively when getting the BBox of the parent group
// var angle = angle * Math.PI / 180.0;
// var rminx = Number.MAX_VALUE, rminy = Number.MAX_VALUE,
// rmaxx = Number.MIN_VALUE, rmaxy = Number.MIN_VALUE;
// var cx = round(bb.x + bb.width/2),
// cy = round(bb.y + bb.height/2);
// var pts = [ [bb.x - cx, bb.y - cy],
// [bb.x + bb.width - cx, bb.y - cy],
// [bb.x + bb.width - cx, bb.y + bb.height - cy],
// [bb.x - cx, bb.y + bb.height - cy] ];
// var j = 4;
// while (j--) {
// var x = pts[j][0],
// y = pts[j][1],
// r = Math.sqrt( x*x + y*y );
// var theta = Math.atan2(y,x) + angle;
// x = round(r * Math.cos(theta) + cx);
// y = round(r * Math.sin(theta) + cy);
//
// // now set the bbox for the shape after it's been rotated
// if (x < rminx) rminx = x;
// if (y < rminy) rminy = y;
// if (x > rmaxx) rmaxx = x;
// if (y > rmaxy) rmaxy = y;
// }
//
// bb.x = rminx;
// bb.y = rminy;
// bb.width = rmaxx - rminx;
// bb.height = rmaxy - rminy;
}
return bb;
} catch(e) {
console.log(elem, e);
}
};
var full_bb;
$.each(elems, function() {
if(full_bb) return;
if(!this.parentNode) return;
full_bb = getCheckedBBox(this);
});
// This shouldn't ever happen...
if(full_bb == null) return null;
// full_bb doesn't include the stoke, so this does no good!
// if(elems.length == 1) return full_bb;
var max_x = full_bb.x + full_bb.width;
var max_y = full_bb.y + full_bb.height;
var min_x = full_bb.x;
var min_y = full_bb.y;
// FIXME: same re-creation problem with this function as getCheckedBBox() above
var getOffset = function(elem) {
var sw = elem.getAttribute("stroke-width");
var offset = 0;
if (elem.getAttribute("stroke") != "none" && !isNaN(sw)) {
offset += sw/2;
}
return offset;
}
var bboxes = [];
$.each(elems, function(i, elem) {
var cur_bb = getCheckedBBox(elem);
if(cur_bb) {
var offset = getOffset(elem);
min_x = Math.min(min_x, cur_bb.x - offset);
min_y = Math.min(min_y, cur_bb.y - offset);
bboxes.push(cur_bb);
}
});
full_bb.x = min_x;
full_bb.y = min_y;
$.each(elems, function(i, elem) {
var cur_bb = bboxes[i];
// ensure that elem is really an element node
if (cur_bb && elem.nodeType == 1) {
var offset = getOffset(elem);
max_x = Math.max(max_x, cur_bb.x + cur_bb.width + offset);
max_y = Math.max(max_y, cur_bb.y + cur_bb.height + offset);
}
});
full_bb.width = max_x - min_x;
full_bb.height = max_y - min_y;
return full_bb;
}
// Function: 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
//
// Parameters:
// parent - The parent DOM element to search within
//
// Returns:
// An array with all "visible" elements.
var getVisibleElements = this.getVisibleElements = function(parent) {
if(!parent) parent = $(svgcontent).children(); // Prevent layers from being included
if (parent.find("#canvas_background").length) parent.splice(0, 1) // Prevent background from being included
var contentElems = [];
$(parent).children().each(function(i, elem) {
try {
if (elem.getBBox()) {
contentElems.push(elem);
}
} catch(e) {}
});
return contentElems.reverse();
};
// Function: getVisibleElementsAndBBoxes
// 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
//
// Parameters:
// parent - The parent DOM element to search within
//
// Returns:
// An array with objects that include:
// * elem - The element
// * bbox - The element's BBox as retrieved from getStrokedBBox
var getVisibleElementsAndBBoxes = this.getVisibleElementsAndBBoxes = function(parent) {
if(!parent) parent = $(svgcontent).children(); // Prevent layers from being included
var contentElems = [];
$(parent).children().each(function(i, elem) {
try {
if (elem.getBBox()) {
contentElems.push({'elem':elem, 'bbox':getStrokedBBox([elem])});
}
} catch(e) {}
});
return contentElems.reverse();
};
// Function: groupSvgElem
// Wrap an SVG element into a group element, mark the group as 'gsvg'
//
// Parameters:
// elem - SVG element to wrap
var groupSvgElem = this.groupSvgElem = function(elem) {
var g = document.createElementNS(svgns, "g");
elem.parentNode.replaceChild(g, elem);
$(g).append(elem).data('gsvg', elem)[0].id = getNextId();
}
// Function: copyElem
// Create a clone of an element, updating its ID and its children's IDs when needed
//
// Parameters:
// el - DOM element to clone
//
// Returns: The cloned element
var copyElem = function(el) {
var new_el = document.createElementNS(el.namespaceURI, el.nodeName);
// set the copied element's new id
new_el.removeAttribute("id");
// manually create a copy of the element
$.each(el.attributes, function(i, attr) {
if (attr.localName != '-moz-math-font-style') {
new_el.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.nodeValue);
}
});
// 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(svgedit.browser.isWebkit() && el.nodeName == 'path') {
var fixed_d = pathActions.convertPath(el);
new_el.setAttribute('d', fixed_d);
}
// now create copies of all children
$.each(el.childNodes, function(i, child) {
switch(child.nodeType) {
case 1: // element node
new_el.appendChild(copyElem(child));
break;
case 3: // text node
new_el.textContent = child.nodeValue;
break;
default:
break;
}
});
if($(el).data('gsvg')) {
$(new_el).data('gsvg', new_el.firstChild);
} else if($(el).data('symbol')) {
var ref = $(el).data('symbol');
$(new_el).data('ref', ref).data('symbol', ref);
}
else if(new_el.tagName == 'image') {
preventClickDefault(new_el);
}
new_el.id = getNextId();
return new_el;
};
// Set scope for these functions
var getId, getNextId, call;
(function(c) {
// Object to contain editor event names and callback functions
var events = {};
getId = c.getId = function() { return getCurrentDrawing().getId(); };
getNextId = c.getNextId = function() { return getCurrentDrawing().getNextId(); };
// Function: call
// Run the callback function associated with the given event
//
// Parameters:
// event - String with the event name
// arg - Argument to pass through to the callback function
call = c.call = function(event, arg) {
if (events[event]) {
return events[event](this, arg);
}
};
// Function: bind
// Attaches a callback function to an event
//
// Parameters:
// event - String indicating the name of the event
// f - The callback function to bind to the event
//
// Return:
// The previous event
c.bind = function(event, f) {
var old = events[event];
events[event] = f;
return old;
};
}(canvas));
// Function: canvas.prepareSvg
// Runs the SVG Document through the sanitizer and then updates its paths.
//
// Parameters:
// newDoc - The SVG DOM document
this.prepareSvg = function(newDoc) {
this.sanitizeSvg(newDoc.documentElement);
// convert paths into absolute commands
var paths = newDoc.getElementsByTagNameNS(svgns, "path");
for (var i = 0, len = paths.length; i < len; ++i) {
var path = paths[i];
path.setAttribute('d', pathActions.convertPath(path));
pathActions.fixEnd(path);
}
};
// Function getRefElem
// Get the reference element associated with the given attribute value
//
// Parameters:
// attrVal - The attribute value as a string
var getRefElem = this.getRefElem = function(attrVal) {
return getElem(getUrlFromAttr(attrVal).substr(1));
}
// Function: ffClone
// 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
//
// Parameters:
// elem - The (text) DOM element to clone
var ffClone = function(elem) {
if(!svgedit.browser.isGecko()) return elem;
var clone = elem.cloneNode(true)
elem.parentNode.insertBefore(clone, elem);
elem.parentNode.removeChild(elem);
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);
// };
// Function: setRotationAngle
// Removes any old rotations if present, prepends a new rotation at the
// transformed center
//
// Parameters:
// val - The new rotation angle in degrees
// preventUndo - Boolean indicating 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];
if (!elem) return;
var oldTransform = elem.getAttribute("transform");
var bbox = svgedit.utilities.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 R_nc and insert it
if (val != 0) {
var center = transformPoint(cx,cy,transformListToTransform(tlist).matrix);
var R_nc = svgroot.createSVGTransform();
R_nc.setRotate(val, center.x, center.y);
if(tlist.numberOfItems) {
tlist.insertItemBefore(R_nc, 0);
} else {
tlist.appendItem(R_nc);
}
}
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);
}
var 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);
};
// Function: recalculateAllSelectedDimensions
// Runs recalculateDimensions on the selected elements,
// adding the changes to a single batch command
var recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions = function() {
var text = (current_resize_mode == "none" ? "position" : "size");
var batchCmd = new BatchCommand(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);
}
};
// 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'];
// Debug tool to easily see the current matrix in the browser's console
var logMatrix = function(m) {
console.log([m.a,m.b,m.c,m.d,m.e,m.f]);
};
// Function: remapElement
// Applies coordinate changes to an element based on the given matrix
//
// Parameters:
// selected - DOM element to be changed
// changes - Object with changes to be remapped
// m - Matrix object to use for remapping coordinates
var remapElement = this.remapElement = function(selected,changes,m) {
var remap = function(x,y) { return transformPoint(x,y,m); },
scalew = function(w) { return m.a*w; },
scaleh = function(h) { return m.d*h; },
doSnapping = curConfig.gridSnapping && selected.parentNode.parentNode.localName === "svg",
finishUp = function() {
if(doSnapping) for(var o in changes) changes[o] = snapToGrid(changes[o]);
assignAttributes(selected, changes, 1000, true);
}
box = svgedit.utilities.getBBox(selected);
for(var i = 0; i < 2; i++) {
var type = i === 0 ? 'fill' : 'stroke';
var attrVal = selected.getAttribute(type);
if(attrVal && attrVal.indexOf('url(') === 0) {
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 = getNextId();
findDefs().appendChild(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 === "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,
t_new = matrixMultiply(existing.inverse(), m, existing);
changes.x = parseFloat(changes.x) + t_new.e;
changes.y = parseFloat(changes.y) + t_new.f;
}
else {
// we just absorb all matrices into the element and don't do any remapping
var chlist = getTransformList(selected);
var mt = svgroot.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 = svgroot.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 = svgedit.math.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 pt1 = remap(changes.x1,changes.y1),
pt2 = remap(changes.x2,changes.y2);
changes.x1 = pt1.x;
changes.y1 = pt1.y;
changes.x2 = pt2.x;
changes.y2 = pt2.y;
case "text":
var tspan = selected.querySelectorAll('tspan');
var i = tspan.length
while(i--) {
var selX = convertToNum("x", selected.getAttribute('x'));
var tx = convertToNum("x", tspan[i].getAttribute('x'));
var selY = convertToNum("y", selected.getAttribute('y'));
var ty = convertToNum("y", tspan[i].getAttribute('y'));
var offset = new Object();
if (!isNaN(selX) && !isNaN(tx) && selX!=0 && tx!=0 && changes.x)
offset.x = changes.x - (selX - tx);
if (!isNaN(selY) && !isNaN(ty) && selY!=0 && ty!=0 && changes.y)
offset.y = changes.y - (selY - ty);
if (offset.x || offset.y)
assignAttributes(tspan[i], offset, 1000, true);
}
finishUp();
break;
case "use":
finishUp();
break;
case "g":
var gsvg = $(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];
pt = remap(pt.x,pt.y);
changes.points[i].x = pt.x;
changes.points[i].y = pt.y;
}
var len = changes.points.length;
var pstr = "";
for (var i = 0; i < len; ++i) {
var pt = changes.points[i];
pstr += pt.x + "," + pt.y + " ";
}
selected.setAttribute("points", pstr);
break;
case "path":
var segList = selected.pathSegList;
var len = segList.numberOfItems;
changes.d = new Array(len);
for (var i = 0; i < len; ++i) {
var seg = segList.getItem(i);
changes.d[i] = {
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
};
}
var len = changes.d.length,
firstseg = changes.d[0],
currentpt = remap(firstseg.x,firstseg.y);
changes.d[0].x = currentpt.x;
changes.d[0].y = currentpt.y;
for (var i = 1; i < len; ++i) {
var seg = changes.d[i];
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
pt = remap(thisx,thisy),
pt1 = remap(seg.x1,seg.y1),
pt2 = remap(seg.x2,seg.y2);
seg.x = pt.x;
seg.y = pt.y;
seg.x1 = pt1.x;
seg.y1 = pt1.y;
seg.x2 = pt2.x;
seg.y2 = pt2.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 = "";
var len = changes.d.length;
for (var i = 0; i < len; ++i) {
var seg = changes.d[i];
var type = seg.type;
dstr += pathMap[type];
switch(type) {
case 13: // relative horizontal line (h)
case 12: // absolute horizontal line (H)
dstr += seg.x + " ";
break;
case 15: // relative vertical line (v)
case 14: // absolute vertical line (V)
dstr += seg.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 += seg.x + "," + seg.y + " ";
break;
case 7: // relative cubic (c)
case 6: // absolute cubic (C)
dstr += seg.x1 + "," + seg.y1 + " " + seg.x2 + "," + seg.y2 + " " +
seg.x + "," + seg.y + " ";
break;
case 9: // relative quad (q)
case 8: // absolute quad (Q)
dstr += seg.x1 + "," + seg.y1 + " " + seg.x + "," + seg.y + " ";
break;
case 11: // relative elliptical arc (a)
case 10: // absolute elliptical arc (A)
dstr += seg.r1 + "," + seg.r2 + " " + seg.angle + " " + (+seg.largeArcFlag) +
" " + (+seg.sweepFlag) + " " + seg.x + "," + seg.y + " ";
break;
case 17: // relative smooth cubic (s)
case 16: // absolute smooth cubic (S)
dstr += seg.x2 + "," + seg.y2 + " " + seg.x + "," + seg.y + " ";
break;
}
}
selected.setAttribute("d", dstr);
break;
}
};
// Function: updateClipPath
// Updates a <clipPath>s values based on the given translation of an element
//
// Parameters:
// attr - The clip-path attribute value with the clipPath's ID
// tx - The translation's x value
// ty - The translation's y value
var updateClipPath = function(attr, tx, ty) {
var path = getRefElem(attr).firstChild;
var cp_xform = getTransformList(path);
var newxlate = svgroot.createSVGTransform();
newxlate.setTranslate(tx, ty);
cp_xform.appendItem(newxlate);
// Update clipPath's dimensions
recalculateDimensions(path);
}
// Function: recalculateDimensions
// Decides the course of action based on the element's transform list
//
// Parameters:
// selected - The DOM element to recalculate
//
// Returns:
// Undo command object with the resulting change
var recalculateDimensions = this.recalculateDimensions = function(selected) {
if (selected == null) return null;
var tlist = getTransformList(selected);
// remove any unnecessary transforms
if (tlist && tlist.numberOfItems > 0) {
var k = tlist.numberOfItems;
while (k--) {
var xform = tlist.getItem(k);
if (xform.type === 0) {
tlist.removeItem(k);
}
// remove identity matrices
else if (xform.type === 1) {
if (svgedit.math.isIdentity(xform.matrix)) {
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) {
selected.removeAttribute("transform");
return null;
}
// TODO: Make this work for more than 2
if (tlist) {
var k = tlist.numberOfItems;
var mxs = [];
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 m_new = svgroot.createSVGTransformFromMatrix(matrixMultiply(mxs[1][0], mxs[0][0]));
tlist.removeItem(mxs[0][1]);
tlist.removeItem(mxs[1][1]);
tlist.insertItemBefore(m_new, 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 = $(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 = {}, initial = null, 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 = $(selected).attr(attrs);
$.each(changes, function(attr, val) {
changes[attr] = convertToNum(attr, val);
});
} else if(gsvg) {
// GSVG exception
changes = {
x: $(gsvg).attr('x') || 0,
y: $(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 = $.extend(true, {}, changes);
$.each(initial, function(attr, val) {
initial[attr] = convertToNum(attr, val);
});
}
// save the start transform value too
initial["transform"] = start_transform ? start_transform : "";
// if it's a regular group, we have special processing to flatten transforms
if ((selected.tagName == "g" && !gsvg) || selected.tagName == "a") {
var box = svgedit.utilities.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),
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;
if ( Math.abs(a) > (1.0e-10) ) {
var s = Math.sin(a)/(1 - Math.cos(a));
} else {
// FIXME: This blows up if the angle is exactly 0!
var s = 2/a;
}
for (var i = 0; i < tlist.numberOfItems; ++i) {
var xform = tlist.getItem(i);
if (xform.type == 4) {
// extract old center through mystical arts
var rm = xform.matrix;
oldcenter.y = (s*rm.e + rm.f)/2;
oldcenter.x = (rm.e - s*rm.f)/2;
tlist.removeItem(i);
break;
}
}
}
var tx = 0, ty = 0,
operation = 0,
N = tlist.numberOfItems;
if(N) {
var first_m = tlist.getItem(0).matrix;
}
// 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 (<metadata>, <defs>, etc)
if (!childTlist) continue;
var m = 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);
// var 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);
var old_start_transform = start_transform;
var childxforms = [];
start_transform = child.getAttribute("transform");
if(angle || hasMatrixTransform(childTlist)) {
var e2t = svgroot.createSVGTransform();
e2t.setMatrix(matrixMultiply(tm, sm, tmn, m));
childTlist.clear();
childTlist.appendItem(e2t);
childxforms.push(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(m.inverse(), tmn, m);
// [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(), m.inverse(), tm, sm, tmn, m, 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);
childxforms.push(translateBack);
childxforms.push(scale);
childxforms.push(translateOrigin);
// logMatrix(translateBack.matrix);
// logMatrix(scale.matrix);
} // not rotated
batchCmd.addSubCommand( recalculateDimensions(child) );
// TODO: If any <use> 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
// var uses = selected.getElementsByTagNameNS(svgns, "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) );
// }
// }
start_transform = old_start_transform;
} // 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 M_inv = transformListToTransform(tlist).matrix.inverse();
var M2 = matrixMultiply( M_inv, 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 clipPaths_done = [];
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(clipPaths_done.indexOf(attr) === -1) {
updateClipPath(attr, tx, ty);
clipPaths_done.push(attr);
}
}
var old_start_transform = start_transform;
start_transform = child.getAttribute("transform");
var childTlist = getTransformList(child);
// some children might not have a transform (<metadata>, <defs>, 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 <use> 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(svgns, "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) );
}
}
start_transform = old_start_transform;
}
}
}
clipPaths_done = [];
start_transform = old_start_transform;
}
}
// 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 m = tlist.getItem(0).matrix,
children = selected.childNodes,
c = children.length;
while (c--) {
var child = children.item(c);
if (child.nodeType == 1) {
var old_start_transform = start_transform;
start_transform = child.getAttribute("transform");
var childTlist = getTransformList(child);
if (!childTlist) continue;
var em = matrixMultiply(m, transformListToTransform(childTlist).matrix);
var e2m = svgroot.createSVGTransform();
e2m.setMatrix(em);
childTlist.clear();
childTlist.appendItem(e2m,0);
batchCmd.addSubCommand( recalculateDimensions(child) );
start_transform = old_start_transform;
// Convert stroke
// TODO: Find out if this should actually happen somewhere else
var sw = child.getAttribute("stroke-width");
if (child.getAttribute("stroke") !== "none" && !isNaN(sw)) {
var avg = (Math.abs(em.a) + Math.abs(em.d)) / 2;
child.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 + first_m.e,
y: oldcenter.y + first_m.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 m = 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 rnew_inv = rnew.matrix.inverse(),
m_inv = m.inverse(),
extrat = matrixMultiply(m_inv, rnew_inv, rold, m);
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 children = selected.childNodes;
var c = children.length;
while (c--) {
var child = children.item(c);
if (child.nodeType == 1) {
var old_start_transform = start_transform;
start_transform = child.getAttribute("transform");
var childTlist = getTransformList(child);
var newxlate = svgroot.createSVGTransform();
newxlate.setTranslate(tx,ty);
if(childTlist.numberOfItems) {
childTlist.insertItemBefore(newxlate, 0);
} else {
childTlist.appendItem(newxlate);
}
batchCmd.addSubCommand( recalculateDimensions(child) );
start_transform = old_start_transform;
}
}
}
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 (<metadata> etc), need to handle this
var box = svgedit.utilities.getBBox(selected);
// Paths (and possbly other shapes) will have no BBox while still in <defs>,
// 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 m = svgroot.createSVGMatrix(),
// temporarily strip off the rotate and save the old center
angle = getRotationAngle(selected);
if (angle) {
var 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;
if ( Math.abs(a) > (1.0e-10) ) {
var s = Math.sin(a)/(1 - Math.cos(a));
} else {
// FIXME: This blows up if the angle is exactly 0!
var s = 2/a;
}
for (var i = 0; i < tlist.numberOfItems; ++i) {
var xform = tlist.getItem(i);
if (xform.type == 4) {
// extract old center through mystical arts
var rm = xform.matrix;
oldcenter.y = (s*rm.e + rm.f)/2;
oldcenter.x = (rm.e - s*rm.f)/2;
tlist.removeItem(i);
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(!svgedit.browser.isWebkit()) {
var fill = selected.getAttribute('fill');
if(fill && fill.indexOf('url(') === 0) {
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
m = transformListToTransform(tlist).matrix;
var gtlist = getTransformList(paint);
var gmatrix = transformListToTransform(gtlist).matrix;
m = matrixMultiply(m, gmatrix);
var m_str = "matrix(" + [m.a,m.b,m.c,m.d,m.e,m.f].join(",") + ")";
paint.setAttribute(type + 'Transform', m_str);
}
}
}
// 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 <use> with a given [T][S][T] would convert to a matrix.
// Is that bad?
// && selected.nodeName != "use"
{
operation = 3; // scale
m = 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
m = transformListToTransform(tlist).matrix;
var e2t = svgroot.createSVGTransform();
e2t.setMatrix(m);
tlist.clear();
tlist.appendItem(e2t);
// reset the matrix so that the element is not re-mapped
m = 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,
meq_inv = meq.inverse();
m = matrixMultiply( meq_inv, 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
m = transformListToTransform(tlist).matrix;
switch (selected.tagName) {
case 'line':
changes = $(selected).attr(["x1","y1","x2","y2"]);
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 i = 0; i < len; ++i) {
var pt = list.getItem(i);
changes.points[i] = {x:pt.x,y:pt.y};
}
}
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 newRot = svgroot.createSVGTransform();
newRot.setRotate(angle,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 or resize, we need to remap the element and absorb the xform
if (operation == 1 || operation == 2 || operation == 3) {
remapElement(selected,changes,m);
} // 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 + m.e,
y: oldcenter.y + m.f
};
}
var newRot = svgroot.createSVGTransform();
newRot.setRotate(angle, newcenter.x, newcenter.y);
if(tlist.numberOfItems) {
tlist.insertItemBefore(newRot, 0);
} else {
tlist.appendItem(newRot);
}
}
}
// [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 m = 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 rnew_inv = rnew.matrix.inverse();
var m_inv = m.inverse();
var extrat = matrixMultiply(m_inv, rnew_inv, rold, m);
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;
};
// Root Current Transformation Matrix in user units
var root_sctm = null;
// Group: Selection
// Function: clearSelection
// Clears the selection. The 'selected' handler is then called.
// Parameters:
// noCall - Optional boolean that when true does not call the "selected" handler
var clearSelection = this.clearSelection = function(noCall) {
if (selectedElements[0] != null) {
var len = selectedElements.length;
for (var i = 0; i < len; ++i) {
var elem = selectedElements[i];
if (elem == null) break;
selectorManager.releaseSelector(elem);
selectedElements[i] = null;
}
// selectedBBoxes[0] = null;
}
if(!noCall) call("selected", selectedElements);
};
// TODO: do we need to worry about selectedBBoxes here?
// Function: addToSelection
// Adds a list of elements to the selection. The 'selected' handler is then called.
//
// Parameters:
// elemsToAdd - an array of DOM elements to add to the selection
// showGrips - a boolean flag indicating whether the resize grips should be shown
var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) {
if (elemsToAdd.length == 0) { 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 || !svgedit.utilities.getBBox(elem)) 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.indexOf(elem) == -1) {
selectedElements[j] = elem;
// only the first selectedBBoxes element is ever used in the codebase these days
// if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
j++;
var sel = selectorManager.requestSelector(elem);
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: http://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);
} else if(a == null) {
return 1;
}
});
// Make sure first elements are not null
while(selectedElements[0] == null) selectedElements.shift(0);
};
// Function: selectOnly()
// Selects only the given elements, shortcut for clearSelection(); addToSelection()
//
// Parameters:
// 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
// Function: removeFromSelection
// Removes elements from the selection.
//
// Parameters:
// elemsToRemove - an array of elements to remove from selection
var removeFromSelection = this.removeFromSelection = function(elemsToRemove) {
if (selectedElements[0] == null) { return; }
if (elemsToRemove.length == 0) { return; }
// find every element and remove it from our array copy
var newSelectedItems = new Array(selectedElements.length);
j = 0,
len = selectedElements.length;
for (var i = 0; i < len; ++i) {
var elem = selectedElements[i];
if (elem) {
// keep the item
if (elemsToRemove.indexOf(elem) == -1) {
newSelectedItems[j] = elem;
j++;
}
else { // remove the item and its selector
selectorManager.releaseSelector(elem);
}
}
}
// the copy becomes the master now
selectedElements = newSelectedItems;
};
// Function: selectAllInCurrentLayer
// Clears the selection, then adds all elements in the current layer to the selection.
this.selectAllInCurrentLayer = function() {
var current_layer = getCurrentDrawing().getCurrentLayer();
if (current_layer) {
current_mode = "select";
selectOnly($(current_group || current_layer).children());
}
};
// Function: getMouseTarget
// Gets the desired element from a mouse event
//
// Parameters:
// evt - Event object from the mouse event
//
// Returns:
// DOM element we want
var getMouseTarget = this.getMouseTarget = function(evt) {
if (evt == null || evt.target == null) {
return null;
}
var mouse_target = evt.target;
// if it was a <use>, Opera and WebKit return the SVGElementInstance
if (mouse_target.correspondingUseElement) mouse_target = mouse_target.correspondingUseElement;
// for foreign content, go up until we find the foreignObject
// WebKit browsers set the mouse target to the svgcanvas div
if ([mathns, htmlns].indexOf(mouse_target.namespaceURI) >= 0 &&
mouse_target.id != "svgcanvas")
{
while (mouse_target.nodeName != "foreignObject") {
mouse_target = mouse_target.parentNode;
if(!mouse_target) return svgroot;
}
}
// Get the desired mouse_target with jQuery selector-fu
// If it's root-like, select the root
var current_layer = getCurrentDrawing().getCurrentLayer();
if([svgroot, container, svgcontent, current_layer].indexOf(mouse_target) >= 0) {
return svgroot;
}
var $target = $(mouse_target);
// If it's a selection grip, return the grip parent
if($target.closest('#selectorParentGroup').length) {
// While we could instead have just returned mouse_target,
// this makes it easier to indentify as being a selector grip
return selectorManager.selectorParentGroup;
}
while (mouse_target.parentNode && mouse_target.parentNode !== (current_group || current_layer)) {
mouse_target = mouse_target.parentNode;
}
//
// // go up until we hit a child of a layer
// while (mouse_target.parentNode.parentNode.tagName == 'g') {
// mouse_target = mouse_target.parentNode;
// }
// Webkit bubbles the mouse event all the way up to the div, so we
// set the mouse_target to the svgroot like the other browsers
// if (mouse_target.nodeName.toLowerCase() == "div") {
// mouse_target = svgroot;
// }
return mouse_target;
};
// Mouse events
(function() {
var d_attr = null,
start_x = null,
start_y = null,
r_start_x = null,
r_start_y = null,
init_bbox = {},
freehand = {
minx: null,
miny: null,
maxx: null,
maxy: null
};
// - 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(evt)
{
if (canvas.spaceKey) return;
var right_click = evt.button === 2;
root_sctm = svgcontent.querySelector("g").getScreenCTM().inverse();
var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
mouse_x = pt.x * current_zoom,
mouse_y = pt.y * current_zoom;
evt.preventDefault();
if(right_click) {
current_mode = "select";
lastClickPoint = pt;
}
var x = mouse_x / current_zoom,
y = mouse_y / current_zoom,
mouse_target = getMouseTarget(evt);
if(mouse_target.tagName === 'a' && mouse_target.childNodes.length === 1) {
mouse_target = mouse_target.firstChild;
}
// real_x/y ignores grid-snap value
var real_x = r_start_x = start_x = x;
var real_y = r_start_y = start_y = y;
if(curConfig.gridSnapping){
x = snapToGrid(x);
y = snapToGrid(y);
start_x = snapToGrid(start_x);
start_y = snapToGrid(start_y);
}
// if it is a selector grip, then it must be a single element selected,
// set the mouse_target to that and update the mode to rotate/resize
if (mouse_target == selectorManager.selectorParentGroup && selectedElements[0] != null) {
var grip = evt.target;
var griptype = elData(grip, "type");
// rotating
if (griptype == "rotate") {
current_mode = "rotate";
current_rotate_mode = elData(grip, "dir");
}
// resizing
else if(griptype == "resize") {
current_mode = "resize";
current_resize_mode = elData(grip, "dir");
}
mouse_target = selectedElements[0];
}
start_transform = mouse_target.getAttribute("transform");
var tlist = getTransformList(mouse_target);
switch (current_mode) {
case "select":
started = true;
current_resize_mode = "none";
if(right_click) started = false;
if (mouse_target != svgroot) {
// if this element is not yet selected, clear selection and select it
if (selectedElements.indexOf(mouse_target) == -1) {
// 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([mouse_target]);
justSelected = mouse_target;
pathActions.clear();
}
// else if it's a path, go into pathedit mode in mouseup
if(!right_click) {
// insert a dummy transform so if the element(s) are moved it will have
// a transform to use for its translate
for (var 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(!right_click){
clearSelection();
current_mode = "multiselect";
if (rubberBox == null) {
rubberBox = selectorManager.getRubberBandBox();
}
r_start_x *= current_zoom;
r_start_y *= current_zoom;
// console.log('p',[evt.pageX, evt.pageY]);
// console.log('c',[evt.clientX, evt.clientY]);
// console.log('o',[evt.offsetX, evt.offsetY]);
// console.log('s',[start_x, start_y]);
assignAttributes(rubberBox, {
'x': r_start_x,
'y': r_start_y,
'width': 0,
'height': 0,
'display': 'inline'
}, 100);
}
break;
case "zoom":
started = true;
if (rubberBox == null) {
rubberBox = selectorManager.getRubberBandBox();
}
assignAttributes(rubberBox, {
'x': real_x * current_zoom,
'y': real_x * current_zoom,
'width': 0,
'height': 0,
'display': 'inline'
}, 100);
break;
case "resize":
started = true;
start_x = x;
start_y = y;
// Getting the BBox from the selection box, since we know we
// want to orient around it
init_bbox = svgedit.utilities.getBBox($('#selectedBox0')[0]);
var bb = {};
$.each(init_bbox, function(key, val) {
bb[key] = val/current_zoom;
});
init_bbox = bb;
// append three dummy transforms to the tlist so that
// we can translate,scale,translate in mousemove
var pos = getRotationAngle(mouse_target)?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(svgedit.browser.supportsNonScalingStroke()) {
//Handle crash for newer Webkit: 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 (all isChrome blocks) once vendor fixes the issue
var isWebkit = svgedit.browser.isWebkit();
if(isWebkit) {
var delayedStroke = function(ele) {
var _stroke = ele.getAttributeNS(null, 'stroke');
ele.removeAttributeNS(null, 'stroke');
//Re-apply stroke after delay. Anything higher than 1 seems to cause flicker
setTimeout(function() { ele.setAttributeNS(null, 'stroke', _stroke) }, 0);
}
}
mouse_target.style.vectorEffect = 'non-scaling-stroke';
if(isWebkit) delayedStroke(mouse_target);
var all = mouse_target.getElementsByTagName('*'),
len = all.length;
for(var i = 0; i < len; i++) {
all[i].style.vectorEffect = 'non-scaling-stroke';
if(isWebkit) delayedStroke(all[i]);
}
}
}
break;
case "fhellipse":
case "fhrect":
case "fhpath":
started = true;
d_attr = real_x + "," + real_y + " ";
var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
addSvgElementFromJson({
"element": "polyline",
"curStyles": true,
"attr": {
"points": d_attr,
"id": getNextId(),
"fill": "none",
"opacity": cur_shape.opacity / 2,
"stroke-linecap": "round",
"style": "pointer-events:none"
}
});
freehand.minx = real_x;
freehand.maxx = real_x;
freehand.miny = real_y;
freehand.maxy = real_y;
break;
case "image":
started = true;
var newImage = addSvgElementFromJson({
"element": "image",
"attr": {
"x": x,
"y": y,
"width": 0,
"height": 0,
"id": getNextId(),
"opacity": cur_shape.opacity / 2,
"style": "pointer-events:inherit"
}
});
setHref(newImage, last_good_img_url);
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)
case "rect":
started = true;
start_x = x;
start_y = y;
addSvgElementFromJson({
"element": "rect",
"curStyles": true,
"attr": {
"x": x,
"y": y,
"width": 0,
"height": 0,
"id": getNextId(),
"opacity": cur_shape.opacity / 2
}
});
break;
case "line":
started = true;
var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
addSvgElementFromJson({
"element": "line",
"curStyles": true,
"attr": {
"x1": x,
"y1": y,
"x2": x,
"y2": y,
"id": getNextId(),
"stroke": cur_shape.stroke,
"stroke-width": stroke_w,
"stroke-dasharray": cur_shape.stroke_dasharray,
"stroke-linejoin": cur_shape.stroke_linejoin,
"stroke-linecap": cur_shape.stroke_linecap,
"stroke-opacity": cur_shape.stroke_opacity,
"fill": "none",
"opacity": cur_shape.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": cur_shape.opacity / 2
}
});
break;
case "ellipse":
started = true;
addSvgElementFromJson({
"element": "ellipse",
"curStyles": true,
"attr": {
"cx": x,
"cy": y,
"rx": 0,
"ry": 0,
"id": getNextId(),
"opacity": cur_shape.opacity / 2
}
});
break;
case "text":
started = true;
var newText = addSvgElementFromJson({
"element": "text",
"curStyles": true,
"attr": {
"x": x,
"y": y,
"id": getNextId(),
"fill": cur_text.fill,
"stroke-width": cur_text.stroke_width,
"font-size": cur_text.font_size,
"font-family": cur_text.font_family,
"text-anchor": "start",
"xml:space": "preserve",
"opacity": cur_shape.opacity
}
});
// newText.textContent = "text";
break;
case "path":
// Fall through
case "pathedit":
start_x *= current_zoom;
start_y *= current_zoom;
pathActions.mouseDown(evt, mouse_target, start_x, start_y);
started = true;
break;
case "textedit":
start_x *= current_zoom;
start_y *= current_zoom;
textActions.mouseDown(evt, mouse_target, start_x, start_y);
started = true;
break;
case "rotate":
started = true;
// we are starting an undoable change (a drag-rotation)
canvas.undoMgr.beginUndoableChange("transform", selectedElements);
document.getElementById("workarea").className = "rotate";
break;
default:
// This could occur in an extension
break;
}
var ext_result = runExtensions("mouseDown", {
event: evt,
start_x: start_x,
start_y: start_y,
selectedElements: selectedElements
}, true);
$.each(ext_result, function(i, r) {
if(r && r.started) {
started = true;
}
});
if (current_mode) {
document.getElementById("workarea").className =
(current_mode == "resize")
? evt.target.style.cursor
: current_mode
}
};
// 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(evt) {
if (evt.originalEvent.touches && evt.originalEvent.touches.length > 1) return;
if (!started) return;
if(evt.button === 1 || canvas.spaceKey) return;
var selected = selectedElements[0],
pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
mouse_x = pt.x * current_zoom,
mouse_y = pt.y * current_zoom,
shape = getElem(getId());
var real_x = x = mouse_x / current_zoom;
var real_y = y = mouse_y / current_zoom;
if(curConfig.gridSnapping){
x = snapToGrid(x);
y = snapToGrid(y);
}
evt.preventDefault();
switch (current_mode)
{
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) {
var dx = x - start_x;
var dy = y - start_y;
if(curConfig.gridSnapping){
dx = snapToGrid(dx);
dy = snapToGrid(dy);
}
if(evt.shiftKey) {
var xya = snapToAngle(start_x,start_y,x,y); x=xya.x; y=xya.y;
}
if (dx != 0 || dy != 0) {
var len = selectedElements.length;
for (var i = 0; i < len; ++i) {
var selected = selectedElements[i];
if (selected == null) break;
// if (i==0) {
// var box = svgedit.utilities.getBBox(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();
var 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
if (xya) {
dx = xya.x - start_x
dy = xya.y - start_y
}
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();
}
//duplicate only once
// alt drag = create a clone and save the reference
if(evt.altKey) {
//clone doesn't exist yet
if (!canvas.addClones) {
canvas.addClones = canvas.cloneSelectedElements(0,0, xform);
canvas.removeClones = function(){
if (canvas.addClones) {
canvas.addClones.forEach(function(clone){
if (clone.parentNode) clone.parentNode.removeChild(clone)
canvas.addClones = false;
})
}
}
window.addEventListener("keyup", canvas.removeClones)
}
}
call("transition", selectedElements);
}
}
break;
case "multiselect":
real_x *= current_zoom;
real_y *= current_zoom;
assignAttributes(rubberBox, {
'x': Math.min(r_start_x, real_x),
'y': Math.min(r_start_y, real_y),
'width': Math.abs(real_x - r_start_x),
'height': Math.abs(real_y - r_start_y)
},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 = [], elemsToAdd = [],
newList = getIntersectionList(),
len = selectedElements.length;
for (var i = 0; i < len; ++i) {
var ind = newList.indexOf(selectedElements[i]);
if (ind == -1) {
elemsToRemove.push(selectedElements[i]);
}
else {
newList[ind] = null;
}
}
len = newList.length;
for (i = 0; i < len; ++i) { if (newList[i]) elemsToAdd.push(newList[i]); }
if (elemsToRemove.length > 0)
canvas.removeFromSelection(elemsToRemove);
if (elemsToAdd.length > 0)
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
var tlist = getTransformList(selected),
hasMatrix = hasMatrixTransform(tlist),
box = hasMatrix ? init_bbox : svgedit.utilities.getBBox(selected),
left=box.x, top=box.y, width=box.width,
height=box.height, dx=(x-start_x), dy=(y-start_y);
if(curConfig.gridSnapping){
dx = snapToGrid(dx);
dy = snapToGrid(dy);
height = snapToGrid(height);
width = snapToGrid(width);
}
// if rotated, adjust the dx,dy values
var 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(current_resize_mode.indexOf("n")==-1 && current_resize_mode.indexOf("s")==-1) {
dy = 0;
}
if(current_resize_mode.indexOf("e")==-1 && current_resize_mode.indexOf("w")==-1) {
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(current_resize_mode.indexOf("n") >= 0) {
sy = height ? (height-dy)/height : 1;
ty = height;
}
// if we dragging on the east side, then adjust the scale factor and tx
if(current_resize_mode.indexOf("w") >= 0) {
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, 0+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":
real_x *= current_zoom;
real_y *= current_zoom;
assignAttributes(rubberBox, {
'x': Math.min(r_start_x*current_zoom, real_x),
'y': Math.min(r_start_y*current_zoom, real_y),
'width': Math.abs(real_x - r_start_x*current_zoom),
'height': Math.abs(real_y - r_start_y*current_zoom)
},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) { var xya = snapToAngle(start_x,start_y,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":
case "image":
var square = (current_mode == 'square') || evt.shiftKey,
w = Math.abs(x - start_x),
h = Math.abs(y - start_y),
new_x, new_y;
if(square) {
w = h = Math.max(w, h);
new_x = start_x < x ? start_x : start_x - w;
new_y = start_y < y ? start_y : start_y - h;
} else {
new_x = Math.min(start_x,x);
new_y = Math.min(start_y,y);
}
if (evt.altKey){
w *=2;
h *=2;
new_x = start_x - w/2;
new_y = start_y - h/2;
}
if(curConfig.gridSnapping){
w = snapToGrid(w);
h = snapToGrid(h);
new_x = snapToGrid(new_x);
new_y = snapToGrid(new_y);
}
assignAttributes(shape,{
'width': w,
'height': h,
'x': new_x,
'y': new_y
},1000);
break;
case "circle":
var c = $(shape).attr(["cx", "cy"]);
var cx = c.cx, cy = c.cy,
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":
var c = $(shape).attr(["cx", "cy"]);
var cx = Math.abs(start_x + (x - start_x)/2)
var cy = Math.abs(start_y + (y - start_y)/2);
if(curConfig.gridSnapping){
x = snapToGrid(x);
cx = snapToGrid(cx);
y = snapToGrid(y);
cy = snapToGrid(cy);
}
var rx = Math.abs(start_x - cx)
var ry = Math.abs(start_y - cy);
if (evt.shiftKey) {
ry = rx
cy = (y > start_y) ? start_y + rx : start_y - rx
}
if (evt.altKey) {
cx = start_x
cy = start_y
rx = Math.abs(x - cx)
ry = evt.shiftKey ? rx : Math.abs(y - cy);
}
shape.setAttributeNS(null, "rx", rx );
shape.setAttributeNS(null, "ry", ry );
shape.setAttributeNS(null, "cx", cx );
shape.setAttributeNS(null, "cy", cy );
break;
case "fhellipse":
case "fhrect":
freehand.minx = Math.min(real_x, freehand.minx);
freehand.maxx = Math.max(real_x, freehand.maxx);
freehand.miny = Math.min(real_y, freehand.miny);
freehand.maxy = Math.max(real_y, freehand.maxy);
// break; missing on purpose
case "fhpath":
d_attr += + real_x + "," + real_y + " ";
shape.setAttributeNS(null, "points", d_attr);
break;
// update path stretch line coordinates
case "path":
// fall through
case "pathedit":
x *= current_zoom;
y *= current_zoom;
if(curConfig.gridSnapping){
x = snapToGrid(x);
y = snapToGrid(y);
start_x = snapToGrid(start_x);
start_y = snapToGrid(start_y);
}
if(evt.shiftKey) {
var path = svgedit.path.path;
if(path) {
var x1 = path.dragging?path.dragging[0]:start_x;
var y1 = path.dragging?path.dragging[1]:start_y;
} else {
var x1 = start_x;
var y1 = start_y;
}
var xya = snapToAngle(x1,y1,x,y);
x=xya.x; y=xya.y;
}
if(rubberBox && rubberBox.getAttribute('display') !== 'none') {
real_x *= current_zoom;
real_y *= current_zoom;
assignAttributes(rubberBox, {
'x': Math.min(r_start_x*current_zoom, real_x),
'y': Math.min(r_start_y*current_zoom, real_y),
'width': Math.abs(real_x - r_start_x*current_zoom),
'height': Math.abs(real_y - r_start_y*current_zoom)
},100);
}
pathActions.mouseMove(evt, x, y);
break;
case "textedit":
x *= current_zoom;
y *= current_zoom;
// if(rubberBox && rubberBox.getAttribute('display') != 'none') {
// assignAttributes(rubberBox, {
// 'x': Math.min(start_x,x),
// 'y': Math.min(start_y,y),
// 'width': Math.abs(x-start_x),
// 'height': Math.abs(y-start_y)
// },100);
// }
textActions.mouseMove(mouse_x, mouse_y);
break;
case "rotate":
var box = svgedit.utilities.getBBox(selected),
cx = box.x + box.width/2,
cy = box.y + box.height/2,
m = getMatrix(selected),
center = transformPoint(cx,cy,m);
cx = center.x;
cy = center.y;
var ccx = box.x // ne
var ccy = box.y // ne
if (current_rotate_mode == "nw") ccx = box.x + box.width;
if (current_rotate_mode == "se") ccy = box.y + box.height;
if (current_rotate_mode == "sw"){ ccx = box.x + box.width; ccy = box.y + box.height; }
compensation_angle = ((Math.atan2(cy-ccy,cx-ccx) * (180/Math.PI))-90) % 360;
var angle = ((Math.atan2(cy-y,cx-x) * (180/Math.PI))-90) % 360;
angle += compensation_angle;
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: mouse_x,
mouse_y: mouse_y,
selected: selected
});
}; // mouseMove()
/* mouseover mode
var mouseOver = function(evt) {
if(canvas.spaceKey || evt.button === 1 || current_mode != "select") return;
evt.stopPropagation();
mouse_target = getMouseTarget(evt);
if (svghover.lastChild) svghover.removeChild(svghover.lastChild);
if (mouse_target.id == "svgroot") return
switch (mouse_target.nodeName) {
case "polyline":
case "line":
case "path":
case "ellipse":
case "rect":
var clone = mouse_target.cloneNode(true);
clone.setAttribute("stroke", "#c00")
clone.setAttribute("stroke-width", "1")
clone.setAttribute("stroke-opacity", "1")
clone.setAttribute("shape-rendering", "crispEdges")
clone.setAttribute("fill", "none")
hover_group.appendChild(clone);
break;
default:
break;
}
}
*/
// - 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(evt)
{
canvas.addClones = false;
window.removeEventListener("keyup", canvas.removeClones)
selectedElements = selectedElements.filter(Boolean)
if(evt.button === 2) return;
var tempJustSelected = justSelected;
justSelected = null;
if (!started) return;
var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
mouse_x = pt.x * current_zoom,
mouse_y = pt.y * current_zoom,
x = mouse_x / current_zoom,
y = mouse_y / current_zoom,
element = getElem(getId()),
keep = false;
var real_x = x;
var real_y = y;
// TODO: Make true when in multi-unit mode
var useUnit = false; // (curConfig.baseUnit !== 'px');
started = false;
switch (current_mode)
{
// intentionally fall-through to select here
case "resize":
case "multiselect":
if (rubberBox != null) {
rubberBox.setAttribute("display", "none");
curBBoxes = [];
}
current_mode = "select";
case "select":
if (selectedElements[0] != null) {
// if we only have one selected element
if (selectedElements.length == 1) {
// 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:
cur_properties.fill = selected.getAttribute("fill");
cur_properties.fill_opacity = selected.getAttribute("fill-opacity");
cur_properties.stroke = selected.getAttribute("stroke");
cur_properties.stroke_opacity = selected.getAttribute("stroke-opacity");
cur_properties.stroke_width = selected.getAttribute("stroke-width");
cur_properties.stroke_dasharray = selected.getAttribute("stroke-dasharray");
cur_properties.stroke_linejoin = selected.getAttribute("stroke-linejoin");
cur_properties.stroke_linecap = selected.getAttribute("stroke-linecap");
}
if (selected.tagName == "text") {
cur_text.font_size = selected.getAttribute("font-size");
cur_text.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
r_start_x = r_start_x;
r_start_y = r_start_y;
var difference_x = Math.abs(real_x-r_start_x);
var difference_y = Math.abs(real_y-r_start_y);
if (difference_y > 1 || difference_y > 1) {
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 {
var t = evt.target;
if (selectedElements[0].nodeName === "path" && selectedElements[1] == null) {
pathActions.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(svgedit.browser.supportsNonScalingStroke()) {
var elem = selectedElements[0];
if (elem) {
elem.removeAttribute('style');
svgedit.utilities.walkTree(elem, function(elem) {
elem.removeAttribute('style');
});
}
}
}
return;
break;
case "zoom":
if (rubberBox != null) {
rubberBox.setAttribute("display", "none");
}
var factor = evt.altKey?.5:2;
call("zoomed", {
'x': Math.min(r_start_x, real_x),
'y': Math.min(r_start_y, real_y),
'width': Math.abs(real_x - r_start_x),
'height': Math.abs(real_y - r_start_y),
'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
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.smoothPolylineIntoPath(element);
}
break;
case "line":
var attrs = $(element).attr(["x1", "x2", "y1", "y2"]);
keep = (attrs.x1 != attrs.x2 || attrs.y1 != attrs.y2);
break;
case "foreignObject":
case "square":
case "rect":
case "image":
var attrs = $(element).attr(["width", "height"]);
// Image should be kept regardless of size (use inherit dimensions later)
keep = (attrs.width != 0 || attrs.height != 0) || current_mode === "image";
break;
case "circle":
keep = (element.getAttribute('r') != 0);
break;
case "ellipse":
var attrs = $(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.mouseUp(evt, element, mouse_x, mouse_y);
element = res.element;
keep = res.keep;
break;
case "pathedit":
keep = true;
element = null;
pathActions.mouseUp(evt);
break;
case "textedit":
keep = false;
element = null;
textActions.mouseUp(evt, mouse_x, mouse_y);
break;
case "rotate":
keep = true;
element = null;
current_mode = "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 ext_result = runExtensions("mouseUp", {
event: evt,
mouse_x: mouse_x,
mouse_y: mouse_y
}, true);
$.each(ext_result, function(i, r) {
if(r) {
keep = r.keep || keep;
element = r.element;
started = r.started || started;
}
});
if (!keep && element != null) {
getCurrentDrawing().releaseId(getId());
element.parentNode.removeChild(element);
element = null;
var 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 <a> elements
while (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 <div> when the canvas is clicked, Firefox/Opera return <svg>
if ( (current_mode != "path" || !drawn_path) &&
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;
if(useUnit) svgedit.units.convertAttrs(element);
var ani_dur = .2, c_ani;
if(opac_ani.beginElement && element.getAttribute('opacity') != cur_shape.opacity) {
c_ani = $(opac_ani).clone().attr({
to: cur_shape.opacity,
dur: ani_dur
}).appendTo(element);
try {
// Fails in FF4 on foreignObject
c_ani[0].beginElement();
} catch(e){}
} else {
ani_dur = 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(c_ani) c_ani.remove();
element.setAttribute("opacity", cur_shape.opacity);
element.setAttribute("style", "pointer-events:inherit");
cleanupElement(element);
if(current_mode === "path") {
pathActions.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(element));
call("changed",[element]);
}, ani_dur * 1000);
}
start_transform = null;
};
var dblClick = function(evt) {
var evt_target = evt.target;
var parent = evt_target.parentNode;
var mouse_target = getMouseTarget(evt);
var tagName = mouse_target.tagName;
if(parent === current_group) return;
if(tagName === 'text' && current_mode !== 'textedit') {
var pt = transformPoint( evt.pageX, evt.pageY, root_sctm );
textActions.select(mouse_target, pt.x, pt.y);
}
if((tagName === "g" || tagName === "a") && getRotationAngle(mouse_target)) {
// TODO: Allow method of in-group editing without having to do
// this (similar to editing rotated paths)
// Ungroup and regroup
pushGroupProperties(mouse_target);
mouse_target = selectedElements[0];
clearSelection(true);
}
// Reset context
if(current_group) {
leaveContext();
}
if((parent.tagName !== 'g' && parent.tagName !== 'a') ||
parent === getCurrentDrawing().getCurrentLayer() ||
mouse_target === selectorManager.selectorParentGroup)
{
// Escape from in-group edit
return;
}
setContext(mouse_target);
}
// prevent links from being followed in the canvas
var handleLinkInCanvas = function(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.
$(container).mousedown(mouseDown).mousemove(mouseMove).click(handleLinkInCanvas).dblclick(dblClick).mouseup(mouseUp);
// $(window).mouseup(mouseUp);
$(container).bind("mousewheel DOMMouseScroll", function(e){
if(!e.shiftKey) return;
e.preventDefault();
root_sctm = svgcontent.getScreenCTM().inverse();
var pt = transformPoint( e.pageX, e.pageY, root_sctm );
var bbox = {
'x': pt.x,
'y': pt.y,
'width': 0,
'height': 0
};
// Respond to mouse wheel in IE/Webkit/Opera.
// (It returns up/dn motion in multiples of 120)
if(e.wheelDelta) {
if (e.wheelDelta >= 120) {
bbox.factor = 2;
} else if (e.wheelDelta <= -120) {
bbox.factor = .5;
}
} else if(e.detail) {
if (e.detail > 0) {
bbox.factor = .5;
} else if (e.detail < 0) {
bbox.factor = 2;
}
}
if(!bbox.factor) return;
call("zoomed", bbox);
});
}());
// Function: preventClickDefault
// Prevents default browser click behaviour on the given element
//
// Parameters:
// img - The DOM element to prevent the cilck on
var preventClickDefault = function(img) {
$(img).click(function(e){e.preventDefault()});
}
// Group: Text edit functions
// Functions relating to editing text elements
var textActions = canvas.textActions = function() {
var curtext;
var textinput;
var cursor;
var selblock;
var blinker;
var chardata = [];
var textbb, transbb;
var matrix;
var last_x, last_y;
var allow_dbl;
function setCursor(index) {
var empty = (textinput.value === "");
$(textinput).focus();
if(!arguments.length) {
if(empty) {
index = 0;
} else {
if(textinput.selectionEnd !== textinput.selectionStart) return;
index = textinput.selectionEnd;
}
}
var charbb;
charbb = chardata[index];
if(!empty) {
textinput.setSelectionRange(index, index);
}
cursor = getElem("text_cursor");
if (!cursor) {
cursor = document.createElementNS(svgns, "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 start_pt = ptToScreen(charbb.x, textbb.y);
var end_pt = ptToScreen(charbb.x, (textbb.y + textbb.height));
assignAttributes(cursor, {
x1: start_pt.x,
y1: start_pt.y,
x2: end_pt.x,
y2: end_pt.y,
visibility: 'visible',
display: 'inline'
});
if(selblock) selblock.setAttribute('d', 'M 0 0');
}
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(svgns, "path");
assignAttributes(selblock, {
'id': "text_selectblock",
'fill': "green",
'opacity': .5,
'style': "pointer-events:none"
});
getElem("selectorParentGroup").appendChild(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(mouse_x, mouse_y) {
// Position cursor here
var pt = svgroot.createSVGPoint();
pt.x = mouse_x;
pt.y = mouse_y;
// 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(mouse_x <= 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(mouse_x > mid) {
charpos++;
}
return charpos;
}
function setCursorFromPoint(mouse_x, mouse_y) {
setCursor(getIndexFromPoint(mouse_x, mouse_y));
}
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(x_in, y_in) {
var out = {
x: x_in,
y: y_in
}
out.x /= current_zoom;
out.y /= current_zoom;
if(matrix) {
var pt = transformPoint(out.x, out.y, matrix.inverse());
out.x = pt.x;
out.y = pt.y;
}
return out;
}
function ptToScreen(x_in, y_in) {
var out = {
x: x_in,
y: y_in
}
if(matrix) {
var pt = transformPoint(out.x, out.y, matrix);
out.x = pt.x;
out.y = pt.y;
}
out.x *= current_zoom;
out.y *= current_zoom;
return out;
}
function hideCursor() {
if(cursor) {
cursor.setAttribute('visibility', 'hidden');
}
}
function selectAll(evt) {
setSelection(0, curtext.textContent.length);
$(this).unbind(evt);
}
function selectWord(evt) {
if(!allow_dbl || !curtext) return;
var ept = transformPoint( evt.pageX, evt.pageY, root_sctm ),
mouse_x = ept.x * current_zoom,
mouse_y = ept.y * current_zoom;
var pt = screenToPt(mouse_x, mouse_y);
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
$(evt.target).click(selectAll);
setTimeout(function() {
$(evt.target).unbind('click', selectAll);
}, 300);
}
return {
select: function(target, x, y) {
curtext = target;
textActions.toEditMode(x, y);
},
start: function(elem) {
curtext = elem;
textActions.toEditMode();
},
mouseDown: function(evt, mouse_target, start_x, start_y) {
var pt = screenToPt(start_x, start_y);
textinput.focus();
setCursorFromPoint(pt.x, pt.y);
last_x = start_x;
last_y = start_y;
// TODO: Find way to block native selection
},
mouseMove: function(mouse_x, mouse_y) {
var pt = screenToPt(mouse_x, mouse_y);
setEndSelectionFromPoint(pt.x, pt.y);
},
mouseUp: function(evt, mouse_x, mouse_y) {
var pt = screenToPt(mouse_x, mouse_y);
setEndSelectionFromPoint(pt.x, pt.y, true);
// TODO: Find a way to make this work: Use transformed BBox instead of evt.target
// if(last_x === mouse_x && last_y === mouse_y
// && !svgedit.math.rectsIntersect(transbb, {x: pt.x, y: pt.y, width:0, height:0})) {
// textActions.toSelectMode(true);
// }
if(
evt.target !== curtext
&& mouse_x < last_x + 2
&& mouse_x > last_x - 2
&& mouse_y < last_y + 2
&& mouse_y > last_y - 2) {
textActions.toSelectMode(true);
}
},
setCursor: setCursor,
toEditMode: function(x, y) {
selectOnly([curtext], false)
allow_dbl = false;
current_mode = "textedit";
selectorManager.requestSelector(curtext).showGrips(false);
// Make selector group accept clicks
var sel = selectorManager.requestSelector(curtext).selectorRect;
textActions.init();
$(curtext).css('cursor', 'text');
if(!arguments.length) {
setCursor();
} else {
var pt = screenToPt(x, y);
setCursorFromPoint(pt.x, pt.y);
}
setTimeout(function() {
allow_dbl = true;
}, 300);
},
toSelectMode: function(selectElem) {
current_mode = "select";
clearInterval(blinker);
blinker = null;
if(selblock) $(selblock).attr('display','none');
if(cursor) $(cursor).attr('visibility','hidden');
$(curtext).css('cursor', 'move');
if(selectElem) {
clearSelection();
$(curtext).css('cursor', 'move');
call("selected", [curtext]);
addToSelection([curtext], true);
}
if(curtext && !curtext.textContent.length) {
// No content, so delete
canvas.deleteSelectedElements();
}
$(textinput).blur();
curtext = false;
// if(svgedit.browser.supportsEditableText()) {
// curtext.removeAttribute('editable');
// }
},
setInputElem: function(elem) {
textinput = elem;
// $(textinput).blur(hideCursor);
},
clear: function() {
if(current_mode == "textedit") {
textActions.toSelectMode();
}
},
init: function(inputElem) {
if(!curtext) return;
// if(svgedit.browser.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 = svgedit.utilities.getBBox(curtext);
matrix = xform?getMatrix(curtext):null;
chardata = Array(len);
textinput.focus();
$(curtext).unbind('dblclick', selectWord).dblclick(selectWord);
if(!len) {
var end = {x: textbb.x + (textbb.width/2), width: 0};
}
for(var i=0; i<len; i++) {
var start = curtext.getStartPositionOfChar(i);
var end = curtext.getEndPositionOfChar(i);
if(!svgedit.browser.supportsGoodTextCharPos()) {
var offset = canvas.contentW * current_zoom;
start.x -= offset;
end.x -= offset;
start.x /= current_zoom;
end.x /= current_zoom;
}
// 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);
}
}
}();
// TODO: Migrate all of this code into path.js
// Group: Path edit functions
// Functions relating to editing path elements
var pathActions = canvas.pathActions = function() {
var subpath = false;
var current_path;
var newPoint, firstCtrl;
function resetD(p) {
p.setAttribute("d", pathActions.convertPath(p));
}
// TODO: Move into path.js
svgedit.path.Path.prototype.endChanges = function(text) {
if(svgedit.browser.isWebkit()) resetD(this.elem);
var cmd = new ChangeElementCommand(this.elem, {d: this.last_d}, text);
addCommandToHistory(cmd);
call("changed", [this.elem]);
}
svgedit.path.Path.prototype.addPtsToSelection = function(indexes) {
if(!$.isArray(indexes)) indexes = [indexes];
for(var i=0; i< indexes.length; i++) {
var index = indexes[i];
var seg = this.segs[index];
if(seg.ptgrip) {
if(this.selected_pts.indexOf(index) == -1 && index >= 0) {
this.selected_pts.push(index);
}
}
};
this.selected_pts.sort();
var i = this.selected_pts.length,
grips = new Array(i);
// Loop through points to be selected and highlight each
while(i--) {
var pt = this.selected_pts[i];
var seg = this.segs[pt];
seg.select(true);
grips[i] = seg.ptgrip;
}
// TODO: Correct this:
pathActions.canDeleteNodes = true;
pathActions.closed_subpath = this.subpathIsClosed(this.selected_pts[0]);
call("selected", grips);
}
var current_path = null,
drawn_path = null,
hasMoved = false,
stretchy = null;
this.lastCtrlPoint = [0, 0];
// 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(element) {
var 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
// - http://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
// - http://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
// - http://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 (var 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 = svgedit.path.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");
for(;i < N;++i) {
var pt = points.getItem(i);
d.push([pt.x,pt.y].join(","));
}
d = d.join(" ");
// create new path element
element = addSvgElementFromJson({
"element": "path",
"curStyles": true,
"attr": {
"id": getId(),
"d": d,
"fill": "none"
}
});
// No need to call "changed", as this is already done under mouseUp
}
return element;
};
return {
mouseDown: function(evt, mouse_target, start_x, start_y) {
if(current_mode === "path") {
mouse_x = start_x;
mouse_y = start_y;
var x = mouse_x/current_zoom,
y = mouse_y/current_zoom,
stretchy = getElem("path_stretch_line");
newPoint = [x, y];
if(curConfig.gridSnapping){
x = snapToGrid(x);
y = snapToGrid(y);
mouse_x = snapToGrid(mouse_x);
mouse_y = snapToGrid(mouse_y);
}
if (!stretchy) {
stretchy = document.createElementNS(svgns, "path");
assignAttributes(stretchy, {
'id': "path_stretch_line",
'stroke': "#22C",
'stroke-width': "0.5",
'fill': 'none'
});
stretchy = getElem("selectorParentGroup").appendChild(stretchy);
}
stretchy.setAttribute("display", "inline");
this.stretchy = stretchy;
var keep = null;
// if pts array is empty, create path element with M at current point
if (!drawn_path) {
d_attr = "M" + x + "," + y + " ";
drawn_path = addSvgElementFromJson({
"element": "path",
"curStyles": true,
"attr": {
"d": d_attr,
"id": getNextId(),
"opacity": cur_shape.opacity / 2
}
});
// set stretchy line to first point
stretchy.setAttribute('d', ['M', mouse_x, mouse_y, mouse_x, mouse_y].join(' '));
var index = subpath ? svgedit.path.path.segs.length : 0;
svgedit.path.addPointGrip(index, mouse_x, mouse_y);
svgedit.path.first_grip = null;
}
else {
// determine if we clicked on an existing point
var seglist = drawn_path.pathSegList;
var i = seglist.numberOfItems;
var FUZZ = 6/current_zoom;
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
var id = getId();
// Remove previous path object if previously created
svgedit.path.removePath_(id);
var newpath = getElem(id);
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 abs_x = seglist.getItem(0).x;
var abs_y = seglist.getItem(0).y;
var grip_x = svgedit.path.first_grip ? svgedit.path.first_grip[0]/current_zoom : seglist.getItem(0).x;
var grip_y = svgedit.path.first_grip ? svgedit.path.first_grip[1]/current_zoom : seglist.getItem(0).y;
var s_seg = stretchy.pathSegList.getItem(1);
if(s_seg.pathSegType === 4) {
var newseg = drawn_path.createSVGPathSegLinetoAbs(abs_x, abs_y);
} else {
var newseg = drawn_path.createSVGPathSegCurvetoCubicAbs(
abs_x,
abs_y,
s_seg.x1 / current_zoom,
s_seg.y1 / current_zoom,
grip_x,
grip_y
);
}
var endseg = drawn_path.createSVGPathSegClosePath();
seglist.appendItem(newseg);
seglist.appendItem(endseg);
} else if(len < 3) {
keep = false;
return keep;
}
$(stretchy).remove();
// this will signal to commit the path
element = newpath;
drawn_path = null;
started = false;
if(subpath) {
if(svgedit.path.path.matrix) {
remapElement(newpath, {}, svgedit.path.path.matrix.inverse());
}
var new_d = newpath.getAttribute("d");
var orig_d = $(svgedit.path.path.elem).attr("d");
$(svgedit.path.path.elem).attr("d", orig_d + new_d);
$(newpath).remove();
if(svgedit.path.path.matrix) {
svgedit.path.recalcRotatedPath();
}
svgedit.path.path.init();
pathActions.toEditMode(svgedit.path.path.elem);
svgedit.path.path.selectPt();
return false;
}
}
// else, create a new point, update path element
else {
// Checks if current target or parents are #svgcontent
if(!$.contains(container, getMouseTarget(evt))) {
// Clicked outside canvas, so don't make point
console.log("Clicked outside canvas");
return false;
}
var num = drawn_path.pathSegList.numberOfItems;
var last = drawn_path.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
var s_seg = stretchy.pathSegList.getItem(1);
if(s_seg.pathSegType === 4) {
var newseg = drawn_path.createSVGPathSegLinetoAbs(round(x), round(y));
} else {
var newseg = drawn_path.createSVGPathSegCurvetoCubicAbs(
round(x),
round(y),
s_seg.x1 / current_zoom,
s_seg.y1 / current_zoom,
s_seg.x2 / current_zoom,
s_seg.y2 / current_zoom
);
}
drawn_path.pathSegList.appendItem(newseg);
x *= current_zoom;
y *= current_zoom;
// update everything to the latest point
stretchy.setAttribute('d', ['M', x, y, x, y].join(' '));
var pointGrip1 = svgedit.path.addCtrlGrip('1c1');
var pointGrip2 = svgedit.path.addCtrlGrip('0c2');
var ctrlLine = svgedit.path.getCtrlLine(1);
var ctrlLine2 = svgedit.path.getCtrlLine(2);
pointGrip1.setAttribute('cx', x);
pointGrip1.setAttribute('cy', y);
pointGrip2.setAttribute('cx', x);
pointGrip2.setAttribute('cy', y);
ctrlLine.setAttribute('x1', x);
ctrlLine.setAttribute('x2', x);
ctrlLine.setAttribute('y1', y);
ctrlLine.setAttribute('y2', y);
ctrlLine2.setAttribute('x1', x);
ctrlLine2.setAttribute('x2', x);
ctrlLine2.setAttribute('y1', y);
ctrlLine2.setAttribute('y2', y);
var index = num;
if(subpath) index += svgedit.path.path.segs.length;
svgedit.path.addPointGrip(index, x, y);
}
keep = true;
}
return;
}
// TODO: Make sure current_path isn't null at this point
if(!svgedit.path.path) return;
svgedit.path.path.storeD();
var id = evt.target.id;
if (id.substr(0,14) == "pathpointgrip_") {
// Select this point
var cur_pt = svgedit.path.path.cur_pt = parseInt(id.substr(14));
svgedit.path.path.dragging = [start_x, start_y];
var seg = svgedit.path.path.segs[cur_pt];
// only clear selection if shift is not pressed (otherwise, add
// node to selection)
if (!evt.shiftKey) {
if(svgedit.path.path.selected_pts.length <= 1 || !seg.selected) {
svgedit.path.path.clearSelection();
}
svgedit.path.path.addPtsToSelection(cur_pt);
} else if(seg.selected) {
svgedit.path.path.removePtFromSelection(cur_pt);
} else {
svgedit.path.path.addPtsToSelection(cur_pt);
}
} else if(id.indexOf("ctrlpointgrip_") == 0) {
svgedit.path.path.dragging = [start_x, start_y];
var parts = id.split('_')[1].split('c');
var cur_pt = parts[0]-0;
var ctrl_num = parts[1]-0;
var num = ctrl_num;
var path = svgedit.path.path.segs[cur_pt];
svgedit.path.path.selectPt(cur_pt, ctrl_num);
/////////////////
//check if linked
var seg, anum, pt;
if (num == 2) {
anum = 1;
seg = path.next;
if(!seg) return;
pt = path.item;
} else {
anum = 2;
seg = path.prev;
if(!seg) return;
pt = seg.item;
}
var get_distance = function(pt1, pt2) {
var a = pt1.x - pt2.x;
var b = pt1.y - pt2.y;
return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}
function get_angle(pt1, pt2)
{
var dy = pt1.y - pt2.y;
var dx = pt1.x - pt2.x;
var theta = Math.atan2(dy, dx);
return theta *= 180/Math.PI // rads to degs
}
var grip = {
x: path.item["x" + num],
y: path.item["y" + num]
}
if (num == 2) {
var node = {
x: path.item["x"],
y: path.item["y"]
}
}
else {
var node = {
x: seg.item["x"],
y: seg.item["y"]
}
}
var pair = {
x: seg.item["x" + anum],
y: seg.item["y" + anum]
}
var distance_between_node_grip = get_distance(grip, node);
var distance_between_pair_grip = get_distance(pair, node);
var angle_grip = Math.round(get_angle(grip, node), 0);
var angle_pair = Math.round(get_angle(pair, node), 0);
var is_complementary = (Math.abs(angle_grip - angle_pair) == 180);
//console.log("distance: " + Math.abs(distance_between_node_grip - distance_between_pair_grip) + " angle = " + (Math.round(Math.abs(get_angle(grip, node)) + Math.abs(get_angle(pair, node)), 0)))
if (Math.abs(distance_between_node_grip - distance_between_pair_grip) < 5 && is_complementary) {
svgedit.path.setLinkControlPoints(true);
svgedit.path.is_linked = true;
}
else {
svgedit.path.setLinkControlPoints(false);
svgedit.path.is_linked = false;
}
///////
}
// Start selection box
if(!svgedit.path.path.dragging) {
if (rubberBox == null) {
rubberBox = selectorManager.getRubberBandBox();
}
assignAttributes(rubberBox, {
'x': start_x * current_zoom,
'y': start_y * current_zoom,
'width': 0,
'height': 0,
'display': 'inline'
}, 100);
}
},
mouseMove: function(evt, mouse_x, mouse_y) {
hasMoved = true;
var is_linked = !evt.altKey;
if(current_mode === "path") {
if(!drawn_path) return;
var seglist = drawn_path.pathSegList;
var index = seglist.numberOfItems - 1;
var pointGrip1 = svgedit.path.addCtrlGrip('1c1');
var pointGrip2 = svgedit.path.addCtrlGrip('0c2');
if(newPoint) {
// First point
// Set control points
var current_pointGrip2_x = pointGrip2.getAttribute('cx') / current_zoom || 0;
var current_pointGrip2_y = pointGrip2.getAttribute('cy') / current_zoom || 0;
// dragging pointGrip1
pointGrip1.setAttribute('cx', mouse_x);
pointGrip1.setAttribute('cy', mouse_y);
pointGrip1.setAttribute('display', 'inline');
var pt_x = newPoint[0];
var pt_y = newPoint[1];
// set curve
var seg = seglist.getItem(index);
var cur_x = mouse_x / current_zoom;
var cur_y = mouse_y / current_zoom;
var alt_x = (is_linked) ? (pt_x + (pt_x - cur_x)) : current_pointGrip2_x;
var alt_y = (is_linked) ? (pt_y + (pt_y - cur_y)) : current_pointGrip2_y;
pointGrip2.setAttribute('cx', alt_x * current_zoom);
pointGrip2.setAttribute('cy', alt_y * current_zoom);
pointGrip2.setAttribute('display', 'inline');
var ctrlLine = svgedit.path.getCtrlLine(1);
var ctrlLine2 = svgedit.path.getCtrlLine(2);
assignAttributes(ctrlLine, {
x1: mouse_x,
y1: mouse_y,
x2: pt_x * current_zoom,
y2: pt_y * current_zoom,
display: 'inline'
});
assignAttributes(ctrlLine2, {
x1: alt_x * current_zoom,
y1: alt_y * current_zoom,
x2: pt_x * current_zoom,
y2: pt_y * current_zoom,
display: 'inline'
});
if(index === 0) {
firstCtrl = [mouse_x, mouse_y];
} else {
var last_x, last_y;
var last = seglist.getItem(index - 1);
var last_x = last.x;
var last_y = last.y
if(last.pathSegType === 6) {
last_x += (last_x - last.x2);
last_y += (last_y - last.y2);
} else if(firstCtrl) {
last_x = firstCtrl[0]/current_zoom;
last_y = firstCtrl[1]/current_zoom;
}
svgedit.path.replacePathSeg(6, index, [pt_x, pt_y, this.lastCtrlPoint[0]/current_zoom, this.lastCtrlPoint[1]/current_zoom, alt_x, alt_y], drawn_path);
}
} else {
var stretchy = this.stretchy;
if (stretchy) {
var prev = seglist.getItem(index);
var lastpoint = (evt.target.id === 'pathpointgrip_0');
var lastgripx = mouse_x;
var lastgripy = mouse_y;
if (lastpoint && svgedit.path.first_grip) {
lastgripx = svgedit.path.first_grip[0];
lastgripy = svgedit.path.first_grip[1];
}
if(prev.pathSegType === 6) {
var prev_x = this.lastCtrlPoint[0]/current_zoom || prev.x + (prev.x - prev.x2);
var prev_y = this.lastCtrlPoint[1]/current_zoom || prev.y + (prev.y - prev.y2);
svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, prev_x * current_zoom, prev_y * current_zoom, lastgripx, lastgripy], stretchy);
} else if(firstCtrl) {
svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, firstCtrl[0], firstCtrl[1], mouse_x, mouse_y], stretchy);
} else {
svgedit.path.replacePathSeg(4, 1, [mouse_x, mouse_y], stretchy);
}
}
}
return;
}
// if we are dragging a point, let's move it
if (svgedit.path.path.dragging) {
var pt = svgedit.path.getPointFromGrip({
x: svgedit.path.path.dragging[0],
y: svgedit.path.path.dragging[1]
}, svgedit.path.path);
var mpt = svgedit.path.getPointFromGrip({
x: mouse_x,
y: mouse_y
}, svgedit.path.path);
var diff_x = mpt.x - pt.x;
var diff_y = mpt.y - pt.y;
svgedit.path.path.dragging = [mouse_x, mouse_y];
if (!is_linked || !svgedit.path.is_linked) svgedit.path.setLinkControlPoints(false);
else svgedit.path.setLinkControlPoints(true);
if(svgedit.path.path.dragctrl) {
svgedit.path.path.moveCtrl(diff_x, diff_y);
} else {
svgedit.path.path.movePts(diff_x, diff_y);
}
} else {
//select
svgedit.path.path.selected_pts = [];
svgedit.path.path.eachSeg(function(i) {
var seg = this;
if(!seg.next && !seg.prev) return;
var item = seg.item;
var rbb = rubberBox.getBBox();
var pt = svgedit.path.getGripPt(seg);
var pt_bb = {
x: pt.x,
y: pt.y,
width: 0,
height: 0
};
var sel = svgedit.math.rectsIntersect(rbb, pt_bb);
this.select(sel);
//Note that addPtsToSelection is not being run
if(sel) svgedit.path.path.selected_pts.push(seg.index);
});
}
},
mouseUp: function(evt, element, mouse_x, mouse_y) {
var lastpointgrip = getElem('ctrlpointgrip_1c1');
var firstpointgrip = getElem('ctrlpointgrip_0c2');
if (lastpointgrip)
this.lastCtrlPoint = [lastpointgrip.getAttribute('cx'), lastpointgrip.getAttribute('cy')];
else
this.lastCtrlPoint = [mouse_x, mouse_y]
if (!svgedit.path.first_grip) {
if (firstpointgrip) {
svgedit.path.first_grip = [firstpointgrip.getAttribute('cx'), firstpointgrip.getAttribute('cy')];
}
else {
svgedit.path.first_grip = [mouse_x, mouse_y];
}
}
// Create mode
if(current_mode === "path") {
newPoint = null;
if(!drawn_path) {
element = getElem(getId());
started = false;
firstCtrl = null;
}
return {
keep: true,
element: element
}
}
// Edit mode
if (svgedit.path.path.dragging) {
var last_pt = svgedit.path.path.cur_pt;
svgedit.path.path.dragging = false;
svgedit.path.path.dragctrl = false;
svgedit.path.path.update();
if(hasMoved) {
svgedit.path.path.endChanges("Move path point(s)");
}
if(!evt.shiftKey && !hasMoved) {
svgedit.path.path.selectPt(last_pt);
}
}
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(element) {
svgedit.path.path = svgedit.path.getPath_(element);
current_mode = "pathedit";
clearSelection();
svgedit.path.path.show(true).update();
svgedit.path.path.oldbbox = svgedit.utilities.getBBox(svgedit.path.path.elem);
subpath = false;
},
toSelectMode: function(elem) {
var selPath = (elem == svgedit.path.path.elem);
current_mode = "select";
svgedit.path.path.show(false);
current_path = false;
clearSelection();
if(svgedit.path.path.matrix) {
// Rotated, so may need to re-calculate the center
svgedit.path.recalcRotatedPath();
}
if(selPath) {
call("selected", [elem]);
addToSelection([elem], true);
}
},
addSubPath: function(on) {
if(on) {
// Internally we go into "path" mode, but in the UI it will
// still appear as if in "pathedit" mode.
current_mode = "path";
subpath = true;
} else {
pathActions.clear(true);
pathActions.toEditMode(svgedit.path.path.elem);
}
},
select: function(target) {
if (current_path === target) {
pathActions.toEditMode(target);
current_mode = "pathedit";
} // going into pathedit mode
else {
current_path = target;
}
},
reorient: function() {
var elem = selectedElements[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));
clearSelection();
this.resetOrientation(elem);
addCommandToHistory(batchCmd);
// Set matrix to null
svgedit.path.getPath_(elem).show(false).matrix = null;
this.clear();
addToSelection([elem], true);
call("changed", selectedElements);
},
clear: function(remove) {
current_path = null;
if (drawn_path) {
var elem = getElem(getId());
$(getElem("path_stretch_line")).remove();
if (remove) $(elem).remove();
$(getElem("pathpointgrip_container")).find('*').attr('display', 'none');
drawn_path = firstCtrl = null;
started = false;
} else if (current_mode == "pathedit") {
this.toSelectMode();
}
if(svgedit.path.path) svgedit.path.path.init().show(false);
},
resetOrientation: function(path) {
if(path == null || path.nodeName != 'path') return false;
var tlist = getTransformList(path);
var m = transformListToTransform(tlist).matrix;
tlist.clear();
path.removeAttribute("transform");
var segList = path.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) {
// var fixed_d = pathActions.convertPath(path);
// path.setAttribute('d', fixed_d);
// segList = path.pathSegList;
// var len = segList.numberOfItems;
// }
var last_x, last_y;
for (var i = 0; i < len; ++i) {
var seg = segList.getItem(i);
var type = seg.pathSegType;
if(type == 1) continue;
var pts = [];
$.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);
}
});
svgedit.path.replacePathSeg(type, i, pts, path);
}
reorientGrads(path, m);
},
zoomChange: function() {
if(current_mode == "pathedit") {
svgedit.path.path.update();
}
},
getNodePoint: function() {
if (!svgedit.path.path) return;
var sel_pt = svgedit.path.path.selected_pts.length ? svgedit.path.path.selected_pts[0] : 1;
var seg = svgedit.path.path.segs[sel_pt];
return {
x: seg.item.x,
y: seg.item.y,
type: seg.type
};
},
linkControlPoints: function(linkPoints) {
svgedit.path.setLinkControlPoints(linkPoints);
},
clonePathNode: function() {
svgedit.path.path.storeD();
var sel_pts = svgedit.path.path.selected_pts;
var segs = svgedit.path.path.segs;
var i = sel_pts.length;
var nums = [];
while(i--) {
var pt = sel_pts[i];
svgedit.path.path.addSeg(pt);
nums.push(pt + i);
nums.push(pt + i + 1);
}
svgedit.path.path.init().addPtsToSelection(nums);
svgedit.path.path.endChanges("Clone path node(s)");
},
opencloseSubPath: function() {
var sel_pts = svgedit.path.path.selected_pts;
// Only allow one selected node for now
if(sel_pts.length !== 1) return;
var elem = svgedit.path.path.elem;
var list = elem.pathSegList;
var len = list.numberOfItems;
var index = sel_pts[0];
var open_pt = null;
var start_item = null;
// Check if subpath is already open
svgedit.path.path.eachSeg(function(i) {
if(this.type === 2 && i <= index) {
start_item = this.item;
}
if(i <= index) return true;
if(this.type === 2) {
// Found M first, so open
open_pt = i;
return false;
} else if(this.type === 1) {
// Found Z first, so closed
open_pt = false;
return false;
}
});
if(open_pt == null) {
// Single path, so close last seg
open_pt = svgedit.path.path.segs.length - 1;
}
if(open_pt !== false) {
// Close this path
// Create a line going to the previous "M"
var newseg = elem.createSVGPathSegLinetoAbs(start_item.x, start_item.y);
var closer = elem.createSVGPathSegClosePath();
if(open_pt == svgedit.path.path.segs.length) {
list.appendItem(newseg);
list.appendItem(closer);
} else {
svgedit.path.insertItemBefore(elem, closer, open_pt);
svgedit.path.insertItemBefore(elem, newseg, open_pt);
}
svgedit.path.path.init().selectPt(open_pt+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 = svgedit.path.path.segs[index];
if(seg.mate) {
list.removeItem(index); // Removes last "L"
list.removeItem(index); // Removes the "Z"
svgedit.path.path.init().selectPt(index - 1);
return;
}
var last_m, z_seg;
// 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
last_m = i;
} else if(i === index) {
// Remove it
list.removeItem(last_m);
// index--;
} else if(item.pathSegType === 1 && index < i) {
// Remove the closing seg of this subpath
z_seg = i-1;
list.removeItem(i);
break;
}
}
var num = (index - last_m) - 1;
while(num--) {
svgedit.path.insertItemBefore(elem, list.getItem(last_m), z_seg);
}
var pt = list.getItem(last_m);
// Make this point the new "M"
svgedit.path.replacePathSeg(2, last_m, [pt.x, pt.y]);
var i = index
svgedit.path.path.init().selectPt(0);
},
deletePathNode: function() {
if(!pathActions.canDeleteNodes) return;
svgedit.path.path.storeD();
var sel_pts = svgedit.path.path.selected_pts;
var i = sel_pts.length;
while(i--) {
var pt = sel_pts[i];
svgedit.path.path.deleteSeg(pt);
}
// Cleanup
var cleanup = function() {
var segList = svgedit.path.path.elem.pathSegList;
var len = segList.numberOfItems;
var remItems = function(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 prev_type = segList.getItem(len-1).pathSegType;
// Path has M M
if(prev_type === 2) {
remItems(len-1, 1);
cleanup();
break;
// Entire path ends with Z M
} else if(prev_type === 1 && segList.numberOfItems-1 === len) {
remItems(len, 1);
cleanup();
break;
}
}
}
}
return false;
}
cleanup();
// Completely delete a path with 1 or 0 segments
if(svgedit.path.path.elem.pathSegList.numberOfItems <= 1) {
canvas.setMode("select")
canvas.deleteSelectedElements();
return;
}
svgedit.path.path.init();
svgedit.path.path.clearSelection();
// TODO: Find right way to select point now
// path.selectPt(sel_pt);
if(window.opera) { // Opera repaints incorrectly
var cp = $(svgedit.path.path.elem); cp.attr('d',cp.attr('d'));
}
svgedit.path.path.endChanges("Delete path node(s)");
},
smoothPolylineIntoPath: smoothPolylineIntoPath,
setSegType: function(v) {
svgedit.path.path.setSegType(v);
},
moveNode: function(attr, newValue) {
var sel_pts = svgedit.path.path.selected_pts;
if(!sel_pts.length) return;
svgedit.path.path.storeD();
// Get first selected point
var seg = svgedit.path.path.segs[sel_pts[0]];
var diff = {x:0, y:0};
diff[attr] = newValue - seg.item[attr];
seg.move(diff.x, diff.y);
svgedit.path.path.endChanges("Move path point");
},
fixEnd: function(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 last_m;
for (var i = 0; i < len; ++i) {
var item = segList.getItem(i);
if(item.pathSegType === 2) {
last_m = item;
}
if(item.pathSegType === 1) {
var prev = segList.getItem(i-1);
if(prev.x != last_m.x || prev.y != last_m.y) {
// Add an L segment here
var newseg = elem.createSVGPathSegLinetoAbs(last_m.x, last_m.y);
svgedit.path.insertItemBefore(elem, newseg, i);
// Can this be done better?
pathActions.fixEnd(elem);
break;
}
}
}
if(svgedit.browser.isWebkit()) resetD(elem);
},
// Convert a path to one with only absolute or relative values
convertPath: function(path, toRel) {
var segList = path.pathSegList;
var len = segList.numberOfItems;
var curx = 0, cury = 0;
var d = "";
var last_m = 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']();
var addToD = function(pnts, more, last) {
var str = '';
var more = more?' '+more.join(' '):'';
var last = last?' '+svgedit.units.shortFloat(last):'';
$.each(pnts, function(i, pnt) {
pnts[i] = svgedit.units.shortFloat(pnt);
});
d += letter + pnts.join(' ') + more + last;
}
switch (type) {
case 1: // z,Z closepath (Z/z)
d += "z";
2020-08-02 10:57:54 +00:00
if(last_m && !toRel) {
curx = last_m[0];
cury = last_m[1];
}
2020-08-02 10:26:22 +00:00
break;
case 12: // absolute horizontal line (H)
x -= curx;
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
addToD([[x, cury]]);
break;
case 14: // absolute vertical line (V)
y -= cury;
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
addToD([[curx, y]]);
break;
case 2: // absolute move (M)
case 4: // absolute line (L)
case 18: // absolute smooth quad (T)
x -= curx;
y -= cury;
case 5: // relative line (l)
case 3: // relative move (m)
// If the last segment was a "z", this must be relative to
if(last_m && segList.getItem(i-1).pathSegType === 1 && !toRel) {
curx = last_m[0];
cury = last_m[1];
}
case 19: // relative smooth quad (t)
if(toRel) {
curx += x;
cury += y;
} else {
x += curx;
y += cury;
curx = x;
cury = y;
}
2020-08-02 10:57:54 +00:00
if(type === 2 || type === 3) last_m = [curx, cury];
2020-08-02 10:26:22 +00:00
addToD([[x,y]]);
break;
case 6: // absolute cubic (C)
x -= curx; x1 -= curx; x2 -= curx;
y -= cury; y1 -= cury; y2 -= cury;
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;
}
addToD([[x1,y1],[x2,y2],[x,y]]);
break;
case 8: // absolute quad (Q)
x -= curx; x1 -= curx;
y -= cury; y1 -= cury;
case 9: // relative quad (q)
if(toRel) {
curx += x;
cury += y;
} else {
x += curx; x1 += curx;
y += cury; y1 += cury;
curx = x;
cury = y;
}
addToD([[x1,y1],[x,y]]);
break;
case 10: // absolute elliptical arc (A)
x -= curx;
y -= cury;
case 11: // relative elliptical arc (a)
if(toRel) {
curx += x;
cury += y;
} else {
x += curx;
y += cury;
curx = x;
cury = y;
}
addToD([[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;
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;
}
addToD([[x2,y2],[x,y]]);
break;
} // switch on path segment type
} // for each segment
return d;
}
}
}();
// end pathActions
// Group: Serialization
// Function: removeUnusedDefElems
// Looks at DOM elements inside the <defs> 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(svgns, "defs");
if(!defs || !defs.length) return 0;
// if(!defs.firstChild) return;
var defelem_uses = [],
numRemoved = 0;
var attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end'];
var alen = attrs.length;
var all_els = svgcontent.getElementsByTagNameNS(svgns, '*');
var all_len = all_els.length;
for(var i=0; i<all_len; i++) {
var el = all_els[i];
for(var j = 0; j < alen; j++) {
if(el) {
var ref = getUrlFromAttr(el.getAttribute(attrs[j]));
if(ref) {
defelem_uses.push(ref.substr(1));
}
}
}
// gradients can refer to other gradients
var href = getHref(el);
if (href && href.indexOf('#') === 0) {
defelem_uses.push(href.substr(1));
}
};
var defelems = $(defs).find("linearGradient, radialGradient, filter, marker, svg, symbol");
defelem_ids = [],
i = defelems.length;
while (i--) {
var defelem = defelems[i];
var id = defelem.id;
if(defelem_uses.indexOf(id) < 0) {
// Not found, so remove (but remember)
removedElements[id] = defelem;
defelem.parentNode.removeChild(defelem);
numRemoved++;
}
}
return numRemoved;
}
// Function: svgCanvasToString
// Main function to set up the SVG content for output
//
// Returns:
// String containing the SVG image for output
this.svgCanvasToString = function() {
// keep calling it until there are none to remove
while (removeUnusedDefElems() > 0) {};
pathActions.clear(true);
// Keep SVG-Edit comment on top
$.each(svgcontent.childNodes, function(i, node) {
if(i && node.nodeType === 8 && node.data.indexOf('Created with') >= 0) {
svgcontent.insertBefore(node, svgcontent.firstChild);
}
});
// Move out of in-group editing mode
if(current_group) {
leaveContext();
selectOnly([current_group]);
}
//hide grid, otherwise shows a black canvas
$('#canvasGrid').attr('display', 'none');
var naked_svgs = [];
// Unwrap gsvg if it has no special attributes (only id and style)
$(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;
naked_svgs.push(svg);
$(this).replaceWith(svg);
}
});
var output = this.svgToString(svgcontent, 0);
// Rewrap gsvg
if(naked_svgs.length) {
$(naked_svgs).each(function() {
groupSvgElem(this);
});
}
return output;
};
// Function: svgToString
// Sub function ran on each SVG element to convert it to a string as desired
//
// Parameters:
// elem - The SVG element to convert
// indent - Integer with the amount of spaces to indent this tag
//
// Returns:
// String with the given element as an SVG tag
this.svgToString = function(elem, indent) {
var out = new Array(), toXml = svgedit.utilities.toXml;
var unit = curConfig.baseUnit;
var unit_re = new RegExp('^-?[\\d\\.]+' + unit + '$');
if (elem) {
cleanupElement(elem);
var attrs = elem.attributes,
attr,
i,
childs = elem.childNodes;
for (var 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") {
// var unit = curConfig.baseUnit;
// var unit_m = svgedit.units.getTypeMap()[unit];
// res.w = svgedit.units.shortFloat(res.w / unit_m)
// res.h = svgedit.units.shortFloat(res.h / unit_m)
// vb = ' viewBox="' + [0, 0, res.w, res.h].join(' ') + '"';
// res.w += unit;
// res.h += unit;
// }
if(unit !== "px") {
res.w = svgedit.units.convertUnit(res.w, unit) + unit;
res.h = svgedit.units.convertUnit(res.h, unit) + unit;
}
out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="'+svgns+'"');
var nsuris = {};
// Check elements for namespaces, add if found
$(elem).find('*').addBack().each(function() {
var el = this;
$.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 +'"');
}
});
});
var i = attrs.length;
var attr_names = ['width','height','xmlns','x','y','viewBox','id','overflow'];
while (i--) {
attr = attrs.item(i);
var attrVal = toXml(attr.nodeValue);
// Namespaces have already been dealt with, so skip
if(attr.nodeName.indexOf('xmlns:') === 0) continue;
// only serialize attributes we don't use internally
if (attrVal != "" && attr_names.indexOf(attr.localName) == -1)
{
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 moz_attrs = ['-moz-math-font-style', '_moz-math-font-style'];
for (var i=attrs.length-1; i>=0; i--) {
attr = attrs.item(i);
var attrVal = toXml(attr.nodeValue);
//remove bogus attributes added by Gecko
if (moz_attrs.indexOf(attr.localName) >= 0) continue;
if (attrVal != "") {
if(attrVal.indexOf('pointer-events') === 0) continue;
if(attr.localName === "class" && attrVal.indexOf('se_') === 0) continue;
out.push(" ");
if(attr.localName === 'd') attrVal = pathActions.convertPath(elem, true);
if(!isNaN(attrVal)) {
attrVal = svgedit.units.shortFloat(attrVal);
} else if(unit_re.test(attrVal)) {
attrVal = svgedit.units.shortFloat(attrVal) + unit;
}
// Embed images when saving
if(save_options.apply
&& elem.nodeName === 'image'
&& attr.localName === 'href'
&& save_options.images
&& save_options.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 == svgns || nsMap[attr.namespaceURI]) {
out.push(attr.nodeName); out.push("=\"");
out.push(attrVal); out.push("\"");
}
}
}
}
if (elem.hasChildNodes()) {
out.push(">");
indent++;
var bOneLine = false;
for (var 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(toXml(str) + "");
}
break;
case 4: // cdata node
out.push("\n");
out.push(new Array(indent+1).join(" "));
out.push("<![CDATA[");
out.push(child.nodeValue);
out.push("]]>");
break;
case 8: // comment
out.push("\n");
out.push(new Array(indent+1).join(" "));
out.push("<!--");
out.push(child.data);
out.push("-->");
break;
} // switch on node type
}
indent--;
if (!bOneLine) {
out.push("\n");
for (var i=0; i<indent; i++) out.push(" ");
}
out.push("</"); out.push(elem.nodeName); out.push(">");
} else {
out.push("/>");
}
}
return out.join('');
}; // end svgToString()
// Function: embedImage
// Converts a given image file to a data URL when possible, then runs a given callback
//
// Parameters:
// val - String with the path/URL of the image
// 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
$(new Image()).on("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;
}
last_good_img_url = val;
if(callback) callback(encodableImages[val]);
}).attr('src',val);
}
// Function: setGoodImage
// Sets a given URL to be a "last good image" URL
this.setGoodImage = function(val) {
last_good_img_url = val;
}
this.open = function() {
// Nothing by default, handled by optional widget/extension
};
// Function: save
// 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.
//
// Returns:
// Nothing
this.save = function(opts) {
// remove the selected outline before serializing
clearSelection();
// Update save options if provided
if(opts) $.extend(save_options, opts);
save_options.apply = true;
// no need for doctype, see http://jwatt.org/svg/authoring/#doctype-declaration
var str = this.svgCanvasToString();
var blob = new Blob([ str ], {type: "image/svg+xml;charset=utf-8"});
var dropAutoBOM = true;
saveAs(blob, "method-draw-image.svg", dropAutoBOM);
};
// Function: rasterExport
// Generates a PNG Data URL based on the current image, then calls "exported"
// with an object including the string and any issues found
this.rasterExport = function() {
// remove the selected outline before serializing
clearSelection();
// Check for known CanVG issues
var issues = [];
// Selector and notice
var issue_list = {
'feGaussianBlur': "Blurred elements will appear as un-blurred",
'foreignObject': "foreignObject elements will not appear",
'[stroke-dasharray]': "Strokes will appear filled"
};
var content = $(svgcontent);
// Add font/text check if Canvas Text API is not implemented
if(!("font" in $('<canvas>')[0].getContext('2d'))) {
issue_list['text'] = "Text may not appear as expected";
}
$.each(issue_list, function(sel, descr) {
if(content.find(sel).length) {
issues.push(descr);
}
});
var str = this.svgCanvasToString();
call("exported", {svg: str, issues: issues});
};
// Function: getSvgString
// Returns the current drawing as raw SVG XML text.
//
// Returns:
// The current drawing as raw SVG XML text.
this.getSvgString = function() {
save_options.apply = false;
return this.svgCanvasToString();
};
// Function: randomizeIds
// This function determines whether to use a nonce in the prefix, when
// generating IDs for future documents in SVG-Edit.
//
// Parameters:
// an opional boolean, which, 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() {
if (arguments.length > 0 && arguments[0] == false) {
svgedit.draw.randomizeIds(false, getCurrentDrawing());
} else {
svgedit.draw.randomizeIds(true, getCurrentDrawing());
}
};
// Function: uniquifyElems
// Ensure each element has a unique ID
//
// Parameters:
// 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.
//
// <marker id='se_marker_end_svg_7'/>
// <polyline id='svg_7' se:connector='svg_1 svg_6' marker-end='url(#se_marker_end_svg_7)'/>
//
// 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 ref_elems = ["filter", "linearGradient", "pattern", "radialGradient", "symbol", "textPath", "use"];
svgedit.utilities.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
$.each(ref_attrs,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 = svgedit.utilities.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 = svgedit.utilities.getHref(n);
// TODO: what if an <image> or <a> element refers to an element internally?
if(href && ref_elems.indexOf(n.nodeName) >= 0)
{
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];
svgedit.utilities.setHref(hreffer, "#"+newid);
}
}
}
}
// Function setUseData
// Assigns reference data for each use element
var setUseData = this.setUseData = function(parent) {
var elems = $(parent);
if(parent.tagName !== 'use') {
elems = elems.find('use');
}
elems.each(function() {
var id = getHref(this).substr(1);
var ref_elem = getElem(id);
if(!ref_elem) return;
$(this).data('ref', ref_elem);
if(ref_elem.tagName == 'symbol' || ref_elem.tagName == 'svg') {
$(this).data('symbol', ref_elem).data('ref', ref_elem);
}
});
}
// Function convertGradients
// Converts gradients from userSpaceOnUse to objectBoundingBox
var convertGradients = this.convertGradients = function(elem) {
var elems = $(elem).find('linearGradient, radialGradient');
if(!elems.length && svgedit.browser.isWebkit()) {
// Bug in webkit prevents regular *Gradient selector search
elems = $(elem).find('*').filter(function() {
return (this.tagName.indexOf('Gradient') >= 0);
});
}
elems.each(function() {
var grad = this;
if($(grad).attr('gradientUnits') === 'userSpaceOnUse') {
// TODO: Support more than one element with this ref by duplicating parent grad
var elems = $(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]');
if(!elems.length) return;
// get object's bounding box
var bb = svgedit.utilities.getBBox(elems[0]);
// This will occur if the element is inside a <defs> or a <symbol>,
// in which we shouldn't need to convert anyway.
if(!bb) return;
if(grad.tagName === 'linearGradient') {
var g_coords = $(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(g_coords.x1, g_coords.y1, m);
var pt2 = transformPoint(g_coords.x2, g_coords.y2, m);
g_coords.x1 = pt1.x;
g_coords.y1 = pt1.y;
g_coords.x2 = pt2.x;
g_coords.y2 = pt2.y;
grad.removeAttribute('gradientTransform');
}
$(grad).attr({
x1: (g_coords.x1 - bb.x) / bb.width,
y1: (g_coords.y1 - bb.y) / bb.height,
x2: (g_coords.x2 - bb.x) / bb.width,
y2: (g_coords.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.
// var g_coords = $(grad).attr(['cx', 'cy', 'r']);
//
// $(grad).attr({
// cx: (g_coords.cx - bb.x) / bb.width,
// cy: (g_coords.cy - bb.y) / bb.height,
// r: g_coords.r
// });
//
// grad.removeAttribute('gradientUnits');
}
}
});
}
// Function: convertToGroup
// Converts selected/given <use> or child SVG element to a group
var convertToGroup = this.convertToGroup = function(elem) {
if(!elem) {
elem = selectedElements[0];
}
var $elem = $(elem);
var batchCmd = new BatchCommand();
var ts;
if($elem.data('gsvg')) {
// Use the gsvg as the new group
var svg = elem.firstChild;
var pt = $(svg).attr(['x', 'y']);
$(elem.firstChild.firstChild).unwrap();
$(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 <use> element
batchCmd.addSubCommand(new RemoveElementCommand($elem[0], $elem[0].nextSibling, $elem[0].parentNode));
$elem.remove();
// See if other elements reference this symbol
var has_more = $(svgcontent).find('use:data(symbol)').length;
var g = svgdoc.createElementNS(svgns, "g");
var childs = elem.childNodes;
for(var i = 0; i < childs.length; i++) {
g.appendChild(childs[i].cloneNode(true));
}
// Duplicate the gradients for Gecko, since they weren't included in the <symbol>
if(svgedit.browser.isGecko()) {
var dupeGrads = $(findDefs()).children('linearGradient,radialGradient,pattern').clone();
$(g).append(dupeGrads);
}
if (ts) {
g.setAttribute("transform", ts);
}
var parent = elem.parentNode;
uniquifyElems(g);
// Put the dupe gradients back into <defs> (after uniquifying them)
if(svgedit.browser.isGecko()) {
$(findDefs()).append( $(g).find('linearGradient,radialGradient,pattern') );
}
// now give the g itself a new id
g.id = getNextId();
prev.after(g);
if(parent) {
if(!has_more) {
// remove symbol/svg element
var nextSibling = elem.nextSibling;
parent.removeChild(elem);
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
}
batchCmd.addSubCommand(new InsertElementCommand(g));
}
setUseData(g);
if(svgedit.browser.isGecko()) {
convertGradients(findDefs());
} else {
convertGradients(g);
}
// recalculate dimensions on the top-level children so that unnecessary transforms
// are removed
svgedit.utilities.walkTreePost(g, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}});
// Give ID for any visible element missing one
$(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);
}
}
//
// Function: setSvgString
// This function sets the current drawing as the input SVG XML.
//
// Parameters:
// xmlString - The SVG as XML text.
//
// Returns:
// This function returns false if the set was unsuccessful, true otherwise.
this.setSvgString = function(xmlString) {
try {
// convert string into XML document
var newDoc = svgedit.utilities.text2xml(xmlString);
this.prepareSvg(newDoc);
var batchCmd = new BatchCommand("Change Source");
// remove old svg document
var nextSibling = svgcontent.nextSibling;
var oldzoom = svgroot.removeChild(svgcontent);
batchCmd.addSubCommand(new RemoveElementCommand(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.appendChild(svgcontent);
var content = $(svgcontent);
canvas.current_drawing_ = new svgedit.draw.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.indexOf('data:') === 0) {
// Check if an SVG-edit data URI
var m = val.match(/svgedit_url=(.*?);/);
if(m) {
var url = decodeURIComponent(m[1]);
$(new Image()).load(function() {
image.setAttributeNS(xlinkns,'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 <defs>
if($(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') {
$(pa).data('gsvg', this);
pa.id = pa.id || getNextId();
} else {
groupSvgElem(this);
}
});
// Put all paint elems in defs
content.find('linearGradient, radialGradient, pattern').appendTo(findDefs());
// Set ref element for <use> elements
// TODO: This should also be done if the object is re-added through "redo"
setUseData(content);
convertGradients(content[0]);
// recalculate dimensions on the top-level children so that unnecessary transforms
// are removed
svgedit.utilities.walkTreePost(svgcontent, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}});
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 {
$.each(['width', 'height'], function(i, dim) {
// Set to 100 if not given
var val = content.attr(dim);
if(!val) val = '100%';
if((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 = getStrokedBBox();
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 = 200;
if(attrs.height <= 0) attrs.height = 200;
content.attr(attrs);
this.contentW = attrs['width'];
this.contentH = attrs['height'];
$("#canvas_width").val(this.contentW)
$("#canvas_height").val(this.contentH)
var background = $("#canvas_background")
if (background.length) {
var opacity = background.attr("fill-opacity")
opacity = opacity ? parseInt(opacity)*100 : 100
fill = this.getPaint(background.attr("fill"), opacity, "canvas")
methodDraw.paintBox.canvas.setPaint(fill)
}
else {
fill = this.getPaint("none", 100, "canvas")
methodDraw.paintBox.canvas.setPaint(fill)
}
batchCmd.addSubCommand(new InsertElementCommand(svgcontent));
// update root to the correct size
var changes = content.attr(["width", "height"]);
batchCmd.addSubCommand(new ChangeElementCommand(svgroot, changes));
// reset zoom
current_zoom = 1;
// reset transform lists
svgedit.transformlist.resetListMap();
clearSelection();
svgedit.path.clearData();
svgroot.appendChild(selectorManager.selectorParentGroup);
addCommandToHistory(batchCmd);
call("changed", [svgcontent]);
} catch(e) {
console.log(e);
return false;
}
return true;
};
this.getPaint = function(color, opac, type) {
// update the editor's fill paint
var opts = null;
if (color.indexOf("url(#") === 0) {
var refElem = svgCanvas.getRefElem(color);
if(refElem) {
refElem = refElem.cloneNode(true);
} else {
refElem = $("#" + type + "_color defs *")[0];
}
opts = { alpha: opac };
opts[refElem.tagName] = refElem;
}
else if (color.indexOf("#") === 0) {
opts = {
alpha: opac,
solidColor: color.substr(1)
};
}
else {
opts = {
alpha: opac,
solidColor: 'none'
};
}
return new $.jGraduate.Paint(opts);
};
// Function: importSvgString
// This function imports the input SVG XML as a <symbol> in the <defs>, then adds a
// <use> to the current layer.
//
// Parameters:
// xmlString - The SVG as XML text.
//
// Returns:
// This function returns false if the import was unsuccessful, true 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) {
try {
// Get unique ID
var uid = svgedit.utilities.encode64(xmlString.length + xmlString).substr(0,32);
var useExisting = false;
// Look for symbol and make sure symbol exists in image
if(import_ids[uid]) {
if( $(import_ids[uid].symbol).parents('#svgroot').length ) {
useExisting = true;
}
}
var batchCmd = new BatchCommand("Import SVG");
if(useExisting) {
var symbol = import_ids[uid].symbol;
var ts = import_ids[uid].xform;
} else {
// convert string into XML document
var newDoc = svgedit.utilities.text2xml(xmlString);
this.prepareSvg(newDoc);
// import new svg document into our document
var svg;
// 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 (var 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) {
var ts = "scale(" + (canvash/3)/vb[3] + ")";
}
else {
var ts = "scale(" + (canvash/3)/vb[2] + ")";
}
// Hack to make recalculateDimensions understand how to scale
ts = "translate(0) " + ts + " translate(0)";
var symbol = svgdoc.createElementNS(svgns, "symbol");
var defs = findDefs();
if(svgedit.browser.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.
$(svg).find('linearGradient, radialGradient, pattern').appendTo(defs);
}
while (svg.firstChild) {
var first = svg.firstChild;
symbol.appendChild(first);
}
var attrs = svg.attributes;
for(var i=0; i < attrs.length; i++) {
var attr = attrs[i];
symbol.setAttribute(attr.nodeName, attr.nodeValue);
}
symbol.id = getNextId();
// Store data
import_ids[uid] = {
symbol: symbol,
xform: ts
}
findDefs().appendChild(symbol);
batchCmd.addSubCommand(new InsertElementCommand(symbol));
}
var use_el = svgdoc.createElementNS(svgns, "use");
use_el.id = getNextId();
setHref(use_el, "#" + symbol.id);
(current_group || getCurrentDrawing().getCurrentLayer()).appendChild(use_el);
batchCmd.addSubCommand(new InsertElementCommand(use_el));
clearSelection();
use_el.setAttribute("transform", ts);
recalculateDimensions(use_el);
$(use_el).data('symbol', symbol).data('ref', symbol);
addToSelection([use_el]);
// 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 false;
}
return true;
};
// TODO(codedread): Move all layer/context functions in draw.js
// Layer API Functions
// Group: Layers
// Function: identifyLayers
// Updates layer system
var identifyLayers = canvas.identifyLayers = function() {
leaveContext();
getCurrentDrawing().identifyLayers();
};
// Function: createLayer
// 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.
//
// Parameters:
// name - The given name
this.createLayer = function(name) {
var batchCmd = new BatchCommand("Create Layer");
var new_layer = getCurrentDrawing().createLayer(name);
batchCmd.addSubCommand(new InsertElementCommand(new_layer));
addCommandToHistory(batchCmd);
clearSelection();
call("changed", [new_layer]);
};
// Function: cloneLayer
// 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.
//
// Parameters:
// name - The given name
this.cloneLayer = function(name) {
var batchCmd = new BatchCommand("Duplicate Layer");
var new_layer = svgdoc.createElementNS(svgns, "g");
var layer_title = svgdoc.createElementNS(svgns, "title");
layer_title.textContent = name;
new_layer.appendChild(layer_title);
var current_layer = getCurrentDrawing().getCurrentLayer();
$(current_layer).after(new_layer);
var childs = current_layer.childNodes;
for(var i = 0; i < childs.length; i++) {
var ch = childs[i];
if(ch.localName == 'title') continue;
new_layer.appendChild(copyElem(ch));
}
clearSelection();
identifyLayers();
batchCmd.addSubCommand(new InsertElementCommand(new_layer));
addCommandToHistory(batchCmd);
canvas.setCurrentLayer(name);
call("changed", [new_layer]);
};
// Function: deleteCurrentLayer
// Deletes the current layer from the drawing and then clears the selection. This function
// then calls the 'changed' handler. This is an undoable action.
this.deleteCurrentLayer = function() {
var current_layer = getCurrentDrawing().getCurrentLayer();
var nextSibling = current_layer.nextSibling;
var parent = current_layer.parentNode;
current_layer = getCurrentDrawing().deleteCurrentLayer();
if (current_layer) {
var batchCmd = new BatchCommand("Delete Layer");
// store in our Undo History
batchCmd.addSubCommand(new RemoveElementCommand(current_layer, nextSibling, parent));
addCommandToHistory(batchCmd);
clearSelection();
call("changed", [parent]);
return true;
}
return false;
};
// Function: setCurrentLayer
// 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.
//
// Parameters:
// name - the name of the layer you want to switch to.
//
// Returns:
// true if the current layer was switched, otherwise false
this.setCurrentLayer = function(name) {
var result = getCurrentDrawing().setCurrentLayer(svgedit.utilities.toXml(name));
if (result) {
clearSelection();
}
return result;
};
// Function: renameCurrentLayer
// 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.
//
// Parameters:
// newname - the new name you want to give the current layer. This name must be unique
// among all layer names.
//
// Returns:
// true if the rename succeeded, false otherwise.
this.renameCurrentLayer = function(newname) {
var drawing = getCurrentDrawing();
if (drawing.current_layer) {
var oldLayer = drawing.current_layer;
// setCurrentLayer will return false if the name doesn't already exist
// this means we are free to rename our oldLayer
if (!canvas.setCurrentLayer(newname)) {
var batchCmd = new BatchCommand("Rename Layer");
// find the index of the layer
for (var i = 0; i < drawing.getNumLayers(); ++i) {
if (drawing.all_layers[i][1] == oldLayer) break;
}
var oldname = drawing.getLayerName(i);
drawing.all_layers[i][0] = svgedit.utilities.toXml(newname);
// now change the underlying title element contents
var len = oldLayer.childNodes.length;
for (var i = 0; i < len; ++i) {
var child = oldLayer.childNodes.item(i);
// found the <title> element, now append all the
if (child && child.tagName == "title") {
// wipe out old name
while (child.firstChild) { child.removeChild(child.firstChild); }
child.textContent = newname;
batchCmd.addSubCommand(new ChangeElementCommand(child, {"#text":oldname}));
addCommandToHistory(batchCmd);
call("changed", [oldLayer]);
return true;
}
}
}
drawing.current_layer = oldLayer;
}
return false;
};
// Function: setCurrentLayerPosition
// 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.
//
// Parameters:
// newpos - The zero-based index of the new position of the layer. This should be between
// 0 and (number of layers - 1)
//
// Returns:
// true if the current layer position was changed, false otherwise.
this.setCurrentLayerPosition = function(newpos) {
var drawing = getCurrentDrawing();
if (drawing.current_layer && newpos >= 0 && newpos < drawing.getNumLayers()) {
for (var oldpos = 0; oldpos < drawing.getNumLayers(); ++oldpos) {
if (drawing.all_layers[oldpos][1] == drawing.current_layer) break;
}
// some unknown error condition (current_layer not in all_layers)
if (oldpos == drawing.getNumLayers()) { return false; }
if (oldpos != newpos) {
// if our new position is below us, we need to insert before the node after newpos
var refLayer = null;
var oldNextSibling = drawing.current_layer.nextSibling;
if (newpos > oldpos ) {
if (newpos < drawing.getNumLayers()-1) {
refLayer = drawing.all_layers[newpos+1][1];
}
}
// if our new position is above us, we need to insert before the node at newpos
else {
refLayer = drawing.all_layers[newpos][1];
}
svgcontent.insertBefore(drawing.current_layer, refLayer);
addCommandToHistory(new MoveElementCommand(drawing.current_layer, oldNextSibling, svgcontent));
identifyLayers();
canvas.setCurrentLayer(drawing.getLayerName(newpos));
return true;
}
}
return false;
};
// Function: setLayerVisibility
// 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.
//
// Parameters:
// layername - the name of the layer to change the visibility
// bVisible - true/false, whether the layer should be visible
//
// Returns:
// true if the layer's visibility was set, false otherwise
this.setLayerVisibility = function(layername, bVisible) {
var drawing = getCurrentDrawing();
var prevVisibility = drawing.getLayerVisibility(layername);
var layer = drawing.setLayerVisibility(layername, bVisible);
if (layer) {
var oldDisplay = prevVisibility ? 'inline' : 'none';
addCommandToHistory(new ChangeElementCommand(layer, {'display':oldDisplay}, 'Layer Visibility'));
} else {
return false;
}
if (layer == drawing.getCurrentLayer()) {
clearSelection();
pathActions.clear();
}
// call("changed", [selected]);
return true;
};
// Function: moveSelectedToLayer
// 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.
//
// Parameters:
// layername - the name of the layer you want to which you want to move the selected elements
//
// Returns:
// true if the selected elements were moved to the layer, false otherwise.
this.moveSelectedToLayer = function(layername) {
// find the layer
var layer = null;
var drawing = getCurrentDrawing();
for (var i = 0; i < drawing.getNumLayers(); ++i) {
if (drawing.getLayerName(i) == layername) {
layer = drawing.all_layers[i][1];
break;
}
}
if (!layer) return false;
var batchCmd = new BatchCommand("Move Elements to Layer");
// loop for each selected element and move it
var selElems = selectedElements;
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.appendChild(elem);
batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldLayer));
}
addCommandToHistory(batchCmd);
return true;
};
this.mergeLayer = function(skipHistory) {
var batchCmd = new BatchCommand("Merge Layer");
var drawing = getCurrentDrawing();
var prev = $(drawing.current_layer).prev()[0];
if(!prev) return;
var childs = drawing.current_layer.childNodes;
var len = childs.length;
var layerNextSibling = drawing.current_layer.nextSibling;
batchCmd.addSubCommand(new RemoveElementCommand(drawing.current_layer, layerNextSibling, svgcontent));
while(drawing.current_layer.firstChild) {
var ch = drawing.current_layer.firstChild;
if(ch.localName == 'title') {
var chNextSibling = ch.nextSibling;
batchCmd.addSubCommand(new RemoveElementCommand(ch, chNextSibling, drawing.current_layer));
drawing.current_layer.removeChild(ch);
continue;
}
var oldNextSibling = ch.nextSibling;
prev.appendChild(ch);
batchCmd.addSubCommand(new MoveElementCommand(ch, oldNextSibling, drawing.current_layer));
}
// Remove current layer
svgcontent.removeChild(drawing.current_layer);
if(!skipHistory) {
clearSelection();
identifyLayers();
call("changed", [svgcontent]);
addCommandToHistory(batchCmd);
}
drawing.current_layer = prev;
return batchCmd;
}
this.mergeAllLayers = function() {
var batchCmd = new BatchCommand("Merge all Layers");
var drawing = getCurrentDrawing();
drawing.current_layer = drawing.all_layers[drawing.getNumLayers()-1][1];
while($(svgcontent).children('g').length > 1) {
batchCmd.addSubCommand(canvas.mergeLayer(true));
}
clearSelection();
identifyLayers();
call("changed", [svgcontent]);
addCommandToHistory(batchCmd);
}
// Function: leaveContext
// Return from a group context to the regular kind, make any previously
// disabled elements enabled again
var leaveContext = this.leaveContext = function() {
var len = disabled_elems.length;
if(len) {
for(var i = 0; i < len; i++) {
var elem = disabled_elems[i];
var orig = elData(elem, 'orig_opac');
if(orig !== 1) {
elem.setAttribute('opacity', orig);
} else {
elem.removeAttribute('opacity');
}
elem.setAttribute('style', 'pointer-events: inherit');
}
disabled_elems = [];
clearSelection(true);
call("contextset", null);
}
current_group = null;
}
// Function: setContext
// Set the current context (for in-group editing)
var setContext = this.setContext = function(elem) {
leaveContext();
if(typeof elem === 'string') {
elem = getElem(elem);
}
// Edit inside this group
current_group = elem;
// Disable other elements
$(elem).parentsUntil('#svgcontent').addBack().siblings().each(function() {
var opac = this.getAttribute('opacity') || 1;
// Store the original's opacity
elData(this, 'orig_opac', opac);
this.setAttribute('opacity', opac * .33);
this.setAttribute('style', 'pointer-events: none');
disabled_elems.push(this);
});
clearSelection();
call("contextset", current_group);
}
// Group: Document functions
// Function: clear
// Clears the current document. This is not an undoable action.
this.clear = function() {
pathActions.clear();
clearSelection();
// clear the svgcontent node
canvas.clearSvgContentElement();
// create new document
canvas.current_drawing_ = new svgedit.draw.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");
};
// Function: linkControlPoints
// Alias function
this.linkControlPoints = pathActions.linkControlPoints;
// Function: getContentElem
// Returns the content DOM element
this.getContentElem = function() { return svgcontent; };
// Function: getRootElem
// Returns the root DOM element
this.getRootElem = function() { return svgroot; };
// Function: getSelectedElems
// Returns the array with selected DOM elements
this.getSelectedElems = function() { return selectedElements; };
// Function: getResolution
// Returns the current dimensions and zoom level in an object
var getResolution = this.getResolution = function() {
// var vb = svgcontent.getAttribute("viewBox").split(' ');
// return {'w':vb[2], 'h':vb[3], 'zoom': current_zoom};
var width = svgcontent.getAttribute("width")/current_zoom;
var height = svgcontent.getAttribute("height")/current_zoom;
return {
'w': width,
'h': height,
'zoom': current_zoom
};
};
// Function: getZoom
// Returns the current zoom level
this.getZoom = function(){return current_zoom;};
// Function: getVersion
// Returns a string which describes the revision number of SvgCanvas.
this.getVersion = function() {
return "svgcanvas.js ($Rev: 2082 $)";
};
// Function: setConfig
// Update configuration options with given values
//
// Parameters:
// opts - Object with options (see curConfig for examples)
this.setConfig = function(opts) {
$.extend(curConfig, opts);
}
// Function: getTitle
// Returns the current group/SVG's title contents
this.getTitle = function(elem) {
elem = elem || selectedElements[0];
if(!elem) return;
elem = $(elem).data('gsvg') || $(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 '';
}
// Function: setGroupTitle
// Sets the group/SVG's title content
// TODO: Combine this with setDocumentTitle
this.setGroupTitle = function(val) {
var elem = selectedElements[0];
elem = $(elem).data('gsvg') || elem;
var ts = $(elem).children('title');
var batchCmd = new BatchCommand("Set Label");
if(!val.length) {
// Remove title element
var tsNextSibling = ts.nextSibling;
batchCmd.addSubCommand(new RemoveElementCommand(ts[0], tsNextSibling, elem));
ts.remove();
} else if(ts.length) {
// Change title contents
var title = ts[0];
batchCmd.addSubCommand(new ChangeElementCommand(title, {'#text': title.textContent}));
title.textContent = val;
} else {
// Add title element
title = svgdoc.createElementNS(svgns, "title");
title.textContent = val;
$(elem).prepend(title);
batchCmd.addSubCommand(new InsertElementCommand(title));
}
addCommandToHistory(batchCmd);
}
// Function: getDocumentTitle
// Returns the current document title or an empty string if not found
this.getDocumentTitle = function() {
return canvas.getTitle(svgcontent);
}
// Function: setDocumentTitle
// Adds/updates a title element for the document with the given name.
// This is an undoable action
//
// Parameters:
// newtitle - String with the new title
this.setDocumentTitle = function(newtitle) {
var childs = svgcontent.childNodes, doc_title = false, old_title = '';
var batchCmd = new BatchCommand("Change Image Title");
for (var i=0; i<childs.length; i++) {
if(childs[i].nodeName == 'title') {
doc_title = childs[i];
old_title = doc_title.textContent;
break;
}
}
if(!doc_title) {
doc_title = svgdoc.createElementNS(svgns, "title");
svgcontent.insertBefore(doc_title, svgcontent.firstChild);
}
if(newtitle.length) {
doc_title.textContent = newtitle;
} else {
// No title given, so element is not necessary
doc_title.parentNode.removeChild(doc_title);
}
batchCmd.addSubCommand(new ChangeElementCommand(doc_title, {'#text': old_title}));
addCommandToHistory(batchCmd);
}
// Function: getEditorNS
// Returns the editor's namespace URL, optionally adds it to root element
//
// Parameters:
// add - Boolean to indicate whether or not to add the namespace value
this.getEditorNS = function(add) {
if(add) {
svgcontent.setAttribute('xmlns:se', se_ns);
}
return se_ns;
}
// Function: setResolution
// Changes the document's dimensions to the given size
//
// Parameters:
// x - Number with the width of the new dimensions in user units.
// Can also be the string "fit" to indicate "fit to content"
// y - Number with the height of the new dimensions in user units.
//
// Returns:
// Boolean to indicate 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;
if(x == 'fit') {
// Get bounding box
var bbox = getStrokedBBox();
if(bbox) {
batchCmd = new BatchCommand("Fit Canvas to Content");
var visEls = getVisibleElements();
addToSelection(visEls);
var dx = [], dy = [];
$.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("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(svgcontent, {"width":w, "height":h}));
svgcontent.setAttribute("viewBox", [0, 0, x/current_zoom, y/current_zoom].join(' '));
batchCmd.addSubCommand(new ChangeElementCommand(svgcontent, {"viewBox": ["0 0", w, h].join(' ')}));
addCommandToHistory(batchCmd);
background = document.getElementById("canvas_background");
if (background) {
background.setAttribute("x", -1)
background.setAttribute("y", -1)
background.setAttribute("width", x+2)
background.setAttribute("height", y+2)
}
call("changed", [svgcontent]);
}
return [x,y];
};
// Function: getOffset
// Returns an object with x, y values indicating the svgcontent element's
// position in the editor's canvas.
this.getOffset = function() {
return $(svgcontent).attr(['x', 'y']);
}
// Function: setBBoxZoom
// Sets the zoom level on the canvas-side based on the given value
//
// Parameters:
// val - Bounding box object to zoom to or string indicating zoom option
// editor_w - Integer with the editor's workarea box's width
// editor_h - Integer with the editor's workarea box's height
this.setBBoxZoom = function(val, editor_w, editor_h) {
var spacer = .85;
var bb;
var calcZoom = function(bb) {
if(!bb) return false;
var w_zoom = Math.round((editor_w / bb.width)*100 * spacer)/100;
var h_zoom = Math.round((editor_h / bb.height)*100 * spacer)/100;
var zoomlevel = Math.min(w_zoom,h_zoom);
canvas.setZoom(zoomlevel);
return {'zoom': zoomlevel, 'bbox': bb};
}
if(typeof val == 'object') {
bb = val;
if(bb.width == 0 || bb.height == 0) {
var newzoom = bb.zoom?bb.zoom:current_zoom * bb.factor;
canvas.setZoom(newzoom);
return {'zoom': current_zoom, 'bbox': bb};
}
return calcZoom(bb);
}
switch (val) {
case 'selection':
if(!selectedElements[0]) return;
var sel_elems = $.map(selectedElements, function(n){ if(n) return n; });
bb = getStrokedBBox(sel_elems);
break;
case 'canvas':
var res = getResolution();
spacer = .95;
bb = {width:res.w, height:res.h ,x:0, y:0};
break;
case 'content':
bb = getStrokedBBox();
break;
case 'layer':
bb = getStrokedBBox(getVisibleElements(getCurrentDrawing().getCurrentLayer()));
break;
default:
return;
}
return calcZoom(bb);
}
// Function: setZoom
// Sets the zoom to the given level
//
// Parameters:
// 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);
current_zoom = zoomlevel;
$.each(selectedElements, function(i, elem) {
if(!elem) return;
selectorManager.requestSelector(elem).resize();
});
pathActions.zoomChange();
runExtensions("zoomChanged", zoomlevel);
}
// Function: getMode
// Returns the current editor mode string
this.getMode = function() {
return current_mode;
};
// Function: setMode
// Sets the editor's mode to the given string
//
// Parameters:
// name - String with the new mode to change to
this.setMode = function(name) {
pathActions.clear();
textActions.clear();
$("#workarea").attr("class", name);
cur_properties = (selectedElements[0] && selectedElements[0].nodeName == 'text') ? cur_text : cur_shape;
current_mode = name;
};
// Group: Element Styling
// Function: getColor
// Returns the current fill/stroke option
this.getColor = function(type) {
return cur_properties[type];
};
// Function: setColor
// Change the current stroke/fill color/gradient value
//
// Parameters:
// type - String indicating fill or stroke
// val - The value to set the stroke attribute to
// preventUndo - Boolean indicating whether or not this should be and undoable option
this.setColor = function(type, val, preventUndo) {
cur_shape[type] = val;
cur_properties[type + '_paint'] = {type:"solidColor"};
var elems = [];
var i = selectedElements.length;
while (i--) {
var elem = selectedElements[i];
if (elem) {
if (elem.tagName == "g")
svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);});
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);
}
}
// Function: findDefs
// Return the document's <defs> element, create it first if necessary
var findDefs = function() {
var defs = svgcontent.getElementsByTagNameNS(svgns, "defs");
if (defs.length > 0) {
defs = defs[0];
}
else {
defs = svgdoc.createElementNS(svgns, "defs" );
if(svgcontent.firstChild) {
// first child is a comment, so call nextSibling
svgcontent.insertBefore( defs, svgcontent.firstChild.nextSibling);
} else {
svgcontent.appendChild(defs);
}
}
return defs;
};
// Function: setGradient
// 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(!cur_properties[type + '_paint'] || cur_properties[type + '_paint'].type == "solidColor") return;
var grad = canvas[type + 'Grad'];
// find out if there is a duplicate gradient already in the defs
var duplicate_grad = findDuplicateGradient(grad);
var defs = findDefs();
// no duplicate found, so import gradient into defs
if (!duplicate_grad) {
var orig_grad = 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 = duplicate_grad;
}
canvas.setColor(type, "url(#" + grad.id + ")");
if (type == "canvas") {
var background = document.getElementById("canvas_background");
if (background) {
background.setAttribute('fill', "url(#" + grad.id + ")")
}
}
}
// Function: findDuplicateGradient
// Check if exact gradient already exists
//
// Parameters:
// grad - The gradient DOM element to compare to others
//
// Returns:
// The existing gradient if found, null if not
var findDuplicateGradient = function(grad) {
var defs = findDefs();
var existing_grads = $(defs).find("linearGradient, radialGradient");
var i = existing_grads.length;
var rad_attrs = ['r','cx','cy','fx','fy'];
while (i--) {
var og = existing_grads[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 grad_attrs = $(grad).attr(rad_attrs);
var og_attrs = $(og).attr(rad_attrs);
var diff = false;
$.each(rad_attrs, function(i, attr) {
if(grad_attrs[attr] != og_attrs[attr]) diff = true;
});
if(diff) continue;
}
// else could be a duplicate, iterate through stops
var stops = grad.getElementsByTagNameNS(svgns, "stop");
var ostops = og.getElementsByTagNameNS(svgns, "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;
};
function reorientGrads(elem, m) {
var bb = svgedit.utilities.getBBox(elem);
for(var i = 0; i < 2; i++) {
var type = i === 0 ? 'fill' : 'stroke';
var attrVal = elem.getAttribute(type);
if(attrVal && attrVal.indexOf('url(') === 0) {
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 g_coords = {};
g_coords.x1 = (pt1.x - bb.x) / bb.width;
g_coords.y1 = (pt1.y - bb.y) / bb.height;
g_coords.x2 = (pt2.x - bb.x) / bb.width;
g_coords.y2 = (pt2.y - bb.y) / bb.height;
var newgrad = grad.cloneNode(true);
$(newgrad).attr(g_coords);
newgrad.id = getNextId();
findDefs().appendChild(newgrad);
elem.setAttribute(type, 'url(#' + newgrad.id + ')');
}
}
}
}
// Function: setPaint
// Set a color/gradient to a fill/stroke
//
// Parameters:
// type - String with "fill" or "stroke"
// paint - The jGraduate paint object to apply
this.setPaint = function(type, paint) {
// make a copy
var p = new $.jGraduate.Paint(paint);
this.setPaintOpacity(type, p.alpha/100, true);
// now set the current paint object
cur_properties[type + '_paint'] = p;
switch ( p.type ) {
case "solidColor":
if (p.solidColor != "none" && p.solidColor != "#none") {
this.setColor(type, "#"+p.solidColor)
}
else {
this.setColor(type, "none");
var selector = (type == "fill") ? "#fill_color rect" : "#stroke_color rect"
document.querySelector(selector).setAttribute('fill', 'none');
}
break;
case "linearGradient":
case "radialGradient":
canvas[type + 'Grad'] = p[p.type];
setGradient(type);
break;
default:
// console.log("none!");
}
};
// this.setStrokePaint = function(p) {
// // make a copy
// var p = new $.jGraduate.Paint(p);
// this.setStrokeOpacity(p.alpha/100);
//
// // now set the current paint object
// cur_properties.stroke_paint = p;
// switch ( p.type ) {
// case "solidColor":
// this.setColor('stroke', p.solidColor != "none" ? "#"+p.solidColor : "none");;
// break;
// case "linearGradient"
// case "radialGradient"
// canvas.strokeGrad = p[p.type];
// setGradient(type);
// default:
// // console.log("none!");
// }
// };
//
// this.setFillPaint = function(p, addGrad) {
// // make a copy
// var p = new $.jGraduate.Paint(p);
// this.setFillOpacity(p.alpha/100, true);
//
// // now set the current paint object
// cur_properties.fill_paint = p;
// if (p.type == "solidColor") {
// this.setColor('fill', p.solidColor != "none" ? "#"+p.solidColor : "none");
// }
// else if(p.type == "linearGradient") {
// canvas.fillGrad = p.linearGradient;
// if(addGrad) setGradient();
// }
// else if(p.type == "radialGradient") {
// canvas.fillGrad = p.radialGradient;
// if(addGrad) setGradient();
// }
// else {
// // console.log("none!");
// }
// };
// Function: getStrokeWidth
// Returns the current stroke-width value
this.getStrokeWidth = function() {
return cur_properties.stroke_width;
};
// Function: setStrokeWidth
// 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
//
// Parameters:
// val - A Float indicating the new stroke width value
this.setStrokeWidth = function(val) {
if(val == 0 && ['line', 'path'].indexOf(current_mode) >= 0) {
canvas.setStrokeWidth(1);
return;
}
cur_properties.stroke_width = val;
var elems = [];
var i = selectedElements.length;
while (i--) {
var elem = selectedElements[i];
if (elem) {
if (elem.tagName == "g")
svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);});
else
elems.push(elem);
}
}
if (elems.length > 0) {
changeSelectedAttribute("stroke-width", val, elems);
call("changed", selectedElements);
}
};
// Function: setStrokeAttr
// Set the given stroke-related attribute the given value for selected elements
//
// Parameters:
// attr - String with the attribute name
// val - String or number with the attribute value
this.setStrokeAttr = function(attr, val) {
cur_shape[attr.replace('-','_')] = val;
var elems = [];
var i = selectedElements.length;
while (i--) {
var elem = selectedElements[i];
if (elem) {
if (elem.tagName == "g")
svgedit.utilities.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);
}
};
// Function: getStyle
// Returns current style options
this.getStyle = function() {
return cur_shape;
}
// Function: getOpacity
// Returns the current opacity
this.getOpacity = function() {
return cur_shape.opacity;
};
// Function: setOpacity
// Sets the given opacity to the current selected elements
this.setOpacity = function(val) {
cur_shape.opacity = val;
changeSelectedAttribute("opacity", val);
};
// Function: getOpacity
// Returns the current fill opacity
this.getFillOpacity = function() {
return cur_shape.fill_opacity;
};
// Function: getStrokeOpacity
// Returns the current stroke opacity
this.getStrokeOpacity = function() {
return cur_shape.stroke_opacity;
};
// Function: setPaintOpacity
// Sets the current fill/stroke opacity
//
// Parameters:
// type - String with "fill" or "stroke"
// val - Float with the new opacity value
// preventUndo - Boolean indicating whether or not this should be an undoable action
this.setPaintOpacity = function(type, val, preventUndo) {
cur_shape[type + '_opacity'] = val;
if (!preventUndo)
changeSelectedAttribute(type + "-opacity", val);
else
changeSelectedAttributeNoUndo(type + "-opacity", val);
};
// Function: getBlur
// Gets the stdDeviation blur value of the given element
//
// Parameters:
// elem - The element to check the blur value for
this.getBlur = function(elem) {
var val = 0;
// var elem = selectedElements[0];
if(elem) {
var filter_url = elem.getAttribute('filter');
if(filter_url) {
var blur = getElem(elem.id + '_blur');
if(blur) {
val = blur.firstChild.getAttribute('stdDeviation');
}
}
}
return val;
};
(function() {
var cur_command = null;
var filter = null;
var filterHidden = false;
// Function: setBlurNoUndo
// Sets the stdDeviation blur value on the selected element without being undoable
//
// Parameters:
// 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(svgedit.browser.isWebkit()) {
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();
cur_command.addSubCommand(bCmd);
addCommandToHistory(cur_command);
cur_command = null;
filter = null;
}
// Function: setBlurOffsets
// 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
//
// Parameters:
// filter - The filter DOM element to update
// 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(!svgedit.browser.isWebkit()) {
filter.removeAttribute('x');
filter.removeAttribute('y');
filter.removeAttribute('width');
filter.removeAttribute('height');
}
}
}
// Function: setBlur
// Adds/updates the blur filter to the selected element
//
// Parameters:
// val - Float with the new stdDeviation blur value
// complete - Boolean indicating whether or not the action should be completed (to add to the undo manager)
canvas.setBlur = function(val, complete) {
if(cur_command) {
finishChange();
return;
}
// Looks for associated blur, creates one if not found
var elem = selectedElements[0];
var elem_id = elem.id;
filter = getElem(elem_id + '_blur');
val -= 0;
var batchCmd = new BatchCommand();
// 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": elem_id + '_blur'
}
});
filter.appendChild(newblur);
findDefs().appendChild(filter);
batchCmd.addSubCommand(new InsertElementCommand(filter));
}
var changes = {filter: elem.getAttribute('filter')};
if(val === 0) {
elem.removeAttribute("filter");
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
return;
} else {
changeSelectedAttribute("filter", 'url(#' + elem_id + '_blur)');
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
canvas.setBlurOffsets(filter, val);
}
cur_command = batchCmd;
canvas.undoMgr.beginUndoableChange("stdDeviation", [filter?filter.firstChild:null]);
if(complete) {
canvas.setBlurNoUndo(val);
finishChange();
}
};
}());
// Function: getBold
// Check whether selected element is bold or not
//
// Returns:
// Boolean indicating whether or not element is bold
this.getBold = function() {
var selectedElems = selectedElements.filter(Boolean)
var isBold = true
selectedElems.forEach(function(el){
if (el.getAttribute("font-weight") != "bold") isBold = false;
});
return isBold;
};
// Function: setBold
// Make the selected element bold or normal
//
// Parameters:
// b - Boolean indicating bold (true) or normal (false)
this.setBold = function(b) {
var selectedElems = selectedElements.filter(Boolean)
selectedElems.forEach(function(selected){
if (selected != null && selected.tagName == "text") changeSelectedAttribute("font-weight", b ? "bold" : "normal");
});
if (!selectedElems[0].textContent) textActions.setCursor();
};
// Function: getItalic
// Check whether selected element is italic or not
//
// Returns:
// Boolean indicating whether or not element is italic
this.getItalic = function() {
var selectedElems = selectedElements.filter(Boolean)
var isItalic = true
selectedElems.forEach(function(el){
if (el.getAttribute("font-style") != "italic") isItalic = false;
});
return isItalic;
};
// Function: setItalic
// Make the selected element italic or normal
//
// Parameters:
// b - Boolean indicating italic (true) or normal (false)
this.setItalic = function(i) {
var selectedElems = selectedElements.filter(Boolean)
selectedElems.forEach(function(selected){
if (selected != null && selected.tagName == "text") changeSelectedAttribute("font-style", i ? "italic" : "normal");
});
if (!selectedElems[0].textContent) textActions.setCursor();
};
// Function: getFontFamily
// Returns the current font family
this.getFontFamily = function() {
return cur_text.font_family;
};
// Function: setFontFamily
// Set the new font family
//
// Parameters:
// val - String with the new font family
this.setFontFamily = function(val) {
cur_text.font_family = val;
changeSelectedAttribute("font-family", val);
if(selectedElements[0] && !selectedElements[0].textContent) {
textActions.setCursor();
}
};
// Function: setFontColor
// Set the new font color
//
// Parameters:
// val - String with the new font color
this.setFontColor = function(val) {
cur_text.fill = val;
changeSelectedAttribute("fill", val);
};
// Function: getFontColor
// Returns the current font color
this.getFontSize = function() {
return cur_text.fill;
};
// Function: getFontSize
// Returns the current font size
this.getFontSize = function() {
return cur_text.font_size;
};
// Function: setFontSize
// Applies the given font size to the selected element
//
// Parameters:
// val - Float with the new font size
this.setFontSize = function(val) {
cur_text.font_size = val;
changeSelectedAttribute("font-size", val);
if(!selectedElements[0].textContent) {
textActions.setCursor();
}
};
// Function: getText
// Returns the current text (textContent) of the selected element
this.getText = function() {
var selected = selectedElements[0];
if (selected == null) { return ""; }
return selected.textContent;
};
// Function: setTextContent
// Updates the text element with the given string
//
// Parameters:
// val - String with the new text
this.setTextContent = function(val) {
changeSelectedAttribute("#text", val);
textActions.init(val);
textActions.setCursor();
};
// Function: setImageURL
// Sets the new image URL for the selected image element. Updates its size if
// a new URL is given
//
// Parameters:
// val - String with the image URL/path
this.setImageURL = function(val) {
var elem = selectedElements[0];
if(!elem) return;
var attrs = $(elem).attr(['width', 'height']);
var setsize = (!attrs.width || !attrs.height);
var cur_href = getHref(elem);
// Do nothing if no URL change or size change
if(cur_href !== val) {
setsize = true;
} else if(!setsize) return;
var batchCmd = new BatchCommand("Change Image URL");
setHref(elem, val);
batchCmd.addSubCommand(new ChangeElementCommand(elem, {
"#href": cur_href
}));
if(setsize) {
$(new Image()).load(function() {
var changes = $(elem).attr(['width', 'height']);
$(elem).attr({
width: this.width,
height: this.height
});
selectorManager.requestSelector(elem).resize();
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
addCommandToHistory(batchCmd);
call("changed", [elem]);
}).attr('src',val);
} else {
addCommandToHistory(batchCmd);
}
};
// Function: setLinkURL
// Sets the new link URL for the selected anchor element.
//
// Parameters:
// 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 parents_a = $(elem).parents('a');
if(parents_a.length) {
elem = parents_a[0];
} else {
return;
}
}
var cur_href = getHref(elem);
if(cur_href === val) return;
var batchCmd = new BatchCommand("Change Link URL");
setHref(elem, val);
batchCmd.addSubCommand(new ChangeElementCommand(elem, {
"#href": cur_href
}));
addCommandToHistory(batchCmd);
};
// Function elementAreSame
// Checks if all the selected Elements are the same type
//
// Parameters:
// None
this.elementsAreSame = function(elements) {
if (!elements.length || elements[0] == null) return null
else {
var isSameElement = function(el) {
if (el && selectedElements[0])
return (el.nodeName == selectedElements[0].nodeName);
else return null;
}
return selectedElements.every(isSameElement);
}
}
// Function: setRectRadius
// Sets the rx & ry values to the selected rect element to change its corner radius
//
// Parameters:
// val - The new radius
this.setRectRadius = function(val) {
if (canvas.elementsAreSame(selectedElements) && selectedElements[0].tagName == "rect") {
var assign_rr = function(selected){
var r = selected.getAttribute("rx");
if (r != val) {
selected.setAttribute("rx", val);
selected.setAttribute("ry", val);
addCommandToHistory(new ChangeElementCommand(selected, {"rx":r, "ry":r}, "Radius"));
call("changed", [selected]);
}
}
selectedElements.forEach(assign_rr)
}
};
// Function: makeHyperlink
// Wraps the selected element(s) in an anchor element or converts group to one
this.makeHyperlink = function(url) {
canvas.groupSelectedElements('a', url);
// TODO: If element is a single "g", convert to "a"
// if(selectedElements.length > 1 && selectedElements[1]) {
}
// Function: removeHyperlink
this.removeHyperlink = function() {
canvas.ungroupSelectedElement();
}
// Group: Element manipulation
// Function: setSegType
// Sets the new segment type to the selected segment(s).
//
// Parameters:
// new_type - Integer with the new segment type
// See http://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg for list
this.setSegType = function(new_type) {
pathActions.setSegType(new_type);
}
// TODO(codedread): Remove the getBBox argument and split this function into two.
// Function: convertToPath
// Convert selected element to a path, or get the BBox of an element-as-path
//
// Parameters:
// elem - The DOM element to be converted
// 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) {
if(elem == null) {
var elems = selectedElements;
$.each(selectedElements, function(i, elem) {
if(elem) canvas.convertToPath(elem);
});
return;
}
if(!getBBox) {
var batchCmd = new BatchCommand("Convert element to Path");
}
var attrs = getBBox?{}:{
"fill": cur_shape.fill,
"fill-opacity": cur_shape.fill_opacity,
"stroke": cur_shape.stroke,
"stroke-width": cur_shape.stroke_width,
"stroke-dasharray": cur_shape.stroke_dasharray,
"stroke-linejoin": cur_shape.stroke_linejoin,
"stroke-linecap": cur_shape.stroke_linecap,
"stroke-opacity": cur_shape.stroke_opacity,
"opacity": cur_shape.opacity,
"visibility":"hidden"
};
// any attribute on the element not covered by the above
// TODO: make this list global so that we can properly maintain it
// TODO: what about @transform, @clip-rule, @fill-rule, etc?
$.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() {
if (elem.getAttribute(this)) {
attrs[this] = elem.getAttribute(this);
}
});
var path = addSvgElementFromJson({
"element": "path",
"attr": attrs
});
var eltrans = elem.getAttribute("transform");
if(eltrans) {
path.setAttribute("transform",eltrans);
}
var id = elem.id;
var parent = elem.parentNode;
if(elem.nextSibling) {
parent.insertBefore(path, elem);
} else {
parent.appendChild(path);
}
var d = '';
var joinSegs = function(segs) {
$.each(segs, function(j, seg) {
var l = seg[0], pts = seg[1];
d += l;
for(var i=0; i < pts.length; i+=2) {
d += (pts[i] +','+pts[i+1]) + ' ';
}
});
}
// Possibly the cubed root of 6, but 1.81 works best
var num = 1.81;
switch (elem.tagName) {
case 'ellipse':
case 'circle':
var a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
var cx = a.cx, cy = a.cy, rx = a.rx, ry = a.ry;
if(elem.tagName == 'circle') {
rx = ry = $(elem).attr('r');
}
joinSegs([
['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':
var a = $(elem).attr(["x1", "y1", "x2", "y2"]);
d = "M"+a.x1+","+a.y1+"L"+a.x2+","+a.y2;
break;
case 'polyline':
case 'polygon':
d = "M" + elem.getAttribute('points');
break;
case 'rect':
var r = $(elem).attr(['rx', 'ry']);
var rx = r.rx, ry = r.ry;
var b = elem.getBBox();
var x = b.x, y = b.y, w = b.width, h = b.height;
var num = 4-num; // Why? Because!
if(!rx && !ry) {
// Regular rect
joinSegs([
['M',[x, y]],
['L',[x+w, y]],
['L',[x+w, y+h]],
['L',[x, y+h]],
['L',[x, y]],
['Z',[]]
]);
} else {
if (!ry) ry = rx
joinSegs([
['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:
path.parentNode.removeChild(path);
break;
}
if(d) {
path.setAttribute('d',d);
}
if(!getBBox) {
// Replace the current element with the converted one
// Reorient if it has a matrix
if(eltrans) {
var tlist = getTransformList(path);
if(hasMatrixTransform(tlist)) {
pathActions.resetOrientation(path);
}
}
var nextSibling = elem.nextSibling;
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
batchCmd.addSubCommand(new InsertElementCommand(path));
clearSelection();
elem.parentNode.removeChild(elem)
path.setAttribute('id', id);
path.removeAttribute("visibility");
addToSelection([path], true);
addCommandToHistory(batchCmd);
} else {
// Get the correct BBox of the new path, then discard it
pathActions.resetOrientation(path);
var bb = false;
try {
bb = path.getBBox();
} catch(e) {
// Firefox fails
}
path.parentNode.removeChild(path);
return bb;
}
};
// Function: changeSelectedAttributeNoUndo
// This function makes the changes to the elements. It does not add the change
// to the history stack.
//
// Parameters:
// attr - String with the attribute name
// newValue - String or number with the new attribute value
// elems - The DOM elements to apply the change to
var changeSelectedAttributeNoUndo = this.changeSelectedAttributeNoUndo = function(attr, newValue, elems) {
if(current_mode == 'pathedit') {
// Editing node
pathActions.moveNode(attr, newValue);
}
var elems = elems || selectedElements;
var i = elems.length;
var no_xy_elems = ['g', 'polyline', 'path'];
var good_g_attrs = ['transform', 'opacity', 'filter'];
while (i--) {
var elem = elems[i];
if (elem == null) continue;
// Go into "select" mode for text changes
if(current_mode === "textedit" && attr !== "#text" && elem.textContent.length) {
textActions.toSelectMode(elem);
}
// Set x,y vals on elements that don't have them
if((attr === 'x' || attr === 'y') && no_xy_elems.indexOf(elem.tagName) >= 0) {
var bbox = getStrokedBBox([elem]);
var diff_x = attr === 'x' ? newValue - bbox.x : 0;
var diff_y = attr === 'y' ? newValue - bbox.y : 0;
canvas.moveSelectedElements(diff_x*current_zoom, diff_y*current_zoom, true);
continue;
}
var oldval = attr === "#text" ? elem.textContent : elem.getAttribute(attr);
if (oldval == null) oldval = "";
if (oldval !== String(newValue)) {
if (attr == "#text") {
var old_w = svgedit.utilities.getBBox(elem).width;
elem.textContent = newValue;
} else if (attr == "#href") {
setHref(elem, newValue);
}
else elem.setAttribute(attr, newValue);
// 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.indexOf(elem) >= 0) {
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 = svgedit.utilities.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
} // for each elem
};
// Function: changeSelectedAttribute
// 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.
//
// Parameters:
// attr - String with the attribute name
// newValue - String or number with the new attribute value
// elems - The DOM elements to apply the change to
var changeSelectedAttribute = this.changeSelectedAttribute = function(attr, val, elems) {
var elems = elems || selectedElements;
canvas.undoMgr.beginUndoableChange(attr, elems);
var i = elems.length;
changeSelectedAttributeNoUndo(attr, val, elems);
var batchCmd = canvas.undoMgr.finishUndoableChange();
if (!batchCmd.isEmpty()) {
addCommandToHistory(batchCmd);
}
};
// Function: deleteSelectedElements
// Removes all selected elements from the DOM and adds the change to the
// history stack
this.deleteSelectedElements = function() {
var batchCmd = new BatchCommand("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.
svgedit.path.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 nextSibling = t.nextSibling;
var elem = parent.removeChild(t);
selectedCopy.push(selected); //for the copy
selectedElements[i] = null;
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
}
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
call("changed", selectedCopy);
clearSelection();
};
// Function: cutSelectedElements
// Removes all selected elements from the DOM and adds the change to the
// history stack. Remembers removed elements on the clipboard
// TODO: Combine similar code with deleteSelectedElements
this.cutSelectedElements = function() {
var batchCmd = new BatchCommand("Cut 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.
svgedit.path.removePath_(t.id);
var nextSibling = t.nextSibling;
var elem = parent.removeChild(t);
selectedCopy.push(selected); //for the copy
selectedElements[i] = null;
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
}
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
call("changed", selectedCopy);
clearSelection();
canvas.clipBoard = selectedCopy;
};
// Function: copySelectedElements
// Remembers the current selected elements on the clipboard
this.copySelectedElements = function() {
canvas.clipBoard = $.merge([], selectedElements);
};
this.pasteElements = function(type, x, y) {
var cb = canvas.clipBoard;
var len = cb.length;
if(!len) return;
var pasted = [];
var batchCmd = new BatchCommand('Paste elements');
// Move elements to lastClickPoint
while (len--) {
var elem = cb[len];
if(!elem) continue;
var copy = copyElem(elem);
// See if elem with elem ID is in the DOM already
if(!getElem(elem.id)) copy.id = elem.id;
pasted.push(copy);
(current_group || getCurrentDrawing().getCurrentLayer()).appendChild(copy);
batchCmd.addSubCommand(new InsertElementCommand(copy));
}
svgCanvas.clearSelection();
setTimeout(function(){selectOnly(pasted)},100);
addCommandToHistory(batchCmd);
call("changed", pasted);
}
// Function: groupSelectedElements
// Wraps all the selected elements in a group (g) element
// Parameters:
// type - type of element to group into, defaults to <g>
this.groupSelectedElements = function(type) {
if(!type) type = 'g';
var cmd_str = '';
switch ( type ) {
case "a":
cmd_str = "Make hyperlink";
var url = '';
if(arguments.length > 1) {
url = arguments[1];
}
break;
default:
type = 'g';
cmd_str = "Group Elements";
break;
}
var batchCmd = new BatchCommand(cmd_str);
// create and insert the group element
var g = addSvgElementFromJson({
"element": type,
"attr": {
"id": getNextId()
}
});
if(type === 'a') {
setHref(g, url);
}
batchCmd.addSubCommand(new InsertElementCommand(g));
// now move all children into the group
var i = selectedElements.length;
while (i--) {
var elem = selectedElements[i];
if (elem == null) continue;
if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) {
elem = elem.parentNode;
}
var oldNextSibling = elem.nextSibling;
var oldParent = elem.parentNode;
g.appendChild(elem);
batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent));
}
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
// update selection
selectOnly([g], true);
};
// Function: pushGroupProperties
// 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("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 i = 0;
var gangle = getRotationAngle(g);
var gattrs = $(g).attr(['filter', 'opacity']);
var gfilter, gblur;
for(var i = 0; i < len; i++) {
var elem = children[i];
if(elem.nodeType !== 1) continue;
if(gattrs.opacity !== null && gattrs.opacity !== 1) {
var c_opac = elem.getAttribute('opacity') || 1;
var new_opac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100)/100;
changeSelectedAttribute('opacity', new_opac, [elem]);
}
if(gattrs.filter) {
var cblur = this.getBlur(elem);
var orig_cblur = cblur;
if(!gblur) gblur = this.getBlur(g);
if(cblur) {
// Is this formula correct?
cblur = (gblur-0) + (cblur-0);
} else if(cblur === 0) {
cblur = gblur;
}
// If child has no current filter, get group's filter or clone it.
if(!orig_cblur) {
// Set group's filter to use first child's ID
if(!gfilter) {
gfilter = getRefElem(gattrs.filter);
} else {
// Clone the group's filter
gfilter = copyElem(gfilter);
findDefs().appendChild(gfilter);
}
} else {
gfilter = getRefElem(elem.getAttribute('filter'));
}
// Change this in future for different filters
var suffix = (gfilter.firstChild.tagName === 'feGaussianBlur')?'blur':'filter';
gfilter.id = elem.id + '_' + suffix;
changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [elem]);
// Update blur value
if(cblur) {
changeSelectedAttribute('stdDeviation', cblur, [gfilter.firstChild]);
canvas.setBlurOffsets(gfilter, cblur);
}
}
var chtlist = getTransformList(elem);
// Don't process gradient transforms
if(~elem.tagName.indexOf('Gradient')) chtlist = null;
// Hopefully not a problem to add this. Necessary for elements like <desc/>
if(!chtlist) continue;
// Apparently <defs> can get get a transformlist, but we don't want it to have one!
if(elem.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(elem);
if (cangle) {
rcm = chtlist.getItem(0).matrix;
}
// get child's old center of rotation
var cbox = svgedit.utilities.getBBox(elem);
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 = elem.getAttribute("transform");
var changes = {};
changes["transform"] = oldxform ? oldxform : "";
var newxform = svgroot.createSVGTransform();
// [ gm ] [ chm ] = [ chm ] [ gm' ]
// [ gm' ] = [ chm_inv ] [ gm ] [ chm ]
var chm = transformListToTransform(chtlist).matrix,
chm_inv = chm.inverse();
var gm = matrixMultiply( chm_inv, m, chm );
newxform.setMatrix(gm);
chtlist.appendItem(newxform);
}
var cmd = recalculateDimensions(elem);
if(cmd) batchCmd.addSubCommand(cmd);
}
}
// remove transform and make it undo-able
if (xform) {
var changes = {};
changes["transform"] = xform;
g.setAttribute("transform", "");
g.removeAttribute("transform");
batchCmd.addSubCommand(new ChangeElementCommand(g, changes));
}
if (undoable && !batchCmd.isEmpty()) {
return batchCmd;
}
}
// Function: ungroupSelectedElement
// 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).data('gsvg') || $(g).data('symbol')) {
// Is svg, so actually convert to group
convertToGroup(g);
return;
} else if(g.tagName === 'use') {
// Somehow doesn't have data set, so retrieve
var symbol = getElem(getHref(g).substr(1));
$(g).data('symbol', symbol).data('ref', symbol);
convertToGroup(g);
return;
}
var parents_a = $(g).parents('a');
if(parents_a.length) {
g = parents_a[0];
}
// Look for parent "a"
if (g.tagName === "g" || g.tagName === "a") {
var batchCmd = new BatchCommand("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 elem = g.firstChild;
var oldNextSibling = elem.nextSibling;
var oldParent = elem.parentNode;
// Remove child title elements
if(elem.tagName === 'title') {
var nextSibling = elem.nextSibling;
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent));
oldParent.removeChild(elem);
continue;
}
children[i++] = elem = parent.insertBefore(elem, anchor);
batchCmd.addSubCommand(new MoveElementCommand(elem, 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(g, gNextSibling, parent));
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
// update selection
addToSelection(children);
}
};
// Function: moveToTopSelectedElement
// Repositions the selected element to the bottom in the DOM to appear on top of
// other elements
this.moveToTopSelectedElement = function() {
var selected = selectedElements.filter(Boolean).reverse();
var batchCmd = new BatchCommand("Move to top");
selected.forEach(function(element){
var t = element;
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) {
batchCmd.addSubCommand(new MoveElementCommand(t, oldNextSibling, oldParent, "top"));
call("changed", [t]);
}
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
})
};
// Function: moveToBottomSelectedElement
// Repositions the selected element to the top in the DOM to appear under
// other elements
this.moveToBottomSelectedElement = function() {
var selected = selectedElements.filter(Boolean).reverse();
var batchCmd = new BatchCommand("Move to top");
selected.forEach(function(element){
var t = element;
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) {
batchCmd.addSubCommand(new MoveElementCommand(t, oldNextSibling, oldParent, "bottom"));
call("changed", [t]);
}
})
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
};
// Function: moveUpDownSelected
// Moves the select element up or down the stack, based on the visibly
// intersecting elements
//
// Parameters:
// dir - String that's either 'Up' or 'Down'
this.moveUpDownSelected = function(dir) {
var selected = selectedElements.filter(Boolean);
if(dir == 'Down') selected.reverse();
var batchCmd = new BatchCommand("Move " + dir);
selected.forEach(function(selected){
curBBoxes = [];
var closest, found_cur;
// jQuery sorts this list
var list = $(getIntersectionList(getStrokedBBox([selected]))).toArray();
if(dir == 'Down') list.reverse();
$.each(list, function() {
if(!found_cur) {
if(this == selected) {
found_cur = true;
}
return;
}
closest = this;
return false;
});
if(!closest) return;
var t = selected;
var oldParent = t.parentNode;
var oldNextSibling = t.nextSibling;
$(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) {
batchCmd.addSubCommand(new MoveElementCommand(t, oldNextSibling, oldParent, "Move " + dir));
call("changed", [t]);
}
});
if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
};
// Function: moveSelectedElements
// Moves selected elements on the X/Y axis
//
// Parameters:
// dx - Float with the distance to move on the x-axis
// dy - Float with the distance to move on the y-axis
// 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 /= current_zoom;
dy /= current_zoom;
}
var undoable = undoable || true;
var batchCmd = new BatchCommand("position");
var i = selectedElements.length;
while (i--) {
var selected = selectedElements[i];
if (selected != null) {
// if (i==0)
// selectedBBoxes[0] = svgedit.utilities.getBBox(selected);
// var b = {};
// for(var 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;
}
};
// Function: cloneSelectedElements
// Create deep DOM copies (clones) of all selected elements and move them slightly
// from their originals
this.cloneSelectedElements = function(x,y, drag) {
var batchCmd = new BatchCommand("Clone Elements");
// find all the elements selected (stop at first null)
var len = selectedElements.length;
for (var i = 0; i < len; ++i) {
var 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 i = copiedElements.length;
clones = []
while (i--) {
// clone each element and replace it within copiedElements
var elem = copiedElements[i]
var clone = copyElem(copiedElements[i]);
var parent = (current_group || getCurrentDrawing().getCurrentLayer())
if (drag) {
//removed the dragged transform until that moment
tlist = getTransformList(clone)
tlist.removeItem(0)
recalculateDimensions(clone)
parent.insertBefore(clone, elem);
}
else {
parent.appendChild(clone);
}
clones.push(clone)
batchCmd.addSubCommand(new InsertElementCommand(clone));
}
if (!batchCmd.isEmpty()) {
addToSelection(copiedElements.reverse()); // Need to reverse for correct selection-adding
if (!drag) this.moveSelectedElements(x,y,false);
addCommandToHistory(batchCmd);
}
return clones
};
// Function: alignSelectedElements
// Aligns selected elements
//
// Parameters:
// type - String with single character indicating the alignment type
// relative_to - String that must be one of the following:
// "selected", "largest", "smallest", "page"
this.alignSelectedElements = function(type, relative_to) {
var bboxes = [], angles = [];
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;
var len = selectedElements.length;
if (!len) return;
for (var i = 0; i < len; ++i) {
if (selectedElements[i] == null) break;
var elem = selectedElements[i];
bboxes[i] = getStrokedBBox([elem]);
// now bbox is axis-aligned and handles rotation
switch (relative_to) {
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 (relative_to == 'page') {
minx = 0;
miny = 0;
maxx = canvas.contentW;
maxy = canvas.contentH;
}
var dx = new Array(len);
var dy = new Array(len);
for (var i = 0; i < len; ++i) {
if (selectedElements[i] == null) break;
var elem = selectedElements[i];
var bbox = bboxes[i];
dx[i] = 0;
dy[i] = 0;
switch (type) {
case 'l': // left (horizontal)
dx[i] = minx - bbox.x;
break;
case 'c': // center (horizontal)
dx[i] = (minx+maxx)/2 - (bbox.x + bbox.width/2);
break;
case 'r': // right (horizontal)
dx[i] = maxx - (bbox.x + bbox.width);
break;
case 't': // top (vertical)
dy[i] = miny - bbox.y;
break;
case 'm': // middle (vertical)
dy[i] = (miny+maxy)/2 - (bbox.y + bbox.height/2);
break;
case 'b': // bottom (vertical)
dy[i] = maxy - (bbox.y + bbox.height);
break;
}
}
this.moveSelectedElements(dx,dy);
};
// Group: Additional editor tools
this.contentW = getResolution().w;
this.contentH = getResolution().h;
// Function: updateCanvas
// Updates the editor canvas width/height/position after a zoom has occurred
//
// Parameters:
// w - Float with the new width
// h - Float with the new height
//
// Returns:
// Object with the following values:
// * x - The canvas' new x coordinate
// * y - The canvas' new y coordinate
// * old_x - The canvas' old x coordinate
// * old_y - 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 = $('#canvasBackground')[0];
var old_x = svgcontent.getAttribute('x');
var old_y = svgcontent.getAttribute('y');
var x = (w/2 - this.contentW*current_zoom/2);
var y = (h/2 - this.contentH*current_zoom/2);
assignAttributes(svgcontent, {
width: this.contentW*current_zoom,
height: this.contentH*current_zoom,
'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 bg_img = getElem('background_image');
if (bg_img) {
assignAttributes(bg_img, {
'width': '100%',
'height': '100%'
});
}
selectorManager.selectorParentGroup.setAttribute("transform","translate(" + x + "," + y + ")");
return {x:x, y:y, old_x:old_x, old_y:old_y, d_x:x - old_x, d_y:y - old_y};
}
// Function: setBackground
// Set the background of the editor (NOT the actual document)
//
// Parameters:
// color - String with fill color to apply
// url - URL or path to image to use
this.setBackground = function(color, url) {
var bg = getElem('canvasBackground');
var border = $(bg).find('rect')[0];
var bg_img = getElem('background_image');
border.setAttribute('fill',color);
if(url) {
if(!bg_img) {
bg_img = svgdoc.createElementNS(svgns, "image");
assignAttributes(bg_img, {
'id': 'background_image',
'width': '100%',
'height': '100%',
'preserveAspectRatio': 'xMinYMin',
'style':'pointer-events:none'
});
}
setHref(bg_img, url);
bg.appendChild(bg_img);
} else if(bg_img) {
bg_img.parentNode.removeChild(bg_img);
}
}
// Function: cycleElement
// Select the next/previous element within the current layer
//
// Parameters:
// next - Boolean where true = next and false = previous element
this.cycleElement = function(next) {
var cur_elem = selectedElements[0];
var elem = false;
var all_elems = getVisibleElements(current_group || getCurrentDrawing().getCurrentLayer());
if(!all_elems.length) return;
if (cur_elem == null) {
var num = next?all_elems.length-1:0;
elem = all_elems[num];
} else {
var i = all_elems.length;
while(i--) {
if(all_elems[i] == cur_elem) {
var num = next?i-1:i+1;
if(num >= all_elems.length) {
num = 0;
} else if(num < 0) {
num = all_elems.length-1;
}
elem = all_elems[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
// 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,
call: call,
ChangeElementCommand: ChangeElementCommand,
copyElem: copyElem,
ffClone: ffClone,
findDefs: findDefs,
findDuplicateGradient: findDuplicateGradient,
getElem: getElem,
getId: getId,
getIntersectionList: getIntersectionList,
getMouseTarget: getMouseTarget,
getNextId: getNextId,
getPathBBox: getPathBBox,
getUrlFromAttr: getUrlFromAttr,
hasMatrixTransform: hasMatrixTransform,
identifyLayers: identifyLayers,
InsertElementCommand: InsertElementCommand,
isIdentity: svgedit.math.isIdentity,
logMatrix: logMatrix,
matrixMultiply: matrixMultiply,
MoveElementCommand: MoveElementCommand,
preventClickDefault: preventClickDefault,
recalculateAllSelectedDimensions: recalculateAllSelectedDimensions,
recalculateDimensions: recalculateDimensions,
remapElement: remapElement,
RemoveElementCommand: RemoveElementCommand,
removeUnusedDefElems: removeUnusedDefElems,
round: round,
runExtensions: runExtensions,
sanitizeSvg: sanitizeSvg,
SVGEditTransformList: svgedit.transformlist.SVGTransformList,
toString: toString,
transformBox: svgedit.math.transformBox,
transformListToTransform: transformListToTransform,
transformPoint: transformPoint,
walkTree: svgedit.utilities.walkTree
}
return obj;
};
}
/*
* svg-editor.js
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Pavol Rusnak
* Copyright(c) 2010 Jeff Schiller
* Copyright(c) 2010 Narendra Sisodiya
* Copyright(c) 2012 Mark MacKay
*
*/
// Dependencies:
// 1) units.js
// 2) browser.js
// 3) svgcanvas.js
if(!window.methodDraw) window.methodDraw = function($) {
var svgCanvas;
var Editor = {};
var is_ready = false;
var curConfig = {
canvas_expansion: 1,
dimensions: [800,600],
initFill: {color: 'fff', opacity: 1},
initStroke: {width: 1.5, color: '000', opacity: 1},
initOpacity: 1,
imgPath: 'images/',
extPath: 'extensions/',
jGraduatePath: 'images/',
extensions: [],
initTool: 'select',
wireframe: false,
colorPickerCSS: false,
gridSnapping: false,
gridColor: "#000",
baseUnit: 'px',
snappingStep: 10,
showRulers: (svgedit.browser.isTouch()) ? false : true,
show_outside_canvas: false,
no_save_warning: true,
initFont: 'Helvetica, Arial, sans-serif'
};
var curPrefs = {}; //$.extend({}, defaultPrefs);
var customHandlers = {};
Editor.curConfig = curConfig;
Editor.tool_scale = 1;
Editor.setConfig = function(opts) {
$.extend(true, curConfig, opts);
if(opts.extensions) {
curConfig.extensions = opts.extensions;
}
}
// Extension mechanisms must call setCustomHandlers with two functions: opts.open and opts.save
// opts.open's responsibilities are:
// - invoke a file chooser dialog in 'open' mode
// - let user pick a SVG file
// - calls setCanvas.setSvgString() with the string contents of that file
// opts.save's responsibilities are:
// - accept the string contents of the current document
// - invoke a file chooser dialog in 'save' mode
// - save the file to location chosen by the user
Editor.setCustomHandlers = function(opts) {
Editor.ready(function() {
if(opts.open) {
$('#tool_open > input[type="file"]').remove();
$('#tool_open').show();
svgCanvas.open = opts.open;
}
if(opts.save) {
Editor.show_save_warning = false;
svgCanvas.bind("saved", opts.save);
}
if(opts.pngsave) {
svgCanvas.bind("exported", opts.pngsave);
}
customHandlers = opts;
});
}
Editor.randomizeIds = function() {
svgCanvas.randomizeIds(arguments)
}
Editor.init = function() {
// For external openers
(function() {
// let the opener know SVG Edit is ready
var w = window.opener;
if (w) {
try {
var methodDrawReadyEvent = w.document.createEvent("Event");
methodDrawReadyEvent.initEvent("methodDrawReady", true, true);
w.document.documentElement.dispatchEvent(methodDrawReadyEvent);
}
catch(e) {}
}
})();
$("body").toggleClass("touch", svgedit.browser.isTouch());
$("#canvas_width").val(curConfig.dimensions[0]);
$("#canvas_height").val(curConfig.dimensions[1]);
var extFunc = function() {
$.each(curConfig.extensions, function() {
var extname = this;
$.getScript(curConfig.extPath + extname, function(d) {
// Fails locally in Chrome 5
if(!d) {
var s = document.createElement('script');
s.src = curConfig.extPath + extname;
document.querySelector('head').appendChild(s);
}
});
});
}
// Load extensions
// Bit of a hack to run extensions in local Opera/IE9
if(document.location.protocol === 'file:') {
setTimeout(extFunc, 100);
} else {
extFunc();
}
$.svgIcons(curConfig.imgPath + 'svg_edit_icons.svg', {
w:27, h:27,
id_match: false,
no_img: true, // Opera & Firefox 4 gives odd behavior w/images
fallback_path: curConfig.imgPath,
fallback:{
'logo':'logo.png',
'select':'select.png',
'select_node':'select_node.png',
'pencil':'pencil.png',
'pen':'line.png',
'rect':'square.png',
'ellipse':'ellipse.png',
'path':'path.png',
'text':'text.png',
'image':'image.png',
'zoom':'zoom.png',
'delete':'delete.png',
'spapelib':'shapelib.png',
'node_delete':'node_delete.png',
'align_left':'align-left.png',
'align_center':'align-center.png',
'align_right':'align-right.png',
'align_top':'align-top.png',
'align_middle':'align-middle.png',
'align_bottom':'align-bottom.png',
'arrow_right':'flyouth.png',
'arrow_down':'dropdown.gif'
},
placement: {
'#logo':'logo',
'#tool_select':'select',
'#tool_fhpath':'pencil',
'#tool_line':'pen',
'#tool_rect,#tools_rect_show':'rect',
'#tool_ellipse,#tools_ellipse_show':'ellipse',
'#tool_path':'path',
'#tool_text,#layer_rename':'text',
'#tool_image':'image',
'#tool_zoom':'zoom',
'#tool_node_clone':'node_clone',
'#tool_node_delete':'node_delete',
'#tool_add_subpath':'add_subpath',
'#tool_openclose_path':'open_path',
'#tool_alignleft, #tool_posleft':'align_left',
'#tool_aligncenter, #tool_poscenter':'align_center',
'#tool_alignright, #tool_posright':'align_right',
'#tool_aligntop, #tool_postop':'align_top',
'#tool_alignmiddle, #tool_posmiddle':'align_middle',
'#tool_alignbottom, #tool_posbottom':'align_bottom',
'#cur_position':'align',
'#zoomLabel':'zoom'
},
resize: {
'#logo .svg_icon': 15,
'.flyout_arrow_horiz .svg_icon': 5,
'#fill_bg .svg_icon, #stroke_bg .svg_icon': svgedit.browser.isTouch() ? 24 : 24,
'.palette_item:first .svg_icon': svgedit.browser.isTouch() ? 30 : 16,
'#zoomLabel .svg_icon': 16,
'#zoom_dropdown .svg_icon': 7
},
callback: function(icons) {
$('.toolbar_button button > svg, .toolbar_button button > img').each(function() {
$(this).parent().prepend(this);
});
$('.tool_button, .tool_button_current').addClass("loaded")
var tleft = $('#tools_left');
if (tleft.length != 0) {
var min_height = tleft.offset().top + tleft.outerHeight();
}
// Look for any missing flyout icons from plugins
$('.tools_flyout').each(function() {
var shower = $('#' + this.id + '_show');
var sel = shower.attr('data-curopt');
// Check if there's an icon here
if(!shower.children('svg, img').length) {
var clone = $(sel).children().clone();
if(clone.length) {
clone[0].removeAttribute('style'); //Needed for Opera
shower.append(clone);
}
}
});
methodDraw.runCallbacks();
setTimeout(function() {
$('.flyout_arrow_horiz:empty').each(function() {
$(this).append($.getSvgIcon('arrow_right').width(5).height(5));
});
}, 1);
}
});
$('#rulers').on("dblclick", function(e){
$("#base_unit_container").css({
top: e.pageY-10,
left: e.pageX-50,
display: 'block'
})
})
$("#base_unit_container")
.on("mouseleave mouseenter", function(e){
t = setTimeout(function(){$("#base_unit_container").fadeOut(500)}, 200)
if(event.type == "mouseover") clearTimeout(t)
})
$("#base_unit")
.on("change", function(e) {
savePreferences();
});
Editor.canvas = svgCanvas = new $.SvgCanvas(document.getElementById("svgcanvas"), curConfig);
Editor.show_save_warning = false;
Editor.paintBox = {fill: null, stroke:null, canvas:null};
var palette = ["#444444", "#482816", "#422C10", "#3B2F0E", "#32320F",
"#293414", "#1F361B", "#153723", "#0C372C",
"#083734", "#0E353B", "#1A333F", "#273141",
"#332D40", "#3E2A3C", "#462735", "#4B252D",
"#4D2425", "#4C261D", "#666666", "#845335", "#7B572D",
"#6F5C2A", "#62612C", "#546433", "#46673D",
"#396849", "#306856", "#2D6862", "#33666C",
"#426373", "#535F75", "#645A73", "#74556D",
"#805064", "#884D58", "#8B4D4B", "#894F3F",
"#999999", "#C48157", "#B8874D", "#A98E49", "#97944B",
"#849854", "#729C62", "#619E73", "#559E84",
"#529D94", "#5B9BA2", "#6D97AB", "#8391AE",
"#9A8AAB", "#AF84A3", "#BF7E96", "#C97A86",
"#CE7975", "#CC7C65", "#BBBBBB", "#FFB27C", "#FABA6F",
"#E6C36A", "#CFCA6D", "#B8D078", "#A0D58A",
"#8CD79F", "#7DD8B5", "#7AD6CA", "#84D3DB",
"#9ACEE6", "#B6C7EA", "#D3BEE7", "#EDB6DC",
"#FFAFCC", "#FFAAB8", "#FFA9A2", "#FFAC8D",
"#DDDDDD", "#FFE7A2", "#FFF093", "#FFFA8D", "#FFFF91",
"#EEFF9F", "#D1FFB4", "#B9FFCE", "#A8FFE9",
"#A4FFFF", "#B1FFFF", "#CBFFFF", "#EDFFFF",
"#FFF5FF", "#FFEBFF", "#FFE2FF", "#FFDCEC",
"#FFDBD2", "#FFDFB8"
],
isMac = (navigator.platform.indexOf("Mac") >= 0),
isWebkit = (navigator.userAgent.indexOf("AppleWebKit") >= 0),
modKey = (isMac ? "meta+" : "ctrl+"), // ⌘
path = svgCanvas.pathActions,
undoMgr = svgCanvas.undoMgr,
Utils = svgedit.utilities,
default_img_url = curConfig.imgPath + "placeholder.svg",
workarea = $("#workarea"),
canv_menu = $("#cmenu_canvas"),
exportWindow = null,
tool_scale = 1,
ui_context = 'toolbars',
orig_source = '';
// This puts the correct shortcuts in the menus
if (!isMac) {
$('.shortcut').each(function(){
var text = $(this).text();
$(this).text(text.split("⌘").join("Ctrl+"))
});
}
// This sets up alternative dialog boxes. They mostly work the same way as
// their UI counterparts, expect instead of returning the result, a callback
// needs to be included that returns the result as its first parameter.
// In the future we may want to add additional types of dialog boxes, since
// they should be easy to handle this way.
(function() {
$('#dialog_container').draggable({cancel:'#dialog_content, #dialog_buttons *', containment: 'window'});
var box = $('#dialog_box'), btn_holder = $('#dialog_buttons');
var dbox = function(type, msg, callback, defText) {
$('#dialog_content').html('<p>'+msg.replace(/\n/g,'</p><p>')+'</p>')
.toggleClass('prompt',(type=='prompt'));
btn_holder.empty();
var ok = $('<input type="button" value="OK">').appendTo(btn_holder);
if(type != 'alert') {
$('<input type="button" value="Cancel">')
.appendTo(btn_holder)
.on("click touchstart", function() { box.hide();callback(false)});
}
if(type == 'prompt') {
var input = $('<input type="text">').prependTo(btn_holder);
input.val(defText || '');
input.bind('keydown', 'return', function() {ok.trigger("click touchstart");});
}
if(type == 'process') {
ok.hide();
}
box.show();
ok.on("click touchstart", function() {
box.hide();
var resp = (type == 'prompt')?input.val():true;
if(callback) callback(resp);
}).focus();
if(type == 'prompt') input.focus();
}
$.alert = function(msg, cb) { dbox('alert', msg, cb);};
$.confirm = function(msg, cb) { dbox('confirm', msg, cb);};
$.process_cancel = function(msg, cb) { dbox('process', msg, cb);};
$.prompt = function(msg, txt, cb) { dbox('prompt', msg, cb, txt);};
}());
var setSelectMode = function() {
var curr = $('.tool_button_current');
if(curr.length && curr[0].id !== 'tool_select') {
curr.removeClass('tool_button_current').addClass('tool_button');
$('#tool_select').addClass('tool_button_current').removeClass('tool_button');
}
svgCanvas.setMode('select');
};
var setEyedropperMode = function() {
var curr = $('.tool_button_current');
if(curr.length && curr[0].id !== 'tool_eyedropper') {
curr.removeClass('tool_button_current').addClass('tool_button');
$('#tool_eyedropper').addClass('tool_button_current').removeClass('tool_button');
}
svgCanvas.setMode('eyedropper');
}
var togglePathEditMode = function(editmode, elems) {
$('#tools_bottom_2,#tools_bottom_3').toggle(!editmode);
if(editmode) {
// Change select icon
$('.context_panel').hide();
$('#path_node_panel').show();
$('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
$('#tool_select').addClass('tool_button_current').removeClass('tool_button');
setIcon('#tool_select', 'select_node');
multiselected = false;
} else {
if (elems[0]) {
var selector = svgCanvas.selectorManager.requestSelector(elems[0])
selector.reset(elems[0]);
selector.selectorRect.setAttribute('display', 'inline');
}
setIcon('#tool_select', 'select');
}
}
// used to make the flyouts stay on the screen longer the very first time
var flyoutspeed = 1250;
var textBeingEntered = false;
var selectedElement = null;
var multiselected = false;
var editingsource = false;
var docprops = false;
var preferences = false;
var cur_context = '';
var saveHandler = function(window,svg) {
};
var exportHandler = function(window, data) {
var issues = data.issues;
if(!$('#export_canvas').length) {
$('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
}
var c = $('#export_canvas')[0];
c.width = svgCanvas.contentW;
c.height = svgCanvas.contentH;
canvg(c, data.svg, {renderCallback: function() {
var datauri = c.toDataURL('image/png');
if (!datauri) return false;
var filename = "Method Draw Image";
var type = 'image/png';
var file = svgedit.utilities.dataURItoBlob(datauri, type);
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}});
};
// called when we've selected a different element
var selectedChanged = function(window,elems) {
var mode = svgCanvas.getMode();
if(mode === "select") setSelectMode();
if (mode === "pathedit") return updateContextPanel();
// if elems[1] is present, then we have more than one element
selectedElement = (elems.length == 1 || elems[1] == null ? elems[0] : null);
elems = elems.filter(Boolean)
multiselected = (elems.length >= 2) ? elems : false;
if (svgCanvas.elementsAreSame(multiselected)) selectedElement = multiselected[0]
if (selectedElement != null) {
$('#multiselected_panel').hide()
updateToolbar();
if (multiselected.length) {//multiselected elements are the same
$('#tools_top').addClass('multiselected')
}
}
else if (multiselected.length) {
$('.context_panel').hide()
$('#tools_top').removeClass('multiselected')
$('#multiselected_panel').show()
}
else {
$('.context_panel').hide()
$('#canvas_panel').show()
$('#tools_top').removeClass('multiselected')
}
svgCanvas.runExtensions("selectedChanged", {
elems: elems,
selectedElement: selectedElement,
multiselected: multiselected
});
};
// Call when part of element is in process of changing, generally
// on mousemove actions like rotate, move, etc.
var elementTransition = function(window,elems) {
var mode = svgCanvas.getMode();
var elem = elems[0];
if(!elem) return;
multiselected = (elems.length >= 2 && elems[1] != null) ? elems : null;
// Only updating fields for single elements for now
if(!multiselected) {
switch ( mode ) {
case "rotate":
var ang = svgCanvas.getRotationAngle(elem);
$('#angle').val(Math.round(ang));
rotateCursor(ang);
$('#tool_reorient').toggleClass('disabled', ang == 0);
break;
// TODO: Update values that change on move/resize, etc
// case "select":
// case "resize":
// break;
}
}
svgCanvas.runExtensions("elementTransition", {
elems: elems
});
};
// called when any element has changed
var elementChanged = function(window,elems) {
var mode = svgCanvas.getMode();
if(mode === "select") {
setSelectMode();
}
for (var i = 0; i < elems.length; ++i) {
var elem = elems[i];
// if the element changed was the svg, then it could be a resolution change
if (elem && elem.tagName === "svg") {
//populateLayers();
updateCanvas();
}
// Update selectedElement if element is no longer part of the image.
// This occurs for the text elements in Firefox
else if(elem && selectedElement && selectedElement.parentNode == null) {
// || elem && elem.tagName == "path" && !multiselected) { // This was added in r1430, but not sure why
selectedElement = elem;
}
}
Editor.show_save_warning = true;
// we update the contextual panel with potentially new
// positional/sizing information (we DON'T want to update the
// toolbar here as that creates an infinite loop)
// also this updates the history buttons
// we tell it to skip focusing the text control if the
// text element was previously in focus
updateContextPanel();
// In the event a gradient was flipped:
if(selectedElement && mode === "select") {
Editor.paintBox.fill.update();
Editor.paintBox.stroke.update();
}
svgCanvas.runExtensions("elementChanged", {
elems: elems
});
};
var zoomChanged = function(window, bbox, autoCenter) {
var scrbar = 15,
res = svgCanvas.getResolution(),
w_area = workarea,
canvas_pos = $('#svgcanvas').position();
var z_info = svgCanvas.setBBoxZoom(bbox, w_area.width()-scrbar, w_area.height()-scrbar);
if(!z_info) return;
var zoomlevel = z_info.zoom,
bb = z_info.bbox;
if(zoomlevel < .001) {
changeZoom({value: .1});
return;
}
if (typeof animatedZoom != 'undefined') window.cancelAnimationFrame(animatedZoom)
// zoom duration 500ms
var start = Date.now();
var duration = 500;
var diff = (zoomlevel) - (res.zoom)
var zoom = $('#zoom')[0]
var current_zoom = res.zoom
var animateZoom = function(timestamp) {
var progress = Date.now() - start
var tick = progress / duration
tick = (Math.pow((tick-1), 3) +1);
svgCanvas.setZoom(current_zoom + (diff*tick));
updateCanvas();
if (tick < 1 && tick > -.90) {
window.animatedZoom = requestAnimationFrame(animateZoom)
}
else {
$("#zoom").val(parseInt(zoomlevel*100))
$("option", "#zoom_select").removeAttr("selected")
$("option[value="+ parseInt(zoomlevel*100) +"]", "#zoom_select").attr("selected", "selected")
}
}
animateZoom()
if(svgCanvas.getMode() == 'zoom' && bb.width) {
// Go to select if a zoom box was drawn
setSelectMode();
}
zoomDone();
}
$('#cur_context_panel').delegate('a', 'click', function() {
var link = $(this);
if(link.attr('data-root')) {
svgCanvas.leaveContext();
} else {
svgCanvas.setContext(link.text());
}
svgCanvas.clearSelection();
return false;
});
var contextChanged = function(win, context) {
var link_str = '';
if(context) {
var str = '';
link_str = '<a href="#" data-root="y">' + svgCanvas.getCurrentDrawing().getCurrentLayerName() + '</a>';
$(context).parentsUntil('#svgcontent > g').addBack().each(function() {
if(this.id) {
str += ' > ' + this.id;
if(this !== context) {
link_str += ' > <a href="#">' + this.id + '</a>';
} else {
link_str += ' > ' + this.id;
}
}
});
cur_context = str;
} else {
cur_context = null;
}
$('#cur_context_panel').toggle(!!context).html(link_str);
}
// Makes sure the current selected paint is available to work with
var prepPaints = function() {
Editor.paintBox.fill.prep();
Editor.paintBox.stroke.prep();
}
var flyout_funcs = {};
var setupFlyouts = function(holders) {
$.each(holders, function(hold_sel, btn_opts) {
var buttons = $(hold_sel).children();
var show_sel = hold_sel + '_show';
var shower = $(show_sel);
var def = false;
buttons.addClass('tool_button')
.unbind('click mousedown mouseup') // may not be necessary
.each(function(i) {
// Get this buttons options
var opts = btn_opts[i];
// Remember the function that goes with this ID
flyout_funcs[opts.sel] = opts.fn;
if(opts.isDefault) def = i;
// Clicking the icon in flyout should set this set's icon
var func = function(event) {
var options = opts;
//find the currently selected tool if comes from keystroke
if (event.type === "keydown") {
var flyoutIsSelected = $(options.parent + "_show").hasClass('tool_button_current');
var currentOperation = $(options.parent + "_show").attr("data-curopt");
$.each(holders[opts.parent], function(i, tool){
if (tool.sel == currentOperation) {
if(!event.shiftKey || !flyoutIsSelected) {
options = tool;
}
else {
options = holders[opts.parent][i+1] || holders[opts.parent][0];
}
}
});
}
if($(this).hasClass('disabled')) return false;
if (toolButtonClick(show_sel)) {
options.fn();
}
if(options.icon) {
var icon = $.getSvgIcon(options.icon, true);
} else {
var icon = $(options.sel).children().eq(0).clone();
}
icon[0].setAttribute('width',shower.width());
icon[0].setAttribute('height',shower.height());
shower.children(':not(.flyout_arrow_horiz)').remove();
shower.append(icon).attr('data-curopt', options.sel); // This sets the current mode
}
$(this).mouseup(func);
if(opts.key) {
$(document).bind('keydown', opts.key[0] + " shift+" + opts.key[0], func);
}
});
if(def) {
shower.attr('data-curopt', btn_opts[def].sel);
} else if(!shower.attr('data-curopt')) {
// Set first as default
shower.attr('data-curopt', btn_opts[0].sel);
}
var timer;
var pos = $(show_sel).position();
$(hold_sel).css({'left': pos.left+34, 'top': pos.top+77});
// Clicking the "show" icon should set the current mode
shower.mousedown(function(evt) {
$('#workarea').one("mousedown", function(){$('#tools_shapelib').hide()})
if ($('#tools_shapelib').is(":visible")) toolButtonClick(show_sel, false);
if(shower.hasClass('disabled')) return false;
var holder = $(hold_sel);
var l = pos.left+34;
var w = holder.width()*-1;
var time = holder.data('shown_popop')?200:0;
timer = setTimeout(function() {
// Show corresponding menu
if(!shower.data('isLibrary')) {
holder.css('left', w).show().animate({
left: l
},50);
} else {
holder.css('left', l).show();
}
holder.data('shown_popop',true);
},time);
evt.preventDefault();
}).mouseup(function(evt) {
clearTimeout(timer);
var opt = $(this).attr('data-curopt');
// Is library and popped up, so do nothing
if(shower.data('isLibrary') && $(show_sel.replace('_show','')).is(':visible')) {
toolButtonClick(show_sel, true);
return;
}
if (toolButtonClick(show_sel) && (opt in flyout_funcs)) {
flyout_funcs[opt]();
}
});
// $('#tools_rect').mouseleave(function(){$('#tools_rect').fadeOut();});
});
setFlyoutTitles();
}
var makeFlyoutHolder = function(id, child) {
var div = $('<div>',{
'class': 'tools_flyout',
id: id
}).appendTo('#svg_editor').append(child);
return div;
}
var setFlyoutPositions = function() {
$('.tools_flyout').each(function() {
var shower = $('#' + this.id + '_show');
var pos = shower.offset();
var w = shower.outerWidth();
$(this).css({left: (pos.left + w)*tool_scale, top: pos.top});
});
}
var setFlyoutTitles = function() {
$('.tools_flyout').each(function() {
var shower = $('#' + this.id + '_show');
if(shower.data('isLibrary')) return;
var tooltips = [];
$(this).children().each(function() {
tooltips.push(this.title);
});
shower[0].title = tooltips.join(' / ');
});
}
var resize_timer;
var extAdded = function(window, ext) {
var cb_called = false;
var resize_done = false;
var cb_ready = true; // Set to false to delay callback (e.g. wait for $.svgIcons)
function prepResize() {
if(resize_timer) {
clearTimeout(resize_timer);
resize_timer = null;
}
if(!resize_done) {
resize_timer = setTimeout(function() {
resize_done = true;
setIconSize(curPrefs.iconsize);
}, 50);
}
}
var runCallback = function() {
if(ext.callback && !cb_called && cb_ready) {
cb_called = true;
ext.callback();
}
}
var btn_selects = [];
if(ext.context_tools) {
$.each(ext.context_tools, function(i, tool) {
// Add select tool
var cont_id = tool.container_id?(' id="' + tool.container_id + '"'):"";
var panel = $('#' + tool.panel);
// create the panel if it doesn't exist
if(!panel.length)
panel = $('<div>', {id: tool.panel}).appendTo("#tools_top").hide();
// TODO: Allow support for other types, or adding to existing tool
switch (tool.type) {
case 'tool_button':
var html = '<div class="tool_button">' + tool.id + '</div>';
var div = $(html).appendTo(panel);
if (tool.events) {
$.each(tool.events, function(evt, func) {
$(div).bind(evt, func);
});
}
break;
case 'select':
var html = '<label' + cont_id + '>'
+ '<select id="' + tool.id + '">';
$.each(tool.options, function(val, text) {
var sel = (val == tool.defval) ? " selected":"";
html += '<option value="'+val+'"' + sel + '>' + text + '</option>';
});
html += "</select></label>";
// Creates the tool, hides & adds it, returns the select element
var sel = $(html).appendTo(panel).find('select');
$.each(tool.events, function(evt, func) {
$(sel).bind(evt, func);
});
break;
case 'button-select':
var html = '<div id="' + tool.id + '" class="dropdown toolset" title="' + tool.title + '">'
+ '<div id="cur_' + tool.id + '" class="icon_label"></div><button></button></div>';
var list = $('<ul id="' + tool.id + '_opts"></ul>').appendTo('#option_lists');
if(tool.colnum) {
list.addClass('optcols' + tool.colnum);
}
// Creates the tool, hides & adds it, returns the select element
var dropdown = $(html).appendTo(panel).children();
btn_selects.push({
elem: ('#' + tool.id),
list: ('#' + tool.id + '_opts'),
title: tool.title,
callback: tool.events.change,
cur: ('#cur_' + tool.id)
});
break;
case 'input':
var html = '<label' + cont_id + '>'
+ '<span id="' + tool.id + '_label">'
+ tool.label + ':</span>'
+ '<input id="' + tool.id + '" title="' + tool.title
+ '" size="' + (tool.size || "4") + '" value="' + (tool.defval || "") + '" type="text"/></label>'
// Creates the tool, hides & adds it, returns the select element
// Add to given tool.panel
var inp = $(html).appendTo(panel).find('input');
if(tool.spindata) {
inp.SpinButton(tool.spindata);
}
if(tool.events) {
$.each(tool.events, function(evt, func) {
inp.bind(evt, func);
});
}
break;
default:
break;
}
});
}
if(ext.buttons) {
var fallback_obj = {},
placement_obj = {},
svgicons = ext.svgicons;
var holders = {};
// Add buttons given by extension
$.each(ext.buttons, function(i, btn) {
var icon;
var id = btn.id;
var num = i;
// Give button a unique ID
while($('#'+id).length) {
id = btn.id + '_' + (++num);
}
if(!svgicons) {
icon = (btn.type == "menu") ? "" : $('<img src="' + btn.icon + '">');
} else {
fallback_obj[id] = btn.icon;
var svgicon = btn.svgicon ? btn.svgicon : btn.id;
if(btn.type == 'app_menu') {
placement_obj['#' + id + ' > div'] = svgicon;
} else {
placement_obj['#' + id] = svgicon;
}
}
var cls, parent;
// Set button up according to its type
switch ( btn.type ) {
case 'mode_flyout':
case 'mode':
cls = 'tool_button';
if(btn.cls) {
cls += " " + btn.cls;
}
parent = "#tools_left";
break;
case 'context':
cls = 'tool_button';
parent = "#" + btn.panel;
// create the panel if it doesn't exist
if(!$(parent).length)
$('<div>', {id: btn.panel}).appendTo("#tools_top");
break;
case 'menu':
cls = 'menu_item tool_button';
parent = "#" + (btn.after || btn.panel);
break;
case 'app_menu':
cls = '';
parent = btn.parent || '#main_menu ul';
// create the panel if it doesn't exist
if(!$(parent).length)
$('<div>', {id: btn.panel}).appendTo("#tools_top");
break;
}
var button = $((btn.list || btn.type == 'app_menu')?'<li/>':'<div/>')
.attr("id", id)
.attr("title", btn.title)
.addClass(cls);
if(!btn.includeWith && !btn.list) {
if("position" in btn) {
$(parent).children().eq(btn.position).before(button);
} else {
if (btn.type != "menu" || !btn.after) button.appendTo(parent);
else $(parent).after(button);
}
if(btn.type =='mode_flyout') {
// Add to flyout menu / make flyout menu
// var opts = btn.includeWith;
// // opts.button, default, position
var ref_btn = $(button);
var flyout_holder = ref_btn.parent();
// Create a flyout menu if there isn't one already
if(!ref_btn.parent().hasClass('tools_flyout')) {
// Create flyout placeholder
var tls_id = ref_btn[0].id.replace('tool_','tools_')
var show_btn = ref_btn.clone()
.attr('id',tls_id + '_show')
.append($('<div>',{'class':'flyout_arrow_horiz'}));
ref_btn.before(show_btn);
// Create a flyout div
flyout_holder = makeFlyoutHolder(tls_id, ref_btn);
flyout_holder.data('isLibrary', true);
show_btn.data('isLibrary', true);
}
// var ref_data = Actions.getButtonData(opts.button);
placement_obj['#' + tls_id + '_show'] = btn.id;
// TODO: Find way to set the current icon using the iconloader if this is not default
// Include data for extension button as well as ref button
var cur_h = holders['#'+flyout_holder[0].id] = [{
sel: '#'+id,
fn: btn.events.click,
icon: btn.id,
//key: btn.key,
isDefault: true
}, ref_data];
} else if(btn.type == 'app_menu' || btn.type == 'menu') {
button.append(btn.title);
}
} else if(btn.list) {
// Add button to list
button.addClass('push_button');
$('#' + btn.list + '_opts').append(button);
if(btn.isDefault) {
$('#cur_' + btn.list).append(button.children().clone());
var svgicon = btn.svgicon?btn.svgicon:btn.id;
placement_obj['#cur_' + btn.list] = svgicon;
}
} else if(btn.includeWith) {
// Add to flyout menu / make flyout menu
var opts = btn.includeWith;
// opts.button, default, position
var ref_btn = $(opts.button);
var flyout_holder = ref_btn.parent();
// Create a flyout menu if there isn't one already
if(!ref_btn.parent().hasClass('tools_flyout')) {
// Create flyout placeholder
var tls_id = ref_btn[0].id.replace('tool_','tools_')
var show_btn = ref_btn.clone()
.attr('id',tls_id + '_show')
.append($('<div>',{'class':'flyout_arrow_horiz'}));
ref_btn.before(show_btn);
// Create a flyout div
flyout_holder = makeFlyoutHolder(tls_id, ref_btn);
}
var ref_data = Actions.getButtonData(opts.button);
if(opts.isDefault) {
placement_obj['#' + tls_id + '_show'] = btn.id;
}
// TODO: Find way to set the current icon using the iconloader if this is not default
// Include data for extension button as well as ref button
var cur_h = holders['#'+flyout_holder[0].id] = [{
sel: '#'+id,
fn: btn.events.click,
icon: btn.id,
key: btn.key,
isDefault: btn.includeWith?btn.includeWith.isDefault:0
}, ref_data];
// {sel:'#tool_rect', fn: clickRect, evt: 'mouseup', key: 4, parent: '#tools_rect', icon: 'rect'}
var pos = ("position" in opts)?opts.position:'last';
var len = flyout_holder.children().length;
// Add at given position or end
if(!isNaN(pos) && pos >= 0 && pos < len) {
flyout_holder.children().eq(pos).before(button);
} else {
flyout_holder.append(button);
cur_h.reverse();
}
}
if(!svgicons) {
button.append(icon);
}
if(!btn.list) {
// Add given events to button
$.each(btn.events, function(name, func) {
if(name == "click") {
if(btn.type == 'mode') {
if(btn.includeWith) {
button.bind(name, func);
} else {
button.bind(name, function() {
if(toolButtonClick(button)) {
func();
}
});
}
if(btn.key) {
$(document).bind('keydown', btn.key, func);
if(btn.title) button.attr("title", btn.title + ' ['+btn.key+']');
}
} else {
button.bind(name, func);
}
} else {
button.bind(name, func);
}
});
}
setupFlyouts(holders);
});
$.each(btn_selects, function() {
addAltDropDown(this.elem, this.list, this.callback, {seticon: true});
});
if (svgicons)
cb_ready = false; // Delay callback
$.svgIcons(svgicons, {
w:27, h:27,
id_match: false,
no_img: (!isWebkit),
fallback: fallback_obj,
placement: placement_obj,
callback: function(icons) {
// Non-ideal hack to make the icon match the current size
if(curPrefs.iconsize && curPrefs.iconsize != 'm') {
prepResize();
}
cb_ready = true; // Ready for callback
runCallback();
}
});
}
runCallback();
};
var getPaint = function(color, opac, type) {
// update the editor's fill paint
var opts = null;
if (color.indexOf("url(#") === 0) {
var refElem = svgCanvas.getRefElem(color);
if(refElem) {
refElem = refElem.cloneNode(true);
} else {
refElem = $("#" + type + "_color defs *")[0];
}
opts = { alpha: opac };
opts[refElem.tagName] = refElem;
}
else if (color.indexOf("#") === 0) {
opts = {
alpha: opac,
solidColor: color.substr(1)
};
}
else {
opts = {
alpha: opac,
solidColor: 'none'
};
}
return new $.jGraduate.Paint(opts);
};
// set the canvas properties at init
var res = svgCanvas.getResolution();
if(curConfig.baseUnit !== "px") {
res.w = svgedit.units.convertUnit(res.w) + curConfig.baseUnit;
res.h = svgedit.units.convertUnit(res.h) + curConfig.baseUnit;
}
var createBackground = function(fill) {
svgCanvas.createLayer("background")
cur_shape = svgCanvas.addSvgElementFromJson({
"element": "rect",
"attr": {
"x": -1,
"y": -1,
"width": res.w+2,
"height": res.h+2,
"stroke": "none",
"id": "canvas_background",
"opacity": 1,
"fill": fill || "#fff",
"style": "pointer-events:none"
}
});
svgCanvas.setCurrentLayer("Layer 1")
svgCanvas.setCurrentLayerPosition("1")
}
// create a new layer background if it doesn't exist
if (!document.getElementById('canvas_background')) createBackground();
var fill = document.getElementById('canvas_background').getAttribute("fill");
// updates the toolbar (colors, opacity, etc) based on the selected element
// This function also updates the opacity and id elements that are in the context panel
var updateToolbar = function() {
if (selectedElement != null) {
switch ( selectedElement.tagName ) {
case 'use':
$(".context_panel").hide();
$("#use_panel").show();
break;
case 'image':
$(".context_panel").hide();
$("#image_panel").show();
break;
case 'foreignObject':
$(".context_panel").hide();
break;
case 'g':
case 'a':
// Look for common styles
var gWidth = null;
var childs = selectedElement.getElementsByTagName('*');
for(var i = 0, len = childs.length; i < len; i++) {
var swidth = childs[i].getAttribute("stroke-width");
if(i === 0) {
gWidth = swidth;
} else if(gWidth !== swidth) {
gWidth = null;
}
}
$('#stroke_width').val(gWidth === null ? "0" : gWidth);
updateContextPanel();
break;
default:
//removed because multiselect shouldnt set color
//Editor.paintBox.fill.update(false);
//Editor.paintBox.stroke.update(false);
$('#stroke_width').val(selectedElement.getAttribute("stroke-width") || 0);
var dash = selectedElement.getAttribute("stroke-dasharray") || "none"
$('option', '#stroke_style').removeAttr('selected');
$('#stroke_style option[value="'+ dash +'"]').attr("selected", "selected");
$('#stroke_style').trigger('change');
$.fn.dragInput.updateCursor($('#stroke_width')[0])
$.fn.dragInput.updateCursor($('#blur')[0])
}
}
// All elements including image and group have opacity
if(selectedElement != null) {
var opac_perc = ((selectedElement.getAttribute("opacity")||1.0)*100);
$('#group_opacity').val(opac_perc);
$.fn.dragInput.updateCursor($('#group_opacity')[0])
}
};
var setImageURL = Editor.setImageURL = function(url) {
if(!url) url = default_img_url;
svgCanvas.setImageURL(url);
$('#image_url').val(url);
}
var setInputWidth = function(elem) {
var w = Math.min(Math.max(12 + elem.value.length * 6, 50), 300);
$(elem).width(w);
}
// updates the context panel tools based on the selected element
var updateContextPanel = function(e) {
var elem = selectedElement;
// If element has just been deleted, consider it null
if(elem != null && !elem.parentNode) elem = null;
if (multiselected && multiselected[0] != null && !multiselected[0].parentNode) multiselected = false;
var currentLayerName = svgCanvas.getCurrentDrawing().getCurrentLayerName();
var currentMode = svgCanvas.getMode();
var unit = curConfig.baseUnit !== 'px' ? curConfig.baseUnit : null;
var is_node = currentMode == 'pathedit'; //elem ? (elem.id && elem.id.indexOf('pathpointgrip') == 0) : false;
if (is_node) {
$('.context_panel').hide();
$('#path_node_panel').show();
$('#stroke_panel').hide();
var point = path.getNodePoint();
$('#tool_add_subpath').removeClass('push_button_pressed').addClass('tool_button');
$('#tool_node_delete').toggleClass('disabled', !path.canDeleteNodes);
// Show open/close button based on selected point
setIcon('#tool_openclose_path', path.closed_subpath ? 'open_path' : 'close_path');
if(point) {
var seg_type = $('#seg_type');
if(unit) {
point.x = svgedit.units.convertUnit(point.x);
point.y = svgedit.units.convertUnit(point.y);
}
$('#path_node_x').val(Math.round(point.x));
$('#path_node_y').val(Math.round(point.y));
if(point.type) {
seg_type.val(point.type).removeAttr('disabled');
$("#seg_type_label").html(point.type == 4 ? "Straight" : "Curve")
} else {
seg_type.val(4).attr('disabled','disabled');
}
}
$("#tools_top").removeClass("multiselected")
$("#stroke_panel").hide();
$("#canvas_panel").hide();
return;
}
var menu_items = $('#cmenu_canvas li');
$('.context_panel').hide();
$('.menu_item', '#edit_menu').addClass('disabled');
$('.menu_item', '#object_menu').addClass('disabled');
//hack to show the proper multialign box
if (multiselected) {
multiselected = multiselected.filter(Boolean);
elem = (svgCanvas.elementsAreSame(multiselected)) ? multiselected[0] : null
if (elem) $("#tools_top").addClass("multiselected")
}
if (!elem && !multiselected) {
$("#tools_top").removeClass("multiselected")
$("#stroke_panel").hide();
$("#canvas_panel").show();
}
if (elem != null) {
$("#stroke_panel").show();
var elname = elem.nodeName;
var angle = svgCanvas.getRotationAngle(elem);
$('#angle').val(Math.round(angle));
var blurval = svgCanvas.getBlur(elem);
$('#blur').val(blurval);
if(!is_node && currentMode != 'pathedit') {
$('#selected_panel').show();
$('.action_selected').removeClass('disabled');
// Elements in this array already have coord fields
var x, y
if(['g', 'polyline', 'path'].indexOf(elname) >= 0) {
var bb = svgCanvas.getStrokedBBox([elem]);
if(bb) {
x = bb.x;
y = bb.y;
}
}
if(unit) {
x = svgedit.units.convertUnit(x);
y = svgedit.units.convertUnit(y);
}
$("#" + elname +"_x").val(Math.round(x))
$("#" + elname +"_y").val(Math.round(y))
if (elname === "polyline") {
//we're acting as if polylines were paths
$("#path_x").val(Math.round(x))
$("#path_y").val(Math.round(y))
}
// Elements in this array cannot be converted to a path
var no_path = ['image', 'text', 'path', 'g', 'use'].indexOf(elname) == -1;
if (no_path) $('.action_path_convert_selected').removeClass('disabled');
if (elname === "path") $('.action_path_selected').removeClass('disabled');
}
var link_href = null;
if (el_name === 'a') {
link_href = svgCanvas.getHref(elem);
$('#g_panel').show();
}
if(elem.parentNode.tagName === 'a') {
if(!$(elem).siblings().length) {
$('#a_panel').show();
link_href = svgCanvas.getHref(elem.parentNode);
}
}
// Hide/show the make_link buttons
$('#tool_make_link, #tool_make_link').toggle(!link_href);
if(link_href) {
$('#link_url').val(link_href);
}
// update contextual tools here
var panels = {
g: [],
a: [],
rect: ['rx','width','height', 'x', 'y'],
image: ['width','height', 'x', 'y'],
circle: ['cx','cy','r'],
ellipse: ['cx','cy','rx','ry'],
line: ['x1','y1','x2','y2'],
text: ['x', 'y'],
'use': [],
path : []
};
var el_name = elem.tagName;
if($(elem).data('gsvg')) {
$('#g_panel').show();
}
if (el_name == "path" || el_name == "polyline") {
$('#path_panel').show();
}
if(panels[el_name]) {
var cur_panel = panels[el_name];
$('#' + el_name + '_panel').show();
// corner radius has to live in a different panel
// because otherwise it changes the position of the
// of the elements
if(el_name == "rect") $("#cornerRadiusLabel").show()
else $("#cornerRadiusLabel").hide()
$.each(cur_panel, function(i, item) {
var attrVal = elem.getAttribute(item);
if(curConfig.baseUnit !== 'px' && elem[item]) {
var bv = elem[item].baseVal.value;
attrVal = svgedit.units.convertUnit(bv);
}
//update the draginput cursors
var name_item = document.getElementById(el_name + '_' + item);
name_item.value = Math.round(attrVal) || 0;
if (name_item.getAttribute("data-cursor") === "true") {
$.fn.dragInput.updateCursor(name_item );
}
});
if(el_name == 'text') {
var font_family = elem.getAttribute("font-family");
var select = document.getElementById("font_family_dropdown");
select.selectedIndex = 3
$('#text_panel').css("display", "inline");
$('#tool_italic').toggleClass('active', svgCanvas.getItalic())
$('#tool_bold').toggleClass('active', svgCanvas.getBold())
$('#font_family').val(font_family);
$('#font_size').val(elem.getAttribute("font-size"));
$('#text').val(elem.textContent);
$('#preview_font').text(font_family.split(",")[0].replace(/'/g, "")).css('font-family', font_family);
if (svgCanvas.addedNew) {
// Timeout needed for IE9
setTimeout(function() {
$('#text').focus().select();
},100);
}
} // text
else if(el_name == 'image') {
setImageURL(svgCanvas.getHref(elem));
} // image
else if(el_name === 'g' || el_name === 'use') {
$('#container_panel').show();
$('.action_group_selected').removeClass('disabled');
var title = svgCanvas.getTitle();
}
}
menu_items[(el_name === 'g' ? 'en':'dis') + 'ableContextMenuItems']('#ungroup');
menu_items[((el_name === 'g' || !multiselected) ? 'dis':'en') + 'ableContextMenuItems']('#group');
}
if (multiselected) {
$('#multiselected_panel').show();
$('.action_multi_selected').removeClass('disabled');
menu_items
.enableContextMenuItems('#group')
.disableContextMenuItems('#ungroup');
}
if (!elem) {
menu_items.disableContextMenuItems('#delete,#cut,#copy,#group,#ungroup,#move_front,#move_up,#move_down,#move_back');
}
// update history buttons
if (undoMgr.getUndoStackSize() > 0) {
$('#tool_undo').removeClass( 'disabled');
}
else {
$('#tool_undo').addClass( 'disabled');
}
if (undoMgr.getRedoStackSize() > 0) {
$('#tool_redo').removeClass( 'disabled');
}
else {
$('#tool_redo').addClass( 'disabled');
}
svgCanvas.addedNew = false;
if ( (elem && !is_node) || multiselected) {
// update the selected elements' layer
$('#selLayerNames').removeAttr('disabled').val(currentLayerName);
// Enable regular menu options
canv_menu.enableContextMenuItems('#delete,#cut,#copy,#move_front,#move_up,#move_down,#move_back');
}
};
$('#text').on("focus", function(e){ textBeingEntered = true; } );
$('#text').on("blur", function(){ textBeingEntered = false; } );
// bind the selected event to our function that handles updates to the UI
svgCanvas.bind("selected", selectedChanged);
svgCanvas.bind("transition", elementTransition);
svgCanvas.bind("changed", elementChanged);
svgCanvas.bind("exported", exportHandler);
svgCanvas.bind("zoomed", zoomChanged);
svgCanvas.bind("contextset", contextChanged);
svgCanvas.bind("extension_added", extAdded);
svgCanvas.textActions.setInputElem($("#text")[0]);
var str = '<div class="palette_item transparent" data-rgb="none"></div>\
<div class="palette_item black" data-rgb="#000000"></div>\
<div class="palette_item white" data-rgb="#ffffff"></div>'
palette.forEach(function(item, i){
str += '<div class="palette_item" style="background-color: ' + item + ';" data-rgb="' + item + '"></div>';
});
$('#palette').append(str);
var changeFontSize = function(ctl) {
svgCanvas.setFontSize(ctl.value);
}
var changeStrokeWidth = function(ctl) {
var val = ctl.value;
if(val == 0 && selectedElement && ['line', 'polyline'].indexOf(selectedElement.nodeName) >= 0) {
val = ctl.value = 1;
}
svgCanvas.setStrokeWidth(val);
}
//cache
var $indicator = $('#tool_angle_indicator')
var $reorient = $('#tool_reorient')
rotateCursor = function(angle){
var rotate_string = 'rotate('+ angle + 'deg)'
$indicator.css({
'-webkit-transform': rotate_string,
'-moz-transform': rotate_string,
'-o-transform': rotate_string,
'-ms-transform': rotate_string,
'transform': rotate_string
});
}
var changeRotationAngle = function(ctl) {
var preventUndo = true;
svgCanvas.setRotationAngle(ctl.value, preventUndo);
rotateCursor(ctl.value)
$('#tool_reorient').toggleClass('disabled', ctl.value == 0);
}
var changeZoom = function(ctl) {
var zoomlevel = ctl.value / 100;
if(zoomlevel < .001) {
ctl.value = .1;
return;
}
var zoom = svgCanvas.getZoom();
var w_area = workarea;
zoomChanged(window, {
width: 0,
height: 0,
// center pt of scroll position
x: (w_area[0].scrollLeft + w_area.width()/2)/zoom,
y: (w_area[0].scrollTop + w_area.height()/2)/zoom,
zoom: zoomlevel
}, true);
}
var changeBlur = function(ctl, completed) {
val = ctl.value;
$('#blur').val(val);
if (completed) {
svgCanvas.setBlur(val, true);
}
else {
svgCanvas.setBlurNoUndo(val);
}
}
var operaRepaint = function() {
// Repaints canvas in Opera. Needed for stroke-dasharray change as well as fill change
if(!window.opera) return;
$('<p/>').hide().appendTo('body').remove();
}
$('#stroke_style').change(function(){
svgCanvas.setStrokeAttr('stroke-dasharray', $(this).val());
$("#stroke_style_label").html(this.options[this.selectedIndex].text)
operaRepaint();
});
$('#seg_type').change(function() {
svgCanvas.setSegType($(this).val());
$("#seg_type_label").html(this.options[this.selectedIndex].text)
});
// Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
$('select').change(function(){$(this).blur();});
$('#font_family').change(function() {
svgCanvas.setFontFamily(this.value);
});
$('#text').keyup(function(){
svgCanvas.setTextContent(this.value);
});
changeAttribute = function(el, completed) {
var attr = el.getAttribute("data-attr");
var multiplier = el.getAttribute("data-multiplier") || 1;
multiplier = parseFloat(multiplier);
var val = el.value * multiplier;
var valid = svgedit.units.isValidUnit(attr, val, selectedElement);
if(!valid) {
$.alert("Invalid value given");
el.value = selectedElement.getAttribute(attr);
return false;
}
//if (!noUndo) svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.changeSelectedAttributeNoUndo(attr, val);
};
picking = false;
$(document).on("mouseup", function(){picking = false;})
$('#palette').on("mousemove mousedown touchstart touchmove", ".palette_item", function(evt){
evt.preventDefault();
if (evt.type == "mousedown") picking = true;
if (picking) {
var isStroke = $('#tool_stroke').hasClass('active');
var picker = isStroke ? "stroke" : "fill";
var color = $(this).attr('data-rgb');
var paint = null;
var noUndo = true;
if (evt.type == "mousedown") noUndo = false
// Webkit-based browsers returned 'initial' here for no stroke
if (color === 'transparent' || color === 'initial' || color === '#none') {
color = 'none';
paint = new $.jGraduate.Paint();
}
else {
paint = new $.jGraduate.Paint({alpha: 100, solidColor: color.substr(1)});
}
Editor.paintBox[picker].setPaint(paint);
if (isStroke) {
svgCanvas.setColor('stroke', color, noUndo);
if (color != 'none' && svgCanvas.getStrokeOpacity() != 1) {
svgCanvas.setPaintOpacity('stroke', 1.0);
}
} else {
svgCanvas.setColor('fill', color, noUndo);
if (color != 'none' && svgCanvas.getFillOpacity() != 1) {
svgCanvas.setPaintOpacity('fill', 1.0);
}
}
}
}).bind('contextmenu', function(e) {e.preventDefault()});
$("#toggle_stroke_tools").toggle(function() {
$(".stroke_tool").css('display','table-cell');
$(this).addClass('expanded');
resetScrollPos();
}, function() {
$(".stroke_tool").css('display','none');
$(this).removeClass('expanded');
resetScrollPos();
});
// This is a common function used when a tool has been clicked (chosen)
// It does several common things:
// - removes the tool_button_current class from whatever tool currently has it
// - hides any flyouts
// - adds the tool_button_current class to the button passed in
var toolButtonClick = function(button, noHiding) {
if ($(button).hasClass('disabled')) return false;
if($(button).parent().hasClass('tools_flyout')) return true;
var fadeFlyouts = fadeFlyouts || 'normal';
if(!noHiding) {
$('.tools_flyout').fadeOut(fadeFlyouts);
}
$('#styleoverrides').text('');
$('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
$(button).addClass('tool_button_current').removeClass('tool_button');
return true;
};
(function() {
var last_x = null, last_y = null, w_area = workarea[0],
panning = false, keypan = false;
var move_pan = function(evt) {
if(panning === false) return;
w_area.scrollLeft -= (evt.clientX - last_x);
w_area.scrollTop -= (evt.clientY - last_y);
last_x = evt.clientX;
last_y = evt.clientY;
if(evt.type === 'mouseup' || evt.type === 'touchend') panning = false;
return false;
}
var start_pan = function(evt) {
if(evt.button === 1 || keypan === true || (evt.originalEvent.touches && evt.originalEvent.touches.length >= 2)) {
panning = true;
last_x = evt.clientX;
last_y = evt.clientY;
return false;
}
}
$('#svgcanvas')
.on('mousemove mouseup touchend', move_pan)
.on("mousedown touchmove", start_pan)
$(window).mouseup(function() {
panning = false;
});
$(document).bind('keydown', 'space', function(evt) {
evt.preventDefault();
svgCanvas.spaceKey = keypan = true;
}).bind('keyup', 'space', function(evt) {
evt.preventDefault();
svgCanvas.spaceKey = keypan = false;
}).bind('keydown', 'alt', function(evt) {
if(svgCanvas.getMode() === 'zoom') {
workarea.addClass('out');
}
}).bind('keyup', 'alt', function(evt) {
if(svgCanvas.getMode() === 'zoom') {
workarea.removeClass('out');
}
})
}());
function setStrokeOpt(opt, changeElem) {
var id = opt.id;
var bits = id.split('_');
var pre = bits[0];
var val = bits[1];
if(changeElem) {
svgCanvas.setStrokeAttr('stroke-' + pre, val);
}
operaRepaint();
setIcon('#cur_' + pre , id, 20);
$(opt).addClass('current').siblings().removeClass('current');
}
//menu handling
var menus = $('.menu');
var blinker = function(e) {
e.target.style.background = "#fff";
setTimeout(function(){e.target.style.background = "#ddd";}, 50);
setTimeout(function(){e.target.style.background = "#fff";}, 150);
setTimeout(function(){e.target.style.background = "#ddd";}, 200);
setTimeout(function(){e.target.style.background = "";}, 200);
setTimeout(function(){$('#menu_bar').removeClass('active')}, 220);
return false;
}
var closer = function(e){
if (e.target.nodeName && e.target.nodeName.toLowerCase() === "input") return false;
if (!$(e.target).hasClass("menu_title") && !$(e.target).parent().hasClass("menu_title")) {
if(!$(e.target).hasClass("disabled") && $(e.target).hasClass("menu_item")) blinker(e)
else $('#menu_bar').removeClass('active')
}
}
$('.menu_item').on('mousedown touchstart', function(e){blinker(e)});
$("svg, body").on('mousedown touchstart', function(e){closer(e)});
var accumulatedDelta = 0
$('#workarea').on('mousewheel', function(e, delta, deltaX, deltaY){
if (e.altKey || e.ctrlKey) {
e.preventDefault();
zoom = parseInt($("#zoom").val())
$("#zoom").val(parseInt(zoom + deltaY*(e.altKey ? 10 : 5))).change()
}
});
$('.menu_title')
.on('mousedown', function() {
$("#tools_shapelib").hide()
$("#menu_bar").toggleClass('active');
menus.removeClass('open');
$(this).parent().addClass('open');
})
.on('mouseover', function() {
menus.removeClass('open');
$(this).parent().addClass('open');
});
// Made public for UI customization.
// TODO: Group UI functions into a public methodDraw.ui interface.
Editor.addDropDown = function(elem, callback, dropUp) {
if ($(elem).length == 0) return; // Quit if called on non-existant element
var button = $(elem).find('button');
var list = $(elem).find('ul').attr('id', $(elem)[0].id + '-list');
if(!dropUp) {
// Move list to place where it can overflow container
$('#option_lists').append(list);
}
var on_button = false;
if(dropUp) {
$(elem).addClass('dropup');
}
list.find('li').bind('mouseup', callback);
$(window).mouseup(function(evt) {
if(!on_button) {
button.removeClass('down');
list.hide();
}
on_button = false;
});
button.bind('mousedown',function() {
if (!button.hasClass('down')) {
button.addClass('down');
if(!dropUp) {
var pos = $(elem).offset();
// position slider
list.css({
top: pos.top,
left: pos.left - 110
});
}
list.show();
on_button = true;
} else {
button.removeClass('down');
list.hide();
}
}).hover(function() {
on_button = true;
}).mouseout(function() {
on_button = false;
});
}
// TODO: Combine this with addDropDown or find other way to optimize
var addAltDropDown = function(elem, list, callback, opts) {
var button = $(elem);
var list = $(list);
var on_button = false;
var dropUp = opts.dropUp;
if(dropUp) {
$(elem).addClass('dropup');
}
list.find('li').bind('mouseup', function() {
if(opts.seticon) {
setIcon('#cur_' + button[0].id , $(this).children());
$(this).addClass('current').siblings().removeClass('current');
}
callback.apply(this, arguments);
});
$(window).mouseup(function(evt) {
if(!on_button) {
button.removeClass('down');
list.hide();
list.css({top:0, left:0});
}
on_button = false;
});
var height = list.height();
$(elem).bind('mousedown',function() {
var off = $(elem).offset();
if(dropUp) {
off.top -= list.height();
off.left += 8;
} else {
off.top += $(elem).height();
}
$(list).offset(off);
if (!button.hasClass('down')) {
button.addClass('down');
list.show();
on_button = true;
return false;
} else {
button.removeClass('down');
// CSS position must be reset for Webkit
list.hide();
list.css({top:0, left:0});
}
}).hover(function() {
on_button = true;
}).mouseout(function() {
on_button = false;
});
if(opts.multiclick) {
list.mousedown(function() {
on_button = true;
});
}
}
$('#font_family_dropdown').change(function() {
var fam = this.options[this.selectedIndex].value
var fam_display = this.options[this.selectedIndex].text
$('#preview_font').html(fam_display).css("font-family", fam);
$('#font_family').val(fam).change();
});
$('div', '#position_opts').each(function(){
this.addEventListener("mouseup", function(){
var letter = this.id.replace('tool_pos','').charAt(0);
svgCanvas.alignSelectedElements(letter, 'page');
})
});
/*
When a flyout icon is selected
(if flyout) {
- Change the icon
- Make pressing the button run its stuff
}
- Run its stuff
When its shortcut key is pressed
- If not current in list, do as above
, else:
- Just run its stuff
*/
// Unfocus text input when workarea is mousedowned.
(function() {
var inp;
var unfocus = function() {
$(inp).blur();
}
$('#svg_editor').find('button, select, input:not(#text)').focus(function() {
inp = this;
ui_context = 'toolbars';
workarea.mousedown(unfocus);
}).blur(function() {
ui_context = 'canvas';
workarea.unbind('mousedown', unfocus);
// Go back to selecting text if in textedit mode
if(svgCanvas.getMode() == 'textedit') {
$('#text').focus();
}
});
}());
var clickSelect = function() {
if (toolButtonClick('#tool_select')) {
svgCanvas.setMode('select');
}
};
var clickFHPath = function() {
if (toolButtonClick('#tool_fhpath')) {
svgCanvas.setMode('fhpath');
}
};
var clickLine = function() {
if (toolButtonClick('#tool_line')) {
svgCanvas.setMode('line');
}
};
var clickSquare = function(){
if (toolButtonClick('#tool_square')) {
svgCanvas.setMode('square');
}
};
var clickRect = function(){
if (toolButtonClick('#tool_rect')) {
svgCanvas.setMode('rect');
}
};
var clickFHRect = function(){
if (toolButtonClick('#tool_fhrect')) {
svgCanvas.setMode('fhrect');
}
};
var clickCircle = function(){
if (toolButtonClick('#tool_circle')) {
svgCanvas.setMode('circle');
}
};
var clickEllipse = function(){
if (toolButtonClick('#tool_ellipse')) {
svgCanvas.setMode('ellipse');
}
};
var clickFHEllipse = function(){
if (toolButtonClick('#tool_fhellipse')) {
svgCanvas.setMode('fhellipse');
}
};
var clickImage = function(){
if (toolButtonClick('#tool_image')) {
svgCanvas.setMode('image');
}
};
var clickZoom = function(){
if (toolButtonClick('#tool_zoom')) {
svgCanvas.setMode('zoom');
}
};
var dblclickZoom = function(){
if (toolButtonClick('#tool_zoom')) {
zoomImage();
setSelectMode();
}
};
var clickText = function(){
if (toolButtonClick('#tool_text')) {
svgCanvas.setMode('text');
}
};
var clickPath = function(){
if (toolButtonClick('#tool_path')) {
svgCanvas.setMode('path');
}
};
// Delete is a contextual tool that only appears in the ribbon if
// an element has been selected
var deleteSelected = function() {
if (selectedElement != null || multiselected) {
svgCanvas.deleteSelectedElements();
}
if (path.getNodePoint()) {
path.deletePathNode();
}
};
var cutSelected = function() {
if (selectedElement != null || multiselected) {
flash($('#edit_menu'));
svgCanvas.cutSelectedElements();
}
};
var copySelected = function() {
if (selectedElement != null || multiselected) {
flash($('#edit_menu'));
svgCanvas.copySelectedElements();
}
};
var pasteSelected = function() {
flash($('#edit_menu'));
var zoom = svgCanvas.getZoom();
var x = (workarea[0].scrollLeft + workarea.width()/2)/zoom - svgCanvas.contentW;
var y = (workarea[0].scrollTop + workarea.height()/2)/zoom - svgCanvas.contentH;
svgCanvas.pasteElements('point', x, y);
}
var moveToTopSelected = function() {
if (selectedElement != null) {
flash($('#object_menu'));
svgCanvas.moveToTopSelectedElement();
}
};
var moveToBottomSelected = function() {
if (selectedElement != null) {
flash($('#object_menu'));
svgCanvas.moveToBottomSelectedElement();
}
};
var moveUpSelected = function() {
if (selectedElement != null) {
flash($('#object_menu'));
svgCanvas.moveUpDownSelected("Up");
}
};
var moveDownSelected = function() {
if (selectedElement != null) {
flash($('#object_menu'));
svgCanvas.moveUpDownSelected("Down");
}
};
var moveUpDownSelected = function(dir) {
if (selectedElement != null) {
flash($('#object_menu'));
svgCanvas.moveUpDownSelected(dir);
}
};
var convertToPath = function() {
if (selectedElement != null) {
svgCanvas.convertToPath();
var elems = svgCanvas.getSelectedElems()
svgCanvas.selectorManager.requestSelector(elems[0]).reset(elems[0])
svgCanvas.selectorManager.requestSelector(elems[0]).selectorRect.setAttribute("display", "none");
svgCanvas.setMode("pathedit")
path.toEditMode(elems[0]);
svgCanvas.clearSelection();
updateContextPanel();
}
}
var reorientPath = function() {
if (selectedElement != null) {
path.reorient();
}
}
var makeHyperlink = function() {
if (selectedElement != null || multiselected) {
$.prompt("Enter the new hyperlink URL", "http://", function(url) {
if(url) svgCanvas.makeHyperlink(url);
});
}
}
var moveSelected = function(dx,dy) {
if (selectedElement != null || multiselected) {
if(curConfig.gridSnapping) {
// Use grid snap value regardless of zoom level
var multi = svgCanvas.getZoom() * curConfig.snappingStep;
dx *= multi;
dy *= multi;
}
$('input').blur()
svgCanvas.moveSelectedElements(dx,dy);
}
};
var linkControlPoints = function() {
// var linked = document.getElementById('tool_node_link').checked;
// path.linkControlPoints(linked);
}
var clonePathNode = function() {
if (path.getNodePoint()) {
path.clonePathNode();
}
};
var deletePathNode = function() {
if (path.getNodePoint()) {
path.deletePathNode();
}
};
var addSubPath = function() {
var button = $('#tool_add_subpath');
var sp = !button.hasClass('push_button_pressed');
if (sp) {
button.addClass('push_button_pressed').removeClass('tool_button');
} else {
button.removeClass('push_button_pressed').addClass('tool_button');
}
path.addSubPath(sp);
};
var opencloseSubPath = function() {
path.opencloseSubPath();
}
var selectNext = function() {
svgCanvas.cycleElement(1);
};
var selectPrev = function() {
svgCanvas.cycleElement(0);
};
var rotateSelected = function(cw,step) {
if (selectedElement == null || multiselected) return;
if(!cw) step *= -1;
var new_angle = $('#angle').val()*1 + step;
svgCanvas.setRotationAngle(new_angle);
updateContextPanel();
};
var clickClear = function(){
var dims = curConfig.dimensions;
$.confirm("<strong>Do you want to clear the drawing?</strong>\nThis will also erase your undo history", function(ok) {
if(!ok) return;
setSelectMode();
svgCanvas.deleteSelectedElements();
svgCanvas.clear();
svgCanvas.setResolution(dims[0], dims[1]);
updateCanvas(true);
createBackground();
zoomImage();
updateContextPanel();
prepPaints();
svgCanvas.runExtensions('onNewDocument');
});
};
var clickBold = function(){
svgCanvas.setBold( !svgCanvas.getBold() );
updateContextPanel();
};
var clickItalic = function(){
svgCanvas.setItalic( !svgCanvas.getItalic() );
updateContextPanel();
};
var clickExport = function() {
if(window.canvg) {
svgCanvas.rasterExport();
} else {
$.getScript('/js/lib/rgbcolor.js', function() {
$.getScript('/js/lib/canvg.js', function() {
svgCanvas.rasterExport();
});
});
}
}
// by default, svgCanvas.open() is a no-op.
// it is up to an extension mechanism (opera widget, etc)
// to call setCustomHandlers() which will make it do something
var clickOpen = function(){
svgCanvas.open();
};
var clickImport = function(){
};
var flash = function($menu){
var menu_title = $menu.prev();
menu_title.css({
"background": "white",
"color": "black"
});
setTimeout(function(){menu_title.removeAttr("style")}, 200);
}
var clickUndo = function(){
if (undoMgr.getUndoStackSize() > 0) {
flash($('#edit_menu'));
undoMgr.undo();
}
};
var clickRedo = function(){
if (undoMgr.getRedoStackSize() > 0) {
flash($('#edit_menu'));
undoMgr.redo();
}
};
var clickGroup = function(){
// group
if (multiselected) {
flash($('#object_menu'));
svgCanvas.groupSelectedElements();
}
// ungroup
else if(selectedElement){
flash($('#object_menu'));
svgCanvas.ungroupSelectedElement();
}
};
var clickClone = function(){
flash($('#edit_menu'));
svgCanvas.cloneSelectedElements(20,20);
};
var clickAlign = function() {
var letter = this.id.replace('tool_align','').charAt(0);
svgCanvas.alignSelectedElements(letter, $('#align_relative_to').val());
};
var clickSwitch = function() {
var stroke_rect = document.querySelector('#tool_stroke rect');
$("#tool_stroke").toggleClass('active')
$("#tool_fill").toggleClass('active')
var fill_rect = document.querySelector('#tool_fill rect');
var fill_color = fill_rect.getAttribute("fill");
var stroke_color = stroke_rect.getAttribute("fill");
var stroke_opacity = parseFloat(stroke_rect.getAttribute("stroke-opacity"));
if (isNaN(stroke_opacity)) {stroke_opacity = 100;}
var fill_opacity = parseFloat(fill_rect.getAttribute("fill-opacity"));
if (isNaN(fill_opacity)) {fill_opacity = 100;}
var stroke = getPaint(stroke_color, stroke_opacity, "stroke");
var fill = getPaint(fill_color, fill_opacity, "fill");
Editor.paintBox.fill.setPaint(stroke, true);
Editor.paintBox.stroke.setPaint(fill, true);
};
var zoomImage = function(multiplier) {
var res = svgCanvas.getResolution();
multiplier = multiplier?res.zoom * multiplier:1;
// setResolution(res.w * multiplier, res.h * multiplier, true);
$('#zoom').val(multiplier * 100);
svgCanvas.setZoom(multiplier);
zoomDone();
updateCanvas(true);
};
var zoomDone = function() {
// updateBgImage();
updateWireFrame();
//updateCanvas(); // necessary?
}
var clickWireframe = function() {
flash($('#view_menu'));
var wf = !$('#tool_wireframe').hasClass('push_button_pressed');
if (wf)
$('#tool_wireframe').addClass('push_button_pressed');
else
$('#tool_wireframe').removeClass('push_button_pressed');
workarea.toggleClass('wireframe');
if(supportsNonSS) return;
var wf_rules = $('#wireframe_rules');
if(!wf_rules.length) {
wf_rules = $('<style id="wireframe_rules"><\/style>').appendTo('head');
} else {
wf_rules.empty();
}
updateWireFrame();
}
var clickSnapGrid = function() {
flash($('#view_menu'));
var sg = !$('#tool_snap').hasClass('push_button_pressed');
if (sg)
$('#tool_snap').addClass('push_button_pressed');
else
$('#tool_snap').removeClass('push_button_pressed');
curConfig.gridSnapping = sg;
}
var minimizeModal = function() {
if (window.self != window.top) { //we're in an iframe
top.exit_fullscreen();
}
}
var clickRulers = function() {
flash($('#view_menu'));
var rulers = !$('#tool_rulers').hasClass('push_button_pressed');
if (rulers) {
$('#tool_rulers').addClass('push_button_pressed');
$('#show_rulers').attr("checked", true);
curConfig.showRulers = true;
}
else {
$('#tool_rulers').removeClass('push_button_pressed');
$('#show_rulers').attr("checked", false);
curConfig.showRulers = false;
}
$('#rulers').toggle(!!curConfig.showRulers)
}
var updateWireFrame = function() {
// Test support
if(supportsNonSS) return;
var rule = "#workarea.wireframe #svgcontent * { stroke-width: " + 1/svgCanvas.getZoom() + "px; }";
$('#wireframe_rules').text(workarea.hasClass('wireframe') ? rule : "");
}
var showSourceEditor = function(e, forSaving){
if (editingsource) return;
flash($('#view_menu'));
editingsource = true;
$('#save_output_btns').toggle(!!forSaving);
$('#tool_source_back').toggle(!forSaving);
var str = orig_source = svgCanvas.getSvgString();
$('#svg_source_textarea').val(str);
$('#svg_source_editor').fadeIn();
$('#svg_source_textarea').focus().select();
};
var clickSave = function(){
flash($('#file_menu'));
// In the future, more options can be provided here
var saveOpts = {
'images': curPrefs.img_save,
'round_digits': 6
}
svgCanvas.save(saveOpts);
};
var saveSourceEditor = function(){
if (!editingsource) return;
var saveChanges = function() {
svgCanvas.clearSelection();
hideSourceEditor();
zoomImage();
prepPaints();
}
if (!svgCanvas.setSvgString($('#svg_source_textarea').val())) {
$.confirm("There were parsing errors in your SVG source.\nRevert back to original SVG source?", function(ok) {
if(!ok) return false;
saveChanges();
});
} else {
saveChanges();
}
setSelectMode();
};
function setBackground(color, url) {
// if(color == curPrefs.bkgd_color && url == curPrefs.bkgd_url) return;
$.pref('bkgd_color', color);
$.pref('bkgd_url', url);
// This should be done in svgcanvas.js for the borderRect fill
svgCanvas.setBackground(color, url);
}
var setIcon = Editor.setIcon = function(elem, icon_id, forcedSize) {
var icon = (typeof icon_id === 'string') ? $.getSvgIcon(icon_id, true) : icon_id.clone();
if(!icon) {
console.log('NOTE: Icon image missing: ' + icon_id);
return;
}
$(elem).find("img").replaceWith(icon);
}
var ua_prefix;
(ua_prefix = function() {
var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/;
var someScript = document.getElementsByTagName('script')[0];
for(var prop in someScript.style) {
if(regex.test(prop)) {
// test is faster than match, so it's better to perform
// that on the lot and match only when necessary
return prop.match(regex)[0];
}
}
// Nothing found so far?
if('WebkitOpacity' in someScript.style) return 'Webkit';
if('KhtmlOpacity' in someScript.style) return 'Khtml';
return '';
}());
var scaleElements = function(elems, scale) {
var prefix = '-' + ua_prefix.toLowerCase() + '-';
var sides = ['top', 'left', 'bottom', 'right'];
elems.each(function() {
// console.log('go', scale);
// Handled in CSS
// this.style[ua_prefix + 'Transform'] = 'scale(' + scale + ')';
var el = $(this);
var w = el.outerWidth() * (scale - 1);
var h = el.outerHeight() * (scale - 1);
var margins = {};
for(var i = 0; i < 4; i++) {
var s = sides[i];
var cur = el.data('orig_margin-' + s);
if(cur == null) {
cur = parseInt(el.css('margin-' + s));
// Cache the original margin
el.data('orig_margin-' + s, cur);
}
var val = cur * scale;
if(s === 'right') {
val += w;
} else if(s === 'bottom') {
val += h;
}
el.css('margin-' + s, val);
// el.css('outline', '1px solid red');
}
});
}
var setIconSize = Editor.setIconSize = function(size, force) {
if(size == curPrefs.size && !force) return;
// return;
// var elems = $('.tool_button, .push_button, .tool_button_current, .disabled, .icon_label, #url_notice, #tool_open');
var sel_toscale = '#tools_top .toolset, #editor_panel > *, #history_panel > *,\
#main_button, #tools_left > *, #path_node_panel > *, #multiselected_panel > *,\
#g_panel > *, #tool_font_size > *, .tools_flyout';
var elems = $(sel_toscale);
var scale = 1;
if(typeof size == 'number') {
scale = size;
} else {
var icon_sizes = { s:.75, m:1, l:1.25, xl:1.5 };
scale = icon_sizes[size];
}
Editor.tool_scale = tool_scale = scale;
setFlyoutPositions();
var hidden_ps = elems.parents(':hidden');
hidden_ps.css('visibility', 'hidden').show();
scaleElements(elems, scale);
hidden_ps.css('visibility', 'visible').hide();
var rule_elem = $('#tool_size_rules');
if(!rule_elem.length) {
rule_elem = $('<style id="tool_size_rules"><\/style>').appendTo('head');
} else {
rule_elem.empty();
}
if(size != 'm') {
var style_str = '';
$.each(cssResizeRules, function(selector, rules) {
selector = '#svg_editor ' + selector.replace(/,/g,', #svg_editor');
style_str += selector + '{';
$.each(rules, function(prop, values) {
if(typeof values === 'number') {
var val = (values * scale) + 'px';
} else if(values[size] || values.all) {
var val = (values[size] || values.all);
}
style_str += (prop + ':' + val + ';');
});
style_str += '}';
});
//this.style[ua_prefix + 'Transform'] = 'scale(' + scale + ')';
var prefix = '-' + ua_prefix.toLowerCase() + '-';
style_str += (sel_toscale + '{' + prefix + 'transform: scale(' + scale + ');}'
+ ' #svg_editor div.toolset .toolset {' + prefix + 'transform: scale(1); margin: 1px !important;}' // Hack for markers
+ ' #svg_editor .ui-slider {' + prefix + 'transform: scale(' + (1/scale) + ');}' // Hack for sliders
);
rule_elem.text(style_str);
}
setFlyoutPositions();
}
var cancelOverlays = function() {
$('#dialog_box').hide();
if (!editingsource && !docprops && !preferences) {
if(cur_context) {
svgCanvas.leaveContext();
}
return;
};
if (editingsource) {
if (orig_source !== $('#svg_source_textarea').val()) {
$.confirm("Ignore changes made to SVG source?", function(ok) {
if(ok) hideSourceEditor();
});
} else {
hideSourceEditor();
}
}
else if (docprops) {
hideDocProperties();
} else if (preferences) {
hidePreferences();
}
resetScrollPos();
};
var hideSourceEditor = function(){
$('#svg_source_editor').hide();
editingsource = false;
$('#svg_source_textarea').blur();
};
var win_wh = {width:$(window).width(), height:$(window).height()};
var resetScrollPos = $.noop, curScrollPos;
/* Fix for Issue 781: Drawing area jumps to top-left corner on window resize (IE9)
if(svgedit.browser.isIE()) {
(function() {
resetScrollPos = function() {
if(workarea[0].scrollLeft === 0
&& workarea[0].scrollTop === 0) {
workarea[0].scrollLeft = curScrollPos.left;
workarea[0].scrollTop = curScrollPos.top;
}
}
curScrollPos = {
left: workarea[0].scrollLeft,
top: workarea[0].scrollTop
};
$(window).resize(resetScrollPos);
methodDraw.ready(function() {
// TODO: Find better way to detect when to do this to minimize
// flickering effect
setTimeout(function() {
resetScrollPos();
}, 500);
});
workarea.scroll(function() {
curScrollPos = {
left: workarea[0].scrollLeft,
top: workarea[0].scrollTop
};
});
}());
}*/
$(window).resize(function(evt) {
updateCanvas();
});
(function() {
workarea.scroll(function() {
// TODO: jQuery's scrollLeft/Top() wouldn't require a null check
if ($('#ruler_x').length != 0) {
$('#ruler_x')[0].scrollLeft = workarea[0].scrollLeft;
}
if ($('#ruler_y').length != 0) {
$('#ruler_y')[0].scrollTop = workarea[0].scrollTop;
}
});
}());
$('#url_notice').click(function() {
$.alert(this.title);
});
$('#change_image_url').click(promptImgURL);
function promptImgURL() {
var curhref = svgCanvas.getHref(selectedElement);
curhref = curhref.indexOf("data:") === 0?"":curhref;
$.prompt("Enter the new image URL", curhref, function(url) {
if(url) setImageURL(url);
});
}
// TODO: go back to the color boxes having white background-color and then setting
// background-image to none.png (otherwise partially transparent gradients look weird)
var colorPicker = function(elem) {
var picker = elem[0].id == 'stroke_color' ? 'stroke' : 'fill';
var is_background = elem[0].id == "canvas_color"
if (is_background) picker = 'canvas'
// var opacity = (picker == 'stroke' ? $('#stroke_opacity') : $('#fill_opacity'));
var paint = Editor.paintBox[picker].paint;
var title = (picker == 'stroke' ? 'Pick a Stroke Paint and Opacity' : 'Pick a Fill Paint and Opacity');
var was_none = false;
var pos = is_background ? {'right': 175, 'top': 50} : {'left': 50, 'bottom': 50}
$("#color_picker")
.draggable({cancel:'.jGraduate_tabs, .jGraduate_colPick, .jGraduate_gradPick, .jPicker', containment: 'window'})
.removeAttr("style")
.css(pos)
.jGraduate(
{
paint: paint,
window: { pickerTitle: title },
images: { clientPath: curConfig.jGraduatePath },
newstop: 'inverse'
},
function(p) {
paint = new $.jGraduate.Paint(p);
Editor.paintBox[picker].setPaint(paint);
svgCanvas.setPaint(picker, paint);
$('#color_picker').hide();
},
function(p) {
$('#color_picker').hide();
});
};
var PaintBox = function(container, type) {
var background = document.getElementById("canvas_background");
var cur = {color: "fff", opacity: 1}
if (type == "stroke") cur = curConfig['initStroke'];
if (type == "fill") cur = curConfig['initFill'];
if (type == "canvas" && background) {
var rgb = background.getAttribute("fill").match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (rgb) {
var hex = ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
("0" + parseInt(rgb[3],10).toString(16)).slice(-2);
cur = {color: hex, opacity: 1}
}
}
// set up gradients to be used for the buttons
var svgdocbox = new DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%"\
fill="#' + cur.color + '" opacity="' + cur.opacity + '"/>\
<defs><linearGradient id="gradbox_"/></defs></svg>', 'text/xml');
var docElem = svgdocbox.documentElement;
docElem = $(container)[0].appendChild(document.importNode(docElem, true));
if (type === 'canvas') docElem.setAttribute('width',60.5);
else docElem.setAttribute('width',"100%");
this.rect = docElem.firstChild;
this.defs = docElem.getElementsByTagName('defs')[0];
this.grad = this.defs.firstChild;
this.paint = new $.jGraduate.Paint({solidColor: cur.color});
this.type = type;
this.setPaint = function(paint, apply, noUndo) {
this.paint = paint;
var fillAttr = "none";
var ptype = paint.type;
var opac = paint.alpha / 100;
switch ( ptype ) {
case 'solidColor':
fillAttr = (paint[ptype] == 'none' || paint[ptype] == 'one') ? 'none' : "#" + paint[ptype];
break;
case 'linearGradient':
case 'radialGradient':
this.defs.removeChild(this.grad);
this.grad = this.defs.appendChild(paint[ptype]);
var id = this.grad.id = 'gradbox_' + this.type;
fillAttr = "url(#" + id + ')';
}
this.rect.setAttribute('fill', fillAttr);
this.rect.setAttribute('opacity', opac);
if (this.type == "canvas") {
//recache background in case it changed
var background = document.getElementById("canvas_background");
if (background) {
res = svgCanvas.getResolution()
background.setAttribute("x", -1);
background.setAttribute("y", -1);
background.setAttribute("width", res.w+2);
background.setAttribute("height", res.h+2);
if (fillAttr.indexOf("url") == -1) background.setAttribute('fill', fillAttr)
}
else createBackground(fillAttr)
}
if(apply) {
svgCanvas.setColor(this.type, fillAttr, true);
svgCanvas.setPaintOpacity(this.type, opac, true);
}
}
this.update = function(apply) {
if(!selectedElement) return;
var type = this.type;
switch ( selectedElement.tagName ) {
case 'use':
case 'image':
case 'foreignObject':
// These elements don't have fill or stroke, so don't change
// the current value
return;
case 'g':
case 'a':
var gPaint = null;
var childs = selectedElement.getElementsByTagName('*');
for(var i = 0, len = childs.length; i < len; i++) {
var elem = childs[i];
var p = elem.getAttribute(type);
if(i === 0) {
gPaint = p;
} else if(gPaint !== p) {
gPaint = null;
break;
}
}
if(gPaint === null) {
// No common color, don't update anything
var paintColor = null;
return;
}
var paintColor = gPaint;
var paintOpacity = 1;
break;
default:
var paintOpacity = parseFloat(selectedElement.getAttribute(type + "-opacity"));
if (isNaN(paintOpacity)) {
paintOpacity = 1.0;
}
var defColor = type === "fill" ? "black" : "none";
var paintColor = selectedElement.getAttribute(type) || defColor;
}
if(apply) {
svgCanvas.setColor(type, paintColor, true);
svgCanvas.setPaintOpacity(type, paintOpacity, true);
}
paintOpacity *= 100;
var paint = getPaint(paintColor, paintOpacity, type);
// update the rect inside #fill_color/#stroke_color
this.setPaint(paint);
}
this.prep = function() {
var ptype = this.paint.type;
switch ( ptype ) {
case 'linearGradient':
case 'radialGradient':
var paint = new $.jGraduate.Paint({copy: this.paint});
svgCanvas.setPaint(type, paint);
}
}
};
Editor.paintBox.fill = new PaintBox('#fill_color', 'fill');
Editor.paintBox.stroke = new PaintBox('#stroke_color', 'stroke');
Editor.paintBox.canvas = new PaintBox('#canvas_color', 'canvas');
$('#stroke_width').val(curConfig.initStroke.width);
$('#group_opacity').val(curConfig.initOpacity * 100);
// Use this SVG elem to test vectorEffect support
var test_el = Editor.paintBox.fill.rect.cloneNode(false);
test_el.setAttribute('style','vector-effect:non-scaling-stroke');
var supportsNonSS = (test_el.style.vectorEffect === 'non-scaling-stroke');
test_el.removeAttribute('style');
var svgdocbox = Editor.paintBox.fill.rect.ownerDocument;
// Use this to test support for blur element. Seems to work to test support in Webkit
var blur_test = svgdocbox.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
if(typeof blur_test.stdDeviationX === "undefined") {
$('#tool_blur').hide();
}
$(blur_test).remove();
// Test for embedImage support (use timeout to not interfere with page load)
setTimeout(function() {
2020-08-02 10:57:54 +00:00
svgCanvas.embedImage(curConfig.imgPath + 'placeholder.svg', function(datauri) {
2020-08-02 10:26:22 +00:00
if(!datauri) {
// Disable option
$('#image_save_opts [value=embed]').attr('disabled','disabled');
$('#image_save_opts input').val(['ref']);
curPrefs.img_save = 'ref';
$('#image_opt_embed').css('color','#666').attr('title', "Feature not supported");
}
});
},1000);
$('#tool_fill').click(function(){
if ($('#tool_fill').hasClass('active')) {
colorPicker($('#fill_color'));
}
else {
$('#tool_fill').addClass('active');
$("#tool_stroke").removeClass('active');
}
});
$('#tool_stroke').on("click", function(){
if ($('#tool_stroke').hasClass('active')) {
colorPicker($('#stroke_color'));
}
else {
$('#tool_stroke').addClass('active');
$("#tool_fill").removeClass('active');
}
});
$('#tool_canvas').on("click touchstart", function(){
colorPicker($('#canvas_color'));
});
$('#tool_stroke').on("touchstart", function(){
$('#tool_stroke').addClass('active');
$("#tool_fill").removeClass('active');
colorPicker($('#stroke_color'));
});
$('#tool_fill').on("touchstart", function(){
$('#tool_fill').addClass('active');
$("#tool_stroke").removeClass('active');
colorPicker($('#fill_color'));
});
$('#zoom_select').on("change", function() {
var val = this.options[this.selectedIndex].text
val = val.split("%")[0]
$("#zoom").val(val).trigger("change")
});
$('.push_button').mousedown(function() {
if (!$(this).hasClass('disabled')) {
$(this).addClass('push_button_pressed').removeClass('push_button');
}
}).mouseout(function() {
$(this).removeClass('push_button_pressed').addClass('push_button');
}).mouseup(function() {
$(this).removeClass('push_button_pressed').addClass('push_button');
});
// function changeResolution(x,y) {
// var zoom = svgCanvas.getResolution().zoom;
// setResolution(x * zoom, y * zoom);
// }
var centerCanvas = function() {
// this centers the canvas vertically in the workarea (horizontal handled in CSS)
workarea.css('line-height', workarea.height() + 'px');
};
$(window).bind('load resize', centerCanvas);
function stepFontSize(elem, step) {
var orig_val = elem.value-0;
var sug_val = orig_val + step;
var increasing = sug_val >= orig_val;
if(step === 0) return orig_val;
if(orig_val >= 24) {
if(increasing) {
return Math.round(orig_val * 1.1);
} else {
return Math.round(orig_val / 1.1);
}
} else if(orig_val <= 1) {
if(increasing) {
return orig_val * 2;
} else {
return orig_val / 2;
}
} else {
return sug_val;
}
}
function stepZoom(elem, step) {
var orig_val = elem.value-0;
if(orig_val === 0) return 100;
var sug_val = orig_val + step;
if(step === 0) return orig_val;
if(orig_val >= 100) {
return sug_val;
} else {
if(sug_val >= orig_val) {
return orig_val * 2;
} else {
return orig_val / 2;
}
}
}
var changeCanvasSize = function(ctl){
var width = $("#canvas_width");
var height = $("#canvas_height");
var w = width.val();
var h = height.val()
if(w != "fit" && !svgedit.units.isValidUnit('width', w)) {
$.alert("Invalid value given");
width.parent().addClass('error');
return false;
}
width.parent().removeClass('error');
if(h != "fit" && !svgedit.units.isValidUnit('height', h)) {
$.alert("Invalid value given");
height.parent().addClass('error');
return false;
}
height.parent().removeClass('error');
if(!svgCanvas.setResolution(w, h)) {
$.alert("No content to fit to");
var dims = svgCanvas.getResolution()
width.val(dims.w)
height.val(dims.h)
return false;
}
updateCanvas();
}
$('#resolution').change(function(){
var w = $('#canvas_width')[0];
var h = $('#canvas_height')[0];
if(!this.selectedIndex) {
$('#resolution_label').html("Custom");
w.removeAttribute("readonly");
w.focus();
w.select();
if(w.value == 'fit') {
w.value = 100
h.value = 100
}
} else if(this.value == 'content') {
w.value = 'fit'
h.value = 'fit'
changeCanvasSize();
var res = svgCanvas.getResolution()
w.value = res.w
h.value = res.h
} else {
var dims = this.value.split('x');
dims[0] = parseInt(dims[0]);
dims[1] = parseInt(dims[1]);
var diff_w = dims[0] - w.value;
var diff_h = dims[1] - h.value;
//animate
var start = Date.now();
var duration = 1000;
var animateCanvasSize = function(timestamp) {
var progress = Date.now() - start;
var tick = progress / duration;
tick = (Math.pow((tick-1), 3) +1);
w.value = (dims[0] - diff_w + (tick*diff_w)).toFixed(0);
h.value = (dims[1] - diff_h + (tick*diff_h)).toFixed(0);
changeCanvasSize();
if (tick >= 1) {
var res = svgCanvas.getResolution()
$('#canvas_width').val(res.w.toFixed())
$('#canvas_height').val(res.h.toFixed())
$('#resolution_label').html("<div class='pull'>" + res.w + "<span>×</span></br>" + res.h + "</div>");
}
else {
requestAnimationFrame(animateCanvasSize)
}
}
animateCanvasSize()
}
});
$('#zoom').change(function(){
changeZoom(this)
})
//Prevent browser from erroneously repopulating fields
$('input,select').attr("autocomplete","off");
// Associate all button actions as well as non-button keyboard shortcuts
var Actions = function() {
// sel:'selector', fn:function, evt:'event', key:[key, preventDefault, NoDisableInInput]
var tool_buttons = [
{sel:'#tool_select', fn: clickSelect, evt: 'click', key: ['V', true]},
{sel:'#tool_fhpath', fn: clickFHPath, evt: 'click', key: ['Q', true]},
{sel:'#tool_line', fn: clickLine, evt: 'click', key: ['L', true]},
{sel:'#tool_rect', fn: clickRect, evt: 'click', key: ['R', true], icon: 'rect'},
{sel:'#tool_ellipse', fn: clickEllipse, evt: 'mouseup', key: ['C', true], icon: 'ellipse'},
//{sel:'#tool_circle', fn: clickCircle, evt: 'mouseup', icon: 'circle'},
//{sel:'#tool_fhellipse', fn: clickFHEllipse, evt: 'mouseup', parent: '#tools_ellipse', icon: 'fh_ellipse'},
{sel:'#tool_path', fn: clickPath, evt: 'click', key: ['P', true]},
{sel:'#tool_text', fn: clickText, evt: 'click', key: ['T', true]},
{sel:'#tool_image', fn: clickImage, evt: 'mouseup'},
{sel:'#tool_zoom', fn: clickZoom, evt: 'mouseup', key: ['Z', true]},
{sel:'#tool_clear', fn: clickClear, evt: 'mouseup', key: [modKey + 'N', true]},
{sel:'#tool_save', fn: function() { editingsource ? saveSourceEditor(): clickSave() }, evt: 'mouseup', key: [modKey + 'S', true]},
{sel:'#tool_export', fn: clickExport, evt: 'mouseup'},
{sel:'#tool_open', fn: clickOpen, evt: 'mouseup'},
{sel:'#tool_import', fn: clickImport, evt: 'mouseup'},
{sel:'#tool_source', fn: showSourceEditor, evt: 'click', key: [modKey + 'U', true]},
{sel:'#tool_wireframe', fn: clickWireframe, evt: 'click'},
{sel:'#tool_snap', fn: clickSnapGrid, evt: 'click'},
{sel:'#tool_rulers', fn: clickRulers, evt: 'click'},
{sel:'#tool_source_cancel,#svg_source_overlay,#tool_docprops_cancel,#tool_prefs_cancel', fn: cancelOverlays, evt: 'click', key: ['esc', false, false], hidekey: true},
{sel:'#tool_source_save', fn: saveSourceEditor, evt: 'click'},
{sel:'#tool_delete,#tool_delete_multi', fn: deleteSelected, evt: 'click', key: ['del/backspace', true]},
{sel:'#tool_reorient', fn: reorientPath, evt: 'click'},
{sel:'#tool_node_link', fn: linkControlPoints, evt: 'change'},
{sel:'#tool_node_clone', fn: clonePathNode, evt: 'click'},
{sel:'#tool_node_delete', fn: deletePathNode, evt: 'click'},
{sel:'#tool_openclose_path', fn: opencloseSubPath, evt: 'click'},
{sel:'#tool_add_subpath', fn: addSubPath, evt: 'click'},
{sel:'#tool_move_top', fn: moveToTopSelected, evt: 'click', key: modKey + 'shift+up'},
{sel:'#tool_move_bottom', fn: moveToBottomSelected, evt: 'click', key: modKey + 'shift+down'},
{sel:'#tool_move_up', fn: moveUpSelected, evt:'click', key: [modKey+'up', true]},
{sel:'#tool_move_down', fn: moveDownSelected, evt:'click', key: [modKey+'down', true]},
{sel:'#tool_topath', fn: convertToPath, evt: 'click'},
{sel:'#tool_make_link,#tool_make_link_multi', fn: makeHyperlink, evt: 'click'},
{sel:'#tool_clone,#tool_clone_multi', fn: clickClone, evt: 'click', key: [modKey + 'D', true]},
{sel:'#tool_group', fn: clickGroup, evt: 'click', key: [modKey + 'G', true]},
{sel:'#tool_ungroup', fn: clickGroup, evt: 'click', key: modKey + 'shift+G'},
{sel:'#tool_unlink_use', fn: clickGroup, evt: 'click'},
{sel:'[id^=tool_align]', fn: clickAlign, evt: 'click'},
{sel:'#tool_undo', fn: clickUndo, evt: 'click', key: modKey + 'z'},
{sel:'#tool_redo', fn: clickRedo, evt: 'click', key: ['y', true]},
{sel:'#tool_cut', fn: cutSelected, evt: 'click', key: [modKey+'x', true]},
{sel:'#tool_copy', fn: copySelected, evt: 'click', key: modKey+'c'},
{sel:'#tool_paste', fn: pasteSelected, evt: 'click', key: modKey+'v'},
{sel:'#tool_switch', fn: clickSwitch, evt: 'click', key: ['x', true]},
{sel:'#tool_bold', fn: clickBold, evt: 'mousedown', key: [modKey + 'B', true]},
{sel:'#tool_italic', fn: clickItalic, evt: 'mousedown', key: [modKey + 'I', true]},
//{sel:'#sidepanel_handle', fn: toggleSidePanel, key: ['X']},
{sel:'#copy_save_done', fn: cancelOverlays, evt: 'click'},
// Shortcuts not associated with buttons
{key: 'ctrl+left', fn: function(){rotateSelected(0,1)}},
{key: 'ctrl+right', fn: function(){rotateSelected(1,1)}},
{key: 'ctrl+shift+left', fn: function(){rotateSelected(0,5)}},
{key: 'ctrl+shift+right', fn: function(){rotateSelected(1,5)}},
{key: 'shift+O', fn: selectPrev},
{key: 'shift+P', fn: selectNext},
{key: [modKey+'+', true], fn: function(){zoomImage(2);}},
{key: [modKey+'-', true], fn: function(){zoomImage(.5);}},
{key: ['up', true], fn: function(){moveSelected(0,-1);}},
{key: ['down', true], fn: function(){moveSelected(0,1);}},
{key: ['left', true], fn: function(){moveSelected(-1,0);}},
{key: ['right', true], fn: function(){moveSelected(1,0);}},
{key: 'shift+up', fn: function(){moveSelected(0,-10)}},
{key: 'shift+down', fn: function(){moveSelected(0,10)}},
{key: 'shift+left', fn: function(){moveSelected(-10,0)}},
{key: 'shift+right', fn: function(){moveSelected(10,0)}},
{key: ['alt+up', true], fn: function(){svgCanvas.cloneSelectedElements(0,-1)}},
{key: ['alt+down', true], fn: function(){svgCanvas.cloneSelectedElements(0,1)}},
{key: ['alt+left', true], fn: function(){svgCanvas.cloneSelectedElements(-1,0)}},
{key: ['alt+right', true], fn: function(){svgCanvas.cloneSelectedElements(1,0)}},
{key: ['alt+shift+up', true], fn: function(){svgCanvas.cloneSelectedElements(0,-10)}},
{key: ['alt+shift+down', true], fn: function(){svgCanvas.cloneSelectedElements(0,10)}},
{key: ['alt+shift+left', true], fn: function(){svgCanvas.cloneSelectedElements(-10,0)}},
{key: ['alt+shift+right', true], fn: function(){svgCanvas.cloneSelectedElements(10,0)}},
{key: modKey + 'A', fn: function(){svgCanvas.selectAllInCurrentLayer();}},
{key: 'I', fn: function(){setEyedropperMode()}},
// Standard shortcuts
{key: modKey + 'shift+z', fn: clickRedo},
{key: 'esc', fn: minimizeModal}
];
// Tooltips not directly associated with a single function
var key_assocs = {
'4/Shift+4': '#tools_rect_show',
'5/Shift+5': '#tools_ellipse_show'
};
return {
setAll: function() {
var flyouts = {};
$.each(tool_buttons, function(i, opts) {
// Bind function to button
if(opts.sel) {
var btn = $(opts.sel);
if (btn.length == 0) return true; // Skip if markup does not exist
if(opts.evt) {
if (svgedit.browser.isTouch() && opts.evt === "click") opts.evt = "mousedown"
btn[opts.evt](opts.fn);
}
// Add to parent flyout menu, if able to be displayed
if(opts.parent && $(opts.parent + '_show').length != 0) {
var f_h = $(opts.parent);
if(!f_h.length) {
f_h = makeFlyoutHolder(opts.parent.substr(1));
}
f_h.append(btn);
if(!$.isArray(flyouts[opts.parent])) {
flyouts[opts.parent] = [];
}
flyouts[opts.parent].push(opts);
}
}
// Bind function to shortcut key
if(opts.key) {
// Set shortcut based on options
var keyval, shortcut = '', disInInp = true, fn = opts.fn, pd = false;
if($.isArray(opts.key)) {
keyval = opts.key[0];
if(opts.key.length > 1) pd = opts.key[1];
if(opts.key.length > 2) disInInp = opts.key[2];
} else {
keyval = opts.key;
}
keyval += '';
if (svgedit.browser.isMac && keyval.indexOf("+") != -1) {
var modifier_key = keyval.split("+")[0];
if (modifier_key == "ctrl") keyval.replace("ctrl", "cmd")
}
$.each(keyval.split('/'), function(i, key) {
$(document).bind('keydown', key, function(e) {
fn();
if(pd) {
e.preventDefault();
}
// Prevent default on ALL keys?
return false;
});
});
// Put shortcut in title
if(opts.sel && !opts.hidekey && btn.attr('title')) {
var new_title = btn.attr('title').split('[')[0] + ' (' + keyval + ')';
key_assocs[keyval] = opts.sel;
// Disregard for menu items
if(!btn.parents('#main_menu').length) {
btn.attr('title', new_title);
}
}
}
});
// Setup flyouts
setupFlyouts(flyouts);
$(window).bind('keydown', 'tab', function(e) {
if(ui_context === 'canvas') {
e.preventDefault();
selectNext();
}
}).bind('keydown', 'shift+tab', function(e) {
if(ui_context === 'canvas') {
e.preventDefault();
selectPrev();
}
});
$('#tool_zoom').dblclick(dblclickZoom);
},
setTitles: function() {
$.each(key_assocs, function(keyval, sel) {
var menu = ($(sel).parents('#main_menu').length);
$(sel).each(function() {
if(menu) {
var t = $(this).text().split(' [')[0];
} else {
var t = this.title.split(' [')[0];
}
var key_str = '';
// Shift+Up
$.each(keyval.split('/'), function(i, key) {
var mod_bits = key.split('+'), mod = '';
if(mod_bits.length > 1) {
mod = mod_bits[0] + '+';
key = mod_bits[1];
}
key_str += (i?'/':'') + mod + (key);
});
if(menu) {
this.lastChild.textContent = t +' ['+key_str+']';
} else {
this.title = t +' ['+key_str+']';
}
});
});
},
getButtonData: function(sel) {
var b;
$.each(tool_buttons, function(i, btn) {
if(btn.sel === sel) b = btn;
});
return b;
}
};
}();
Actions.setAll();
// Select given tool
Editor.ready(function() {
var tool,
itool = curConfig.initTool,
container = $("#tools_left, #svg_editor .tools_flyout"),
pre_tool = container.find("#tool_" + itool),
reg_tool = container.find("#" + itool);
if(pre_tool.length) {
tool = pre_tool;
} else if(reg_tool.length){
tool = reg_tool;
} else {
tool = $("#tool_select");
}
tool.click().mouseup();
if(curConfig.wireframe) {
$('#tool_wireframe').click();
}
if(curConfig.showlayers) {
toggleSidePanel();
}
$('#rulers').toggle(!!curConfig.showRulers);
});
$('#canvas_height').dragInput({ min: 10, max: null, step: 10, callback: changeCanvasSize, cursor: false, dragAdjust: .1 });
$('#canvas_width') .dragInput({ min: 10, max: null, step: 10, callback: changeCanvasSize, cursor: false, dragAdjust: .1 });
$('#rect_width') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#rect_height') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#ellipse_cx') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#ellipse_cy') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#ellipse_rx') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#ellipse_ry') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$("#image_height") .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#circle_cx') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#circle_cy') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#circle_r') .dragInput({ min: 1, max: null, step: 1, callback: changeAttribute, cursor: false });
$("#image_height") .dragInput({ min: 0, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#selected_x') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#selected_y') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$("#path_node_x") .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$("#path_node_y") .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$("#image_width") .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#line_x1') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#line_x2') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#line_y1') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#line_y2') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#path_x') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#path_y') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#rect_x') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#rect_y') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#g_x') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#g_y') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#image_x') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#text_y') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#text_x') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#image_y') .dragInput({ min: null, max: null, step: 1, callback: changeAttribute, cursor: false });
$('#rect_rx') .dragInput({ min: 0, max: 100, step: 1, callback: changeAttribute, cursor: true });
$('#stroke_width') .dragInput({ min: 0, max: 99, step: 1, callback: changeStrokeWidth, cursor: true, smallStep: 0.1, start: 1.5 });
$('#angle') .dragInput({ min: -180, max: 180, step: 1, callback: changeRotationAngle, cursor: false, dragAdjust: 0.5 });
$('#font_size') .dragInput({ min: 1, max: 250, step: 1, callback: changeFontSize, cursor: true, stepfunc: stepFontSize, dragAdjust: .15 });
$('#group_opacity').dragInput({ min: 0, max: 100, step: 5, callback: changeAttribute, cursor: true, start: 100 });
$('#blur') .dragInput({ min: 0, max: 10, step: .1, callback: changeBlur, cursor: true, start: 0 });
// Set default zoom
$('#zoom').val(svgCanvas.getZoom() * 100);
$("#workarea").contextMenu({
menu: 'cmenu_canvas',
inSpeed: 0
},
function(action, el, pos) {
switch ( action ) {
case 'delete':
deleteSelected();
break;
case 'cut':
cutSelected();
break;
case 'copy':
copySelected();
break;
case 'paste':
svgCanvas.pasteElements();
break;
case 'paste_in_place':
svgCanvas.pasteElements('in_place');
break;
case 'group':
svgCanvas.groupSelectedElements();
break;
case 'ungroup':
svgCanvas.ungroupSelectedElement();
break;
case 'move_front':
moveToTopSelected();
break;
case 'move_up':
moveUpDownSelected('Up');
break;
case 'move_down':
moveUpDownSelected('Down');
break;
case 'move_back':
moveToBottomSelected();
break;
default:
if(svgedit.contextmenu && svgedit.contextmenu.hasCustomHandler(action)){
svgedit.contextmenu.getCustomHandler(action).call();
}
break;
}
});
$('.contextMenu li').mousedown(function(ev) {
ev.preventDefault();
})
$('#cmenu_canvas li').disableContextMenu();
canv_menu.enableContextMenuItems('#delete,#cut,#copy');
window.onbeforeunload = function() {
// Suppress warning if page is empty
if(undoMgr.getUndoStackSize() === 0) {
Editor.show_save_warning = false;
}
// show_save_warning is set to "false" when the page is saved.
if(!curConfig.no_save_warning && Editor.show_save_warning) {
// Browser already asks question about closing the page
return "There are unsaved changes.";
}
};
Editor.openPrep = function(func) {
$('#main_menu').hide();
if(undoMgr.getUndoStackSize() === 0) {
func(true);
} else {
$.confirm("Do you want to open a new file?\nThis will also erase your undo history", func);
}
}
if (window.FileReader) {
var import_image = function(e) {
e.stopPropagation();
e.preventDefault();
$("#workarea").removeAttr("style");
$('#main_menu').hide();
var file = null;
if (e.type == "drop") file = e.dataTransfer.files[0]
else file = this.files[0];
if (file) {
if(file.type.indexOf("image") != -1) {
//detected an image
//svg handing
if(file.type.indexOf("svg") != -1) {
var reader = new FileReader();
reader.onloadend = function(e) {
svgCanvas.importSvgString(e.target.result, true);
svgCanvas.ungroupSelectedElement()
svgCanvas.ungroupSelectedElement()
svgCanvas.groupSelectedElements()
svgCanvas.alignSelectedElements("m", "page")
svgCanvas.alignSelectedElements("c", "page")
};
reader.readAsText(file);
}
//image handling
else {
var reader = new FileReader();
reader.onloadend = function(e) {
// lets insert the new image until we know its dimensions
insertNewImage = function(img_width, img_height){
var newImage = svgCanvas.addSvgElementFromJson({
"element": "image",
"attr": {
"x": 0,
"y": 0,
"width": img_width,
"height": img_height,
"id": svgCanvas.getNextId(),
"style": "pointer-events:inherit"
}
});
svgCanvas.setHref(newImage, e.target.result);
svgCanvas.selectOnly([newImage])
svgCanvas.alignSelectedElements("m", "page")
svgCanvas.alignSelectedElements("c", "page")
updateContextPanel();
}
// put a placeholder img so we know the default dimensions
var img_width = 100;
var img_height = 100;
var img = new Image()
img.src = e.target.result
document.body.appendChild(img);
img.onload = function() {
img_width = img.offsetWidth
img_height = img.offsetHeight
insertNewImage(img_width, img_height);
document.body.removeChild(img);
}
};
reader.readAsDataURL(file)
}
}
}
}
var workarea = $("#workarea")
function onDragEnter(e) {
e.stopPropagation();
e.preventDefault();
workarea.css({
"-webkit-transform": "scale3d(1.1,1.1,1)",
"-moz-transform": "scale3d(1.1,1.1,1)",
"-o-transform": "scale(1.1)",
"-ms-transform": "scale3d(1.1,1.1,1)",
"transform": "scale3d(1.1,1.1,1)"
})
}
function onDragOver(e) {
e.stopPropagation();
e.preventDefault();
}
function onDragLeave(e) {
workarea.removeAttr("style")
e.stopPropagation();
e.preventDefault();
}
workarea[0].addEventListener('dragenter', onDragEnter, false);
workarea[0].addEventListener('dragover', onDragOver, false);
workarea[0].addEventListener('dragleave', onDragLeave, false);
workarea[0].addEventListener('drop', import_image, false);
var open = $('<input type="file">').change(function() {
var f = this;
Editor.openPrep(function(ok) {
if(!ok) return;
svgCanvas.clear();
if(f.files.length==1) {
var reader = new FileReader();
reader.onloadend = function(e) {
loadSvgString(e.target.result);
updateCanvas();
};
reader.readAsText(f.files[0]);
}
});
});
$("#tool_open").show().prepend(open);
var img_import = $('<input type="file">').change(import_image);
$("#tool_import").show().prepend(img_import);
}
var updateCanvas = Editor.updateCanvas = function(center, new_ctr) {
var w = workarea.width(), h = workarea.height();
var w_orig = w, h_orig = h;
var zoom = svgCanvas.getZoom();
var w_area = workarea;
var cnvs = $("#svgcanvas");
var old_ctr = {
x: w_area[0].scrollLeft + w_orig/2,
y: w_area[0].scrollTop + h_orig/2
};
var multi = curConfig.canvas_expansion;
w = Math.max(w_orig, svgCanvas.contentW * zoom * multi);
h = Math.max(h_orig, svgCanvas.contentH * zoom * multi);
if(w == w_orig && h == h_orig) {
workarea.css('overflow','hidden');
} else {
workarea.css('overflow','scroll');
}
var old_can_y = cnvs.height()/2;
var old_can_x = cnvs.width()/2;
cnvs.width(w).height(h);
var new_can_y = h/2;
var new_can_x = w/2;
var offset = svgCanvas.updateCanvas(w, h);
var ratio = new_can_x / old_can_x;
var scroll_x = w/2 - w_orig/2;
var scroll_y = h/2 - h_orig/2;
if(!new_ctr) {
var old_dist_x = old_ctr.x - old_can_x;
var new_x = new_can_x + old_dist_x * ratio;
var old_dist_y = old_ctr.y - old_can_y;
var new_y = new_can_y + old_dist_y * ratio;
new_ctr = {
x: new_x,
y: new_y
};
} else {
new_ctr.x += offset.x,
new_ctr.y += offset.y;
}
//width.val(offset.x)
//height.val(offset.y)
if(center) {
// Go to top-left for larger documents
if(svgCanvas.contentW > w_area.width()) {
// Top-left
workarea[0].scrollLeft = offset.x - 10;
workarea[0].scrollTop = offset.y - 10;
} else {
// Center
w_area[0].scrollLeft = scroll_x;
w_area[0].scrollTop = scroll_y;
}
} else {
w_area[0].scrollLeft = new_ctr.x - w_orig/2;
w_area[0].scrollTop = new_ctr.y - h_orig/2;
}
if(curConfig.showRulers) {
updateRulers(cnvs, zoom);
workarea.scroll();
}
}
// Make [1,2,5] array
var r_intervals = [];
for(var i = .1; i < 1E5; i *= 10) {
r_intervals.push(1 * i);
r_intervals.push(2 * i);
r_intervals.push(5 * i);
}
function updateRulers(scanvas, zoom) {
var workarea = document.getElementById("workarea");
var title_show = document.getElementById("title_show");
var offset_x = 66;
var offset_y = 48;
if(!zoom) zoom = svgCanvas.getZoom();
if(!scanvas) scanvas = $("#svgcanvas");
var limit = 30000;
var c_elem = svgCanvas.getContentElem();
var units = svgedit.units.getTypeMap();
var unit = units[curConfig.baseUnit]; // 1 = 1px
for(var d = 0; d < 2; d++) {
var is_x = (d === 0);
var dim = is_x ? 'x' : 'y';
var lentype = is_x?'width':'height';
var content_d = c_elem.getAttribute(dim)-0;
var $hcanv_orig = $('#ruler_' + dim + ' canvas:first');
// Bit of a hack to fully clear the canvas in Safari & IE9
$hcanv = $hcanv_orig.clone();
$hcanv_orig.replaceWith($hcanv);
var hcanv = $hcanv[0];
// Set the canvas size to the width of the container
var ruler_len = scanvas[lentype]()*2;
var total_len = ruler_len;
hcanv.parentNode.style[lentype] = total_len + 'px';
var canv_count = 1;
var ctx_num = 0;
var ctx_arr;
var ctx = hcanv.getContext("2d");
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect(0,0,hcanv.width,hcanv.height);
// Remove any existing canvasses
$hcanv.siblings().remove();
// Create multiple canvases when necessary (due to browser limits)
if(ruler_len >= limit) {
var num = parseInt(ruler_len / limit) + 1;
ctx_arr = Array(num);
ctx_arr[0] = ctx;
for(var i = 1; i < num; i++) {
hcanv[lentype] = limit;
var copy = hcanv.cloneNode(true);
hcanv.parentNode.appendChild(copy);
ctx_arr[i] = copy.getContext('2d');
}
copy[lentype] = ruler_len % limit;
// set copy width to last
ruler_len = limit;
}
hcanv[lentype] = ruler_len;
var u_multi = unit * zoom;
// Calculate the main number interval
var raw_m = 50 / u_multi;
var multi = 1;
for(var i = 0; i < r_intervals.length; i++) {
var num = r_intervals[i];
multi = num;
if(raw_m <= num) {
break;
}
}
var big_int = multi * u_multi;
ctx.font = "normal 9px 'Lucida Grande', sans-serif";
ctx.fillStyle = "#777";
var ruler_d = ((content_d / u_multi) % multi) * u_multi;
var label_pos = ruler_d - big_int;
for (; ruler_d < total_len; ruler_d += big_int) {
label_pos += big_int;
var real_d = ruler_d - content_d;
var cur_d = Math.round(ruler_d) + .5;
if(is_x) {
ctx.moveTo(cur_d, 15);
ctx.lineTo(cur_d, 0);
} else {
ctx.moveTo(15, cur_d);
ctx.lineTo(0, cur_d);
}
var num = (label_pos - content_d) / u_multi;
var label;
if(multi >= 1) {
label = Math.round(num);
} else {
var decs = (multi+'').split('.')[1].length;
label = num.toFixed(decs)-0;
}
// Do anything special for negative numbers?
// var is_neg = label < 0;
// real_d2 = Math.abs(real_d2);
// Change 1000s to Ks
if(label !== 0 && label !== 1000 && label % 1000 === 0) {
label = (label / 1000) + 'K';
}
if(is_x) {
ctx.fillText(label, ruler_d+2, 8);
ctx.fillStyle = "#777";
} else {
var str = (label+'').split('');
for(var i = 0; i < str.length; i++) {
ctx.fillText(str[i], 1, (ruler_d+9) + i*9);
ctx.fillStyle = "#777";
}
}
var part = big_int / 10;
for(var i = 1; i < 10; i++) {
var sub_d = Math.round(ruler_d + part * i) + .5;
if(ctx_arr && sub_d > ruler_len) {
ctx_num++;
ctx.stroke();
if(ctx_num >= ctx_arr.length) {
i = 10;
ruler_d = total_len;
continue;
}
ctx = ctx_arr[ctx_num];
ruler_d -= limit;
sub_d = Math.round(ruler_d + part * i) + .5;
}
var line_num = (i % 2)?12:10;
if(is_x) {
ctx.moveTo(sub_d, 15);
ctx.lineTo(sub_d, line_num);
} else {
ctx.moveTo(15, sub_d);
ctx.lineTo(line_num ,sub_d);
}
}
}
ctx.strokeStyle = "#666";
ctx.stroke();
}
}
// $(function() {
updateCanvas(true);
// });
// var revnums = "svg-editor.js ($Rev: 2083 $) ";
// revnums += svgCanvas.getVersion();
// $('#copyright')[0].setAttribute("title", revnums);
// Callback handler for embedapi.js
try{
var json_encode = function(obj){
//simple partial JSON encoder implementation
if(window.JSON && JSON.stringify) return JSON.stringify(obj);
var enc = arguments.callee; //for purposes of recursion
if(typeof obj == "boolean" || typeof obj == "number"){
return obj+'' //should work...
}else if(typeof obj == "string"){
//a large portion of this is stolen from Douglas Crockford's json2.js
return '"'+
obj.replace(
/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g
, function (a) {
return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
})
+'"'; //note that this isn't quite as purtyful as the usualness
}else if(obj.length){ //simple hackish test for arrayish-ness
for(var i = 0; i < obj.length; i++){
obj[i] = enc(obj[i]); //encode every sub-thingy on top
}
return "["+obj.join(",")+"]";
}else{
var pairs = []; //pairs will be stored here
for(var k in obj){ //loop through thingys
pairs.push(enc(k)+":"+enc(obj[k])); //key: value
}
return "{"+pairs.join(",")+"}" //wrap in the braces
}
}
window.addEventListener("message", function(e){
var cbid = parseInt(e.data.substr(0, e.data.indexOf(";")));
try{
e.source.postMessage("SVGe"+cbid+";"+json_encode(eval(e.data)), "*");
}catch(err){
e.source.postMessage("SVGe"+cbid+";error:"+err.message, "*");
}
}, false)
}catch(err){
window.embed_error = err;
}
// For Compatibility with older extensions
$(function() {
window.svgCanvas = svgCanvas;
svgCanvas.ready = methodDraw.ready;
});
Editor.setLang = function(lang, allStrings) {
$.pref('lang', lang);
$('#lang_select').val(lang);
if(allStrings) {
var notif = allStrings.notification;
svgCanvas.runExtensions("langChanged", lang);
// Update flyout tooltips
setFlyoutTitles();
// Copy title for certain tool elements
var elems = {
'#stroke_color': '#tool_stroke .icon_label, #tool_stroke .color_block',
'#fill_color': '#tool_fill label, #tool_fill .color_block',
'#linejoin_miter': '#cur_linejoin',
'#linecap_butt': '#cur_linecap'
}
$.each(elems, function(source, dest) {
$(dest).attr('title', $(source)[0].title);
});
// Copy alignment titles
$('#multiselected_panel div[id^=tool_align]').each(function() {
$('#tool_pos' + this.id.substr(10))[0].title = this.title;
});
}
};
};
var callbacks = [];
function loadSvgString(str, callback) {
var success = svgCanvas.setSvgString(str) !== false;
callback = callback || $.noop;
if(success) {
callback(true);
} else {
$.alert("Error: Unable to load SVG data", function() {
callback(false);
});
}
}
Editor.ready = function(cb) {
if(!is_ready) {
callbacks.push(cb);
} else {
cb();
}
};
Editor.runCallbacks = function() {
$.each(callbacks, function() {
this();
});
is_ready = true;
};
Editor.loadFromString = function(str) {
Editor.ready(function() {
loadSvgString(str);
});
};
Editor.loadFromURL = function(url, opts) {
if(!opts) opts = {};
var cache = opts.cache;
var cb = opts.callback;
Editor.ready(function() {
$.ajax({
'url': url,
'dataType': 'text',
cache: !!cache,
success: function(str) {
loadSvgString(str, cb);
},
error: function(xhr, stat, err) {
if(xhr.status != 404 && xhr.responseText) {
loadSvgString(xhr.responseText, cb);
} else {
$.alert("Unable to load from URL" + ": \n"+err+'', cb);
}
}
});
});
};
Editor.loadFromDataURI = function(str) {
Editor.ready(function() {
var pre = 'data:image/svg+xml;base64,';
var src = str.substring(pre.length);
loadSvgString(svgedit.utilities.decode64(src));
});
};
Editor.addExtension = function() {
var args = arguments;
// Note that we don't want this on Editor.ready since some extensions
// may want to run before then (like server_opensave).
$(function() {
if(svgCanvas) svgCanvas.addExtension.apply(this, args);
});
};
return Editor;
}(jQuery);
// Run init once DOM is loaded
$(methodDraw.init);
// Mark MacKay http://method.ac MIT License
$.fn.dragInput = function(cfg){
return this.each(function(){
this.repeating = false;
// Apply specified options or defaults:
// (Ought to refactor this some day to use $.extend() instead)
this.dragCfg = {
min: cfg && !isNaN(parseFloat(cfg.min)) ? Number(cfg.min) : null, // Fixes bug with min:0
max: cfg && !isNaN(parseFloat(cfg.max)) ? Number(cfg.max) : null,
step: cfg && Number(cfg.step) ? cfg.step : 1,
stepfunc: cfg && cfg.stepfunc ? cfg.stepfunc : false,
dragAdjust: cfg && cfg.dragAdjust ? cfg.dragAdjust : 1,
height: 70,
cursor: cfg && cfg.cursor ? Boolean(cfg.cursor) : false,
start: cfg && cfg.start ? Number(cfg.start) : 0,
_btn_width: 20,
_direction: null,
_delay: null,
_repeat: null,
callback: cfg && cfg.callback ? cfg.callback : null
};
// if a smallStep isn't supplied, use half the regular step
this.dragCfg.smallStep = cfg && cfg.smallStep ? cfg.smallStep : this.dragCfg.step/2;
var dragAdjust = this.dragCfg.dragAdjust;
var $label = $(this).parent();
var $input = $(this);
var cursorHeight = this.dragCfg.height;
var min = this.dragCfg.min;
var max = this.dragCfg.max
var step = this.dragCfg.step
var area = (max - min > 0) ? (max - min) / step : 200;
var scale = area/cursorHeight * step;
var lastY = 0;
var attr = this.getAttribute("data-attr");
var canvas = methodDraw.canvas
var isTouch = svgedit.browser.isTouch();
var completed = true //for mousewheel
var $cursor = (area && this.dragCfg.cursor)
? $("<div class='draginput_cursor' />").appendTo($label)
: false
$input.attr("readonly", "readonly")
if ($cursor && !isNaN(this.dragCfg.start)) $cursor.css("top", (this.dragCfg.start*-1)/scale+cursorHeight)
//this is where all the magic happens
this.adjustValue = function(i, completed){
var v;
i = parseFloat(i);
if(isNaN(this.value)) {
v = this.dragCfg.reset;
} else if($.isFunction(this.dragCfg.stepfunc)) {
v = this.dragCfg.stepfunc(this, i);
} else {
v = Number((Number(this.value) + Number(i)).toFixed(5));
}
if (max !== null) v = Math.min(v, max);
if (min !== null) v = Math.max(v, min);
if ($cursor) this.updateCursor(v);
this.value = v;
$label.attr("data-value", v)
if ($.isFunction(this.dragCfg.callback)) this.dragCfg.callback(this, completed)
};
$label.toggleClass("draginput", $label.is("label"))
// when the mouse is down and moving
this.move = function(e, oy, val) {
if (isTouch) {
e = e.originalEvent.touches[0]
}
// just got started let's save for undo purposes
if (lastY === 0) {
lastY = oy;
}
var deltaY = (e.pageY - lastY) *-1
lastY = e.pageY;
val = (deltaY * scale) * dragAdjust
var fixed = (step < 1) ? 1 : 0
this.adjustValue(val.toFixed(fixed)) //no undo true
};
//when the mouse is released
this.stop = function() {
var selectedElems = canvas.getSelectedElems();
$('body').removeClass('dragging');
$label.removeClass("active");
completed = true;
$(window).unbind("mousemove.draginput touchmove.draginput mouseup.draginput touchend.draginput");
lastY = 0;
if (selectedElems[0]) {
var batchCmd = canvas.undoMgr.finishUndoableChange();
if (!batchCmd.isEmpty()) canvas.undoMgr.addCommandToHistory(batchCmd);
}
this.adjustValue(0, completed)
}
this.updateCursor = function(){
var value = parseFloat(this.value);
var pos = (value*-1)/scale+cursorHeight;
$cursor.css("top", pos);
}
this.launch = function(e) {
var selectedElems = canvas.getSelectedElems();
if (isTouch) e = e.originalEvent.touches[0];
var oy = e.pageY;
var val = this.value;
var el = this;
canvas.undoMgr.beginUndoableChange(attr, selectedElems)
$('body').addClass('dragging');
$label.addClass('active');
$(window).bind("mousemove.draginput touchmove.draginput", function(e){el.move(e, oy, parseFloat(val))})
$(window).bind("mouseup.draginput touchend.draginput", function(e){el.stop()})
}
$(this)
.attr("readonly", "readonly")
.attr("data-scale", scale)
.attr("data-domain", cursorHeight)
.attr("data-cursor", ($cursor != false))
.bind("mousedown touchstart", function(e){
this.blur();
this.launch(e);
})
.bind("dblclick taphold", function(e) {
this.removeAttribute("readonly", "readonly");
this.focus();
this.select();
})
.keydown(function(e){
// Respond to up/down arrow keys.
switch(e.keyCode){
case 13: this.adjustValue(0); this.blur(); break; // Enter
}
})
.focus(function(e){
if (this.getAttribute("readonly") === "readonly") this.blur()
})
.blur(function(e){
this.setAttribute("readonly", "readonly")
})
.bind("mousewheel", function(e, delta, deltaX, deltaY){
var selectedElems = canvas.getSelectedElems();
if (completed) canvas.undoMgr.beginUndoableChange(attr, selectedElems)
completed = false
clearTimeout(window.undoTimeout)
window.undoTimeout = setTimeout(function(){
wheel_input.stop()
},200)
var wheel_input = this;
if (deltaY > 0)
this.adjustValue(this.dragCfg.step);
else if (deltaY < 0)
this.adjustValue(-this.dragCfg.step);
e.preventDefault();
})
});
};
// public function
$.fn.dragInput.updateCursor = function(el){
var value = parseFloat(el.value);
var scale = parseFloat(el.getAttribute("data-scale"));
var domain = parseFloat(el.getAttribute("data-domain"));
var pos = ((value*-1)/scale+domain) + "px";
var cursor = el.parentNode.lastChild
if (cursor.className == "draginput_cursor") cursor.style.top = pos;
}
/**
* Package: svgedit.contextmenu
*
* Licensed under the Apache License, Version 2
*
* Author: Adam Bender
*/
// Dependencies:
// 1) jQuery (for dom injection of context menus)\
var svgedit = svgedit || {};
(function() {
var self = this;
if (!svgedit.contextmenu) {
svgedit.contextmenu = {};
}
self.contextMenuExtensions = {}
var addContextMenuItem = function(menuItem) {
// menuItem: {id, label, shortcut, action}
if (!menuItemIsValid(menuItem)) {
console
.error("Menu items must be defined and have at least properties: id, label, action, where action must be a function");
return;
}
if (menuItem.id in self.contextMenuExtensions) {
console.error('Cannot add extension "' + menuItem.id
+ '", an extension by that name already exists"');
return;
}
// Register menuItem action, see below for deferred menu dom injection
console.log("Registed contextmenu item: {id:"+ menuItem.id+", label:"+menuItem.label+"}");
self.contextMenuExtensions[menuItem.id] = menuItem;
//TODO: Need to consider how to handle custom enable/disable behavior
}
var hasCustomHandler = function(handlerKey) {
return self.contextMenuExtensions[handlerKey] && true;
}
var getCustomHandler = function(handlerKey) {
return self.contextMenuExtensions[handlerKey].action;
}
var injectExtendedContextMenuItemIntoDom = function(menuItem) {
if (Object.keys(self.contextMenuExtensions).length == 0) {
// 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.
$("#cmenu_canvas").append("<li class='separator'>");
}
var shortcut = menuItem.shortcut || "";
$("#cmenu_canvas").append("<li class='disabled'><a href='#" + menuItem.id + "'>"
+ menuItem.label + "<span class='shortcut'>"
+ shortcut + "</span></a></li>");
}
var menuItemIsValid = function(menuItem) {
return menuItem && menuItem.id && menuItem.label && menuItem.action && typeof menuItem.action == 'function';
}
// Defer injection to wait out initial menu processing. This probably goes away once all context
// menu behavior is brought here.
methodDraw.ready(function() {
for (menuItem in contextMenuExtensions) {
injectExtendedContextMenuItemIntoDom(contextMenuExtensions[menuItem]);
}
});
svgedit.contextmenu.resetCustomMenus = function(){self.contextMenuExtensions = {}}
svgedit.contextmenu.add = addContextMenuItem;
svgedit.contextmenu.hasCustomHandler = hasCustomHandler;
svgedit.contextmenu.getCustomHandler = getCustomHandler;
})();
/*
* jPicker 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)
*
*/
(function($, version)
{
Math.precision = function(value, precision)
{
if (precision === undefined) precision = 0;
return Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision);
};
var Slider = // encapsulate slider functionality for the ColorMap and ColorBar - could be useful to use a jQuery UI draggable for this with certain extensions
function(bar, options)
{
var $this = this, // private properties, methods, and events - keep these variables and classes invisible to outside code
arrow = bar.find('img:first'), // the arrow image to drag
minX = 0,
maxX = 100,
rangeX = 100,
minY = 0,
maxY = 100,
rangeY = 100,
x = 0,
y = 0,
offset,
timeout,
changeEvents = new Array(),
fireChangeEvents =
function(context)
{
for (var i = 0; i < changeEvents.length; i++) changeEvents[i].call($this, $this, context);
},
mouseDown = // bind the mousedown to the bar not the arrow for quick snapping to the clicked location
function(e)
{
var off = bar.offset();
offset = { l: off.left | 0, t: off.top | 0 };
clearTimeout(timeout);
timeout = setTimeout( // using setTimeout for visual updates - once the style is updated the browser will re-render internally allowing the next Javascript to run
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
},
mouseMove = // set the values as the mouse moves
function(e)
{
clearTimeout(timeout);
timeout = setTimeout(
function()
{
setValuesFromMousePosition.call($this, e);
}, 0);
e.stopPropagation();
e.preventDefault();
return false;
},
mouseUp = // unbind the document events - they aren't needed when not dragging
function(e)
{
$(document).unbind('mouseup', mouseUp).unbind('mousemove', mouseMove);
e.stopPropagation();
e.preventDefault();
return false;
},
setValuesFromMousePosition = // calculate mouse position and set value within the current range
function(e)
{
var locX = e.pageX - offset.l,
locY = e.pageY - offset.t,
barW = bar.w, // local copies for YUI compressor
barH = bar.h;
// 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 });
},
draw =
function()
{
var arrowOffsetX = 0,
arrowOffsetY = 0,
barW = bar.w,
barH = bar.h,
arrowW = arrow.w,
arrowH = arrow.h;
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);
},
val =
function(name, value, context)
{
var set = value !== undefined;
if (!set)
{
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,
newX,
newY;
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);
},
range =
function (name, value)
{
var set = value !== undefined;
if (!set)
{
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,
newMaxX,
newMinY,
newMaxY;
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;
}
},
bind =
function (callback)
{
if ($.isFunction(callback)) changeEvents.push(callback);
},
unbind =
function (callback)
{
if (!$.isFunction(callback)) return;
var i;
while ((i = $.inArray(callback, changeEvents)) != -1) changeEvents.splice(i, 1);
},
destroy =
function()
{
// unbind all possible events and null objects
$(document).unbind('mouseup', mouseUp).unbind('mousemove', mouseMove);
bar.unbind('mousedown', mouseDown);
bar = null;
arrow = null;
changeEvents = null;
};
$.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);
},
ColorValuePicker = // controls for all the input elements for the typing in color values
function(picker, color, bindedHex, alphaPrecision)
{
var $this = this, // private properties and methods
inputs = picker.find('td.Text input'),
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,
keyDown = // input box key down - use arrows to alter color
function(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;
}
},
keyUp = // input box key up - validate value and set color
function(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;
}
},
blur = // input box blur - reset to original if value empty
function(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;
}
}
},
validateKey = // validate key
function(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;
},
setValueInRange = // constrain value within range
function(value, min, max)
{
if (value == '' || isNaN(value)) return min;
if (value > max) return max;
if (value < min) return min;
return value;
},
colorChanged =
function(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) : '');
},
destroy =
function()
{
// 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;
};
$.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: // 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)
function(init)
{
var $this = this,
r,
g,
b,
a,
h,
s,
v,
changeEvents = new Array(),
fireChangeEvents =
function(context)
{
for (var i = 0; i < changeEvents.length; i++) changeEvents[i].call($this, $this, context);
},
val =
function(name, value, context)
{
var set = value !== undefined;
if (!set)
{
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 ret == {} ? val.call($this, 'all') : ret;
break;
}
}
if (context != null && context == $this) return;
var changed = false;
if (name == null) name = '';
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 || '00000000');
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 = {}, rgb = false, hsv = false;
if (value.r !== undefined && !name.indexOf('r') == -1) name += 'r';
if (value.g !== undefined && !name.indexOf('g') == -1) name += 'g';
if (value.b !== undefined && !name.indexOf('b') == -1) name += 'b';
if (value.a !== undefined && !name.indexOf('a') == -1) name += 'a';
if (value.h !== undefined && !name.indexOf('h') == -1) name += 'h';
if (value.s !== undefined && !name.indexOf('s') == -1) name += 's';
if (value.v !== undefined && !name.indexOf('v') == -1) 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 ret = ColorMethods.rgbToHsv({ r: r, g: g, b: b });
h = ret.h;
s = ret.s;
v = ret.v;
}
else if (hsv)
{
h = h || 0;
s = s != null ? s : 100;
v = v != null ? v : 100;
var ret = ColorMethods.hsvToRgb({ h: h, s: s, v: v });
r = ret.r;
g = ret.g;
b = ret.b;
}
a = a != null ? a : 255;
fireChangeEvents.call($this, context || $this);
}
break;
}
},
bind =
function(callback)
{
if ($.isFunction(callback)) changeEvents.push(callback);
},
unbind =
function(callback)
{
if (!$.isFunction(callback)) return;
var i;
while ((i = $.inArray(callback, changeEvents)) != -1) changeEvents.splice(i, 1);
},
destroy =
function()
{
changeEvents = null;
}
$.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);
}
},
ColorMethods: // color conversion methods - make public to give use to external scripts
{
hexToRgba:
function(hex)
{
hex = this.validateHex(hex);
if (hex == '') return { r: null, g: null, b: null, a: null };
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(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(rgba)
{
return this.intToHex(rgba.r) + this.intToHex(rgba.g) + this.intToHex(rgba.b) + this.intToHex(rgba.a);
},
intToHex:
function(dec)
{
var result = (dec | 0).toString(16);
if (result.length == 1) result = ('0' + result);
return result.toLowerCase();
},
hexToInt:
function(hex)
{
return parseInt(hex, 16);
},
rgbToHsv:
function(rgb)
{
var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255, hsv = { h: 0, s: 0, v: 0 }, min = 0, max = 0, delta;
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;
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(hsv)
{
var rgb = { r: 0, g: 0, b: 0, a: 100 }, 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 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('<span class="jPicker"><span class="Icon"><span class="Color">&nbsp;</span><span class="Alpha">&nbsp;</span><span class="Image" title="Click To Open Color Picker">&nbsp;</span><span class="Container">&nbsp;</span></span></span>');
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
container = null,
colorMapDiv = null,
colorBarDiv = null,
colorMapL1 = null, // different layers of colorMap and colorBar
colorMapL2 = null,
colorMapL3 = null,
colorBarL1 = null,
colorBarL2 = null,
colorBarL3 = null,
colorBarL4 = null,
colorBarL5 = null,
colorBarL6 = null,
colorMap = null, // color maps
colorBar = null,
colorPicker = null,
elementStartX = null, // Used to record the starting css positions for dragging the control
elementStartY = null,
pageStartX = null, // Used to record the mousedown coordinates for dragging the control
pageStartY = null,
activePreview = null, // color boxes above the radio buttons
currentPreview = null,
okButton = null,
cancelButton = null,
grid = null, // preset colors grid
iconColor = null, // iconColor for popup icon
iconAlpha = null, // iconAlpha for popup icon
iconImage = null, // iconImage popup icon
moveBar = null, // drag bar
setColorMode = // set color mode and update visuals for the new color mode
function(colorMode)
{
var active = color.active, // local copies for YUI compressor
clientPath = images.clientPath,
hex = active.val('hex'),
rgbMap,
rgbBar;
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 ('Invalid Mode');
break;
}
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);
},
activeColorChanged = // Update color when user changes text values
function(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);
},
mapValueChanged = // user has dragged the ColorMap pointer
function(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;
}
},
colorBarValueChanged = // user has dragged the ColorBar slider
function(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;
}
},
positionMapAndBarArrows = // position map and bar arrows to match current color
function(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;
}
}
},
updatePreview =
function(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) { }
},
updateMapVisuals =
function(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 s = ui.val('s');
setAlpha.call($this, colorMapL2, 100 - (s != null ? s : 100));
break;
case 'v':
var v = ui.val('v');
setAlpha.call($this, colorMapL1, v != null ? v : 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));
},
updateBarVisuals =
function(ui)
{
switch (settings.color.mode)
{
case 'h':
var a = ui.val('a');
setAlpha.call($this, colorBarL5, Math.precision(((255 - (a || 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 hValue = 0, vValue = 0, rgba = ui.val('rgba');
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 a = ui.val('a');
setBG.call($this, colorBarDiv, ui.val('hex') || '000000');
setAlpha.call($this, colorBarL5, a != null ? 0 : 100);
setAlpha.call($this, colorBarL6, a != null ? 100 : 0);
break;
}
},
setBG =
function(el, c)
{
el.css({ backgroundColor: c && c.length == 6 && '#' + c || 'transparent' });
},
setImg =
function(img, src)
{
if (isLessThanIE7 && (src.indexOf('AlphaBar.png') != -1 || src.indexOf('Bars.png') != -1 || src.indexOf('Maps.png') != -1))
{
img.attr('pngSrc', src);
img.css({ backgroundImage: 'none', filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + '\', sizingMethod=\'scale\')' });
}
else img.css({ backgroundImage: 'url(\'' + src + '\')' });
},
setImgLoc =
function(img, y)
{
img.css({ top: y + 'px' });
},
setAlpha =
function(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.indexOf('AlphaBar.png') != -1 || src.indexOf('Bars.png') != -1 || src.indexOf('Maps.png') != -1))
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.indexOf('AlphaBar.png') != -1 || src.indexOf('Bars.png') != -1 || src.indexOf('Maps.png') != -1))
obj.css({ filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + '\', sizingMethod=\'scale\')' });
else obj.css({ opacity: '' });
}
else obj.css({ opacity: '' });
}
},
revertColor = // revert color to original color when opened
function()
{
color.active.val('ahex', color.current.val('ahex'));
},
commitColor = // commit the color changes
function()
{
color.current.val('ahex', color.active.val('ahex'));
},
radioClicked =
function(e)
{
$(this).parents('tbody:first').find('input:radio[value!="'+e.target.value+'"]').removeAttr('checked');
setColorMode.call($this, e.target.value);
},
currentClicked =
function()
{
revertColor.call($this);
},
cancelClicked =
function()
{
revertColor.call($this);
settings.window.expandable && hide.call($this);
$.isFunction(cancelCallback) && cancelCallback.call($this, color.active, cancelButton);
},
okClicked =
function()
{
commitColor.call($this);
settings.window.expandable && hide.call($this);
$.isFunction(commitCallback) && commitCallback.call($this, color.active, okButton);
},
iconImageClicked =
function()
{
show.call($this);
},
currentColorChanged =
function(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));
},
expandableColorChanged =
function(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'
});
},
moveBarMouseDown =
function(e)
{
var element = settings.window.element, // local copies for YUI compressor
page = settings.window.page;
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
},
documentMouseMove =
function(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;
},
documentMouseUp =
function(e)
{
$(document).unbind('mousemove', documentMouseMove).unbind('mouseup', documentMouseUp);
e.stopPropagation();
e.preventDefault();
return false;
},
quickPickClicked =
function(e)
{
e.preventDefault();
e.stopPropagation();
color.active.val('ahex', $(this).attr('title') || null, e.target);
return false;
},
commitCallback = $.isFunction($arguments[1]) && $arguments[1] || null,
liveCallback = $.isFunction($arguments[2]) && $arguments[2] || null,
cancelCallback = $.isFunction($arguments[3]) && $arguments[3] || null,
show =
function()
{
color.current.val('ahex', color.active.val('ahex'));
var attachIFrame = function()
{
if (!settings.window.expandable || $.support.boxModel) return;
var table = container.find('table:first');
container.before('<iframe/>');
container.prev().css({ width: table.width(), height: container.height(), opacity: 0, position: 'absolute', left: container.css("left"), top: container.css("top") });
};
if (settings.window.expandable)
{
$(document.body).children('div.jPicker.Container').css({zIndex:10});
container.css({zIndex:20});
}
switch (settings.window.effects.type)
{
case 'fade':
container.fadeIn(settings.window.effects.speed.show, attachIFrame);
break;
case 'slide':
container.slideDown(settings.window.effects.speed.show, attachIFrame);
break;
case 'show':
default:
container.show(settings.window.effects.speed.show, attachIFrame);
break;
}
},
hide =
function()
{
var removeIFrame = function()
{
if (settings.window.expandable) container.css({ zIndex: 10 });
if (!settings.window.expandable || $.support.boxModel) return;
container.prev().remove();
};
switch (settings.window.effects.type)
{
case 'fade':
container.fadeOut(settings.window.effects.speed.hide, removeIFrame);
break;
case 'slide':
container.slideUp(settings.window.effects.speed.hide, removeIFrame);
break;
case 'show':
default:
container.hide(settings.window.effects.speed.hide, removeIFrame);
break;
}
},
initialize =
function()
{
var win = settings.window,
popup = win.expandable ? $($this).next().find('.Container:first') : null;
container = win.expandable ? $('<div/>') : $($this);
container.addClass('jPicker Container');
if (win.expandable) container.hide();
container.get(0).onselectstart = function(event){ if (event.target.nodeName.toLowerCase() !== 'input') return false; };
// inject html source code - we are using a single table for this control - I know tables are considered bad, but it takes care of equal height columns and
// this control really is tabular data, so I believe it is the right move
var all = color.active.val('all');
if (win.alphaPrecision < 0) win.alphaPrecision = 0;
else if (win.alphaPrecision > 2) win.alphaPrecision = 2;
var controlHtml='<table class="jPicker" cellpadding="0" cellspacing="0"><tbody>' + (win.expandable ? '<tr><td class="Move" colspan="5">&nbsp;</td></tr>' : '') + '<tr><td rowspan="9"><h2 class="Title">' + (win.title || localization.text.title) + '</h2><div class="Map"><span class="Map1">&nbsp;</span><span class="Map2">&nbsp;</span><span class="Map3">&nbsp;</span><img src="' + images.clientPath + images.colorMap.arrow.file + '" class="Arrow"/></div></td><td rowspan="9"><div class="Bar"><span class="Map1">&nbsp;</span><span class="Map2">&nbsp;</span><span class="Map3">&nbsp;</span><span class="Map4">&nbsp;</span><span class="Map5">&nbsp;</span><span class="Map6">&nbsp;</span><img src="' + images.clientPath + images.colorBar.arrow.file + '" class="Arrow"/></div></td><td colspan="2" class="Preview"><div class="prev_div">' + localization.text.newColor + '<div class="color_preview"><span class="Active" title="' + localization.tooltips.colors.newColor + '">&nbsp;</span><span class="Current" title="' + localization.tooltips.colors.currentColor + '">&nbsp;</span></div></div>' + localization.text.currentColor + '</td><td rowspan="9" class="Button"><input type="button" class="Ok" value="' + localization.text.ok + '" title="' + localization.tooltips.buttons.ok + '"/><input type="button" class="Cancel" value="' + localization.text.cancel + '" title="' + localization.tooltips.buttons.cancel + '"/><div class="Grid">&nbsp;</div></td></tr><tr class="Hue"><td class="Radio"><label title="' + localization.tooltips.hue.radio + '"><input type="radio" value="h"' + (settings.color.mode == 'h' ? ' checked="checked"' : '') + '/>H:</label></td><td class="Text"><input type="text" maxlength="3" value="' + (all != null ? all.h : '') + '" title="' + localization.tooltips.hue.textbox + '"/>&nbsp;º</td></tr><tr class="Saturation"><td class="Radio"><label title="' + localization.tooltips.saturation.radio + '"><input type="radio" value="s"' + (settings.color.mode == 's' ? ' checked="checked"' : '') + '/>S:</label></td><td class="Text"><input type="text" maxlength="3" value="' + (all != null ? all.s : '') + '" title="' + localization.tooltips.saturation.textbox + '"/>&nbsp;%</td></tr><tr class="Value"><td class="Radio"><label title="' + localization.tooltips.value.radio + '"><input type="radio" value="v"' + (settings.color.mode == 'v' ? ' checked="checked"' : '') + '/>V:</label></td><td class="Text"><input type="text" maxlength="3" value="' + (all != null ? all.v : '') + '" title="' + localization.tooltips.value.textbox + '"/>&nbsp;%<br/><br/></td></tr><tr class="Red"><td class="Radio"><label title="' + localization.tooltips.red.radio + '"><input type="radio" value="r"' + (settings.color.mode == 'r' ? ' checked="checked"' : '') + '/>R:</label></td><td class="Text"><input type="text" maxlength="3" value="' + (all != null ? all.r : '') + '" title="' + localization.tooltips.red.textbox + '"/></td></tr><tr class="Green"><td class="Radio"><label title="' + localization.tooltips.green.radio + '"><input type="radio" value="g"' + (settings.color.mode == 'g' ? ' checked="checked"' : '') + '/>G:</label></td><td class="Text"><input type="text" maxlength="3" value="' + (all != null ? all.g : '') + '" title="' + localization.tooltips.green.textbox + '"/></td></tr><tr class="Blue"><td class="Radio"><label title="' + localization.tooltips.blue.radio + '"><input type="radio" value="b"' + (settings.color.mode == 'b' ? ' checked="checked"' : '') + '/>B:</label></td><td class="Text"><input type="text" maxlength="3" value="' + (all != null ? all.b : '') + '" title="' + localization.tooltips.blue.textbox + '"/></td></tr><tr class="Alpha"><td class="Radio">' + (win.alphaSupport ? '<label title="' + localization.tooltips.alpha.radio + '"><input type="radio" value="a"' + (settings.color.mode == 'a' ? ' checked="checked"' : '') + '/>A:</label>' : '&nbsp;') + '</td><td class="Text">' + (win.alphaSupport ? '<input type="text" maxlength="' + (3 + win.alphaPrecision) + '" value="' + (all != null ? Math.precision((all.a * 100) / 255, win.alphaPrecision) : '') + '" titl
if (win.expandable)
{
container.html(controlHtml);
if($(document.body).children('div.jPicker.Container').length==0)$(document.body).prepend(container);
else $(document.body).children('div.jPicker.Container:last').after(container);
container.mousedown(
function()
{
$(document.body).children('div.jPicker.Container').css({zIndex:10});
container.css({zIndex:20});
});
container.css( // positions must be set and display set to absolute before source code injection or IE will size the container to fit the window
{
left:
win.position.x == 'left' ? (popup.offset().left - 530 - (win.position.y == 'center' ? 25 : 0)) + 'px' :
win.position.x == 'center' ? (popup.offset().left - 260) + 'px' :
win.position.x == 'right' ? (popup.offset().left - 10 + (win.position.y == 'center' ? 25 : 0)) + 'px' :
win.position.x == 'screenCenter' ? (($(document).width() >> 1) - 260) + 'px' : (popup.offset().left + parseInt(win.position.x)) + 'px',
position: 'absolute',
top: win.position.y == 'top' ? (popup.offset().top - 312) + 'px' :
win.position.y == 'center' ? (popup.offset().top - 156) + 'px' :
win.position.y == 'bottom' ? (popup.offset().top + 25) + 'px' : (popup.offset().top + parseInt(win.position.y)) + 'px'
});
}
else
{
container = $($this);
container.html(controlHtml);
}
// initialize the objects to the source code just injected
var tbody = container.find('tbody:first');
colorMapDiv = tbody.find('div.Map:first');
colorBarDiv = tbody.find('div.Bar:first');
var MapMaps = colorMapDiv.find('span'),
BarMaps = colorBarDiv.find('span');
colorMapL1 = MapMaps.filter('.Map1:first');
colorMapL2 = MapMaps.filter('.Map2:first');
colorMapL3 = MapMaps.filter('.Map3:first');
colorBarL1 = BarMaps.filter('.Map1:first');
colorBarL2 = BarMaps.filter('.Map2:first');
colorBarL3 = BarMaps.filter('.Map3:first');
colorBarL4 = BarMaps.filter('.Map4:first');
colorBarL5 = BarMaps.filter('.Map5:first');
colorBarL6 = BarMaps.filter('.Map6:first');
// create color pickers and maps
colorMap = new Slider(colorMapDiv,
{
map:
{
width: images.colorMap.width,
height: images.colorMap.height
},
arrow:
{
image: images.clientPath + images.colorMap.arrow.file,
width: images.colorMap.arrow.width,
height: images.colorMap.arrow.height
}
});
colorMap.bind(mapValueChanged);
colorBar = new Slider(colorBarDiv,
{
map:
{
width: images.colorBar.width,
height: images.colorBar.height
},
arrow:
{
image: images.clientPath + images.colorBar.arrow.file,
width: images.colorBar.arrow.width,
height: images.colorBar.arrow.height
}
});
colorBar.bind(colorBarValueChanged);
colorPicker = new ColorValuePicker(tbody, color.active, win.expandable && win.bindToInput ? win.input : null, win.alphaPrecision);
var hex = all != null ? all.hex : null,
preview = tbody.find('.Preview'),
button = tbody.find('.Button');
activePreview = preview.find('.Active:first').css({ backgroundColor: hex && '#' + hex || 'transparent' });
currentPreview = preview.find('.Current:first').css({ backgroundColor: hex && '#' + hex || 'transparent' }).bind('click', currentClicked);
setAlpha.call($this, currentPreview, Math.precision(color.current.val('a') * 100) / 255, 4);
okButton = button.find('.Ok:first').bind('click touchstart', okClicked);
cancelButton = button.find('.Cancel:first').bind('click touchstart', cancelClicked);
grid = button.find('.Grid:first');
setTimeout(
function()
{
setImg.call($this, colorMapL1, images.clientPath + 'Maps.png');
setImg.call($this, colorMapL2, images.clientPath + 'Maps.png');
setImg.call($this, colorMapL3, images.clientPath + 'map-opacity.png');
setImg.call($this, colorBarL1, images.clientPath + 'Bars.png');
setImg.call($this, colorBarL2, images.clientPath + 'Bars.png');
setImg.call($this, colorBarL3, images.clientPath + 'Bars.png');
setImg.call($this, colorBarL4, images.clientPath + 'Bars.png');
setImg.call($this, colorBarL5, images.clientPath + 'bar-opacity.png');
setImg.call($this, colorBarL6, images.clientPath + 'AlphaBar.png');
setImg.call($this, preview.find('div:last'), images.clientPath + 'preview-opacity.png');
}, 0);
tbody.find('td.Radio input').bind('click touchstart', radioClicked);
// initialize quick list
if (color.quickList && color.quickList.length > 0)
{
var html = '';
for (i = 0; i < color.quickList.length; i++)
{
/* if default colors are hex strings, change them to color objects */
if ((typeof (color.quickList[i])).toString().toLowerCase() == 'string') color.quickList[i] = new Color({ hex: color.quickList[i] });
var alpha = color.quickList[i].val('a');
var ahex = color.quickList[i].val('ahex');
if (!win.alphaSupport && ahex) ahex = ahex.substring(0, 6) + 'ff';
var quickHex = color.quickList[i].val('hex');
html+='<span class="QuickColor"' + (ahex && ' title="#' + ahex + '"' || '') + ' style="background-color:' + (quickHex && '#' + quickHex || '') + ';' + (quickHex ? '' : 'background-image:url(' + images.clientPath + 'NoColor.svg)') + (win.alphaSupport && alpha && alpha < 255 ? ';opacity:' + Math.precision(alpha / 255, 4) + ';filter:Alpha(opacity=' + Math.precision(alpha / 2.55, 4) + ')' : '') + '">&nbsp;</span>';
}
setImg.call($this, grid, images.clientPath + 'bar-opacity.png');
grid.html(html);
grid.find('.QuickColor').click(quickPickClicked);
}
setColorMode.call($this, settings.color.mode);
color.active.bind(activeColorChanged);
$.isFunction(liveCallback) && color.active.bind(liveCallback);
color.current.bind(currentColorChanged);
// bind to input
if (win.expandable)
{
$this.icon = popup.parents('.Icon:first');
iconColor = $this.icon.find('.Color:first').css({ backgroundColor: hex && '#' + hex || 'transparent' });
iconAlpha = $this.icon.find('.Alpha:first');
setImg.call($this, iconAlpha, images.clientPath + 'bar-opacity.png');
setAlpha.call($this, iconAlpha, Math.precision(((255 - (all != null ? all.a : 0)) * 100) / 255, 4));
iconImage = $this.icon.find('.Image:first').css(
{
backgroundImage: 'url(\'' + images.clientPath + images.picker.file + '\')'
}).bind('click', iconImageClicked);
if (win.bindToInput&&win.updateInputColor)
win.input.css(
{
backgroundColor: hex && '#' + hex || 'transparent',
color: all == null || all.v > 75 ? '#000000' : '#ffffff'
});
moveBar = tbody.find('.Move:first').bind('mousedown', moveBarMouseDown);
color.active.bind(expandableColorChanged);
}
else show.call($this);
},
destroy =
function()
{
container.find('td.Radio input touchstart').unbind('click', radioClicked);
currentPreview.unbind('click touchstart', currentClicked);
cancelButton.unbind('click touchstart', cancelClicked);
okButton.unbind('click touchstart', okClicked);
if (settings.window.expandable)
{
iconImage.unbind('click', iconImageClicked);
moveBar.unbind('mousedown', moveBarMouseDown);
$this.icon = null;
}
container.find('.QuickColor').unbind('click', quickPickClicked);
colorMapDiv = null;
colorBarDiv = null;
colorMapL1 = null;
colorMapL2 = null;
colorMapL3 = null;
colorBarL1 = null;
colorBarL2 = null;
colorBarL3 = null;
colorBarL4 = null;
colorBarL5 = null;
colorBarL6 = null;
colorMap.destroy();
colorMap = null;
colorBar.destroy();
colorBar = null;
colorPicker.destroy();
colorPicker = null;
activePreview = null;
currentPreview = null;
okButton = null;
cancelButton = null;
grid = null;
commitCallback = null;
cancelCallback = null;
liveCallback = null;
container.html('');
for (i = 0; i < List.length; i++) if (List[i] == $this) List.splice(i, 1);
},
images = settings.images, // local copies for YUI compressor
localization = settings.localization,
color =
{
active: (typeof(settings.color.active)).toString().toLowerCase() == 'string' ? new Color({ ahex: !settings.window.alphaSupport && settings.color.active ? settings.color.active.substring(0, 6) + 'ff' : settings.color.active }) : new Color({ ahex: !settings.window.alphaSupport && settings.color.active.val('ahex') ? settings.color.active.val('ahex').substring(0, 6) + 'ff' : settings.color.active.val('ahex') }),
current: (typeof(settings.color.active)).toString().toLowerCase() == 'string' ? new Color({ ahex: !settings.window.alphaSupport && settings.color.active ? settings.color.active.substring(0, 6) + 'ff' : settings.color.active }) : new Color({ ahex: !settings.window.alphaSupport && settings.color.active.val('ahex') ? settings.color.active.val('ahex').substring(0, 6) + 'ff' : settings.color.active.val('ahex') }),
quickList: settings.color.quickList
};
$.extend(true, $this, // public properties, methods, and callbacks
{
commitCallback: commitCallback, // commitCallback function can be overridden to return the selected color to a method you specify when the user clicks "OK"
liveCallback: liveCallback, // liveCallback function can be overridden to return the selected color to a method you specify in live mode (continuous update)
cancelCallback: cancelCallback, // cancelCallback function can be overridden to a method you specify when the user clicks "Cancel"
color: color,
show: show,
hide: hide,
destroy: destroy // destroys this control entirely, removing all events and objects, and removing itself from the List
});
List.push($this);
setTimeout(
function()
{
initialize.call($this);
}, 0);
});
};
$.fn.jPicker.defaults = /* jPicker defaults - you can change anything in this section (such as the clientPath to your images) without fear of breaking the program */
{
window:
{
title: null, /* any title for the jPicker window itself - displays "Drag Markers To Pick A Color" if left null */
effects:
{
type: 'slide', /* effect used to show/hide an expandable picker. Acceptable values "slide", "show", "fade" */
speed:
{
show: 'slow', /* duration of "show" effect. Acceptable values are "fast", "slow", or time in ms */
hide: 'fast' /* duration of "hide" effect. Acceptable values are "fast", "slow", or time in ms */
}
},
position:
{
x: 'screenCenter', /* acceptable values "left", "center", "right", "screenCenter", or relative px value */
y: 'top' /* acceptable values "top", "bottom", "center", or relative px value */
},
expandable: false, /* default to large static picker - set to true to make an expandable picker (small icon with popup) - set automatically when binded to input element */
liveUpdate: true, /* set false if you want the user to have to click "OK" before the binded input box updates values (always "true" for expandable picker) */
alphaSupport: false, /* set to true to enable alpha picking */
alphaPrecision: 0, /* set decimal precision for alpha percentage display - hex codes do not map directly to percentage integers - range 0-2 */
updateInputColor: true /* set to false to prevent binded input colors from changing */
},
color:
{
mode: 'h', /* acceptabled values "h" (hue), "s" (saturation), "v" (value), "r" (red), "g" (green), "b" (blue), "a" (alpha) */
active: new Color({ ahex: '#ffcc00ff' }), /* acceptable values are any declared $.jPicker.Color object or string HEX value (e.g. #ffc000) WITH OR WITHOUT the "#" prefix */
quickList: /* the quick pick color list */
[
new Color({ h: 360, s: 33, v: 100 }), /* acceptable values are any declared $.jPicker.Color object or string HEX value (e.g. #ffc000) WITH OR WITHOUT the "#" prefix */
new Color({ h: 360, s: 66, v: 100 }),
new Color({ h: 360, s: 100, v: 100 }),
new Color({ h: 360, s: 100, v: 75 }),
new Color({ h: 360, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 100 }),
new Color({ h: 30, s: 33, v: 100 }),
new Color({ h: 30, s: 66, v: 100 }),
new Color({ h: 30, s: 100, v: 100 }),
new Color({ h: 30, s: 100, v: 75 }),
new Color({ h: 30, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 90 }),
new Color({ h: 60, s: 33, v: 100 }),
new Color({ h: 60, s: 66, v: 100 }),
new Color({ h: 60, s: 100, v: 100 }),
new Color({ h: 60, s: 100, v: 75 }),
new Color({ h: 60, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 80 }),
new Color({ h: 90, s: 33, v: 100 }),
new Color({ h: 90, s: 66, v: 100 }),
new Color({ h: 90, s: 100, v: 100 }),
new Color({ h: 90, s: 100, v: 75 }),
new Color({ h: 90, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 70 }),
new Color({ h: 120, s: 33, v: 100 }),
new Color({ h: 120, s: 66, v: 100 }),
new Color({ h: 120, s: 100, v: 100 }),
new Color({ h: 120, s: 100, v: 75 }),
new Color({ h: 120, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 60 }),
new Color({ h: 150, s: 33, v: 100 }),
new Color({ h: 150, s: 66, v: 100 }),
new Color({ h: 150, s: 100, v: 100 }),
new Color({ h: 150, s: 100, v: 75 }),
new Color({ h: 150, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 50 }),
new Color({ h: 180, s: 33, v: 100 }),
new Color({ h: 180, s: 66, v: 100 }),
new Color({ h: 180, s: 100, v: 100 }),
new Color({ h: 180, s: 100, v: 75 }),
new Color({ h: 180, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 40 }),
new Color({ h: 210, s: 33, v: 100 }),
new Color({ h: 210, s: 66, v: 100 }),
new Color({ h: 210, s: 100, v: 100 }),
new Color({ h: 210, s: 100, v: 75 }),
new Color({ h: 210, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 30 }),
new Color({ h: 240, s: 33, v: 100 }),
new Color({ h: 240, s: 66, v: 100 }),
new Color({ h: 240, s: 100, v: 100 }),
new Color({ h: 240, s: 100, v: 75 }),
new Color({ h: 240, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 20 }),
new Color({ h: 270, s: 33, v: 100 }),
new Color({ h: 270, s: 66, v: 100 }),
new Color({ h: 270, s: 100, v: 100 }),
new Color({ h: 270, s: 100, v: 75 }),
new Color({ h: 270, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 10 }),
new Color({ h: 300, s: 33, v: 100 }),
new Color({ h: 300, s: 66, v: 100 }),
new Color({ h: 300, s: 100, v: 100 }),
new Color({ h: 300, s: 100, v: 75 }),
new Color({ h: 300, s: 100, v: 50 }),
new Color({ h: 180, s: 0, v: 0 }),
new Color({ h: 330, s: 33, v: 100 }),
new Color({ h: 330, s: 66, v: 100 }),
new Color({ h: 330, s: 100, v: 100 }),
new Color({ h: 330, s: 100, v: 75 }),
new Color({ h: 330, s: 100, v: 50 }),
new Color()
]
},
images:
{
clientPath: '/jPicker/images/', /* Path to image files */
colorMap:
{
width: 256,
height: 256,
arrow:
{
file: 'mappoint.gif', /* ColorMap arrow icon */
width: 15,
height: 15
}
},
colorBar:
{
width: 20,
height: 256,
arrow:
{
file: 'rangearrows.svg', /* ColorBar arrow icon */
width: 20,
height: 7
}
},
picker:
{
file: 'picker.gif', /* Color Picker icon */
width: 25,
height: 24
}
},
localization: /* alter these to change the text presented by the picker (e.g. different language) */
{
text:
{
title: 'Drag Markers To Pick A Color',
newColor: 'new',
currentColor: 'current',
ok: 'OK',
cancel: 'Cancel'
},
tooltips:
{
colors:
{
newColor: 'New Color - Press &ldquo;OK&rdquo; To Commit',
currentColor: 'Click To Revert To Original Color'
},
buttons:
{
ok: 'Commit To This Color Selection',
cancel: 'Cancel And Revert To Original Color'
},
hue:
{
radio: 'Set To &ldquo;Hue&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Hue&rdquo; Value (0-360&deg;)'
},
saturation:
{
radio: 'Set To &ldquo;Saturation&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Saturation&rdquo; Value (0-100%)'
},
value:
{
radio: 'Set To &ldquo;Value&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Value&rdquo; Value (0-100%)'
},
red:
{
radio: 'Set To &ldquo;Red&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Red&rdquo; Value (0-255)'
},
green:
{
radio: 'Set To &ldquo;Green&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Green&rdquo; Value (0-255)'
},
blue:
{
radio: 'Set To &ldquo;Blue&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Blue&rdquo; Value (0-255)'
},
alpha:
{
radio: 'Set To &ldquo;Alpha&rdquo; Color Mode',
textbox: 'Enter A &ldquo;Alpha&rdquo; Value (0-100)'
},
hex:
{
textbox: 'Enter A &ldquo;Hex&rdquo; Color Value (#000000-#ffffff)',
alpha: 'Enter A &ldquo;Alpha&rdquo; Value (#00-#ff)'
}
}
}
};
})(jQuery, '1.1.6');
/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.6
*
* Requires: 1.2.2+
*/
(function($) {
var types = ['DOMMouseScroll', 'mousewheel'];
if ($.event.fixHooks) {
for ( var i=types.length; i; ) {
$.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
}
}
$.event.special.mousewheel = {
setup: function() {
if ( this.addEventListener ) {
for ( var i=types.length; i; ) {
this.addEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = handler;
}
},
teardown: function() {
if ( this.removeEventListener ) {
for ( var i=types.length; i; ) {
this.removeEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = null;
}
}
};
$.fn.extend({
mousewheel: function(fn) {
return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
},
unmousewheel: function(fn) {
return this.unbind("mousewheel", fn);
}
});
function handler(event) {
var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
event = $.event.fix(orgEvent);
event.type = "mousewheel";
// Old school scrollwheel delta
if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
// New school multidimensional scroll (touchpads) deltas
deltaY = delta;
// Gecko
if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
deltaY = 0;
deltaX = -1*delta;
}
// Webkit
if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
// Add event and delta to the front of the arguments
args.unshift(event, delta, deltaX, deltaY);
return ($.event.dispatch || $.event.handle).apply(this, args);
}
})(jQuery);
/*
* ext-eyedropper.js
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Jeff Schiller
*
*/
// Dependencies:
// 1) jQuery
// 2) history.js
// 3) svg_editor.js
// 4) svgcanvas.js
methodDraw.addExtension("eyedropper", function(S) {
var svgcontent = S.svgcontent,
svgns = "http://www.w3.org/2000/svg",
svgdoc = S.svgroot.parentNode.ownerDocument,
svgCanvas = methodDraw.canvas,
ChangeElementCommand = svgedit.history.ChangeElementCommand,
addToHistory = function(cmd) { svgCanvas.undoMgr.addCommandToHistory(cmd); },
currentStyle = {fillPaint: "red", fillOpacity: 1.0,
strokePaint: "black", strokeOpacity: 1.0,
strokeWidth: 5, strokeDashArray: null,
opacity: 1.0,
strokeLinecap: 'butt',
strokeLinejoin: 'miter'
};
function getStyle(opts) {
// if we are in eyedropper mode, we don't want to disable the eye-dropper tool
var mode = svgCanvas.getMode();
if (mode == "eyedropper") return;
var tool = $('#tool_eyedropper');
}
var getPaint = function(color, opac, type) {
// update the editor's fill paint
var opts = null;
if (color.indexOf("url(#") === 0) {
var refElem = svgCanvas.getRefElem(color);
if(refElem) {
refElem = refElem.cloneNode(true);
} else {
refElem = $("#" + type + "_color defs *")[0];
}
opts = { alpha: opac };
opts[refElem.tagName] = refElem;
}
else if (color.indexOf("#") === 0) {
opts = {
alpha: opac,
solidColor: color.substr(1)
};
}
else {
opts = {
alpha: opac,
solidColor: 'none'
};
}
return new $.jGraduate.Paint(opts);
};
return {
name: "eyedropper",
svgicons: "extensions/eyedropper-icon.xml",
buttons: [{
id: "tool_eyedropper",
type: "mode",
title: "Eye Dropper Tool",
position: 8,
key: "I",
icon: "extensions/eyedropper.png",
events: {
"click": function() {
svgCanvas.setMode("eyedropper");
}
}
}],
mouseDown: function(opts) {
var mode = svgCanvas.getMode();
var e = opts.event;
var target = (e.target.id === "svgroot") ? document.getElementById('canvas_background') : e.target;
if (mode == "eyedropper" && target) {
currentStyle.fillPaint = target.getAttribute("fill") || "white";
currentStyle.fillOpacity = target.getAttribute("fill-opacity") || 1.0;
currentStyle.strokePaint = target.getAttribute("stroke") || 'none';
currentStyle.strokeOpacity = target.getAttribute("stroke-opacity") || 1.0;
currentStyle.strokeWidth = target.getAttribute("stroke-width");
currentStyle.strokeDashArray = target.getAttribute("stroke-dasharray");
currentStyle.strokeLinecap = target.getAttribute("stroke-linecap");
currentStyle.strokeLinejoin = target.getAttribute("stroke-linejoin");
currentStyle.opacity = target.getAttribute("opacity") || 1.0;
opts.selectedElements = opts.selectedElements.filter(Boolean)
if (!opts.selectedElements.length) { //nothing selected, just update colors
var fill = getPaint(currentStyle.fillPaint, currentStyle.fillOpacity*100, "fill");
var stroke = getPaint(currentStyle.strokePaint, currentStyle.strokeOpacity*100, "stroke");
methodDraw.paintBox.fill.setPaint(fill)
methodDraw.paintBox.stroke.setPaint(stroke)
return;
}
if ($.inArray(opts.selectedElements.nodeName, ['g', 'use']) == -1) {
var changes = {};
var change = function(elem, attrname, newvalue) {
changes[attrname] = elem.getAttribute(attrname);
elem.setAttribute(attrname, newvalue);
};
var batchCmd = new S.BatchCommand();
opts.selectedElements.forEach(function(element){
if (currentStyle.fillPaint) change(element, "fill", currentStyle.fillPaint);
if (currentStyle.fillOpacity) change(element, "fill-opacity", currentStyle.fillOpacity);
if (currentStyle.strokePaint) change(element, "stroke", currentStyle.strokePaint);
if (currentStyle.strokeOpacity) change(element, "stroke-opacity", currentStyle.strokeOpacity);
if (currentStyle.strokeWidth) change(element, "stroke-width", currentStyle.strokeWidth);
if (currentStyle.strokeDashArray) change(element, "stroke-dasharray", currentStyle.strokeDashArray);
if (currentStyle.opacity) change(element, "opacity", currentStyle.opacity);
if (currentStyle.strokeLinecap) change(element, "stroke-linecap", currentStyle.strokeLinecap);
if (currentStyle.strokeLinejoin) change(element, "stroke-linejoin", currentStyle.strokeLinejoin);
batchCmd.addSubCommand(new ChangeElementCommand(element, changes));
changes = {};
});
var fill = getPaint(currentStyle.fillPaint, currentStyle.fillOpacity*100, "fill")
var stroke = getPaint(currentStyle.strokePaint, currentStyle.strokeOpacity*100, "stroke")
methodDraw.paintBox.fill.update(true)
methodDraw.paintBox.stroke.update(true)
addToHistory(batchCmd);
}
}
}
};
});
/*
* ext-shapes.js
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Christian Tzurcanu
* Copyright(c) 2010 Alexis Deveria
*
*/
methodDraw.addExtension("shapes", function() {
var current_d, cur_shape_id;
var canv = methodDraw.canvas;
var cur_shape;
var start_x, start_y;
var svgroot = canv.getRootElem();
var lastBBox = {};
// This populates the category list
var categories = {
basic: 'Basic',
object: 'Objects',
symbol: 'Symbols',
arrow: 'Arrows',
flowchart: 'Flowchart',
nature: 'Nature',
game: 'Cards & Chess',
dialog_balloon: 'Dialog balloons',
music: 'Music',
weather: 'Weather &amp; Time',
ui: 'User Interface',
social: 'Social Web'
};
var library = {
'basic': {
data: {
"star_points_5": "m1,116.58409l113.82668,0l35.17332,-108.13487l35.17334,108.13487l113.82666,0l-92.08755,66.83026l35.17514,108.13487l-92.08759,-66.83208l-92.08757,66.83208l35.17515,-108.13487l-92.08758,-66.83026z",
'donut': 'm1,150l0,0c0,-82.29042 66.70958,-149 149,-149l0,0c39.51724,0 77.41599,15.69816 105.35889,43.64108c27.94293,27.94293 43.64111,65.84165 43.64111,105.35892l0,0c0,82.29041 -66.70958,149 -149,149l0,0c-82.29041,0 -149,-66.70959 -149,-149zm74.5,0l0,0c0,41.1452 33.35481,74.5 74.5,74.5c41.14522,0 74.5,-33.3548 74.5,-74.5c0,-41.1452 -33.3548,-74.5 -74.5,-74.5l0,0c-41.14519,0 -74.5,33.35481 -74.5,74.5z',
"triangle": "m1,280.375l149,-260.75l149,260.75z",
"right_triangle": "m1,299l0,-298l298,298z",
"diamond": "m1,150l149,-149l149,149l-149,149l-149,-149z",
"pentagon": "m1.00035,116.97758l148.99963,-108.4053l148.99998,108.4053l-56.91267,175.4042l-184.1741,0l-56.91284,-175.4042z",
"hexagon": "m1,149.99944l63.85715,-127.71428l170.28572,0l63.85713,127.71428l-63.85713,127.71428l-170.28572,0l-63.85715,-127.71428z",
"septagon1": "m0.99917,191.06511l29.51249,-127.7108l119.48833,-56.83673l119.48836,56.83673l29.51303,127.7108l-82.69087,102.41679l-132.62103,0l-82.69031,-102.41679z",
"heptagon": "m1,88.28171l87.28172,-87.28171l123.43653,0l87.28172,87.28171l0,123.43654l-87.28172,87.28172l-123.43653,0l-87.28172,-87.28172l0,-123.43654z",
"decagon": "m1,150.00093l28.45646,-88.40318l74.49956,-54.63682l92.08794,0l74.50002,54.63682l28.45599,88.40318l-28.45599,88.40318l-74.50002,54.63681l-92.08794,0l-74.49956,-54.63681l-28.45646,-88.40318z",
"dodecagon": "m1,110.07421l39.92579,-69.14842l69.14842,-39.92579l79.85159,0l69.14842,39.92579l39.92578,69.14842l0,79.85159l-39.92578,69.14842l-69.14842,39.92578l-79.85159,0l-69.14842,-39.92578l-39.92579,-69.14842l0,-79.85159z",
"trapezoid": "m1,299l55.875,-298l186.25001,0l55.87498,298z",
"dialog_balloon_1": "m0.99786,35.96579l0,0c0,-19.31077 15.28761,-34.96524 34.14583,-34.96524l15.52084,0l0,0l74.50001,0l139.68748,0c9.05606,0 17.74118,3.68382 24.14478,10.24108c6.40356,6.55726 10.00107,15.45081 10.00107,24.72416l0,87.41311l0,0l0,52.44785l0,0c0,19.31078 -15.2876,34.96524 -34.14584,34.96524l-139.68748,0l-97.32507,88.90848l22.82506,-88.90848l-15.52084,0c-18.85822,0 -34.14583,-15.65446 -34.14583,-34.96524l0,0l0,-52.44785l0,0z",
'heart': 'm150,73c61,-175 300,0 0,225c-300,-225 -61,-400 0,-225z',
"cylinder": "m299.0007,83.77844c0,18.28676 -66.70958,33.11111 -149.00002,33.11111m149.00002,-33.11111l0,0c0,18.28676 -66.70958,33.11111 -149.00002,33.11111c-82.29041,0 -148.99997,-14.82432 -148.99997,-33.11111m0,0l0,0c0,-18.28674 66.70956,-33.1111 148.99997,-33.1111c82.29044,0 149.00002,14.82436 149.00002,33.1111l0,132.44449c0,18.28674 -66.70958,33.11105 -149.00002,33.11105c-82.29041,0 -148.99997,-14.82431 -148.99997,-33.11105z",
"arrow_up": "m1.49805,149.64304l148.50121,-148.00241l148.50121,148.00241l-74.25061,0l0,148.71457l-148.5012,0l0,-148.71457z",
"arrow_u_turn": "m1.00059,299.00055l0,-167.62497l0,0c0,-72.00411 58.37087,-130.37499 130.375,-130.37499l0,0l0,0c34.57759,0 67.73898,13.7359 92.18906,38.18595c24.45006,24.45005 38.18593,57.61144 38.18593,92.18904l0,18.625l37.24997,0l-74.49995,74.50002l-74.50002,-74.50002l37.25,0l0,-18.625c0,-30.8589 -25.0161,-55.87498 -55.87498,-55.87498l0,0l0,0c-30.85892,0 -55.875,25.01608 -55.875,55.87498l0,167.62497z",
"arrow_left_up": "m0.99865,224.5l74.50004,-74.5l0,37.25l111.74991,0l0,-111.75l-37.25,0l74.5,-74.5l74.5,74.5l-37.25,0l0,186.25l-186.24989,0l0,37.25l-74.50005,-74.5z",
"plaque": "m-0.00197,49.94376l0,0c27.5829,0 49.94327,-22.36036 49.94327,-49.94327l199.76709,0l0,0c0,27.5829 22.36037,49.94327 49.94325,49.94327l0,199.7671l0,0c-27.58289,0 -49.94325,22.36034 -49.94325,49.94325l-199.76709,0c0,-27.58292 -22.36037,-49.94325 -49.94327,-49.94325z",
"page": "m249.3298,298.99744l9.9335,-39.73413l39.73413,-9.93355l-49.66763,49.66768l-248.33237,0l0,-298.00001l298.00001,0l0,248.33234",
"cross": "m0.99844,99.71339l98.71494,0l0,-98.71495l101.26279,0l0,98.71495l98.71495,0l0,101.2628l-98.71495,0l0,98.71494l-101.26279,0l0,-98.71494l-98.71494,0z",
"divide": "m150,0.99785l0,0c25.17819,0 45.58916,20.41097 45.58916,45.58916c0,25.17821 -20.41096,45.58916 -45.58916,45.58916c-25.17822,0 -45.58916,-20.41093 -45.58916,-45.58916c0,-25.1782 20.41093,-45.58916 45.58916,-45.58916zm0,296.25203c-25.17822,0 -45.58916,-20.41095 -45.58916,-45.58917c0,-25.17819 20.41093,-45.58916 45.58916,-45.58916c25.17819,0 45.58916,20.41096 45.58916,45.58916c0,25.17822 -20.41096,45.58917 -45.58916,45.58917zm-134.06754,-193.71518l268.13507,0l0,91.17833l-268.13507,0z",
"minus": "m0.99887,102.39503l297.49445,0l0,95.2112l-297.49445,0z",
"times": "m1.00089,73.36786l72.36697,-72.36697l76.87431,76.87368l76.87431,-76.87368l72.36765,72.36697l-76.87433,76.87431l76.87433,76.87431l-72.36765,72.36765l-76.87431,-76.87433l-76.87431,76.87433l-72.36697,-72.36765l76.87368,-76.87431l-76.87368,-76.87431z"
},
buttons: []
}
};
var cur_lib = library.basic;
var mode_id = 'shapelib';
function loadIcons() {
$('#shape_buttons').empty();
// Show lib ones
$('#shape_buttons').append(cur_lib.buttons);
}
function loadLibrary(cat_id) {
var lib = library[cat_id];
if(!lib) {
$('#shape_buttons').html('Loading...');
$.getJSON('shapelib/' + cat_id + '.json', function(result, textStatus) {
cur_lib = library[cat_id] = {
data: result.data,
size: result.size,
fill: result.fill
}
makeButtons(cat_id, result);
loadIcons();
});
return;
}
cur_lib = lib;
if(!lib.buttons.length) makeButtons(cat_id, lib);
loadIcons();
}
function makeButtons(cat, shapes) {
var size = cur_lib.size || 300;
var fill = cur_lib.fill || false;
var off = size * .05;
var vb = [-off, -off, size + off*2, size + off*2].join(' ');
var stroke = fill ? 0: (size/30);
var shape_icon = new DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg"><svg viewBox="' + vb + '"><path fill="#333" stroke="transparent" stroke-width="' + stroke + '" /><\/svg><\/svg>',
'text/xml');
var width = 40;
var height = 40;
shape_icon.documentElement.setAttribute('width', width);
shape_icon.documentElement.setAttribute('height', height);
var svg_elem = $(document.importNode(shape_icon.documentElement,true));
var data = shapes.data;
cur_lib.buttons = [];
for(var id in data) {
var path_d = data[id];
var icon = svg_elem.clone();
icon.find('path').attr('d', path_d);
var icon_btn = icon.wrap('<div class="tool_button">').parent().attr({
id: mode_id + '_' + id,
title: id
});
// Store for later use
cur_lib.buttons.push(icon_btn[0]);
}
}
return {
svgicons: "extensions/ext-shapes.xml",
buttons: [{
id: "tool_shapelib",
type: "mode_flyout", // _flyout
position: 6,
title: "Shape library",
icon: "extensions/ext-shapes.png",
events: {
"click": function() {
canv.setMode(mode_id);
}
}
}],
callback: function() {
var btn_div = $('<div id="shape_buttons">');
$('#tools_shapelib > *').wrapAll(btn_div);
var shower = $('#tools_shapelib_show');
loadLibrary('basic');
// Do mouseup on parent element rather than each button
$('#shape_buttons').mouseup(function(evt) {
var btn = $(evt.target).closest('div.tool_button');
if(!btn.length) return;
var copy = btn.children().clone().attr({width: 24, height: 24});
shower.children(':not(.flyout_arrow_horiz)').remove();
shower
.append(copy)
.attr('data-curopt', '#' + btn[0].id) // This sets the current mode
.mouseup();
canv.setMode(mode_id);
cur_shape_id = btn[0].id.substr((mode_id+'_').length);
current_d = cur_lib.data[cur_shape_id];
$('.tools_flyout').fadeOut();
});
//
var shape_cats = $('<div id="shape_cats">');
var cat_str = '';
$.each(categories, function(id, label) {
cat_str += '<div data-cat=' + id + '>' + label + '</div>';
});
shape_cats.html(cat_str).children().bind('mouseup', function() {
var catlink = $(this);
catlink.siblings().removeClass('current');
catlink.addClass('current');
loadLibrary(catlink.attr('data-cat'));
// Get stuff
return false;
});
shape_cats.children().eq(0).addClass('current');
$('#tools_shapelib').prepend(shape_cats);
shower.mouseup(function() {
canv.setMode(current_d ? mode_id : 'select');
});
$('#tool_shapelib').remove();
var h = $('#tools_shapelib').height();
$('#tools_shapelib').css({
'margin-top': -(h/2),
'margin-left': 3
});
},
mouseDown: function(opts) {
var mode = canv.getMode();
if(mode !== mode_id) return;
var e = opts.event;
var x = start_x = opts.start_x;
var y = start_y = opts.start_y;
var cur_style = canv.getStyle();
cur_shape = canv.addSvgElementFromJson({
"element": "path",
"curStyles": true,
"attr": {
"d": current_d,
"id": canv.getNextId(),
"opacity": cur_style.opacity / 2,
"style": "pointer-events:none"
}
});
cur_shape.setAttribute("d", current_d);
// Make sure shape uses absolute values
if(/[a-z]/.test(current_d)) {
current_d = cur_lib.data[cur_shape_id] = canv.pathActions.convertPath(cur_shape);
cur_shape.setAttribute('d', current_d);
canv.pathActions.fixEnd(cur_shape);
}
cur_shape.setAttribute('transform', "translate(" + x + "," + y + ") scale(0.005) translate(" + -x + "," + -y + ")");
// console.time('b');
canv.recalculateDimensions(cur_shape);
var tlist = canv.getTransformList(cur_shape);
lastBBox = cur_shape.getBBox();
totalScale = {
sx: 1,
sy: 1
};
return {
started: true
}
// current_d
},
mouseMove: function(opts) {
var mode = canv.getMode();
if(mode !== mode_id) return;
var zoom = canv.getZoom();
var evt = opts.event
var x = opts.mouse_x/zoom;
var y = opts.mouse_y/zoom;
var tlist = canv.getTransformList(cur_shape),
box = cur_shape.getBBox(),
left = box.x, top = box.y, width = box.width,
height = box.height;
var dx = (x-start_x), dy = (y-start_y);
var newbox = {
'x': Math.min(start_x,x),
'y': Math.min(start_y,y),
'width': Math.abs(x-start_x),
'height': Math.abs(y-start_y)
};
var ts = null,
tx = 0, ty = 0,
sy = height ? (height+dy)/height : 1,
sx = width ? (width+dx)/width : 1;
var sx = newbox.width / lastBBox.width;
var sy = newbox.height / lastBBox.height;
sx = sx || 1;
sy = sy || 1;
// Not perfect, but mostly works...
if(x < start_x) {
tx = lastBBox.width;
}
if(y < start_y) ty = lastBBox.height;
// update the transform list with translate,scale,translate
var translateOrigin = svgroot.createSVGTransform(),
scale = svgroot.createSVGTransform(),
translateBack = svgroot.createSVGTransform();
translateOrigin.setTranslate(-(left+tx), -(top+ty));
if(evt.shiftKey) {
replaced = true
var max = Math.min(Math.abs(sx), Math.abs(sy));
sx = max * (sx < 0 ? -1 : 1);
sy = max * (sy < 0 ? -1 : 1);
if (totalScale.sx != totalScale.sy) {
var multiplierX = (totalScale.sx > totalScale.sy) ? 1 : totalScale.sx/totalScale.sy;
var multiplierY = (totalScale.sy > totalScale.sx) ? 1 : totalScale.sy/totalScale.sx;
sx *= multiplierY
sy *= multiplierX
}
}
totalScale.sx *= sx;
totalScale.sy *= sy;
scale.setScale(sx,sy);
translateBack.setTranslate(left+tx, top+ty);
var N = tlist.numberOfItems;
tlist.appendItem(translateBack);
tlist.appendItem(scale);
tlist.appendItem(translateOrigin);
canv.recalculateDimensions(cur_shape);
lastBBox = cur_shape.getBBox();
},
mouseUp: function(opts) {
var mode = canv.getMode();
if(mode !== mode_id) return;
if(opts.mouse_x == start_x && opts.mouse_y == start_y) {
return {
keep: false,
element: cur_shape,
started: false
}
}
canv.setMode("select")
return {
keep: true,
element: cur_shape,
started: false
}
}
}
});
/*
* ext-grid.js
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Redou Mine
* Copyright(c) 2010 Alexis Deveria
*
*/
// Dependencies:
// 1) units.js
// 2) everything else
methodDraw.addExtension("view_grid", function(s) {
if (!document.getElementById("canvasGrid")){
var svgdoc = document.getElementById("svgcanvas").ownerDocument,
svgns = "http://www.w3.org/2000/svg",
dims = methodDraw.curConfig.dimensions,
svgroot = s.svgroot;
var svgCanvas = methodDraw.canvas;
var showGrid = false;
var assignAttributes = s.assignAttributes;
var hcanvas = document.createElement('canvas');
$(hcanvas).hide().appendTo('body');
var canvasgrid = svgdoc.createElementNS(svgns, "g");
assignAttributes(canvasgrid, {
'id': 'canvasGrid',
'width': '100%',
'height': '100%',
'x': 0,
'y': 0,
'overflow': 'visible',
'display': 'none'
});
var canvBG = $('#canvas_background');
canvBG.after(canvasgrid);
// grid-pattern
var gridPattern = svgdoc.createElementNS(svgns, "pattern");
assignAttributes(gridPattern, {
'id': 'gridpattern',
'patternUnits': 'userSpaceOnUse',
'x': 0, //-(value.strokeWidth / 2), // position for strokewidth
'y': 0, //-(value.strokeWidth / 2), // position for strokewidth
'width': 100,
'height': 100
});
var gridimg = svgdoc.createElementNS(svgns, "image");
assignAttributes(gridimg, {
'x': 0,
'y': 0,
'width': 100,
'height': 100
});
gridPattern.appendChild(gridimg);
$('#svgroot defs').append(gridPattern);
// grid-box
var gridBox = svgdoc.createElementNS(svgns, "rect");
assignAttributes(gridBox, {
'width': '100%',
'height': '100%',
'x': 0,
'y': 0,
'stroke-width': 0,
'stroke': 'none',
'fill': 'url(#gridpattern)',
'style': 'pointer-events: none; display:visible;'
});
$('#canvasGrid').append(gridBox);
}
// });
function updateGrid(zoom) {
// TODO: Try this with <line> elements, then compare performance difference
var bgwidth = +canvBG.attr('width');
var bgheight = +canvBG.attr('height');
var units = svgedit.units.getTypeMap();
var unit = units[methodDraw.curConfig.baseUnit]; // 1 = 1px
var r_intervals = [.01, .1, 1, 10, 100, 1000];
var d = 0;
var is_x = (d === 0);
var dim = is_x ? 'x' : 'y';
var lentype = is_x?'width':'height';
var c_elem = svgCanvas.getContentElem();
var content_d = c_elem.getAttribute(dim)-0;
var hcanv = hcanvas;
var u_multi = unit * zoom;
// Calculate the main number interval
var raw_m = 100 / u_multi;
var multi = 1;
for(var i = 0; i < r_intervals.length; i++) {
var num = r_intervals[i];
multi = num;
if(raw_m <= num) {
break;
}
}
var big_int = multi * u_multi;
// Set the canvas size to the width of the container
hcanv.width = big_int;
hcanv.height = big_int;
var ctx = hcanv.getContext("2d");
var ruler_d = 0;
var cur_d = .5;
var part = big_int / 10;
ctx.globalAlpha = 0.2;
ctx.strokeStyle = "#000";
for(var i = 1; i < 10; i++) {
var sub_d = Math.round(part * i) + .5;
// var line_num = (i % 2)?12:10;
var line_num = 0;
ctx.moveTo(sub_d, big_int);
ctx.lineTo(sub_d, line_num);
ctx.moveTo(big_int, sub_d);
ctx.lineTo(line_num ,sub_d);
}
ctx.stroke();
ctx.beginPath();
ctx.globalAlpha = 0.5;
ctx.moveTo(cur_d, big_int);
ctx.lineTo(cur_d, 0);
ctx.moveTo(big_int, cur_d);
ctx.lineTo(0, cur_d);
ctx.stroke();
var datauri = hcanv.toDataURL('image/png');
gridimg.setAttribute('width', big_int);
gridimg.setAttribute('height', big_int);
gridimg.parentNode.setAttribute('width', big_int);
gridimg.parentNode.setAttribute('height', big_int);
svgCanvas.setHref(gridimg, datauri);
}
return {
name: "view_grid",
zoomChanged: function(zoom) {
// update size
if(showGrid) updateGrid(zoom);
},
buttons: [{
id: "view_grid",
type: "menu",
after: "tool_wireframe",
panel: "view_menu",
title: "View Grid",
events: {
'click': function() {
var gr = !$('#view_grid').hasClass('push_button_pressed');
if (gr) {
methodDraw.curConfig.showGrid = showGrid = true;
$('#view_grid').addClass('push_button_pressed');
$('#canvasGrid').attr('display', 'inline');
updateGrid(svgCanvas.getZoom());
}
else {
methodDraw.curConfig.showGrid = showGrid = false;
$('#view_grid').removeClass('push_button_pressed');
$('#canvasGrid').attr('display', 'none');
}
}
}
}]
};
});
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
/**
* Copyright (c) 2011 Zauber S.A. <http://www.zaubersoftware.com/>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Guido Marucci Blas - guido@zaubersoftware.com
* @description Adds a handler for a custom event 'taphold' that handles a
* tap and hold on touch interfaces.
*/
(function($) {
var TAP_AND_HOLD_TRIGGER_TIMER = 1000;
var MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT = 5;
var TOUCHSTART = "touchstart";
var TOUCHEND = "touchend";
var TOUCHMOVE = "touchmove";
// For debugging only
// var TOUCHSTART = "mousedown";
// var TOUCHEND = "mouseup";
// var TOUCHMOVE = "mousemove";
var tapAndHoldTimer = null;
function calculateEuclideanDistance(x1, y1, x2, y2) {
var diffX = (x2 - x1);
var diffY = (y2 - y1);
return Math.sqrt((diffX * diffX) + (diffY * diffY));
};
function onTouchStart(event) {
var e = event.originalEvent;
// Only start detector if and only if one finger is over the widget
if (!e.touches || (e.targetTouches.length === 1 && e.touches.length === 1)) {
startTapAndHoldDetector.call(this, event)
var element = $(this);
element.bind(TOUCHMOVE, onTouchMove);
element.bind(TOUCHEND, onTouchEnd);
} else {
stopTapAndHoldDetector.call(this);
}
};
function onTouchMove(event) {
if (tapAndHoldTimer == null) {
return;
}
var e = event.originalEvent;
var x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
var y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
var tapAndHoldPoint = $(this).data("taphold.point");
var euclideanDistance = calculateEuclideanDistance(tapAndHoldPoint.x, tapAndHoldPoint.y, x, y);
if (euclideanDistance > MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT) {
stopTapAndHoldDetector.call(this);
}
};
function onTouchEnd(event) {
stopTapAndHoldDetector.call(this);
};
function onTapAndHold(event) {
clear.call(this);
$(this).data("taphold.handler").call(this, event);
};
function clear() {
tapAndHoldTimer = null;
$(this).unbind(TOUCHMOVE, onTouchMove);
$(this).unbind(TOUCHEND, onTouchEnd);
};
function startTapAndHoldDetector(event) {
if (tapAndHoldTimer != null) {
return;
}
var self = this;
tapAndHoldTimer = setTimeout(function(){
onTapAndHold.call(self, event)
}, TAP_AND_HOLD_TRIGGER_TIMER);
// Stores tap x & y
var e = event.originalEvent;
var tapAndHoldPoint = {};
tapAndHoldPoint.x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
tapAndHoldPoint.y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
$(this).data("taphold.point", tapAndHoldPoint);
};
function stopTapAndHoldDetector() {
clearTimeout(tapAndHoldTimer);
clear.call(this);
};
$.event.special["taphold"] = {
setup: function() {
},
add: function(handleObj) {
$(this).data("taphold.handler", handleObj.handler);
if (handleObj.data) {
$(this).bind(TOUCHSTART, handleObj.data, onTouchStart);
} else {
$(this).bind(TOUCHSTART, onTouchStart);
}
},
remove: function(handleObj) {
stopTapAndHoldDetector.call(this);
if (handleObj.data) {
$(this).unbind(TOUCHSTART, handleObj.data, onTouchStart);
} else {
$(this).unbind(TOUCHSTART, onTouchStart);
}
},
teardown: function() {
}
};
})(jQuery);
/* FileSaver.js
* A saveAs() FileSaver implementation.
* 1.1.20151003
*
* By Eli Grey, http://eligrey.com
* License: MIT
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
*/
/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs = saveAs || (function(view) {
"use strict";
// IE <10 is explicitly unsupported
if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
return;
}
var
doc = view.document
// only get URL when necessary in case Blob.js hasn't overridden it yet
, get_URL = function() {
return view.URL || view.webkitURL || view;
}
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
, can_use_save_link = "download" in save_link
, click = function(node) {
var event = new MouseEvent("click");
node.dispatchEvent(event);
}
, is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
, webkit_req_fs = view.webkitRequestFileSystem
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
, throw_outside = function(ex) {
(view.setImmediate || view.setTimeout)(function() {
throw ex;
}, 0);
}
, force_saveable_type = "application/octet-stream"
, fs_min_size = 0
// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
// for the reasoning behind the timeout and revocation flow
, arbitrary_revoke_timeout = 500 // in ms
, revoke = function(file) {
var revoker = function() {
if (typeof file === "string") { // file is an object URL
get_URL().revokeObjectURL(file);
} else { // file is a File
file.remove();
}
};
if (view.chrome) {
revoker();
} else {
setTimeout(revoker, arbitrary_revoke_timeout);
}
}
, dispatch = function(filesaver, event_types, event) {
event_types = [].concat(event_types);
var i = event_types.length;
while (i--) {
var listener = filesaver["on" + event_types[i]];
if (typeof listener === "function") {
try {
listener.call(filesaver, event || filesaver);
} catch (ex) {
throw_outside(ex);
}
}
}
}
, auto_bom = function(blob) {
// prepend BOM for UTF-8 XML and text/* types (including HTML)
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob(["\ufeff", blob], {type: blob.type});
}
return blob;
}
, FileSaver = function(blob, name, no_auto_bom) {
if (!no_auto_bom) {
blob = auto_bom(blob);
}
// First try a.download, then web filesystem, then object URLs
var
filesaver = this
, type = blob.type
, blob_changed = false
, object_url
, target_view
, dispatch_all = function() {
dispatch(filesaver, "writestart progress write writeend".split(" "));
}
// on any filesys errors revert to saving with object URLs
, fs_error = function() {
if (target_view && is_safari && typeof FileReader !== "undefined") {
// Safari doesn't allow downloading of blob urls
var reader = new FileReader();
reader.onloadend = function() {
var base64Data = reader.result;
target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/));
filesaver.readyState = filesaver.DONE;
dispatch_all();
};
reader.readAsDataURL(blob);
filesaver.readyState = filesaver.INIT;
return;
}
// don't create more object URLs than needed
if (blob_changed || !object_url) {
object_url = get_URL().createObjectURL(blob);
}
if (target_view) {
target_view.location.href = object_url;
} else {
var new_tab = view.open(object_url, "_blank");
if (new_tab == undefined && is_safari) {
//Apple do not allow window.open, see http://bit.ly/1kZffRI
view.location.href = object_url
}
}
filesaver.readyState = filesaver.DONE;
dispatch_all();
revoke(object_url);
}
, abortable = function(func) {
return function() {
if (filesaver.readyState !== filesaver.DONE) {
return func.apply(this, arguments);
}
};
}
, create_if_not_found = {create: true, exclusive: false}
, slice
;
filesaver.readyState = filesaver.INIT;
if (!name) {
name = "download";
}
if (can_use_save_link) {
object_url = get_URL().createObjectURL(blob);
setTimeout(function() {
save_link.href = object_url;
save_link.download = name;
click(save_link);
dispatch_all();
revoke(object_url);
filesaver.readyState = filesaver.DONE;
});
return;
}
// Object and web filesystem URLs have a problem saving in Google Chrome when
// viewed in a tab, so I force save with application/octet-stream
// http://code.google.com/p/chromium/issues/detail?id=91158
// Update: Google errantly closed 91158, I submitted it again:
// https://code.google.com/p/chromium/issues/detail?id=389642
if (view.chrome && type && type !== force_saveable_type) {
slice = blob.slice || blob.webkitSlice;
blob = slice.call(blob, 0, blob.size, force_saveable_type);
blob_changed = true;
}
// Since I can't be sure that the guessed media type will trigger a download
// in WebKit, I append .download to the filename.
// https://bugs.webkit.org/show_bug.cgi?id=65440
if (webkit_req_fs && name !== "download") {
name += ".download";
}
if (type === force_saveable_type || webkit_req_fs) {
target_view = view;
}
if (!req_fs) {
fs_error();
return;
}
fs_min_size += blob.size;
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
var save = function() {
dir.getFile(name, create_if_not_found, abortable(function(file) {
file.createWriter(abortable(function(writer) {
writer.onwriteend = function(event) {
target_view.location.href = file.toURL();
filesaver.readyState = filesaver.DONE;
dispatch(filesaver, "writeend", event);
revoke(file);
};
writer.onerror = function() {
var error = writer.error;
if (error.code !== error.ABORT_ERR) {
fs_error();
}
};
"writestart progress write abort".split(" ").forEach(function(event) {
writer["on" + event] = filesaver["on" + event];
});
writer.write(blob);
filesaver.abort = function() {
writer.abort();
filesaver.readyState = filesaver.DONE;
};
filesaver.readyState = filesaver.WRITING;
}), fs_error);
}), fs_error);
};
dir.getFile(name, {create: false}, abortable(function(file) {
// delete file if it already exists
file.remove();
save();
}), abortable(function(ex) {
if (ex.code === ex.NOT_FOUND_ERR) {
save();
} else {
fs_error();
}
}));
}), fs_error);
}), fs_error);
}
, FS_proto = FileSaver.prototype
, saveAs = function(blob, name, no_auto_bom) {
return new FileSaver(blob, name, no_auto_bom);
}
;
// IE 10+ (native saveAs)
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
return function(blob, name, no_auto_bom) {
if (!no_auto_bom) {
blob = auto_bom(blob);
}
return navigator.msSaveOrOpenBlob(blob, name || "download");
};
}
FS_proto.abort = function() {
var filesaver = this;
filesaver.readyState = filesaver.DONE;
dispatch(filesaver, "abort");
};
FS_proto.readyState = FS_proto.INIT = 0;
FS_proto.WRITING = 1;
FS_proto.DONE = 2;
FS_proto.error =
FS_proto.onwritestart =
FS_proto.onprogress =
FS_proto.onwrite =
FS_proto.onabort =
FS_proto.onerror =
FS_proto.onwriteend =
null;
return saveAs;
}(
typeof self !== "undefined" && self
|| typeof window !== "undefined" && window
|| this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window
if (typeof module !== "undefined" && module.exports) {
module.exports.saveAs = saveAs;
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
define([], function() {
return saveAs;
});
}