Update SVG-Edit

This commit is contained in:
Jacques Distler 2010-11-30 23:23:58 -06:00
parent f914a40fa9
commit fccb1e2b01
24 changed files with 6126 additions and 3751 deletions

View file

@ -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 <a href='http://opera.com'>latest version</a> 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();
})();

View file

@ -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++){

View file

@ -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];

View file

@ -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;
};
})();

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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;
};
})();

View file

@ -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 <use> without a xlink:href, so we just remove the node here
if (node.nodeName == "use" && !svgedit.utilities.getHref(node)) {
parent.removeChild(node);
return;
}
// if the element has attributes pointing to a non-local reference,
// need to remove the attribute
$.each(["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"],function(i,attr) {
var val = node.getAttribute(attr);
if (val) {
val = svgedit.utilities.getUrlFromAttr(val);
// simply check for first character being a '#'
if (val && val[0] !== "#") {
node.setAttribute(attr, "");
node.removeAttribute(attr);
}
}
});
// recurse to children
i = node.childNodes.length;
while (i--) { svgedit.sanitize.sanitizeSvg(node.childNodes.item(i)); }
}
// else, remove this element
else {
// remove all children from this node and insert them before this node
// FIXME: in the case of animation elements this will hardly ever be correct
var children = [];
while (node.hasChildNodes()) {
children.push(parent.insertBefore(node.firstChild, node));
}
// remove this node from the document altogether
parent.removeChild(node);
// call sanitizeSvg on each of those children
var i = children.length;
while (i--) { svgedit.sanitize.sanitizeSvg(children[i]); }
}
};
})();

View file

@ -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 <g> element that holds all visual elements of the selector
this.selectorGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'id': ('selectorGroup' + this.id)}
});
// this holds a reference to the path rect
this.selectorRect = this.selectorGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'path',
'attr': {
'id': ('selectedBox' + this.id),
'fill': 'none',
'stroke': '#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 <g> element that contains all selector rects/grips
this.selectorParentGroup = null;
// this is a special rect that is used for multi-select
this.rubberBandBox = null;
// this will hold objects of type svgedit.select.Selector (see above)
this.selectors = [];
// this holds a map of SVG elements to their Selector object
this.selectorMap = {};
// this holds a reference to the grip elements
this.selectorGrips = {
'nw': null,
'n' : null,
'ne': null,
'e' : null,
'se': null,
's' : null,
'sw': null,
'w' : null
};
this.selectorGripsGroup = null;
this.rotateGripConnector = null;
this.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_;
};
})();

View file

@ -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;
}

View file

@ -10,14 +10,22 @@
<link rel="stylesheet" href="svg-editor.css" type="text/css"/>
<link rel="stylesheet" href="spinbtn/JQuerySpinBtn.css" type="text/css"/>
<!-- Development version of script tags: -->
<!-- <script type="text/javascript" src="jquery.js"></script> -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js"></script>
<script type="text/javascript" src="jquery.js"></script>
<!--script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js"></script-->
<script type="text/javascript" src="js-hotkeys/jquery.hotkeys.min.js"></script>
<script type="text/javascript" src="jgraduate/jquery.jgraduate.js"></script>
<script type="text/javascript" src="svgicons/jquery.svgicons.js"></script>
<script type="text/javascript" src="jquerybbq/jquery.bbq.min.js"></script>
<script type="text/javascript" src="spinbtn/JQuerySpinBtn.js"></script>
<script type="text/javascript" src="contextmenu/jquery.contextMenu.js"></script>
<script type="text/javascript" src="browsersupport.js"></script>
<script type="text/javascript" src="svgtransformlist.js"></script>
<script type="text/javascript" src="math.js"></script>
<script type="text/javascript" src="units.js"></script>
<script type="text/javascript" src="svgutils.js"></script>
<script type="text/javascript" src="sanitize.js"></script>
<script type="text/javascript" src="history.js"></script>
<script type="text/javascript" src="select.js"></script>
<script type="text/javascript" src="svgcanvas.js"></script>
<script type="text/javascript" src="svg-editor.js"></script>
<script type="text/javascript" src="locale/locale.js"></script>
@ -30,10 +38,8 @@
<script type="text/javascript" src="jquerybbq/jquery.bbq.min.js"></script>
<script type="text/javascript" src="jgraduate/jquery.jgraduate.min.js"></script>
<script type="text/javascript" src="spinbtn/JQuerySpinBtn.min.js"></script>
<script type="text/javascript" src="svgcanvas.min.js"></script>
<script type="text/javascript" src="svg-editor.min.js"></script>
script type="text/javascript" src="locale/locale.min.js"></script-->
<script type="text/javascript" src="svgedit.compiled.js"></script>
-->
<!-- always minified scripts -->
<script type="text/javascript" src="jquery-ui/jquery-ui-1.8.custom.min.js"></script>
@ -397,7 +403,7 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<div id="path_node_panel">
<div class="tool_sep"></div>
<div class="tool_button" id="tool_node_link" title="Link Control Points"></div>
<div class="tool_button push_button_pressed" id="tool_node_link" title="Link Control Points"></div>
<div class="tool_sep"></div>
<label id="tool_node_x">x:
<input id="path_node_x" class="attr_changer" title="Change node's x coordinate" size="3" data-attr="x"/>

View file

