diff --git a/public/svg-edit/editor/browsersupport.js b/public/svg-edit/editor/browsersupport.js new file mode 100644 index 00000000..90cd94bf --- /dev/null +++ b/public/svg-edit/editor/browsersupport.js @@ -0,0 +1,118 @@ +/** + * Package: svgedit.browsersupport + * + * Licensed under the Apache License, Version 2 + * + * Copyright(c) 2010 Jeff Schiller + * Copyright(c) 2010 Alexis Deveria + */ + +// Dependencies: +// 1) jQuery (for $.alert()) + +(function() { + +if (!window.svgedit) { + window.svgedit = {}; +} + +if (!svgedit.browsersupport) { + svgedit.browsersupport = {}; +} + +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; + +svgedit.browsersupport.isOpera = function() { return isOpera_; } +svgedit.browsersupport.isWebkit = function() { return isWebkit_; } +svgedit.browsersupport.isGecko = function() { return isGecko_; } + +// segList functions (for FF1.5 and 2.0) +function supportPathReplaceItem() { + var path = document.createElementNS(svgns, 'path'); + path.setAttribute('d','M0,0 10,10'); + var seglist = path.pathSegList; + var seg = path.createSVGPathSegLinetoAbs(5,5); + try { + seglist.replaceItem(seg, 0); + return true; + } catch(err) {} + return false; +} + +function supportPathInsertItemBefore() { + var path = document.createElementNS(svgns,'path'); + path.setAttribute('d','M0,0 10,10'); + var seglist = path.pathSegList; + var seg = path.createSVGPathSegLinetoAbs(5,5); + try { + seglist.insertItemBefore(seg, 0); + return true; + } catch(err) {} + return false; +} + +// text character positioning +function supportTextCharPos() { + var retValue = false; + var svgcontent = document.createElementNS(svgns, 'svg'); + document.documentElement.appendChild(svgcontent); + try { + var text = document.createElementNS(svgns,'text'); + text.textContent = 'a'; + svgcontent.appendChild(text); + text.getStartPositionOfChar(0); + retValue = true; + } catch(err) {} + document.documentElement.removeChild(svgcontent); + return retValue; +} + +function supportEditableText() { + // TODO: Find better way to check support for this + return svgedit.browsersupport.isOpera(); +} + +function supportGoodDecimals() { + // 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 latest version in which the problems have been fixed."); + } + return retValue; +} + +function supportNonScalingStroke() { + var rect = document.createElementNS(svgns, 'rect'); + rect.setAttribute('style','vector-effect:non-scaling-stroke'); + return rect.style.vectorEffect === 'non-scaling-stroke'; +} + +function supportNativeSVGTransformLists() { + var rect = document.createElementNS(svgns, 'rect'); + var rxform = rect.transform.baseVal; + + var t1 = svg.createSVGTransform(); + rxform.appendItem(t1); + return rxform.getItem(0) == t1; +} + +svgedit.browsersupport.pathReplaceItem = supportPathReplaceItem(); +svgedit.browsersupport.pathInsertItemBefore = supportPathInsertItemBefore(); +svgedit.browsersupport.textCharPos = supportTextCharPos(); +svgedit.browsersupport.editableText = supportEditableText(); +svgedit.browsersupport.goodDecimals = supportGoodDecimals(); +svgedit.browsersupport.nonScalingStroke = supportNonScalingStroke(); +svgedit.browsersupport.nativeTransformLists = supportNativeSVGTransformLists(); + +})(); \ No newline at end of file diff --git a/public/svg-edit/editor/embedapi.js b/public/svg-edit/editor/embedapi.js index 72b53817..40dc92cc 100644 --- a/public/svg-edit/editor/embedapi.js +++ b/public/svg-edit/editor/embedapi.js @@ -72,7 +72,7 @@ function embedded_svg_edit(frame){ //Newer, well, it extracts things that aren't documented as well. All functions accessible through the normal thingy can now be accessed though the API //var l=[];for(var i in svgCanvas){if(typeof svgCanvas[i] == "function"){l.push(i)}}; //run in svgedit itself - var functions = ["updateElementFromJson", "embedImage", "fixOperaXML", "clearSelection", "addToSelection", "removeFromSelection", "addNodeToSelection", "open", "save", "getSvgString", "setSvgString", "createLayer", "deleteCurrentLayer", "getNumLayers", "getLayer", "getCurrentLayer", "setCurrentLayer", "renameCurrentLayer", "setCurrentLayerPosition", "getLayerVisibility", "setLayerVisibility", "moveSelectedToLayer", "getLayerOpacity", "setLayerOpacity", "clear", "clearPath", "getNodePoint", "clonePathNode", "deletePathNode", "getResolution", "getImageTitle", "setImageTitle", "setResolution", "setBBoxZoom", "setZoom", "getMode", "setMode", "getStrokeColor", "setStrokeColor", "getFillColor", "setFillColor", "setStrokePaint", "setFillPaint", "getStrokeWidth", "setStrokeWidth", "getStrokeStyle", "setStrokeStyle", "getOpacity", "setOpacity", "getFillOpacity", "setFillOpacity", "getStrokeOpacity", "setStrokeOpacity", "getTransformList", "getBBox", "getRotationAngle", "setRotationAngle", "each", "bind", "setIdPrefix", "getBold", "setBold", "getItalic", "setItalic", "getFontFamily", "setFontFamily", "getFontSize", "setFontSize", "getText", "setTextContent", "setImageURL", "setRectRadius", "setSegType", "quickClone", "beginUndoableChange", "changeSelectedAttributeNoUndo", "finishUndoableChange", "changeSelectedAttribute", "deleteSelectedElements", "groupSelectedElements", "ungroupSelectedElement", "moveToTopSelectedElement", "moveToBottomSelectedElement", "moveSelectedElements", "getStrokedBBox", "getVisibleElements", "cycleElement", "getUndoStackSize", "getRedoStackSize", "getNextUndoCommandText", "getNextRedoCommandText", "undo", "redo", "cloneSelectedElements", "alignSelectedElements", "getZoom", "getVersion", "setIconSize", "setLang", "setCustomHandlers"] + var functions = ["updateElementFromJson", "embedImage", "fixOperaXML", "clearSelection", "addToSelection", "removeFromSelection", "addNodeToSelection", "open", "save", "getSvgString", "setSvgString", "createLayer", "deleteCurrentLayer", "getNumLayers", "getLayer", "getCurrentLayer", "setCurrentLayer", "renameCurrentLayer", "setCurrentLayerPosition", "getLayerVisibility", "setLayerVisibility", "moveSelectedToLayer", "getLayerOpacity", "setLayerOpacity", "clear", "clearPath", "getNodePoint", "clonePathNode", "deletePathNode", "getResolution", "getImageTitle", "setImageTitle", "setResolution", "setBBoxZoom", "setZoom", "getMode", "setMode", "getStrokeColor", "setStrokeColor", "getFillColor", "setFillColor", "setStrokePaint", "setFillPaint", "getStrokeWidth", "setStrokeWidth", "getStrokeStyle", "setStrokeStyle", "getOpacity", "setOpacity", "getFillOpacity", "setFillOpacity", "getStrokeOpacity", "setStrokeOpacity", "getTransformList", "getBBox", "getRotationAngle", "setRotationAngle", "each", "bind", "setIdPrefix", "getBold", "setBold", "getItalic", "setItalic", "getFontFamily", "setFontFamily", "getFontSize", "setFontSize", "getText", "setTextContent", "setImageURL", "setRectRadius", "setSegType", "quickClone", "changeSelectedAttributeNoUndo", "changeSelectedAttribute", "deleteSelectedElements", "groupSelectedElements", "ungroupSelectedElement", "moveToTopSelectedElement", "moveToBottomSelectedElement", "moveSelectedElements", "getStrokedBBox", "getVisibleElements", "cycleElement", "getUndoStackSize", "getRedoStackSize", "getNextUndoCommandText", "getNextRedoCommandText", "undo", "redo", "cloneSelectedElements", "alignSelectedElements", "getZoom", "getVersion", "setIconSize", "setLang", "setCustomHandlers"] //TODO: rewrite the following, it's pretty scary. for(var i = 0; i < functions.length; i++){ diff --git a/public/svg-edit/editor/extensions/ext-grid.js b/public/svg-edit/editor/extensions/ext-grid.js index bbb7e084..c6be63b8 100644 --- a/public/svg-edit/editor/extensions/ext-grid.js +++ b/public/svg-edit/editor/extensions/ext-grid.js @@ -8,6 +8,10 @@ * */ +// Dependencies: +// 1) units.js +// 2) everything else + svgEditor.addExtension("view_grid", function(s) { var svgdoc = document.getElementById("svgcanvas").ownerDocument, @@ -78,7 +82,7 @@ svgEditor.addExtension("view_grid", function(s) { var bgwidth = +canvBG.attr('width'); var bgheight = +canvBG.attr('height'); - var units = svgCanvas.getUnits(); + var units = svgedit.units.getTypeMap(); var unit = units[svgEditor.curConfig.baseUnit]; // 1 = 1px var r_intervals = [.01, .1, 1, 10, 100, 1000]; diff --git a/public/svg-edit/editor/history.js b/public/svg-edit/editor/history.js new file mode 100644 index 00000000..c0e9d88e --- /dev/null +++ b/public/svg-edit/editor/history.js @@ -0,0 +1,601 @@ +/** + * Package: svedit.history + * + * Licensed under the Apache License, Version 2 + * + * Copyright(c) 2010 Jeff Schiller + */ + +// Dependencies: +// 1) jQuery +// 2) svgtransformlist.js +// 3) svgutils.js + +(function() { + +if (!window.svgedit) { + window.svgedit = {}; +} + +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(); + * void unapply(); + * Element[] elements(); + * String getText(); + * + * static String type(); + * } + */ + +// 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); + 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 = 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); + } + } + } + + // 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; +}; + + +/** + * 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 (see above). + * command is an object fulfilling the HistoryCommand interface (see above). + */ + +// 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; +}; + + +})(); \ No newline at end of file diff --git a/public/svg-edit/editor/jgraduate/css/jgraduate.css b/public/svg-edit/editor/jgraduate/css/jgraduate.css index 06ab1708..4368e00e 100644 --- a/public/svg-edit/editor/jgraduate/css/jgraduate.css +++ b/public/svg-edit/editor/jgraduate/css/jgraduate.css @@ -1,7 +1,11 @@ /* * jGraduate Default CSS * - * Copyright (c) 2009 Jeff Schiller + * Copyright (c) 2010 Jeff Schiller + * http://blog.codedread.com/ + * + * Copyright (c) 2010 Alexis Deveria + * http://a.deveria.com/ * * Licensed under the Apache License Version 2 */ @@ -48,14 +52,14 @@ li.jGraduate_tab_current { display: none; } -.jGraduate_lgPick { +.jGraduate_gradPick { display: none; border: outset 1px #666; padding: 10px 7px 5px 5px; overflow: auto; } -.jGraduate_rgPick { +.jGraduate_gradPick { display: none; border: outset 1px #666; padding: 10px 7px 5px 5px; @@ -80,6 +84,30 @@ div.jGraduate_GradContainer { background-image: url(../images/map-opacity.png); background-position: 0px 0px; height: 256px; + width: 256px; + position: relative; +} + +div.jGraduate_GradContainer div.grad_coord { + background: #000; + border: 1px solid #fff; + z-index: 2; + border-radius: 5px; + -moz-border-radius: 5px; + width: 10px; + height: 10px; + position: absolute; + margin: -5px -5px; + top: 0; + left: 0; + text-align: center; + font-size: xx-small; + line-height: 10px; + color: #fff; + text-decoration: none; + cursor: pointer; + -moz-user-select: none; + -webkit-user-select: none; } .jGraduate_AlphaArrows { @@ -98,16 +126,14 @@ div.jGraduate_Opacity { cursor: ew-resize; } -div.lg_jGraduate_OpacityField { - position: absolute; - bottom: 25px; - left: 292px; -} - -div.jGraduate_Form { - float: left; - width: 140px; - margin: -3px 3px 0px 4px; +div.jGraduate_StopSlider { +/* border: 2px inset #eee;*/ + margin: 0 0 0 -10px; + width: 276px; + overflow: visible; + background: #efefef; + height: 45px; + cursor: pointer; } div.jGraduate_StopSection { @@ -115,49 +141,8 @@ div.jGraduate_StopSection { text-align: center; } -div.jGraduate_RadiusField { - - text-align: center; - float: left; -} - -div.jGraduate_RadiusField input { - margin-top: 10px; -} - -.jGraduate_RadiusField .jGraduate_Form_Section { - width: 250px; - padding: 2px; - height: 80px; - overflow: visible; -} - -.jGraduate_Form_Section input[type=text] { - width: 38px; -} - -.jGraduate_Radius { - border:1px solid #BBB; - cursor:ew-resize; - height:20px; - margin-top:14px; - position: relative; -} -.jGraduate_RadiusArrows { - top: 0; - left: 0; - position: absolute; - margin-top: -10px; - margin-left: 250.5px; -} - - -div.jGraduate_OkCancel { - float: left; - width: 113px; -} input.jGraduate_Ok, input.jGraduate_Cancel { display: block; @@ -199,12 +184,21 @@ div.jGraduate_Form_Section { -moz-border-radius: 5px; -webkit-border-radius: 5px; padding: 15px 5px 5px 5px; - margin: 2px; + margin: 5px 2px; width: 110px; text-align: center; overflow: auto; } +div.jGraduate_Form_Section label { + padding: 0 2px; +} + +div.jGraduate_StopSection input[type=text], +div.jGraduate_Slider input[type=text] { + width: 33px; +} + div.jGraduate_LightBox { position: fixed; top: 0px; @@ -223,17 +217,47 @@ div.jGraduate_stopPicker { } -.jGraduate_rgPick { - width: 530px; +.jGraduate_gradPick { + width: 535px; } -.jGraduate_rgPick div.jGraduate_Form { +.jGraduate_gradPick div.jGraduate_OpacField { + + position: absolute; + left: 0; + bottom: 5px; +/* + width: 270px; + + left: 284px; + width: 266px; + height: 200px; + top: 167px; + margin: -3px 3px 0px 4px; +*/ +} + +.jGraduate_gradPick .jGraduate_Form { + float: left; width: 270px; position: absolute; left: 284px; width: 266px; - top: 130px; - margin: -3px 3px 0px 4px; + height: 200px; + top: 167px; + margin: -3px 3px 0px 10px; +} + +.jGraduate_gradPick .jGraduate_Points { + position: static; + width: 150px; + margin-left: 0; +} + +.jGraduate_SpreadMethod { + position: absolute; + right: 8px; + top: 100px; } .jGraduate_Colorblocks { @@ -252,19 +276,77 @@ div.jGraduate_stopPicker { float: none; } -.jGraduate_rgPick div.jGraduate_StopSection { +.jGraduate_gradPick div.jGraduate_StopSection { float: left; width: 133px; - margin: 0; + margin-top: -8px; } -.jGraduate_rgPick .jGraduate_OkCancel { - position: absolute; - right: 0; + +.jGraduate_gradPick .jGraduate_Form_Section { + padding-top: 9px; } -.rg_jGraduate_OpacityField { + +.jGraduate_Slider { + text-align: center; + float: left; + width: 100%; +} + +.jGraduate_Slider .jGraduate_Form_Section { + border: none; + width: 250px; + padding: 0 2px; + overflow: visible; +} + +.jGraduate_Slider label { + display: inline-block; + float: left; + line-height: 50px; + padding: 0; +} + +.jGraduate_Slider label.prelabel { + width: 40px; + text-align: left; +} + +.jGraduate_SliderBar { + width: 140px; + float: left; + margin-right: 5px; + border:1px solid #BBB; + height:20px; + margin-top:14px; + margin-left:5px; + position: relative; +} + +div.jGraduate_Slider input { + margin-top: 5px; +} + +div.jGraduate_Slider img { + top: 0; + left: 0; position: absolute; - left: 288px; - bottom: 24px; + margin-top: -10px; + cursor:ew-resize; +} + + +.jGraduate_gradPick .jGraduate_OkCancel { + position: absolute; + top: 39px; + right: 10px; + width: 113px; + +} + +.jGraduate_OpacField { + position: absolute; + right: -10px; + bottom: 0; } \ No newline at end of file diff --git a/public/svg-edit/editor/jgraduate/jquery.jgraduate.js b/public/svg-edit/editor/jgraduate/jquery.jgraduate.js index 2838f70b..cb1cc021 100644 --- a/public/svg-edit/editor/jgraduate/jquery.jgraduate.js +++ b/public/svg-edit/editor/jgraduate/jquery.jgraduate.js @@ -1,5 +1,5 @@ /* - * jGraduate 0.3.x + * jGraduate 0.4 * * jQuery Plugin for a gradient picker * @@ -116,13 +116,37 @@ $.jGraduate = { jQuery.fn.jGraduateDefaults = { paint: new $.jGraduate.Paint(), window: { - pickerTitle: "Drag markers to pick a paint", + pickerTitle: "Drag markers to pick a paint" }, images: { - clientPath: "images/", - }, + clientPath: "images/" + } }; +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; @@ -138,14 +162,16 @@ jQuery.fn.jGraduate = } var okClicked = function() { - // TODO: Fix this ugly hack - if($this.paint.type == "radialGradient") { - $this.paint.linearGradient = null; - } else if($this.paint.type == "linearGradient") { - $this.paint.radialGradient = null; - } else if($this.paint.type == "solidColor") { - $this.paint.linearGradient = null; - $this.paint.radialGradient = null; + 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(); @@ -160,11 +186,12 @@ jQuery.fn.jGraduate = // 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, + 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'}); @@ -177,402 +204,332 @@ jQuery.fn.jGraduate = '
  • Radial Gradient
  • ' + '' + '
    ' + - '
    ' + - '
    '); + '
    ' + + '
    ' + + '
    ' + + + ); var colPicker = $(idref + '> .jGraduate_colPick'); - var lgPicker = $(idref + '> .jGraduate_lgPick'); - var rgPicker = $(idref + '> .jGraduate_rgPick'); + var gradPicker = $(idref + '> .jGraduate_gradPick'); - lgPicker.html( + gradPicker.html( '
    ' + '

    ' + $settings.window.pickerTitle + '

    ' + - '
    ' + - '
    ' + - '' + - '
    ' + + '
    ' + + '
    ' + '
    ' + - '
    ' + + '
    ' + '
    ' + - '' + + '' + '
    ' + '' + '' + '' + '' + - '
    ' + - '' + '
    ' + '
    ' + '
    ' + - '' + + '' + '
    ' + '' + '' + '' + '' + - '
    ' + - '' + '
    ' + '
    ' + - '
    ' + - '' + - '%' + - '
    ' + '
    ' + - '
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    '); - - rgPicker.html( - '
    ' + - '

    ' + $settings.window.pickerTitle + '

    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    Center:' + - '
    ' + - '
    ' + - - '
    Outer:' + - '
    ' + - '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '' + '
    ' + '
    ' + - '
    ' + - '
    ' + '
    ' + - '
    ' + - '' + - '
    ' + - '' + - '' + - '' + - '' + - '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '' + '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '' + - '' + - '' + - '' + - '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '' + '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '%' + - '
    ' + - '' + - '
    ' + - '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '' + '
    ' + '
    ' + - '
    ' + - '' + - '%' + - '
    ' + '
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    '); - + '' + + '' + + '
    '); + // -------------- // 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; - $.each(['lg', 'rg'], function(i) { - var grad_id = id + '_' + this; - var container = document.getElementById(grad_id+'_jGraduate_GradContainer'); - var svg = container.appendChild(document.createElementNS(ns.svg, 'svg')); - svg.id = grad_id + '_jgraduate_svg'; - svg.setAttribute('width', MAX); - svg.setAttribute('height', MAX); - svg.setAttribute("xmlns", ns.svg); - }); + var curType, curGradient, previewRect; + + var attr_input = {}; + + var SLIDERW = 145; + $('.jGraduate_SliderBar').width(SLIDERW); + var container = $('#' + id+'_jGraduate_GradContainer')[0]; - // Linear gradient - (function() { - var svg = document.getElementById(id + '_lg_jgraduate_svg'); + 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' + }, svg); + } + + if(isSolid) { + grad = curGradient = $('#' + id + '_lg_jgraduate_grad')[0]; + var color = $this.paint[curType]; + mkStop(0, '#' + color, 1); + mkStop(1, '#' + color, 0.5); + } + + + 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); - // if we are sent a gradient, import it - if ($this.paint.type == "linearGradient") { - $this.paint.linearGradient.id = id+'_jgraduate_grad'; - $this.paint.linearGradient = svg.appendChild($this.paint.linearGradient.cloneNode(true)); - } else { // we create a gradient - var grad = svg.appendChild(document.createElementNS(ns.svg, 'linearGradient')); - grad.id = id+'_jgraduate_grad'; - grad.setAttribute('x1','0.0'); - grad.setAttribute('y1','0.0'); - grad.setAttribute('x2','1.0'); - grad.setAttribute('y2','1.0'); - - var begin = grad.appendChild(document.createElementNS(ns.svg, 'stop')); - begin.setAttribute('offset', '0.0'); - begin.setAttribute('stop-color', '#ff0000'); - - var end = grad.appendChild(document.createElementNS(ns.svg, 'stop')); - end.setAttribute('offset', '1.0'); - end.setAttribute('stop-color', '#ffff00'); + var cx = parseFloat(grad.getAttribute('cx')||0.5), + cy = parseFloat(grad.getAttribute('cy')||0.5), + fx = parseFloat(grad.getAttribute('fx')|| cx), + fy = parseFloat(grad.getAttribute('fy')|| cy); + + + var previewRect = mkElem('rect', { + id: id + '_jgraduate_rect', + x: MARGINX, + y: MARGINY, + width: SIZEX, + height: SIZEY, + fill: 'url(#'+id+'_jgraduate_grad)', + 'fill-opacity': gradalpha/100 + }, svg); + + // stop visuals created here + var beginCoord = $('
    ').attr({ + 'class': 'grad_coord jGraduate_lg_field', + title: 'Begin Stop' + }).text(1).css({ + top: y1 * MAX, + left: x1 * MAX + }).data('coord', 'start').appendTo(container); + + var endCoord = beginCoord.clone().text(2).css({ + top: y2 * MAX, + left: x2 * MAX + }).attr('title', 'End stop').data('coord', 'end').appendTo(container); + + var centerCoord = $('
    ').attr({ + 'class': 'grad_coord jGraduate_rg_field', + title: 'Center stop' + }).text('C').css({ + top: cy * MAX, + left: cx * MAX + }).data('coord', 'center').appendTo(container); + + var focusCoord = centerCoord.clone().text('F').css({ + top: fy * MAX, + left: fx * MAX, + display: 'none' + }).attr('title', 'Focus point').data('coord', 'focus').appendTo(container); + + focusCoord[0].id = id + '_jGraduate_focusCoord'; + + var coords = $(idref + ' .grad_coord'); + + $(container).hover(function() { + coords.animate({ + opacity: 1 + }, 500); + }, function() { + coords.animate({ + opacity: .2 + }, 500); + }); + + $.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function(i, attr) { + var attrval = curGradient.getAttribute(attr); - $this.paint.linearGradient = grad; + 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"; + } } - - var gradalpha = $this.paint.alpha; - $('#' + id + '_lg_jGraduate_OpacityInput').val(gradalpha); - var posx = parseInt(255*(gradalpha/100)) - 4.5; - $('#' + id + '_lg_jGraduate_AlphaArrows').css({'margin-left':posx}); - - var x1 = parseFloat($this.paint.linearGradient.getAttribute('x1')||0.0), - y1 = parseFloat($this.paint.linearGradient.getAttribute('y1')||0.0), - x2 = parseFloat($this.paint.linearGradient.getAttribute('x2')||1.0), - y2 = parseFloat($this.paint.linearGradient.getAttribute('y2')||0.0); - - var rect = document.createElementNS(ns.svg, 'rect'); - rect.id = id + '_lg_jgraduate_rect'; - rect.setAttribute('x', MARGINX); - rect.setAttribute('y', MARGINY); - rect.setAttribute('width', SIZEY); - rect.setAttribute('height', SIZEY); - rect.setAttribute('fill', 'url(#'+id+'_jgraduate_grad)'); - rect.setAttribute('fill-opacity', '1.0'); - rect = svg.appendChild(rect); - $('#' + id + '_lg_jgraduate_rect').attr('fill-opacity', gradalpha/100); - - // stop visuals created here - var beginStop = document.createElementNS(ns.svg, 'image'); - beginStop.id = id + "_stop1"; - beginStop.setAttribute('class', 'stop'); - beginStop.setAttributeNS(ns.xlink, 'href', $settings.images.clientPath + 'mappoint.gif'); - beginStop.setAttributeNS(ns.xlink, "title", "Begin Stop"); - beginStop.appendChild(document.createElementNS(ns.svg, 'title')).appendChild( - document.createTextNode("Begin Stop")); - beginStop.setAttribute('width', 18); - beginStop.setAttribute('height', 18); - beginStop.setAttribute('x', MARGINX + SIZEX*x1 - STOP_RADIUS); - beginStop.setAttribute('y', MARGINY + SIZEY*y1 - STOP_RADIUS); - beginStop.setAttribute('cursor', 'move'); - // must append only after setting all attributes due to Webkit Bug 27952 - // https://bugs.webkit.org/show_bug.cgi?id=27592 - beginStop = svg.appendChild(beginStop); - - var endStop = document.createElementNS(ns.svg, 'image'); - endStop.id = id + "_stop2"; - endStop.setAttribute('class', 'stop'); - endStop.setAttributeNS(ns.xlink, 'href', $settings.images.clientPath + 'mappoint.gif'); - endStop.setAttributeNS(ns.xlink, "title", "End Stop"); - endStop.appendChild(document.createElementNS(ns.svg, 'title')).appendChild( - document.createTextNode("End Stop")); - endStop.setAttribute('width', 18); - endStop.setAttribute('height', 18); - endStop.setAttribute('x', MARGINX + SIZEX*x2 - STOP_RADIUS); - endStop.setAttribute('y', MARGINY + SIZEY*y2 - STOP_RADIUS); - endStop.setAttribute('cursor', 'move'); - endStop = svg.appendChild(endStop); - - // bind GUI elements - $('#'+id+'_lg_jGraduate_Ok').bind('click', function() { - $this.paint.type = "linearGradient"; - $this.paint.solidColor = null; - okClicked(); - }); - $('#'+id+'_lg_jGraduate_Cancel').bind('click', function(paint) { - cancelClicked(); - }); - - var x1 = $this.paint.linearGradient.getAttribute('x1'); - if(!x1) x1 = "0.0"; - var x1Input = $('#'+id+'_jGraduate_x1'); - x1Input.val(x1); - x1Input.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 0.0; - } - $this.paint.linearGradient.setAttribute('x1', this.value); - beginStop.setAttribute('x', MARGINX + SIZEX*this.value - STOP_RADIUS); - }); - - var y1 = $this.paint.linearGradient.getAttribute('y1'); - if(!y1) y1 = "0.0"; - var y1Input = $('#'+id+'_jGraduate_y1'); - y1Input.val(y1); - y1Input.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 0.0; - } - $this.paint.linearGradient.setAttribute('y1', this.value); - beginStop.setAttribute('y', MARGINY + SIZEY*this.value - STOP_RADIUS); - }); - - var x2 = $this.paint.linearGradient.getAttribute('x2'); - if(!x2) x2 = "1.0"; - var x2Input = $('#'+id+'_jGraduate_x2'); - x2Input.val(x2); - x2Input.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 1.0; - } - $this.paint.linearGradient.setAttribute('x2', this.value); - endStop.setAttribute('x', MARGINX + SIZEX*this.value - STOP_RADIUS); - }); - - var y2 = $this.paint.linearGradient.getAttribute('y2'); - if(!y2) y2 = "0.0"; - y2Input = $('#'+id+'_jGraduate_y2'); - y2Input.val(y2); - y2Input.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 0.0; - } - $this.paint.linearGradient.setAttribute('y2', this.value); - endStop.setAttribute('y', MARGINY + SIZEY*this.value - STOP_RADIUS); - }); - - var stops = $this.paint.linearGradient.getElementsByTagNameNS(ns.svg, 'stop'); - var numstops = stops.length; - // if there are not at least two stops, then - if (numstops < 2) { - while (numstops < 2) { - $this.paint.linearGradient.appendChild( document.createElementNS(ns.svg, 'stop') ); - ++numstops; - } - stops = $this.paint.linearGradient.getElementsByTagNameNS(ns.svg, 'stop'); - } - - var setLgOpacitySlider = function(e, div) { - var offset = div.offset(); - var x = (e.pageX - offset.left - parseInt(div.css('border-left-width'))); - if (x > 255) x = 255; - if (x < 0) x = 0; - var posx = x - 4.5; - x /= 255; - $('#' + id + '_lg_jGraduate_AlphaArrows').css({'margin-left':posx}); - $('#' + id + '_lg_jgraduate_rect').attr('fill-opacity', x); - x = parseInt(x*100); - $('#' + id + '_lg_jGraduate_OpacityInput').val(x); - $this.paint.alpha = x; - }; - - // handle dragging on the opacity slider - var bSlidingOpacity = false; - $('#' + id + '_lg_jGraduate_Opacity').mousedown(function(evt) { - setLgOpacitySlider(evt, $(this)); - bSlidingOpacity = true; - evt.preventDefault(); - }).mousemove(function(evt) { - if (bSlidingOpacity) { - setLgOpacitySlider(evt, $(this)); - evt.preventDefault(); - } - }).mouseup(function(evt) { - setLgOpacitySlider(evt, $(this)); - bSlidingOpacity = false; - evt.preventDefault(); - }); - - // handle dragging the stop around the swatch - var draggingStop = null; - var startx = -1, starty = -1; - // for whatever reason, Opera does not allow $('image.stop') here, - // and Firefox 1.5 does not allow $('.stop') - $('.stop, #color_picker_lg_jGraduate_GradContainer image').mousedown(function(evt) { - draggingStop = this; - startx = evt.clientX; - starty = evt.clientY; - evt.preventDefault(); - }); - $('#'+id+'_lg_jgraduate_svg').mousemove(function(evt) { - if (null != draggingStop) { - var dx = evt.clientX - startx; - var dy = evt.clientY - starty; - startx += dx; - starty += dy; - var x = parseFloat(draggingStop.getAttribute('x')) + dx; - var y = parseFloat(draggingStop.getAttribute('y')) + dy; - - // clamp stop to the swatch - if (x < MARGINX - STOP_RADIUS) x = MARGINX - STOP_RADIUS; - if (y < MARGINY - STOP_RADIUS) y = MARGINY - STOP_RADIUS; - if (x > MARGINX + SIZEX - STOP_RADIUS) x = MARGINX + SIZEX - STOP_RADIUS; - if (y > MARGINY + SIZEY - STOP_RADIUS) y = MARGINY + SIZEY - STOP_RADIUS; - - draggingStop.setAttribute('x', x); - draggingStop.setAttribute('y', y); - - // calculate stop offset - var fracx = (x - MARGINX + STOP_RADIUS)/SIZEX; - var fracy = (y - MARGINY + STOP_RADIUS)/SIZEY; - - if (draggingStop.id == (id+'_stop1')) { - x1Input.val(fracx); - y1Input.val(fracy); - $this.paint.linearGradient.setAttribute('x1', fracx); - $this.paint.linearGradient.setAttribute('y1', fracy); - } - else { - x2Input.val(fracx); - y2Input.val(fracy); - $this.paint.linearGradient.setAttribute('x2', fracx); - $this.paint.linearGradient.setAttribute('y2', fracy); + + 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; } - evt.preventDefault(); - } - }); - $('#'+id+'_lg_jgraduate_svg').mouseup(function(evt) { - draggingStop = null; - }); + 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 beginColor = stops[0].getAttribute('stop-color'); - if(!beginColor) beginColor = '#000'; - beginColorBox = $('#'+id+'_jGraduate_colorBoxBegin'); - beginColorBox.css({'background-color':beginColor}); - - var beginOpacity = stops[0].getAttribute('stop-opacity'); - if(!beginOpacity) beginOpacity = '1.0'; - $('#'+id+'lg_jGraduate_beginOpacity').html( (beginOpacity*100)+'%' ); - - var endColor = stops[stops.length-1].getAttribute('stop-color'); - if(!endColor) endColor = '#000'; - endColorBox = $('#'+id+'_jGraduate_colorBoxEnd'); - endColorBox.css({'background-color':endColor}); - - var endOpacity = stops[stops.length-1].getAttribute('stop-opacity'); - if(!endOpacity) endOpacity = '1.0'; - $('#'+id+'jGraduate_endOpacity').html( (endOpacity*100)+'%' ); + 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'; - $('#'+id+'_jGraduate_colorBoxBegin').click(function() { + 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 colorbox = $(this); - var thisAlpha = (parseFloat(beginOpacity)*255).toString(16); + 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 = beginColor.substr(1) + 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){ - beginColor = color.val('hex') ? ('#'+color.val('hex')) : "none"; - beginOpacity = color.val('ahex') ? color.val('ahex')/100 : 1; - colorbox.css('background', beginColor); - $('#'+id+'_jGraduate_beginOpacity').html(parseInt(beginOpacity*100)+'%'); - stops[0].setAttribute('stop-color', beginColor); - stops[0].setAttribute('stop-opacity', beginOpacity); + }, 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() { @@ -580,462 +537,584 @@ jQuery.fn.jGraduate = $('#'+id+'_jGraduate_stopPicker').hide(); }); }); - $('#'+id+'_jGraduate_colorBoxEnd').click(function() { - $('div.jGraduate_LightBox').show(); - var colorbox = $(this); - var thisAlpha = (parseFloat(endOpacity)*255).toString(16); - while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; } - color = endColor.substr(1) + thisAlpha; - $('#'+id+'_jGraduate_stopPicker').css({'left': 100, 'top': 15}).jPicker({ - window: { title: "Pick the end color and opacity for the gradient" }, - images: { clientPath: $settings.images.clientPath }, - color: { active: color, alphaSupport: true } - }, function(color){ - endColor = color.val('hex') ? ('#'+color.val('hex')) : "none"; - endOpacity = color.val('ahex') ? color.val('ahex')/100 : 1; - colorbox.css('background', endColor); - $('#'+id+'_jGraduate_endOpacity').html(parseInt(endOpacity*100)+'%'); - stops[1].setAttribute('stop-color', endColor); - stops[1].setAttribute('stop-opacity', endOpacity); - $('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(); + } - // Radial gradient - (function() { - var svg = document.getElementById(id + '_rg_jgraduate_svg'); - // if we are sent a gradient, import it - if ($this.paint.type == "radialGradient") { - $this.paint.radialGradient.id = id+'_rg_jgraduate_grad'; - $this.paint.radialGradient = svg.appendChild($this.paint.radialGradient.cloneNode(true)); - } else { // we create a gradient - var grad = svg.appendChild(document.createElementNS(ns.svg, 'radialGradient')); - grad.id = id+'_rg_jgraduate_grad'; - grad.setAttribute('cx','0.5'); - grad.setAttribute('cy','0.5'); - grad.setAttribute('r','0.5'); - - var begin = grad.appendChild(document.createElementNS(ns.svg, 'stop')); - begin.setAttribute('offset', '0.0'); - begin.setAttribute('stop-color', '#ff0000'); - - var end = grad.appendChild(document.createElementNS(ns.svg, 'stop')); - end.setAttribute('offset', '1.0'); - end.setAttribute('stop-color', '#ffff00'); - - $this.paint.radialGradient = grad; + 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(); } - - var gradalpha = $this.paint.alpha; - $('#' + id + '_rg_jGraduate_OpacityInput').val(gradalpha); - var posx = parseInt(255*(gradalpha/100)) - 4.5; - $('#' + id + '_rg_jGraduate_AlphaArrows').css({'margin-left':posx}); - - var grad = $this.paint.radialGradient; - - var cx = parseFloat(grad.getAttribute('cx')||0.5), - cy = parseFloat(grad.getAttribute('cy')||0.5), - fx = parseFloat(grad.getAttribute('fx')||0.5), - fy = parseFloat(grad.getAttribute('fy')||0.5); - - // No match, so show focus point - var showFocus = grad.getAttribute('fx') != null && !(cx == fx && cy == fy); - - var rect = document.createElementNS(ns.svg, 'rect'); - rect.id = id + '_rg_jgraduate_rect'; - rect.setAttribute('x', MARGINX); - rect.setAttribute('y', MARGINY); - rect.setAttribute('width', SIZEY); - rect.setAttribute('height', SIZEY); - rect.setAttribute('fill', 'url(#'+id+'_rg_jgraduate_grad)'); - rect.setAttribute('fill-opacity', '1.0'); - - rect = svg.appendChild(rect); - - $('#' + id + '_rg_jgraduate_rect').attr('fill-opacity', gradalpha/100); - - // stop visuals created here - var centerPoint = document.createElementNS(ns.svg, 'image'); - centerPoint.id = id + "_center_pt"; - centerPoint.setAttribute('class', 'stop'); - centerPoint.setAttributeNS(ns.xlink, 'href', $settings.images.clientPath + 'mappoint_c.png'); - centerPoint.setAttributeNS(ns.xlink, "title", "Center Point"); - centerPoint.appendChild(document.createElementNS(ns.svg, 'title')).appendChild( - document.createTextNode("Center Point")); - centerPoint.setAttribute('width', 18); - centerPoint.setAttribute('height', 18); - centerPoint.setAttribute('x', MARGINX + SIZEX*cx - STOP_RADIUS); - centerPoint.setAttribute('y', MARGINY + SIZEY*cy - STOP_RADIUS); - centerPoint.setAttribute('cursor', 'move'); - - - var focusPoint = document.createElementNS(ns.svg, 'image'); - focusPoint.id = id + "_focus_pt"; - focusPoint.setAttribute('class', 'stop'); - focusPoint.setAttributeNS(ns.xlink, 'href', $settings.images.clientPath + 'mappoint_f.png'); - focusPoint.setAttributeNS(ns.xlink, "title", "Focus Point"); - focusPoint.appendChild(document.createElementNS(ns.svg, 'title')).appendChild( - document.createTextNode("Focus Point")); - focusPoint.setAttribute('width', 18); - focusPoint.setAttribute('height', 18); - focusPoint.setAttribute('x', MARGINX + SIZEX*fx - STOP_RADIUS); - focusPoint.setAttribute('y', MARGINY + SIZEY*fy - STOP_RADIUS); - focusPoint.setAttribute('cursor', 'move'); - - // must append only after setting all attributes due to Webkit Bug 27952 - // https://bugs.webkit.org/show_bug.cgi?id=27592 - - // centerPoint is added last so it is moved first - focusPoint = svg.appendChild(focusPoint); - centerPoint = svg.appendChild(centerPoint); - - // bind GUI elements - $('#'+id+'_rg_jGraduate_Ok').bind('click', function() { - $this.paint.type = "radialGradient"; - $this.paint.solidColor = null; - okClicked(); - }); - $('#'+id+'_rg_jGraduate_Cancel').bind('click', function(paint) { - cancelClicked(); - }); - - var cx = $this.paint.radialGradient.getAttribute('cx'); - if(!cx) cx = "0.0"; - var cxInput = $('#'+id+'_jGraduate_cx'); - cxInput.val(cx); - cxInput.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 0.0; - } - $this.paint.radialGradient.setAttribute('cx', this.value); - centerPoint.setAttribute('x', MARGINX + SIZEX*this.value - STOP_RADIUS); - }); - - var cy = $this.paint.radialGradient.getAttribute('cy'); - if(!cy) cy = "0.0"; - var cyInput = $('#'+id+'_jGraduate_cy'); - cyInput.val(cy); - cyInput.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 0.0; - } - $this.paint.radialGradient.setAttribute('cy', this.value); - centerPoint.setAttribute('y', MARGINY + SIZEY*this.value - STOP_RADIUS); - }); - - var fx = $this.paint.radialGradient.getAttribute('fx'); - if(!fx) fx = "1.0"; - var fxInput = $('#'+id+'_jGraduate_fx'); - fxInput.val(fx); - fxInput.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 1.0; - } - $this.paint.radialGradient.setAttribute('fx', this.value); - focusPoint.setAttribute('x', MARGINX + SIZEX*this.value - STOP_RADIUS); - }); - - var fy = $this.paint.radialGradient.getAttribute('fy'); - if(!fy) fy = "0.0"; - var fyInput = $('#'+id+'_jGraduate_fy'); - fyInput.val(fy); - fyInput.change( function() { - if (isNaN(parseFloat(this.value)) || this.value < 0.0 || this.value > 1.0) { - this.value = 0.0; - } - $this.paint.radialGradient.setAttribute('fy', this.value); - focusPoint.setAttribute('y', MARGINY + SIZEY*this.value - STOP_RADIUS); - }); - - if(!showFocus) { - focusPoint.setAttribute('display', 'none'); - fxInput.val(""); - fyInput.val(""); + 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) { - $("#" + id + "_jGraduate_match_ctr")[0].checked = !showFocus; - - var lastfx, lastfy; - - $("#" + id + "_jGraduate_match_ctr").change(function() { - showFocus = !this.checked; - focusPoint.setAttribute('display', showFocus?'inline':'none'); - fxInput.val(""); - fyInput.val(""); - var grad = $this.paint.radialGradient; - if(!showFocus) { - lastfx = grad.getAttribute('fx'); - lastfy = grad.getAttribute('fy'); - grad.removeAttribute('fx'); - grad.removeAttribute('fy'); + 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 { - var fx = lastfx || .5; - var fy = lastfy || .5; - grad.setAttribute('fx', fx); - grad.setAttribute('fy', fy); - fxInput.val(fx); - fyInput.val(fy); + 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; }); - var stops = $this.paint.radialGradient.getElementsByTagNameNS(ns.svg, 'stop'); - var numstops = stops.length; - // if there are not at least two stops, then - if (numstops < 2) { - while (numstops < 2) { - $this.paint.radialGradient.appendChild( document.createElementNS(ns.svg, 'stop') ); - ++numstops; - } - stops = $this.paint.radialGradient.getElementsByTagNameNS(ns.svg, 'stop'); + } + + 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).click(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(); } - var radius = $this.paint.radialGradient.getAttribute('r')-0; - var radiusx = parseInt((245/2)*(radius)) - 4.5; - $('#' + id + '_jGraduate_RadiusArrows').css({'margin-left':radiusx}); - $('#' + id + '_jGraduate_RadiusInput').val(parseInt(radius*100)).change(function(e) { - var x = this.value / 100; - if(x < 0.01) { - x = 0.01; + + 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', function() { + $this.paint.type = curType; + $this.paint[curType] = curGradient.cloneNode(true);; + $this.paint.solidColor = null; + okClicked(); + }); + $('#'+id+'_jGraduate_Cancel').bind('click', function(paint) { + cancelClicked(); + }); + + if(curType === 'radialGradient') { + if(showFocus) { + focusCoord.show(); + } else { + focusCoord.hide(); + 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); - $this.paint.radialGradient.setAttribute('r', x); - // Allow higher value, but pretend it's the max for the slider - if(x > 2) x = 2; - var posx = parseInt((245/2) * x) - 4.5; - $('#' + id + '_jGraduate_RadiusArrows').css({'margin-left':posx}); - - }); - - var setRgOpacitySlider = function(e, div) { - var offset = div.offset(); - var x = (e.pageX - offset.left - parseInt(div.css('border-left-width'))); - if (x > 255) x = 255; - if (x < 0) x = 0; - var posx = x - 4.5; - x /= 255; - $('#' + id + '_rg_jGraduate_AlphaArrows').css({'margin-left':posx}); - $('#' + id + '_rg_jgraduate_rect').attr('fill-opacity', x); - x = parseInt(x*100); - $('#' + id + '_rg_jGraduate_OpacityInput').val(x); - $this.paint.alpha = x; - }; - - // handle dragging on the opacity slider - var bSlidingOpacity = false; - $('#' + id + '_rg_jGraduate_Opacity').mousedown(function(evt) { - setRgOpacitySlider(evt, $(this)); - bSlidingOpacity = true; - evt.preventDefault(); - }).mousemove(function(evt) { - if (bSlidingOpacity) { - setRgOpacitySlider(evt, $(this)); - evt.preventDefault(); - } - }).mouseup(function(evt) { - setRgOpacitySlider(evt, $(this)); - bSlidingOpacity = false; - evt.preventDefault(); - }); - - var setRadiusSlider = function(e, div) { - var offset = div.offset(); - var x = (e.pageX - offset.left - parseInt(div.css('border-left-width'))); - if (x > 245) x = 245; - if (x <= 1) x = 1; - var posx = x - 5; - x /= (245/2); - $('#' + id + '_jGraduate_RadiusArrows').css({'margin-left':posx}); - $this.paint.radialGradient.setAttribute('r', x); - x = parseInt(x*100); - - $('#' + id + '_jGraduate_RadiusInput').val(x); - }; - - // handle dragging on the radius slider - var bSlidingRadius = false; - $('#' + id + '_jGraduate_Radius').mousedown(function(evt) { - setRadiusSlider(evt, $(this)); - bSlidingRadius = true; - evt.preventDefault(); - }).mousemove(function(evt) { - if (bSlidingRadius) { - setRadiusSlider(evt, $(this)); - evt.preventDefault(); - } - }).mouseup(function(evt) { - setRadiusSlider(evt, $(this)); - bSlidingRadius = false; - evt.preventDefault(); - }); - - - // handle dragging the stop around the swatch - var draggingStop = null; - var startx = -1, starty = -1; - // for whatever reason, Opera does not allow $('image.stop') here, - // and Firefox 1.5 does not allow $('.stop') - $('.stop, #color_picker_rg_jGraduate_GradContainer image').mousedown(function(evt) { - draggingStop = this; - startx = evt.clientX; - starty = evt.clientY; - evt.preventDefault(); - }); - $('#'+id+'_rg_jgraduate_svg').mousemove(function(evt) { - if (null != draggingStop) { - var dx = evt.clientX - startx; - var dy = evt.clientY - starty; - startx += dx; - starty += dy; - var x = parseFloat(draggingStop.getAttribute('x')) + dx; - var y = parseFloat(draggingStop.getAttribute('y')) + dy; - - // clamp stop to the swatch - if (x < MARGINX - STOP_RADIUS) x = MARGINX - STOP_RADIUS; - if (y < MARGINY - STOP_RADIUS) y = MARGINY - STOP_RADIUS; - if (x > MARGINX + SIZEX - STOP_RADIUS) x = MARGINX + SIZEX - STOP_RADIUS; - if (y > MARGINY + SIZEY - STOP_RADIUS) y = MARGINY + SIZEY - STOP_RADIUS; - - draggingStop.setAttribute('x', x); - draggingStop.setAttribute('y', y); - - // calculate stop offset - var fracx = (x - MARGINX + STOP_RADIUS)/SIZEX; - var fracy = (y - MARGINY + STOP_RADIUS)/SIZEY; + 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; - if (draggingStop.id == (id+'_center_pt')) { - cxInput.val(fracx); - cyInput.val(fracy); - $this.paint.radialGradient.setAttribute('cx', fracx); - $this.paint.radialGradient.setAttribute('cy', fracy); + case 'opacity': + $this.paint.alpha = val; + previewRect.setAttribute('fill-opacity', val / 100); + xpos = val * (SLIDERW / 100); + break; - if(!showFocus) { - $this.paint.radialGradient.setAttribute('fx', fracx); - $this.paint.radialGradient.setAttribute('fy', fracy); + case 'ellip': + scale_x = scale_y = 1; + if(val === 0) { + xpos = SLIDERW * .5; + break; } - } - else { - fxInput.val(fracx); - fyInput.val(fracy); - $this.paint.radialGradient.setAttribute('fx', fracx); - $this.paint.radialGradient.setAttribute('fy', fracy); - } + 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; - evt.preventDefault(); + case 'angle': + angle = val; + xpos = angle / 180; + xpos += .5; + xpos *= SLIDERW; + if(isRad) xform(); } - }); - $('#'+id+'_rg_jgraduate_svg').mouseup(function(evt) { - draggingStop = null; - }); - - var centerColor = stops[0].getAttribute('stop-color'); - if(!centerColor) centerColor = '#000'; - centerColorBox = $('#'+id+'_jGraduate_colorBoxCenter'); - centerColorBox.css({'background-color':centerColor}); - - var centerOpacity = stops[0].getAttribute('stop-opacity'); - if(!centerOpacity) centerOpacity = '1.0'; - $('#'+id+'jGraduate_centerOpacity').html( (centerOpacity*100)+'%' ); - - var outerColor = stops[stops.length-1].getAttribute('stop-color'); - if(!outerColor) outerColor = '#000'; - outerColorBox = $('#'+id+'_jGraduate_colorBoxOuter'); - outerColorBox.css({'background-color':outerColor}); - - var outerOpacity = stops[stops.length-1].getAttribute('stop-opacity'); - if(!outerOpacity) outerOpacity = '1.0'; - $('#'+id+'rg_jGraduate_outerOpacity').html( (outerOpacity*100)+'%' ); - - $('#'+id+'_jGraduate_colorBoxCenter').click(function() { - $('div.jGraduate_LightBox').show(); - var colorbox = $(this); - var thisAlpha = (parseFloat(centerOpacity)*255).toString(16); - while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; } - color = centerColor.substr(1) + thisAlpha; - $('#'+id+'_rg_jGraduate_stopPicker').css({'left': 100, 'bottom': 15}).jPicker({ - window: { title: "Pick the center color and opacity for the gradient" }, - images: { clientPath: $settings.images.clientPath }, - color: { active: color, alphaSupport: true } - }, function(color){ - centerColor = color.val('hex') ? ('#'+color.val('hex')) : "none"; - centerOpacity = color.val('ahex') ? color.val('ahex')/100 : 1; - colorbox.css('background', centerColor); - $('#'+id+'_rg_jGraduate_centerOpacity').html(parseInt(centerOpacity*100)+'%'); - stops[0].setAttribute('stop-color', centerColor); - stops[0].setAttribute('stop-opacity', centerOpacity); - $('div.jGraduate_LightBox').hide(); - $('#'+id+'_rg_jGraduate_stopPicker').hide(); - }, null, function() { - $('div.jGraduate_LightBox').hide(); - $('#'+id+'_rg_jGraduate_stopPicker').hide(); - }); - }); - $('#'+id+'_jGraduate_colorBoxOuter').click(function() { - $('div.jGraduate_LightBox').show(); - var colorbox = $(this); - var thisAlpha = (parseFloat(outerOpacity)*255).toString(16); - while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; } - color = outerColor.substr(1) + thisAlpha; - $('#'+id+'_rg_jGraduate_stopPicker').css({'left': 100, 'top': 15}).jPicker({ - window: { title: "Pick the outer color and opacity for the gradient" }, - images: { clientPath: $settings.images.clientPath }, - color: { active: color, alphaSupport: true } - }, function(color){ - outerColor = color.val('hex') ? ('#'+color.val('hex')) : "none"; - outerOpacity = color.val('ahex') ? color.val('ahex')/100 : 1; - colorbox.css('background', outerColor); - $('#'+id+'_jGraduate_outerOpacity').html(parseInt(outerOpacity*100)+'%'); - stops[1].setAttribute('stop-color', outerColor); - stops[1].setAttribute('stop-opacity', outerOpacity); - $('div.jGraduate_LightBox').hide(); - $('#'+id+'_rg_jGraduate_stopPicker').hide(); - }, null, function() { - $('div.jGraduate_LightBox').hide(); - $('#'+id+'_rg_jGraduate_stopPicker').hide(); - }); - }); - - // -------------- - 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; - - // 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(); } - ); - }()); + 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.click(function() { tabs.removeClass('jGraduate_tab_current'); $(this).addClass('jGraduate_tab_current'); $(idref + " > div").hide(); - $(idref + ' .jGraduate_' + $(this).attr('data-type') + 'Pick').show(); + 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'); diff --git a/public/svg-edit/editor/math.js b/public/svg-edit/editor/math.js new file mode 100644 index 00000000..5ae1e949 --- /dev/null +++ b/public/svg-edit/editor/math.js @@ -0,0 +1,234 @@ +/** + * Package: svedit.math + * + * Licensed under the Apache License, Version 2 + * + * Copyright(c) 2010 Alexis Deveria + * Copyright(c) 2010 Jeff Schiller + */ + +// Dependencies: +// None. + +(function() { + +if (!window.svgedit) { + window.svgedit = {}; +} + +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.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) { + 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; +}; + + +})(); \ No newline at end of file diff --git a/public/svg-edit/editor/sanitize.js b/public/svg-edit/editor/sanitize.js new file mode 100644 index 00000000..0a14bb9f --- /dev/null +++ b/public/svg-edit/editor/sanitize.js @@ -0,0 +1,274 @@ +/** + * Package: svgedit.sanitize + * + * Licensed under the Apache License, Version 2 + * + * Copyright(c) 2010 Alexis Deveria + * Copyright(c) 2010 Jeff Schiller + */ + +// Dependencies: +// 1) browsersupport.js +// 2) svgutils.js + +(function() { + +if (!window.svgedit) { + window.svgedit = {}; +} + +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": [], + "desc": [], + "ellipse": ["class", "clip-path", "clip-rule", "cx", "cy", "fill", "fill-opacity", "fill-rule", "filter", "id", "mask", "opacity", "requiredFeatures", "rx", "ry", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"], + "feGaussianBlur": ["class", "color-interpolation-filters", "id", "requiredFeatures", "stdDeviation"], + "filter": ["class", "color-interpolation-filters", "filterRes", "filterUnits", "height", "id", "primitiveUnits", "requiredFeatures", "width", "x", "xlink:href", "y"], + "foreignObject": ["class", "font-size", "height", "id", "opacity", "requiredFeatures", "style", "transform", "width", "x", "y"], + "g": ["class", "clip-path", "clip-rule", "id", "display", "fill", "fill-opacity", "fill-rule", "filter", "mask", "opacity", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform", "font-family", "font-size", "font-style", "font-weight", "text-anchor"], + "image": ["class", "clip-path", "clip-rule", "filter", "height", "id", "mask", "opacity", "requiredFeatures", "style", "systemLanguage", "transform", "width", "x", "xlink:href", "xlink:title", "y"], + "line": ["class", "clip-path", "clip-rule", "fill", "fill-opacity", "fill-rule", "filter", "id", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform", "x1", "x2", "y1", "y2"], + "linearGradient": ["class", "id", "gradientTransform", "gradientUnits", "requiredFeatures", "spreadMethod", "systemLanguage", "x1", "x2", "xlink:href", "y1", "y2"], + "marker": ["id", "class", "markerHeight", "markerUnits", "markerWidth", "orient", "preserveAspectRatio", "refX", "refY", "systemLanguage", "viewBox"], + "mask": ["class", "height", "id", "maskContentUnits", "maskUnits", "width", "x", "y"], + "metadata": ["class", "id"], + "path": ["class", "clip-path", "clip-rule", "d", "fill", "fill-opacity", "fill-rule", "filter", "id", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"], + "pattern": ["class", "height", "id", "patternContentUnits", "patternTransform", "patternUnits", "requiredFeatures", "style", "systemLanguage", "viewBox", "width", "x", "xlink:href", "y"], + "polygon": ["class", "clip-path", "clip-rule", "id", "fill", "fill-opacity", "fill-rule", "filter", "id", "class", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "points", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"], + "polyline": ["class", "clip-path", "clip-rule", "id", "fill", "fill-opacity", "fill-rule", "filter", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "points", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"], + "radialGradient": ["class", "cx", "cy", "fx", "fy", "gradientTransform", "gradientUnits", "id", "r", "requiredFeatures", "spreadMethod", "systemLanguage", "xlink:href"], + "rect": ["class", "clip-path", "clip-rule", "fill", "fill-opacity", "fill-rule", "filter", "height", "id", "mask", "opacity", "requiredFeatures", "rx", "ry", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform", "width", "x", "y"], + "stop": ["class", "id", "offset", "requiredFeatures", "stop-color", "stop-opacity", "style", "systemLanguage"], + "svg": ["class", "clip-path", "clip-rule", "filter", "id", "height", "mask", "preserveAspectRatio", "requiredFeatures", "style", "systemLanguage", "viewBox", "width", "x", "xmlns", "xmlns:se", "xmlns:xlink", "y"], + "switch": ["class", "id", "requiredFeatures", "systemLanguage"], + "symbol": ["class", "fill", "fill-opacity", "fill-rule", "filter", "font-family", "font-size", "font-style", "font-weight", "id", "opacity", "preserveAspectRatio", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform", "viewBox"], + "text": ["class", "clip-path", "clip-rule", "fill", "fill-opacity", "fill-rule", "filter", "font-family", "font-size", "font-style", "font-weight", "id", "mask", "opacity", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "text-anchor", "transform", "x", "xml:space", "y"], + "textPath": ["class", "id", "method", "requiredFeatures", "spacing", "startOffset", "style", "systemLanguage", "transform", "xlink:href"], + "title": [], + "tspan": ["class", "clip-path", "clip-rule", "dx", "dy", "fill", "fill-opacity", "fill-rule", "filter", "font-family", "font-size", "font-style", "font-weight", "id", "mask", "opacity", "requiredFeatures", "rotate", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "text-anchor", "textLength", "transform", "x", "xml:space", "y"], + "use": ["class", "clip-path", "clip-rule", "fill", "fill-opacity", "fill-rule", "filter", "height", "id", "mask", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "transform", "width", "x", "xlink:href", "y"], + + // MathML Elements + "annotation": ["encoding"], + "annotation-xml": ["encoding"], + "maction": ["actiontype", "other", "selection"], + "math": ["class", "id", "display", "xmlns"], + "menclose": ["notation"], + "merror": [], + "mfrac": ["linethickness"], + "mi": ["mathvariant"], + "mmultiscripts": [], + "mn": [], + "mo": ["fence", "lspace", "maxsize", "minsize", "rspace", "stretchy"], + "mover": [], + "mpadded": ["lspace", "width"], + "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.browsersupport.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 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]); } + + } +}; + +})(); + diff --git a/public/svg-edit/editor/select.js b/public/svg-edit/editor/select.js new file mode 100644 index 00000000..a75161d1 --- /dev/null +++ b/public/svg-edit/editor/select.js @@ -0,0 +1,531 @@ +/** + * Package: svedit.select + * + * Licensed under the Apache License, Version 2 + * + * Copyright(c) 2010 Alexis Deveria + * Copyright(c) 2010 Jeff Schiller + */ + +// Dependencies: +// 1) jQuery +// 2) browsersupport.js +// 3) math.js +// 4) svgutils.js + +(function() { + +if (!window.svgedit) { + window.svgedit = {}; +} + +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 element that holds all visual elements of the selector + this.selectorGroup = svgFactory_.createSVGElement({ + 'element': 'g', + 'attr': {'id': ('selectorGroup' + this.id)} + }); + + // this holds a reference to the path rect + this.selectorRect = this.selectorGroup.appendChild( + svgFactory_.createSVGElement({ + 'element': 'path', + 'attr': { + 'id': ('selectedBox' + this.id), + 'fill': 'none', + 'stroke': '#22C', + 'stroke-width': '1', + 'stroke-dasharray': '5,5', + // need to specify this so that the rect is not selectable + 'style': 'pointer-events:none' + } + }) + ); + + // this holds a reference to the grip coordinates for this selector + this.gripCoords = { + 'nw': null, + 'n' : null, + 'ne': null, + 'e' : null, + 'se': null, + 's' : null, + 'sw': null, + 'w' : null + }; + + this.reset(this.selectedElement); +}; + + +// 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) { + // TODO: use suspendRedraw() here + 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 sr_handle = svgFactory_.svgRoot().suspendRedraw(100); + + var dstr = 'M' + nbax + ',' + nbay + + ' L' + (nbax+nbaw) + ',' + nbay + + ' ' + (nbax+nbaw) + ',' + (nbay+nbah) + + ' ' + nbax + ',' + (nbay+nbah) + 'z'; + selectedBox.setAttribute('d', dstr); + + var xform = angle ? 'rotate(' + [angle,cx,cy].join(',') + ')' : ''; + this.selectorGroup.setAttribute('transform', xform); + + // TODO(codedread): Is this if needed? +// if(selected === selectedElements[0]) { + this.gripCoords = { + 'nw': [nbax, nbay], + 'ne': [nbax+nbaw, nbay], + 'sw': [nbax, nbay+nbah], + 'se': [nbax+nbaw, nbay+nbah], + 'n': [nbax + (nbaw)/2, nbay], + 'w': [nbax, nbay + (nbah)/2], + 'e': [nbax + nbaw, nbay + (nbah)/2], + 's': [nbax + (nbaw)/2, nbay + nbah] + }; + + for(var dir in this.gripCoords) { + var coords = this.gripCoords[dir]; + selectedGrips[dir].setAttribute('cx', coords[0]); + selectedGrips[dir].setAttribute('cy', coords[1]); + }; + + // we want to go 20 pixels in the negative transformed y direction, ignoring scale + mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw)/2); + mgr.rotateGripConnector.setAttribute('y1', nbay); + mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw)/2); + mgr.rotateGripConnector.setAttribute('y2', nbay - 20); + + mgr.rotateGrip.setAttribute('cx', nbax + (nbaw)/2); + mgr.rotateGrip.setAttribute('cy', nbay - 20); +// } + + svgFactory_.svgRoot().unsuspendRedraw(sr_handle); +}; + + +// Class: svgedit.select.SelectorManager +svgedit.select.SelectorManager = function() { + // this will hold the element that contains all selector rects/grips + this.selectorParentGroup = null; + + // this is a special rect that is used for multi-select + this.rubberBandBox = null; + + // this will hold objects of type 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.rotateGrip = 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; + + // add the corner grips + for (var dir in this.selectorGrips) { + var grip = svgFactory_.createSVGElement({ + 'element': 'circle', + 'attr': { + 'id': ('selectorGrip_resize_' + dir), + 'fill': '#22C', + 'r': 4, + 'style': ('cursor:' + dir + '-resize'), + // This expands the mouse-able area of the grips making them + // easier to grab with the mouse. + // This works in Opera and WebKit, but does not work in Firefox + // see https://bugzilla.mozilla.org/show_bug.cgi?id=500174 + 'stroke-width': 2, + 'pointer-events': 'all' + } + }); + + $.data(grip, 'dir', dir); + $.data(grip, 'type', 'resize'); + this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip); + } + + // add rotator elems + this.rotateGripConnector = this.selectorGripsGroup.appendChild( + svgFactory_.createSVGElement({ + 'element': 'line', + 'attr': { + 'id': ('selectorGrip_rotateconnector'), + 'stroke': '#22C', + 'stroke-width': '1' + } + }) + ); + + this.rotateGrip = this.selectorGripsGroup.appendChild( + svgFactory_.createSVGElement({ + 'element': 'circle', + 'attr': { + 'id': 'selectorGrip_rotate', + 'fill': 'lime', + 'r': 4, + 'stroke': '#22C', + 'stroke-width': 2, + 'style': 'cursor:url(' + config_.imgPath + 'rotate.png) 12 12, auto;' + } + }) + ); + $.data(this.rotateGrip, 'type', 'rotate'); + + 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.browsersupport.isWebkit() ? 'none' : 'visible'), // Chrome 7 has a problem with this when zooming out + 'style': 'pointer-events:none' + } + }); + + var rect = svgFactory_.createSVGElement({ + 'element': 'rect', + 'attr': { + 'width': '100%', + 'height': '100%', + 'x': 0, + 'y': 0, + 'stroke-width': 1, + 'stroke': '#000', + 'fill': '#FFF', + 'style': 'pointer-events:none' + } + }); + + // Both Firefox and WebKit are too slow with this filter region (especially at higher + // zoom levels) and Opera has at least one bug +// if (!svgedit.browsersupport.isOpera()) rect.setAttribute('filter', 'url(#canvashadow)'); + 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': '#22C', + 'fill-opacity': 0.15, + 'stroke': '#22C', + 'stroke-width': 0.5, + '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(); +}; + +/** + * Function: svgedit.select.getSelectorManager + * + * Returns: + * The SelectorManager instance. + */ +svgedit.select.getSelectorManager = function() { + return selectorManager_; +}; + +})(); \ No newline at end of file diff --git a/public/svg-edit/editor/svg-editor.css b/public/svg-edit/editor/svg-editor.css index 035c9214..c13f1b03 100644 --- a/public/svg-edit/editor/svg-editor.css +++ b/public/svg-edit/editor/svg-editor.css @@ -1,5 +1,5 @@ body { - background: #D8D8D8; + background: #D0D0D0; } #svg_editor * { @@ -223,7 +223,7 @@ #svg_editor #sidepanel_handle { display: inline-block; position: absolute; - background-color: #D8D8D8; + background-color: #D0D0D0; font-weight: bold; left: 0px; top: 40%; @@ -485,7 +485,7 @@ top: 75px; left: 0; padding-left: 2px; - background: #D8D8D8; /* Needed so flyout icons don't appear on the left */ + background: #D0D0D0; /* Needed so flyout icons don't appear on the left */ z-index: 4; } diff --git a/public/svg-edit/editor/svg-editor.html b/public/svg-edit/editor/svg-editor.html index 4b9910e0..09f207aa 100644 --- a/public/svg-edit/editor/svg-editor.html +++ b/public/svg-edit/editor/svg-editor.html @@ -10,14 +10,22 @@ - - + + + + + + + + + + @@ -30,10 +38,8 @@ - - -script type="text/javascript" src="locale/locale.min.js"> - + +--> @@ -397,7 +403,7 @@ script type="text/javascript" src="locale/locale.min.js">
    - +