From e5ca66ae41074fe6b1acc88f8d5838fe43128dbd Mon Sep 17 00:00:00 2001 From: Jacques Distler Date: Fri, 10 Sep 2010 15:19:23 -0500 Subject: [PATCH] Sync with SVG-Edit --- public/svg-edit/editor/canvg/canvg.js | 473 ++++++++-- .../editor/contextmenu/jquery.contextMenu.js | 198 +++++ .../contextmenu/jquery.contextMenu.min.js | 22 + .../editor/extensions/ext-connector.js | 9 +- public/svg-edit/editor/extensions/ext-grid.js | 150 ++++ .../editor/extensions/ext-imagelib.js | 17 +- .../editor/extensions/ext-imagelib.xml | 16 +- .../editor/extensions/ext-server_opensave.js | 57 +- .../svg-edit/editor/extensions/grid-icon.xml | 30 + .../svg-edit/editor/images/svg_edit_icons.svg | 16 +- .../editor/js-hotkeys/jquery.hotkeys.min.js | 2 +- public/svg-edit/editor/svg-editor.css | 104 ++- public/svg-edit/editor/svg-editor.html | 34 +- public/svg-edit/editor/svg-editor.js | 343 ++++--- public/svg-edit/editor/svgcanvas.js | 835 ++++++++++++++---- .../editor/svgicons/jquery.svgicons.js | 121 ++- public/svg-edit/test/test1.html | 2 +- 17 files changed, 1962 insertions(+), 467 deletions(-) create mode 100755 public/svg-edit/editor/contextmenu/jquery.contextMenu.js create mode 100644 public/svg-edit/editor/contextmenu/jquery.contextMenu.min.js create mode 100644 public/svg-edit/editor/extensions/ext-grid.js create mode 100644 public/svg-edit/editor/extensions/grid-icon.xml diff --git a/public/svg-edit/editor/canvg/canvg.js b/public/svg-edit/editor/canvg/canvg.js index 17d0ef4b..bc0d9e37 100644 --- a/public/svg-edit/editor/canvg/canvg.js +++ b/public/svg-edit/editor/canvg/canvg.js @@ -11,11 +11,29 @@ if(!window.console) { window.console.log = function(str) {}; window.console.dir = function(str) {}; } + +// <3 IE +if(!Array.indexOf){ + Array.prototype.indexOf = function(obj){ + for(var i=0; i ignore mouse events + // ignoreAnimation: true => ignore animations + // renderCallback: function => will call the function after the first render is completed + // forceRedraw: function => will call the function on every frame, if it returns true, will redraw + this.canvg = function (target, s, opts) { if (typeof target == 'string') { target = document.getElementById(target); } @@ -30,6 +48,7 @@ if(!window.console) { svg = target.svg; svg.stop(); } + svg.opts = opts; var ctx = target.getContext('2d'); if (s.substr(0,1) == '<') { @@ -43,7 +62,7 @@ if(!window.console) { } function build() { - var svg = {}; + var svg = { }; svg.FRAMERATE = 30; @@ -52,6 +71,7 @@ if(!window.console) { svg.Definitions = {}; svg.Styles = {}; svg.Animations = []; + svg.Images = []; svg.ctx = ctx; svg.ViewPort = new (function () { this.viewPorts = []; @@ -69,6 +89,14 @@ if(!window.console) { }); } svg.init(); + + // images loaded + svg.ImagesLoaded = function() { + for (var i=0; i x_off) x = x_off-15; + if(y > y_off) y = y_off-15; + + // Show the menu + $(document).unbind('click'); + $(menu).css({ top: y, left: x }).fadeIn(o.inSpeed); + // Hover events + $(menu).find('A').mouseover( function() { + $(menu).find('LI.hover').removeClass('hover'); + $(this).parent().addClass('hover'); + }).mouseout( function() { + $(menu).find('LI.hover').removeClass('hover'); + }); + + // Keyboard + $(document).keypress( function(e) { + switch( e.keyCode ) { + case 38: // up + if( $(menu).find('LI.hover').size() == 0 ) { + $(menu).find('LI:last').addClass('hover'); + } else { + $(menu).find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover'); + if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:last').addClass('hover'); + } + break; + case 40: // down + if( $(menu).find('LI.hover').size() == 0 ) { + $(menu).find('LI:first').addClass('hover'); + } else { + $(menu).find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover'); + if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:first').addClass('hover'); + } + break; + case 13: // enter + $(menu).find('LI.hover A').trigger('click'); + break; + case 27: // esc + $(document).trigger('click'); + break + } + }); + + // When items are selected + $('#' + o.menu).find('A').unbind('mouseup'); + $('#' + o.menu).find('LI:not(.disabled) A').mouseup( function() { + $(document).unbind('click').unbind('keypress'); + $(".contextMenu").hide(); + // Callback + if( callback ) callback( $(this).attr('href').substr(1), $(srcElement), {x: x - offset.left, y: y - offset.top, docX: x, docY: y} ); + return false; + }); + + // Hide bindings + setTimeout( function() { // Delay for Mozilla + $(document).click( function() { + $(document).unbind('click').unbind('keypress'); + $(menu).fadeOut(o.outSpeed); + return false; + }); + }, 0); + } + }); + }); + + // Disable text selection + if( $.browser.mozilla ) { + $('#' + o.menu).each( function() { $(this).css({ 'MozUserSelect' : 'none' }); }); + } else if( $.browser.msie ) { + $('#' + o.menu).each( function() { $(this).bind('selectstart.disableTextSelect', function() { return false; }); }); + } else { + $('#' + o.menu).each(function() { $(this).bind('mousedown.disableTextSelect', function() { return false; }); }); + } + // Disable browser context menu (requires both selectors to work in IE/Safari + FF/Chrome) + $(el).add($('UL.contextMenu')).bind('contextmenu', function() { return false; }); + + }); + return $(this); + }, + + // Disable context menu items on the fly + disableContextMenuItems: function(o) { + if( o == undefined ) { + // Disable all + $(this).find('LI').addClass('disabled'); + return( $(this) ); + } + $(this).each( function() { + if( o != undefined ) { + var d = o.split(','); + for( var i = 0; i < d.length; i++ ) { + $(this).find('A[href="' + d[i] + '"]').parent().addClass('disabled'); + + } + } + }); + return( $(this) ); + }, + + // Enable context menu items on the fly + enableContextMenuItems: function(o) { + if( o == undefined ) { + // Enable all + $(this).find('LI.disabled').removeClass('disabled'); + return( $(this) ); + } + $(this).each( function() { + if( o != undefined ) { + var d = o.split(','); + for( var i = 0; i < d.length; i++ ) { + $(this).find('A[href="' + d[i] + '"]').parent().removeClass('disabled'); + + } + } + }); + return( $(this) ); + }, + + // Disable context menu(s) + disableContextMenu: function() { + $(this).each( function() { + $(this).addClass('disabled'); + }); + return( $(this) ); + }, + + // Enable context menu(s) + enableContextMenu: function() { + $(this).each( function() { + $(this).removeClass('disabled'); + }); + return( $(this) ); + }, + + // Destroy context menu(s) + destroyContextMenu: function() { + // Destroy specified context menus + $(this).each( function() { + // Disable action + $(this).unbind('mousedown').unbind('mouseup'); + }); + return( $(this) ); + } + + }); +})(jQuery); \ No newline at end of file diff --git a/public/svg-edit/editor/contextmenu/jquery.contextMenu.min.js b/public/svg-edit/editor/contextmenu/jquery.contextMenu.min.js new file mode 100644 index 00000000..2517baf5 --- /dev/null +++ b/public/svg-edit/editor/contextmenu/jquery.contextMenu.min.js @@ -0,0 +1,22 @@ +// jQuery Context Menu Plugin +// +// Version 1.01 +// +// Cory S.N. LaViska +// A Beautiful Site (http://abeautifulsite.net/) +// +// More info: http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/ +// +// Terms of Use +// +// This plugin is dual-licensed under the GNU General Public License +// and the MIT License and is copyright A Beautiful Site, LLC. +// +jQuery&&function(){$.extend($.fn,{contextMenu:function(a,e){if(a.menu==undefined)return false;if(a.inSpeed==undefined)a.inSpeed=150;if(a.outSpeed==undefined)a.outSpeed=75;if(a.inSpeed==0)a.inSpeed=-1;if(a.outSpeed==0)a.outSpeed=-1;$(this).each(function(){var d=$(this),i=$(d).offset();$("#"+a.menu).addClass("contextMenu");$(this).mousedown(function(j){j.stopPropagation();$(this).mouseup(function(f){f.stopPropagation();var k=$(this);$(this).unbind("mouseup");if(j.button==2){$(".contextMenu").hide(); +var b=$("#"+a.menu);if($(d).hasClass("disabled"))return false;var c={},g,h;if(self.innerHeight){c.pageYOffset=self.pageYOffset;c.pageXOffset=self.pageXOffset;c.innerHeight=self.innerHeight;c.innerWidth=self.innerWidth}else if(document.documentElement&&document.documentElement.clientHeight){c.pageYOffset=document.documentElement.scrollTop;c.pageXOffset=document.documentElement.scrollLeft;c.innerHeight=document.documentElement.clientHeight;c.innerWidth=document.documentElement.clientWidth}else if(document.body){c.pageYOffset= +document.body.scrollTop;c.pageXOffset=document.body.scrollLeft;c.innerHeight=document.body.clientHeight;c.innerWidth=document.body.clientWidth}f.pageX?g=f.pageX:g=f.clientX+c.scrollLeft;f.pageY?h=f.pageY:h=f.clientY+c.scrollTop;$(document).unbind("click");$(b).css({top:h,left:g}).fadeIn(a.inSpeed);$(b).find("A").mouseover(function(){$(b).find("LI.hover").removeClass("hover");$(this).parent().addClass("hover")}).mouseout(function(){$(b).find("LI.hover").removeClass("hover")});$(document).keypress(function(l){switch(l.keyCode){case 38:if($(b).find("LI.hover").size()== +0)$(b).find("LI:last").addClass("hover");else{$(b).find("LI.hover").removeClass("hover").prevAll("LI:not(.disabled)").eq(0).addClass("hover");$(b).find("LI.hover").size()==0&&$(b).find("LI:last").addClass("hover")}break;case 40:if($(b).find("LI.hover").size()==0)$(b).find("LI:first").addClass("hover");else{$(b).find("LI.hover").removeClass("hover").nextAll("LI:not(.disabled)").eq(0).addClass("hover");$(b).find("LI.hover").size()==0&&$(b).find("LI:first").addClass("hover")}break;case 13:$(b).find("LI.hover A").trigger("click"); +break;case 27:$(document).trigger("click");break}});$("#"+a.menu).find("A").unbind("click");$("#"+a.menu).find("LI:not(.disabled) A").click(function(){$(document).unbind("click").unbind("keypress");$(".contextMenu").hide();e&&e($(this).attr("href").substr(1),$(k),{x:g-i.left,y:h-i.top,docX:g,docY:h});return false});setTimeout(function(){$(document).click(function(){$(document).unbind("click").unbind("keypress");$(b).fadeOut(a.outSpeed);return false})},0)}})});if($.browser.mozilla)$("#"+a.menu).each(function(){$(this).css({MozUserSelect:"none"})}); +else $.browser.msie?$("#"+a.menu).each(function(){$(this).bind("selectstart.disableTextSelect",function(){return false})}):$("#"+a.menu).each(function(){$(this).bind("mousedown.disableTextSelect",function(){return false})});$(d).add($("UL.contextMenu")).bind("contextmenu",function(){return false})});return $(this)},disableContextMenuItems:function(a){if(a==undefined){$(this).find("LI").addClass("disabled");return $(this)}$(this).each(function(){if(a!=undefined)for(var e=a.split(","),d=0;d
\ + $('
\
').insertAfter('#svg_docprops'); browser = $('#imgbrowse'); @@ -101,24 +101,27 @@ svgEditor.addExtension("imagelib", function() { var header = $('

').prependTo(browser).text(all_libs); - var cancel = $('').appendTo(browser).click(function() { + var cancel = $('').appendTo(browser).click(function() { $('#imgbrowse_holder').hide(); }).css({ position: 'absolute', top: 5, - right: 5 + right: -10 }); - var back = $('').appendTo(browser).click(function() { + var back = $('').appendTo(browser).click(function() { frame.attr('src', 'about:blank').hide(); lib_opts.show(); header.text(all_libs); }).css({ position: 'absolute', top: 5, - left: 5 + left: 10 }); + cancel.prepend($.getSvgIcon('cancel', true)); + back.prepend($.getSvgIcon('tool_imagelib', true)); + $.each(img_libs, function(i, opts) { $('
  • ').appendTo(lib_opts).text(opts.name).click(function() { frame.attr('src', opts.url).show(); @@ -175,7 +178,7 @@ svgEditor.addExtension("imagelib", function() { #imgbrowse > div,\ #imgbrowse > ul {\ position: absolute;\ - top: 36px;\ + top: 45px;\ left: 10px;\ right: 10px;\ bottom: 10px;\ diff --git a/public/svg-edit/editor/extensions/ext-imagelib.xml b/public/svg-edit/editor/extensions/ext-imagelib.xml index 2834b8d6..936578c0 100644 --- a/public/svg-edit/editor/extensions/ext-imagelib.xml +++ b/public/svg-edit/editor/extensions/ext-imagelib.xml @@ -1,10 +1,14 @@ - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/public/svg-edit/editor/extensions/ext-server_opensave.js b/public/svg-edit/editor/extensions/ext-server_opensave.js index 51236995..278d9e5b 100644 --- a/public/svg-edit/editor/extensions/ext-server_opensave.js +++ b/public/svg-edit/editor/extensions/ext-server_opensave.js @@ -18,7 +18,7 @@ svgEditor.addExtension("server_opensave", { svgEditor.setCustomHandlers({ save: function(win, data) { - var svg = "\n" + data; + var svg = "\n" + data; var title = svgCanvas.getDocumentTitle(); var filename = title.replace(/[^a-z0-9\.\_\-]+/gi, '_'); @@ -42,33 +42,34 @@ svgEditor.addExtension("server_opensave", { c.width = svgCanvas.contentW; c.height = svgCanvas.contentH; - canvg(c, data.svg); - var datauri = c.toDataURL('image/png'); - - var uiStrings = svgEditor.uiStrings; - var note = ''; - - // Check if there's issues - if(issues.length) { - var pre = "\n \u2022 "; - note += ("\n\n" + pre + issues.join(pre)); - } - - if(note.length) { - alert(note); - } - - var title = svgCanvas.getDocumentTitle(); - var filename = title.replace(/[^a-z0-9\.\_\-]+/gi, '_'); - - var form = $('
    ').attr({ - method: 'post', - action: save_png_action, - target: 'output_frame' - }) .append('') - .append('') - .appendTo('body') - .submit().remove(); + canvg(c, data.svg, {renderCallback: function() { + var datauri = c.toDataURL('image/png'); + + var uiStrings = svgEditor.uiStrings; + var note = ''; + + // Check if there's issues + if(issues.length) { + var pre = "\n \u2022 "; + note += ("\n\n" + pre + issues.join(pre)); + } + + if(note.length) { + alert(note); + } + + var title = svgCanvas.getDocumentTitle(); + var filename = title.replace(/[^a-z0-9\.\_\-]+/gi, '_'); + + var form = $('').attr({ + method: 'post', + action: save_png_action, + target: 'output_frame' + }) .append('') + .append('') + .appendTo('body') + .submit().remove(); + }}); } diff --git a/public/svg-edit/editor/extensions/grid-icon.xml b/public/svg-edit/editor/extensions/grid-icon.xml new file mode 100644 index 00000000..d34eb9d0 --- /dev/null +++ b/public/svg-edit/editor/extensions/grid-icon.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svg-edit/editor/images/svg_edit_icons.svg b/public/svg-edit/editor/images/svg_edit_icons.svg index 26959c20..2b1a3780 100644 --- a/public/svg-edit/editor/images/svg_edit_icons.svg +++ b/public/svg-edit/editor/images/svg_edit_icons.svg @@ -540,6 +540,18 @@ + + + + + + + + + + + + @@ -721,8 +733,8 @@ - - T + + T T diff --git a/public/svg-edit/editor/js-hotkeys/jquery.hotkeys.min.js b/public/svg-edit/editor/js-hotkeys/jquery.hotkeys.min.js index 52d95517..15e6fe30 100644 --- a/public/svg-edit/editor/js-hotkeys/jquery.hotkeys.min.js +++ b/public/svg-edit/editor/js-hotkeys/jquery.hotkeys.min.js @@ -12,4 +12,4 @@ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ */ -(function(b){b.hotkeys={version:"0.8",specialKeys:{8:"backspace",9:"tab",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",191:"/",224:"meta"},shiftNums:{"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"_","=":"+",";":": ","'":'"',",":"<",".":">","/":"?","\\":"|"}};function a(d){if(typeof d.data!=="string"){return}var c=d.handler,e=d.data.toLowerCase().split(" ");d.handler=function(n){if(this!==n.target&&(/textarea|select/i.test(n.target.nodeName)||n.target.type==="text")){return}var h=n.type!=="keypress"&&b.hotkeys.specialKeys[n.which],o=String.fromCharCode(n.which).toLowerCase(),k,m="",g={};if(n.altKey&&h!=="alt"){m+="alt+"}if(n.ctrlKey&&h!=="ctrl"){m+="ctrl+"}if(n.metaKey&&!n.ctrlKey&&h!=="meta"){m+="meta+"}if(n.shiftKey&&h!=="shift"){m+="shift+"}if(h){g[m+h]=true}else{g[m+o]=true;g[m+b.hotkeys.shiftNums[o]]=true;if(m==="shift+"){g[b.hotkeys.shiftNums[o]]=true}}for(var j=0,f=e.length;j","/":"?","\\":"|"}};function a(d){if(typeof d.data!=="string"){return}var c=d.handler,e=d.data.toLowerCase().split(" ");d.handler=function(n){if(this!==n.target&&(/textarea|select/i.test(n.target.nodeName)||n.target.type==="text")){return}var h=n.type!=="keypress"&&b.hotkeys.specialKeys[n.which],o=String.fromCharCode(n.which).toLowerCase(),k,m="",g={};if(n.altKey&&h!=="alt"){m+="alt+"}if(n.ctrlKey&&h!=="ctrl"){m+="ctrl+"}if(n.metaKey&&!n.ctrlKey&&h!=="meta"){m+="meta+"}if(n.shiftKey&&h!=="shift"){m+="shift+"}if(h){g[m+h]=true}else{g[m+o]=true;g[m+b.hotkeys.shiftNums[o]]=true;if(m==="shift+"){g[b.hotkeys.shiftNums[o]]=true}}for(var j=0,f=e.length;j + @@ -61,6 +62,7 @@ script type="text/javascript" src="locale/locale.min.js">
    +
    @@ -562,6 +564,10 @@ script type="text/javascript" src="locale/locale.min.js"> +
    +

    Copy the contents of this box into a text editor, then save the file with a .svg extension.

    + +
    @@ -581,7 +587,7 @@ script type="text/javascript" src="locale/locale.min.js">Image Properties
    @@ -650,10 +656,16 @@ script type="text/javascript" src="locale/locale.min.js">
    Editor Background
    - +

    Note: Background will not be saved with image.

    +
    + Grid + + +
    +
    @@ -667,5 +679,23 @@ script type="text/javascript" src="locale/locale.min.js"> + + + + + diff --git a/public/svg-edit/editor/svg-editor.js b/public/svg-edit/editor/svg-editor.js index 4a6963fb..15b2b060 100644 --- a/public/svg-edit/editor/svg-editor.js +++ b/public/svg-edit/editor/svg-editor.js @@ -11,6 +11,8 @@ */ (function() { + // TODO: Find out what causes bugs in jQuery animate for IE9 + if($.browser.msie) $.fx.off = true; if(!window.svgEditor) window.svgEditor = function($) { var svgCanvas; @@ -46,10 +48,12 @@ langPath: 'locale/', extPath: 'extensions/', jGraduatePath: 'jgraduate/images/', - extensions: ['ext-markers.js','ext-connector.js', 'ext-eyedropper.js', 'ext-imagelib.js', 'ext-itex.js'], + extensions: ['ext-markers.js','ext-connector.js', 'ext-eyedropper.js', 'ext-imagelib.js','ext-grid.js', 'ext-itex.js'], initTool: 'select', wireframe: false, - colorPickerCSS: null + colorPickerCSS: null, + gridSnapping: false, + snappingStep: 5 }, uiStrings = Editor.uiStrings = { "invalidAttrValGiven":"Invalid value given", @@ -435,13 +439,14 @@ "#aaaaff", "#d4aaff", "#ffaaff", "#ffaad4", ]; - isMac = false, //(navigator.platform.indexOf("Mac") != -1); - modKey = "", //(isMac ? "meta+" : "ctrl+"); + isMac = (navigator.platform.indexOf("Mac") != -1); + modKey = (isMac ? "meta+" : "ctrl+"); // ⌘ path = svgCanvas.pathActions, undoMgr = svgCanvas.undoMgr, Utils = svgCanvas.Utils, default_img_url = curConfig.imgPath + "logo.png", workarea = $("#workarea"), + canv_menu = $("#cmenu_canvas"), show_save_warning = false, exportWindow = null, tool_scale = 1; @@ -496,9 +501,12 @@ }()); var setSelectMode = function() { - $('.tool_button_current').removeClass('tool_button_current').addClass('tool_button'); - $('#tool_select').addClass('tool_button_current').removeClass('tool_button'); - $('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all} #svgcanvas svg{cursor:default}'); + var curr = $('.tool_button_current'); + if(curr[0].id !== 'tool_select') { + curr.removeClass('tool_button_current').addClass('tool_button'); + $('#tool_select').addClass('tool_button_current').removeClass('tool_button'); + $('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all} #svgcanvas svg{cursor:default}'); + } svgCanvas.setMode('select'); }; @@ -539,6 +547,15 @@ // Opens the SVG in new window, with warning about Mozilla bug #308590 when applicable + var ua = navigator.userAgent; + + // Chrome 5 (and 6?) don't allow saving, show source instead ( http://code.google.com/p/chromium/issues/detail?id=46735 ) + // IE9 doesn't allow standalone Data URLs ( https://connect.microsoft.com/IE/feedback/details/542600/data-uri-images-fail-when-loaded-by-themselves ) + if((~ua.indexOf('Chrome') && $.browser.version >= 533) || ~ua.indexOf('MSIE')) { + showSourceEditor(0,true); + return; + } + var win = window.open("data:image/svg+xml;base64," + Utils.encode64(svg)); // Alert will only appear the first time saved OR the first time the bug is encountered @@ -548,7 +565,7 @@ var note = uiStrings.saveFromBrowser.replace('%s', 'SVG'); // Check if FF and has - if(navigator.userAgent.indexOf('Gecko/') !== -1) { + if(ua.indexOf('Gecko/') !== -1) { if(svg.indexOf('\ - \ - \ - \ - ', 'text/xml'); - - var boxgrad = svgdocbox.getElementById('gradbox_'); - boxgrad.id = 'gradbox_fill'; - svgdocbox.documentElement.setAttribute('width',16.5); - $('#fill_color').append( document.importNode(svgdocbox.documentElement,true) ); - boxgrad.id = 'gradbox_stroke'; - svgdocbox.documentElement.setAttribute('width',16.5); - $('#stroke_color').append( document.importNode(svgdocbox.documentElement,true) ); - $('#stroke_color rect').attr({ - 'fill': '#' + curConfig.initStroke.color, - 'opacity': curConfig.initStroke.opacity - }); - - $('#stroke_width').val(curConfig.initStroke.width); - $('#group_opacity').val(curConfig.initOpacity * 100); - - // Use this SVG elem to test vectorEffect support - var test_el = svgdocbox.documentElement.firstChild; - test_el.setAttribute('style','vector-effect:non-scaling-stroke'); - var supportsNonSS = (test_el.style.vectorEffect == 'non-scaling-stroke'); - test_el.removeAttribute('style'); - - // Use this to test support for blur element. Seems to work to test support in Webkit - var blur_test = svgdocbox.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur'); - if(typeof blur_test.stdDeviationX === "undefined") { - $('#tool_blur').hide(); + if(window.DOMParser) { + // set up gradients to be used for the buttons + var svgdocbox = new DOMParser().parseFromString( + '\ + \ + \ + \ + ', 'text/xml'); + var docElem = svgdocbox.documentElement; + + + var boxgrad = svgdocbox.getElementById('gradbox_'); + boxgrad.id = 'gradbox_fill'; + docElem.setAttribute('width',16.5); + $('#fill_color').append( document.importNode(docElem,true) ); + + boxgrad.id = 'gradbox_stroke'; + docElem.setAttribute('width',16.5); + $('#stroke_color').append( document.importNode(docElem,true) ); + $('#stroke_color rect').attr({ + 'fill': '#' + curConfig.initStroke.color, + 'opacity': curConfig.initStroke.opacity + }); + + $('#stroke_width').val(curConfig.initStroke.width); + $('#group_opacity').val(curConfig.initOpacity * 100); + + // Use this SVG elem to test vectorEffect support + var test_el = docElem.firstChild; + test_el.setAttribute('style','vector-effect:non-scaling-stroke'); + var supportsNonSS = (test_el.style.vectorEffect == 'non-scaling-stroke'); + test_el.removeAttribute('style'); + + // Use this to test support for blur element. Seems to work to test support in Webkit + var blur_test = svgdocbox.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur'); + if(typeof blur_test.stdDeviationX === "undefined") { + $('#tool_blur').hide(); + } + $(blur_test).remove(); + } else { + var svgns = "http://www.w3.org/2000/svg"; + var svgdocbox = document.createElementNS(svgns, 'svg'); + var rect = svgCanvas.addSvgElementFromJson({ + element: 'rect', + attr: { + width: '100%', + height: '100%', + fill: '#' + curConfig.initFill.color, + opacity: curConfig.initFill.opacity + } + }); + svgdocbox.appendChild(rect); + var linearGradient = svgCanvas.addSvgElementFromJson({ + element: 'linearGradient', + attr: { + id: 'gradbox_' + } + }); + svgdocbox.appendChild(linearGradient); + var docElem = svgdocbox; } - $(blur_test).remove(); + + // Test for embedImage support (use timeout to not interfere with page load) setTimeout(function() { @@ -3499,17 +3581,17 @@ {sel:'#tool_text', fn: clickText, evt: 'click', key: 7}, {sel:'#tool_image', fn: clickImage, evt: 'mouseup', key: 8}, {sel:'#tool_zoom', fn: clickZoom, evt: 'mouseup', key: 9}, - {sel:'#tool_clear', fn: clickClear, evt: 'mouseup', key: [modKey+'N', true]}, - {sel:'#tool_save', fn: function() { editingsource?saveSourceEditor():clickSave()}, evt: 'mouseup', key: [modKey+'S', true]}, + {sel:'#tool_clear', fn: clickClear, evt: 'mouseup', key: ['N', true]}, + {sel:'#tool_save', fn: function() { editingsource?saveSourceEditor():clickSave()}, evt: 'mouseup', key: ['S', true]}, {sel:'#tool_export', fn: clickExport, evt: 'mouseup'}, - {sel:'#tool_open', fn: clickOpen, evt: 'mouseup', key: [modKey+'O', true]}, + {sel:'#tool_open', fn: clickOpen, evt: 'mouseup', key: ['O', true]}, {sel:'#tool_import', fn: clickImport, evt: 'mouseup'}, {sel:'#tool_source', fn: showSourceEditor, evt: 'click', key: ['U', true]}, {sel:'#tool_wireframe', fn: clickWireframe, evt: 'click', key: ['F', true]}, {sel:'#tool_source_cancel,#svg_source_overlay,#tool_docprops_cancel', fn: cancelOverlays, evt: 'click', key: ['esc', false, false], hidekey: true}, {sel:'#tool_source_save', fn: saveSourceEditor, evt: 'click'}, {sel:'#tool_docprops_save', fn: saveDocProperties, evt: 'click'}, - {sel:'#tool_docprops', fn: showDocProperties, evt: 'mouseup', key: [modKey+'P', true]}, + {sel:'#tool_docprops', fn: showDocProperties, evt: 'mouseup', key: ['P', true]}, {sel:'#tool_delete,#tool_delete_multi', fn: deleteSelected, evt: 'click', key: ['del/backspace', true]}, {sel:'#tool_reorient', fn: reorientPath, evt: 'click'}, {sel:'#tool_node_link', fn: linkControlPoints, evt: 'click'}, @@ -3520,10 +3602,10 @@ {sel:'#tool_move_top', fn: moveToTopSelected, evt: 'click', key: 'shift+up'}, {sel:'#tool_move_bottom', fn: moveToBottomSelected, evt: 'click', key: 'shift+down'}, {sel:'#tool_topath', fn: convertToPath, evt: 'click'}, - {sel:'#tool_undo', fn: clickUndo, evt: 'click', key: [modKey+'Z', true]}, - {sel:'#tool_redo', fn: clickRedo, evt: 'click', key: [modKey+'Y', true]}, - {sel:'#tool_clone,#tool_clone_multi', fn: clickClone, evt: 'click', key: [modKey+'C', true]}, - {sel:'#tool_group', fn: clickGroup, evt: 'click', key: [modKey+'G', true]}, + {sel:'#tool_undo', fn: clickUndo, evt: 'click', key: ['Z', true]}, + {sel:'#tool_redo', fn: clickRedo, evt: 'click', key: ['Y', true]}, + {sel:'#tool_clone,#tool_clone_multi', fn: clickClone, evt: 'click', key: ['C', true]}, + {sel:'#tool_group', fn: clickGroup, evt: 'click', key: ['G', true]}, {sel:'#tool_ungroup', fn: clickGroup, evt: 'click'}, {sel:'#tool_unlink_use', fn: clickGroup, evt: 'click'}, {sel:'[id^=tool_align]', fn: clickAlign, evt: 'click'}, @@ -3532,15 +3614,18 @@ // {sel:'#tools_ellipse_show', fn: clickEllipse, evt: 'click'}, {sel:'#tool_bold', fn: clickBold, evt: 'mousedown'}, {sel:'#tool_italic', fn: clickItalic, evt: 'mousedown'}, - {sel:'#sidepanel_handle', fn: toggleSidePanel, key: [modKey+'X']}, + {sel:'#sidepanel_handle', fn: toggleSidePanel, key: ['X']}, + {sel:'#copy_save_done', fn: cancelOverlays, evt: 'click'}, // Shortcuts not associated with buttons {key: 'shift+left', fn: function(){rotateSelected(0)}}, {key: 'shift+right', fn: function(){rotateSelected(1)}}, {key: 'shift+O', fn: selectPrev}, {key: 'shift+P', fn: selectNext}, - {key: ['ctrl+up', true], fn: function(){zoomImage(2);}}, - {key: ['ctrl+down', true], fn: function(){zoomImage(.5);}}, + {key: [modKey+'up', true], fn: function(){zoomImage(2);}}, + {key: [modKey+'down', true], fn: function(){zoomImage(.5);}}, + {key: [modKey+'[', true], fn: function(){moveUpDownSelected('Down');}}, + {key: [modKey+']', true], fn: function(){moveUpDownSelected('Up');}}, {key: ['up', true], fn: function(){moveSelected(0,-1);}}, {key: ['down', true], fn: function(){moveSelected(0,1);}}, {key: ['left', true], fn: function(){moveSelected(-1,0);}}, @@ -3705,6 +3790,48 @@ $('#blur').SpinButton({ step: .1, min: 0, max: 10, callback: changeBlur }); $('#zoom').SpinButton({ min: 0.001, max: 10000, step: 50, stepfunc: stepZoom, callback: changeZoom }); + $("#workarea").contextMenu({ + menu: 'cmenu_canvas', + inSpeed: 0 + }, + function(action, el, pos) { + switch ( action ) { + case 'delete': + deleteSelected(); + break; + case 'cut': + cutSelected(); + break; + case 'copy': + copySelected(); + break; + case 'paste': + svgCanvas.pasteElements(); + break; + case 'paste_in_place': + svgCanvas.pasteElements('in_place'); + break; + case 'move_down': + moveUpDownSelected('Down'); + break; + case 'move_up': + moveUpDownSelected('Up'); + break; + + } + + if(svgCanvas.clipBoard.length) { + canv_menu.enableContextMenuItems('#paste,#paste_in_place'); + } + }); + + $('.contextMenu li').mousedown(function(ev) { + ev.preventDefault(); + }) + + $('#cmenu_canvas li').disableContextMenu(); + canv_menu.enableContextMenuItems('#delete,#cut,#copy'); + window.onbeforeunload = function() { // Suppress warning if page is empty if(undoMgr.getUndoStackSize() === 0) { @@ -3835,7 +3962,7 @@ updateCanvas(true); // }); - // var revnums = "svg-editor.js ($Rev: 1659 $) "; + // var revnums = "svg-editor.js ($Rev: 1706 $) "; // revnums += svgCanvas.getVersion(); // $('#copyright')[0].setAttribute("title", revnums); @@ -3996,7 +4123,7 @@ Editor.addExtension = function() { var args = arguments; $(function() { - svgCanvas.addExtension.apply(this, args); + if(svgCanvas) svgCanvas.addExtension.apply(this, args); }); }; diff --git a/public/svg-edit/editor/svgcanvas.js b/public/svg-edit/editor/svgcanvas.js index c32c6e4c..8854f20c 100644 --- a/public/svg-edit/editor/svgcanvas.js +++ b/public/svg-edit/editor/svgcanvas.js @@ -31,7 +31,7 @@ if(window.opera) { // - When getting attributes, a string that's a number is return as type number. // - If an array is supplied as first parameter, multiple values are returned // as an object with values for each given attributes - + var proxied = jQuery.fn.attr, svgns = "http://www.w3.org/2000/svg"; jQuery.fn.attr = function(key, value) { var len = this.length; @@ -181,7 +181,10 @@ var isOpera = !!window.opera, dimensions: [640, 480] }; - + + // 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 hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath'; @@ -390,6 +393,14 @@ var Utils = this.Utils = function() { return {x:x, y:y, a:snapangle}; }, + // Function: snapToGrid + // round value to for snapping + "snapToGrid" : function(value){ + var stepSize = svgEditor.curConfig.snappingStep; + value = Math.round(value/stepSize)*stepSize; + return value; + }, + // 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 @@ -400,13 +411,13 @@ var Utils = this.Utils = function() { var out; try{ - var dXML = ($.browser.msie)?new ActiveXObject("Microsoft.XMLDOM"):new DOMParser(); + 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($.browser.msie) out = (dXML.loadXML(sXML))?dXML:false; + 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"); }; @@ -416,10 +427,10 @@ var Utils = this.Utils = function() { }(); +var elData = $.data; // TODO: declare the variables and set them as null, then move this setup stuff to // an initialization function - probably just use clear() - var canvas = this, // Namespace constants @@ -444,23 +455,30 @@ var canvas = this, svgdoc = container.ownerDocument, // Array with width/height of canvas - dimensions = curConfig.dimensions, + dimensions = curConfig.dimensions; - // Create Root SVG element. This is a container for the document being edited, not the document itself. - svgroot = svgdoc.importNode(Utils.text2xml('' + - '' + - '' + - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - '').documentElement, true); - + if($.browser.msie) { + var svgroot = document.createElementNS(svgns, 'svg'); + svgroot.id = 'svgroot'; + svgroot.setAttribute('width', dimensions[0]); + svgroot.setAttribute('height', dimensions[1]); + + } else { + // Create Root SVG element. This is a container for the document being edited, not the document itself. + var svgroot = svgdoc.importNode(Utils.text2xml('' + + '' + + '' + + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '').documentElement, true); + } container.appendChild(svgroot); @@ -663,7 +681,7 @@ var ChangeElementCommand = this.undoCmd.changeElement = function(elem, attrs, te this.oldValues = attrs; for (var attr in attrs) { if (attr == "#text") this.newValues[attr] = elem.textContent; - else if (attr == "#href") this.newValues[attr] = elem.getAttributeNS(xlinkns, "href"); + else if (attr == "#href") this.newValues[attr] = getHref(elem); else this.newValues[attr] = elem.getAttribute(attr); } @@ -674,7 +692,7 @@ var ChangeElementCommand = this.undoCmd.changeElement = function(elem, attrs, te for(var attr in this.newValues ) { if (this.newValues[attr]) { if (attr == "#text") this.elem.textContent = this.newValues[attr]; - else if (attr == "#href") this.elem.setAttributeNS(xlinkns, "xlink:href", this.newValues[attr]) + else if (attr == "#href") setHref(this.elem, this.newValues[attr]) else this.elem.setAttribute(attr, this.newValues[attr]); } else { @@ -716,7 +734,7 @@ var ChangeElementCommand = this.undoCmd.changeElement = function(elem, attrs, te for(var attr in this.oldValues ) { if (this.oldValues[attr]) { if (attr == "#text") this.elem.textContent = this.oldValues[attr]; - else if (attr == "#href") this.elem.setAttributeNS(xlinkns, "xlink:href", this.oldValues[attr]); + else if (attr == "#href") setHref(this.elem, this.oldValues[attr]); else this.elem.setAttribute(attr, this.oldValues[attr]); if (attr == "stdDeviation") canvas.setBlurOffsets(this.elem.parentNode, this.oldValues[attr]); @@ -826,7 +844,7 @@ var RemoveElementCommand = this.undoCmd.removeElement = function(elem, parent, t delete svgTransformLists[this.elem.id]; } - this.elem = this.parent.insertBefore(this.elem, this.elem.nextSibling); + this.elem = this.parent.insertBefore(this.elem, this.elem.nextSibling); if (this.parent == svgcontent) { identifyLayers(); } @@ -1142,58 +1160,17 @@ var SelectorManager; } }) ); - // this holds a reference to the grip elements for this selector - this.selectorGrips = { "nw":null, - "n":null, - "ne":null, - "e":null, - "se":null, - "s":null, - "sw":null, - "w":null - }; - this.rotateGripConnector = this.selectorGroup.appendChild( addSvgElementFromJson({ - "element": "line", - "attr": { - "id": ("selectorGrip_rotateconnector_" + this.id), - "stroke": "#22C", - "stroke-width": "1" - } - }) ); - - this.rotateGrip = this.selectorGroup.appendChild( addSvgElementFromJson({ - "element": "circle", - "attr": { - "id": ("selectorGrip_rotate_" + this.id), - "fill": "lime", - "r": 4, - "stroke": "#22C", - "stroke-width": 2, - "style": "cursor:url(" + curConfig.imgPath + "rotate.png) 12 12, auto;" - } - }) ); + // 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 + }; - // add the corner grips - for (var dir in this.selectorGrips) { - this.selectorGrips[dir] = this.selectorGroup.appendChild( - addSvgElementFromJson({ - "element": "circle", - "attr": { - "id": ("selectorGrip_resize_" + dir + "_" + this.id), - "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", - "display":"none" - } - }) ); - } - // Function: Selector.showGrips // Show the resize grips of this selector // @@ -1202,13 +1179,12 @@ var SelectorManager; this.showGrips = function(show) { // TODO: use suspendRedraw() here var bShow = show ? "inline" : "none"; - this.rotateGrip.setAttribute("display", bShow); - this.rotateGripConnector.setAttribute("display", bShow); + selectorManager.selectorGripsGroup.setAttribute("display", bShow); var elem = this.selectedElement; - for (var dir in this.selectorGrips) { - this.selectorGrips[dir].setAttribute("display", bShow); + if(elem && show) { + this.selectorGroup.appendChild(selectorManager.selectorGripsGroup); + this.updateGripCursors(getRotationAngle(elem)); } - if(elem) this.updateGripCursors(getRotationAngle(elem)); }; // Function: Selector.updateGripCursors @@ -1220,7 +1196,7 @@ var SelectorManager; var dir_arr = []; var steps = Math.round(angle / 45); if(steps < 0) steps += 8; - for (var dir in this.selectorGrips) { + for (var dir in selectorManager.selectorGrips) { dir_arr.push(dir); } while(steps > 0) { @@ -1228,8 +1204,8 @@ var SelectorManager; steps--; } var i = 0; - for (var dir in this.selectorGrips) { - this.selectorGrips[dir].setAttribute('style', ("cursor:" + dir_arr[i] + "-resize")); + for (var dir in selectorManager.selectorGrips) { + selectorManager.selectorGrips[dir].setAttribute('style', ("cursor:" + dir_arr[i] + "-resize")); i++; }; }; @@ -1238,7 +1214,8 @@ var SelectorManager; // Updates the selector to match the element's size this.resize = function() { var selectedBox = this.selectorRect, - selectedGrips = this.selectorGrips, + mgr = selectorManager, + selectedGrips = mgr.selectorGrips, selected = this.selectedElement, sw = selected.getAttribute("stroke-width"); var offset = 1/current_zoom; @@ -1321,7 +1298,7 @@ var SelectorManager; + " " + nbax + "," + (nbay+nbah) + "z"; assignAttributes(selectedBox, {'d': dstr}); - var gripCoords = { + this.gripCoords = { nw: [nbax, nbay], ne: [nbax+nbaw, nbay], sw: [nbax, nbay+nbah], @@ -1333,8 +1310,8 @@ var SelectorManager; }; if(selected == selectedElements[0]) { - for(var dir in gripCoords) { - var coords = gripCoords[dir]; + for(var dir in this.gripCoords) { + var coords = this.gripCoords[dir]; assignAttributes(selectedGrips[dir], { cx: coords[0], cy: coords[1] }); @@ -1349,11 +1326,11 @@ var SelectorManager; } // we want to go 20 pixels in the negative transformed y direction, ignoring scale - assignAttributes(this.rotateGripConnector, { x1: nbax + (nbaw)/2, + assignAttributes(mgr.rotateGripConnector, { x1: nbax + (nbaw)/2, y1: nbay, x2: nbax + (nbaw)/2, y2: nbay- 20}); - assignAttributes(this.rotateGrip, { cx: nbax + (nbaw)/2, + assignAttributes(mgr.rotateGrip, { cx: nbax + (nbaw)/2, cy: nbay - 20 }); svgroot.unsuspendRedraw(sr_handle); @@ -1392,11 +1369,70 @@ var SelectorManager; // create parent selector group and add it to svgroot mgr.selectorParentGroup = svgdoc.createElementNS(svgns, "g"); mgr.selectorParentGroup.setAttribute("id", "selectorParentGroup"); + mgr.selectorGripsGroup = svgdoc.createElementNS(svgns, "g"); svgroot.appendChild(mgr.selectorParentGroup); + mgr.selectorParentGroup.appendChild(mgr.selectorGripsGroup); mgr.selectorMap = {}; mgr.selectors = []; mgr.rubberBandBox = null; + // this holds a reference to the grip elements + mgr.selectorGrips = { "nw":null, + "n":null, + "ne":null, + "e":null, + "se":null, + "s":null, + "sw":null, + "w":null + }; + + // add the corner grips + for (var dir in mgr.selectorGrips) { + var grip = addSvgElementFromJson({ + "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" + } + }); + + elData(grip, "dir", dir); + elData(grip, "type", "resize"); + this.selectorGrips[dir] = mgr.selectorGripsGroup.appendChild(grip); + } + + // add rotator elems + this.rotateGripConnector = this.selectorGripsGroup.appendChild( addSvgElementFromJson({ + "element": "line", + "attr": { + "id": ("selectorGrip_rotateconnector"), + "stroke": "#22C", + "stroke-width": "1" + } + }) ); + + this.rotateGrip = this.selectorGripsGroup.appendChild( addSvgElementFromJson({ + "element": "circle", + "attr": { + "id": "selectorGrip_rotate", + "fill": "lime", + "r": 4, + "stroke": "#22C", + "stroke-width": 2, + "style": "cursor:url(" + curConfig.imgPath + "rotate.png) 12 12, auto;" + } + }) ); + elData(this.rotateGrip, "type", "rotate"); + if($("#canvasBackground").length) return; var canvasbg = svgdoc.createElementNS(svgns, "svg"); @@ -1836,14 +1872,14 @@ var addSvgElementFromJson = this.addSvgElementFromJson = function(data) { (function() { // TODO: make this string optional and set by the client var comment = svgdoc.createComment(" Created with SVG-edit - http://svg-edit.googlecode.com/ "); - // Lead to invalid content with Instiki's Sanitizer - // svgcontent.appendChild(comment); + // Lead to invalid content with Instiki's Sanitizer + // svgcontent.appendChild(comment); // TODO For Issue 208: this is a start on a thumbnail // var svgthumb = svgdoc.createElementNS(svgns, "use"); // svgthumb.setAttribute('width', '100'); // svgthumb.setAttribute('height', '100'); - // svgthumb.setAttributeNS(xlinkns, 'href', '#svgcontent'); + // setHref(svgthumb, '#svgcontent'); // svgroot.appendChild(svgthumb); })(); @@ -1936,7 +1972,13 @@ var cur_shape = all_properties.shape, curBBoxes = [], // Object to contain all included extensions - extensions = {}; + extensions = {}, + + // Canvas point for the most recent right click + lastClickPoint = null; + +// Clipboard for cut, copy&pasted elements +canvas.clipBoard = []; // Should this return an array by default, so extension results aren't overwritten? var runExtensions = this.runExtensions = function(action, vars, returnArray) { @@ -2028,11 +2070,19 @@ var getIntersectionList = this.getIntersectionList = function(rect) { if (resultList == null || typeof(resultList.item) != "function") { resultList = []; - - var rubberBBox = rubberBox.getBBox(); - $.each(rubberBBox, function(key, val) { - rubberBBox[key] = val / current_zoom; - }); + + if(!rect) { + var rubberBBox = rubberBox.getBBox(); + var bb = {}; + $.each(rubberBBox, function(key, val) { + // Can't set values to a real BBox object, so make a fake one + bb[key] = val / current_zoom; + }); + rubberBBox = bb; + + } else { + var rubberBBox = rect; + } var i = curBBoxes.length; while (i--) { if(!rubberBBox.width || !rubberBBox.width) continue; @@ -2132,7 +2182,6 @@ var getStrokedBBox = this.getStrokedBBox = function(elems) { // bb.width = rmaxx - rminx; // bb.height = rmaxy - rminy; } - return bb; } catch(e) { console.log(elem, e); @@ -2145,6 +2194,12 @@ var getStrokedBBox = this.getStrokedBBox = function(elems) { if(full_bb) return; if(!this.parentNode) return; full_bb = getCheckedBBox(this); + if(full_bb) { + var b = {}; + for(var i in full_bb) b[i] = full_bb[i]; + full_bb = b; + } + }); // This shouldn't ever happen... @@ -2237,7 +2292,6 @@ var groupSvgElem = this.groupSvgElem = function(elem) { $(g).append(elem).data('gsvg', elem)[0].id = getNextId(); } - // Function: copyElem // Create a clone of an element, updating its ID and its children's IDs when needed // @@ -2467,7 +2521,7 @@ var sanitizeSvg = this.sanitizeSvg = function(node) { // for some elements that have a xlink:href, ensure the URI refers to a local element // (but not for links) - var href = node.getAttributeNS(xlinkns,"href"); + var href = getHref(node); if(href && $.inArray(node.nodeName, ["filter", "linearGradient", "pattern", "radialGradient", "textPath", "use"]) != -1) @@ -2475,13 +2529,13 @@ var sanitizeSvg = this.sanitizeSvg = function(node) { // TODO: we simply check if the first character is a #, is this bullet-proof? if (href[0] != "#") { // remove the attribute (but keep the element) - node.setAttributeNS(xlinkns, "xlink:href", ""); + setHref(node, ""); node.removeAttributeNS(xlinkns, "href"); } } // Safari crashes on a without a xlink:href, so we just remove the node here - if (node.nodeName == "use" && !node.getAttributeNS(xlinkns,"href")) { + if (node.nodeName == "use" && !getHref(node)) { parent.removeChild(node); return; } @@ -2759,7 +2813,11 @@ this.setRotationAngle = function(val, preventUndo) { var center = transformPoint(cx,cy,transformListToTransform(tlist).matrix); var R_nc = svgroot.createSVGTransform(); R_nc.setRotate(val, center.x, center.y); - tlist.insertItemBefore(R_nc,0); + if(tlist.numberOfItems) { + tlist.insertItemBefore(R_nc, 0); + } else { + tlist.appendItem(R_nc); + } } else if (tlist.numberOfItems == 0) { elem.removeAttribute("transform"); @@ -3004,15 +3062,42 @@ var remapElement = this.remapElement = function(selected,changes,m) { changes.y = changes.y-0 + Math.min(0,changes.height); changes.width = Math.abs(changes.width); changes.height = Math.abs(changes.height); + if(svgEditor.curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){ + changes.x = Utils.snapToGrid(changes.x); + changes.y = Utils.snapToGrid(changes.y); + changes.width = Utils.snapToGrid(changes.width); + changes.height = Utils.snapToGrid(changes.height); + } assignAttributes(selected, changes, 1000, true); break; case "ellipse": changes.rx = Math.abs(changes.rx); changes.ry = Math.abs(changes.ry); + if(svgEditor.curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){ + changes.cx = Utils.snapToGrid(changes.cx); + changes.cy = Utils.snapToGrid(changes.cy); + changes.rx = Utils.snapToGrid(changes.rx); + changes.ry = Utils.snapToGrid(changes.ry); + } case "circle": if(changes.r) changes.r = Math.abs(changes.r); + if(svgEditor.curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){ + changes.cx = Utils.snapToGrid(changes.cx); + changes.cy = Utils.snapToGrid(changes.cy); + changes.r = Utils.snapToGrid(changes.r); + } case "line": + if(svgEditor.curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){ + changes.x1 = Utils.snapToGrid(changes.x1); + changes.y1 = Utils.snapToGrid(changes.y1); + changes.x2 = Utils.snapToGrid(changes.x2); + changes.y2 = Utils.snapToGrid(changes.y2); + } case "text": + if(svgEditor.curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){ + changes.x = Utils.snapToGrid(changes.x); + changes.y = Utils.snapToGrid(changes.y); + } case "use": assignAttributes(selected, changes, 1000, true); break; @@ -3355,7 +3440,7 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) { // var u = uses.length; // while (u--) { // var useElem = uses.item(u); -// if(href == useElem.getAttributeNS(xlinkns, "href")) { +// if(href == getHref(useElem)) { // var usexlate = svgroot.createSVGTransform(); // usexlate.setTranslate(-tx,-ty); // getTransformList(useElem).insertItemBefore(usexlate,0); @@ -3433,7 +3518,7 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) { var u = uses.length; while (u--) { var useElem = uses.item(u); - if(href == useElem.getAttributeNS(xlinkns, "href")) { + if(href == getHref(useElem)) { var usexlate = svgroot.createSVGTransform(); usexlate.setTranslate(-tx,-ty); getTransformList(useElem).insertItemBefore(usexlate,0); @@ -3704,10 +3789,12 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) { // if it was a translate, put back the rotate at the new center if (operation == 2) { if (angle) { - newcenter = { - x: oldcenter.x + m.e, - y: oldcenter.y + m.f - }; + if(!hasMatrixTransform(tlist)) { + newcenter = { + x: oldcenter.x + m.e, + y: oldcenter.y + m.f + }; + } var newRot = svgroot.createSVGTransform(); newRot.setRotate(angle, newcenter.x, newcenter.y); tlist.insertItemBefore(newRot, 0); @@ -4183,10 +4270,13 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // Mouse events (function() { + var off_x, off_y; var d_attr = null, start_x = null, start_y = null, + r_start_x = null, + r_start_y = null, init_bbox = {}, freehand = { minx: null, @@ -4201,12 +4291,29 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // and do nothing else var mouseDown = function(evt) { - if(evt.button === 1 || canvas.spaceKey) return; + if(canvas.spaceKey) return; + + var right_click = evt.button === 2; + root_sctm = svgcontent.getScreenCTM().inverse(); var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ), mouse_x = pt.x * current_zoom, mouse_y = pt.y * current_zoom; + + if($.browser.msie) { + var off = $(container.parentNode).offset(); + off_x = svgcontent.getAttribute('x')-0 + off.left - container.parentNode.scrollLeft; + off_y = svgcontent.getAttribute('y')-0 + off.top - container.parentNode.scrollTop; + mouse_x = -(off_x - evt.pageX); + mouse_y = -(off_y - evt.pageY); + } + evt.preventDefault(); + + if(right_click) { + current_mode = "select"; + lastClickPoint = pt; + } // This would seem to be unnecessary... // if($.inArray(current_mode, ['select', 'resize']) == -1) { @@ -4217,22 +4324,31 @@ var getMouseTarget = this.getMouseTarget = function(evt) { y = mouse_y / current_zoom, mouse_target = getMouseTarget(evt); - start_x = x; - start_y = y; - + // real_x/y ignores grid-snap value + var real_x = r_start_x = start_x = x; + var real_y = r_start_y = start_y = y; + + if(svgEditor.curConfig.gridSnapping){ + x = Utils.snapToGrid(x); + y = Utils.snapToGrid(y); + start_x = Utils.snapToGrid(start_x); + start_y = Utils.snapToGrid(start_y); + } + // if it is a selector grip, then it must be a single element selected, // set the mouse_target to that and update the mode to rotate/resize + if (mouse_target == selectorManager.selectorParentGroup && selectedElements[0] != null) { - var gripid = evt.target.id, - griptype = gripid.substr(0,20); + var grip = evt.target; + var griptype = elData(grip, "type"); // rotating - if (griptype == "selectorGrip_rotate_") { + if (griptype == "rotate") { current_mode = "rotate"; } // resizing - else if(griptype == "selectorGrip_resize_") { + else if(griptype == "resize") { current_mode = "resize"; - current_resize_mode = gripid.substr(20,gripid.indexOf("_",20)-20); + current_resize_mode = elData(grip, "dir"); } mouse_target = selectedElements[0]; } @@ -4243,6 +4359,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { case "select": started = true; current_resize_mode = "none"; + if(right_click) started = false; if (mouse_target != svgroot) { // if this element is not yet selected, clear selection and select it @@ -4258,26 +4375,33 @@ var getMouseTarget = this.getMouseTarget = function(evt) { pathActions.clear(); } // else if it's a path, go into pathedit mode in mouseup - - // insert a dummy transform so if the element(s) are moved it will have - // a transform to use for its translate - for (var i = 0; i < selectedElements.length; ++i) { - if(selectedElements[i] == null) continue; - var slist = getTransformList(selectedElements[i]); - slist.insertItemBefore(svgroot.createSVGTransform(), 0); + + if(!right_click) { + // insert a dummy transform so if the element(s) are moved it will have + // a transform to use for its translate + for (var i = 0; i < selectedElements.length; ++i) { + if(selectedElements[i] == null) continue; + var slist = getTransformList(selectedElements[i]); + slist.insertItemBefore(svgroot.createSVGTransform(), 0); + } } } - else { + else if(!right_click){ clearSelection(); current_mode = "multiselect"; if (rubberBox == null) { rubberBox = selectorManager.getRubberBandBox(); } - start_x *= current_zoom; - start_y *= current_zoom; + r_start_x *= current_zoom; + r_start_y *= current_zoom; +// console.log('p',[evt.pageX, evt.pageY]); +// console.log('c',[evt.clientX, evt.clientY]); +// console.log('o',[evt.offsetX, evt.offsetY]); +// console.log('s',[start_x, start_y]); + assignAttributes(rubberBox, { - 'x': start_x, - 'y': start_y, + 'x': r_start_x, + 'y': r_start_y, 'width': 0, 'height': 0, 'display': 'inline' @@ -4286,14 +4410,12 @@ var getMouseTarget = this.getMouseTarget = function(evt) { break; case "zoom": started = true; - start_x = x; - start_y = y; if (rubberBox == null) { rubberBox = selectorManager.getRubberBandBox(); } assignAttributes(rubberBox, { - 'x': start_x * current_zoom, - 'y': start_y * current_zoom, + 'x': real_x * current_zoom, + 'y': real_x * current_zoom, 'width': 0, 'height': 0, 'display': 'inline' @@ -4366,7 +4488,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { "style": "pointer-events:inherit" } }); - newImage.setAttributeNS(xlinkns, "xlink:href", last_good_img_url); + setHref(newImage, last_good_img_url); preventClickDefault(newImage); break; case "square": @@ -4514,9 +4636,21 @@ var getMouseTarget = this.getMouseTarget = function(evt) { mouse_y = pt.y * current_zoom, shape = getElem(getId()); - x = mouse_x / current_zoom; - y = mouse_y / current_zoom; - + // IE9 gives the wrong root_sctm + // TODO: Use non-browser sniffing way to make this work + if($.browser.msie) { + mouse_x = -(off_x - evt.pageX); + mouse_y = -(off_y - evt.pageY); + } + + var real_x = x = mouse_x / current_zoom; + var real_y = y = mouse_y / current_zoom; + + if(svgEditor.curConfig.gridSnapping){ + x = Utils.snapToGrid(x); + y = Utils.snapToGrid(y); + } + evt.preventDefault(); switch (current_mode) @@ -4529,6 +4663,11 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var dx = x - start_x; var dy = y - start_y; + if(svgEditor.curConfig.gridSnapping){ + dx = Utils.snapToGrid(dx); + dy = Utils.snapToGrid(dy); + } + if(evt.shiftKey) { var xya = Utils.snapToAngle(start_x,start_y,x,y); x=xya.x; y=xya.y; } if (dx != 0 || dy != 0) { @@ -4564,13 +4703,13 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } break; case "multiselect": - x *= current_zoom; - y *= current_zoom; + real_x *= current_zoom; + real_y *= current_zoom; assignAttributes(rubberBox, { - 'x': Math.min(start_x,x), - 'y': Math.min(start_y,y), - 'width': Math.abs(x-start_x), - 'height': Math.abs(y-start_y) + 'x': Math.min(r_start_x, real_x), + 'y': Math.min(r_start_y, real_y), + 'width': Math.abs(real_x - r_start_x), + 'height': Math.abs(real_y - r_start_y) },100); // for each selected: @@ -4608,7 +4747,14 @@ var getMouseTarget = this.getMouseTarget = function(evt) { box=hasMatrix?init_bbox:getBBox(selected), left=box.x, top=box.y, width=box.width, height=box.height, dx=(x-start_x), dy=(y-start_y); - + + if(svgEditor.curConfig.gridSnapping){ + dx = Utils.snapToGrid(dx); + dy = Utils.snapToGrid(dy); + height = Utils.snapToGrid(height); + width = Utils.snapToGrid(width); + } + // if rotated, adjust the dx,dy values var angle = getRotationAngle(selected); if (angle) { @@ -4647,6 +4793,14 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var translateOrigin = svgroot.createSVGTransform(), scale = svgroot.createSVGTransform(), translateBack = svgroot.createSVGTransform(); + + if(svgEditor.curConfig.gridSnapping){ + left = Utils.snapToGrid(left); + tx = Utils.snapToGrid(tx); + top = Utils.snapToGrid(top); + ty = Utils.snapToGrid(ty); + } + translateOrigin.setTranslate(-(left+tx),-(top+ty)); if(evt.shiftKey) { if(sx == 1) sx = sy @@ -4684,13 +4838,13 @@ var getMouseTarget = this.getMouseTarget = function(evt) { selectorManager.requestSelector(selected).resize(); break; case "zoom": - x *= current_zoom; - y *= current_zoom; + real_x *= current_zoom; + real_y *= current_zoom; assignAttributes(rubberBox, { - 'x': Math.min(start_x*current_zoom,x), - 'y': Math.min(start_y*current_zoom,y), - 'width': Math.abs(x-start_x*current_zoom), - 'height': Math.abs(y-start_y*current_zoom) + 'x': Math.min(r_start_x*current_zoom, real_x), + 'y': Math.min(r_start_y*current_zoom, real_y), + 'width': Math.abs(real_x - r_start_x*current_zoom), + 'height': Math.abs(real_y - r_start_y*current_zoom) },100); break; case "text": @@ -4704,6 +4858,11 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var handle = null; if (!window.opera) svgroot.suspendRedraw(1000); + if(svgEditor.curConfig.gridSnapping){ + x = Utils.snapToGrid(x); + y = Utils.snapToGrid(y); + } + var x2 = x; var y2 = y; @@ -4733,6 +4892,13 @@ var getMouseTarget = this.getMouseTarget = function(evt) { new_y = Math.min(start_y,y); } + if(svgEditor.curConfig.gridSnapping){ + w = Utils.snapToGrid(w); + h = Utils.snapToGrid(h); + new_x = Utils.snapToGrid(new_x); + new_y = Utils.snapToGrid(new_y); + } + assignAttributes(shape,{ 'width': w, 'height': h, @@ -4745,6 +4911,9 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var c = $(shape).attr(["cx", "cy"]); var cx = c.cx, cy = c.cy, rad = Math.sqrt( (x-cx)*(x-cx) + (y-cy)*(y-cy) ); + if(svgEditor.curConfig.gridSnapping){ + rad = Utils.snapToGrid(rad); + } shape.setAttributeNS(null, "r", rad); break; case "ellipse": @@ -4753,6 +4922,12 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // Opera has a problem with suspendRedraw() apparently handle = null; if (!window.opera) svgroot.suspendRedraw(1000); + if(svgEditor.curConfig.gridSnapping){ + x = Utils.snapToGrid(x); + cx = Utils.snapToGrid(cx); + y = Utils.snapToGrid(y); + cy = Utils.snapToGrid(cy); + } shape.setAttributeNS(null, "rx", Math.abs(x - cx) ); var ry = Math.abs(evt.shiftKey?(x - cx):(y - cy)); shape.setAttributeNS(null, "ry", ry ); @@ -4778,6 +4953,12 @@ var getMouseTarget = this.getMouseTarget = function(evt) { x *= current_zoom; y *= current_zoom; + if(svgEditor.curConfig.gridSnapping){ + x = Utils.snapToGrid(x); + y = Utils.snapToGrid(y); + start_x = Utils.snapToGrid(start_x); + start_y = Utils.snapToGrid(start_y); + } if(evt.shiftKey) { var x1 = path.dragging?path.dragging[0]:start_x; var y1 = path.dragging?path.dragging[1]:start_y; @@ -4787,10 +4968,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { if(rubberBox && rubberBox.getAttribute('display') != 'none') { assignAttributes(rubberBox, { - 'x': Math.min(start_x,x), - 'y': Math.min(start_y,y), - 'width': Math.abs(x-start_x), - 'height': Math.abs(y-start_y) + 'x': Math.min(r_start_x, real_x), + 'y': Math.min(r_start_y, real_y), + 'width': Math.abs(real_x - r_start_x), + 'height': Math.abs(real_y - r_start_y) },100); } @@ -4821,7 +5002,9 @@ var getMouseTarget = this.getMouseTarget = function(evt) { cx = center.x; cy = center.y; var angle = ((Math.atan2(cy-y,cx-x) * (180/Math.PI))-90) % 360; - + if(svgEditor.curConfig.gridSnapping){ + angle = Utils.snapToGrid(angle); + } if(evt.shiftKey) { // restrict rotations to nice angles (WRS) var snap = 45; angle= Math.round(angle/snap)*snap; @@ -4850,7 +5033,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // this is done in when we recalculate the selected dimensions() var mouseUp = function(evt) { - if(evt.button === 1) return; + if(evt.button === 2) return; var tempJustSelected = justSelected; justSelected = null; if (!started) return; @@ -4861,7 +5044,11 @@ var getMouseTarget = this.getMouseTarget = function(evt) { y = mouse_y / current_zoom, element = getElem(getId()), keep = false; - + + var real_x = x; + var real_y = y; + + started = false; switch (current_mode) { @@ -4901,7 +5088,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // always recalculate dimensions to strip off stray identity transforms recalculateAllSelectedDimensions(); // if it was being dragged/resized - if (x != start_x || y != start_y) { + if (real_x != r_start_x || real_y != r_start_y) { var len = selectedElements.length; for (var i = 0; i < len; ++i) { if (selectedElements[i] == null) break; @@ -4937,10 +5124,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } var factor = evt.shiftKey?.5:2; call("zoomed", { - 'x': Math.min(start_x,x), - 'y': Math.min(start_y,y), - 'width': Math.abs(x-start_x), - 'height': Math.abs(y-start_y), + 'x': Math.min(r_start_x, real_x), + 'y': Math.min(r_start_y, real_y), + 'width': Math.abs(real_x - r_start_x), + 'height': Math.abs(real_y - r_start_y), 'factor': factor }); return; @@ -5050,6 +5237,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } // perform recalculation to weed out any stray identity transforms that might get stuck recalculateAllSelectedDimensions(); + call("changed", selectedElements); break; default: // This could occur in an extension @@ -5090,6 +5278,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { t.id != "svgcanvas" && t.id != "svgroot") { // switch into "select" mode if we've clicked on an element + clearSelection(true); addToSelection([t], true); canvas.setMode("select"); } @@ -5122,10 +5311,14 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } else if (current_mode == "text" || current_mode == "image" || current_mode == "foreignObject") { // keep us in the tool we were in unless it was a text or image element addToSelection([element], true); + } else { + clearSelection(true); + addToSelection([element], true); } // we create the insert command that is stored on the stack // undo means to call cmd.unapply(), redo means to call cmd.apply() addCommandToHistory(new InsertElementCommand(element)); + call("changed",[element]); }, ani_dur * 1000); } @@ -6666,6 +6859,12 @@ var pathActions = this.pathActions = function() { var x = mouse_x/current_zoom, y = mouse_y/current_zoom, stretchy = getElem("path_stretch_line"); + + if(svgEditor.curConfig.gridSnapping){ + x = Utils.snapToGrid(x); + y = Utils.snapToGrid(y); + } + if (!stretchy) { stretchy = document.createElementNS(svgns, "line"); assignAttributes(stretchy, { @@ -6789,7 +6988,10 @@ var pathActions = this.pathActions = function() { d_attr += "L" + round(x) + "," + round(y) + " "; newpath.setAttribute("d", d_attr); - + + x *= current_zoom; + y *= current_zoom; + // set stretchy line to latest point assignAttributes(stretchy, { 'x1': x, @@ -7261,7 +7463,7 @@ var pathActions = this.pathActions = function() { var addToD = function(pnts, more, last) { var str = ''; var more = more?' '+more.join(' '):''; - var last = last?shortFloat(last):''; + var last = last?' '+shortFloat(last):''; $.each(pnts, function(i, pnt) { pnts[i] = shortFloat(pnt); }); @@ -7423,13 +7625,13 @@ var removeUnusedDefElems = this.removeUnusedDefElems = function() { } // gradients can refer to other gradients - var href = el.getAttributeNS(xlinkns, "href"); + var href = getHref(el); if (href && href.indexOf('#') == 0) { defelem_uses.push(href.substr(1)); } }; - var defelems = $(svgcontent).find("linearGradient, radialGradient, filter, marker"); + var defelems = $(svgcontent).find("linearGradient, radialGradient, filter, marker, svg"); defelem_ids = [], i = defelems.length; while (i--) { @@ -7713,7 +7915,6 @@ this.rasterExport = function() { // Selector and notice var issue_list = { 'feGaussianBlur': uiStrings.exportNoBlur, - 'image': uiStrings.exportNoImage, 'foreignObject': uiStrings.exportNoforeignObject, '[stroke-dasharray]': uiStrings.exportNoDashArray }; @@ -7807,7 +8008,7 @@ var uniquifyElems = this.uniquifyElems = function(g) { }); // check xlink:href now - var href = n.getAttributeNS(xlinkns,"href"); + var href = getHref(n); // TODO: what if an or element refers to an element internally? if(href && $.inArray(n.nodeName, ["filter", "linearGradient", "pattern", @@ -7847,7 +8048,7 @@ var uniquifyElems = this.uniquifyElems = function(g) { var k = hreffers.length; while (k--) { var hreffer = hreffers[k]; - hreffer.setAttributeNS(xlinkns, "xlink:href", "#"+newid); + setHref(hreffer, "#"+newid); } } } @@ -7934,6 +8135,11 @@ var convertToGroup = this.convertToGroup = function(elem) { // are removed walkTreePost(g, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}}); + // Give ID for any visible element missing one + $(g).find(visElems).each(function() { + if(!this.id) this.id = getNextId(); + }); + clearSelection(); addToSelection([g]); @@ -7968,6 +8174,9 @@ this.setSvgString = function(xmlString) { // set new svg document svgcontent = svgroot.appendChild(svgdoc.importNode(newDoc.documentElement, true)); + + var content = $(svgcontent); + // retrieve or set the nonce n = svgcontent.getAttributeNS(se_ns, 'nonce'); if (n) { @@ -7980,10 +8189,10 @@ this.setSvgString = function(xmlString) { if (extensions["Arrows"]) call("setarrownonce", nonce) ; } // change image href vals if possible - $(svgcontent).find('image').each(function() { + content.find('image').each(function() { var image = this; preventClickDefault(image); - var val = this.getAttributeNS(xlinkns, "href"); + var val = getHref(this); if(val.indexOf('data:') === 0) { // Check if an SVG-edit data URI var m = val.match(/svgedit_url=(.*?);/); @@ -7999,7 +8208,7 @@ this.setSvgString = function(xmlString) { }); // Wrap child SVGs in group elements - $(svgcontent).find('svg').each(function() { + content.find('svg').each(function() { // Skip if it's in a if($(this).closest('defs').length) return; @@ -8016,8 +8225,10 @@ this.setSvgString = function(xmlString) { }); // Set ref element for elements - $(svgcontent).find('use').each(function() { - var id = this.getAttributeNS(xlinkns,"href").substr(1); + + // TODO: This should also be done if the object is re-added through "redo" + content.find('use').each(function() { + var id = getHref(this).substr(1); var ref_elem = getElem(id); $(this).data('ref', ref_elem); if(ref_elem.tagName == 'symbol' || ref_elem.tagName == 'svg') { @@ -8026,7 +8237,7 @@ this.setSvgString = function(xmlString) { }); // convert gradients with userSpaceOnUse to objectBoundingBox - $(svgcontent).find('linearGradient, radialGradient').each(function() { + content.find('linearGradient, radialGradient').each(function() { var grad = this; if($(grad).attr('gradientUnits') === 'userSpaceOnUse') { // TODO: Support more than one element with this ref by duplicating parent grad @@ -8076,8 +8287,6 @@ this.setSvgString = function(xmlString) { // are removed walkTreePost(svgcontent, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}}); - var content = $(svgcontent); - var attrs = { id: 'svgcontent', overflow: curConfig.show_outside_canvas?'visible':'hidden' @@ -8108,13 +8317,26 @@ this.setSvgString = function(xmlString) { }); } + // identify layers + identifyLayers(); + + // Give ID for any visible layer children missing one + content.children().find(visElems).each(function() { + if(!this.id) this.id = getNextId(); + }); + // Percentage width/height, so let's base it on visible elements if(percs) { var bb = getStrokedBBox(); - attrs.width = bb.width; - attrs.height = bb.height; + attrs.width = bb.width + bb.x; + attrs.height = bb.height + bb.y; } + // Just in case negative numbers are given or + // result from the percs calculation + if(attrs.width <= 0) attrs.width = 100; + if(attrs.height <= 0) attrs.height = 100; + content.attr(attrs); this.contentW = attrs['width']; this.contentH = attrs['height']; @@ -8127,9 +8349,6 @@ this.setSvgString = function(xmlString) { // reset zoom current_zoom = 1; - // identify layers - identifyLayers(); - // reset transform lists svgTransformLists = {}; clearSelection(); @@ -8173,7 +8392,7 @@ this.importSvgString = function(xmlString) { // import new svg document into our document var svg = svgdoc.importNode(newDoc.documentElement, true); - + var innerw = convertToNum('width', svg.getAttribute("width")), innerh = convertToNum('height', svg.getAttribute("height")), innervb = svg.getAttribute("viewBox"), @@ -8212,10 +8431,10 @@ this.importSvgString = function(xmlString) { symbol.id = getNextId(); var use_el = svgdoc.createElementNS(svgns, "use"); - use_el.id = getNextId(); - use_el.setAttributeNS(xlinkns, "xlink:href", "#" + symbol.id); + setHref(use_el, "#" + symbol.id); findDefs().appendChild(symbol); current_layer.appendChild(use_el); + use_el.id = getNextId(); clearSelection(); use_el.setAttribute("transform", ts); @@ -8323,6 +8542,36 @@ this.createLayer = function(name) { call("changed", [new_layer]); }; +// Function: cloneLayer +// Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents +// to it, and then clears the selection This function then calls the 'changed' handler. +// This is an undoable action. +// +// Parameters: +// name - The given name +this.cloneLayer = function(name) { + var batchCmd = new BatchCommand("Duplicate Layer"); + var new_layer = svgdoc.createElementNS(svgns, "g"); + var layer_title = svgdoc.createElementNS(svgns, "title"); + layer_title.textContent = name; + new_layer.appendChild(layer_title); + $(current_layer).after(new_layer); + var childs = current_layer.childNodes; + for(var i = 0; i < childs.length; i++) { + var ch = childs[i]; + if(ch.localName == 'title') continue; + new_layer.appendChild(copyElem(ch)); + } + + clearSelection(); + identifyLayers(); + + batchCmd.addSubCommand(new InsertElementCommand(new_layer)); + addCommandToHistory(batchCmd); + canvas.setCurrentLayer(name); + call("changed", [new_layer]); +}; + // Function: deleteCurrentLayer // Deletes the current layer from the drawing and then clears the selection. This function // then calls the 'changed' handler. This is an undoable action. @@ -8343,6 +8592,15 @@ this.deleteCurrentLayer = function() { return false; }; +// Function: hasLayer +// Check if layer with given name already exists +this.hasLayer = function(name) { + for(var i = 0; i < all_layers.length; i++) { + if(all_layers[i][0] == name) return true; + } + return false; +}; + // Function: getNumLayers // Returns the number of layers in the current drawing. // @@ -8594,6 +8852,55 @@ this.moveSelectedToLayer = function(layername) { return true; }; +this.mergeLayer = function(skipHistory) { + var batchCmd = new BatchCommand("Merge Layer"); + var prev = $(current_layer).prev()[0]; + if(!prev) return; + var childs = current_layer.childNodes; + var len = childs.length; + batchCmd.addSubCommand(new RemoveElementCommand(current_layer, svgcontent)); + + while(current_layer.firstChild) { + var ch = current_layer.firstChild; + if(ch.localName == 'title') { + batchCmd.addSubCommand(new RemoveElementCommand(ch, current_layer)); + current_layer.removeChild(ch); + continue; + } + var oldNextSibling = ch.nextSibling; + prev.appendChild(ch); + batchCmd.addSubCommand(new MoveElementCommand(ch, oldNextSibling, current_layer)); + } + + // Remove current layer + svgcontent.removeChild(current_layer); + + if(!skipHistory) { + clearSelection(); + identifyLayers(); + + call("changed", [svgcontent]); + + addCommandToHistory(batchCmd); + } + + current_layer = prev; + return batchCmd; +} + +this.mergeAllLayers = function() { + var batchCmd = new BatchCommand("Merge all Layers"); + current_layer = all_layers[all_layers.length-1][1]; + while($(svgcontent).children('g').length > 1) { + batchCmd.addSubCommand(canvas.mergeLayer(true)); + } + + clearSelection(); + identifyLayers(); + call("changed", [svgcontent]); + addCommandToHistory(batchCmd); +} + // Function: getLayerOpacity // Returns the opacity of the given layer. If the input name is not a layer, null is returned. // @@ -8707,7 +9014,7 @@ this.getZoom = function(){return current_zoom;}; // Function: getVersion // Returns a string which describes the revision number of SvgCanvas. this.getVersion = function() { - return "svgcanvas.js ($Rev: 1653 $)"; + return "svgcanvas.js ($Rev: 1712 $)"; }; // Function: setUiStrings @@ -8743,6 +9050,19 @@ this.getTitle = function(elem) { return ''; } +// Function: getHref +// Returns the given element's xlink:href value +var getHref = this.getHref = function(elem) { + return elem.getAttributeNS(xlinkns, "href"); +} + +// Function: setHref +// Sets the given element's xlink:href value +var setHref = this.setHref = function(elem, val) { + elem.setAttributeNS(xlinkns, "xlink:href", val); +} + + // Function: setGroupTitle // Sets the group/SVG's title content // TODO: Combine this with setDocumentTitle @@ -8979,7 +9299,6 @@ this.getMode = function() { this.setMode = function(name) { pathActions.clear(true); textActions.clear(); - cur_properties = (selectedElements[0] && selectedElements[0].nodeName == 'text') ? cur_text : cur_shape; current_mode = name; }; @@ -9597,7 +9916,7 @@ this.setImageURL = function(val) { var attrs = $(elem).attr(['width', 'height']); var setsize = (!attrs.width || !attrs.height); - var cur_href = elem.getAttributeNS(xlinkns, "href"); + var cur_href = getHref(elem); // Do nothing if no URL change or size change if(cur_href !== val) { @@ -9606,7 +9925,7 @@ this.setImageURL = function(val) { var batchCmd = new BatchCommand("Change Image URL"); - elem.setAttributeNS(xlinkns, "xlink:href", val); + setHref(elem, val); batchCmd.addSubCommand(new ChangeElementCommand(elem, { "#href": cur_href })); @@ -9907,7 +10226,7 @@ var changeSelectedAttributeNoUndo = function(attr, newValue, elems) { // } } else if (attr == "#href") { - elem.setAttributeNS(xlinkns, "xlink:href", newValue); + setHref(elem, newValue); } else elem.setAttribute(attr, newValue); if (i==0) @@ -10007,6 +10326,87 @@ this.deleteSelectedElements = function() { clearSelection(); }; +// Function: cutSelectedElements +// Removes all selected elements from the DOM and adds the change to the +// history stack. Remembers removed elements on the clipboard + +// TODO: Combine similar code with deleteSelectedElements +this.cutSelectedElements = function() { + var batchCmd = new BatchCommand("Cut Elements"); + var len = selectedElements.length; + var selectedCopy = []; //selectedElements is being deleted + for (var i = 0; i < len; ++i) { + var selected = selectedElements[i]; + if (selected == null) break; + + var parent = selected.parentNode; + var t = selected; + // this will unselect the element and remove the selectedOutline + selectorManager.releaseSelector(t); + var elem = parent.removeChild(t); + selectedCopy.push(selected) //for the copy + selectedElements[i] = null; + batchCmd.addSubCommand(new RemoveElementCommand(elem, parent)); + } + if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd); + call("changed", selectedCopy); + clearSelection(); + + canvas.clipBoard = selectedCopy; +}; + +// Function: copySelectedElements +// Remembers the current selected elements on the clipboard +this.copySelectedElements = function() { + canvas.clipBoard = $.merge([], selectedElements); +}; + +this.pasteElements = function(type) { + var cb = canvas.clipBoard; + var len = cb.length; + if(!len) return; + + var pasted = []; + var batchCmd = new BatchCommand('Paste elements'); + + // Move elements to lastClickPoint + + while (len--) { + var elem = cb[len]; + if(!elem) continue; + var copy = copyElem(elem); + + // See if elem with elem ID is in the DOM already + if(!getElem(elem.id)) copy.id = elem.id; + + pasted.push(copy); + current_layer.appendChild(copy); + batchCmd.addSubCommand(new InsertElementCommand(copy)); + } + + clearSelection(true); + addToSelection(pasted); + + if(type !== 'in_place') { + var bbox = getStrokedBBox(pasted); + var cx = lastClickPoint.x - (bbox.x + bbox.width/2), + cy = lastClickPoint.y - (bbox.y + bbox.height/2), + dx = [], + dy = []; + + $.each(pasted, function(i, item) { + dx.push(cx); + dy.push(cy); + }); + + var cmd = canvas.moveSelectedElements(dx, dy, false); + batchCmd.addSubCommand(cmd); + } + + addCommandToHistory(batchCmd); + call("changed", pasted); +} + // Function: groupSelectedElements // Wraps all the selected elements in a group (g) element this.groupSelectedElements = function() { @@ -10272,6 +10672,41 @@ this.moveToBottomSelectedElement = function() { } }; +// Function: moveUpDownSelected +// Moves the select element up or down the stack, based on the visibly +// intersecting elements +// +// Parameters: +// dir - String that's either 'Up' or 'Down' +this.moveUpDownSelected = function(dir) { + var selected = selectedElements[0]; + if (!selected) return; + + curBBoxes = []; + var closest, found_cur; + // jQuery sorts this list + var list = $(getIntersectionList(getStrokedBBox([selected]))).toArray(); + if(dir == 'Down') list.reverse(); + + $.each(list, function() { + if(!found_cur) { + if(this == selected) { + found_cur = true; + } + return; + } + closest = this; + return false; + }); + if(!closest) return; + + var t = selected; + var oldParent = t.parentNode; + var oldNextSibling = t.nextSibling; + $(closest)[dir == 'Down'?'before':'after'](t); + addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, "Move " + dir)); +} + // Function: moveSelectedElements // Moves selected elements on the X/Y axis // @@ -10298,6 +10733,10 @@ this.moveSelectedElements = function(dx,dy,undoable) { if (i==0) selectedBBoxes[i] = getBBox(selected); + var b = {}; + for(var j in selectedBBoxes[i]) b[j] = selectedBBoxes[i][j]; + selectedBBoxes[i] = b; + var xform = svgroot.createSVGTransform(); var tlist = getTransformList(selected); @@ -10315,8 +10754,12 @@ this.moveSelectedElements = function(dx,dy,undoable) { } xform.setTranslate(dx,dy); } - - tlist.insertItemBefore(xform, 0); + + if(tlist.numberOfItems) { + tlist.insertItemBefore(xform, 0); + } else { + tlist.appendItem(xform); + } var cmd = recalculateDimensions(selected); if (cmd) { @@ -10526,7 +10969,7 @@ this.setBackground = function(color, url) { 'style':'pointer-events:none' }); } - bg_img.setAttributeNS(xlinkns, "xlink:href", url); + setHref(bg_img, url); bg.appendChild(bg_img); } else if(bg_img) { bg_img.parentNode.removeChild(bg_img); diff --git a/public/svg-edit/editor/svgicons/jquery.svgicons.js b/public/svg-edit/editor/svgicons/jquery.svgicons.js index 85d19dff..5453f3bd 100644 --- a/public/svg-edit/editor/svgicons/jquery.svgicons.js +++ b/public/svg-edit/editor/svgicons/jquery.svgicons.js @@ -128,7 +128,40 @@ $(function() { (function($) { - var svg_icons = {}; + function makeSVG(el) { + // manually create a copy of the element + var new_el = document.createElementNS(el.namespaceURI, el.nodeName); + $.each(el.attributes, function(i, attr) { + var ns = attr.namespaceURI; + if(ns) { + new_el.setAttributeNS(ns, attr.nodeName, attr.nodeValue); + } else { + new_el.setAttribute(attr.nodeName, attr.nodeValue); + } + if(attr.nodeName == 'transform') { + + console.log('val1:', attr.nodeValue); + console.log('val2:', new_el.getAttribute('transform')); + } + }); + + // now create copies of all children + $.each(el.childNodes, function(i, child) { + switch(child.nodeType) { + case 1: // element node + new_el.appendChild(makeSVG(child)); + break; + case 3: // text node + new_el.textContent = child.nodeValue; + break; + default: + break; + } + }); + return new_el; + } + + var svg_icons = {}, fixIDs; $.svgIcons = function(file, opts) { var svgns = "http://www.w3.org/2000/svg", @@ -168,7 +201,19 @@ $(function() { }); } else { if(err.responseXML) { + // Is there a non-ActiveX solution in IE9? svgdoc = err.responseXML; + + if(!svgdoc.childNodes.length) { + if(window.ActiveXObject) { + svgdoc = new ActiveXObject("Microsoft.XMLDOM"); + svgdoc.loadXML(err.responseText); + } else { + $(useFallback); + } + } else { + $(useFallback); + } $(function() { getIcons('ajax'); }); @@ -220,20 +265,29 @@ $(function() { } } }); - elems = $(svgdoc.firstChild).children(); //.getElementsByTagName('foreignContent'); - var testSrc = data_pre + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzUiIGhlaWdodD0iMjc1Ij48L3N2Zz4%3D'; - testImg = $(new Image()).attr({ - src: testSrc, - width: 0, - height: 0 - }).appendTo('body') - .load(function () { - // Safari 4 crashes, Opera and Chrome don't - makeIcons(!isSafari); - }).error(function () { - makeIcons(); - }); + elems = $(svgdoc.firstChild).children(); //.getElementsByTagName('foreignContent'); + + if(!opts.no_img) { + + var testSrc = data_pre + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzUiIGhlaWdodD0iMjc1Ij48L3N2Zz4%3D'; + + testImg = $(new Image()).attr({ + src: testSrc, + width: 0, + height: 0 + }).appendTo('body') + .load(function () { + // Safari 4 crashes, Opera and Chrome don't + makeIcons(!isSafari); + }).error(function () { + makeIcons(); + }); + } else { + setTimeout(function() { + if(!icons_made) makeIcons(); + },500); + } } function makeIcons(toImage, fallback) { @@ -290,9 +344,12 @@ $(function() { var id = elem.getAttribute('id'); if(id == 'svg_eof') return; holder = $('#' + id); - - var svg = elem.getElementsByTagNameNS(svgns, 'svg')[0]; - var svgroot = svgdoc.createElementNS(svgns, "svg"); + if(elem.getElementsByTagNameNS) { + var svg = elem.getElementsByTagNameNS(svgns, 'svg')[0]; + } else { + var svg = elem.getElementsByTagName('svg')[0]; + } + var svgroot = document.createElementNS(svgns, "svg"); svgroot.setAttributeNS(svgns, 'viewBox', [0,0,icon_w,icon_h].join(' ')); // Make flexible by converting width/height to viewBox @@ -318,7 +375,13 @@ $(function() { // With cloning, causes issue in Opera/Win/Non-EN if(!isOpera) svg = svg.cloneNode(true); - svgroot.appendChild(svg); + // TODO: Figure out why makeSVG is necessary for IE9 + try { + svgroot.appendChild(svg); + } catch(e) { + // For IE9 + svgroot.appendChild(makeSVG(svg)); + } if(toImage) { // Without cloning, Safari will crash @@ -339,8 +402,12 @@ $(function() { $.each(opts.placement, function(sel, id) { if(!svg_icons[id]) return; $(sel).each(function(i) { - var copy = svg_icons[id].clone(); - if(i > 0 && !toImage) copy = fixIDs(copy, i, true); + // TODO: Figure out why makeSVG is necessary for IE9 + try { + var copy = svg_icons[id].clone(); + } catch(e) { + var copy = makeSVG(svg_icons[id][0]); + } setIcon($(this), copy, id); }) }); @@ -348,18 +415,16 @@ $(function() { if(!fallback) { if(toImage) temp_holder.remove(); if(data_el) data_el.remove(); - testImg.remove(); + if(testImg) testImg.remove(); } - if(opts.resize) $.resizeSvgIcons(opts.resize); - icons_made = true; if(opts.callback) opts.callback(svg_icons); } - function fixIDs(svg_el, svg_num, force) { + fixIDs = function(svg_el, svg_num, force) { var defs = svg_el.find('defs'); if(!defs.length) return svg_el; @@ -433,7 +498,13 @@ $(function() { } } - $.getSvgIcon = function(id) { return svg_icons[id]; } + $.getSvgIcon = function(id, uniqueClone) { + var icon = svg_icons[id]; + if(uniqueClone) { + icon = fixIDs(icon, 0, true).clone(true); + } + return icon; + } $.resizeSvgIcons = function(obj) { // FF2 and older don't detect .svg_icon, so we change it detect svg elems instead diff --git a/public/svg-edit/test/test1.html b/public/svg-edit/test/test1.html index 762c6557..bdf4b46a 100644 --- a/public/svg-edit/test/test1.html +++ b/public/svg-edit/test/test1.html @@ -87,7 +87,7 @@ curseg = seglist.getItem(1); equal(curseg.pathSegTypeAsLetter, "L", "Before conversion, segment #2 type"); curseg = seglist.getItem(2); - equal(curseg.pathSegType, 1, "Before conversion, segment #3 type"); + equal(curseg.pathSegTypeAsLetter, "Z", "Before conversion, segment #3 type" + d_abs); // convert and verify segments var d = convert(p1, true);