@ -10,6 +10,10 @@
*
*/
// Dependencies:
// 1) units.js
// 2) svgcanvas.js
(function() {
if(!window.svgEditor) window.svgEditor = function($) {
@ -462,14 +466,14 @@
"#00007f", "#3f007f", "#7f007f", "#7f003f",
"#ffaaaa", "#ffd4aa", "#ffffaa", "#d4ffaa",
"#aaffaa", "#aaffd4", "#aaffff", "#aad4ff",
"#aaaaff", "#d4aaff", "#ffaaff", "#ffaad4",
"#aaaaff", "#d4aaff", "#ffaaff", "#ffaad4"
],
isMac = (navigator.platform.indexOf("Mac") >= 0),
isWebkit = (navigator.userAgent.indexOf("AppleWebKit") >= 0),
modKey = (isMac ? "meta+" : "ctrl+"), // ⌘
path = svgCanvas.pathActions,
undoMgr = svgCanvas.undoMgr,
Utils = svgCanvas.Utils,
Utils = svgedit.utilities,
default_img_url = curConfig.imgPath + "logo.png",
workarea = $("#workarea"),
canv_menu = $("#cmenu_canvas"),
@ -489,7 +493,7 @@
// In the future we may want to add additional types of dialog boxes, since
// they should be easy to handle this way.
(function() {
$('#dialog_container').draggable({cancel:'#dialog_content, #dialog_buttons *'});
$('#dialog_container').draggable({cancel:'#dialog_content, #dialog_buttons *', containment: 'window'});
var box = $('#dialog_box'), btn_holder = $('#dialog_buttons');
var dbox = function(type, msg, callback, defText) {
@ -1321,11 +1325,30 @@
// This function also updates the opacity and id elements that are in the context panel
var updateToolbar = function() {
if (selectedElement != null && ['use', 'image', 'foreignObject', 'g', 'a'].indexOf(selectedElement.tagName) === -1) {
var all_swidth = null;
// if(selectedElement.tagName === "g" || selectedElement.tagName === "a") {
// // Look for common styles
// var childs = selectedElement.getElementsByTagName('*');
// console.log('ch', childs);
// for(var i = 0, len = childs.length; i < len; i++) {
// var elem = childs[i];
// var swidth = elem.getAttribute("stroke-width");
// if(swidth && swidth !== all_swidth) {
// // different, so do don't check more
// all_swidth = null;
// break;
// } else if(swidth) {
// console.log('e', elem, swidth);
// all_swidth = swidth;
// }
// }
// }
paintBox.fill.update(true);
paintBox.stroke.update(true);
$('#stroke_width').val(selectedElement.getAttribute("stroke-width")||1);
$('#stroke_width').val(all_swidth || selectedElement.getAttribute("stroke-width") || 1);
$('#stroke_style').val(selectedElement.getAttribute("stroke-dasharray")||"none");
var attr = selectedElement.getAttribute("stroke-linejoin") || 'miter';
@ -1784,7 +1807,7 @@
$('.attr_changer').change(function() {
var attr = this.getAttribute("data-attr");
var val = this.value;
var valid = svgCanvas.isValidUnit(attr, val);
var valid = svgedit.units.isValidUnit(attr, val);
if(!valid) {
$.alert(uiStrings.invalidAttrValGiven);
@ -1797,11 +1820,7 @@
} else if(curConfig.baseUnit !== 'px') {
// Convert unitless value to one with given unit
// val = svgCanvas.convertUnit(bv, "px");
// selectedElement[attr].baseVal.newValueSpecifiedUnits();
// this.value = val;
// selectedElement[attr].baseVal
var unitData = svgCanvas.getUnits();
var unitData = svgedit.units.getTypeMap();
if(selectedElement[attr] || svgCanvas.getMode() === "pathedit" || attr === "x" || attr === "y") {
val *= unitData[curConfig.baseUnit];
@ -2594,7 +2613,7 @@
$('#svg_source_textarea').focus();
};
$('#svg_docprops_container, #svg_prefs_container').draggable({cancel:'button,fieldset'});
$('#svg_docprops_container, #svg_prefs_container').draggable({cancel:'button,fieldset', containment: 'window'});
var showDocProperties = function(){
if (docprops) return;
@ -2605,6 +2624,11 @@
// update resolution option with actual resolution
var res = svgCanvas.getResolution();
if(curConfig.baseUnit !== "px") {
res.w = svgCanvas.convertUnit(res.w) + curConfig.baseUnit;
res.h = svgCanvas.convertUnit(res.h) + curConfig.baseUnit;
}
$('#canvas_width').val(res.w);
$('#canvas_height').val(res.h);
$('#canvas_title').val(svgCanvas.getDocumentTitle());
@ -2694,7 +2718,7 @@
var width = $('#canvas_width'), w = width.val();
var height = $('#canvas_height'), h = height.val();
if(w != "fit" && !svgCanvas.isValidUnit('width', w)) {
if(w != "fit" && !svgedit.units.isValidUnit('width', w)) {
$.alert(uiStrings.invalidAttrValGiven);
width.parent().addClass('error');
return false;
@ -2702,7 +2726,7 @@
width.parent().removeClass('error');
if(h != "fit" && !svgCanvas.isValidUnit('height', h)) {
if(h != "fit" && !svgedit.units.isValidUnit('height', h)) {
$.alert(uiStrings.invalidAttrValGiven);
height.parent().addClass('error');
return false;
@ -2920,7 +2944,7 @@
"div#workarea": {
'left': 38,
'top': 74
},
}
// "#tools_bottom": {
// 'left': {s: '27px', l: '46px', xl: '65px'},
// 'height': {s: '58px', l: '98px', xl: '145px'}
@ -3170,7 +3194,7 @@
var was_none = false;
var pos = elem.position();
$("#color_picker")
.draggable({cancel:'.jGraduate_tabs,.jGraduate_colPick,.jGraduate_lgPick,.jGraduate_rgPick'})
.draggable({cancel:'.jGraduate_tabs, .jGraduate_colPick, .jGraduate_gradPick, .jPicker', containment: 'window'})
.css(curConfig.colorPickerCSS || {'left': pos.left, 'bottom': 50 - pos.top})
.jGraduate(
{
@ -3197,7 +3221,7 @@
var buttonsNeedingStroke = [ '#tool_fhpath', '#tool_line' ];
var buttonsNeedingFillAndStroke = [ '#tools_rect .tool_button', '#tools_ellipse .tool_button', '#tool_text', '#tool_path'];
if (bNoStroke) {
for (index in buttonsNeedingStroke) {
for (var index in buttonsNeedingStroke) {
var button = buttonsNeedingStroke[index];
if ($(button).hasClass('tool_button_current')) {
clickSelect();
@ -3206,14 +3230,14 @@
}
}
else {
for (index in buttonsNeedingStroke) {
for (var index in buttonsNeedingStroke) {
var button = buttonsNeedingStroke[index];
$(button).removeClass('disabled');
}
}
if (bNoStroke && bNoFill) {
for (index in buttonsNeedingFillAndStroke) {
for (var index in buttonsNeedingFillAndStroke) {
var button = buttonsNeedingFillAndStroke[index];
if ($(button).hasClass('tool_button_current')) {
clickSelect();
@ -3222,7 +3246,7 @@
}
}
else {
for (index in buttonsNeedingFillAndStroke) {
for (var index in buttonsNeedingFillAndStroke) {
var button = buttonsNeedingFillAndStroke[index];
$(button).removeClass('disabled');
}
@ -3971,7 +3995,8 @@
// Select given tool
Editor.ready(function() {
var itool = curConfig.initTool,
var tool,
itool = curConfig.initTool,
container = $("#tools_left, #svg_editor .tools_flyout"),
pre_tool = container.find("#tool_" + itool),
reg_tool = container.find("#" + itool);
@ -4249,7 +4274,7 @@
var c_elem = svgCanvas.getContentElem();
var units = svgCanvas.getUnits();
var units = svgedit.units.getTypeMap();
var unit = units[curConfig.baseUnit]; // 1 = 1px
for(var d = 0; d < 2; d++) {
@ -4328,6 +4353,7 @@
}
var num = (label_pos - content_d) / u_multi;
var label;
if(multi >= 1) {
label = Math.round(num);
} else {
@ -4389,7 +4415,7 @@
updateCanvas(true);
// });
// var revnums = "svg-editor.js ($Rev: 1814 $) ";
// var revnums = "svg-editor.js ($Rev: 1877 $) ";
// revnums += svgCanvas.getVersion();
// $('#copyright')[0].setAttribute("title", revnums);
@ -4404,7 +4430,7 @@
// Callback handler for embedapi.js
try{
json_encode = function(obj){
var json_encode = function(obj){
//simple partial JSON encoder implementation
if(window.JSON && JSON.stringify) return JSON.stringify(obj);
var enc = arguments.callee; //for purposes of recursion
@ -4560,7 +4586,7 @@
Editor.ready(function() {
var pre = 'data:image/svg+xml;base64,';
var src = str.substring(pre.length);
loadSvgString(svgCanvas.Utils.decode64(src));
loadSvgString(svgedit.utilities.decode64(src));
});
};

File diff suppressed because it is too large Load diff

View file

@ -357,25 +357,52 @@ $(function() {
var defs = svg_el.find('defs');
if(!defs.length) return svg_el;
defs.find('[id]').each(function(i) {
if(isOpera) {
var id_elems = defs.find('*').filter(function() {
return !!this.id;
});
} else {
var id_elems = defs.find('[id]');
}
var all_elems = svg_el[0].getElementsByTagName('*'), len = all_elems.length;
id_elems.each(function(i) {
var id = this.id;
var no_dupes = ($(svgdoc).find('#' + id).length <= 1);
if(isOpera) no_dupes = false; // Opera didn't clone svg_el, so not reliable
// if(!force && no_dupes) return;
var new_id = 'x' + id + svg_num + i;
this.id = new_id;
var old_val = 'url(#' + id + ')';
var new_val = 'url(#' + new_id + ')';
// Selector method, possibly faster but fails in Opera / jQuery 1.4.3
// svg_el.find('[fill="url(#' + id + ')"]').each(function() {
// this.setAttribute('fill', 'url(#' + new_id + ')');
// }).end().find('[stroke="url(#' + id + ')"]').each(function() {
// this.setAttribute('stroke', 'url(#' + new_id + ')');
// }).end().find('use').each(function() {
// if(this.getAttribute('xlink:href') == '#' + id) {
// this.setAttributeNS(xlinkns,'href','#' + new_id);
// }
// }).end().find('[filter="url(#' + id + ')"]').each(function() {
// this.setAttribute('filter', 'url(#' + new_id + ')');
// });
svg_el.find('[fill="url(#' + id + ')"]').each(function() {
this.setAttribute('fill', 'url(#' + new_id + ')');
}).end().find('[stroke="url(#' + id + ')"]').each(function() {
this.setAttribute('stroke', 'url(#' + new_id + ')');
}).end().find('use').each(function() {
if(this.getAttribute('xlink:href') == '#' + id) {
this.setAttributeNS(xlinkns,'href','#' + new_id);
for(var i = 0; i < len; i++) {
var elem = all_elems[i];
if(elem.getAttribute('fill') === old_val) {
elem.setAttribute('fill', new_val);
}
}).end().find('[filter="url(#' + id + ')"]').each(function() {
this.setAttribute('filter', 'url(#' + new_id + ')');
});
if(elem.getAttribute('stroke') === old_val) {
elem.setAttribute('stroke', new_val);
}
if(elem.getAttribute('filter') === old_val) {
elem.setAttribute('filter', new_val);
}
}
});
return svg_el;
}

View file

@ -0,0 +1,292 @@
/**
* SVGTransformList
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) browsersupport.js
(function() {
if (!window.svgedit) {
window.svgedit = {};
}
if (!svgedit.transformlist) {
svgedit.transformlist = {};
}
var svgroot = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
// Helper function.
function transformToString(xform) {
var m = xform.matrix,
text = "";
switch(xform.type) {
case 1: // MATRIX
text = "matrix(" + [m.a,m.b,m.c,m.d,m.e,m.f].join(",") + ")";
break;
case 2: // TRANSLATE
text = "translate(" + m.e + "," + m.f + ")";
break;
case 3: // SCALE
if (m.a == m.d) text = "scale(" + m.a + ")";
else text = "scale(" + m.a + "," + m.d + ")";
break;
case 4: // ROTATE
var cx = 0, cy = 0;
// this prevents divide by zero
if (xform.angle != 0) {
var K = 1 - m.a;
cy = ( K * m.f + m.b*m.e ) / ( K*K + m.b*m.b );
cx = ( m.e - m.b * cy ) / K;
}
text = "rotate(" + xform.angle + " " + cx + "," + cy + ")";
break;
}
return text;
};
/**
* Map of SVGTransformList objects.
*/
var listMap_ = {};
// **************************************************************************************
// SVGTransformList implementation for Webkit
// These methods do not currently raise any exceptions.
// These methods also do not check that transforms are being inserted. This is basically
// implementing as much of SVGTransformList that we need to get the job done.
//
// interface SVGEditTransformList {
// attribute unsigned long numberOfItems;
// void clear ( )
// SVGTransform initialize ( in SVGTransform newItem )
// SVGTransform getItem ( in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform insertItemBefore ( in SVGTransform newItem, in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform replaceItem ( in SVGTransform newItem, in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform removeItem ( in unsigned long index ) (DOES NOT THROW DOMException, INDEX_SIZE_ERR)
// SVGTransform appendItem ( in SVGTransform newItem )
// NOT IMPLEMENTED: SVGTransform createSVGTransformFromMatrix ( in SVGMatrix matrix );
// NOT IMPLEMENTED: SVGTransform consolidate ( );
// }
// **************************************************************************************
svgedit.transformlist.SVGTransformList = function(elem) {
this._elem = elem || null;
this._xforms = [];
// TODO: how do we capture the undo-ability in the changed transform list?
this._update = function() {
var tstr = "";
var concatMatrix = svgroot.createSVGMatrix();
for (var i = 0; i < this.numberOfItems; ++i) {
var xform = this._list.getItem(i);
tstr += transformToString(xform) + " ";
}
this._elem.setAttribute("transform", tstr);
};
this._list = this;
this._init = function() {
// Transform attribute parser
var str = this._elem.getAttribute("transform");
if(!str) return;
// TODO: Add skew support in future
var re = /\s*((scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/;
var arr = [];
var m = true;
while(m) {
m = str.match(re);
str = str.replace(re,'');
if(m && m[1]) {
var x = m[1];
var bits = x.split(/\s*\(/);
var name = bits[0];
var val_bits = bits[1].match(/\s*(.*?)\s*\)/);
val_bits[1] = val_bits[1].replace(/(\d)-/g, "$1 -");
var val_arr = val_bits[1].split(/[, ]+/);
var letters = 'abcdef'.split('');
var mtx = svgroot.createSVGMatrix();
$.each(val_arr, function(i, item) {
val_arr[i] = parseFloat(item);
if(name == 'matrix') {
mtx[letters[i]] = val_arr[i];
}
});
var xform = svgroot.createSVGTransform();
var fname = 'set' + name.charAt(0).toUpperCase() + name.slice(1);
var values = name=='matrix'?[mtx]:val_arr;
if (name == 'scale' && values.length == 1) {
values.push(values[0]);
} else if (name == 'translate' && values.length == 1) {
values.push(0);
} else if (name == 'rotate' && values.length == 1) {
values.push(0);
values.push(0);
}
xform[fname].apply(xform, values);
this._list.appendItem(xform);
}
}
};
this._removeFromOtherLists = function(item) {
if (item) {
// Check if this transform is already in a transformlist, and
// remove it if so.
var found = false;
for (var id in listMap_) {
var tl = listMap_[id];
for (var i = 0, len = tl._xforms.length; i < len; ++i) {
if(tl._xforms[i] == item) {
found = true;
tl.removeItem(i);
break;
}
}
if (found) {
break;
}
}
}
};
this.numberOfItems = 0;
this.clear = function() {
this.numberOfItems = 0;
this._xforms = [];
};
this.initialize = function(newItem) {
this.numberOfItems = 1;
this._removeFromOtherLists(newItem);
this._xforms = [newItem];
};
this.getItem = function(index) {
if (index < this.numberOfItems && index >= 0) {
return this._xforms[index];
}
throw {code: 1}; // DOMException with code=INDEX_SIZE_ERR
};
this.insertItemBefore = function(newItem, index) {
var retValue = null;
if (index >= 0) {
if (index < this.numberOfItems) {
this._removeFromOtherLists(newItem);
var newxforms = new Array(this.numberOfItems + 1);
// TODO: use array copying and slicing
for ( var i = 0; i < index; ++i) {
newxforms[i] = this._xforms[i];
}
newxforms[i] = newItem;
for ( var j = i+1; i < this.numberOfItems; ++j, ++i) {
newxforms[j] = this._xforms[i];
}
this.numberOfItems++;
this._xforms = newxforms;
retValue = newItem;
this._list._update();
}
else {
retValue = this._list.appendItem(newItem);
}
}
return retValue;
};
this.replaceItem = function(newItem, index) {
var retValue = null;
if (index < this.numberOfItems && index >= 0) {
this._removeFromOtherLists(newItem);
this._xforms[index] = newItem;
retValue = newItem;
this._list._update();
}
return retValue;
};
this.removeItem = function(index) {
if (index < this.numberOfItems && index >= 0) {
var retValue = this._xforms[index];
var newxforms = new Array(this.numberOfItems - 1);
for (var i = 0; i < index; ++i) {
newxforms[i] = this._xforms[i];
}
for (var j = i; j < this.numberOfItems-1; ++j, ++i) {
newxforms[j] = this._xforms[i+1];
}
this.numberOfItems--;
this._xforms = newxforms;
this._list._update();
return retValue;
} else {
throw {code: 1}; // DOMException with code=INDEX_SIZE_ERR
}
};
this.appendItem = function(newItem) {
this._removeFromOtherLists(newItem);
this._xforms.push(newItem);
this.numberOfItems++;
this._list._update();
return newItem;
};
};
svgedit.transformlist.resetListMap = function() {
listMap_ = {};
};
/**
* Removes transforms of the given element from the map.
* Parameters:
* elem - a DOM Element
*/
svgedit.transformlist.removeElementFromListMap = function(elem) {
if (elem.id && listMap_[elem.id]) {
delete listMap_[elem.id];
}
};
// Function: getTransformList
// Returns an object that behaves like a SVGTransformList for the given DOM element
//
// Parameters:
// elem - DOM element to get a transformlist from
svgedit.transformlist.getTransformList = function(elem) {
if (!svgedit.browsersupport.nativeTransformLists) {
var id = elem.id;
if(!id) {
// Get unique ID for temporary element
id = 'temp';
}
var t = listMap_[id];
if (!t || id == 'temp') {
listMap_[id] = new svgedit.transformlist.SVGTransformList(elem);
listMap_[id]._init();
t = listMap_[id];
}
return t;
}
else if (elem.transform) {
return elem.transform.baseVal;
}
else if (elem.gradientTransform) {
return elem.gradientTransform.baseVal;
}
else if (elem.patternTransform) {
return elem.patternTransform.baseVal;
}
return null;
};
})();

View file

@ -0,0 +1,476 @@
/**
* Package: svgedit.utilities
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
// 2) browsersupport.js
// 3) svgtransformlist.js
// 4) math.js
(function() {
if (!window.svgedit) {
window.svgedit = {};
}
if (!svgedit.utilities) {
svgedit.utilities = {};
}
// Constants
// String used to encode base64.
var KEYSTR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var XLINKNS = "http://www.w3.org/1999/xlink";
// Much faster than running getBBox() every time
var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
var visElems_arr = visElems.split(',');
//var hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
// Function: svgedit.utilities.toXml
// Converts characters in a string to XML-friendly entities.
//
// Example: "&" becomes "&amp;"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.utilities.toXml = function(str) {
return $('<p/>').text(str).html();
};
// Function: svgedit.utilities.fromXml
// Converts XML entities in a string to single characters.
// Example: "&amp;" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.utilities.fromXml = function(str) {
return $('<p/>').html(str).text();
};
// This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
// schiller: Removed string concatenation in favour of Array.join() optimization,
// also precalculate the size of the array needed.
// Function: svgedit.utilities.encode64
// Converts a string to base64
svgedit.utilities.encode64 = function(input) {
// base64 strings are 4/3 larger than the original string
// input = svgedit.utilities.encodeUTF8(input); // convert non-ASCII characters
input = svgedit.utilities.convertToXMLReferences(input);
if(window.btoa) return window.btoa(input); // Use native if available
var output = new Array( Math.floor( (input.length + 2) / 3 ) * 4 );
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0, p = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output[p++] = KEYSTR.charAt(enc1);
output[p++] = KEYSTR.charAt(enc2);
output[p++] = KEYSTR.charAt(enc3);
output[p++] = KEYSTR.charAt(enc4);
} while (i < input.length);
return output.join('');
};
// Function: svgedit.utilities.decode64
// Converts a string from base64
svgedit.utilities.decode64 = function(input) {
if(window.atob) return window.atob(input);
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = KEYSTR.indexOf(input.charAt(i++));
enc2 = KEYSTR.indexOf(input.charAt(i++));
enc3 = KEYSTR.indexOf(input.charAt(i++));
enc4 = KEYSTR.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);
return unescape(output);
};
// Currently not being used, so commented out for now
// based on http://phpjs.org/functions/utf8_encode:577
// codedread:does not seem to work with webkit-based browsers on OSX
// "encodeUTF8": function(input) {
// //return unescape(encodeURIComponent(input)); //may or may not work
// var output = '';
// for (var n = 0; n < input.length; n++){
// var c = input.charCodeAt(n);
// if (c < 128) {
// output += input[n];
// }
// else if (c > 127) {
// if (c < 2048){
// output += String.fromCharCode((c >> 6) | 192);
// }
// else {
// output += String.fromCharCode((c >> 12) | 224) + String.fromCharCode((c >> 6) & 63 | 128);
// }
// output += String.fromCharCode((c & 63) | 128);
// }
// }
// return output;
// },
// Function: svgedit.utilities.convertToXMLReferences
// Converts a string to use XML references
svgedit.utilities.convertToXMLReferences = function(input) {
var output = '';
for (var n = 0; n < input.length; n++){
var c = input.charCodeAt(n);
if (c < 128) {
output += input[n];
} else if(c > 127) {
output += ("&#" + c + ";");
}
}
return output;
};
// Function: text2xml
// Cross-browser compatible method of converting a string to an XML tree
// found this function here: http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
svgedit.utilities.text2xml = function(sXML) {
if(sXML.indexOf('<svg:svg') >= 0) {
sXML = sXML.replace(/<(\/?)svg:/g, '<$1').replace('xmlns:svg', 'xmlns');
}
var out;
try{
var dXML = (window.DOMParser)?new DOMParser():new ActiveXObject("Microsoft.XMLDOM");
dXML.async = false;
} catch(e){
throw new Error("XML Parser could not be instantiated");
};
try{
if(dXML.loadXML) out = (dXML.loadXML(sXML))?dXML:false;
else out = dXML.parseFromString(sXML, "text/xml");
}
catch(e){ throw new Error("Error parsing XML string"); };
return out;
};
// Function: bboxToObj
// Converts a SVGRect into an object.
//
// Parameters:
// bbox - a SVGRect
//
// Returns:
// An object with properties names x, y, width, height.
svgedit.utilities.bboxToObj = function(bbox) {
return {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height
}
};
// Function: walkTree
// Walks the tree and executes the callback on each element in a top-down fashion
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.utilities.walkTree = function(elem, cbFn){
if (elem && elem.nodeType == 1) {
cbFn(elem);
var i = elem.childNodes.length;
while (i--) {
svgedit.utilities.walkTree(elem.childNodes.item(i), cbFn);
}
}
};
// Function: walkTreePost
// Walks the tree and executes the callback on each element in a depth-first fashion
// TODO: FIXME: Shouldn't this be calling walkTreePost?
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.utilities.walkTreePost = function(elem, cbFn) {
if (elem && elem.nodeType == 1) {
var i = elem.childNodes.length;
while (i--) {
svgedit.utilities.walkTree(elem.childNodes.item(i), cbFn);
}
cbFn(elem);
}
};
// Function: svgedit.utilities.getUrlFromAttr
// Extracts the URL from the url(...) syntax of some attributes.
// Three variants:
// * <circle fill="url(someFile.svg#foo)" />
// * <circle fill="url('someFile.svg#foo')" />
// * <circle fill='url("someFile.svg#foo")' />
//
// Parameters:
// attrVal - The attribute value as a string
//
// Returns:
// String with just the URL, like someFile.svg#foo
svgedit.utilities.getUrlFromAttr = function(attrVal) {
if (attrVal) {
// url("#somegrad")
if (attrVal.indexOf('url("') === 0) {
return attrVal.substring(5,attrVal.indexOf('"',6));
}
// url('#somegrad')
else if (attrVal.indexOf("url('") === 0) {
return attrVal.substring(5,attrVal.indexOf("'",6));
}
else if (attrVal.indexOf("url(") === 0) {
return attrVal.substring(4,attrVal.indexOf(')'));
}
}
return null;
};
// Function: svgedit.utilities.getHref
// Returns the given element's xlink:href value
svgedit.utilities.getHref = function(elem) {
return elem.getAttributeNS(XLINKNS, "href");
}
// Function: svgedit.utilities.setHref
// Sets the given element's xlink:href value
svgedit.utilities.setHref = function(elem, val) {
elem.setAttributeNS(XLINKNS, "xlink:href", val);
}
// Function: findDefs
// Parameters:
// svgElement - The <svg> element.
//
// Returns:
// The document's <defs> element, create it first if necessary
svgedit.utilities.findDefs = function(svgElement) {
var svgElement = svgDoc.documentElement;
var defs = svgElement.getElementsByTagNameNS(svgns, "defs");
if (defs.length > 0) {
defs = defs[0];
}
else {
// first child is a comment, so call nextSibling
defs = svgElement.insertBefore( svgElement.ownerDocument.createElementNS(svgns, "defs" ), svgElement.firstChild.nextSibling);
}
return defs;
};
// TODO(codedread): Consider moving the next to functions to bbox.js
// Function: svgedit.utilities.getPathBBox
// Get correct BBox for a path in Webkit
// Converted from code found here:
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
//
// Parameters:
// path - The path DOM element to get the BBox for
//
// Returns:
// A BBox-like object
svgedit.utilities.getPathBBox = function(path) {
var seglist = path.pathSegList;
var tot = seglist.numberOfItems;
var bounds = [[], []];
var start = seglist.getItem(0);
var P0 = [start.x, start.y];
for(var i=0; i < tot; i++) {
var seg = seglist.getItem(i);
if(!seg.x) continue;
// Add actual points to limits
bounds[0].push(P0[0]);
bounds[1].push(P0[1]);
if(seg.x1) {
var P1 = [seg.x1, seg.y1],
P2 = [seg.x2, seg.y2],
P3 = [seg.x, seg.y];
for(var j=0; j < 2; j++) {
var calc = function(t) {
return Math.pow(1-t,3) * P0[j]
+ 3 * Math.pow(1-t,2) * t * P1[j]
+ 3 * (1-t) * Math.pow(t,2) * P2[j]
+ Math.pow(t,3) * P3[j];
};
var b = 6 * P0[j] - 12 * P1[j] + 6 * P2[j];
var a = -3 * P0[j] + 9 * P1[j] - 9 * P2[j] + 3 * P3[j];
var c = 3 * P1[j] - 3 * P0[j];
if(a == 0) {
if(b == 0) {
continue;
}
var t = -c / b;
if(0 < t && t < 1) {
bounds[j].push(calc(t));
}
continue;
}
var b2ac = Math.pow(b,2) - 4 * c * a;
if(b2ac < 0) continue;
var t1 = (-b + Math.sqrt(b2ac))/(2 * a);
if(0 < t1 && t1 < 1) bounds[j].push(calc(t1));
var t2 = (-b - Math.sqrt(b2ac))/(2 * a);
if(0 < t2 && t2 < 1) bounds[j].push(calc(t2));
}
P0 = P3;
} else {
bounds[0].push(seg.x);
bounds[1].push(seg.y);
}
}
var x = Math.min.apply(null, bounds[0]);
var w = Math.max.apply(null, bounds[0]) - x;
var y = Math.min.apply(null, bounds[1]);
var h = Math.max.apply(null, bounds[1]) - y;
return {
'x': x,
'y': y,
'width': w,
'height': h
};
};
// Function: svgedit.utilities.getBBox
// Get the given/selected element's bounding box object, convert it to be more
// usable when necessary
//
// Parameters:
// elem - Optional DOM element to get the BBox for
svgedit.utilities.getBBox = function(elem) {
var selected = elem || selectedElements[0];
if (elem.nodeType != 1) return null;
var ret = null;
var elname = selected.nodeName;
if(elname === 'text' && selected.textContent === '') {
selected.textContent = 'a'; // Some character needed for the selector to use.
ret = selected.getBBox();
selected.textContent = '';
} else if(elname === 'path' && svgedit.browsersupport.isWebkit()) {
ret = svgedit.utilities.getPathBBox(selected);
} else if(elname === 'use' && !svgedit.browsersupport.isWebkit() || elname === 'foreignObject') {
ret = selected.getBBox();
var bb = {};
bb.width = ret.width;
bb.height = ret.height;
bb.x = ret.x + parseFloat(selected.getAttribute('x')||0);
bb.y = ret.y + parseFloat(selected.getAttribute('y')||0);
ret = bb;
} else if(~visElems_arr.indexOf(elname)) {
try { ret = selected.getBBox();}
catch(e) {
// Check if element is child of a foreignObject
var fo = $(selected).closest("foreignObject");
if(fo.length) {
try {
ret = fo[0].getBBox();
} catch(e) {
ret = null;
}
} else {
ret = null;
}
}
}
if(ret) {
ret = svgedit.utilities.bboxToObj(ret);
}
// get the bounding box from the DOM (which is in that element's coordinate system)
return ret;
};
// Function: svgedit.utilities.getRotationAngle
// Get the rotation angle of the given/selected DOM element
//
// Parameters:
// elem - Optional DOM element to get the angle for
// to_rad - Boolean that when true returns the value in radians rather than degrees
//
// Returns:
// Float with the angle in degrees or radians
svgedit.utilities.getRotationAngle = function(elem, to_rad) {
var selected = elem || selectedElements[0];
// find the rotation transform (if any) and set it
var tlist = svgedit.transformlist.getTransformList(selected);
if(!tlist) return 0; // <svg> elements have no tlist
var N = tlist.numberOfItems;
for (var i = 0; i < N; ++i) {
var xform = tlist.getItem(i);
if (xform.type == 4) {
return to_rad ? xform.angle * Math.PI / 180.0 : xform.angle;
}
}
return 0.0;
};
})();

View file

@ -0,0 +1,260 @@
/**
* Package: svgedit.units
*
* Licensed under the Apache License, Version 2
*
* Copyright(c) 2010 Alexis Deveria
* Copyright(c) 2010 Jeff Schiller
*/
// Dependencies:
// 1) jQuery
(function() {
if (!window.svgedit) {
window.svgedit = {};
}
if (!svgedit.units) {
svgedit.units = {};
}
var w_attrs = ['x', 'x1', 'cx', 'rx', 'width'];
var h_attrs = ['y', 'y1', 'cy', 'ry', 'height'];
var unit_attrs = $.merge(['r','radius'], w_attrs);
var unitNumMap = {
'%': 2,
'em': 3,
'ex': 4,
'px': 5,
'cm': 6,
'mm': 7,
'in': 8,
'pt': 9,
'pc': 10
};
$.merge(unit_attrs, h_attrs);
// Container of elements.
var elementContainer_;
/**
* Stores mapping of unit type to user coordinates.
*/
var typeMap_ = {px: 1};
/**
* ElementContainer interface
*
* function getBaseUnit() - returns a string of the base unit type of the container ("em")
* function getElement() - returns an element in the container given an id
* function getHeight() - returns the container's height
* function getWidth() - returns the container's width
*/
/**
* Function: svgedit.units.init()
* Initializes this module.
*
* Parameters:
* elementContainer - an object implementing the ElementContainer interface.
*/
svgedit.units.init = function(elementContainer) {
elementContainer_ = elementContainer;
var svgns = 'http://www.w3.org/2000/svg';
// Get correct em/ex values by creating a temporary SVG.
var svg = document.createElementNS(svgns, 'svg');
document.body.appendChild(svg);
var rect = document.createElementNS(svgns,'rect');
rect.setAttribute('width',"1em");
rect.setAttribute('height',"1ex");
rect.setAttribute('x',"1in");
svg.appendChild(rect);
var bb = rect.getBBox();
document.body.removeChild(svg);
var inch = bb.x;
typeMap_['em'] = bb.width;
typeMap_['ex'] = bb.height;
typeMap_['in'] = inch;
typeMap_['cm'] = inch / 2.54;
typeMap_['mm'] = inch / 25.4;
typeMap_['pt'] = inch / 72;
typeMap_['pc'] = inch / 6;
typeMap_['%'] = 0;
};
// Group: Unit conversion functions
// Function: svgedit.units.getTypeMap
// Returns the unit object with values for each unit
svgedit.units.getTypeMap = function() {
return typeMap_;
};
// Function: svgedit.units.convertUnit
// Converts the number to given unit or baseUnit
svgedit.units.convertUnit = function(val, unit) {
unit = unit || elementContainer_.getBaseUnit();
// baseVal.convertToSpecifiedUnits(unitNumMap[unit]);
// var val = baseVal.valueInSpecifiedUnits;
// baseVal.convertToSpecifiedUnits(1);
return val / typeMap_[unit];
};
// Function: svgedit.units.setUnitAttr
// Sets an element's attribute based on the unit in its current value.
//
// Parameters:
// elem - DOM element to be changed
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to convert
svgedit.units.setUnitAttr = function(elem, attr, val) {
if(!isNaN(val)) {
// New value is a number, so check currently used unit
var old_val = elem.getAttribute(attr);
// Enable this for alternate mode
// if(old_val !== null && (isNaN(old_val) || elementContainer_.getBaseUnit() !== 'px')) {
// // Old value was a number, so get unit, then convert
// var unit;
// if(old_val.substr(-1) === '%') {
// var res = getResolution();
// unit = '%';
// val *= 100;
// if(w_attrs.indexOf(attr) >= 0) {
// val = val / res.w;
// } else if(h_attrs.indexOf(attr) >= 0) {
// val = val / res.h;
// } else {
// return val / Math.sqrt((res.w*res.w) + (res.h*res.h))/Math.sqrt(2);
// }
// } else {
// if(elementContainer_.getBaseUnit() !== 'px') {
// unit = elementContainer_.getBaseUnit();
// } else {
// unit = old_val.substr(-2);
// }
// val = val / typeMap_[unit];
// }
//
// val += unit;
// }
}
elem.setAttribute(attr, val);
};
var attrsToConvert = {
"line": ['x1', 'x2', 'y1', 'y2'],
"circle": ['cx', 'cy', 'r'],
"ellipse": ['cx', 'cy', 'rx', 'ry'],
"foreignObject": ['x', 'y', 'width', 'height'],
"rect": ['x', 'y', 'width', 'height'],
"image": ['x', 'y', 'width', 'height'],
"use": ['x', 'y', 'width', 'height'],
"text": ['x', 'y']
};
// Function: svgedit.units.convertAttrs
// Converts all applicable attributes to the configured baseUnit
//
// Parameters:
// element - a DOM element whose attributes should be converted
svgedit.units.convertAttrs = function(element) {
var elName = element.tagName;
var unit = elementContainer_.getBaseUnit();
var attrs = attrsToConvert[elName];
if(!attrs) return;
var len = attrs.length
for(var i = 0; i < len; i++) {
var attr = attrs[i];
var cur = element.getAttribute(attr);
if(cur) {
if(!isNaN(cur)) {
element.setAttribute(attr, (cur / typeMap_[unit]) + unit);
} else {
// Convert existing?
}
}
}
};
// Function: svgedit.units.convertToNum
// Converts given values to numbers. Attributes must be supplied in
// case a percentage is given
//
// Parameters:
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to convert
svgedit.units.convertToNum = function(attr, val) {
// Return a number if that's what it already is
if(!isNaN(val)) return val-0;
if(val.substr(-1) === '%') {
// Deal with percentage, depends on attribute
var num = val.substr(0, val.length-1)/100;
var width = elementContainer_.getWidth();
var height = elementContainer_.getHeight();
if(w_attrs.indexOf(attr) >= 0) {
return num * width;
} else if(h_attrs.indexOf(attr) >= 0) {
return num * height;
} else {
return num * Math.sqrt((width*width) + (height*height))/Math.sqrt(2);
}
} else {
var unit = val.substr(-2);
var num = val.substr(0, val.length-2);
// Note that this multiplication turns the string into a number
return num * typeMap_[unit];
}
};
// Function: svgedit.units.isValidUnit
// Check if an attribute's value is in a valid format
//
// Parameters:
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to check
svgedit.units.isValidUnit = function(attr, val) {
var valid = false;
if(unit_attrs.indexOf(attr) >= 0) {
// True if it's just a number
if(!isNaN(val)) {
valid = true;
} else {
// Not a number, check if it has a valid unit
val = val.toLowerCase();
$.each(typeMap_, function(unit) {
if(valid) return;
var re = new RegExp('^-?[\\d\\.]+' + unit + '$');
if(re.test(val)) valid = true;
});
}
} else if (attr == "id") {
// if we're trying to change the id, make sure it's not already present in the doc
// and the id value is valid.
var result = false;
// because getElem() can throw an exception in the case of an invalid id
// (according to http://www.w3.org/TR/xml-id/ IDs must be a NCName)
// we wrap it in an exception and only return true if the ID was valid and
// not already present
try {
var elem = elementContainer_.getElement(val);
result = (elem == null);
} catch(e) {}
return result;
} else valid = true;
return valid;
};
})();

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>All SVG-edit Tests</title>
</head>
<body>
<h1>All SVG-edit Tests</h1>
<p>This file frames all SVG-edit test pages. This should only include tests known to work. These tests are known to pass 100% in the following: Firefox 3.6, Chrome 7, IE9 Preview 6 (1.9.8006.6000), Opera 10.63. If a test is broken in this page, it is possible that <em>YOU</em> broke it. Please do not submit code that breaks any of these tests.</p>
<iframe src='svgtransformlist_test.html' width='100%' height='300'></iframe>
<iframe src='math_test.html' width='100%' height='300'></iframe>
<iframe src='svgutils_test.html' width='100%' height='300'></iframe>
<iframe src='history_test.html' width='100%' height='300'></iframe>
<iframe src='select_test.html' width='100%' height='300'></iframe>
</body>
<script>
window.setTimeout(function() {
var iframes = document.getElementsByTagName('iframe');
for (var i = 0, len = iframes.length; i < len; ++i) {
var f = iframes[i];
f.style.height = (f.contentDocument.body.scrollHeight + 20) + 'px';
}
}, 1200);
</script>
</html>

View file

@ -0,0 +1,575 @@
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
<script src='../editor/jquery.js'></script>
<script type='text/javascript' src='../editor/history.js'></script>
<script type='text/javascript' src='qunit/qunit.js'></script>
<script type='text/javascript'>
$(function() {
// TODO(codedread): Write tests for handling history events.
// Mocked out methods.
svgedit.transformlist = {};
svgedit.transformlist.removeElementFromListMap = function(elem) {};
svgedit.utilities = {};
svgedit.utilities.getHref = function(elem) { return '#foo'; };
svgedit.utilities.setHref = function(elem, val) {};
svgedit.utilities.getRotationAngle = function(elem) { return 0; };
// log function
QUnit.log = function(result, message) {
if (window.console && window.console.log) {
window.console.log(result +' :: '+ message);
}
};
var svgns = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgns, 'svg');
var undoMgr = null;
var divparent = document.getElementById('divparent');
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
var div3 = document.getElementById('div3');
var div4 = document.getElementById('div4');
var div5 = document.getElementById('div5');
module('svgedit.history');
var MockCommand = function(opt_text) { this.text_ = opt_text; };
MockCommand.prototype.apply = function() {};
MockCommand.prototype.unapply = function() {};
MockCommand.prototype.getText = function() { return this.text_; };
MockCommand.prototype.elements = function() { return []; };
var MockHistoryEventHandler = function() {};
MockHistoryEventHandler.prototype.handleHistoryEvent = function(eventType, command) {};
function setUp() {
undoMgr = new svgedit.history.UndoManager();
}
function tearDown() {
undoMgr = null;
}
test('Test svgedit.history package', function() {
expect(13);
ok(svgedit.history);
ok(svgedit.history.MoveElementCommand);
ok(svgedit.history.InsertElementCommand);
ok(svgedit.history.ChangeElementCommand);
ok(svgedit.history.RemoveElementCommand);
ok(svgedit.history.BatchCommand);
ok(svgedit.history.UndoManager);
equals(typeof svgedit.history.MoveElementCommand, typeof function(){});
equals(typeof svgedit.history.InsertElementCommand, typeof function(){});
equals(typeof svgedit.history.ChangeElementCommand, typeof function(){});
equals(typeof svgedit.history.RemoveElementCommand, typeof function(){});
equals(typeof svgedit.history.BatchCommand, typeof function(){});
equals(typeof svgedit.history.UndoManager, typeof function(){});
});
test('Test UndoManager methods', function() {
expect(14);
setUp();
ok(undoMgr);
ok(undoMgr.addCommandToHistory);
ok(undoMgr.getUndoStackSize);
ok(undoMgr.getRedoStackSize);
ok(undoMgr.resetUndoStack);
ok(undoMgr.getNextUndoCommandText);
ok(undoMgr.getNextRedoCommandText);
equals(typeof undoMgr, typeof {});
equals(typeof undoMgr.addCommandToHistory, typeof function(){});
equals(typeof undoMgr.getUndoStackSize, typeof function(){});
equals(typeof undoMgr.getRedoStackSize, typeof function(){});
equals(typeof undoMgr.resetUndoStack, typeof function(){});
equals(typeof undoMgr.getNextUndoCommandText, typeof function(){});
equals(typeof undoMgr.getNextRedoCommandText, typeof function(){});
tearDown();
});
test('Test UndoManager.addCommandToHistory() function', function() {
expect(3);
setUp();
equals(undoMgr.getUndoStackSize(), 0);
undoMgr.addCommandToHistory(new MockCommand());
equals(undoMgr.getUndoStackSize(), 1);
undoMgr.addCommandToHistory(new MockCommand());
equals(undoMgr.getUndoStackSize(), 2);
tearDown();
});
test('Test UndoManager.getUndoStackSize() and getRedoStackSize() functions', function() {
expect(18);
setUp();
undoMgr.addCommandToHistory(new MockCommand());
undoMgr.addCommandToHistory(new MockCommand());
undoMgr.addCommandToHistory(new MockCommand());
equals(undoMgr.getUndoStackSize(), 3);
equals(undoMgr.getRedoStackSize(), 0);
undoMgr.undo();
equals(undoMgr.getUndoStackSize(), 2);
equals(undoMgr.getRedoStackSize(), 1);
undoMgr.undo();
equals(undoMgr.getUndoStackSize(), 1);
equals(undoMgr.getRedoStackSize(), 2);
undoMgr.undo();
equals(undoMgr.getUndoStackSize(), 0);
equals(undoMgr.getRedoStackSize(), 3);
undoMgr.undo();
equals(undoMgr.getUndoStackSize(), 0);
equals(undoMgr.getRedoStackSize(), 3);
undoMgr.redo();
equals(undoMgr.getUndoStackSize(), 1);
equals(undoMgr.getRedoStackSize(), 2);
undoMgr.redo();
equals(undoMgr.getUndoStackSize(), 2);
equals(undoMgr.getRedoStackSize(), 1);
undoMgr.redo();
equals(undoMgr.getUndoStackSize(), 3);
equals(undoMgr.getRedoStackSize(), 0);
undoMgr.redo();
equals(undoMgr.getUndoStackSize(), 3);
equals(undoMgr.getRedoStackSize(), 0);
tearDown();
});
test('Test UndoManager.resetUndoStackSize() function', function() {
expect(4);
setUp();
undoMgr.addCommandToHistory(new MockCommand());
undoMgr.addCommandToHistory(new MockCommand());
undoMgr.addCommandToHistory(new MockCommand());
undoMgr.undo();
equals(undoMgr.getUndoStackSize(), 2);
equals(undoMgr.getRedoStackSize(), 1);
undoMgr.resetUndoStack();
equals(undoMgr.getUndoStackSize(), 0);
equals(undoMgr.getRedoStackSize(), 0);
tearDown();
});
test('Test UndoManager.getNextUndoCommandText() function', function() {
expect(9);
setUp();
equals(undoMgr.getNextUndoCommandText(), '');
undoMgr.addCommandToHistory(new MockCommand('First'));
undoMgr.addCommandToHistory(new MockCommand('Second'));
undoMgr.addCommandToHistory(new MockCommand('Third'));
equals(undoMgr.getNextUndoCommandText(), 'Third');
undoMgr.undo();
equals(undoMgr.getNextUndoCommandText(), 'Second');
undoMgr.undo();
equals(undoMgr.getNextUndoCommandText(), 'First');
undoMgr.undo();
equals(undoMgr.getNextUndoCommandText(), '');
undoMgr.redo();
equals(undoMgr.getNextUndoCommandText(), 'First');
undoMgr.redo();
equals(undoMgr.getNextUndoCommandText(), 'Second');
undoMgr.redo();
equals(undoMgr.getNextUndoCommandText(), 'Third');
undoMgr.redo();
equals(undoMgr.getNextUndoCommandText(), 'Third');
tearDown();
});
test('Test UndoManager.getNextRedoCommandText() function', function() {
expect(8);
setUp();
equals(undoMgr.getNextRedoCommandText(), '');
undoMgr.addCommandToHistory(new MockCommand('First'));
undoMgr.addCommandToHistory(new MockCommand('Second'));
undoMgr.addCommandToHistory(new MockCommand('Third'));
equals(undoMgr.getNextRedoCommandText(), '');
undoMgr.undo();
equals(undoMgr.getNextRedoCommandText(), 'Third');
undoMgr.undo();
equals(undoMgr.getNextRedoCommandText(), 'Second');
undoMgr.undo();
equals(undoMgr.getNextRedoCommandText(), 'First');
undoMgr.redo();
equals(undoMgr.getNextRedoCommandText(), 'Second');
undoMgr.redo();
equals(undoMgr.getNextRedoCommandText(), 'Third');
undoMgr.redo();
equals(undoMgr.getNextRedoCommandText(), '');
tearDown();
});
test('Test UndoManager.undo() and redo() functions', function() {
expect(10);
setUp();
var lastCalled = null;
var cmd1 = new MockCommand();
var cmd2 = new MockCommand();
var cmd3 = new MockCommand();
cmd1.apply = function() { lastCalled = 'cmd1.apply'; };
cmd2.apply = function() { lastCalled = 'cmd2.apply'; };
cmd3.apply = function() { lastCalled = 'cmd3.apply'; };
cmd1.unapply = function() { lastCalled = 'cmd1.unapply'; };
cmd2.unapply = function() { lastCalled = 'cmd2.unapply'; };
cmd3.unapply = function() { lastCalled = 'cmd3.unapply'; };
undoMgr.addCommandToHistory(cmd1);
undoMgr.addCommandToHistory(cmd2);
undoMgr.addCommandToHistory(cmd3);
ok(!lastCalled);
undoMgr.undo();
equals(lastCalled, 'cmd3.unapply');
undoMgr.redo();
equals(lastCalled, 'cmd3.apply');
undoMgr.undo();
undoMgr.undo();
equals(lastCalled, 'cmd2.unapply');
undoMgr.undo();
equals(lastCalled, 'cmd1.unapply');
lastCalled = null;
undoMgr.undo();
ok(!lastCalled);
undoMgr.redo();
equals(lastCalled, 'cmd1.apply');
undoMgr.redo();
equals(lastCalled, 'cmd2.apply');
undoMgr.redo();
equals(lastCalled, 'cmd3.apply');
lastCalled = null;
undoMgr.redo();
ok(!lastCalled);
tearDown();
});
test('Test MoveElementCommand', function() {
expect(26);
setUp();
var move = new svgedit.history.MoveElementCommand(div3, div1, divparent);
ok(move.unapply);
ok(move.apply);
equals(typeof move.unapply, typeof function(){});
equals(typeof move.apply, typeof function(){});
move.unapply();
equals(divparent.firstElementChild, div3);
equals(divparent.firstElementChild.nextElementSibling, div1);
equals(divparent.lastElementChild, div2);
move.apply();
equals(divparent.firstElementChild, div1);
equals(divparent.firstElementChild.nextElementSibling, div2);
equals(divparent.lastElementChild, div3);
move = new svgedit.history.MoveElementCommand(div1, null, divparent);
move.unapply();
equals(divparent.firstElementChild, div2);
equals(divparent.firstElementChild.nextElementSibling, div3);
equals(divparent.lastElementChild, div1);
move.apply();
equals(divparent.firstElementChild, div1);
equals(divparent.firstElementChild.nextElementSibling, div2);
equals(divparent.lastElementChild, div3);
move = new svgedit.history.MoveElementCommand(div2, div5, div4);
move.unapply();
equals(divparent.firstElementChild, div1);
equals(divparent.firstElementChild.nextElementSibling, div3);
equals(divparent.lastElementChild, div3);
equals(div4.firstElementChild, div2);
equals(div4.firstElementChild.nextElementSibling, div5);
move.apply();
equals(divparent.firstElementChild, div1);
equals(divparent.firstElementChild.nextElementSibling, div2);
equals(divparent.lastElementChild, div3);
equals(div4.firstElementChild, div5);
equals(div4.lastElementChild, div5);
tearDown();
});
test('Test InsertElementCommand', function() {
expect(20);
setUp();
var insert = new svgedit.history.InsertElementCommand(div3);
ok(insert.unapply);
ok(insert.apply);
equals(typeof insert.unapply, typeof function(){});
equals(typeof insert.apply, typeof function(){});
insert.unapply();
equals(divparent.childElementCount, 2);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div2);
equals(divparent.lastElementChild, div2);
insert.apply();
equals(divparent.childElementCount, 3);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div2);
equals(div2.nextElementSibling, div3);
insert = new svgedit.history.InsertElementCommand(div2);
insert.unapply();
equals(divparent.childElementCount, 2);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div3);
equals(divparent.lastElementChild, div3);
insert.apply();
equals(divparent.childElementCount, 3);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div2);
equals(div2.nextElementSibling, div3);
tearDown();
});
test('Test RemoveElementCommand', function() {
expect(22);
setUp();
var div6 = document.createElement('div');
div6.id = 'div6';
var remove = new svgedit.history.RemoveElementCommand(div6, null, divparent);
ok(remove.unapply);
ok(remove.apply);
equals(typeof remove.unapply, typeof function(){});
equals(typeof remove.apply, typeof function(){});
remove.unapply();
equals(divparent.childElementCount, 4);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div2);
equals(div2.nextElementSibling, div3);
equals(div3.nextElementSibling, div6);
remove.apply();
equals(divparent.childElementCount, 3);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div2);
equals(div2.nextElementSibling, div3);
remove = new svgedit.history.RemoveElementCommand(div6, div2, divparent);
remove.unapply();
equals(divparent.childElementCount, 4);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div6);
equals(div6.nextElementSibling, div2);
equals(div2.nextElementSibling, div3);
remove.apply();
equals(divparent.childElementCount, 3);
equals(divparent.firstElementChild, div1);
equals(div1.nextElementSibling, div2);
equals(div2.nextElementSibling, div3);
tearDown();
});
test('Test ChangeElementCommand', function() {
expect(20);
setUp();
div1.setAttribute('title', 'new title');
var change = new svgedit.history.ChangeElementCommand(div1,
{'title': 'old title', 'class': 'foo'});
ok(change.unapply);
ok(change.apply);
equals(typeof change.unapply, typeof function(){});
equals(typeof change.apply, typeof function(){});
change.unapply();
equals(div1.getAttribute('title'), 'old title');
equals(div1.getAttribute('class'), 'foo');
change.apply();
equals(div1.getAttribute('title'), 'new title');
ok(!div1.getAttribute('class'));
div1.textContent = 'inner text';
change = new svgedit.history.ChangeElementCommand(div1,
{'#text': null});
change.unapply();
ok(!div1.textContent);
change.apply();
equals(div1.textContent, 'inner text');
div1.textContent = '';
change = new svgedit.history.ChangeElementCommand(div1,
{'#text': 'old text'});
change.unapply();
equals(div1.textContent, 'old text');
change.apply();
ok(!div1.textContent);
// TODO(codedread): Refactor this #href stuff in history.js and svgcanvas.js
var rect = document.createElementNS(svgns, 'rect');
var justCalled = null;
var gethrefvalue = null;
var sethrefvalue = null;
svgedit.utilities.getHref = function(elem) {
equals(elem, rect);
justCalled = 'getHref';
return gethrefvalue;
};
svgedit.utilities.setHref = function(elem, val) {
equals(elem, rect);
equals(val, sethrefvalue);
justCalled = 'setHref';
};
gethrefvalue = '#newhref';
change = new svgedit.history.ChangeElementCommand(rect,
{'#href': '#oldhref'});
equals(justCalled, 'getHref');
justCalled = null;
sethrefvalue = '#oldhref';
change.unapply();
equals(justCalled, 'setHref');
justCalled = null;
sethrefvalue = '#newhref';
change.apply();
equals(justCalled, 'setHref');
tearDown();
});
test('Test BatchCommand', function() {
expect(13);
setUp();
var concatResult = '';
MockCommand.prototype.apply = function() { concatResult += this.text_; };
var batch = new svgedit.history.BatchCommand();
ok(batch.unapply);
ok(batch.apply);
ok(batch.addSubCommand);
ok(batch.isEmpty);
equals(typeof batch.unapply, typeof function(){});
equals(typeof batch.apply, typeof function(){});
equals(typeof batch.addSubCommand, typeof function(){});
equals(typeof batch.isEmpty, typeof function(){});
ok(batch.isEmpty());
batch.addSubCommand(new MockCommand('a'));
ok(!batch.isEmpty());
batch.addSubCommand(new MockCommand('b'));
batch.addSubCommand(new MockCommand('c'));
ok(!concatResult);
batch.apply();
equals(concatResult, 'abc');
MockCommand.prototype.apply = function() {};
MockCommand.prototype.unapply = function() { concatResult += this.text_; };
concatResult = '';
batch.unapply();
equals(concatResult, 'cba');
MockCommand.prototype.unapply = function() {};
tearDown();
});
});
</script>
</head>
<body>
<h1 id='qunit-header'>Unit Tests for history.js</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'>
</ol>
<div id='divparent' style='visibility:hidden'>
<div id='div1'></div>
<div id='div2'></div>
<div id='div3'></div>
</div>
<div id='div4' style='visibility:hidden'>
<div id='div5'></div>
</div>
</body>
</html>

View file

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
<script src='../editor/jquery.js'></script>
<script type='text/javascript' src='../editor/math.js'></script>
<script type='text/javascript' src='qunit/qunit.js'></script>
<script type='text/javascript'>
$(function() {
// log function
QUnit.log = function(result, message) {
if (window.console && window.console.log) {
window.console.log(result +' :: '+ message);
}
};
var svgns = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgns, 'svg');
module('svgedit.math');
test('Test svgedit.math package', function() {
expect(7);
ok(svgedit.math);
ok(svgedit.math.transformPoint);
ok(svgedit.math.isIdentity);
ok(svgedit.math.matrixMultiply);
equals(typeof svgedit.math.transformPoint, typeof function(){});
equals(typeof svgedit.math.isIdentity, typeof function(){});
equals(typeof svgedit.math.matrixMultiply, typeof function(){});
});
test('Test svgedit.math.transformPoint() function', function() {
expect(6);
var transformPoint = svgedit.math.transformPoint;
var m = svg.createSVGMatrix();
m.a = 1; m.b = 0;
m.c = 0; m.d = 1;
m.e = 0; m.f = 0;
var pt = transformPoint(100, 200, m);
equals(pt.x, 100);
equals(pt.y, 200);
m.e = 300; m.f = 400;
pt = transformPoint(100, 200, m);
equals(pt.x, 400);
equals(pt.y, 600);
m.a = 0.5; m.b = 0.75;
m.c = 1.25; m.d = 2;
pt = transformPoint(100, 200, m);
equals(pt.x, 100 * m.a + 200 * m.c + m.e);
equals(pt.y, 100 * m.b + 200 * m.d + m.f);
});
test('Test svgedit.math.isIdentity() function', function() {
expect(2);
ok(svgedit.math.isIdentity(svg.createSVGMatrix()));
var m = svg.createSVGMatrix();
m.a = 1; m.b = 0;
m.c = 0; m.d = 1;
m.e = 0; m.f = 0;
ok(svgedit.math.isIdentity(m));
});
test('Test svgedit.math.matrixMultiply() function', function() {
expect(5);
var mult = svgedit.math.matrixMultiply;
var isIdentity = svgedit.math.isIdentity;
// translate there and back
var tr_1 = svg.createSVGMatrix().translate(100,50),
tr_2 = svg.createSVGMatrix().translate(-90,0),
tr_3 = svg.createSVGMatrix().translate(-10,-50),
I = mult(tr_1,tr_2,tr_3);
ok(isIdentity(I), 'Expected identity matrix when translating there and back');
// rotate there and back
// TODO: currently Mozilla fails this when rotating back at -50 and then -40 degrees
// (b and c are *almost* zero, but not zero)
var rot_there = svg.createSVGMatrix().rotate(90),
rot_back = svg.createSVGMatrix().rotate(-90); // TODO: set this to -50
rot_back_more = svg.createSVGMatrix().rotate(0); // TODO: set this to -40
I = mult(rot_there, rot_back, rot_back_more);
ok(isIdentity(I), 'Expected identity matrix when rotating there and back');
// scale up and down
var scale_up = svg.createSVGMatrix().scale(4),
scale_down = svg.createSVGMatrix().scaleNonUniform(0.25,1);
scale_down_more = svg.createSVGMatrix().scaleNonUniform(1,0.25);
I = mult(scale_up, scale_down, scale_down_more);
ok(isIdentity(I), 'Expected identity matrix when scaling up and down');
// test multiplication with its inverse
I = mult(rot_there, rot_there.inverse());
ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse');
I = mult(rot_there.inverse(), rot_there);
ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse');
});
});
</script>
</head>
<body>
<h1 id='qunit-header'>Unit Tests for math.js</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'>
</ol>
</body>
</html>

View file

@ -0,0 +1,140 @@
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
<script src='../editor/jquery.js'></script>
<script type='text/javascript' src='../editor/browsersupport.js'></script>
<script type='text/javascript' src='../editor/math.js'></script>
<script type='text/javascript' src='../editor/svgutils.js'></script>
<script type='text/javascript' src='../editor/select.js'></script>
<script type='text/javascript' src='qunit/qunit.js'></script>
<script type='text/javascript'>
$(function() {
// log function
QUnit.log = function(result, message) {
if (window.console && window.console.log) {
window.console.log(result +' :: '+ message);
}
};
module('svgedit.select');
var svgns = 'http://www.w3.org/2000/svg';
var sandbox = document.getElementById('sandbox');
var svgroot;
var svgcontent;
var rect;
var mockConfig = {
dimensions: [640,480]
};
var mockFactory = {
createSVGElement: function(jsonMap) {
var elem = document.createElementNS(svgns, jsonMap['element']);
for (var attr in jsonMap['attr']) {
elem.setAttribute(attr, jsonMap['attr'][attr]);
}
return elem;
},
svgRoot: function() { return svgroot; },
svgContent: function() { return svgcontent; }
};
function setUp() {
svgroot = mockFactory.createSVGElement({
'element': 'svg',
'attr': {'id': 'svgroot'}
});
svgcontent = svgroot.appendChild(
mockFactory.createSVGElement({
'element': 'svg',
'attr': {'id': 'svgcontent'}
})
);
rect = svgcontent.appendChild(
mockFactory.createSVGElement({
'element': 'rect',
'attr': {
'id': 'rect',
'x': '50',
'y': '75',
'width': '200',
'height': '100'
}
})
);
sandbox.appendChild(svgroot);
svgedit.select.init(mockConfig, mockFactory);
}
function tearDown() {
while (sandbox.hasChildNodes()) {
sandbox.removeChild(sandbox.firstChild);
}
}
test('Test svgedit.select package', function() {
expect(10);
ok(svgedit.select);
ok(svgedit.select.Selector);
ok(svgedit.select.SelectorManager);
ok(svgedit.select.init);
ok(svgedit.select.getSelectorManager);
equals(typeof svgedit.select, typeof {});
equals(typeof svgedit.select.Selector, typeof function(){});
equals(typeof svgedit.select.SelectorManager, typeof function(){});
equals(typeof svgedit.select.init, typeof function(){});
equals(typeof svgedit.select.getSelectorManager, typeof function(){});
});
test('Test Selector DOM structure', function() {
expect(20);
setUp();
ok(svgroot);
ok(svgroot.hasChildNodes());
equals(svgroot.childNodes.length, 3);
// Verify existence of canvas background.
var cb = svgroot.childNodes.item(0);
ok(cb);
equals(cb.id, 'canvasBackground');
ok(svgroot.childNodes.item(1));
equals(svgroot.childNodes.item(1), svgcontent);
// Verify existence of selectorParentGroup.
var spg = svgroot.childNodes.item(2);
ok(spg);
equals(spg.id, 'selectorParentGroup');
equals(spg.tagName, 'g');
// Verify existence of all grip elements.
ok(spg.querySelector('#selectorGrip_resize_nw'));
ok(spg.querySelector('#selectorGrip_resize_n'));
ok(spg.querySelector('#selectorGrip_resize_ne'));
ok(spg.querySelector('#selectorGrip_resize_e'));
ok(spg.querySelector('#selectorGrip_resize_se'));
ok(spg.querySelector('#selectorGrip_resize_s'));
ok(spg.querySelector('#selectorGrip_resize_sw'));
ok(spg.querySelector('#selectorGrip_resize_w'));
ok(spg.querySelector('#selectorGrip_rotateconnector'));
ok(spg.querySelector('#selectorGrip_rotate'));
tearDown();
});
});
</script>
</head>
<body>
<h1 id='qunit-header'>Unit Tests for select.js</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'>
</ol>
<div id='sandbox'></div>
</body>
</html>

View file

@ -0,0 +1,418 @@
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
<script src='../editor/jquery.js'></script>
<script>
// Mock for browsersupport
window.svgedit = {};
svgedit.browsersupport = {};
svgedit.browsersupport.nativeTransformLists = false;
</script>
<script type='text/javascript' src='../editor/svgtransformlist.js'></script>
<script type='text/javascript' src='qunit/qunit.js'></script>
<script type='text/javascript'>
$(function() {
// log function
QUnit.log = function(result, message) {
if (window.console && window.console.log) {
window.console.log(result +' :: '+ message);
}
};
var svgns = 'http://www.w3.org/2000/svg';
var svgroot = document.getElementById('svgroot');
var svgcontent, rect, circle;
var NEAR_ZERO = 1e-7;
function almostEquals(a, b, msg) {
msg = msg || (a + ' did not equal ' + b);
ok(Math.abs(a - b) < NEAR_ZERO, msg);
}
function checkOutOfBoundsException(obj, fn, arg1) {
var caughtException = false;
try {
obj[fn](arg1);
}
catch(e) {
if (e.code == 1) {
caughtException = true;
}
}
ok(caughtException, 'Caugh an INDEX_SIZE_ERR exception');
}
function setUp() {
svgcontent = svgroot.appendChild(document.createElementNS(svgns, 'svg'));
rect = svgcontent.appendChild(document.createElementNS(svgns, 'rect'));
rect.id = 'r';
circle = svgcontent.appendChild(document.createElementNS(svgns, 'circle'));
circle.id = 'c';
}
function tearDown() {
svgedit.transformlist.resetListMap();
while (svgroot.hasChildNodes()) {
svgroot.removeChild(svgroot.firstChild);
}
}
module('svgedit.svgtransformlist');
test('Test svgedit.transformlist package', function() {
expect(2);
ok(svgedit.transformlist);
ok(svgedit.transformlist.getTransformList);
});
test('Test svgedit.transformlist.getTransformList() function', function() {
expect(4);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
var cxform = svgedit.transformlist.getTransformList(circle);
ok(rxform);
ok(cxform);
equals(typeof rxform, typeof {});
equals(typeof cxform, typeof {});
tearDown();
});
test('Test SVGTransformList.numberOfItems property', function() {
expect(2);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
equals(typeof rxform.numberOfItems, typeof 0);
equals(rxform.numberOfItems, 0);
tearDown();
});
test('Test SVGTransformList.initialize()', function() {
expect(6);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
var cxform = svgedit.transformlist.getTransformList(circle);
var t = svgcontent.createSVGTransform();
ok(t);
ok(rxform.initialize);
equals(typeof rxform.initialize, typeof function(){});
rxform.initialize(t);
equals(rxform.numberOfItems, 1);
equals(cxform.numberOfItems, 0);
// If a transform was already in a transform list, this should
// remove it from its old list and add it to this list.
cxform.initialize(t);
// This also fails in Firefox native.
// equals(rxform.numberOfItems, 0, 'Did not remove transform from list before initializing another transformlist');
equals(cxform.numberOfItems, 1);
tearDown();
});
test('Test SVGTransformList.appendItem() and getItem()', function() {
expect(12);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
var cxform = svgedit.transformlist.getTransformList(circle);
var t1 = svgcontent.createSVGTransform(),
t2 = svgcontent.createSVGTransform(),
t3 = svgcontent.createSVGTransform();
ok(rxform.appendItem);
ok(rxform.getItem);
equals(typeof rxform.appendItem, typeof function(){});
equals(typeof rxform.getItem, typeof function(){});
rxform.appendItem(t1);
rxform.appendItem(t2);
rxform.appendItem(t3);
equals(rxform.numberOfItems, 3);
var rxf = rxform.getItem(0);
equals(rxf, t1);
equals(rxform.getItem(1), t2);
equals(rxform.getItem(2), t3);
checkOutOfBoundsException(rxform, 'getItem', -1);
checkOutOfBoundsException(rxform, 'getItem', 3);
cxform.appendItem(t1);
// These also fail in Firefox native.
// equals(rxform.numberOfItems, 2, 'Did not remove a transform from a list before appending it to a new transformlist');
// equals(rxform.getItem(0), t2, 'Found the wrong transform in a transformlist');
// equals(rxform.getItem(1), t3, 'Found the wrong transform in a transformlist');
equals(cxform.numberOfItems, 1);
equals(cxform.getItem(0), t1);
tearDown();
});
test('Test SVGTransformList.removeItem()', function() {
expect(7);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
var t1 = svgcontent.createSVGTransform(),
t2 = svgcontent.createSVGTransform();
ok(rxform.removeItem);
equals(typeof rxform.removeItem, typeof function(){});
rxform.appendItem(t1);
rxform.appendItem(t2);
var removedTransform = rxform.removeItem(0);
equals(rxform.numberOfItems, 1);
equals(removedTransform, t1);
equals(rxform.getItem(0), t2);
checkOutOfBoundsException(rxform, 'removeItem', -1);
checkOutOfBoundsException(rxform, 'removeItem', 1);
tearDown();
});
test('Test SVGTransformList.replaceItem()', function() {
expect(8);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
var cxform = svgedit.transformlist.getTransformList(circle);
ok(rxform.replaceItem);
equals(typeof rxform.replaceItem, typeof function(){});
var t1 = svgcontent.createSVGTransform(),
t2 = svgcontent.createSVGTransform(),
t3 = svgcontent.createSVGTransform();
rxform.appendItem(t1);
rxform.appendItem(t2);
cxform.appendItem(t3);
var newItem = rxform.replaceItem(t3, 0);
equals(rxform.numberOfItems, 2);
equals(newItem, t3);
equals(rxform.getItem(0), t3);
equals(rxform.getItem(1), t2);
// Fails in Firefox native
// equals(cxform.numberOfItems, 0);
// test replaceItem within a list
rxform.appendItem(t1);
rxform.replaceItem(t1, 0);
// Fails in Firefox native
// equals(rxform.numberOfItems, 2);
equals(rxform.getItem(0), t1);
equals(rxform.getItem(1), t2);
tearDown();
});
test('Test SVGTransformList.insertItemBefore()', function() {
expect(10);
setUp();
var rxform = svgedit.transformlist.getTransformList(rect);
var cxform = svgedit.transformlist.getTransformList(circle);
ok(rxform.insertItemBefore);
equals(typeof rxform.insertItemBefore, typeof function(){});
var t1 = svgcontent.createSVGTransform(),
t2 = svgcontent.createSVGTransform(),
t3 = svgcontent.createSVGTransform();
rxform.appendItem(t1);
rxform.appendItem(t2);
cxform.appendItem(t3);
var newItem = rxform.insertItemBefore(t3, 0);
equals(rxform.numberOfItems, 3);
equals(newItem, t3);
equals(rxform.getItem(0), t3);
equals(rxform.getItem(1), t1);
equals(rxform.getItem(2), t2);
// Fails in Firefox native
// equals(cxform.numberOfItems, 0);
rxform.insertItemBefore(t2, 1);
// Fails in Firefox native (they make copies of the transforms)
// equals(rxform.numberOfItems, 3);
equals(rxform.getItem(0), t3);
equals(rxform.getItem(1), t2);
equals(rxform.getItem(2), t1);
tearDown();
});
test('Test SVGTransformList.init() for translate(200,100)', function() {
expect(8);
setUp();
rect.setAttribute('transform', 'translate(200,100)');
var rxform = svgedit.transformlist.getTransformList(rect);
equals(1, rxform.numberOfItems);
var translate = rxform.getItem(0);
equals(translate.type, 2);
var m = translate.matrix;
equals(m.a, 1);
equals(m.b, 0);
equals(m.c, 0);
equals(m.d, 1);
equals(m.e, 200);
equals(m.f, 100);
tearDown();
});
test('Test SVGTransformList.init() for scale(4)', function() {
expect(8);
setUp();
rect.setAttribute('transform', 'scale(4)');
var rxform = svgedit.transformlist.getTransformList(rect);
equals(1, rxform.numberOfItems);
var scale = rxform.getItem(0);
equals(3, scale.type);
var m = scale.matrix;
equals(m.a, 4);
equals(m.b, 0);
equals(m.c, 0);
equals(m.d, 4);
equals(m.e, 0);
equals(m.f, 0);
tearDown();
});
test('Test SVGTransformList.init() for scale(4,3)', function() {
expect(8);
setUp();
rect.setAttribute('transform', 'scale(4,3)');
var rxform = svgedit.transformlist.getTransformList(rect);
equals(1, rxform.numberOfItems);
var scale = rxform.getItem(0);
equals(3, scale.type);
var m = scale.matrix;
equals(m.a, 4);
equals(m.b, 0);
equals(m.c, 0);
equals(m.d, 3);
equals(m.e, 0);
equals(m.f, 0);
tearDown();
});
test('Test SVGTransformList.init() for rotate(45)', function() {
expect(9);
setUp();
rect.setAttribute('transform', 'rotate(45)');
var rxform = svgedit.transformlist.getTransformList(rect);
equals(1, rxform.numberOfItems);
var rotate = rxform.getItem(0);
equals(4, rotate.type);
equals(45, rotate.angle);
var m = rotate.matrix;
almostEquals(m.a, 1/Math.sqrt(2));
almostEquals(m.b, 1/Math.sqrt(2));
almostEquals(m.c, -1/Math.sqrt(2));
almostEquals(m.d, 1/Math.sqrt(2));
equals(m.e, 0);
equals(m.f, 0);
tearDown();
});
test('Test SVGTransformList.init() for rotate(45, 100, 200)', function() {
expect(9);
setUp();
rect.setAttribute('transform', 'rotate(45, 100, 200)');
var rxform = svgedit.transformlist.getTransformList(rect);
equals(1, rxform.numberOfItems);
var rotate = rxform.getItem(0);
equals(4, rotate.type);
equals(45, rotate.angle);
var m = rotate.matrix;
almostEquals(m.a, 1/Math.sqrt(2));
almostEquals(m.b, 1/Math.sqrt(2));
almostEquals(m.c, -1/Math.sqrt(2));
almostEquals(m.d, 1/Math.sqrt(2));
var r = svgcontent.createSVGMatrix();
r.a = 1/Math.sqrt(2); r.b = 1/Math.sqrt(2);
r.c = -1/Math.sqrt(2); r.d = 1/Math.sqrt(2);
var t = svgcontent.createSVGMatrix();
t.e = -100; t.f = -200;
var t_ = svgcontent.createSVGMatrix();
t_.e = 100; t_.f = 200;
var result = t_.multiply(r).multiply(t);
almostEquals(m.e, result.e);
almostEquals(m.f, result.f);
tearDown();
});
test('Test SVGTransformList.init() for matrix(1, 2, 3, 4, 5, 6)', function() {
expect(8);
setUp();
rect.setAttribute('transform', 'matrix(1,2,3,4,5,6)');
var rxform = svgedit.transformlist.getTransformList(rect);
equals(1, rxform.numberOfItems);
var mt = rxform.getItem(0);
equals(1, mt.type);
var m = mt.matrix;
equals(m.a, 1);
equals(m.b, 2);
equals(m.c, 3);
equals(m.d, 4);
equals(m.e, 5);
equals(m.f, 6);
tearDown();
});
});
</script>
</head>
<body>
<h1 id='qunit-header'>Unit Tests for svgtransformlist.js</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'>
</ol>
<div id='svgroot' style='visibility:hidden'></div>
</body>
</html>

View file

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
<script src='../editor/jquery.js'></script>
<!-- svgutils.js depends on these two... mock out? -->
<script type='text/javascript' src='../editor/browsersupport.js'></script>
<script type='text/javascript' src='../editor/svgtransformlist.js'></script>
<script type='text/javascript' src='../editor/svgutils.js'></script>
<script type='text/javascript' src='qunit/qunit.js'></script>
<script type='text/javascript'>
$(function() {
// log function
QUnit.log = function(result, message) {
if (window.console && window.console.log) {
window.console.log(result +' :: '+ message);
}
};
var svgns = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgns, 'svg');
module('svgedit.utilities');
test('Test svgedit.utilities package', function() {
expect(3);
ok(svgedit.utilities);
ok(svgedit.utilities.toXml);
equals(typeof svgedit.utilities.toXml, typeof function(){});
});
test('Test svgedit.utilities.toXml() function', function() {
expect(6);
var toXml = svgedit.utilities.toXml;
equals(toXml('a'), 'a');
equals(toXml('ABC_'), 'ABC_');
equals(toXml('PB&J'), 'PB&amp;J');
equals(toXml('2 < 5'), '2 &lt; 5');
equals(toXml('5 > 2'), '5 &gt; 2');
equals(toXml('\'<&>"'), '\'&lt;&amp;&gt;"');
});
test('Test svgedit.utilities.fromXml() function', function() {
expect(6);
var fromXml = svgedit.utilities.fromXml;
equals(fromXml('a'), 'a');
equals(fromXml('ABC_'), 'ABC_');
equals(fromXml('PB&amp;J'), 'PB&J');
equals(fromXml('2 &lt; 5'), '2 < 5');
equals(fromXml('5 &gt; 2'), '5 > 2');
equals(fromXml('&lt;&amp;&gt;'), '<&>');
});
test('Test svgedit.utilities.encode64() function', function() {
expect(4);
var encode64 = svgedit.utilities.encode64;
equals(encode64('abcdef'), 'YWJjZGVm');
equals(encode64('12345'), 'MTIzNDU=');
equals(encode64(' '), 'IA==');
equals(encode64('`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'), 'YH4hQCMkJV4mKigpLV89K1t7XX1cfDs6JyIsPC4+Lz8=');
});
test('Test svgedit.utilities.decode64() function', function() {
expect(4);
var decode64 = svgedit.utilities.decode64;
equals(decode64('YWJjZGVm'), 'abcdef');
equals(decode64('MTIzNDU='), '12345');
equals(decode64('IA=='), ' ');
equals(decode64('YH4hQCMkJV4mKigpLV89K1t7XX1cfDs6JyIsPC4+Lz8='), '`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?');
});
test('Test svgedit.utilities.convertToXMLReferences() function', function() {
expect(1);
var convert = svgedit.utilities.convertToXMLReferences;
equals(convert('ABC'), 'ABC');
// equals(convert('ÀBC'), '&#192;BC');
});
test('Test svgedit.utilities.bboxToObj() function', function() {
expect(5);
var bboxToObj = svgedit.utilities.bboxToObj;
var rect = svg.createSVGRect();
rect.x = 1;
rect.y = 2;
rect.width = 3;
rect.height = 4;
var obj = bboxToObj(rect);
equals(typeof obj, typeof {});
equals(obj.x, 1);
equals(obj.y, 2);
equals(obj.width, 3);
equals(obj.height, 4);
});
test("Test getUrlFromAttr", function() {
expect(4);
equal(svgedit.utilities.getUrlFromAttr("url(#foo)"), "#foo");
equal(svgedit.utilities.getUrlFromAttr("url(somefile.svg#foo)"), "somefile.svg#foo");
equal(svgedit.utilities.getUrlFromAttr("url('#foo')"), "#foo");
equal(svgedit.utilities.getUrlFromAttr('url("#foo")'), "#foo");
});
});
</script>
</head>
<body>
<h1 id='qunit-header'>Unit Tests for svgutils.js</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'>
</ol>
</body>
</html>

View file

@ -5,6 +5,14 @@
<script type="text/javascript" src="../editor/jquery-ui/jquery-ui-1.8.custom.min.js"></script>
<script type="text/javascript" src="../editor/svgicons/jquery.svgicons.js"></script>
<script type="text/javascript" src="../editor/locale/locale.js"></script>
<script type="text/javascript" src="../editor/browsersupport.js"></script>
<script type="text/javascript" src="../editor/svgtransformlist.js"></script>
<script type="text/javascript" src="../editor/math.js"></script>
<script type="text/javascript" src="../editor/units.js"></script>
<script type="text/javascript" src="../editor/svgutils.js"></script>
<script type="text/javascript" src="../editor/sanitize.js"></script>
<script type="text/javascript" src="../editor/history.js"></script>
<script type="text/javascript" src="../editor/select.js"></script>
<script type="text/javascript" src="../editor/svgcanvas.js"></script>
<script type="text/javascript" src="qunit/qunit.js"></script>
<script type="text/javascript">
@ -101,46 +109,6 @@
QUnit.log(d);
});
module("Transform Module");
test("Test matrixMultiply", function() {
expect(5);
// translate there and back
var tr_1 = svgroot.createSVGMatrix().translate(100,50),
tr_2 = svgroot.createSVGMatrix().translate(-90,0),
tr_3 = svgroot.createSVGMatrix().translate(-10,-50),
I = svgCanvas.matrixMultiply(tr_1,tr_2,tr_3);
equal(isIdentity(I), true,
"Expected identity matrix when translating there and back, got " + matrixString(I));
// rotate there and back
// TODO: currently Mozilla fails this when rotating back at -50 and then -40 degrees
// (b and c are *almost* zero, but not zero)
var rot_there = svgroot.createSVGMatrix().rotate(90),
rot_back = svgroot.createSVGMatrix().rotate(-90); // TODO: set this to -50
rot_back_more = svgroot.createSVGMatrix().rotate(0); // TODO: set this to -40
I = svgCanvas.matrixMultiply(rot_there, rot_back, rot_back_more);
equal(isIdentity(I), true,
"Expected identity matrix when rotating there and back, got " + matrixString(I));
// scale up and down
var scale_up = svgroot.createSVGMatrix().scale(4),
scale_down = svgroot.createSVGMatrix().scaleNonUniform(0.25,1);
scale_down_more = svgroot.createSVGMatrix().scaleNonUniform(1,0.25);
I = svgCanvas.matrixMultiply(scale_up, scale_down, scale_down_more);
equal(isIdentity(I), true,
"Expected identity matrix when scaling up and down, got " + matrixString(I));
// test multiplication with its inverse
I = svgCanvas.matrixMultiply(rot_there, rot_there.inverse());
equal(isIdentity(I), true,
"Expected identity matrix when multiplying a matrix by its inverse, got " + matrixString(I));
I = svgCanvas.matrixMultiply(rot_there.inverse(), rot_there);
equal(isIdentity(I), true,
"Expected identity matrix when multiplying a matrix by its inverse, got " + matrixString(I));
});
module("Import Module");
test("Test import use", function() {
@ -162,15 +130,6 @@
equal(nfu, null, "Removed <use> element that had no href");
});
test("Test getUrlFromAttr", function() {
expect(4);
equal(svgCanvas.getUrlFromAttr("url(#foo)"), "#foo");
equal(svgCanvas.getUrlFromAttr("url(somefile.svg#foo)"), "somefile.svg#foo");
equal(svgCanvas.getUrlFromAttr("url('#foo')"), "#foo");
equal(svgCanvas.getUrlFromAttr('url("#foo")'), "#foo");
});
// This test shows that an element with an invalid attribute is still parsed in properly
// and only the attribute is not imported
test("Test invalid attribute", function() {
@ -235,15 +194,6 @@
equal(math.namespaceURI, "http://www.w3.org/1998/Math/MathML", "Preserved MathML namespace");
});
test("Test escaping XML entities", function() {
expect(3);
equal(svgCanvas.Utils.toXml("<"), "&lt;", "Escaped < properly");
equal(svgCanvas.Utils.toXml(">"), "&gt;", "Escaped > properly");
equal(svgCanvas.Utils.toXml("&"), "&amp;", "Escaped & properly");
// TODO: what about &quot; and &apos; ?
});
test("Test importing SVG into existing drawing", function() {
expect(3);